/******************************************************************************* * This file is part of the LibreCAD project, a 2D CAD program Copyright (C) 2024 LibreCAD.org Copyright (C) 2024 sand1024 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 2 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, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. ******************************************************************************/ #include #include #include "lc_layerdialog_ex.h" #include "lc_layertreeitem.h" #include "lc_layertreemodel_options.h" #include "lc_layertreeoptionsdialog.h" #include "lc_layertreeview.h" #include "lc_layertreewidget.h" #include "lc_exportlayersdialogservice.h" #include "qc_applicationwindow.h" #include "qg_actionhandler.h" #include "rs_debug.h" #include "rs_dialogfactory.h" #include "rs_dialogfactoryinterface.h" #include "rs_graphic.h" #include "lc_flexlayout.h" #include "lc_layersexporter.h" #include "lc_layertreemodel.h" #include "rs_settings.h" #include "lc_widgets_common.h" #include "rs_graphicview.h" #include "rs_layer.h" /** * Constructor. */ LC_LayerTreeWidget::LC_LayerTreeWidget(QG_ActionHandler* ah, QWidget* parent, const char* name, Qt::WindowFlags f): LC_GraphicViewAwareWidget(parent, name, f) { m_actionHandler = ah; m_layerList = nullptr; // TODO - should this flag be persistent? Let's keep it transient so far, let's see m_flatListMode = false; QLayout *layFiltering = initFilterAndSettingsSection(); initTreeModel(); m_layerTreeView = initTreeView(); QLayout *layButtons = initButtonsBar(); auto *lay = new QVBoxLayout(this); lay->setContentsMargins(2, 2, 2, 2); lay->addLayout(layFiltering); lay->addLayout(layButtons); QSizePolicy policy = m_layerTreeView->sizePolicy(); policy.setVerticalStretch(1); m_layerTreeView->setSizePolicy(policy); lay->addWidget(m_layerTreeView); this->setLayout(lay); updateWidgetSettings(); } void LC_LayerTreeWidget::initTreeModel(){ auto *options = new LC_LayerTreeModelOptions(); options->load(); m_layerTreeModel = new LC_LayerTreeModel(this, options); m_layerTreeModel->setFlatMode(m_flatListMode); } /** * UI initialization of TreeView. Model should be already created * @return */ LC_LayerTreeView *LC_LayerTreeWidget::initTreeView(){ auto *treeView = new LC_LayerTreeView(this); treeView->setup(m_layerTreeModel); QHeaderView *pTreeHeader{treeView->header()}; pTreeHeader->setMinimumSectionSize(LC_LayerTreeModel::ICONWIDTH + 4); pTreeHeader->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents); pTreeHeader->setStretchLastSection(true); pTreeHeader->hide(); treeView->setColumnWidth(LC_LayerTreeModel::VISIBLE, LC_LayerTreeModel::ICONWIDTH); treeView->setColumnWidth(LC_LayerTreeModel::VISIBLE, LC_LayerTreeModel::ICONWIDTH); treeView->setColumnWidth(LC_LayerTreeModel::LOCKED, LC_LayerTreeModel::ICONWIDTH); treeView->setColumnWidth(LC_LayerTreeModel::PRINT, LC_LayerTreeModel::ICONWIDTH); treeView->setColumnWidth(LC_LayerTreeModel::CONSTRUCTION, LC_LayerTreeModel::ICONWIDTH); treeView->setColumnWidth(LC_LayerTreeModel::COLOR_SAMPLE, LC_LayerTreeModel::ICONWIDTH); treeView->setUniformRowHeights(true); treeView->setAlternatingRowColors(false); treeView->setSelectionMode(QAbstractItemView::NoSelection); treeView->setEditTriggers(QAbstractItemView::NoEditTriggers); treeView->setFocusPolicy(Qt::NoFocus); treeView->setMinimumHeight(65); treeView->setDragDropMode(QAbstractItemView::InternalMove); treeView->setDragEnabled(true); treeView->setSelectionMode(QAbstractItemView::SingleSelection); treeView->setAcceptDrops(true); treeView->setDropIndicatorShown(true); treeView->setExpandsOnDoubleClick(false); treeView->setContextMenuPolicy(Qt::CustomContextMenu); #ifndef DONT_FORCE_WIDGETS_CSS treeView->setStyleSheet("QWidget {background-color: white;} QScrollBar{ background-color: none }"); #endif connect(treeView, &QTreeView::customContextMenuRequested, this, &LC_LayerTreeWidget::onCustomContextMenu); connect(treeView, &QTreeView::clicked, this, &LC_LayerTreeWidget::slotTreeClicked); connect(treeView, &QTreeView::doubleClicked, this, &LC_LayerTreeWidget::slotTreeDoubleClicked); return treeView; } /** * UI initialization of filtering section * @brief LC_LayerTreeWidget::initFilterAndSettingsSection * @return */ QLayout *LC_LayerTreeWidget::initFilterAndSettingsSection(){ auto *layFiltering = new QHBoxLayout; // lineEdit to filter layer list with RegEx m_matchLayerName = new QLineEdit(this); m_matchLayerName->setReadOnly(false); m_matchLayerName->setPlaceholderText(tr("Filter")); m_matchLayerName->setClearButtonEnabled(true); m_matchLayerName->setToolTip(tr("Looking for matching layer names")); connect(m_matchLayerName, &QLineEdit::textChanged, this, &LC_LayerTreeWidget::slotFilteringMaskChanged); // TODO - in general, it is possible to use persistent settings for the state, yet not sure it is reasonable m_matchModeCheckBox = new QCheckBox(this); m_matchModeCheckBox->setText(tr("Highlight Mode")); m_matchModeCheckBox->setChecked(true); connect(m_matchModeCheckBox, &QCheckBox::clicked, this, &LC_LayerTreeWidget::slotFilteringMaskChanged); layFiltering->addWidget(m_matchLayerName); layFiltering->addWidget(m_matchModeCheckBox); // settings button auto* but = new QToolButton(this); but->setIcon(QIcon(":/icons/settings.lci")); but->setToolTip(tr("Settings")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::invokeSettingsDialog); layFiltering->addWidget(but); return layFiltering; } /** * UI initialization for actions bar * TODO - in general, it's possible to use separate widget for button bar and manage the state there. * Lets eave it for now as it is in order to minify affecting codebase, probably will refactor later */ QLayout *LC_LayerTreeWidget::initButtonsBar(){ auto *layButtons = new LC_FlexLayout(0,5,5); QToolButton *but; // show all layer: but = new QToolButton(this); // but->setIcon(QIcon(":/icons/visible.lci")); but->setIcon(QIcon(":/icons/visible_all.lci")); but->setToolTip(tr("Show all layers")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::showAllLayers); layButtons->addWidget(but); // hide all layer: but = new QToolButton(this); but->setIcon(QIcon(":/icons/not_visible_all.lci")); but->setToolTip(tr("Hide all layers")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::hideAllLayers); layButtons->addWidget(but); // toggle secondary layers but = new QToolButton(this); but->setIcon(QIcon(":/icons/dim_vertical.lci")); but->setToolTip(tr("Show Secondary Layers")); but->setCheckable(true); but->setChecked(true); // visible by default connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::toggleSecondaryLayersVisibility); layButtons->addWidget(but); m_btnShowSecondaryLayers = but; // Visible only active but = new QToolButton(this); but->setIcon(QIcon(":/icons/select_all.lci")); but->setToolTip(tr("Show Active Layer Only")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::showActiveLayerOnly); layButtons->addWidget(but); // expand all layers but = new QToolButton(this); but->setIcon(QIcon(":/icons/order.lci")); but->setToolTip(tr("Expand All")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::expandAllLayers); layButtons->addWidget(but); m_btnExpandAll = but; // collapse all layers but = new QToolButton(this); but->setIcon(QIcon(":/icons/upmost.lci")); but->setToolTip(tr("Collapse All")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::collapseAllLayers); layButtons->addWidget(but/*, 10*/); m_btnCollapseAll = but; // expand all layers but = new QToolButton(this); but->setIcon(QIcon(":/icons/dim_aligned.lci")); but->setToolTip(tr("Collapse Secondary")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::collapseSecondaryLayers); layButtons->addWidget(but); m_btnCollapseSecondary = but; // unlock all layers: but = new QToolButton(this); but->setIcon(QIcon(":/icons/unlocked.lci")); but->setToolTip(tr("Unlock all layers")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::unlockAllLayers); layButtons->addWidget(but); // lock all layers: but = new QToolButton(this); but->setIcon(QIcon(":/icons/locked.lci")); but->setToolTip(tr("Lock all layers")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::lockAllLayers); layButtons->addWidget(but); // add layer: but = new QToolButton(this); but->setIcon(QIcon(":/icons/add.lci")); but->setToolTip(tr("Add a layer")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::addLayer); layButtons->addWidget(but); // add dimensional layer: but = new QToolButton(this); but->setIcon(QIcon(":/icons/dim_horizontal.lci")); but->setToolTip(tr("Add dimensions Layer")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::addDimensionalLayerForActiveLayer); m_btnAddDimensional = but; layButtons->addWidget(but); // remove layer: but = new QToolButton(this); but->setIcon(QIcon(":/icons/remove.lci")); but->setToolTip(tr("Remove layer")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::removeActiveLayers); layButtons->addWidget(but); // rename layer: but = new QToolButton(this); but->setIcon(QIcon(":/icons/rename_active_block.lci")); but->setToolTip(tr("Modify layer attributes / rename")); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::editActiveLayer); layButtons->addWidget(but); // rename layer: but = new QToolButton(this); but->setIcon(QIcon(":/icons/down.lci")); but->setToolTip(tr("Flat List Mode)")); but->setCheckable(true); connect(but, &QToolButton::clicked, this, &LC_LayerTreeWidget::toggleFlatView); layButtons->addWidget(but); m_btnListMode = but; updateToolBarButtons(); return layButtons; } /** * Sets the RS_LayerList this layer widget should show. If layer list does not equal to previously * set one, that leads to complete rebuild of underlying model */ void LC_LayerTreeWidget::setLayerList(RS_LayerList *ll){ if (m_layerList != nullptr) { m_layerList->removeListener(this); } m_layerList = ll; if (ll != nullptr){ m_layerList->addListener(this); } update(); } /** * Updates the layer box from the layers in the graphic. * Primary method used to rebuild model for modifications and * updating the UI. */ void LC_LayerTreeWidget::update(){ RS_DEBUG->print("QG_LayerWidget::update() begin"); if (m_layerTreeView == nullptr){ RS_DEBUG->print(RS_Debug::D_ERROR, "QG_LayerWidget::update: nullptr layerView"); return; } if (m_layerList == nullptr){ RS_DEBUG->print(RS_Debug::D_NOTICE, "QG_LayerWidget::update: nullptr layerList"); m_layerTreeModel->setLayerList(nullptr); return; } if (m_withinSelfActivation){ // layer is activated by our widget, not externally via listener, so we need just modify active // path for currently active layer and do not rebuild existing model m_layerTreeModel->proceedActiveLayerChanged(m_layerList); } else { // complete rebuild of the model and update of UI int yPos = m_layerTreeView->verticalScrollBar()->value(); m_layerTreeView->verticalScrollBar()->setValue(yPos); QStringList treeExpansionState = m_layerTreeView->saveTreeExpansionState(); // model will be rebuilt as result of setting layer list m_layerTreeModel->setLayerList(m_layerList); if (!treeExpansionState.isEmpty()){ m_layerTreeView->restoreTreeExpansionState(treeExpansionState); } m_layerTreeView->viewport()->update(); } RS_DEBUG->print("QG_LayerWidget::update(): OK"); } /** * Activates the given layer and makes it the active layer in the layers list. */ void LC_LayerTreeWidget::activateLayer(RS_Layer *layer){ RS_DEBUG->print("QG_LayerWidget::activateLayer() begin"); if (!layer || !m_layerList){ RS_DEBUG->print(RS_Debug::D_ERROR, "QG_LayerWidget::activateLayer: nullptr layer or layerList"); return; } m_layerList->activate(layer, false); update(); m_layerTreeView->viewport()->update(); //update active layer name in main window status bar QC_ApplicationWindow::getAppWindow()->slotUpdateActiveLayer(); RS_DEBUG->print("QG_LayerWidget::activateLayer() end"); } //---------------- three expanded state control ---------------------- void LC_LayerTreeWidget::collapseAllLayers(){ m_layerTreeView->collapseAll(); } void LC_LayerTreeWidget::expandAllLayers(){ m_layerTreeView->expandAll(); } void LC_LayerTreeWidget::expandItems(int depth){ if (depth == -1){ m_layerTreeView->expandAll(); } else { m_layerTreeView->expandToDepth(depth); } } /** * Collapsing all secondary items - all that are not normal or virtual */ void LC_LayerTreeWidget::collapseSecondaryLayers(){ m_layerTreeView->setUpdatesEnabled(false); foreach (QModelIndex index, m_layerTreeModel->getPersistentIndexList()) { if (m_layerTreeView->isExpanded(index)){ LC_LayerTreeItem *childItem = m_layerTreeModel->getItemForIndex(index); int type = childItem->getLayerType(); if (type == LC_LayerTreeItem::NORMAL){ m_layerTreeView->collapse(index); } } } m_layerTreeView->setUpdatesEnabled(true); m_layerTreeView->viewport()->update(); // QStringList expansion = layerTreeView->saveTreeExpansionState(); // layerTreeModel->reset(); // layerTreeView->restoreTreeExpansionState(expansion); } //-------------- Processing clicks on treeView /** * Expand items by double click. Custom implementation, since double click for normal layer items * will bring edit dialog, and double click on virtual layer item will fully expand all its descendants * @brief LC_LayerTreeWidget::onTreeDoubleClicked * @param index */ void LC_LayerTreeWidget::slotTreeDoubleClicked(const QModelIndex &index /*const QString& layerName*/){ if (index.isValid()){ LC_LayerTreeItem *layerItem = m_layerTreeModel->getItemForIndex(index); if (layerItem->isVirtual()){ // for virtual layer, we expand children // From Qt 5.13, another method might be used // layerTreeView -> expandRecursively(index,1); // TODO - think about expansion/collapsing on double click m_layerTreeView->setUpdatesEnabled(false); m_layerTreeView->expandChildren(index); m_layerTreeView->setUpdatesEnabled(true); } else { // for normal layer - we'll invoke editing editSelectedLayer(); } } } /** * Called when the user activates (highlights) a layer. * Method is used for toggling flag states for layers, as well as for activation of specific layer * Clicks on virtual layer items will affect descendent items flags and click on Name expands children of that virtual layer. * Clicks on real layer items works in the way similar to original layer list. */ void LC_LayerTreeWidget::slotTreeClicked(const QModelIndex &layerIdx /*const QString& layerName*/){ if (!layerIdx.isValid() || m_layerList == nullptr){ return; } auto *layerItem = static_cast(layerIdx.internalPointer()); // use translated column index to ensure proper column detection int column = m_layerTreeModel->translateColumn(layerIdx.column()); QList layers; QList layersToDisable = layers; QList layersToEnable = layers; LC_LayerTreeItemAcceptor ACCEPT_ALL = LC_LayerTreeItemAcceptor(); if (layerItem->isVirtual()){ switch (column) { case LC_LayerTreeModel::VISIBLE: { // we use slightly different logic for visibility check // comparing to other modes. // here we rely on the current state of node bool isVisible = layerItem->isVisible(); if (isVisible){ // we'd like to disable layer - so we should disable its secondary children too layerItem->collectLayers(layersToDisable, &ACCEPT_ALL); } else { // we enable this level, sub-virtual layers and normal layers - yet without sub-layers // Therefore we select all child except secondary QG_LayerTreeItemAcceptorSecondary ACCEPT_SECONDARY = QG_LayerTreeItemAcceptorSecondary(false); layerItem->collectLayers(layersToEnable, &ACCEPT_SECONDARY); } manageLayersVisibilityFlag(layersToEnable, layersToDisable, false); break; } case LC_LayerTreeModel::LOCKED:{ if (layerItem->isLocked()){ layerItem->collectLayers(layersToDisable, &ACCEPT_ALL); } else{ layerItem->collectLayers(layersToEnable, &ACCEPT_ALL); } manageLayersLockFlag(layersToEnable, layersToDisable, false); break; } case LC_LayerTreeModel::PRINT:{ if (layerItem->isPrint()){ layerItem->collectLayers(layersToDisable, &ACCEPT_ALL); } else{ layerItem->collectLayers(layersToEnable, &ACCEPT_ALL); } manageLayersPrintFlag(layersToEnable, layersToDisable, false); break; } case LC_LayerTreeModel::CONSTRUCTION: { if (layerItem->isConstruction()){ layerItem->collectLayers(layersToDisable, &ACCEPT_ALL); } else{ layerItem->collectLayers(layersToEnable, &ACCEPT_ALL); } manageLayersConstructionFlag(layersToEnable, layersToDisable, false); break; } case LC_LayerTreeModel::NAME: { // just convenient way to expand children of this layer m_layerTreeView->expand(layerIdx); break; } default: break; } } else { // real layer RS_Layer *lay = layerItem->getLayer(); if (lay == nullptr) return; if (column == LC_LayerTreeModel::NAME){ // Layer activation is here // set flag that activation of layer initiated by the widget. // this will prevent complete re-build of the model as soon as // corresponding listener will be invoked, so only the active path // to selected layer will be updated. m_withinSelfActivation = true; m_layerList->activate(lay, true); m_withinSelfActivation = false; return; } // handling clicks on flags int layerType = layerItem->getLayerType(); bool normalLayerWithChildren = layerType == LC_LayerTreeItem::NORMAL && layerItem->childCount() > 0; switch (column) { case LC_LayerTreeModel::VISIBLE: { if (normalLayerWithChildren){ bool isVisible = layerItem->isVisible(); if (isVisible){ // we'd like to disable layer - so we should disable its secondary children layerItem->collectLayers(layersToDisable, &ACCEPT_ALL); layersToDisable << lay; manageLayersVisibilityFlag(layersToEnable, layersToDisable, false); } else { // we enable ony the layer itself, without touching it secondary children m_actionHandler->setCurrentAction(RS2::ActionLayersToggleView, lay); } } else { m_actionHandler->setCurrentAction(RS2::ActionLayersToggleView, lay); } break; } case LC_LayerTreeModel::LOCKED: if (normalLayerWithChildren){ if (layerItem->isLocked()){ layerItem->collectLayers(layersToDisable, &ACCEPT_ALL, true); } else{ layerItem->collectLayers(layersToEnable, &ACCEPT_ALL, true); } manageLayersLockFlag(layersToEnable, layersToDisable, false); } else{ m_actionHandler->setCurrentAction(RS2::ActionLayersToggleLock, lay); } break; case LC_LayerTreeModel::PRINT: if (normalLayerWithChildren){ if (layerItem->isPrint()){ layerItem->collectLayers(layersToDisable, &ACCEPT_ALL, true); } else{ layerItem->collectLayers(layersToEnable, &ACCEPT_ALL, true); } manageLayersPrintFlag(layersToEnable, layersToDisable, false); } else{ m_actionHandler->setCurrentAction(RS2::ActionLayersTogglePrint, lay); } break; case LC_LayerTreeModel::CONSTRUCTION: if (normalLayerWithChildren){ if (layerItem->isConstruction()){ layerItem->collectLayers(layersToDisable, &ACCEPT_ALL, true); } else{ layerItem->collectLayers(layersToEnable, &ACCEPT_ALL, true); } manageLayersConstructionFlag(layersToEnable, layersToDisable, false); } else { m_actionHandler->setCurrentAction(RS2::ActionLayersToggleConstruction, lay); } break; default: break; } } } // ---------- Filtering mask /** * Called when reg-expression matchLayerName->text or match mode check box changed. * Simply notifies model about filtering change and updates model and ui */ void LC_LayerTreeWidget::slotFilteringMaskChanged(){ QString mask = m_matchLayerName->text(); bool highlightMode = m_matchModeCheckBox->isChecked(); m_layerTreeModel->setFilteringRegexp(mask, highlightMode); update(); } /** * Shows a context menu for the layer widget. Launched with a right click. * Menu is dynamic and is based on the particular item on which click is performed */ void LC_LayerTreeWidget::onCustomContextMenu(const QPoint &point){ if (m_actionHandler){ auto *contextMenu = new QMenu(this); /*auto *caption = new QLabel(tr("Layer Menu"), this); QPalette palette; palette.setColor(caption->backgroundRole(), RS_Color(0, 0, 0)); palette.setColor(caption->foregroundRole(), RS_Color(255, 255, 255)); caption->setPalette(palette); caption->setAlignment(Qt::AlignCenter);*/ // Actions for all layers: using ActionMemberFunc = void (LC_LayerTreeWidget::*)(); const auto addActionFunc = [this, &contextMenu](const QString& iconName, const QString& name, ActionMemberFunc func) { contextMenu->addAction(QIcon(":/icons/" + iconName + ".lci"), name, this, func); }; QModelIndex index = m_layerTreeView->indexAt(point); if (index.isValid()){ LC_LayerTreeItem *layerItem = m_layerTreeModel->getItemForIndex(index); int layerType = layerItem->getLayerType(); bool isVirtual = layerItem->isVirtual(); QString title = "Layer: " + layerItem->getName(); contextMenu->setTitle(title); if (isVirtual){ addActionFunc("add", tr("&Add Child Layer"), &LC_LayerTreeWidget::addChildLayerForSelectedItem); addActionFunc("rename_active_block", tr("&Rename"), &LC_LayerTreeWidget::renameVirtualLayer); addActionFunc("remove", tr("&Remove Layers (Sub-Tree)"), &LC_LayerTreeWidget::removeLayersForSelectedItem); contextMenu->addSeparator(); addActionFunc("copy", tr("&Copy Structure (Sub-Tree)"), &LC_LayerTreeWidget::createLayerCopy); addActionFunc("paste", tr("&Duplicate Content (Sub-Tree)"), &LC_LayerTreeWidget::createLayerDuplicate); // TODO - should we take care of virtual layer's visibility somehow? addActionFunc("deselect_layer", tr("&Select Entities (Sub-Tree)"), &LC_LayerTreeWidget::selectLayersEntities); } else { bool NON_ZERO_LAYER = !layerItem->isZero(); addActionFunc("rename_active_block", tr("&Edit Layer &Attributes"), &LC_LayerTreeWidget::editSelectedLayer); if (NON_ZERO_LAYER){ addActionFunc("remove", tr("&Remove Layer"), &LC_LayerTreeWidget::removeLayersForSelectedItem); } contextMenu->addSeparator(); if (layerType == LC_LayerTreeItem::NORMAL){ if (NON_ZERO_LAYER){ bool hasItems = false; if (!layerItem->hasChildOfType(LC_LayerTreeItem::DIMENSIONAL)){ addActionFunc("dim_horizontal", tr("&Add Dimensions Sub-Layer"), &LC_LayerTreeWidget::addDimensionalLayerForSelectedItem); hasItems = true; } if (!layerItem->hasChildOfType(LC_LayerTreeItem::INFORMATIONAL)){ addActionFunc("mtext", tr("&Add Info Sub-Layer"), &LC_LayerTreeWidget::addInformationalLayerForSelectedItem); hasItems = true; } if (!layerItem->hasChildOfType(LC_LayerTreeItem::ALTERNATE_POSITION)){ addActionFunc("rotate", tr("&Add Alternative View Sub-Layer"), &LC_LayerTreeWidget::addAddAlternativePositionLayerForSelectedItem); hasItems = true; } if (!m_flatListMode){ if (layerItem->childCount() > 0){ addActionFunc("remove", tr("&Remove Sub-layers"), &LC_LayerTreeWidget::removeChildLayersForSelected); hasItems = true; } } if (hasItems){ contextMenu->addSeparator(); } if (layerItem->childCount() == 0){ contextMenu->addAction(tr("Convert to Dimensional Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToDimensional); contextMenu->addAction(tr("Convert to Info Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToInformational); contextMenu->addAction(tr("Convert to Alternative Position Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToAlternativePosition); contextMenu->addSeparator(); } } } else { contextMenu->addAction(tr("Convert to Normal Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToNormal); if (layerType != LC_LayerTreeItem::DIMENSIONAL){ contextMenu->addAction(tr("Convert to Dimensional Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToDimensional); } if (layerType != LC_LayerTreeItem::INFORMATIONAL){ contextMenu->addAction(tr("Convert to Info Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToInformational); } if (layerType != LC_LayerTreeItem::ALTERNATE_POSITION){ contextMenu->addAction(tr("Convert to Alternative Position Layer"), this, &LC_LayerTreeWidget::convertLayerTypeToAlternativePosition); } contextMenu->addSeparator(); } if (layerItem->isVisible() && !layerItem->isLocked()){ addActionFunc("deselect_layer",tr("&Select Layer's Entities"), &LC_LayerTreeWidget::selectLayersEntities); } contextMenu->addSeparator(); addActionFunc("copy", tr("&Create Layer Copy"), &LC_LayerTreeWidget::createLayerCopy); addActionFunc("paste", tr("&Duplicate Layer With Content"), &LC_LayerTreeWidget::createLayerDuplicate); contextMenu->addSeparator(); if (!layerItem->isLocked()){ addActionFunc("move_copy", tr("Move Selection to Layer"), &LC_LayerTreeWidget::moveSelectionToLayer); addActionFunc("duplicate", tr("Duplicate Selection to Layer"), &LC_LayerTreeWidget::duplicateSelectionToLayer); contextMenu->addSeparator(); } } contextMenu->addSeparator(); } else { // click is not on item addActionFunc("add", tr("&Add Layer"), &LC_LayerTreeWidget::addLayer); } addActionFunc("visible", tr("&Freeze Others Layers"), &LC_LayerTreeWidget::hideOtherThanSelectedLayers); addActionFunc("visible_all", tr("&Defreeze All Layers"), &LC_LayerTreeWidget::showAllLayers); addActionFunc("not_visible_all",tr("&Freeze All Layers"), &LC_LayerTreeWidget::hideAllLayers); addActionFunc("unlocked", tr("&Unlock All Layers"), &LC_LayerTreeWidget::unlockAllLayers); addActionFunc("locked", tr("&Lock All Layers"), &LC_LayerTreeWidget::lockAllLayers); addActionFunc("print", tr("Enable &Printing All Layers"), &LC_LayerTreeWidget::printAllLayers); addActionFunc("noprint", tr("&Disable Printing All Layers"), &LC_LayerTreeWidget::noPrintAllLayers); contextMenu->addSeparator(); if (index.isValid()) { addActionFunc("layer_export_single",tr("&Export Single Layer"), &LC_LayerTreeWidget::exportSelectedLayer); if (!m_flatListMode){ LC_LayerTreeItem *layerItem = m_layerTreeModel->getItemForIndex(index); if (layerItem->childCount() > 0){ addActionFunc("layer_export_selected", tr("&Export Layer Sub-Tree"), &LC_LayerTreeWidget::exportLayerSubTree); } } } addActionFunc("layer_export_visible", tr("Export &Visible Layer(s)"), &LC_LayerTreeWidget::exportVisibleLayers); contextMenu->addSeparator(); addActionFunc("close_all", tr("&Find And Remove Empty Layers"), &LC_LayerTreeWidget::removeEmptyLayers); contextMenu->exec(QCursor::pos()); delete contextMenu; } // e->accept(); } // ------------- Layers Visibility Control /** * Makes all layers visible */ void LC_LayerTreeWidget::showAllLayers(){ m_btnShowSecondaryLayers->setChecked(true); LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToShow = m_layerTreeModel->collectLayers(&ACCEPT_ALL); QList layersToHide; manageLayersVisibilityFlag(layersToShow, layersToHide, false); } /** * Hides all layers except actual one */ void LC_LayerTreeWidget::showActiveLayerOnly(){ if (nullptr != m_layerList){ RS_Layer *activeLayer = m_layerList->getActive(); if (activeLayer != nullptr){ QG_LayerTreeItemAcceptorSameLayerAs ACCEPT_ALL_EXCEPT(activeLayer, true); QList layersToHide = m_layerTreeModel->collectLayers(&ACCEPT_ALL_EXCEPT); QList layersToShow; layersToShow << activeLayer; manageLayersVisibilityFlag(layersToShow, layersToHide, false); } } } /** * Makes all layers invisible */ void LC_LayerTreeWidget::hideAllLayers(){ m_btnShowSecondaryLayers->setChecked(false); LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToShow; QList layersToHide = m_layerTreeModel->collectLayers(&ACCEPT_ALL); manageLayersVisibilityFlag(layersToShow, layersToHide, false); } /** * Hides all layers except selected layer (it might be different from actual one) */ void LC_LayerTreeWidget::hideOtherThanSelectedLayers(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToHide = m_layerTreeModel->collectLayers(&ACCEPT_ALL); QList layersToShow; currentItem->collectLayers(layersToShow, &ACCEPT_ALL, true); int count = layersToShow.count(); for (int i = 0; i< count; i++){ RS_Layer* layer = layersToShow.at(i); layersToHide.removeAll(layer); } if (count > 0){ m_layerList->activate(layersToShow.at(0), false); } manageLayersVisibilityFlag(layersToShow, layersToHide, false); } } /** * Toggles visibility for secondary layers (dimensions, info and alternative position) */ void LC_LayerTreeWidget::toggleSecondaryLayersVisibility(){ LC_LayerTreeItem *root = m_layerTreeModel->getRoot(); QList secondaryItems; QG_LayerTreeItemAcceptorSecondary acceptor; root->collectDescendantChildren(secondaryItems, &acceptor, false); bool showSecondary = m_btnShowSecondaryLayers->isChecked(); int count = secondaryItems.count(); if (count > 0){ QList layersToEnable; QList layersToDisable; for (int i = 0; i < count; i++) { LC_LayerTreeItem *layerItem = secondaryItems.at(i); RS_Layer *layer = layerItem->getLayer(); LC_LayerTreeItem *primaryLayerItem = layerItem->getPrimaryItem(); if (primaryLayerItem != nullptr){ // the logic here is straightforward // we enable secondary layer only if primary layer is enabled. // so we'll not show dimensions layer if primary normal layer is not visible... if (showSecondary && primaryLayerItem->isVisible()){ layersToEnable << layer; } else { layersToDisable << layer; } } else { if (showSecondary){ layersToEnable << layer; } else { layersToDisable << layer; } } } manageLayersVisibilityFlag(layersToEnable, layersToDisable, false); } } // ----------- Lock/unlock layers /** * All layers included into view become unlocked (affects only matched layers) */ void LC_LayerTreeWidget::unlockAllLayers(){ LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToLock; QList layersToUnlock = m_layerTreeModel->collectLayers(&ACCEPT_ALL); manageLayersLockFlag(layersToLock, layersToUnlock, false); } /** * All matched layers become locked */ void LC_LayerTreeWidget::lockAllLayers(){ LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToLock = m_layerTreeModel->collectLayers(&ACCEPT_ALL); QList layersToUnlock; manageLayersLockFlag(layersToLock, layersToUnlock, false); } /** * All matched layers will be printable */ void LC_LayerTreeWidget::printAllLayers(){ LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToPrint = m_layerTreeModel->collectLayers(&ACCEPT_ALL); QList layersToNoPrint; manageLayersPrintFlag(layersToPrint, layersToNoPrint, false); } /** * All matched layers will be not printable */ void LC_LayerTreeWidget::noPrintAllLayers(){ LC_LayerTreeItemAcceptor ACCEPT_ALL; QList layersToPrint; QList layersToNoPrint= m_layerTreeModel->collectLayers(&ACCEPT_ALL); manageLayersPrintFlag(layersToPrint, layersToNoPrint, false); } /** * Collects layers with no entities and allows the user to remove them via layer removal dialog */ void LC_LayerTreeWidget::removeEmptyLayers(){ // first we inspect all entities ao collect found layers in set QSet foundLayers; for (auto en: *m_document) { RS_Layer *l = en->getLayer(true); if (l != nullptr){ foundLayers.insert(l); } } // compare layers provided by layers list with set of found layers QList layersWithNoEntities; unsigned int layersCount = m_layerList->count(); for (unsigned int i = 0; i< layersCount; i++){ RS_Layer* l = m_layerList->at(i); if (foundLayers.contains(l)){ // do nothing, some entities are found on this layer } else{ layersWithNoEntities << l; } } int layersWithNoEntitiesCount = layersWithNoEntities.count(); if (layersWithNoEntitiesCount > 0){ // empty layers found bool emptyLayerIsFiltered = false; // collect list of tree items for removal. Items are needed for proper showing display name in removal dialog... // TODO - still need to think about layer removal dialog and layer identification there. Probably it's better to show full name instead of display name QList itemsToRemove; for (int i = 0; i < layersWithNoEntitiesCount; i++){ RS_Layer* l = layersWithNoEntities.at(i); LC_LayerTreeItem* item = m_layerTreeModel->getItemForLayer(l); if (item == nullptr){ if (m_layerTreeModel->isRegexpApplied()){ // this means that there is regexp and layer is filtered... emptyLayerIsFiltered = true; } } else{ itemsToRemove<key()) { case Qt::Key_Escape: emit escape(); break; default: QWidget::keyPressEvent(e); break; } } //--------------- LayerListener methods void LC_LayerTreeWidget::layerActivated(RS_Layer *layer){ RS_DEBUG->print("LC_LayerTreeWidget::layerActivated()"); activateLayer(layer); } void LC_LayerTreeWidget::layerAdded([[maybe_unused]] RS_Layer *layer){ RS_DEBUG->print("QG_LayerWidget::layerAdded() begin"); update(); } void LC_LayerTreeWidget::layerEdited(RS_Layer *){ RS_DEBUG->print("LC_LayerTreeWidget::layerEdited()"); update(); m_layerTreeView->viewport()->update(); } void LC_LayerTreeWidget::layerRemoved(RS_Layer *){ RS_DEBUG->print("LC_LayerTreeWidget::layerRemoved()"); update(); activateLayer(m_layerList->at(0)); } void LC_LayerTreeWidget::layerToggled(RS_Layer *){ RS_DEBUG->print("LC_LayerTreeWidget::layerToggled()"); update(); } void LC_LayerTreeWidget::layerToggledLock(RS_Layer *){ update(); } void LC_LayerTreeWidget::layerToggledPrint(RS_Layer *){ update(); } void LC_LayerTreeWidget::layerToggledConstruction(RS_Layer *){ update(); } // --------- Drag & Drop support --- /** * Drag start event = just saving the item which starts dragging * @param dropIndex */ void LC_LayerTreeWidget::onDragEnterEvent(const QModelIndex &dropIndex){ // save source item for which drag is invoked LC_LayerTreeItem *currentlyDraggingItem = m_layerTreeModel->getItemForIndex(dropIndex); // LC_ERR << "Drag Enter for " << currentlyDraggingItem->getName() << " " << currentlyDraggingItem->getFullName(); m_layerTreeModel->setCurrentlyDraggingItem(currentlyDraggingItem); } /** * On Drop event. * @param dropIndex * @param position */ void LC_LayerTreeWidget::onDropEvent(const QModelIndex &dropIndex, DropIndicatorPosition position){ RS_DEBUG->print(RS_Debug::D_WARNING, "onDropEvent"); LC_LayerTreeItem *currentlyDraggingItem = m_layerTreeModel->getCurrentlyDraggingItem(); if (currentlyDraggingItem){ LC_LayerTreeItem *destinationItem = nullptr; if (dropIndex.isValid()){ // going to drop on other item destinationItem = m_layerTreeModel->getItemForIndex(dropIndex); } else if (position == LC_LayerTreeWidget::OnViewport){ // drop is on viewport, so we'll add source to root item destinationItem = m_layerTreeModel->getRoot(); } if (destinationItem != nullptr){ // do actual rename bool layersRenamed = m_layerTreeModel->performReStructure(currentlyDraggingItem, destinationItem); if (layersRenamed){ m_layerTreeView->expand(dropIndex); m_layerList->fireEdit(nullptr); } } m_layerTreeModel->setCurrentlyDraggingItem(nullptr); } } //---------------- context menu processing ---------------------- //--------------- Adding layers ----------------------- /** * Creates new information layer for selected item */ void LC_LayerTreeWidget::addInformationalLayerForSelectedItem(){ doAddSecondaryLevelForSelectedItem(LC_LayerTreeItem::INFORMATIONAL); } /** * Creates new alternative position layer for selected item */ void LC_LayerTreeWidget::addAddAlternativePositionLayerForSelectedItem(){ doAddSecondaryLevelForSelectedItem(LC_LayerTreeItem::ALTERNATE_POSITION); } /** * creates nwe dimensional layer for selected item */ void LC_LayerTreeWidget::addDimensionalLayerForSelectedItem(){ doAddSecondaryLevelForSelectedItem(LC_LayerTreeItem::DIMENSIONAL); } /** * Adds secondary layer with required type * @param layerType new layer type to be created */ void LC_LayerTreeWidget::doAddSecondaryLevelForSelectedItem(int layerType){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); if (!currentItem->isVirtual()){ // check for duplicate first if (!currentItem->hasChildOfType(layerType)){ invokeLayerAddDialog(currentItem, layerType); } else{ // duplicate found QMessageBox::information(parentWidget(), QMessageBox::tr("Add Layer"), QMessageBox::tr("Such child layer already exist for \n[%1].\n" "Please specify a different name.") .arg(currentItem->getName()), QMessageBox::Ok); } } } } /** * utility method for adding secondary levels for selected item */ void LC_LayerTreeWidget::addChildLayerForSelectedItem(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); invokeLayerAddDialog(currentItem, LC_LayerTreeItem::NOT_DEFINED_LAYER_TYPE); } } /** * Adds new layer */ void LC_LayerTreeWidget::addLayer(){ invokeLayerAddDialog(nullptr, LC_LayerTreeItem::NOT_DEFINED_LAYER_TYPE); } /** * Adds dimensional layer for currently active layer, if that layer is normal. * If dimensional layer already exists - just shows a dialog, otherwise - invokes normal layer creation flow */ void LC_LayerTreeWidget::addDimensionalLayerForActiveLayer(){ if (nullptr != m_layerList){ RS_Layer *activeLayer = m_layerList->getActive(); if (activeLayer != nullptr){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForLayer(activeLayer); if (currentItem != nullptr){ if (currentItem->getLayerType() == LC_LayerTreeItem::NORMAL){ int newLayerType = LC_LayerTreeItem::DIMENSIONAL; if (!currentItem->hasChildOfType(newLayerType)){ invokeLayerAddDialog(currentItem, newLayerType); } else{ QMessageBox::information(parentWidget(), QMessageBox::tr("Add Layer"), QMessageBox::tr("Such child layer already exist for \n[%1].\n") .arg(currentItem->getName()), QMessageBox::Ok); } } else{ QMessageBox::information(parentWidget(), QMessageBox::tr("Add Layer"), QMessageBox::tr("Dimensional layer may be added only for normal active layer.\n") .arg(currentItem->getName()), QMessageBox::Ok); } } } } } //--------- Editing Layers ----------------- /** * Invokes editing dialog for active layer */ void LC_LayerTreeWidget::editActiveLayer(){ if (nullptr != m_layerList){ RS_Layer *activeLayer = m_layerList->getActive(); if (activeLayer != nullptr){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForLayer(activeLayer); if (currentItem != nullptr){ invokeLayerEditOrRenameDialog(currentItem, true); } } } } /** * Invokes editing dialog for selected layer (called from context menu, so selected layer may differ from actual one */ void LC_LayerTreeWidget::editSelectedLayer(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); invokeLayerEditOrRenameDialog(currentItem, true); } } /** * Renames virtual layer (and so, renames all descendants of it accordingly) */ void LC_LayerTreeWidget::renameVirtualLayer(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); if (currentItem->isVirtual()){ invokeLayerEditOrRenameDialog(currentItem, false); } } } // ----------- Entities selection related /** * Selects all entities that belongs to selected layer (and it descendants) */ void LC_LayerTreeWidget::selectLayersEntities(){ QList layersList = collectLayersForSelectedItem(); if (!layersList.isEmpty()){ doSelectLayersEntities(layersList); } } /** * Moves currently selected entities to selected layer. Layer of original entities is changed to selected layer. */ void LC_LayerTreeWidget::moveSelectionToLayer(){ moveOrDuplicateSelectionToSelectedItemLayer(false, QMessageBox::tr("Move Selection")); } /** * Creates duplicates for selected entities and add these duplicated entities to selected layer. */ void LC_LayerTreeWidget::duplicateSelectionToLayer(){ moveOrDuplicateSelectionToSelectedItemLayer(true, QMessageBox::tr("Duplicate Selection")); } /** * Utility method that identifies item for current index, as user whether pens should be resolved and * delegates actual processing to doMoveSelectionToLayer method where selected entities are moved or * copied to the provided layer * @param duplicate if true, duplicate should be performed, if false - move of entities * @param title title for user choice dialog */ void LC_LayerTreeWidget::moveOrDuplicateSelectionToSelectedItemLayer(bool duplicate, const QString &title){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *layerItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); if (!layerItem->isLocked()){ if (!layerItem->isVirtual()){ bool resolvePens = QMessageBox::question(this, title, QMessageBox::tr("Replace \"By Layer\" value to source layers values?\n\n" "If Yes - entities with \"By Layer\" pens will look on new layer exactly as on previous layers and " "\"By Layer\" value will be replaced by resolved pens.\n\n" "If No - \"By Layer\" values remains and so pen of target layer will define pen for such entities."), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; doMoveSelectionToLayer(layerItem, duplicate, resolvePens); } } } } // --------------- Layer type conversion -------------- /** * Converts selected layer to dimensional layer type */ void LC_LayerTreeWidget::convertLayerTypeToDimensional(){ doConvertSelectedItemLayerToNewType(LC_LayerTreeItem::DIMENSIONAL); } /** * Converts selected layer to informational layer type */ void LC_LayerTreeWidget::convertLayerTypeToInformational(){ doConvertSelectedItemLayerToNewType(LC_LayerTreeItem::INFORMATIONAL); } /** * Converts selected layer to alternative position layer type */ void LC_LayerTreeWidget::convertLayerTypeToAlternativePosition(){ doConvertSelectedItemLayerToNewType(LC_LayerTreeItem::ALTERNATE_POSITION); } /** * Converts secondary layer to normal type */ void LC_LayerTreeWidget::convertLayerTypeToNormal(){ doConvertSelectedItemLayerToNewType(LC_LayerTreeItem::NORMAL); } /** * Utility method for actual conversion of selected item to new layer type. * Just relies on the model for renames, and fires model update * @param newType */ void LC_LayerTreeWidget::doConvertSelectedItemLayerToNewType(int newType){ QModelIndex selectedItem = getSelectedItemIndex(); bool converted = m_layerTreeModel->convertToType(selectedItem, newType); if (converted){ m_layerList->fireEdit(nullptr); } } //----------- Layers removal -------- /** * Removes layers (including descendants) for selected item */ void LC_LayerTreeWidget::removeLayersForSelectedItem(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); doRemoveLayersFromSource(currentItem, false); } } /** * Removes descendants for selected layer item. Selected layer survives */ void LC_LayerTreeWidget::removeChildLayersForSelected(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); doRemoveLayersFromSource(currentItem, true); } } /** * Removes active layer (as well as it descendants) */ void LC_LayerTreeWidget::removeActiveLayers(){ if (nullptr != m_layerList){ RS_Layer *activeLayer = m_layerList->getActive(); if (activeLayer){ LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForLayer(activeLayer); if (currentItem != nullptr){ doRemoveLayersFromSource(currentItem, false); } } } } //---------------- Layer Copy and Duplicate -------------- /** * Creates copy of selected layer as it descendants. Copy of layers (with name rename, if needed) as well as * layer's attributes are created, yet no entities from source layers are copied */ void LC_LayerTreeWidget::createLayerCopy(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ doCreateLayersCopy(selectedItemIndex, false); } } /** * Creates a copy of selected item and its descendants, and also creates copies for all entities that belongs * to original layers and add these entities' copies to appropriate layers copies */ void LC_LayerTreeWidget::createLayerDuplicate(){ QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()){ doCreateLayersCopy(selectedItemIndex, true); } } /** * Switches between hierarchical tree and flat view for layers list */ void LC_LayerTreeWidget::toggleFlatView(){ m_flatListMode = !m_flatListMode; m_layerTreeModel->setFlatMode(m_flatListMode); update(); updateToolBarButtons(); // TODO - add settings for expansion depth? int expandDepth = 0; expandItems(expandDepth); } /** * Utility method that updates visibility/state for buttons in toolbar according to current view mode */ void LC_LayerTreeWidget::updateToolBarButtons(){ m_btnCollapseSecondary->setVisible(!m_flatListMode); m_btnCollapseAll->setVisible(!m_flatListMode); m_btnExpandAll->setVisible(!m_flatListMode); m_btnListMode->setChecked(m_flatListMode); m_btnAddDimensional-> setVisible(!m_flatListMode); } /** * Returns model index for currently selected item * @return */ QModelIndex LC_LayerTreeWidget::getSelectedItemIndex(){ QModelIndex result; QModelIndexList selectedIndexes = m_layerTreeView->selectionModel()->selectedIndexes(); if (selectedIndexes.size() == 1){ // only one selected item is expected result = selectedIndexes.at(0); } return result; } /** * Utility method that returns list of visible layers for selected item and its descendants * @return list of layers */ QList LC_LayerTreeWidget::collectLayersForSelectedItem(){ QList layersList; QModelIndex selectedIndex = getSelectedItemIndex(); if (selectedIndex.isValid()){ LC_LayerTreeItem *layerItem = m_layerTreeModel->getItemForIndex(selectedIndex); QG_LayerTreeItemAcceptorVisible ACCEPT_VISIBLE; layerItem->collectLayers(layersList, &ACCEPT_VISIBLE, true); } return layersList; } /** * Utility method that deselects entities on locked layer. Actually, similar code is located in specific actions. * Included there for consistency and reducing dependencies to other codebase * TODO - in general, a refactoring and creation of some utility within the engine is reasonable for such kind of code * @param layer */ void LC_LayerTreeWidget::deselectEntitiesOnLockedLayer(RS_Layer *layer){ if (layer == nullptr) return; if (!layer->isLocked()) return; for (auto e: *m_document) { if (e != nullptr && e->isVisible() && e->getLayer() == layer){ e->setSelected(false); } } redrawView(); } /** * Utility method that deselects entities on given layer. * @param layer */ void LC_LayerTreeWidget::deselectEntities(RS_Layer *layer){ if (layer == nullptr) return; for (auto entity: *m_document) { if (entity != nullptr && entity->isVisible() && entity->getLayer() == layer){ entity->setSelected(false); } } redrawView(); } /** * Either makes provide layers visible and invisible, or just toggles visibility flag for given layers. * @param layersToEnable list of layers to make visible * @param layersToDisable list of layers to hide * @param toggleMode - flag whether toggle mode should be called internally. if true, list in layersToDisable is ignored */ void LC_LayerTreeWidget::manageLayersVisibilityFlag(QList &layersToEnable, QList &layersToDisable, bool toggleMode){ if (m_graphicView != nullptr){ if (toggleMode){ m_layerList->toggleFreezeMulti(layersToEnable); } else { m_layerList->setFreezeMulti(layersToEnable, layersToDisable); } m_document->updateInserts(); m_document->calculateBorders(); } } /** * Either makes provide layers locked and unlocked, or just toggles lock flag for given layers. * @param layersToEnable list of layers to lock * @param layersToDisable list of layers to unlock * @param toggleMode - flag whether toggle mode should be called internally. if true, list in layersToDisable is ignored */ void LC_LayerTreeWidget::manageLayersLockFlag(QList &layersToLockOrToggle, QList &layersToUnlock, bool toggleMode){ int count; QList layersForEntitiesUpdate; if (toggleMode){ m_layerList->toggleLockMulti(layersToLockOrToggle); count = layersToLockOrToggle.count(); layersForEntitiesUpdate = layersToLockOrToggle; } else { m_layerList->setLockMulti(layersToUnlock, layersToLockOrToggle); count = layersToUnlock.count(); layersForEntitiesUpdate = layersToUnlock; } for (int i = 0; i < count; i++) { RS_Layer *layer = layersForEntitiesUpdate.at(i); if (layer != nullptr){ deselectEntitiesOnLockedLayer(layer); } } } /** * Toggles construction flag for given list of layers. It may be changed to set/unset logic later * @param layersToBeConstruction list of layers to lock * @param layersNonConstruction not used so far * @param toggleMode - not used */ void LC_LayerTreeWidget::manageLayersConstructionFlag(QList &layersToBeConstruction, QList &layersNonConstruction, bool toggleMode){ QList layersForEntitiesUpdate; if (toggleMode){ m_layerList->toggleConstructionMulti(layersToBeConstruction); layersForEntitiesUpdate = layersToBeConstruction; } else { m_layerList->setConstructionMulti(layersNonConstruction, layersToBeConstruction); layersForEntitiesUpdate = layersNonConstruction; } int count = layersForEntitiesUpdate.count(); for (int i = 0; i < count; i++) { RS_Layer *layer = layersForEntitiesUpdate.at(i); if (layer!= nullptr){ deselectEntities(layer); } } } /** * Toggles print flag for given list of layers. It may be changed to set/unset logic later * @param layersToPrint list of layers to lock * @param layersNoPrint not used so far * @param toggleMode - not used */ void LC_LayerTreeWidget::manageLayersPrintFlag(QList &layersToPrint, QList &layersNoPrint, bool toggleMode){ QList layersForEntitiesUpdate; if (toggleMode){ m_layerList->togglePrintMulti(layersToPrint); layersForEntitiesUpdate = layersToPrint; } else{ m_layerList->setPrintMulti(layersNoPrint, layersToPrint); layersForEntitiesUpdate = layersNoPrint; } int count = layersForEntitiesUpdate.count(); for (int i = 0; i < count; i++) { RS_Layer *layer = layersForEntitiesUpdate.at(i); if (layer != nullptr){ deselectEntities(layer); } } } /** * Selects entities that belongs to provide list of layers * @param layers layers on which entities should be selected */ void LC_LayerTreeWidget::doSelectLayersEntities(QList &layers){ // NOTE: actually, the more correct location for this logic is RS_Selection class or something like that... // yet leave it for now here to reduce amount of codebase modifications. for (auto en: *m_document) { if (en != nullptr && en->isVisible() && !en->isSelected() && (!(en->getLayer() && en->getLayer()->isLocked()))){ RS_Layer *l = en->getLayer(true); if (l != nullptr && layers.contains(l)){ en->setSelected(true); } } } redrawView(); auto selectionInfo = m_document->getSelectionInfo(); LC_ActionContext* ctx = QC_ApplicationWindow::getAppWindow().get()->getActionContext(); ctx->updateSelectionWidget(selectionInfo.count, selectionInfo.length); } /** * Creates a copy of layers structure denoted by given modelIndex (selected level plus it descendants) * @param sourceIndex model index for topmost item to copy * @param duplicateEntities if true, entities on copied layers will be also duplicated */ void LC_LayerTreeWidget::doCreateLayersCopy(const QModelIndex &sourceIndex, bool duplicateEntities){ QHash layersMap = m_layerTreeModel->createLayersCopy(sourceIndex); if (layersMap.count() > 0){ QHashIterator iter{layersMap}; while (iter.hasNext()) { iter.next(); RS_Layer *sourceLayer = iter.key(); RS_Layer *copyLayer = iter.value(); copyLayerAttributes(copyLayer, sourceLayer); if (duplicateEntities){ duplicateLayerEntities(sourceLayer, copyLayer); } // that's pretty ugly that there are no batch operations in LayerList.... // it may lead to exceeding invocation of listeners m_layerList->add(copyLayer); } // just additional invocation of listeners on layer list... m_layerList->fireEdit(nullptr); } } /** * Method creates duplicates of entities on source layer on copy layer * @param sourceLayer layer which is copied * @param copyLayer created copy of source layer */ void LC_LayerTreeWidget::duplicateLayerEntities(RS_Layer *sourceLayer, RS_Layer *copyLayer){ // TODO - what about UNDO? for (RS_Entity* entity: std::as_const(*m_document)) { if (entity == nullptr) continue; RS_Layer *layer = entity->getLayer(true); if (layer != nullptr && layer == sourceLayer){ RS_Entity *duplicateEntity = entity->clone(); duplicateEntity->setLayer(copyLayer); m_document->addEntity(duplicateEntity); } } } /** * Utility method that copies attributes of source layer to its copy * @param copyLayer layer to which attributes should be copied * @param sourceLayer source layer */ void LC_LayerTreeWidget::copyLayerAttributes(RS_Layer *copyLayer, RS_Layer *sourceLayer){ copyLayer->setConstruction(sourceLayer->isConstruction()); copyLayer->setConverted(sourceLayer->isConverted()); copyLayer->setPrint(sourceLayer->isPrint()); RS_Pen sourcePen = sourceLayer->getPen(); RS_Pen pen; pen.setColor(sourcePen.getColor()); pen.setWidth(sourcePen.getWidth()); pen.setLineType(sourcePen.getLineType()); pen.setAlpha(sourcePen.getAlpha()); pen.setScreenWidth(sourcePen.getScreenWidth()); pen.setDashOffset(sourcePen.dashOffset()); // TODO - check whether flags should be actually copied there pen.setFlags(sourcePen.getFlags()); copyLayer->setPen(pen); copyLayer->freeze(sourceLayer->isFrozen()); } void LC_LayerTreeWidget::redrawView(){ if (m_graphicView != nullptr){ m_graphicView->redraw(); } } // TODO - actually this operations are good candidates for moving them into separate actions // that may be invoked not only from layers widget... at least, moving to active layer may be... /** * Moves or copies currently selected entities to the layer that is stored by provided tree item. * If items are copied, selection will be on duplicated entities, or remains on the same entities if they are moved. * Optional resolving of pens allows saving selected items appearance (for "By Layer" values) so such entities will * look on target layer in the same way as in the source one. * @param layerItem - item that denotes layer * @param duplicate - if true, entities will be copied, otherwise - moved between layers * @param resolvePend - if true, pen of original entity is resolved first and assigned to the entity that will be on target layer */ void LC_LayerTreeWidget::doMoveSelectionToLayer(LC_LayerTreeItem* layerItem, bool duplicate, bool resolvePens){ RS_Layer *targetLayer = layerItem->getLayer(); // using if there is just for a bit better performance if (duplicate){ for (auto en: *m_document) { // iterate over all entities if (en != nullptr){ if (en->isVisible() && en->isSelected() && !en->isParentSelected()){ RS_Layer *l = en->getLayer(true); if (l != nullptr && l != targetLayer){ // don't move to itself en->setSelected(false); RS_Entity *duplicateEntity = en->clone(); if (resolvePens){ // resolve pen in original entities, so "by layer" and "by block" values will be replaced by resolved values RS_Pen resolvedPen = en->getPen(true); // assigning resolved pen back to the entity's copy duplicateEntity ->setPen(resolvedPen); } // switch selection to newly duplicated entity duplicateEntity->setSelected(true); duplicateEntity->setLayer(targetLayer); m_document->addEntity(duplicateEntity); } } } } } else { for (auto en: *m_document) { if (en != nullptr){ if (en->isVisible() && en->isSelected() && !en->isParentSelected()){ RS_Layer *l = en->getLayer(true); if (l != nullptr && l != targetLayer){ if (resolvePens){ // before changing the layer, resolve pen and // set it back to the entity. RS_Pen resolvedPen = en->getPen(true); en ->setPen(resolvedPen); } en->setLayer(targetLayer); } } } } } redrawView(); } // TBD - Undo support? /** * Removes layers starting from provide source * @param source starting point for removal * @param removeChildrenOnly - if true, only children are removed, if false - source will be removed too */ void LC_LayerTreeWidget::doRemoveLayersFromSource(LC_LayerTreeItem *source, bool removeChildrenOnly){ LC_LayerTreeItemAcceptor acceptAllAcceptor; QList itemsToRemove; // prepare list of layers to be removed source->collectDescendantChildren(itemsToRemove, &acceptAllAcceptor, !removeChildrenOnly); doRemoveLayerItems(itemsToRemove); } /** * Removes list of layers denoted by the list of items. Virtual items are skipped, and confirmation dialog is shown * @param itemsToRemove items to remove layers */ void LC_LayerTreeWidget::doRemoveLayerItems(QList &itemsToRemove){ QStringList layerNames; QList layersToRemove; for (auto item: itemsToRemove) { if (!item->isVirtual()){ if (item->isZero()){ // don't let removing zero layer QMessageBox::information(this, QMessageBox::tr("Remove Layer"), QMessageBox::tr("Layer \"0\" can never be removed."), QMessageBox::Ok); } else { if (item->isLocked()){ // well, the original LayersWidget allows to remove locked layers... // so we'll do so and will not treat this separately } layerNames << item->getDisplayName(); layersToRemove << item->getLayer(); } } } // display layers removal dialog if needed if (!layerNames.empty()){ int dialogResult = invokeLayersRemovalDialog(layerNames); if (dialogResult == QMessageBox::Ok){ doRemoveLayers(layersToRemove); } } } /** * Performs actual removal of provided list of layers * @param layersToRemove list of layers to remove */ void LC_LayerTreeWidget::doRemoveLayers(QList &layersToRemove){ for (auto layer: layersToRemove) { RS_Graphic *graphic = m_document->getGraphic(); if (graphic != nullptr){ graphic->removeLayer(layer); } } m_document->updateInserts(); m_document->calculateBorders(); m_layerList->setModified(true); } // ------------ Dialogs ------------- /** * Displays layers removal confirmation dialog and returns result of it execution * * Of course, method could be moved to DialogFactory... keeping it there for minimizing affecting other codebase * * @param layerNames list of layer names to be removed * @return result of the dialog execution */ int LC_LayerTreeWidget::invokeLayersRemovalDialog(QStringList &layerNames){ // layers added, show confirmation dialog QString title(QMessageBox::tr("Remove %n layer(s)", "", layerNames.size())); QStringList text_lines = {QMessageBox::tr("Listed layers and all entities on them will be removed."), "", QMessageBox::tr("Warning: this action can NOT be undone!"),}; QStringList detail_lines = {QMessageBox::tr("Layers for removal:"), "",}; detail_lines << layerNames; QMessageBox msgBox(QMessageBox::Warning, title, text_lines.join("\n"), QMessageBox::Ok | QMessageBox::Cancel); msgBox.setDetailedText(detail_lines.join("\n")); int result = msgBox.exec(); return result; } /** * Executes layer generic layer edit dialog. If dialog accepted, applies editing results * @param pItem layer item for editing * @param edit true - editing mode, false - virtual layer rename mode */ void LC_LayerTreeWidget::invokeLayerEditOrRenameDialog(LC_LayerTreeItem *pItem, bool edit){ LC_LayerDialogEx dlg = LC_LayerDialogEx(this, QMessageBox::tr("Layer DialogEx"), m_layerTreeModel, pItem, m_layerList); RS_Layer* layer = nullptr; if (edit){ dlg.setMode(LC_LayerDialogEx::MODE_EDIT_LAYER); layer = pItem->getLayer(); dlg.setLayerType(pItem->getLayerType()); dlg.setConstruction(layer->isConstruction()); } else{ dlg.setMode(LC_LayerDialogEx::MODE_RENAME_VIRTUAL); } QString originalName = QString(pItem->getName()); QString path = m_layerTreeModel->generateLayersDisplayPathString(pItem); dlg.setLayerName(originalName); dlg.setParentPath(path); dlg.setLayer(layer); int dialogResult = dlg.exec(); if (dialogResult == QDialog::Accepted){ QString newName = dlg.getLayerName(); if (edit){ // do editing for layer // first apply generic attributes RS_Pen pen = dlg.getPen(); layer->setPen(pen); layer->setConstruction(dlg.isConstruction()); // handle rename QString layerName = dlg.getLayerName(); int editedLayerType = dlg.getEditedLayerType(); if (originalName != layerName || editedLayerType != pItem->getLayerType()){ // layer is also renamed m_layerTreeModel -> renamePrimaryLayer(pItem, layerName, editedLayerType); } m_layerList->fireEdit(nullptr); } else{ // just rename virtual layer bool renamed = m_layerTreeModel -> renameVirtualLayer(pItem, newName); if (renamed){ m_layerList->fireEdit(nullptr); } } } } /** * Displays layer dialog for adding new layers and creates new layer base on dialog data. * @param parentItem if not null - parent item, so created layer will be under this parent * @param layerType - type of layer should be created. If LC_LayerTreeItem::NOT_DEFINED_LAYER_TYPE - allows to select type */ void LC_LayerTreeWidget::invokeLayerAddDialog(LC_LayerTreeItem *parentItem, int layerType){ LC_LayerDialogEx dlg = LC_LayerDialogEx(this, QMessageBox::tr("Layer DialogEx"), m_layerTreeModel, parentItem, m_layerList); // setup dialog first int dialogMode = LC_LayerDialogEx::MODE_ADD_CHILD_LAYER; switch (layerType){ case LC_LayerTreeItem::NOT_DEFINED_LAYER_TYPE:{ if (parentItem == nullptr){ dialogMode = LC_LayerDialogEx::MODE_ADD_LAYER; } else{ dialogMode = LC_LayerDialogEx::MODE_ADD_CHILD_LAYER; } break; } case LC_LayerTreeItem::DIMENSIONAL: case LC_LayerTreeItem::INFORMATIONAL: case LC_LayerTreeItem::ALTERNATE_POSITION:{ dialogMode = LC_LayerDialogEx::MODE_ADD_SECONDARY_LAYER; break; } default:{ break; } } dlg.setMode(dialogMode); // prepare parent path QString name = ""; QString path = ""; if (parentItem != nullptr){ name = QString(parentItem->getName()); path = m_layerTreeModel->generateLayersDisplayPathString(parentItem); } dlg.setLayerName(name); dlg.setParentPath(path); dlg.setLayerType(layerType); // temporary layer for editing auto* tmpLayer = new RS_Layer(""); // if layer type is provided - we'll use default pen for it RS_Pen defaultPen = m_layerTreeModel->getOptions() ->getDefaultPen(layerType == LC_LayerTreeItem::NOT_DEFINED_LAYER_TYPE ? LC_LayerTreeItem::NORMAL : layerType); RS_Pen penCopy = RS_Pen(defaultPen); tmpLayer->setPen(penCopy); dlg.setLayer(tmpLayer); int dialogResult = dlg.exec(); if (dialogResult == QDialog::Accepted){ QString layerName = dlg.getLayerName(); int editedLayerType = dlg.getEditedLayerType(); // check for duplicate name is performed by the dialog on validate, so here we'll simply create a layer QString newLayerName = m_layerTreeModel->createFullLayerName(parentItem, layerName, editedLayerType, true); RS_Pen pen = dlg.getPen(); tmpLayer->setConstruction(dlg.isConstruction()); tmpLayer->setName(newLayerName); tmpLayer->setPen(pen); m_layerList->add(tmpLayer); // listeners will be invoked there } else{ // dialog cancelled, so remove temporary layer delete tmpLayer; } } /** * Displays settings dialog and if needed, saves setting and updates widget */ void LC_LayerTreeWidget::invokeSettingsDialog(){ LC_LayerTreeModelOptions* options = m_layerTreeModel->getOptions(); LC_LayerTreeOptionsDialog dlg = LC_LayerTreeOptionsDialog(this, options); int dialogResult = dlg.exec(); if (dialogResult == QDialog::Accepted){ options->save(); update(); } } void LC_LayerTreeWidget::updateWidgetSettings(){ LC_GROUP("Widgets"); { bool flatIcons = LC_GET_BOOL("DockWidgetsFlatIcons", true); int iconSize = LC_GET_INT("DockWidgetsIconSize", 16); QSize size(iconSize, iconSize); QList widgets = this->findChildren(); foreach(QToolButton *w, widgets) { w->setAutoRaise(flatIcons); w->setIconSize(size); } } LC_GROUP_END(); } void LC_LayerTreeWidget::setGraphicView(RS_GraphicView *gview){ m_graphicView = gview; if (gview == nullptr) { setLayerList(nullptr); m_document = nullptr; } else { auto doc = gview->getContainer()->getDocument(); setLayerList(gview->getGraphic(true)->getLayerList()); m_document = doc; } } void LC_LayerTreeWidget::exportSelectedLayer() { QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()) { LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); if (!currentItem->isVirtual()) { LC_LayersExportOptions exportOptions; auto layerToExport = currentItem->getLayer(); exportOptions.m_layers.push_back(layerToExport); auto sourceGraphic = m_document->getGraphic(); LC_ExportLayersService::exportLayers(exportOptions, sourceGraphic); } } } void LC_LayerTreeWidget::exportLayersList(QList layersToExport) { if (!layersToExport.isEmpty()) { LC_LayersExportOptions exportOptions; for (const auto layer: layersToExport) { exportOptions.m_layers.push_back(layer); } auto sourceGraphic = m_document->getGraphic(); LC_ExportLayersService::exportLayers(exportOptions, sourceGraphic); } } void LC_LayerTreeWidget::exportLayerSubTree() { QModelIndex selectedItemIndex = getSelectedItemIndex(); if (selectedItemIndex.isValid()) { LC_LayerTreeItem *currentItem = m_layerTreeModel->getItemForIndex(selectedItemIndex); LC_LayerTreeItemAcceptor ACCEPT_ALL = LC_LayerTreeItemAcceptor(); QList layersToExport; currentItem->collectLayers(layersToExport, &ACCEPT_ALL, true); exportLayersList(layersToExport); } } void LC_LayerTreeWidget::exportVisibleLayers() { auto layerItem = m_layerTreeModel->getRoot(); QG_LayerTreeItemAcceptorVisible ACCEPT_VISIBLE; QList layersToExport; layerItem->collectLayers(layersToExport, &ACCEPT_VISIBLE, false); exportLayersList(layersToExport); }