diff --git a/sources/TerminalStrip/ui/freeterminaleditor.cpp b/sources/TerminalStrip/ui/freeterminaleditor.cpp index e9863f448..b0beda5d3 100644 --- a/sources/TerminalStrip/ui/freeterminaleditor.cpp +++ b/sources/TerminalStrip/ui/freeterminaleditor.cpp @@ -41,6 +41,9 @@ FreeTerminalEditor::FreeTerminalEditor(QETProject *project, QWidget *parent) : m_model = new FreeTerminalModel(m_project, this); ui->m_table_view->setModel(m_model); ui->m_table_view->setCurrentIndex(m_model->index(0,0)); + //Selected rows can be dragged onto a strip of the terminal strip tree + ui->m_table_view->setDragEnabled(true); + ui->m_table_view->setDragDropMode(QAbstractItemView::DragOnly); if (m_project) { connect(m_project, &QObject::destroyed, this, &FreeTerminalEditor::reload); @@ -274,6 +277,14 @@ void FreeTerminalEditor::on_m_move_pb_clicked() reload(); } +/** + * @brief FreeTerminalEditor::hasPendingEdits + * @return true if cells were edited and not yet applied + */ +bool FreeTerminalEditor::hasPendingEdits() const { + return !m_model->modifiedModelRealTerminalData().isEmpty(); +} + void FreeTerminalEditor::selectionChanged() { const bool has_selection = !ui->m_table_view->selectionModel()->selectedIndexes().isEmpty(); diff --git a/sources/TerminalStrip/ui/freeterminaleditor.h b/sources/TerminalStrip/ui/freeterminaleditor.h index dc75f06c3..2320a60ba 100644 --- a/sources/TerminalStrip/ui/freeterminaleditor.h +++ b/sources/TerminalStrip/ui/freeterminaleditor.h @@ -39,6 +39,7 @@ class FreeTerminalEditor : public QWidget void reload(); void apply(); + bool hasPendingEdits() const; void setProject(QETProject *project); diff --git a/sources/TerminalStrip/ui/freeterminalmodel.cpp b/sources/TerminalStrip/ui/freeterminalmodel.cpp index e11fb6ac7..7bfe62674 100644 --- a/sources/TerminalStrip/ui/freeterminalmodel.cpp +++ b/sources/TerminalStrip/ui/freeterminalmodel.cpp @@ -16,6 +16,7 @@ along with QElectroTech. If not, see . */ #include +#include #include "freeterminalmodel.h" #include "../../elementprovider.h" @@ -239,7 +240,7 @@ QVariant FreeTerminalModel::headerData(int section, Qt::Orientation orientation, Qt::ItemFlags FreeTerminalModel::flags(const QModelIndex &index) const { - Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; + Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsDragEnabled; auto c = index.column(); if (c == LABEL_CELL || c == TYPE_CELL || c == FUNCTION_CELL) @@ -320,6 +321,73 @@ QVector > FreeTerminalModel::realTerminalForIndex(c return vector_; } +QStringList FreeTerminalModel::mimeTypes() const { + return QStringList{freeTerminalMimeType()}; +} + +/** + * @brief FreeTerminalModel::mimeData + * @param indexes + * @return the uuids of the terminal elements of the rows in @a indexes, + * to be dropped on a terminal strip of the terminal strip tree. + */ +QMimeData *FreeTerminalModel::mimeData(const QModelIndexList &indexes) const +{ + QVector uuids; + for (const auto &real_t : realTerminalForIndex(indexes)) { + uuids.append(real_t->elementUuid()); + } + return mimeDataForUuids(uuids); +} + +/** + * @brief FreeTerminalModel::freeTerminalMimeType + * @return the mime type of a drag of free terminals + */ +QString FreeTerminalModel::freeTerminalMimeType() { + return QStringLiteral("application/x-qet-free-terminal-uuids"); +} + +/** + * @brief FreeTerminalModel::uuidsFromMimeData + * @param mime_data + * @return the uuids of the terminal elements carried by @a mime_data, + * empty if it is not a drag of free terminals. + */ +QVector FreeTerminalModel::uuidsFromMimeData(const QMimeData *mime_data) +{ + QVector uuids; + if (!mime_data || !mime_data->hasFormat(freeTerminalMimeType())) { + return uuids; + } + + const auto lines = QString::fromLatin1(mime_data->data(freeTerminalMimeType())).split(QLatin1Char('\n'), Qt::SkipEmptyParts); + for (const auto &line : lines) { + const QUuid uuid(line); + if (!uuid.isNull()) { + uuids.append(uuid); + } + } + return uuids; +} + +/** + * @brief FreeTerminalModel::mimeDataForUuids + * @param uuids + * @return a new QMimeData carrying the uuids of terminal elements in @a uuids + */ +QMimeData *FreeTerminalModel::mimeDataForUuids(const QVector &uuids) +{ + QStringList lines; + for (const auto &uuid : uuids) { + lines.append(uuid.toString()); + } + + auto mime_data = new QMimeData(); + mime_data->setData(freeTerminalMimeType(), lines.join(QLatin1Char('\n')).toLatin1()); + return mime_data; +} + /** * @brief FreeTerminalModel::fillTerminalVector */ diff --git a/sources/TerminalStrip/ui/freeterminalmodel.h b/sources/TerminalStrip/ui/freeterminalmodel.h index d455832fa..7fb12568a 100644 --- a/sources/TerminalStrip/ui/freeterminalmodel.h +++ b/sources/TerminalStrip/ui/freeterminalmodel.h @@ -26,6 +26,7 @@ #include "../../qetproject.h" class RealTerminal; +class QMimeData; /** * @brief The FreeTerminalModel class @@ -61,6 +62,12 @@ class FreeTerminalModel : public QAbstractTableModel modelRealTerminalData dataAtRow(int row) const; QVector> realTerminalForIndex(const QModelIndexList &index_list) const; + QStringList mimeTypes() const override; + QMimeData *mimeData(const QModelIndexList &indexes) const override; + static QString freeTerminalMimeType(); + static QVector uuidsFromMimeData(const QMimeData *mime_data); + static QMimeData *mimeDataForUuids(const QVector &uuids); + private: void fillTerminalVector(); diff --git a/sources/TerminalStrip/ui/terminalstripeditorwindow.cpp b/sources/TerminalStrip/ui/terminalstripeditorwindow.cpp index 32beac65a..c47660bd9 100644 --- a/sources/TerminalStrip/ui/terminalstripeditorwindow.cpp +++ b/sources/TerminalStrip/ui/terminalstripeditorwindow.cpp @@ -67,6 +67,10 @@ TerminalStripEditorWindow::TerminalStripEditorWindow(QETProject *project, QWidge m_terminal_strip_editor = new TerminalStripEditor{m_project, this}; connect(m_tree_dock, &TerminalStripTreeDockWidget::currentStripChanged, this, &TerminalStripEditorWindow::currentStripChanged); + //A move reloads the free terminal table, which would lose its unapplied edits + m_tree_dock->setDropCheck([this]() { + return !m_free_terminal_editor->hasPendingEdits(); + }); ui->m_stacked_widget->insertWidget(EMPTY_PAGE, new QWidget(ui->m_stacked_widget)); ui->m_stacked_widget->insertWidget(FREE_TERMINAL_PAGE, m_free_terminal_editor); diff --git a/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp b/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp index fd81bd61e..2611ee5b8 100644 --- a/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp +++ b/sources/TerminalStrip/ui/terminalstriptreedockwidget.cpp @@ -27,7 +27,13 @@ #include "../../qetgraphicsitem/terminalelement.h" #include "../terminalstrip.h" #include "../../qetinformation.h" +#include "freeterminalmodel.h" +#include +#include +#include +#include +#include #include TerminalStripTreeDockWidget::TerminalStripTreeDockWidget(QETProject *project, QWidget *parent) : @@ -38,6 +44,12 @@ TerminalStripTreeDockWidget::TerminalStripTreeDockWidget(QETProject *project, QW setProject(project); ui->m_tree_view->expandRecursively(ui->m_tree_view->rootIndex()); + + //Free terminals are dragged from this tree or from the free terminal + //table and dropped on a strip. The tree items themselves never move, + //the tree is rebuilt from the project after the drop. + ui->m_tree_view->viewport()->setAcceptDrops(true); + ui->m_tree_view->viewport()->installEventFilter(this); } TerminalStripTreeDockWidget::~TerminalStripTreeDockWidget() @@ -365,3 +377,152 @@ void TerminalStripTreeDockWidget::setCurrentStrip(TerminalStrip *strip) m_current_strip = strip; emit currentStripChanged(strip); } + +/** + * @brief TerminalStripTreeDockWidget::setDropCheck + * @param check : called before accepting a drop of free terminals, + * a drop is refused when it returns false. + */ +void TerminalStripTreeDockWidget::setDropCheck(std::function check) { + m_drop_check = check; +} + +/** + * @brief TerminalStripTreeDockWidget::eventFilter + * Drag a free terminal of the tree, and drop free terminals + * (from the tree or from the free terminal table) on a strip. + */ +bool TerminalStripTreeDockWidget::eventFilter(QObject *watched, QEvent *event) +{ + if (watched != ui->m_tree_view->viewport()) { + return QDockWidget::eventFilter(watched, event); + } + + switch (event->type()) + { + case QEvent::MouseButtonPress: + { + auto me = static_cast(event); + m_drag_uuid = QUuid(); + auto item = ui->m_tree_view->itemAt(me->pos()); + if (me->button() == Qt::LeftButton + && item + && item->type() == Terminal + && item->parent() + && item->parent()->type() == FreeTerminal) + { + m_drag_start_pos = me->pos(); + m_drag_uuid = item->data(0, UUID_USER_ROLE).toUuid(); + } + break; + } + case QEvent::MouseMove: + { + auto me = static_cast(event); + if (!m_drag_uuid.isNull() + && (me->buttons() & Qt::LeftButton) + && (me->pos() - m_drag_start_pos).manhattanLength() >= QApplication::startDragDistance()) + { + auto drag = new QDrag(ui->m_tree_view); + drag->setMimeData(FreeTerminalModel::mimeDataForUuids({m_drag_uuid})); + drag->setPixmap(QET::Icons::ElementTerminal.pixmap(16, 16)); + m_drag_uuid = QUuid(); + drag->exec(Qt::CopyAction); + return true; + } + break; + } + case QEvent::DragEnter: + { + //Accepted wherever it enters, or no DragMove follows to reach a strip + auto de = static_cast(event); + if (!freeTerminals(de->mimeData()).isEmpty()) { + de->acceptProposedAction(); + } else { + de->ignore(); + } + return true; + } + case QEvent::DragMove: + { + auto de = static_cast(event); + if (dropAllowed(de->mimeData(), de->position().toPoint())) { + de->acceptProposedAction(); + } else { + de->ignore(); + } + return true; + } + case QEvent::Drop: + { + auto de = static_cast(event); + const auto pos = de->position().toPoint(); + if (!dropAllowed(de->mimeData(), pos)) { + de->ignore(); + return true; + } + auto strip = stripAt(pos); + const auto terminals = freeTerminals(de->mimeData()); + de->acceptProposedAction(); + + //The command rebuilds the tree (TerminalStrip::orderChanged) + m_project->undoStack()->push(new AddTerminalToStripCommand(terminals, strip)); + setSelectedStrip(strip); + return true; + } + default: + break; + } + + return QDockWidget::eventFilter(watched, event); +} + +/** + * @brief TerminalStripTreeDockWidget::stripAt + * @param pos : position in the viewport of the tree + * @return the strip of the item at @a pos, the item being the strip + * or one of its terminals, or nullptr. + */ +TerminalStrip *TerminalStripTreeDockWidget::stripAt(const QPoint &pos) const +{ + auto item = ui->m_tree_view->itemAt(pos); + if (item && item->type() == Terminal) { + item = item->parent(); + } + if (item && item->type() == Strip) { + return m_item_strip_H.value(item); + } + return nullptr; +} + +/** + * @brief TerminalStripTreeDockWidget::freeTerminals + * @param mime_data + * @return the free terminals carried by @a mime_data + * which are still free in the project. + */ +QVector> TerminalStripTreeDockWidget::freeTerminals(const QMimeData *mime_data) const +{ + QVector> terminals; + for (const auto &uuid : FreeTerminalModel::uuidsFromMimeData(mime_data)) + { + const auto real_t = m_uuid_terminal_H.value(uuid); + if (real_t && !real_t->parentStrip() && !terminals.contains(real_t)) { + terminals.append(real_t); + } + } + return terminals; +} + +/** + * @brief TerminalStripTreeDockWidget::dropAllowed + * @return true if @a mime_data carries free terminals + * and there is a strip at @a pos to drop them in. + */ +bool TerminalStripTreeDockWidget::dropAllowed(const QMimeData *mime_data, const QPoint &pos) const +{ + return m_project + && stripAt(pos) + && !freeTerminals(mime_data).isEmpty() + && (!m_drop_check || m_drop_check()); +} diff --git a/sources/TerminalStrip/ui/terminalstriptreedockwidget.h b/sources/TerminalStrip/ui/terminalstriptreedockwidget.h index 142d72f97..0600218e4 100644 --- a/sources/TerminalStrip/ui/terminalstriptreedockwidget.h +++ b/sources/TerminalStrip/ui/terminalstriptreedockwidget.h @@ -21,11 +21,15 @@ #include #include #include +#include + +#include class QETProject; class QTreeWidgetItem; class TerminalStrip; class RealTerminal; +class QMimeData; namespace Ui { class TerminalStripTreeDockWidget; @@ -58,6 +62,10 @@ class TerminalStripTreeDockWidget : public QDockWidget QString currentLocation() const; void setSelectedStrip(TerminalStrip *strip); QSharedPointer currentRealTerminal() const; + void setDropCheck(std::function check); + + protected: + bool eventFilter(QObject *watched, QEvent *event) override; signals: void currentStripChanged(TerminalStrip *strip); @@ -70,6 +78,9 @@ class TerminalStripTreeDockWidget : public QDockWidget QTreeWidgetItem* addTerminalStrip(TerminalStrip *terminal_strip); void addFreeTerminal(); void setCurrentStrip(TerminalStrip *strip); + TerminalStrip *stripAt(const QPoint &pos) const; + QVector> freeTerminals(const QMimeData *mime_data) const; + bool dropAllowed(const QMimeData *mime_data, const QPoint &pos) const; private: Ui::TerminalStripTreeDockWidget *ui; @@ -82,6 +93,9 @@ class TerminalStripTreeDockWidget : public QDockWidget QVector m_strip_changed_connection; bool m_current_is_free_terminal{false}; QMetaObject::Connection m_project_destroy_connection; + std::function m_drop_check; + QPoint m_drag_start_pos; + QUuid m_drag_uuid; }; #endif // TERMINALSTRIPTREEDOCKWIDGET_H