diff --git a/.gitignore b/.gitignore index d5ce34d..5600118 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,6 @@ compile_commands.json # QtCreator local machine specific files for imported projects *creator.user* -*_qmlcache.qrc \ No newline at end of file +*_qmlcache.qrc.DS_Store +.idea +.DS_Store diff --git a/Application.cpp b/Application.cpp new file mode 100644 index 0000000..7d072d7 --- /dev/null +++ b/Application.cpp @@ -0,0 +1,8 @@ +#include "Application.h" + +namespace mvc { + Application::Application() { + view_.subscribe(controller_.input()); + model_.subscribe(view_.input()); + } +}// namespace mvc diff --git a/Application.h b/Application.h new file mode 100644 index 0000000..bc76336 --- /dev/null +++ b/Application.h @@ -0,0 +1,18 @@ +#ifndef COURSEPROJECT_APPLICATION_H +#define COURSEPROJECT_APPLICATION_H + +#include "Controller.h" + +namespace mvc { + class Application { + public: + Application(); + + private: + AVLTree model_; + View view_; + Controller controller_{&model_}; + }; +}// namespace mvc + +#endif//COURSEPROJECT_APPLICATION_H diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e724d85 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.24) +project(CourseProject) + +set(CMAKE_CXX_STANDARD 20) +set(CXX_STANDARD_REQUIRED ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) + +set(CMAKE_PREFIX_PATH "/opt/homebrew/opt/qt/lib/cmake") + +find_package(Qt6 COMPONENTS + Core + Gui + Widgets + REQUIRED) + +add_executable(CourseProject main.cpp mainwindow.cpp mainwindow.ui Model.cpp View.cpp Controller.cpp InfoTree.cpp aboutwindow.cpp Application.cpp) +target_link_libraries(CourseProject + Qt::Core + Qt::Gui + Qt::Widgets + ) + diff --git a/Controller.cpp b/Controller.cpp new file mode 100644 index 0000000..1e99d2a --- /dev/null +++ b/Controller.cpp @@ -0,0 +1,29 @@ +#include "Controller.h" + +namespace mvc { + Controller::Controller(AVLTree *model) + : model_(model) { + } + + void Controller::action(const ViewData &data) { + switch (data.operation) { + case View::Operation::Add: { + model_->insert(data.value); + break; + } + case View::Operation::Delete: { + model_->deleteNode(data.value); + break; + } + case View::Operation::Search: { + model_->search(data.value); + break; + } + case View::Operation::DeleteAll: { + model_->deleteAll(); + } + default: + break; + } + } +}// namespace mvc diff --git a/Controller.h b/Controller.h new file mode 100644 index 0000000..09c4e43 --- /dev/null +++ b/Controller.h @@ -0,0 +1,28 @@ +#ifndef COURSEPROJECT_CONTROLLER_H +#define COURSEPROJECT_CONTROLLER_H + +#include + +#include "Model.h" +#include "View.h" + +namespace mvc { + class Controller { + private: + using ViewData = View::Command; + using Observer = NSLibrary::CObserver; + using Input = NSLibrary::CColdInput; + + public: + Controller(AVLTree *ptr); + Observer *input() { return &observer_; } + + private: + void action(const ViewData &data); + + AVLTree *model_; + Input observer_ = [this](const ViewData &data) { action(data); }; + }; +}// namespace mvc + +#endif//COURSEPROJECT_CONTROLLER_H diff --git a/InfoTree.cpp b/InfoTree.cpp new file mode 100644 index 0000000..6251d4d --- /dev/null +++ b/InfoTree.cpp @@ -0,0 +1,131 @@ +#include "InfoTree.h" + +namespace mvc { + InfoTree::~InfoTree() { + clear(root_); + } + + std::pair InfoTree::findValue(int x, int y) { + if (this == nullptr) + return {0, false}; + std::queue que; + que.push(root_); + Info *cur; + while (!que.empty()) { + cur = que.front(); + que.pop(); + if ((x > cur->x - kRadius_ && x < cur->x + kRadius_) && (y > cur->y - kRadius_ && y < cur->y + kRadius_)) + return {cur->key, true}; + if (cur->left != nullptr) + que.push(cur->left); + if (cur->right != nullptr) + que.push(cur->right); + } + return {0, false}; + } + + void InfoTree::clear(Info *treeInfo) { + if (treeInfo != nullptr) { + clear(treeInfo->left); + clear(treeInfo->right); + delete treeInfo; + } + } + + void InfoTree::setWidth(Info *cur) { + if (cur == nullptr) + return; + setWidth(cur->left); + setWidth(cur->right); + + if (cur->right == nullptr && cur->left == nullptr) + cur->width = 2 * kRadius_; + else if (cur->left == nullptr) + cur->width = 2 * cur->right->width + kWidth_; + else if (cur->right == nullptr) + cur->width = 2 * cur->left->width + kWidth_; + else + cur->width = 2 * std::max(cur->left->width, cur->right->width) + kWidth_; + } + + void InfoTree::setXCoord(Info *cur, std::queue &que, int &count, int ×Next, int ×Now) { + if (cur->left != nullptr) { + que.push(cur->left); + ++timesNext; + cur->left->x = cur->x - kWidth_ / 2 - cur->left->width / 2; + } + if (cur->right != nullptr) { + que.push(cur->right); + ++timesNext; + cur->right->x = cur->x + kWidth_ / 2 + cur->right->width / 2; + } + --timesNow; + if (timesNow == 0) { + ++count; + timesNow = timesNext; + timesNext = 0; + } + } + + void InfoTree::calcXCoord() { + Info *coord = root_; + setWidth(coord); + Info *root = root_; + if (root == nullptr) + return; + std::queue que; + que.push(root); + Info *cur; + int count = 0, timesNow = 1, timesNext = 0; + while (!que.empty()) { + cur = que.front(); + que.pop(); + setXCoord(cur, que, count, timesNext, timesNow); + } + } + + Info *InfoTree::buildInfoTree(const Node *node) { + if (node == nullptr) + return nullptr; + else { + Info *temp = new Info; + temp->key = node->key; + temp->left = buildInfoTree(node->leftCh); + temp->right = buildInfoTree(node->rightCh); + return temp; + } + } + + void InfoTree::setYCoord(Info *cur, std::queue &que, int &count, int ×Next, int ×Now) { + cur->y = count * (2 * kRadius_ + kHeight_); + if (cur->left != nullptr) { + que.push(cur->left); + ++timesNext; + } + if (cur->right != nullptr) { + que.push(cur->right); + ++timesNext; + } + --timesNow; + if (timesNow == 0) { + ++count; + timesNow = timesNext; + timesNext = 0; + } + } + + void InfoTree::calcYCoord() { + Info *root = root_; + if (root == nullptr) + return; + std::queue que; + que.push(root); + Info *cur; + int count = 0, timesNow = 1, timesNext = 0; + while (!que.empty()) { + cur = que.front(); + que.pop(); + setYCoord(cur, que, count, timesNext, timesNow); + } + } +}// namespace mvc diff --git a/InfoTree.h b/InfoTree.h new file mode 100644 index 0000000..29a38f2 --- /dev/null +++ b/InfoTree.h @@ -0,0 +1,49 @@ +#ifndef COURSEPROJECT_INFOTREE_H +#define COURSEPROJECT_INFOTREE_H + +#include + +#include "Model.h" + +namespace mvc { + + struct Info { + int x = 0; + int y = 0; + int key; + Info *left; + Info *right; + int width; + }; + + class InfoTree { + public: + InfoTree(const Node *rootGet) + : root_(buildInfoTree(rootGet)) { + calcYCoord(); + calcXCoord(); + } + ~InfoTree(); + Info *getRoot() { return root_; } + std::pair findValue(int x, int y); + void clear(Info *treeInfo); + + private: + static constexpr int kRadius_ = 40; + static constexpr int kHeight_ = 30; + static constexpr int kWidth_ = 15; + + private: + void setWidth(Info *cur); + void setXCoord(Info *cur, std::queue &que, int &count, int ×Next, int ×Now); + void calcXCoord(); + Info *buildInfoTree(const Node *node); + void setYCoord(Info *cur, std::queue &que, int &count, int ×Next, int ×Now); + void calcYCoord(); + + Info *root_ = nullptr; + }; + +}// namespace mvc + +#endif//COURSEPROJECT_INFOTREE_H diff --git a/Model.cpp b/Model.cpp new file mode 100644 index 0000000..7d74cb7 --- /dev/null +++ b/Model.cpp @@ -0,0 +1,230 @@ +#include "Model.h" + +namespace mvc { + + AVLTree::~AVLTree() { + clearHelp(root_); + } + + void AVLTree::insert(int key) { + operation_ = Add; + message_ = Fine; + root_ = insert(root_, key); + if (root_ == nullptr) + return; + port_.notify(); + } + + void AVLTree::deleteNode(int key) { + operation_ = Delete; + message_ = Fine; + root_ = deleteNode(root_, key); + port_.notify(); + } + + void AVLTree::search(int key) { + operation_ = Search; + message_ = Fine; + search(root_, key, message_); + if (message_ == 2) + operation_ = Add; + port_.notify(); + } + + void AVLTree::deleteAll() { + clearHelp(root_); + operation_ = DeleteAll; + message_ = Fine; + port_.notify(); + } + + void AVLTree::clearHelp(Node *&node) { + if (node != nullptr) { + clearHelp(node->leftCh); + clearHelp(node->rightCh); + delete node; + node = nullptr; + } + } + + Node *AVLTree::leftRotate(Node *node) { + Node *rCh = node->rightCh; + Node *lGrCh = node->rightCh->leftCh; + node->rightCh->leftCh = node; + node->rightCh = lGrCh; + node->height = findMaxHeight(node->leftCh, node->rightCh) + 1; + rCh->height = findMaxHeight(rCh->leftCh, rCh->rightCh) + 1; + return rCh; + } + + Node *AVLTree::rightRotate(Node *node) { + Node *lCh = node->leftCh; + Node *rGrCh = node->leftCh->rightCh; + node->leftCh->rightCh = node; + node->leftCh = rGrCh; + node->height = findMaxHeight(node->leftCh, node->rightCh) + 1; + lCh->height = findMaxHeight(lCh->leftCh, lCh->rightCh) + 1; + return lCh; + } + + int AVLTree::findBalanceFactor(Node *node) { + int l = 0, r = 0; + if (node == nullptr) + return 0; + if (node->leftCh != nullptr) + l = node->leftCh->height; + if (node->rightCh != nullptr) + r = node->rightCh->height; + return l - r; + } + + int AVLTree::getHeight(const Node *node) { + if (node == nullptr) + return 0; + return node->height; + } + + int AVLTree::findMaxHeight(const Node *left, const Node *right) { + return std::max(getHeight(left), getHeight(right)); + } + + Node *AVLTree::balanceInsert(Node *node, int key) { + node->height = findMaxHeight(node->leftCh, node->rightCh) + 1; + int balanceFactor = findBalanceFactor(node); + + if (balanceFactor > 1 && key < node->leftCh->key) { + port_.notify(); + return rightRotate(node); + } + if (balanceFactor > 1 && key > node->leftCh->key) { + port_.notify(); + node->leftCh = leftRotate(node->leftCh); + port_.notify(); + return rightRotate(node); + } + if (balanceFactor < -1 && key > node->rightCh->key) { + port_.notify(); + return leftRotate(node); + } + if (balanceFactor < -1 && key < node->rightCh->key) { + port_.notify(); + node->rightCh = rightRotate(node->rightCh); + port_.notify(); + return leftRotate(node); + } + return node; + } + + Node *AVLTree::insert(Node *node, int key) { + if (node == nullptr) { + message_ = Fine; + return new Node{key}; + } + if (key < node->key) + node->leftCh = insert(node->leftCh, key); + else if (key > node->key) + node->rightCh = insert(node->rightCh, key); + else { + message_ = AlreadyPresent; + return node; + } + return balanceInsert(node, key); + } + + Node *AVLTree::balanceDelete(Node *node) { + if (node == nullptr) + return node; + node->height = findMaxHeight(node->rightCh, node->leftCh) + 1; + int balanceFactor = findBalanceFactor(node); + if (balanceFactor > 1 && findBalanceFactor(node->leftCh) >= 0) { + port_.notify(); + return rightRotate(node); + } + if (balanceFactor > 1 && findBalanceFactor(node->leftCh) < 0) { + port_.notify(); + node->leftCh = leftRotate(node->leftCh); + port_.notify(); + return rightRotate(node); + } + if (balanceFactor < -1 && findBalanceFactor(node->rightCh) < 0) { + port_.notify(); + return leftRotate(node); + } + if (balanceFactor < -1 && findBalanceFactor(node->rightCh) >= 0) { + port_.notify(); + node->rightCh = rightRotate(node->rightCh); + port_.notify(); + return leftRotate(node); + } + return node; + } + + Node *AVLTree::inorderSuccessor(Node *x) { + Node *cur = x->rightCh; + while (cur->leftCh != nullptr) + cur = cur->leftCh; + return cur; + } + + void AVLTree::deleteNodeTwoChildren(Node *node) { + Node *cur = AVLTree::inorderSuccessor(node); + node->key = cur->key; + port_.notify(); + node->rightCh = deleteNode(node->rightCh, cur->key); + } + + Node *AVLTree::deleteNodeOneZeroChildren(Node *node) { + Node *cur = nullptr; + if (node->leftCh) + cur = node->leftCh; + if (node->rightCh) + cur = node->rightCh; + if (cur == nullptr) { + cur = node; + node = nullptr; + } else + *node = *cur; + port_.notify(); + delete cur; + return node; + } + + Node *AVLTree::deleteNode(Node *node, int key) { + if (node == nullptr) { + message_ = NotPresent; + return node; + } + if (key < node->key) + node->leftCh = deleteNode(node->leftCh, key); + else if (key > node->key) + node->rightCh = deleteNode(node->rightCh, key); + else { + message_ = Fine; + if (node->leftCh == nullptr || node->rightCh == nullptr) + node = deleteNodeOneZeroChildren(node); + else + deleteNodeTwoChildren(node); + } + return balanceDelete(node); + } + + void AVLTree::search(Node *node, int key, Message &message) { + while (node != nullptr) { + if (node->key < key) { + passing_ = node->key; + port_.notify(); + node = node->rightCh; + } else if (node->key > key) { + passing_ = node->key; + port_.notify(); + node = node->leftCh; + } else { + passing_ = key; + port_.notify(); + return; + } + } + message = NotPresent; + } + +}// namespace mvc diff --git a/Model.h b/Model.h new file mode 100644 index 0000000..2a0711e --- /dev/null +++ b/Model.h @@ -0,0 +1,91 @@ +#ifndef COURSEPROJECT_MODEL_H +#define COURSEPROJECT_MODEL_H + +#include +#include + +#include "Observer/Observer.h" + +namespace mvc { + struct Node { + int key; + int height = 1; + Node *leftCh = nullptr; + Node *rightCh = nullptr; + }; + + class AVLTree { + public: + AVLTree() + : root_(nullptr) {} + ~AVLTree(); + + AVLTree(const AVLTree &) = delete; + AVLTree(AVLTree &&) noexcept = delete; + AVLTree &operator=(const AVLTree &) = delete; + AVLTree &operator=(AVLTree &&) noexcept = delete; + + void insert(int key); + void deleteNode(int key); + void search(int key); + void deleteAll(); + + public: + enum Operation { + Add, + Delete, + Search, + Traversal, + DeleteAll + }; + enum Message { + Fine, + NotPresent, + AlreadyPresent + }; + struct Data { + Node *&value; + Message &message; + Operation &operation; + int &passing_; + }; + + private: + void clearHelp(Node *&treeptr); + + Node *leftRotate(Node *node); + Node *rightRotate(Node *node); + int findBalanceFactor(Node *node); + static int getHeight(const Node *node); + int findMaxHeight(const Node *left, const Node *right); + Node *balanceInsert(Node *node, int key); + Node *insert(Node *node, int key); + + Node *balanceDelete(Node *node); + Node *inorderSuccessor(Node *x); + void deleteNodeTwoChildren(Node *node); + Node *deleteNodeOneZeroChildren(Node *node); + Node *deleteNode(Node *node, int key); + + void search(Node *node, int key, Message &message); + + private: + using Observer = NSLibrary::CObserver; + using Observable = NSLibrary::CObservableData; + + public: + void subscribe(Observer *observer) { + assert(observer); + port_.subscribe(observer); + } + + private: + Node *root_ = nullptr; + Message message_; + Operation operation_; + int passing_; + Observable port_ = Data{root_, message_, operation_, passing_}; + }; +}// namespace mvc + +#endif//COURSEPROJECT_MODEL_H diff --git a/Observer/Impl/Observable.h b/Observer/Impl/Observable.h new file mode 100644 index 0000000..ae90ec2 --- /dev/null +++ b/Observer/Impl/Observable.h @@ -0,0 +1,220 @@ +#ifndef IMPL_OBSERVABLE_H +#define IMPL_OBSERVABLE_H + +#include "Source.h" + +#include + +namespace NSLibrary { + +namespace NSObserverDetail { +template +class CObserver; +} + +template +using CObserver = NSObserverDetail::CObserver; + +namespace NSObservableDetail { + +template +class CObservableBase { +public: + using CData = TData; + using CObserverContainer = std::list*>; + + CObservableBase() = default; + CObservableBase(const CObservableBase&) = delete; + CObservableBase(CObservableBase&&) noexcept = delete; + CObservableBase& operator=(const CObservableBase&) = delete; + CObservableBase& operator=(CObservableBase&&) noexcept = delete; + + ~CObservableBase() { + unsubscribeAll(); + } + + void notify() { + for (CObserver* Observer : Observers_) + notifyOne(Observer); + } + +protected: + class CUnsubscriber { + public: + CUnsubscriber() = default; + CUnsubscriber(CObserver* Observer, CObserverContainer* Observers) + : Observer_(Observer), Observers_(Observers) { + assert(Observer_); + assert(Observers_); + } + + void unsubscribe(); + + bool isSubscribed() const { + return Observers_ != nullptr; + } + + private: + CObserver* Observer_ = nullptr; + CObserverContainer* Observers_ = nullptr; + }; + + CUnsubscriber makeUnsubscriber(CObserver* Observer) { + assert(Observer); + return CUnsubscriber(Observer, &Observers_); + } + + CObserverContainer* observers() { + return &Observers_; + } + +private: + void notifyOne(CObserver* Observer); + void unsubscribeAll(); + + CObserverContainer Observers_; +}; + +template +class CConnectorImpl : public TBase { + using CBase = TBase; + using CBase::CBase; + using CGetAction = typename CSource::CGetAction; + +public: + class CConnection { + public: + using CUnsubscriber = typename CBase::CUnsubscriber; + using CGetType = typename CSource::CGetType; + using CGetAction = typename CSource::CGetAction; + + CConnection() = default; + CConnection(CUnsubscriber Unsubscriber, CSource* Source) + : Unsubscriber_(std::move(Unsubscriber)), Source_(Source) { + assert(Unsubscriber_.isSubscribed()); + assert(Source_); + } + + bool isSubscribed() const { + return Unsubscriber_.isSubscribed(); + } + + void unsubscribe() { + if (!isSubscribed()) + return; + Unsubscriber_.unsubscribe(); + Source_ = nullptr; + } + + CGetAction Getter() const { + if (Source_ == nullptr) + return CSource::getNothing; + assert(Source_); + return Source_->Getter(); + } + + CGetType get() const { + if (Source_ == nullptr) + return CSource::getNothing(); + assert(Source_); + return Source_->get(); + } + + bool hasValue() const { + if (Source_ == nullptr) + return false; + assert(Source_); + return Source_->hasValue(); + } + + private: + CUnsubscriber Unsubscriber_{}; + CSource* Source_ = nullptr; + }; + + CConnectorImpl() = default; + CConnectorImpl(CGetAction Action) : Source_(std::move(Action)) { + assert(Source_.hasGetter()); + } + + void setSource(CGetAction Action) { + assert(Action); + Source_.set(std::move(Action)); + CBase::notify(); + } + +protected: + CConnection makeConnection(CObserver* Observer) { + assert(Observer); + return CConnection(CBase::makeUnsubscriber(Observer), &Source_); + } + +private: + CSource Source_{}; +}; + +template +class CConnectorImpl : public TBase { + using CBase = TBase; + using CBase::CBase; + +public: + class CConnection { + public: + using CUnsubscriber = typename CBase::CUnsubscriber; + using CGetType = void; + using CGetAction = void; + + CConnection() = default; + CConnection(CUnsubscriber Unsubscriber) : Unsubscriber_(Unsubscriber) { + assert(Unsubscriber_.isSubscribed()); + } + + bool isSubscribed() const { + return Unsubscriber_.isSubscribed(); + } + + void unsubscribe() { + if (!isSubscribed()) + return; + Unsubscriber_.unsubscribe(); + } + + private: + CUnsubscriber Unsubscriber_{}; + }; + +protected: + CConnection makeConnection(CObserver* Observer) { + assert(Observer); + return CConnection(CBase::makeUnsubscriber(Observer)); + } +}; + +template +using CConnector = CConnectorImpl; + +template +class CSubscriberImpl : public TBase { + using CBase = TBase; + using CBase::CBase; + using CData = typename CBase::CData; + +public: + void subscribe(CObserver* Observer); +}; + +template +using CObservableSubscriber = CSubscriberImpl; + +template +using CObservable = CObservableSubscriber>>; + +} // namespace NSObservableDetail + +template +using CObservable = NSObservableDetail::CObservable; + +} // namespace NSLibrary + +#endif // IMPL_OBSERVABLE_H diff --git a/Observer/Impl/ObservableImpl.h b/Observer/Impl/ObservableImpl.h new file mode 100644 index 0000000..4fe9534 --- /dev/null +++ b/Observer/Impl/ObservableImpl.h @@ -0,0 +1,49 @@ +#ifndef IMPL_OBSERVABLEIMPL_H +#define IMPL_OBSERVABLEIMPL_H + +#include "Observable.h" +#include "Observer.h" + +namespace NSLibrary { + +namespace NSObservableDetail { +template +void CObservableBase::notifyOne(CObserver* Observer) { + assert(Observer); + Observer->onNotify(); +} + +template +void CObservableBase::unsubscribeAll() { + while (!Observers_.empty()) { + assert(Observers_.front()); + Observers_.front()->unsubscribe(); + } +} + +template +void CObservableBase::CUnsubscriber::unsubscribe() { + if (!isSubscribed()) + return; + assert(Observer_); + Observer_->onUnsubscribe(); + assert(Observers_); + Observers_->remove(Observer_); + Observers_ = nullptr; + Observer_ = nullptr; +} + +template +void CSubscriberImpl::subscribe(CObserver* Observer) { + assert(Observer); + if (Observer->isSubscribed()) + Observer->unsubscribe(); + CBase::observers()->push_back(Observer); + Observer->_setConnection(CBase::makeConnection(Observer)); + Observer->onSubscribe(); +} + +} // namespace NSObservableDetail +} // namespace NSLibrary + +#endif // IMPL_OBSERVABLEIMPL_H diff --git a/Observer/Impl/Observer.h b/Observer/Impl/Observer.h new file mode 100644 index 0000000..b8898e8 --- /dev/null +++ b/Observer/Impl/Observer.h @@ -0,0 +1,197 @@ +#ifndef IMPL_OBSERVER_H +#define IMPL_OBSERVER_H + +#include "Observable.h" + +#include + +namespace NSLibrary { +namespace NSObserverDetail { + +template +class CObserverBase { +public: + using CConnection = typename CObservable::CConnection; + using CGetType = typename CConnection::CGetType; + using CGetAction = typename CConnection::CGetAction; + using CData = TData; + + CObserverBase() = default; + CObserverBase(const CObserverBase&) = delete; + CObserverBase(CObserverBase&&) noexcept = delete; + CObserverBase& operator=(const CObserverBase&) = delete; + CObserverBase& operator=(CObserverBase&&) noexcept = delete; + + bool isSubscribed() const { + return Connection_.isSubscribed(); + } + + void _setConnection(CConnection Connection) { + assert(Connection.isSubscribed()); + Connection_ = std::move(Connection); + } + + bool hasValue() const { + return Connection_.hasValue(); + } + + CGetType data() const { + return Connection_.get(); + } + + CGetAction Getter() const { + return Connection_.Getter(); + } + +protected: + CConnection& Connection() { + return Connection_; + } + +private: + CConnection Connection_{}; +}; + +template +class CObserverReactorImpl : public TBase { + using CArgType = typename TBase::CGetType; + using CBase = TBase; + +public: + using CData = TData; + using CMethod = std::function; + + CObserverReactorImpl(CMethod OnSubscribe, CMethod OnNotify, + CMethod OnUnsubscribe) + : OnSubscribe_(std::move(OnSubscribe)), OnNotify_(std::move(OnNotify)), + OnUnsubscribe_(std::move(OnUnsubscribe)) { + assert(OnSubscribe_); + assert(OnNotify_); + assert(OnUnsubscribe_); + } + + void onSubscribe() { + if (CBase::isSubscribed()) { + assert(OnSubscribe_); + OnSubscribe_(CBase::data()); + } + } + + void onNotify() { + if (CBase::isSubscribed()) { + assert(OnNotify_); + OnNotify_(CBase::data()); + } + } + + void onUnsubscribe() { + if (CBase::isSubscribed()) { + assert(OnUnsubscribe_); + OnUnsubscribe_(CBase::data()); + } + } + + static void doNothing(CArgType) { + } + +protected: + CMethod OnSubscribe_; + CMethod OnNotify_; + CMethod OnUnsubscribe_; +}; + +template +class CObserverReactorImpl : public TBase { + using CBase = TBase; + +public: + using CData = void; + using CMethod = std::function; + + CObserverReactorImpl(CMethod OnSubscribe, CMethod OnNotify, + CMethod OnUnsubscribe) + : OnSubscribe_(std::move(OnSubscribe)), OnNotify_(std::move(OnNotify)), + OnUnsubscribe_(std::move(OnUnsubscribe)) { + assert(OnSubscribe_); + assert(OnNotify_); + assert(OnUnsubscribe_); + } + + void onSubscribe() { + if (CBase::isSubscribed()) { + assert(OnSubscribe_); + OnSubscribe_(); + } + } + + void onNotify() { + if (CBase::isSubscribed()) { + assert(OnNotify_); + OnNotify_(); + } + } + + void onUnsubscribe() { + if (CBase::isSubscribed()) { + assert(OnUnsubscribe_); + OnUnsubscribe_(); + } + } + + static void doNothing() { + } + +protected: + CMethod OnSubscribe_; + CMethod OnNotify_; + CMethod OnUnsubscribe_; +}; + +template +using CObserverReactor = CObserverReactorImpl; + +template +class CObserver : public CObserverReactor> { + using CBase = CObserverReactor>; + +public: + using CData = TData; + using CMethod = typename CBase::CMethod; + + template + CObserver(T1&& OnSubscribe, T2&& OnNotify, T3&& OnUnsubscribe) + : CBase(std::forward(OnSubscribe), std::forward(OnNotify), + std::forward(OnUnsubscribe)) { + } + + ~CObserver() { + unsubscribe(); + } + + void unsubscribe() { + CBase::Connection().unsubscribe(); + } + + void setSubscribe(CMethod OnSubscribe) { + assert(OnSubscribe); + CBase::OnSubscribe_ = std::move(OnSubscribe); + } + + void setNotify(CMethod OnNotify) { + assert(OnNotify); + CBase::OnNotify_ = std::move(OnNotify); + } + + void setUnsubscribe(CMethod OnUnsubscribe) { + assert(OnUnsubscribe); + CBase::OnUnsubscribe_ = std::move(OnUnsubscribe); + } +}; +} // namespace NSObserverDetail + +template +using CObserver = NSObserverDetail::CObserver; + +} // namespace NSLibrary + +#endif // IMPL_OBSERVER_H diff --git a/Observer/Impl/Source.h b/Observer/Impl/Source.h new file mode 100644 index 0000000..28f2531 --- /dev/null +++ b/Observer/Impl/Source.h @@ -0,0 +1,73 @@ +#ifndef IMPL_SOURCE_H +#define IMPL_SOURCE_H + +#include "TypeHelper.h" + +#include +#include +#include + +namespace NSLibrary { + +template> +class CSource { +public: + static bool constexpr isPassedByValue = NSType::isArithmetic || + NSType::isPointer || + NSType::isEnum; + using CReturnValueType = + std::conditional_t>; + using CGetType = std::optional; + using CGetSignature = CGetType(); + using CGetAction = std::function; + + CSource() = default; + CSource(CGetAction Action) : GetAction_(std::move(Action)) { + assert(hasGetter()); + } + + bool hasGetter() const { + return static_cast(GetAction_); + } + + CGetType operator()() const { + return get(); + } + + void set(CGetAction Action) { + assert(Action); + GetAction_ = std::move(Action); + } + + CGetType get() const { + if (!hasGetter()) + return getNothing(); + assert(hasGetter()); + return GetAction_(); + } + + CGetAction Getter() const { + if (!hasGetter()) + return getNothing; + assert(hasGetter()); + return GetAction_; + } + + bool hasValue() const { + if (!hasGetter()) + return false; + assert(hasGetter()); + return GetAction_().has_value(); + } + + static CGetType getNothing() { + return CGetType(); + } + +private: + CGetAction GetAction_ = getNothing; +}; + +} // namespace NSLibrary + +#endif // IMPL_SOURCE_H diff --git a/Observer/Impl/TypeHelper.h b/Observer/Impl/TypeHelper.h new file mode 100644 index 0000000..d12f9f1 --- /dev/null +++ b/Observer/Impl/TypeHelper.h @@ -0,0 +1,33 @@ +#ifndef IMPL_TYPEHELPER_H +#define IMPL_TYPEHELPER_H + +#include + +namespace NSLibrary { +namespace NSType { + +template +class TD; + +template +bool constexpr isArithmetic = std::is_arithmetic_v; + +template +bool constexpr isPointer = std::is_pointer_v; + +template +bool constexpr isEnum = std::is_enum_v; + +template +using EnableIfNotRef = std::enable_if_t>; + +template +using ConstRef = std::add_lvalue_reference_t>; + +template> +using ConstRefWrapp = std::reference_wrapper>; + +} // namespace NSType +} // namespace NSLibrary + +#endif // IMPL_TYPEHELPER_H diff --git a/Observer/Observer.h b/Observer/Observer.h new file mode 100644 index 0000000..5ef703d --- /dev/null +++ b/Observer/Observer.h @@ -0,0 +1,235 @@ +#ifndef OBSERVER_H +#define OBSERVER_H + +#include "Impl/ObservableImpl.h" + +namespace NSLibrary { +namespace NSObservableDataDetail { + +template +class CStorage { + using CDataOptional = std::optional; + +public: + template + CStorage(TArgs&&... args) : Data_(std::forward(args)...) { + } + +protected: + template + void set(TArgs&&... args) { + Data_.emplace(std::forward(args)...); + } + + CDataOptional Data_{}; +}; + +template +class CObservableData : protected CStorage, + protected CObservable { + using CStorageBase = CStorage; + using CObservableBase = CObservable; + +public: + template + CObservableData(TArgs&&... args) + : CStorageBase(std::forward(args)...), + CObservableBase([&Data = CStorageBase::Data_]() -> + typename CSource::CGetType { return Data; }) { + } + + template + void set(TArgs&&... args) { + CStorageBase::set(std::forward(args)...); + CObservableBase::notify(); + } + + using CObservableBase::notify; + using CObservableBase::subscribe; +}; +} // namespace NSObservableDataDetail + +template +using CObservableData = NSObservableDataDetail::CObservableData; + +using CNotifier = CObservable; + +template +class CObserverStrict : public CObserver { + using CBase = CObserver; + +public: + template + CObserverStrict(T1&& OnSubscribe, T2&& OnNotify, T3&& OnUnsubscribe) + : CBase( + [OnSubscribe](typename CBase::CGetType optData) { + if (optData.has_value()) + OnSubscribe(*optData); + }, + [OnNotify](typename CBase::CGetType optData) { + if (optData.has_value()) + OnNotify(*optData); + }, + [OnUnsubscribe](typename CBase::CGetType optData) { + if (optData.has_value()) + OnUnsubscribe(*optData); + }) { + } +}; + +template<> +class CObserverStrict : public CObserver { + using CBase = CObserver; + +public: + using CBase::CBase; +}; + +template +class CObserverHot : public CObserver { + using CBase = CObserver; + +public: + template + CObserverHot(T1&& OnSubscribe, T2&& OnNotify) + : CBase(std::forward(OnSubscribe), std::forward(OnNotify), + CBase::doNothing) { + } +}; + +template +class CObserverCold : public CObserver { + using CBase = CObserver; + +public: + template + CObserverCold(T&& OnNotify) + : CBase(CBase::doNothing, std::forward(OnNotify), CBase::doNothing) { + } +}; + +template +class CObserverHotStrict : public CObserverHot { + using CBase = CObserverHot; + +public: + template + CObserverHotStrict(T1&& OnSubscribe, T2&& OnNotify) + : CBase( + [OnSubscribe](typename CBase::CGetType optData) { + if (optData.has_value()) + OnSubscribe(*optData); + }, + [OnNotify](typename CBase::CGetType optData) { + if (optData.has_value()) + OnNotify(*optData); + }) { + } +}; + +template +class CHotInput : public CObserverHotStrict { + using CBase = CObserverHotStrict; + +public: + template + CHotInput(T Action) : CBase(Action, Action) { + } +}; + +template +class CObserverColdStrict : public CObserverCold { + using CBase = CObserverCold; + +public: + template + CObserverColdStrict(T&& OnNotify) + : CBase([OnNotify](typename CBase::CGetType optData) { + if (optData.has_value()) + OnNotify(*optData); + }) { + } +}; + +template +using CColdInput = CObserverColdStrict; + +template +class CHotActiveInput : public CObserver { + using CBase = CObserver; + +public: + template + CHotActiveInput(T Action) + : CBase( + [Action, this](typename CBase::CGetType optData) { + if (optData.has_value()) { + activate(); + Action(*optData); + } + }, + [Action, this](typename CBase::CGetType optData) { + if (optData.has_value()) { + activate(); + Action(*optData); + } + }, + [this](typename CBase::CGetType) { deactivate(); }) { + } + + bool isActive() const { + assert(!isActive_ || CBase::hasValue()); + return isActive_; + } + + void deactivate() { + isActive_ = false; + } + +private: + void activate() { + isActive_ = true; + } + + bool isActive_ = false; +}; + +template +class CColdActiveInput : public CObserver { + using CBase = CObserver; + +public: + template + CColdActiveInput(T Action) + : CBase( + CObserver::doNothing, + [Action, this](typename CBase::CGetType optData) { + if (optData.has_value()) { + activate(); + Action(*optData); + } + }, + [this](typename CBase::CGetType) { deactivate(); }) { + } + + bool isActive() const { + assert(!isActive_ || CBase::hasValue()); + return isActive_; + } + + void deactivate() { + isActive_ = false; + } + +private: + void activate() { + isActive_ = true; + assert(CBase::hasValue()); + } + + bool isActive_ = false; +}; + +} // namespace NSLibrary + +#endif // OBSERVER_H diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec3aaf9 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# AVL Tree +This repository contains step-by-step AVL Tree visualising application written in C++ language. Project itself is Qt based, written in Clion. +It has diverse functionality, which involves not only insertion, deletion and search operations, but also allows to study traversal types. +What is more, to improve user's experience step back option and change of speed possibility are included.For those, who do not want to insert nodes one by one, possibility to download the whole tree from .txt file was included.\ +In the following picture interace of the application is shown:\ +\ +![Screenshot](interface.png) +# Project implementation +Implementation of application is based on MVC and observer pattern, where observable and observer are fields in a model class and view class, respectively. This was made on purpose to avoid a lot of inheritance. Interchange of data is established in the following way: Controller receives information from the View object and calls respective methods of model (addition, deletion or search). These methods change state of the model and notify about that view through observer and observable fields. Via these fields data is transferred from subject to subscribed observer, which draws updated AVL Tree. +# Build instructions +To build program user has to change options in CMakeLists.txt, so that they suit configurations of Qt application. Inside program, in file View.cpp inside constructor appropriate link to stylesheet.qss file should be provided (otherwise, colour of the buttons and slider will be lost). diff --git a/View.cpp b/View.cpp new file mode 100644 index 0000000..b94e6a4 --- /dev/null +++ b/View.cpp @@ -0,0 +1,369 @@ +#include "View.h" + +namespace mvc { + + View::View() { + mainWindow_ = new MainWindow(); + addMenu(); + addControlPannel(); + adjustMainWindow(); + connectObjects(); + mainWindow_->setWindowTitle("AVL tree"); + + QFile file("/Users/mayyaspirina/Downloads/pumpum-addTree/stylesheet.qss"); + file.open(QFile::ReadOnly); + QString styleSheet = QLatin1String(file.readAll()); + mainWindow_->setStyleSheet(styleSheet); + + mainWindow_->show(); + } + + void View::addMenu() { + QMenuBar *menu = new QMenuBar(mainWindow_); + QMenu *about = new QMenu("About", mainWindow_); + QMenu *load = new QMenu("Load", mainWindow_); + QAction *act = new QAction(about); + QAction *actLoad = new QAction(load); + act->setText("Info"); + actLoad->setText("Load"); + about->addAction(act); + about->addAction(actLoad); + menu->addMenu(about); + QObject::connect(act, &QAction::triggered, this, &View::aboutMenu); + QObject::connect(actLoad, &QAction::triggered, this, &View::loadMenu); + } + + void View::addControlPannel() { + addButton_ = new QPushButton("Add node", mainWindow_); + deleteButton_ = new QPushButton("Delete node", mainWindow_); + searchButton_ = new QPushButton("Search node", mainWindow_); + + inOrderButton_ = new QPushButton("Inorder traversal", mainWindow_); + preOrderButton_ = new QPushButton("Preorder traversal", mainWindow_); + postOrderButton_ = new QPushButton("Postorder traversal", mainWindow_); + + stepBackButton_ = new QPushButton("Step back", mainWindow_); + + QValidator *validator = new QIntValidator(mainWindow_); + editText_ = new QLineEdit(mainWindow_); + editText_->setValidator(validator); + + treeSpot_ = new QGraphicsView(mainWindow_); + scene_ = new CustomScene(this); + scene_->setBackgroundBrush(Qt::white); + treeSpot_->setScene(scene_); + + slider_ = new QSlider(Qt::Horizontal, mainWindow_); + slider_->setTickInterval(50); + slider_->setMinimum(250); + slider_->setMaximum(1250); + } + + void View::adjustMainWindow() { + QHBoxLayout *layout = new QHBoxLayout(mainWindow_); + + QGroupBox *box = new QGroupBox(mainWindow_); + QVBoxLayout *buttonLayout = new QVBoxLayout(box); + layout->addWidget(treeSpot_, 75); + layout->addWidget(box, 25); + buttonLayout->setSpacing(15); + buttonLayout->setAlignment(Qt::AlignTop); + + QLabel *header1 = new QLabel(mainWindow_); + header1->setText(tr("Type integer:")); + buttonLayout->addWidget(header1); + buttonLayout->addWidget(editText_); + buttonLayout->addWidget(addButton_); + buttonLayout->addWidget(deleteButton_); + buttonLayout->addWidget(searchButton_); + + QSpacerItem *spacer = new QSpacerItem(2, 70); + buttonLayout->addSpacerItem(spacer); + QLabel *header2 = new QLabel(mainWindow_); + header2->setText(tr("Tree traversals:")); + buttonLayout->addWidget(header2); + buttonLayout->addWidget(inOrderButton_); + buttonLayout->addWidget(preOrderButton_); + buttonLayout->addWidget(postOrderButton_); + + buttonLayout->addSpacerItem(spacer); + QLabel *header4 = new QLabel(mainWindow_); + header4->setText(tr("Speed:")); + buttonLayout->addWidget(header4); + buttonLayout->addWidget(slider_); + buttonLayout->addWidget(stepBackButton_); + + buttonLayout->addSpacerItem(spacer); + QLabel *header3 = new QLabel(mainWindow_); + header3->setText(tr("Node count:")); + buttonLayout->addWidget(header3); + count_ = new QLineEdit(mainWindow_); + count_->setReadOnly(true); + count_->setFixedSize(50, 50); + count_->setAlignment(Qt::AlignCenter); + count_->setText("0"); + buttonLayout->addWidget(count_); + } + + void View::connectObjects() { + QObject::connect(addButton_, SIGNAL(clicked()), this, SLOT(addNode())); + QObject::connect(deleteButton_, SIGNAL(clicked(bool)), this, SLOT(deleteNode())); + QObject::connect(searchButton_, SIGNAL(clicked(bool)), this, SLOT(searchNode())); + QObject::connect(inOrderButton_, SIGNAL(clicked()), this, SLOT(inOrder())); + QObject::connect(preOrderButton_, SIGNAL(clicked(bool)), this, SLOT(preOrder())); + QObject::connect(postOrderButton_, SIGNAL(clicked(bool)), this, SLOT(postOrder())); + QObject::connect(stepBackButton_, SIGNAL(clicked(bool)), this, SLOT(stepBack())); + } + + void View::drawNode(Info *cur, std::queue &que, int &count, AVLTree::Operation &operation, int passing) { + QBrush cyanbrush(Qt::darkCyan); + QPen blackpen(Qt::black); + blackpen.setWidth(1); + QPen redpen(Qt::red); + redpen.setWidth(3); + + if (operation == AVLTree::Search && cur->key == passing || operation == AVLTree::Traversal && cur->key == passing) + scene_->addEllipse(cur->x, cur->y, kRadius_, kRadius_, redpen, cyanbrush); + else + scene_->addEllipse(cur->x, cur->y, kRadius_, kRadius_, blackpen, cyanbrush); + + QGraphicsTextItem *text = scene_->addText(QString::number(cur->key)); + text->setPos(cur->x + 10, cur->y + 10); + + if (cur->left != nullptr) { + que.push(cur->left); + scene_->addLine(cur->x + 10, cur->y + 37, cur->left->x + 29, cur->left->y + 3); + ++count; + } + if (cur->right != nullptr) { + que.push(cur->right); + scene_->addLine(cur->x + 31, cur->y + 37, cur->right->x + 13, cur->right->y + 3); + ++count; + } + } + + void View::drawTree(Info *treeInfo, AVLTree::Operation &operation, int passing) { + scene_->clear(); + scene_->setBackgroundBrush(Qt::white); + Info *root = treeInfo; + if (root == nullptr) + return; + std::queue que; + que.push(root); + Info *cur; + int count = 1; + while (!que.empty()) { + cur = que.front(); + que.pop(); + drawNode(cur, que, count, operation, passing); + } + count_->setText(QString::number(count)); + } + + void delay(int millisecondsWait) { + QTimer t; + QEventLoop loop; + t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit); + t.start(millisecondsWait); + loop.exec(); + } + + void View::draw(const AVLTree::Data &data) { + if (data.operation == AVLTree::DeleteAll) { + scene_->clear(); + treeInfo_.release(); + count_->setText("0"); + } + if (!hist_) { + switch (data.message) { + case AVLTree::AlreadyPresent: { + if (load_) + return; + QMessageBox msgBox; + msgBox.setText("already present"); + msgBox.exec(); + return; + } + case AVLTree::NotPresent: { + if (load_) + return; + QMessageBox msgBox; + msgBox.setText("not present"); + msgBox.exec(); + return; + } + default: + break; + } + } + if (history_.size() == 0) + history_.push_back({value_, Operation::Add}); + else if (!hist_ && history_[history_.size() - 1] != std::make_pair(value_, operation_)) { + if (data.operation == AVLTree::Operation::Add) + history_.push_back({value_, Operation::Add}); + else if (data.operation == AVLTree::Operation::Delete) + history_.push_back({value_, Operation::Delete}); + } + const Node *node = data.value; + if (node == nullptr) { + scene_->clear(); + treeInfo_.release(); + count_->setText("0"); + return; + } + if (treeInfo_ != nullptr) { + treeInfo_.release(); + } + treeInfo_ = std::make_unique(node); + if (treeInfo_) { + if (!load_ && !hist_) + delay(1500 - slider_->value()); + drawTree(treeInfo_->getRoot(), data.operation, data.passing_); + } + } + + void View::deleteClicked(QPointF &point) { + std::pair temp = treeInfo_->findValue(point.x(), point.y()); + if (!temp.second) + return; + editText_->setText(QString::number(temp.first)); + } + + void View::inOrderTraversal(Info *node) { + if (node == nullptr) + return; + inOrderTraversal(node->left); + delay(1500 - slider_->value()); + AVLTree::Operation op = AVLTree::Operation::Traversal; + drawTree(treeInfo_->getRoot(), op, node->key); + inOrderTraversal(node->right); + } + + void View::preOrderTraversal(Info *node) { + if (node == nullptr) + return; + delay(1500 - slider_->value()); + AVLTree::Operation op = AVLTree::Operation::Traversal; + drawTree(treeInfo_->getRoot(), op, node->key); + preOrderTraversal(node->left); + preOrderTraversal(node->right); + } + + void View::postOrderTraversal(Info *node) { + if (node == nullptr) + return; + postOrderTraversal(node->left); + postOrderTraversal(node->right); + delay(1500 - slider_->value()); + AVLTree::Operation op = AVLTree::Operation::Traversal; + drawTree(treeInfo_->getRoot(), op, node->key); + } + + void View::getDataFromFile(QString &fileName) { + QFile inputFile(fileName); + inputFile.open(QFile::ReadOnly | QFile::Text); + QTextStream inputStream(&inputFile); + QString key; + operation_ = Operation::Add; + while (!inputStream.atEnd()) { + + QString line = inputStream.readLine(); + QList temp = line.split(" "); + for (QString &item: temp) { + bool convert; + value_ = item.toInt(&convert); + if (!convert) + continue; + commandPort.notify(); + } + } + inputFile.close(); + } + + void View::addNode() { + operation_ = Operation::Add; + if (editText_->text().size() == 0) + return; + value_ = editText_->text().toInt(); + editText_->clear(); + commandPort.notify(); + } + + void View::deleteNode() { + operation_ = Operation::Delete; + value_ = editText_->text().toInt(); + editText_->clear(); + commandPort.notify(); + } + + void View::searchNode() { + operation_ = Operation::Search; + value_ = editText_->text().toInt(); + editText_->clear(); + commandPort.notify(); + } + + void View::inOrder() { + if (treeInfo_ == nullptr) + return; + inOrderTraversal(treeInfo_->getRoot()); + } + + void View::preOrder() { + if (treeInfo_ == nullptr) + return; + preOrderTraversal(treeInfo_->getRoot()); + } + + void View::postOrder() { + if (treeInfo_ == nullptr) + return; + postOrderTraversal(treeInfo_->getRoot()); + } + + void View::aboutMenu() { + secondWindow_ = std::make_unique(); + secondWindow_->setWindowTitle("Additional information"); + secondWindow_->show(); + } + + void View::loadMenu() { + QString fileName = QFileDialog::getOpenFileName(mainWindow_, "Open File", + "../", + "Data (*.txt)"); + if (fileName.isEmpty()) + return; + operation_ = Operation::DeleteAll; + commandPort.notify(); + history_.clear(); + history_.push_back({0, Operation::Add}); + load_ = true; + getDataFromFile(fileName); + load_ = false; + } + + void View::stepBack() { + if (history_.size() == 1) + return; + hist_ = true; + operation_ = Operation::DeleteAll; + commandPort.notify(); + operation_ = Operation::Add; + history_.pop_back(); + for (size_t i = 1; i < history_.size(); ++i) { + value_ = history_[i].first; + operation_ = history_[i].second; + commandPort.notify(); + } + if (history_.size() == 1) { + scene_->clear(); + count_->setText("0"); + } + hist_ = false; + } + + void View::CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { + QPointF pos = event->scenePos(); + ptr->deleteClicked(pos); + } +}// namespace mvc diff --git a/View.h b/View.h new file mode 100644 index 0000000..8469302 --- /dev/null +++ b/View.h @@ -0,0 +1,141 @@ +#ifndef COURSEPROJECT_VIEW_H +#define COURSEPROJECT_VIEW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "InfoTree.h" +#include "Observer/Observer.h" +#include "aboutwindow.h" +#include "mainwindow.h" + +namespace mvc { + + class View : public QGraphicsScene { + Q_OBJECT + public: + View(); + ~View() = default; + + enum class Operation { + Add, + Delete, + Search, + DeleteAll + }; + struct Command { + int &value; + Operation &operation; + }; + + void subscribe(NSLibrary::CObserver *observer) { + assert(observer); + commandPort.subscribe(observer); + } + + private: + using ObserverAVL = NSLibrary::CObserver; + using ObserverCommand = NSLibrary::CObservableData; + using Input = NSLibrary::CHotInput; + + public: + ObserverAVL *input() { return &observer; } + + private: + void addMenu(); + void addControlPannel(); + void adjustMainWindow(); + void connectObjects(); + + void drawNode(Info *cur, std::queue &que, int &count, AVLTree::Operation &operation, int passing); + void drawTree(Info *treeInfo, AVLTree::Operation &operation, int passing); + void draw(const AVLTree::Data &data); + void deleteClicked(QPointF &point); + + void inOrderTraversal(Info *node); + void preOrderTraversal(Info *node); + void postOrderTraversal(Info *node); + + void getDataFromFile(QString &fileName); + + private slots: + void addNode(); + void deleteNode(); + void searchNode(); + void inOrder(); + void preOrder(); + void postOrder(); + void aboutMenu(); + void loadMenu(); + void stepBack(); + + public: + class CustomScene : public QGraphicsScene { + public: + CustomScene(View *ptr) + : ptr(ptr){}; + ~CustomScene() = default; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + + private: + View *ptr; + }; + + private: + static constexpr int kRadius_ = 40; + + private: + MainWindow *mainWindow_; + std::unique_ptr secondWindow_; + + QPushButton *addButton_; + QPushButton *deleteButton_; + QPushButton *searchButton_; + QPushButton *inOrderButton_; + QPushButton *preOrderButton_; + QPushButton *postOrderButton_; + QPushButton *stepBackButton_; + QLineEdit *editText_; + QLineEdit *count_; + QSlider *slider_; + QGraphicsView *treeSpot_; + CustomScene *scene_; + + std::vector> history_; + + int value_; + Operation operation_; + bool load_ = false; + bool hist_ = false; + std::unique_ptr treeInfo_ = nullptr; + ObserverCommand commandPort = Command{value_, operation_}; + + Input observer = [this](const AVLTree::Data &data) { draw(data); }; + }; +}// namespace mvc + +#endif//COURSEPROJECT_VIEW_H diff --git a/aboutwindow.cpp b/aboutwindow.cpp new file mode 100644 index 0000000..cd3e058 --- /dev/null +++ b/aboutwindow.cpp @@ -0,0 +1,98 @@ +#include "aboutwindow.h" +#include "ui_AboutWindow.h" + +namespace mvc { + AboutWindow::AboutWindow(QWidget *parent) : QDialog(parent), ui(new Ui::AboutWindow) { + ui->setupUi(this); + this->setFixedSize(500, 570); + QTextEdit *text = new QTextEdit(this); + text->setText("All people, who are trying to study programming languages, sooner or later understand that they need to get familiar with tree data structures. However, it can be a complicated task to capture how complex data structures work, especially when person encounters them for a first time. Visualization is a useful tool, which can make study of data structures more comprehensive.\n" + "This application presents the work of AVL Tree - a self-balancing Binary Search Tree, which offers O(log(n)) complexity of search, insertion and deletion operations, where n is the number of nodes. This efficient data structure was proposed in 1968 by two Soviet mathematicians Georgy Maximovich Adelson-Velsky and Evgenii Mikhailovich Landis. Because of self-balancing and almost never degenerative structure, AVL Tree offers effective way to store data. To elaborate on what AVL tree is its features should be mentioned:\n" + "1) Key in the node is smaller that any key in the right subtree and greater than any key in the left subtree (feature of a Binary Search Tree).\n" + "2) Difference between heights of right and left subtrees(also known as balance factor) is either -1, 0 or 1."); + QLabel *labelIns = new QLabel("Insertion algorithm", this); + QTextEdit *textIns = new QTextEdit(this); + textIns->setText("Insert node A in the manner similar to insertion in BST, that is to the leaf node. \n" + "\n" + "Go to the parent of A and find first node, which is unbalanced. Balance the tree, performing following operations. (Operation type depends on where child and grandchild of unbalanced node are located).\n" + "Important to mention that child and grandchild should be on the way from inserted node. Let's denote x, as inserted node, y as its child, z as grandchild\n" + "Let's discuss how nodes can be located with respect to each other. \n" + + "1. Left Left Case (This operation will be called right rotation)\n" + "Initial:\n" + "X: y left child; Y: z left child\n" + "New:\n" + "Y: z left child, x right child\n" + "\n" + + "2. Right Right Case (This operation will be called right rotation)\n" + "Initial:\n" + "X: y right child; Y: z right child\n" + "New:\n" + "Y: z right child, x left child\n" + "\n" + + "3. Left Right Case\n" + "Initial:\n" + "X: y left child; Y: z right child\n" + "New:\n" + "Z: y left child, x right child (first apply left rotate to y, after that apply right rotate to x)\n" + "\n" + + "4. Right Left Case \n" + "Initial:\n" + "X: y right child; Y: z left child\n" + "New:\n" + "Z: y right child, x left child (first apply right rotate to y, after that apply left rotate to x)\n" + "\n"); + + QLabel *del = new QLabel("Deletion algorithm", this); + QTextEdit *textDel = new QTextEdit(this); + textDel->setText("Delete node A in the manner similar to the deletion from BST, in the csse of this application find inorder successor, change it with node and perform standard deletion.\n" + "\n" + "Go to the parent of A and find first node, which is unbalanced. Balance the tree, performing following operations. (Operation type depends on where child and grandchild of unbalanced node are located).\n" + "Difference from insertion operation is that in this case child and grandchild from the subtree with a greatest height should be considered. \n" + "1. Left Left Case \n" + "Initial:\n" + "X: y left child; Y: z left child\n" + "New:\n" + "Y: z left child, x right child (apply right rotate to x)\n" + "\n" + + "2. Right Right Case \n" + "Initial:\n" + "X: y right child; Y: z right child\n" + "New:\n" + "Y: z right child, x left child (apply left rotate to x)\n" + "\n" + + + "3. Left Right Case \n" + "Initial:\n" + "X: y left child; Y: z right child\n" + "New:\n" + "Z: y left child, x right child (first apply left rotate to y, after that apply right rotate to x)\n" + "\n" + + "4. Right Left Case \n" + "Initial:\n" + "X: y right child; Y: z left child\n" + "New:\n" + "Z: y right child, x left child (first apply right rotate to y, after that apply left rotate to x)\n" + "Operations should be repeated until root node is reached.\n" + "Information is taken from https://www.geeksforgeeks.org/introduction-to-avl-tree/"); + text->setReadOnly(true); + textDel->setReadOnly(true); + textIns->setReadOnly(true); + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(text); + layout->addWidget(labelIns); + layout->addWidget(textIns); + layout->addWidget(del); + layout->addWidget(textDel); + } + + AboutWindow::~AboutWindow() { + delete ui; + } +}// namespace mvc diff --git a/aboutwindow.h b/aboutwindow.h new file mode 100644 index 0000000..bf3510e --- /dev/null +++ b/aboutwindow.h @@ -0,0 +1,29 @@ +#ifndef COURSEPROJECT_ABOUTWINDOW_H +#define COURSEPROJECT_ABOUTWINDOW_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { + class AboutWindow; +} +QT_END_NAMESPACE + +namespace mvc { + class AboutWindow : public QDialog { + Q_OBJECT + + public: + explicit AboutWindow(QWidget *parent = nullptr); + ~AboutWindow(); + + private: + Ui::AboutWindow *ui; + }; +}// namespace mvc + + +#endif//COURSEPROJECT_ABOUTWINDOW_H diff --git a/aboutwindow.ui b/aboutwindow.ui new file mode 100644 index 0000000..21cb7ed --- /dev/null +++ b/aboutwindow.ui @@ -0,0 +1,22 @@ + + + + + + AboutWindow + + + + 0 + 0 + 400 + 300 + + + + AboutWindow + + + + + \ No newline at end of file diff --git a/interface.png b/interface.png new file mode 100644 index 0000000..c3296ca Binary files /dev/null and b/interface.png differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..635fb8e --- /dev/null +++ b/main.cpp @@ -0,0 +1,8 @@ +#include "Application.h" +#include + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + mvc::Application c; + return QApplication::exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..6e58945 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,15 @@ +#include "mainwindow.h" +#include "ui_MainWindow.h" + +namespace mvc { + MainWindow::MainWindow(QWidget *parent) + : QWidget(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); + this->setAttribute(Qt::WA_AcceptTouchEvents, true); + } + + MainWindow::~MainWindow() { + delete ui; + } + +}// namespace mvc diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..c29c3a6 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,27 @@ +#ifndef COURSEPROJECT_MAINWINDOW_H +#define COURSEPROJECT_MAINWINDOW_H + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui { + class MainWindow; +} +QT_END_NAMESPACE + +namespace mvc { + class MainWindow : public QWidget { + Q_OBJECT + + public: + explicit MainWindow(QWidget *parent = nullptr); + + ~MainWindow() override; + + private: + Ui::MainWindow *ui; + }; +}// namespace mvc + +#endif//COURSEPROJECT_MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..6e6a292 --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,33 @@ + + + + + + MainWindow + + + + 0 + 0 + 400 + 300 + + + + MainWindow + + + + + + + 0 + 0 + + + + + + + + \ No newline at end of file diff --git a/stylesheet.qss b/stylesheet.qss new file mode 100644 index 0000000..cb3878c --- /dev/null +++ b/stylesheet.qss @@ -0,0 +1,43 @@ +QPushButton, QMenu, QMenuBar { + background-color: rgb(0,128,128); + border-width: 1px; + border-color: #59515f; + border-style: solid; + border-radius: 6px; + padding: 4px +} + +QPushButton:hover{ + background-color: #171717; + border-color: #322d35; + color: #656565; +} + +QSlider::groove:horizontal { + height: 5px; +background: rgb(181,181,181); +} + +QSlider::handle:horizontal { + background: #b1b1b1; + border-style: solid; + border-width: 1px; + border-color: rgb(207,207,207); + width: 6px; + margin: -5px 0; + border-radius: 2px; +} + +QSlider::add-page:horizontal { + background: rgb(181,181,181); +} + +QSlider::sub-page:horizontal { + background-color: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #ffa02f, stop: 0.5 #d7801a, stop: 1 #ffa02f); +} + + +QWidget, !QLineEdit { + background-color: rgb(45,45,45); +} +