diff options
| -rw-r--r-- | src/citra_qt/configuration/config.cpp | 2 | ||||
| -rw-r--r-- | src/citra_qt/game_list.cpp | 233 | ||||
| -rw-r--r-- | src/citra_qt/game_list.h | 47 | ||||
| -rw-r--r-- | src/citra_qt/main.cpp | 23 | ||||
| -rw-r--r-- | src/citra_qt/main.h | 3 | ||||
| -rw-r--r-- | src/citra_qt/main.ui | 9 | ||||
| -rw-r--r-- | src/citra_qt/ui_settings.h | 1 |
7 files changed, 299 insertions, 19 deletions
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 0b9b73f9e..2b99447ec 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp | |||
| @@ -177,6 +177,7 @@ void Config::ReadValues() { | |||
| 177 | 177 | ||
| 178 | UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); | 178 | UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool(); |
| 179 | UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); | 179 | UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool(); |
| 180 | UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool(); | ||
| 180 | UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); | 181 | UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool(); |
| 181 | UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); | 182 | UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool(); |
| 182 | UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); | 183 | UISettings::values.first_start = qt_config->value("firstStart", true).toBool(); |
| @@ -295,6 +296,7 @@ void Config::SaveValues() { | |||
| 295 | 296 | ||
| 296 | qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); | 297 | qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode); |
| 297 | qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); | 298 | qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar); |
| 299 | qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar); | ||
| 298 | qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); | 300 | qt_config->setValue("showStatusBar", UISettings::values.show_status_bar); |
| 299 | qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); | 301 | qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing); |
| 300 | qt_config->setValue("firstStart", UISettings::values.first_start); | 302 | qt_config->setValue("firstStart", UISettings::values.first_start); |
diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index a9ec9e830..d6e26ed47 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp | |||
| @@ -4,9 +4,9 @@ | |||
| 4 | 4 | ||
| 5 | #include <QFileInfo> | 5 | #include <QFileInfo> |
| 6 | #include <QHeaderView> | 6 | #include <QHeaderView> |
| 7 | #include <QKeyEvent> | ||
| 7 | #include <QMenu> | 8 | #include <QMenu> |
| 8 | #include <QThreadPool> | 9 | #include <QThreadPool> |
| 9 | #include <QVBoxLayout> | ||
| 10 | #include "common/common_paths.h" | 10 | #include "common/common_paths.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/string_util.h" | 12 | #include "common/string_util.h" |
| @@ -15,10 +15,189 @@ | |||
| 15 | #include "game_list_p.h" | 15 | #include "game_list_p.h" |
| 16 | #include "ui_settings.h" | 16 | #include "ui_settings.h" |
| 17 | 17 | ||
| 18 | GameList::GameList(QWidget* parent) : QWidget{parent} { | 18 | GameList::SearchField::KeyReleaseEater::KeyReleaseEater(GameList* gamelist) { |
| 19 | QVBoxLayout* layout = new QVBoxLayout; | 19 | this->gamelist = gamelist; |
| 20 | edit_filter_text_old = ""; | ||
| 21 | } | ||
| 22 | |||
| 23 | // EventFilter in order to process systemkeys while editing the searchfield | ||
| 24 | bool GameList::SearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* event) { | ||
| 25 | // If it isn't a KeyRelease event then continue with standard event processing | ||
| 26 | if (event->type() != QEvent::KeyRelease) | ||
| 27 | return QObject::eventFilter(obj, event); | ||
| 28 | |||
| 29 | QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); | ||
| 30 | int rowCount = gamelist->tree_view->model()->rowCount(); | ||
| 31 | QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); | ||
| 32 | |||
| 33 | // If the searchfield's text hasn't changed special function keys get checked | ||
| 34 | // If no function key changes the searchfield's text the filter doesn't need to get reloaded | ||
| 35 | if (edit_filter_text == edit_filter_text_old) { | ||
| 36 | switch (keyEvent->key()) { | ||
| 37 | // Escape: Resets the searchfield | ||
| 38 | case Qt::Key_Escape: { | ||
| 39 | if (edit_filter_text_old.isEmpty()) { | ||
| 40 | return QObject::eventFilter(obj, event); | ||
| 41 | } else { | ||
| 42 | gamelist->search_field->edit_filter->clear(); | ||
| 43 | edit_filter_text = ""; | ||
| 44 | } | ||
| 45 | break; | ||
| 46 | } | ||
| 47 | // Return and Enter | ||
| 48 | // If the enter key gets pressed first checks how many and which entry is visable | ||
| 49 | // If there is only one result launch this game | ||
| 50 | case Qt::Key_Return: | ||
| 51 | case Qt::Key_Enter: { | ||
| 52 | QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); | ||
| 53 | QModelIndex root_index = item_model->invisibleRootItem()->index(); | ||
| 54 | QStandardItem* child_file; | ||
| 55 | QString file_path; | ||
| 56 | int resultCount = 0; | ||
| 57 | for (int i = 0; i < rowCount; ++i) { | ||
| 58 | if (!gamelist->tree_view->isRowHidden(i, root_index)) { | ||
| 59 | ++resultCount; | ||
| 60 | child_file = gamelist->item_model->item(i, 0); | ||
| 61 | file_path = child_file->data(GameListItemPath::FullPathRole).toString(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | if (resultCount == 1) { | ||
| 65 | // To avoid loading error dialog loops while confirming them using enter | ||
| 66 | // Also users usually want to run a diffrent game after closing one | ||
| 67 | gamelist->search_field->edit_filter->setText(""); | ||
| 68 | edit_filter_text = ""; | ||
| 69 | emit gamelist->GameChosen(file_path); | ||
| 70 | } else { | ||
| 71 | return QObject::eventFilter(obj, event); | ||
| 72 | } | ||
| 73 | break; | ||
| 74 | } | ||
| 75 | default: | ||
| 76 | return QObject::eventFilter(obj, event); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | edit_filter_text_old = edit_filter_text; | ||
| 80 | return QObject::eventFilter(obj, event); | ||
| 81 | } | ||
| 82 | |||
| 83 | void GameList::SearchField::setFilterResult(int visable, int total) { | ||
| 84 | QString result_of_text = tr("of"); | ||
| 85 | QString result_text; | ||
| 86 | if (total == 1) { | ||
| 87 | result_text = tr("result"); | ||
| 88 | } else { | ||
| 89 | result_text = tr("results"); | ||
| 90 | } | ||
| 91 | label_filter_result->setText( | ||
| 92 | QString("%1 %2 %3 %4").arg(visable).arg(result_of_text).arg(total).arg(result_text)); | ||
| 93 | } | ||
| 94 | |||
| 95 | void GameList::SearchField::clear() { | ||
| 96 | edit_filter->setText(""); | ||
| 97 | } | ||
| 20 | 98 | ||
| 99 | void GameList::SearchField::setFocus() { | ||
| 100 | if (edit_filter->isVisible()) { | ||
| 101 | edit_filter->setFocus(); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | GameList::SearchField::SearchField(GameList* parent) : QWidget{parent} { | ||
| 106 | KeyReleaseEater* keyReleaseEater = new KeyReleaseEater(parent); | ||
| 107 | layout_filter = new QHBoxLayout; | ||
| 108 | layout_filter->setMargin(8); | ||
| 109 | label_filter = new QLabel; | ||
| 110 | label_filter->setText(tr("Filter:")); | ||
| 111 | edit_filter = new QLineEdit; | ||
| 112 | edit_filter->setText(""); | ||
| 113 | edit_filter->setPlaceholderText(tr("Enter pattern to filter")); | ||
| 114 | edit_filter->installEventFilter(keyReleaseEater); | ||
| 115 | edit_filter->setClearButtonEnabled(true); | ||
| 116 | connect(edit_filter, SIGNAL(textChanged(const QString&)), parent, | ||
| 117 | SLOT(onTextChanged(const QString&))); | ||
| 118 | label_filter_result = new QLabel; | ||
| 119 | button_filter_close = new QToolButton(this); | ||
| 120 | button_filter_close->setText("X"); | ||
| 121 | button_filter_close->setCursor(Qt::ArrowCursor); | ||
| 122 | button_filter_close->setStyleSheet("QToolButton{ border: none; padding: 0px; color: " | ||
| 123 | "#000000; font-weight: bold; background: #F0F0F0; }" | ||
| 124 | "QToolButton:hover{ border: none; padding: 0px; color: " | ||
| 125 | "#EEEEEE; font-weight: bold; background: #E81123}"); | ||
| 126 | connect(button_filter_close, SIGNAL(clicked()), parent, SLOT(onFilterCloseClicked())); | ||
| 127 | layout_filter->setSpacing(10); | ||
| 128 | layout_filter->addWidget(label_filter); | ||
| 129 | layout_filter->addWidget(edit_filter); | ||
| 130 | layout_filter->addWidget(label_filter_result); | ||
| 131 | layout_filter->addWidget(button_filter_close); | ||
| 132 | setLayout(layout_filter); | ||
| 133 | } | ||
| 134 | |||
| 135 | /** | ||
| 136 | * Checks if all words separated by spaces are contained in another string | ||
| 137 | * This offers a word order insensitive search function | ||
| 138 | * | ||
| 139 | * @param String that gets checked if it contains all words of the userinput string | ||
| 140 | * @param String containing all words getting checked | ||
| 141 | * @return true if the haystack contains all words of userinput | ||
| 142 | */ | ||
| 143 | bool GameList::containsAllWords(QString haystack, QString userinput) { | ||
| 144 | QStringList userinput_split = userinput.split(" ", QString::SplitBehavior::SkipEmptyParts); | ||
| 145 | return std::all_of(userinput_split.begin(), userinput_split.end(), | ||
| 146 | [haystack](QString s) { return haystack.contains(s); }); | ||
| 147 | } | ||
| 148 | |||
| 149 | // Event in order to filter the gamelist after editing the searchfield | ||
| 150 | void GameList::onTextChanged(const QString& newText) { | ||
| 151 | int rowCount = tree_view->model()->rowCount(); | ||
| 152 | QString edit_filter_text = newText.toLower(); | ||
| 153 | |||
| 154 | QModelIndex root_index = item_model->invisibleRootItem()->index(); | ||
| 155 | |||
| 156 | // If the searchfield is empty every item is visible | ||
| 157 | // Otherwise the filter gets applied | ||
| 158 | if (edit_filter_text.isEmpty()) { | ||
| 159 | for (int i = 0; i < rowCount; ++i) { | ||
| 160 | tree_view->setRowHidden(i, root_index, false); | ||
| 161 | } | ||
| 162 | search_field->setFilterResult(rowCount, rowCount); | ||
| 163 | } else { | ||
| 164 | QStandardItem* child_file; | ||
| 165 | QString file_path, file_name, file_title, file_programmid; | ||
| 166 | int result_count = 0; | ||
| 167 | for (int i = 0; i < rowCount; ++i) { | ||
| 168 | child_file = item_model->item(i, 0); | ||
| 169 | file_path = child_file->data(GameListItemPath::FullPathRole).toString().toLower(); | ||
| 170 | file_name = file_path.mid(file_path.lastIndexOf("/") + 1); | ||
| 171 | file_title = child_file->data(GameListItemPath::TitleRole).toString().toLower(); | ||
| 172 | file_programmid = | ||
| 173 | child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); | ||
| 174 | |||
| 175 | // Only items which filename in combination with its title contains all words | ||
| 176 | // that are in the searchfiel will be visible in the gamelist | ||
| 177 | // The search is case insensitive because of toLower() | ||
| 178 | // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent | ||
| 179 | // multiple conversions of edit_filter_text for each game in the gamelist | ||
| 180 | if (containsAllWords(file_name.append(" ").append(file_title), edit_filter_text) || | ||
| 181 | (file_programmid.count() == 16 && edit_filter_text.contains(file_programmid))) { | ||
| 182 | tree_view->setRowHidden(i, root_index, false); | ||
| 183 | ++result_count; | ||
| 184 | } else { | ||
| 185 | tree_view->setRowHidden(i, root_index, true); | ||
| 186 | } | ||
| 187 | search_field->setFilterResult(result_count, rowCount); | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | |||
| 192 | void GameList::onFilterCloseClicked() { | ||
| 193 | main_window->filterBarSetChecked(false); | ||
| 194 | } | ||
| 195 | |||
| 196 | GameList::GameList(GMainWindow* parent) : QWidget{parent} { | ||
| 197 | this->main_window = parent; | ||
| 198 | layout = new QVBoxLayout; | ||
| 21 | tree_view = new QTreeView; | 199 | tree_view = new QTreeView; |
| 200 | search_field = new SearchField(this); | ||
| 22 | item_model = new QStandardItemModel(tree_view); | 201 | item_model = new QStandardItemModel(tree_view); |
| 23 | tree_view->setModel(item_model); | 202 | tree_view->setModel(item_model); |
| 24 | 203 | ||
| @@ -46,7 +225,9 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { | |||
| 46 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | 225 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); |
| 47 | 226 | ||
| 48 | layout->setContentsMargins(0, 0, 0, 0); | 227 | layout->setContentsMargins(0, 0, 0, 0); |
| 228 | layout->setSpacing(0); | ||
| 49 | layout->addWidget(tree_view); | 229 | layout->addWidget(tree_view); |
| 230 | layout->addWidget(search_field); | ||
| 50 | setLayout(layout); | 231 | setLayout(layout); |
| 51 | } | 232 | } |
| 52 | 233 | ||
| @@ -54,6 +235,18 @@ GameList::~GameList() { | |||
| 54 | emit ShouldCancelWorker(); | 235 | emit ShouldCancelWorker(); |
| 55 | } | 236 | } |
| 56 | 237 | ||
| 238 | void GameList::setFilterFocus() { | ||
| 239 | search_field->setFocus(); | ||
| 240 | } | ||
| 241 | |||
| 242 | void GameList::setFilterVisible(bool visablility) { | ||
| 243 | search_field->setVisible(visablility); | ||
| 244 | } | ||
| 245 | |||
| 246 | void GameList::clearFilter() { | ||
| 247 | search_field->clear(); | ||
| 248 | } | ||
| 249 | |||
| 57 | void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { | 250 | void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { |
| 58 | item_model->invisibleRootItem()->appendRow(entry_items); | 251 | item_model->invisibleRootItem()->appendRow(entry_items); |
| 59 | } | 252 | } |
| @@ -69,11 +262,16 @@ void GameList::ValidateEntry(const QModelIndex& item) { | |||
| 69 | std::string std_file_path(file_path.toStdString()); | 262 | std::string std_file_path(file_path.toStdString()); |
| 70 | if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) | 263 | if (!FileUtil::Exists(std_file_path) || FileUtil::IsDirectory(std_file_path)) |
| 71 | return; | 264 | return; |
| 265 | // Users usually want to run a diffrent game after closing one | ||
| 266 | search_field->clear(); | ||
| 72 | emit GameChosen(file_path); | 267 | emit GameChosen(file_path); |
| 73 | } | 268 | } |
| 74 | 269 | ||
| 75 | void GameList::DonePopulating() { | 270 | void GameList::DonePopulating() { |
| 76 | tree_view->setEnabled(true); | 271 | tree_view->setEnabled(true); |
| 272 | int rowCount = tree_view->model()->rowCount(); | ||
| 273 | search_field->setFilterResult(rowCount, rowCount); | ||
| 274 | search_field->setFocus(); | ||
| 77 | } | 275 | } |
| 78 | 276 | ||
| 79 | void GameList::PopupContextMenu(const QPoint& menu_location) { | 277 | void GameList::PopupContextMenu(const QPoint& menu_location) { |
| @@ -151,25 +349,26 @@ static bool HasSupportedFileExtension(const std::string& file_name) { | |||
| 151 | void GameList::RefreshGameDirectory() { | 349 | void GameList::RefreshGameDirectory() { |
| 152 | if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { | 350 | if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { |
| 153 | LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); | 351 | LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); |
| 352 | search_field->clear(); | ||
| 154 | PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | 353 | PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); |
| 155 | } | 354 | } |
| 156 | } | 355 | } |
| 157 | 356 | ||
| 158 | /** | 357 | /** |
| 159 | * Adds the game list folder to the QFileSystemWatcher to check for updates. | 358 | * Adds the game list folder to the QFileSystemWatcher to check for updates. |
| 160 | * | 359 | * |
| 161 | * The file watcher will fire off an update to the game list when a change is detected in the game | 360 | * The file watcher will fire off an update to the game list when a change is detected in the game |
| 162 | * list folder. | 361 | * list folder. |
| 163 | * | 362 | * |
| 164 | * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and | 363 | * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and |
| 165 | * this function is fast enough to not stall the UI thread. If performance is an issue, it should | 364 | * this function is fast enough to not stall the UI thread. If performance is an issue, it should |
| 166 | * be moved to another thread and properly locked to prevent concurrency issues. | 365 | * be moved to another thread and properly locked to prevent concurrency issues. |
| 167 | * | 366 | * |
| 168 | * @param dir folder to check for changes in | 367 | * @param dir folder to check for changes in |
| 169 | * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each | 368 | * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each |
| 170 | * directory recursively to the watcher and will update the file list if any of the folders | 369 | * directory recursively to the watcher and will update the file list if any of the folders |
| 171 | * change. The number determines how deep the recursion should traverse. | 370 | * change. The number determines how deep the recursion should traverse. |
| 172 | */ | 371 | */ |
| 173 | void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { | 372 | void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { |
| 174 | const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, | 373 | const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, |
| 175 | const std::string& virtual_name) -> bool { | 374 | const std::string& virtual_name) -> bool { |
diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index b141fa3a5..3c06cddc8 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h | |||
| @@ -5,13 +5,19 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <QFileSystemWatcher> | 7 | #include <QFileSystemWatcher> |
| 8 | #include <QHBoxLayout> | ||
| 9 | #include <QLabel> | ||
| 10 | #include <QLineEdit> | ||
| 8 | #include <QModelIndex> | 11 | #include <QModelIndex> |
| 9 | #include <QSettings> | 12 | #include <QSettings> |
| 10 | #include <QStandardItem> | 13 | #include <QStandardItem> |
| 11 | #include <QStandardItemModel> | 14 | #include <QStandardItemModel> |
| 12 | #include <QString> | 15 | #include <QString> |
| 16 | #include <QToolButton> | ||
| 13 | #include <QTreeView> | 17 | #include <QTreeView> |
| 18 | #include <QVBoxLayout> | ||
| 14 | #include <QWidget> | 19 | #include <QWidget> |
| 20 | #include "main.h" | ||
| 15 | 21 | ||
| 16 | class GameListWorker; | 22 | class GameListWorker; |
| 17 | 23 | ||
| @@ -26,9 +32,40 @@ public: | |||
| 26 | COLUMN_COUNT, // Number of columns | 32 | COLUMN_COUNT, // Number of columns |
| 27 | }; | 33 | }; |
| 28 | 34 | ||
| 29 | explicit GameList(QWidget* parent = nullptr); | 35 | class SearchField : public QWidget { |
| 36 | public: | ||
| 37 | void setFilterResult(int visable, int total); | ||
| 38 | void clear(); | ||
| 39 | void setFocus(); | ||
| 40 | explicit SearchField(GameList* parent = nullptr); | ||
| 41 | |||
| 42 | private: | ||
| 43 | class KeyReleaseEater : public QObject { | ||
| 44 | public: | ||
| 45 | explicit KeyReleaseEater(GameList* gamelist); | ||
| 46 | |||
| 47 | private: | ||
| 48 | GameList* gamelist = nullptr; | ||
| 49 | QString edit_filter_text_old; | ||
| 50 | |||
| 51 | protected: | ||
| 52 | bool eventFilter(QObject* obj, QEvent* event); | ||
| 53 | }; | ||
| 54 | QHBoxLayout* layout_filter = nullptr; | ||
| 55 | QTreeView* tree_view = nullptr; | ||
| 56 | QLabel* label_filter = nullptr; | ||
| 57 | QLineEdit* edit_filter = nullptr; | ||
| 58 | QLabel* label_filter_result = nullptr; | ||
| 59 | QToolButton* button_filter_close = nullptr; | ||
| 60 | }; | ||
| 61 | |||
| 62 | explicit GameList(GMainWindow* parent = nullptr); | ||
| 30 | ~GameList() override; | 63 | ~GameList() override; |
| 31 | 64 | ||
| 65 | void clearFilter(); | ||
| 66 | void setFilterFocus(); | ||
| 67 | void setFilterVisible(bool visablility); | ||
| 68 | |||
| 32 | void PopulateAsync(const QString& dir_path, bool deep_scan); | 69 | void PopulateAsync(const QString& dir_path, bool deep_scan); |
| 33 | 70 | ||
| 34 | void SaveInterfaceLayout(); | 71 | void SaveInterfaceLayout(); |
| @@ -41,6 +78,10 @@ signals: | |||
| 41 | void ShouldCancelWorker(); | 78 | void ShouldCancelWorker(); |
| 42 | void OpenSaveFolderRequested(u64 program_id); | 79 | void OpenSaveFolderRequested(u64 program_id); |
| 43 | 80 | ||
| 81 | private slots: | ||
| 82 | void onTextChanged(const QString& newText); | ||
| 83 | void onFilterCloseClicked(); | ||
| 84 | |||
| 44 | private: | 85 | private: |
| 45 | void AddEntry(const QList<QStandardItem*>& entry_items); | 86 | void AddEntry(const QList<QStandardItem*>& entry_items); |
| 46 | void ValidateEntry(const QModelIndex& item); | 87 | void ValidateEntry(const QModelIndex& item); |
| @@ -49,7 +90,11 @@ private: | |||
| 49 | void PopupContextMenu(const QPoint& menu_location); | 90 | void PopupContextMenu(const QPoint& menu_location); |
| 50 | void UpdateWatcherList(const std::string& path, unsigned int recursion); | 91 | void UpdateWatcherList(const std::string& path, unsigned int recursion); |
| 51 | void RefreshGameDirectory(); | 92 | void RefreshGameDirectory(); |
| 93 | bool containsAllWords(QString haystack, QString userinput); | ||
| 52 | 94 | ||
| 95 | SearchField* search_field; | ||
| 96 | GMainWindow* main_window = nullptr; | ||
| 97 | QVBoxLayout* layout = nullptr; | ||
| 53 | QTreeView* tree_view = nullptr; | 98 | QTreeView* tree_view = nullptr; |
| 54 | QStandardItemModel* item_model = nullptr; | 99 | QStandardItemModel* item_model = nullptr; |
| 55 | GameListWorker* current_worker = nullptr; | 100 | GameListWorker* current_worker = nullptr; |
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index b17ed6968..ea66cc425 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp | |||
| @@ -93,7 +93,7 @@ void GMainWindow::InitializeWidgets() { | |||
| 93 | render_window = new GRenderWindow(this, emu_thread.get()); | 93 | render_window = new GRenderWindow(this, emu_thread.get()); |
| 94 | render_window->hide(); | 94 | render_window->hide(); |
| 95 | 95 | ||
| 96 | game_list = new GameList(); | 96 | game_list = new GameList(this); |
| 97 | ui.horizontalLayout->addWidget(game_list); | 97 | ui.horizontalLayout->addWidget(game_list); |
| 98 | 98 | ||
| 99 | // Create status bar | 99 | // Create status bar |
| @@ -247,6 +247,9 @@ void GMainWindow::RestoreUIState() { | |||
| 247 | ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); | 247 | ui.action_Display_Dock_Widget_Headers->setChecked(UISettings::values.display_titlebar); |
| 248 | OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); | 248 | OnDisplayTitleBars(ui.action_Display_Dock_Widget_Headers->isChecked()); |
| 249 | 249 | ||
| 250 | ui.action_Show_Filter_Bar->setChecked(UISettings::values.show_filter_bar); | ||
| 251 | game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); | ||
| 252 | |||
| 250 | ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); | 253 | ui.action_Show_Status_Bar->setChecked(UISettings::values.show_status_bar); |
| 251 | statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); | 254 | statusBar()->setVisible(ui.action_Show_Status_Bar->isChecked()); |
| 252 | } | 255 | } |
| @@ -283,6 +286,8 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 283 | &GMainWindow::ToggleWindowMode); | 286 | &GMainWindow::ToggleWindowMode); |
| 284 | connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, | 287 | connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, |
| 285 | &GMainWindow::OnDisplayTitleBars); | 288 | &GMainWindow::OnDisplayTitleBars); |
| 289 | ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F")); | ||
| 290 | connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); | ||
| 286 | connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); | 291 | connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); |
| 287 | } | 292 | } |
| 288 | 293 | ||
| @@ -444,6 +449,7 @@ void GMainWindow::ShutdownGame() { | |||
| 444 | ui.action_Stop->setEnabled(false); | 449 | ui.action_Stop->setEnabled(false); |
| 445 | render_window->hide(); | 450 | render_window->hide(); |
| 446 | game_list->show(); | 451 | game_list->show(); |
| 452 | game_list->setFilterFocus(); | ||
| 447 | 453 | ||
| 448 | // Disable status bar updates | 454 | // Disable status bar updates |
| 449 | status_bar_update_timer.stop(); | 455 | status_bar_update_timer.stop(); |
| @@ -617,6 +623,15 @@ void GMainWindow::OnConfigure() { | |||
| 617 | } | 623 | } |
| 618 | } | 624 | } |
| 619 | 625 | ||
| 626 | void GMainWindow::OnToggleFilterBar() { | ||
| 627 | game_list->setFilterVisible(ui.action_Show_Filter_Bar->isChecked()); | ||
| 628 | if (ui.action_Show_Filter_Bar->isChecked()) { | ||
| 629 | game_list->setFilterFocus(); | ||
| 630 | } else { | ||
| 631 | game_list->clearFilter(); | ||
| 632 | } | ||
| 633 | } | ||
| 634 | |||
| 620 | void GMainWindow::OnSwapScreens() { | 635 | void GMainWindow::OnSwapScreens() { |
| 621 | Settings::values.swap_screen = !Settings::values.swap_screen; | 636 | Settings::values.swap_screen = !Settings::values.swap_screen; |
| 622 | Settings::Apply(); | 637 | Settings::Apply(); |
| @@ -671,6 +686,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) { | |||
| 671 | #endif | 686 | #endif |
| 672 | UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); | 687 | UISettings::values.single_window_mode = ui.action_Single_Window_Mode->isChecked(); |
| 673 | UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); | 688 | UISettings::values.display_titlebar = ui.action_Display_Dock_Widget_Headers->isChecked(); |
| 689 | UISettings::values.show_filter_bar = ui.action_Show_Filter_Bar->isChecked(); | ||
| 674 | UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); | 690 | UISettings::values.show_status_bar = ui.action_Show_Status_Bar->isChecked(); |
| 675 | UISettings::values.first_start = false; | 691 | UISettings::values.first_start = false; |
| 676 | 692 | ||
| @@ -720,6 +736,11 @@ bool GMainWindow::ConfirmChangeGame() { | |||
| 720 | return answer != QMessageBox::No; | 736 | return answer != QMessageBox::No; |
| 721 | } | 737 | } |
| 722 | 738 | ||
| 739 | void GMainWindow::filterBarSetChecked(bool state) { | ||
| 740 | ui.action_Show_Filter_Bar->setChecked(state); | ||
| 741 | emit(OnToggleFilterBar()); | ||
| 742 | } | ||
| 743 | |||
| 723 | #ifdef main | 744 | #ifdef main |
| 724 | #undef main | 745 | #undef main |
| 725 | #endif | 746 | #endif |
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index ec841eaa5..2f398eb7b 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | 7 | ||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <QMainWindow> | 9 | #include <QMainWindow> |
| 10 | #include <QTimer> | ||
| 10 | #include "ui_main.h" | 11 | #include "ui_main.h" |
| 11 | 12 | ||
| 12 | class CallstackWidget; | 13 | class CallstackWidget; |
| @@ -41,6 +42,7 @@ class GMainWindow : public QMainWindow { | |||
| 41 | }; | 42 | }; |
| 42 | 43 | ||
| 43 | public: | 44 | public: |
| 45 | void filterBarSetChecked(bool state); | ||
| 44 | GMainWindow(); | 46 | GMainWindow(); |
| 45 | ~GMainWindow(); | 47 | ~GMainWindow(); |
| 46 | 48 | ||
| @@ -122,6 +124,7 @@ private slots: | |||
| 122 | void OnMenuRecentFile(); | 124 | void OnMenuRecentFile(); |
| 123 | void OnSwapScreens(); | 125 | void OnSwapScreens(); |
| 124 | void OnConfigure(); | 126 | void OnConfigure(); |
| 127 | void OnToggleFilterBar(); | ||
| 125 | void OnDisplayTitleBars(bool); | 128 | void OnDisplayTitleBars(bool); |
| 126 | void ToggleWindowMode(); | 129 | void ToggleWindowMode(); |
| 127 | void OnCreateGraphicsSurfaceViewer(); | 130 | void OnCreateGraphicsSurfaceViewer(); |
diff --git a/src/citra_qt/main.ui b/src/citra_qt/main.ui index 47dbb6ef7..f64b878f0 100644 --- a/src/citra_qt/main.ui +++ b/src/citra_qt/main.ui | |||
| @@ -88,6 +88,7 @@ | |||
| 88 | </widget> | 88 | </widget> |
| 89 | <addaction name="action_Single_Window_Mode"/> | 89 | <addaction name="action_Single_Window_Mode"/> |
| 90 | <addaction name="action_Display_Dock_Widget_Headers"/> | 90 | <addaction name="action_Display_Dock_Widget_Headers"/> |
| 91 | <addaction name="action_Show_Filter_Bar"/> | ||
| 91 | <addaction name="action_Show_Status_Bar"/> | 92 | <addaction name="action_Show_Status_Bar"/> |
| 92 | <addaction name="menu_View_Debugging"/> | 93 | <addaction name="menu_View_Debugging"/> |
| 93 | </widget> | 94 | </widget> |
| @@ -167,6 +168,14 @@ | |||
| 167 | <string>Display Dock Widget Headers</string> | 168 | <string>Display Dock Widget Headers</string> |
| 168 | </property> | 169 | </property> |
| 169 | </action> | 170 | </action> |
| 171 | <action name="action_Show_Filter_Bar"> | ||
| 172 | <property name="checkable"> | ||
| 173 | <bool>true</bool> | ||
| 174 | </property> | ||
| 175 | <property name="text"> | ||
| 176 | <string>Show Filter Bar</string> | ||
| 177 | </property> | ||
| 178 | </action> | ||
| 170 | <action name="action_Show_Status_Bar"> | 179 | <action name="action_Show_Status_Bar"> |
| 171 | <property name="checkable"> | 180 | <property name="checkable"> |
| 172 | <bool>true</bool> | 181 | <bool>true</bool> |
diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index 6408ece2b..bc37f81c5 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h | |||
| @@ -27,6 +27,7 @@ struct Values { | |||
| 27 | 27 | ||
| 28 | bool single_window_mode; | 28 | bool single_window_mode; |
| 29 | bool display_titlebar; | 29 | bool display_titlebar; |
| 30 | bool show_filter_bar; | ||
| 30 | bool show_status_bar; | 31 | bool show_status_bar; |
| 31 | 32 | ||
| 32 | bool confirm_before_closing; | 33 | bool confirm_before_closing; |