diff --git a/bin/app/data/gui/history_list/history_list.css b/bin/app/data/gui/history_list/history_list.css new file mode 100644 index 00000000..d4429ae7 --- /dev/null +++ b/bin/app/data/gui/history_list/history_list.css @@ -0,0 +1,19 @@ +QListWidget::item { + border-bottom: 1px solid ; +} + +#history_list { + background: ; +} + +#history_item, #history_item_current { + color: ; + font-family: ""; + font-size: px; + padding-left: 20px; + padding-top: 1px; +} + +#history_item_current { + font-weight: bold; +} diff --git a/bin/app/data/gui/history_list/images/arrow.png b/bin/app/data/gui/history_list/images/arrow.png new file mode 100644 index 00000000..f12ba7df Binary files /dev/null and b/bin/app/data/gui/history_list/images/arrow.png differ diff --git a/bin/app/data/gui/undoredo_view/images/history.png b/bin/app/data/gui/undoredo_view/images/history.png new file mode 100644 index 00000000..3d4c42b4 Binary files /dev/null and b/bin/app/data/gui/undoredo_view/images/history.png differ diff --git a/bin/app/data/gui/undoredo_view/undoredo_view.css b/bin/app/data/gui/undoredo_view/undoredo_view.css index 90e8178b..e3fb111d 100644 --- a/bin/app/data/gui/undoredo_view/undoredo_view.css +++ b/bin/app/data/gui/undoredo_view/undoredo_view.css @@ -5,7 +5,6 @@ QPushButton { background: ; border: none; - background-position: 15px 5px; height: 30px; width: 30px; } @@ -13,7 +12,6 @@ QPushButton { #undo_button { border-bottom-left-radius: 12px; border-top-left-radius: 12px; - margin-right: 2px; } #redo_button { @@ -21,6 +19,12 @@ QPushButton { border-top-right-radius: 12px; } +#history_button { + width: 14px; + margin-left: 2px; + margin-right: 2px; +} + QPushButton:disabled { background: ; } diff --git a/src/lib/Application.cpp b/src/lib/Application.cpp index 722f9e16..b9765cca 100644 --- a/src/lib/Application.cpp +++ b/src/lib/Application.cpp @@ -158,6 +158,11 @@ bool Application::isInTrial() const return m_isInTrial; } +void Application::updateHistory(const std::vector& history) +{ + m_mainView->updateHistoryMenu(history); +} + void Application::createAndLoadProject(const FilePath& projectSettingsFilePath) { MessageStatus("Loading Project: " + projectSettingsFilePath.str(), false, true).dispatch(); diff --git a/src/lib/Application.h b/src/lib/Application.h index 216daf48..40e1b36a 100644 --- a/src/lib/Application.h +++ b/src/lib/Application.h @@ -53,6 +53,8 @@ public: bool isInTrial() const; + void updateHistory(const std::vector& history); + private: static std::shared_ptr s_instance; static std::string s_uuid; diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index c7128572..ddcd7712 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -397,6 +397,7 @@ add_files( utility/messaging/type/MessageStatus.h utility/messaging/type/MessageStatusFilterChanged.h utility/messaging/type/MessageSwitchColorScheme.h + utility/messaging/type/MessageToUndoRedoPosition.h utility/messaging/type/MessageUndo.h utility/messaging/type/MessageWindowClosed.h utility/messaging/type/MessageWindowFocus.h diff --git a/src/lib/component/controller/ActivationController.cpp b/src/lib/component/controller/ActivationController.cpp index f114d348..04e2667d 100644 --- a/src/lib/component/controller/ActivationController.cpp +++ b/src/lib/component/controller/ActivationController.cpp @@ -56,6 +56,7 @@ void ActivationController::handleMessage(MessageActivateFile* message) MessageActivateTokens m(message); m.tokenIds.push_back(fileId); m.tokenNames.push_back(message->filePath.str()); + m.searchMatches = m_storageAccess->getSearchMatchesForTokenIds({ fileId }); m.dispatchImmediately(); } else @@ -87,6 +88,7 @@ void ActivationController::handleMessage(MessageActivateNodes* message) } m.tokenNames.push_back(node.nameHierarchy); } + m.searchMatches = m_storageAccess->getSearchMatchesForTokenIds(m.tokenIds); m.dispatchImmediately(); } @@ -128,7 +130,7 @@ void ActivationController::handleMessage(MessageSearch* message) m.tokenIds = message->getTokenIdsOfMatches(); m.searchMatches = matches; m.tokenNames = m_storageAccess->getNameHierarchiesForNodeIds(m.tokenIds); - m.isFromSearch = true; + m.isFromSearch = message->isFromSearch; m.dispatchImmediately(); } diff --git a/src/lib/component/controller/UndoRedoController.cpp b/src/lib/component/controller/UndoRedoController.cpp index a2123d6c..07e9696e 100644 --- a/src/lib/component/controller/UndoRedoController.cpp +++ b/src/lib/component/controller/UndoRedoController.cpp @@ -5,6 +5,7 @@ #include "utility/messaging/type/MessageSearch.h" #include "utility/utility.h" +#include "Application.h" #include "component/view/UndoRedoView.h" #include "data/access/StorageAccess.h" @@ -28,6 +29,9 @@ void UndoRedoController::clear() m_list.clear(); m_iterator = m_list.begin(); + m_history.clear(); + m_historyOffset = 0; + getView()->setUndoButtonEnabled(false); getView()->setRedoButtonEnabled(false); } @@ -199,6 +203,8 @@ void UndoRedoController::handleMessage(MessageRedo* message) } replayCommands(oldIterator); + + updateHistory(); } void UndoRedoController::handleMessage(MessageRefresh* message) @@ -288,6 +294,54 @@ void UndoRedoController::handleMessage(MessageShowScope* message) processCommand(command); } +void UndoRedoController::handleMessage(MessageToUndoRedoPosition* message) +{ + size_t index = 0; + const size_t activeIndex = message->index + m_historyOffset; + + for (std::list::reverse_iterator rit = m_list.rbegin(); rit != m_list.rend(); rit++) + { + if (rit->order == Command::ORDER_ACTIVATE) + { + if (index == activeIndex) + { + std::list::iterator it = --(rit.base()); + std::list::iterator start = it; + + do + { + std::advance(it, 1); + } + while (it != m_list.end() && it->order != Command::ORDER_ACTIVATE); + + m_iterator = it; + + if (start != m_iterator) + { + replayCommands(start); + } + else + { + replayCommand(m_iterator); + MessageFlushUpdates(false).dispatch(); + } + } + + index++; + + if (index > activeIndex + 1) + { + break; + } + } + } + + getView()->setUndoButtonEnabled(index > activeIndex + 1); + getView()->setRedoButtonEnabled(m_iterator != m_list.end() && m_iterator->order == Command::ORDER_ACTIVATE); + + updateHistory(); +} + void UndoRedoController::handleMessage(MessageUndo* message) { if (!m_list.size()) @@ -325,6 +379,8 @@ void UndoRedoController::handleMessage(MessageUndo* message) m_iterator = it; replayCommands(); + + updateHistory(); } void UndoRedoController::replayCommands() @@ -410,7 +466,7 @@ void UndoRedoController::replayCommand(std::list::iterator it) if (!msg->isEdge && !msg->isAggregation) { msg->tokenIds = m_storageAccess->getNodeIdsForNameHierarchies(msg->tokenNames); - msg->searchMatches.clear(); + msg->searchMatches = m_storageAccess->getSearchMatchesForTokenIds(msg->tokenIds); } } @@ -468,6 +524,11 @@ void UndoRedoController::processCommand(Command command) getView()->setRedoButtonEnabled(false); } } + + if (command.order == Command::ORDER_ACTIVATE) + { + updateHistory(); + } } bool UndoRedoController::sameMessageTypeAsLast(MessageBase* message) const @@ -485,6 +546,101 @@ MessageBase* UndoRedoController::lastMessage() const return std::prev(m_iterator)->message.get(); } +void UndoRedoController::updateHistory() +{ + const size_t historySize = 20; + + std::vector matches; + bool firstActiveMessage = false; + + size_t index = 0; + int currentIndex = -1; + m_historyOffset = 0; + + for (std::list::const_reverse_iterator it = m_list.rbegin(); it != m_list.rend(); it++) + { + if (m_iterator == it.base()) + { + currentIndex = index; + } + + if (it->order == Command::ORDER_ACTIVATE) + { + index++; + + SearchMatch match = getSearchMatchForMessage(it->message.get()); + if (!firstActiveMessage) + { + firstActiveMessage = true; + + for (size_t i = 0; i < m_history.size(); i++) + { + if (m_history[i] == match) + { + m_history.erase(m_history.begin() + i); + break; + } + } + m_history.push_front(match); + if (m_history.size() > historySize) + { + m_history.pop_back(); + } + } + + if (match.text.size()) + { + matches.push_back(match); + + if (matches.size() > historySize) + { + matches.erase(matches.begin()); + m_historyOffset++; + } + + if (matches.size() == historySize && currentIndex != -1 && currentIndex - m_historyOffset != historySize - 1) + { + break; + } + } + } + } + + getView()->updateHistory(matches, currentIndex - m_historyOffset); + Application::getInstance()->updateHistory(std::vector(m_history.begin(), m_history.end())); +} + +SearchMatch UndoRedoController::getSearchMatchForMessage(MessageBase* message) const +{ + if (message->getType() == MessageActivateAll::getStaticType()) + { + return SearchMatch::createCommand(SearchMatch::COMMAND_ALL); + } + else if (message->getType() == MessageActivateTokens::getStaticType()) + { + MessageActivateTokens* msg = dynamic_cast(message); + if (msg->searchMatches.size()) + { + return msg->searchMatches.front(); + } + } + else if (message->getType() == MessageSearchFullText::getStaticType()) + { + MessageSearchFullText* msg = dynamic_cast(message); + std::string prefix(msg->caseSensitive ? 2 : 1, SearchMatch::FULLTEXT_SEARCH_CHARACTER); + + SearchMatch match(prefix + msg->searchTerm); + match.searchType = SearchMatch::SEARCH_FULLTEXT; + return match; + } + else if (message->getType() == MessageShowErrors::getStaticType()) + { + return SearchMatch::createCommand(SearchMatch::COMMAND_ERROR); + } + + return SearchMatch(); +} + void UndoRedoController::dump() const { std::cout << "\nUndo Redo Stack:\n----------\n"; diff --git a/src/lib/component/controller/UndoRedoController.h b/src/lib/component/controller/UndoRedoController.h index caa36c91..33d7b25f 100644 --- a/src/lib/component/controller/UndoRedoController.h +++ b/src/lib/component/controller/UndoRedoController.h @@ -24,6 +24,7 @@ #include "utility/messaging/type/MessageShowErrors.h" #include "utility/messaging/type/MessageShowReference.h" #include "utility/messaging/type/MessageShowScope.h" +#include "utility/messaging/type/MessageToUndoRedoPosition.h" #include "utility/messaging/type/MessageUndo.h" #include "component/controller/Controller.h" @@ -52,6 +53,7 @@ class UndoRedoController , public MessageListener , public MessageListener , public MessageListener + , public MessageListener , public MessageListener { public: @@ -98,6 +100,7 @@ private: virtual void handleMessage(MessageShowErrors* message); virtual void handleMessage(MessageShowReference* message); virtual void handleMessage(MessageShowScope* message); + virtual void handleMessage(MessageToUndoRedoPosition* message); virtual void handleMessage(MessageUndo* message); void replayCommands(); @@ -109,12 +112,18 @@ private: bool sameMessageTypeAsLast(MessageBase* message) const; MessageBase* lastMessage() const; + void updateHistory(); + SearchMatch getSearchMatchForMessage(MessageBase* message) const; + void dump() const; StorageAccess* m_storageAccess; std::list m_list; std::list::iterator m_iterator; + + std::deque m_history; + size_t m_historyOffset; }; #endif // UNDO_REDO_CONTROLLER_H diff --git a/src/lib/component/view/MainView.h b/src/lib/component/view/MainView.h index 7f353767..cbb70362 100644 --- a/src/lib/component/view/MainView.h +++ b/src/lib/component/view/MainView.h @@ -6,6 +6,8 @@ #include "component/view/ViewLayout.h" +struct SearchMatch; + class MainView : public ViewLayout { @@ -19,7 +21,9 @@ public: virtual void hideStartScreen() = 0; virtual void setTitle(const std::string& title) = 0; virtual void activateWindow() = 0; + virtual void updateRecentProjectMenu() = 0; + virtual void updateHistoryMenu(const std::vector& history) = 0; }; #endif // MAIN_VIEW_H diff --git a/src/lib/component/view/UndoRedoView.h b/src/lib/component/view/UndoRedoView.h index f9e81c39..3bcd064a 100644 --- a/src/lib/component/view/UndoRedoView.h +++ b/src/lib/component/view/UndoRedoView.h @@ -4,6 +4,7 @@ #include "component/view/View.h" class UndoRedoController; +struct SearchMatch; class UndoRedoView : public View { @@ -15,6 +16,9 @@ public: virtual void setRedoButtonEnabled(bool enabled) = 0; virtual void setUndoButtonEnabled(bool enabled) = 0; + + virtual void updateHistory(const std::vector& searchMatches, size_t currentIndex) = 0; + protected: UndoRedoController* getController(); }; diff --git a/src/lib/data/search/SearchMatch.cpp b/src/lib/data/search/SearchMatch.cpp index 8bded0b4..4f2cdeee 100644 --- a/src/lib/data/search/SearchMatch.cpp +++ b/src/lib/data/search/SearchMatch.cpp @@ -170,6 +170,11 @@ bool SearchMatch::operator<(const SearchMatch& other) const return false; } +bool SearchMatch::operator==(const SearchMatch& other) const +{ + return text == other.text && searchType == other.searchType; +} + size_t SearchMatch::getTextSizeForSorting(const std::string* str) const { // check if templated symbol and only use size up to template stuff diff --git a/src/lib/data/search/SearchMatch.h b/src/lib/data/search/SearchMatch.h index bc7169e6..119b8bb6 100644 --- a/src/lib/data/search/SearchMatch.h +++ b/src/lib/data/search/SearchMatch.h @@ -42,6 +42,7 @@ struct SearchMatch SearchMatch(const std::string& query); bool operator<(const SearchMatch& other) const; + bool operator==(const SearchMatch& other) const; size_t getTextSizeForSorting(const std::string* str) const; diff --git a/src/lib/utility/messaging/type/MessageSearch.h b/src/lib/utility/messaging/type/MessageSearch.h index 5f679c6b..57ba3c9a 100644 --- a/src/lib/utility/messaging/type/MessageSearch.h +++ b/src/lib/utility/messaging/type/MessageSearch.h @@ -11,7 +11,8 @@ class MessageSearch { public: MessageSearch(const std::vector& matches) - : m_matches(matches) + : isFromSearch(true) + , m_matches(matches) { } @@ -70,6 +71,8 @@ public: os << getMatchesAsString(); } + bool isFromSearch; + private: const std::vector m_matches; }; diff --git a/src/lib/utility/messaging/type/MessageToUndoRedoPosition.h b/src/lib/utility/messaging/type/MessageToUndoRedoPosition.h new file mode 100644 index 00000000..b269372f --- /dev/null +++ b/src/lib/utility/messaging/type/MessageToUndoRedoPosition.h @@ -0,0 +1,24 @@ +#ifndef MESSAGE_TO_UNDO_REDO_POSITION_H +#define MESSAGE_TO_UNDO_REDO_POSITION_H + +#include "utility/messaging/Message.h" +#include "utility/types.h" + +class MessageToUndoRedoPosition + : public Message +{ +public: + MessageToUndoRedoPosition(size_t index) + : index(index) + { + } + + static const std::string getStaticType() + { + return "MessageToUndoRedoPosition"; + } + + size_t index; +}; + +#endif // MESSAGE_TO_UNDO_REDO_POSITION_H diff --git a/src/lib/utility/utilityString.cpp b/src/lib/utility/utilityString.cpp index 7d9150ab..b707b2d0 100644 --- a/src/lib/utility/utilityString.cpp +++ b/src/lib/utility/utilityString.cpp @@ -243,6 +243,24 @@ namespace utility return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback)); } + std::string elide(const std::string& str, ElideMode mode, size_t size) + { + if (str.size() <= size || str.size() <= 3) + { + return str; + } + + switch (mode) + { + case ELIDE_LEFT: + return "..." + str.substr(str.size() - size - 3, str.size()); + case ELIDE_MIDDLE: + return str.substr(0, size / 2 - 1) + "..." + str.substr(str.size() - (size / 2 - 2), str.size()); + case ELIDE_RIGHT: + return str.substr(0, size - 3) + "..."; + } + } + std::string substrBetween(const std::string &str, const std::string &delimiter1, const std::string &delimiter2) { size_t found_delimiter1 = str.find(delimiter1); diff --git a/src/lib/utility/utilityString.h b/src/lib/utility/utilityString.h index da681e8f..45a83a43 100644 --- a/src/lib/utility/utilityString.h +++ b/src/lib/utility/utilityString.h @@ -48,6 +48,15 @@ namespace utility std::string trim(const std::string &str); + enum ElideMode + { + ELIDE_LEFT, + ELIDE_MIDDLE, + ELIDE_RIGHT + }; + + std::string elide(const std::string& str, ElideMode mode, size_t size); + template ContainerType split(const std::string& str, const std::string& delimiter) { diff --git a/src/lib_gui/CMakeLists.txt b/src/lib_gui/CMakeLists.txt index d85edb37..4a5f1a1f 100644 --- a/src/lib_gui/CMakeLists.txt +++ b/src/lib_gui/CMakeLists.txt @@ -34,6 +34,8 @@ add_files( qt/element/QtFontPicker.h qt/element/QtHelpButton.cpp qt/element/QtHelpButton.h + qt/element/QtHistoryList.cpp + qt/element/QtHistoryList.h qt/element/QtIconButton.cpp qt/element/QtIconButton.h qt/element/QtLineEdit.cpp diff --git a/src/lib_gui/qt/element/QtHistoryList.cpp b/src/lib_gui/qt/element/QtHistoryList.cpp new file mode 100644 index 00000000..85f6876a --- /dev/null +++ b/src/lib_gui/qt/element/QtHistoryList.cpp @@ -0,0 +1,141 @@ +#include "QtHistoryList.h" + +#include +#include + +#include "utility/messaging/type/MessageToUndoRedoPosition.h" +#include "utility/ResourcePaths.h" +#include "utility/utilityString.h" + +#include "qt/utility/QtDeviceScaledPixmap.h" +#include "qt/utility/utilityQt.h" + +#include "component/view/GraphViewStyle.h" +#include "data/search/SearchMatch.h" +#include "settings/ColorScheme.h" + +QtHistoryItem::QtHistoryItem(const SearchMatch& match, size_t index, bool isCurrent) + : index(index) +{ + QBoxLayout* layout = new QHBoxLayout(); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + layout->setAlignment(Qt::AlignTop); + + std::string name = utility::elide(match.nodeType == Node::NODE_FILE ? match.text : match.name, utility::ELIDE_RIGHT, 100); + + m_name = new QLabel(name.c_str(), this); + m_name->setAttribute(Qt::WA_MacShowFocusRect, 0); + m_name->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + m_name->setObjectName(isCurrent ? "history_item_current" : "history_item"); + m_name->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + layout->addWidget(m_name); + + setLayout(layout); + + ColorScheme* scheme = ColorScheme::getInstance().get(); + std::string searchTextColor = scheme->getColor("search/popup/text"); + + std::string color; + std::string hoverColor; + std::string textColor = searchTextColor; + std::string textHoverColor = searchTextColor; + + if (match.searchType == SearchMatch::SEARCH_TOKEN) + { + color = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), false).fill; + hoverColor = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), true).fill; + textColor = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), false).text; + textHoverColor = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), true).text; + } + else + { + std::string typeName = match.getSearchTypeName(); + + color = scheme->getSearchTypeColor(typeName, "fill"); + hoverColor = scheme->getSearchTypeColor(typeName, "fill", "hover"); + textColor = scheme->getSearchTypeColor(typeName, "text"); + textHoverColor = scheme->getSearchTypeColor(typeName, "text", "hover");; + } + + std::stringstream css; + css << "QLabel { background-color:" << color << "; color:" << textColor << ";} "; + + if (!isCurrent) + { + css << "QLabel:hover { background-color:" << hoverColor << "; color:" << textHoverColor << ";} "; + } + + m_name->setStyleSheet(css.str().c_str()); + + if (isCurrent) + { + QSize size = getSizeHint(); + + QtDeviceScaledPixmap pixmap(QString::fromStdString(ResourcePaths::getGuiPath().str() + "history_list/images/arrow.png")); + pixmap.scaleToHeight(size.height() / 3); + + QLabel* arrow = new QLabel(this); + arrow->setPixmap(utility::colorizePixmap(pixmap.pixmap(), QColor(scheme->getColor("search/popup/text").c_str()))); + arrow->setGeometry(size.height() / 3, size.height() / 3, size.height() / 3, size.height() / 3); + arrow->show(); + } +} + +QSize QtHistoryItem::getSizeHint() const +{ + return QSize(m_name->fontMetrics().width(m_name->text()) + 40, m_name->fontMetrics().height() + 8); +} + + +QtHistoryList::QtHistoryList(const std::vector& history, size_t currentIndex) + : m_currentIndex(currentIndex) +{ + setObjectName("history_list"); + setWindowFlags(Qt::Popup); + + for (size_t i = 0; i < history.size(); i++) + { + QListWidgetItem *item = new QListWidgetItem(this); + QtHistoryItem* line = new QtHistoryItem(history[i], i, i == currentIndex); + item->setSizeHint(line->getSizeHint()); + setItemWidget(item, line); + } + + setStyleSheet(utility::getStyleSheet(ResourcePaths::getGuiPath().concat(FilePath("history_list/history_list.css"))).c_str()); + + connect(this, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(onItemClicked(QListWidgetItem*))); +} + +void QtHistoryList::showPopup(QPoint pos) +{ + QSize size(50, 1); + for (int i = 0; i < count(); i++) + { + QtHistoryItem* it = dynamic_cast(itemWidget(item(i))); + QSize itemSize = it->getSizeHint(); + if (itemSize.width() > size.width()) + { + size.setWidth(itemSize.width()); + } + + size.setHeight(size.height() + item(i)->sizeHint().height()); + } + + setGeometry(pos.x(), pos.y(), size.width(), size.height()); + show(); +} + +void QtHistoryList::onItemClicked(QListWidgetItem *item) +{ + QtHistoryItem* historyItem = dynamic_cast(itemWidget(item)); + if (historyItem) + { + if (historyItem->index != m_currentIndex) + { + MessageToUndoRedoPosition(historyItem->index).dispatch(); + } + close(); + } +} diff --git a/src/lib_gui/qt/element/QtHistoryList.h b/src/lib_gui/qt/element/QtHistoryList.h new file mode 100644 index 00000000..abdb7fbe --- /dev/null +++ b/src/lib_gui/qt/element/QtHistoryList.h @@ -0,0 +1,44 @@ +#ifndef QT_HISTORY_LIST_H +#define QT_HISTORY_LIST_H + +#include + +class QLabel; +struct SearchMatch; + +class QtHistoryItem + : public QWidget +{ + Q_OBJECT + +public: + QtHistoryItem(const SearchMatch& match, size_t index, bool isCurrent); + + QSize getSizeHint() const; + + size_t index; + +private: + QLabel* m_name; +}; + + + +class QtHistoryList + : public QListWidget +{ + Q_OBJECT + +public: + QtHistoryList(const std::vector& history, size_t currentIndex); + + void showPopup(QPoint pos); + +private slots: + void onItemClicked(QListWidgetItem *item); + +private: + size_t m_currentIndex; +}; + +#endif // QT_HISTORY_LIST_H diff --git a/src/lib_gui/qt/element/QtSmartSearchBox.cpp b/src/lib_gui/qt/element/QtSmartSearchBox.cpp index 8d04f01f..b4143354 100644 --- a/src/lib_gui/qt/element/QtSmartSearchBox.cpp +++ b/src/lib_gui/qt/element/QtSmartSearchBox.cpp @@ -724,7 +724,6 @@ void QtSmartSearchBox::updateElements() if (match.searchType == SearchMatch::SEARCH_TOKEN) { - element->setObjectName(QString::fromStdString("search_element_" + match.getNodeTypeAsUnderscoredString())); color = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), false).fill; hoverColor = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), true).fill; textColor = GraphViewStyle::getNodeColor(Node::getUnderscoredTypeString(match.nodeType), false).text; @@ -734,7 +733,6 @@ void QtSmartSearchBox::updateElements() { std::string typeName = match.getSearchTypeName(); - element->setObjectName(QString::fromStdString("search_element_" + typeName)); color = scheme->getSearchTypeColor(typeName, "fill"); hoverColor = scheme->getSearchTypeColor(typeName, "fill", "hover"); textColor = scheme->getSearchTypeColor(typeName, "text"); diff --git a/src/lib_gui/qt/element/QtUndoRedo.cpp b/src/lib_gui/qt/element/QtUndoRedo.cpp index 14ba97e0..fa1c006d 100644 --- a/src/lib_gui/qt/element/QtUndoRedo.cpp +++ b/src/lib_gui/qt/element/QtUndoRedo.cpp @@ -2,16 +2,19 @@ #include #include +#include #include "utility/messaging/type/MessageUndo.h" #include "utility/messaging/type/MessageRedo.h" #include "utility/ResourcePaths.h" +#include "qt/element/QtHistoryList.h" #include "qt/utility/utilityQt.h" #include "qt/utility/QtContextMenu.h" #include "settings/ApplicationSettings.h" QtUndoRedo::QtUndoRedo() + : m_pressed(false) { setObjectName("undo_redo_bar"); @@ -21,11 +24,17 @@ QtUndoRedo::QtUndoRedo() m_undoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac m_undoButton->setEnabled(false); + m_historyButton = new QPushButton(this); + m_historyButton->setObjectName("history_button"); + m_historyButton->setToolTip("history"); + m_historyButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac + m_historyButton->setEnabled(false); + m_redoButton = new QPushButton(this); m_redoButton->setObjectName("redo_button"); m_redoButton->setToolTip("forward"); m_redoButton->setAttribute(Qt::WA_LayoutUsesWidgetRect); // fixes layouting on Mac - m_redoButton->setEnabled(false); + m_redoButton->setEnabled(false); QBoxLayout* layout = new QHBoxLayout(); layout->setSpacing(0); @@ -33,47 +42,97 @@ QtUndoRedo::QtUndoRedo() layout->setAlignment(Qt::AlignTop); setLayout(layout); layout->addWidget(m_undoButton); + layout->addWidget(m_historyButton); layout->addWidget(m_redoButton); - connect(m_undoButton, SIGNAL(clicked()), this, SLOT(undo())); - connect(m_redoButton, SIGNAL(clicked()), this, SLOT(redo())); + connect(m_undoButton, SIGNAL(pressed()), this, SLOT(buttonPressed())); + connect(m_redoButton, SIGNAL(pressed()), this, SLOT(buttonPressed())); + connect(m_historyButton, SIGNAL(pressed()), this, SLOT(buttonPressed())); - refreshStyle(); + connect(m_undoButton, SIGNAL(released()), this, SLOT(undoReleased())); + connect(m_redoButton, SIGNAL(released()), this, SLOT(redoReleased())); + connect(m_historyButton, SIGNAL(released()), this, SLOT(showHistory())); + + refreshStyle(); } QtUndoRedo::~QtUndoRedo() { } -void QtUndoRedo::undo() +void QtUndoRedo::buttonPressed() { - MessageUndo().dispatch(); + m_pressed = true; + QTimer::singleShot(250, this, SLOT(showHistory())); + m_historyButton->setAttribute(Qt::WA_UnderMouse, false); } -void QtUndoRedo::redo() +void QtUndoRedo::undoReleased() { - MessageRedo().dispatch(); + if (m_pressed) + { + m_pressed = false; + MessageUndo().dispatch(); + } +} + +void QtUndoRedo::redoReleased() +{ + if (m_pressed) + { + m_pressed = false; + MessageRedo().dispatch(); + } +} + +void QtUndoRedo::showHistory() +{ + if (m_pressed) + { + m_pressed = false; + + m_undoButton->setDown(false); + m_redoButton->setDown(false); + m_historyButton->setDown(false); + + m_undoButton->setAttribute(Qt::WA_UnderMouse, false); + m_redoButton->setAttribute(Qt::WA_UnderMouse, false); + m_historyButton->setAttribute(Qt::WA_UnderMouse, false); + + QtHistoryList* list = new QtHistoryList(m_history, m_currentIndex); + + QPoint pos = m_undoButton->mapToGlobal(m_undoButton->pos()); + + list->showPopup(QPoint(pos.x(), pos.y() + m_undoButton->size().height() + 5)); + } } void QtUndoRedo::setUndoButtonEnabled(bool enabled) { - m_undoButton->setEnabled(enabled); + m_undoButton->setEnabled(enabled); QtContextMenu::getInstance()->enableUndo(enabled); - } void QtUndoRedo::setRedoButtonEnabled(bool enabled) { - m_redoButton->setEnabled(enabled); + m_redoButton->setEnabled(enabled); QtContextMenu::getInstance()->enableRedo(enabled); } +void QtUndoRedo::updateHistory(const std::vector& searchMatches, size_t currentIndex) +{ + m_history = searchMatches; + m_currentIndex = currentIndex; + m_historyButton->setEnabled(m_history.size() > 1); +} + void QtUndoRedo::refreshStyle() { float height = std::max(ApplicationSettings::getInstance()->getFontSize() + 16, 30); m_undoButton->setFixedHeight(height); m_redoButton->setFixedHeight(height); + m_historyButton->setFixedHeight(height); m_undoButton->setIcon(utility::createButtonIcon( ResourcePaths::getGuiPath().str() + "undoredo_view/images/arrow_left.png", @@ -84,4 +143,9 @@ void QtUndoRedo::refreshStyle() ResourcePaths::getGuiPath().str() + "undoredo_view/images/arrow_right.png", "search/button" )); + + m_historyButton->setIcon(utility::createButtonIcon( + ResourcePaths::getGuiPath().str() + "undoredo_view/images/history.png", + "search/button" + )); } diff --git a/src/lib_gui/qt/element/QtUndoRedo.h b/src/lib_gui/qt/element/QtUndoRedo.h index db23f9b5..818d3537 100644 --- a/src/lib_gui/qt/element/QtUndoRedo.h +++ b/src/lib_gui/qt/element/QtUndoRedo.h @@ -4,6 +4,7 @@ #include #include +#include "data/search/SearchMatch.h" class QPushButton; @@ -19,16 +20,27 @@ public: void setRedoButtonEnabled(bool enabled); void setUndoButtonEnabled(bool enabled); + void updateHistory(const std::vector& searchMatches, size_t currentIndex); + void refreshStyle(); private slots: - void undo(); - void redo(); + void buttonPressed(); + + void undoReleased(); + void redoReleased(); + + void showHistory(); private: QPushButton* m_undoButton; + QPushButton* m_historyButton; QPushButton* m_redoButton; + std::vector m_history; + size_t m_currentIndex; + + bool m_pressed; }; #endif // QT_UNDO_REDO_H diff --git a/src/lib_gui/qt/view/QtMainView.cpp b/src/lib_gui/qt/view/QtMainView.cpp index d68585ac..652bd0d8 100644 --- a/src/lib_gui/qt/view/QtMainView.cpp +++ b/src/lib_gui/qt/view/QtMainView.cpp @@ -104,6 +104,16 @@ void QtMainView::updateRecentProjectMenu() m_updateRecentProjectMenuFunctor(); } +void QtMainView::updateHistoryMenu(const std::vector& history) +{ + m_onQtThread( + [=]() + { + m_window->updateHistoryMenu(history); + } + ); +} + void QtMainView::handleMessage(MessageForceEnterLicense* message) { m_forceLicenseScreenFunctor(message->licenseExpired); diff --git a/src/lib_gui/qt/view/QtMainView.h b/src/lib_gui/qt/view/QtMainView.h index 6c29e982..e6e20984 100644 --- a/src/lib_gui/qt/view/QtMainView.h +++ b/src/lib_gui/qt/view/QtMainView.h @@ -49,6 +49,7 @@ public: virtual void setTitle(const std::string& title); virtual void activateWindow(); virtual void updateRecentProjectMenu(); + virtual void updateHistoryMenu(const std::vector& history); private: void handleMessage(MessageForceEnterLicense* message); diff --git a/src/lib_gui/qt/view/QtUndoRedoView.cpp b/src/lib_gui/qt/view/QtUndoRedoView.cpp index d99407a2..0b28ba56 100644 --- a/src/lib_gui/qt/view/QtUndoRedoView.cpp +++ b/src/lib_gui/qt/view/QtUndoRedoView.cpp @@ -1,17 +1,13 @@ #include "qt/view/QtUndoRedoView.h" +#include "utility/ResourcePaths.h" #include "qt/utility/utilityQt.h" #include "qt/view/QtMainView.h" #include "qt/view/QtViewWidgetWrapper.h" -#include "utility/ResourcePaths.h" - QtUndoRedoView::QtUndoRedoView(ViewLayout* viewLayout) : UndoRedoView(viewLayout) - , m_refreshFunctor(std::bind(&QtUndoRedoView::doRefreshView, this)) - , m_setRedoButtonEnabledFunctor(std::bind(&QtUndoRedoView::doSetRedoButtonEnabled, this, std::placeholders::_1)) - , m_setUndoButtonEnabledFunctor(std::bind(&QtUndoRedoView::doSetUndoButtonEnabled, this, std::placeholders::_1)) { m_widget = new QtUndoRedo(); setStyleSheet(); @@ -32,36 +28,47 @@ void QtUndoRedoView::initView() void QtUndoRedoView::refreshView() { - m_refreshFunctor(); -} - -void QtUndoRedoView::setStyleSheet() -{ - m_widget->setStyleSheet(utility::getStyleSheet(ResourcePaths::getGuiPath().concat(FilePath("undoredo_view/undoredo_view.css"))).c_str()); -} - -void QtUndoRedoView::doRefreshView() -{ - setStyleSheet(); - m_widget->refreshStyle(); -} - -void QtUndoRedoView::doSetRedoButtonEnabled(bool enabled) -{ - m_widget->setRedoButtonEnabled(enabled); -} - -void QtUndoRedoView::doSetUndoButtonEnabled(bool enabled) -{ - m_widget->setUndoButtonEnabled(enabled); + m_onQtThread( + [=]() + { + setStyleSheet(); + m_widget->refreshStyle(); + } + ); } void QtUndoRedoView::setRedoButtonEnabled(bool enabled) { - m_setRedoButtonEnabledFunctor(enabled); + m_onQtThread( + [=]() + { + m_widget->setRedoButtonEnabled(enabled); + } + ); } void QtUndoRedoView::setUndoButtonEnabled(bool enabled) { - m_setUndoButtonEnabledFunctor(enabled); + m_onQtThread( + [=]() + { + m_widget->setUndoButtonEnabled(enabled); + } + ); +} + +void QtUndoRedoView::updateHistory(const std::vector& searchMatches, size_t currentIndex) +{ + m_onQtThread( + [=]() + { + m_widget->updateHistory(searchMatches, currentIndex); + } + ); +} + +void QtUndoRedoView::setStyleSheet() +{ + m_widget->setStyleSheet(utility::getStyleSheet( + ResourcePaths::getGuiPath().concat(FilePath("undoredo_view/undoredo_view.css"))).c_str()); } diff --git a/src/lib_gui/qt/view/QtUndoRedoView.h b/src/lib_gui/qt/view/QtUndoRedoView.h index b38f31c0..c844944d 100644 --- a/src/lib_gui/qt/view/QtUndoRedoView.h +++ b/src/lib_gui/qt/view/QtUndoRedoView.h @@ -23,18 +23,14 @@ public: virtual void setRedoButtonEnabled(bool enabled); virtual void setUndoButtonEnabled(bool enabled); + virtual void updateHistory(const std::vector& searchMatches, size_t currentIndex); + private: - void doRefreshView(); - - void doSetRedoButtonEnabled(bool enabled); - void doSetUndoButtonEnabled(bool enabled); - - QtThreadedFunctor m_refreshFunctor; - QtThreadedFunctor m_setRedoButtonEnabledFunctor; - QtThreadedFunctor m_setUndoButtonEnabledFunctor; - void setStyleSheet(); + + QtThreadedLambdaFunctor m_onQtThread; + QtUndoRedo* m_widget; }; -#endif // !QT_UNDO_REDO_VIEW_H +#endif // QT_UNDO_REDO_VIEW_H diff --git a/src/lib_gui/qt/window/QtMainWindow.cpp b/src/lib_gui/qt/window/QtMainWindow.cpp index e4063481..73484073 100644 --- a/src/lib_gui/qt/window/QtMainWindow.cpp +++ b/src/lib_gui/qt/window/QtMainWindow.cpp @@ -95,7 +95,8 @@ bool MouseReleaseFilter::eventFilter(QObject* obj, QEvent* event) QtMainWindow::QtMainWindow() - : m_showDockWidgetTitleBars(true) + : m_historyMenu(nullptr) + , m_showDockWidgetTitleBars(true) , m_windowStack(this) { setObjectName("QtMainWindow"); @@ -115,6 +116,7 @@ QtMainWindow::QtMainWindow() setupProjectMenu(); setupEditMenu(); setupViewMenu(); + setupHistoryMenu(); setupBookmarksMenu(); setupHelpMenu(); @@ -252,6 +254,12 @@ void QtMainWindow::forceEnterLicense(bool expired) } } +void QtMainWindow::updateHistoryMenu(const std::vector& history) +{ + m_history = history; + setupHistoryMenu(); +} + bool QtMainWindow::event(QEvent* event) { if (event->type() == QEvent::WindowActivate) @@ -534,7 +542,7 @@ void QtMainWindow::resetWindowLayout() void QtMainWindow::openRecentProject() { - QAction *action = qobject_cast(sender()); + QAction *action = qobject_cast(sender()); if (action) { MessageLoadProject(FilePath(action->data().toString().toStdString()), false).dispatch(); @@ -590,6 +598,19 @@ void QtMainWindow::showBookmarkBrowser() MessageDisplayBookmarks().dispatch(); } +void QtMainWindow::openHistoryAction() +{ + QAction* action = qobject_cast(sender()); + if (action) + { + SearchMatch& match = m_history[action->data().toInt()]; + + MessageSearch msg({ match }); + msg.isFromSearch = false; + msg.dispatch(); + } +} + void QtMainWindow::setupProjectMenu() { QMenu *menu = new QMenu(tr("&Project"), this); @@ -626,10 +647,6 @@ void QtMainWindow::setupEditMenu() QMenu *menu = new QMenu(tr("&Edit"), this); menuBar()->addMenu(menu); - menu->addAction(tr("Back"), this, SLOT(undo()), QKeySequence::Undo); - menu->addAction(tr("Forward"), this, SLOT(redo()), QKeySequence::Redo); - - menu->addSeparator(); m_trialDisabledActions.push_back(menu->addAction(tr("&Refresh"), this, SLOT(refresh()), QKeySequence::Refresh)); if (QSysInfo::windowsVersion() != QSysInfo::WV_None) { @@ -689,6 +706,41 @@ void QtMainWindow::setupViewMenu() m_viewMenu = menu; } +void QtMainWindow::setupHistoryMenu() +{ + if (!m_historyMenu) + { + m_historyMenu = new QMenu(tr("&History"), this); + menuBar()->addMenu(m_historyMenu); + } + else + { + m_historyMenu->clear(); + } + + m_historyMenu->addAction(tr("Back"), this, SLOT(undo()), QKeySequence::Undo); + m_historyMenu->addAction(tr("Forward"), this, SLOT(redo()), QKeySequence::Redo); + + m_historyMenu->addSeparator(); + + QAction* title = new QAction(tr("Recently Active Symbols")); + title->setEnabled(false); + m_historyMenu->addAction(title); + + for (size_t i = 0; i < m_history.size(); i++) + { + SearchMatch& match = m_history[i]; + std::string name = utility::elide(match.nodeType == Node::NODE_FILE ? match.text : match.name, utility::ELIDE_RIGHT, 50); + + QAction* action = new QAction(); + action->setText(name.c_str()); + action->setData(QVariant(int(i))); + + connect(action, SIGNAL(triggered()), this, SLOT(openHistoryAction())); + m_historyMenu->addAction(action); + } +} + void QtMainWindow::setupBookmarksMenu() { QMenu *menu = new QMenu(tr("&Bookmarks"), this); diff --git a/src/lib_gui/qt/window/QtMainWindow.h b/src/lib_gui/qt/window/QtMainWindow.h index 824f6536..40cf4573 100644 --- a/src/lib_gui/qt/window/QtMainWindow.h +++ b/src/lib_gui/qt/window/QtMainWindow.h @@ -7,6 +7,7 @@ #include +#include "data/search/SearchMatch.h" #include "qt/utility/QtThreadedFunctor.h" #include "qt/window/QtWindowStack.h" @@ -69,6 +70,8 @@ public: void forceEnterLicense(bool expired); + void updateHistoryMenu(const std::vector& history); + protected: bool event(QEvent* event); void keyPressEvent(QKeyEvent* event); @@ -126,6 +129,8 @@ private slots: void showBookmarkCreator(); void showBookmarkBrowser(); + void openHistoryAction(); + private: struct DockWidget { @@ -138,6 +143,7 @@ private: void setupEditMenu(); void setupProjectMenu(); void setupViewMenu(); + void setupHistoryMenu(); void setupBookmarksMenu(); void setupHelpMenu(); @@ -153,6 +159,10 @@ private: std::vector m_dockWidgets; QMenu* m_viewMenu; QAction* m_viewSeparator; + + QMenu* m_historyMenu; + std::vector m_history; + QAction** m_recentProjectAction; QAction* m_showTitleBarsAction;