add dashboards
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
#include "dashboardwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCloseEvent>
|
||||
#include <QDrag>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDragLeaveEvent>
|
||||
#include <QDropEvent>
|
||||
#include <QFrame>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QSettings>
|
||||
#include <QSplitter>
|
||||
#include <QToolButton>
|
||||
#include <QUuid>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtMath>
|
||||
|
||||
#include <functional>
|
||||
|
||||
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<void(DashboardTile *)> closeRequested;
|
||||
std::function<void(const QString &, DashboardTile *)> 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<QMouseEvent *>(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<QMouseEvent *>(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<QWidget *> DashboardWindow::toolWindows(const QString &toolId) const
|
||||
{
|
||||
QList<QWidget *> 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<QByteArray> 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<double>(tiles.size())));
|
||||
const int rows = qCeil(static_cast<double>(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);
|
||||
}
|
||||
Reference in New Issue
Block a user