From 4fbbc09895ab8529b1f67d497ae174385b4d900a Mon Sep 17 00:00:00 2001 From: Torsten Paul Date: Mon, 12 May 2014 22:47:42 +0200 Subject: [PATCH] Make editor and console windows dockable and make MDI mode a runtime option. --- src/GLView.cc | 2 +- src/MainWindow.h | 11 ++ src/MainWindow.ui | 412 ++++++++++++++++++++++++--------------------- src/Preferences.cc | 20 +++ src/Preferences.h | 4 + src/Preferences.ui | 18 +- src/mainwin.cc | 286 ++++++++++++++++++------------- src/openscad.cc | 5 +- 8 files changed, 447 insertions(+), 311 deletions(-) diff --git a/src/GLView.cc b/src/GLView.cc index 80ebdc24..db9b3465 100644 --- a/src/GLView.cc +++ b/src/GLView.cc @@ -349,7 +349,7 @@ void GLView::gimbalCamPaintGL() glColor3d(1.0, 0.0, 0.0); if (this->renderer) { -#if defined(ENABLE_MDI) && defined(ENABLE_OPENCSG) +#if defined(ENABLE_OPENCSG) // FIXME: This belongs in the OpenCSG renderer, but it doesn't know about this ID yet OpenCSG::setContext(this->opencsg_id); #endif diff --git a/src/MainWindow.h b/src/MainWindow.h index 7056d46d..ed8de88c 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -64,6 +64,7 @@ public: static const int maxRecentFiles = 10; QAction *actionRecentFile[maxRecentFiles]; + QMap knownFileExtensions; MainWindow(const QString &filename); ~MainWindow(); @@ -74,6 +75,8 @@ protected: private slots: void updatedFps(); void updateTVal(); + void updateMdiMode(bool mdi); + void updateUndockMode(bool undockMode); void setFileName(const QString &filename); void setFont(const QString &family, uint size); void showProgress(); @@ -81,6 +84,7 @@ private slots: private: void openFile(const QString &filename); + void handleFileDrop(const QString &filename); void refreshDocument(); void updateTemporalVariables(); bool fileChangedOnDisk(); @@ -96,6 +100,7 @@ private: void saveBackup(); void writeBackup(class QFile *file); QString get2dExportFilename(QString format, QString extension); + void setDockWidgetTitle(QDockWidget *dockWidget, QString prefix, bool topLevel); class QMessageBox *openglbox; @@ -168,6 +173,10 @@ public: public slots: void actionReloadRenderPreview(); + void on_editorDock_visibilityChanged(bool); + void on_consoleDock_visibilityChanged(bool); + void editorTopLevelChanged(bool); + void consoleTopLevelChanged(bool); #ifdef ENABLE_OPENCSG void viewModePreview(); #endif @@ -207,6 +216,8 @@ public slots: private: static void report_func(const class AbstractNode*, void *vp, int mark); + static bool mdiMode; + static bool undockMode; char const * afterCompileSlot; bool procevents; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 98afbd8d..fc499c62 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -6,208 +6,96 @@ 0 0 - 846 - 647 + 936 + 693 + + 0 + 0 - - - Qt::Horizontal + + + - - - true + + + 0 - - - 0 - - - - - true - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - + + + + + 200 + 100 + + + + + + + + true + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + 0 - - - - - - Find - - - - - Replace - - - - - - - - Search string - - - - - - - < - - - - - - - > - - - - - - - Done - - - - - - - Replacement string - - - - - - - Replace - - - - - - - All - - - - - - - - - - - Monaco - 8 - - - - Qt::WheelFocus - - - - - - - - - - - Qt::Vertical - - - - - Qt::ClickFocus - - - true - - - - - - - - true - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - - 0 - - - - - Time: - - - - - - - - - - FPS: - - - - - - - - - - Steps: - - - - - - - - - - Dump Pictures - - - - - - - - + + + + Time: + + + + + + + + + + FPS: + + + + + + + + + + Steps: + + + + + + + + + + Dump Pictures + + + + + + + @@ -217,8 +105,8 @@ 0 0 - 846 - 22 + 936 + 34 @@ -361,6 +249,146 @@ + + + 1 + + + + + 0 + + + + + true + + + + 0 + 0 + + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + + + + Find + + + + + Replace + + + + + + + + Search string + + + + + + + < + + + + + + + > + + + + + + + Done + + + + + + + Replacement string + + + + + + + Replace + + + + + + + All + + + + + + + + + + + 100 + 100 + + + + + Monaco + 8 + + + + Qt::WheelFocus + + + + + + + + + 8 + + + + + 0 + + + + + Qt::ClickFocus + + + true + + + + + + &New diff --git a/src/Preferences.cc b/src/Preferences.cc index 2c149696..94df31b3 100644 --- a/src/Preferences.cc +++ b/src/Preferences.cc @@ -96,6 +96,8 @@ Preferences::Preferences(QWidget *parent) : QMainWindow(parent) #endif this->defaultmap["advanced/openCSGLimit"] = RenderSettings::inst()->openCSGTermLimit; this->defaultmap["advanced/forceGoldfeather"] = false; + this->defaultmap["advanced/mdi"] = true; + this->defaultmap["advanced/undockableWindows"] = false; // Toolbar QActionGroup *group = new QActionGroup(this); @@ -335,6 +337,22 @@ void Preferences::on_checkNowButton_clicked() } } +void +Preferences::on_mdiCheckBox_toggled(bool state) +{ + QSettings settings; + settings.setValue("advanced/mdi", state); + emit updateMdiMode(state); +} + +void +Preferences::on_undockCheckBox_toggled(bool state) +{ + QSettings settings; + settings.setValue("advanced/undockableWindows", state); + emit updateUndockMode(state); +} + void Preferences::on_openCSGWarningBox_toggled(bool state) { @@ -461,6 +479,8 @@ void Preferences::updateGUI() this->polysetCacheSizeEdit->setText(getValue("advanced/polysetCacheSize").toString()); this->opencsgLimitEdit->setText(getValue("advanced/openCSGLimit").toString()); this->forceGoldfeatherBox->setChecked(getValue("advanced/forceGoldfeather").toBool()); + this->mdiCheckBox->setChecked(getValue("advanced/mdi").toBool()); + this->undockCheckBox->setChecked(getValue("advanced/undockableWindows").toBool()); } void Preferences::apply() const diff --git a/src/Preferences.h b/src/Preferences.h index 1ee7b856..102ddf15 100644 --- a/src/Preferences.h +++ b/src/Preferences.h @@ -34,10 +34,14 @@ public slots: void on_mouseWheelZoomBox_toggled(bool); void on_updateCheckBox_toggled(bool); void on_snapshotCheckBox_toggled(bool); + void on_mdiCheckBox_toggled(bool); + void on_undockCheckBox_toggled(bool); void on_checkNowButton_clicked(); signals: void requestRedraw() const; + void updateMdiMode(bool mdi) const; + void updateUndockMode(bool mdi) const; void fontChanged(const QString &family, uint size) const; void openCSGSettingsChanged() const; void syntaxHighlightChanged(const QString &s); diff --git a/src/Preferences.ui b/src/Preferences.ui index a078bbfb..2eb1b129 100644 --- a/src/Preferences.ui +++ b/src/Preferences.ui @@ -7,7 +7,7 @@ 0 0 823 - 433 + 440 @@ -27,7 +27,7 @@ - 1 + 4 @@ -607,6 +607,20 @@ + + + + Allow to open multiple documents + + + + + + + Enable undocking of Editor and Console + + + diff --git a/src/mainwin.cc b/src/mainwin.cc index 325ff16d..8620b0c1 100644 --- a/src/mainwin.cc +++ b/src/mainwin.cc @@ -76,6 +76,7 @@ #include #include #include +#include #include @@ -157,10 +158,17 @@ settings_valueList(const QString &key, const QList &defaultList = QListsetAttribute(Qt::WA_DeleteOnClose); @@ -195,7 +203,16 @@ MainWindow::MainWindow(const QString &filename) fps = 0; fsteps = 1; - editActionZoomIn->setShortcuts(QList() << editActionZoomIn->shortcuts() << QKeySequence("CTRL+=")); + const QString importStatement = "import(\"%1\");\n"; + const QString surfaceStatement = "surface(\"%1\");\n"; + knownFileExtensions["stl"] = importStatement; + knownFileExtensions["off"] = importStatement; + knownFileExtensions["dxf"] = importStatement; + knownFileExtensions["dat"] = surfaceStatement; + knownFileExtensions["png"] = surfaceStatement; + knownFileExtensions["scad"] = ""; + + editActionZoomIn->setShortcuts(QList() << editActionZoomIn->shortcuts() << QKeySequence("CTRL+=")); connect(this, SIGNAL(highlightError(int)), editor, SLOT(highlightError(int))); connect(this, SIGNAL(unhighlightLastError()), editor, SLOT(unhighlightLastError())); @@ -358,8 +375,8 @@ MainWindow::MainWindow(const QString &filename) connect(this->viewActionPerspective, SIGNAL(triggered()), this, SLOT(viewPerspective())); connect(this->viewActionOrthogonal, SIGNAL(triggered()), this, SLOT(viewOrthogonal())); connect(this->viewActionHide, SIGNAL(triggered()), this, SLOT(hideConsole())); - connect(this->viewActionZoomIn, SIGNAL(triggered()), qglview, SLOT(ZoomIn())); - connect(this->viewActionZoomOut, SIGNAL(triggered()), qglview, SLOT(ZoomOut())); + connect(this->viewActionZoomIn, SIGNAL(triggered()), qglview, SLOT(ZoomIn())); + connect(this->viewActionZoomOut, SIGNAL(triggered()), qglview, SLOT(ZoomOut())); // Help menu connect(this->helpActionAbout, SIGNAL(triggered()), this, SLOT(helpAbout())); @@ -367,7 +384,6 @@ MainWindow::MainWindow(const QString &filename) connect(this->helpActionManual, SIGNAL(triggered()), this, SLOT(helpManual())); connect(this->helpActionLibraryInfo, SIGNAL(triggered()), this, SLOT(helpLibrary())); - setCurrentOutput(); PRINT(helptitle); @@ -387,7 +403,9 @@ MainWindow::MainWindow(const QString &filename) connect(this->qglview, SIGNAL(doAnimateUpdate()), this, SLOT(animateUpdate())); connect(Preferences::inst(), SIGNAL(requestRedraw()), this->qglview, SLOT(updateGL())); - connect(Preferences::inst(), SIGNAL(fontChanged(const QString&,uint)), + connect(Preferences::inst(), SIGNAL(updateMdiMode(bool)), this, SLOT(updateMdiMode(bool))); + connect(Preferences::inst(), SIGNAL(updateUndockMode(bool)), this, SLOT(updateUndockMode(bool))); + connect(Preferences::inst(), SIGNAL(fontChanged(const QString&,uint)), this, SLOT(setFont(const QString&,uint))); connect(Preferences::inst(), SIGNAL(openCSGSettingsChanged()), this, SLOT(openCSGSettingsChanged())); @@ -408,13 +426,13 @@ MainWindow::MainWindow(const QString &filename) // make sure it looks nice.. QSettings settings; + restoreState(settings.value("window/state", QByteArray()).toByteArray()); resize(settings.value("window/size", QSize(800, 600)).toSize()); move(settings.value("window/position", QPoint(0, 0)).toPoint()); - QList s1sizes = settings_valueList("window/splitter1sizes",QList()<<400<<400); - QList s2sizes = settings_valueList("window/splitter2sizes",QList()<<400<<200); - splitter1->setSizes(s1sizes); - splitter2->setSizes(s2sizes); + connect(this->editorDock, SIGNAL(topLevelChanged(bool)), this, SLOT(editorTopLevelChanged(bool))); + connect(this->consoleDock, SIGNAL(topLevelChanged(bool)), this, SLOT(consoleTopLevelChanged(bool))); + // display this window and check for OpenGL 2.0 (OpenCSG) support viewModeThrownTogether(); show(); @@ -459,6 +477,8 @@ MainWindow::loadViewSettings(){ editActionHide->setChecked(true); hideEditor(); } + updateMdiMode(settings.value("advanced/mdi").toBool()); + updateUndockMode(settings.value("advanced/undockableWindows").toBool()); } void @@ -476,6 +496,23 @@ MainWindow::loadDesignSettings() #endif } +void MainWindow::updateMdiMode(bool mdi) +{ + MainWindow::mdiMode = mdi; +} + +void MainWindow::updateUndockMode(bool undockMode) +{ + MainWindow::undockMode = undockMode; + if (undockMode) { + editorDock->setFeatures(editorDock->features() | QDockWidget::DockWidgetFloatable); + consoleDock->setFeatures(consoleDock->features() | QDockWidget::DockWidgetFloatable); + } else { + editorDock->setFeatures(editorDock->features() & ~QDockWidget::DockWidgetFloatable); + consoleDock->setFeatures(consoleDock->features() & ~QDockWidget::DockWidgetFloatable); + } +} + MainWindow::~MainWindow() { if (root_module) delete root_module; @@ -515,7 +552,6 @@ void MainWindow::report_func(const class AbstractNode*, void *vp, int mark) /*! Requests to open a file from an external event, e.g. by double-clicking a filename. */ -#ifdef ENABLE_MDI void MainWindow::requestOpenFile(const QString &filename) { // if we have an empty open window, use that one @@ -532,37 +568,41 @@ void MainWindow::requestOpenFile(const QString &filename) // otherwise, create a new one new MainWindow(filename); } -#else -void MainWindow::requestOpenFile(const QString &) -{ -} -#endif +/*! + Open the given file. In MDI mode a new window is created if the current + one is not empty. Otherwise the current window content is overwritten. + Any check whether to replace the content have to be made before. + */ void MainWindow::openFile(const QString &new_filename) { - QString actual_filename = new_filename; - QFileInfo fi(new_filename); - if (fi.suffix().toLower().contains(QRegExp("^(stl|off|dxf)$"))) { - actual_filename = QString(); + if (MainWindow::mdiMode) { + if (!editor->toPlainText().isEmpty()) { + new MainWindow(new_filename); + return; + } } -#ifdef ENABLE_MDI - if (!editor->toPlainText().isEmpty()) { - new MainWindow(actual_filename); - clearCurrentOutput(); - return; - } -#endif - setFileName(actual_filename); + + setCurrentOutput(); editor->setPlainText(""); this->last_compiled_doc = ""; + const QFileInfo fileInfo(new_filename); + const QString suffix = fileInfo.suffix().toLower(); + const bool knownFileType = knownFileExtensions.contains(suffix); + const QString cmd = knownFileExtensions[suffix]; + if (knownFileType && cmd.isEmpty()) { + setFileName(new_filename); + updateRecentFiles(); + } else { + setFileName(""); + editor->setPlainText(cmd.arg(new_filename)); + } + fileChangedOnDisk(); // force cached autoReloadId to update refreshDocument(); - updateRecentFiles(); - if (actual_filename.isEmpty()) { - this->editor->setPlainText(QString("import(\"%1\");\n").arg(new_filename)); - } + clearCurrentOutput(); } void @@ -570,38 +610,17 @@ MainWindow::setFileName(const QString &filename) { if (filename.isEmpty()) { this->fileName.clear(); - this->top_ctx.setDocumentPath(currentdir); -#if QT_VERSION >= QT_VERSION_CHECK(4, 4, 0) - setWindowTitle(""); setWindowFilePath("untitled.scad"); -#else - setWindowTitle("OpenSCAD - New Document[*]"); -#endif - } - else { + + this->top_ctx.setDocumentPath(currentdir); + } else { QFileInfo fileinfo(filename); + this->fileName = fileinfo.exists() ? fileinfo.absoluteFilePath() : fileinfo.fileName(); + setWindowFilePath(this->fileName); - // Check that the canonical file path exists - only update recent files - // if it does. Should prevent empty list items on initial open etc. - QString infoFileName = fileinfo.absoluteFilePath(); - - if (!infoFileName.isEmpty()) { - this->fileName = infoFileName; -#if QT_VERSION >= QT_VERSION_CHECK(4, 4, 0) - setWindowTitle(""); - this->setWindowFilePath(infoFileName); -#else - setWindowTitle("OpenSCAD - " + fileinfo.fileName() + "[*]"); -#endif - } else { - this->fileName = fileinfo.fileName(); - setWindowTitle("OpenSCAD - " + fileinfo.fileName() + "[*]"); - } - - this->top_ctx.setDocumentPath(fileinfo.dir().absolutePath().toLocal8Bit().constData()); QDir::setCurrent(fileinfo.dir().absolutePath()); + this->top_ctx.setDocumentPath(fileinfo.dir().absolutePath().toLocal8Bit().constData()); } - } void MainWindow::updateRecentFiles() @@ -963,15 +982,15 @@ void MainWindow::actionUpdateCheck() void MainWindow::actionNew() { -#ifdef ENABLE_MDI - new MainWindow(QString()); -#else - if (!maybeSave()) - return; + if (MainWindow::mdiMode) { + new MainWindow(QString()); + } else { + if (!maybeSave()) + return; - setFileName(""); - editor->setPlainText(""); -#endif + setFileName(""); + editor->setPlainText(""); + } } void MainWindow::actionOpen() @@ -979,42 +998,30 @@ void MainWindow::actionOpen() QSettings settings; QString last_dirname = settings.value("lastOpenDirName").toString(); QString new_filename = QFileDialog::getOpenFileName(this, "Open File", - last_dirname, "OpenSCAD Designs (*.scad *.csg)"); - if (new_filename!="") { - QDir last_dir = QFileInfo( new_filename ).dir(); - last_dirname = last_dir.path(); - settings.setValue("lastOpenDirName", last_dirname); - } -#ifdef ENABLE_MDI - if (!new_filename.isEmpty()) { - openFile(new_filename); - } -#else - if (!new_filename.isEmpty()) { - if (!maybeSave()) - return; + last_dirname, "OpenSCAD Designs (*.scad *.csg)"); - setCurrentOutput(); - openFile(new_filename); - clearCurrentOutput(); + if (new_filename.isEmpty()) { + return; } -#endif + + QDir last_dir = QFileInfo(new_filename).dir(); + last_dirname = last_dir.path(); + settings.setValue("lastOpenDirName", last_dirname); + if (!MainWindow::mdiMode && !maybeSave()) { + return; + } + + openFile(new_filename); } void MainWindow::actionOpenRecent() { - QAction *action = qobject_cast(sender()); - -#ifdef ENABLE_MDI - openFile(action->data().toString()); -#else - if (!maybeSave()) + if (!MainWindow::mdiMode && !maybeSave()) { return; - - if (action) { - openFile(action->data().toString()); } -#endif + + QAction *action = qobject_cast(sender()); + openFile(action->data().toString()); } void MainWindow::clearRecentFiles() @@ -1063,6 +1070,10 @@ void MainWindow::updateRecentFileActions() void MainWindow::actionOpenExample() { + if (!MainWindow::mdiMode && !maybeSave()) { + return; + } + QAction *action = qobject_cast(sender()); if (action) { openFile(qexamplesdir + QDir::separator() + action->text()); @@ -1180,18 +1191,6 @@ void MainWindow::actionReload() } } -void MainWindow::hideEditor() -{ - QSettings settings; - if (editActionHide->isChecked()) { - editorPane->hide(); - settings.setValue("view/hideEditor",true); - } else { - editorPane->show(); - settings.setValue("view/hideEditor",false); - } -} - void MainWindow::pasteViewportTranslation() { QTextCursor cursor = editor->textCursor(); @@ -2058,15 +2057,57 @@ void MainWindow::viewResetView() this->qglview->updateGL(); } -void MainWindow::hideConsole() +void MainWindow::on_editorDock_visibilityChanged(bool visible) { QSettings settings; - if (viewActionHide->isChecked()) { - console->hide(); - settings.setValue("view/hideConsole",true); + settings.setValue("view/hideEditor", !visible); + editActionHide->setChecked(!visible); + editorTopLevelChanged(editorDock->isFloating()); +} + +void MainWindow::on_consoleDock_visibilityChanged(bool visible) +{ + QSettings settings; + settings.setValue("view/hideConsole", !visible); + viewActionHide->setChecked(!visible); + consoleTopLevelChanged(consoleDock->isFloating()); +} + +void MainWindow::editorTopLevelChanged(bool topLevel) +{ + setDockWidgetTitle(editorDock, QString("Editor"), topLevel); +} + +void MainWindow::consoleTopLevelChanged(bool topLevel) +{ + setDockWidgetTitle(consoleDock, QString("Console"), topLevel); +} + +void MainWindow::setDockWidgetTitle(QDockWidget *dockWidget, QString prefix, bool topLevel) +{ + QString title(prefix); + if (topLevel) { + const QFileInfo fileInfo(windowFilePath()); + title += " (" + fileInfo.fileName() + ")"; + } + dockWidget->setWindowTitle(title); +} + +void MainWindow::hideEditor() +{ + if (editActionHide->isChecked()) { + editorDock->close(); } else { - console->show(); - settings.setValue("view/hideConsole",false); + editorDock->show(); + } +} + +void MainWindow::hideConsole() +{ + if (viewActionHide->isChecked()) { + consoleDock->hide(); + } else { + consoleDock->show(); } } @@ -2083,11 +2124,27 @@ void MainWindow::dropEvent(QDropEvent *event) for (int i = 0; i < urls.size(); i++) { if (urls[i].scheme() != "file") continue; - openFile(urls[i].toLocalFile()); + + handleFileDrop(urls[i].toLocalFile()); } clearCurrentOutput(); } +void MainWindow::handleFileDrop(const QString &filename) +{ + const QFileInfo fileInfo(filename); + const QString suffix = fileInfo.suffix().toLower(); + const QString cmd = knownFileExtensions[suffix]; + if (cmd.isEmpty()) { + if (!MainWindow::mdiMode && !maybeSave()) { + return; + } + openFile(filename); + } else { + editor->insertPlainText(cmd.arg(filename)); + } +} + void MainWindow::helpAbout() { @@ -2162,8 +2219,7 @@ void MainWindow::closeEvent(QCloseEvent *event) QSettings settings; settings.setValue("window/size", size()); settings.setValue("window/position", pos()); - settings_setValueList("window/splitter1sizes",splitter1->sizes()); - settings_setValueList("window/splitter2sizes",splitter2->sizes()); + settings.setValue("window/state", saveState()); if (this->tempFile) { delete this->tempFile; this->tempFile = NULL; diff --git a/src/openscad.cc b/src/openscad.cc index 115e3383..cd3dacc8 100644 --- a/src/openscad.cc +++ b/src/openscad.cc @@ -540,7 +540,10 @@ int gui(vector &inputFiles, const fs::path &original_path, int argc, cha QCoreApplication::setOrganizationDomain("openscad.org"); QCoreApplication::setApplicationName("OpenSCAD"); QCoreApplication::setApplicationVersion(TOSTRING(OPENSCAD_VERSION)); - +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + QGuiApplication::setApplicationDisplayName("OpenSCAD"); +#endif + // Other global settings qRegisterMetaType >();