| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| |
|
| | #include <QApplication>
|
| | #include <QCheckBox>
|
| | #include <QFrame>
|
| | #include <QGridLayout>
|
| | #include <QLabel>
|
| | #include <QListView>
|
| | #include <QMdiSubWindow>
|
| | #include <QMessageBox>
|
| | #include <QPushButton>
|
| | #include <QScrollArea>
|
| | #include <QTimer>
|
| | #include <QWidget>
|
| | #include <QStackedWidget>
|
| | #include <QShowEvent>
|
| |
|
| | #include "StartView.h"
|
| | #include "FileCardDelegate.h"
|
| | #include "FileCardView.h"
|
| | #include "FirstStartWidget.h"
|
| | #include "FlowLayout.h"
|
| | #include "NewFileButton.h"
|
| | #include <App/DocumentObject.h>
|
| | #include <App/Application.h>
|
| | #include <Base/Interpreter.h>
|
| | #include <Base/Tools.h>
|
| | #include <Gui/Action.h>
|
| | #include <Gui/Application.h>
|
| | #include <Gui/Command.h>
|
| | #include <Gui/Document.h>
|
| | #include <Gui/MainWindow.h>
|
| | #include <Gui/ModuleIO.h>
|
| | #include <Gui/View3DInventor.h>
|
| | #include <Gui/View3DInventorViewer.h>
|
| | #include <gsl/pointers>
|
| | #include <string>
|
| |
|
| | using namespace StartGui;
|
| |
|
| | TYPESYSTEM_SOURCE_ABSTRACT(StartGui::StartView, Gui::MDIView)
|
| |
|
| |
|
| | StartView::StartView(QWidget* parent)
|
| | : Gui::MDIView(nullptr, parent)
|
| | , _contents(new QStackedWidget(parent))
|
| | , _newFileLabel {nullptr}
|
| | , _examplesLabel {nullptr}
|
| | , _recentFilesLabel {nullptr}
|
| | , _customFolderLabel {nullptr}
|
| | , _showOnStartupCheckBox {nullptr}
|
| | {
|
| | setObjectName(QLatin1String("StartView"));
|
| | auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/Mod/Start"
|
| | );
|
| | auto cardSpacing = hGrp->GetInt("FileCardSpacing", 15);
|
| | auto showExamples = hGrp->GetBool("ShowExamples", true);
|
| |
|
| |
|
| | std::string customFolder(hGrp->GetASCII("CustomFolder", ""));
|
| | bool showCustomFolder = false;
|
| | if (!customFolder.empty()) {
|
| | showCustomFolder = true;
|
| | }
|
| |
|
| |
|
| | auto firstStartScrollArea = gsl::owner<QScrollArea*>(new QScrollArea());
|
| | auto firstStartScrollWidget = gsl::owner<QWidget*>(new QWidget(firstStartScrollArea));
|
| | firstStartScrollArea->setWidget(firstStartScrollWidget);
|
| | firstStartScrollArea->setWidgetResizable(true);
|
| |
|
| | auto firstStartRegion = gsl::owner<QHBoxLayout*>(new QHBoxLayout(firstStartScrollWidget));
|
| | firstStartRegion->setAlignment(Qt::AlignCenter);
|
| | auto firstStartWidget = gsl::owner<FirstStartWidget*>(new FirstStartWidget(this));
|
| | connect(firstStartWidget, &FirstStartWidget::dismissed, this, &StartView::firstStartWidgetDismissed);
|
| | firstStartRegion->addWidget(firstStartWidget);
|
| | _contents->addWidget(firstStartScrollArea);
|
| |
|
| |
|
| | auto documentsWidget = gsl::owner<QWidget*>(new QWidget());
|
| | _contents->addWidget(documentsWidget);
|
| | auto documentsMainLayout = gsl::owner<QVBoxLayout*>(new QVBoxLayout());
|
| | documentsWidget->setLayout(documentsMainLayout);
|
| | auto documentsScrollArea = gsl::owner<QScrollArea*>(new QScrollArea());
|
| | documentsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded);
|
| | documentsMainLayout->addWidget(documentsScrollArea);
|
| | auto documentsScrollWidget = gsl::owner<QWidget*>(new QWidget(documentsScrollArea));
|
| | documentsScrollArea->setWidget(documentsScrollWidget);
|
| | documentsScrollArea->setWidgetResizable(true);
|
| | auto documentsContentLayout = gsl::owner<QVBoxLayout*>(new QVBoxLayout(documentsScrollWidget));
|
| | documentsContentLayout->setSizeConstraint(QLayout::SizeConstraint::SetMinAndMaxSize);
|
| |
|
| | _newFileLabel = gsl::owner<QLabel*>(new QLabel());
|
| | documentsContentLayout->addWidget(_newFileLabel);
|
| |
|
| | auto createNewRow = gsl::owner<QWidget*>(new QWidget);
|
| | auto flowLayout = gsl::owner<FlowLayout*>(new FlowLayout);
|
| |
|
| |
|
| | flowLayout->setContentsMargins({});
|
| |
|
| |
|
| | createNewRow->setObjectName(QStringLiteral("CreateNewRow"));
|
| | createNewRow->setLayout(flowLayout);
|
| |
|
| | documentsContentLayout->addWidget(createNewRow);
|
| | configureNewFileButtons(flowLayout);
|
| |
|
| | _recentFilesLabel = gsl::owner<QLabel*>(new QLabel());
|
| | documentsContentLayout->addWidget(_recentFilesLabel);
|
| | auto recentFilesListWidget = gsl::owner<FileCardView*>(new FileCardView(_contents));
|
| | connect(recentFilesListWidget, &QListView::clicked, this, &StartView::fileCardSelected);
|
| | documentsContentLayout->addWidget(recentFilesListWidget);
|
| |
|
| | auto customFolderListWidget = gsl::owner<FileCardView*>(new FileCardView(_contents));
|
| | customFolderListWidget->setVisible(showCustomFolder);
|
| | _customFolderLabel = gsl::owner<QLabel*>(new QLabel());
|
| | _customFolderLabel->setVisible(showCustomFolder);
|
| | documentsContentLayout->addWidget(_customFolderLabel);
|
| |
|
| | connect(customFolderListWidget, &QListView::clicked, this, &StartView::fileCardSelected);
|
| | documentsContentLayout->addWidget(customFolderListWidget);
|
| |
|
| | auto examplesListWidget = gsl::owner<FileCardView*>(new FileCardView(_contents));
|
| | examplesListWidget->setVisible(showExamples);
|
| | _examplesLabel = gsl::owner<QLabel*>(new QLabel());
|
| | _examplesLabel->setVisible(showExamples);
|
| | documentsContentLayout->addWidget(_examplesLabel);
|
| |
|
| | connect(examplesListWidget, &QListView::clicked, this, &StartView::fileCardSelected);
|
| | documentsContentLayout->addWidget(examplesListWidget);
|
| |
|
| | documentsContentLayout->setSpacing(static_cast<int>(cardSpacing));
|
| | documentsContentLayout->addStretch();
|
| |
|
| |
|
| |
|
| | auto footerLayout = gsl::owner<QHBoxLayout*>(new QHBoxLayout());
|
| | documentsMainLayout->addLayout(footerLayout);
|
| |
|
| | _openFirstStart = gsl::owner<QPushButton*>(new QPushButton());
|
| | _openFirstStart->setIcon(QIcon(QLatin1String(":/icons/preferences-general.svg")));
|
| | connect(_openFirstStart, &QPushButton::clicked, this, &StartView::openFirstStartClicked);
|
| |
|
| | _showOnStartupCheckBox = gsl::owner<QCheckBox*>(new QCheckBox());
|
| | bool showOnStartup = hGrp->GetBool("ShowOnStartup", true);
|
| | _showOnStartupCheckBox->setCheckState(
|
| | showOnStartup ? Qt::CheckState::Unchecked : Qt::CheckState::Checked
|
| | );
|
| | connect(_showOnStartupCheckBox, &QCheckBox::toggled, this, &StartView::showOnStartupChanged);
|
| |
|
| | footerLayout->addWidget(_openFirstStart);
|
| | footerLayout->addStretch();
|
| | footerLayout->addWidget(_showOnStartupCheckBox);
|
| |
|
| | setCentralWidget(_contents);
|
| |
|
| |
|
| | auto firstStart = hGrp->GetBool("FirstStart2024", true);
|
| | _contents->setCurrentWidget(firstStart ? firstStartScrollArea : documentsWidget);
|
| | configureCustomFolderListWidget(customFolderListWidget);
|
| | configureExamplesListWidget(examplesListWidget);
|
| | configureRecentFilesListWidget(recentFilesListWidget, _recentFilesLabel);
|
| |
|
| | QTimer::singleShot(2000, [this, recentFilesListWidget]() {
|
| | auto updateFun = [this, recentFilesListWidget]() {
|
| | configureRecentFilesListWidget(recentFilesListWidget, _recentFilesLabel);
|
| | };
|
| | auto recentFiles = Gui::getMainWindow()->findChild<Gui::RecentFilesAction*>();
|
| | if (recentFiles != nullptr) {
|
| | connect(recentFiles, &Gui::RecentFilesAction::recentFilesListModified, this, updateFun);
|
| | }
|
| | });
|
| |
|
| | isInitialized = true;
|
| |
|
| | retranslateUi();
|
| | }
|
| |
|
| | void StartView::configureNewFileButtons(QLayout* layout) const
|
| | {
|
| | auto newEmptyFile = gsl::owner<NewFileButton*>(new NewFileButton(
|
| | {tr("Empty File"),
|
| | tr("Creates a new empty FreeCAD file"),
|
| | QLatin1String(":/icons/document-new.svg")}
|
| | ));
|
| | auto openFile = gsl::owner<NewFileButton*>(new NewFileButton(
|
| | {tr("Open File"),
|
| | tr("Opens an existing CAD file or 3D model"),
|
| | QLatin1String(":/icons/document-open.svg")}
|
| | ));
|
| | auto partDesign = gsl::owner<NewFileButton*>(new NewFileButton(
|
| | {tr("Parametric Body"),
|
| | tr("Creates a body with the Part Design workbench"),
|
| | QLatin1String(":/icons/PartDesignWorkbench.svg")}
|
| | ));
|
| | auto assembly = gsl::owner<NewFileButton*>(new NewFileButton(
|
| | {tr("Assembly"),
|
| | tr("Creates an assembly project"),
|
| | QLatin1String(":/icons/AssemblyWorkbench.svg")}
|
| | ));
|
| | auto draft = gsl::owner<NewFileButton*>(new NewFileButton(
|
| | {tr("2D Draft"), tr("Creates a 2D Draft document"), QLatin1String(":/icons/DraftWorkbench.svg")}
|
| | ));
|
| | auto arch = gsl::owner<NewFileButton*>(new NewFileButton(
|
| | {tr("BIM/Architecture"),
|
| | tr("Creates an architectural project"),
|
| | QLatin1String(":/icons/BIMWorkbench.svg")}
|
| | ));
|
| |
|
| |
|
| | layout->addWidget(partDesign);
|
| | layout->addWidget(assembly);
|
| | layout->addWidget(draft);
|
| | layout->addWidget(arch);
|
| | layout->addWidget(newEmptyFile);
|
| | layout->addWidget(openFile);
|
| |
|
| | 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<FileCardDelegate*>(new FileCardDelegate(fileCardWidget));
|
| | fileCardWidget->setItemDelegate(delegate);
|
| |
|
| | fileCardWidget->setMinimumWidth(fileCardWidget->parentWidget()->width());
|
| |
|
| |
|
| |
|
| | }
|
| |
|
| |
|
| | 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::configureCustomFolderListWidget(QListView* customFolderListWidget)
|
| | {
|
| | _customFolderModel.loadCustomFolder();
|
| | customFolderListWidget->setModel(&_customFolderModel);
|
| | configureFileCardWidget(customFolderListWidget);
|
| | }
|
| |
|
| |
|
| | void StartView::newEmptyFile()
|
| | {
|
| | Gui::Application::Instance->commandManager().runCommandByName("Std_New");
|
| | postStart(PostStartBehavior::switchWorkbench);
|
| | }
|
| |
|
| | void StartView::newPartDesignFile()
|
| | {
|
| | 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()
|
| | {
|
| | auto originalDocument = Gui::Application::Instance->activeDocument();
|
| | Gui::Application::Instance->commandManager().runCommandByName("Std_Open");
|
| | Gui::Application::checkForRecomputes();
|
| | if (Gui::Application::Instance->activeDocument() != originalDocument) {
|
| |
|
| |
|
| | postStart(PostStartBehavior::switchWorkbench);
|
| | }
|
| | }
|
| |
|
| | void StartView::newAssemblyFile()
|
| | {
|
| | 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()
|
| | {
|
| | 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()
|
| | {
|
| | Gui::Application::Instance->commandManager().runCommandByName("Std_New");
|
| | try {
|
| | Gui::Application::Instance->activateWorkbench("BIMWorkbench");
|
| | }
|
| | catch (...) {
|
| | Gui::Application::Instance->activateWorkbench("ArchWorkbench");
|
| | }
|
| |
|
| |
|
| | Gui::Command::doCommand(
|
| | Gui::Command::Gui,
|
| | "Gui.activeDocument().activeView().viewDefaultOrientation(None, 10000.0)"
|
| | );
|
| | postStart(PostStartBehavior::doNotSwitchWorkbench);
|
| | }
|
| |
|
| | bool StartView::onHasMsg(const char* pMsg) const
|
| | {
|
| | if (strcmp("AllowsOverlayOnHover", pMsg) == 0) {
|
| | return false;
|
| | }
|
| |
|
| | return MDIView::onHasMsg(pMsg);
|
| | }
|
| |
|
| | void StartView::postStart(PostStartBehavior behavior)
|
| | {
|
| | 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());
|
| | }
|
| | }
|
| | if (auto closeStart = hGrp->GetBool("closeStart", false)) {
|
| | for (QWidget* w = this; w != nullptr; w = w->parentWidget()) {
|
| | if (auto mdiSub = qobject_cast<QMdiSubWindow*>(w)) {
|
| | mdiSub->close();
|
| | return;
|
| | }
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | void StartView::fileCardSelected(const QModelIndex& index)
|
| | {
|
| | try {
|
| | auto filename = index.data(static_cast<int>(Start::DisplayedFilesModelRoles::path)).toString();
|
| | Gui::ModuleIO::verifyAndOpenFile(filename);
|
| | }
|
| | 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
|
| | );
|
| |
|
| |
|
| | }
|
| |
|
| | void StartView::openFirstStartClicked()
|
| | {
|
| | _contents->setCurrentIndex(0);
|
| | }
|
| |
|
| | void StartView::firstStartWidgetDismissed()
|
| | {
|
| | auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/Mod/Start"
|
| | );
|
| | hGrp->SetBool("FirstStart2024", false);
|
| | _contents->setCurrentIndex(1);
|
| | }
|
| |
|
| | void StartView::changeEvent(QEvent* event)
|
| | {
|
| | if (!isInitialized) {
|
| | return;
|
| | }
|
| |
|
| | _openFirstStart->setEnabled(true);
|
| | Gui::Document* doc = Gui::Application::Instance->activeDocument();
|
| | if (doc) {
|
| | if (auto view = dynamic_cast<Gui::View3DInventor*>(doc->getActiveView())) {
|
| | Gui::View3DInventorViewer* viewer = view->getViewer();
|
| | if (viewer->isEditing()) {
|
| | _openFirstStart->setEnabled(false);
|
| | }
|
| | }
|
| | }
|
| |
|
| | if (event->type() == QEvent::LanguageChange) {
|
| | this->retranslateUi();
|
| | }
|
| |
|
| | Gui::MDIView::changeEvent(event);
|
| | }
|
| |
|
| | void StartView::showEvent(QShowEvent* event)
|
| | {
|
| | if (auto mainWindow = Gui::getMainWindow()) {
|
| | if (auto mdiArea = mainWindow->findChild<QMdiArea*>()) {
|
| | connect(
|
| | mdiArea,
|
| | &QMdiArea::subWindowActivated,
|
| | this,
|
| | &StartView::onMdiSubWindowActivated,
|
| | Qt::UniqueConnection
|
| | );
|
| | }
|
| | }
|
| | Gui::MDIView::showEvent(event);
|
| | }
|
| |
|
| | void StartView::onMdiSubWindowActivated(QMdiSubWindow* subWindow)
|
| | {
|
| |
|
| |
|
| | bool isOurWindow = subWindow && subWindow->isAncestorOf(this);
|
| | setListViewUpdatesEnabled(isOurWindow);
|
| | }
|
| |
|
| | void StartView::setListViewUpdatesEnabled(bool enabled)
|
| | {
|
| |
|
| | QList<QListView*> listViews = findChildren<QListView*>();
|
| | for (QListView* listView : listViews) {
|
| | listView->setUpdatesEnabled(enabled);
|
| | if (listView->viewport()) {
|
| | listView->viewport()->setUpdatesEnabled(enabled);
|
| | }
|
| | }
|
| | }
|
| |
|
| | void StartView::retranslateUi()
|
| | {
|
| | QString title = QCoreApplication::translate("Workbench", "Start");
|
| | setWindowTitle(title);
|
| |
|
| | const QLatin1String h1Start("<h1>");
|
| | const QLatin1String h1End("</h1>");
|
| |
|
| | _newFileLabel->setText(h1Start + tr("New File") + h1End);
|
| | _examplesLabel->setText(h1Start + tr("Examples") + h1End);
|
| | _recentFilesLabel->setText(h1Start + tr("Recent Files") + h1End);
|
| |
|
| | auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
| | "User parameter:BaseApp/Preferences/Mod/Start"
|
| | );
|
| | std::string customFolder(hGrp->GetASCII("CustomFolder", ""));
|
| | bool shortCustomFolder = hGrp->GetBool("ShortCustomFolder", true);
|
| | if (!customFolder.empty()) {
|
| | if (shortCustomFolder) {
|
| | _customFolderLabel->setToolTip(QString::fromUtf8(customFolder.c_str()));
|
| | customFolder = customFolder.substr(customFolder.find_last_of("/\\") + 1);
|
| | }
|
| | _customFolderLabel->setText(h1Start + QString::fromUtf8(customFolder.c_str()) + h1End);
|
| | }
|
| |
|
| | QString application = QString::fromUtf8(App::Application::Config()["ExeName"].c_str());
|
| | _openFirstStart->setText(tr("Open First Start Setup"));
|
| | _showOnStartupCheckBox->setText(tr("Do not show this Start page again (start with blank screen)"));
|
| | }
|
| |
|