From 20ce168712582b9057d55d6744f92e135f46e738 Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Sun, 31 May 2015 22:52:16 +0200 Subject: [PATCH] ui: scroll to code snippet containing clicked edge This change scrolls animated to the snippet representing the clicked edge instead of showing only this code snippet. --- src/app/qt/element/QtCodeArea.cpp | 23 +++- src/app/qt/element/QtCodeArea.h | 2 + src/app/qt/element/QtCodeFile.cpp | 13 +++ src/app/qt/element/QtCodeFile.h | 6 +- src/app/qt/element/QtCodeFileList.cpp | 103 +++++++++++++++++- src/app/qt/element/QtCodeFileList.h | 10 ++ src/app/qt/element/QtCodeSnippet.cpp | 5 + src/app/qt/element/QtCodeSnippet.h | 2 + src/app/qt/view/QtCodeView.cpp | 26 +++-- src/app/qt/view/QtCodeView.h | 5 + .../component/controller/CodeController.cpp | 6 + src/lib/component/view/CodeView.h | 2 + 12 files changed, 187 insertions(+), 16 deletions(-) diff --git a/src/app/qt/element/QtCodeArea.cpp b/src/app/qt/element/QtCodeArea.cpp index 06f3cd79..41097dc1 100644 --- a/src/app/qt/element/QtCodeArea.cpp +++ b/src/app/qt/element/QtCodeArea.cpp @@ -148,6 +148,21 @@ void QtCodeArea::updateContent() annotateText(); } +bool QtCodeArea::isActive() const +{ + const std::vector& ids = m_fileWidget->getActiveTokenIds(); + + for (const Annotation& annotation: m_annotations) + { + if (std::find(ids.begin(), ids.end(), annotation.tokenId) != ids.end()) + { + return true; + } + } + + return false; +} + void QtCodeArea::resizeEvent(QResizeEvent *e) { QPlainTextEdit::resizeEvent(e); @@ -167,7 +182,6 @@ void QtCodeArea::showEvent(QShowEvent* event) void QtCodeArea::paintEvent(QPaintEvent* event) { QPainter painter(viewport()); - // painter.fillRect(rect(), Qt::white); QTextBlock block = firstVisibleBlock(); int top = blockBoundingGeometry(block).translated(contentOffset()).top(); @@ -177,7 +191,7 @@ void QtCodeArea::paintEvent(QPaintEvent* event) for (const ScopeAnnotation& scope : m_scopeAnnotations) { - if (scope.isFocused) + if (scope.isFocused || scope.startLine == scope.endLine) { qColor = QColor("#90E4EEF2"); } @@ -410,7 +424,7 @@ void QtCodeArea::annotateText() selection.cursor.setPosition(annotation.end, QTextCursor::KeepAnchor); scopeAnnotation.endLine = selection.cursor.blockNumber(); - if (annotation.isScope) + if (annotation.isScope || (isActive && ids.size() == 1)) { if (isActive || isFocused) { @@ -422,7 +436,8 @@ void QtCodeArea::annotateText() scopeAnnotations.push_back(scopeAnnotation); } } - else + + if (!annotation.isScope || (isActive && ids.size() == 1)) { extraSelections.append(selection); } diff --git a/src/app/qt/element/QtCodeArea.h b/src/app/qt/element/QtCodeArea.h index 3e35a634..bd5c9354 100644 --- a/src/app/qt/element/QtCodeArea.h +++ b/src/app/qt/element/QtCodeArea.h @@ -58,6 +58,8 @@ public: void updateContent(); + bool isActive() const; + protected: virtual void resizeEvent(QResizeEvent *event); virtual void showEvent(QShowEvent* event); diff --git a/src/app/qt/element/QtCodeFile.cpp b/src/app/qt/element/QtCodeFile.cpp index 674ae03c..7f02b500 100644 --- a/src/app/qt/element/QtCodeFile.cpp +++ b/src/app/qt/element/QtCodeFile.cpp @@ -143,6 +143,19 @@ void QtCodeFile::addCodeSnippet( clickedSnippetButton(); } +QWidget* QtCodeFile::findFirstActiveSnippet() const +{ + for (std::shared_ptr snippet : m_snippets) + { + if (snippet->isActive()) + { + return snippet.get(); + } + } + + return nullptr; +} + void QtCodeFile::updateContent() { for (std::shared_ptr snippet : m_snippets) diff --git a/src/app/qt/element/QtCodeFile.h b/src/app/qt/element/QtCodeFile.h index 6d3b5309..d0fe6e27 100644 --- a/src/app/qt/element/QtCodeFile.h +++ b/src/app/qt/element/QtCodeFile.h @@ -37,12 +37,16 @@ public: std::shared_ptr locationFile ); + QWidget* findFirstActiveSnippet() const; + void updateContent(); +public slots: + void clickedSnippetButton(); + private slots: void clickedTitle(); void clickedMinimizeButton(); - void clickedSnippetButton(); void clickedMaximizeButton(); private: diff --git a/src/app/qt/element/QtCodeFileList.cpp b/src/app/qt/element/QtCodeFileList.cpp index 9debbc9d..7ae22c4b 100644 --- a/src/app/qt/element/QtCodeFileList.cpp +++ b/src/app/qt/element/QtCodeFileList.cpp @@ -1,6 +1,8 @@ #include "qt/element/QtCodeFileList.h" +#include #include +#include #include #include "utility/file/FileSystem.h" @@ -23,6 +25,8 @@ QtCodeFileList::QtCodeFileList(QWidget* parent) setWidgetResizable(true); setWidget(m_frame.get()); + + connect(this, SIGNAL(shouldScrollToSnippet(QWidget*)), this, SLOT(scrollToSnippet(QWidget*)), Qt::QueuedConnection); } QtCodeFileList::~QtCodeFileList() @@ -83,7 +87,6 @@ const std::vector& QtCodeFileList::getActiveTokenIds() const void QtCodeFileList::setActiveTokenIds(const std::vector& activeTokenIds) { m_activeTokenIds = activeTokenIds; - updateFiles(); } const std::vector& QtCodeFileList::getErrorMessages() const @@ -94,14 +97,26 @@ const std::vector& QtCodeFileList::getErrorMessages() const void QtCodeFileList::setErrorMessages(const std::vector& errorMessages) { m_errorMessages = errorMessages; - updateFiles(); } -void QtCodeFileList::updateFiles() +void QtCodeFileList::scrollToFirstActiveSnippet() { + updateFiles(); + + QWidget* widget = nullptr; for (std::shared_ptr file: m_files) { - file->updateContent(); + widget = file->findFirstActiveSnippet(); + if (widget) + { + if (!widget->isVisible()) + { + file->clickedSnippetButton(); + } + + emit shouldScrollToSnippet(widget); + return; + } } } @@ -116,3 +131,83 @@ void QtCodeFileList::defocusToken() m_focusedTokenId = 0; updateFiles(); } + +void QtCodeFileList::scrollToSnippet(QWidget* widget) +{ + this->ensureWidgetVisibleAnimated(widget); +} + +void QtCodeFileList::updateFiles() +{ + for (std::shared_ptr file: m_files) + { + file->updateContent(); + } +} + +void QtCodeFileList::ensureWidgetVisibleAnimated(QWidget *childWidget, int xmargin, int ymargin) +{ + if (!widget()->isAncestorOf(childWidget)) + { + return; + } + + const QRect microFocus = childWidget->inputMethodQuery(Qt::ImCursorRectangle).toRect(); + const QRect defaultMicroFocus = childWidget->QWidget::inputMethodQuery(Qt::ImCursorRectangle).toRect(); + QRect focusRect = (microFocus != defaultMicroFocus) + ? QRect(childWidget->mapTo(widget(), microFocus.topLeft()), microFocus.size()) + : QRect(childWidget->mapTo(widget(), QPoint(0, 0)), childWidget->size()); + const QRect visibleRect(-widget()->pos(), viewport()->size()); + + if (visibleRect.contains(focusRect)) + { + return; + } + + focusRect.adjust(-xmargin, -ymargin, xmargin, ymargin); + + QScrollBar* scrollBar = nullptr; + int value = 0; + + if (focusRect.width() > visibleRect.width()) + { + scrollBar = horizontalScrollBar(); + value = focusRect.center().x() - viewport()->width() / 2; + } + else if (focusRect.right() > visibleRect.right()) + { + scrollBar = horizontalScrollBar(); + value = focusRect.right() - viewport()->width(); + } + else if (focusRect.left() < visibleRect.left()) + { + scrollBar = horizontalScrollBar(); + value = focusRect.left(); + } + + if (focusRect.height() > visibleRect.height()) + { + scrollBar = verticalScrollBar(); + value = focusRect.center().y() - viewport()->height() / 2; + } + else if (focusRect.bottom() > visibleRect.bottom()) + { + scrollBar = verticalScrollBar(); + value = focusRect.bottom() - viewport()->height(); + } + else if (focusRect.top() < visibleRect.top()) + { + scrollBar = verticalScrollBar(); + value = focusRect.top(); + } + + if (scrollBar) + { + QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value"); + anim->setDuration(std::abs(scrollBar->value() - value)); + anim->setStartValue(scrollBar->value()); + anim->setEndValue(value); + anim->setEasingCurve(QEasingCurve::OutQuad); + anim->start(); + } +} diff --git a/src/app/qt/element/QtCodeFileList.h b/src/app/qt/element/QtCodeFileList.h index 1b6abe5b..2116ce36 100644 --- a/src/app/qt/element/QtCodeFileList.h +++ b/src/app/qt/element/QtCodeFileList.h @@ -17,6 +17,9 @@ class QtCodeFileList { Q_OBJECT +signals: + void shouldScrollToSnippet(QWidget* widget); + public: QtCodeFileList(QWidget* parent = 0); virtual ~QtCodeFileList(); @@ -40,12 +43,19 @@ public: const std::vector& getErrorMessages() const; void setErrorMessages(const std::vector& errorMessages); + void scrollToFirstActiveSnippet(); + void focusToken(Id tokenId); void defocusToken(); +private slots: + void scrollToSnippet(QWidget* widget); + private: void updateFiles(); + void ensureWidgetVisibleAnimated(QWidget *childWidget, int xmargin = 50, int ymargin = 50); + std::shared_ptr m_frame; std::vector> m_files; diff --git a/src/app/qt/element/QtCodeSnippet.cpp b/src/app/qt/element/QtCodeSnippet.cpp index 26843b38..55c42cf2 100644 --- a/src/app/qt/element/QtCodeSnippet.cpp +++ b/src/app/qt/element/QtCodeSnippet.cpp @@ -71,6 +71,11 @@ void QtCodeSnippet::updateContent() updateDots(); } +bool QtCodeSnippet::isActive() const +{ + return m_codeArea->isActive(); +} + void QtCodeSnippet::updateDots() { if (!m_dots) diff --git a/src/app/qt/element/QtCodeSnippet.h b/src/app/qt/element/QtCodeSnippet.h index d5881b4d..5e1f29c0 100644 --- a/src/app/qt/element/QtCodeSnippet.h +++ b/src/app/qt/element/QtCodeSnippet.h @@ -34,6 +34,8 @@ public: void updateLineNumberAreaWidthForDigits(int digits); void updateContent(); + bool isActive() const; + private: void updateDots(); diff --git a/src/app/qt/view/QtCodeView.cpp b/src/app/qt/view/QtCodeView.cpp index 82037804..0576e1d5 100644 --- a/src/app/qt/view/QtCodeView.cpp +++ b/src/app/qt/view/QtCodeView.cpp @@ -11,6 +11,7 @@ QtCodeView::QtCodeView(ViewLayout* viewLayout) , m_refreshViewFunctor(std::bind(&QtCodeView::doRefreshView, this)) , m_showCodeSnippetsFunctor(std::bind(&QtCodeView::doShowCodeSnippets, this, std::placeholders::_1)) , m_showCodeFileFunctor(std::bind(&QtCodeView::doShowCodeFile, this, std::placeholders::_1)) + , m_doScrollToFirstActiveSnippetFunctor(std::bind(&QtCodeView::doScrollToFirstActiveSnippet, this)) , m_focusTokenFunctor(std::bind(&QtCodeView::doFocusToken, this, std::placeholders::_1)) , m_defocusTokenFunctor(std::bind(&QtCodeView::doDefocusToken, this)) { @@ -56,6 +57,21 @@ void QtCodeView::showCodeFile(const CodeSnippetParams& params) m_showCodeFileFunctor(params); } +void QtCodeView::scrollToFirstActiveSnippet() +{ + m_doScrollToFirstActiveSnippetFunctor(); +} + +void QtCodeView::focusToken(const Id tokenId) +{ + m_focusTokenFunctor(tokenId); +} + +void QtCodeView::defocusToken() +{ + m_defocusTokenFunctor(); +} + void QtCodeView::doRefreshView() { setStyleSheet(m_widget); @@ -79,14 +95,10 @@ void QtCodeView::doShowCodeFile(const CodeSnippetParams& params) m_widget->addCodeSnippet(1, params.title, params.code, params.locationFile); } -void QtCodeView::focusToken(const Id tokenId) +void QtCodeView::doScrollToFirstActiveSnippet() { - m_focusTokenFunctor(tokenId); -} - -void QtCodeView::defocusToken() -{ - m_defocusTokenFunctor(); + m_widget->setActiveTokenIds(m_activeTokenIds); + m_widget->scrollToFirstActiveSnippet(); } void QtCodeView::doFocusToken(const Id tokenId) diff --git a/src/app/qt/view/QtCodeView.h b/src/app/qt/view/QtCodeView.h index b4fe295c..4c25d50c 100644 --- a/src/app/qt/view/QtCodeView.h +++ b/src/app/qt/view/QtCodeView.h @@ -32,6 +32,8 @@ public: virtual void showCodeSnippets(const std::vector& snippets); virtual void showCodeFile(const CodeSnippetParams& params); + virtual void scrollToFirstActiveSnippet(); + virtual void focusToken(const Id tokenId); virtual void defocusToken(); @@ -41,6 +43,8 @@ private: void doShowCodeSnippets(const std::vector& snippets); void doShowCodeFile(const CodeSnippetParams& params); + void doScrollToFirstActiveSnippet(); + void doFocusToken(const Id tokenId); void doDefocusToken(); @@ -49,6 +53,7 @@ private: QtThreadedFunctor<> m_refreshViewFunctor; QtThreadedFunctor&> m_showCodeSnippetsFunctor; QtThreadedFunctor m_showCodeFileFunctor; + QtThreadedFunctor<> m_doScrollToFirstActiveSnippetFunctor; QtThreadedFunctor m_focusTokenFunctor; QtThreadedFunctor<> m_defocusTokenFunctor; diff --git a/src/lib/component/controller/CodeController.cpp b/src/lib/component/controller/CodeController.cpp index fdbc555c..49b09c6a 100644 --- a/src/lib/component/controller/CodeController.cpp +++ b/src/lib/component/controller/CodeController.cpp @@ -52,6 +52,12 @@ void CodeController::handleMessage(MessageActivateTokens* message) view->setActiveTokenIds(activeTokenIds); view->setErrorMessages(std::vector()); + if (message->isEdge) + { + view->scrollToFirstActiveSnippet(); + return; + } + TokenLocationCollection collection = m_storageAccess->getTokenLocationsForTokenIds(activeTokenIds); view->showCodeSnippets(getSnippetsForActiveTokenLocations(collection, declarationId)); diff --git a/src/lib/component/view/CodeView.h b/src/lib/component/view/CodeView.h index 3dbe27c2..a26fc30c 100644 --- a/src/lib/component/view/CodeView.h +++ b/src/lib/component/view/CodeView.h @@ -43,6 +43,8 @@ public: virtual void showCodeSnippets(const std::vector& snippets) = 0; virtual void showCodeFile(const CodeSnippetParams& params) = 0; + virtual void scrollToFirstActiveSnippet() = 0; + virtual void focusToken(const Id tokenId) = 0; virtual void defocusToken() = 0;