From ba9e5b2944e211641e6bd6fa8a34f781a855affb Mon Sep 17 00:00:00 2001 From: Linloir <3145078758@qq.com> Date: Sun, 5 Dec 2021 09:05:54 +0800 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 43 ++ GraphBuilder.pro | 42 ++ LICENSE | 21 + customScrollContainer.cpp | 410 ++++++++++++++ customScrollContainer.h | 144 +++++ customWidgets.cpp | 828 ++++++++++++++++++++++++++++ customWidgets.h | 258 +++++++++ graph_implement.cpp | 882 ++++++++++++++++++++++++++++++ graph_implement.h | 216 ++++++++ graph_view.cpp | 1085 +++++++++++++++++++++++++++++++++++++ graph_view.h | 371 +++++++++++++ icons.qrc | 10 + icons/back.svg | 1 + icons/create.png | Bin 0 -> 2127 bytes icons/create.svg | 1 + icons/layers.svg | 1 + icons/open.png | Bin 0 -> 2172 bytes icons/open.svg | 1 + icons/settings.svg | 1 + logo.ico | Bin 0 -> 16958 bytes main.cpp | 14 + mainwindow.cpp | 449 +++++++++++++++ mainwindow.h | 67 +++ mainwindow.ui | 307 +++++++++++ mycanvas.cpp | 462 ++++++++++++++++ mycanvas.h | 61 +++ slidepage.cpp | 164 ++++++ slidepage.h | 80 +++ 29 files changed, 5921 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 GraphBuilder.pro create mode 100644 LICENSE create mode 100644 customScrollContainer.cpp create mode 100644 customScrollContainer.h create mode 100644 customWidgets.cpp create mode 100644 customWidgets.h create mode 100644 graph_implement.cpp create mode 100644 graph_implement.h create mode 100644 graph_view.cpp create mode 100644 graph_view.h create mode 100644 icons.qrc create mode 100644 icons/back.svg create mode 100644 icons/create.png create mode 100644 icons/create.svg create mode 100644 icons/layers.svg create mode 100644 icons/open.png create mode 100644 icons/open.svg create mode 100644 icons/settings.svg create mode 100644 logo.ico create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui create mode 100644 mycanvas.cpp create mode 100644 mycanvas.h create mode 100644 slidepage.cpp create mode 100644 slidepage.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5291a38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.dll +*.dylib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* diff --git a/GraphBuilder.pro b/GraphBuilder.pro new file mode 100644 index 0000000..89dede9 --- /dev/null +++ b/GraphBuilder.pro @@ -0,0 +1,42 @@ +QT += core gui +QT += svg + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +CONFIG += c++11 + +# You can make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + customScrollContainer.cpp \ + customWidgets.cpp \ + graph_implement.cpp \ + graph_view.cpp \ + main.cpp \ + mainwindow.cpp \ + mycanvas.cpp \ + slidepage.cpp + +HEADERS += \ + customScrollContainer.h \ + customWidgets.h \ + graph_implement.h \ + graph_view.h \ + mainwindow.h \ + mycanvas.h \ + slidepage.h + +FORMS += \ + mainwindow.ui + +RC_ICONS = logo.ico + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +RESOURCES += \ + icons.qrc diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..919570a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Linloir + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/customScrollContainer.cpp b/customScrollContainer.cpp new file mode 100644 index 0000000..079609f --- /dev/null +++ b/customScrollContainer.cpp @@ -0,0 +1,410 @@ +#include "customScrollContainer.h" + +ScrollAreaCustom::ScrollAreaCustom(QWidget *parent) : QWidget(parent) +{ + //initialize list container and timer + container = new ScrollListContainer(this); + container->move(0, 0); + container->resize(this->width(), 3); + getCord = new QTimer; + getCord->setSingleShot(true); + rfrshView = new QTimer; + getCord->setSingleShot(true); + + indicator = new ScrollIndicator(this); + indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height())); + indicator->move(this->width() - indicator->width() - 3, 0); + + this->setMouseTracking(true); + container->setMouseTracking(true); + indicator->setMouseTracking(true); + + bounce = new QPropertyAnimation(container, "pos"); + + QObject::connect(getCord, SIGNAL(timeout()), this, SLOT(updateSpd())); + QObject::connect(rfrshView, SIGNAL(timeout()), this, SLOT(scrollContainer())); + QObject::connect(indicator, SIGNAL(scrollPage(int)), this, SLOT(scrollIndicator(int))); +} + +void ScrollAreaCustom::paintEvent(QPaintEvent *event){ + container->resize(this->width(), container->height()); + if(container->height() > this->height() && container->y() < this->height() - container->height() && curSpd == 0 && bounce->state() == QAbstractAnimation::Stopped) + container->move(container->x(), this->height() - container->height()); + if(container->height() <= this->height()){ + container->move(container->x(), 0); + indicator->hide(); + } + else{ + indicator->show(); + } + indicator->resize(indicator->width(), (int)((double)this->height() * this->height() / (double)container->height())); + indicator->move(this->width() - indicator->width() - 3, -container->y() * this->height() / container->height()); +} + +void ScrollAreaCustom::mousePressEvent(QMouseEvent *event){ + if(container->height() > this->height()){ + if(container->y() <= 0 && container->y() + container->height() >= this->height()) + pressed = true; + lastY = event->pos().y(); + } + getCord->stop(); + rfrshView->stop(); + curSpd = 0; + outOfEdge = false; + moveStored = 0; + nextMove = 1; +} + +void ScrollAreaCustom::mouseMoveEvent(QMouseEvent *event){ + setCursor(Qt::ArrowCursor); + if(pressed){ + //start scroll + if(!getCord->isActive() && event->pos().y() - lastY != 0){ + //start 30ms timer + getCord->start(30); + strtY = event->pos().y(); + } + if(container->y() <= 0 && container->y() + container->height() >= this->height()) + container->move(container->x(), container->y() + event->pos().y() - lastY); + else{ + if(!outOfEdge){ + bfEdgeY = event->pos().y(); + container->move(container->x(), container->y() + event->pos().y() - lastY); + outOfEdge = true; + } + else{ + int pos = container->y() >= 0 ? 1 : -1; + int dp = event->pos().y() - bfEdgeY; + if(dp == 0){ + outOfEdge = false; + nextMove = 1; + moveStored = 0; + if(container->y() >= 0) + container->move(container->x(), 0); + else + container->move(container->x(), this->height() - container->height()); + } + else if(dp / abs(dp) != pos){ + outOfEdge = false; + container->move(container->x(), this->y() + event->pos().y() - bfEdgeY); + nextMove = 1; + moveStored = 0; + } + else{ + while(abs(moveStored) + nextMove <= abs(event->pos().y() - bfEdgeY)){ + moveStored += nextMove * pos; + container->move(container->x(), container->y() + pos); + nextMove++; + } + while(nextMove > 1 && abs(moveStored) > abs(event->pos().y() - bfEdgeY)){ + nextMove--; + moveStored -= nextMove * pos; + container->move(container->x(), container->y() - pos); + } + if(moveStored == 0){ + outOfEdge = false; + if(container->y() >= 0) + container->move(container->x(), 0); + else + container->move(container->x(), this->height() - container->height()); + nextMove = 1; + moveStored = 0; + } + } + } + } + lastY = event->pos().y(); + } +} + +void ScrollAreaCustom::mouseReleaseEvent(QMouseEvent *event){ + //start scrolling + if(container->y() > 0 || container->y() + container->height() < this->height()) + bounceBack(); + else + rfrshView->start(30); + pressed = false; +} + +void ScrollAreaCustom::bounceBack(){ + rfrshView->stop(); + getCord->stop(); + bounce->setDuration(500); + bounce->setStartValue(container->pos()); + if(container->y() > 0) + bounce->setEndValue(QPoint(container->x(), 0)); + else + bounce->setEndValue(QPoint(container->x(), this->height() - container->height())); + bounce->setEasingCurve(QEasingCurve::OutQuad); + bounce->start(); +} + +void ScrollAreaCustom::scrollContainer(){ + //scroll + if(curSpd > 0){ + if(curSpd > MAXSPEED && !ignoreMaxSpeed) + curSpd = MAXSPEED; + else if(curSpd <= MAXSPEED) ignoreMaxSpeed = false; + int dp = scrollDown ? curSpd : -curSpd; + container->move(container->x(), container->y() + dp); + } + else + return; + if(container->y() <= 0 && container->y() + container->height() >= this->height()){ + curSpd -= damp; + curSpd = curSpd < 0 ? 0 : curSpd; + } + else + curSpd /= 2; + if(curSpd == 0 && (container->y() > 0 || container->y() + container->height() < this->height())) + bounceBack(); + else + rfrshView->start(30); +} + +void ScrollAreaCustom::updateSpd(){ + int spd = lastY - strtY; + scrollDown = spd >= 0; + strtY = lastY; + curSpd = abs(spd); +} + +void ScrollAreaCustom::addWidget(QWidget *newWidget, bool setAnimation){ + newWidget->setParent(container); + container->AddWidget(newWidget, setAnimation); +} + +void ScrollAreaCustom::removeWidget(QWidget *w){ + container->RemoveWidget(w); +} + +void ScrollAreaCustom::scrollToTop(){ + curSpd = sqrt(8 * (- container->pos().y()) + 2) / 2; + scrollDown = true; + getCord->stop(); + rfrshView->stop(); + outOfEdge = false; + moveStored = 0; + nextMove = 1; + ignoreMaxSpeed = true; + rfrshView->start(30); +} + +void ScrollAreaCustom::updateHeight(){ + container->updateHeight(); +} + +void ScrollAreaCustom::clear(){ + container->clear(); +} + +void ScrollAreaCustom::scrollIndicator(int dp){ + int newY = container->y() - dp * container->height() / this->height(); + if(newY > 0) + newY = 0; + else if(newY < this->height() - container->height()) + newY = this->height() - container->height(); + container->move(container->x(), newY); + update(); +} + +void ScrollAreaCustom::wheelEvent(QWheelEvent *event){ + if(container->y() > 0 || container->y() + container->height() < this->height()) + return; + curSpd += 5; + bool newDirection = event->angleDelta().y() > 0; + if(newDirection != scrollDown) + curSpd = 5; + if(curSpd > MAXSPEED) + curSpd = MAXSPEED; + scrollDown = newDirection; + if(!rfrshView->isActive()) + rfrshView->start(30); + update(); +} + +ScrollListContainer::ScrollListContainer(QWidget *parent) : QWidget(parent){} + +void ScrollListContainer::paintEvent(QPaintEvent *event){ + for(int i = 0; i < widgets.size(); i++){ + widgets[i]->resize(this->width(), widgets[i]->height()); + } +} + +void ScrollListContainer::AddWidget(QWidget *widget, bool setAnimation){ + //Add animation for all widgets current + this->resize(this->width(), this->height() + widget->height() + spacing); + widgets.push_back(widget); + size++; + ys.push_back(0); + widget->resize(this->width(), widget->height()); + widget->show(); + + if(setAnimation){ + QGraphicsOpacityEffect* widgetOpac = new QGraphicsOpacityEffect(widget); + widgetOpac->setOpacity(0); + widget->setGraphicsEffect(widgetOpac); + QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup; + QSequentialAnimationGroup* newWidgetFadeIn = new QSequentialAnimationGroup; + for(int i = 0; i < size - 1; i++){ + ys[i] += widget->height() + spacing; + QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos"); + move->setDuration(750); + move->setStartValue(widgets[i]->pos()); + move->setEndValue(QPoint(widgets[i]->x(), ys[i])); + move->setEasingCurve(QEasingCurve::InOutQuart); + dpGroup->addAnimation(move); + } + newWidgetFadeIn->addPause(300); + QPropertyAnimation* fade = new QPropertyAnimation(widgetOpac, "opacity", widget); + fade->setDuration(300); + fade->setStartValue(0); + fade->setEndValue(0.99); + newWidgetFadeIn->addAnimation(fade); + dpGroup->addAnimation(newWidgetFadeIn); + dpGroup->start(); + connect(dpGroup, &QPropertyAnimation::stateChanged, [=](){ + if(dpGroup->state() == QAbstractAnimation::Stopped){ + if(widgetOpac->opacity() != 0.99){ + fade->start(QAbstractAnimation::DeleteWhenStopped); + connect(fade,&QPropertyAnimation::finished,[=](){widgetOpac->deleteLater();}); + } + else{ + dpGroup->deleteLater(); + widgetOpac->deleteLater(); + } + } + }); + } + else{ + for(int i = 0; i < size - 1; i++){ + ys[i] += widget->height() + spacing; + widgets[i]->move(QPoint(widgets[i]->pos().x(), ys[i])); + } + } +} + +void ScrollListContainer::RemoveWidget(QWidget *widget){ + int index; + if(widget == nullptr){ + index = size - 1; + if(index != -1) + widget = widgets[index]; + } + else + index = widgets.indexOf(widget); + if(index == -1 || widget == nullptr){ + return; + } + this->resize(this->width(), this->height() - widget->height() - spacing); + this->parentWidget()->update(); + widget->hide(); + widget->setParent(nullptr); + QParallelAnimationGroup* dpGroup = new QParallelAnimationGroup; + for(int i = index - 1; i >= 0; i--){ + ys[i] -= (widget->height() + spacing); + QPropertyAnimation* move = new QPropertyAnimation(widgets[i], "pos"); + move->setDuration(750); + move->setStartValue(widgets[i]->pos()); + move->setEndValue(QPoint(widgets[i]->x(), ys[i])); + move->setEasingCurve(QEasingCurve::InOutQuart); + dpGroup->addAnimation(move); + } + dpGroup->start(QAbstractAnimation::DeleteWhenStopped); + widgets.remove(index); + size--; + ys.remove(index); +} + +void ScrollListContainer::updateHeight(){ + for(int i = size - 2; i >= 0; i--){ + ys[i] = ys[i + 1] + widgets[i + 1]->height() + spacing; + widgets[i]->move(widgets[i]->pos().x(), ys[i]); + } + this->resize(this->width(), ys[0] + widgets[0]->height() + 3); +} + +void ScrollListContainer::clear(){ + int n = size; + for(int i = 0; i < n; i++) + RemoveWidget(); +} + +ScrollIndicator::ScrollIndicator(QWidget *parent) : QWidget(parent) +{ + this->resize(defaultWidth, 0); + hovTimer = new QTimer(this); + hovTimer->setSingleShot(true); + aniPause = new QTimer(this); + aniPause->setSingleShot(true); + QObject::connect(hovTimer, SIGNAL(timeout()), this, SLOT(setHoverActive())); + this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + this->curColor = defaultColor; + + this->setMouseTracking(true); +} + +void ScrollIndicator::paintEvent(QPaintEvent *event){ + QPainter painter(this); + painter.setPen(Qt::NoPen); + painter.setBrush(curColor); + painter.drawRect(this->rect()); +} + +void ScrollIndicator::enterEvent(QEnterEvent *event){ + if(!pressed){ + hovTimer->start(100); + curColor = hoverColor; + update(); + } +} + +void ScrollIndicator::leaveEvent(QEvent *event){ + hovTimer->stop(); + curColor = defaultColor; + QPropertyAnimation* narrow = new QPropertyAnimation(this, "geometry"); + narrow->setDuration(300); + narrow->setStartValue(QRect(this->x(), this->y(), this->width(), this->height())); + narrow->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidth, this->y(), defaultWidth, this->height())); + narrow->setEasingCurve(QEasingCurve::InOutQuad); + narrow->start(QAbstractAnimation::DeleteWhenStopped); + aniPause->start(300); + update(); +} + +void ScrollIndicator::mousePressEvent(QMouseEvent *event){ + curColor = pressColor; + pressed = true; + //>note: globalPos -> globalPosition here due to deprecation + //> may cause issues + lastY = event->globalPosition().y(); + update(); +} + +void ScrollIndicator::mouseMoveEvent(QMouseEvent *event){ + if(pressed && !aniPause->isActive()){ + //>note: globalPos -> globalPosition here due to deprecation + //> may cause issues + int dp = event->globalPosition().y() - lastY; + emit scrollPage(dp); + //>note: globalPos -> globalPosition here due to deprecation + //> may cause issues + lastY = event->globalPosition().y(); + } +} + +void ScrollIndicator::mouseReleaseEvent(QMouseEvent *event){ + pressed = false; + curColor = hoverColor; + update(); +} + +void ScrollIndicator::setHoverActive(){ + QPropertyAnimation* widen = new QPropertyAnimation(this, "geometry"); + widen->setDuration(300); + widen->setStartValue(QRect(this->x(), this->y(), this->width(), this->height())); + widen->setEndValue(QRect(this->parentWidget()->width() - margin - defaultWidthAtFocus, this->y(), defaultWidthAtFocus, this->height())); + widen->setEasingCurve(QEasingCurve::InOutQuad); + widen->start(QAbstractAnimation::DeleteWhenStopped); + aniPause->start(300); +} diff --git a/customScrollContainer.h b/customScrollContainer.h new file mode 100644 index 0000000..2349021 --- /dev/null +++ b/customScrollContainer.h @@ -0,0 +1,144 @@ +#ifndef CUSTOMSCROLLCONTAINER_H +#define CUSTOMSCROLLCONTAINER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAXSPEED 70 + +class ScrollAreaCustom; +class ScrollListContainer; +class ScrollIndicator; + +class ScrollAreaCustom : public QWidget +{ + Q_OBJECT + +private: + QTimer* getCord; + QTimer* rfrshView; + + ScrollListContainer* container; + ScrollIndicator* indicator; + + QPropertyAnimation* bounce; + + bool pressed = false; + bool scrollDown = true; + bool outOfEdge = false; + bool ignoreMaxSpeed = false; + + int strtY; + int lastY; + int bfEdgeY; //last y value before out of edge + + int curSpd = 0; + int damp = 1; + int moveStored = 0; + int nextMove = 1; + + void paintEvent(QPaintEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent* event); + void bounceBack(); + + +public: + explicit ScrollAreaCustom(QWidget *parent = nullptr); + void addWidget(QWidget* newWidget, bool setAnimation = true); + void addWidgets(QVector widgets){for(int i = 0; i < widgets.size(); i++)addWidget(widgets[i], false);} + void removeWidget(QWidget* w = nullptr); + void scrollToTop(); + void updateHeight(); + void clear(); + +signals: + +private slots: + void scrollContainer(); + void updateSpd(); + void scrollIndicator(int dp); + +}; + +class ScrollListContainer : public QWidget +{ + Q_OBJECT +public: + explicit ScrollListContainer(QWidget *parent = nullptr); + void AddWidget(QWidget* widget, bool setAnimation = true); + void RemoveWidget(QWidget* widget = nullptr); + void updateHeight(); + void clear(); + //void RemoveWidget(QWidget* widget); + +private: + //QTimer* newWidgetFade; + int spacing = 3; + QVector widgets; + int size = 0; + QVector ys; + + void paintEvent(QPaintEvent* event); + +signals: + +private slots: + +}; + +class ScrollIndicator : public QWidget +{ + Q_OBJECT + +private: + QColor curColor; + QColor defaultColor = QColor(100, 100, 100, 130); + QColor hoverColor = QColor(70, 70, 70, 150); + QColor pressColor = QColor(50, 50, 50, 170); + + QTimer* hovTimer; + QTimer* aniPause; + + int lastY; + + int defaultWidth = 2; + int defaultWidthAtFocus = 9; + int margin = 3; + + bool pressed = false; + +public: + explicit ScrollIndicator(QWidget *parent = nullptr); + +private: + void paintEvent(QPaintEvent* event); + void enterEvent(QEnterEvent* event); + void leaveEvent(QEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + +signals: + void scrollPage(int); + +private slots: + void setHoverActive(); + +}; + + + +#endif // CUSTOMSCROLLCONTAINER_H diff --git a/customWidgets.cpp b/customWidgets.cpp new file mode 100644 index 0000000..00b4ca0 --- /dev/null +++ b/customWidgets.cpp @@ -0,0 +1,828 @@ +#include "customWidgets.h" + +//*********************************************************// +//CustomIcon class implementation +//*********************************************************// + +customIcon::customIcon(QString iconPath, QString hint, int r, QWidget *parent): + QPushButton(parent), + radius(r), + iconHint(hint){ + QSvgRenderer renderer; + renderer.load(iconPath); + QSize size = renderer.defaultSize(); + iconImg = new QPixmap(size); + iconImg->fill(Qt::transparent); + QPainter painter(iconImg); + painter.setRenderHints(QPainter::Antialiasing); + renderer.render(&painter); + + widgetRatio = iconImg->height() / iconImg->width(); + bgColor = defaultColor; +} + +customIcon::customIcon(const QPixmap &icon, QString hint, int r, QWidget *parent): + QPushButton(parent), + radius(r), + iconHint(hint){ + iconImg = new QPixmap(icon); + + widgetRatio = iconImg->height() / iconImg->width(); + bgColor = defaultColor; +} + +void customIcon::paintEvent(QPaintEvent *event){ + resize(height() / widgetRatio, height()); + + QPainter bgPainter(this); + bgPainter.setRenderHints(QPainter::Antialiasing); + bgPainter.setPen(Qt::NoPen); + bgPainter.setBrush(bgColor); + bgPainter.drawRoundedRect(this->rect(), radius, radius); + + QPainter pixmapPainter(this); + pixmapPainter.setRenderHints(QPainter::Antialiasing); + pixmapPainter.translate(width() / 2, height() / 2); + pixmapPainter.rotate(rotation); + pixmapPainter.translate(-width() / 2, -height() / 2); + int w = iconSizeRate * width(); + int h = iconSizeRate * height(); + pixmapPainter.drawPixmap(width() / 2 - w / 2, height() / 2 - h / 2, w, h, *iconImg); +} + +void customIcon::enterEvent(QEnterEvent *event){ + bgColor = hoverColor; + update(); +} + +void customIcon::leaveEvent(QEvent *event){ + bgColor = defaultColor; + update(); +} + +void customIcon::mousePressEvent(QMouseEvent *event){ + emit clicked(); + setFocus(); + iconSizeRate -= 0.1; + update(); +} + +void customIcon::mouseReleaseEvent(QMouseEvent *event){ + iconSizeRate += 0.1; + update(); +} + + +//*********************************************************// +//selectionItem class implementation +//*********************************************************// + +selectionItem::selectionItem(QString name, QString info, QWidget *parent) : + QWidget(parent){ + /* set labels */ + QFont titleFont = QFont("Corbel", 13); + QFontMetrics fm(titleFont); + qreal height = fm.lineSpacing(); + title = new QLabel(this); + title->setText(name); + title->setFont(titleFont); + title->setMinimumHeight(height); + title->setStyleSheet("color:#2c2c2c"); + title->setAlignment(Qt::AlignLeft | Qt::AlignBottom); + QFont descFont = QFont("Corbel Light", 11); + fm = QFontMetrics(descFont); + height = fm.lineSpacing(); + description = new QLabel(this); + description->setText(info); + description->setFont(descFont); + description->setMinimumHeight(height); + description->setAlignment(Qt::AlignLeft | Qt::AlignTop); + description->setStyleSheet("color:#707070"); + + indicator = new QWidget(this); + + /* set minimum height and layout */ + setFixedHeight(title->height() + (info == "" ? 0 : description->height() + 5)); + indicator->resize(6, 0.4 * this->height()); + indicator->move(4, 0.3 * this->height()); + indicator->setStyleSheet("border-radius:3px;background-color:#0078D4"); + opac = new QGraphicsOpacityEffect(indicator); + opac->setOpacity(0); + indicator->setGraphicsEffect(opac); + + QVBoxLayout *contentLayout = new QVBoxLayout(this); + contentLayout->setContentsMargins(20, 0, 0, 0); + contentLayout->setSpacing(2); + this->setLayout(contentLayout); + contentLayout->addWidget(title); + if(info != "") + contentLayout->addWidget(description); + contentLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + + /* set background widget */ + bgWidget = new QWidget(this); + bgWidget->resize(this->size()); + bgWidget->setStyleSheet("border-radius:5px;background-color:#00000000"); + bgWidget->lower(); + bgWidget->show(); + + this->setMouseTracking(true); +} + +void selectionItem::enterEvent(QEnterEvent *event){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#0a000000"); + QParallelAnimationGroup *enter = new QParallelAnimationGroup(this); + QPropertyAnimation *longer = new QPropertyAnimation(indicator, "geometry", this); + longer->setStartValue(indicator->geometry()); + longer->setEndValue(QRectF(4, 0.25 * this->height(), 6, this->height() * 0.5)); + longer->setDuration(150); + longer->setEasingCurve(QEasingCurve::OutBack); + QPropertyAnimation *fadeIn = new QPropertyAnimation(opac, "opacity", this); + fadeIn->setStartValue(opac->opacity()); + fadeIn->setEndValue(0.99); + fadeIn->setDuration(100); + enter->addAnimation(longer); + enter->addAnimation(fadeIn); + enter->start(); +} + +void selectionItem::leaveEvent(QEvent *event){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#00000000"); + QParallelAnimationGroup *leave = new QParallelAnimationGroup(this); + QPropertyAnimation *shorter = new QPropertyAnimation(indicator, "geometry", this); + shorter->setStartValue(indicator->geometry()); + shorter->setEndValue(QRectF(4, 0.3 * this->height(), 6, this->height() * 0.4)); + shorter->setDuration(150); + shorter->setEasingCurve(QEasingCurve::OutBack); + QPropertyAnimation *fadeOut = new QPropertyAnimation(opac, "opacity", this); + fadeOut->setStartValue(opac->opacity()); + fadeOut->setEndValue(onSelected ? 0.99 : 0); + fadeOut->setDuration(100); + leave->addAnimation(shorter); + leave->addAnimation(fadeOut); + leave->start(); + + if(mousePressed) + mousePressed = false; +} + +void selectionItem::mousePressEvent(QMouseEvent *event){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#1a000000"); + QPropertyAnimation *shorter = new QPropertyAnimation(indicator, "geometry", this); + shorter->setStartValue(indicator->geometry()); + shorter->setEndValue(QRectF(4, 0.4 * this->height(), 6, this->height() * 0.2)); + shorter->setDuration(100); + shorter->setEasingCurve(QEasingCurve::OutBack); + shorter->start(); + + mousePressed = true; +} + +void selectionItem::mouseReleaseEvent(QMouseEvent *event){ + if(mousePressed){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#0a000000"); + QPropertyAnimation *longer = new QPropertyAnimation(indicator, "geometry", this); + longer->setStartValue(indicator->geometry()); + longer->setEndValue(QRectF(4, 0.25 * this->height(), 6, this->height() * 0.5)); + longer->setDuration(150); + longer->setEasingCurve(QEasingCurve::OutBack); + longer->start(); + + if(!onSelected){ + onSelected = true; + title->setStyleSheet("color:#005FB8"); + description->setStyleSheet("color:#3a8fb7"); + emit selected(this); + setFocus(); + } + mousePressed = false; + } +} + +void selectionItem::resizeEvent(QResizeEvent *event){ + bgWidget->resize(this->size()); +} + +void selectionItem::Select(){ + if(!onSelected){ + onSelected = true; + title->setStyleSheet("color:#005FB8"); + description->setStyleSheet("color:#3a8fb7"); + indicator->setGeometry(4, 0.5 * this->height(), 6, 0); + + QParallelAnimationGroup *sel = new QParallelAnimationGroup(this); + QPropertyAnimation *longer = new QPropertyAnimation(indicator, "geometry", this); + longer->setStartValue(indicator->geometry()); + longer->setEndValue(QRectF(4, 0.3 * this->height(), 6, this->height() * 0.4)); + longer->setDuration(150); + longer->setEasingCurve(QEasingCurve::OutBack); + QPropertyAnimation *fadeIn = new QPropertyAnimation(opac, "opacity", this); + fadeIn->setStartValue(opac->opacity()); + fadeIn->setEndValue(0.99); + fadeIn->setDuration(100); + sel->addAnimation(longer); + sel->addAnimation(fadeIn); + sel->start(); + + emit selected(this); + } +} + +void selectionItem::Deselect(){ + if(onSelected){ + onSelected = false; + title->setStyleSheet("color:#2c2c2c"); + description->setStyleSheet("color:#707070"); + + QPropertyAnimation *fadeOut = new QPropertyAnimation(opac, "opacity", this); + fadeOut->setStartValue(opac->opacity()); + fadeOut->setEndValue(0); + fadeOut->setDuration(100); + fadeOut->start(); + } +} + +singleSelectGroup::singleSelectGroup(QString name, QWidget *parent) : + QWidget(parent){ + QFont titleFont = QFont("Corbel", 16); + QFontMetrics fm(titleFont); + qreal height = fm.lineSpacing(); + groupName = new QLabel(this); + groupName->setMinimumHeight(height); + groupName->setFont(titleFont); + groupName->setText(name); + + QWidget *spacingLine = new QWidget(this); + spacingLine->setFixedHeight(1); + spacingLine->setStyleSheet("background-color:#0a000000"); + + this->setFixedHeight(groupName->height() + middleSpacing + 1 + bottomSpacing); + + mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(10, 0, 10, bottomSpacing); + mainLayout->setSpacing(middleSpacing); + mainLayout->addWidget(groupName); + mainLayout->addWidget(spacingLine); +} + +void singleSelectGroup::AddItem(selectionItem *item){ + selections.push_back(item); + this->setFixedHeight(this->height() + middleSpacing + item->height()); + mainLayout->addWidget(item); + if(selectedID == -1){ + item->Select(); + selectedID = 0; + } + connect(item, SIGNAL(selected(selectionItem*)), this, SLOT(changeSelection(selectionItem*))); + emit itemChange(); +} + +void singleSelectGroup::RemoveItem(selectionItem *item){ + int id = selections.indexOf(item); + if(id < 0) return; + selections.erase(selections.begin() + id); + mainLayout->removeWidget(item); + item->setParent(nullptr); + item->deleteLater(); + this->setFixedHeight(this->height() - middleSpacing - item->height()); + if(selections.size() == 0) + selectedID = -1; + else{ + selectedID = id < selections.size() ? id : id - 1; + selections[selectedID]->Select(); + } + emit selectedItemChange(selectedID); + emit itemChange(); +} + +void singleSelectGroup::SetSelection(selectionItem *item){ + int id = selections.indexOf(item); + selections[id]->Select(); +} + +void singleSelectGroup::changeSelection(selectionItem *item){ + int id = selections.indexOf(item); + for(int i = 0; i < selections.size(); i++){ + if(i == id) continue; + selections[i]->Deselect(); + } + selectedID = id; + emit selectedItemChange(id); +} + +horizontalValueAdjuster::horizontalValueAdjuster(QString name, qreal min, qreal max, qreal step, QWidget *parent) : + QWidget(parent), + curValue(min), + minValue(min), + maxValue(max), + stepValue(step) +{ + QFont titleFont = QFont("Corbel", 16); + QFontMetrics fm(titleFont); + qreal height = fm.lineSpacing(); + title = new QLabel(this); + title->setMinimumHeight(height); + title->setFont(titleFont); + title->setText(name); + + QWidget *spacingLine = new QWidget(this); + spacingLine->setFixedHeight(1); + spacingLine->setStyleSheet("background-color:#0a000000"); + + slider = new QSlider(Qt::Horizontal, this); + slider->setMinimum(0); + slider->setMaximum((max - min) / step + 1); + slider->setPageStep(1); + QString grooveStyle = "QSlider::groove:horizontal{height:6px; border-radius:3px;} "; + QString sliderStyle = "QSlider::handle:horizontal{width:12px; margin-bottom:-3px; margin-top:-3px; background:#c2c2c2; border-radius:6px;} "; + QString sliderHStyle = "QSlider::handle:horizontal:hover{width:12px; margin-bottom:-3px; margin-top:-3px; background:#3a8fb7; border-radius:6px;} "; + QString sliderPStyle = "QSlider::handle:horizontal:pressed{width:12px; margin-bottom:-3px; margin-top:-3px; background:#005fb8; border-radius:6px;} "; + QString subStyle = "QSlider::sub-page:horizontal{background:#0078D4; border-radius:3px} "; + QString addStyle = "QSlider::add-page:horizontal{background:#1a000000; border-radius:3px} "; + slider->setStyleSheet(grooveStyle+sliderStyle+sliderHStyle+sliderPStyle+subStyle+addStyle); + + + QFont valueFont = QFont("Corbel", 13); + fm = QFontMetrics(titleFont); + height = fm.lineSpacing(); + valueLabel = new QLabel(this); + valueLabel->setMinimumHeight(height); + valueLabel->setFont(valueFont); + valueLabel->setText(QString::asprintf("%g", min)); + valueLabel->setMinimumWidth(30); + valueLabel->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + valueLabel->setStyleSheet("margin-bottom:5px"); + + QWidget *content = new QWidget(this); + content->setMinimumHeight(valueLabel->height() < slider->height() ? valueLabel->height() : slider->height()); + QHBoxLayout *contentLayout = new QHBoxLayout(content); + contentLayout->setAlignment(Qt::AlignVCenter); + content->setLayout(contentLayout); + contentLayout->setContentsMargins(0, 0, 0, 0); + contentLayout->setSpacing(10); + contentLayout->addWidget(valueLabel); + contentLayout->addWidget(slider); + + this->setMinimumHeight(title->height() + 2 * middleSpacing + 1 + content->height() + bottomSpacing); + QVBoxLayout *mainLayout = new QVBoxLayout(this); + this->setLayout(mainLayout); + mainLayout->setContentsMargins(10, 0, 10, bottomSpacing); + mainLayout->setSpacing(middleSpacing); + mainLayout->addWidget(title); + mainLayout->addWidget(spacingLine); + mainLayout->addWidget(content); + + connect(slider, &QSlider::valueChanged, this, [=](qreal value){ + valueLabel->setText(QString::asprintf("%g", value * stepValue + minValue)); + curValue = value * stepValue + minValue; + emit valueChanged(curValue); + }); +} + +void horizontalValueAdjuster::setValue(qreal value){ + valueLabel->setText(QString::asprintf("%g", value)); + slider->setValue((value - minValue) / stepValue); + curValue = value; + emit valueChanged(value); +} + +//******************** + +//itemGroup::itemGroup(const QString &name, QWidget *parent){ +// QFont titleFont = QFont("DengXian", 16, QFont::DemiBold); +// QFontMetrics fm(titleFont); +// qreal height = fm.lineSpacing(); +// groupName = new QLabel(this); +// groupName->setFixedHeight(height); +// groupName->setFont(titleFont); +// groupName->setText(name); +// +// QWidget *spacingLine = new QWidget(this); +// spacingLine->setFixedHeight(1); +// spacingLine->setStyleSheet("background-color:#0a000000"); +// +// this->setFixedHeight(groupName->height() + middleSpacing + 1 + bottomSpacing); +// +// mainLayout = new QVBoxLayout(this); +// mainLayout->setContentsMargins(10, 0, 10, bottomSpacing); +// mainLayout->setSpacing(middleSpacing); +// mainLayout->addWidget(groupName); +// mainLayout->addWidget(spacingLine); +//} +// +//void itemGroup::AddItem(QWidget *item){ +// items.push_back(item); +// this->setFixedHeight(this->height() + middleSpacing + item->height()); +// mainLayout->addWidget(item); +//} +// +//void itemGroup::RemoveItem(QWidget *item){ +// items.erase(items.begin() + items.indexOf(item)); +// mainLayout->removeWidget(item); +// this->setFixedHeight(this->height() - middleSpacing - item->height()); +//} + + +//***************** + +bigIconButton::bigIconButton(const QString &iconPath, const QString &description, int radius, QWidget *parent) : + QWidget(parent), + cornerRadius(radius) +{ + iconImg = new QPixmap(iconPath); + + /* set icon label and text label */ + icon = new QLabel(this); + icon->setPixmap(*iconImg); + icon->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + icon->setAlignment(Qt::AlignCenter); + + QFont textFont = QFont("Corbel", 13); + QFontMetrics fm(textFont); + text = new QLabel(this); + text->setFont(textFont); + text->setText(description); + text->setWordWrap(true); + text->setMinimumHeight(fm.lineSpacing()); + text->setAlignment(Qt::AlignCenter); + + /* set indicator */ + indicator = new QWidget(this); + indicator->resize(6, 6); + indicator->move(this->width() - 3, this->height() - 21); + indicator->setStyleSheet("border-radius:3px;background-color:#afafaf"); + + /* set background */ + bgWidget = new QWidget(this); + bgWidget->resize(this->size()); + radiusStyle = QString::asprintf("border-radius:%dpx;", cornerRadius); + bgWidget->setStyleSheet(radiusStyle + "background-color:#00000000"); + bgWidget->lower(); + bgWidget->show(); + + /* set layout */ + QVBoxLayout *layout = new QVBoxLayout(this); + this->setLayout(layout); + layout->setContentsMargins(15, 35, 15, 35); + layout->setSpacing(15); + layout->addWidget(icon); + layout->addWidget(text); + layout->setAlignment(Qt::AlignCenter); + + this->setMinimumHeight(200); +} + +void bigIconButton::resizeEvent(QResizeEvent *event){ + bgWidget->setFixedSize(this->size()); + if(onSelected){ + indicator->resize(this->width() * 0.1, 6); + indicator->move(this->width() * 0.45, this->height() - 21); + } + else{ + indicator->resize(this->width() * 0.4, 6); + indicator->move(this->width() * 0.3, this->height() - 21); + } +} + +void bigIconButton::enterEvent(QEnterEvent *event){ + bgWidget->setStyleSheet(radiusStyle + "background-color:#0a0078D4"); + QPropertyAnimation *longer = new QPropertyAnimation(indicator, "geometry", this); + longer->setStartValue(indicator->geometry()); + longer->setEndValue(QRectF(this->width() * 0.2, this->height() - 21, this->width() * 0.6, 6)); + longer->setDuration(150); + longer->setEasingCurve(QEasingCurve::OutBack); + longer->start(); + + indicator->setStyleSheet("border-radius:3px;background-color:#0078d4"); +} + +void bigIconButton::leaveEvent(QEvent *event){ + bgWidget->setStyleSheet(radiusStyle + "background-color:#00000000"); + QPropertyAnimation *shorter = new QPropertyAnimation(indicator, "geometry", this); + shorter->setStartValue(indicator->geometry()); + if(!onSelected) + shorter->setEndValue(QRectF(this->width() * 0.45, this->height() - 21, this->width() * 0.1, 6)); + else + shorter->setEndValue(QRectF(this->width() * 0.3, this->height() - 21, this->width() * 0.4, 6)); + shorter->setDuration(250); + shorter->setEasingCurve(QEasingCurve::OutBack); + shorter->start(); + + if(!onSelected) + indicator->setStyleSheet("border-radius:3px;background-color:#afafaf"); + + if(mousePressed) + mousePressed = false; +} + +void bigIconButton::mousePressEvent(QMouseEvent *event){ + bgWidget->setStyleSheet(radiusStyle + "background-color:#1a0078D4"); + QPropertyAnimation *shorter = new QPropertyAnimation(indicator, "geometry", this); + shorter->setStartValue(indicator->geometry()); + shorter->setEndValue(QRectF(this->width() * 0.4, this->height() - 21, this->width() * 0.2, 6)); + shorter->setDuration(100); + shorter->setEasingCurve(QEasingCurve::OutBack); + shorter->start(); + + mousePressed = true; +} + +void bigIconButton::mouseReleaseEvent(QMouseEvent *event){ + if(mousePressed){ + bgWidget->setStyleSheet(radiusStyle + "background-color:#0a0078D4"); + QPropertyAnimation *longer = new QPropertyAnimation(indicator, "geometry", this); + longer->setStartValue(indicator->geometry()); + longer->setEndValue(QRectF(this->width() * 0.2, this->height() - 21, this->width() * 0.6, 6)); + longer->setDuration(150); + longer->setEasingCurve(QEasingCurve::OutBack); + longer->start(); + + mousePressed = false; + emit clicked(); + if(selectable){ + emit selected(); + onSelected = true; + } + } +} + +void bigIconButton::setScale(qreal scale){ + iconImg = new QPixmap(iconImg->scaled(iconImg->size() * scale, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + icon->setPixmap(*iconImg); +} + +textInputItem::textInputItem(const QString &name, QWidget *parent) : + QWidget(parent) +{ + QFont nameFont = QFont("Corbel", 12); + QFontMetrics fm(nameFont); + qreal height = fm.lineSpacing(); + itemName = new QLabel(this); + itemName->setText(name); + itemName->setFont(nameFont); + itemName->setFixedHeight(height); + itemName->setStyleSheet("color:#1c1c1c"); + + QFont textFont = QFont("Corbel", 12); + fm = QFontMetrics(textFont); + editor = new QLineEdit(this); + editor->setText(""); + editor->setFixedHeight(fm.lineSpacing()); + editor->setStyleSheet("color:#5c5c5c;background-color:#00000000;border-style:none;"); + editor->setReadOnly(true); + editor->setFont(textFont); + + bgWidget = new QWidget(this); + bgWidget->setStyleSheet("background-color:#00000000;border-radius:5px;"); + bgWidget->lower(); + bgWidget->show(); + + indicator = new QWidget(this); + indicator->setFixedHeight(4); + indicator->setStyleSheet("background-color:#0078d4;border-radius:2px"); + + opac = new QGraphicsOpacityEffect(this); + opac->setOpacity(0); + indicator->setGraphicsEffect(opac); + + this->setFixedHeight(itemName->height() + 10); + + connect(editor, &QLineEdit::returnPressed, this, [=](){ + leaveEditEffect(); + onEditing = false; + editor->setReadOnly(true); + curText = editor->text(); + }); + connect(editor, &QLineEdit::editingFinished, this, [=](){ + leaveEditEffect(); + onEditing = false; + editor->setReadOnly(true); + curText = editor->text(); + QTimer *delay = new QTimer(this); + connect(delay, &QTimer::timeout, this, [=](){mousePressed = false;}); + delay->setSingleShot(true); + delay->start(10); + mousePressed = false; + emit textEdited(curText); + }); +} + +void textInputItem::resizeEvent(QResizeEvent *event){ + itemName->move(margin, this->height() / 2 - itemName->height() / 2); + itemName->setFixedWidth(this->width() * 0.3 - margin - spacing); + int width = QFontMetrics(editor->font()).size(Qt::TextSingleLine, editor->text()).width() + 3; + if(!onEditing){ + if(width > this->width() * 0.7 - margin) + editor->resize(this->width() * 0.7 - margin, editor->height()); + else + editor->resize(width, editor->height()); + editor->move(this->width() - margin - editor->width(), this->height() / 2 - editor->height() / 2); + indicator->move(this->width() - margin, this->height() - 7); + } + else{ + editor->resize(this->width() * 0.7 - margin, editor->height()); + editor->move(this->width() * 0.3, this->height() / 2 - editor->height() / 2 - 2); + indicator->move(this->width() * 0.3, this->height() - 7); + } + bgWidget->setFixedSize(this->size()); +} + +void textInputItem::enterEditEffect(){ + editor->setCursorPosition(editor->text().length()); + editor->setStyleSheet("color:#1c1c1c;background-color:#00000000;border-style:none;"); + QParallelAnimationGroup *group = new QParallelAnimationGroup(this); + QPropertyAnimation *longer = new QPropertyAnimation(indicator, "geometry", this); + longer->setStartValue(indicator->geometry()); + longer->setEndValue(QRectF(this->width() * 0.3, this->height() - 7, this->width() * 0.7 - margin, 4)); + longer->setDuration(500); + longer->setEasingCurve(QEasingCurve::InOutExpo); + QPropertyAnimation *fade = new QPropertyAnimation(opac, "opacity", this); + fade->setStartValue(opac->opacity()); + fade->setEndValue(0.99); + fade->setDuration(150); + QPropertyAnimation *move = new QPropertyAnimation(editor, "geometry", this); + move->setStartValue(editor->geometry()); + move->setEndValue(QRectF(this->width() * 0.3, this->height() / 2 - editor->height() / 2 - 2, this->width() * 0.7 - margin, editor->height())); + move->setDuration(500); + move->setEasingCurve(QEasingCurve::InOutExpo); + group->addAnimation(longer); + group->addAnimation(fade); + group->addAnimation(move); + group->start(); +} + +void textInputItem::leaveEditEffect(){ + editor->setCursorPosition(0); + editor->setStyleSheet("color:#5c5c5c;background-color:#00000000;border-style:none;"); + QParallelAnimationGroup *group = new QParallelAnimationGroup(this); + QPropertyAnimation *shorter = new QPropertyAnimation(indicator, "geometry", this); + shorter->setStartValue(indicator->geometry()); + shorter->setEndValue(QRectF(this->width() - margin - 4, this->height() - 7, 4, 4)); + shorter->setDuration(500); + shorter->setEasingCurve(QEasingCurve::InOutExpo); + QPropertyAnimation *fade = new QPropertyAnimation(opac, "opacity", this); + fade->setStartValue(opac->opacity()); + fade->setEndValue(0); + fade->setDuration(350); + QPropertyAnimation *move = new QPropertyAnimation(editor, "geometry", this); + move->setStartValue(editor->geometry()); + int width = QFontMetrics(editor->font()).size(Qt::TextSingleLine, editor->text()).width() + 3; + if(width > this->width() * 0.7 - margin) + move->setEndValue(QRectF(this->width() * 0.3, this->height() / 2 - editor->height() / 2, this->width() * 0.7 - margin, editor->height())); + else + move->setEndValue(QRectF(this->width() - width - margin, this->height() / 2 - editor->height() / 2, width, editor->height())); + move->setDuration(500); + move->setEasingCurve(QEasingCurve::InOutExpo); + group->addAnimation(shorter); + group->addAnimation(fade); + group->addAnimation(move); + group->start(); +} + +void textInputItem::enterEvent(QEnterEvent *event){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#0a000000"); +} + +void textInputItem::leaveEvent(QEvent *event){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#00000000"); +} + +void textInputItem::mousePressEvent(QMouseEvent *event){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#1a000000"); + mousePressed = true; +} + +void textInputItem::mouseReleaseEvent(QMouseEvent *event){ + if(mousePressed){ + bgWidget->setStyleSheet("border-radius:5px;background-color:#0a000000"); + if(onEditing){ + leaveEditEffect(); + onEditing = false; + curText = editor->text(); + editor->setReadOnly(true); + emit textEdited(curText); + } + else{ + if(enabled){ + enterEditEffect(); + editor->raise(); + onEditing = true; + editor->setReadOnly(false); + editor->setText(curText + ""); + editor->setFocus(); + } + } + mousePressed = false; + } +} + +void textInputItem::setValue(const QString &text){ + editor->setText(text); + editor->setCursorPosition(0); + curText = text; + int width = QFontMetrics(editor->font()).size(Qt::TextSingleLine, editor->text()).width() + 3; + if(!onEditing){ + if(width > this->width() * 0.7 - margin) + editor->resize(this->width() * 0.7 - margin, editor->height()); + else + editor->resize(width, editor->height()); + editor->move(this->width() - margin - editor->width(), this->height() / 2 - editor->height() / 2); + } + else{ + editor->resize(this->width() * 0.7 - margin, editor->height()); + editor->move(this->width() * 0.3, this->height() / 2 - editor->height() / 2 - 2); + } +} + + +textButton::textButton(QString text, QWidget *parent, qreal ratio) : + QWidget(parent) +{ + QFont textFont = QFont("Corbel", 10); + QFontMetrics fm(textFont); + qreal height = fm.lineSpacing(); + btnText = new QLabel(this); + btnText->setText(text); + btnText->setFont(textFont); + btnText->setFixedHeight(height); + btnText->setFixedWidth(fm.size(Qt::TextSingleLine, text).width() + 2); + btnText->setStyleSheet("color:#1c1c1c"); + btnText->setAlignment(Qt::AlignCenter); + + bgWidget = new QWidget(this); + bgWidget->setStyleSheet("background-color:"+defaultColor+";border-radius:5px;"); + + this->setFixedHeight(btnText->height() / ratio); +} + +textButton::textButton(QString text, QString defC, QString hoverC, QString pressedC, QWidget *parent, qreal ratio): + QWidget(parent), + defaultColor(defC), + hoverColor(hoverC), + pressedColor(pressedC) +{ + QFont textFont = QFont("Corbel", 10); + QFontMetrics fm(textFont); + qreal height = fm.lineSpacing(); + btnText = new QLabel(this); + btnText->setText(text); + btnText->setFont(textFont); + btnText->setFixedHeight(height); + btnText->setFixedWidth(fm.size(Qt::TextSingleLine, text).width() + 2); + btnText->setStyleSheet("color:#1c1c1c"); + btnText->setAlignment(Qt::AlignCenter); + + bgWidget = new QWidget(this); + bgWidget->setStyleSheet("background-color:"+defaultColor+";border-radius:5px;"); + + this->setFixedHeight(btnText->height() / ratio); +} + +void textButton::resizeEvent(QResizeEvent *event){ + bgWidget->resize(this->size()); + btnText->move(this->width() / 2 - btnText->width() / 2, this->height() / 2 - btnText->height() / 2); +} + +void textButton::enterEvent(QEnterEvent *event){ + bgWidget->setStyleSheet("background-color:"+hoverColor+";border-radius:5px;"); +} + +void textButton::leaveEvent(QEvent *event){ + bgWidget->setStyleSheet("background-color:"+defaultColor+";border-radius:5px;"); + if(mousePressed){ + bgWidget->setStyleSheet("background-color:"+pressedColor+";border-radius:5px;"); + QPropertyAnimation *enlarge = new QPropertyAnimation(bgWidget, "geometry", this); + enlarge->setStartValue(bgWidget->geometry()); + enlarge->setEndValue(QRect(0, 0, this->width(), this->height())); + enlarge->setDuration(150); + enlarge->setEasingCurve(QEasingCurve::OutBounce); + enlarge->start(); + mousePressed = false; + } +} + +void textButton::mousePressEvent(QMouseEvent *event){ + bgWidget->setStyleSheet("background-color:"+pressedColor+";border-radius:5px;"); + QPropertyAnimation *shrink = new QPropertyAnimation(bgWidget, "geometry", this); + shrink->setStartValue(bgWidget->geometry()); + shrink->setEndValue(QRect(0.05 * this->width(), 0.05 * this->height(), this->width() * 0.9, this->height() * 0.9)); + shrink->setDuration(100); + shrink->setEasingCurve(QEasingCurve::OutBack); + shrink->start(); + mousePressed = true; + setFocus(); +} + +void textButton::mouseReleaseEvent(QMouseEvent *event){ + if(mousePressed){ + bgWidget->setStyleSheet("background-color:"+hoverColor+";border-radius:5px;"); + QPropertyAnimation *enlarge = new QPropertyAnimation(bgWidget, "geometry", this); + enlarge->setStartValue(bgWidget->geometry()); + enlarge->setEndValue(QRect(0, 0, this->width(), this->height())); + enlarge->setDuration(150); + enlarge->setEasingCurve(QEasingCurve::OutBounce); + enlarge->start(); + mousePressed = false; + emit clicked(); + } +} diff --git a/customWidgets.h b/customWidgets.h new file mode 100644 index 0000000..5047185 --- /dev/null +++ b/customWidgets.h @@ -0,0 +1,258 @@ +#ifndef CUSTOMWIDGETS_H +#define CUSTOMWIDGETS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class customIcon : public QPushButton{ + Q_OBJECT + + Q_PROPERTY(qreal rotationAngle READ rotationAngle WRITE setRotationAngle NOTIFY rotationAngleChanged) + +private: + int radius; + qreal widgetRatio; + qreal iconSizeRate = 0.8; + qreal rotation = 0; + QPixmap *iconImg; + QString iconHint; + + /* for hover and click effects */ + QColor bgColor; + QColor defaultColor = QColor(0, 0, 0, 0); + QColor hoverColor = QColor(241, 241, 241, 200); + +protected: + void paintEvent(QPaintEvent* event); + void enterEvent(QEnterEvent *event); + void leaveEvent(QEvent* event); + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + +protected: + qreal rotationAngle() const {return rotation;} + +public: + customIcon(QString iconPath, QString hint = "", int r = 0, QWidget *parent = nullptr); + customIcon(const QPixmap &icon, QString hint = "", int r = 0, QWidget *parent = nullptr); + + void setRotationAngle(qreal angle = 0){rotation = angle;update();} + +signals: + void rotationAngleChanged(); +}; + +class selectionItem : public QWidget{ + Q_OBJECT + +private: + QLabel *title; + QLabel *description; + QWidget *indicator; + QWidget *mainContent; + QWidget *bgWidget; + QGraphicsOpacityEffect *opac; + bool onSelected = false; + bool mousePressed = false; + + void enterEvent(QEnterEvent *event); + void leaveEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent *event); + +public: + selectionItem(QString name, QString info = "", QWidget *parent = nullptr); + void Select(); + void Deselect(); + void setTitle(QString titleText){title->setText(titleText);} + void setDescription(QString descText){description->setText(descText);} + +signals: + void selected(selectionItem *item); + //void heightChange(); + +}; + +class singleSelectGroup : public QWidget{ + Q_OBJECT + +private: + const int middleSpacing = 5; + const int bottomSpacing = 30; + QLabel *groupName; + QVBoxLayout *mainLayout; + int selectedID = -1; + QVector selections; + +public: + singleSelectGroup(QString name = "", QWidget *parent = nullptr); + void AddItem(selectionItem *item); + void RemoveItem(selectionItem *item); + void SetSelection(selectionItem *item); + qreal value(){return selectedID;} + +signals: + void selectedItemChange(int selectID); + void itemChange(); + +private slots: + void changeSelection(selectionItem *item); +}; + +class horizontalValueAdjuster : public QWidget{ + Q_OBJECT + +private: + const int middleSpacing = 5; + const int bottomSpacing = 30; + QLabel *title; + qreal curValue; + qreal minValue; + qreal maxValue; + qreal stepValue; + QWidget *editArea; + QLabel *valueLabel; + //QDoubleSpinBox *editLabel; + customIcon *decreaseBtn; + customIcon *increaseBtn; + QSlider *slider; + +public: + horizontalValueAdjuster(QString name, qreal min, qreal max, qreal step, QWidget *parent = nullptr); + void setValue(qreal value); + qreal value(){return curValue;} + +signals: + void valueChanged(qreal value); + +}; + +//class itemGroup : public QWidget{ +// Q_OBJECT +// +//private: +// const int middleSpacing = 5; +// const int bottomSpacing = 30; +// QLabel *groupName; +// QVBoxLayout *mainLayout; +// QVector items; +// +//public: +// itemGroup(const QString &name, QWidget *parent = nullptr); +// void AddItem(QWidget *item); +// void RemoveItem(QWidget *item); +//}; + +class bigIconButton : public QWidget{ + Q_OBJECT + +private: + QPixmap *iconImg; + QLabel *text; + QLabel *icon; + QWidget *bgWidget; + QWidget *indicator; + + int cornerRadius; + QString radiusStyle; + + bool selectable = false; + bool mousePressed = false; + bool onSelected = false; + + void enterEvent(QEnterEvent *event); + void leaveEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent *event); + +public: + bigIconButton(const QString &iconPath, const QString &description, int radius, QWidget *parent = nullptr); + void setSelectable(bool sel = true){selectable = sel;} + void setScale(qreal scale); + +signals: + void clicked(); + void selected(); +}; + +class textInputItem : public QWidget{ + Q_OBJECT + +private: + const int margin = 10; + const int spacing = 10; + QLabel *itemName; + QLineEdit *editor; + QWidget *bgWidget; + QWidget *indicator; + QGraphicsOpacityEffect *opac; + + QString curText = ""; + bool mousePressed = false; + bool onEditing = false; + + bool enabled = true; + + void enterEditEffect(); + void leaveEditEffect(); + + void enterEvent(QEnterEvent *event); + void leaveEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent *event); + //void focusOutEvent(QFocusEvent *event); + +public: + textInputItem(const QString &name, QWidget *parent = nullptr); + QLineEdit* lineEditor(){return editor;} + QString value(){return editor->text();} + + void setValue(const QString &text); + void setValidator(QValidator *vali){editor->setValidator(vali);} + void setEnabled(bool enable = true){enabled = enable;} + +signals: + void textEdited(QString text); +}; + +class textButton : public QWidget{ + Q_OBJECT + +private: + QLabel *btnText; + QWidget *bgWidget; + QString defaultColor = "#0a0078d4"; + QString hoverColor = "#1a0078d4"; + QString pressedColor = "#2a0078d4"; + + bool mousePressed; + + void enterEvent(QEnterEvent *event); + void leaveEvent(QEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent *event); + +public: + textButton(QString text, QWidget *parent = nullptr, qreal ratio = 0.5); + textButton(QString text, QString defC, QString hoverC, QString pressedC, QWidget *parent = nullptr, qreal ratio = 0.5); + +signals: + void clicked(); +}; + +#endif // CUSTOMWIDGETS_H diff --git a/graph_implement.cpp b/graph_implement.cpp new file mode 100644 index 0000000..6093b4d --- /dev/null +++ b/graph_implement.cpp @@ -0,0 +1,882 @@ +#include "graph_implement.h" +#include + +AbstractGraph::~AbstractGraph(){} + +void ALVex::visit(){ + if(visited) + return; + info->gVex->visit(true); + visited = true; +} + +void ALVex::access(const QString &hint){ + info->gVex->access(hint); +} + +void ALArc::visit(){ + gArc->visit(true); +} + +void ALArc::access(){ + gArc->access(); +} + +ALGraph::~ALGraph(){ + int i = vexList.size() - 1; + while(i >= 0) + DelVex(i--); + vexList.clear(); +} + +void ALGraph::AddVex(MyGraphicsVexItem *gvex){ + ALVex newVex(gvex); + vexList.push_back(newVex); +} + +void ALGraph::AddVex(VexInfo *info){ + ALVex newVex(info); + vexList.push_back(newVex); +} + +void ALGraph::AddArc(MyGraphicsLineItem *garc, int weight){ + int strtVex = GetIdOf(garc->stVex()); + int endVex = GetIdOf(garc->edVex()); + + ALArc *temp = vexList[strtVex].firstArc; + ALArc *newArc = new ALArc(garc, endVex, temp); + newArc->weight = weight; + vexList[strtVex].firstArc = newArc; + + if(type == UDG){ + temp = vexList[endVex].firstArc; + newArc = new ALArc(garc, strtVex, temp); + vexList[endVex].firstArc = newArc; + } +} + +void ALGraph::DelVex(MyGraphicsVexItem *gvex){ + int vexID = GetIdOf(gvex); + DelVex(vexID); +} + +void ALGraph::DelVex(int vexID){ + //Delete out arc + ALArc *curArc = vexList[vexID].firstArc; + while(curArc != nullptr){ + ALArc *next = curArc->nextArc; + delete curArc; + curArc = next; + } + //Delete in arc and adjust arcs + for(int i = 0; i < vexList.size(); i++){ + if(i == vexID) continue; + ALArc *dummyHead = new ALArc(nullptr, 0, vexList[i].firstArc); + ALArc *preArc = dummyHead; + while(preArc->nextArc != nullptr){ + if(preArc->nextArc->eVexID == vexID){ + ALArc *next = preArc->nextArc; + preArc->nextArc = next->nextArc; + delete next; + continue; + } + if(preArc->nextArc->eVexID > vexID) + preArc->nextArc->eVexID--; + preArc = preArc->nextArc; + } + vexList[i].firstArc = dummyHead->nextArc; + delete dummyHead; + } + vexList.erase(vexList.begin() + vexID); +} + +void ALGraph::DelArc(MyGraphicsLineItem *garc){ + int sVex = GetIdOf(garc->stVex()); + int eVex = GetIdOf(garc->edVex()); + DelArc(sVex, eVex); +} + +void ALGraph::DelArc(int sVexID, int eVexID){ + //Delete sVex -> eVex + if(vexList[sVexID].firstArc != nullptr){ + if(vexList[sVexID].firstArc->eVexID == eVexID){ + ALArc *awaitDel = vexList[sVexID].firstArc; + vexList[sVexID].firstArc = awaitDel->nextArc; + delete awaitDel; + } + else{ + ALArc *preArc = vexList[sVexID].firstArc; + while(preArc->nextArc != nullptr && preArc->nextArc->eVexID != eVexID) + preArc = preArc->nextArc; + if(preArc->nextArc != nullptr){ + ALArc *awaitDel = preArc->nextArc; + preArc->nextArc = awaitDel->nextArc; + delete awaitDel; + } + } + } + //Delete eVex -> sVex + if(type == UDG && vexList[eVexID].firstArc != nullptr){ + if(vexList[eVexID].firstArc->eVexID == sVexID){ + ALArc *awaitDel = vexList[eVexID].firstArc; + vexList[eVexID].firstArc = awaitDel->nextArc; + delete awaitDel; + } + else{ + ALArc *preArc = vexList[eVexID].firstArc; + while(preArc->nextArc != nullptr && preArc->nextArc->eVexID != sVexID) + preArc = preArc->nextArc; + if(preArc->nextArc != nullptr){ + ALArc *awaitDel = preArc->nextArc; + preArc->nextArc = awaitDel->nextArc; + delete awaitDel; + } + } + } +} + +int ALGraph::GetIdOf(MyGraphicsVexItem *gvex){ + int i = 0; + while(i < vexList.size() && !vexList[i].info->gVex->equalTo(gvex)) + i++; + return i == vexList.size() ? -1 : i; +} + +ALArc* ALGraph::FindArc(int sID, int eID){ + if(sID < 0 || sID >= vexList.size()) + return nullptr; + ALArc *p = vexList[sID].firstArc; + while(p != nullptr){ + if(p->eVexID == eID) + return p; + p = p->nextArc; + } + return nullptr; +} + +void ALGraph::SetWeight(MyGraphicsLineItem *garc, int weight){ + int strtVex = GetIdOf(garc->stVex()); + int endVex = GetIdOf(garc->edVex()); + ALArc *p = vexList[strtVex].firstArc; + while(p != nullptr){ + if(p->eVexID == endVex){ + p->weight = weight; + p->gArc->setText(QString::asprintf("%d", weight)); + } + p = p->nextArc; + } + if(type == UDG){ + p = vexList[endVex].firstArc; + while(p != nullptr){ + if(p->eVexID == strtVex){ + p->weight = weight; + } + p = p->nextArc; + } + } +} + +void ALGraph::ConvertType(int _type){ + if(_type == type) return; + type = _type; + if(type == UDG){ + for(int i = 0; i < vexList.size(); i++){ + ALArc *dummyHead = new ALArc(nullptr, i, vexList[i].firstArc); + ALArc *pre = dummyHead; + while(pre->nextArc != nullptr){ + if(pre->nextArc->eVexID == GetIdOf(pre->nextArc->gArc->edVex())){ + ALArc *temp = vexList[pre->nextArc->eVexID].firstArc; + vexList[pre->nextArc->eVexID].firstArc = new ALArc(pre->nextArc->gArc, i, temp); + pre->nextArc->gArc->setDirection(false); + } + pre = pre->nextArc; + } + delete dummyHead; + } + } + else{ + for(int i = 0; i < vexList.size(); i++){ + ALArc *dummyHead = new ALArc(nullptr, i, vexList[i].firstArc); + ALArc *pre = dummyHead; + while(pre->nextArc != nullptr){ + if(pre->nextArc->eVexID != GetIdOf(pre->nextArc->gArc->edVex())){ + ALArc *temp = pre->nextArc; + pre->nextArc = temp->nextArc; + temp->gArc->setDirection(true); + delete temp; + continue; + } + pre = pre->nextArc; + } + vexList[i].firstArc = dummyHead->nextArc; + delete dummyHead; + } + } +} + +void ALGraph::ClearVisit(){ + for(int i = 0; i < vexList.size(); i++){ + vexList[i].visited = false; + vexList[i].info->gVex->visit(false); + } +} + +void ALGraph::ResetDistance(){ + for(int i = 0; i < vexList.size(); i++){ + vexList[i].info->strtVexInfo = nullptr; + vexList[i].info->distance = VexInfo::INF; + vexList[i].info->preVexID = -1; + vexList[i].info->gVex->access("", false); + } +} + +void ALGraph::DFS(int strtID){ + if(strtID == -1) + return; + vector awaitVexList; + vector awaitArcList; + awaitVexList.push_back(strtID); + while(awaitVexList.size() > 0){ + int nextVex = awaitVexList.back(); + ALArc *nextArc = awaitArcList.size() > 0 ? awaitArcList.back() : nullptr; + awaitVexList.pop_back(); + if(nextArc) + awaitArcList.pop_back(); + for(ALArc *p = vexList[nextVex].firstArc; p != nullptr; p = p->nextArc){ + if(vexList[p->eVexID].visited == false){ + awaitVexList.push_back(p->eVexID); + awaitArcList.push_back(p); + if(type == UDG && GetIdOf(p->gArc->edVex()) != p->eVexID) + p->gArc->reverseDirection(); + } + } + if(nextArc && !vexList[nextArc->eVexID].visited) + nextArc->visit(); + vexList[nextVex].visit(); + } +} + +void ALGraph::BFS(int strtID){ + if(strtID == -1) + return; + vector awaitVexList; + vector awaitArcList; + awaitVexList.push_back(strtID); + while(awaitVexList.size() > 0){ + int nextVex = awaitVexList[0]; + ALArc *nextArc = awaitArcList.size() > 0 ? awaitArcList[0] : nullptr; + awaitVexList.erase(awaitVexList.begin()); + if(nextArc) + awaitArcList.erase(awaitArcList.begin()); + for(ALArc *p = vexList[nextVex].firstArc; p != nullptr; p = p->nextArc){ + if(vexList[p->eVexID].visited == false){ + awaitVexList.push_back(p->eVexID); + awaitArcList.push_back(p); + if(type == UDG && GetIdOf(p->gArc->edVex()) != p->eVexID) + p->gArc->reverseDirection(); + } + } + if(nextArc && !vexList[nextArc->eVexID].visited) + nextArc->visit(); + vexList[nextVex].visit(); + } +} + +void ALGraph::Dijkstra(int strtID){ + //Clear previous result + ClearVisit(); + ResetDistance(); + //Set start vex info to all vertexes + for(int i = 0; i < vexList.size(); i++) + vexList[i].info->strtVexInfo = vexList[strtID].info; + //Start dijkstra + vexList[strtID].info->distance = 0; + vexList[strtID].access("strt"); + while(true){ + //Find next + int minVexID = -1; + for(int i = 0; i < vexList.size(); i++){ + if(vexList[i].visited || vexList[i].info->distance == VexInfo::INF) + continue; + if(minVexID == -1) + minVexID = i; + else if(vexList[i].info->distance < vexList[minVexID].info->distance) + minVexID = i; + } + if(minVexID == -1) + break; + //Set visit to edge and vex + ALArc *edge = FindArc(vexList[minVexID].info->preVexID, minVexID); + if(edge){ + if(type == UDG && GetIdOf(edge->gArc->edVex()) != minVexID) + edge->gArc->reverseDirection(); + edge->visit(); + } + vexList[minVexID].visit(); + //Find adjacent + for(ALArc *p = vexList[minVexID].firstArc; p != nullptr; p = p->nextArc){ + if(!vexList[p->eVexID].visited){ + if(GetIdOf(p->gArc->edVex()) != p->eVexID) + p->gArc->reverseDirection(); + p->access(); + if(vexList[p->eVexID].info->distance == VexInfo::INF || + vexList[p->eVexID].info->distance > vexList[minVexID].info->distance + p->weight){ + vexList[p->eVexID].info->preVexID = minVexID; + vexList[p->eVexID].info->distance = vexList[minVexID].info->distance + p->weight; + vexList[p->eVexID].access(QString::asprintf("%d", vexList[p->eVexID].info->distance)); + } + } + } + } +} + +AMLGraph* ALGraph::ConvertToAML(){ + AMLGraph *converted = new AMLGraph(type); + for(int i = 0; i < vexList.size(); i++){ + converted->AddVex(vexList[i].info); + } + for(int i = 0; i < vexList.size(); i++){ + ALArc *p = vexList[i].firstArc; + while(p != nullptr){ + if(type == DG || (type == UDG && i == GetIdOf(p->gArc->edVex()))) + converted->AddArc(p->gArc, p->weight); + p = p->nextArc; + } + } + return converted; +} + +/**************************************************************************************/ + +void AMLVex::visit(){ + if(visited) + return; + info->gVex->visit(true); + visited = true; +} + +void AMLVex::access(const QString &hint){ + info->gVex->access(hint); +} + +void AMLArc::visit(){ + gArc->visit(true); +} + +void AMLArc::access(){ + gArc->access(); +} + +AMLGraph::~AMLGraph(){ + int i = outVexList.size() - 1; + while(i >= 0){ + DelVex(i--); + } + outVexList.clear(); + if(type == DG) + inVexList.clear(); +} + +void AMLGraph::AddVex(MyGraphicsVexItem *gvex){ + AMLVex newVex(gvex); + outVexList.push_back(newVex); + + if(type == DG){ + inVexList.push_back(newVex); + } +} + +void AMLGraph::AddVex(VexInfo *info){ + AMLVex newVex(info); + outVexList.push_back(newVex); + + if(type == DG){ + inVexList.push_back(newVex); + } +} + +void AMLGraph::AddArc(MyGraphicsLineItem *garc, int weight){ + int strtVex = GetIdOf(garc->stVex()); + int endVex = GetIdOf(garc->edVex()); + + AMLArc *nextOutArc = outVexList[strtVex].firstArc; + AMLArc *nextInArc = type == DG ? inVexList[endVex].firstArc : outVexList[endVex].firstArc; + AMLArc *newArc = new AMLArc(garc, strtVex, endVex, nextOutArc, nextInArc); + newArc->weight = weight; + outVexList[strtVex].firstArc = newArc; + if(type == DG) + inVexList[endVex].firstArc = newArc; + else + outVexList[endVex].firstArc = newArc; +} + +void AMLGraph::DelVex(MyGraphicsVexItem *gvex){ + int vexID = GetIdOf(gvex); + DelVex(vexID); +} + +void AMLGraph::DelVex(int vexID){ + if(type == DG){ + //Delete out arc + AMLArc *outArc = outVexList[vexID].firstArc; + while(outArc != nullptr){ + AMLArc *dummyHead = new AMLArc(nullptr, 0, 0, nullptr, inVexList[outArc->inVexID].firstArc); + AMLArc *preInArc = dummyHead; + while(preInArc->nextInArc != nullptr){ + if(preInArc->nextInArc->outVexID == vexID){ + AMLArc *next = preInArc->nextInArc; + preInArc->nextInArc = next->nextInArc; + delete next; + } + else + preInArc = preInArc->nextInArc; + } + inVexList[outArc->inVexID].firstArc = dummyHead->nextInArc; + delete dummyHead; + outArc = outArc->nextOutArc; + } + //Delete in arc and adjust ID + for(int i = 0; i < outVexList.size(); i++){ + if(i == vexID) continue; + AMLArc *dummyHead = new AMLArc(nullptr, 0, 0, outVexList[i].firstArc, nullptr); + AMLArc *preOutArc = dummyHead; + while(preOutArc->nextOutArc != nullptr){ + if(preOutArc->nextOutArc->inVexID == vexID){ + AMLArc *next = preOutArc->nextOutArc; + preOutArc->nextOutArc = next->nextOutArc; + delete next; + continue; + } + if(preOutArc->nextOutArc->inVexID > vexID) + preOutArc->nextOutArc->inVexID--; + if(preOutArc->nextOutArc->outVexID > vexID) + preOutArc->nextOutArc->outVexID--; + preOutArc = preOutArc->nextOutArc; + } + outVexList[i].firstArc = dummyHead->nextOutArc; + delete dummyHead; + } + outVexList.erase(outVexList.begin() + vexID); + inVexList.erase(inVexList.begin() + vexID); + } + else{ + //Traverse all and adjust ID + AMLArc *p = outVexList[vexID].firstArc; + while(p != nullptr){ + if(p->outVexID == vexID){ + AMLArc *temp = p->nextOutArc; + p->nextOutArc = nullptr; + p = temp; + } + else{ + AMLArc *temp = p->nextInArc; + p->nextInArc = nullptr; + p = temp; + } + } + outVexList[vexID].firstArc = nullptr; + for(int i = 0; i < outVexList.size(); i++){ + if(i == vexID) continue; + AMLArc *dummyHead = new AMLArc(nullptr, i, -1, outVexList[i].firstArc, nullptr); + AMLArc *preArc = dummyHead; + AMLArc *nextArc = preArc->nextOutArc; + while(nextArc != nullptr){ + if(nextArc->inVexID == vexID || nextArc->outVexID == vexID){ + if(preArc->outVexID == i){ + if(nextArc->outVexID == i){ + preArc->nextOutArc = nextArc->nextOutArc; + nextArc->nextOutArc = nullptr; + delete nextArc; + } + else{ + preArc->nextOutArc = nextArc->nextInArc; + nextArc->nextInArc = nullptr; + delete nextArc; + } + } + else{ + if(nextArc->outVexID == i){ + preArc->nextInArc = nextArc->nextOutArc; + nextArc->nextOutArc = nullptr; + delete nextArc; + } + else{ + preArc->nextInArc = nextArc->nextInArc; + nextArc->nextInArc = nullptr; + delete nextArc; + } + } + nextArc = preArc->outVexID == i ? preArc->nextOutArc : preArc->nextInArc; + continue; + } + preArc = nextArc; + nextArc = preArc->outVexID == i ? preArc->nextOutArc : preArc->nextInArc; + if(preArc->inVexID > vexID) + preArc->inVexID = - preArc->inVexID; + else if(preArc->inVexID < 0) + preArc->inVexID = - preArc->inVexID; + if(preArc->outVexID > vexID) + preArc->outVexID = - preArc->outVexID; + else if(preArc->outVexID < 0) + preArc->outVexID = - preArc->outVexID; + } + outVexList[i].firstArc = dummyHead->nextOutArc; + delete dummyHead; + } + //Delete vexID + outVexList.erase(outVexList.begin() + vexID); + } +} + +void AMLGraph::DelArc(MyGraphicsLineItem *garc){ + int sVex = GetIdOf(garc->stVex()); + int eVex = GetIdOf(garc->edVex()); + DelArc(sVex, eVex); +} + +void AMLGraph::DelArc(int sVexID, int eVexID){ + if(type == DG){ + AMLArc *dummyHead; + AMLArc *preArc; + //Delete sVex -> eVex + dummyHead = new AMLArc(nullptr, sVexID, -1, outVexList[sVexID].firstArc, nullptr); + preArc = dummyHead; + while(preArc->nextOutArc != nullptr){ + if(preArc->nextOutArc->inVexID == eVexID){ + preArc->nextOutArc = preArc->nextOutArc->nextOutArc; + continue; + } + else + preArc = preArc->nextOutArc; + } + outVexList[sVexID].firstArc = dummyHead->nextOutArc; + delete dummyHead; + //Delete eVex -> sVex + dummyHead = new AMLArc(nullptr, -1, eVexID, nullptr, inVexList[eVexID].firstArc); + preArc = dummyHead; + while(preArc->nextInArc != nullptr){ + if(preArc->nextInArc->outVexID == sVexID){ + AMLArc *awaitDel = preArc->nextInArc; + preArc->nextInArc = preArc->nextInArc->nextInArc; + delete awaitDel; + continue; + } + else + preArc = preArc->nextInArc; + } + inVexList[eVexID].firstArc = dummyHead->nextInArc; + delete dummyHead; + } + else{ + AMLArc *dummyHead; + AMLArc *preArc; + AMLArc *nextArc; + //Delete sVex -> eVex + dummyHead = new AMLArc(nullptr, sVexID, -1, outVexList[sVexID].firstArc, nullptr); + preArc = dummyHead; + nextArc = outVexList[sVexID].firstArc; + while(nextArc != nullptr){ + if(nextArc->inVexID == eVexID || nextArc->outVexID == eVexID){ + if(preArc->outVexID == sVexID){ + if(nextArc->outVexID == sVexID){ + preArc->nextOutArc = nextArc->nextOutArc; + nextArc->nextOutArc = nullptr; + } + else{ + preArc->nextOutArc = nextArc->nextInArc; + nextArc->nextInArc = nullptr; + } + } + else{ + if(nextArc->outVexID == sVexID){ + preArc->nextInArc = nextArc->nextOutArc; + nextArc->nextOutArc = nullptr; + } + else{ + preArc->nextInArc = nextArc->nextInArc; + nextArc->nextInArc = nullptr; + } + } + nextArc = preArc->outVexID == sVexID ? preArc->nextOutArc : preArc->nextInArc; + continue; + } + else{ + preArc = nextArc; + nextArc = preArc->outVexID == sVexID ? preArc->nextOutArc : preArc->nextInArc; + } + } + outVexList[sVexID].firstArc = dummyHead->nextOutArc; + delete dummyHead; + //Delete eVex -> sVex and release arc + dummyHead = new AMLArc(nullptr, -1, eVexID, nullptr, outVexList[eVexID].firstArc); + preArc = dummyHead; + nextArc = outVexList[eVexID].firstArc; + while(nextArc != nullptr){ + if(nextArc->inVexID == sVexID || nextArc->outVexID == sVexID){ + if(preArc->outVexID == eVexID){ + if(nextArc->outVexID == eVexID){ + preArc->nextOutArc = nextArc->nextOutArc; + delete nextArc; + } + else{ + preArc->nextOutArc = nextArc->nextInArc; + delete nextArc; + } + } + else{ + if(nextArc->outVexID == eVexID){ + preArc->nextInArc = nextArc->nextOutArc; + delete nextArc; + } + else{ + preArc->nextInArc = nextArc->nextInArc; + delete nextArc; + } + } + nextArc = preArc->outVexID == eVexID ? preArc->nextOutArc : preArc->nextInArc; + continue; + } + else{ + preArc = nextArc; + nextArc = preArc->outVexID == eVexID ? preArc->nextOutArc : preArc->nextInArc; + } + } + outVexList[eVexID].firstArc = dummyHead->nextInArc; + delete dummyHead; + } +} + +int AMLGraph::GetIdOf(MyGraphicsVexItem *gvex){ + int i = 0; + while(i < outVexList.size() && outVexList[i].info->gVex != gvex) + i++; + return i == outVexList.size() ? -1 : i; +} + +AMLArc* AMLGraph::FindArc(int strtID, int endID){ + if(strtID < 0 || strtID >= outVexList.size()) + return nullptr; + AMLArc *p = outVexList[strtID].firstArc; + while(p != nullptr){ + if(p->outVexID == endID || p->inVexID == endID) + return p; + p = p->outVexID == strtID ? p->nextOutArc : p->nextInArc; + } + return nullptr; +} + +void AMLGraph::SetWeight(MyGraphicsLineItem *garc, int weight){ + int strtVex = GetIdOf(garc->stVex()); + int endVex = GetIdOf(garc->edVex()); + AMLArc *p = outVexList[strtVex].firstArc; + while(p != nullptr){ + if(p->inVexID == endVex || p->outVexID == endVex){ + p->weight = weight; + p->gArc->setText(QString::asprintf("%d", weight)); + } + p = p->inVexID == strtVex ? p->nextInArc : p->nextOutArc; + } + if(type == DG){ + p = inVexList[endVex].firstArc; + while(p != nullptr){ + if(p->inVexID == strtVex || p->outVexID == strtVex){ + p->weight = weight; + p->gArc->setText(QString::asprintf("%d", weight)); + } + p = p->inVexID == endVex ? p->nextInArc : p->nextOutArc; + } + } +} + +void AMLGraph::ConvertType(int _type){ + if(_type == type) return; + type = _type; + if(type == UDG){ + for(int i = 0; i < inVexList.size(); i++){ + AMLArc *p = inVexList[i].firstArc; + while(p != nullptr && p->nextInArc != nullptr){ + p->gArc->setDirection(false); + p = p->nextInArc; + } + AMLArc *temp = outVexList[i].firstArc; + if(p){ + outVexList[i].firstArc = inVexList[i].firstArc; + p->nextInArc = temp; + } + while(temp != nullptr){ + temp->gArc->setDirection(false); + temp = temp->nextOutArc; + } + } + } + else{ + inVexList.clear(); + inVexList.assign(outVexList.begin(), outVexList.end()); + for(int i = 0; i < inVexList.size(); i++) + inVexList[i].firstArc = nullptr; + for(int i = 0; i < outVexList.size(); i++){ + AMLArc *dummyHead = new AMLArc(nullptr, i, -1, outVexList[i].firstArc, nullptr); + AMLArc *pre = dummyHead; + AMLArc *next = outVexList[i].firstArc; + while(next != nullptr){ + next->gArc->setDirection(true); + if(next->outVexID != i){ + //A reversed edge + pre->nextOutArc = next->nextInArc; + AMLArc *temp = inVexList[next->inVexID].firstArc; + inVexList[next->inVexID].firstArc = next; + next->nextInArc = temp; + next = pre->nextOutArc; + continue; + } + pre = next; + next = pre->nextOutArc; + } + outVexList[i].firstArc = dummyHead->nextOutArc; + delete dummyHead; + } + } +} + +void AMLGraph::ClearVisit(){ + for(int i = 0; i < outVexList.size(); i++){ + outVexList[i].visited = false; + outVexList[i].info->gVex->visit(false); + } + if(type == DG) + for(int i = 0; i < inVexList.size(); i++) + inVexList[i].visited = false; +} + +void AMLGraph::ResetDistance(){ + for(int i = 0; i < outVexList.size(); i++){ + outVexList[i].info->strtVexInfo = nullptr; + outVexList[i].info->distance = VexInfo::INF; + outVexList[i].info->preVexID = -1; + outVexList[i].info->gVex->access("", false); + } + if(type == DG) + for(int i = 0; i < outVexList.size(); i++){ + inVexList[i].info->strtVexInfo = nullptr; + inVexList[i].info->distance = VexInfo::INF; + inVexList[i].info->preVexID = -1; + } +} + +void AMLGraph::DFS(int strtID){ + if(strtID == -1) + return; + vector awaitVexList; + vectorawaitArcList; + awaitVexList.push_back(strtID); + while(awaitVexList.size() > 0){ + int nextVex = awaitVexList.back(); + AMLArc *nextArc = awaitArcList.size() > 0 ? awaitArcList.back() : nullptr; + awaitVexList.pop_back(); + if(nextArc) + awaitArcList.pop_back(); + for(AMLArc *p = outVexList[nextVex].firstArc; p != nullptr; p = p->outVexID == strtID ? p->nextOutArc : p->nextInArc){ + int endID = p->outVexID == nextVex ? p->inVexID : p->outVexID; + if(outVexList[endID].visited == false){ + awaitVexList.push_back(endID); + awaitArcList.push_back(p); + if(type == UDG && GetIdOf(p->gArc->edVex()) != endID) + p->gArc->reverseDirection(); + } + } + if(nextArc && !outVexList[nextVex].visited) + nextArc->visit(); + outVexList[nextVex].visit(); + } +} + +void AMLGraph::BFS(int strtID){ + if(strtID == -1) + return; + vector awaitVexList; + vector awaitArcList; + awaitVexList.push_back(strtID); + while(awaitVexList.size() > 0){ + int nextVex = awaitVexList[0]; + AMLArc *nextArc = awaitArcList.size() > 0 ? awaitArcList[0] : nullptr; + awaitVexList.erase(awaitVexList.begin()); + if(nextArc) + awaitArcList.erase(awaitArcList.begin()); + for(AMLArc *p = outVexList[nextVex].firstArc; p != nullptr; p = p->outVexID == strtID ? p->nextOutArc : p->nextInArc){ + int endID = p->outVexID == nextVex ? p->inVexID : p->outVexID; + if(outVexList[endID].visited == false){ + awaitVexList.push_back(endID); + awaitArcList.push_back(p); + if(type == UDG && GetIdOf(p->gArc->edVex()) != endID) + p->gArc->reverseDirection(); + } + } + if(nextArc && !outVexList[nextVex].visited) + nextArc->visit(); + outVexList[nextVex].visit(); + } +} + +void AMLGraph::Dijkstra(int strtID){ + //Clear previous result + ClearVisit(); + ResetDistance(); + //Set start vex info to all vertexes + for(int i = 0; i < outVexList.size(); i++) + outVexList[i].info->strtVexInfo = outVexList[strtID].info; + //Start dijkstra + outVexList[strtID].info->distance = 0; + outVexList[strtID].access("strt"); + while(true){ + //Find next + int minVexID = -1; + for(int i = 0; i < outVexList.size(); i++){ + if(outVexList[i].visited || outVexList[i].info->distance == VexInfo::INF) + continue; + if(minVexID == -1) + minVexID = i; + else if(outVexList[i].info->distance < outVexList[minVexID].info->distance) + minVexID = i; + } + if(minVexID == -1) + break; + //Set visit to edge and vex + AMLArc *edge = FindArc(outVexList[minVexID].info->preVexID, minVexID); + if(edge){ + if(type == UDG && GetIdOf(edge->gArc->edVex()) != minVexID) + edge->gArc->reverseDirection(); + edge->visit(); + } + outVexList[minVexID].visit(); + //Find adjacent + for(AMLArc *p = outVexList[minVexID].firstArc; p != nullptr; p = p->outVexID == strtID ? p->nextOutArc : p->nextInArc){ + int endID = p->outVexID == minVexID ? p->inVexID : p->outVexID; + if(!outVexList[endID].visited){ + if(GetIdOf(p->gArc->edVex()) != endID) + p->gArc->reverseDirection(); + p->access(); + if(outVexList[endID].info->distance == VexInfo::INF || + outVexList[endID].info->distance > outVexList[minVexID].info->distance + p->weight){ + outVexList[endID].info->preVexID = minVexID; + outVexList[endID].info->distance = outVexList[minVexID].info->distance + p->weight; + outVexList[endID].access(QString::asprintf("%d", outVexList[endID].info->distance)); + } + } + } + } +} + +ALGraph* AMLGraph::ConvertToAL(){ + ALGraph *converted = new ALGraph(type); + for(int i = 0; i < outVexList.size(); i++) + converted->AddVex(outVexList[i].info); + for(int i = 0; i < outVexList.size(); i++){ + AMLArc *p = outVexList[i].firstArc; + while(p != nullptr){ + if(p->outVexID == i) + converted->AddArc(p->gArc); + p = p->outVexID == i ? p->nextOutArc : p->nextInArc; + } + } + return converted; +} diff --git a/graph_implement.h b/graph_implement.h new file mode 100644 index 0000000..e5cbc95 --- /dev/null +++ b/graph_implement.h @@ -0,0 +1,216 @@ +#ifndef GRAPH_IMPLEMENT_H +#define GRAPH_IMPLEMENT_H + +#include "graph_view.h" + +using namespace std; + +class VexInfo{ +public: + enum { INF = 2147483647 }; + MyGraphicsVexItem *gVex; + VexInfo *strtVexInfo = nullptr; + int preVexID = -1; + int distance = INF; + + VexInfo(MyGraphicsVexItem *vex) : gVex(vex){} +}; + +class AbstractGraph{ +protected: + int type; + int vexNum = 0; + +public: + enum { UDG = 1, DG = 2 }; + + AbstractGraph(int _type = DG) : type(_type){} + virtual ~AbstractGraph() = 0; + /* Insert */ + virtual void AddVex(MyGraphicsVexItem *gvex) = 0; + virtual void AddVex(VexInfo *info) = 0; + virtual void AddArc(MyGraphicsLineItem *garc, int weight = 1) = 0; + + /* Delete */ + virtual void DelVex(MyGraphicsVexItem *gvex) = 0; + virtual void DelVex(int vexID) = 0; + virtual void DelArc(MyGraphicsLineItem *garc) = 0; + virtual void DelArc(int sVexID, int eVexID) = 0; + + /* Find */ + virtual int GetIdOf(MyGraphicsVexItem *gvex) = 0; + virtual VexInfo* GetInfoOf(int id) = 0; + virtual VexInfo* GetInfoOf(MyGraphicsVexItem *gvex) = 0; + + /* Modify */ + //virtual void SetText(MyGraphicsVexItem *gvex); + virtual void SetWeight(MyGraphicsLineItem *garc, int weight) = 0; + virtual void ConvertType(int _type) = 0; + + /* Other Function */ + virtual void ClearVisit() = 0; + virtual void ResetDistance() = 0; + virtual void DFS(int strtID) = 0; + virtual void DFS(MyGraphicsVexItem *strtVex) = 0; + virtual void BFS(int strtID) = 0; + virtual void BFS(MyGraphicsVexItem *strtVex) = 0; + virtual void Dijkstra(int strtID) = 0; + virtual void Dijkstra(MyGraphicsVexItem *strtVex) = 0; + + virtual int Type() const = 0; +}; + +class ALVex; +class ALArc; +class ALGraph; + +class AMLVex; +class AMLArc; +class AMLGraph; + +/* Classes of ALGraph */ + +class ALVex{ +public: + bool visited = false; + + VexInfo *info = nullptr; + ALArc *firstArc = nullptr; + + ALVex(MyGraphicsVexItem *vex){info = new VexInfo(vex);} + ALVex(VexInfo *_info){info = _info;} + bool equalTo(const ALVex &v){return info == v.info;} + void visit(); + void access(const QString &hint = ""); +}; + +class ALArc{ +public: + MyGraphicsLineItem *gArc; + int weight = 1; + int eVexID; + ALArc *nextArc = nullptr; + + ALArc(MyGraphicsLineItem *garc, int eVex, ALArc *next = nullptr) : gArc(garc), eVexID(eVex), nextArc(next){} + void visit(); + void access(); +}; + +class ALGraph : public AbstractGraph{ +private: + vector vexList; + +public: + ALGraph(int _type = DG) : AbstractGraph(_type){} + ~ALGraph(); + /* Insert */ + void AddVex(MyGraphicsVexItem *gvex); + void AddVex(VexInfo *info); + void AddArc(MyGraphicsLineItem *garc, int weight = 1); + + /* Delete */ + void DelVex(MyGraphicsVexItem *gvex); + void DelVex(int vexID); + void DelArc(MyGraphicsLineItem *garc); + void DelArc(int sVexID, int eVexID); + + /* Find */ + int GetIdOf(MyGraphicsVexItem *gvex); + VexInfo* GetInfoOf(int id){return vexList[id].info;} + VexInfo* GetInfoOf(MyGraphicsVexItem *gvex){return vexList[GetIdOf(gvex)].info;} + ALArc* FindArc(int sID, int eID); + + /* Modify */ + void SetWeight(MyGraphicsLineItem *garc, int weight); + void ConvertType(int _type); + + /* Other Function */ + void ClearVisit(); + void ResetDistance(); + void DFS(int strtID); + void DFS(MyGraphicsVexItem *strtVex){DFS(GetIdOf(strtVex));} + void BFS(int strtID); + void BFS(MyGraphicsVexItem *strtVex){BFS(GetIdOf(strtVex));} + void Dijkstra(int strtID); + void Dijkstra(MyGraphicsVexItem *strtVex){Dijkstra(GetIdOf(strtVex));} + AMLGraph* ConvertToAML(); + + int Type() const { return type; } +}; + +/* Classes of AMLGraph */ + +class AMLVex{ +public: + bool visited = false; + + VexInfo *info = nullptr; + AMLArc *firstArc = nullptr; + + AMLVex(MyGraphicsVexItem *gvex){info = new VexInfo(gvex);} + AMLVex(VexInfo *_info){info = _info;} + bool equalTo(const AMLVex &v){return info == v.info;} + void visit(); + void access(const QString &hint = ""); +}; + +class AMLArc{ +public: + MyGraphicsLineItem *gArc; + int weight = 1; + int outVexID; + AMLArc *nextOutArc = nullptr; + int inVexID; + AMLArc *nextInArc = nullptr; + + AMLArc(MyGraphicsLineItem *garc, int sVex, int eVex) : gArc(garc), outVexID(sVex), inVexID(eVex){} + AMLArc(MyGraphicsLineItem *garc, int sVex, int eVex, AMLArc *nextOut, AMLArc *nextIn) : + gArc(garc), outVexID(sVex), nextOutArc(nextOut), inVexID(eVex), nextInArc(nextIn){} + void visit(); + void access(); +}; + +class AMLGraph : public AbstractGraph{ +private: + vector outVexList; + vector inVexList; + +public: + AMLGraph(int _type = DG) : AbstractGraph(_type){} + ~AMLGraph(); + /* Insert */ + void AddVex(MyGraphicsVexItem *gvex); + void AddVex(VexInfo *info); + void AddArc(MyGraphicsLineItem *garc, int weight = 1); + + /* Delete */ + void DelVex(MyGraphicsVexItem *gvex); + void DelVex(int vexID); + void DelArc(MyGraphicsLineItem *garc); + void DelArc(int sVexID, int eVexID); + + /* Find */ + int GetIdOf(MyGraphicsVexItem *gvex); + VexInfo* GetInfoOf(int id){return outVexList[id].info;} + VexInfo* GetInfoOf(MyGraphicsVexItem *gvex){return outVexList[GetIdOf(gvex)].info;} + AMLArc* FindArc(int strtID, int endID); + + /* Modify */ + void SetWeight(MyGraphicsLineItem *garc, int weight); + void ConvertType(int _type); + + /* Other Function */ + void ClearVisit(); + void ResetDistance(); + void DFS(int strtID); + void DFS(MyGraphicsVexItem *strtVex){DFS(GetIdOf(strtVex));} + void BFS(int strtID); + void BFS(MyGraphicsVexItem *strtVex){BFS(GetIdOf(strtVex));} + void Dijkstra(int strtID); + void Dijkstra(MyGraphicsVexItem *strtVex){Dijkstra(GetIdOf(strtVex));} + ALGraph* ConvertToAL(); + + int Type() const { return type; } +}; + +#endif // GRAPH_IMPLEMENT_H diff --git a/graph_view.cpp b/graph_view.cpp new file mode 100644 index 0000000..4209395 --- /dev/null +++ b/graph_view.cpp @@ -0,0 +1,1085 @@ +#include "graph_view.h" +#include + +viewLog::viewLog(QString log, QWidget *parent) : + QLabel(parent) +{ + logText = log; + this->setFont(logFont); + this->setText(log); + this->setFixedHeight(QFontMetrics(logFont).lineSpacing()); +} + +void viewLog::resizeEvent(QResizeEvent *event){ + QString elideText = QFontMetrics(logFont).elidedText(logText, Qt::ElideRight, this->width() - 5); + this->setText(elideText); + this->show(); +} + +//MyGraphicsView +MyGraphicsView::MyGraphicsView(int _type, QWidget *parent) : + QGraphicsView(parent), + type(_type){ + this->setMouseTracking(true); + this->setBackgroundBrush(Qt::transparent); + myGraphicsScene = new QGraphicsScene(); + this->setScene(myGraphicsScene); + this->setRenderHint(QPainter::Antialiasing); + this->setCursor(Qt::CrossCursor); + this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + setTransformationAnchor(QGraphicsView::AnchorUnderMouse); + setResizeAnchor(QGraphicsView::AnchorUnderMouse); +} + +void MyGraphicsView::nextAni(){ + if(aniQueue.size() > 0){ + QTimeLine *next = aniQueue.front(); + curAni = next; + aniQueue.pop_front(); + connect(next, &QTimeLine::finished, [=](){nextAni(); next->deleteLater();}); + next->setDuration(next->duration() / speedRate); + next->start(); + } + else{ + onAni = false; + curAni = nullptr; + } +} + +void MyGraphicsView::mousePressEvent(QMouseEvent *event){ + if(event->button() == Qt::MiddleButton){ + onMiddlePress = true; + lastPos = mapToScene(event->pos()); + return; + } + if(hasVisitedItem){ + emit visitClear(); + if(curAni){ + curAni->stop(); + curAni->deleteLater(); + curAni = nullptr; + onAni = false; + } + aniQueue.clear(); + hasVisitedItem = false; + } + if(itemState & ADD){ + if(event->button() == Qt::LeftButton){ + selItem = nullptr; + emit mouseLeftClicked(mapToScene(event->pos())); + } + else{ + clearSketch(); + itemState = selItem == nullptr ? NON : SEL | VEX; + } + } + else{ + itemState = NON; + if(event->button() == Qt::LeftButton) + emit mouseLeftClicked(mapToScene(event->pos())); + else if(event->button() == Qt::RightButton){ + emit mouseRightClicked(mapToScene(event->pos())); + onRightPress = true; + } + } + changeCursor(); +} + +void MyGraphicsView::mouseReleaseEvent(QMouseEvent *event){ + if(onMiddlePress){ + onMiddlePress = false; + return; + } + if(itemState == NON){ + if(selItem == nullptr){ + //create vex + if(onRightPress) + onRightPress = false; + else + addVex(mapToScene(event->pos())); + } + else{ + //deselect + if(event->buttons() == 0){ + selItem = nullptr; + emit unselected(); + } + else if(selItem != nullptr) + itemState |= SEL; + } + } + else if(itemState & ADD){ + clearSketch(); + itemState = NON; + if(selItem == nullptr){ + selItem = addVex(mapToScene(event->pos())); + ((MyGraphicsVexItem*)selItem)->select(); + addLine(strtVex, (MyGraphicsVexItem*)selItem); + } + else if(selItem->type() == QGraphicsItem::UserType + 1){ + addLine(strtVex, (MyGraphicsVexItem*)selItem); + } + else{ + itemState = SEL | selItem->type(); + } + emit selected(selItem); + } + else if(itemState & SEL) + emit selected(selItem); + emit mouseReleased(); + changeCursor(); +} + +void MyGraphicsView::mouseMoveEvent(QMouseEvent *event){ + if(onMiddlePress){ + QPointF dp = mapToScene(event->pos()) - lastPos; + setSceneRect(sceneRect().x() - dp.x(), sceneRect().y() - dp.y(), sceneRect().width(), sceneRect().height()); + lastPos = mapToScene(event->pos()); + } + changeCursor(); + emit mouseMoved(mapToScene(event->pos())); + if(itemState & ADD){ + clearSketch(); + sketchLine(strtVex->scenePos() + strtVex->rect().center(), mapToScene(event->pos())); + } +} + +void MyGraphicsView::wheelEvent(QWheelEvent *event){ + QPointF cursorPoint = event->position(); + QPointF scenePos = this->mapToScene(QPoint(cursorPoint.x(), cursorPoint.y())); + + qreal viewWidth = this->viewport()->width(); + qreal viewHeight = this->viewport()->height(); + + qreal hScale = cursorPoint.x() / viewWidth; + qreal vScale = cursorPoint.y() / viewHeight; + + qreal scaleFactor = this->transform().m11(); + int wheelDeltaValue = event->angleDelta().y(); + if (wheelDeltaValue > 0) + { + if(scaleFactor > 2) return; + this->scale(1.1, 1.1); + } + else + { + if(scaleFactor < 0.5) return; + this->scale(1.0 / 1.1, 1.0 / 1.1); + } + QPointF viewPoint = this->transform().map(scenePos); + horizontalScrollBar()->setValue(int(viewPoint.x() - viewWidth * hScale)); + verticalScrollBar()->setValue(int(viewPoint.y() - viewHeight * vScale)); +} + +void MyGraphicsView::changeCursor(){ + +} + +MyGraphicsVexItem* MyGraphicsView::addVex(QPointF center, qreal radius){ + MyGraphicsVexItem *newVex = new MyGraphicsVexItem(center, radius, vexID++); + this->scene()->addItem(newVex); + newVex->estConnection(this); + newVex->showAnimation(); + vexNum++; + vexes.push_back(newVex); + emit vexAdded(newVex); + emit logAdded(new viewLog("[Vex] | Added \""+newVex->Text()+"\"")); + return newVex; +} + +void MyGraphicsView::clearSketch(){ + if(sketchItem != nullptr){ + scene()->removeItem(sketchItem); + sketchItem = nullptr; + } +} + +void MyGraphicsView::sketchLine(QPointF start, QPointF end){ + QGraphicsLineItem *newLine = new QGraphicsLineItem(start.x(), start.y(), end.x(), end.y()); + QPen pen; + pen.setWidth(3); + pen.setStyle(Qt::DashLine); + pen.setBrush(QColor(58, 143, 192, 100)); + pen.setCapStyle(Qt::RoundCap); + newLine->setPen(pen); + scene()->addItem(newLine); + newLine->stackBefore(selItem); + sketchItem = newLine; +} + +void MyGraphicsView::addLine(MyGraphicsVexItem *start, MyGraphicsVexItem *end){ + MyGraphicsLineItem *newLine = new MyGraphicsLineItem(start, end, type == DG); + scene()->addItem(newLine); + newLine->estConnection(this); + newLine->refrshLine(); + newLine->setZValue(--zValue); + start->addStartLine(newLine); + end->addEndLine(newLine); + arcNum++; + lines.push_back(newLine); + emit arcAdded(newLine); + emit logAdded(new viewLog("[Arc] | Added \""+newLine->stVex()->Text()+"\" -> \""+newLine->edVex()->Text()+"\"")); +} + +MyGraphicsVexItem* MyGraphicsView::selectedVex(){ + return selItem ? (selItem->type() == QGraphicsItem::UserType + 1 ? (MyGraphicsVexItem*)selItem : nullptr) : nullptr; +} + +MyGraphicsLineItem* MyGraphicsView::selectedArc(){ + return selItem ? (selItem->type() == QGraphicsItem::UserType + 2 ? (MyGraphicsLineItem*)selItem : nullptr) : nullptr; +} + +void MyGraphicsView::RemoveVex(MyGraphicsVexItem *vex){ + vexes.erase(vexes.begin() + vexes.indexOf(vex)); + vex->remove(); +} + +void MyGraphicsView::RemoveArc(MyGraphicsLineItem *line){ + lines.erase(lines.begin() + lines.indexOf(line)); + line->remove(); +} + +void MyGraphicsView::SaveToFile(QTextStream &ts){ + //vexes + ts << vexes.size() << "\n"; + for(int i = 0; i < vexes.size(); i++){ + ts << vexes[i]->getData() << "\n"; + } + //lines + ts << lines.size() << "\n"; + for(int i = 0; i < lines.size(); i++){ + ts << getIdOf(lines[i]->stVex()) << " " << getIdOf(lines[i]->edVex()) << "\n"; + } + for(int i = 0; i < lines.size(); i++){ + ts << lines[i]->weightText().toInt() << " "; + } +} + +void MyGraphicsView::ReadFromFile(QTextStream &ts){ + ts.readLine(); + int v = ts.readLine().toInt(); + for(int i = 0; i < v; i++){ + double x, y, r; + ts >> x >> y >> r; + ts.readLine(); + MyGraphicsVexItem *vex = addVex(QPointF(x, y), r); + vex->setText(ts.readLine()); + } + int l = ts.readLine().toInt(); + for(int i = 0; i < l; i++){ + int s, e; + ts >> s >> e; + addLine(vexes[s], vexes[e]); + } +} + +void MyGraphicsView::setHover(bool in){ + if(in) + mouseState |= ON_HOVER; + else + mouseState &= ~ON_HOVER; +} + +void MyGraphicsView::setSel(QGraphicsItem *sel){ + int state = SEL | (sel->type() - QGraphicsItem::UserType); + if(itemState == NON){ + itemState = state; + selItem = sel; + } + else if(itemState & SEL){ + if(itemState > state){ + itemState = state; + selItem = sel; + } + } + else if(itemState & ADD){ + if(selItem == nullptr || selItem->type() > sel->type()) + selItem = sel; + } +} + +void MyGraphicsView::startLine(MyGraphicsVexItem *startVex){ + itemState = ADD | LINE; + strtVex = startVex; +} + +void MyGraphicsView::setMenu(QGraphicsItem *target, bool display){ + if(display){ + itemState |= SEL; + selItem = target; + } +} + +void MyGraphicsView::addAnimation(QTimeLine *ani){ + aniQueue.push_back(ani); + if(!onAni){ + onAni = true; + nextAni(); + } +} + +/*****************************************************************************/ +unsigned int MyGraphicsVexItem::internalID = 0; + +MyGraphicsVexItem::MyGraphicsVexItem(QPointF _center, qreal _r, int nameID, QGraphicsItem *parent) : + QGraphicsEllipseItem(_center.x() - 0.5, _center.y() - 0.5, 1, 1, parent), + center(_center), + radius(_r){ + id = internalID++; + nameText = QString::asprintf("V%d", nameID); + nameTag = new QGraphicsSimpleTextItem; + nameTag->setPos(center + QPointF(radius, - radius - QFontMetrics(nameFont).height())); + nameTag->setFont(nameFont); + nameTag->setText(nameText); + nameTag->setZValue(this->zValue()); + this->setPen(Qt::NoPen); + this->setBrush(regBrush); +} + +void MyGraphicsVexItem::showAnimation(){ + //stopAnimation(); + state = PREPARING; + QTimeLine *timeLine = new QTimeLine(500, this); + timeLine->setFrameRange(0, 200); + QEasingCurve curve = QEasingCurve::OutBounce; + qreal baseRadius = this->rect().width() / 2; + qreal difRadius = radius - baseRadius; + connect(timeLine, &QTimeLine::frameChanged, timeLine, [=](int frame){ + qreal curProgress = curve.valueForProgress(frame / 200.0); + qreal curRadius = baseRadius + difRadius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + }); + curAnimation = timeLine; + startAnimation(); + connect(timeLine, &QTimeLine::finished, [this](){this->state &= ~PREPARING;}); + //timeLine->start(); +} + +void MyGraphicsVexItem::hoverInEffect(){ + stopAnimation(); + QTimeLine *timeLine = new QTimeLine(300, this); + timeLine->setFrameRange(0, 100); + QEasingCurve curve = QEasingCurve::OutBounce; + qreal baseRadius = this->rect().width() / 2; + qreal difRadius = 1.25 * radius - baseRadius; + connect(timeLine, &QTimeLine::frameChanged, [=](int frame){ + qreal curProgress = curve.valueForProgress(frame / 100.0); + qreal curRadius = baseRadius + difRadius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + + }); + curAnimation = timeLine; + startAnimation(); +} + +void MyGraphicsVexItem::hoverOutEffect(){ + stopAnimation(); + QTimeLine *timeLine = new QTimeLine(300, this); + timeLine->setFrameRange(0, 100); + QEasingCurve curve = QEasingCurve::OutBounce; + qreal baseRadius = this->rect().width() / 2; + qreal difRadius = radius - baseRadius; + connect(timeLine, &QTimeLine::frameChanged, [=](int frame){ + qreal curProgress = curve.valueForProgress(frame / 100.0); + qreal curRadius = baseRadius + difRadius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + }); + curAnimation = timeLine; + startAnimation(); +} + +void MyGraphicsVexItem::onClickEffect(){ + stopAnimation(); + qreal curRadius = 0.75 * radius; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); +} + +void MyGraphicsVexItem::onReleaseEffect(){ + stopAnimation(); + QTimeLine *timeLine = new QTimeLine(300, this); + timeLine->setFrameRange(0, 100); + QEasingCurve curve = QEasingCurve::OutBounce; + qreal baseRadius = this->rect().width() / 2; + qreal difRadius = (state & ON_HOVER) == 0 ? radius - baseRadius : radius * 1.25 - baseRadius; + connect(timeLine, &QTimeLine::frameChanged, [=](int frame){ + qreal curProgress = curve.valueForProgress(frame / 100.0); + qreal curRadius = baseRadius + difRadius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + }); + curAnimation = timeLine; + startAnimation(); +} + +void MyGraphicsVexItem::startAnimation(){ + if(curAnimation != nullptr){ + curAnimation->start(); + } +} + +void MyGraphicsVexItem::stopAnimation(){ + if(curAnimation != nullptr){ + curAnimation->stop(); + curAnimation->deleteLater(); + curAnimation = nullptr; + } +} + +void MyGraphicsVexItem::move(QPointF position){ + QPointF displacement = position - (this->scenePos() + this->rect().center()); + this->setRect(QRectF(this->rect().x() + displacement.x(), this->rect().y() + displacement.y(), this->rect().width(), this->rect().height())); + center = center + displacement; + if(tag) + tag->moveBy(displacement.x(), displacement.y()); + for(int i = 0; i < linesStartWith.size(); i++) + linesStartWith[i]->moveStart(this); + for(int i = 0; i < linesEndWith.size(); i++) + linesEndWith[i]->moveEnd(this); + nameTag->moveBy(displacement.x(), displacement.y()); +} + +void MyGraphicsVexItem::estConnection(MyGraphicsView* view){ + view->scene()->addItem(nameTag); + connect(view, SIGNAL(mouseMoved(QPointF)), this, SLOT(onMouseMove(QPointF))); + connect(view, SIGNAL(mouseLeftClicked(QPointF)), this, SLOT(onLeftClick(QPointF))); + connect(view, SIGNAL(mouseRightClicked(QPointF)), this, SLOT(onRightClick(QPointF))); + connect(view, SIGNAL(mouseReleased()), this, SLOT(onMouseRelease())); + connect(this, SIGNAL(setHover(bool)), view, SLOT(setHover(bool))); + connect(this, SIGNAL(selected(QGraphicsItem*)), view, SLOT(setSel(QGraphicsItem*))); + connect(this, SIGNAL(lineFrom(MyGraphicsVexItem*)), view, SLOT(startLine(MyGraphicsVexItem*))); + connect(this, SIGNAL(menuStateChanged(QGraphicsItem*, bool)), view, SLOT(setMenu(QGraphicsItem*, bool))); + connect(this, SIGNAL(addAnimation(QTimeLine*)), view, SLOT(addAnimation(QTimeLine*))); + connect(this, SIGNAL(logAdded(viewLog*)), view, SLOT(addLog(viewLog*))); + connect(this, SIGNAL(removed(MyGraphicsVexItem*)), view, SLOT(vexRemoved(MyGraphicsVexItem*))); +} + +void MyGraphicsVexItem::select(){ + state = ON_SELECTED; + this->setBrush(selBrush); + if(tag) + tag->setBrush(selBrush); + emit selected(this); +} + +void MyGraphicsVexItem::visit(bool visited){ + if(visited){ + QTimeLine *visitEffect = new QTimeLine; + visitEffect->setDuration(1000); + visitEffect->setFrameRange(0, 200); + QEasingCurve curveIn = QEasingCurve::InElastic; + QEasingCurve curveOut = QEasingCurve::OutBounce; + connect(visitEffect, &QTimeLine::frameChanged, this, [=](int frame){ + if(frame > 100){ + this->setBrush(visitedBrush); + if(tag) + tag->setBrush(visitedBrush); + } + if(frame < 100){ + qreal curProgress = curveIn.valueForProgress(frame / 100.0); + qreal curRadius = radius + 0.3 * radius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + } + else{ + qreal curProgress = curveOut.valueForProgress((frame - 100.0) / 100.0); + qreal curRadius = 1.3 * radius - 0.3 * radius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + } + }); + connect(visitEffect, &QTimeLine::stateChanged, this, [=](){ + if(visitEffect->state() == QTimeLine::Running) + emit logAdded(new viewLog("[Vex] | \""+nameText+"\" set visited")); + }); + emit addAnimation(visitEffect); + } + else{ + if(state & ON_SELECTED){ + this->setBrush(selBrush); + if(tag) + tag->setBrush(selBrush); + } + else{ + this->setBrush(regBrush); + if(tag) + tag->setBrush(regBrush); + } + } +} + +void MyGraphicsVexItem::access(const QString &hint, bool isAccess){ + if(isAccess){ + if(!tag) + tag = new QGraphicsSimpleTextItem; + tag->setPos(center + QPointF(radius, radius)); + tag->setFont(hintFont); + tag->setZValue(this->zValue()); + QTimeLine *accessEffect = new QTimeLine; + accessEffect->setDuration(1000); + accessEffect->setFrameRange(0, 200); + QEasingCurve curveIn = QEasingCurve::InElastic; + QEasingCurve curveOut = QEasingCurve::OutBounce; + connect(accessEffect, &QTimeLine::frameChanged, this, [=](int frame){ + if(frame > 100){ + this->setBrush(accessBrush); + this->tag->setBrush(accessBrush); + this->scene()->addItem(tag); + tag->setText(hint); + this->hintText = hint; + } + if(frame < 100){ + qreal curProgress = curveIn.valueForProgress(frame / 100.0); + qreal curRadius = radius + 0.3 * radius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + this->tag->setScale(1 + curProgress * 0.2); + } + else{ + qreal curProgress = curveOut.valueForProgress((frame - 100.0) / 100.0); + qreal curRadius = 1.3 * radius - 0.3 * radius * curProgress; + this->setRect(QRectF(center.x() - curRadius, center.y() - curRadius, curRadius * 2, curRadius * 2)); + this->tag->setScale(1.2 - curProgress * 0.2); + } + }); + connect(accessEffect, &QTimeLine::stateChanged, this, [=](){ + if(accessEffect->state() == QTimeLine::Running) + emit logAdded(new viewLog("[Vex] | \""+nameText+"\" accessed with hint "+hint)); + }); + emit addAnimation(accessEffect); + } + else{ + hintText = ""; + if(tag) + scene()->removeItem(tag); + tag = nullptr; + } +} + +void MyGraphicsVexItem::remove(){ + int s = linesStartWith.size(); + for(int i = 0; i < s; i++){ + linesStartWith[0]->remove(); + } + linesStartWith.clear(); + s = linesEndWith.size(); + for(int i = 0; i < s; i++){ + linesEndWith[0]->remove(); + } + linesEndWith.clear(); + if(tag) + scene()->removeItem(tag); + scene()->removeItem(nameTag); + scene()->removeItem(this); + emit removed(this); + this->deleteLater(); +} + +void MyGraphicsVexItem::onMouseMove(QPointF position){ + if(state & PREPARING) + return; + if((state & ON_LEFT_CLICK) == 0){ + if(this->contains(position)){ + if((state & ON_HOVER) == 0){ + emit setHover(true); + hoverInEffect(); + state |= ON_HOVER; + } + } + else{ + if(state & ON_HOVER){ + emit setHover(false); + hoverOutEffect(); + state &= ~ON_HOVER; + } + } + } + else{ + move(position); + state &= ~ON_SELECTED; + } +} + +void MyGraphicsVexItem::onLeftClick(QPointF position){ + if(state & PREPARING) + return; + if(state & (ON_LEFT_CLICK | ON_RIGHT_CLICK)) + return; + if(this->contains(position)){ + emit selected(this); + state |= ON_LEFT_CLICK; + onClickEffect(); + } + else{ + if(state & (ON_MENU | ON_SELECTED)){ + if(state & ON_MENU){ + emit menuStateChanged(this, false); + state &= ~ON_MENU; + } + if(state & ON_SELECTED){ + this->setBrush(regBrush); + state &= ~ON_SELECTED; + } + } + else + state &= UNDEFINED; + } + visit(false); +} + +void MyGraphicsVexItem::onRightClick(QPointF position){ + if(state & PREPARING) + return; + if(state & (ON_LEFT_CLICK | ON_RIGHT_CLICK)) + return; + if(this->contains(position)){ + emit selected(this); + state |= ON_RIGHT_CLICK; + onClickEffect(); + } + else{ + if(state & (ON_MENU | ON_SELECTED)){ + if(state & ON_MENU){ + emit menuStateChanged(this, false); + state &= ~ON_MENU; + } + if(state & ON_SELECTED){ + this->setBrush(regBrush); + state &= ~ON_SELECTED; + } + } + else + state &= UNDEFINED; + //if(state & ON_SELECTED) + // this->setBrush(regBrush); + //state &= UNDEFINED; + } + visit(false); +} + +void MyGraphicsVexItem::onMouseRelease(){ + if(state & PREPARING) + return; + if(state & (ON_LEFT_CLICK | ON_RIGHT_CLICK)){ + if(state & ON_SELECTED){ + if(state & ON_LEFT_CLICK) + emit lineFrom(this); + else if(state & ON_RIGHT_CLICK){ + emit menuStateChanged(this, true); + state |= (ON_MENU | ON_SELECTED); + } + } + else{ + this->setBrush(selBrush); + if(state & ON_LEFT_CLICK) + state |= ON_SELECTED; + if(state & ON_RIGHT_CLICK){ + emit menuStateChanged(this, true); + state |= (ON_MENU | ON_SELECTED); + } + } + state &= ~(ON_LEFT_CLICK | ON_RIGHT_CLICK); + onReleaseEffect(); + } +} + +/********************************************************/ + +MyGraphicsLineItem::MyGraphicsLineItem(MyGraphicsVexItem *start, MyGraphicsVexItem *end, bool hasDir, QGraphicsItem *parent) : + QGraphicsLineItem(parent), + hasDirection(hasDir), + startVex(start), + endVex(end){ + //Set display effect + defaultPen.setWidth(lineWidth); + defaultPen.setStyle(lineStyle); + defaultPen.setCapStyle(capStyle); + defaultPen.setColor(defaultColor); + curPen = defaultPen; + //textItem = new QGraphicsSimpleTextItem(text); +} + +void MyGraphicsLineItem::refrshLine(){ + setLengthRate(1); + drawLine(); +} + +void MyGraphicsLineItem::drawLine(){ + //draw invisible line + this->setLine(sP.x(), sP.y(), eP.x(), eP.y()); + //this->setPen(curPen); + QPen bgPen; + bgPen.setColor(QColor(255, 255, 255, 0)); + bgPen.setWidth(lineWidth + 5); + this->setPen(bgPen); + + if(line1){ + scene()->removeItem(line1); + line1 = nullptr; + } + if(line2){ + scene()->removeItem(line2); + line2 = nullptr; + } + + center = (startVex->scenePos() + startVex->rect().center() + endVex->scenePos() + endVex->rect().center())/2; + + drawText(); + + if(text != "" && (eP - center).x() * (sP - center).x() <= 0){ + qreal dx = 0; + qreal dy = 0; + int f1 = 1, f2 = 1; + if(textItem->boundingRect().width() != 0){ + if(abs(textItem->boundingRect().height() / textItem->boundingRect().width()) < abs(tan(angle))){ + dx = (textItem->boundingRect().height() + 10) / (2 * tan(angle)); + dy = (textItem->boundingRect().height() + 10) / 2; + f2 = angle > 0 ? -1 : 1; + } + else{ + dy = (textItem->boundingRect().width() + 10) * tan(angle) / 2; + dx = (textItem->boundingRect().width() + 10) / 2; + f1 = tan(angle) < 0 ? -1 : 1; + f2 = angle >= 0 ? -1 : 1; + } + } + dx *= f1 * f2; + dy *= f1 * f2; + QGraphicsLineItem *newLine1 = new QGraphicsLineItem(sP.x(), sP.y(), center.x() + dx, center.y() + dy); + QGraphicsLineItem *newLine2 = new QGraphicsLineItem(center.x() - dx, center.y() - dy, eP.x(), eP.y()); + + newLine1->setZValue(this->zValue() - 1); + newLine2->setZValue(this->zValue() - 1); + newLine1->setPen(curPen); + newLine2->setPen(curPen); + + scene()->addItem(newLine1); + scene()->addItem(newLine2); + line1 = newLine1; + line2 = newLine2; + } + else{ + QGraphicsLineItem *newLine = new QGraphicsLineItem(sP.x(), sP.y(), eP.x(), eP.y()); + newLine->setPen(curPen); + newLine->setZValue(this->zValue() - 1); + this->scene()->addItem(newLine); + line1 = newLine; + } + + if(hasDirection){ + delArrow(); + drawArrow(); + } + else{ + if(arrow) + delArrow(); + } +} + +void MyGraphicsLineItem::drawText(){ + if(textItem){ + this->scene()->removeItem(textItem); + textItem = nullptr; + } + QGraphicsSimpleTextItem *t = new QGraphicsSimpleTextItem(text); + t->setFont(textFont); + t->setPos(center - QPointF(t->boundingRect().width(), t->boundingRect().height()) / 2); + QColor c = curPen.color(); + t->setBrush(c.darker(150)); + this->scene()->addItem(t); + textItem = t; +} + +void MyGraphicsLineItem::drawArrow(){ + QPointF leftEnd = QPointF(eP.x() - cos(angle - M_PI / 6) * arrowLength, eP.y() - sin(angle - M_PI / 6) * arrowLength); + QPointF rightEnd = QPointF(eP.x() - cos(angle + M_PI / 6) * arrowLength, eP.y() - sin(angle + M_PI / 6) * arrowLength); + + QPainterPath arrowPath; + arrowPath.moveTo(leftEnd); + arrowPath.lineTo(eP); + arrowPath.lineTo(rightEnd); + + QGraphicsPathItem* arrowItem = new QGraphicsPathItem(arrowPath); + arrowItem->setPen(curPen); + this->scene()->addItem(arrowItem); + arrow = arrowItem; +} + +void MyGraphicsLineItem::delArrow(){ + if(arrow != nullptr){ + this->scene()->removeItem(arrow); + arrow = nullptr; + } +} + +void MyGraphicsLineItem::setLengthRate(qreal r){ + sP = startVex->scenePos() + startVex->rect().center(); + eP = endVex->scenePos() + endVex->rect().center(); + dP = eP - sP; + angle = atan2(dP.y(), dP.x()); + eP -= QPointF(endVex->getRadius() * cos(angle), endVex->getRadius() * sin(angle)); + sP += QPointF(endVex->getRadius() * cos(angle), endVex->getRadius() * sin(angle)); + dP = (eP - sP) * r; + eP = sP + dP; +} + +void MyGraphicsLineItem::estConnection(MyGraphicsView *view){ + connect(view, SIGNAL(mouseMoved(QPointF)), this, SLOT(onMouseMove(QPointF))); + connect(view, SIGNAL(mouseLeftClicked(QPointF)), this, SLOT(onLeftClick(QPointF))); + connect(view, SIGNAL(mouseRightClicked(QPointF)), this, SLOT(onRightClick(QPointF))); + connect(view, SIGNAL(mouseReleased()), this, SLOT(onMouseRelease())); + connect(this, SIGNAL(setHover(bool)), view, SLOT(setHover(bool))); + connect(this, SIGNAL(selected(QGraphicsItem*)), view, SLOT(setSel(QGraphicsItem*))); + connect(this, SIGNAL(menuStateChanged(QGraphicsItem*, bool)), view, SLOT(setMenu(QGraphicsItem*, bool))); + connect(this, SIGNAL(addAnimation(QTimeLine*)), view, SLOT(addAnimation(QTimeLine*))); + connect(this, SIGNAL(logAdded(viewLog*)), view, SLOT(addLog(viewLog*))); + connect(this, SIGNAL(removed(MyGraphicsLineItem*)), view, SLOT(arcRemoved(MyGraphicsLineItem*))); +} + +void MyGraphicsLineItem::reverseDirection(){ + delArrow(); + startVex->removeStartLine(this); + endVex->removeEndLine(this); + MyGraphicsVexItem *temp = startVex; + startVex = endVex; + endVex = temp; + startVex->addStartLine(this); + endVex->addEndLine(this); + refrshLine(); +} + +void MyGraphicsLineItem::moveStart(MyGraphicsVexItem *start){ + delArrow(); + startVex = start; + refrshLine(); +} + +void MyGraphicsLineItem::moveEnd(MyGraphicsVexItem *end){ + delArrow(); + endVex = end; + refrshLine(); +} + +void MyGraphicsLineItem::setText(const QString &_text){ + text = _text; + refrshLine(); +} + +void MyGraphicsLineItem::setDirection(bool hasDir){ + hasDirection = hasDir; + refrshLine(); +} + +void MyGraphicsLineItem::hoverInEffect(){ + curPen.setWidth(lineWidth + 1); + //curPen.setColor(hoverColor); + refrshLine(); +} + +void MyGraphicsLineItem::hoverOutEffect(){ + curPen.setWidth(lineWidth); + //curPen.setColor(state & ON_VISIT ? visitColor : state & ON_SELECTED ? selColor : defaultColor); + refrshLine(); +} + +void MyGraphicsLineItem::onClickEffect(){ + curPen.setWidth(lineWidth - 1); + refrshLine(); +} + +void MyGraphicsLineItem::onReleaseEffect(){ + curPen.setWidth(lineWidth); + curPen.setColor(selColor); + refrshLine(); +} + +void MyGraphicsLineItem::onSelectEffect(){ + curPen.setColor(selColor); + refrshLine(); +} + +void MyGraphicsLineItem::deSelectEffect(){ + curPen = defaultPen; + refrshLine(); +} + +void MyGraphicsLineItem::onMouseMove(QPointF position){ + if(this->contains(position)){ + emit setHover(true); + hoverInEffect(); + state |= ON_HOVER; + } + else{ + if(state & ON_HOVER){ + emit setHover(false); + hoverOutEffect(); + state &= ~ON_HOVER; + } + } +} + +void MyGraphicsLineItem::onLeftClick(QPointF position){ + if(state & (ON_LEFT_CLICK | ON_RIGHT_CLICK)) + return; + if(state & ON_VISIT) + visit(false); + if(this->contains(position)){ + emit selected(this); + onClickEffect(); + state |= ON_LEFT_CLICK; + } + else{ + if(state & (ON_MENU | ON_SELECTED)){ + if(state & ON_MENU){ + emit menuStateChanged(this, false); + state &= ~ON_MENU; + } + if(state & ON_SELECTED){ + deSelectEffect(); + state &= ~ON_SELECTED; + } + } + else + state &= UNDEFINED; + } +} + +void MyGraphicsLineItem::onRightClick(QPointF position){ + if(state & (ON_LEFT_CLICK | ON_RIGHT_CLICK)) + return; + if(state & ON_VISIT) + visit(false); + if(this->contains(position)){ + emit selected(this); + onClickEffect(); + state |= ON_RIGHT_CLICK; + } + else{ + if(state & (ON_MENU | ON_SELECTED)){ + if(state & ON_MENU){ + emit menuStateChanged(this, false); + state &= ~ON_MENU; + } + if(state & ON_SELECTED){ + deSelectEffect(); + state &= ~ON_SELECTED; + } + } + else + state &= UNDEFINED; + } +} + +void MyGraphicsLineItem::onMouseRelease(){ + if(state & (ON_LEFT_CLICK | ON_RIGHT_CLICK)){ + onReleaseEffect(); + if((state & ON_SELECTED) == 0) + onSelectEffect(); + if(state & ON_RIGHT_CLICK){ + emit menuStateChanged(this, true); + state |= ON_MENU; + } + state |= ON_SELECTED; + state &= ~(ON_LEFT_CLICK | ON_RIGHT_CLICK); + } +} + +void MyGraphicsLineItem::visit(bool visited){ + if(visited){ + state |= ON_VISIT; + QTimeLine *visitEffect = new QTimeLine; + visitEffect->setDuration(1000); + visitEffect->setFrameRange(0, 200); + QEasingCurve curve = QEasingCurve::InOutQuad; + QGraphicsLineItem *newLine1 = new QGraphicsLineItem(line1->line()); + QGraphicsLineItem *newLine2 = line2 ? new QGraphicsLineItem(line2->line()) : nullptr; + connect(visitEffect, &QTimeLine::stateChanged, this, [=](){ + if(visitEffect->state() == QTimeLine::Running){ + QPen pen; + pen.setWidth(3); + pen.setStyle(Qt::DashLine); + pen.setBrush(QColor(58, 143, 192, 100)); + pen.setCapStyle(Qt::RoundCap); + newLine1->setPen(pen); + newLine1->setZValue(this->zValue() - 2); + scene()->addItem(newLine1); + if(newLine2){ + newLine2->setPen(pen); + newLine2->setZValue(this->zValue() - 2); + scene()->addItem(newLine2); + } + emit logAdded(new viewLog("[Arc] | Arc \""+startVex->Text()+"\" -> \""+endVex->Text()+"\" set visited")); + } + else{ + scene()->removeItem(newLine1); + if(newLine2) + scene()->removeItem(newLine2); + } + }); + connect(visitEffect, &QTimeLine::frameChanged, this, [=](int frame){ + this->curPen.setColor(visitColor); + qreal curProgress = curve.valueForProgress(frame / 200.0); + setLengthRate(curProgress); + drawLine(); + }); + emit addAnimation(visitEffect); + } + else{ + state &= ~ON_VISIT; + curPen = defaultPen; + refrshLine(); + } +} + +void MyGraphicsLineItem::remove(){ + startVex->removeStartLine(this); + endVex->removeEndLine(this); + if(line1) + scene()->removeItem(line1); + if(line2) + scene()->removeItem(line2); + if(arrow) + scene()->removeItem(arrow); + if(textItem) + scene()->removeItem(textItem); + scene()->removeItem(this); + emit removed(this); + this->deleteLater(); +} + +void MyGraphicsLineItem::access(){ + QTimeLine *accessEffect = new QTimeLine; + accessEffect->setDuration(1000); + accessEffect->setFrameRange(0, 200); + QEasingCurve curve = QEasingCurve::InOutQuad; + QGraphicsLineItem *newLine1 = new QGraphicsLineItem(line1->line()); + QGraphicsLineItem *newLine2 = line2 ? new QGraphicsLineItem(line2->line()) : nullptr; + connect(accessEffect, &QTimeLine::stateChanged, this, [=](){ + if(accessEffect->state() == QTimeLine::Running){ + QPen pen; + pen.setWidth(3); + pen.setStyle(Qt::DashLine); + pen.setBrush(QColor(58, 143, 192, 100)); + pen.setCapStyle(Qt::RoundCap); + newLine1->setPen(pen); + newLine1->setZValue(this->zValue() - 2); + scene()->addItem(newLine1); + if(newLine2){ + newLine2->setPen(pen); + newLine2->setZValue(this->zValue() - 2); + scene()->addItem(newLine2); + } + emit logAdded(new viewLog("[Arc] | Arc \""+startVex->Text()+"\" -> \""+endVex->Text()+"\" accessed")); + } + else{ + scene()->removeItem(newLine1); + if(newLine2) + scene()->removeItem(newLine2); + } + }); + connect(accessEffect, &QTimeLine::frameChanged, this, [=](int frame){ + this->curPen.setColor(accessColor); + qreal curProgress = curve.valueForProgress(frame / 200.0); + setLengthRate(curProgress); + drawLine(); + }); + emit addAnimation(accessEffect); + state |= ON_SELECTED; +} diff --git a/graph_view.h b/graph_view.h new file mode 100644 index 0000000..53d1940 --- /dev/null +++ b/graph_view.h @@ -0,0 +1,371 @@ +#ifndef GRAPH_VIEW_H +#define GRAPH_VIEW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +//Header for MyVex class +#include +#include + +//Headers for lines +#include +#include +#include + +#include +#include + +class viewLog : public QLabel{ + Q_OBJECT +private: + QFont logFont = QFont("Corbel", 12); + QString logText; + void resizeEvent(QResizeEvent *event); +public: + viewLog(QString log, QWidget *parent = nullptr); +}; + +class MyGraphicsView; +class MyGraphicsVexItem; +class MyGraphicsLineItem; + + +// Summary: +// MyGraphicsView class is customized for visualizing graph +// +// Functions: +// -Left click to add vex +// -Left click on vex to add arc +// -Right click on vex or arc to view information +// -Drag vex to adjust place (adjust connected arcs either) + +class MyGraphicsView : public QGraphicsView{ + Q_OBJECT + +private: + enum mouseStates{ + NORMAL = 0b00000000, + ON_HOVER = 0b00010000, + ON_SELECTED = 0b00100000, + ON_MOVING = 0b01000000 + }; + + enum itemStates{ + NON = 0b00000000, + SEL = 0b00010000, + ADD = 0b00100000, + VEX = 0b00000001, + LINE = 0b00000010 + }; + + QGraphicsScene* myGraphicsScene; + int type; + int vexID = 0; + + int getIdOf(MyGraphicsVexItem* vex){return vexes.indexOf(vex);} + + int mouseState = NORMAL; + int itemState = NON; + bool onRightPress = false; + + //For dragging and scaling + bool onMiddlePress = false; + QPointF lastPos; + + QGraphicsItem *selItem = nullptr; + + MyGraphicsVexItem *strtVex = nullptr; + QGraphicsItem *sketchItem = nullptr; + qreal zValue = -1; + + /* Animation loop */ + QQueue aniQueue; + bool onAni = false; + QTimeLine *curAni = nullptr; + qreal speedRate = 1; + void nextAni(); + + void mousePressEvent(QMouseEvent* event); + void mouseReleaseEvent(QMouseEvent* event); + void mouseMoveEvent(QMouseEvent* event); + void wheelEvent(QWheelEvent *event); + void resizeEvent(QResizeEvent *event){this->setSceneRect(this->rect());} + + void changeCursor(); + + MyGraphicsVexItem* addVex(QPointF center, qreal radius = 10); + void clearSketch(); + void sketchLine(QPointF start, QPointF end); + void addLine(MyGraphicsVexItem* start, MyGraphicsVexItem* end); + +public: + enum { UDG = 128, DG = 256 }; + int vexNum = 0; + int arcNum = 0; + /* For Saving */ + QVector vexes; + QVector lines; + + /* for visit flag */ + bool hasVisitedItem = false; + + MyGraphicsView(int _type = UDG, QWidget *parent = nullptr); + + MyGraphicsVexItem* selectedVex(); + MyGraphicsLineItem* selectedArc(); + + void RemoveVex(MyGraphicsVexItem *vex); + void RemoveArc(MyGraphicsLineItem *line); + + void setAniRate(qreal rate){speedRate = rate;} + void setType(int _type){type = _type;} + void unSelect(){itemState = NON;selItem = nullptr;emit unselected();} + + void SaveToFile(QTextStream &ts); + void ReadFromFile(QTextStream &ts); + +signals: + void mouseMoved(QPointF position); + void mouseLeftClicked(QPointF position); + void mouseRightClicked(QPointF position); + void mouseReleased(); + + void vexAdded(MyGraphicsVexItem *vex); + void arcAdded(MyGraphicsLineItem *arc); + void logAdded(viewLog *log); + void selected(QGraphicsItem *item); + void unselected(); + void visitClear(); + +public slots: + void setHover(bool in = true); + void setSel(QGraphicsItem *sel); + void startLine(MyGraphicsVexItem* startVex); + void setMenu(QGraphicsItem *target, bool display = true); + void addLog(viewLog *log){emit logAdded(log);} + void vexRemoved(MyGraphicsVexItem* vex){vexes.erase(vexes.begin() + vexes.indexOf(vex));vexNum--;} + void arcRemoved(MyGraphicsLineItem* line){lines.erase(lines.begin() + lines.indexOf(line));arcNum--;} + + void addAnimation(QTimeLine *ani); + +}; + + +// Summary: +// MyGraphicsVexItem realize the interactions with vex + +class MyGraphicsVexItem : public QObject, public QGraphicsEllipseItem{ + Q_OBJECT + +private: + enum { + PREPARING = 0b10000000, + UNDEFINED = 0b00000000, + ON_HOVER = 0b00000001, + ON_LEFT_CLICK = 0b00000010, + ON_RIGHT_CLICK = 0b00000100, + ON_SELECTED = 0b00001000, + ON_LINING = 0b00010000, + ON_MENU = 0b00100000, + ON_VISIT = 0b01000000, + ON_ACCESS = 0b10000000 + }; + +private: + static unsigned int internalID; + QBrush regBrush = QBrush(QColor(58, 143, 192)); + QBrush selBrush = QBrush(QColor(208, 90, 110)); + QBrush visitedBrush = QBrush(QColor(93, 172, 129)); + QBrush accessBrush = QBrush(QColor(152, 109, 178)); + + QPointF center; + qreal radius; + int state = UNDEFINED; + QTimeLine* curAnimation = nullptr; + + QVector linesStartWith; + QVector linesEndWith; + + /* For display temporary tag */ + QGraphicsSimpleTextItem *nameTag = nullptr; + QString nameText = ""; + QFont nameFont = QFont("Corbel", 13, QFont::Normal, true); + QGraphicsSimpleTextItem *tag = nullptr; + QString hintText = ""; + QFont hintFont = QFont("Corbel", 12); + + void displayText(); + + void hoverInEffect(); + void hoverOutEffect(); + void onClickEffect(); + void onReleaseEffect(); + void startAnimation(); + void stopAnimation(); + + void move(QPointF position); + +public: + enum { Type = UserType + 1 }; + int id; + MyGraphicsVexItem(QPointF _center, qreal _r, int nameID = 0, QGraphicsItem *parent = nullptr); + + /* initializing */ + void estConnection(MyGraphicsView* view); + void showAnimation(); + + void select(); + void visit(bool visited = true); + void access(const QString &hint = "", bool isAccess = true); + QString Text(){return nameText;} + void setText(const QString & text){nameTag->setText(text);nameText = text;} + void addStartLine(MyGraphicsLineItem *line){linesStartWith.push_back(line);} + void removeStartLine(MyGraphicsLineItem *line){linesStartWith.remove(linesStartWith.indexOf(line));} + void addEndLine(MyGraphicsLineItem *line){linesEndWith.push_back(line);} + void removeEndLine(MyGraphicsLineItem *line){linesEndWith.remove(linesEndWith.indexOf(line));} + void remove(); + + bool equalTo(MyGraphicsVexItem *v){return id == v->id;} + int type() const override {return Type;} + qreal getRadius() {return radius;} + QString getData(){return QString::asprintf("%g %g %g\n", center.x(), center.y(), radius)+nameText;} + +signals: + void setHover(bool in = true); + void selected(QGraphicsItem *sel); + void lineFrom(MyGraphicsVexItem *start); + void menuStateChanged(QGraphicsItem *item, bool display = true); + void logAdded(viewLog *log); + void removed(MyGraphicsVexItem *vex); + + void addAnimation(QTimeLine *ani); + +public slots: + void onMouseMove(QPointF position); + void onLeftClick(QPointF position); + void onRightClick(QPointF position); + void onMouseRelease(); + +}; + +class MyGraphicsLineItem : public QObject, public QGraphicsLineItem{ + Q_OBJECT + +private: + enum { + UNDEFINED = 0b00000000, + ON_HOVER = 0b00000001, + ON_LEFT_CLICK = 0b00000010, + ON_RIGHT_CLICK = 0b00000100, + ON_SELECTED = 0b00001000, + ON_MENU = 0b00100000, + ON_VISIT = 0b01000000 + }; + + /* basic data */ + bool hasDirection; + MyGraphicsVexItem *startVex; + MyGraphicsVexItem *endVex; + QGraphicsLineItem *line1 = nullptr; + QGraphicsLineItem *line2 = nullptr; + QGraphicsPathItem *arrow = nullptr; + QGraphicsSimpleTextItem *textItem = nullptr; + QString text = ""; + + int state = UNDEFINED; + + /* about animation */ + QTimeLine *curAnimation; + + /* detail of the line */ + qreal lineWidth = 3; + qreal arrowLength = 10; + Qt::PenStyle lineStyle = Qt::SolidLine; + Qt::PenCapStyle capStyle = Qt::RoundCap; + QColor defaultColor = QColor(125, 185, 222); + QColor hoverColor = QColor(0, 98, 132); + QColor selColor = QColor(208, 90, 110); + QColor visitColor = QColor(93, 172, 129); + QColor accessColor = QColor(178, 143, 206); + QPen defaultPen; + QPen curPen; + QFont textFont = QFont("Corbel", 12); + QColor textColor = QColor(0, 0, 0); + + /* for calculation and line rendering */ + qreal angle = 0; + QPointF center; + QPointF sP, eP, dP; + + void setLengthRate(qreal r=1); + void drawLine(); + void drawText(); + void drawArrow(); + void delArrow(); + + /* effects */ + void hoverInEffect(); + void hoverOutEffect(); + void onClickEffect(); + void onReleaseEffect(); + void onSelectEffect(); + void deSelectEffect(); + +public: + enum { Type = UserType + 2 }; + MyGraphicsLineItem(MyGraphicsVexItem *start, MyGraphicsVexItem *end, bool hasDir = false, QGraphicsItem *parent = nullptr); + + /* initialize functions */ + void estConnection(MyGraphicsView *view); + void refrshLine(); + + /* adjust functions */ + void reverseDirection(); + void moveStart(MyGraphicsVexItem *start); + void moveEnd(MyGraphicsVexItem *end); + void setText(const QString & _text); + void setDirection(bool hasDir = true); + + /* effects */ + //void startAnimation(){} + //void stopAnimation(){} + + /* retrieve */ + MyGraphicsVexItem* stVex(){return startVex;} + MyGraphicsVexItem* edVex(){return endVex;} + QString weightText(){return text;} + + void visit(bool visited = true); + void remove(); + void access(); + + int type() const override {return Type;} + +signals: + void setHover(bool in = true); + void selected(QGraphicsItem *sel); + void menuStateChanged(QGraphicsItem *item, bool display = true); + void logAdded(viewLog *log); + void removed(MyGraphicsLineItem *line); + + void addAnimation(QTimeLine *ani); + +private slots: + void onMouseMove(QPointF position); + void onLeftClick(QPointF position); + void onRightClick(QPointF position); + void onMouseRelease(); + +}; + +#endif // GRAPH_VIEW_H diff --git a/icons.qrc b/icons.qrc new file mode 100644 index 0000000..06bec3d --- /dev/null +++ b/icons.qrc @@ -0,0 +1,10 @@ + + + icons/back.svg + icons/settings.svg + icons/open.svg + icons/open.png + icons/create.png + icons/layers.svg + + diff --git a/icons/back.svg b/icons/back.svg new file mode 100644 index 0000000..7b6200b --- /dev/null +++ b/icons/back.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/create.png b/icons/create.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f00091b248ce1ad15aa4f05a6a0d76fd1b9ad1 GIT binary patch literal 2127 zcmbVNYitx%6y6q6Xe(Gl35EojOfjjz>^$}{J0n{xyKQ%YEw!vXlqh#+?(UA=of&4P zyW7%8X;mZ<2_TOj5R{0Zu|*W*N1;585Z**oM4A#5d{t`D@I!*$*>0EeCT=o2ckcb} zch33Fx#upesjeDUFuK5Eu?+M3ytU{($$W<9p@06yISsw?Ro`6QVzHK(Pflv{q3^R*DcPiosaUN;~a#mun)%Pz*&Jsxmm7~7 zhRTyG^5wc#=&)|i+ph>DBDKcggxN+oZ zUkGtEODpRcnUG*)3aBJ)rOc4hKvBrz)OszJHZBSzjKMgR4IN?WELM#whN4H6`%q_> zZ!>_T4Fs|_?&(WBo;9Hx6$unZMv!}=^2R7F3j2 zf1+yoUB(#3N@1n5C0SIG`ZEJIKrb+$8%J5A37R1&I!HTs+Qr-K6DSu?QGL*WB1++g zyP-~=;!uungQAiVfdL*076m@6XmNl7mf|1+NmY*ESXM^fqr?;q8AjgO2G09Eo*GRF zOEI*d*H%rz{1qN2%{iR}W2MvX1_Hca)(s#F(C>BQNF1vqi9F+^LtzkN2*3(}JK!j@ z?9-LR`y?IZxFK_pX2J!TQB1lsE=dH6{tbjFn8tV&;fvvx6B5 zcB`oFYexr(2TSy5CDRxC-4fk#I{uGD8To}MkRuRvauPR#A2}t1OnB^?q-8Fxu5~zOL;cR&41m$;n?`lkeL)cw8`d z`1qXsRn4#a(T6b+upPrDZr*)y{f{?Ze6Q)!=`(b59sSF^`l%bLE;s#J%#S);w4>W=C)xl;{!WO;l~*;j|k+}8GErEjh1{1$$+{c(K-chJDYU2@))S4T`+eBs!E7npCx zs*PI?9H(QYUyo?7e*Us;+u84C9eM2UHwuZS@hi5!>971tmac7}hR%sxv+1s^5M6H?g?)de_=zma@I<*n%bu!QRFAndI#H?S_m<_Z^K>s-^Kxy`@e@DK+q=!Y^8FRn-u5Z; GTmA!-*4sJ& literal 0 HcmV?d00001 diff --git a/icons/create.svg b/icons/create.svg new file mode 100644 index 0000000..ef92778 --- /dev/null +++ b/icons/create.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/layers.svg b/icons/layers.svg new file mode 100644 index 0000000..0e44bf5 --- /dev/null +++ b/icons/layers.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/open.png b/icons/open.png new file mode 100644 index 0000000000000000000000000000000000000000..01a2a12aaf7160dd9a4dca1fe1ac41ab32f2def0 GIT binary patch literal 2172 zcmbVO4Qvx-814oR28@{U6Tu&jTR;rE-u3@_Yk{%Ot+0+UD%+6YzTSOp58Auq?zVOt z;DCWjG#N~Ufv5`*MIwwKk_qArfuN#bbU`*EGY%BOk0xdi2I6;HSH{1w$@RYbzUO=1 z_kF(ieed2VEXYk8GIoegr%UtXxr@-E)Dmw#!-^3D;6y((#3B+xqs>!e5?~iW74yR&FSzil2foBHo^#>FRxjn1vSEnN ztCHcIssbNdwTN|a_|z$wGsYl-2vh+Ui-ZM*iMj9|UIvY|X%feJAnGC)o~0SYO1yd^6#eX=mCUXI%$o*p67J72w zAyt(al8i>9hN#&f%0bfTa5zZHM4C(lLJ&%&pn@17DCvC+Zm6&_FR8pJU>YOvi{+{d zN1pbE5RnqJf)bYr2}Z_%L>dj07E%w8V-q;3Tn_gb=U5VkVFU`Qg0RK}Rtkx#sD#9a zP$!n}GJvG@dJ{Gt=u0G$Frlb96)23jAP+<+-&+lw$RdtA?)mLjt0ll$2)h;95Qha4 zG_acpKm$J;2yiS*Tl?5%$vi4Z5bn>adBq{dfS>kT{6?BEQ+6vs`7MZybx;KD2tX^a zm`!%OO-uWpOk(nQ1)XtaypZO=#qm*?$9j^70jyROE}Ye}0djbJnty;E|H*QEe>4OU z=_9hF*SaDG)F_Z)RuF0T*eoO;>bxjaK)Ij$5|9DvMNn2;ct92-7?7ke&jKx0vO?hE zC7w7POck*^N%c@W3aEIrME@;idSky|qI;_2|40;v_G0Pu`ms zJ!}2?V;>wfU2T}?_UIw;zJLOvmeJc_UDbeOoi6EXk2}j3`sH$4WNj@qETyA(`UGFH zzOFDeZ(8#3x+Z;HP3?L~U)OT$*MOr-q^%hSST z1D@VEXo-IKwD;?VyVnlAqRY^Ay?I7go4Mk({Exn0)!4S9!%(zuRLkEJPaV5@G_&TM zX&Eq>_WAtBzBRbEn+!Cs7f4-*CMr%HO+cKgi zc(}O@-_O?nxIbk#cd}&l+zx5LrDfrwO*Q8_#-&~rD>paKYnpRyD0}R3uzc0IJty*C zPrLEMXQMMV%*>s#YxTS)=TkAg{|ljHUq{2iQkVbsfs~BR%hL}tgUeeaR$ z7soV@JlOf%C-dnwpU-7s{4fA3q9&PQie zG \ No newline at end of file diff --git a/icons/settings.svg b/icons/settings.svg new file mode 100644 index 0000000..1eed623 --- /dev/null +++ b/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/logo.ico b/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..0e7ec639cbe89c31b83b638d29126c1026625459 GIT binary patch literal 16958 zcmeI4Yiv|S7>1`5={buIO%zp1T zvvcO0H5!w{zoH_;-=3ymm@%D=F$F-CDFfAesi?+e|7IhQjX*X6*$8AKkc~j{5%^=a zF^Q7==;_;|4$ZmLwTCXy-M1>MzE+IVS5|I@hIUrpum0cp1f$S-KT``?_LBYR3)^gghttEXQvNT)dX`Wx;1v+um1G@byf-`WGz&4)e^T?;?IkmqW;5seSP8}L0G_ECNXj0Clb_Nhx> zygEm}z6W~$u^m!b2YUZ8aR0*2($x}%!Mkt@tbS|%q^<;dfjP9SzS9^{(%JzAJVtybfDH&&hxJR%O+X1GS6l zR^CnM491}?bz|TwxBy-pcFyh`SvMiaYD&gdqrC)j6oXV#Pz&W|ptj0Ab%uF)-2`(vg*DJ9-~aN_qf8>$wH&mLPWL`>1NFV&5m*U( ze|g4S_;PZC_db39wg2aZx&G_W-JZH`KFZgMjn$4GSF6ppq`o&ygtuTn=*$>BTWz1& zVEwxP>$@My{a-o)+6Ga62`b=_kMavZ*Sz+Twb$rwg+869C&L;z1p1vgs@cz zv01Wyg3)=_?&pcxuj%96`>`n}s|VWxrocgn#NdG1EE+x~ch^&GHP@}7AL#t7=ZGWl zF9bE8q;4C`ha%{-a+G%;7=ngfNyI?U3U^AkNHw9woqK6N6+^chkCUs%78RpmI(z^} z;W7j@pP)|XX1#O22ReZLewZDzYBZo@0K5ZdA=Vl=!!RBwS z{&%%2@95We+tC<5|Hah4n(t~j4jbTExC`1sbe?TK_}MC>DM)kbI)VHCM|HK@)iqW_ z>U{m$+qJ(Xp?`E__1MCHb{DrP-807wLF3Isma&hfc5Xq|5o~Y=b{YPuYFMGon+U)_DpXN zZ36m}xc_-^(m5!47Hk6gHIGx_G{jp6IwR?<Z0v}{hLDB)mLA>DQL}V$VMO= zfoue_5y(cMsYM{t&{Sn=dRCf3FH>gPdDfVgvem}q%3k4rKj7Lb0;qBrcAh^@zHjxx z8aMZJu4he!(dR48^KBu1DHTR)Y<{H7v?ZG#K}VS^x+-OB#_}wEJbT)=Ub`zsL#q8G z&vMbTm7Xn(R5-TG%u~4~{>T>MAJ&TtwkB@zY-NoPk<)&>s~A)1ia!rtA^AW50e<)? A+5i9m literal 0 HcmV?d00001 diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..cd83d19 --- /dev/null +++ b/main.cpp @@ -0,0 +1,14 @@ +#include "mainwindow.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.setWindowFlag(Qt::FramelessWindowHint); + w.setAttribute(Qt::WA_TranslucentBackground); + w.show(); + return a.exec(); + +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..5e77620 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,449 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + ui->centralwidget->setMouseTracking(true); + QTimer *t = new QTimer(this); + connect(t, &QTimer::timeout, this, [=](){Init();}); + t->setSingleShot(true); + t->start(10); + + connect(ui->adjSizeBtn, &QPushButton::clicked, this, [=](){controlWindowScale();}); +} + +void MainWindow::Init(){ + /* Create main widget and set mask, style sheet and shadow */ + QPainterPath path; + path.addRoundedRect(ui->mainWidget->rect(), cornerRadius - 1, cornerRadius - 1); + QRegion mask(path.toFillPolygon().toPolygon()); + ui->mainWidget->setMask(mask); + + QString mainStyle; + ui->mainWidget->setObjectName("mainWidget"); + mainStyle = "QWidget#mainWidget{background-color:" + mainBackGround.name() + QString::asprintf(";border-radius:%dpx", cornerRadius) + "}"; + ui->mainWidget->setStyleSheet(mainStyle); + + windowShadow = new QGraphicsDropShadowEffect(this); + windowShadow->setBlurRadius(30); + windowShadow->setColor(QColor(0, 0, 0)); + windowShadow->setOffset(0, 0); + ui->mainWidget->setGraphicsEffect(windowShadow); + /**********************************************************/ + + /* Create border in order to cover the zigzag edge of the region */ + border = new QWidget(this); + border->move(ui->mainWidget->pos() - QPoint(1, 1)); + border->resize(ui->mainWidget->size() + QSize(2, 2)); + QString borderStyle; + borderStyle = "background-color:#00FFFFFF;border:1.5px solid #686868; border-radius:" + QString::asprintf("%d",cornerRadius) + "px"; + border->setStyleSheet(borderStyle); + border->setAttribute(Qt::WA_TransparentForMouseEvents); + border->show(); + /*****************************************************************/ + + /* Create settings page */ + defaultSettingsPage = new SlidePage(cornerRadius, "ABOUT", ui->mainWidget); + textInputItem *version = new textInputItem("version", defaultSettingsPage); + version->setValue("1.0"); + version->setEnabled(false); + textInputItem *updateDate = new textInputItem("last-upd", defaultSettingsPage); + updateDate->setValue("2021/12/4"); + updateDate->setEnabled(false); + textInputItem *Author = new textInputItem("author", defaultSettingsPage); + Author->setValue("Linloir | Made with love"); + Author->setEnabled(false); + defaultSettingsPage->AddContent(Author); + defaultSettingsPage->AddContent(updateDate); + defaultSettingsPage->AddContent(version); + curSettingsPage = defaultSettingsPage; + defaultSettingsPage->show(); + pageList.push_back(defaultSettingsPage); + + /************************/ + + /* Initialize display area */ + QFont titleFont = QFont("Corbel Light", 24); + QFontMetrics titleFm(titleFont); + canvasTitle = new QLineEdit(this); + canvasTitle->setFont(titleFont); + canvasTitle->setText("START"); + canvasTitle->setMaxLength(20); + canvasTitle->setReadOnly(true); + canvasTitle->setMinimumHeight(titleFm.height()); + canvasTitle->setMaximumWidth(titleFm.size(Qt::TextSingleLine, "START").width() + 10); + canvasTitle->setStyleSheet("background-color:#00000000;border-style:none;border-width:0px;margin-left:1px;"); + connect(canvasTitle, &QLineEdit::textEdited, canvasTitle, [=](QString text){canvasTitle->setMaximumWidth(titleFm.size(Qt::TextSingleLine, text).width());}); + + QFont descFont = QFont("Corbel Light", 12); + QFontMetrics descFm(descFont); + canvasDesc = new QLineEdit(this); + canvasDesc->setFont(descFont); + canvasDesc->setText("Add your first canvas to start"); + canvasDesc->setMaxLength(128); + canvasDesc->setReadOnly(true); + canvasDesc->setMinimumHeight(descFm.lineSpacing()); + canvasDesc->setStyleSheet("background-color:#00000000;border-style:none;border-width:0px;"); + + settingsIcon = new customIcon(":/icons/icons/settings.svg", "settings", 5, this); + settingsIcon->setMinimumHeight(canvasTitle->height() * 0.7); + settingsIcon->setMaximumWidth(canvasTitle->height() * 0.7); + connect(settingsIcon, &customIcon::clicked, this, [=](){ + QPropertyAnimation *rotate = new QPropertyAnimation(settingsIcon, "rotationAngle", this); + rotate->setDuration(750); + rotate->setStartValue(0); + rotate->setEndValue(90); + rotate->setEasingCurve(QEasingCurve::InOutExpo); + rotate->start(); + curSettingsPage->slideIn(); + }); + layersIcon = new customIcon(":/icons/icons/layers.svg", "layers", 5, this); + layersIcon->setMinimumHeight(canvasTitle->height() * 0.7); + layersIcon->setMaximumWidth(canvasTitle->height() * 0.7); + + /* create title */ + + QWidget *titleInnerWidget = new QWidget(this); + titleInnerWidget->setFixedHeight(canvasTitle->height()); + QHBoxLayout *innerLayout = new QHBoxLayout(titleInnerWidget); + titleInnerWidget->setLayout(innerLayout); + innerLayout->setContentsMargins(0, 0, 0, 0); + innerLayout->setSpacing(10); + innerLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); + innerLayout->addWidget(canvasTitle); + innerLayout->addWidget(settingsIcon); + innerLayout->addWidget(layersIcon); + + QWidget *titleWidget = new QWidget(this); + titleWidget->setMaximumHeight(canvasTitle->height() + canvasDesc->height()); + QVBoxLayout *outerLayout = new QVBoxLayout(titleWidget); + titleWidget->setLayout(outerLayout); + outerLayout->setContentsMargins(0, 0, 0, 0); + outerLayout->setSpacing(0); + outerLayout->addWidget(titleInnerWidget); + outerLayout->addWidget(canvasDesc); + + /* create default page */ + + defaultPage = new QWidget(ui->mainWidget); + defaultPage->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + bigIconButton *createNew = new bigIconButton(":/icons/icons/create.png", "Create new", 10, this); + createNew->setScale(0.9); + bigIconButton *openFile = new bigIconButton(":/icons/icons/open.png", "Open from file", 10, this); + connect(openFile, &bigIconButton::clicked, this, [=](){ + QString inputPath = QFileDialog::getOpenFileName(this, tr("Open map"), " ", tr("Map File(*.map)")); + if(!inputPath.isEmpty()){ + MyCanvas *newCanvas = loadCanvas(inputPath); + if(newCanvas != nullptr){ + canvasList.push_back(newCanvas); + selectionItem *newLayer = new selectionItem(newCanvas->name(), newCanvas->description(), layersPage); + layerSel->AddItem(newLayer); + layerSel->SetSelection(newLayer); + pageList.push_back(newCanvas->settingPage()); + connect(newLayer, &selectionItem::selected, this, [=](){selectCanvas(newCanvas);}); + selectCanvas(newCanvas); + connect(newCanvas, &MyCanvas::nameChanged, this, [=](QString text){ + canvasTitle->setText(text); + canvasTitle->setMaximumWidth(QFontMetrics(QFont("Corbel Light", 24)).size(Qt::TextSingleLine, canvasTitle->text()).width() + 10); + newLayer->setTitle(text); + }); + connect(newCanvas, &MyCanvas::descChanged, this, [=](QString text){this->canvasDesc->setText(text);newLayer->setDescription(text);}); + connect(newCanvas, &MyCanvas::setDel, this, [=](MyCanvas *c){curSettingsPage->slideOut();deleteCanvas(c);layerSel->RemoveItem(newLayer);}); + createNewPage->slideOut(); + } + } + }); + QHBoxLayout *defaultPageLayout = new QHBoxLayout(defaultPage); + defaultPage->setLayout(defaultPageLayout); + defaultPageLayout->setContentsMargins(50, 30, 50, 80); + defaultPageLayout->setSpacing(20); + defaultPageLayout->addWidget(createNew); + defaultPageLayout->addWidget(openFile); + + /* create layers page */ + //for add new page + textInputItem *rename = new textInputItem("Name:",createNewPage); + rename->setValue("Layer_" + QString::asprintf("%d", canvasList.size())); + textInputItem *redescribe = new textInputItem("Detail:",createNewPage); + redescribe->setValue("No description"); + + layersPage = new SlidePage(cornerRadius, "LAYERS", ui->mainWidget); + layersPage->stackUnder(createNewPage); + connect(layersIcon, &customIcon::clicked, layersPage, &SlidePage::slideIn); + layerSel = new singleSelectGroup("Layers", layersPage); + connect(layerSel, &singleSelectGroup::itemChange, layersPage, [=](){layersPage->UpdateContents();}); + textButton *openFileBtn = new textButton("Open file", layersPage); + connect(openFileBtn, &textButton::clicked, this, [=](){ + QString inputPath = QFileDialog::getOpenFileName(this, tr("Open map"), " ", tr("Map File(*.map)")); + if(!inputPath.isEmpty()){ + MyCanvas *newCanvas = loadCanvas(inputPath); + if(newCanvas != nullptr){ + canvasList.push_back(newCanvas); + selectionItem *newLayer = new selectionItem(newCanvas->name(), newCanvas->description(), layersPage); + layerSel->AddItem(newLayer); + layerSel->SetSelection(newLayer); + pageList.push_back(newCanvas->settingPage()); + connect(newLayer, &selectionItem::selected, this, [=](){selectCanvas(newCanvas);}); + selectCanvas(newCanvas); + connect(newCanvas, &MyCanvas::nameChanged, this, [=](QString text){ + canvasTitle->setText(text); + canvasTitle->setMaximumWidth(QFontMetrics(QFont("Corbel Light", 24)).size(Qt::TextSingleLine, canvasTitle->text()).width() + 10); + newLayer->setTitle(text); + }); + connect(newCanvas, &MyCanvas::descChanged, this, [=](QString text){this->canvasDesc->setText(text);newLayer->setDescription(text);}); + connect(newCanvas, &MyCanvas::setDel, this, [=](MyCanvas *c){curSettingsPage->slideOut();deleteCanvas(c);layerSel->RemoveItem(newLayer);}); + createNewPage->slideOut(); + } + } + }); + textButton *addNewBtn = new textButton("Create new", layersPage); + layersPage->AddContent(addNewBtn); + layersPage->AddContent(openFileBtn); + layersPage->AddContent(layerSel); + connect(addNewBtn, &textButton::clicked, this, [=](){rename->setValue("Layer_" + QString::asprintf("%d", canvasList.size()));redescribe->setValue("No description");createNewPage->slideIn();}); + layersPage->show(); + pageList.push_back(layersPage); + + /* create add new slide page */ + createNewPage = new SlidePage(cornerRadius, "CREATE CANVAS", ui->mainWidget); + QLineEdit *canvasName = new QLineEdit(this); + canvasName->setMaximumHeight(20); + QLineEdit *canvasDesc = new QLineEdit(this); + canvasDesc->setMaximumHeight(20); + + QWidget *whiteSpace = new QWidget(createNewPage); + whiteSpace->setFixedHeight(30); + singleSelectGroup *structureSel = new singleSelectGroup("Structure",createNewPage); + selectionItem *item_1 = new selectionItem("AL", "Use adjacent list for canvas", createNewPage); + selectionItem *item_2 = new selectionItem("AML", "Use multiple adjacent list for canvas", createNewPage); + structureSel->AddItem(item_1); + structureSel->AddItem(item_2); + singleSelectGroup *dirSel = new singleSelectGroup("Mode", createNewPage); + selectionItem *item_3 = new selectionItem("DG", "Directed graph", createNewPage); + selectionItem *item_4 = new selectionItem("UDG", "Undirected graph", createNewPage); + dirSel->AddItem(item_3); + dirSel->AddItem(item_4); + textButton *submit = new textButton("Create!", createNewPage); + connect(submit, &textButton::clicked, this, [=](){ + MyCanvas *newCanvas = new MyCanvas(cornerRadius, + rename->value(), + redescribe->value(), + structureSel->value() == 0 ? MyCanvas::AL : MyCanvas::AML, + dirSel->value() == 0 ? MyCanvas::DG : MyCanvas::UDG, ui->mainWidget); + canvasList.push_back(newCanvas); + selectionItem *newLayer = new selectionItem(newCanvas->name(), newCanvas->description(), layersPage); + layerSel->AddItem(newLayer); + layerSel->SetSelection(newLayer); + pageList.push_back(newCanvas->settingPage()); + connect(newLayer, &selectionItem::selected, this, [=](){selectCanvas(newCanvas);}); + selectCanvas(newCanvas); + connect(newCanvas, &MyCanvas::nameChanged, this, [=](QString text){ + canvasTitle->setText(text); + canvasTitle->setMaximumWidth(QFontMetrics(QFont("Corbel Light", 24)).size(Qt::TextSingleLine, canvasTitle->text()).width() + 10); + newLayer->setTitle(text); + }); + connect(newCanvas, &MyCanvas::descChanged, this, [=](QString text){this->canvasDesc->setText(text);newLayer->setDescription(text);}); + connect(newCanvas, &MyCanvas::setDel, this, [=](MyCanvas *c){curSettingsPage->slideOut();deleteCanvas(c);layerSel->RemoveItem(newLayer);}); + createNewPage->slideOut(); + }); + createNewPage->AddContent(submit); + createNewPage->AddContent(dirSel); + createNewPage->AddContent(structureSel); + createNewPage->AddContent(whiteSpace); + createNewPage->AddContent(redescribe); + createNewPage->AddContent(rename); + connect(createNew, &bigIconButton::clicked, createNewPage, [=](){rename->setValue("Layer_" + QString::asprintf("%d", canvasList.size()));redescribe->setValue("No description");createNewPage->slideIn();}); + createNewPage->show(); + pageList.push_back(createNewPage); + + ui->displayLayout->addWidget(titleWidget); + ui->displayLayout->addWidget(defaultPage); + ui->displayLayout->setAlignment(Qt::AlignTop); +} + +void MainWindow::selectCanvas(MyCanvas *canvas){ + if(!curCanvas){ + ui->displayLayout->removeWidget(defaultPage); + defaultPage->hide(); + ui->displayLayout->addWidget(canvas); + canvas->show(); + } + else{ + ui->displayLayout->removeWidget(curCanvas); + curCanvas->hide(); + ui->displayLayout->addWidget(canvas); + canvas->show(); + } + curCanvas = canvas; + canvas->settingPage()->setParent(ui->mainWidget); + curSettingsPage = canvas->settingPage(); + canvasTitle->setText(curCanvas->name()); + canvasTitle->setMaximumWidth(QFontMetrics(QFont("Corbel Light", 24)).size(Qt::TextSingleLine, canvasTitle->text()).width() + 10); + canvasDesc->setText(curCanvas->description()); +} + +void MainWindow::deleteCanvas(MyCanvas *canvas){ + int index = canvasList.indexOf(canvas); + if(index < 0) + return; + canvasList.erase(canvasList.begin() + index); + ui->displayLayout->removeWidget(curCanvas); + curCanvas->hide(); + if(canvasList.size() > 0){ + selectCanvas(canvasList[0]); + } + else{ + ui->displayLayout->addWidget(defaultPage); + defaultPage->show(); + curCanvas = nullptr; + canvasTitle->setText("START"); + canvasTitle->setMaximumWidth(QFontMetrics(QFont("Corbel Light", 24)).size(Qt::TextSingleLine, "START").width() + 10); + canvasDesc->setText("Add your first canvas to start"); + curSettingsPage = defaultSettingsPage; + } + pageList.erase(pageList.begin() + pageList.indexOf(canvas->settingPage())); + delete canvas; + ui->mainWidget->update(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::mousePressEvent(QMouseEvent *event){ + if(event->button() == Qt::LeftButton){ + mousePressed = true; + lastPos = event->globalPosition().toPoint() - this->frameGeometry().topLeft(); + } +} + +void MainWindow::mouseMoveEvent(QMouseEvent *event){ + if(event->buttons() == Qt::NoButton) + mousePressed = false; + if(!mousePressed){ + mouseState = 0; + if(!maximized && abs(event->pos().x() - ui->mainWidget->pos().x()) < 5) + mouseState |= AT_LEFT; + if(!maximized && abs(event->pos().y() - ui->mainWidget->pos().y()) < 5) + mouseState |= AT_TOP; + if(!maximized && abs(event->pos().x() - ui->mainWidget->pos().x() - ui->mainWidget->width()) < 5) + mouseState |= AT_RIGHT; + if(!maximized && abs(event->pos().y() - ui->mainWidget->pos().y() - ui->mainWidget->height()) < 5) + mouseState |= AT_BOTTOM; + if(mouseState == AT_TOP_LEFT || mouseState == AT_BOTTOM_RIGHT) + setCursor(Qt::SizeFDiagCursor); + else if(mouseState == AT_TOP_RIGHT || mouseState == AT_BOTTOM_LEFT) + setCursor(Qt::SizeBDiagCursor); + else if(mouseState & (AT_LEFT | AT_RIGHT)) + setCursor(Qt::SizeHorCursor); + else if(mouseState & (AT_TOP | AT_BOTTOM)) + setCursor(Qt::SizeVerCursor); + else + unsetCursor(); + } + else{ + if(mouseState == 0){ + if(maximized){ + qDebug() << event->pos() << ui->mainWidget->width(); + qreal wRatio = (double)event->pos().x() / (double)ui->mainWidget->width(); + qDebug() << wRatio; + controlWindowScale(); + qDebug() << ui->mainWidget->width(); + qDebug() << QPoint(event->globalPosition().x() - ui->mainWidget->width() * wRatio, event->globalPosition().y()); + qDebug() << this->frameGeometry().topLeft(); + this->move(QPoint(event->globalPosition().x() - ui->mainWidget->width() * wRatio, -30)); + qDebug() << this->frameGeometry().topLeft(); + lastPos = QPoint(ui->mainWidget->width() * wRatio, event->pos().y()); + } + else + this->move(event->globalPosition().toPoint() - lastPos); + } + else{ + QPoint d = event->globalPosition().toPoint() - frameGeometry().topLeft() - lastPos; + if(mouseState & AT_LEFT){ + this->move(this->frameGeometry().x() + d.x(), this->frameGeometry().y()); + this->resize(this->width() - d.x(), this->height()); + } + if(mouseState & AT_RIGHT){ + this->resize(this->width() + d.x(), this->height()); + } + if(mouseState & AT_TOP){ + this->move(this->frameGeometry().x(), this->frameGeometry().y() + d.y()); + this->resize(this->width(), this->height() - d.y()); + } + if(mouseState & AT_BOTTOM){ + this->resize(this->width(), this->height() + d.y()); + } + } + lastPos = event->globalPosition().toPoint() - this->frameGeometry().topLeft(); + } +} + +void MainWindow::resizeEvent(QResizeEvent *event){ + //Resize border + if(border) + border->resize(ui->mainWidget->size() + QSize(2, 2)); + + //Resize mask + QPainterPath path; + path.addRoundedRect(ui->mainWidget->rect(), cornerRadius - 1, cornerRadius - 1); + QRegion mask(path.toFillPolygon().toPolygon()); + ui->mainWidget->setMask(mask); + + //Resize all pages + for(int i = 0; i < pageList.size(); i++){ + pageList[i]->resize(ui->mainWidget->width() * 0.3 < pageList[i]->preferWidth ? pageList[i]->preferWidth - 1 : ui->mainWidget->width() * 0.3 - 1, ui->mainWidget->height()); + pageList[i]->resize(pageList[i]->width() + 1, pageList[i]->height()); + } +} + +void MainWindow::controlWindowScale(){ + if(!maximized){ + lastGeometry = this->frameGeometry(); + windowShadow->setEnabled(false); + ui->verticalLayout->setContentsMargins(0, 0, 0, 0); + border->hide(); + QString mainStyle = "QWidget#mainWidget{background-color:" + mainBackGround.name() + ";border-radius:0px;}"; + ui->mainWidget->setStyleSheet(mainStyle); + this->showMaximized(); + maximized = true; + QPainterPath path; + path.addRect(ui->mainWidget->rect()); + QRegion mask(path.toFillPolygon().toPolygon()); + ui->mainWidget->setMask(mask); + } + else{ + ui->verticalLayout->setContentsMargins(30, 30, 30, 30); + this->showNormal(); + QString mainStyle = "QWidget#mainWidget{background-color:" + mainBackGround.name() + QString::asprintf(";border-radius:%dpx", cornerRadius) + "}"; + ui->mainWidget->setStyleSheet(mainStyle); + QPainterPath path; + path.addRoundedRect(ui->mainWidget->rect(), cornerRadius - 1, cornerRadius - 1); + QRegion mask(path.toFillPolygon().toPolygon()); + ui->mainWidget->setMask(mask); + border->show(); + windowShadow->setEnabled(true); + this->resize(lastGeometry.width(), lastGeometry.height()); + this->move(lastGeometry.x(), lastGeometry.y()); + maximized = false; + } +} + +MyCanvas* MainWindow::loadCanvas(const QString &path){ + QFile input(path); + input.open(QIODevice::ReadOnly); + QTextStream ts(&input); + QString magicString = ts.readLine(); + if(magicString != "VFdGeWFXUnZaekl3TURJd05ESTE=") return nullptr; + MyCanvas *newCanvas = new MyCanvas(ts, cornerRadius, ui->mainWidget); + input.close(); + return newCanvas; +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..f6b9daf --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,67 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include "slidepage.h" +#include "mycanvas.h" + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QMainWindow +{ + Q_OBJECT +private: + int cornerRadius = 20; + QWidget *border = nullptr; + QWidget *defaultPage; + QGraphicsDropShadowEffect *windowShadow; + QColor mainBackGround = QColor(251, 251, 251); + + QLineEdit *canvasTitle = nullptr; + QLineEdit *canvasDesc = nullptr; + customIcon *settingsIcon = nullptr; + customIcon *layersIcon = nullptr; + QWidget *canvasDisplay = nullptr; + + QVector pageList; + SlidePage *createNewPage = nullptr; + SlidePage *defaultSettingsPage = nullptr; + SlidePage *curSettingsPage = nullptr; + SlidePage *layersPage = nullptr; + singleSelectGroup *layerSel = nullptr; + + QVector canvasList; + MyCanvas *curCanvas = nullptr; + + void selectCanvas(MyCanvas *canvas); + void deleteCanvas(MyCanvas *canvas); + void Init(); + + enum {AT_LEFT = 1, AT_TOP = 2, AT_RIGHT = 4, AT_BOTTOM = 8, + AT_TOP_LEFT = 3, AT_TOP_RIGHT = 6, AT_BOTTOM_LEFT = 9, AT_BOTTOM_RIGHT = 12}; + bool mousePressed = false; + int mouseState; + QPoint lastPos; + void mouseMoveEvent(QMouseEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event){mousePressed = false;} + void resizeEvent(QResizeEvent *event); + + bool maximized = false; + QRect lastGeometry; + void controlWindowScale(); + + MyCanvas* loadCanvas(const QString &path); + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; +}; +#endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..4bcf46d --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,307 @@ + + + MainWindow + + + + 0 + 0 + 900 + 650 + + + + true + + + MainWindow + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 30 + + + 30 + + + 30 + + + 30 + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + 16777215 + 30 + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 13 + + + 15 + + + 20 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 12 + 12 + + + + + 12 + 12 + + + + QPushButton{background-color:#c2c2c2;border-radius:6px} +QPushButton:hover {background-color:#f9bf45;border-radius:6px;} +QPushButton:pressed {background-color:#ffb11b;border-radius:6px;} + + + + + + + + + + + 12 + 12 + + + + + 12 + 12 + + + + QPushButton{background-color:#c2c2c2;border-radius:6px} +QPushButton:hover {background-color:#227d51;border-radius:6px;} +QPushButton:pressed {background-color:#2d6d4b;border-radius:6px;} + + + + + + + + + + + 0 + 0 + + + + + 12 + 12 + + + + + 12 + 12 + + + + QPushButton{background-color:#c2c2c2;border-radius:6px} +QPushButton:hover {background-color:#cb4042;border-radius:6px;} +QPushButton:pressed {background-color:#ab3b3a;border-radius:6px;} + + + + + + + + + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 30 + + + 15 + + + 30 + + + 30 + + + + + + + + + + + + + + + + + + + + minimumBtn + clicked() + MainWindow + showMinimized() + + + 688 + 54 + + + 399 + 299 + + + + + closeBtn + clicked() + MainWindow + close() + + + 742 + 54 + + + 399 + 299 + + + + + diff --git a/mycanvas.cpp b/mycanvas.cpp new file mode 100644 index 0000000..7a6e6a3 --- /dev/null +++ b/mycanvas.cpp @@ -0,0 +1,462 @@ +#include "mycanvas.h" + +MyCanvas::MyCanvas(int radius, QString name, QString desc, int structure, int _type, QWidget *parent) : + QWidget(parent), + canvasName(name), + canvasDescription(desc), + structure_type(structure), + type(_type) +{ + /* create canvas */ + mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + this->setLayout(mainLayout); + view = new MyGraphicsView(type == UDG ? MyGraphicsView::UDG : MyGraphicsView::DG); + view->setSceneRect(view->rect()); + view->setStyleSheet("background-color: #FFFFFF;border:1px solid #cfcfcf;border-radius:10px;"); + mainLayout->addWidget(view); + g = structure == AL ? (AbstractGraph*)(new ALGraph(type)) : (AbstractGraph*)(new AMLGraph(type)); + connect(view, SIGNAL(vexAdded(MyGraphicsVexItem*)), this, SLOT(addVex(MyGraphicsVexItem*))); + connect(view, SIGNAL(arcAdded(MyGraphicsLineItem*)), this, SLOT(addArc(MyGraphicsLineItem*))); + connect(view, &MyGraphicsView::visitClear, this, [=](){g->ClearVisit();}); + this->setFocusPolicy(Qt::ClickFocus); + + CreateSettings(radius); +} + +MyCanvas::MyCanvas(QTextStream &ts, int radius, QWidget *parent) : + QWidget(parent) +{ + canvasName = ts.readLine(); + canvasDescription = ts.readLine(); + ts >> structure_type >> type; + + /* create canvas */ + mainLayout = new QHBoxLayout(this); + mainLayout->setContentsMargins(0, 0, 0, 0); + this->setLayout(mainLayout); + view = new MyGraphicsView(type == UDG ? MyGraphicsView::UDG : MyGraphicsView::DG); + view->setSceneRect(view->rect()); + view->setStyleSheet("background-color: #FFFFFF;border:1px solid #cfcfcf;border-radius:10px;"); + mainLayout->addWidget(view); + g = structure_type == AL ? (AbstractGraph*)(new ALGraph(type)) : (AbstractGraph*)(new AMLGraph(type)); + connect(view, SIGNAL(vexAdded(MyGraphicsVexItem*)), this, SLOT(addVex(MyGraphicsVexItem*))); + connect(view, SIGNAL(arcAdded(MyGraphicsLineItem*)), this, SLOT(addArc(MyGraphicsLineItem*))); + connect(view, &MyGraphicsView::visitClear, this, [=](){g->ClearVisit();}); + view->ReadFromFile(ts); + for(int i = 0; i < view->arcNum; i++){ + int w; + ts >> w; + if(w != 0) + g->SetWeight(view->lines[i], w); + } + this->setFocusPolicy(Qt::ClickFocus); + + CreateSettings(radius); +} + +void MyCanvas::CreateSettings(int radius){ + /* create settings page */ + settings = new SlidePage(radius, "SETTINGS", this->parentWidget()); + singleSelectGroup *structureSetting = new singleSelectGroup("Structure", this); + selectionItem *setAL = new selectionItem("AL", "Adjacent list structure", this); + selectionItem *setAML = new selectionItem("AML", "Adjacent multiple list", this); + structureSetting->AddItem(setAL); + structureSetting->AddItem(setAML); + structureSetting->SetSelection(structure_type == AL ? setAL : setAML); + connect(structureSetting, &singleSelectGroup::selectedItemChange, this, [=](int id){ + if(id == 1){ + ALGraph *old = (ALGraph*)g; + g = old->ConvertToAML(); + old->~ALGraph(); + structure_type = AML; + } + else{ + AMLGraph *old = (AMLGraph*)g; + g = old->ConvertToAL(); + old->~AMLGraph(); + structure_type = AL; + } + }); + singleSelectGroup *dirSetting = new singleSelectGroup("Mode", this); + selectionItem *setDG = new selectionItem("DG", "Directed graph", this); + selectionItem *setUDG = new selectionItem("UDG", "Undirected graph", this); + dirSetting->AddItem(setDG); + dirSetting->AddItem(setUDG); + dirSetting->SetSelection(type == DG ? setDG : setUDG); + connect(dirSetting, &singleSelectGroup::selectedItemChange, this, [=](int id){ + g->ConvertType(id == 0 ? AbstractGraph::DG : AbstractGraph::UDG); + view->setType(id == 0 ? MyGraphicsView::DG : MyGraphicsView::UDG); + type = id == 0 ? DG : UDG; + }); + QWidget *whiteSpace = new QWidget(this); + whiteSpace->setFixedHeight(30); + horizontalValueAdjuster *aniSpeed = new horizontalValueAdjuster("Animation speed", 0.1, 20, 0.1, this); + aniSpeed->setValue(1.0); + connect(aniSpeed, &horizontalValueAdjuster::valueChanged, view, [=](qreal value){view->setAniRate(value);}); + textInputItem *rename = new textInputItem("Name", this); + rename->setValue(canvasName); + connect(rename, &textInputItem::textEdited, this, [=](QString text){canvasName = text; emit nameChanged(text);}); + textInputItem *redesc = new textInputItem("Detail", this); + redesc->setValue(canvasDescription); + connect(redesc, &textInputItem::textEdited, this, [=](QString text){canvasDescription = text; emit descChanged(text);}); + textButton *saveBtn = new textButton("Save to file", this); + connect(saveBtn, &textButton::clicked, this, [=](){ + QString savePath = QFileDialog::getSaveFileName(this, tr("Save map"), " ", tr("Map file(*.map)")); + if(!savePath.isEmpty()) + SaveToFile(savePath); + }); + textButton *delBtn = new textButton("Delete", "#0acb1b45","#1acb1b45","#2acb1b45",this); + connect(delBtn, &textButton::clicked, this, [=](){emit setDel(this);}); + settings->AddContent(delBtn); + settings->AddContent(saveBtn); + settings->AddContent(dirSetting); + settings->AddContent(structureSetting); + settings->AddContent(aniSpeed); + settings->AddContent(whiteSpace); + settings->AddContent(redesc); + settings->AddContent(rename); + settings->show(); + + QTimer *delay = new QTimer(this); + connect(delay, &QTimer::timeout, this, [=](){Init();}); + delay->setSingleShot(true); + delay->start(10); +} + +void MyCanvas::Init(){ + /* Create info widget */ + infoWidget = new QWidget(this); + mainLayout->addWidget(infoWidget); + mainLayout->setStretch(0, 7); + mainLayout->setStretch(1, 3); + infoWidget->setMinimumWidth(250); + infoWidget->setMaximumWidth(500); + + //Set basic layout + QVBoxLayout *infoLayout = new QVBoxLayout(infoWidget); + infoWidget->setLayout(infoLayout); + infoLayout->setContentsMargins(10, 0, 0, 0); + infoLayout->setAlignment(Qt::AlignTop); + + QFont titleFont = QFont("Corbel", 20); + + QWidget *upper = new QWidget(infoWidget); + QVBoxLayout *upperLayout = new QVBoxLayout(upper); + upper->setLayout(upperLayout); + upperLayout->setContentsMargins(0, 0, 0, 0); + upper->setContentsMargins(0, 0, 0, 0); + pageName = new QLabel(infoWidget); + pageName->setText("INFO"); + pageName->setFont(titleFont); + pageName->setAlignment(Qt::AlignLeft | Qt::AlignTop); + pageName->setStyleSheet("color:#2c2c2c"); + QWidget *upperSplitter = new QWidget(upper); + upperSplitter->setFixedSize(30, 6); + upperSplitter->setStyleSheet("background-color:#3c3c3c;border-radius:3px;"); + upperLayout->addWidget(pageName); + upperLayout->addWidget(upperSplitter); + + QWidget *lower = new QWidget(infoWidget); + QVBoxLayout *lowerLayout = new QVBoxLayout(lower); + lower->setLayout(lowerLayout); + lowerLayout->setContentsMargins(0, 0, 0, 0); + QLabel *logLabel = new QLabel(lower); + logLabel->setText("LOG"); + logLabel->setFont(titleFont); + logLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); + logLabel->setStyleSheet("color:#2c2c2c"); + QWidget *lowerSplitter = new QWidget(lower); + lowerSplitter->setFixedSize(30, 6); + lowerSplitter->setStyleSheet("background-color:#3c3c3c;border-radius:3px;"); + ScrollAreaCustom *logDisplay = new ScrollAreaCustom(lower); + logDisplay->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + lowerLayout->addWidget(logLabel); + lowerLayout->addWidget(lowerSplitter); + lowerLayout->addWidget(logDisplay); + + infoLayout->addWidget(upper); + infoLayout->addWidget(lower); + + //Add specific items and connections + //Default page + QWidget *defInfoPage = new QWidget(infoWidget); + QVBoxLayout *defInfoLayout = new QVBoxLayout(defInfoPage); + defInfoPage->setLayout(defInfoLayout); + defInfoLayout->setContentsMargins(0, 0, 0, 0); + defInfoLayout->setAlignment(Qt::AlignTop); + QWidget *defTextItems = new QWidget(defInfoPage); + defTextItems->setObjectName("DefTextItems"); + defTextItems->setStyleSheet("QWidget#DefTextItems{border:1px solid #cfcfcf;border-radius:5px;}"); + QVBoxLayout *defTextLayout = new QVBoxLayout(defTextItems); + defTextItems->setLayout(defTextLayout); + defTextLayout->setContentsMargins(0, 5, 0, 5); + textInputItem *textName = new textInputItem("Name", defInfoPage); + textName->setValue(canvasName); + connect(this, &MyCanvas::nameChanged, this, [=](){textName->setValue(canvasName);}); + textName->setEnabled(false); + defTextLayout->addWidget(textName); + textInputItem *textDesc = new textInputItem("Detail", defInfoPage); + textDesc->setValue(canvasDescription); + connect(this, &MyCanvas::descChanged, this, [=](){textDesc->setValue(canvasDescription);}); + textDesc->setEnabled(false); + defTextLayout->addWidget(textDesc); + textInputItem *vexNumText = new textInputItem("Vex", defInfoPage); + vexNumText->setValue(QString::asprintf("%d", view->vexNum)); + vexNumText->setEnabled(false); + defTextLayout->addWidget(vexNumText); + textInputItem *arcNumText = new textInputItem("Arc", defInfoPage); + arcNumText->setValue(QString::asprintf("%d", view->arcNum)); + arcNumText->setEnabled(false); + defTextLayout->addWidget(arcNumText); + defInfoLayout->addWidget(defTextItems); + upperLayout->addWidget(defInfoPage); + defInfoPage->show(); + + //VexPage + QWidget *vexInfoPage = new QWidget(infoWidget); + QVBoxLayout *vexInfoLayout = new QVBoxLayout(vexInfoPage); + vexInfoLayout->setContentsMargins(0, 0, 0, 0); + vexInfoLayout->setAlignment(Qt::AlignTop); + vexInfoPage->setLayout(vexInfoLayout); + QWidget *vexTextItems = new QWidget(vexInfoPage); + vexTextItems->setObjectName("VexTextItems"); + vexTextItems->setStyleSheet("QWidget#VexTextItems{border:1px solid #cfcfcf;border-radius:5px;}"); + QVBoxLayout *vexTextLayout = new QVBoxLayout(vexTextItems); + vexTextItems->setLayout(vexTextLayout); + vexTextLayout->setContentsMargins(0, 5, 0, 5); + textInputItem *textTag = new textInputItem("Tag", vexInfoPage); + vexTextLayout->addWidget(textTag); + textInputItem *dijStart = new textInputItem("Start", vexInfoPage); + dijStart->setValue("Run dijkstra first"); + dijStart->setEnabled(false); + vexTextLayout->addWidget(dijStart); + textInputItem *dijDistance = new textInputItem("Dist", vexInfoPage); + dijDistance->setValue("Infinite"); + dijDistance->setEnabled(false); + vexTextLayout->addWidget(dijDistance); + textInputItem *dijPrev = new textInputItem("Prev", vexInfoPage); + dijPrev->setValue("Run dijkstra first"); + dijPrev->setEnabled(false); + vexTextLayout->addWidget(dijPrev); + vexInfoLayout->addWidget(vexTextItems); + QWidget *traverseBar = new QWidget(vexInfoPage); + QHBoxLayout *traverseLayout = new QHBoxLayout(traverseBar); + traverseBar->setLayout(traverseLayout); + traverseLayout->setContentsMargins(0, 0, 0, 0); + textButton *startBfs = new textButton("BFS", vexInfoPage); + traverseLayout->addWidget(startBfs); + textButton *startDfs = new textButton("DFS", vexInfoPage); + traverseLayout->addWidget(startDfs); + vexInfoLayout->addWidget(traverseBar); + textButton *startDij = new textButton("Start Dijkstra", vexInfoPage); + startDij->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + vexInfoLayout->addWidget(startDij); + textButton *delVex = new textButton("Delete", "#1acb1b45","#2acb1b45","#3acb1b45", vexInfoPage); + vexInfoLayout->addWidget(delVex); + upperLayout->addWidget(vexInfoPage); + vexInfoPage->hide(); + + //ArcPage + QWidget *arcInfoPage = new QWidget(infoWidget); + QVBoxLayout *arcInfoLayout = new QVBoxLayout(arcInfoPage); + arcInfoLayout->setContentsMargins(0, 0, 0, 0); + arcInfoLayout->setAlignment(Qt::AlignTop); + arcInfoPage->setLayout(arcInfoLayout); + QWidget *arcTextItems = new QWidget(arcInfoPage); + arcTextItems->setObjectName("VexTextItems"); + arcTextItems->setStyleSheet("QWidget#VexTextItems{border:1px solid #cfcfcf;border-radius:5px;}"); + QVBoxLayout *arcTextLayout = new QVBoxLayout(arcTextItems); + arcTextItems->setLayout(arcTextLayout); + arcTextLayout->setContentsMargins(0, 5, 0, 5); + textInputItem *arcWeight = new textInputItem("Pow", arcInfoPage); + arcTextLayout->addWidget(arcWeight); + QRegularExpression re("^[1-9]\\d*$"); + arcWeight->setValidator(new QRegularExpressionValidator(re)); + textInputItem *arcStart = new textInputItem("Start", arcInfoPage); + arcStart->setValue("NA"); + arcStart->setEnabled(false); + arcTextLayout->addWidget(arcStart); + textInputItem *arcEnd = new textInputItem("End", arcInfoPage); + arcEnd->setValue("NA"); + arcEnd->setEnabled(false); + arcTextLayout->addWidget(arcEnd); + arcInfoLayout->addWidget(arcTextItems); + textButton *reverseBtn = new textButton("Reverse", arcInfoPage); + arcInfoLayout->addWidget(reverseBtn); + textButton *delArc = new textButton("Delete", "#1acb1b45","#2acb1b45","#3acb1b45", arcInfoPage); + arcInfoLayout->addWidget(delArc); + upperLayout->addWidget(arcInfoPage); + arcInfoPage->hide(); + + connect(view, &MyGraphicsView::vexAdded, this, [=](){vexNumText->setValue(QString::asprintf("%d",view->vexNum));}); + connect(view, &MyGraphicsView::arcAdded, this, [=](){arcNumText->setValue(QString::asprintf("%d",view->arcNum));}); + connect(view, &MyGraphicsView::selected, this, [=](QGraphicsItem *item){ + int type = item->type(); + if(type == MyGraphicsVexItem::Type){ + defInfoPage->hide(); + arcInfoPage->hide(); + vexInfoPage->show(); + textTag->setValue(view->selectedVex()->Text()); + if(g->GetInfoOf(view->selectedVex())->strtVexInfo == nullptr){ + dijStart->setValue("Run dijkstra first"); + dijPrev->setValue("Run dijkstra first"); + dijDistance->setValue("Infinite"); + } + else{ + dijStart->setValue(g->GetInfoOf(view->selectedVex())->strtVexInfo->gVex->Text()); + if(g->GetInfoOf(view->selectedVex())->preVexID == -1) + dijPrev->setValue("This vex"); + else + dijPrev->setValue(g->GetInfoOf(g->GetInfoOf(view->selectedVex())->preVexID)->gVex->Text()); + dijDistance->setValue(g->GetInfoOf(view->selectedVex())->distance == 2147483647 ? "Infinite" : QString::asprintf("%d", g->GetInfoOf(view->selectedVex())->distance)); + } + } + else if(type == MyGraphicsLineItem::Type){ + defInfoPage->hide(); + vexInfoPage->hide(); + arcInfoPage->show(); + arcWeight->setValue(view->selectedArc()->weightText() == "" ? "1" : view->selectedArc()->weightText()); + arcStart->setValue(view->selectedArc()->stVex()->Text()); + arcEnd->setValue(view->selectedArc()->edVex()->Text()); + } + else{ + vexInfoPage->hide(); + arcInfoPage->hide(); + defInfoPage->show(); + vexNumText->setValue(QString::asprintf("%d",view->vexNum)); + arcNumText->setValue(QString::asprintf("%d",view->arcNum)); + } + }); + connect(textTag, &textInputItem::textEdited, this, [=](QString text){ + logDisplay->addWidget(new viewLog("[Vex] | Rename \""+view->selectedVex()->Text()+"\" to \""+text+"\"")); + if(view->selectedVex() != nullptr) + view->selectedVex()->setText(text); + if(g->GetInfoOf(view->selectedVex())->strtVexInfo != nullptr){ + dijStart->setValue(g->GetInfoOf(view->selectedVex())->strtVexInfo->gVex->Text()); + if(g->GetInfoOf(view->selectedVex())->preVexID != -1) + dijPrev->setValue(g->GetInfoOf(g->GetInfoOf(view->selectedVex())->preVexID)->gVex->Text()); + } + }); + connect(arcWeight, &textInputItem::textEdited, this, [=](QString text){ + logDisplay->addWidget(new viewLog("[Arc] | \""+view->selectedArc()->stVex()->Text()+"\" -> \""+view->selectedArc()->edVex()->Text()+"\" set to "+text)); + g->SetWeight(view->selectedArc(), text.toDouble()); + }); + connect(view, &MyGraphicsView::unselected, this, [=](){ + vexInfoPage->hide(); + arcInfoPage->hide(); + defInfoPage->show(); + vexNumText->setValue(QString::asprintf("%d",view->vexNum)); + arcNumText->setValue(QString::asprintf("%d",view->arcNum)); + }); + connect(startBfs, &textButton::clicked, this, [=](){ + viewLog *newLog = new viewLog("[BFS] | --- BFS start ---"); + newLog->setStyleSheet("color:#0078d4"); + logDisplay->addWidget(newLog); + bfs(); + }); + connect(startDfs, &textButton::clicked, this, [=](){ + viewLog *newLog = new viewLog("[DFS] | --- DFS start ---"); + newLog->setStyleSheet("color:#0078d4"); + logDisplay->addWidget(newLog); + dfs(); + }); + connect(startDij, &textButton::clicked, this, [=](){ + viewLog *newLog = new viewLog("[Dij] | --- Dijkstra start ---"); + newLog->setStyleSheet("color:#0078d4"); + logDisplay->addWidget(newLog); + dijkstra(); + if(g->GetInfoOf(view->selectedVex())->strtVexInfo == nullptr){ + dijStart->setValue("Run dijkstra first"); + dijPrev->setValue("Run dijkstra first"); + dijDistance->setValue("Infinite"); + } + else{ + dijStart->setValue(g->GetInfoOf(view->selectedVex())->strtVexInfo->gVex->Text()); + if(g->GetInfoOf(view->selectedVex())->preVexID == -1) + dijPrev->setValue("This vex"); + else + dijPrev->setValue(g->GetInfoOf(g->GetInfoOf(view->selectedVex())->preVexID)->gVex->Text()); + dijDistance->setValue(g->GetInfoOf(view->selectedVex())->distance == 2147483647 ? "Infinite" : QString::asprintf("%d", g->GetInfoOf(view->selectedVex())->distance)); + } + }); + connect(reverseBtn, &textButton::clicked, this, [=](){ + if(g->Type() == AbstractGraph::UDG) + view->selectedArc()->reverseDirection(); + else{ + g->DelArc(view->selectedArc()); + view->selectedArc()->reverseDirection(); + g->AddArc(view->selectedArc()); + } + }); + connect(delVex, &textButton::clicked, this, [=](){ + logDisplay->addWidget(new viewLog("[Vex] | Delete vex \""+view->selectedVex()->Text()+"\"")); + g->DelVex(view->selectedVex()); + view->selectedVex()->remove(); + g->ResetDistance(); + g->ClearVisit(); + view->unSelect(); + }); + connect(delArc, &textButton::clicked, this, [=](){ + logDisplay->addWidget(new viewLog("[Arc] | Delete arc \""+view->selectedArc()->stVex()->Text()+"\" -> \""+view->selectedArc()->edVex()->Text()+"\"")); + g->DelArc(view->selectedArc()); + view->selectedArc()->remove(); + g->ResetDistance(); + g->ClearVisit(); + view->unSelect(); + }); + + connect(view, &MyGraphicsView::logAdded, this, [=](viewLog* log){logDisplay->addWidget(log);}); + +} + +void MyCanvas::SaveToFile(const QString &path){ + QFile output(path); + output.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream ts(&output); + ts << "VFdGeWFXUnZaekl3TURJd05ESTE=\n"; + ts << canvasName << "\n"; + ts << canvasDescription << "\n"; + ts << structure_type << " " << type << "\n"; + view->SaveToFile(ts); + output.close(); +} + +void MyCanvas::addVex(MyGraphicsVexItem *vex){ + g->AddVex(vex); +} + +void MyCanvas::addArc(MyGraphicsLineItem *arc){ + g->AddArc(arc); +} + +void MyCanvas::dfs(){ + MyGraphicsVexItem *strtVex = view->selectedVex(); + if(strtVex != nullptr){ + g->DFS(strtVex); + view->hasVisitedItem = true; + } +} + +void MyCanvas::bfs(){ + MyGraphicsVexItem *strtVex = view->selectedVex(); + if(strtVex != nullptr){ + g->BFS(strtVex); + view->hasVisitedItem = true; + } +} + +void MyCanvas::dijkstra(){ + MyGraphicsVexItem *strtVex = view->selectedVex(); + if(strtVex){ + g->Dijkstra(strtVex); + view->hasVisitedItem = true; + } +} + +void MyCanvas::setWeight(int w){ + MyGraphicsLineItem *arc = view->selectedArc(); + if(arc){ + g->SetWeight(arc, w); + } +} diff --git a/mycanvas.h b/mycanvas.h new file mode 100644 index 0000000..82fca79 --- /dev/null +++ b/mycanvas.h @@ -0,0 +1,61 @@ +#ifndef MYCANVAS_H +#define MYCANVAS_H + +#include +#include +#include "slidepage.h" +#include "graph_view.h" +#include "graph_implement.h" + +class MyCanvas : public QWidget +{ + Q_OBJECT +private: + QString canvasName; + QString canvasDescription; + + SlidePage *settings; + + //For display + MyGraphicsView *view; + QHBoxLayout *mainLayout; + QWidget *infoWidget; + QLabel *pageName; + + AbstractGraph *g; + int structure_type; + int type; + + void CreateSettings(int r); + void Init(); + void SaveToFile(const QString &path); + +public: + enum { UDG = AbstractGraph::UDG, DG = AbstractGraph::DG }; + enum { AL = 128, AML = 256 }; + + explicit MyCanvas(int radius, QString name = "", QString desc = "", int structure = AL, int _type = UDG, QWidget *parent = nullptr); + MyCanvas(QTextStream &ts, int radius, QWidget *parent = nullptr); + QString name(){return canvasName;} + QString description(){return canvasDescription;} + SlidePage *settingPage(){return settings;} + +signals: + void nameChanged(QString name); + void descChanged(QString desc); + void setDel(MyCanvas* target); + +private slots: + void addVex(MyGraphicsVexItem*); + void addArc(MyGraphicsLineItem*); + //void delVex(MyGraphicsVexItem*); + //void delArc(MyGraphicsLineItem*); + void dfs(); + void bfs(); + void dijkstra(); + void setWeight(int w); + void setAniRate(int step){view->setAniRate(step / 100.0);} + +}; + +#endif // MYCANVAS_H diff --git a/slidepage.cpp b/slidepage.cpp new file mode 100644 index 0000000..76fabf6 --- /dev/null +++ b/slidepage.cpp @@ -0,0 +1,164 @@ +#include "slidepage.h" + +SlidePage::SlidePage(int radius, QString name, QWidget *parent) : + QWidget(parent), + cornerRadius(radius), + pageName(name) +{ + //if(parent) + // resize(parent->width() * 0.8 <= preferWidth ? parent->width() * 0.8 : preferWidth, parent->height()); + resize(parent->width() * 0.3 <= preferWidth ? preferWidth : parent->width() * 0.3, parent->height()); + this->move(QPoint(-this->width() - 30, 0)); + + pageContentContainer = new ScrollAreaCustom(this); + //> note: Important!!! + pageContentContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + nameLabel = new QLabel(pageName, this); + textFont.setStyleStrategy(QFont::PreferAntialias); + nameLabel->setFont(textFont); + + backIcon = new customIcon(":/icons/icons/back.svg", "", 5, this); + + opacity = new QGraphicsOpacityEffect(this); + opacity->setOpacity(0); + pageContentContainer->setGraphicsEffect(opacity); + nameLabel->setGraphicsEffect(opacity); + + QString style; + style = "background-color:white;border-radius:" + QString::asprintf("%d", cornerRadius) + "px"; + bgWidget = new QWidget(this); + bgWidget->lower(); + bgWidget->resize(this->size()); + bgWidget->setStyleSheet(style); + bgWidget->show(); + + /* Intialize layout */ + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(20, 40, 20, 20); + QWidget *titleBar = new QWidget(this); + QHBoxLayout *titleLayout = new QHBoxLayout(titleBar); + titleLayout->setAlignment(Qt::AlignLeft); + titleBar->setLayout(titleLayout); + titleLayout->addWidget(backIcon); + titleLayout->addWidget(nameLabel); + mainLayout->addWidget(titleBar); + mainLayout->addWidget(pageContentContainer); + mainLayout->setAlignment(Qt::AlignTop); + this->setLayout(mainLayout); + + sheildLayer = new SheildLayer(this->parentWidget()); + sheildLayer->resize(this->parentWidget()->size()); + sheildLayer->setGraphicsEffect(opacity); + sheildLayer->setMouseTracking(true); + connect(sheildLayer, &SheildLayer::clicked, this, [=](){slideOut();setFocus();}); + + /* Set shadow */ + QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(this); + shadow->setBlurRadius(30); + shadow->setColor(QColor(0, 0, 0)); + shadow->setOffset(0, 0); + this->setGraphicsEffect(shadow); + + /* set policies */ + this->setFocusPolicy(Qt::ClickFocus); + this->setMouseTracking(true); + bgWidget->setMouseTracking(true); + sheildLayer->setMouseTracking(true); + + /* connect */ + connect(backIcon, &QPushButton::clicked, this, [=](){slideOut();}); +} + +void SlidePage::resizeEvent(QResizeEvent *event){ + bgWidget->resize(this->size()); + sheildLayer->resize(this->parentWidget()->size()); + if(!onShown && !curAni) + this->move(QPoint(-this->width() - 30, 0)); + else if(!onShown && curAni) + emit sizeChange(); + +} + +void SlidePage::SetRadius(int radius){ + cornerRadius = radius; + QString style; + style = "background-color:white;border-radius:" + QString::asprintf("%d", cornerRadius) + "px"; + this->setStyleSheet(style); +} + +void SlidePage::SetName(QString name){ + pageName = name; + nameLabel->setText(pageName); +} + +void SlidePage::slideIn(){ + if(curAni){ + curAni->stop(); + curAni->deleteLater(); + curAni = nullptr; + } + onShown = true; + sheildLayer->raise(); + sheildLayer->setEnabled(true); + this->raise(); + sheildLayer->show(); + QParallelAnimationGroup *inGroup = new QParallelAnimationGroup(this); + QPropertyAnimation *slideInAni = new QPropertyAnimation(this, "pos", this); + slideInAni->setStartValue(this->pos()); + slideInAni->setEndValue(QPoint(0, 0)); + slideInAni->setDuration(1000); + slideInAni->setEasingCurve(QEasingCurve::InOutExpo); + QPropertyAnimation *fadeInAni = new QPropertyAnimation(opacity, "opacity", this); + fadeInAni->setStartValue(opacity->opacity()); + //> note: DO NOT CHANGE 0.99 TO 1!!!!! + //> Will cause unexpected position shift (maybe qt's bug) + fadeInAni->setEndValue(0.99); + fadeInAni->setDuration(750); + QSequentialAnimationGroup *rotate = new QSequentialAnimationGroup(this); + QPropertyAnimation *rotateAni = new QPropertyAnimation(backIcon, "rotationAngle", this); + rotateAni->setStartValue(180); + rotateAni->setEndValue(360); + rotateAni->setDuration(750); + rotateAni->setEasingCurve(QEasingCurve::InOutExpo); + rotate->addPause(250); + rotate->addAnimation(rotateAni); + inGroup->addAnimation(slideInAni); + inGroup->addAnimation(fadeInAni); + inGroup->addAnimation(rotate); + connect(inGroup, &QParallelAnimationGroup::finished, this, [=](){this->curAni = nullptr;}); + inGroup->start(); + curAni = inGroup; +} + +void SlidePage::slideOut(){ + if(curAni){ + curAni->stop(); + curAni->deleteLater(); + curAni = nullptr; + } + onShown = false; + sheildLayer->setEnabled(false); + QParallelAnimationGroup *outGroup = new QParallelAnimationGroup(this); + QPropertyAnimation *slideOutAni = new QPropertyAnimation(this, "pos", this); + slideOutAni->setStartValue(this->pos()); + slideOutAni->setEndValue(QPoint(-this->width() - 30, 0)); + slideOutAni->setDuration(1000); + slideOutAni->setEasingCurve(QEasingCurve::InOutExpo); + QPropertyAnimation *fadeOutAni = new QPropertyAnimation(opacity, "opacity", this); + fadeOutAni->setStartValue(opacity->opacity()); + fadeOutAni->setEndValue(0); + fadeOutAni->setDuration(750); + QPropertyAnimation *rotateAni = new QPropertyAnimation(backIcon, "rotationAngle", this); + rotateAni->setStartValue(360); + rotateAni->setEndValue(180); + rotateAni->setDuration(750); + rotateAni->setEasingCurve(QEasingCurve::InOutExpo); + outGroup->addAnimation(slideOutAni); + outGroup->addAnimation(fadeOutAni); + outGroup->addAnimation(rotateAni); + connect(outGroup, &QPropertyAnimation::finished, this, [=](){this->curAni = nullptr;pageContentContainer->scrollToTop();sheildLayer->hide();}); + connect(this, &SlidePage::sizeChange, slideOutAni, [=](){slideOutAni->setEndValue(QPoint(-this->width() - 30, 0));}); + outGroup->start(); + curAni = outGroup; +} diff --git a/slidepage.h b/slidepage.h new file mode 100644 index 0000000..2a61574 --- /dev/null +++ b/slidepage.h @@ -0,0 +1,80 @@ +#ifndef SLIDEPAGE_H +#define SLIDEPAGE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "customScrollContainer.h" +#include "customWidgets.h" + +class SheildLayer : public QWidget{ + Q_OBJECT + +private: + bool pressed = false; + bool enabled = true; + QWidget *bg; + void mousePressEvent(QMouseEvent *event){if(enabled)pressed = true;} + void mouseReleaseEvent(QMouseEvent *event){if(enabled && pressed)emit clicked();pressed = false;} + void resizeEvent(QResizeEvent *event){bg->resize(this->parentWidget()->size());} +public: + SheildLayer(QWidget *parent = nullptr) : QWidget(parent){ + bg = new QWidget(this); + bg->resize(parent->size()); + bg->setStyleSheet("background-color:#5a000000"); + bg->setAttribute(Qt::WA_TransparentForMouseEvents); + bg->show(); + } + void setEnabled(bool e){enabled = e;} +signals: + void clicked(); +}; + +class SlidePage : public QWidget +{ + Q_OBJECT +private: + int cornerRadius; + QString pageName; + ScrollAreaCustom *pageContentContainer; + QLabel *nameLabel; + customIcon *backIcon; + SheildLayer *sheildLayer; + QWidget *bgWidget; + QFont textFont = QFont("Corbel Light", 24); + + bool onShown = false; + QParallelAnimationGroup *curAni = nullptr; + QGraphicsOpacityEffect *opacity; + + void resizeEvent(QResizeEvent *event); + +public: + const int preferWidth = 350; + explicit SlidePage(int radius, QString name, QWidget *parent = nullptr); + void SetRadius(int radius); + void SetName(QString name); + void AddContent(QWidget* widget){widget->setParent(this);pageContentContainer->addWidget(widget, false);} + void AddContents(QVector widgets){pageContentContainer->addWidgets(widgets);} + void RemoveContents(QVector widgets){for(int i = 0; i < widgets.size(); i++)pageContentContainer->removeWidget(widgets[i]);} + void UpdateContents(){pageContentContainer->updateHeight();} + void ScrollToTop(){pageContentContainer->scrollToTop();} + +signals: + void sizeChange(); + +public slots: + void slideIn(); + void slideOut(); + +}; + +#endif // SLIDEPAGE_H