mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	citra-qt: game list search function (#2673)
* citra-qt: game list search function * Empty search field during game list refresh * Code improvements * Code formatting * Autofocus search field * JayFoxRox's recommendations * lioncash's review
This commit is contained in:
		
							parent
							
								
									ea53d6085a
								
							
						
					
					
						commit
						06d4654966
					
				
					 7 changed files with 299 additions and 19 deletions
				
			
		|  | @ -177,6 +177,7 @@ void Config::ReadValues() { | |||
| 
 | ||||
|     UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); | ||||
|     UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); | ||||
|     UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool(); | ||||
|     UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); | ||||
|     UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); | ||||
|     UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); | ||||
|  | @ -295,6 +296,7 @@ void Config::SaveValues() { | |||
| 
 | ||||
|     qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); | ||||
|     qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); | ||||
|     qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar); | ||||
|     qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); | ||||
|     qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); | ||||
|     qt_config->setValue("firstStart", UISettings::values.first_start); | ||||
|  |  | |||
|  | @ -4,9 +4,9 @@ | |||
| 
 | ||||
| #include <QFileInfo> | ||||
| #include <QHeaderView> | ||||
| #include <QKeyEvent> | ||||
| #include <QMenu> | ||||
| #include <QThreadPool> | ||||
| #include <QVBoxLayout> | ||||
| #include "common/common_paths.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/string_util.h" | ||||
|  | @ -15,10 +15,189 @@ | |||
| #include "game_list_p.h" | ||||
| #include "ui_settings.h" | ||||
| 
 | ||||
| GameList::GameList(QWidget* parent) : QWidget{parent} { | ||||
|     QVBoxLayout* layout = new QVBoxLayout; | ||||
| GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) { | ||||
|     this->gamelist = gamelist; | ||||
|     edit_filter_text_old = ""; | ||||
| } | ||||
| 
 | ||||
| // EventFilter in order to process systemkeys while editing the searchfield
 | ||||
| bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { | ||||
|     // If it isn't a KeyRelease event then continue with standard event processing
 | ||||
|     if (event->type() != QEvent::KeyRelease) | ||||
|         return QObject::eventFilter(obj, event); | ||||
| 
 | ||||
|     QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); | ||||
|     int rowCount = gamelist->tree_view->model()->rowCount(); | ||||
|     QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); | ||||
| 
 | ||||
|     // If the searchfield's text hasn't changed special function keys get checked
 | ||||
|     // If no function key changes the searchfield's text the filter doesn't need to get reloaded
 | ||||
