mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-01 15:24:13 +02:00
Add drag and drop of free terminals onto a terminal strip (#409)
In the terminal strip manager, free terminals could only be moved into a strip with the "move to" list and its button. Dragging one onto a strip, which is what users try first (#409), did nothing. Free terminals can now be dragged onto a strip in the tree on the left, from either place they are listed: - the free terminal table: every selected row is moved; - the tree itself: the dragged free terminal is moved. Dropping on a strip, or on a terminal already in that strip, moves them with the same undo command as the button, so one undo puts them back. The strip is then selected, showing the terminals just added. A drop anywhere else is refused. A drop is also refused while the free terminal table has edits that are not applied yet: moving reloads that table and would lose them. The button is disabled in the same case. The drag carries the uuids of the terminal elements under its own mime type, and the tree only moves terminals that are still free in the project when the drop happens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -39,6 +39,7 @@ class FreeTerminalEditor : public QWidget
|
||||
|
||||
void reload();
|
||||
void apply();
|
||||
bool hasPendingEdits() const;
|
||||
|
||||
void setProject(QETProject *project);
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <QComboBox>
|
||||
#include <QMimeData>
|
||||
|
||||
#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<QSharedPointer<RealTerminal> > 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<QUuid> 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<QUuid> FreeTerminalModel::uuidsFromMimeData(const QMimeData *mime_data)
|
||||
{
|
||||
QVector<QUuid> 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<QUuid> &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
|
||||
*/
|
||||
|
||||
@@ -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<QSharedPointer<RealTerminal>> realTerminalForIndex(const QModelIndexList &index_list) const;
|
||||
|
||||
QStringList mimeTypes() const override;
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
static QString freeTerminalMimeType();
|
||||
static QVector<QUuid> uuidsFromMimeData(const QMimeData *mime_data);
|
||||
static QMimeData *mimeDataForUuids(const QVector<QUuid> &uuids);
|
||||
|
||||
private:
|
||||
void fillTerminalVector();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -27,7 +27,13 @@
|
||||
#include "../../qetgraphicsitem/terminalelement.h"
|
||||
#include "../terminalstrip.h"
|
||||
#include "../../qetinformation.h"
|
||||
#include "freeterminalmodel.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDrag>
|
||||
#include <QDropEvent>
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QUuid>
|
||||
|
||||
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<bool ()> 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<QMouseEvent *>(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<QMouseEvent *>(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<QDragEnterEvent *>(event);
|
||||
if (!freeTerminals(de->mimeData()).isEmpty()) {
|
||||
de->acceptProposedAction();
|
||||
} else {
|
||||
de->ignore();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case QEvent::DragMove:
|
||||
{
|
||||
auto de = static_cast<QDragMoveEvent *>(event);
|
||||
if (dropAllowed(de->mimeData(), de->position().toPoint())) {
|
||||
de->acceptProposedAction();
|
||||
} else {
|
||||
de->ignore();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case QEvent::Drop:
|
||||
{
|
||||
auto de = static_cast<QDropEvent *>(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<QSharedPointer<RealTerminal>> TerminalStripTreeDockWidget::freeTerminals(const QMimeData *mime_data) const
|
||||
{
|
||||
QVector<QSharedPointer<RealTerminal>> 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());
|
||||
}
|
||||
|
||||
@@ -21,11 +21,15 @@
|
||||
#include <QDockWidget>
|
||||
#include <QPointer>
|
||||
#include <QHash>
|
||||
#include <QUuid>
|
||||
|
||||
#include <functional>
|
||||
|
||||
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<RealTerminal> currentRealTerminal() const;
|
||||
void setDropCheck(std::function<bool()> 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<QSharedPointer<RealTerminal>> 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<QMetaObject::Connection> m_strip_changed_connection;
|
||||
bool m_current_is_free_terminal{false};
|
||||
QMetaObject::Connection m_project_destroy_connection;
|
||||
std::function<bool()> m_drop_check;
|
||||
QPoint m_drag_start_pos;
|
||||
QUuid m_drag_uuid;
|
||||
};
|
||||
|
||||
#endif // TERMINALSTRIPTREEDOCKWIDGET_H
|
||||
|
||||
Reference in New Issue
Block a user