// Copyright (C) 2016 Jochen Becher // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "modeleditor.h" #include "actionhandler.h" #include "diagramsviewmanager.h" #include "dragtool.h" #include "editordiagramview.h" #include "elementtasks.h" #include "extdocumentcontroller.h" #include "extpropertiesmview.h" #include "modeldocument.h" #include "modeleditor_constants.h" #include "modeleditor_plugin.h" #include "modeleditortr.h" #include "modelsmanager.h" #include "uicontroller.h" #include "qmt/controller/undocontroller.h" #include "qmt/diagram/dpackage.h" #include "qmt/diagram_controller/diagramcontroller.h" #include "qmt/diagram_controller/dcontainer.h" #include "qmt/diagram_controller/dreferences.h" #include "qmt/diagram_controller/dselection.h" #include "qmt/diagram_scene/diagramscenemodel.h" #include "qmt/diagram_ui/diagram_mime_types.h" #include "qmt/diagram_ui/diagramsmanager.h" #include "qmt/model/mpackage.h" #include "qmt/model/mclass.h" #include "qmt/model/mcomponent.h" #include "qmt/model/mcanvasdiagram.h" #include "qmt/model_controller/modelcontroller.h" #include "qmt/model_controller/mcontainer.h" #include "qmt/model_controller/mreferences.h" #include "qmt/model_controller/mselection.h" #include "qmt/model_ui/treemodel.h" #include "qmt/model_ui/treemodelmanager.h" #include "qmt/model_widgets_ui/modeltreefilter.h" #include "qmt/model_widgets_ui/modeltreeview.h" #include "qmt/model_widgets_ui/propertiesview.h" #include "qmt/stereotype/shapepaintvisitor.h" #include "qmt/stereotype/stereotypecontroller.h" #include "qmt/stereotype/stereotypeicon.h" #include "qmt/stereotype/toolbar.h" #include "qmt/style/style.h" #include "qmt/style/stylecontroller.h" #include "qmt/tasks/diagramscenecontroller.h" #include "qmt/tasks/finddiagramvisitor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace Utils; namespace ModelEditor { namespace Internal { static const char PROPERTYNAME_TOOLBARID[] = "ToolbarId"; static const double ZOOM_FACTOR = 1.05; class ModelEditor::ModelEditorPrivate { public: UiController *uiController = nullptr; ActionHandler *actionHandler = nullptr; ModelDocument *document = nullptr; qmt::PropertiesView *propertiesView = nullptr; MiniSplitter *rightSplitter = nullptr; QWidget *leftGroup = nullptr; QHBoxLayout *leftGroupLayout = nullptr; QToolBox *leftToolBox = nullptr; QStackedWidget *diagramStack = nullptr; EditorDiagramView *diagramView = nullptr; QLabel *noDiagramLabel = nullptr; DiagramsViewManager *diagramsViewManager = nullptr; QWidget *rightPanel = nullptr; QVBoxLayout *rightPanelLayout = nullptr; QScrollArea *viewFilterScrollArea = nullptr; qmt::ModelTreeFilter *viewFilterWidget = nullptr; MiniSplitter *rightHorizSplitter = nullptr; qmt::ModelTreeView *modelTreeView = nullptr; qmt::TreeModelManager *modelTreeViewServant = nullptr; QScrollArea *propertiesScrollArea = nullptr; QWidget *propertiesGroupWidget = nullptr; QWidget *toolbar = nullptr; QComboBox *diagramSelector = nullptr; SelectedArea selectedArea = SelectedArea::Nothing; QString lastExportDirPath; QAction *syncBrowserWithDiagramAction = nullptr; QAction *syncDiagramWithBrowserAction = nullptr; QAction *syncEachOtherAction = nullptr; int formerViewFilterHeight = 0; }; ModelEditor::ModelEditor(UiController *uiController, ActionHandler *actionHandler) : d(new ModelEditorPrivate) { setContext(Context(Constants::MODEL_EDITOR_ID)); d->uiController = uiController; d->actionHandler = actionHandler; d->document = new ModelDocument(this); connect(d->document, &ModelDocument::contentSet, this, &ModelEditor::onContentSet); init(); } ModelEditor::~ModelEditor() { closeCurrentDiagram(false); delete d->toolbar; delete d; } IDocument *ModelEditor::document() const { return d->document; } QWidget *ModelEditor::toolBar() { return d->toolbar; } QByteArray ModelEditor::saveState() const { return saveState(currentDiagram()); } void ModelEditor::restoreState(const QByteArray &state) { QDataStream stream(state); int version = 0; stream >> version; if (version >= 1) { qmt::Uid uid; stream >> uid; if (version >= 2) { bool sync = false; bool syncBrowserWithDiagram = false; bool syncDiagramWithBrowser = false; bool syncEachOther = false; stream >> sync >> syncBrowserWithDiagram >> syncDiagramWithBrowser >> syncEachOther; d->actionHandler->synchronizeBrowserAction()->setChecked(sync); d->syncBrowserWithDiagramAction->setChecked( syncBrowserWithDiagram || (!syncDiagramWithBrowser && !syncEachOther)); d->syncDiagramWithBrowserAction->setChecked(syncDiagramWithBrowser); d->syncEachOtherAction->setChecked(syncEachOther); } if (uid.isValid()) { qmt::MDiagram *diagram = d->document->documentController()->modelController()->findObject(uid); if (diagram) { openDiagram(diagram, false); } } } } void ModelEditor::init() { // create and configure properties view d->propertiesView = new qmt::PropertiesView(this); // create and configure editor ui d->rightSplitter = new MiniSplitter; connect(d->rightSplitter, &QSplitter::splitterMoved, this, &ModelEditor::onRightSplitterMoved); connect(d->uiController, &UiController::rightSplitterChanged, this, &ModelEditor::onRightSplitterChanged); d->leftGroup = new QWidget(d->rightSplitter); d->leftGroupLayout = new QHBoxLayout(d->leftGroup); d->leftGroupLayout->setContentsMargins(0, 0, 0, 0); d->leftGroupLayout->setSpacing(0); d->leftToolBox = new QToolBox(d->leftGroup); // Windows style does not truncate the tab label to a very small width (GTK+ does) static QStyle *windowsStyle = QStyleFactory::create("Windows"); if (windowsStyle) d->leftToolBox->setStyle(windowsStyle); // TODO improve this (and the diagram colors) for use with dark theme d->leftToolBox->setStyleSheet( QLatin1String("QToolBox::tab {" " margin-left: 2px;" " background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1," " stop: 0 #E1E1E1, stop: 0.4 #DDDDDD," " stop: 0.5 #D8D8D8, stop: 1.0 #D3D3D3);" " color: #606060;" "}" "" "QToolBox::tab:selected {" " font: italic;" " color: black;" "}")); QFont font = d->leftToolBox->font(); font.setPointSizeF(font.pointSizeF() * 0.8); d->leftToolBox->setFont(font); connect(d->leftToolBox, &QToolBox::currentChanged, this, &ModelEditor::onToolbarSelectionChanged); d->diagramStack = new QStackedWidget(d->leftGroup); d->diagramView = new EditorDiagramView(d->diagramStack); d->diagramStack->addWidget(d->diagramView); d->noDiagramLabel = new QLabel(d->diagramStack); const QString placeholderText = Tr::tr("" "
" "
Open a diagram
" "
" "
" "
• Double-click on diagram in model tree
" "
• Select \"Open Diagram\" from package's context menu in model tree
" "
" "
" ""); d->noDiagramLabel->setText(placeholderText); d->diagramStack->addWidget(d->noDiagramLabel); d->diagramStack->setCurrentWidget(d->noDiagramLabel); d->leftGroupLayout->addWidget(d->leftToolBox, 0); auto frame = new QFrame(d->leftGroup); frame->setFrameShape(QFrame::VLine); d->leftGroupLayout->addWidget(frame, 0); d->leftGroupLayout->addWidget(d->diagramStack, 1); d->rightPanel = new QWidget(d->rightSplitter); d->rightPanelLayout = new QVBoxLayout(d->rightPanel); d->rightPanelLayout->setContentsMargins(0, 0, 0, 0); d->rightPanelLayout->setSpacing(0); d->viewFilterScrollArea = new QScrollArea(d->rightPanel); d->viewFilterScrollArea->setFrameShape(QFrame::NoFrame); d->viewFilterScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->viewFilterScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->viewFilterScrollArea->setWidgetResizable(true); d->viewFilterScrollArea->setVisible(false); d->viewFilterWidget = new qmt::ModelTreeFilter(d->viewFilterScrollArea); d->viewFilterScrollArea->setWidget(d->viewFilterWidget); d->rightHorizSplitter = new MiniSplitter(d->rightPanel); d->rightHorizSplitter->setOrientation(Qt::Vertical); connect(d->rightHorizSplitter, &QSplitter::splitterMoved, this, &ModelEditor::onRightHorizSplitterMoved); connect(d->uiController, &UiController::rightHorizSplitterChanged, this, &ModelEditor::onRightHorizSplitterChanged); d->modelTreeView = new qmt::ModelTreeView(d->rightHorizSplitter); d->modelTreeView->setFrameShape(QFrame::NoFrame); connect(d->viewFilterWidget, &qmt::ModelTreeFilter::viewDataChanged, d->modelTreeView, &qmt::ModelTreeView::setModelTreeViewData); connect(d->viewFilterWidget, &qmt::ModelTreeFilter::filterDataChanged, d->modelTreeView, &qmt::ModelTreeView::setModelTreeFilterData); d->modelTreeViewServant = new qmt::TreeModelManager(this); d->modelTreeViewServant->setModelTreeView(d->modelTreeView); d->propertiesScrollArea = new QScrollArea(d->rightHorizSplitter); d->propertiesScrollArea->setFrameShape(QFrame::NoFrame); d->propertiesScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->propertiesScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->propertiesScrollArea->setWidgetResizable(true); d->rightHorizSplitter->insertWidget(0, d->modelTreeView); d->rightHorizSplitter->insertWidget(1, d->propertiesScrollArea); d->rightHorizSplitter->setStretchFactor(0, 2); // magic stretch factors for equal sizing d->rightHorizSplitter->setStretchFactor(1, 3); d->rightPanelLayout->addWidget(d->viewFilterScrollArea, 0); d->rightPanelLayout->addWidget(d->rightHorizSplitter, 1); d->rightSplitter->insertWidget(0, d->leftGroup); d->rightSplitter->insertWidget(1, d->rightPanel); d->rightSplitter->setStretchFactor(0, 1); d->rightSplitter->setStretchFactor(1, 0); setWidget(d->rightSplitter); // restore splitter sizes after any stretch factor has been set as fallback if (d->uiController->hasRightSplitterState()) d->rightSplitter->restoreState(d->uiController->rightSplitterState()); if (d->uiController->hasRightHorizSplitterState()) d->rightHorizSplitter->restoreState(d->uiController->rightHorizSplitterState()); // create and configure toolbar d->toolbar = new QWidget(); auto toolbarLayout = new QHBoxLayout(d->toolbar); toolbarLayout->setContentsMargins(0, 0, 0, 0); toolbarLayout->setSpacing(0); auto openParentButton = Command::toolButtonWithAppendedShortcut(d->actionHandler->openParentDiagramAction(), Constants::OPEN_PARENT_DIAGRAM); toolbarLayout->addWidget(openParentButton); d->diagramSelector = new QComboBox(d->toolbar); connect(d->diagramSelector, &QComboBox::activated, this, &ModelEditor::onDiagramSelectorSelected); toolbarLayout->addWidget(d->diagramSelector, 1); toolbarLayout->addStretch(1); toolbarLayout->addWidget(createToolbarCommandButton( Core::Constants::ZOOM_RESET, [this] { resetZoom(); }, d->toolbar)); toolbarLayout->addWidget(createToolbarCommandButton( Core::Constants::ZOOM_IN, [this] { zoomIn(); }, d->toolbar)); toolbarLayout->addWidget(createToolbarCommandButton( Core::Constants::ZOOM_OUT, [this] { zoomOut(); }, d->toolbar)); toolbarLayout->addWidget(createToolbarCommandButton(Constants::ACTION_ADD_PACKAGE, [this] { onAddPackage(); }, d->toolbar)); toolbarLayout->addWidget(createToolbarCommandButton(Constants::ACTION_ADD_COMPONENT, [this] { onAddComponent(); }, d->toolbar)); toolbarLayout->addWidget(createToolbarCommandButton(Constants::ACTION_ADD_CLASS, [this] { onAddClass(); }, d->toolbar)); toolbarLayout->addWidget(createToolbarCommandButton(Constants::ACTION_ADD_CANVAS_DIAGRAM, [this] { onAddCanvasDiagram(); }, d->toolbar)); toolbarLayout->addSpacing(20); auto toggleViewFilterButton = Command::toolButtonWithAppendedShortcut(d->actionHandler->toggleViewFilterAction(), Constants::ACTION_TOGGLE_VIEWFILTER); toolbarLayout->addWidget(toggleViewFilterButton); auto syncToggleButton = Command::toolButtonWithAppendedShortcut(d->actionHandler->synchronizeBrowserAction(), Constants::ACTION_SYNC_BROWSER); QMenu *syncMenu = new QMenu(syncToggleButton); QActionGroup *syncGroup = new QActionGroup(syncMenu); d->syncBrowserWithDiagramAction = syncMenu->addAction(Tr::tr("Synchronize Structure with Diagram")); d->syncBrowserWithDiagramAction->setCheckable(true); d->syncBrowserWithDiagramAction->setActionGroup(syncGroup); d->syncDiagramWithBrowserAction = syncMenu->addAction(Tr::tr("Synchronize Diagram with Structure")); d->syncDiagramWithBrowserAction->setCheckable(true); d->syncDiagramWithBrowserAction->setActionGroup(syncGroup); d->syncEachOtherAction = syncMenu->addAction(Tr::tr("Keep Synchronized")); d->syncEachOtherAction->setCheckable(true); d->syncEachOtherAction->setActionGroup(syncGroup); syncToggleButton->setMenu(syncMenu); d->syncBrowserWithDiagramAction->setChecked(true); toolbarLayout->addWidget(syncToggleButton); } void ModelEditor::initDocument() { if (d->diagramsViewManager) return; initToolbars(); ExtDocumentController *documentController = d->document->documentController(); d->diagramView->setPxNodeController(documentController->pxNodeController()); d->diagramsViewManager = new DiagramsViewManager(this); connect(d->diagramsViewManager, &DiagramsViewManager::openNewDiagram, this, &ModelEditor::showDiagram); connect(d->diagramsViewManager, &DiagramsViewManager::closeOpenDiagram, this, &ModelEditor::closeDiagram); connect(d->diagramsViewManager, &DiagramsViewManager::closeAllOpenDiagrams, this, &ModelEditor::closeAllDiagrams); documentController->diagramsManager()->setDiagramsView(d->diagramsViewManager); d->propertiesView->setDiagramController(documentController->diagramController()); d->propertiesView->setModelController(documentController->modelController()); d->propertiesView->setStereotypeController(documentController->stereotypeController()); d->propertiesView->setStyleController(documentController->styleController()); d->propertiesView->setMViewFactory([=](qmt::PropertiesView *view) { auto extView = new ExtPropertiesMView(view); extView->setProjectController(documentController->projectController()); return extView; }); d->modelTreeView->setTreeModel(documentController->sortedTreeModel()); d->modelTreeView->setElementTasks(documentController->elementTasks()); d->modelTreeViewServant->setTreeModel(documentController->treeModel()); connect(ModelEditorPlugin::modelsManager(), &ModelsManager::diagramClipboardChanged, this, &ModelEditor::onDiagramClipboardChanged, Qt::QueuedConnection); connect(documentController->undoController()->undoStack(), &QUndoStack::canUndoChanged, this, &ModelEditor::onCanUndoChanged, Qt::QueuedConnection); connect(documentController->undoController()->undoStack(), &QUndoStack::canRedoChanged, this, &ModelEditor::onCanRedoChanged, Qt::QueuedConnection); connect(documentController->treeModel(), &qmt::TreeModel::modelReset, this, &ModelEditor::onTreeModelReset, Qt::QueuedConnection); connect(documentController->diagramController(), &qmt::DiagramController::modified, this, &ModelEditor::onDiagramModified, Qt::QueuedConnection); connect(documentController->diagramsManager(), &qmt::DiagramsManager::diagramSelectionChanged, this, &ModelEditor::onDiagramSelectionChanged, Qt::QueuedConnection); connect(documentController->diagramsManager(), &qmt::DiagramsManager::diagramActivated, this, &ModelEditor::onDiagramActivated, Qt::QueuedConnection); connect(documentController->diagramSceneController(), &qmt::DiagramSceneController::newElementCreated, this, &ModelEditor::onNewElementCreated, Qt::QueuedConnection); connect(EditorManager::instance(), &EditorManager::currentEditorChanged, this, &ModelEditor::onCurrentEditorChanged, Qt::QueuedConnection); connect(d->diagramView, &EditorDiagramView::zoomIn, this, &ModelEditor::zoomInAtPos); connect(d->diagramView, &EditorDiagramView::zoomOut, this, &ModelEditor::zoomOutAtPos); connect(d->modelTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModelEditor::onTreeViewSelectionChanged, Qt::QueuedConnection); connect(d->modelTreeView, &qmt::ModelTreeView::treeViewActivated, this, &ModelEditor::onTreeViewActivated, Qt::QueuedConnection); connect(d->modelTreeView, &QAbstractItemView::doubleClicked, this, &ModelEditor::onTreeViewDoubleClicked, Qt::QueuedConnection); connect(documentController->modelController(), &qmt::ModelController::endMoveObject, this, &ModelEditor::updateDiagramSelector); connect(documentController->modelController(), &qmt::ModelController::endRemoveObject, this, &ModelEditor::updateDiagramSelector); connect(documentController->modelController(), &qmt::ModelController::endResetModel, this, &ModelEditor::updateDiagramSelector); connect(documentController->modelController(), &qmt::ModelController::endUpdateObject, this, &ModelEditor::updateDiagramSelector); updateSelectedArea(SelectedArea::Nothing); } qmt::MDiagram *ModelEditor::currentDiagram() const { if (!d->diagramView->diagramSceneModel()) return nullptr; return d->diagramView->diagramSceneModel()->diagram(); } void ModelEditor::showDiagram(qmt::MDiagram *diagram) { openDiagram(diagram, true); } void ModelEditor::undo() { d->document->documentController()->undoController()->undoStack()->undo(); } void ModelEditor::redo() { d->document->documentController()->undoController()->undoStack()->redo(); } void ModelEditor::cut() { ExtDocumentController *documentController = d->document->documentController(); ModelsManager *modelsManager = ModelEditorPlugin::modelsManager(); switch (d->selectedArea) { case SelectedArea::Nothing: break; case SelectedArea::Diagram: setDiagramClipboard(documentController->cutFromDiagram(currentDiagram())); break; case SelectedArea::TreeView: modelsManager->setModelClipboard(documentController, documentController->cutFromModel(d->modelTreeViewServant->selectedObjects())); break; } } void ModelEditor::copy() { ExtDocumentController *documentController = d->document->documentController(); ModelsManager *modelsManager = ModelEditorPlugin::modelsManager(); switch (d->selectedArea) { case SelectedArea::Nothing: break; case SelectedArea::Diagram: if (documentController->hasDiagramSelection(currentDiagram())) setDiagramClipboard(documentController->copyFromDiagram(currentDiagram())); else documentController->copyDiagram(currentDiagram()); break; case SelectedArea::TreeView: modelsManager->setModelClipboard(documentController, documentController->copyFromModel(d->modelTreeViewServant->selectedObjects())); break; } } void ModelEditor::paste() { ExtDocumentController *documentController = d->document->documentController(); ModelsManager *modelsManager = ModelEditorPlugin::modelsManager(); switch (d->selectedArea) { case SelectedArea::Nothing: break; case SelectedArea::Diagram: // on cut/copy diagram and model elements were copied. documentController->pasteIntoModel(currentDiagram(), modelsManager->modelClipboard(), qmt::ModelController::PasteOnlyNewElements); documentController->pasteIntoDiagram(currentDiagram(), modelsManager->diagramClipboard()); break; case SelectedArea::TreeView: documentController->pasteIntoModel(d->modelTreeViewServant->selectedObject(), modelsManager->modelClipboard(), documentController == modelsManager->modelClipboardDocumentController() ? qmt::ModelController::PasteAlwaysWithNewKeys : qmt::ModelController::PasteAlwaysAndKeepKeys); break; } } void ModelEditor::removeSelectedElements() { switch (d->selectedArea) { case SelectedArea::Nothing: break; case SelectedArea::Diagram: d->document->documentController()->removeFromDiagram(currentDiagram()); break; case SelectedArea::TreeView: break; } } void ModelEditor::deleteSelectedElements() { ExtDocumentController *documentController = d->document->documentController(); switch (d->selectedArea) { case SelectedArea::Nothing: break; case SelectedArea::Diagram: documentController->deleteFromDiagram(currentDiagram()); break; case SelectedArea::TreeView: documentController->deleteFromModel(d->modelTreeViewServant->selectedObjects()); break; } } void ModelEditor::selectAll() { d->document->documentController()->selectAllOnDiagram(currentDiagram()); } void ModelEditor::openParentDiagram() { qmt::MDiagram *diagram = currentDiagram(); if (diagram) d->document->documentController()->elementTasks()->openParentDiagram(diagram); } void ModelEditor::editProperties() { d->propertiesView->editSelectedElement(); } void ModelEditor::editSelectedItem() { onEditSelectedElement(); } void ModelEditor::exportDiagram() { exportToImage(/*selectedElements=*/false); } void ModelEditor::exportSelectedElements() { exportToImage(/*selectedElements=*/true); } void ModelEditor::exportToImage(bool selectedElements) { qmt::MDiagram *diagram = currentDiagram(); if (diagram) { if (d->lastExportDirPath.isEmpty()) d->lastExportDirPath = d->document->filePath().toFileInfo().canonicalPath(); QString filter = Tr::tr("Images (*.png *.jpeg *.jpg *.tif *.tiff);;PDF (*.pdf)"); #ifndef QT_NO_SVG filter += Tr::tr(";;SVG (*.svg)"); #endif // QT_NO_SVG QString fileName = FileUtils::getSaveFilePath( nullptr, selectedElements ? Tr::tr("Export Selected Elements") : Tr::tr("Export Diagram"), FilePath::fromString(d->lastExportDirPath), filter).toFSPathString(); if (!fileName.isEmpty()) { qmt::DocumentController *documentController = d->document->documentController(); qmt::DiagramSceneModel *sceneModel = documentController->diagramsManager()->diagramSceneModel(diagram); bool success = false; QString suffix = QFileInfo(fileName).suffix().toLower(); // TODO use QFileDialog::selectedNameFilter() as fallback if no suffix is given if (suffix.isEmpty()) { suffix = "png"; fileName += ".png"; } if (suffix == "pdf") success = sceneModel->exportPdf(fileName, selectedElements); #ifndef QT_NO_SVG else if (suffix == "svg") success = sceneModel->exportSvg(fileName, selectedElements); #endif // QT_NO_SVG else success = sceneModel->exportImage(fileName, selectedElements); if (success) d->lastExportDirPath = QFileInfo(fileName).canonicalPath(); else if (selectedElements) QMessageBox::critical(ICore::dialogParent(), Tr::tr("Exporting Selected Elements Failed"), Tr::tr("Exporting the selected elements of the current " "diagram into file
\"%1\"
failed.") .arg(fileName)); else QMessageBox::critical(ICore::dialogParent(), Tr::tr("Exporting Diagram Failed"), Tr::tr("Exporting the diagram into file
\"%1\"
failed.") .arg(fileName)); } } } void ModelEditor::zoomIn() { QTransform transform = d->diagramView->transform(); transform.scale(ZOOM_FACTOR, ZOOM_FACTOR); d->diagramView->setTransform(transform); showZoomIndicator(); } void ModelEditor::zoomOut() { QTransform transform = d->diagramView->transform(); transform.scale(1.0 / ZOOM_FACTOR, 1.0 / ZOOM_FACTOR); d->diagramView->setTransform(transform); showZoomIndicator(); } void ModelEditor::zoomInAtPos(const QPoint &pos) { zoomAtPos(pos, ZOOM_FACTOR); } void ModelEditor::zoomOutAtPos(const QPoint &pos) { zoomAtPos(pos, 1.0 / ZOOM_FACTOR); } void ModelEditor::zoomAtPos(const QPoint &pos, double scale) { QPointF scenePos = d->diagramView->mapToScene(pos); QTransform transform = d->diagramView->transform(); transform.scale(scale, scale); d->diagramView->setTransform(transform); QPoint scaledPos = d->diagramView->mapFromScene(scenePos); QPoint delta = pos - scaledPos; QScrollBar *hBar = d->diagramView->horizontalScrollBar(); if (hBar) hBar->setValue(hBar->value() - delta.x()); QScrollBar *vBar = d->diagramView->verticalScrollBar(); if (vBar) vBar->setValue(vBar->value() - delta.y()); showZoomIndicator(); } void ModelEditor::resetZoom() { d->diagramView->setTransform(QTransform()); showZoomIndicator(); } void ModelEditor::showModelTreeFilter(bool visible) { d->viewFilterScrollArea->setVisible(visible); } void ModelEditor::toggleModelTreeFilter() { d->viewFilterScrollArea->setVisible(!d->viewFilterScrollArea->isVisible()); } qmt::MPackage *ModelEditor::guessSelectedPackage() const { qmt::MPackage *package = nullptr; switch (d->selectedArea) { case SelectedArea::Nothing: package = d->modelTreeViewServant->selectedPackage(); break; case SelectedArea::Diagram: { qmt::DocumentController *documentController = d->document->documentController(); qmt::DiagramsManager *diagramsManager = documentController->diagramsManager(); qmt::MDiagram *diagram = currentDiagram(); qmt::DSelection selection = diagramsManager->diagramSceneModel(diagram)->selectedElements(); if (selection.indices().size() == 1) { qmt::DPackage *diagramElement = documentController->diagramController()->findElement(selection.indices().at(0).elementKey(), diagram); if (diagramElement) package = documentController->modelController()->findObject(diagramElement->modelUid()); } break; } case SelectedArea::TreeView: package = d->modelTreeViewServant->selectedPackage(); break; } return package; } void ModelEditor::updateSelectedArea(SelectedArea selectedArea) { d->selectedArea = selectedArea; qmt::DocumentController *documentController = d->document->documentController(); ModelsManager *modelsManager = ModelEditorPlugin::modelsManager(); bool canCutCopyDelete = false; bool canRemove = false; bool canPaste = false; bool canSelectAll = false; bool canCopyDiagram = false; bool canOpenParentDiagram = false; bool canExportDiagram = false; bool canExportSelectedElements = false; QList propertiesModelElements; QList propertiesDiagramElements; qmt::MDiagram *propertiesDiagram = nullptr; qmt::MDiagram *activeDiagram = currentDiagram(); switch (d->selectedArea) { case SelectedArea::Nothing: canSelectAll = activeDiagram && !activeDiagram->diagramElements().isEmpty(); canExportDiagram = activeDiagram != nullptr; break; case SelectedArea::Diagram: { if (activeDiagram) { bool hasSelection = documentController->diagramsManager()->diagramSceneModel(activeDiagram)->hasSelection(); canCutCopyDelete = hasSelection; canRemove = hasSelection; canPaste = !modelsManager->isDiagramClipboardEmpty(); canSelectAll = !activeDiagram->diagramElements().isEmpty(); canCopyDiagram = !hasSelection; canExportDiagram = true; canExportSelectedElements = hasSelection; if (hasSelection) { qmt::DSelection selection = documentController->diagramsManager()->diagramSceneModel(activeDiagram)->selectedElements(); if (!selection.isEmpty()) { const QList indexes = selection.indices(); for (qmt::DSelection::Index index : indexes) { qmt::DElement *diagramElement = documentController->diagramController()->findElement(index.elementKey(), activeDiagram); if (diagramElement) propertiesDiagramElements.append(diagramElement); } if (!propertiesDiagramElements.isEmpty()) propertiesDiagram = activeDiagram; } } } break; } case SelectedArea::TreeView: { bool hasSelection = !d->modelTreeViewServant->selectedObjects().isEmpty(); bool hasSingleSelection = d->modelTreeViewServant->selectedObjects().indices().size() == 1; canCutCopyDelete = hasSelection && !d->modelTreeViewServant->isRootPackageSelected(); canPaste = hasSingleSelection && !modelsManager->isModelClipboardEmpty(); canSelectAll = activeDiagram && !activeDiagram->diagramElements().isEmpty(); canExportDiagram = activeDiagram != nullptr; const QModelIndexList indexes = d->modelTreeView->selectedSourceModelIndexes(); if (!indexes.isEmpty()) { for (const QModelIndex &propertiesIndex : indexes) { if (propertiesIndex.isValid()) { qmt::MElement *modelElement = documentController->treeModel()->element(propertiesIndex); if (modelElement) propertiesModelElements.append(modelElement); } } } break; } } canOpenParentDiagram = d->document->documentController()->elementTasks()->hasParentDiagram(currentDiagram()); d->actionHandler->cutAction()->setEnabled(canCutCopyDelete); d->actionHandler->copyAction()->setEnabled(canCutCopyDelete || canCopyDiagram); d->actionHandler->pasteAction()->setEnabled(canPaste); d->actionHandler->removeAction()->setEnabled(canRemove); d->actionHandler->deleteAction()->setEnabled(canCutCopyDelete); d->actionHandler->selectAllAction()->setEnabled(canSelectAll); d->actionHandler->openParentDiagramAction()->setEnabled(canOpenParentDiagram); d->actionHandler->exportDiagramAction()->setEnabled(canExportDiagram); d->actionHandler->exportSelectedElementsAction()->setEnabled(canExportSelectedElements); if (!propertiesModelElements.isEmpty()) showProperties(propertiesModelElements); else if (!propertiesDiagramElements.isEmpty()) showProperties(propertiesDiagram, propertiesDiagramElements); else clearProperties(); } void ModelEditor::showProperties(const QList &modelElements) { if (modelElements != d->propertiesView->selectedModelElements()) { clearProperties(); if (modelElements.size() > 0) { d->propertiesView->setSelectedModelElements(modelElements); d->propertiesGroupWidget = d->propertiesView->widget(); d->propertiesScrollArea->setWidget(d->propertiesGroupWidget); } } } void ModelEditor::showProperties(qmt::MDiagram *diagram, const QList &diagramElements) { if (diagram != d->propertiesView->selectedDiagram() || diagramElements != d->propertiesView->selectedDiagramElements()) { clearProperties(); if (diagram && diagramElements.size() > 0) { d->propertiesView->setSelectedDiagramElements(diagramElements, diagram); d->propertiesGroupWidget = d->propertiesView->widget(); d->propertiesScrollArea->setWidget(d->propertiesGroupWidget); } } } void ModelEditor::clearProperties() { d->propertiesView->clearSelection(); if (d->propertiesGroupWidget) { QWidget *scrollWidget = d->propertiesScrollArea->takeWidget(); Q_UNUSED(scrollWidget) // avoid warning in release mode QMT_CHECK(scrollWidget == d->propertiesGroupWidget); d->propertiesGroupWidget->deleteLater(); d->propertiesGroupWidget = nullptr; } } void ModelEditor::expandModelTreeToDepth(int depth) { d->modelTreeView->expandToDepth(depth); } QToolButton *ModelEditor::createToolbarCommandButton(const Utils::Id &id, const std::function &slot, QWidget *parent) { Command *command = ActionManager::command(id); QTC_CHECK(command); const QString text = command ? command->description() : QString(); auto action = new QAction(text, this); action->setIcon(command ? command->action()->icon() : QIcon()); auto button = Command::toolButtonWithAppendedShortcut(action, command); button->setParent(parent); connect(button, &QToolButton::clicked, this, slot); return button; } /*! Tries to change the \a button icon to the icon specified by \a name from the current theme. Returns \c true if icon is updated, \c false otherwise. */ bool ModelEditor::updateButtonIconByTheme(QAbstractButton *button, const QString &name) { QMT_ASSERT(button, return false); QMT_ASSERT(!name.isEmpty(), return false); if (QIcon::hasThemeIcon(name)) { button->setIcon(QIcon::fromTheme(name)); return true; } return false; } void ModelEditor::showZoomIndicator() { int scale = int(d->diagramView->transform().map(QPointF(100, 100)).x() + 0.5); Utils::FadingIndicator::showText(d->diagramStack, Tr::tr("Zoom: %1%").arg(scale), Utils::FadingIndicator::SmallText); } void ModelEditor::onAddPackage() { ExtDocumentController *documentController = d->document->documentController(); qmt::MPackage *package = documentController->createNewPackage(d->modelTreeViewServant->selectedPackage()); d->modelTreeView->selectFromSourceModelIndex(documentController->treeModel()->indexOf(package)); QTimer::singleShot(0, this, [this] { onEditSelectedElement(); }); } void ModelEditor::onAddComponent() { ExtDocumentController *documentController = d->document->documentController(); qmt::MComponent *component = documentController->createNewComponent(d->modelTreeViewServant->selectedPackage()); d->modelTreeView->selectFromSourceModelIndex(documentController->treeModel()->indexOf(component)); QTimer::singleShot(0, this, [this] { onEditSelectedElement(); }); } void ModelEditor::onAddClass() { ExtDocumentController *documentController = d->document->documentController(); qmt::MClass *klass = documentController->createNewClass(d->modelTreeViewServant->selectedPackage()); d->modelTreeView->selectFromSourceModelIndex(documentController->treeModel()->indexOf(klass)); QTimer::singleShot(0, this, [this] { onEditSelectedElement(); }); } void ModelEditor::onAddCanvasDiagram() { ExtDocumentController *documentController = d->document->documentController(); qmt::MDiagram *diagram = documentController->createNewCanvasDiagram(d->modelTreeViewServant->selectedPackage()); d->modelTreeView->selectFromSourceModelIndex(documentController->treeModel()->indexOf(diagram)); QTimer::singleShot(0, this, [this] { onEditSelectedElement(); }); } void ModelEditor::onCurrentEditorChanged(IEditor *editor) { if (this == editor) { QUndoStack *undoStack = d->document->documentController()->undoController()->undoStack(); d->actionHandler->undoAction()->setDisabled(!undoStack->canUndo()); d->actionHandler->redoAction()->setDisabled(!undoStack->canRedo()); updateSelectedArea(SelectedArea::Nothing); } } void ModelEditor::onCanUndoChanged(bool canUndo) { if (this == EditorManager::currentEditor()) d->actionHandler->undoAction()->setEnabled(canUndo); } void ModelEditor::onCanRedoChanged(bool canRedo) { if (this == EditorManager::currentEditor()) d->actionHandler->redoAction()->setEnabled(canRedo); } void ModelEditor::onTreeModelReset() { updateSelectedArea(SelectedArea::Nothing); } void ModelEditor::onTreeViewSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { Q_UNUSED(selected) Q_UNUSED(deselected) synchronizeDiagramWithBrowser(); updateSelectedArea(SelectedArea::TreeView); } void ModelEditor::onTreeViewActivated() { updateSelectedArea(SelectedArea::TreeView); } void ModelEditor::onTreeViewDoubleClicked(const QModelIndex &index) { ExtDocumentController *documentController = d->document->documentController(); QModelIndex treeModelIndex = d->modelTreeView->mapToSourceModelIndex(index); qmt::MElement *melement = documentController->treeModel()->element(treeModelIndex); // double click on package is already used for toggeling tree if (melement && !d->modelTreeView->model()->hasChildren(index)) documentController->elementTasks()->openElement(melement); } void ModelEditor::onCurrentDiagramChanged(const qmt::MDiagram *diagram) { if (diagram == currentDiagram()) { updateSelectedArea(SelectedArea::Diagram); } else { updateSelectedArea(SelectedArea::Nothing); } } void ModelEditor::onDiagramActivated(const qmt::MDiagram *diagram) { Q_UNUSED(diagram) updateSelectedArea(SelectedArea::Diagram); } void ModelEditor::onDiagramClipboardChanged(bool isEmpty) { Q_UNUSED(isEmpty) if (this == EditorManager::currentEditor()) updateSelectedArea(d->selectedArea); } void ModelEditor::onNewElementCreated(qmt::DElement *element, qmt::MDiagram *diagram) { if (diagram == currentDiagram()) { ExtDocumentController *documentController = d->document->documentController(); documentController->diagramsManager()->diagramSceneModel(diagram)->selectElement(element); qmt::MElement *melement = documentController->modelController()->findElement(element->modelUid()); if (!(melement && melement->flags().testFlag(qmt::MElement::ReverseEngineered))) QTimer::singleShot(0, this, [this] { onEditSelectedElement(); }); } } void ModelEditor::onDiagramSelectionChanged(const qmt::MDiagram *diagram) { if (diagram == currentDiagram()) { synchronizeBrowserWithDiagram(diagram); updateSelectedArea(SelectedArea::Diagram); } } void ModelEditor::onDiagramModified(const qmt::MDiagram *diagram) { Q_UNUSED(diagram) updateSelectedArea(d->selectedArea); } void ModelEditor::onRightSplitterMoved(int pos, int index) { Q_UNUSED(pos) Q_UNUSED(index) d->uiController->onRightSplitterChanged(d->rightSplitter->saveState()); } void ModelEditor::onRightSplitterChanged(const QByteArray &state) { d->rightSplitter->restoreState(state); } void ModelEditor::onRightHorizSplitterMoved(int pos, int index) { Q_UNUSED(pos) Q_UNUSED(index) d->uiController->onRightHorizSplitterChanged(d->rightHorizSplitter->saveState()); } void ModelEditor::onRightHorizSplitterChanged(const QByteArray &state) { d->rightHorizSplitter->restoreState(state); } void ModelEditor::onToolbarSelectionChanged() { storeToolbarIdInDiagram(currentDiagram()); } void ModelEditor::initToolbars() { QHash toolBars; qmt::DocumentController *documentController = d->document->documentController(); qmt::StereotypeController *stereotypeController = documentController->stereotypeController(); QList toolbars = stereotypeController->toolbars(); std::stable_sort(toolbars.begin(), toolbars.end(), [=](const qmt::Toolbar &lhs, const qmt::Toolbar &rhs) { return lhs.priority() > rhs.priority(); }); for (const qmt::Toolbar &toolbar : std::as_const(toolbars)) { QWidget *toolBar = toolBars.value(toolbar.id()); QLayout *toolBarLayout = nullptr; if (!toolBar) { toolBar = new QWidget(d->leftToolBox); toolBar->setProperty(PROPERTYNAME_TOOLBARID, toolbar.id()); toolBarLayout = new QVBoxLayout(toolBar); toolBarLayout->setContentsMargins(2, 2, 2, 2); toolBarLayout->setSpacing(6); // TODO add a user defined string attribute to toolbar as toolbar title d->leftToolBox->addItem(toolBar, toolbar.id()); toolBars.insert(toolbar.id(), toolBar); } else { toolBarLayout = toolBar->layout(); QMT_ASSERT(toolBarLayout, continue); } const QList tools = toolbar.tools(); for (const qmt::Toolbar::Tool &tool : tools) { switch (tool.m_toolType) { case qmt::Toolbar::TooltypeTool: { QString iconPath; qmt::StereotypeIcon::Element stereotypeIconElement = qmt::StereotypeIcon::ElementAny; qmt::StyleEngine::ElementType styleEngineElementType = qmt::StyleEngine::TypeOther; if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_PACKAGE)) { iconPath = ":/modelinglib/48x48/package.png"; stereotypeIconElement = qmt::StereotypeIcon::ElementPackage; styleEngineElementType = qmt::StyleEngine::TypePackage; } else if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_COMPONENT)) { iconPath = ":/modelinglib/48x48/component.png"; stereotypeIconElement = qmt::StereotypeIcon::ElementComponent; styleEngineElementType = qmt::StyleEngine::TypeComponent; } else if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_CLASS)) { iconPath = ":/modelinglib/48x48/class.png"; stereotypeIconElement = qmt::StereotypeIcon::ElementClass; styleEngineElementType = qmt::StyleEngine::TypeClass; } else if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_ITEM)) { iconPath = ":/modelinglib/48x48/item.png"; stereotypeIconElement = qmt::StereotypeIcon::ElementItem; styleEngineElementType = qmt::StyleEngine::TypeItem; } else if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_ANNOTATION)) { iconPath = ":/modelinglib/48x48/annotation.png"; styleEngineElementType = qmt::StyleEngine::TypeAnnotation; } else if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_BOUNDARY)) { iconPath = ":/modelinglib/48x48/boundary.png"; styleEngineElementType = qmt::StyleEngine::TypeBoundary; } else if (tool.m_elementType == QLatin1String(qmt::ELEMENT_TYPE_SWIMLANE)) { iconPath = ":/modelinglib/48x48/swimlane.png"; styleEngineElementType = qmt::StyleEngine::TypeSwimlane; } QIcon icon; QString newElementName = Tr::tr("New %1").arg(tool.m_name); if (!tool.m_stereotype.isEmpty() && stereotypeIconElement != qmt::StereotypeIcon::ElementAny) { const qmt::Style *style = documentController->styleController()->adaptStyle(styleEngineElementType); icon = stereotypeController->createIcon( stereotypeIconElement, {tool.m_stereotype}, {}, style, QSize(128, 128), QMarginsF(6.0, 4.0, 6.0, 8.0), 8.0); if (!icon.isNull()) { QString stereotypeIconId = stereotypeController->findStereotypeIconId( stereotypeIconElement, {tool.m_stereotype}); qmt::StereotypeIcon stereotypeIcon = stereotypeController->findStereotypeIcon(stereotypeIconId); if (stereotypeIcon.hasName()) newElementName = stereotypeIcon.name(); } } if (icon.isNull()) icon = QIcon(iconPath); if (!icon.isNull()) { toolBarLayout->addWidget(new DragTool(icon, tool.m_name, newElementName, tool.m_elementType, tool.m_stereotype, toolBar)); } break; } case qmt::Toolbar::TooltypeSeparator: { toolBarLayout->addWidget(Layouting::createHr(d->leftToolBox)); break; } } } } // fallback if no toolbar was defined if (!toolBars.isEmpty()) { QString generalId = "General"; auto toolBar = new QWidget(d->leftToolBox); toolBar->setProperty(PROPERTYNAME_TOOLBARID, generalId); auto toolBarLayout = new QVBoxLayout(toolBar); toolBarLayout->setContentsMargins(2, 2, 2, 2); toolBarLayout->setSpacing(6); d->leftToolBox->insertItem(0, toolBar, generalId); toolBars.insert(generalId, toolBar); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/package.png"), Tr::tr("Package"), Tr::tr("New Package"), QLatin1String(qmt::ELEMENT_TYPE_PACKAGE), QString(), toolBar)); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/component.png"), Tr::tr("Component"), Tr::tr("New Component"), QLatin1String(qmt::ELEMENT_TYPE_COMPONENT), QString(), toolBar)); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/class.png"), Tr::tr("Class"), Tr::tr("New Class"), QLatin1String(qmt::ELEMENT_TYPE_CLASS), QString(), toolBar)); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/item.png"), Tr::tr("Item"), Tr::tr("New Item"), QLatin1String(qmt::ELEMENT_TYPE_ITEM), QString(), toolBar)); toolBarLayout->addWidget(Layouting::createHr(d->leftToolBox)); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/annotation.png"), Tr::tr("Annotation"), QString(), QLatin1String(qmt::ELEMENT_TYPE_ANNOTATION), QString(), toolBar)); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/boundary.png"), Tr::tr("Boundary"), QString(), QLatin1String(qmt::ELEMENT_TYPE_BOUNDARY), QString(), toolBar)); toolBarLayout->addWidget( new DragTool(QIcon(":/modelinglib/48x48/swimlane.png"), Tr::tr("Swimlane"), QString(), QLatin1String(qmt::ELEMENT_TYPE_SWIMLANE), QString(), toolBar)); } // add stretch to all layouts and calculate width of tool bar int maxWidth = 48; for (QWidget *toolBar : std::as_const(toolBars)) { QMT_ASSERT(toolBar, continue); auto layout = qobject_cast(toolBar->layout()); QMT_ASSERT(layout, continue); layout->addStretch(1); toolBar->adjustSize(); if (maxWidth < toolBar->width()) maxWidth = toolBar->width(); } d->leftToolBox->setFixedWidth(maxWidth); d->leftToolBox->setCurrentIndex(0); } void ModelEditor::openDiagram(qmt::MDiagram *diagram, bool addToHistory) { closeCurrentDiagram(addToHistory); if (diagram) { qmt::DiagramSceneModel *diagramSceneModel = d->document->documentController()->diagramsManager()->bindDiagramSceneModel(diagram); d->diagramView->setDiagramSceneModel(diagramSceneModel); d->diagramStack->setCurrentWidget(d->diagramView); updateSelectedArea(SelectedArea::Nothing); addDiagramToSelector(diagram); if (!diagram->toolbarId().isEmpty()) { for (int i = 0; i < d->leftToolBox->count(); ++i) { QWidget *widget = d->leftToolBox->widget(i); if (widget && widget->property(PROPERTYNAME_TOOLBARID).toString() == diagram->toolbarId()) { d->leftToolBox->setCurrentIndex(i); break; } } } else { storeToolbarIdInDiagram(diagram); } } } void ModelEditor::closeCurrentDiagram(bool addToHistory) { ExtDocumentController *documentController = d->document->documentController(); qmt::DiagramsManager *diagramsManager = documentController->diagramsManager(); qmt::DiagramSceneModel *sceneModel = d->diagramView->diagramSceneModel(); if (sceneModel) { qmt::MDiagram *diagram = sceneModel->diagram(); if (diagram) { if (addToHistory) addToNavigationHistory(diagram); d->diagramStack->setCurrentWidget(d->noDiagramLabel); d->diagramView->setDiagramSceneModel(nullptr); diagramsManager->unbindDiagramSceneModel(diagram); } } } void ModelEditor::closeDiagram(const qmt::MDiagram *diagram) { ExtDocumentController *documentController = d->document->documentController(); qmt::DiagramsManager *diagramsManager = documentController->diagramsManager(); qmt::DiagramSceneModel *sceneModel = d->diagramView->diagramSceneModel(); if (sceneModel && diagram == sceneModel->diagram()) { addToNavigationHistory(diagram); d->diagramStack->setCurrentWidget(d->noDiagramLabel); d->diagramView->setDiagramSceneModel(nullptr); diagramsManager->unbindDiagramSceneModel(diagram); } } void ModelEditor::closeAllDiagrams() { closeCurrentDiagram(true); } void ModelEditor::onContentSet() { initDocument(); // open diagram ExtDocumentController *documentController = d->document->documentController(); qmt::MDiagram *rootDiagram = documentController->findOrCreateRootDiagram(); showDiagram(rootDiagram); // select diagram in model tree view QModelIndex modelIndex = documentController->treeModel()->indexOf(rootDiagram); if (modelIndex.isValid()) d->modelTreeView->selectFromSourceModelIndex(modelIndex); expandModelTreeToDepth(0); } void ModelEditor::setDiagramClipboard(const qmt::DContainer &dcontainer) { ExtDocumentController *documentController = d->document->documentController(); qmt::ModelController *modelController = documentController->modelController(); qmt::MSelection modelSelection; for (const auto &delement : dcontainer.elements()) { qmt::Uid melementUid = delement->modelUid(); qmt::Uid mownerUid = modelController->ownerKey(melementUid); modelSelection.append(melementUid, mownerUid); } qmt::MContainer mcontainer = modelController->copyElements(modelSelection); ModelEditorPlugin::modelsManager()->setDiagramClipboard(documentController, dcontainer, mcontainer); } void ModelEditor::addDiagramToSelector(const qmt::MDiagram *diagram) { QString diagramLabel = buildDiagramLabel(diagram); QVariant diagramUid = QVariant::fromValue(diagram->uid()); int i = d->diagramSelector->findData(diagramUid); if (i >= 0) d->diagramSelector->removeItem(i); d->diagramSelector->insertItem(0, QIcon(":/modelinglib/48x48/canvas-diagram.png"), diagramLabel, diagramUid); d->diagramSelector->setCurrentIndex(0); while (d->diagramSelector->count() > 20) d->diagramSelector->removeItem(d->diagramSelector->count() - 1); } void ModelEditor::updateDiagramSelector() { int i = 0; while (i < d->diagramSelector->count()) { qmt::Uid diagramUid = d->diagramSelector->itemData(i).value(); if (diagramUid.isValid()) { qmt::MDiagram *diagram = d->document->documentController()->modelController()->findObject(diagramUid); if (diagram) { QString diagramLabel = buildDiagramLabel(diagram); if (diagramLabel != d->diagramSelector->itemText(i)) d->diagramSelector->setItemText(i, diagramLabel); ++i; continue; } } d->diagramSelector->removeItem(i); } } void ModelEditor::onDiagramSelectorSelected(int index) { qmt::Uid diagramUid = d->diagramSelector->itemData(index).value(); if (diagramUid.isValid()) { qmt::MDiagram *diagram = d->document->documentController()->modelController()->findObject(diagramUid); if (diagram) { showDiagram(diagram); return; } } d->diagramSelector->setCurrentIndex(0); } QString ModelEditor::buildDiagramLabel(const qmt::MDiagram *diagram) { QString label = diagram->name(); qmt::MObject *owner = diagram->owner(); QStringList path; while (owner) { path.append(owner->name()); owner = owner->owner(); } if (!path.isEmpty()) { label += " ["; label += path.last(); for (int i = path.count() - 2; i >= 0; --i) { label += QLatin1Char('.'); label += path.at(i); } label += QLatin1Char(']'); } return label; } void ModelEditor::storeToolbarIdInDiagram(qmt::MDiagram *diagram) { int leftToolboxIndex = d->leftToolBox->currentIndex(); if (diagram && leftToolboxIndex >= 0 && leftToolboxIndex < d->leftToolBox->count()) { QWidget *widget = d->leftToolBox->widget(leftToolboxIndex); if (widget) { QString toolbarId = widget->property(PROPERTYNAME_TOOLBARID).toString(); if (toolbarId != diagram->toolbarId()) { // NOTE: This update is done without calling ModelController::startUpdateObject() by intention. // This update does not need to be seen by any other controller nor shall it // No undo shall be possible // The document should not be set to modified state diagram->setToolbarId(toolbarId); } } } } void ModelEditor::addToNavigationHistory(const qmt::MDiagram *diagram) { if (EditorManager::currentEditor() == this) { EditorManager::cutForwardNavigationHistory(); EditorManager::addCurrentPositionToNavigationHistory(saveState(diagram)); } } QByteArray ModelEditor::saveState(const qmt::MDiagram *diagram) const { QByteArray state; QDataStream stream(&state, QIODevice::WriteOnly); stream << 2; // version number if (diagram) stream << diagram->uid(); else stream << qmt::Uid::invalidUid(); stream << d->actionHandler->synchronizeBrowserAction()->isChecked() << d->syncBrowserWithDiagramAction->isChecked() << d->syncDiagramWithBrowserAction->isChecked() << d->syncEachOtherAction->isChecked(); return state; } void ModelEditor::onEditSelectedElement() { // TODO introduce similar method for selected elements in model tree // currently this method is called on adding new elements in model tree // but the method is a no-op in that case. qmt::MDiagram *diagram = d->propertiesView->selectedDiagram(); QList elements = d->propertiesView->selectedDiagramElements(); if (diagram && !elements.isEmpty()) { qmt::DElement *element = elements.at(0); if (element) { qmt::DiagramSceneModel *diagramSceneModel = d->document->documentController()->diagramsManager()->diagramSceneModel(diagram); if (diagramSceneModel->isElementEditable(element)) { d->diagramStack->currentWidget()->setFocus(); diagramSceneModel->editElement(element); return; } } d->propertiesView->editSelectedElement(); } } bool ModelEditor::isSyncBrowserWithDiagram() const { return d->actionHandler->synchronizeBrowserAction()->isChecked() && (d->syncBrowserWithDiagramAction->isChecked() || d->syncEachOtherAction->isChecked()); } bool ModelEditor::isSyncDiagramWithBrowser() const { return d->actionHandler->synchronizeBrowserAction()->isChecked() && (d->syncDiagramWithBrowserAction->isChecked() || d->syncEachOtherAction->isChecked()); } void ModelEditor::synchronizeDiagramWithBrowser() { if (isSyncDiagramWithBrowser()) { if (currentDiagram()) { bool done = false; qmt::DocumentController *documentController = d->document->documentController(); const QModelIndexList indexes = d->modelTreeView->selectedSourceModelIndexes(); if (!indexes.isEmpty()) { for (const QModelIndex &index : indexes) { if (index.isValid()) { qmt::MElement *modelElement = documentController->treeModel()->element(index); if (modelElement) { const QList diagramElements = currentDiagram()->diagramElements(); for (qmt::DElement *diagramElement : diagramElements) { if (diagramElement->modelUid() == modelElement->uid()) { // disconnect temporarily avoiding double update of properties Ui disconnect(documentController->diagramsManager(), &qmt::DiagramsManager::diagramSelectionChanged, this, &ModelEditor::onDiagramSelectionChanged); d->diagramView->diagramSceneModel()->selectElement(diagramElement); QGraphicsItem *item = d->diagramView->diagramSceneModel()->graphicsItem(diagramElement); if (item) d->diagramView->ensureVisible(item); connect(documentController->diagramsManager(), &qmt::DiagramsManager::diagramSelectionChanged, this, &ModelEditor::onDiagramSelectionChanged, Qt::QueuedConnection); done = true; break; } } if (done) break; } } } } } } } void ModelEditor::synchronizeBrowserWithDiagram(const qmt::MDiagram *diagram) { if (isSyncBrowserWithDiagram()) { qmt::DocumentController *documentController = d->document->documentController(); qmt::DSelection selection = documentController->diagramsManager()->diagramSceneModel(diagram)->selectedElements(); if (!selection.isEmpty()) { const QList indexes = selection.indices(); for (qmt::DSelection::Index index : indexes) { qmt::DElement *diagramElement = documentController->diagramController()->findElement(index.elementKey(), diagram); if (diagramElement) { qmt::MElement *modelElement = documentController->modelController()->findElement(diagramElement->modelUid()); if (modelElement) { QModelIndex index = d->modelTreeViewServant->treeModel()->indexOf(modelElement); if (index.isValid()) { // disconnect temporarily avoiding double update of properties Ui disconnect(d->modelTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModelEditor::onTreeViewSelectionChanged); d->modelTreeView->selectFromSourceModelIndex(index); connect(d->modelTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ModelEditor::onTreeViewSelectionChanged, Qt::QueuedConnection); break; } } } } } } } } // namespace Internal } // namespace ModelEditor