#include "dashboardwindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { const char *TileMimeType = "application/x-giescan-dashboard-tile"; } class DashboardTile : public QFrame { public: explicit DashboardTile(QWidget *tool, const QString &toolId, QWidget *parent = nullptr) : QFrame(parent), tool(tool), dropOverlay(nullptr), identifier(QUuid::createUuid().toString(QUuid::WithoutBraces)) { setAcceptDrops(true); setFrameShape(QFrame::StyledPanel); setMinimumSize(120, 90); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setProperty("dashboardToolId", toolId); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); layout->setSizeConstraint(QLayout::SetNoConstraint); titleBar = new QWidget(this); titleBar->setObjectName("dashboardTileTitleBar"); titleBar->setCursor(Qt::OpenHandCursor); titleBar->setContextMenuPolicy(Qt::PreventContextMenu); titleBar->installEventFilter(this); QHBoxLayout *titleLayout = new QHBoxLayout(titleBar); titleLayout->setContentsMargins(8, 2, 2, 2); titleLayout->setSpacing(6); QLabel *title = new QLabel(tool->windowTitle().isEmpty() ? toolId : tool->windowTitle(), titleBar); title->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); title->setAttribute(Qt::WA_TransparentForMouseEvents); titleLayout->addWidget(title); QToolButton *closeButton = new QToolButton(titleBar); closeButton->setText(QString::fromUtf8("×")); closeButton->setToolTip(QObject::tr("Remove from dashboard")); closeButton->setAutoRaise(true); closeButton->setFixedSize(24, 24); titleLayout->addWidget(closeButton); layout->addWidget(titleBar); tool->setWindowFlags(Qt::Widget); tool->setMinimumSize(0, 0); tool->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); tool->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); layout->addWidget(tool, 1); tool->show(); dropOverlay = new QFrame(this); dropOverlay->setAttribute(Qt::WA_TransparentForMouseEvents); dropOverlay->setStyleSheet( "QFrame { background-color: rgba(40, 120, 210, 105); border: 3px solid palette(highlight); }" "QLabel { color: white; background: transparent; border: none; font-weight: bold; }"); QVBoxLayout *overlayLayout = new QVBoxLayout(dropOverlay); QLabel *dropLabel = new QLabel(QObject::tr("Drop here"), dropOverlay); dropLabel->setAlignment(Qt::AlignCenter); overlayLayout->addWidget(dropLabel); dropOverlay->setGeometry(rect()); dropOverlay->hide(); connect(closeButton, &QToolButton::clicked, this, [this]() { if (closeRequested) closeRequested(this); }); } QWidget *toolWidget() const { return tool; } QWidget *takeToolWidget() { QWidget *result = tool; if (result) { layout()->removeWidget(result); result->setParent(nullptr); } tool = nullptr; return result; } QString tileId() const { return identifier; } std::function closeRequested; std::function tileDropped; protected: bool eventFilter(QObject *watched, QEvent *event) override { if (watched != titleBar) return QFrame::eventFilter(watched, event); if (event->type() == QEvent::ContextMenu) return true; if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *mouseEvent = static_cast(event); if (mouseEvent->button() == Qt::LeftButton) { dragStart = mouseEvent->pos(); titleBar->setCursor(Qt::ClosedHandCursor); } } else if (event->type() == QEvent::MouseButtonRelease) { titleBar->setCursor(Qt::OpenHandCursor); } else if (event->type() == QEvent::MouseMove) { QMouseEvent *mouseEvent = static_cast(event); if ((mouseEvent->buttons() & Qt::LeftButton) && (mouseEvent->pos() - dragStart).manhattanLength() >= QApplication::startDragDistance()) { QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; mimeData->setData(TileMimeType, identifier.toUtf8()); drag->setMimeData(mimeData); drag->exec(Qt::MoveAction); titleBar->setCursor(Qt::OpenHandCursor); return true; } } return QFrame::eventFilter(watched, event); } void dragEnterEvent(QDragEnterEvent *event) override { if (event->mimeData()->hasFormat(TileMimeType)) { dropOverlay->setGeometry(rect()); dropOverlay->raise(); dropOverlay->show(); event->acceptProposedAction(); } } void dragLeaveEvent(QDragLeaveEvent *event) override { dropOverlay->hide(); event->accept(); } void dropEvent(QDropEvent *event) override { if (!event->mimeData()->hasFormat(TileMimeType)) return; dropOverlay->hide(); if (tileDropped) tileDropped(QString::fromUtf8(event->mimeData()->data(TileMimeType)), this); event->acceptProposedAction(); } void resizeEvent(QResizeEvent *event) override { QFrame::resizeEvent(event); dropOverlay->setGeometry(rect()); } private: QWidget *tool; QWidget *titleBar; QFrame *dropOverlay; QString identifier; QPoint dragStart; }; DashboardWindow::DashboardWindow(const QString &dashboardId, const QString &dashboardName, QWidget *parent) : QMainWindow(parent), id(dashboardId), name(dashboardName), workspace(new QWidget(this)), workspaceLayout(new QVBoxLayout(workspace)), outerSplitter(nullptr) { setAttribute(Qt::WA_DeleteOnClose, false); workspaceLayout->setContentsMargins(2, 2, 2, 2); workspaceLayout->setSpacing(0); workspaceLayout->setSizeConstraint(QLayout::SetNoConstraint); workspace->setMinimumSize(0, 0); setCentralWidget(workspace); setDashboardName(name); QSettings settings; settings.beginGroup("Dashboards/" + id); const QByteArray geometry = settings.value("Geometry").toByteArray(); if (geometry.isEmpty()) resize(1200, 760); else restoreGeometry(geometry); settings.endGroup(); } DashboardWindow::~DashboardWindow() { saveLayout(); } QString DashboardWindow::dashboardId() const { return id; } QString DashboardWindow::dashboardName() const { return name; } void DashboardWindow::setDashboardName(const QString &dashboardName) { name = dashboardName; setWindowTitle(tr("Dashboard — %1").arg(name)); } void DashboardWindow::addToolWindow(QWidget *widget, const QString &toolId, const QRect &savedGeometry) { Q_UNUSED(savedGeometry); if (!widget) return; DashboardTile *tile = new DashboardTile(widget, toolId, workspace); tile->closeRequested = [this](DashboardTile *requestedTile) { closeTile(requestedTile); }; tile->tileDropped = [this](const QString &sourceId, DashboardTile *target) { moveTile(sourceId, target); }; tiles.append(tile); rebuildLayout(); } bool DashboardWindow::removeToolWindow(QWidget *widget) { for (int i = 0; i < tiles.size(); ++i) { DashboardTile *tile = tiles.at(i); if (tile->toolWidget() != widget) continue; tiles.removeAt(i); tile->takeToolWidget(); tile->deleteLater(); widget->setWindowFlags(Qt::Window); rebuildLayout(); return true; } return false; } QList DashboardWindow::toolWindows(const QString &toolId) const { QList result; for (DashboardTile *tile : tiles) { if (tile->property("dashboardToolId").toString() == toolId && tile->toolWidget()) result.append(tile->toolWidget()); } return result; } void DashboardWindow::closeTile(DashboardTile *tile) { if (!tile) return; QWidget *tool = tile->toolWidget(); tiles.removeOne(tile); if (tool && tool->property("dashboardPersistent").toBool()) { tile->takeToolWidget(); tool->setWindowFlags(Qt::Window); tool->hide(); } tile->deleteLater(); rebuildLayout(); } void DashboardWindow::moveTile(const QString &sourceId, DashboardTile *target) { int sourceIndex = -1; const int targetIndex = tiles.indexOf(target); for (int i = 0; i < tiles.size(); ++i) { if (tiles.at(i)->tileId() == sourceId) { sourceIndex = i; break; } } if (sourceIndex < 0 || targetIndex < 0 || sourceIndex == targetIndex) return; tiles.swapItemsAt(sourceIndex, targetIndex); rebuildLayout(true); } void DashboardWindow::rebuildLayout(bool preserveSizes) { QByteArray outerState; QList rowStates; if (preserveSizes && outerSplitter) { outerState = outerSplitter->saveState(); for (QSplitter *row : rowSplitters) rowStates.append(row->saveState()); } for (DashboardTile *tile : tiles) tile->setParent(workspace); while (QLayoutItem *item = workspaceLayout->takeAt(0)) { if (item->widget()) delete item->widget(); delete item; } outerSplitter = nullptr; rowSplitters.clear(); if (tiles.isEmpty()) return; outerSplitter = new QSplitter(Qt::Vertical, workspace); outerSplitter->setChildrenCollapsible(false); outerSplitter->setHandleWidth(5); workspaceLayout->addWidget(outerSplitter); const int columns = qCeil(qSqrt(static_cast(tiles.size()))); const int rows = qCeil(static_cast(tiles.size()) / columns); int tileIndex = 0; for (int rowIndex = 0; rowIndex < rows; ++rowIndex) { QSplitter *row = new QSplitter(Qt::Horizontal, outerSplitter); row->setChildrenCollapsible(false); row->setHandleWidth(5); rowSplitters.append(row); const int remaining = tiles.size() - tileIndex; const int tilesInRow = qMin(columns, remaining); for (int column = 0; column < tilesInRow; ++column) { DashboardTile *tile = tiles.at(tileIndex++); row->addWidget(tile); tile->show(); } outerSplitter->addWidget(row); } if (preserveSizes) { outerSplitter->restoreState(outerState); for (int i = 0; i < qMin(rowSplitters.size(), rowStates.size()); ++i) rowSplitters.at(i)->restoreState(rowStates.at(i)); } } void DashboardWindow::restoreLayout() { if (!outerSplitter) return; QSettings settings; settings.beginGroup("Dashboards/" + id); outerSplitter->restoreState(settings.value("OuterSplitter").toByteArray()); const int count = settings.beginReadArray("RowSplitters"); for (int i = 0; i < qMin(count, rowSplitters.size()); ++i) { settings.setArrayIndex(i); rowSplitters.at(i)->restoreState(settings.value("State").toByteArray()); } settings.endArray(); settings.endGroup(); } void DashboardWindow::saveLayout() { QSettings settings; settings.beginGroup("Dashboards/" + id); settings.setValue("Name", name); settings.setValue("Geometry", saveGeometry()); if (outerSplitter) settings.setValue("OuterSplitter", outerSplitter->saveState()); settings.beginWriteArray("RowSplitters", rowSplitters.size()); for (int i = 0; i < rowSplitters.size(); ++i) { settings.setArrayIndex(i); settings.setValue("State", rowSplitters.at(i)->saveState()); } settings.endArray(); settings.beginWriteArray("Windows", tiles.size()); for (int i = 0; i < tiles.size(); ++i) { settings.setArrayIndex(i); settings.setValue("Tool", tiles.at(i)->property("dashboardToolId").toString()); } settings.endArray(); settings.endGroup(); } void DashboardWindow::closeEvent(QCloseEvent *event) { saveLayout(); QMainWindow::closeEvent(event); }