// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * * * Copyright (c) 2024 The FreeCAD Project Association AISBL * * * * This file is part of FreeCAD. * * * * FreeCAD is free software: you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 2.1 of the * * License, or (at your option) any later version. * * * * FreeCAD is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with FreeCAD. If not, see * * . * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #include #include #endif #include "StartView.h" #include "FileCardDelegate.h" #include "FileCardView.h" #include "Gui/Workbench.h" #include #include #include #include #include #include #include <3rdParty/GSL/include/gsl/pointers> using namespace StartGui; TYPESYSTEM_SOURCE_ABSTRACT(StartGui::StartView, Gui::MDIView) // NOLINT namespace { struct NewButton { QString heading; QString description; QString iconPath; }; gsl::owner createNewButton(const NewButton& newButton) { auto hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Start"); const auto cardSpacing = static_cast(hGrp->GetInt("FileCardSpacing", 20)); // NOLINT const auto newFileIconSize = static_cast(hGrp->GetInt("NewFileIconSize", 48)); // NOLINT auto button = gsl::owner(new QPushButton()); auto mainLayout = gsl::owner(new QHBoxLayout(button)); auto iconLabel = gsl::owner(new QLabel(button)); mainLayout->addWidget(iconLabel); QIcon baseIcon(newButton.iconPath); iconLabel->setPixmap(baseIcon.pixmap(newFileIconSize, newFileIconSize)); auto textLayout = gsl::owner(new QVBoxLayout); auto textLabelLine1 = gsl::owner(new QLabel(button)); textLabelLine1->setText(QLatin1String("") + newButton.heading + QLatin1String("")); auto textLabelLine2 = gsl::owner(new QLabel(button)); textLabelLine2->setText(newButton.description); textLabelLine2->setWordWrap(true); textLayout->addWidget(textLabelLine1); textLayout->addWidget(textLabelLine2); textLayout->setSpacing(0); mainLayout->addItem(textLayout); mainLayout->addStretch(); button->setMinimumHeight(newFileIconSize + cardSpacing); return button; } } // namespace StartView::StartView(Gui::Document* pcDocument, QWidget* parent) : Gui::MDIView(pcDocument, parent) , _contents(new QScrollArea(parent)) { setObjectName(QLatin1String("StartView")); auto hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Start"); auto cardSpacing = hGrp->GetInt("FileCardSpacing", 20); // NOLINT auto scrolledWidget = gsl::owner(new QWidget(this)); _contents->setWidget(scrolledWidget); _contents->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); _contents->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); _contents->setWidgetResizable(true); auto layout = gsl::owner(new QVBoxLayout(scrolledWidget)); layout->setSizeConstraint(QLayout::SizeConstraint::SetMinAndMaxSize); // New WB notice: temporary to explain why all your setting disappeared auto newStartWBNotice = gsl::owner( new QLabel(tr("NOTE: The Start Workbench has been completely rewritten to remove all " "network access, and to remove its dependency on Chromium. This is still a " "work-in-progress, and not all settings from the previous version of Start " "have been migrated yet."))); newStartWBNotice->setWordWrap(true); layout->addWidget(newStartWBNotice); // Launch start automatically? QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str()); auto launchAutomaticallyCheckbox = gsl::owner(new QCheckBox(tr("Show Start when starting %1").arg(application))); bool showOnStartup = hGrp->GetBool("ShowOnStartup", true); launchAutomaticallyCheckbox->setCheckState(showOnStartup ? Qt::CheckState::Checked : Qt::CheckState::Unchecked); connect(launchAutomaticallyCheckbox, &QCheckBox::toggled, this, &StartView::showOnStartupChanged); layout->addWidget(launchAutomaticallyCheckbox); const QLatin1String h1Start("

"); const QLatin1String h1End("