|     if (edit_filter_text == edit_filter_text_old) { | ||||
|         switch (keyEvent->key()) { | ||||
|         // Escape: Resets the searchfield
 | ||||
|         case Qt::Key_Escape: { | ||||
|             if (edit_filter_text_old.isEmpty()) { | ||||
|                 return QObject::eventFilter(obj, event); | ||||
|             } else { | ||||
|                 gamelist->search_field->edit_filter->clear(); | ||||
|                 edit_filter_text = ""; | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         // Return and Enter
 | ||||
|         // If the enter key gets pressed first checks how many and which entry is visable
 | ||||
|         // If there is only one result launch this game
 | ||||
|         case Qt::Key_Return: | ||||
|         case Qt::Key_Enter: { | ||||
|             QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); | ||||
|             QModelIndex root_index = item_model->invisibleRootItem()->index(); | ||||
|             QStandardItem* child_file; | ||||
|             QString file_path; | ||||
|             int resultCount = 0; | ||||
|             for (int i = 0; i < rowCount; ++i) { | ||||
|                 if (!gamelist->tree_view->isRowHidden(i, root_index)) { | ||||
|                     ++resultCount; | ||||
|                     child_file = gamelist->item_model->item(i, 0); | ||||
|                     file_path = child_file->data(GameListItemPath::FullPathRole).toString(); | ||||
|                 } | ||||
|             } | ||||
|             if (resultCount == 1) { | ||||
|                 // To avoid loading error dialog loops while confirming them using enter
 | ||||
|                 // Also users usually want to run a diffrent game after closing one
 | ||||
|                 gamelist->search_field->edit_filter->setText(""); | ||||
|                 edit_filter_text = ""; | ||||
|                 emit gamelist->GameChosen(file_path); | ||||
|             } else { | ||||
|                 return QObject::eventFilter(obj, event); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         default: | ||||
|             return QObject::eventFilter(obj, event); | ||||
|         } | ||||
|     } | ||||
|     edit_filter_text_old = edit_filter_text; | ||||
|     return QObject::eventFilter(obj, event); | ||||
| } | ||||
| 
 | ||||
| void GameList::SearchField::setFilterResult(int visable, int total) { | ||||
|     QString result_of_text = tr("of"); | ||||
|     QString result_text; | ||||
|     if (total == 1) { | ||||
|         result_text = tr("result"); | ||||
|     } else { | ||||
|         result_text = tr("results"); | ||||
|     } | ||||
|     label_filter_result->setText( | ||||
|         QString("%1 %2 %3 %4").arg(visable).arg(result_of_text).arg(total).arg(result_text)); | ||||
| } | ||||
| 
 | ||||
| void GameList::SearchField::clear() { | ||||
|     edit_filter->setText(""); | ||||
| } | ||||
| 
 | ||||
| void GameList::SearchField::setFocus() { | ||||
|     if (edit_filter->isVisible()) { | ||||
|         edit_filter->setFocus(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { | ||||
|     KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); | ||||
|     layout_filter = new QHBoxLayout; | ||||
|     layout_filter->setMargin(8); | ||||
|     label_filter = new QLabel; | ||||
|     label_filter->setText(tr("Filter:")); | ||||
|     edit_filter = new QLineEdit; | ||||
|     edit_filter->setText(""); | ||||
|     edit_filter->setPlaceholderText(tr("Enter pattern to filter")); | ||||
|     edit_filter->installEventFilter(keyReleaseEater); | ||||
|     edit_filter->setClearButtonEnabled(true); | ||||
|     connect(edit_filter, SIGNAL(textChanged(const QString&)), parent, | ||||
|             SLOT(onTextChanged(const QString&))); | ||||
|     label_filter_result = new QLabel; | ||||
|     button_filter_close = new QToolButton(this); | ||||
|     button_filter_close->setText("X"); | ||||
|     button_filter_close->setCursor(Qt::ArrowCursor); | ||||
|     button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: " | ||||
|                                        "#000000; font-weight: bold; background: #F0F0F0; }" | ||||
|                                        "QToolButton:hover{ border: none; padding: 0px; color: " | ||||
|                                        "#EEEEEE; font-weight: bold; background: #E81123}"); | ||||
|     connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked())); | ||||
|     layout_filter->setSpacing(10); | ||||
|     layout_filter->addWidget(label_filter); | ||||
|     layout_filter->addWidget(edit_filter); | ||||
|     layout_filter->addWidget(label_filter_result); | ||||
|     layout_filter->addWidget(button_filter_close); | ||||
|     setLayout(layout_filter); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
| * Checks if all words separated by spaces are contained in another string | ||||
| * This offers a word order insensitive search function | ||||
| * | ||||
| * @param String that gets checked if it contains all words of the userinput string | ||||
| * @param String containing all words getting checked | ||||
| * @return true if the haystack contains all words of userinput | ||||
| */ | ||||
| bool GameList::containsAllWords(QString haystack, QString userinput) { | ||||
|     QStringList userinput_split = userinput.split(" ", QString::SplitBehavior::SkipEmptyParts); | ||||
|     return std::all_of(userinput_split.begin(), userinput_split.end(), | ||||
|                        [haystack](QString s) { return haystack.contains(s); }); | ||||
| } | ||||
| 
 | ||||
| // Event in order to filter the gamelist after editing the searchfield
 | ||||
| void GameList::onTextChanged(const QString& newText) { | ||||
|     int rowCount = tree_view->model()->rowCount(); | ||||
|     QString edit_filter_text = newText.toLower(); | ||||
| 
 | ||||
|     QModelIndex root_index = item_model->invisibleRootItem()->index(); | ||||
| 
 | ||||
|     // If the searchfield is empty every item is visible
 | ||||
|     // Otherwise the filter gets applied
 | ||||
|     if (edit_filter_text.isEmpty()) { | ||||
|         for (int i = 0; i < rowCount; ++i) { | ||||
|             tree_view->setRowHidden(i, root_index, false); | ||||
|         } | ||||
|         search_field->setFilterResult(rowCount, rowCount); | ||||
|     } else { | ||||
|         QStandardItem* child_file; | ||||
|         QString file_path, file_name, file_title, file_programmid; | ||||
|         int result_count = 0; | ||||
|         for (int i = 0; i < rowCount; ++i) { | ||||
|             child_file = item_model->item(i, 0); | ||||
|             file_path = child_file->data(GameListItemPath::FullPathRole).toString().toLower(); | ||||
|             file_name = file_path.mid(file_path.lastIndexOf("/") + 1); | ||||
|             file_title = child_file->data(GameListItemPath::TitleRole).toString().toLower(); | ||||
|             file_programmid = | ||||
|                 child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); | ||||
| 
 | ||||
|             // Only items which filename in combination with its title contains all words
 | ||||
|             // that are in the searchfiel will be visible in the gamelist
 | ||||
|             // The search is case insensitive because of toLower()
 | ||||
|             // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
 | ||||
|             // multiple conversions of edit_filter_text for each game in the gamelist
 | ||||
|             if (containsAllWords(file_name.append(" ").append(file_title), edit_filter_text) || | ||||
|                 (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) { | ||||
|                 tree_view->setRowHidden(i, root_index, false); | ||||
|                 ++result_count; | ||||
|             } else { | ||||
|                 tree_view->setRowHidden(i, root_index, true); | ||||
|             } | ||||
|             search_field->setFilterResult(result_count, rowCount); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GameList::onFilterCloseClicked() { | ||||
|     main_window->filterBarSetChecked(false); | ||||
| } | ||||
| 
 | ||||
| GameList::GameList(GMainWindow* parent) : QWidget{parent} { | ||||
|     this->main_window = parent; | ||||
|     layout = new QVBoxLayout; | ||||
|     tree_view = new QTreeView; | ||||
|     search_field = new SearchField(this); | ||||
|     item_model = new QStandardItemModel(tree_view); | ||||
|     tree_view->setModel(item_model); | ||||
| 
 | ||||
|  | @ -46,7 +225,9 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { | |||
|     qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | ||||
| 
 | ||||
|     layout->setContentsMargins(0, 0, 0, 0); | ||||
|     layout->setSpacing(0); | ||||
|     layout->addWidget(tree_view); | ||||
|     layout->addWidget(search_field); | ||||
|     setLayout(layout); | ||||
| } | ||||
| 
 | ||||
|  | @ -54,6 +235,18 @@ GameList::~GameList() { | |||
|     emit ShouldCancelWorker(); | ||||
| } | ||||
| 
 | ||||
| void GameList::setFilterFocus() { | ||||
|     search_field->setFocus(); | ||||
| } | ||||
| 
 | ||||
| void GameList::setFilterVisible(bool visablility) { | ||||
|     search_field->setVisible(visablility); | ||||
| } | ||||
| 
 | ||||
| void GameList::clearFilter() { | ||||
|     search_field->clear(); | ||||
| } | ||||
| 
 | ||||
| void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { | ||||
|     item_model->invisibleRootItem()->appendRow(entry_items); | ||||
| } | ||||
|  | @ -69,11 +262,16 @@ void GameList::ValidateEntry(const QModelIndex& item) { | |||
|     std::string std_file_path(file_path.toStdString()); | ||||
|     if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) | ||||
|         return; | ||||
|     // Users usually want to run a diffrent game after closing one
 | ||||
|     search_field->clear(); | ||||
|     emit GameChosen(file_path); | ||||
| } | ||||
| 
 | ||||
| void GameList::DonePopulating() { | ||||
|     tree_view->setEnabled(true); | ||||
|     int rowCount = tree_view->model()->rowCount(); | ||||
|     search_field->setFilterResult(rowCount, rowCount); | ||||
|     search_field->setFocus(); | ||||
| } | ||||
| 
 | ||||
| void GameList::PopupContextMenu(const QPoint& menu_location) { | ||||
|  | @ -151,25 +349,26 @@ static bool HasSupportedFileExtension(const std::string& file_name) { | |||
| void GameList::RefreshGameDirectory() { | ||||
|     if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { | ||||
|         LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); | ||||
|         search_field->clear(); | ||||
|         PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  * Adds the game list folder to the QFileSystemWatcher to check for updates. | ||||
|  * | ||||
|  * The file watcher will fire off an update to the game list when a change is detected in the game | ||||
|  * list folder. | ||||
|  * | ||||
|  * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and | ||||
|  * this function is fast enough to not stall the UI thread. If performance is an issue, it should | ||||
|  * be moved to another thread and properly locked to prevent concurrency issues. | ||||
|  * | ||||
|  * @param dir folder to check for changes in | ||||
|  * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each | ||||
|  *        directory recursively to the watcher and will update the file list if any of the folders | ||||
|  *        change. The number determines how deep the recursion should traverse. | ||||
|  */ | ||||
| * Adds the game list folder to the QFileSystemWatcher to check for updates. | ||||
| * | ||||
| * The file watcher will fire off an update to the game list when a change is detected in the game | ||||
| * list folder. | ||||
| * | ||||
| * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and | ||||
| * this function is fast enough to not stall the UI thread. If performance is an issue, it should | ||||
| * be moved to another thread and properly locked to prevent concurrency issues. | ||||
| * | ||||
| * @param dir folder to check for changes in | ||||
| * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each | ||||
| *        directory recursively to the watcher and will update the file list if any of the folders | ||||
| *        change. The number determines how deep the recursion should traverse. | ||||
| */ | ||||
| void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { | ||||
|     const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, | ||||
|                                             const std::string& virtual_name) -> bool { | ||||
|  |  | |||
|  | @ -5,13 +5,19 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <QFileSystemWatcher> | ||||
| #include <QHBoxLayout> | ||||
| #include <QLabel> | ||||
| #include <QLineEdit> | ||||
| #include <QModelIndex> | ||||
| #include <QSettings> | ||||
| #include <QStandardItem> | ||||
| #include <QStandardItemModel> | ||||
| #include <QString> | ||||
| #include <QToolButton> | ||||
| #include <QTreeView> | ||||
| #include <QVBoxLayout> | ||||
| #include <QWidget> | ||||
| #include "main.h" | ||||
| 
 | ||||
| class GameListWorker; | ||||
| 
 | ||||
|  | @ -26,9 +32,40 @@ public: | |||
|         COLUMN_COUNT, // Number of columns
 | ||||
|     }; | ||||
| 
 | ||||
|     explicit GameList(QWidget* parent = nullptr); | ||||
|     class SearchField : public QWidget { | ||||
|     public: | ||||
|         void setFilterResult(int visable, int total); | ||||
|         void clear(); | ||||
|         void setFocus(); | ||||
|         explicit SearchField(GameList* parent = nullptr); | ||||
| 
 | ||||
|     private: | ||||
|         class KeyReleaseEater : public QObject { | ||||
|         public: | ||||
|             explicit KeyReleaseEater(GameList* gamelist); | ||||
| 
 | ||||
|         private: | ||||
|             GameList* gamelist = nullptr; | ||||
|             QString edit_filter_text_old; | ||||
| 
 | ||||
|         protected: | ||||
|             bool eventFilter(QObject* obj, QEvent* event); | ||||
|         }; | ||||
|         QHBoxLayout* layout_filter = nullptr; | ||||
|         QTreeView* tree_view = nullptr; | ||||
|         QLabel* label_filter = nullptr; | ||||
|         QLineEdit* edit_filter = nullptr; | ||||
|         QLabel* label_filter_result = nullptr; | ||||
|         QToolButton* button_filter_close = nullptr; | ||||
|     }; | ||||
| 
 | ||||
|     explicit GameList(GMainWindow* parent = nullptr); | ||||
|     ~GameList() override; | ||||
| 
 | ||||
|     void clearFilter(); | ||||
|     void setFilterFocus(); | ||||
|     void setFilterVisible(bool visablility); | ||||
| 
 | ||||
|     void PopulateAsync(const QString& dir_path, bool deep_scan); | ||||
| 
 | ||||
|     void SaveInterfaceLayout(); | ||||
|  | @ -41,6 +78,10 @@ signals: | |||
|     void ShouldCancelWorker(); | ||||
|     void OpenSaveFolderRequested(u64 program_id); | ||||
| 
 | ||||
| private slots: | ||||
|     void onTextChanged(const QString& newText); | ||||
|     void onFilterCloseClicked(); | ||||
| 
 | ||||
| private: | ||||
|     void AddEntry(const QList<QStandardItem*>& entry_items); | ||||
|     void ValidateEntry(const QModelIndex& item); | ||||
|  | @ -49,7 +90,11 @@ private: | |||
|     void PopupContextMenu(const QPoint& menu_location); | ||||
|     void UpdateWatcherList(const std::string& path, unsigned int recursion); | ||||
|     void RefreshGameDirectory(); | ||||
|     bool containsAllWords(QString haystack, QString userinput); | ||||
| 
 | ||||
|     SearchField* search_field; | ||||
|     GMainWindow* main_window = nullptr; | ||||
|     QVBoxLayout* layout = nullptr; | ||||
|     QTreeView* tree_view = nullptr; | ||||
|     QStandardItemModel* item_model = nullptr; | ||||
|     GameListWorker* current_worker = nullptr; | ||||
|  |  | |||
|  | @ -93,7 +93,7 @@ void GMainWindow::InitializeWidgets() { | |||
|     render_window = new GRenderWindow(this, emu_thread.get()); | ||||
|     render_window->hide(); | ||||
| 
 | ||||
|     game_list = new GameList(); | ||||
|     game_list = new GameList(this); | ||||
|     ui.horizontalLayout->addWidget(game_list); | ||||
| 
 | ||||
|     // Create status bar
 | ||||
|  | @ -247,6 +247,9 @@ void GMainWindow::RestoreUIState() { | |||
|     ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); | ||||
|     OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); | ||||
| 
 | ||||
|     ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar); | ||||
|     game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); | ||||
| 
 | ||||
|     ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); | ||||
|     statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); | ||||
| } | ||||
|  | @ -283,6 +286,8 @@ void GMainWindow::ConnectMenuEvents() { | |||
|             &GMainWindow::ToggleWindowMode); | ||||
|     connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, | ||||
|             &GMainWindow::OnDisplayTitleBars); | ||||
|     ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F")); | ||||
|     connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); | ||||
|     connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); | ||||
| } | ||||
| 
 | ||||
