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