"); auto newFileLabel = gsl::owner(new QLabel(h1Start + tr("New File") + h1End)); layout->addWidget(newFileLabel); auto gridLayout = gsl::owner(new QGridLayout); layout->addLayout(gridLayout); configureNewFileButtons(gridLayout); auto recentFilesLabel = gsl::owner(new QLabel(h1Start + tr("Recent Files") + h1End)); layout->addWidget(recentFilesLabel); auto recentFilesListWidget = gsl::owner(new FileCardView(_contents)); connect(recentFilesListWidget, &QListView::clicked, this, &StartView::fileCardSelected); layout->addWidget(recentFilesListWidget); auto examplesLabel = gsl::owner(new QLabel(h1Start + tr("Examples") + h1End)); layout->addWidget(examplesLabel); auto examplesListWidget = gsl::owner(new FileCardView(_contents)); connect(examplesListWidget, &QListView::clicked, this, &StartView::fileCardSelected); layout->addWidget(examplesListWidget); layout->setSpacing(static_cast(cardSpacing)); layout->addStretch(); setCentralWidget(_contents); QString title = QCoreApplication::translate("Workbench", "Start"); setWindowTitle(title); configureExamplesListWidget(examplesListWidget); configureRecentFilesListWidget(recentFilesListWidget, recentFilesLabel); } void StartView::configureNewFileButtons(QGridLayout* layout) const { auto newEmptyFile = createNewButton({tr("Empty file"), tr("Create a new empty FreeCAD file"), QLatin1String(":/icons/document-new.svg")}); auto openFile = createNewButton({tr("Open File"), tr("Open an existing CAD file or 3D model"), QLatin1String(":/icons/document-open.svg")}); auto partDesign = createNewButton({tr("Parametric Part"), tr("Create a part with the Part Design workbench"), QLatin1String(":/icons/PartDesignWorkbench.svg")}); auto assembly = createNewButton({tr("Assembly"), tr("Create an assembly project"), QLatin1String(":/icons/AssemblyWorkbench.svg")}); auto draft = createNewButton({tr("2D Draft"), tr("Create a 2D Draft with the Draft workbench"), QLatin1String(":/icons/DraftWorkbench.svg")}); auto arch = createNewButton({tr("BIM/Architecture"), tr("Create an architectural project"), QLatin1String(":/icons/ArchWorkbench.svg")}); // TODO: Ensure all of the required WBs are actually available // TODO: Make this layout more flexible (e.g. use a single line if possible) layout->addWidget(partDesign, 0, 0); layout->addWidget(assembly, 0, 1); layout->addWidget(draft, 0, 2); layout->addWidget(arch, 1, 0); layout->addWidget(newEmptyFile, 1, 1); layout->addWidget(openFile, 1, 2); connect(newEmptyFile, &QPushButton::clicked, this, &StartView::newEmptyFile); connect(openFile, &QPushButton::clicked, this, &StartView::openExistingFile); connect(partDesign, &QPushButton::clicked, this, &StartView::newPartDesignFile); connect(assembly, &QPushButton::clicked, this, &StartView::newAssemblyFile); connect(draft, &QPushButton::clicked, this, &StartView::newDraftFile); connect(arch, &QPushButton::clicked, this, &StartView::newArchFile); } void StartView::configureFileCardWidget(QListView* fileCardWidget) { auto delegate = gsl::owner(new FileCardDelegate); fileCardWidget->setItemDelegate(delegate); fileCardWidget->setMinimumWidth(fileCardWidget->parentWidget()->width()); fileCardWidget->setGridSize( fileCardWidget->itemDelegate()->sizeHint(QStyleOptionViewItem(), fileCardWidget->model()->index(0, 0))); } void StartView::configureRecentFilesListWidget(QListView* recentFilesListWidget, QLabel* recentFilesLabel) { _recentFilesModel.loadRecentFiles(); recentFilesListWidget->setModel(&_recentFilesModel); configureFileCardWidget(recentFilesListWidget); auto recentFilesGroup = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/RecentFiles"); auto numRecentFiles {recentFilesGroup->GetInt("RecentFiles", 0)}; if (numRecentFiles == 0) { recentFilesListWidget->hide(); recentFilesLabel->hide(); } else { recentFilesListWidget->show(); recentFilesLabel->show(); } } void StartView::configureExamplesListWidget(QListView* examplesListWidget) { _examplesModel.loadExamples(); examplesListWidget->setModel(&_examplesModel); configureFileCardWidget(examplesListWidget); } void StartView::newEmptyFile() const { Gui::Application::Instance->commandManager().runCommandByName("Std_New"); postStart(PostStartBehavior::switchWorkbench); } void StartView::newPartDesignFile() const { Gui::Application::Instance->commandManager().runCommandByName("Std_New"); Gui::Application::Instance->activateWorkbench("PartDesignWorkbench"); Gui::Application::Instance->commandManager().runCommandByName("PartDesign_Body"); postStart(PostStartBehavior::doNotSwitchWorkbench); } void StartView::openExistingFile() const { auto originalDocument = Gui::Application::Instance->activeDocument(); Gui::Application::Instance->commandManager().runCommandByName("Std_Open"); if (Gui::Application::Instance->activeDocument() != originalDocument) { // Only run this if the user chose a new document to open (that is, they didn't cancel the // open file dialog) postStart(PostStartBehavior::switchWorkbench); } } void StartView::newAssemblyFile() const { Gui::Application::Instance->commandManager().runCommandByName("Std_New"); Gui::Application::Instance->activateWorkbench("AssemblyWorkbench"); Gui::Application::Instance->commandManager().runCommandByName("Assembly_CreateAssembly"); Gui::Application::Instance->commandManager().runCommandByName("Std_Refresh"); postStart(PostStartBehavior::doNotSwitchWorkbench); } void StartView::newDraftFile() const { Gui::Application::Instance->commandManager().runCommandByName("Std_New"); Gui::Application::Instance->activateWorkbench("DraftWorkbench"); Gui::Application::Instance->commandManager().runCommandByName("Std_ViewTop"); postStart(PostStartBehavior::doNotSwitchWorkbench); } void StartView::newArchFile() const { Gui::Application::Instance->commandManager().runCommandByName("Std_New"); try { Gui::Application::Instance->activateWorkbench("BIMWorkbench"); } catch (...) { Gui::Application::Instance->activateWorkbench("ArchWorkbench"); } postStart(PostStartBehavior::doNotSwitchWorkbench); } void StartView::postStart(PostStartBehavior behavior) const { auto hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Start"); if (behavior == PostStartBehavior::switchWorkbench) { auto wb = hGrp->GetASCII("AutoloadModule", ""); if (wb == "$LastModule") { wb = App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/General") ->GetASCII("LastModule", ""); } if (!wb.empty()) { Gui::Application::Instance->activateWorkbench(wb.c_str()); } } auto closeStart = hGrp->GetBool("closeStart", false); if (closeStart) { this->window()->close(); } } void StartView::fileCardSelected(const QModelIndex& index) { auto file = index.data(static_cast(Start::DisplayedFilesModelRoles::path)).toString(); auto command = std::string("FreeCAD.loadFile('") + file.toStdString() + "')"; try { Base::Interpreter().runString(command.c_str()); postStart(PostStartBehavior::doNotSwitchWorkbench); } catch (Base::PyException& e) { Base::Console().Error(e.getMessage().c_str()); } catch (Base::Exception& e) { Base::Console().Error(e.getMessage().c_str()); } catch (...) { Base::Console().Error("An unknown error occurred"); } } void StartView::showOnStartupChanged(bool checked) { auto hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Start"); hGrp->SetBool("ShowOnStartup", checked); }