|  | @ -444,6 +449,7 @@ void GMainWindow::ShutdownGame() { | |||
|     ui.action_Stop->setEnabled(false); | ||||
|     render_window->hide(); | ||||
|     game_list->show(); | ||||
|     game_list->setFilterFocus(); | ||||
| 
 | ||||
|     // Disable status bar updates
 | ||||
|     status_bar_update_timer.stop(); | ||||
|  | @ -617,6 +623,15 @@ void GMainWindow::OnConfigure() { | |||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnToggleFilterBar() { | ||||
|     game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); | ||||
|     if (ui.action_Show_Filter_Bar->isChecked()) { | ||||
|         game_list->setFilterFocus(); | ||||
|     } else { | ||||
|         game_list->clearFilter(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::OnSwapScreens() { | ||||
|     Settings::values.swap_screen = !Settings::values.swap_screen; | ||||
|     Settings::Apply(); | ||||
|  | @ -671,6 +686,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | |||
| #endif | ||||
|     UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); | ||||
|     UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); | ||||
|     UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); | ||||
|     UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); | ||||
|     UISettings::values.first_start = false; | ||||
| 
 | ||||
|  | @ -720,6 +736,11 @@ bool GMainWindow::ConfirmChangeGame() { | |||
|     return answer != QMessageBox::No; | ||||
| } | ||||
| 
 | ||||
| void GMainWindow::filterBarSetChecked(bool state) { | ||||
|     ui.action_Show_Filter_Bar->setChecked(state); | ||||
|     emit(OnToggleFilterBar()); | ||||
| } | ||||
| 
 | ||||
| #ifdef main | ||||
| #undef main | ||||
| #endif | ||||
|  |  | |||
|  | @ -7,6 +7,7 @@ | |||
| 
 | ||||
| #include <memory> | ||||
| #include <QMainWindow> | ||||
| #include <QTimer> | ||||
| #include "ui_main.h" | ||||
| 
 | ||||
| class CallstackWidget; | ||||
|  | @ -41,6 +42,7 @@ class GMainWindow : public QMainWindow { | |||
|     }; | ||||
| 
 | ||||
| public: | ||||
|     void filterBarSetChecked(bool state); | ||||
|     GMainWindow(); | ||||
|     ~GMainWindow(); | ||||
| 
 | ||||
|  | @ -122,6 +124,7 @@ private slots: | |||
|     void OnMenuRecentFile(); | ||||
|     void OnSwapScreens(); | ||||
|     void OnConfigure(); | ||||
|     void OnToggleFilterBar(); | ||||
|     void OnDisplayTitleBars(bool); | ||||
|     void ToggleWindowMode(); | ||||
|     void OnCreateGraphicsSurfaceViewer(); | ||||
|  |  | |||
|  | @ -88,6 +88,7 @@ | |||
|     </widget> | ||||
|     <addaction name="action_Single_Window_Mode"/> | ||||
|     <addaction name="action_Display_Dock_Widget_Headers"/> | ||||
|     <addaction name="action_Show_Filter_Bar"/> | ||||
|     <addaction name="action_Show_Status_Bar"/> | ||||
|     <addaction name="menu_View_Debugging"/> | ||||
|    </widget> | ||||
|  | @ -167,6 +168,14 @@ | |||
|     <string>Display Dock Widget Headers</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Show_Filter_Bar"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|    </property> | ||||
|    <property name="text"> | ||||
|     <string>Show Filter Bar</string> | ||||
|    </property> | ||||
|   </action> | ||||
|   <action name="action_Show_Status_Bar"> | ||||
|    <property name="checkable"> | ||||
|     <bool>true</bool> | ||||
|  |  | |||
|  | @ -27,6 +27,7 @@ struct Values { | |||
| 
 | ||||
|     bool single_window_mode; | ||||
|     bool display_titlebar; | ||||
|     bool show_filter_bar; | ||||
|     bool show_status_bar; | ||||
| 
 | ||||
|     bool confirm_before_closing; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue