From 9a4ceb54e83ec0101a4653a6bb68f10528196e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Wed, 26 Apr 2023 22:05:43 +0300 Subject: [PATCH 01/10] commit --- .DS_Store | Bin 0 -> 8196 bytes CMakeLists.txt | 23 ++++ Connection.cpp | 9 ++ Connection.h | 17 +++ Controller.cpp | 17 +++ Controller.h | 31 +++++ Model.cpp | 218 ++++++++++++++++++++++++++++++ Model.h | 63 +++++++++ Observer/Impl/Observable.h | 220 ++++++++++++++++++++++++++++++ Observer/Impl/ObservableImpl.h | 49 +++++++ Observer/Impl/Observer.h | 197 +++++++++++++++++++++++++++ Observer/Impl/Source.h | 73 ++++++++++ Observer/Impl/TypeHelper.h | 33 +++++ Observer/Observer.h | 235 +++++++++++++++++++++++++++++++++ View.cpp | 226 +++++++++++++++++++++++++++++++ View.h | 101 ++++++++++++++ main.cpp | 11 ++ mainwindow.cpp | 12 ++ mainwindow.h | 24 ++++ mainwindow.ui | 33 +++++ 20 files changed, 1592 insertions(+) create mode 100644 .DS_Store create mode 100644 CMakeLists.txt create mode 100644 Connection.cpp create mode 100644 Connection.h create mode 100644 Controller.cpp create mode 100644 Controller.h create mode 100644 Model.cpp create mode 100644 Model.h create mode 100644 Observer/Impl/Observable.h create mode 100644 Observer/Impl/ObservableImpl.h create mode 100644 Observer/Impl/Observer.h create mode 100644 Observer/Impl/Source.h create mode 100644 Observer/Impl/TypeHelper.h create mode 100644 Observer/Observer.h create mode 100644 View.cpp create mode 100644 View.h create mode 100644 main.cpp create mode 100644 mainwindow.cpp create mode 100644 mainwindow.h create mode 100644 mainwindow.ui diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..338944638391d7dc0b00ab2e58f06d4aae455502 GIT binary patch literal 8196 zcmeHML2uJA6n^f?nrd5B1W3IgMdDgYVPj(AQo3glZi;SGkjDXtdTUU=>(YfMfUTbVO&=q0?0S-bF0mMGeRE$In7}&};c%wnj+~J$o3xh#iuYVW0wfwd9f>UrdocFC0Icmk7csOWw`fvH{ zwUnVZD|fuZz#p~CH}_=}cY?_8D?-rq;qvZH5Ow9KDTh&aptvo4!6`b$cDXVhKYX-T zb+_xgld3!3dr+^sJKMXHNzu7==icL2?bGlqlAqLANSH?qZRzv5{d}>Jp11l@D5GO| z^PNhxPcMwR&+%nsKrwO+_9Mj^UBALVsVj3OML2xSQ4BVp)llwHFU=LVGu+J;+>;EC zN42cYnEGff#Nnf~yv-jO?ySu$H`8X0wMn+=sPz}D8k<^gUlQxBH1{7tlL+fn(r9@f zi8W1fH&<{oeN=iTd0Q)Z_}aBZ(Im5o`Il-hh0FUWSjQxzkYZ$drqoM%JM vec = data.addValue; + if(vec.size() == 0) + return; + if(vec.back() == 0) + ptr->insert(vec[0]); + else + ptr->deleteNode(vec[0]); +} \ No newline at end of file diff --git a/Controller.h b/Controller.h new file mode 100644 index 0000000..57c5ed3 --- /dev/null +++ b/Controller.h @@ -0,0 +1,31 @@ +#ifndef COURSEPROJECT_CONTROLLER_H +#define COURSEPROJECT_CONTROLLER_H + +#include + +#include "Model.h" +#include "View.h" + +class Controller { +public: + Controller() = default; + Controller(AVLTree* ptr); + + ///what to do with data + void action(const View::SendData& data); + + ///subscribe controller to view + NSLibrary::CObserver* input() { return &observerAdd; } + + ///public, but let it be like that + AVLTree* ptr; + +private: + NSLibrary::CHotInput observerAdd = [this](const View::SendData& data) + { action(data); }; + NSLibrary::CHotInput observerDelete = [this](const View::SendData& data) + { action(data); }; +}; + + +#endif //COURSEPROJECT_CONTROLLER_H diff --git a/Model.cpp b/Model.cpp new file mode 100644 index 0000000..9257020 --- /dev/null +++ b/Model.cpp @@ -0,0 +1,218 @@ +#include "Model.h" + +int AVLTree::findMaxHeight(Node* left, Node* right) +{ + int l = 0, r = 0; + if(left != nullptr) + l = left->height; + if(right != nullptr) + r = right->height; + return std::max(l, r); +} + +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; +} + +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; +} +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::insert(Node* node, int key) +{ + ///find leaf node, where to insert newNode + if(node == nullptr) + 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 + return node; + + ///update height of ancestor + node->height = findMaxHeight(node->leftCh, node->rightCh) + 1; + + ///find balance factor + int balanceFactor = findBalanceFactor(node); + + ///check whether tree is unbalanced and balance it + if(balanceFactor > 1 && key < node->leftCh->key) + return rightRotate(node); + if(balanceFactor > 1 && key > node->leftCh->key) + { + node->leftCh = leftRotate(node->leftCh); + return rightRotate(node); + } + if(balanceFactor < -1 && key > node->rightCh->key) + return leftRotate(node); + if(balanceFactor < -1 && key < node->rightCh->key) + { + node->rightCh = rightRotate(node->rightCh); + return leftRotate(node); + } + return node; +} + +void AVLTree::insert(int key) +{ + int t = 1; + root_ = insert(root_, key); + + if(root_== nullptr) + return; + insertPort.notify(); +} + +Node* AVLTree::inorderSuccessor(Node* x) +{ + Node* cur = x->rightCh; + while(cur->leftCh != nullptr) + cur = cur->leftCh; + return cur; +} + +Node* AVLTree::deleteNode(Node *node, int key) +{ + ///findNode, which should be deleted, and delete it + if(node == nullptr) + return node; + if(key < node->key) + node->leftCh = deleteNode(node->leftCh, key); + else if(key > node->key) + node->rightCh = deleteNode(node->rightCh, key); + else + { + ///one or zero children + if(node->leftCh == nullptr || node->rightCh == nullptr) + { + 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; + delete cur; + } + ///two children + else + { + Node* cur = AVLTree::inorderSuccessor(node); + node->key = cur->key; + node->rightCh = deleteNode(node->rightCh, cur->key); + } + } + + if(node == nullptr) + return node; + ///update height of the node + node->height = findMaxHeight(node->rightCh, node->leftCh) + 1; + + ///find balance factor + int balanceFactor = findBalanceFactor(node); + ///check whether tree is unbalanced and balance it + if(balanceFactor > 1 && findBalanceFactor(node->leftCh) >= 0) + return rightRotate(node); + if(balanceFactor > 1 && findBalanceFactor(node->leftCh) < 0) + { + node->leftCh = leftRotate(node->leftCh); + return rightRotate(node); + } + if(balanceFactor < -1 && findBalanceFactor(node->rightCh) < 0) + return leftRotate(node); + if(balanceFactor < -1 && findBalanceFactor(node->rightCh) >= 0) + { + node->rightCh = rightRotate(node->rightCh); + return leftRotate(node); + } + return node; +} + +void AVLTree::deleteNode(int key) +{ + root_ = deleteNode(root_, key); + insertPort.notify(); +} + +void AVLTree::clearHelp(Node*& node) +{ + if (node != nullptr) + { + clearHelp(node->leftCh); + clearHelp(node->rightCh); + delete node; + node = nullptr; + } +} +AVLTree::~AVLTree() +{ + clearHelp(root_); +} + +//Node* AVLTree::search(Node *node, int key) +//{ +// if(node == nullptr) +// return node; +// if(key < node->key) +// node->leftCh = search(node->leftCh, key); +// else if(key > node->key) +// node->rightCh = search(node->rightCh, key); +// else +// return node; +//} + +//Node* AVLTree::search(int key) +//{ +// return search(_root, key); +//} + +//void AVLTree::levelPrint(Node* root) +//{ +// if (!root) +// return; +// std::queue q; +// q.push(root); + +// while (!q.empty()) +// { +// Node* cur = q.front(); +// q.pop(); +// std::cout << cur->key <<"\n"; +// if (cur->leftCh) +// q.push(cur->leftCh); +// if (cur->rightCh) +// q.push(cur->rightCh); +// } +//} \ No newline at end of file diff --git a/Model.h b/Model.h new file mode 100644 index 0000000..53d3ebb --- /dev/null +++ b/Model.h @@ -0,0 +1,63 @@ +#ifndef COURSEPROJECT_MODEL_H +#define COURSEPROJECT_MODEL_H + +#include +#include +#include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" + +class Node{ +public: + + Node(){} + Node(int key) + : key(key), height(1), rightCh(nullptr), leftCh(nullptr) + {} + int key; + int height; + Node* leftCh; + Node* rightCh; +}; + +class AVLTree{ +public: + AVLTree() + : root_(nullptr) + {} + + void clearHelp(Node*& treeptr); + ~AVLTree(); + + void insert(int key); + int findMaxHeight(Node* left, Node* right); + int findBalanceFactor(Node* node); + Node* rightRotate(Node* node); + Node* leftRotate(Node* node); + Node* insert(Node* node, int key); + + Node* inorderSuccessor(Node* x); + void deleteNode(int key); + Node* deleteNode(Node* node, int key); + void levelPrint(Node* root); + + Node* search(int key); + Node* search(Node* node, int key); + + + struct ModAddData{ + std::reference_wrapper Value; + }; + void subscribe(NSLibrary::CObserver* observer) + { + assert(observer); + insertPort.subscribe(observer); + } +private: + +// std::vector root_; + Node* root_ = nullptr; + int Value; + NSLibrary::CObservableData insertPort = ModAddData{std::cref(root_)}; +}; + + +#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/View.cpp b/View.cpp new file mode 100644 index 0000000..3c03d0a --- /dev/null +++ b/View.cpp @@ -0,0 +1,226 @@ +#include "View.h" + +View::View(MainWindow* mainWindow) + : mainWindow(mainWindow) +{ + addButton = new QPushButton("Add node"); + deleteButton = new QPushButton("Delete node"); + editText = new QLineEdit(); + + treeSpot = new QGraphicsView(); + scene = new QGraphicsScene(); + scene->setBackgroundBrush(Qt::white); + treeSpot->setScene(scene); + + QVBoxLayout *layout = new QVBoxLayout(mainWindow); + + layout->addWidget(editText); + layout->addWidget(addButton); + layout->addWidget(deleteButton); + layout->addWidget(treeSpot); + + QObject::connect(addButton, SIGNAL(clicked()), this, SLOT(addNode())); + QObject::connect(deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteNode())); + +} + +bool View::checkEditorForInteger(QLineEdit* edt, int& res) +{ + bool convOk; + res = edt->text().toInt(&convOk); + if (!convOk) + { + QMessageBox msgBox; + msgBox.setText(edt->objectName() + " not integer"); + msgBox.exec(); + return false; + } + return true; +} + +void View::addNode() +{ + addValue.clear(); + int val; + if (!checkEditorForInteger(editText, val)) { + editText->clear(); + return; + } + addValue.push_back(editText->text().toInt()); + addValue.push_back(0); + editText->clear(); + addPort.notify(); +} + +void View::deleteNode() +{ + addValue.clear(); + int val; + if (!checkEditorForInteger(editText, val)) { + editText->clear(); + return; + } + addValue.push_back(editText->text().toInt()); + addValue.push_back(1); + editText->clear(); + addPort.notify(); +} + +void View::clear(Info* treeInfo) +{ + if(treeInfo != nullptr) + { + clear(treeInfo->left); + clear(treeInfo->right); + delete treeInfo; + } +} + +View::Info* View::copy(const Node* root) +{ + if(root == nullptr) + return nullptr; + else{ + Info* temp = new Info; + temp->key = root->key; + temp->left = copy(root->leftCh); + temp->right = copy(root->rightCh); + return temp; + } +} + +void View::calcYCoord(const Node* rootGet) +{ + clear(treeInfo); + ///create copy of a tree + treeInfo = copy(rootGet); + Info* root = treeInfo; + if (root == nullptr) + return; + ///implement level-order traversal + std::queue que; + que.push(root); + Info* cur; + int count = 0, timesNow = 1, timesNext = 0; + while (!que.empty()) { + cur = que.front(); + que.pop(); + cur->y = count * (2 * R + H); + 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 View::postOrder(Info* cur) +{ + if (cur == nullptr) + return; + postOrder(cur->left); + postOrder(cur->right); + + if(cur->right == nullptr && cur->left == nullptr) + cur->width = 2 * R; + else if(cur->left == nullptr) + cur->width = cur->right->width + W; + else if(cur->right == nullptr) + cur->width = cur->left->width + W; + else + cur->width = cur->left->width + cur->right->width + W; +} + +void View::calcXCoord(Info* rootGet) { + Info* coord = rootGet; + ///post-order tree traversal + postOrder(coord); + ///implement level-order traversal + Info* root = treeInfo; + 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(); + if (cur->left != nullptr){ + que.push(cur->left); + ++timesNext; + cur->left->x = cur->x - W / 2 - cur->left->width / 2; + } + if (cur->right != nullptr){ + que.push(cur->right); + ++timesNext; + cur->right->x = cur->x + W / 2 + cur->right->width / 2; + } + --timesNow; + if(timesNow == 0) + { + ++count; + timesNow = timesNext; + timesNext = 0; + } + } +} + +void View::drawCircles() +{ + scene->clear(); + scene->setBackgroundBrush(Qt::white); + Info* root = treeInfo; + if (root == nullptr) + return; + std::queue que; + que.push(root); + Info* cur; + while (!que.empty()) { + cur = que.front(); + que.pop(); + + QBrush redbrush(Qt::red); + QPen blackpen(Qt::black); + blackpen.setWidth(3); + + QGraphicsEllipseItem* ellipse = scene->addEllipse(cur->x,cur->y,40,40,blackpen, redbrush); + + 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 + 35, cur->left->x + 20, cur->left->y + 5); + } + if (cur->right != nullptr) { + que.push(cur->right); + scene->addLine(cur->x + 30, cur->y + 35, cur->right->x + 10, cur->right->y + 5); + } + } +} + + +void View::drawTree(const AVLTree::ModAddData &data) { + const Node* root = data.Value; + if(root == nullptr) { + scene->clear(); + return; + } + + ///create tree that will help to draw + calcYCoord(root); + calcXCoord(treeInfo); + + drawCircles(); +} diff --git a/View.h b/View.h new file mode 100644 index 0000000..c3cdf15 --- /dev/null +++ b/View.h @@ -0,0 +1,101 @@ +#ifndef COURSEPROJECT_VIEW_H +#define COURSEPROJECT_VIEW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" +#include "Model.h" + +class View : public QObject +{ + Q_OBJECT +public: + View(MainWindow* mainWindow); + + bool checkEditorForInteger(QLineEdit* edt, int& res); + + /// Controller to view + ///struct with data to share with controller, for addition + struct SendData{ + std::reference_wrapper> addValue; + }; + ///subscribe controller to view + void subscribe(NSLibrary::CObserver* observer) + { + assert(observer); + addPort.subscribe(observer); + } + + + class Info{ + public: + Info(){} + Info(int key, int x, int y) + : key(key), x(0), y(0), left(nullptr), right(nullptr) + {} + int x; + int y; + int key; + Info* left; + Info* right; + int width; + }; + + /// View to model + void drawTree(const AVLTree::ModAddData& data); + NSLibrary::CObserver* input() { return &observerAdd; } + void calcYCoord(const Node* root); + void postOrder(Info* cur); + void calcXCoord(Info* root); + void clear(Info* treeInfo); + Info* copy(const Node* root); + + void drawCircles(); + void paintEvent(QPaintEvent *event); + + +private slots: + void addNode(); + void deleteNode(); + +private: + + const int R = 40; + const int H = 30; + const int W = 20; + + MainWindow* mainWindow; + + QPushButton *addButton; + QPushButton* deleteButton; + QLineEdit* editText; + + QGraphicsView* treeSpot; + QGraphicsScene* scene; + +// std::vector treeInfo; + Info* treeInfo; + + std::vector addValue; + NSLibrary::CObservableData addPort = SendData{addValue}; + +// NSLibrary::CHotInput observerDrawAdd = [this](const AVLTree::MyData& data) +// { drawAdd(data); }; + + NSLibrary::CHotInput observerAdd = [this](const AVLTree::ModAddData& data) + { drawTree(data); }; +}; + +#endif //COURSEPROJECT_VIEW_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a7ffd55 --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include +#include "mainwindow.h" +#include "Connection.h" + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + MainWindow w; + Connection c(&w); + w.show(); + return QApplication::exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..d69f96f --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,12 @@ +#include "mainwindow.h" +#include "ui_MainWindow.h" + + +MainWindow::MainWindow(QWidget *parent) + : QWidget(parent), ui(new Ui::MainWindow) { + ui->setupUi(this); +} + +MainWindow::~MainWindow() { + delete ui; +} diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..9d25fb1 --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,24 @@ +#ifndef COURSEPROJECT_MAINWINDOW_H +#define COURSEPROJECT_MAINWINDOW_H + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui { class MainWindow; } +QT_END_NAMESPACE + +class MainWindow : public QWidget { +Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = nullptr); + + ~MainWindow() override; + +private: + Ui::MainWindow *ui; +}; + + +#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 From f732c61ef36305d3599eea8b6d8423cc615e92ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Mon, 22 May 2023 15:18:14 +0300 Subject: [PATCH 02/10] update --- .DS_Store | Bin 8196 -> 6148 bytes .idea/.gitignore | 8 + .idea/.name | 1 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/test.iml | 2 + .idea/vcs.xml | 6 + CMakeLists.txt | 3 +- Connection.cpp | 13 +- Connection.h | 20 +-- Controller.cpp | 31 ++-- Controller.h | 39 ++-- InfoTree.cpp | 124 +++++++++++++ InfoTree.h | 43 +++++ Model.cpp | 432 +++++++++++++++++++++++++-------------------- Model.h | 63 ------- Observer/.DS_Store | Bin 0 -> 6148 bytes View.cpp | 418 ++++++++++++++++++++++--------------------- View.h | 189 +++++++++++--------- aboutwindow.cpp | 12 ++ aboutwindow.h | 27 +++ aboutwindow.ui | 22 +++ main.cpp | 4 +- mainwindow.cpp | 15 +- mainwindow.h | 19 +- 25 files changed, 894 insertions(+), 609 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/.name create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/test.iml create mode 100644 .idea/vcs.xml create mode 100644 InfoTree.cpp create mode 100644 InfoTree.h delete mode 100644 Model.h create mode 100644 Observer/.DS_Store create mode 100644 aboutwindow.cpp create mode 100644 aboutwindow.h create mode 100644 aboutwindow.ui diff --git a/.DS_Store b/.DS_Store index 338944638391d7dc0b00ab2e58f06d4aae455502..fc0c39a57a9666d126e407e60b0c7af1f1fe904d 100644 GIT binary patch delta 161 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{MGjUEV6q~50D9Q|y2a6Rklrf|-6eX1x7i=tC z&dA6-`H%2ac5`zb1!D`7$$tdwCtnv4>M0|AX9)q kfE!4-f=u67_?>w&zlglZi;SGkjDXtdTUU=>(YfMfUTbVO&=q0?0S-bF0mMGeRE$In7}&};c%wnj+~J$o3xh#iuYVW0wfwd9f>UrdocFC0Icmk7csOWw`fvH{ zwUnVZD|fuZz#p~CH}_=}cY?_8D?-rq;qvZH5Ow9KDTh&aptvo4!6`b$cDXVhKYX-T zb+_xgld3!3dr+^sJKMXHNzu7==icL2?bGlqlAqLANSH?qZRzv5{d}>Jp11l@D5GO| z^PNhxPcMwR&+%nsKrwO+_9Mj^UBALVsVj3OML2xSQ4BVp)llwHFU=LVGu+J;+>;EC zN42cYnEGff#Nnf~yv-jO?ySu$H`8X0wMn+=sPz}D8k<^gUlQxBH1{7tlL+fn(r9@f zi8W1fH&<{oeN=iTd0Q)Z_}aBZ(Im5o`Il-hh0FUWSjQxzkYZ$drqoM%JM + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..51ab974 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/test.iml b/.idea/test.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/test.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e20f5b3..3dade88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.24) project(CourseProject) +set(CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 20) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -14,7 +15,7 @@ find_package(Qt6 COMPONENTS Widgets REQUIRED) -add_executable(CourseProject main.cpp mainwindow.cpp mainwindow.ui Model.cpp View.cpp Controller.cpp Connection.cpp) +add_executable(CourseProject main.cpp mainwindow.cpp mainwindow.ui Model.cpp View.cpp Controller.cpp Connection.cpp InfoTree.cpp aboutwindow.cpp) target_link_libraries(CourseProject Qt::Core Qt::Gui diff --git a/Connection.cpp b/Connection.cpp index 2375f26..da695c3 100644 --- a/Connection.cpp +++ b/Connection.cpp @@ -1,9 +1,8 @@ #include "Connection.h" -Connection::Connection(MainWindow *mainWindow) - : _view(mainWindow) -{ - _controller.ptr = &_model; - _view.subscribe(_controller.input()); - _model.subscribe(_view.input()); -} \ No newline at end of file +namespace mvc { + Application::Application() { + view_.subscribe(controller_.input()); + model_.subscribe(view_.input()); + } +} diff --git a/Connection.h b/Connection.h index c280bd1..65ceaa8 100644 --- a/Connection.h +++ b/Connection.h @@ -3,15 +3,15 @@ #include "Controller.h" -class Connection{ -public: - Connection(MainWindow* mainWindow); - //~Connection(); - -private: - AVLTree _model; - View _view; - Controller _controller; -}; +namespace mvc { + class Application { + public: + Application(); + private: + AVLTree model_; + View view_; + Controller controller_{&model_}; + }; +} #endif //COURSEPROJECT_CONNECTION_H diff --git a/Controller.cpp b/Controller.cpp index 6a2c825..640fcd5 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -1,17 +1,22 @@ #include "Controller.h" -Controller::Controller(AVLTree* ptr) - :ptr(ptr) -{ +namespace mvc { + Controller::Controller(AVLTree *model) + : model_(model) { + } + void Controller::action(const ViewData &data) { + if(data.operation == View::Operation::Add) + model_->insert(data.value); + else if(data.operation == View::Operation::Delete) + model_->deleteNode(data.value); + else if(data.operation == View::Operation::Search) + model_->search(data.value); + else if(data.operation == View::Operation::Traversal && data.type == View::Type::InOrder) + model_->inOrder(); + else if(data.operation == View::Operation::Traversal && data.type == View::Type::PreOrder) + model_->preOrder(); + else + model_->postOrder(); + } } - -void Controller::action(const View::SendData &data) { - const std::vector vec = data.addValue; - if(vec.size() == 0) - return; - if(vec.back() == 0) - ptr->insert(vec[0]); - else - ptr->deleteNode(vec[0]); -} \ No newline at end of file diff --git a/Controller.h b/Controller.h index 57c5ed3..c764ae2 100644 --- a/Controller.h +++ b/Controller.h @@ -6,26 +6,23 @@ #include "Model.h" #include "View.h" -class Controller { -public: - Controller() = default; - Controller(AVLTree* ptr); - - ///what to do with data - void action(const View::SendData& data); - - ///subscribe controller to view - NSLibrary::CObserver* input() { return &observerAdd; } - - ///public, but let it be like that - AVLTree* ptr; - -private: - NSLibrary::CHotInput observerAdd = [this](const View::SendData& data) - { action(data); }; - NSLibrary::CHotInput observerDelete = [this](const View::SendData& data) - { action(data); }; -}; - +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); }; + }; +} #endif //COURSEPROJECT_CONTROLLER_H diff --git a/InfoTree.cpp b/InfoTree.cpp new file mode 100644 index 0000000..00069b1 --- /dev/null +++ b/InfoTree.cpp @@ -0,0 +1,124 @@ +#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 - RADIUS && x < cur->x + RADIUS) && (y > cur->y - RADIUS && y < cur->y + RADIUS)) + 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 * RADIUS; + else if (cur->left == nullptr) + cur->width = 2 * cur->right->width + WIDTH; + else if (cur->right == nullptr) + cur->width = 2 * cur->left->width + WIDTH; + else + cur->width = 2 * std::max(cur->left->width, cur->right->width) + WIDTH; + } + + 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(); + if (cur->left != nullptr) { + que.push(cur->left); + ++timesNext; + cur->left->x = cur->x - WIDTH / 2 - cur->left->width / 2; + } + if (cur->right != nullptr) { + que.push(cur->right); + ++timesNext; + cur->right->x = cur->x + WIDTH / 2 + cur->right->width / 2; + } + --timesNow; + if (timesNow == 0) { + ++count; + timesNow = timesNext; + timesNext = 0; + } + } + } + + Info *InfoTree::copy(const Node *node) { + if (node == nullptr) + return nullptr; + else { + Info *temp = new Info; + temp->key = node->key; + temp->left = copy(node->leftCh); + temp->right = copy(node->rightCh); + return temp; + } + } + + void InfoTree::calcYCoord(const Node *rootGet) { + root_ = copy(rootGet); + 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(); + cur->y = count * (2 * RADIUS + HEIGHT); + 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; + } + } + } +} diff --git a/InfoTree.h b/InfoTree.h new file mode 100644 index 0000000..a855a6c --- /dev/null +++ b/InfoTree.h @@ -0,0 +1,43 @@ +#ifndef COURSEPROJECT_INFOTREE_H +#define COURSEPROJECT_INFOTREE_H + +#include "Model.h" +#include + +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){ + calcYCoord(rootGet); + calcXCoord(); + } + ~InfoTree(); + Info* getRoot() { return root_; } + std::pair findValue(int x, int y); + + private: + void clear(Info* treeInfo); + void setWidth(Info* cur); + void calcXCoord(); + Info* copy(const Node* node); + void calcYCoord(const Node* node); + + static constexpr int RADIUS = 40; + static constexpr int HEIGHT = 30; + static constexpr int WIDTH = 15; + Info* root_ = nullptr; + }; + +} + +#endif//COURSEPROJECT_INFOTREE_H diff --git a/Model.cpp b/Model.cpp index 9257020..b5d9837 100644 --- a/Model.cpp +++ b/Model.cpp @@ -1,218 +1,276 @@ #include "Model.h" -int AVLTree::findMaxHeight(Node* left, Node* right) -{ - int l = 0, r = 0; - if(left != nullptr) - l = left->height; - if(right != nullptr) - r = right->height; - return std::max(l, r); -} +namespace mvc { -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; -} + AVLTree::~AVLTree() { + clearHelp(root_); + } -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; -} -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; -} + void AVLTree::insert(int key) { + operation_ = Add; + root_ = insert(root_, key); + if (root_ == nullptr) + return; + port_.notify(); + } + void AVLTree::deleteNode(int key) { + operation_ = Delete; + root_ = deleteNode(root_, key); + port_.notify(); + } -Node* AVLTree::insert(Node* node, int key) -{ - ///find leaf node, where to insert newNode - if(node == nullptr) - 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 - return node; + void AVLTree::search(int key) { + operation_ = Search; + message_ = 0; + search(root_, key, message_); + if(message_ == 2) + operation_ = Add; + port_.notify(); + } - ///update height of ancestor - node->height = findMaxHeight(node->leftCh, node->rightCh) + 1; + void AVLTree::inOrder() { + message_ = 0; + operation_ = Operation::Traversal; + inOrderTraversal(root_); + } - ///find balance factor - int balanceFactor = findBalanceFactor(node); + void AVLTree::preOrder() { + message_ = 0; + operation_ = Operation::Traversal; + preOrderTraversal(root_); + } - ///check whether tree is unbalanced and balance it - if(balanceFactor > 1 && key < node->leftCh->key) - return rightRotate(node); - if(balanceFactor > 1 && key > node->leftCh->key) - { - node->leftCh = leftRotate(node->leftCh); - return rightRotate(node); + void AVLTree::postOrder() { + message_ = 0; + operation_ = Operation::Traversal; + postOrderTraversal(root_); + } + + 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; } - if(balanceFactor < -1 && key > node->rightCh->key) - return leftRotate(node); - if(balanceFactor < -1 && key < node->rightCh->key) + + int AVLTree::findMaxHeight(const Node *left, const Node *right) { + return std::max(getHeight(left), getHeight(right)); + } + + Node* AVLTree::balanceInsert(Node* node, int key) { - node->rightCh = rightRotate(node->rightCh); - return leftRotate(node); + 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_ = 0; + 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_ = 1; + return node; + } + return balanceInsert(node, key); } - return node; -} -void AVLTree::insert(int key) -{ - int t = 1; - root_ = insert(root_, 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; + } - if(root_== nullptr) - return; - insertPort.notify(); -} + Node *AVLTree::inorderSuccessor(Node *x) { + Node *cur = x->rightCh; + while (cur->leftCh != nullptr) + cur = cur->leftCh; + return cur; + } -Node* AVLTree::inorderSuccessor(Node* x) -{ - Node* cur = x->rightCh; - while(cur->leftCh != nullptr) - cur = cur->leftCh; - return cur; -} + void AVLTree::twoChildren(Node* node) + { + Node* cur = AVLTree::inorderSuccessor(node); + node->key = cur->key; + port_.notify(); + node->rightCh = deleteNode(node->rightCh, cur->key); + } -Node* AVLTree::deleteNode(Node *node, int key) -{ - ///findNode, which should be deleted, and delete it - if(node == nullptr) + Node* AVLTree::oneZeroChildren(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; - if(key < node->key) - node->leftCh = deleteNode(node->leftCh, key); - else if(key > node->key) - node->rightCh = deleteNode(node->rightCh, key); - else + } + + Node* AVLTree::deleteNode(Node *node, int key) { - ///one or zero children - if(node->leftCh == nullptr || node->rightCh == nullptr) - { - 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; - delete cur; + if(node == nullptr) { + message_ = 2; + return node; } - ///two children + if(key < node->key) + node->leftCh = deleteNode(node->leftCh, key); + else if(key > node->key) + node->rightCh = deleteNode(node->rightCh, key); else { - Node* cur = AVLTree::inorderSuccessor(node); - node->key = cur->key; - node->rightCh = deleteNode(node->rightCh, cur->key); + message_ = 0; + if(node->leftCh == nullptr || node->rightCh == nullptr) + node = oneZeroChildren(node); + else + twoChildren(node); } + return balanceDelete(node); } - if(node == nullptr) - return node; - ///update height of the node - node->height = findMaxHeight(node->rightCh, node->leftCh) + 1; - - ///find balance factor - int balanceFactor = findBalanceFactor(node); - ///check whether tree is unbalanced and balance it - if(balanceFactor > 1 && findBalanceFactor(node->leftCh) >= 0) - return rightRotate(node); - if(balanceFactor > 1 && findBalanceFactor(node->leftCh) < 0) + void AVLTree::search(Node *node, int key, int& repeat) { - node->leftCh = leftRotate(node->leftCh); - return rightRotate(node); + 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; + } + } + repeat = 2; } - if(balanceFactor < -1 && findBalanceFactor(node->rightCh) < 0) - return leftRotate(node); - if(balanceFactor < -1 && findBalanceFactor(node->rightCh) >= 0) - { - node->rightCh = rightRotate(node->rightCh); - return leftRotate(node); + + void AVLTree::inOrderTraversal(Node *node) { + if(node == nullptr) + return; + inOrderTraversal(node->leftCh); + passing_ = node->key; + port_.notify(); + inOrderTraversal(node->rightCh); } - return node; -} -void AVLTree::deleteNode(int key) -{ - root_ = deleteNode(root_, key); - insertPort.notify(); -} + void AVLTree::preOrderTraversal(Node *node) { + if(node == nullptr) + return; + passing_ = node->key; + port_.notify(); + preOrderTraversal(node->leftCh); + preOrderTraversal(node->rightCh); + } -void AVLTree::clearHelp(Node*& node) -{ - if (node != nullptr) - { - clearHelp(node->leftCh); - clearHelp(node->rightCh); - delete node; - node = nullptr; + void AVLTree::postOrderTraversal(Node *node) { + if(node == nullptr) + return; + postOrderTraversal(node->leftCh); + postOrderTraversal(node->rightCh); + passing_ = node->key; + port_.notify(); } -} -AVLTree::~AVLTree() -{ - clearHelp(root_); -} -//Node* AVLTree::search(Node *node, int key) -//{ -// if(node == nullptr) -// return node; -// if(key < node->key) -// node->leftCh = search(node->leftCh, key); -// else if(key > node->key) -// node->rightCh = search(node->rightCh, key); -// else -// return node; -//} - -//Node* AVLTree::search(int key) -//{ -// return search(_root, key); -//} - -//void AVLTree::levelPrint(Node* root) -//{ -// if (!root) -// return; -// std::queue q; -// q.push(root); - -// while (!q.empty()) -// { -// Node* cur = q.front(); -// q.pop(); -// std::cout << cur->key <<"\n"; -// if (cur->leftCh) -// q.push(cur->leftCh); -// if (cur->rightCh) -// q.push(cur->rightCh); -// } -//} \ No newline at end of file +} diff --git a/Model.h b/Model.h deleted file mode 100644 index 53d3ebb..0000000 --- a/Model.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef COURSEPROJECT_MODEL_H -#define COURSEPROJECT_MODEL_H - -#include -#include -#include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" - -class Node{ -public: - - Node(){} - Node(int key) - : key(key), height(1), rightCh(nullptr), leftCh(nullptr) - {} - int key; - int height; - Node* leftCh; - Node* rightCh; -}; - -class AVLTree{ -public: - AVLTree() - : root_(nullptr) - {} - - void clearHelp(Node*& treeptr); - ~AVLTree(); - - void insert(int key); - int findMaxHeight(Node* left, Node* right); - int findBalanceFactor(Node* node); - Node* rightRotate(Node* node); - Node* leftRotate(Node* node); - Node* insert(Node* node, int key); - - Node* inorderSuccessor(Node* x); - void deleteNode(int key); - Node* deleteNode(Node* node, int key); - void levelPrint(Node* root); - - Node* search(int key); - Node* search(Node* node, int key); - - - struct ModAddData{ - std::reference_wrapper Value; - }; - void subscribe(NSLibrary::CObserver* observer) - { - assert(observer); - insertPort.subscribe(observer); - } -private: - -// std::vector root_; - Node* root_ = nullptr; - int Value; - NSLibrary::CObservableData insertPort = ModAddData{std::cref(root_)}; -}; - - -#endif //COURSEPROJECT_MODEL_H diff --git a/Observer/.DS_Store b/Observer/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6af0d30321e00bae0b5c5f7d5cc57e1a7014a1d6 GIT binary patch literal 6148 zcmeHKPfNov6i-}r9Yg3rVaI^ifg5a2@KWkLc(tMjmD$pv#jY7^XAfi0vwk5ziJ!;! zl2jbdtB89KlHdEAG(TwmFvhqykGhOGj4>M;B1dJ7pnGj-%OoRm93w2UX_&|m>^Bqp z>ww>Gv2!+IQEZq^R1e-y;HaAcGJG|A7$a^K|asCV0MGnrIbln=|OlE zkBgDBcPi67h|}>*6~xgPQf{x~G?IlU=V_FwT3-k3hTRxBt;M3(>$F9;-&?lD;zzx{3?%-7$63S0b*ct z88Byp-P~LXXywEJG4KNexIYMJh_1m>quM&4!|OA~EkqR1@hyQU47vtOjSvChx)e~C za`VLCx*Yt%A9}DTbGCk49-g%V+C4NB%qvj=0e$5X00!521|`N T3i?$!AYB9$A=D8Azrer;J|jvh literal 0 HcmV?d00001 diff --git a/View.cpp b/View.cpp index 3c03d0a..fd45d56 100644 --- a/View.cpp +++ b/View.cpp @@ -1,226 +1,238 @@ #include "View.h" - -View::View(MainWindow* mainWindow) - : mainWindow(mainWindow) -{ - addButton = new QPushButton("Add node"); - deleteButton = new QPushButton("Delete node"); - editText = new QLineEdit(); - - treeSpot = new QGraphicsView(); - scene = new QGraphicsScene(); - scene->setBackgroundBrush(Qt::white); - treeSpot->setScene(scene); - - QVBoxLayout *layout = new QVBoxLayout(mainWindow); - - layout->addWidget(editText); - layout->addWidget(addButton); - layout->addWidget(deleteButton); - layout->addWidget(treeSpot); - - QObject::connect(addButton, SIGNAL(clicked()), this, SLOT(addNode())); - QObject::connect(deleteButton, SIGNAL(clicked(bool)), this, SLOT(deleteNode())); - -} - -bool View::checkEditorForInteger(QLineEdit* edt, int& res) -{ - bool convOk; - res = edt->text().toInt(&convOk); - if (!convOk) - { - QMessageBox msgBox; - msgBox.setText(edt->objectName() + " not integer"); - msgBox.exec(); - return false; +#include +#include +#include + +namespace mvc { + + View::View() { + mainWindow = new MainWindow(); + mainWindow->setWindowTitle("AVL tree"); + mainWindow->show(); + + QMenuBar* menu = new QMenuBar(mainWindow); + QMenu* about = new QMenu("About"); + QAction* act = new QAction(about); + act->setText("Info"); + about->addAction(act); + menu->addMenu(about); + QObject::connect(act, &QAction::triggered, this, &View::aboutMenu); + + 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); + + 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); + + 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->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_); + + 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())); } - return true; -} -void View::addNode() -{ - addValue.clear(); - int val; - if (!checkEditorForInteger(editText, val)) { - editText->clear(); - return; - } - addValue.push_back(editText->text().toInt()); - addValue.push_back(0); - editText->clear(); - addPort.notify(); -} - -void View::deleteNode() -{ - addValue.clear(); - int val; - if (!checkEditorForInteger(editText, val)) { - editText->clear(); - return; - } - addValue.push_back(editText->text().toInt()); - addValue.push_back(1); - editText->clear(); - addPort.notify(); -} - -void View::clear(Info* treeInfo) -{ - if(treeInfo != nullptr) + void View::drawTree(Info* treeInfo, AVLTree::Operation& operation, int passing) { - clear(treeInfo->left); - clear(treeInfo->right); - delete treeInfo; + 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(); + + 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) + ellipse_ = scene_->addEllipse(cur->x,cur->y,RADIUS,RADIUS,redpen, cyanbrush); + else + ellipse_ = scene_->addEllipse(cur->x,cur->y,RADIUS,RADIUS,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; + } + } + count_->setText(QString::number(count)); } -} -View::Info* View::copy(const Node* root) -{ - if(root == nullptr) - return nullptr; - else{ - Info* temp = new Info; - temp->key = root->key; - temp->left = copy(root->leftCh); - temp->right = copy(root->rightCh); - return temp; + void delay(int millisecondsWait) + { + QTimer t; + QEventLoop loop; + t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit); + t.start(millisecondsWait); + loop.exec(); } -} -void View::calcYCoord(const Node* rootGet) -{ - clear(treeInfo); - ///create copy of a tree - treeInfo = copy(rootGet); - Info* root = treeInfo; - if (root == nullptr) - return; - ///implement level-order traversal - std::queue que; - que.push(root); - Info* cur; - int count = 0, timesNow = 1, timesNext = 0; - while (!que.empty()) { - cur = que.front(); - que.pop(); - cur->y = count * (2 * R + H); - if (cur->left != nullptr){ - que.push(cur->left); - ++timesNext; - } - if (cur->right != nullptr){ - que.push(cur->right); - ++timesNext; + void View::drawPrep(const AVLTree::Data &data) { + if(data.message == 1) + { + QMessageBox msgBox; + msgBox.setText("already present"); + msgBox.exec(); } - --timesNow; - if(timesNow == 0) + if(data.message == 2) { - ++count; - timesNow = timesNext; - timesNext = 0; + QMessageBox msgBox; + msgBox.setText("not present"); + msgBox.exec(); + } + const Node *node = data.value; + if (node == nullptr) { + scene_->clear(); + return; + } + if(treeInfo_ != nullptr) + delete treeInfo_; + treeInfo_ = new InfoTree(node); + if(treeInfo_) { + delay(speed_); + drawTree(treeInfo_->getRoot(), data.operation, data.passing_); } } -} -void View::postOrder(Info* cur) -{ - if (cur == nullptr) - return; - postOrder(cur->left); - postOrder(cur->right); - - if(cur->right == nullptr && cur->left == nullptr) - cur->width = 2 * R; - else if(cur->left == nullptr) - cur->width = cur->right->width + W; - else if(cur->right == nullptr) - cur->width = cur->left->width + W; - else - cur->width = cur->left->width + cur->right->width + W; -} + void View::deleteClicked(QPointF& point) { + speed_ = slider_->value(); + std::pair temp = treeInfo_->findValue(point.x(), point.y()); + if(!temp.second) + return; + editText_->setText(QString::number(temp.first)); + } -void View::calcXCoord(Info* rootGet) { - Info* coord = rootGet; - ///post-order tree traversal - postOrder(coord); - ///implement level-order traversal - Info* root = treeInfo; - 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(); - if (cur->left != nullptr){ - que.push(cur->left); - ++timesNext; - cur->left->x = cur->x - W / 2 - cur->left->width / 2; - } - if (cur->right != nullptr){ - que.push(cur->right); - ++timesNext; - cur->right->x = cur->x + W / 2 + cur->right->width / 2; - } - --timesNow; - if(timesNow == 0) - { - ++count; - timesNow = timesNext; - timesNext = 0; - } + void View::addNode() { + speed_ = slider_->value(); + operation_ = Operation::Add; + if(editText_->text().size() == 0) + return; + value_ = editText_->text().toInt(); + editText_->clear(); + commandPort.notify(); } -} -void View::drawCircles() -{ - scene->clear(); - scene->setBackgroundBrush(Qt::white); - Info* root = treeInfo; - if (root == nullptr) - return; - std::queue que; - que.push(root); - Info* cur; - while (!que.empty()) { - cur = que.front(); - que.pop(); - - QBrush redbrush(Qt::red); - QPen blackpen(Qt::black); - blackpen.setWidth(3); - - QGraphicsEllipseItem* ellipse = scene->addEllipse(cur->x,cur->y,40,40,blackpen, redbrush); - - 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 + 35, cur->left->x + 20, cur->left->y + 5); - } - if (cur->right != nullptr) { - que.push(cur->right); - scene->addLine(cur->x + 30, cur->y + 35, cur->right->x + 10, cur->right->y + 5); - } + void View::deleteNode() { + speed_ = slider_->value(); + operation_ = Operation::Delete; + value_ = editText_->text().toInt(); + editText_->clear(); + commandPort.notify(); + } + + void View::searchNode() { + speed_ = slider_->value(); + operation_ = Operation::Search; + value_ = editText_->text().toInt(); + editText_->clear(); + commandPort.notify(); } -} + void View::inOrder(){ + speed_ = slider_->value(); + operation_ = Operation::Traversal; + type_ = Type::InOrder; + commandPort.notify(); + } + void View::preOrder(){ + speed_ = slider_->value(); + operation_ = Operation::Traversal; + type_ = Type::PreOrder; + commandPort.notify(); + } + void View::postOrder(){ + speed_ = slider_->value(); + operation_ = Operation::Traversal; + type_ = Type::PostOrder; + commandPort.notify(); + } -void View::drawTree(const AVLTree::ModAddData &data) { - const Node* root = data.Value; - if(root == nullptr) { - scene->clear(); - return; + void View::aboutMenu() { + secondWindow_ = new AboutWindow(); + secondWindow_->setWindowTitle("Additional information"); + secondWindow_->show(); } - ///create tree that will help to draw - calcYCoord(root); - calcXCoord(treeInfo); + void View::CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event){ + QPointF pos = event->scenePos(); + ptr->deleteClicked(pos); + } - drawCircles(); } + diff --git a/View.h b/View.h index c3cdf15..3ec745e 100644 --- a/View.h +++ b/View.h @@ -1,101 +1,118 @@ #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 #include -#include -#include "mainwindow.h" #include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" -#include "Model.h" - -class View : public QObject -{ - Q_OBJECT -public: - View(MainWindow* mainWindow); - - bool checkEditorForInteger(QLineEdit* edt, int& res); - - /// Controller to view - ///struct with data to share with controller, for addition - struct SendData{ - std::reference_wrapper> addValue; - }; - ///subscribe controller to view - void subscribe(NSLibrary::CObserver* observer) - { - assert(observer); - addPort.subscribe(observer); - } +#include "InfoTree.h" +#include "mainwindow.h" +#include "aboutwindow.h" +namespace mvc { - class Info{ + class View : public QGraphicsScene{ + Q_OBJECT public: - Info(){} - Info(int key, int x, int y) - : key(key), x(0), y(0), left(nullptr), right(nullptr) - {} - int x; - int y; - int key; - Info* left; - Info* right; - int width; - }; - - /// View to model - void drawTree(const AVLTree::ModAddData& data); - NSLibrary::CObserver* input() { return &observerAdd; } - void calcYCoord(const Node* root); - void postOrder(Info* cur); - void calcXCoord(Info* root); - void clear(Info* treeInfo); - Info* copy(const Node* root); - - void drawCircles(); - void paintEvent(QPaintEvent *event); - - -private slots: - void addNode(); - void deleteNode(); - -private: - - const int R = 40; - const int H = 30; - const int W = 20; - - MainWindow* mainWindow; - - QPushButton *addButton; - QPushButton* deleteButton; - QLineEdit* editText; - - QGraphicsView* treeSpot; - QGraphicsScene* scene; - -// std::vector treeInfo; - Info* treeInfo; - - std::vector addValue; - NSLibrary::CObservableData addPort = SendData{addValue}; + View(); + + enum class Operation { + Add, Delete, Search, Traversal + }; + enum class Type{ + InOrder, PreOrder, PostOrder + }; + struct Command { + int& value; + Operation& operation; + Type& type; + }; + + 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 drawTree(Info* treeInfo, AVLTree::Operation& operation, int passing); + void drawPrep(const AVLTree::Data& data); + void deleteClicked(QPointF& point); + + private slots: + void addNode(); + void deleteNode(); + void searchNode(); + void inOrder(); + void preOrder(); + void postOrder(); + void aboutMenu(); -// NSLibrary::CHotInput observerDrawAdd = [this](const AVLTree::MyData& data) -// { drawAdd(data); }; + public: - NSLibrary::CHotInput observerAdd = [this](const AVLTree::ModAddData& data) - { drawTree(data); }; -}; + class CustomScene : public QGraphicsScene{ + public: + CustomScene(View* ptr) + :ptr(ptr){}; + ~CustomScene() = default; + virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + private: + View* ptr; + }; + + private: + MainWindow* mainWindow; + AboutWindow* secondWindow_; + + QPushButton *addButton_; + QPushButton *deleteButton_; + QPushButton* searchButton_; + QPushButton* inOrderButton_; + QPushButton* preOrderButton_; + QPushButton* postOrderButton_; + QLineEdit *editText_; + QLineEdit* count_; + QSlider* slider_; + QGraphicsEllipseItem* ellipse_; + + QGraphicsView *treeSpot_; + CustomScene* scene_; + + int value_; + Operation operation_; + Type type_; + InfoTree* treeInfo_ = nullptr; + int speed_ = 750; + static constexpr int RADIUS = 40; + ObserverCommand commandPort = Command{value_, operation_, type_}; + + Input observer = [this](const AVLTree::Data &data) { drawPrep(data); }; + }; +} -#endif //COURSEPROJECT_VIEW_H +#endif//COURSEPROJECT_VIEW_H diff --git a/aboutwindow.cpp b/aboutwindow.cpp new file mode 100644 index 0000000..273e855 --- /dev/null +++ b/aboutwindow.cpp @@ -0,0 +1,12 @@ +#include "aboutwindow.h" +#include "ui_AboutWindow.h" + +namespace mvc { + AboutWindow::AboutWindow(QWidget *parent) : QDialog(parent), ui(new Ui::AboutWindow) { + ui->setupUi(this); + } + + AboutWindow::~AboutWindow() { + delete ui; + } +} diff --git a/aboutwindow.h b/aboutwindow.h new file mode 100644 index 0000000..e13d538 --- /dev/null +++ b/aboutwindow.h @@ -0,0 +1,27 @@ +#ifndef COURSEPROJECT_ABOUTWINDOW_H +#define COURSEPROJECT_ABOUTWINDOW_H + +#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() override; + + private: + Ui::AboutWindow *ui; + }; +} + + +#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/main.cpp b/main.cpp index a7ffd55..55c9b50 100644 --- a/main.cpp +++ b/main.cpp @@ -4,8 +4,6 @@ int main(int argc, char *argv[]) { QApplication a(argc, argv); - MainWindow w; - Connection c(&w); - w.show(); + mvc::Application c; return QApplication::exec(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index d69f96f..250f9f6 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,12 +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(QWidget *parent) - : QWidget(parent), ui(new Ui::MainWindow) { - ui->setupUi(this); -} + MainWindow::~MainWindow() { + delete ui; + } -MainWindow::~MainWindow() { - delete ui; } diff --git a/mainwindow.h b/mainwindow.h index 9d25fb1..322562e 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -8,17 +8,18 @@ QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE -class MainWindow : public QWidget { -Q_OBJECT +namespace mvc { + class MainWindow : public QWidget { + Q_OBJECT -public: - explicit MainWindow(QWidget *parent = nullptr); + public: + explicit MainWindow(QWidget *parent = nullptr); - ~MainWindow() override; - -private: - Ui::MainWindow *ui; -}; + ~MainWindow() override; + private: + Ui::MainWindow *ui; + }; +} #endif //COURSEPROJECT_MAINWINDOW_H From df9080349687a42b46e023914c456f6818a3b5bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Mon, 22 May 2023 15:28:50 +0300 Subject: [PATCH 03/10] Ds_store --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From a133817bf6afe3c2f4446c88d5b0e513a3909ad0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Mon, 22 May 2023 15:30:13 +0300 Subject: [PATCH 04/10] idea --- .DS_Store | Bin 6148 -> 0 bytes Observer/.DS_Store | Bin 6148 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 Observer/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index fc0c39a57a9666d126e407e60b0c7af1f1fe904d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z-O8-BN@c6nb3nTCmz`6)&OIvtUFIDz!01gE1>f+8#et)}d>_vZ7O*x1}UJRLklkFk0-WODf3X<4(lfESn)_7$YJkq_UA zK?Uc;OX5hyci>wrig6^w05L!eEFlB-niKUU+#79)7$63I#{k|B5){$3m>bkr2MoFe z0IYyn3(T>Xz#M7OwU`?O4+uA@fF_mO7K58~uuB`~TFecabjEG-!R?#5?NGRWJFG8t zIODEC8i@g7;41^z`++!E|Brv}|8)=z!~ij{oDA?v-|P2aZ??7;c8RrCg5H3lU|eqS lBLyb96hkbQ;s&S|uuGf(x)yVTU;&{&0-6RIh=D(4;2kctQ?CF3 diff --git a/Observer/.DS_Store b/Observer/.DS_Store deleted file mode 100644 index 6af0d30321e00bae0b5c5f7d5cc57e1a7014a1d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKPfNov6i-}r9Yg3rVaI^ifg5a2@KWkLc(tMjmD$pv#jY7^XAfi0vwk5ziJ!;! zl2jbdtB89KlHdEAG(TwmFvhqykGhOGj4>M;B1dJ7pnGj-%OoRm93w2UX_&|m>^Bqp z>ww>Gv2!+IQEZq^R1e-y;HaAcGJG|A7$a^K|asCV0MGnrIbln=|OlE zkBgDBcPi67h|}>*6~xgPQf{x~G?IlU=V_FwT3-k3hTRxBt;M3(>$F9;-&?lD;zzx{3?%-7$63S0b*ct z88Byp-P~LXXywEJG4KNexIYMJh_1m>quM&4!|OA~EkqR1@hyQU47vtOjSvChx)e~C za`VLCx*Yt%A9}DTbGCk49-g%V+C4NB%qvj=0e$5X00!521|`N T3i?$!AYB9$A=D8Azrer;J|jvh From 1726d0e32d4ece3d1a72c798ce7982e02f96836d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Mon, 22 May 2023 15:31:39 +0300 Subject: [PATCH 05/10] idea --- .idea/.gitignore | 8 -------- .idea/.name | 1 - .idea/misc.xml | 4 ---- .idea/modules.xml | 8 -------- .idea/test.iml | 2 -- .idea/vcs.xml | 6 ------ 6 files changed, 29 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/.name delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/test.iml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name deleted file mode 100644 index 5e9c1c4..0000000 --- a/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -CourseProject \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 79b3c94..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 51ab974..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/test.iml b/.idea/test.iml deleted file mode 100644 index f08604b..0000000 --- a/.idea/test.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 94167fc62d9cc979887a6833aa21c1ff295b72e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Wed, 7 Jun 2023 11:41:57 +0300 Subject: [PATCH 06/10] step back --- Connection.cpp => Application.cpp | 4 +- Connection.h => Application.h | 7 +- CMakeLists.txt | 6 +- Controller.cpp | 32 ++-- InfoTree.cpp | 91 ++++----- InfoTree.h | 22 ++- Model.cpp | 81 +++----- Model.h | 84 +++++++++ View.cpp | 301 ++++++++++++++++++++++-------- View.h | 50 +++-- aboutwindow.cpp | 112 +++++++++++ aboutwindow.h | 6 +- main.cpp | 3 +- stylesheet.qss | 46 +++++ 14 files changed, 620 insertions(+), 225 deletions(-) rename Connection.cpp => Application.cpp (64%) rename Connection.h => Application.h (65%) create mode 100644 Model.h create mode 100644 stylesheet.qss diff --git a/Connection.cpp b/Application.cpp similarity index 64% rename from Connection.cpp rename to Application.cpp index da695c3..118f202 100644 --- a/Connection.cpp +++ b/Application.cpp @@ -1,7 +1,7 @@ -#include "Connection.h" +#include "Application.h" namespace mvc { - Application::Application() { + Application::Application() { view_.subscribe(controller_.input()); model_.subscribe(view_.input()); } diff --git a/Connection.h b/Application.h similarity index 65% rename from Connection.h rename to Application.h index 65ceaa8..77fc277 100644 --- a/Connection.h +++ b/Application.h @@ -1,5 +1,5 @@ -#ifndef COURSEPROJECT_CONNECTION_H -#define COURSEPROJECT_CONNECTION_H +#ifndef COURSEPROJECT_APPLICATION_H +#define COURSEPROJECT_APPLICATION_H #include "Controller.h" @@ -14,4 +14,5 @@ namespace mvc { Controller controller_{&model_}; }; } -#endif //COURSEPROJECT_CONNECTION_H + +#endif//COURSEPROJECT_APPLICATION_H diff --git a/CMakeLists.txt b/CMakeLists.txt index 3dade88..e57c482 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.24) project(CourseProject) -set(CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD 20) +set(CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) @@ -13,12 +13,14 @@ find_package(Qt6 COMPONENTS Core Gui Widgets + Pdf REQUIRED) -add_executable(CourseProject main.cpp mainwindow.cpp mainwindow.ui Model.cpp View.cpp Controller.cpp Connection.cpp InfoTree.cpp aboutwindow.cpp) +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 + Qt::Pdf ) diff --git a/Controller.cpp b/Controller.cpp index 640fcd5..c4dbdb9 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -6,17 +6,25 @@ namespace mvc { } void Controller::action(const ViewData &data) { - if(data.operation == View::Operation::Add) - model_->insert(data.value); - else if(data.operation == View::Operation::Delete) - model_->deleteNode(data.value); - else if(data.operation == View::Operation::Search) - model_->search(data.value); - else if(data.operation == View::Operation::Traversal && data.type == View::Type::InOrder) - model_->inOrder(); - else if(data.operation == View::Operation::Traversal && data.type == View::Type::PreOrder) - model_->preOrder(); - else - model_->postOrder(); + 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; + } } } diff --git a/InfoTree.cpp b/InfoTree.cpp index 00069b1..56c08a0 100644 --- a/InfoTree.cpp +++ b/InfoTree.cpp @@ -6,7 +6,7 @@ namespace mvc { } std::pair InfoTree::findValue(int x, int y) { - if(this == nullptr) + if (this == nullptr) return {0, false}; std::queue que; que.push(root_); @@ -14,7 +14,7 @@ namespace mvc { while (!que.empty()) { cur = que.front(); que.pop(); - if((x > cur->x - RADIUS && x < cur->x + RADIUS) && (y > cur->y - RADIUS && y < cur->y + RADIUS)) + 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); @@ -39,13 +39,32 @@ namespace mvc { setWidth(cur->right); if (cur->right == nullptr && cur->left == nullptr) - cur->width = 2 * RADIUS; + cur->width = 2 * kRadius_; else if (cur->left == nullptr) - cur->width = 2 * cur->right->width + WIDTH; + cur->width = 2 * cur->right->width + kWidth_; else if (cur->right == nullptr) - cur->width = 2 * cur->left->width + WIDTH; + cur->width = 2 * cur->left->width + kWidth_; else - cur->width = 2 * std::max(cur->left->width, cur->right->width) + WIDTH; + cur->width = 2 * std::max(cur->left->width, cur->right->width) + kWidth_; + } + + void InfoTree::setXCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow){ + 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() { @@ -61,39 +80,41 @@ namespace mvc { while (!que.empty()) { cur = que.front(); que.pop(); - if (cur->left != nullptr) { - que.push(cur->left); - ++timesNext; - cur->left->x = cur->x - WIDTH / 2 - cur->left->width / 2; - } - if (cur->right != nullptr) { - que.push(cur->right); - ++timesNext; - cur->right->x = cur->x + WIDTH / 2 + cur->right->width / 2; - } - --timesNow; - if (timesNow == 0) { - ++count; - timesNow = timesNext; - timesNext = 0; - } + setXCoord(cur, que, count, timesNext, timesNow); } } - Info *InfoTree::copy(const Node *node) { + Info *InfoTree::buildInfoTree(const Node *node) { if (node == nullptr) return nullptr; else { Info *temp = new Info; temp->key = node->key; - temp->left = copy(node->leftCh); - temp->right = copy(node->rightCh); + temp->left = buildInfoTree(node->leftCh); + temp->right = buildInfoTree(node->rightCh); return temp; } } - void InfoTree::calcYCoord(const Node *rootGet) { - root_ = copy(rootGet); + void InfoTree::setYCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow) { + 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; @@ -104,21 +125,7 @@ namespace mvc { while (!que.empty()) { cur = que.front(); que.pop(); - cur->y = count * (2 * RADIUS + HEIGHT); - 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; - } + setYCoord(cur, que, count, timesNext, timesNow); } } } diff --git a/InfoTree.h b/InfoTree.h index a855a6c..d8d0ae2 100644 --- a/InfoTree.h +++ b/InfoTree.h @@ -17,24 +17,30 @@ namespace mvc { class InfoTree { public: - InfoTree(const Node* rootGet){ - calcYCoord(rootGet); + 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: - void clear(Info* treeInfo); + static constexpr int kRadius_ = 40; + static constexpr int kHeight_ = 30; + static constexpr int kWidth_ = 15; + private: +// void clear(Info* treeInfo); void setWidth(Info* cur); + void setXCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow); void calcXCoord(); - Info* copy(const Node* node); - void calcYCoord(const Node* node); + Info* buildInfoTree(const Node* node); + void setYCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow); + void calcYCoord(); - static constexpr int RADIUS = 40; - static constexpr int HEIGHT = 30; - static constexpr int WIDTH = 15; Info* root_ = nullptr; }; diff --git a/Model.cpp b/Model.cpp index b5d9837..11bcb35 100644 --- a/Model.cpp +++ b/Model.cpp @@ -22,29 +22,17 @@ namespace mvc { void AVLTree::search(int key) { operation_ = Search; - message_ = 0; + message_ = Fine; search(root_, key, message_); if(message_ == 2) operation_ = Add; port_.notify(); } - void AVLTree::inOrder() { - message_ = 0; - operation_ = Operation::Traversal; - inOrderTraversal(root_); - } - - void AVLTree::preOrder() { - message_ = 0; - operation_ = Operation::Traversal; - preOrderTraversal(root_); - } - - void AVLTree::postOrder() { - message_ = 0; - operation_ = Operation::Traversal; - postOrderTraversal(root_); + void AVLTree::deleteAll(){ + clearHelp(root_); + operation_ = DeleteAll; + port_.notify(); } void AVLTree::clearHelp(Node *&node) { @@ -103,23 +91,23 @@ namespace mvc { int balanceFactor = findBalanceFactor(node); if (balanceFactor > 1 && key < node->leftCh->key) { - port_.notify(); + //port_.notify(); return rightRotate(node); } if (balanceFactor > 1 && key > node->leftCh->key) { - port_.notify(); + //port_.notify(); node->leftCh = leftRotate(node->leftCh); - port_.notify(); + //port_.notify(); return rightRotate(node); } if (balanceFactor < -1 && key > node->rightCh->key) { - port_.notify(); + //port_.notify(); return leftRotate(node); } if (balanceFactor < -1 && key < node->rightCh->key) { - port_.notify(); + //port_.notify(); node->rightCh = rightRotate(node->rightCh); - port_.notify(); + //port_.notify(); return leftRotate(node); } return node; @@ -127,7 +115,7 @@ namespace mvc { Node* AVLTree::insert(Node *node, int key) { if (node == nullptr) { - message_ = 0; + message_ = Fine; return new Node{key}; } if (key < node->key) @@ -135,7 +123,7 @@ namespace mvc { else if (key > node->key) node->rightCh = insert(node->rightCh, key); else { - message_ = 1; + message_ = AlreadyPresent; return node; } return balanceInsert(node, key); @@ -176,7 +164,7 @@ namespace mvc { return cur; } - void AVLTree::twoChildren(Node* node) + void AVLTree::deleteNodeTwoChildren(Node* node) { Node* cur = AVLTree::inorderSuccessor(node); node->key = cur->key; @@ -184,7 +172,7 @@ namespace mvc { node->rightCh = deleteNode(node->rightCh, cur->key); } - Node* AVLTree::oneZeroChildren(Node* node) + Node* AVLTree::deleteNodeOneZeroChildren(Node* node) { Node *cur = nullptr; if (node->leftCh) @@ -205,7 +193,7 @@ namespace mvc { Node* AVLTree::deleteNode(Node *node, int key) { if(node == nullptr) { - message_ = 2; + message_ = NotPresent; return node; } if(key < node->key) @@ -214,16 +202,16 @@ namespace mvc { node->rightCh = deleteNode(node->rightCh, key); else { - message_ = 0; + message_ = Fine; if(node->leftCh == nullptr || node->rightCh == nullptr) - node = oneZeroChildren(node); + node = deleteNodeOneZeroChildren(node); else - twoChildren(node); + deleteNodeTwoChildren(node); } return balanceDelete(node); } - void AVLTree::search(Node *node, int key, int& repeat) + void AVLTree::search(Node *node, int key, Message& message) { while(node != nullptr) { @@ -243,34 +231,7 @@ namespace mvc { return; } } - repeat = 2; - } - - void AVLTree::inOrderTraversal(Node *node) { - if(node == nullptr) - return; - inOrderTraversal(node->leftCh); - passing_ = node->key; - port_.notify(); - inOrderTraversal(node->rightCh); - } - - void AVLTree::preOrderTraversal(Node *node) { - if(node == nullptr) - return; - passing_ = node->key; - port_.notify(); - preOrderTraversal(node->leftCh); - preOrderTraversal(node->rightCh); - } - - void AVLTree::postOrderTraversal(Node *node) { - if(node == nullptr) - return; - postOrderTraversal(node->leftCh); - postOrderTraversal(node->rightCh); - passing_ = node->key; - port_.notify(); + message = NotPresent; } } diff --git a/Model.h b/Model.h new file mode 100644 index 0000000..342fdab --- /dev/null +++ b/Model.h @@ -0,0 +1,84 @@ +#ifndef COURSEPROJECT_MODEL_H +#define COURSEPROJECT_MODEL_H + +#include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" +#include +#include + +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_}; + }; +} + +#endif//COURSEPROJECT_MODEL_H diff --git a/View.cpp b/View.cpp index fd45d56..e1fbfd8 100644 --- a/View.cpp +++ b/View.cpp @@ -1,55 +1,78 @@ #include "View.h" -#include -#include -#include + +#include namespace mvc { - View::View() { - mainWindow = new MainWindow(); - mainWindow->setWindowTitle("AVL tree"); - mainWindow->show(); + 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(); + } - QMenuBar* menu = new QMenuBar(mainWindow); - QMenu* about = new QMenu("About"); + 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_); - 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_); - 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); + QValidator *validator = new QIntValidator(mainWindow_); + editText_ = new QLineEdit(mainWindow_); editText_->setValidator(validator); - treeSpot_ = new QGraphicsView(mainWindow); + treeSpot_ = new QGraphicsView(mainWindow_); scene_ = new CustomScene(this); scene_->setBackgroundBrush(Qt::white); treeSpot_->setScene(scene_); - slider_ = new QSlider(Qt::Horizontal, mainWindow); + slider_ = new QSlider(Qt::Horizontal, mainWindow_); slider_->setTickInterval(50); slider_->setMinimum(250); slider_->setMaximum(1250); + } - QHBoxLayout* layout = new QHBoxLayout(mainWindow); + void View::adjustMainWindow() { + QHBoxLayout* layout = new QHBoxLayout(mainWindow_); - QGroupBox* box = new QGroupBox(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); + QLabel* header1 = new QLabel(mainWindow_); header1->setText(tr("Type integer:")); buttonLayout->addWidget(header1); buttonLayout->addWidget(editText_); @@ -59,7 +82,7 @@ namespace mvc { QSpacerItem* spacer = new QSpacerItem(2, 70); buttonLayout->addSpacerItem(spacer); - QLabel* header2 = new QLabel(mainWindow); + QLabel* header2 = new QLabel(mainWindow_); header2->setText(tr("Tree traversals:")); buttonLayout->addWidget(header2); buttonLayout->addWidget(inOrderButton_); @@ -67,28 +90,59 @@ namespace mvc { buttonLayout->addWidget(postOrderButton_); buttonLayout->addSpacerItem(spacer); - QLabel* header4= new QLabel(mainWindow); + 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); + QLabel* header3 = new QLabel(mainWindow_); header3->setText(tr("Node count:")); buttonLayout->addWidget(header3); - count_ = new QLineEdit(mainWindow); + 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) @@ -105,31 +159,7 @@ namespace mvc { while (!que.empty()) { cur = que.front(); que.pop(); - - 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) - ellipse_ = scene_->addEllipse(cur->x,cur->y,RADIUS,RADIUS,redpen, cyanbrush); - else - ellipse_ = scene_->addEllipse(cur->x,cur->y,RADIUS,RADIUS,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; - } + drawNode(cur, que, count, operation, passing); } count_->setText(QString::number(count)); } @@ -143,29 +173,59 @@ namespace mvc { loop.exec(); } - void View::drawPrep(const AVLTree::Data &data) { - if(data.message == 1) + void View::draw(const AVLTree::Data &data) { + if(data.operation == AVLTree::DeleteAll) { - QMessageBox msgBox; - msgBox.setText("already present"); - msgBox.exec(); + scene_->clear(); + treeInfo_.release(); + count_->setText("0"); } - if(data.message == 2) - { - QMessageBox msgBox; - msgBox.setText("not present"); - msgBox.exec(); + 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) - delete treeInfo_; - treeInfo_ = new InfoTree(node); + if(treeInfo_ != nullptr) { + treeInfo_.release(); + } + treeInfo_ = std::make_unique(node); if(treeInfo_) { - delay(speed_); + if(!load_ && !hist_) + delay(speed_); drawTree(treeInfo_->getRoot(), data.operation, data.passing_); } } @@ -178,6 +238,58 @@ namespace mvc { editText_->setText(QString::number(temp.first)); } + void View::inOrderTraversal(Info *node) { + if(node == nullptr) + return; + inOrderTraversal(node->left); + delay(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(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(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() { speed_ = slider_->value(); operation_ = Operation::Add; @@ -206,33 +318,70 @@ namespace mvc { void View::inOrder(){ speed_ = slider_->value(); - operation_ = Operation::Traversal; - type_ = Type::InOrder; - commandPort.notify(); + if(treeInfo_ == nullptr) + return; + inOrderTraversal(treeInfo_->getRoot()); } + void View::preOrder(){ speed_ = slider_->value(); - operation_ = Operation::Traversal; - type_ = Type::PreOrder; - commandPort.notify(); + if(treeInfo_ == nullptr) + return; + preOrderTraversal(treeInfo_->getRoot()); } + void View::postOrder(){ speed_ = slider_->value(); - operation_ = Operation::Traversal; - type_ = Type::PostOrder; - commandPort.notify(); + if(treeInfo_ == nullptr) + return; + postOrderTraversal(treeInfo_->getRoot()); } void View::aboutMenu() { - secondWindow_ = new AboutWindow(); + 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); } - } diff --git a/View.h b/View.h index 3ec745e..15fecfa 100644 --- a/View.h +++ b/View.h @@ -22,6 +22,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" #include "InfoTree.h" @@ -34,17 +39,14 @@ namespace mvc { Q_OBJECT public: View(); + ~View() = default; enum class Operation { - Add, Delete, Search, Traversal - }; - enum class Type{ - InOrder, PreOrder, PostOrder + Add, Delete, Search, DeleteAll }; struct Command { int& value; Operation& operation; - Type& type; }; void subscribe(NSLibrary::CObserver *observer) { @@ -60,10 +62,22 @@ namespace mvc { 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 drawPrep(const AVLTree::Data& data); + 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(); @@ -72,6 +86,8 @@ namespace mvc { void preOrder(); void postOrder(); void aboutMenu(); + void loadMenu(); + void stepBack(); public: @@ -84,10 +100,11 @@ namespace mvc { private: View* ptr; }; - private: - MainWindow* mainWindow; - AboutWindow* secondWindow_; + static constexpr int kRadius_ = 40; + private: + MainWindow* mainWindow_; + std::unique_ptr secondWindow_; QPushButton *addButton_; QPushButton *deleteButton_; @@ -95,23 +112,24 @@ namespace mvc { QPushButton* inOrderButton_; QPushButton* preOrderButton_; QPushButton* postOrderButton_; + QPushButton* stepBackButton_; QLineEdit *editText_; QLineEdit* count_; QSlider* slider_; - QGraphicsEllipseItem* ellipse_; - QGraphicsView *treeSpot_; CustomScene* scene_; + std::vector> history_; + int value_; Operation operation_; - Type type_; - InfoTree* treeInfo_ = nullptr; + bool load_ = false; + bool hist_ = false; + std::unique_ptr treeInfo_ = nullptr; int speed_ = 750; - static constexpr int RADIUS = 40; - ObserverCommand commandPort = Command{value_, operation_, type_}; + ObserverCommand commandPort = Command{value_, operation_}; - Input observer = [this](const AVLTree::Data &data) { drawPrep(data); }; + Input observer = [this](const AVLTree::Data &data) { draw(data); }; }; } diff --git a/aboutwindow.cpp b/aboutwindow.cpp index 273e855..33a99bb 100644 --- a/aboutwindow.cpp +++ b/aboutwindow.cpp @@ -4,6 +4,118 @@ namespace mvc { AboutWindow::AboutWindow(QWidget *parent) : QDialog(parent), ui(new Ui::AboutWindow) { ui->setupUi(this); + 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("Let the newly inserted node be w \n" + "\n" + "Perform standard BST insert for w. \n" + "Starting from w, travel up and find the first unbalanced node. Let z be the first unbalanced node, y be the child of z that comes on the path from w to z and x be the grandchild of z that comes on the path from w to z. \n" + "Re-balance the tree by performing appropriate rotations on the subtree rooted with z. There can be 4 possible cases that need to be handled as x, y and z can be arranged in 4 ways.\n" + "Following are the possible 4 arrangements: \n" + "y is the left child of z and x is the left child of y (Left Left Case) \n" + "y is the left child of z and x is the right child of y (Left Right Case) \n" + "y is the right child of z and x is the right child of y (Right Right Case) \n" + "y is the right child of z and x is the left child of y (Right Left Case)\n" + "1. Left Left Case \n" + "\n" + "T1, T2, T3 and T4 are subtrees.\n" + " z y \n" + " / \\ / \\\n" + " y T4 Right Rotate (z) x z\n" + " / \\ - - - - - - - - -> / \\ / \\ \n" + " x T3 T1 T2 T3 T4\n" + " / \\\n" + " T1 T2\n" + "2. Left Right Case \n" + "\n" + " z z x\n" + " / \\ / \\ / \\ \n" + " y T4 Left Rotate (y) x T4 Right Rotate(z) y z\n" + " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" + "T1 x y T3 T1 T2 T3 T4\n" + " / \\ / \\\n" + " T2 T3 T1 T2\n" + "3. Right Right Case \n" + "\n" + " z y\n" + " / \\ / \\ \n" + "T1 y Left Rotate(z) z x\n" + " / \\ - - - - - - - -> / \\ / \\\n" + " T2 x T1 T2 T3 T4\n" + " / \\\n" + " T3 T4\n" + "4. Right Left Case \n" + "\n" + " z z x\n" + " / \\ / \\ / \\ \n" + "T1 y Right Rotate (y) T1 x Left Rotate(z) z y\n" + " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" + " x T4 T2 y T1 T2 T3 T4\n" + " / \\ / \\\n" + "T2 T3 T3 T4"); + QLabel* del = new QLabel("Deletion algorithm", this); + QTextEdit* textDel = new QTextEdit(this); + textDel->setText("Let w be the node to be deleted \n" + "\n" + "Perform standard BST delete for w. \n" + "Starting from w, travel up and find the first unbalanced node. Let z be the first unbalanced node, y be the larger height child of z, and x be the larger height child of y. Note that the definitions of x and y are different from insertion here. \n" + "Re-balance the tree by performing appropriate rotations on the subtree rooted with z. There can be 4 possible cases that needs to be handled as x, y and z can be arranged in 4 ways. Following are the possible 4 arrangements: \n" + "y is left child of z and x is left child of y (Left Left Case) \n" + "y is left child of z and x is right child of y (Left Right Case) \n" + "y is right child of z and x is right child of y (Right Right Case) \n" + "y is right child of z and x is left child of y (Right Left Case)\n" + "a) Left Left Case \n" + "\n" + "T1, T2, T3 and T4 are subtrees.\n" + " z y \n" + " / \\ / \\\n" + " y T4 Right Rotate (z) x z\n" + " / \\ - - - - - - - - -> / \\ / \\ \n" + " x T3 T1 T2 T3 T4\n" + " / \\\n" + " T1 T2\n" + "b) Left Right Case \n" + "\n" + " z z x\n" + " / \\ / \\ / \\ \n" + " y T4 Left Rotate (y) x T4 Right Rotate(z) y z\n" + " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" + "T1 x y T3 T1 T2 T3 T4\n" + " / \\ / \\\n" + " T2 T3 T1 T2\n" + "c) Right Right Case \n" + "\n" + " z y\n" + " / \\ / \\ \n" + "T1 y Left Rotate(z) z x\n" + " / \\ - - - - - - - -> / \\ / \\\n" + " T2 x T1 T2 T3 T4\n" + " / \\\n" + " T3 T4\n" + "d) Right Left Case \n" + "\n" + " z z x\n" + " / \\ / \\ / \\ \n" + "T1 y Right Rotate (y) T1 x Left Rotate(z) z y\n" + " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" + " x T4 T2 y T1 T2 T3 T4\n" + " / \\ / \\\n" + "T2 T3 T3 T4\n" + "Unlike insertion, in deletion, after we perform a rotation at z, we may have to perform a rotation at ancestors of z. Thus, we must continue to trace the path until we reach the root."); + 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() { diff --git a/aboutwindow.h b/aboutwindow.h index e13d538..6956362 100644 --- a/aboutwindow.h +++ b/aboutwindow.h @@ -2,7 +2,9 @@ #define COURSEPROJECT_ABOUTWINDOW_H #include - +#include +#include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -16,7 +18,7 @@ namespace mvc { public: explicit AboutWindow(QWidget *parent = nullptr); - ~AboutWindow() override; + ~AboutWindow(); private: Ui::AboutWindow *ui; diff --git a/main.cpp b/main.cpp index 55c9b50..763b958 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,5 @@ #include -#include "mainwindow.h" -#include "Connection.h" +#include "Application.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); diff --git a/stylesheet.qss b/stylesheet.qss new file mode 100644 index 0000000..53f41fc --- /dev/null +++ b/stylesheet.qss @@ -0,0 +1,46 @@ +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); +} + +QLineEdit { + background-color: black; + background-attachment: scroll; +} + +QWidget, !QLineEdit { + background-color: rgb(45,45,45); +} \ No newline at end of file From 21989644c17cac70f7d7b1cc70357b1444f3bd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=B9=D1=8F=20=D0=A1=D0=BF=D0=B8=D1=80=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0?= Date: Fri, 9 Jun 2023 22:31:09 +0300 Subject: [PATCH 07/10] final --- Application.cpp | 2 +- Application.h | 2 +- CMakeLists.txt | 2 - Controller.cpp | 9 ++- Controller.h | 10 +-- InfoTree.cpp | 6 +- InfoTree.h | 32 +++++----- Model.cpp | 69 ++++++++++----------- Model.h | 51 +++++++++------- View.cpp | 142 +++++++++++++++++++------------------------ View.h | 95 +++++++++++++++-------------- aboutwindow.cpp | 158 ++++++++++++++++++++---------------------------- aboutwindow.h | 4 +- main.cpp | 2 +- mainwindow.cpp | 2 +- mainwindow.h | 8 ++- stylesheet.qss | 7 +-- 17 files changed, 279 insertions(+), 322 deletions(-) diff --git a/Application.cpp b/Application.cpp index 118f202..7d072d7 100644 --- a/Application.cpp +++ b/Application.cpp @@ -5,4 +5,4 @@ namespace mvc { view_.subscribe(controller_.input()); model_.subscribe(view_.input()); } -} +}// namespace mvc diff --git a/Application.h b/Application.h index 77fc277..bc76336 100644 --- a/Application.h +++ b/Application.h @@ -13,6 +13,6 @@ namespace mvc { View view_; Controller controller_{&model_}; }; -} +}// namespace mvc #endif//COURSEPROJECT_APPLICATION_H diff --git a/CMakeLists.txt b/CMakeLists.txt index e57c482..e724d85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,7 +13,6 @@ find_package(Qt6 COMPONENTS Core Gui Widgets - Pdf REQUIRED) add_executable(CourseProject main.cpp mainwindow.cpp mainwindow.ui Model.cpp View.cpp Controller.cpp InfoTree.cpp aboutwindow.cpp Application.cpp) @@ -21,6 +20,5 @@ target_link_libraries(CourseProject Qt::Core Qt::Gui Qt::Widgets - Qt::Pdf ) diff --git a/Controller.cpp b/Controller.cpp index c4dbdb9..1e99d2a 100644 --- a/Controller.cpp +++ b/Controller.cpp @@ -6,8 +6,7 @@ namespace mvc { } void Controller::action(const ViewData &data) { - switch(data.operation) - { + switch (data.operation) { case View::Operation::Add: { model_->insert(data.value); break; @@ -16,15 +15,15 @@ namespace mvc { model_->deleteNode(data.value); break; } - case View::Operation::Search:{ + case View::Operation::Search: { model_->search(data.value); break; } - case View::Operation::DeleteAll:{ + case View::Operation::DeleteAll: { model_->deleteAll(); } default: break; } } -} +}// namespace mvc diff --git a/Controller.h b/Controller.h index c764ae2..09c4e43 100644 --- a/Controller.h +++ b/Controller.h @@ -10,19 +10,19 @@ namespace mvc { class Controller { private: using ViewData = View::Command; - using Observer = NSLibrary::CObserver; - using Input = NSLibrary::CColdInput; + using Observer = NSLibrary::CObserver; + using Input = NSLibrary::CColdInput; public: Controller(AVLTree *ptr); Observer *input() { return &observer_; } private: - void action(const ViewData& data); + void action(const ViewData &data); AVLTree *model_; Input observer_ = [this](const ViewData &data) { action(data); }; }; -} +}// namespace mvc -#endif //COURSEPROJECT_CONTROLLER_H +#endif//COURSEPROJECT_CONTROLLER_H diff --git a/InfoTree.cpp b/InfoTree.cpp index 56c08a0..6251d4d 100644 --- a/InfoTree.cpp +++ b/InfoTree.cpp @@ -48,7 +48,7 @@ namespace mvc { cur->width = 2 * std::max(cur->left->width, cur->right->width) + kWidth_; } - void InfoTree::setXCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow){ + void InfoTree::setXCoord(Info *cur, std::queue &que, int &count, int ×Next, int ×Now) { if (cur->left != nullptr) { que.push(cur->left); ++timesNext; @@ -96,7 +96,7 @@ namespace mvc { } } - void InfoTree::setYCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow) { + 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); @@ -128,4 +128,4 @@ namespace mvc { setYCoord(cur, que, count, timesNext, timesNow); } } -} +}// namespace mvc diff --git a/InfoTree.h b/InfoTree.h index d8d0ae2..29a38f2 100644 --- a/InfoTree.h +++ b/InfoTree.h @@ -1,49 +1,49 @@ #ifndef COURSEPROJECT_INFOTREE_H #define COURSEPROJECT_INFOTREE_H -#include "Model.h" #include +#include "Model.h" + namespace mvc { - struct Info{ + struct Info { int x = 0; int y = 0; int key; - Info* left; - Info* right; + Info *left; + Info *right; int width; }; class InfoTree { public: - InfoTree(const Node* rootGet) - :root_(buildInfoTree(rootGet)) - { + InfoTree(const Node *rootGet) + : root_(buildInfoTree(rootGet)) { calcYCoord(); calcXCoord(); } ~InfoTree(); - Info* getRoot() { return root_; } + Info *getRoot() { return root_; } std::pair findValue(int x, int y); - void clear(Info* treeInfo); + void clear(Info *treeInfo); private: static constexpr int kRadius_ = 40; static constexpr int kHeight_ = 30; static constexpr int kWidth_ = 15; + private: -// void clear(Info* treeInfo); - void setWidth(Info* cur); - void setXCoord(Info* cur, std::queue& que, int& count, int& timesNext, int& timesNow); + 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& timesNext, int& timesNow); + Info *buildInfoTree(const Node *node); + void setYCoord(Info *cur, std::queue &que, int &count, int ×Next, int ×Now); void calcYCoord(); - Info* root_ = nullptr; + Info *root_ = nullptr; }; -} +}// namespace mvc #endif//COURSEPROJECT_INFOTREE_H diff --git a/Model.cpp b/Model.cpp index 11bcb35..7d74cb7 100644 --- a/Model.cpp +++ b/Model.cpp @@ -8,6 +8,7 @@ namespace mvc { void AVLTree::insert(int key) { operation_ = Add; + message_ = Fine; root_ = insert(root_, key); if (root_ == nullptr) return; @@ -16,6 +17,7 @@ namespace mvc { void AVLTree::deleteNode(int key) { operation_ = Delete; + message_ = Fine; root_ = deleteNode(root_, key); port_.notify(); } @@ -24,14 +26,15 @@ namespace mvc { operation_ = Search; message_ = Fine; search(root_, key, message_); - if(message_ == 2) + if (message_ == 2) operation_ = Add; port_.notify(); } - void AVLTree::deleteAll(){ + void AVLTree::deleteAll() { clearHelp(root_); operation_ = DeleteAll; + message_ = Fine; port_.notify(); } @@ -75,7 +78,7 @@ namespace mvc { return l - r; } - int AVLTree::getHeight(const Node* node) { + int AVLTree::getHeight(const Node *node) { if (node == nullptr) return 0; return node->height; @@ -85,35 +88,34 @@ namespace mvc { return std::max(getHeight(left), getHeight(right)); } - Node* AVLTree::balanceInsert(Node* node, int key) - { + 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(); + port_.notify(); return rightRotate(node); } if (balanceFactor > 1 && key > node->leftCh->key) { - //port_.notify(); + port_.notify(); node->leftCh = leftRotate(node->leftCh); - //port_.notify(); + port_.notify(); return rightRotate(node); } if (balanceFactor < -1 && key > node->rightCh->key) { - //port_.notify(); + port_.notify(); return leftRotate(node); } if (balanceFactor < -1 && key < node->rightCh->key) { - //port_.notify(); + port_.notify(); node->rightCh = rightRotate(node->rightCh); - //port_.notify(); + port_.notify(); return leftRotate(node); } return node; } - Node* AVLTree::insert(Node *node, int key) { + Node *AVLTree::insert(Node *node, int key) { if (node == nullptr) { message_ = Fine; return new Node{key}; @@ -129,7 +131,7 @@ namespace mvc { return balanceInsert(node, key); } - Node* AVLTree::balanceDelete(Node *node) { + Node *AVLTree::balanceDelete(Node *node) { if (node == nullptr) return node; node->height = findMaxHeight(node->rightCh, node->leftCh) + 1; @@ -164,16 +166,14 @@ namespace mvc { return cur; } - void AVLTree::deleteNodeTwoChildren(Node* node) - { - Node* cur = AVLTree::inorderSuccessor(node); + 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 *AVLTree::deleteNodeOneZeroChildren(Node *node) { Node *cur = nullptr; if (node->leftCh) cur = node->leftCh; @@ -182,28 +182,25 @@ namespace mvc { if (cur == nullptr) { cur = node; node = nullptr; - } - else + } else *node = *cur; port_.notify(); delete cur; return node; } - Node* AVLTree::deleteNode(Node *node, int key) - { - if(node == nullptr) { + Node *AVLTree::deleteNode(Node *node, int key) { + if (node == nullptr) { message_ = NotPresent; return node; } - if(key < node->key) + if (key < node->key) node->leftCh = deleteNode(node->leftCh, key); - else if(key > node->key) + else if (key > node->key) node->rightCh = deleteNode(node->rightCh, key); - else - { + else { message_ = Fine; - if(node->leftCh == nullptr || node->rightCh == nullptr) + if (node->leftCh == nullptr || node->rightCh == nullptr) node = deleteNodeOneZeroChildren(node); else deleteNodeTwoChildren(node); @@ -211,21 +208,17 @@ namespace mvc { return balanceDelete(node); } - void AVLTree::search(Node *node, int key, Message& message) - { - while(node != nullptr) - { - if(node->key < key) { + 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) { + } else if (node->key > key) { passing_ = node->key; port_.notify(); node = node->leftCh; - } - else{ + } else { passing_ = key; port_.notify(); return; @@ -234,4 +227,4 @@ namespace mvc { message = NotPresent; } -} +}// namespace mvc diff --git a/Model.h b/Model.h index 342fdab..2a0711e 100644 --- a/Model.h +++ b/Model.h @@ -1,10 +1,11 @@ #ifndef COURSEPROJECT_MODEL_H #define COURSEPROJECT_MODEL_H -#include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" #include #include +#include "Observer/Observer.h" + namespace mvc { struct Node { int key; @@ -16,13 +17,13 @@ namespace mvc { class AVLTree { public: AVLTree() - : root_(nullptr){} + : root_(nullptr) {} ~AVLTree(); - AVLTree(const AVLTree&) = delete; - AVLTree(AVLTree&&) noexcept = delete; - AVLTree& operator=(const AVLTree&) = delete; - AVLTree& operator=(AVLTree&&) noexcept = delete; + 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); @@ -30,17 +31,23 @@ namespace mvc { void deleteAll(); public: - enum Operation{ - Add, Delete, Search, Traversal, DeleteAll + enum Operation { + Add, + Delete, + Search, + Traversal, + DeleteAll }; - enum Message{ - Fine, NotPresent, AlreadyPresent + enum Message { + Fine, + NotPresent, + AlreadyPresent }; struct Data { - Node*& value; - Message& message; - Operation& operation; - int& passing_; + Node *&value; + Message &message; + Operation &operation; + int &passing_; }; private: @@ -49,18 +56,18 @@ namespace mvc { Node *leftRotate(Node *node); Node *rightRotate(Node *node); int findBalanceFactor(Node *node); - static int getHeight(const 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 *balanceInsert(Node *node, int key); + Node *insert(Node *node, int key); - Node* balanceDelete(Node* node); + Node *balanceDelete(Node *node); Node *inorderSuccessor(Node *x); - void deleteNodeTwoChildren(Node* node); - Node* deleteNodeOneZeroChildren(Node* node); + void deleteNodeTwoChildren(Node *node); + Node *deleteNodeOneZeroChildren(Node *node); Node *deleteNode(Node *node, int key); - void search(Node* node, int key, Message& message); + void search(Node *node, int key, Message &message); private: using Observer = NSLibrary::CObserver; @@ -79,6 +86,6 @@ namespace mvc { int passing_; Observable port_ = Data{root_, message_, operation_, passing_}; }; -} +}// namespace mvc #endif//COURSEPROJECT_MODEL_H diff --git a/View.cpp b/View.cpp index e1fbfd8..b94e6a4 100644 --- a/View.cpp +++ b/View.cpp @@ -1,11 +1,8 @@ #include "View.h" -#include - namespace mvc { - View::View() - { + View::View() { mainWindow_ = new MainWindow(); addMenu(); addControlPannel(); @@ -21,12 +18,12 @@ namespace mvc { 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); + 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); @@ -63,16 +60,16 @@ namespace mvc { } void View::adjustMainWindow() { - QHBoxLayout* layout = new QHBoxLayout(mainWindow_); + QHBoxLayout *layout = new QHBoxLayout(mainWindow_); - QGroupBox* box = new QGroupBox(mainWindow_); - QVBoxLayout* buttonLayout = new QVBoxLayout(box); + 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_); + QLabel *header1 = new QLabel(mainWindow_); header1->setText(tr("Type integer:")); buttonLayout->addWidget(header1); buttonLayout->addWidget(editText_); @@ -80,9 +77,9 @@ namespace mvc { buttonLayout->addWidget(deleteButton_); buttonLayout->addWidget(searchButton_); - QSpacerItem* spacer = new QSpacerItem(2, 70); + QSpacerItem *spacer = new QSpacerItem(2, 70); buttonLayout->addSpacerItem(spacer); - QLabel* header2 = new QLabel(mainWindow_); + QLabel *header2 = new QLabel(mainWindow_); header2->setText(tr("Tree traversals:")); buttonLayout->addWidget(header2); buttonLayout->addWidget(inOrderButton_); @@ -90,14 +87,14 @@ namespace mvc { buttonLayout->addWidget(postOrderButton_); buttonLayout->addSpacerItem(spacer); - QLabel* header4= new QLabel(mainWindow_); + 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_); + QLabel *header3 = new QLabel(mainWindow_); header3->setText(tr("Node count:")); buttonLayout->addWidget(header3); count_ = new QLineEdit(mainWindow_); @@ -118,17 +115,17 @@ namespace mvc { QObject::connect(stepBackButton_, SIGNAL(clicked(bool)), this, SLOT(stepBack())); } - void View::drawNode(Info *cur, std::queue& que, int &count, AVLTree::Operation& operation, int passing) { + 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); + 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); + 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); @@ -145,16 +142,15 @@ namespace mvc { } } - void View::drawTree(Info* treeInfo, AVLTree::Operation& operation, int passing) - { + void View::drawTree(Info *treeInfo, AVLTree::Operation &operation, int passing) { scene_->clear(); scene_->setBackgroundBrush(Qt::white); - Info* root = treeInfo; + Info *root = treeInfo; if (root == nullptr) return; - std::queue que; + std::queue que; que.push(root); - Info* cur; + Info *cur; int count = 1; while (!que.empty()) { cur = que.front(); @@ -164,8 +160,7 @@ namespace mvc { count_->setText(QString::number(count)); } - void delay(int millisecondsWait) - { + void delay(int millisecondsWait) { QTimer t; QEventLoop loop; t.connect(&t, &QTimer::timeout, &loop, &QEventLoop::quit); @@ -174,26 +169,23 @@ namespace mvc { } void View::draw(const AVLTree::Data &data) { - if(data.operation == AVLTree::DeleteAll) - { + if (data.operation == AVLTree::DeleteAll) { scene_->clear(); treeInfo_.release(); count_->setText("0"); } - if(!hist_){ - switch(data.message){ - case AVLTree::AlreadyPresent: - { - if(load_) + 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_) + case AVLTree::NotPresent: { + if (load_) return; QMessageBox msgBox; msgBox.setText("not present"); @@ -204,9 +196,9 @@ namespace mvc { break; } } - if(history_.size() == 0) + if (history_.size() == 0) history_.push_back({value_, Operation::Add}); - else if(!hist_ && history_[history_.size() - 1] != std::make_pair(value_, operation_)) { + 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) @@ -219,39 +211,38 @@ namespace mvc { count_->setText("0"); return; } - if(treeInfo_ != nullptr) { + if (treeInfo_ != nullptr) { treeInfo_.release(); } treeInfo_ = std::make_unique(node); - if(treeInfo_) { - if(!load_ && !hist_) - delay(speed_); + if (treeInfo_) { + if (!load_ && !hist_) + delay(1500 - slider_->value()); drawTree(treeInfo_->getRoot(), data.operation, data.passing_); } } - void View::deleteClicked(QPointF& point) { - speed_ = slider_->value(); + void View::deleteClicked(QPointF &point) { std::pair temp = treeInfo_->findValue(point.x(), point.y()); - if(!temp.second) + if (!temp.second) return; editText_->setText(QString::number(temp.first)); } void View::inOrderTraversal(Info *node) { - if(node == nullptr) + if (node == nullptr) return; inOrderTraversal(node->left); - delay(slider_->value()); + 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) + if (node == nullptr) return; - delay(slider_->value()); + delay(1500 - slider_->value()); AVLTree::Operation op = AVLTree::Operation::Traversal; drawTree(treeInfo_->getRoot(), op, node->key); preOrderTraversal(node->left); @@ -259,30 +250,29 @@ namespace mvc { } void View::postOrderTraversal(Info *node) { - if(node == nullptr) + if (node == nullptr) return; postOrderTraversal(node->left); postOrderTraversal(node->right); - delay(slider_->value()); + delay(1500 - slider_->value()); AVLTree::Operation op = AVLTree::Operation::Traversal; drawTree(treeInfo_->getRoot(), op, node->key); } - void View::getDataFromFile(QString& fileName) { + 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()) - { + while (!inputStream.atEnd()) { QString line = inputStream.readLine(); QList temp = line.split(" "); - for (QString& item : temp) { + for (QString &item: temp) { bool convert; value_ = item.toInt(&convert); - if(!convert) + if (!convert) continue; commandPort.notify(); } @@ -291,9 +281,8 @@ namespace mvc { } void View::addNode() { - speed_ = slider_->value(); operation_ = Operation::Add; - if(editText_->text().size() == 0) + if (editText_->text().size() == 0) return; value_ = editText_->text().toInt(); editText_->clear(); @@ -301,7 +290,6 @@ namespace mvc { } void View::deleteNode() { - speed_ = slider_->value(); operation_ = Operation::Delete; value_ = editText_->text().toInt(); editText_->clear(); @@ -309,30 +297,26 @@ namespace mvc { } void View::searchNode() { - speed_ = slider_->value(); operation_ = Operation::Search; value_ = editText_->text().toInt(); editText_->clear(); commandPort.notify(); } - void View::inOrder(){ - speed_ = slider_->value(); - if(treeInfo_ == nullptr) + void View::inOrder() { + if (treeInfo_ == nullptr) return; inOrderTraversal(treeInfo_->getRoot()); } - void View::preOrder(){ - speed_ = slider_->value(); - if(treeInfo_ == nullptr) + void View::preOrder() { + if (treeInfo_ == nullptr) return; preOrderTraversal(treeInfo_->getRoot()); } - void View::postOrder(){ - speed_ = slider_->value(); - if(treeInfo_ == nullptr) + void View::postOrder() { + if (treeInfo_ == nullptr) return; postOrderTraversal(treeInfo_->getRoot()); } @@ -347,7 +331,7 @@ namespace mvc { QString fileName = QFileDialog::getOpenFileName(mainWindow_, "Open File", "../", "Data (*.txt)"); - if(fileName.isEmpty()) + if (fileName.isEmpty()) return; operation_ = Operation::DeleteAll; commandPort.notify(); @@ -359,29 +343,27 @@ namespace mvc { } void View::stepBack() { - if(history_.size() == 1) + 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) - { + for (size_t i = 1; i < history_.size(); ++i) { value_ = history_[i].first; operation_ = history_[i].second; commandPort.notify(); } - if(history_.size() == 1) { + if (history_.size() == 1) { scene_->clear(); count_->setText("0"); } hist_ = false; } - void View::CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event){ + void View::CustomScene::mousePressEvent(QGraphicsSceneMouseEvent *event) { QPointF pos = event->scenePos(); ptr->deleteClicked(pos); } -} - +}// namespace mvc diff --git a/View.h b/View.h index 15fecfa..8469302 100644 --- a/View.h +++ b/View.h @@ -1,52 +1,55 @@ #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 -#include -#include -#include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include -#include "/Users/mayyaspirina/Documents/CourseProject/Observer/Observer.h" #include "InfoTree.h" -#include "mainwindow.h" +#include "Observer/Observer.h" #include "aboutwindow.h" +#include "mainwindow.h" namespace mvc { - class View : public QGraphicsScene{ + class View : public QGraphicsScene { Q_OBJECT public: View(); ~View() = default; enum class Operation { - Add, Delete, Search, DeleteAll + Add, + Delete, + Search, + DeleteAll }; struct Command { - int& value; - Operation& operation; + int &value; + Operation &operation; }; void subscribe(NSLibrary::CObserver *observer) { @@ -58,8 +61,9 @@ namespace mvc { using ObserverAVL = NSLibrary::CObserver; using ObserverCommand = NSLibrary::CObservableData; using Input = NSLibrary::CHotInput; + public: - ObserverAVL* input() { return &observer; } + ObserverAVL *input() { return &observer; } private: void addMenu(); @@ -67,16 +71,16 @@ namespace mvc { 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 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 inOrderTraversal(Info *node); + void preOrderTraversal(Info *node); + void postOrderTraversal(Info *node); - void getDataFromFile(QString& fileName); + void getDataFromFile(QString &fileName); private slots: void addNode(); @@ -90,34 +94,36 @@ namespace mvc { void stepBack(); public: - - class CustomScene : public QGraphicsScene{ + class CustomScene : public QGraphicsScene { public: - CustomScene(View* ptr) - :ptr(ptr){}; + CustomScene(View *ptr) + : ptr(ptr){}; ~CustomScene() = default; virtual void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + private: - View* ptr; + View *ptr; }; + private: static constexpr int kRadius_ = 40; + private: - MainWindow* mainWindow_; + MainWindow *mainWindow_; std::unique_ptr secondWindow_; QPushButton *addButton_; QPushButton *deleteButton_; - QPushButton* searchButton_; - QPushButton* inOrderButton_; - QPushButton* preOrderButton_; - QPushButton* postOrderButton_; - QPushButton* stepBackButton_; + QPushButton *searchButton_; + QPushButton *inOrderButton_; + QPushButton *preOrderButton_; + QPushButton *postOrderButton_; + QPushButton *stepBackButton_; QLineEdit *editText_; - QLineEdit* count_; - QSlider* slider_; + QLineEdit *count_; + QSlider *slider_; QGraphicsView *treeSpot_; - CustomScene* scene_; + CustomScene *scene_; std::vector> history_; @@ -126,11 +132,10 @@ namespace mvc { bool load_ = false; bool hist_ = false; std::unique_ptr treeInfo_ = nullptr; - int speed_ = 750; 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 index 33a99bb..cd3e058 100644 --- a/aboutwindow.cpp +++ b/aboutwindow.cpp @@ -4,113 +4,87 @@ namespace mvc { AboutWindow::AboutWindow(QWidget *parent) : QDialog(parent), ui(new Ui::AboutWindow) { ui->setupUi(this); - QTextEdit* text = new QTextEdit(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("Let the newly inserted node be w \n" + 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" - "Perform standard BST insert for w. \n" - "Starting from w, travel up and find the first unbalanced node. Let z be the first unbalanced node, y be the child of z that comes on the path from w to z and x be the grandchild of z that comes on the path from w to z. \n" - "Re-balance the tree by performing appropriate rotations on the subtree rooted with z. There can be 4 possible cases that need to be handled as x, y and z can be arranged in 4 ways.\n" - "Following are the possible 4 arrangements: \n" - "y is the left child of z and x is the left child of y (Left Left Case) \n" - "y is the left child of z and x is the right child of y (Left Right Case) \n" - "y is the right child of z and x is the right child of y (Right Right Case) \n" - "y is the right child of z and x is the left child of y (Right Left Case)\n" - "1. Left Left Case \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" - "T1, T2, T3 and T4 are subtrees.\n" - " z y \n" - " / \\ / \\\n" - " y T4 Right Rotate (z) x z\n" - " / \\ - - - - - - - - -> / \\ / \\ \n" - " x T3 T1 T2 T3 T4\n" - " / \\\n" - " T1 T2\n" - "2. Left Right Case \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" - " z z x\n" - " / \\ / \\ / \\ \n" - " y T4 Left Rotate (y) x T4 Right Rotate(z) y z\n" - " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" - "T1 x y T3 T1 T2 T3 T4\n" - " / \\ / \\\n" - " T2 T3 T1 T2\n" - "3. Right Right Case \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" - " z y\n" - " / \\ / \\ \n" - "T1 y Left Rotate(z) z x\n" - " / \\ - - - - - - - -> / \\ / \\\n" - " T2 x T1 T2 T3 T4\n" - " / \\\n" - " T3 T4\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" - " z z x\n" - " / \\ / \\ / \\ \n" - "T1 y Right Rotate (y) T1 x Left Rotate(z) z y\n" - " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" - " x T4 T2 y T1 T2 T3 T4\n" - " / \\ / \\\n" - "T2 T3 T3 T4"); - QLabel* del = new QLabel("Deletion algorithm", this); - QTextEdit* textDel = new QTextEdit(this); - textDel->setText("Let w be the node to be deleted \n" - "\n" - "Perform standard BST delete for w. \n" - "Starting from w, travel up and find the first unbalanced node. Let z be the first unbalanced node, y be the larger height child of z, and x be the larger height child of y. Note that the definitions of x and y are different from insertion here. \n" - "Re-balance the tree by performing appropriate rotations on the subtree rooted with z. There can be 4 possible cases that needs to be handled as x, y and z can be arranged in 4 ways. Following are the possible 4 arrangements: \n" - "y is left child of z and x is left child of y (Left Left Case) \n" - "y is left child of z and x is right child of y (Left Right Case) \n" - "y is right child of z and x is right child of y (Right Right Case) \n" - "y is right child of z and x is left child of y (Right Left Case)\n" - "a) Left Left Case \n" - "\n" - "T1, T2, T3 and T4 are subtrees.\n" - " z y \n" - " / \\ / \\\n" - " y T4 Right Rotate (z) x z\n" - " / \\ - - - - - - - - -> / \\ / \\ \n" - " x T3 T1 T2 T3 T4\n" - " / \\\n" - " T1 T2\n" - "b) Left Right Case \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" - " z z x\n" - " / \\ / \\ / \\ \n" - " y T4 Left Rotate (y) x T4 Right Rotate(z) y z\n" - " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" - "T1 x y T3 T1 T2 T3 T4\n" - " / \\ / \\\n" - " T2 T3 T1 T2\n" - "c) Right Right Case \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" - " z y\n" - " / \\ / \\ \n" - "T1 y Left Rotate(z) z x\n" - " / \\ - - - - - - - -> / \\ / \\\n" - " T2 x T1 T2 T3 T4\n" - " / \\\n" - " T3 T4\n" - "d) Right Left Case \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" - " z z x\n" - " / \\ / \\ / \\ \n" - "T1 y Right Rotate (y) T1 x Left Rotate(z) z y\n" - " / \\ - - - - - - - - -> / \\ - - - - - - - -> / \\ / \\\n" - " x T4 T2 y T1 T2 T3 T4\n" - " / \\ / \\\n" - "T2 T3 T3 T4\n" - "Unlike insertion, in deletion, after we perform a rotation at z, we may have to perform a rotation at ancestors of z. Thus, we must continue to trace the path until we reach the root."); + + "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); + QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(text); layout->addWidget(labelIns); layout->addWidget(textIns); @@ -121,4 +95,4 @@ namespace mvc { AboutWindow::~AboutWindow() { delete ui; } -} +}// namespace mvc diff --git a/aboutwindow.h b/aboutwindow.h index 6956362..bf3510e 100644 --- a/aboutwindow.h +++ b/aboutwindow.h @@ -2,9 +2,9 @@ #define COURSEPROJECT_ABOUTWINDOW_H #include +#include #include #include -#include QT_BEGIN_NAMESPACE namespace Ui { @@ -23,7 +23,7 @@ namespace mvc { private: Ui::AboutWindow *ui; }; -} +}// namespace mvc #endif//COURSEPROJECT_ABOUTWINDOW_H diff --git a/main.cpp b/main.cpp index 763b958..635fb8e 100644 --- a/main.cpp +++ b/main.cpp @@ -1,5 +1,5 @@ -#include #include "Application.h" +#include int main(int argc, char *argv[]) { QApplication a(argc, argv); diff --git a/mainwindow.cpp b/mainwindow.cpp index 250f9f6..6e58945 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -12,4 +12,4 @@ namespace mvc { delete ui; } -} +}// namespace mvc diff --git a/mainwindow.h b/mainwindow.h index 322562e..c29c3a6 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -5,7 +5,9 @@ QT_BEGIN_NAMESPACE -namespace Ui { class MainWindow; } +namespace Ui { + class MainWindow; +} QT_END_NAMESPACE namespace mvc { @@ -20,6 +22,6 @@ namespace mvc { private: Ui::MainWindow *ui; }; -} +}// namespace mvc -#endif //COURSEPROJECT_MAINWINDOW_H +#endif//COURSEPROJECT_MAINWINDOW_H diff --git a/stylesheet.qss b/stylesheet.qss index 53f41fc..cb3878c 100644 --- a/stylesheet.qss +++ b/stylesheet.qss @@ -36,11 +36,8 @@ 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); } -QLineEdit { - background-color: black; - background-attachment: scroll; -} QWidget, !QLineEdit { background-color: rgb(45,45,45); -} \ No newline at end of file +} + From 48f0ba639d75efaba89dc00255666e9f543f650c Mon Sep 17 00:00:00 2001 From: spirinamayya <90619187+spirinamayya@users.noreply.github.com> Date: Fri, 9 Jun 2023 23:10:20 +0300 Subject: [PATCH 08/10] Create README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..eeb0d71 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# 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) +# 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). From f3e3f12efaea8a0047a970c5d7328f1e4fbd88a4 Mon Sep 17 00:00:00 2001 From: spirinamayya <90619187+spirinamayya@users.noreply.github.com> Date: Fri, 9 Jun 2023 23:10:38 +0300 Subject: [PATCH 09/10] Add files via upload --- interface.png | Bin 0 -> 174187 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 interface.png diff --git a/interface.png b/interface.png new file mode 100644 index 0000000000000000000000000000000000000000..c3296cad0db5ab3eabb4ab5d211688a3c8cbce05 GIT binary patch literal 174187 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

y{Es&mM(Yilf&V^1f*FbdP}U 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


simg>{a-O$Z)J!*EU$Ek?;D|BZ<9e`S$A+ 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 Ko7}Hm&ipsMBD;(L literal 0 HcmV?d00001 From 0f95f34811ef1c6c951dd306f2532725d308549f Mon Sep 17 00:00:00 2001 From: spirinamayya <90619187+spirinamayya@users.noreply.github.com> Date: Fri, 9 Jun 2023 23:14:37 +0300 Subject: [PATCH 10/10] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eeb0d71..ec3aaf9 100644 --- a/README.md +++ b/README.md @@ -5,5 +5,7 @@ What is more, to improve user's experience step back option and change of speed 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).