--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/mainWindow.cpp Tue Mar 03 16:55:36 2015 +0200 @@ -0,0 +1,1191 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2015 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <QGridLayout> +#include <QMessageBox> +#include <QEvent> +#include <QContextMenuEvent> +#include <QMenuBar> +#include <QStatusBar> +#include <QSplitter> +#include <QListWidget> +#include <QToolButton> +#include <QComboBox> +#include <QDialogButtonBox> +#include <QToolBar> +#include <QProgressBar> +#include <QLabel> +#include <QFileDialog> +#include <QPushButton> +#include <QCoreApplication> +#include <QTimer> +#include <QMetaMethod> +#include <QSettings> +#include "main.h" +#include "glRenderer.h" +#include "mainWindow.h" +#include "ldDocument.h" +#include "configuration.h" +#include "miscallenous.h" +#include "colors.h" +#include "editHistory.h" +#include "radioGroup.h" +#include "addObjectDialog.h" +#include "messageLog.h" +#include "configuration.h" +#include "ui_ldforge.h" +#include "primitives.h" +#include "editmodes/abstractEditMode.h" + +static bool g_isSelectionLocked = false; +static QMap<QAction*, QKeySequence> g_defaultShortcuts; + +CFGENTRY (Bool, ColorizeObjectsList, true) +CFGENTRY (String, QuickColorToolbar, "4:25:14:27:2:3:11:1:22:|:0:72:71:15") +CFGENTRY (Bool, ListImplicitFiles, false) +CFGENTRY (List, HiddenToolbars, {}) +EXTERN_CFGENTRY (List, RecentFiles) +EXTERN_CFGENTRY (Bool, DrawAxes) +EXTERN_CFGENTRY (String, MainColor) +EXTERN_CFGENTRY (Float, MainColorAlpha) +EXTERN_CFGENTRY (Bool, DrawWireframe) +EXTERN_CFGENTRY (Bool, BFCRedGreenView) +EXTERN_CFGENTRY (Bool, DrawAngles) +EXTERN_CFGENTRY (Bool, RandomColors) +EXTERN_CFGENTRY (Bool, DrawSurfaces) +EXTERN_CFGENTRY (Bool, DrawEdgeLines) +EXTERN_CFGENTRY (Bool, DrawConditionalLines) + +// ============================================================================= +// +MainWindow::MainWindow (QWidget* parent, Qt::WindowFlags flags) : + QMainWindow (parent, flags) +{ + g_win = this; + ui = new Ui_LDForgeUI; + ui->setupUi (this); + m_isUpdatingTabs = false; + m_renderer = new GLRenderer (this); + m_tabBar = new QTabBar; + m_tabBar->setTabsClosable (true); + ui->verticalLayout->insertWidget (0, m_tabBar); + + // Stuff the renderer into its frame + QVBoxLayout* rendererLayout = new QVBoxLayout (ui->rendererFrame); + rendererLayout->addWidget (R()); + + connect (ui->objectList, SIGNAL (itemSelectionChanged()), this, SLOT (slot_selectionChanged())); + connect (ui->objectList, SIGNAL (itemDoubleClicked (QListWidgetItem*)), this, SLOT (slot_editObject (QListWidgetItem*))); + connect (m_tabBar, SIGNAL (currentChanged(int)), this, SLOT (changeCurrentFile())); + connect (m_tabBar, SIGNAL (tabCloseRequested (int)), this, SLOT (closeTab (int))); + + if (ActivePrimitiveScanner() != null) + connect (ActivePrimitiveScanner(), SIGNAL (workDone()), this, SLOT (updatePrimitives())); + else + updatePrimitives(); + + m_messageLog = new MessageManager; + m_messageLog->setRenderer (R()); + m_renderer->setMessageLog (m_messageLog); + m_quickColors = LoadQuickColorList(); + slot_selectionChanged(); + setStatusBar (new QStatusBar); + updateActions(); + + // Connect all actions and save default sequences + applyToActions ([&](QAction* act) + { + connect (act, SIGNAL (triggered()), this, SLOT (actionTriggered())); + g_defaultShortcuts[act] = act->shortcut(); + }); + + updateGridToolBar(); + updateEditModeActions(); + updateRecentFilesMenu(); + updateColorToolbar(); + updateTitle(); + loadShortcuts (Config::SettingsObject()); + setMinimumSize (300, 200); + connect (qApp, SIGNAL (aboutToQuit()), this, SLOT (slot_lastSecondCleanup())); + connect (ui->ringToolHiRes, SIGNAL (clicked (bool)), this, SLOT (ringToolHiResClicked (bool))); + connect (ui->ringToolSegments, SIGNAL (valueChanged (int)), + this, SLOT (circleToolSegmentsChanged())); + circleToolSegmentsChanged(); // invoke it manually for initial label text + + for (QVariant const& toolbarname : cfg::HiddenToolbars) + { + QToolBar* toolbar = findChild<QToolBar*> (toolbarname.toString()); + + if (toolbar != null) + toolbar->hide(); + } +} + +MainWindow::~MainWindow() +{ + g_win = null; +} + +// ============================================================================= +// +void MainWindow::actionTriggered() +{ + // Get the name of the sender object and use it to compose the slot name, + // then invoke this slot to call the action. + QMetaObject::invokeMethod (this, + qPrintable (format ("slot_%1", sender()->objectName())), Qt::DirectConnection); + endAction(); +} + +// ============================================================================= +// +void MainWindow::endAction() +{ + // Add a step in the history now. + CurrentDocument()->addHistoryStep(); + + // Update the list item of the current file - we may need to draw an icon + // now that marks it as having unsaved changes. + updateDocumentListItem (CurrentDocument()); + refresh(); +} + +// ============================================================================= +// +void MainWindow::slot_lastSecondCleanup() +{ + delete m_renderer; + delete ui; +} + +// ============================================================================= +// +void MainWindow::updateRecentFilesMenu() +{ + // First, clear any items in the recent files menu +for (QAction * recent : m_recentFiles) + delete recent; + + m_recentFiles.clear(); + + QAction* first = null; + + for (const QVariant& it : cfg::RecentFiles) + { + QString file = it.toString(); + QAction* recent = new QAction (GetIcon ("open-recent"), file, this); + + connect (recent, SIGNAL (triggered()), this, SLOT (slot_recentFile())); + ui->menuOpenRecent->insertAction (first, recent); + m_recentFiles << recent; + first = recent; + } +} + +// ============================================================================= +// +QList<LDQuickColor> LoadQuickColorList() +{ + QList<LDQuickColor> colors; + + for (QString colorname : cfg::QuickColorToolbar.split (":")) + { + if (colorname == "|") + colors << LDQuickColor::getSeparator(); + else + { + LDColor col = LDColor::fromIndex (colorname.toLong()); + + if (col != null) + colors << LDQuickColor (col, null); + } + } + + return colors; +} + +// ============================================================================= +// +void MainWindow::updateColorToolbar() +{ + m_colorButtons.clear(); + ui->toolBarColors->clear(); + ui->toolBarColors->addAction (ui->actionUncolor); + ui->toolBarColors->addSeparator(); + + for (LDQuickColor& entry : m_quickColors) + { + if (entry.isSeparator()) + { + ui->toolBarColors->addSeparator(); + } + else + { + QToolButton* colorButton = new QToolButton; + colorButton->setIcon (MakeColorIcon (entry.color(), 16)); + colorButton->setIconSize (QSize (16, 16)); + colorButton->setToolTip (entry.color().name()); + + connect (colorButton, SIGNAL (clicked()), this, SLOT (slot_quickColor())); + ui->toolBarColors->addWidget (colorButton); + m_colorButtons << colorButton; + + entry.setToolButton (colorButton); + } + } + + updateGridToolBar(); +} + +// ============================================================================= +// +void MainWindow::updateGridToolBar() +{ + // Ensure that the current grid - and only the current grid - is selected. + ui->actionGridCoarse->setChecked (cfg::Grid == Grid::Coarse); + ui->actionGridMedium->setChecked (cfg::Grid == Grid::Medium); + ui->actionGridFine->setChecked (cfg::Grid == Grid::Fine); +} + +// ============================================================================= +// +void MainWindow::updateTitle() +{ + QString title = format (APPNAME " %1", VersionString()); + + // Append our current file if we have one + if (CurrentDocument()) + { + title += ": "; + title += CurrentDocument()->getDisplayName(); + + if (CurrentDocument()->getObjectCount() > 0 and + CurrentDocument()->getObject (0)->type() == OBJ_Comment) + { + // Append title + LDCommentPtr comm = CurrentDocument()->getObject (0).staticCast<LDComment>(); + title += format (": %1", comm->text()); + } + + if (CurrentDocument()->hasUnsavedChanges()) + title += '*'; + } + +#ifdef DEBUG + title += " [debug build]"; +#elif BUILD_ID != BUILD_RELEASE + title += " [pre-release build]"; +#endif // DEBUG + + if (CommitTimeString()[0] != '\0') + title += format (" (%1)", QString::fromUtf8 (CommitTimeString())); + + setWindowTitle (title); +} + +// ============================================================================= +// +int MainWindow::deleteSelection() +{ + if (Selection().isEmpty()) + return 0; + + LDObjectList selCopy = Selection(); + + // Delete the objects that were being selected + for (LDObjectPtr obj : selCopy) + obj->destroy(); + + refresh(); + return selCopy.size(); +} + +// ============================================================================= +// +void MainWindow::buildObjList() +{ + if (not CurrentDocument()) + return; + + // Lock the selection while we do this so that refreshing the object list + // doesn't trigger selection updating so that the selection doesn't get lost + // while this is done. + g_isSelectionLocked = true; + + for (int i = 0; i < ui->objectList->count(); ++i) + delete ui->objectList->item (i); + + ui->objectList->clear(); + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + QString descr; + + switch (obj->type()) + { + case OBJ_Comment: + { + descr = obj.staticCast<LDComment>()->text(); + + // Remove leading whitespace + while (descr[0] == ' ') + descr.remove (0, 1); + + break; + } + + case OBJ_Empty: + break; // leave it empty + + case OBJ_Line: + case OBJ_Triangle: + case OBJ_Quad: + case OBJ_CondLine: + { + for (int i = 0; i < obj->numVertices(); ++i) + { + if (i != 0) + descr += ", "; + + descr += obj->vertex (i).toString (true); + } + break; + } + + case OBJ_Error: + { + descr = format ("ERROR: %1", obj->asText()); + break; + } + + case OBJ_Subfile: + { + LDSubfilePtr ref = obj.staticCast<LDSubfile>(); + + descr = format ("%1 %2, (", ref->fileInfo()->getDisplayName(), ref->position().toString (true)); + + for (int i = 0; i < 9; ++i) + descr += format ("%1%2", ref->transform()[i], (i != 8) ? " " : ""); + + descr += ')'; + break; + } + + case OBJ_BFC: + { + descr = LDBFC::StatementStrings[int (obj.staticCast<LDBFC>()->statement())]; + break; + } + + case OBJ_Overlay: + { + LDOverlayPtr ovl = obj.staticCast<LDOverlay>(); + descr = format ("[%1] %2 (%3, %4), %5 x %6", g_CameraNames[ovl->camera()], + Basename (ovl->fileName()), ovl->x(), ovl->y(), + ovl->width(), ovl->height()); + break; + } + + default: + { + descr = obj->typeName(); + break; + } + } + + QListWidgetItem* item = new QListWidgetItem (descr); + item->setIcon (GetIcon (obj->typeName())); + + // Use italic font if hidden + if (obj->isHidden()) + { + QFont font = item->font(); + font.setItalic (true); + item->setFont (font); + } + + // Color gibberish orange on red so it stands out. + if (obj->type() == OBJ_Error) + { + item->setBackground (QColor ("#AA0000")); + item->setForeground (QColor ("#FFAA00")); + } + elif (cfg::ColorizeObjectsList and obj->isColored() and + obj->color() != null and obj->color() != MainColor() and obj->color() != EdgeColor()) + { + // If the object isn't in the main or edge color, draw this + // list entry in said color. + item->setForeground (obj->color().faceColor()); + } + + obj->qObjListEntry = item; + ui->objectList->insertItem (ui->objectList->count(), item); + } + + g_isSelectionLocked = false; + updateSelection(); + scrollToSelection(); +} + +// ============================================================================= +// +void MainWindow::scrollToSelection() +{ + if (Selection().isEmpty()) + return; + + LDObjectPtr obj = Selection().last(); + ui->objectList->scrollToItem (obj->qObjListEntry); +} + +// ============================================================================= +// +void MainWindow::slot_selectionChanged() +{ + if (g_isSelectionLocked == true or CurrentDocument() == null) + return; + + LDObjectList priorSelection = Selection(); + + // Get the objects from the object list selection + CurrentDocument()->clearSelection(); + const QList<QListWidgetItem*> items = ui->objectList->selectedItems(); + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + for (QListWidgetItem* item : items) + { + if (item == obj->qObjListEntry) + { + obj->select(); + break; + } + } + } + + // The select() method calls may have selected additional items (i.e. invertnexts) + // Update it all now. + updateSelection(); + + // Update the GL renderer + LDObjectList compound = priorSelection + Selection(); + RemoveDuplicates (compound); + + for (LDObjectPtr obj : compound) + R()->compileObject (obj); + + R()->update(); +} + +// ============================================================================= +// +void MainWindow::slot_recentFile() +{ + QAction* qAct = static_cast<QAction*> (sender()); + OpenMainModel (qAct->text()); +} + +// ============================================================================= +// +void MainWindow::slot_quickColor() +{ + QToolButton* button = static_cast<QToolButton*> (sender()); + LDColor col = null; + + for (const LDQuickColor& entry : m_quickColors) + { + if (entry.toolButton() == button) + { + col = entry.color(); + break; + } + } + + if (col == null) + return; + + for (LDObjectPtr obj : Selection()) + { + if (not obj->isColored()) + continue; // uncolored object + + obj->setColor (col); + R()->compileObject (obj); + } + + endAction(); + refresh(); +} + +// ============================================================================= +// +int MainWindow::getInsertionPoint() +{ + // If we have a selection, put the item after it. + if (not Selection().isEmpty()) + return Selection().last()->lineNumber() + 1; + + // Otherwise place the object at the end. + return CurrentDocument()->getObjectCount(); +} + +// ============================================================================= +// +void MainWindow::doFullRefresh() +{ + buildObjList(); + m_renderer->hardRefresh(); +} + +// ============================================================================= +// +void MainWindow::refresh() +{ + buildObjList(); + m_renderer->update(); +} + +// ============================================================================= +// +void MainWindow::updateSelection() +{ + g_isSelectionLocked = true; + QItemSelection itemselect; + int top = -1; + int bottom = -1; + + for (LDObjectPtr obj : Selection()) + { + if (obj->qObjListEntry == null) + continue; + + int row = ui->objectList->row (obj->qObjListEntry); + + if (top == -1) + { + top = bottom = row; + } + else + { + if (row != bottom + 1) + { + itemselect.select (ui->objectList->model()->index (top, 0), + ui->objectList->model()->index (bottom, 0)); + top = -1; + } + + bottom = row; + } + } + + if (top != -1) + { + itemselect.select (ui->objectList->model()->index (top, 0), + ui->objectList->model()->index (bottom, 0)); + } + + ui->objectList->selectionModel()->select (itemselect, QItemSelectionModel::ClearAndSelect); + g_isSelectionLocked = false; +} + +// ============================================================================= +// +LDColor MainWindow::getSelectedColor() +{ + LDColor result; + + for (LDObjectPtr obj : Selection()) + { + if (not obj->isColored()) + continue; // doesn't use color + + if (result != null and obj->color() != result) + return null; // No consensus in object color + + if (result == null) + result = obj->color(); + } + + return result; +} + +// ============================================================================= +// +void MainWindow::closeEvent (QCloseEvent* ev) +{ + // Check whether it's safe to close all files. + if (not IsSafeToCloseAll()) + { + ev->ignore(); + return; + } + + // Save the toolbar set + cfg::HiddenToolbars.clear(); + + for (QToolBar* toolbar : findChildren<QToolBar*>()) + { + if (toolbar->isHidden()) + cfg::HiddenToolbars << toolbar->objectName(); + } + + // Save the configuration before leaving. + Config::Save(); + ev->accept(); +} + +// ============================================================================= +// +void MainWindow::spawnContextMenu (const QPoint pos) +{ + const bool single = (Selection().size() == 1); + LDObjectPtr singleObj = single ? Selection().first() : LDObjectPtr(); + + bool hasSubfiles = false; + + for (LDObjectPtr obj : Selection()) + { + if (obj->type() == OBJ_Subfile) + { + hasSubfiles = true; + break; + } + } + + QMenu* contextMenu = new QMenu; + + if (single and singleObj->type() != OBJ_Empty) + { + contextMenu->addAction (ui->actionEdit); + contextMenu->addSeparator(); + } + + contextMenu->addAction (ui->actionCut); + contextMenu->addAction (ui->actionCopy); + contextMenu->addAction (ui->actionPaste); + contextMenu->addAction (ui->actionDelete); + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetColor); + + if (single) + contextMenu->addAction (ui->actionEditRaw); + + contextMenu->addAction (ui->actionBorders); + contextMenu->addAction (ui->actionSetOverlay); + contextMenu->addAction (ui->actionClearOverlay); + + if (hasSubfiles) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionOpenSubfiles); + } + + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionModeSelect); + contextMenu->addAction (ui->actionModeDraw); + contextMenu->addAction (ui->actionModeCircle); + + if (not Selection().isEmpty()) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSubfileSelection); + } + + if (R()->camera() != EFreeCamera) + { + contextMenu->addSeparator(); + contextMenu->addAction (ui->actionSetDrawDepth); + } + + contextMenu->exec (pos); +} + +// ============================================================================= +// +void MainWindow::deleteByColor (LDColor color) +{ + LDObjectList objs; + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (not obj->isColored() or obj->color() != color) + continue; + + objs << obj; + } + + for (LDObjectPtr obj : objs) + obj->destroy(); +} + +// ============================================================================= +// +void MainWindow::updateEditModeActions() +{ + const EditModeType mode = R()->currentEditModeType(); + ui->actionModeSelect->setChecked (mode == EditModeType::Select); + ui->actionModeDraw->setChecked (mode == EditModeType::Draw); + ui->actionModeRectangle->setChecked (mode == EditModeType::Rectangle); + ui->actionModeCircle->setChecked (mode == EditModeType::Circle); + ui->actionModeMagicWand->setChecked (mode == EditModeType::MagicWand); + ui->actionModeLinePath->setChecked (mode == EditModeType::LinePath); +} + +// ============================================================================= +// +void MainWindow::slot_editObject (QListWidgetItem* listitem) +{ + for (LDObjectPtr it : CurrentDocument()->objects()) + { + if (it->qObjListEntry == listitem) + { + AddObjectDialog::staticDialog (it->type(), it); + break; + } + } +} + +// ============================================================================= +// +bool MainWindow::save (LDDocumentPtr doc, bool saveAs) +{ + QString path = doc->fullPath(); + int64 savesize; + + if (saveAs or path.isEmpty()) + { + QString name = doc->defaultName(); + + if (not doc->fullPath().isEmpty()) + name = doc->fullPath(); + elif (not doc->name().isEmpty()) + name = doc->name(); + + name.replace ("\\", "/"); + path = QFileDialog::getSaveFileName (g_win, tr ("Save As"), + name, tr ("LDraw files (*.dat *.ldr)")); + + if (path.isEmpty()) + { + // User didn't give a file name, abort. + return false; + } + } + + if (doc->save (path, &savesize)) + { + if (doc == CurrentDocument()) + updateTitle(); + + print ("Saved to %1 (%2)", path, MakePrettyFileSize (savesize)); + + // Add it to recent files + AddRecentFile (path); + return true; + } + + QString message = format (tr ("Failed to save to %1: %2"), path, strerror (errno)); + + // Tell the user the save failed, and give the option for saving as with it. + QMessageBox dlg (QMessageBox::Critical, tr ("Save Failure"), message, QMessageBox::Close, g_win); + + // Add a save-as button + QPushButton* saveAsBtn = new QPushButton (tr ("Save As")); + saveAsBtn->setIcon (GetIcon ("file-save-as")); + dlg.addButton (saveAsBtn, QMessageBox::ActionRole); + dlg.setDefaultButton (QMessageBox::Close); + dlg.exec(); + + if (dlg.clickedButton() == saveAsBtn) + return save (doc, true); // yay recursion! + + return false; +} + +void MainWindow::addMessage (QString msg) +{ + m_messageLog->addLine (msg); +} + +// ============================================================================ +void ObjectList::contextMenuEvent (QContextMenuEvent* ev) +{ + g_win->spawnContextMenu (ev->globalPos()); +} + +// ============================================================================= +// +QPixmap GetIcon (QString iconName) +{ + return (QPixmap (format (":/icons/%1.png", iconName))); +} + +// ============================================================================= +// +bool Confirm (const QString& message) +{ + return Confirm (MainWindow::tr ("Confirm"), message); +} + +// ============================================================================= +// +bool Confirm (const QString& title, const QString& message) +{ + return QMessageBox::question (g_win, title, message, + (QMessageBox::Yes | QMessageBox::No), QMessageBox::No) == QMessageBox::Yes; +} + +// ============================================================================= +// +void Critical (const QString& message) +{ + QMessageBox::critical (g_win, MainWindow::tr ("Error"), message, + (QMessageBox::Close), QMessageBox::Close); +} + +// ============================================================================= +// +QIcon MakeColorIcon (LDColor colinfo, const int size) +{ + // Create an image object and link a painter to it. + QImage img (size, size, QImage::Format_ARGB32); + QPainter paint (&img); + QColor col = colinfo.faceColor(); + + if (colinfo == MainColor()) + { + // Use the user preferences for main color here + col = cfg::MainColor; + col.setAlphaF (cfg::MainColorAlpha); + } + + // Paint the icon border + paint.fillRect (QRect (0, 0, size, size), colinfo.edgeColor()); + + // Paint the checkerboard background, visible with translucent icons + paint.drawPixmap (QRect (1, 1, size - 2, size - 2), GetIcon ("checkerboard"), QRect (0, 0, 8, 8)); + + // Paint the color above the checkerboard + paint.fillRect (QRect (1, 1, size - 2, size - 2), col); + return QIcon (QPixmap::fromImage (img)); +} + +// ============================================================================= +// +void MakeColorComboBox (QComboBox* box) +{ + std::map<LDColor, int> counts; + + for (LDObjectPtr obj : CurrentDocument()->objects()) + { + if (not obj->isColored() or obj->color() == null) + continue; + + if (counts.find (obj->color()) == counts.end()) + counts[obj->color()] = 1; + else + counts[obj->color()]++; + } + + box->clear(); + int row = 0; + + for (const auto& pair : counts) + { + QIcon ico = MakeColorIcon (pair.first, 16); + box->addItem (ico, format ("[%1] %2 (%3 object%4)", + pair.first, pair.first.name(), pair.second, Plural (pair.second))); + box->setItemData (row, pair.first.index()); + + ++row; + } +} + +// ============================================================================= +// +void MainWindow::updateDocumentList() +{ + m_isUpdatingTabs = true; + + while (m_tabBar->count() > 0) + m_tabBar->removeTab (0); + + for (LDDocumentPtr f : LDDocument::explicitDocuments()) + { + // Add an item to the list for this file and store the tab index + // in the document so we can find documents by tab index. + f->setTabIndex (m_tabBar->addTab ("")); + updateDocumentListItem (f); + } + + m_isUpdatingTabs = false; +} + +// ============================================================================= +// +void MainWindow::updateDocumentListItem (LDDocumentPtr doc) +{ + bool oldUpdatingTabs = m_isUpdatingTabs; + m_isUpdatingTabs = true; + + if (doc->tabIndex() == -1) + { + // We don't have a list item for this file, so the list either doesn't + // exist yet or is out of date. Build the list now. + updateDocumentList(); + return; + } + + // If this is the current file, it also needs to be the selected item on + // the list. + if (doc == CurrentDocument()) + m_tabBar->setCurrentIndex (doc->tabIndex()); + + m_tabBar->setTabText (doc->tabIndex(), doc->getDisplayName()); + + // If the document.has unsaved changes, draw a little icon next to it to mark that. + m_tabBar->setTabIcon (doc->tabIndex(), doc->hasUnsavedChanges() ? GetIcon ("file-save") : QIcon()); + m_tabBar->setTabData (doc->tabIndex(), doc->name()); + m_isUpdatingTabs = oldUpdatingTabs; +} + +// ============================================================================= +// +// A file is selected from the list of files on the left of the screen. Find out +// which file was picked and change to it. +// +void MainWindow::changeCurrentFile() +{ + if (m_isUpdatingTabs) + return; + + LDDocumentPtr f; + int tabIndex = m_tabBar->currentIndex(); + + // Find the file pointer of the item that was selected. + for (LDDocumentPtr it : LDDocument::explicitDocuments()) + { + if (it->tabIndex() == tabIndex) + { + f = it; + break; + } + } + + // If we picked the same file we're currently on, we don't need to do + // anything. + if (f == null or f == CurrentDocument()) + return; + + LDDocument::setCurrent (f); +} + +// ============================================================================= +// +void MainWindow::refreshObjectList() +{ +#if 0 + ui->objectList->clear(); + LDDocumentPtr f = getCurrentDocument(); + +for (LDObjectPtr obj : *f) + ui->objectList->addItem (obj->qObjListEntry); + +#endif + + buildObjList(); +} + +// ============================================================================= +// +void MainWindow::updateActions() +{ + if (CurrentDocument() != null and CurrentDocument()->history() != null) + { + History* his = CurrentDocument()->history(); + int pos = his->position(); + ui->actionUndo->setEnabled (pos != -1); + ui->actionRedo->setEnabled (pos < (long) his->getSize() - 1); + } + + ui->actionWireframe->setChecked (cfg::DrawWireframe); + ui->actionAxes->setChecked (cfg::DrawAxes); + ui->actionBFCView->setChecked (cfg::BFCRedGreenView); + ui->actionRandomColors->setChecked (cfg::RandomColors); + ui->actionDrawAngles->setChecked (cfg::DrawAngles); + ui->actionDrawSurfaces->setChecked (cfg::DrawSurfaces); + ui->actionDrawEdgeLines->setChecked (cfg::DrawEdgeLines); + ui->actionDrawConditionalLines->setChecked (cfg::DrawConditionalLines); +} + +// ============================================================================= +// +void MainWindow::updatePrimitives() +{ + PopulatePrimitives (ui->primitives); +} + +// ============================================================================= +// +void MainWindow::closeTab (int tabindex) +{ + LDDocumentPtr doc = FindDocument (m_tabBar->tabData (tabindex).toString()); + + if (doc == null) + return; + + doc->dismiss(); +} + +// ============================================================================= +// +void MainWindow::loadShortcuts (QSettings const* settings) +{ + for (QAction* act : findChildren<QAction*>()) + { + QKeySequence seq = settings->value ("shortcut_" + act->objectName(), act->shortcut()).value<QKeySequence>(); + act->setShortcut (seq); + } +} + +// ============================================================================= +// +void MainWindow::saveShortcuts (QSettings* settings) +{ + applyToActions ([&](QAction* act) + { + QString const key = "shortcut_" + act->objectName(); + + if (g_defaultShortcuts[act] != act->shortcut()) + settings->setValue (key, act->shortcut()); + else + settings->remove (key); + }); +} + +// ============================================================================= +// +void MainWindow::applyToActions (std::function<void(QAction*)> function) +{ + for (QAction* act : findChildren<QAction*>()) + { + if (not act->objectName().isEmpty()) + function (act); + } +} + +// ============================================================================= +// +QKeySequence MainWindow::defaultShortcut (QAction* act) // [static] +{ + return g_defaultShortcuts[act]; +} + +// ============================================================================= +// +bool MainWindow::ringToolHiRes() const +{ + return ui->ringToolHiRes->isChecked(); +} + +// ============================================================================= +// +int MainWindow::ringToolSegments() const +{ + return ui->ringToolSegments->value(); +} + +// ============================================================================= +// +void MainWindow::ringToolHiResClicked (bool checked) +{ + if (checked) + { + ui->ringToolSegments->setMaximum (HighResolution); + ui->ringToolSegments->setValue (ui->ringToolSegments->value() * 3); + } + else + { + ui->ringToolSegments->setValue (ui->ringToolSegments->value() / 3); + ui->ringToolSegments->setMaximum (LowResolution); + } +} + +// ============================================================================= +// +void MainWindow::circleToolSegmentsChanged() +{ + int numerator (ui->ringToolSegments->value()); + int denominator (ui->ringToolHiRes->isChecked() ? HighResolution : LowResolution); + Simplify (numerator, denominator); + ui->ringToolSegmentsLabel->setText (format ("%1 / %2", numerator, denominator)); +} + +// ============================================================================= +// +QImage GetImageFromScreencap (uchar* data, int w, int h) +{ + // GL and Qt formats have R and B swapped. Also, GL flips Y - correct it as well. + return QImage (data, w, h, QImage::Format_ARGB32).rgbSwapped().mirrored(); +} + +// ============================================================================= +// +LDQuickColor::LDQuickColor (LDColor color, QToolButton* toolButton) : + m_color (color), + m_toolButton (toolButton) {} + +// ============================================================================= +// +LDQuickColor LDQuickColor::getSeparator() +{ + return LDQuickColor (null, null); +} + +// ============================================================================= +// +bool LDQuickColor::isSeparator() const +{ + return color() == null; +} + +void PopulatePrimitives (QTreeWidget* tw, QString const& selectByDefault) +{ + tw->clear(); + + for (PrimitiveCategory* cat : g_PrimitiveCategories) + { + SubfileListItem* parentItem = new SubfileListItem (tw, null); + parentItem->setText (0, cat->name()); + QList<QTreeWidgetItem*> subfileItems; + + for (Primitive& prim : cat->prims) + { + SubfileListItem* item = new SubfileListItem (parentItem, &prim); + item->setText (0, format ("%1 - %2", prim.name, prim.title)); + subfileItems << item; + + // If this primitive is the one the current object points to, + // select it by default + if (selectByDefault == prim.name) + tw->setCurrentItem (item); + } + + tw->addTopLevelItem (parentItem); + } +}