/*******************************************************************************
 *
 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 <QHeaderView>
#include <QtWidgets>

#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->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.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<LC_LayerTreeItem *>(layerIdx.internalPointer());

    // use translated column index to ensure proper column detection
    int column = m_layerTreeModel->translateColumn(layerIdx.column());

    QList<RS_Layer *> layers;
    QList<RS_Layer *> layersToDisable = layers;
    QList<RS_Layer *> 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:

        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){
                contextMenu->addAction(tr("&Add Child Layer"), this, &LC_LayerTreeWidget::addChildLayerForSelectedItem);
                contextMenu->addAction(tr("&Rename"), this, &LC_LayerTreeWidget::renameVirtualLayer);
                contextMenu->addAction(tr("&Remove Layers (Sub-Tree)"), this, &LC_LayerTreeWidget::removeLayersForSelectedItem);
                contextMenu->addSeparator();
                contextMenu->addAction(tr("&Copy Structure (Sub-Tree)"), this, &LC_LayerTreeWidget::createLayerCopy);
                contextMenu->addAction(tr("&Duplicate Content (Sub-Tree)"), this, &LC_LayerTreeWidget::createLayerDuplicate);
                // TODO - should we take care of virtual layer's visibility somehow?
                contextMenu->addAction(tr("&Select Entities (Sub-Tree)"), this, &LC_LayerTreeWidget::selectLayersEntities);
            } else {

                bool NON_ZERO_LAYER = !layerItem->isZero();

                contextMenu->addAction(tr("&Edit Layer &Attributes"), this, &LC_LayerTreeWidget::editSelectedLayer);
                if (NON_ZERO_LAYER){
                    contextMenu->addAction(tr("&Remove Layer"), this, &LC_LayerTreeWidget::removeLayersForSelectedItem);
                }
                contextMenu->addSeparator();

                if (layerType == LC_LayerTreeItem::NORMAL){
                    if (NON_ZERO_LAYER){
                        bool hasItems = false;

                        if (!layerItem->hasChildOfType(LC_LayerTreeItem::DIMENSIONAL)){
                            contextMenu->addAction(tr("&Add Dimensions Sub-Layer"), this, &LC_LayerTreeWidget::addDimensionalLayerForSelectedItem);
                            hasItems = true;
                        }
                        if (!layerItem->hasChildOfType(LC_LayerTreeItem::INFORMATIONAL)){
                            contextMenu->addAction(tr("&Add Info Sub-Layer"), this, &LC_LayerTreeWidget::addInformationalLayerForSelectedItem);
                            hasItems = true;
                        }
                        if (!layerItem->hasChildOfType(LC_LayerTreeItem::ALTERNATE_POSITION)){
                            contextMenu->addAction(tr("&Add Alternative View Sub-Layer"), this,
                                                   &LC_LayerTreeWidget::addAddAlternativePositionLayerForSelectedItem);
                            hasItems = true;
                        }
                        if (!m_flatListMode){
                            if (layerItem->childCount() > 0){
                                contextMenu->addAction(tr("&Remove Sub-layers"), this, &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()){
                    contextMenu->addAction(tr("&Select Layer's Entities"), this, &LC_LayerTreeWidget::selectLayersEntities);
                }
                contextMenu->addSeparator();
                contextMenu->addAction(tr("&Create Layer Copy"), this, &LC_LayerTreeWidget::createLayerCopy);
                contextMenu->addAction(tr("&Duplicate Layer With Content"), this, &LC_LayerTreeWidget::createLayerDuplicate);

                contextMenu->addSeparator();
                if (!layerItem->isLocked()){
                    contextMenu->addAction(tr("Move Selection to Layer"), this, &LC_LayerTreeWidget::moveSelectionToLayer);
                    contextMenu->addAction(tr("Duplicate Selection to Layer"), this, &LC_LayerTreeWidget::duplicateSelectionToLayer);
                    contextMenu->addSeparator();
                }
            }
            contextMenu->addSeparator();
        } else {
            // click is not on item
            contextMenu->addAction(tr("&Add Layer"), this, &LC_LayerTreeWidget::addLayer);
        }
        contextMenu->addAction(tr("&Freeze Others Layers"), this, &LC_LayerTreeWidget::hideOtherThanSelectedLayers);
        contextMenu->addAction(tr("&Defreeze All Layers"), this, &LC_LayerTreeWidget::showAllLayers);
        contextMenu->addAction(tr("&Freeze All Layers"), this, &LC_LayerTreeWidget::hideAllLayers);
        contextMenu->addAction(tr("&Unlock All Layers"), this, &LC_LayerTreeWidget::unlockAllLayers);
        contextMenu->addAction(tr("&Lock All Layers"), this, &LC_LayerTreeWidget::lockAllLayers);
        contextMenu->addAction(tr("Enable &Printing All Layers"), this, &LC_LayerTreeWidget::printAllLayers);
        contextMenu->addAction(tr("&Disable Printing All Layers"), this, &LC_LayerTreeWidget::noPrintAllLayers);
        contextMenu->addSeparator();
        if (index.isValid()) {
            contextMenu->addAction(tr("&Export Single Layer"), this,&LC_LayerTreeWidget::exportSelectedLayer);
            if (!m_flatListMode){
                LC_LayerTreeItem *layerItem = m_layerTreeModel->getItemForIndex(index);
                if (layerItem->childCount() > 0){
                    contextMenu->addAction(tr("&Export Layer Sub-Tree"), this,&LC_LayerTreeWidget::exportLayerSubTree);
                }
            }
        }
        contextMenu->addAction(tr("Export &Visible Layer(s)"), this, &LC_LayerTreeWidget::exportVisibleLayers);
        contextMenu->addSeparator();
        contextMenu->addAction(tr("&Find And Remove Empty Layers"), this, &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<RS_Layer *> layersToShow = m_layerTreeModel->collectLayers(&ACCEPT_ALL);
    QList<RS_Layer *> 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<RS_Layer *> layersToHide = m_layerTreeModel->collectLayers(&ACCEPT_ALL_EXCEPT);
            QList<RS_Layer *> layersToShow;
            layersToShow << activeLayer;

            manageLayersVisibilityFlag(layersToShow, layersToHide, false);
        }
    }
}
/**
 * Makes all layers invisible
 */
