commit ba9e5b2944e211641e6bd6fa8a34f781a855affb Author: Linloir <3145078758@qq.com> Date: Sun Dec 5 09:05:54 2021 +0800 Initial commit 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 0000000..d5f0009 Binary files /dev/null and b/icons/create.png differ 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 0000000..01a2a12 Binary files /dev/null and b/icons/open.png differ diff --git a/icons/open.svg b/icons/open.svg new file mode 100644 index 0000000..f2fdaa5 --- /dev/null +++ b/icons/open.svg @@ -0,0 +1 @@ + \ 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 0000000..0e7ec63 Binary files /dev/null and b/logo.ico differ 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