ui: Added tabs UI to top of main window (issue #215)

* Added tab bar to top of main window with UI/shortcuts similar to web browsers
* ComponentManager offers components for 2 use-cases: application and tab
* Application components: log view (status/errors), bookmarks, screen search, status bar, tabs, tooltips, dialogs and all tab views disabled
* Tab components: graph, code, history and search
* When a project is loaded there is always at least one tab, otherwise there is none
* Each tab replaces the application views of the same name in the main layout when activated
* Each tab has it's own TaskScheduler, to process tasks independent from other tabs
* The application has a global TaskScheduler for application wide tasks
* The singloton class TaskManager is responsible for managing the TaskSchedulers
* MessageListeners and Messages hold an optional schedulerId, both must be set to only send to a specific TaskScheduler
* Bookmark buttons where split off into a separate view per tab, bookmark managemement is done in the application
* History menu processing is done by the QtMainWindow now
* Screen search is an application component, the responders are always changed to the active tab
* Plugin messages are opened in a new tab
* Symbols can be opened in a new tab via middle click/context menu in graph, code, history dropdown
* Full text index creation is mutexed now in PersistenStorage to make it fully thread-safe
* All tabs are closed when switching projects
* Tab contents are only animated when visible

fortune cookie message = Catch your lucky star!
This commit is contained in:
Eberhard Graether
2018-11-05 01:28:12 +01:00
parent bd4a554b27
commit f13aec70dd
170 changed files with 2972 additions and 823 deletions
+10 -5
View File
@@ -620,6 +620,7 @@ void QtCodeArea::mousePressEvent(QMouseEvent* event)
void QtCodeArea::mouseReleaseEvent(QMouseEvent* event)
{
const int panningThreshold = 5;
if (event->button() == Qt::LeftButton)
{
m_isSelecting = false;
@@ -636,9 +637,7 @@ void QtCodeArea::mouseReleaseEvent(QMouseEvent* event)
}
else
{
QTextCursor cursor = this->cursorForPosition(event->pos());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(cursor.position());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(event->pos());
if (annotations.size())
{
if (m_navigator->hasErrors())
@@ -657,6 +656,10 @@ void QtCodeArea::mouseReleaseEvent(QMouseEvent* event)
}
}
}
else
{
QtCodeField::mouseReleaseEvent(event);
}
}
void QtCodeArea::mouseMoveEvent(QMouseEvent* event)
@@ -681,8 +684,7 @@ void QtCodeArea::mouseMoveEvent(QMouseEvent* event)
scrollbar->setValue(scrollbar->value() - utility::roundToInt(deltaPosRatio * scrollbar->pageStep()));
}
QTextCursor cursor = this->cursorForPosition(event->pos());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(cursor.position());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(event->pos());
bool same = annotations.size() == m_hoveredAnnotations.size();
if (same)
@@ -730,9 +732,12 @@ void QtCodeArea::contextMenuEvent(QContextMenuEvent* event)
{
m_eventPosition = event->pos();
checkOpenInTabActionEnabled(event->pos());
m_setIDECursorPositionAction->setEnabled(!getSourceLocationFile()->getFilePath().empty());
QtContextMenu menu(event, this);
menu.addAction(m_openInTabAction);
menu.addUndoActions();
menu.addSeparator();
menu.addFileActions(getSourceLocationFile()->getFilePath());
menu.addSeparator();
+64 -6
View File
@@ -1,17 +1,20 @@
#include "QtCodeField.h"
#include <QAction>
#include <QPainter>
#include <QTextBlock>
#include <QTextCodec>
#include "SourceLocation.h"
#include "SourceLocationFile.h"
#include "QtContextMenu.h"
#include "QtHighlighter.h"
#include "ApplicationSettings.h"
#include "ColorScheme.h"
#include "MessageActivateLocalSymbols.h"
#include "MessageActivateSourceLocations.h"
#include "MessageActivateTokenIds.h"
#include "MessageTabOpenWith.h"
#include "MessageTooltipShow.h"
#include "TextCodec.h"
#include "tracing.h"
@@ -99,6 +102,12 @@ QtCodeField::QtCodeField(
font.setPixelSize(appSettings->getFontSize());
setFont(font);
setTabStopWidth(appSettings->getCodeTabWidth() * fontMetrics().width('9'));
m_openInTabAction = new QAction("Open in New Tab", this);
m_openInTabAction->setStatusTip("Opens the node in a new tab");
m_openInTabAction->setToolTip("Opens the node in a new tab");
m_openInTabAction->setEnabled(false);
connect(m_openInTabAction, &QAction::triggered, this, &QtCodeField::openInTab);
}
QtCodeField::~QtCodeField()
@@ -251,8 +260,7 @@ void QtCodeField::leaveEvent(QEvent* event)
void QtCodeField::mouseMoveEvent(QMouseEvent* event)
{
QTextCursor cursor = this->cursorForPosition(event->pos());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(cursor.position());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(event->pos());
bool same = annotations.size() == m_hoveredAnnotations.size();
if (same)
@@ -275,6 +283,13 @@ void QtCodeField::mouseMoveEvent(QMouseEvent* event)
void QtCodeField::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::MiddleButton)
{
checkOpenInTabActionEnabled(event->pos());
openInTab();
return;
}
if (event->button() != Qt::LeftButton)
{
return;
@@ -282,9 +297,7 @@ void QtCodeField::mouseReleaseEvent(QMouseEvent* event)
viewport()->setCursor(Qt::ArrowCursor);
QTextCursor cursor = this->cursorForPosition(event->pos());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(cursor.position());
std::vector<const Annotation*> annotations = getInteractiveAnnotationsForPosition(event->pos());
if (!annotations.size())
{
return;
@@ -293,6 +306,16 @@ void QtCodeField::mouseReleaseEvent(QMouseEvent* event)
activateAnnotations(annotations);
}
void QtCodeField::contextMenuEvent(QContextMenuEvent* event)
{
checkOpenInTabActionEnabled(event->pos());
QtContextMenu menu(event, nullptr);
menu.addAction(m_openInTabAction);
menu.addUndoActions();
menu.show();
}
void QtCodeField::focusTokenIds(const std::vector<Id>& focusedTokenIds)
{
annotateText(std::set<Id>(), std::set<Id>(), std::set<Id>(focusedTokenIds.begin(), focusedTokenIds.end()));
@@ -639,10 +662,13 @@ void QtCodeField::setTextColorForAnnotation(const Annotation& annotation, QColor
m_highlighter->applyFormat(annotation.start, annotation.end, format);
}
std::vector<const QtCodeField::Annotation*> QtCodeField::getInteractiveAnnotationsForPosition(int pos) const
std::vector<const QtCodeField::Annotation*> QtCodeField::getInteractiveAnnotationsForPosition(QPoint position) const
{
std::vector<const QtCodeField::Annotation*> annotations;
QTextCursor cursor = this->cursorForPosition(position);
int pos = cursor.position();
for (const Annotation& annotation : m_annotations)
{
const LocationType& type = annotation.locationType;
@@ -656,6 +682,38 @@ std::vector<const QtCodeField::Annotation*> QtCodeField::getInteractiveAnnotatio
return annotations;
}
void QtCodeField::checkOpenInTabActionEnabled(QPoint position)
{
std::vector<Id> locationIds;
for (const Annotation* annotation : getInteractiveAnnotationsForPosition(position))
{
const LocationType& type = annotation->locationType;
if (type == LOCATION_TOKEN || type == LOCATION_QUALIFIER)
{
locationIds.emplace_back(annotation->locationId);
}
}
if (locationIds.size())
{
m_openInTabLocationId = locationIds[0];
}
else
{
m_openInTabLocationId = 0;
}
m_openInTabAction->setEnabled(m_openInTabLocationId);
}
void QtCodeField::openInTab()
{
if (m_openInTabLocationId)
{
MessageTabOpenWith(0, m_openInTabLocationId).dispatch();
}
}
void QtCodeField::createLineLengthCache()
{
m_endTextEditPosition = -1;
+13 -1
View File
@@ -51,6 +51,8 @@ protected:
virtual void mouseMoveEvent(QMouseEvent* event) Q_DECL_OVERRIDE;
virtual void mouseReleaseEvent(QMouseEvent* event) Q_DECL_OVERRIDE;
virtual void contextMenuEvent(QContextMenuEvent* event) Q_DECL_OVERRIDE;
virtual void focusTokenIds(const std::vector<Id>& tokenIds);
virtual void defocusTokenIds(const std::vector<Id>& tokenIds);
@@ -99,12 +101,20 @@ protected:
const AnnotationColor& getAnnotationColorForAnnotation(const Annotation& annotation);
void setTextColorForAnnotation(const Annotation& annotation, QColor color) const;
std::vector<const Annotation*> getInteractiveAnnotationsForPosition(int pos) const;
std::vector<const Annotation*> getInteractiveAnnotationsForPosition(QPoint position) const;
std::vector<Id> getInteractiveTokenIdsForPosition(QPoint position) const;
void checkOpenInTabActionEnabled(QPoint position);
std::vector<Annotation> m_annotations;
std::vector<const Annotation*> m_hoveredAnnotations;
std::vector<int> m_linesToRehighlight;
QAction* m_openInTabAction;
private slots:
void openInTab();
private:
static std::vector<AnnotationColor> s_annotationColors;
@@ -123,6 +133,8 @@ private:
std::vector<std::vector<std::pair<int, int>>> m_multibyteCharacterLocations;
int m_endTextEditPosition;
Id m_openInTabLocationId;
};
#endif // QT_CODE_FIELD_H
@@ -1,8 +1,11 @@
#include "QtCodeFileTitleButton.h"
#include <QMouseEvent>
#include "FileSystem.h"
#include "MessageActivateFile.h"
#include "MessageProjectEdit.h"
#include "MessageTabOpenWith.h"
#include "ResourcePaths.h"
#include "utilityString.h"
@@ -25,6 +28,12 @@ QtCodeFileTitleButton::QtCodeFileTitleButton(QWidget* parent)
setIconSize(QSize(16, 16));
connect(this, &QtCodeFileTitleButton::clicked, this, &QtCodeFileTitleButton::clickedTitle);
m_openInTabAction = new QAction("Open in New Tab", this);
m_openInTabAction->setStatusTip("Opens the file in a new tab");
m_openInTabAction->setToolTip("Opens the file in a new tab");
m_openInTabAction->setEnabled(false);
connect(m_openInTabAction, &QAction::triggered, this, &QtCodeFileTitleButton::openInTab);
}
QtCodeFileTitleButton::~QtCodeFileTitleButton()
@@ -147,6 +156,17 @@ void QtCodeFileTitleButton::updateFromOther(const QtCodeFileTitleButton* other)
updateTexts();
}
void QtCodeFileTitleButton::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::MiddleButton)
{
openInTab();
return;
}
QtSelfRefreshIconButton::mouseReleaseEvent(event);
}
void QtCodeFileTitleButton::contextMenuEvent(QContextMenuEvent* event)
{
FilePath path = m_filePath;
@@ -161,7 +181,11 @@ void QtCodeFileTitleButton::contextMenuEvent(QContextMenuEvent* event)
path = currentProject->getProjectSettingsFilePath();
}
m_openInTabAction->setEnabled(!m_filePath.empty());
QtContextMenu menu(event, this);
menu.addAction(m_openInTabAction);
menu.addUndoActions();
menu.addSeparator();
menu.addFileActions(path);
menu.show();
@@ -186,6 +210,14 @@ void QtCodeFileTitleButton::clickedTitle()
}
}
void QtCodeFileTitleButton::openInTab()
{
if (!m_filePath.empty())
{
MessageTabOpenWith(m_filePath).dispatch();
}
}
void QtCodeFileTitleButton::updateIcon()
{
if (m_filePath.empty())
@@ -31,12 +31,14 @@ public:
void updateFromOther(const QtCodeFileTitleButton* other);
protected:
void mouseReleaseEvent(QMouseEvent* event);
void contextMenuEvent(QContextMenuEvent* event);
virtual void refresh();
private slots:
void clickedTitle();
void openInTab();
private:
void updateIcon();
@@ -46,6 +48,8 @@ private:
TimeStamp m_modificationTime;
bool m_isComplete;
bool m_isIndexed;
QAction* m_openInTabAction;
};
#endif // QT_CODE_FILE_TITLE_BUTTON_H
@@ -64,7 +64,7 @@ void QtCodeNavigateable::ensureWidgetVisibleAnimated(
QScrollBar* scrollBar = area->verticalScrollBar();
if (scrollBar && value)
{
if (animated && ApplicationSettings::getInstance()->getUseAnimations())
if (animated && ApplicationSettings::getInstance()->getUseAnimations() && area->isVisible())
{
QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value");
anim->setDuration(300);
@@ -136,7 +136,7 @@ void QtCodeNavigateable::ensurePercentVisibleAnimated(double percentA, double pe
int diff = value - scrollBar->value();
if (diff > 5 || diff < -5)
{
if (animated && ApplicationSettings::getInstance()->getUseAnimations())
if (animated && ApplicationSettings::getInstance()->getUseAnimations() && area->isVisible())
{
QPropertyAnimation* anim = new QPropertyAnimation(scrollBar, "value");
anim->setDuration(300);
+23 -1
View File
@@ -10,6 +10,7 @@
#include "MessageShowError.h"
#include "MessageScrollCode.h"
#include "ResourcePaths.h"
#include "TabId.h"
#include "utility.h"
#include "SourceLocation.h"
@@ -26,6 +27,7 @@ QtCodeNavigator::QtCodeNavigator(QWidget* parent)
: QWidget(parent)
, m_mode(MODE_NONE)
, m_oldMode(MODE_NONE)
, m_schedulerId(TabId::ignore())
, m_activeTokenId(0)
, m_value(0)
, m_refIndex(0)
@@ -338,6 +340,16 @@ void QtCodeNavigator::setMode(Mode mode)
}
}
Id QtCodeNavigator::getSchedulerId() const
{
return m_schedulerId;
}
void QtCodeNavigator::setSchedulerId(Id schedulerId)
{
m_schedulerId = schedulerId;
}
const std::set<Id>& QtCodeNavigator::getCurrentActiveTokenIds() const
{
return m_currentActiveTokenIds;
@@ -708,6 +720,11 @@ void QtCodeNavigator::deactivateScreenMatch(size_t matchIndex)
m_activeScreenMatchId = 0;
}
bool QtCodeNavigator::hasScreenMatches() const
{
return !m_screenMatches.empty();
}
void QtCodeNavigator::clearScreenMatches()
{
if (m_activeScreenMatchId)
@@ -847,7 +864,7 @@ void QtCodeNavigator::requestScroll(
void QtCodeNavigator::handleScrollRequest()
{
const ScrollRequest& req = m_scrollRequest;
if (req.filePath.empty())
if (req.filePath.empty() || !isVisible())
{
return;
}
@@ -871,6 +888,11 @@ void QtCodeNavigator::scrolled(int value)
MessageScrollCode(value, m_mode == MODE_LIST).dispatch();
}
void QtCodeNavigator::showEvent(QShowEvent* event)
{
emit scrollRequest();
}
void QtCodeNavigator::setValue()
{
QAbstractScrollArea* area = m_current->getScrollArea();
+14 -5
View File
@@ -56,6 +56,9 @@ public:
void setMode(Mode mode);
Id getSchedulerId() const override;
void setSchedulerId(Id schedulerId);
const std::set<Id>& getCurrentActiveTokenIds() const;
void setCurrentActiveTokenIds(const std::vector<Id>& currentActiveTokenIds);
@@ -101,6 +104,7 @@ public:
size_t findScreenMatches(const std::wstring& query);
void activateScreenMatch(size_t matchIndex);
void deactivateScreenMatch(size_t matchIndex);
bool hasScreenMatches() const;
void clearScreenMatches();
void scrollToValue(int value, bool inListMode);
@@ -118,6 +122,9 @@ signals:
public slots:
void scrolled(int value);
protected:
void showEvent(QShowEvent* event) override;
private slots:
void handleScrollRequest();
void setValue();
@@ -172,11 +179,11 @@ private:
QtCodeNavigateable::ScrollTarget target;
};
void handleMessage(MessageCodeReference* message);
void handleMessage(MessageIndexingFinished* message);
void handleMessage(MessageShowReference* message);
void handleMessage(MessageSwitchColorScheme* message);
void handleMessage(MessageWindowFocus* message);
void handleMessage(MessageCodeReference* message) override;
void handleMessage(MessageIndexingFinished* message) override;
void handleMessage(MessageShowReference* message) override;
void handleMessage(MessageSwitchColorScheme* message) override;
void handleMessage(MessageWindowFocus* message) override;
QtThreadedLambdaFunctor m_onQtThread;
@@ -187,6 +194,8 @@ private:
Mode m_mode;
Mode m_oldMode;
Id m_schedulerId;
std::set<Id> m_currentActiveTokenIds;
std::set<Id> m_currentActiveLocationIds;
std::set<Id> m_currentActiveLocalLocationIds;
+34 -4
View File
@@ -2,9 +2,11 @@
#include <QBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QScrollBar>
#include "MessageHistoryToPosition.h"
#include "MessageTabOpenWith.h"
#include "ResourcePaths.h"
#include "utilityString.h"
@@ -12,11 +14,11 @@
#include "utilityQt.h"
#include "GraphViewStyle.h"
#include "SearchMatch.h"
#include "ColorScheme.h"
QtHistoryItem::QtHistoryItem(const SearchMatch& match, size_t index, bool isCurrent)
: index(index)
, m_match(match)
{
QBoxLayout* layout = new QHBoxLayout();
layout->setSpacing(0);
@@ -74,6 +76,11 @@ QSize QtHistoryItem::getSizeHint() const
return QSize(m_name->fontMetrics().width(m_name->text()) + 40, m_name->fontMetrics().height() + 8);
}
const SearchMatch& QtHistoryItem::getMatch() const
{
return m_match;
}
void QtHistoryItem::enterEvent(QEvent *event)
{
QWidget::enterEvent(event);
@@ -111,13 +118,36 @@ void QtHistoryItem::leaveEvent(QEvent *event)
}
QtHistoryListWidget::QtHistoryListWidget(QWidget* parent)
: QListWidget(parent)
{
}
void QtHistoryListWidget::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::MiddleButton)
{
QtHistoryItem* item = dynamic_cast<QtHistoryItem*>(itemWidget(itemAt(event->pos())));
if (item)
{
MessageTabOpenWith(item->getMatch()).dispatch();
}
return;
}
QListWidget::mouseReleaseEvent(event);
}
QtHistoryList::QtHistoryList(const std::vector<SearchMatch>& history, size_t currentIndex)
: m_currentIndex(currentIndex)
{
setWindowFlags(Qt::Popup);
setObjectName("history");
m_list = new QListWidget(this);
m_list = new QtHistoryListWidget(this);
m_list->setObjectName("history_list");
m_list->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_list->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -141,8 +171,8 @@ void QtHistoryList::showPopup(QPoint pos)
QSize size(50, 2);
for (int i = 0; i < m_list->count(); i++)
{
QtHistoryItem* it = dynamic_cast<QtHistoryItem*>(m_list->itemWidget(m_list->item(i)));
QSize itemSize = it->getSizeHint();
QtHistoryItem* item = dynamic_cast<QtHistoryItem*>(m_list->itemWidget(m_list->item(i)));
QSize itemSize = item->getSizeHint();
if (itemSize.width() > size.width())
{
size.setWidth(itemSize.width());
+18 -2
View File
@@ -3,8 +3,9 @@
#include <QListWidget>
#include "SearchMatch.h"
class QLabel;
struct SearchMatch;
class QtHistoryItem
: public QWidget
@@ -16,6 +17,8 @@ public:
QSize getSizeHint() const;
const SearchMatch& getMatch() const;
size_t index;
protected:
@@ -29,6 +32,19 @@ private:
QWidget* m_indicator;
std::string m_indicatorColor;
std::string m_indicatorHoverColor;
const SearchMatch m_match;
};
class QtHistoryListWidget
: public QListWidget
{
public:
QtHistoryListWidget(QWidget* parent = nullptr);
protected:
void mouseReleaseEvent(QMouseEvent* event);
};
@@ -52,7 +68,7 @@ private slots:
void onItemClicked(QListWidgetItem *item);
private:
QListWidget* m_list;
QtHistoryListWidget* m_list;
size_t m_currentIndex;
};
+9 -4
View File
@@ -141,10 +141,15 @@ void QtScreenSearchBox::setMatchIndex(size_t matchIndex)
void QtScreenSearchBox::addResponder(const std::string& name)
{
if (m_checkBoxes.find(name) != m_checkBoxes.end())
{
return;
}
QCheckBox* box = new QCheckBox(name.c_str());
box->setObjectName("filter_checkbox");
box->setChecked(true);
m_checkBoxes.push_back(box);
m_checkBoxes.emplace(name, box);
m_checkboxLayout->addWidget(box);
connect(box, &QCheckBox::stateChanged, this, &QtScreenSearchBox::findMatches);
@@ -173,11 +178,11 @@ void QtScreenSearchBox::findMatches()
[this](ScreenSearchController* controller)
{
std::set<std::string> responderNames;
for (QCheckBox* box : m_checkBoxes)
for (auto p : m_checkBoxes)
{
if (box->isChecked())
if (p.second->isChecked())
{
responderNames.insert(box->text().toStdString());
responderNames.insert(p.first);
}
}
+1 -1
View File
@@ -74,7 +74,7 @@ private:
QtSelfRefreshIconButton* m_closeButton;
QHBoxLayout* m_checkboxLayout;
std::vector<QCheckBox*> m_checkBoxes;
std::map<std::string, QCheckBox*> m_checkBoxes;
size_t m_matchCount = 0;
size_t m_matchIndex = 0;
+22
View File
@@ -0,0 +1,22 @@
#include "QtTabBar.h"
QtTabBar::QtTabBar(QWidget* parent)
: QTabBar(parent)
{
setFocusPolicy(Qt::NoFocus);
}
QSize QtTabBar::minimumSizeHint() const
{
return QSize(0, QTabBar::minimumSizeHint().height());
}
QSize QtTabBar::tabSizeHint(int index) const
{
return QSize(300, QTabBar::tabSizeHint(index).height());
}
QSize QtTabBar::minimumTabSizeHint(int index) const
{
return QSize(45, QTabBar::minimumTabSizeHint(index).height());
}
+19
View File
@@ -0,0 +1,19 @@
#ifndef QT_TAB_BAR_H
#define QT_TAB_BAR_H
#include <QTabBar>
class QtTabBar
: public QTabBar
{
public:
QtTabBar(QWidget* parent = nullptr);
protected:
QSize minimumSizeHint() const override;
QSize tabSizeHint(int index) const override;
QSize minimumTabSizeHint(int index) const override;
};
#endif // QT_TAB_BAR_H