void LC_LayerTreeWidget::hideAllLayers(){
    m_btnShowSecondaryLayers->setChecked(false);
    LC_LayerTreeItemAcceptor ACCEPT_ALL;
    QList<RS_Layer *> layersToShow;
    QList<RS_Layer *> 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<RS_Layer *> layersToHide = m_layerTreeModel->collectLayers(&ACCEPT_ALL);

        QList<RS_Layer *> 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<LC_LayerTreeItem *> secondaryItems;
    QG_LayerTreeItemAcceptorSecondary acceptor;
    root->collectDescendantChildren(secondaryItems, &acceptor, false);

    bool showSecondary = m_btnShowSecondaryLayers->isChecked();
    int count = secondaryItems.count();

    if (count > 0){
        QList<RS_Layer *> layersToEnable;
        QList<RS_Layer *> 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<RS_Layer *> layersToLock;
    QList<RS_Layer *> layersToUnlock = m_layerTreeModel->collectLayers(&ACCEPT_ALL);
    manageLayersLockFlag(layersToLock, layersToUnlock, false);
}
/**
 * All matched layers become locked
 */
void LC_LayerTreeWidget::lockAllLayers(){
    LC_LayerTreeItemAcceptor ACCEPT_ALL;
    QList<RS_Layer *> layersToLock = m_layerTreeModel->collectLayers(&ACCEPT_ALL);
    QList<RS_Layer *> layersToUnlock;
    manageLayersLockFlag(layersToLock, layersToUnlock, false);
}
/**
 * All matched layers will be printable
 */
void LC_LayerTreeWidget::printAllLayers(){
    LC_LayerTreeItemAcceptor ACCEPT_ALL;
    QList<RS_Layer *> layersToPrint = m_layerTreeModel->collectLayers(&ACCEPT_ALL);
    QList<RS_Layer *> layersToNoPrint;
    manageLayersPrintFlag(layersToPrint, layersToNoPrint, false);
}

/**
 * All matched layers will be not printable
 */
void LC_LayerTreeWidget::noPrintAllLayers(){
    LC_LayerTreeItemAcceptor ACCEPT_ALL;
    QList<RS_Layer *> layersToPrint;
    QList<RS_Layer *> 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<RS_Layer*> 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<RS_Layer*> 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<LC_LayerTreeItem*> 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<<item;
            }
        }

        if (emptyLayerIsFiltered){ // not too good to remove something not visible, so let the user one more chance to check
            QString title(QMessageBox::tr("Remove empty layers"));
            QString text = QMessageBox::tr("Layer(s) without entities found, yet they are filtered and not visible.\n\nClear filtering mask and repeat.");
            QMessageBox msgBox(QMessageBox::Information, title, text, QMessageBox::Ok);
            msgBox.exec();
        }
        else{// do actual removal
            doRemoveLayerItems(itemsToRemove);
        }
    }
    else{ // no layers with no entities, just show message about that
        QString title(QMessageBox::tr("Remove empty layers"));
        QString text = QMessageBox::tr("No layers without entities found, nothing to remove.");

        QMessageBox msgBox(QMessageBox::Information, title, text, QMessageBox::Ok);
        msgBox.exec();
    }
}

/**
 * Escape releases focus.
 */
void LC_LayerTreeWidget::keyPressEvent(QKeyEvent *e){
    switch (e->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<RS_Layer *> 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<RS_Layer *> LC_LayerTreeWidget::collectLayersForSelectedItem(){
    QList<RS_Layer *> 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<RS_Layer *> &layersToEnable, QList<RS_Layer *> &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<RS_Layer *> &layersToLockOrToggle, QList<RS_Layer *> &layersToUnlock, bool toggleMode){
    int count;
    QList<RS_Layer *> 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<RS_Layer *> &layersToBeConstruction, QList<RS_Layer *> &layersNonConstruction, bool toggleMode){
    QList<RS_Layer *> 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<RS_Layer *> &layersToPrint, QList<RS_Layer *> &layersNoPrint, bool toggleMode){
    QList<RS_Layer *> 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<RS_Layer *> &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();

    RS_DIALOGFACTORY->updateSelectionWidget(m_document->countSelected(), m_document->totalSelectedLength());
}

/**
 * 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<RS_Layer *, RS_Layer *> layersMap = m_layerTreeModel->createLayersCopy(sourceIndex);
    if (layersMap.count() > 0){
        QHashIterator<RS_Layer *, RS_Layer *> 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 (auto entity: *m_document) {
        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<LC_LayerTreeItem *> 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<LC_LayerTreeItem *> &itemsToRemove){
    QStringList layerNames;
    QList<RS_Layer *> 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<RS_Layer *> &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<QToolButton *> widgets = this->findChildren<QToolButton *>();
        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(doc->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<RS_Layer*> 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<RS_Layer*> layersToExport;
        currentItem->collectLayers(layersToExport, &ACCEPT_ALL, true);
        exportLayersList(layersToExport);
    }
}

void LC_LayerTreeWidget::exportVisibleLayers() {
    auto layerItem = m_layerTreeModel->getRoot();
    QG_LayerTreeItemAcceptorVisible ACCEPT_VISIBLE;
    QList<RS_Layer*> layersToExport;
    layerItem->collectLayers(layersToExport, &ACCEPT_VISIBLE, false);
    exportLayersList(layersToExport);
}
