@ -14,7 +14,6 @@
# include <QMenu>
# include <QMenu>
# include <QThreadPool>
# include <QThreadPool>
# include <fmt/format.h>
# include <fmt/format.h>
# include "common/common_paths.h"
# include "common/common_types.h"
# include "common/common_types.h"
# include "common/logging/log.h"
# include "common/logging/log.h"
# include "core/file_sys/patch_manager.h"
# include "core/file_sys/patch_manager.h"
@ -48,7 +47,7 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
return QObject : : eventFilter ( obj , event ) ;
return QObject : : eventFilter ( obj , event ) ;
} else {
} else {
gamelist - > search_field - > edit_filter - > clear ( ) ;
gamelist - > search_field - > edit_filter - > clear ( ) ;
edit_filter_text = " " ;
edit_filter_text . clear ( ) ;
}
}
break ;
break ;
}
}
@ -71,9 +70,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
}
}
if ( resultCount = = 1 ) {
if ( resultCount = = 1 ) {
// To avoid loading error dialog loops while confirming them using enter
// 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 = " " ;
// Also users usually want to run a diffe rent game after closing one
gamelist - > search_field - > edit_filter - > clear ( ) ;
edit_filter_text . clear ( ) ;
emit gamelist - > GameChosen ( file_path ) ;
emit gamelist - > GameChosen ( file_path ) ;
} else {
} else {
return QObject : : eventFilter ( obj , event ) ;
return QObject : : eventFilter ( obj , event ) ;
@ -93,7 +92,7 @@ void GameListSearchField::setFilterResult(int visible, int total) {
}
}
void GameListSearchField : : clear ( ) {
void GameListSearchField : : clear ( ) {
edit_filter - > setText ( " " ) ;
edit_filter - > clear ( ) ;
}
}
void GameListSearchField : : setFocus ( ) {
void GameListSearchField : : setFocus ( ) {
@ -103,25 +102,26 @@ void GameListSearchField::setFocus() {
}
}
GameListSearchField : : GameListSearchField ( GameList * parent ) : QWidget { parent } {
GameListSearchField : : GameListSearchField ( GameList * parent ) : QWidget { parent } {
KeyReleaseEater * keyReleaseE ater = new KeyReleaseEater ( parent ) ;
auto * const key_release_e ater = new KeyReleaseEater ( parent ) ;
layout_filter = new QHBoxLayout ;
layout_filter = new QHBoxLayout ;
layout_filter - > setMargin ( 8 ) ;
layout_filter - > setMargin ( 8 ) ;
label_filter = new QLabel ;
label_filter = new QLabel ;
label_filter - > setText ( tr ( " Filter: " ) ) ;
label_filter - > setText ( tr ( " Filter: " ) ) ;
edit_filter = new QLineEdit ;
edit_filter = new QLineEdit ;
edit_filter - > setText ( " " ) ;
edit_filter - > clear ( ) ;
edit_filter - > setPlaceholderText ( tr ( " Enter pattern to filter " ) ) ;
edit_filter - > setPlaceholderText ( tr ( " Enter pattern to filter " ) ) ;
edit_filter - > installEventFilter ( keyReleaseE ater ) ;
edit_filter - > installEventFilter ( key_release_e ater ) ;
edit_filter - > setClearButtonEnabled ( true ) ;
edit_filter - > setClearButtonEnabled ( true ) ;
connect ( edit_filter , & QLineEdit : : textChanged , parent , & GameList : : onTextChanged ) ;
connect ( edit_filter , & QLineEdit : : textChanged , parent , & GameList : : onTextChanged ) ;
label_filter_result = new QLabel ;
label_filter_result = new QLabel ;
button_filter_close = new QToolButton ( this ) ;
button_filter_close = new QToolButton ( this ) ;
button_filter_close - > setText ( " X " ) ;
button_filter_close - > setText ( QStringLiteral ( " X " ) ) ;
button_filter_close - > setCursor ( Qt : : ArrowCursor ) ;
button_filter_close - > setCursor ( Qt : : ArrowCursor ) ;
button_filter_close - > setStyleSheet ( " QToolButton{ border: none; padding: 0px; color: "
button_filter_close - > setStyleSheet (
QStringLiteral ( " QToolButton{ border: none; padding: 0px; color: "
" #000000; font-weight: bold; background: #F0F0F0; } "
" #000000; font-weight: bold; background: #F0F0F0; } "
" QToolButton:hover{ border: none; padding: 0px; color: "
" QToolButton:hover{ border: none; padding: 0px; color: "
" #EEEEEE; font-weight: bold; background: #E81123} " ) ;
" #EEEEEE; font-weight: bold; background: #E81123} " ) ) ;
connect ( button_filter_close , & QToolButton : : clicked , parent , & GameList : : onFilterCloseClicked ) ;
connect ( button_filter_close , & QToolButton : : clicked , parent , & GameList : : onFilterCloseClicked ) ;
layout_filter - > setSpacing ( 10 ) ;
layout_filter - > setSpacing ( 10 ) ;
layout_filter - > addWidget ( label_filter ) ;
layout_filter - > addWidget ( label_filter ) ;
@ -141,36 +141,34 @@ GameListSearchField::GameListSearchField(GameList* parent) : QWidget{parent} {
*/
*/
static bool ContainsAllWords ( const QString & haystack , const QString & userinput ) {
static bool ContainsAllWords ( const QString & haystack , const QString & userinput ) {
const QStringList userinput_split =
const QStringList userinput_split =
userinput . split ( ' ' , QString : : SplitBehavior : : SkipEmptyParts ) ;
userinput . split ( QLatin1Char { ' ' } , QString : : SplitBehavior : : SkipEmptyParts ) ;
return std : : all_of ( userinput_split . begin ( ) , userinput_split . end ( ) ,
return std : : all_of ( userinput_split . begin ( ) , userinput_split . end ( ) ,
[ & haystack ] ( const QString & s ) { return haystack . contains ( s ) ; } ) ;
[ & haystack ] ( const QString & s ) { return haystack . contains ( s ) ; } ) ;
}
}
// Event in order to filter the gamelist after editing the searchfield
// 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 ( ) ;
void GameList : : onTextChanged ( const QString & new_text ) {
const int row_count = tree_view - > model ( ) - > rowCount ( ) ;
const QString edit_filter_text = new_text . toLower ( ) ;
const QModelIndex root_index = item_model - > invisibleRootItem ( ) - > index ( ) ;
// If the searchfield is empty every item is visible
// If the searchfield is empty every item is visible
// Otherwise the filter gets applied
// Otherwise the filter gets applied
if ( edit_filter_text . isEmpty ( ) ) {
if ( edit_filter_text . isEmpty ( ) ) {
for ( int i = 0 ; i < rowC ount ; + + i ) {
for ( int i = 0 ; i < row_c ount ; + + i ) {
tree_view - > setRowHidden ( i , root_index , false ) ;
tree_view - > setRowHidden ( i , root_index , false ) ;
}
}
search_field - > setFilterResult ( rowCount , rowC ount ) ;
search_field - > setFilterResult ( row_count , row_c ount ) ;
} else {
} else {
int result_count = 0 ;
int result_count = 0 ;
for ( int i = 0 ; i < rowC ount ; + + i ) {
for ( int i = 0 ; i < row_c ount ; + + i ) {
const QStandardItem * child_file = item_model - > item ( i , 0 ) ;
const QStandardItem * child_file = item_model - > item ( i , 0 ) ;
const QString file_path =
const QString file_path =
child_file - > data ( GameListItemPath : : FullPathRole ) . toString ( ) . toLower ( ) ;
child_file - > data ( GameListItemPath : : FullPathRole ) . toString ( ) . toLower ( ) ;
QString file_name = file_path . mid ( file_path . lastIndexOf ( ' / ' ) + 1 ) ;
const QString file_title =
const QString file_title =
child_file - > data ( GameListItemPath : : TitleRole ) . toString ( ) . toLower ( ) ;
child_file - > data ( GameListItemPath : : TitleRole ) . toString ( ) . toLower ( ) ;
const QString file_programm id =
const QString file_program_ id =
child_file - > data ( GameListItemPath : : ProgramIdRole ) . toString ( ) . toLower ( ) ;
child_file - > data ( GameListItemPath : : ProgramIdRole ) . toString ( ) . toLower ( ) ;
// Only items which filename in combination with its title contains all words
// Only items which filename in combination with its title contains all words
@ -178,14 +176,16 @@ void GameList::onTextChanged(const QString& newText) {
// The search is case insensitive because of toLower()
// The search is case insensitive because of toLower()
// I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
// I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
// multiple conversions of edit_filter_text for each game in the gamelist
// 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 ) ) ) {
const QString file_name = file_path . mid ( file_path . lastIndexOf ( QLatin1Char { ' / ' } ) + 1 ) +
QLatin1Char { ' ' } + file_title ;
if ( ContainsAllWords ( file_name , edit_filter_text ) | |
( file_program_id . count ( ) = = 16 & & edit_filter_text . contains ( file_program_id ) ) ) {
tree_view - > setRowHidden ( i , root_index , false ) ;
tree_view - > setRowHidden ( i , root_index , false ) ;
+ + result_count ;
+ + result_count ;
} else {
} else {
tree_view - > setRowHidden ( i , root_index , true ) ;
tree_view - > setRowHidden ( i , root_index , true ) ;
}
}
search_field - > setFilterResult ( result_count , rowC ount ) ;
search_field - > setFilterResult ( result_count , row_c ount ) ;
}
}
}
}
}
}
@ -216,7 +216,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
tree_view - > setEditTriggers ( QHeaderView : : NoEditTriggers ) ;
tree_view - > setEditTriggers ( QHeaderView : : NoEditTriggers ) ;
tree_view - > setUniformRowHeights ( true ) ;
tree_view - > setUniformRowHeights ( true ) ;
tree_view - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
tree_view - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
tree_view - > setStyleSheet ( " QTreeView{ border: none; } " ) ;
tree_view - > setStyleSheet ( QStringLiteral ( " QTreeView{ border: none; } " ) ) ;
item_model - > insertColumns ( 0 , UISettings : : values . show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1 ) ;
item_model - > insertColumns ( 0 , UISettings : : values . show_add_ons ? COLUMN_COUNT : COLUMN_COUNT - 1 ) ;
item_model - > setHeaderData ( COLUMN_NAME , Qt : : Horizontal , tr ( " Name " ) ) ;
item_model - > setHeaderData ( COLUMN_NAME , Qt : : Horizontal , tr ( " Name " ) ) ;
@ -282,9 +282,9 @@ void GameList::ValidateEntry(const QModelIndex& item) {
const QFileInfo file_info { file_path } ;
const QFileInfo file_info { file_path } ;
if ( file_info . isDir ( ) ) {
if ( file_info . isDir ( ) ) {
const QDir dir { file_path } ;
const QDir dir { file_path } ;
const QStringList matching_main = dir . entryList ( QStringLis t ( " main " ) , QDir : : Files ) ;
const QStringList matching_main = dir . entryList ( { QStringLiteral ( " main " ) } , QDir : : Files ) ;
if ( matching_main . size ( ) = = 1 ) {
if ( matching_main . size ( ) = = 1 ) {
emit GameChosen ( dir . path ( ) + DIR_SEP + matching_main [ 0 ] ) ;
emit GameChosen ( dir . path ( ) + QDir : : separator ( ) + matching_main [ 0 ] ) ;
}
}
return ;
return ;
}
}
@ -360,7 +360,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
}
}
void GameList : : LoadCompatibilityList ( ) {
void GameList : : LoadCompatibilityList ( ) {
QFile compat_list { " :compatibility_list/compatibility_list.json " } ;
QFile compat_list { QStringLiteral ( " :compatibility_list/compatibility_list.json " ) } ;
if ( ! compat_list . open ( QFile : : ReadOnly | QFile : : Text ) ) {
if ( ! compat_list . open ( QFile : : ReadOnly | QFile : : Text ) ) {
LOG_ERROR ( Frontend , " Unable to open game compatibility list " ) ;
LOG_ERROR ( Frontend , " Unable to open game compatibility list " ) ;
@ -378,28 +378,30 @@ void GameList::LoadCompatibilityList() {
return ;
return ;
}
}
const QString string_content = content ;
QJsonDocument json = QJsonDocument : : fromJson ( string_content . toUtf8 ( ) ) ;
QJsonArray arr = json . array ( ) ;
const QJsonDocument json = QJsonDocument : : fromJson ( content ) ;
const QJsonArray arr = json . array ( ) ;
for ( const QJsonValue value : arr ) {
const QJsonObject game = value . toObject ( ) ;
const QString compatibility_key = QStringLiteral ( " compatibility " ) ;
for ( const QJsonValueRef value : arr ) {
QJsonObject game = value . toObject ( ) ;
if ( ! game . contains ( compatibility_key ) | | ! game [ compatibility_key ] . isDouble ( ) ) {
continue ;
}
if ( game . contains ( " compatibility " ) & & game [ " compatibility " ] . isDouble ( ) ) {
int compatibility = game [ " compatibility " ] . toInt ( ) ;
QString directory = game [ " directory " ] . toString ( ) ;
QJsonArray ids = game [ " releases " ] . toArray ( ) ;
const int compatibility = game [ compatibility_key ] . toInt ( ) ;
const QString directory = game [ QStringLiteral ( " directory " ) ] . toString ( ) ;
const QJsonArray ids = game [ QStringLiteral ( " releases " ) ] . toArray ( ) ;
for ( const QJsonValueRef id_ref : ids ) {
QJsonObject id_object = id_ref . toObject ( ) ;
QString id = id_object [ " id " ] . toString ( ) ;
compatibility_list . emplace (
id . toUpper ( ) . toStdString ( ) ,
for ( const QJsonValue id_ref : ids ) {
const QJsonObject id_object = id_ref . toObject ( ) ;
const QString id = id_object [ QStringLiteral ( " id " ) ] . toString ( ) ;
compatibility_list . emplace ( id . toUpper ( ) . toStdString ( ) ,
std : : make_pair ( QString : : number ( compatibility ) , directory ) ) ;
std : : make_pair ( QString : : number ( compatibility ) , directory ) ) ;
}
}
}
}
}
}
}
void GameList : : PopulateAsync ( const QString & dir_path , bool deep_scan ) {
void GameList : : PopulateAsync ( const QString & dir_path , bool deep_scan ) {
const QFileInfo dir_info { dir_path } ;
const QFileInfo dir_info { dir_path } ;
@ -464,7 +466,10 @@ void GameList::LoadInterfaceLayout() {
item_model - > sort ( header - > sortIndicatorSection ( ) , header - > sortIndicatorOrder ( ) ) ;
item_model - > sort ( header - > sortIndicatorSection ( ) , header - > sortIndicatorOrder ( ) ) ;
}
}
const QStringList GameList : : supported_file_extensions = { " nso " , " nro " , " nca " , " xci " , " nsp " } ;
const QStringList GameList : : supported_file_extensions = {
QStringLiteral ( " nso " ) , QStringLiteral ( " nro " ) , QStringLiteral ( " nca " ) ,
QStringLiteral ( " xci " ) , QStringLiteral ( " nsp " ) ,
} ;
void GameList : : RefreshGameDirectory ( ) {
void GameList : : RefreshGameDirectory ( ) {
if ( ! UISettings : : values . game_directory_path . isEmpty ( ) & & current_worker ! = nullptr ) {
if ( ! UISettings : : values . game_directory_path . isEmpty ( ) & & current_worker ! = nullptr ) {