From 8672d143199169cb9132f6ffdc44ca29915839ac Mon Sep 17 00:00:00 2001 From: Eberhard Graether Date: Sun, 17 Dec 2017 13:25:11 +0100 Subject: [PATCH] ui: Show title bar at top and scroll bar at bottom in code view snippet list if necessary (issues #432, #479) * Show title bar at top of snippet list if first file is cut off * Show scrollbar at bottom of snippet list if last snippet if cut off and horizontally scrollable * Extended scrollRequest API to different ScrollTarget modes * Fixed snippet extension broken if maximized first * Split off title bar functionality to separate class QtCodeFileTitleBar --- bin/app/data/gui/code_view/code_view.css | 29 ++- src/lib_gui/CMakeLists.txt | 2 + src/lib_gui/qt/element/QtCodeFile.cpp | 220 ++++------------- src/lib_gui/qt/element/QtCodeFile.h | 24 +- src/lib_gui/qt/element/QtCodeFileList.cpp | 223 +++++++++++++++-- src/lib_gui/qt/element/QtCodeFileList.h | 23 +- src/lib_gui/qt/element/QtCodeFileSingle.cpp | 5 +- src/lib_gui/qt/element/QtCodeFileSingle.h | 6 +- src/lib_gui/qt/element/QtCodeFileTitleBar.cpp | 230 ++++++++++++++++++ src/lib_gui/qt/element/QtCodeFileTitleBar.h | 53 ++++ .../qt/element/QtCodeFileTitleButton.cpp | 15 ++ .../qt/element/QtCodeFileTitleButton.h | 2 + src/lib_gui/qt/element/QtCodeNavigateable.cpp | 131 ++++++---- src/lib_gui/qt/element/QtCodeNavigateable.h | 16 +- src/lib_gui/qt/element/QtCodeNavigator.cpp | 35 +-- src/lib_gui/qt/element/QtCodeNavigator.h | 7 +- src/lib_gui/qt/element/QtCodeSnippet.cpp | 4 +- 17 files changed, 736 insertions(+), 289 deletions(-) create mode 100644 src/lib_gui/qt/element/QtCodeFileTitleBar.cpp create mode 100644 src/lib_gui/qt/element/QtCodeFileTitleBar.h diff --git a/bin/app/data/gui/code_view/code_view.css b/bin/app/data/gui/code_view/code_view.css index df4130d1..47444486 100644 --- a/bin/app/data/gui/code_view/code_view.css +++ b/bin/app/data/gui/code_view/code_view.css @@ -94,14 +94,19 @@ padding-bottom: 7px; } -#code_file #title_widget { +#title_widget { background-color: ; border-top-left-radius: 7px; border-top-right-radius: 7px; font-size: px; } -#code_file #title_label, #single_file_title_bar #file_title { +#title_widget[hovering=true] { + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +#title_label, #single_file_title_bar #file_title { background-color: ; border: none; border-radius: 3px; @@ -110,7 +115,7 @@ padding: 2px 4px; } -#code_file #title_label { +#title_label { margin: 1px 8px; } @@ -118,19 +123,19 @@ margin: 3px 8px; } -#code_file #title_label:hover, #single_file_title_bar #file_title:hover { +#title_label:hover, #single_file_title_bar #file_title:hover { background-color: ; } -#code_file #title_label:pressed, #single_file_title_bar #file_title:pressed { +#title_label:pressed, #single_file_title_bar #file_title:pressed { background-color: ; } -#code_file #title_label[complete=false], #single_file_title_bar #file_title[complete=false] { +#title_label[complete=false], #single_file_title_bar #file_title[complete=false] { border: 1px solid ; } -#code_file #references_label, #single_file_title_bar #references_label { +#references_label, #single_file_title_bar #references_label { background-color: ; border: none; border-radius: 10px; @@ -178,13 +183,13 @@ } #code_area { - background-color: transparent; + background-color: ; color: ; selection-color: ; selection-background-color: ; } -#code_file #file_button { +#title_widget #file_button { background: transparent; border: none; margin: 3px; @@ -192,7 +197,7 @@ max-width: 16px; } -#code_file #file_button:enabled { +#title_widget #file_button:enabled { border-image: url(code_view/images/button_background.png); } @@ -200,3 +205,7 @@ background-color: ; padding-right: 5px; } + +#last_scroll_bar { + background-color: ; +} diff --git a/src/lib_gui/CMakeLists.txt b/src/lib_gui/CMakeLists.txt index bd7930d2..a0396a44 100644 --- a/src/lib_gui/CMakeLists.txt +++ b/src/lib_gui/CMakeLists.txt @@ -22,6 +22,8 @@ add_files( qt/element/QtCodeFileList.h qt/element/QtCodeFileSingle.cpp qt/element/QtCodeFileSingle.h + qt/element/QtCodeFileTitleBar.cpp + qt/element/QtCodeFileTitleBar.h qt/element/QtCodeFileTitleButton.cpp qt/element/QtCodeFileTitleButton.h qt/element/QtCodeNavigateable.cpp diff --git a/src/lib_gui/qt/element/QtCodeFile.cpp b/src/lib_gui/qt/element/QtCodeFile.cpp index a9caa831..c2e13c44 100644 --- a/src/lib_gui/qt/element/QtCodeFile.cpp +++ b/src/lib_gui/qt/element/QtCodeFile.cpp @@ -1,14 +1,11 @@ #include "qt/element/QtCodeFile.h" -#include -#include #include #include "utility/messaging/type/MessageChangeFileView.h" -#include "utility/ResourcePaths.h" #include "data/location/SourceLocationFile.h" -#include "qt/element/QtCodeFileTitleButton.h" +#include "qt/element/QtCodeFileTitleBar.h" #include "qt/element/QtCodeNavigator.h" #include "qt/element/QtCodeSnippet.h" @@ -17,7 +14,6 @@ QtCodeFile::QtCodeFile(const FilePath& filePath, QtCodeNavigator* navigator) , m_navigator(navigator) , m_filePath(filePath) , m_isWholeFile(false) - , m_isCollapsed(true) , m_contentRequested(false) { setObjectName("code_file"); @@ -29,84 +25,17 @@ QtCodeFile::QtCodeFile(const FilePath& filePath, QtCodeNavigator* navigator) layout->setAlignment(Qt::AlignTop); setLayout(layout); - m_titleBar = new QtHoverButton(this); - m_titleBar->setObjectName("title_widget"); - m_titleBar->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + m_titleBar = new QtCodeFileTitleBar(); layout->addWidget(m_titleBar); - connect(dynamic_cast(m_titleBar), &QtHoverButton::hoveredIn, this, &QtCodeFile::enteredTitleBar); - connect(dynamic_cast(m_titleBar), &QtHoverButton::hoveredOut, this, &QtCodeFile::leftTitleBar); - - QHBoxLayout* titleLayout = new QHBoxLayout(); - titleLayout->setMargin(0); - titleLayout->setSpacing(0); - titleLayout->setAlignment(Qt::AlignLeft); - m_titleBar->setLayout(titleLayout); - - m_title = new QtCodeFileTitleButton(this); if (!m_filePath.empty()) { - m_title->setFilePath(filePath); + m_titleBar->getTitleButton()->setFilePath(filePath); } - titleLayout->addWidget(m_title); - - m_titleBar->setMinimumHeight(m_title->height() + 4); - - m_referenceCount = new QLabel(this); - m_referenceCount->setObjectName("references_label"); - m_referenceCount->hide(); - titleLayout->addWidget(m_referenceCount); - - titleLayout->addStretch(3); - - m_minimizeButton = new QtIconStateButton(this); - m_minimizeButton->addState(QtIconStateButton::STATE_DEFAULT, (ResourcePaths::getGuiPath().str() + "code_view/images/minimize_active.png").c_str()); -// m_minimizeButton->addState(QtIconStateButton::STATE_HOVERED, (ResourcePaths::getGuiPath().str() + "code_view/images/minimize_inactive.png").c_str(), "#5E5D5D"); - m_minimizeButton->addState(QtIconStateButton::STATE_HOVERED, (ResourcePaths::getGuiPath().str() + "code_view/images/minimize_inactive.png").c_str(), QColor(0x5E, 0x5D, 0x5D)); - m_minimizeButton->addState(QtIconStateButton::STATE_DISABLED, (ResourcePaths::getGuiPath().str() + "code_view/images/minimize_inactive.png").c_str()); - m_minimizeButton->setIconSize(QSize(16, 16)); - m_minimizeButton->setObjectName("file_button"); - m_minimizeButton->setToolTip("minimize"); - titleLayout->addWidget(m_minimizeButton); - - m_snippetButton = new QtIconStateButton(this); - m_snippetButton->addState(QtIconStateButton::STATE_DEFAULT, (ResourcePaths::getGuiPath().str() + "code_view/images/snippet_active.png").c_str()); -// m_snippetButton->addState(QtIconStateButton::STATE_HOVERED, (ResourcePaths::getGuiPath().str() + "code_view/images/snippet_inactive.png").c_str(), "#5E5D5D"); - m_snippetButton->addState(QtIconStateButton::STATE_HOVERED, (ResourcePaths::getGuiPath().str() + "code_view/images/snippet_inactive.png").c_str(), QColor(0x5E, 0x5D, 0x5D)); - m_snippetButton->addState(QtIconStateButton::STATE_DISABLED, (ResourcePaths::getGuiPath().str() + "code_view/images/snippet_inactive.png").c_str()); - m_snippetButton->setIconSize(QSize(16, 16)); - m_snippetButton->setObjectName("file_button"); - m_snippetButton->setToolTip("show snippets"); - titleLayout->addWidget(m_snippetButton); - - m_maximizeButton = new QtIconStateButton(this); - m_maximizeButton->addState(QtIconStateButton::STATE_DEFAULT, (ResourcePaths::getGuiPath().str() + "code_view/images/maximize_active.png").c_str()); -// m_maximizeButton->addState(QtIconStateButton::STATE_HOVERED, (ResourcePaths::getGuiPath().str() + "code_view/images/maximize_inactive.png").c_str(), "#5E5D5D"); - m_maximizeButton->addState(QtIconStateButton::STATE_HOVERED, (ResourcePaths::getGuiPath().str() + "code_view/images/maximize_inactive.png").c_str(), QColor(0x5E, 0x5D, 0x5D)); - m_maximizeButton->addState(QtIconStateButton::STATE_DISABLED, (ResourcePaths::getGuiPath().str() + "code_view/images/maximize_inactive.png").c_str()); - m_maximizeButton->setIconSize(QSize(16, 16)); - m_maximizeButton->setObjectName("file_button"); - m_maximizeButton->setToolTip("maximize"); - titleLayout->addWidget(m_maximizeButton); - - connect(m_minimizeButton, &QtIconStateButton::hoveredIn, this, &QtCodeFile::leftTitleBar); - connect(m_minimizeButton, &QtIconStateButton::hoveredOut, this, &QtCodeFile::enteredTitleBar); - connect(m_snippetButton, &QtIconStateButton::hoveredIn, this, &QtCodeFile::leftTitleBar); - connect(m_snippetButton, &QtIconStateButton::hoveredOut, this, &QtCodeFile::enteredTitleBar); - connect(m_maximizeButton, &QtIconStateButton::hoveredIn, this, &QtCodeFile::leftTitleBar); - connect(m_maximizeButton, &QtIconStateButton::hoveredOut, this, &QtCodeFile::enteredTitleBar); - - titleLayout->addSpacing(3); - - m_minimizeButton->setEnabled(false); - m_snippetButton->setEnabled(false); - m_maximizeButton->setEnabled(false); - - connect(m_titleBar, &QPushButton::clicked, this, &QtCodeFile::clickedTitleBar); - connect(m_minimizeButton, &QtIconStateButton::clicked, this, &QtCodeFile::clickedMinimizeButton); - connect(m_snippetButton, &QtIconStateButton::clicked, this, &QtCodeFile::clickedSnippetButton); - connect(m_maximizeButton, &QtIconStateButton::clicked, this, &QtCodeFile::clickedMaximizeButton); + connect(m_titleBar, &QtCodeFileTitleBar::minimize, this, &QtCodeFile::clickedMinimizeButton); + connect(m_titleBar, &QtCodeFileTitleBar::snippet, this, &QtCodeFile::clickedSnippetButton); + connect(m_titleBar, &QtCodeFileTitleBar::maximize, this, &QtCodeFile::clickedMaximizeButton); m_snippetLayout = new QVBoxLayout(); m_snippetLayout->setContentsMargins(0, 0, 0, 0); @@ -122,7 +51,7 @@ QtCodeFile::~QtCodeFile() void QtCodeFile::setModificationTime(const TimeStamp modificationTime) { - m_title->setModificationTime(modificationTime); + m_titleBar->getTitleButton()->setModificationTime(modificationTime); } const FilePath& QtCodeFile::getFilePath() const @@ -135,6 +64,11 @@ std::string QtCodeFile::getFileName() const return m_filePath.fileName(); } +const QtCodeFileTitleBar* QtCodeFile::getTitleBar() const +{ + return m_titleBar; +} + QtCodeSnippet* QtCodeFile::addCodeSnippet(const CodeSnippetParams& params) { for (const std::shared_ptr& snippet : m_snippets) @@ -146,12 +80,12 @@ QtCodeSnippet* QtCodeFile::addCodeSnippet(const CodeSnippetParams& params) } } - m_isCollapsed = false; std::shared_ptr snippet(new QtCodeSnippet(params, m_navigator, this)); if (params.reduced) { - m_title->setProject(params.title); + m_titleBar->getTitleButton()->setProject(params.title); + m_isWholeFile = true; } m_snippetLayout->addWidget(snippet.get()); @@ -185,8 +119,6 @@ QtCodeSnippet* QtCodeFile::addCodeSnippet(const CodeSnippetParams& params) QtCodeSnippet* QtCodeFile::insertCodeSnippet(const CodeSnippetParams& params) { - m_isCollapsed = false; - std::shared_ptr snippet(new QtCodeSnippet(params, m_navigator, this)); size_t i = 0; @@ -225,6 +157,26 @@ QtCodeSnippet* QtCodeFile::insertCodeSnippet(const CodeSnippetParams& params) return snippet.get(); } +std::vector QtCodeFile::getVisibleSnippets() const +{ + if (m_fileSnippet && m_fileSnippet->isVisible()) + { + return { m_fileSnippet.get() }; + } + + std::vector snippets; + + for (const std::shared_ptr& snippet : m_snippets) + { + if (snippet->isVisible()) + { + snippets.push_back(snippet.get()); + } + } + + return snippets; +} + QtCodeSnippet* QtCodeFile::getSnippetForLocationId(Id locationId) const { if (m_fileSnippet && m_fileSnippet->isVisible() && m_fileSnippet->getLineNumberForLocationId(locationId)) @@ -286,7 +238,7 @@ std::pair QtCodeFile::getFirstSnippetWithActiveLocationId(Id bool QtCodeFile::isCollapsed() const { - return m_isCollapsed; + return !getFileSnippet() && !m_snippets.size(); } void QtCodeFile::requestContent() @@ -302,7 +254,9 @@ void QtCodeFile::requestContent() MessageChangeFileView::FileState state = m_isWholeFile ? MessageChangeFileView::FILE_MAXIMIZED : MessageChangeFileView::FILE_SNIPPETS; - MessageChangeFileView(m_filePath, state, isCollapsed(), m_navigator->hasErrors()).dispatch(); + bool needsData = (state == MessageChangeFileView::FILE_MAXIMIZED) ? (getFileSnippet() == nullptr) : (m_snippets.size() == 0); + + MessageChangeFileView(m_filePath, state, needsData, m_navigator->hasErrors()).dispatch(); } void QtCodeFile::updateContent() @@ -330,7 +284,7 @@ void QtCodeFile::setWholeFile(bool isWholeFile, int refCount) void QtCodeFile::setIsComplete(bool isComplete) { - m_title->setIsComplete(isComplete); + m_titleBar->getTitleButton()->setIsComplete(isComplete); } void QtCodeFile::setMinimized() @@ -345,13 +299,7 @@ void QtCodeFile::setMinimized() m_fileSnippet->hide(); } - m_minimizeButton->setEnabled(false); - m_snippetButton->setEnabled(m_snippets.size() || (isCollapsed() && !m_isWholeFile)); - m_maximizeButton->setEnabled(true); - - m_minimizeButton->hoverOut(); - m_snippetButton->hoverOut(); - m_maximizeButton->hoverOut(); + m_titleBar->setMinimized(!m_isWholeFile); setStyleSheet("#code_file { padding-bottom: 0; } #code_file #title_widget { border-radius: 7px; }"); } @@ -368,13 +316,7 @@ void QtCodeFile::setSnippets() m_fileSnippet->hide(); } - m_minimizeButton->setEnabled(true); - m_snippetButton->setEnabled(false); - m_maximizeButton->setEnabled(true); - - m_minimizeButton->hoverOut(); - m_snippetButton->hoverOut(); - m_maximizeButton->hoverOut(); + m_titleBar->setSnippets(); setStyleSheet(""); } @@ -391,13 +333,7 @@ void QtCodeFile::setMaximized() m_fileSnippet->show(); } - m_minimizeButton->setEnabled(true); - m_snippetButton->setEnabled(m_snippets.size() || (isCollapsed() && !m_isWholeFile)); - m_maximizeButton->setEnabled(false); - - m_minimizeButton->hoverOut(); - m_snippetButton->hoverOut(); - m_maximizeButton->hoverOut(); + m_titleBar->setMaximized(!m_isWholeFile); setStyleSheet(""); } @@ -438,7 +374,7 @@ void QtCodeFile::updateSnippets() void QtCodeFile::updateTitleBar() { - m_title->updateTexts(); + m_titleBar->getTitleButton()->updateTexts(); } void QtCodeFile::findScreenMatches(const std::string& query, std::vector>* screenMatches) @@ -467,6 +403,8 @@ void QtCodeFile::clickedMinimizeButton() return; } + m_navigator->requestScroll(m_filePath, 0, 0, false, QtCodeNavigateable::SCROLL_VISIBLE); + MessageChangeFileView( m_filePath, MessageChangeFileView::FILE_MINIMIZED, @@ -477,10 +415,12 @@ void QtCodeFile::clickedMinimizeButton() void QtCodeFile::clickedSnippetButton() { + m_navigator->requestScroll(m_filePath, 0, 0, false, QtCodeNavigateable::SCROLL_VISIBLE); + MessageChangeFileView( m_filePath, MessageChangeFileView::FILE_SNIPPETS, - isCollapsed(), + !m_snippets.size(), m_navigator->hasErrors() ).dispatch(); } @@ -494,6 +434,8 @@ void QtCodeFile::clickedMaximizeButton() return; } + m_navigator->requestScroll(m_filePath, 0, 0, false, QtCodeNavigateable::SCROLL_VISIBLE); + MessageChangeFileView( m_filePath, MessageChangeFileView::FILE_MAXIMIZED, @@ -502,63 +444,6 @@ void QtCodeFile::clickedMaximizeButton() ).dispatch(); } -void QtCodeFile::enteredTitleBar(QPushButton* button) -{ - if (m_minimizeButton->isEnabled()) - { - m_minimizeButton->hoverIn(); - } - else if (m_snippetButton->isEnabled()) - { - m_snippetButton->hoverIn(); - } - else if (m_maximizeButton->isEnabled()) - { - m_maximizeButton->hoverIn(); - } -} - -void QtCodeFile::leftTitleBar(QPushButton* button) -{ - if (m_minimizeButton->isEnabled()) - { - if (m_minimizeButton != button) - { - m_minimizeButton->hoverOut(); - } - } - else if (m_snippetButton->isEnabled()) - { - if (m_snippetButton != button) - { - m_snippetButton->hoverOut(); - } - } - else if (m_maximizeButton->isEnabled()) - { - if (m_maximizeButton != button) - { - m_maximizeButton->hoverOut(); - } - } -} - -void QtCodeFile::clickedTitleBar() -{ - if (m_minimizeButton->isEnabled()) - { - clickedMinimizeButton(); - } - else if (m_snippetButton->isEnabled()) - { - clickedSnippetButton(); - } - else - { - clickedMaximizeButton(); - } -} - void QtCodeFile::updateRefCount(int refCount) { if (refCount > 0 && !m_isWholeFile) @@ -580,11 +465,10 @@ void QtCodeFile::updateRefCount(int refCount) } } - m_referenceCount->setText(QString::number(refCount) + " " + label); - m_referenceCount->show(); + m_titleBar->setRefString(QString::number(refCount) + " " + label); } else { - m_referenceCount->hide(); + m_titleBar->setRefString(""); } } diff --git a/src/lib_gui/qt/element/QtCodeFile.h b/src/lib_gui/qt/element/QtCodeFile.h index d5f6716c..d656d7ec 100644 --- a/src/lib_gui/qt/element/QtCodeFile.h +++ b/src/lib_gui/qt/element/QtCodeFile.h @@ -10,12 +10,9 @@ #include "utility/file/FilePath.h" #include "component/view/helper/CodeSnippetParams.h" -#include "qt/element/QtIconButton.h" -class QLabel; -class QPushButton; class QtCodeArea; -class QtCodeFileTitleButton; +class QtCodeFileTitleBar; class QtCodeNavigator; class QtCodeSnippet; class QVBoxLayout; @@ -35,9 +32,12 @@ public: const FilePath& getFilePath() const; std::string getFileName() const; + const QtCodeFileTitleBar* getTitleBar() const; + QtCodeSnippet* addCodeSnippet(const CodeSnippetParams& params); QtCodeSnippet* insertCodeSnippet(const CodeSnippetParams& params); + std::vector getVisibleSnippets() const; QtCodeSnippet* getSnippetForLocationId(Id locationId) const; QtCodeSnippet* getSnippetForLine(unsigned int line) const; QtCodeSnippet* getFileSnippet() const; @@ -67,33 +67,19 @@ public slots: void clickedSnippetButton(); void clickedMaximizeButton(); - void enteredTitleBar(QPushButton* button); - void leftTitleBar(QPushButton* button); - -private slots: - void clickedTitleBar(); - private: void updateRefCount(int refCount); QtCodeNavigator* m_navigator; - QPushButton* m_titleBar; - QtCodeFileTitleButton* m_title; - QLabel* m_referenceCount; - - QtIconStateButton* m_minimizeButton; - QtIconStateButton* m_snippetButton; - QtIconStateButton* m_maximizeButton; + QtCodeFileTitleBar* m_titleBar; QVBoxLayout* m_snippetLayout; std::vector> m_snippets; std::shared_ptr m_fileSnippet; const FilePath m_filePath; - bool m_isWholeFile; - bool m_isCollapsed; mutable bool m_contentRequested; }; diff --git a/src/lib_gui/qt/element/QtCodeFileList.cpp b/src/lib_gui/qt/element/QtCodeFileList.cpp index 196cd03f..f066b603 100644 --- a/src/lib_gui/qt/element/QtCodeFileList.cpp +++ b/src/lib_gui/qt/element/QtCodeFileList.cpp @@ -4,33 +4,58 @@ #include #include "utility/file/FilePath.h" +#include "utility/utilityApp.h" #include "data/location/SourceLocationFile.h" #include "qt/element/QtCodeFile.h" +#include "qt/element/QtCodeFileTitleBar.h" #include "qt/element/QtCodeNavigator.h" #include "qt/element/QtCodeSnippet.h" QtCodeFileList::QtCodeFileList(QtCodeNavigator* navigator) - : QScrollArea() + : QFrame() , m_navigator(navigator) + , m_mirroredTitleBar(nullptr) + , m_mirroredSnippetScrollBar(nullptr) { - setObjectName("code_container"); - setWidgetResizable(true); + m_scrollArea = new QScrollArea(); + + m_scrollArea->setObjectName("code_container"); + m_scrollArea->setWidgetResizable(true); m_filesArea = new QFrame(); m_filesArea->setObjectName("code_file_list"); + QVBoxLayout* innerLayout = new QVBoxLayout(); + innerLayout->setSpacing(8); + innerLayout->setContentsMargins(8, 8, 8, 8); + innerLayout->setAlignment(Qt::AlignTop); + m_filesArea->setLayout(innerLayout); + + m_scrollArea->setWidget(m_filesArea); + + m_firstSnippetTitleBar = new QtCodeFileTitleBar(m_scrollArea, true); + m_firstSnippetTitleBar->hide(); + + m_lastSnippetScrollBar = new QScrollBar(Qt::Horizontal, m_scrollArea); + if (utility::getOsType() != OS_MAC) // don't manipulate scrollbar style on macOS + { + m_lastSnippetScrollBar->setObjectName("last_scroll_bar"); + } + + m_lastSnippetScrollBar->hide(); + connect(m_lastSnippetScrollBar, &QScrollBar::valueChanged, this, &QtCodeFileList::scrollLastSnippet); + QVBoxLayout* layout = new QVBoxLayout(); - layout->setSpacing(8); - layout->setContentsMargins(8, 8, 8, 8); - layout->setAlignment(Qt::AlignTop); - m_filesArea->setLayout(layout); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_scrollArea); + setLayout(layout); - setWidget(m_filesArea); + m_scrollSpeedChangeListener.setScrollBar(m_scrollArea->verticalScrollBar()); - m_scrollSpeedChangeListener.setScrollBar(verticalScrollBar()); - - connect(verticalScrollBar(), &QScrollBar::valueChanged, m_navigator, &QtCodeNavigator::scrolled); + connect(m_scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, this, &QtCodeFileList::updateSnippetTitleAndScrollBar); + connect(m_scrollArea->verticalScrollBar(), &QScrollBar::valueChanged, m_navigator, &QtCodeNavigator::scrolled); } QtCodeFileList::~QtCodeFileList() @@ -45,7 +70,10 @@ void QtCodeFileList::clear() } m_files.clear(); - verticalScrollBar()->setValue(0); + m_scrollArea->verticalScrollBar()->setValue(0); + + updateFirstSnippetTitleBar(nullptr); + updateLastSnippetScrollBar(nullptr); } QtCodeFile* QtCodeFileList::getFile(const FilePath filePath) @@ -83,7 +111,7 @@ void QtCodeFileList::addFile(const FilePath& filePath, bool isWholeFile, int ref QScrollArea* QtCodeFileList::getScrollArea() { - return this; + return m_scrollArea; } void QtCodeFileList::addCodeSnippet(const CodeSnippetParams& params) @@ -108,7 +136,7 @@ void QtCodeFileList::requestFileContent(const FilePath& filePath) getFile(filePath)->requestContent(); } -bool QtCodeFileList::requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop) +bool QtCodeFileList::requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, ScrollTarget target) { QtCodeFile* file = getFile(filePath); if (!file) @@ -132,19 +160,21 @@ bool QtCodeFileList::requestScroll(const FilePath& filePath, uint lineNumber, Id { snippet = file->getSnippetForLine(lineNumber); } - else + else if (file->getFileSnippet() && file->getFileSnippet()->isVisible()) { snippet = file->getFileSnippet(); } if (!snippet) { + ensureWidgetVisibleAnimated(m_filesArea, file->getTitleBar(), QRect(), animated, target); return true; } if (!snippet->isVisible()) { file->setSnippets(); + return true; } uint endLineNumber = 0; @@ -173,8 +203,7 @@ bool QtCodeFileList::requestScroll(const FilePath& filePath, uint lineNumber, Id lineRect |= snippet->getLineRectForLineNumber(endLineNumber); } - ensureWidgetVisibleAnimated(m_filesArea, snippet, lineRect, animated, onTop); - + ensureWidgetVisibleAnimated(m_filesArea, snippet, lineRect, animated, target); return true; } @@ -184,6 +213,8 @@ void QtCodeFileList::updateFiles() { file->updateContent(); } + + updateSnippetTitleAndScrollBar(); } void QtCodeFileList::showContents() @@ -200,6 +231,11 @@ void QtCodeFileList::onWindowFocus() { file->updateTitleBar(); } + + if (m_firstSnippetTitleBar->isVisible()) + { + m_firstSnippetTitleBar->getTitleButton()->updateTexts(); + } } void QtCodeFileList::findScreenMatches(const std::string& query, std::vector>* screenMatches) @@ -245,3 +281,156 @@ std::pair QtCodeFileList::getFirstSnippetWithActiveLocationI return result; } + +void QtCodeFileList::resizeEvent(QResizeEvent* event) +{ + updateFirstSnippetTitleBar(nullptr); + updateLastSnippetScrollBar(nullptr); + + updateSnippetTitleAndScrollBar(); +} + +void QtCodeFileList::updateSnippetTitleAndScrollBar(int value) +{ + QtCodeFile* firstFile = nullptr; + QScrollBar* lastSnippetScrollBar = nullptr; + int fileTitleBarOffset = 0; + + const QRect visibleRect(-m_filesArea->pos(), m_scrollArea->viewport()->size()); + + for (QtCodeFile* file : m_files) + { + QRect fileRect = getFocusRectForWidget(file, m_filesArea); + + if (!firstFile && visibleRect.top() > fileRect.top() + 2 && + visibleRect.top() < fileRect.bottom() - 10 && + file->getVisibleSnippets().size()) + { + firstFile = file; + fileTitleBarOffset = std::min(0, fileRect.bottom() - 10 - (visibleRect.top() + m_firstSnippetTitleBar->height())); + } + + if (visibleRect.bottom() > fileRect.top() && fileRect.bottom() > visibleRect.bottom()) + { + for (QtCodeSnippet* snippet : file->getVisibleSnippets()) + { + QScrollBar* scrollbar = snippet->getArea()->horizontalScrollBar(); + if (!scrollbar || scrollbar->minimum() == scrollbar->maximum()) + { + continue; + } + + QRect snippetRect = getFocusRectForWidget(snippet, m_filesArea); + if (visibleRect.bottom() > snippetRect.top() + scrollbar->height() && + snippetRect.bottom() - scrollbar->height() > visibleRect.bottom()) + { + lastSnippetScrollBar = scrollbar; + break; + } + } + } + + if (lastSnippetScrollBar) + { + break; + } + } + + updateFirstSnippetTitleBar(firstFile, fileTitleBarOffset); + updateLastSnippetScrollBar(lastSnippetScrollBar); +} + +void QtCodeFileList::scrollLastSnippet(int value) +{ + if (m_mirroredSnippetScrollBar && m_mirroredSnippetScrollBar->value() != value) + { + m_mirroredSnippetScrollBar->setValue(value); + } +} + +void QtCodeFileList::scrollLastSnippetScrollBar(int value) +{ + if (m_lastSnippetScrollBar->value() != value) + { + m_lastSnippetScrollBar->setValue(value); + } +} + +void QtCodeFileList::updateFirstSnippetTitleBar(const QtCodeFile* file, int fileTitleBarOffset) +{ + const QtCodeFileTitleBar* mirroredTitleBar = file ? file->getTitleBar() : nullptr; + if (m_mirroredTitleBar != mirroredTitleBar) + { + m_mirroredTitleBar = mirroredTitleBar; + + if (m_mirroredTitleBar && file) + { + m_firstSnippetTitleBar->updateFromOther(mirroredTitleBar); + + connect(m_firstSnippetTitleBar, &QtCodeFileTitleBar::minimize, file, &QtCodeFile::clickedMinimizeButton); + connect(m_firstSnippetTitleBar, &QtCodeFileTitleBar::snippet, file, &QtCodeFile::clickedSnippetButton); + connect(m_firstSnippetTitleBar, &QtCodeFileTitleBar::maximize, file, &QtCodeFile::clickedMaximizeButton); + + m_firstSnippetTitleBar->setGeometry( + file->pos().x() + mirroredTitleBar->pos().x(), + 0, + mirroredTitleBar->width(), + mirroredTitleBar->height() + ); + m_firstSnippetTitleBar->show(); + } + else + { + m_firstSnippetTitleBar->hide(); + } + } + + if (m_firstSnippetTitleBar->isVisible()) + { + QRect rect = m_firstSnippetTitleBar->geometry(); + int height = rect.height(); + if (fileTitleBarOffset != rect.y()) + { + rect.setY(fileTitleBarOffset); + rect.setHeight(height); + m_firstSnippetTitleBar->setGeometry(rect); + m_firstSnippetTitleBar->update(); + } + } +} + +void QtCodeFileList::updateLastSnippetScrollBar(QScrollBar* mirroredScrollBar) +{ + if (m_mirroredSnippetScrollBar != mirroredScrollBar) + { + if (m_mirroredSnippetScrollBar) + { + m_mirroredSnippetScrollBar->disconnect(this); + } + + m_mirroredSnippetScrollBar = mirroredScrollBar; + + if (mirroredScrollBar) + { + connect(mirroredScrollBar, &QScrollBar::valueChanged, this, &QtCodeFileList::scrollLastSnippetScrollBar); + + m_lastSnippetScrollBar->setMinimum(mirroredScrollBar->minimum()); + m_lastSnippetScrollBar->setMaximum(mirroredScrollBar->maximum()); + m_lastSnippetScrollBar->setValue(mirroredScrollBar->value()); + m_lastSnippetScrollBar->setPageStep(mirroredScrollBar->pageStep()); + m_lastSnippetScrollBar->setSingleStep(mirroredScrollBar->singleStep()); + + m_lastSnippetScrollBar->setGeometry( + mirroredScrollBar->mapTo(m_scrollArea, mirroredScrollBar->pos()).x(), + m_scrollArea->viewport()->size().height() - mirroredScrollBar->height(), + mirroredScrollBar->width(), + mirroredScrollBar->height() + ); + m_lastSnippetScrollBar->show(); + } + else + { + m_lastSnippetScrollBar->hide(); + } + } +} diff --git a/src/lib_gui/qt/element/QtCodeFileList.h b/src/lib_gui/qt/element/QtCodeFileList.h index e380cfc3..a74e1d9d 100644 --- a/src/lib_gui/qt/element/QtCodeFileList.h +++ b/src/lib_gui/qt/element/QtCodeFileList.h @@ -11,11 +11,12 @@ #include "qt/utility/QtScrollSpeedChangeListener.h" class QtCodeFile; +class QtCodeFileTitleBar; class QtCodeNavigator; class QtCodeSnippet; class QtCodeFileList - : public QScrollArea + : public QFrame , public QtCodeNavigateable { Q_OBJECT @@ -35,7 +36,7 @@ public: virtual void addCodeSnippet(const CodeSnippetParams& params); virtual void requestFileContent(const FilePath& filePath); - virtual bool requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop); + virtual bool requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, ScrollTarget target); virtual void updateFiles(); virtual void showContents(); @@ -50,12 +51,30 @@ public: std::pair getFirstSnippetWithActiveLocationId(Id tokenId) const; +protected: + virtual void resizeEvent(QResizeEvent* event); + +private slots: + void updateSnippetTitleAndScrollBar(int value = 0); + void scrollLastSnippet(int value); + void scrollLastSnippetScrollBar(int value); + private: + void updateFirstSnippetTitleBar(const QtCodeFile* file, int fileTitleBarOffset = 0); + void updateLastSnippetScrollBar(QScrollBar* mirroredScrollBar); + QtCodeNavigator* m_navigator; + QScrollArea* m_scrollArea; QFrame* m_filesArea; std::vector m_files; + QtCodeFileTitleBar* m_firstSnippetTitleBar; + const QtCodeFileTitleBar* m_mirroredTitleBar; + + QScrollBar* m_lastSnippetScrollBar; + QScrollBar* m_mirroredSnippetScrollBar; + QtScrollSpeedChangeListener m_scrollSpeedChangeListener; }; diff --git a/src/lib_gui/qt/element/QtCodeFileSingle.cpp b/src/lib_gui/qt/element/QtCodeFileSingle.cpp index 9b7e8368..b93bc947 100644 --- a/src/lib_gui/qt/element/QtCodeFileSingle.cpp +++ b/src/lib_gui/qt/element/QtCodeFileSingle.cpp @@ -166,7 +166,8 @@ void QtCodeFileSingle::requestFileContent(const FilePath& filePath) ).dispatch(); } -bool QtCodeFileSingle::requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop) +bool QtCodeFileSingle::requestScroll( + const FilePath& filePath, uint lineNumber, Id locationId, bool animated, ScrollTarget target) { FileData file = getFileData(filePath); if (file.area) @@ -207,7 +208,7 @@ bool QtCodeFileSingle::requestScroll(const FilePath& filePath, uint lineNumber, double percentA = double(lineNumber - 1) / m_area->getEndLineNumber(); double percentB = endLineNumber ? double(endLineNumber - 1) / m_area->getEndLineNumber() : 0.0f; - ensurePercentVisibleAnimated(percentA, percentB, animated, onTop); + ensurePercentVisibleAnimated(percentA, percentB, animated, target); m_scrollRequested = true; diff --git a/src/lib_gui/qt/element/QtCodeFileSingle.h b/src/lib_gui/qt/element/QtCodeFileSingle.h index fe594ceb..b5a254dd 100644 --- a/src/lib_gui/qt/element/QtCodeFileSingle.h +++ b/src/lib_gui/qt/element/QtCodeFileSingle.h @@ -36,14 +36,16 @@ public: virtual void addCodeSnippet(const CodeSnippetParams& params) override; virtual void requestFileContent(const FilePath& filePath) override; - virtual bool requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop) override; + virtual bool requestScroll( + const FilePath& filePath, uint lineNumber, Id locationId, bool animated, ScrollTarget target) override; virtual void updateFiles() override; virtual void showContents() override; virtual void onWindowFocus() override; - virtual void findScreenMatches(const std::string& query, std::vector>* screenMatches) override; + virtual void findScreenMatches( + const std::string& query, std::vector>* screenMatches) override; const FilePath& getCurrentFilePath() const; bool hasFileCached(const FilePath& filePath) const; diff --git a/src/lib_gui/qt/element/QtCodeFileTitleBar.cpp b/src/lib_gui/qt/element/QtCodeFileTitleBar.cpp new file mode 100644 index 00000000..b5195df1 --- /dev/null +++ b/src/lib_gui/qt/element/QtCodeFileTitleBar.cpp @@ -0,0 +1,230 @@ +#include "qt/element/QtCodeFileTitleBar.h" + +#include +#include +#include + +#include "utility/ResourcePaths.h" + +QtCodeFileTitleBar::QtCodeFileTitleBar(QWidget* parent, bool isHovering) + : QtHoverButton(parent) +{ + setObjectName("title_widget"); + setProperty("hovering", isHovering); + setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + + QHBoxLayout* titleLayout = new QHBoxLayout(); + titleLayout->setMargin(0); + titleLayout->setSpacing(0); + titleLayout->setAlignment(Qt::AlignLeft); + setLayout(titleLayout); + + m_titleButton = new QtCodeFileTitleButton(this); + titleLayout->addWidget(m_titleButton); + setMinimumHeight(m_titleButton->height() + 4); + + m_referenceCount = new QLabel(this); + m_referenceCount->setObjectName("references_label"); + m_referenceCount->hide(); + titleLayout->addWidget(m_referenceCount); + + titleLayout->addStretch(3); + + std::string imageDir = ResourcePaths::getGuiPath().str() + "code_view/images/"; + + m_minimizeButton = new QtIconStateButton(this); + m_minimizeButton->addState(QtIconStateButton::STATE_DEFAULT, (imageDir + "minimize_active.png").c_str()); +// m_minimizeButton->addState(QtIconStateButton::STATE_HOVERED, (imageDir + "minimize_inactive.png").c_str(), "#5E5D5D"); + m_minimizeButton->addState(QtIconStateButton::STATE_HOVERED, (imageDir + "minimize_inactive.png").c_str(), QColor(0x5E, 0x5D, 0x5D)); + m_minimizeButton->addState(QtIconStateButton::STATE_DISABLED, (imageDir + "minimize_inactive.png").c_str()); + m_minimizeButton->setIconSize(QSize(16, 16)); + m_minimizeButton->setObjectName("file_button"); + m_minimizeButton->setToolTip("minimize"); + titleLayout->addWidget(m_minimizeButton); + + m_snippetButton = new QtIconStateButton(this); + m_snippetButton->addState(QtIconStateButton::STATE_DEFAULT, (imageDir + "snippet_active.png").c_str()); +// m_snippetButton->addState(QtIconStateButton::STATE_HOVERED, (imageDir + "snippet_inactive.png").c_str(), "#5E5D5D"); + m_snippetButton->addState(QtIconStateButton::STATE_HOVERED, (imageDir + "snippet_inactive.png").c_str(), QColor(0x5E, 0x5D, 0x5D)); + m_snippetButton->addState(QtIconStateButton::STATE_DISABLED, (imageDir + "snippet_inactive.png").c_str()); + m_snippetButton->setIconSize(QSize(16, 16)); + m_snippetButton->setObjectName("file_button"); + m_snippetButton->setToolTip("show snippets"); + titleLayout->addWidget(m_snippetButton); + + m_maximizeButton = new QtIconStateButton(this); + m_maximizeButton->addState(QtIconStateButton::STATE_DEFAULT, (imageDir + "maximize_active.png").c_str()); +// m_maximizeButton->addState(QtIconStateButton::STATE_HOVERED, (imageDir + "maximize_inactive.png").c_str(), "#5E5D5D"); + m_maximizeButton->addState(QtIconStateButton::STATE_HOVERED, (imageDir + "maximize_inactive.png").c_str(), QColor(0x5E, 0x5D, 0x5D)); + m_maximizeButton->addState(QtIconStateButton::STATE_DISABLED, (imageDir + "maximize_inactive.png").c_str()); + m_maximizeButton->setIconSize(QSize(16, 16)); + m_maximizeButton->setObjectName("file_button"); + m_maximizeButton->setToolTip("maximize"); + titleLayout->addWidget(m_maximizeButton); + + titleLayout->addSpacing(3); + + connect(this, &QPushButton::clicked, this, &QtCodeFileTitleBar::clickedTitleBar); + connect(this, &QtHoverButton::hoveredIn, this, &QtCodeFileTitleBar::enteredTitleBar); + connect(this, &QtHoverButton::hoveredOut, this, &QtCodeFileTitleBar::leftTitleBar); + + connect(m_minimizeButton, &QtIconStateButton::hoveredIn, this, &QtCodeFileTitleBar::leftTitleBar); + connect(m_minimizeButton, &QtIconStateButton::hoveredOut, this, &QtCodeFileTitleBar::enteredTitleBar); + connect(m_minimizeButton, &QtIconStateButton::clicked, this, &QtCodeFileTitleBar::clickedMinimizeButton); + + connect(m_snippetButton, &QtIconStateButton::hoveredIn, this, &QtCodeFileTitleBar::leftTitleBar); + connect(m_snippetButton, &QtIconStateButton::hoveredOut, this, &QtCodeFileTitleBar::enteredTitleBar); + connect(m_snippetButton, &QtIconStateButton::clicked, this, &QtCodeFileTitleBar::clickedSnippetButton); + + connect(m_maximizeButton, &QtIconStateButton::hoveredIn, this, &QtCodeFileTitleBar::leftTitleBar); + connect(m_maximizeButton, &QtIconStateButton::hoveredOut, this, &QtCodeFileTitleBar::enteredTitleBar); + connect(m_maximizeButton, &QtIconStateButton::clicked, this, &QtCodeFileTitleBar::clickedMaximizeButton); + + m_minimizeButton->setEnabled(false); + m_snippetButton->setEnabled(false); + m_maximizeButton->setEnabled(false); +} + +QtCodeFileTitleButton* QtCodeFileTitleBar::getTitleButton() const +{ + return m_titleButton; +} + +void QtCodeFileTitleBar::setRefString(const QString& refString) +{ + if (refString.size()) + { + m_referenceCount->setText(refString); + m_referenceCount->show(); + } + else + { + m_referenceCount->hide(); + } +} + +void QtCodeFileTitleBar::setMinimized(bool hasSnippets) +{ + m_minimizeButton->setEnabled(false); + m_snippetButton->setEnabled(hasSnippets); + m_maximizeButton->setEnabled(true); + + m_minimizeButton->hoverOut(); + m_snippetButton->hoverOut(); + m_maximizeButton->hoverOut(); +} + +void QtCodeFileTitleBar::setSnippets() +{ + m_minimizeButton->setEnabled(true); + m_snippetButton->setEnabled(false); + m_maximizeButton->setEnabled(true); + + m_minimizeButton->hoverOut(); + m_snippetButton->hoverOut(); + m_maximizeButton->hoverOut(); +} + +void QtCodeFileTitleBar::setMaximized(bool hasSnippets) +{ + m_minimizeButton->setEnabled(true); + m_snippetButton->setEnabled(hasSnippets); + m_maximizeButton->setEnabled(false); + + m_minimizeButton->hoverOut(); + m_snippetButton->hoverOut(); + m_maximizeButton->hoverOut(); +} + +void QtCodeFileTitleBar::updateFromOther(const QtCodeFileTitleBar* other) +{ + m_titleButton->updateFromOther(other->getTitleButton()); + + setRefString(other->m_referenceCount->text()); + + m_minimizeButton->setEnabled(other->m_minimizeButton->isEnabled()); + m_snippetButton->setEnabled(other->m_snippetButton->isEnabled()); + m_maximizeButton->setEnabled(other->m_maximizeButton->isEnabled()); + + m_minimizeButton->hoverOut(); + m_snippetButton->hoverOut(); + m_maximizeButton->hoverOut(); + + disconnect(); + + connect(this, &QPushButton::clicked, this, &QtCodeFileTitleBar::clickedTitleBar); + connect(this, &QtHoverButton::hoveredIn, this, &QtCodeFileTitleBar::enteredTitleBar); + connect(this, &QtHoverButton::hoveredOut, this, &QtCodeFileTitleBar::leftTitleBar); +} + +void QtCodeFileTitleBar::clickedTitleBar() +{ + if (m_minimizeButton->isEnabled()) + { + emit minimize(); + } + else if (m_snippetButton->isEnabled()) + { + emit snippet(); + } + else + { + emit maximize(); + } +} + +void QtCodeFileTitleBar::enteredTitleBar(QPushButton* button) +{ + if (m_minimizeButton->isEnabled()) + { + m_minimizeButton->hoverIn(); + } + else if (m_snippetButton->isEnabled()) + { + m_snippetButton->hoverIn(); + } + else if (m_maximizeButton->isEnabled()) + { + m_maximizeButton->hoverIn(); + } +} + +void QtCodeFileTitleBar::leftTitleBar(QPushButton* button) +{ + if (m_minimizeButton->isEnabled()) + { + if (m_minimizeButton != button) + { + m_minimizeButton->hoverOut(); + } + } + else if (m_snippetButton->isEnabled()) + { + if (m_snippetButton != button) + { + m_snippetButton->hoverOut(); + } + } + else if (m_maximizeButton->isEnabled()) + { + if (m_maximizeButton != button) + { + m_maximizeButton->hoverOut(); + } + } +} + +void QtCodeFileTitleBar::clickedMinimizeButton() +{ + emit minimize(); +} + +void QtCodeFileTitleBar::clickedSnippetButton() +{ + emit snippet(); +} + +void QtCodeFileTitleBar::clickedMaximizeButton() +{ + emit maximize(); +} diff --git a/src/lib_gui/qt/element/QtCodeFileTitleBar.h b/src/lib_gui/qt/element/QtCodeFileTitleBar.h new file mode 100644 index 00000000..e303f7fb --- /dev/null +++ b/src/lib_gui/qt/element/QtCodeFileTitleBar.h @@ -0,0 +1,53 @@ +#ifndef QT_CODE_FILE_TITLE_BAR_H +#define QT_CODE_FILE_TITLE_BAR_H + +#include "qt/element/QtCodeFileTitleButton.h" +#include "qt/element/QtIconButton.h" + +class QLabel; +class QtCodeFileTitleButton; +class QtIconStateButton; + +class QtCodeFileTitleBar + : public QtHoverButton +{ + Q_OBJECT + +signals: + void minimize(); + void snippet(); + void maximize(); + +public: + QtCodeFileTitleBar(QWidget* parent = nullptr, bool isHovering = false); + + QtCodeFileTitleButton* getTitleButton() const; + + void setRefString(const QString& refString); + + void setMinimized(bool hasSnippets); + void setSnippets(); + void setMaximized(bool hasSnippets); + + void updateFromOther(const QtCodeFileTitleBar* other); + +private slots: + void clickedTitleBar(); + + void enteredTitleBar(QPushButton* button); + void leftTitleBar(QPushButton* button); + + void clickedMinimizeButton(); + void clickedSnippetButton(); + void clickedMaximizeButton(); + +private: + QtCodeFileTitleButton* m_titleButton; + QLabel* m_referenceCount; + + QtIconStateButton* m_minimizeButton; + QtIconStateButton* m_snippetButton; + QtIconStateButton* m_maximizeButton; +}; + +#endif // QT_CODE_FILE_TITLE_BAR_H diff --git a/src/lib_gui/qt/element/QtCodeFileTitleButton.cpp b/src/lib_gui/qt/element/QtCodeFileTitleButton.cpp index c9a6aa52..a3fdec71 100644 --- a/src/lib_gui/qt/element/QtCodeFileTitleButton.cpp +++ b/src/lib_gui/qt/element/QtCodeFileTitleButton.cpp @@ -126,6 +126,21 @@ void QtCodeFileTitleButton::updateTexts() setToolTip(toolTip.c_str()); } +void QtCodeFileTitleButton::updateFromOther(const QtCodeFileTitleButton* other) +{ + if (!other->m_filePath.empty()) + { + setFilePath(other->m_filePath); + } + else + { + setProject(other->text().toStdString()); + } + + setModificationTime(other->m_modificationTime); + setIsComplete(other->m_isComplete); +} + void QtCodeFileTitleButton::contextMenuEvent(QContextMenuEvent* event) { FilePath path = m_filePath; diff --git a/src/lib_gui/qt/element/QtCodeFileTitleButton.h b/src/lib_gui/qt/element/QtCodeFileTitleButton.h index 848252d1..dfd29120 100644 --- a/src/lib_gui/qt/element/QtCodeFileTitleButton.h +++ b/src/lib_gui/qt/element/QtCodeFileTitleButton.h @@ -22,6 +22,8 @@ public: void updateTexts(); + void updateFromOther(const QtCodeFileTitleButton* other); + protected: void contextMenuEvent(QContextMenuEvent* event); diff --git a/src/lib_gui/qt/element/QtCodeNavigateable.cpp b/src/lib_gui/qt/element/QtCodeNavigateable.cpp index 78b12dfb..19f897a5 100644 --- a/src/lib_gui/qt/element/QtCodeNavigateable.cpp +++ b/src/lib_gui/qt/element/QtCodeNavigateable.cpp @@ -11,7 +11,7 @@ QtCodeNavigateable::~QtCodeNavigateable() } void QtCodeNavigateable::ensureWidgetVisibleAnimated( - QWidget* parentWidget, QWidget *childWidget, QRectF rect, bool animated, bool onTop) + const QWidget* parentWidget, const QWidget *childWidget, QRectF rect, bool animated, ScrollTarget target) { QAbstractScrollArea* area = getScrollArea(); if (!area || !parentWidget->isAncestorOf(childWidget)) @@ -19,11 +19,7 @@ void QtCodeNavigateable::ensureWidgetVisibleAnimated( 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(parentWidget, microFocus.topLeft()), microFocus.size()) - : QRect(childWidget->mapTo(parentWidget, QPoint(0, 0)), childWidget->size()); + QRect focusRect = getFocusRectForWidget(childWidget, parentWidget); const QRect visibleRect(-parentWidget->pos(), area->viewport()->size()); if (rect.height() > 0) @@ -36,15 +32,42 @@ void QtCodeNavigateable::ensureWidgetVisibleAnimated( } } - QScrollBar* scrollBar = area->verticalScrollBar(); - int value = focusRect.center().y() - visibleRect.center().y(); - if (onTop || focusRect.height() > visibleRect.height()) + // scroll to top if widget is bigger than view + if (focusRect.height() > visibleRect.height()) { - value = focusRect.top() - visibleRect.top() - 20; + target = SCROLL_TOP; } - if (scrollBar && (value > 50 || value < -50)) + int value = 0; + switch (target) + { + case SCROLL_VISIBLE: + if (focusRect.top() < visibleRect.top()) + { + value = focusRect.top() - visibleRect.top() - 20; + } + else if (focusRect.bottom() > visibleRect.bottom()) + { + value = focusRect.bottom() - visibleRect.bottom() + 20; + } + break; + + case SCROLL_CENTER: + value = focusRect.center().y() - visibleRect.center().y(); + if (abs(value) < 50) + { + value = 0; + } + break; + + case SCROLL_TOP: + value = focusRect.top() - visibleRect.top() - 50; + break; + } + + QScrollBar* scrollBar = area->verticalScrollBar(); + if (scrollBar && value) { if (animated && ApplicationSettings::getInstance()->getUseAnimations()) { @@ -62,7 +85,7 @@ void QtCodeNavigateable::ensureWidgetVisibleAnimated( } } -void QtCodeNavigateable::ensurePercentVisibleAnimated(double percentA, double percentB, bool animated, bool onTop) +void QtCodeNavigateable::ensurePercentVisibleAnimated(double percentA, double percentB, bool animated, ScrollTarget target) { QAbstractScrollArea* area = getScrollArea(); if (!area) @@ -78,45 +101,43 @@ void QtCodeNavigateable::ensurePercentVisibleAnimated(double percentA, double pe return; } - int scrollHeight = totalHeight * percentA; - if (!onTop) - { - if (percentB) - { - int scrollHeightB = totalHeight * percentB; - int rectHeight = scrollHeightB - scrollHeight; - - if (rectHeight < visibleHeight) - { - if (rectHeight < visibleHeight / 2) - { - scrollHeight -= visibleHeight / 4; - } - else - { - scrollHeight += rectHeight / 2 - visibleHeight / 2; - } - } - else - { - scrollHeight -= 20; - } - } - else - { - scrollHeight -= visibleHeight / 4; - } - } - else - { - scrollHeight -= 20; - } - QScrollBar* scrollBar = area->verticalScrollBar(); double scrollFactor = double(scrollBar->maximum()) / scrollableHeight; - int value = scrollHeight * scrollFactor; + int visibleY = double(scrollBar->value()) / scrollFactor; + int scrollY = totalHeight * percentA; + int rectHeight = percentB ? (totalHeight * percentB) - scrollY : 0; + if (rectHeight > visibleHeight) + { + target = SCROLL_TOP; + } + + switch (target) + { + case SCROLL_VISIBLE: + if (scrollY > visibleY && scrollY + rectHeight < visibleY + scrollableHeight) + { + return; + } + + case SCROLL_CENTER: + if (rectHeight < visibleHeight / 2) + { + scrollY -= visibleHeight / 4; + } + else + { + scrollY += rectHeight / 2 - visibleHeight / 2; + } + break; + + case SCROLL_TOP: + scrollY -= 20; + break; + } + + int value = scrollY * scrollFactor; int diff = value - scrollBar->value(); if (diff > 5 || diff < -5) { @@ -135,3 +156,19 @@ void QtCodeNavigateable::ensurePercentVisibleAnimated(double percentA, double pe } } } + +QRect QtCodeNavigateable::getFocusRectForWidget(const QWidget* childWidget, const QWidget* parentWidget) const +{ + const QRect microFocus = childWidget->inputMethodQuery(Qt::ImCursorRectangle).toRect(); + const QRect defaultMicroFocus = childWidget->QWidget::inputMethodQuery(Qt::ImCursorRectangle).toRect(); + + if (microFocus != defaultMicroFocus) + { + return QRect(childWidget->mapTo(parentWidget, microFocus.topLeft()), microFocus.size()); + } + else + { + return QRect(childWidget->mapTo(parentWidget, QPoint(0, 0)), childWidget->size()); + } +} + diff --git a/src/lib_gui/qt/element/QtCodeNavigateable.h b/src/lib_gui/qt/element/QtCodeNavigateable.h index 8bf09aef..ef093a9b 100644 --- a/src/lib_gui/qt/element/QtCodeNavigateable.h +++ b/src/lib_gui/qt/element/QtCodeNavigateable.h @@ -10,12 +10,20 @@ class FilePath; class QRectF; class QAbstractScrollArea; +class QRect; class QtCodeArea; class QWidget; class QtCodeNavigateable { public: + enum ScrollTarget + { + SCROLL_VISIBLE, + SCROLL_CENTER, + SCROLL_TOP + }; + virtual ~QtCodeNavigateable(); virtual QAbstractScrollArea* getScrollArea() = 0; @@ -23,7 +31,7 @@ public: virtual void addCodeSnippet(const CodeSnippetParams& params) = 0; virtual void requestFileContent(const FilePath& filePath) = 0; - virtual bool requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop) = 0; + virtual bool requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, ScrollTarget target) = 0; virtual void updateFiles() = 0; virtual void showContents() = 0; @@ -33,8 +41,10 @@ public: virtual void findScreenMatches(const std::string& query, std::vector>* screenMatches) = 0; protected: - void ensureWidgetVisibleAnimated(QWidget* parentWidget, QWidget *childWidget, QRectF rect, bool animated, bool onTop); - void ensurePercentVisibleAnimated(double percentA, double percentB, bool animated, bool onTop); + void ensureWidgetVisibleAnimated(const QWidget* parentWidget, const QWidget *childWidget, QRectF rect, bool animated, ScrollTarget target); + void ensurePercentVisibleAnimated(double percentA, double percentB, bool animated, ScrollTarget target); + + QRect getFocusRectForWidget(const QWidget* childWidget, const QWidget* parentWidget) const; }; #endif // QT_CODE_NAVIGATEABLE_H diff --git a/src/lib_gui/qt/element/QtCodeNavigator.cpp b/src/lib_gui/qt/element/QtCodeNavigator.cpp index dcaff526..6120e20f 100644 --- a/src/lib_gui/qt/element/QtCodeNavigator.cpp +++ b/src/lib_gui/qt/element/QtCodeNavigator.cpp @@ -451,7 +451,7 @@ void QtCodeNavigator::showActiveSnippet( { if (scrollTo) { - requestScroll(firstReference.filePath, 0, firstReference.locationId, true, false); + requestScroll(firstReference.filePath, 0, firstReference.locationId, true, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); } @@ -569,7 +569,8 @@ void QtCodeNavigator::activateScreenMatch(size_t matchIndex) m_currentActiveLocationIds.insert(m_activeScreenMatchId); p.first->updateContent(); - requestScroll(p.first->getSourceLocationFile()->getFilePath(), 0, m_activeScreenMatchId, true, false); + requestScroll( + p.first->getSourceLocationFile()->getFilePath(), 0, m_activeScreenMatchId, true, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); } @@ -614,7 +615,7 @@ void QtCodeNavigator::scrollToValue(int value, bool inListMode) void QtCodeNavigator::scrollToLine(const FilePath& filePath, unsigned int line) { - requestScroll(filePath, line, 0, false, false); + requestScroll(filePath, line, 0, false, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); } @@ -634,7 +635,7 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen m_list->requestFileContent(m_activeReference.filePath); } - requestScroll(m_activeReference.filePath, 0, m_activeReference.locationId, true, false); + requestScroll(m_activeReference.filePath, 0, m_activeReference.locationId, true, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); updateRefLabel(); @@ -651,7 +652,8 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen { if (m_references.size() && m_references.front().locationType != LOCATION_TOKEN) { - requestScroll(m_references.front().filePath, 0, m_references.front().locationId, false, false); + requestScroll( + m_references.front().filePath, 0, m_references.front().locationId, false, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); } return; @@ -662,7 +664,8 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen std::pair result = m_list->getFirstSnippetWithActiveLocationId(m_activeTokenId); if (result.first != nullptr) { - requestScroll(result.first->getFile()->getFilePath(), 0, result.second, animated, false); + requestScroll( + result.first->getFile()->getFilePath(), 0, result.second, animated, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); return; } @@ -672,7 +675,7 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen Id locationId = m_single->getLocationIdOfFirstActiveLocationOfTokenId(m_activeTokenId); if (locationId) { - requestScroll(m_single->getCurrentFilePath(), 0, locationId, true, false); + requestScroll(m_single->getCurrentFilePath(), 0, locationId, true, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); return; } @@ -681,7 +684,7 @@ void QtCodeNavigator::scrollToDefinition(bool animated, bool ignoreActiveReferen if (m_references.size()) { m_current->requestFileContent(m_references.front().filePath); - requestScroll(m_references.front().filePath, 0, m_references.front().locationId, false, false); + requestScroll(m_references.front().filePath, 0, m_references.front().locationId, false, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); } } @@ -691,14 +694,15 @@ void QtCodeNavigator::scrollToSnippetIfRequested() emit scrollRequest(); } -void QtCodeNavigator::requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop) +void QtCodeNavigator::requestScroll( + const FilePath& filePath, uint lineNumber, Id locationId, bool animated, QtCodeNavigateable::ScrollTarget target) { ScrollRequest req; req.filePath = filePath; req.lineNumber = lineNumber; req.locationId = locationId; req.animated = animated; - req.onTop = onTop; + req.target = target; if (m_mode == MODE_SINGLE) { @@ -709,9 +713,9 @@ void QtCodeNavigator::requestScroll(const FilePath& filePath, uint lineNumber, I } // std::cout << "scroll request: " << req.filePath.str() << " " << req.lineNumber << " " << req.locationId; - // std::cout << " " << req.animated << " " << req.onTop << std::endl; + // std::cout << " " << req.animated << " " << req.target << std::endl; - if ((!m_scrollRequest.lineNumber || !m_scrollRequest.locationId) && (req.lineNumber || req.locationId)) + if ((!m_scrollRequest.lineNumber || !m_scrollRequest.locationId) && !req.filePath.empty()) { m_scrollRequest = req; } @@ -727,7 +731,10 @@ void QtCodeNavigator::handleScrollRequest() return; } - bool done = m_current->requestScroll(req.filePath, req.lineNumber, req.locationId, req.animated, req.onTop); + // std::cout << "handle scroll request: " << req.filePath.str() << " " << req.lineNumber << " " << req.locationId; + // std::cout << " " << req.animated << " " << req.target << std::endl; + + bool done = m_current->requestScroll(req.filePath, req.lineNumber, req.locationId, req.animated, req.target); if (done) { m_scrollRequest = ScrollRequest(); @@ -907,7 +914,7 @@ void QtCodeNavigator::handleMessage(MessageShowReference* message) setCurrentActiveLocationIds(std::vector(1, ref.locationId)); updateFiles(); - requestScroll(ref.filePath, 0, ref.locationId, true, false); + requestScroll(ref.filePath, 0, ref.locationId, true, QtCodeNavigateable::SCROLL_CENTER); emit scrollRequest(); if (ref.locationType == LOCATION_ERROR) diff --git a/src/lib_gui/qt/element/QtCodeNavigator.h b/src/lib_gui/qt/element/QtCodeNavigator.h index 4dfc2f62..747f810a 100644 --- a/src/lib_gui/qt/element/QtCodeNavigator.h +++ b/src/lib_gui/qt/element/QtCodeNavigator.h @@ -94,7 +94,8 @@ public: void scrollToSnippetIfRequested(); - void requestScroll(const FilePath& filePath, uint lineNumber, Id locationId, bool animated, bool onTop); + void requestScroll( + const FilePath& filePath, uint lineNumber, Id locationId, bool animated, QtCodeNavigateable::ScrollTarget target); signals: void scrollRequest(); @@ -146,7 +147,7 @@ private: : lineNumber(0) , locationId(0) , animated(false) - , onTop(false) + , target(QtCodeNavigateable::SCROLL_VISIBLE) { } @@ -155,7 +156,7 @@ private: Id locationId; bool animated; - bool onTop; + QtCodeNavigateable::ScrollTarget target; }; void handleMessage(MessageCodeReference* message); diff --git a/src/lib_gui/qt/element/QtCodeSnippet.cpp b/src/lib_gui/qt/element/QtCodeSnippet.cpp index 4e9a20da..e5e87cdb 100644 --- a/src/lib_gui/qt/element/QtCodeSnippet.cpp +++ b/src/lib_gui/qt/element/QtCodeSnippet.cpp @@ -203,7 +203,7 @@ void QtCodeSnippet::clickedTitle() getFile()->clickedMaximizeButton(); } - m_navigator->requestScroll(getFile()->getFilePath(), getStartLineNumber(), 0, true, false); + m_navigator->requestScroll(getFile()->getFilePath(), getStartLineNumber(), 0, true, QtCodeNavigateable::SCROLL_CENTER); } void QtCodeSnippet::clickedFooter() @@ -212,7 +212,7 @@ void QtCodeSnippet::clickedFooter() { MessageShowScope(m_footerId, m_navigator->hasErrors()).dispatch(); - m_navigator->requestScroll(getFile()->getFilePath(), getEndLineNumber(), 0, true, false); + m_navigator->requestScroll(getFile()->getFilePath(), getEndLineNumber(), 0, true, QtCodeNavigateable::SCROLL_CENTER); } }