2020-06-02 10:39:24 +02:00
# include < QPushButton >
# include <QDialogButtonBox>
# include <QGuiApplication>
# include <QScreen>
# include <QDir>
# include <QMenu>
# include <QAction>
2020-06-11 15:12:44 +02:00
# include <QCheckBox>
2020-06-12 21:09:08 +02:00
# include <QMessageBox>
2020-06-02 10:39:24 +02:00
# include "ui_patch_manager_dialog.h"
# include "patch_manager_dialog.h"
# include "table_item_delegate.h"
2020-06-25 18:26:47 +02:00
# include "gui_settings.h"
2020-06-02 10:39:24 +02:00
# include "qt_utils.h"
# include "Utilities/File.h"
# include "util/logs.hpp"
LOG_CHANNEL ( patch_log ) ;
enum patch_column : int
{
enabled ,
title ,
serials ,
description ,
patch_version ,
author ,
notes
} ;
enum patch_role : int
{
hash_role = Qt : : UserRole ,
2020-06-26 02:58:06 +02:00
title_role ,
serial_role ,
app_version_role ,
2020-06-19 19:00:06 +02:00
description_role ,
2020-06-26 02:58:06 +02:00
patch_group_role ,
2020-06-25 20:53:41 +02:00
persistance_role ,
node_level_role
} ;
enum node_level : int
{
title_level ,
serial_level ,
patch_level
2020-06-02 10:39:24 +02:00
} ;
2020-06-26 18:31:42 +02:00
patch_manager_dialog : : patch_manager_dialog ( std : : shared_ptr < gui_settings > gui_settings , std : : unordered_map < std : : string , std : : set < std : : string > > games , QWidget * parent )
2020-06-02 10:39:24 +02:00
: QDialog ( parent )
2020-06-25 18:26:47 +02:00
, m_gui_settings ( gui_settings )
2020-06-26 18:31:42 +02:00
, m_owned_games ( std : : move ( games ) )
2020-06-02 10:39:24 +02:00
, ui ( new Ui : : patch_manager_dialog )
{
ui - > setupUi ( this ) ;
setModal ( true ) ;
2020-06-11 15:12:44 +02:00
// Load config for special settings
patch_engine : : load_config ( m_legacy_patches_enabled ) ;
ui - > cb_enable_legacy_patches - > setChecked ( m_legacy_patches_enabled ) ;
2020-06-02 10:39:24 +02:00
connect ( ui - > patch_filter , & QLineEdit : : textChanged , this , & patch_manager_dialog : : filter_patches ) ;
connect ( ui - > patch_tree , & QTreeWidget : : currentItemChanged , this , & patch_manager_dialog : : on_item_selected ) ;
connect ( ui - > patch_tree , & QTreeWidget : : itemChanged , this , & patch_manager_dialog : : on_item_changed ) ;
connect ( ui - > patch_tree , & QTreeWidget : : customContextMenuRequested , this , & patch_manager_dialog : : on_custom_context_menu_requested ) ;
2020-06-11 15:12:44 +02:00
connect ( ui - > cb_enable_legacy_patches , & QCheckBox : : stateChanged , this , & patch_manager_dialog : : on_legacy_patches_enabled ) ;
2020-06-26 18:31:42 +02:00
connect ( ui - > cb_owned_games_only , & QCheckBox : : stateChanged , this , & patch_manager_dialog : : on_show_owned_games_only ) ;
2020-06-02 10:39:24 +02:00
connect ( ui - > buttonBox , & QDialogButtonBox : : rejected , this , & QWidget : : close ) ;
connect ( ui - > buttonBox , & QDialogButtonBox : : clicked , [ this ] ( QAbstractButton * button )
{
if ( button = = ui - > buttonBox - > button ( QDialogButtonBox : : Save ) )
{
2020-06-12 21:09:08 +02:00
save_config ( ) ;
2020-06-02 10:39:24 +02:00
accept ( ) ;
}
else if ( button = = ui - > buttonBox - > button ( QDialogButtonBox : : Apply ) )
{
2020-06-12 21:09:08 +02:00
save_config ( ) ;
2020-06-02 10:39:24 +02:00
}
} ) ;
}
patch_manager_dialog : : ~ patch_manager_dialog ( )
{
2020-06-25 18:26:47 +02:00
// Save gui settings
m_gui_settings - > SetValue ( gui : : pm_geometry , saveGeometry ( ) ) ;
m_gui_settings - > SetValue ( gui : : pm_splitter_state , ui - > splitter - > saveState ( ) ) ;
2020-06-02 10:39:24 +02:00
delete ui ;
}
2020-06-25 18:26:47 +02:00
int patch_manager_dialog : : exec ( )
{
show ( ) ;
refresh ( true ) ;
return QDialog : : exec ( ) ;
}
void patch_manager_dialog : : refresh ( bool restore_layout )
2020-06-12 21:09:08 +02:00
{
load_patches ( ) ;
populate_tree ( ) ;
2020-06-26 18:31:42 +02:00
filter_patches ( ui - > patch_filter - > text ( ) ) ;
2020-06-25 18:26:47 +02:00
if ( restore_layout )
{
if ( ! restoreGeometry ( m_gui_settings - > GetValue ( gui : : pm_geometry ) . toByteArray ( ) ) )
{
resize ( QGuiApplication : : primaryScreen ( ) - > availableSize ( ) * 0.7 ) ;
}
if ( ! ui - > splitter - > restoreState ( m_gui_settings - > GetValue ( gui : : pm_splitter_state ) . toByteArray ( ) ) )
{
const int width_left = ui - > splitter - > width ( ) * 0.7 ;
const int width_right = ui - > splitter - > width ( ) - width_left ;
ui - > splitter - > setSizes ( { width_left , width_right } ) ;
}
}
2020-06-12 21:09:08 +02:00
}
2020-06-02 10:39:24 +02:00
void patch_manager_dialog : : load_patches ( )
{
2020-06-19 19:00:06 +02:00
m_map . clear ( ) ;
2020-06-20 13:44:56 +02:00
// NOTE: Make sure these paths are loaded in the same order as they are applied on boot
2020-06-19 22:06:34 +02:00
2020-06-02 10:39:24 +02:00
const std : : string patches_path = fs : : get_config_dir ( ) + " patches/ " ;
2020-06-19 22:06:34 +02:00
const QStringList filters = QStringList ( ) < < " *_patch.yml " ;
2020-06-02 10:39:24 +02:00
2020-06-20 13:44:56 +02:00
QStringList path_list ;
path_list < < " patch.yml " ;
path_list < < " imported_patch.yml " ;
path_list < < QDir ( QString : : fromStdString ( patches_path ) ) . entryList ( filters ) ;
path_list . removeDuplicates ( ) ; // make sure to load patch.yml and imported_patch.yml only once
2020-06-19 22:06:34 +02:00
2020-06-02 10:39:24 +02:00
for ( const auto & path : path_list )
{
2020-06-20 13:44:56 +02:00
patch_engine : : load ( m_map , patches_path + path . toStdString ( ) ) ;
2020-06-02 10:39:24 +02:00
}
}
void patch_manager_dialog : : populate_tree ( )
{
2020-06-19 19:00:06 +02:00
// "Reset" currently used items. Items that aren't persisted will be removed later.
// Using this logic instead of clearing the tree here should persist the expanded status of items.
for ( auto item : ui - > patch_tree - > findItems ( " .* " , Qt : : MatchFlag : : MatchRegExp | Qt : : MatchFlag : : MatchRecursive ) )
{
if ( item )
{
item - > setData ( 0 , persistance_role , false ) ;
}
}
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , container ] : m_map )
2020-06-02 10:39:24 +02:00
{
// Don't show legacy patches, because you can't configure them anyway
2020-06-19 13:46:56 +02:00
if ( container . is_legacy )
2020-06-02 10:39:24 +02:00
{
continue ;
}
2020-06-19 13:46:56 +02:00
const QString q_hash = QString : : fromStdString ( hash ) ;
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
// Add patch items
for ( const auto & [ description , patch ] : container . patch_info_map )
2020-06-02 10:39:24 +02:00
{
2020-06-19 18:59:04 +02:00
if ( patch . is_legacy )
{
continue ;
}
2020-06-26 02:58:06 +02:00
const QString q_patch_group = QString : : fromStdString ( patch . patch_group ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : patch . titles )
2020-06-19 13:46:56 +02:00
{
2020-06-26 02:58:06 +02:00
if ( serials . empty ( ) )
{
continue ;
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
const QString q_title = QString : : fromStdString ( title ) ;
2020-06-27 15:26:14 +02:00
const QString visible_title = title = = patch_key : : all ? tr_all_titles : q_title ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
QTreeWidgetItem * title_level_item = nullptr ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
// Find top level item for this title
2020-06-27 15:26:14 +02:00
if ( const auto list = ui - > patch_tree - > findItems ( visible_title , Qt : : MatchFlag : : MatchExactly , 0 ) ; list . size ( ) > 0 )
2020-06-26 02:58:06 +02:00
{
title_level_item = list [ 0 ] ;
}
2020-06-19 13:46:56 +02:00
2020-06-26 02:58:06 +02:00
// Add a top level item for this title if it doesn't exist yet
if ( ! title_level_item )
{
title_level_item = new QTreeWidgetItem ( ) ;
2020-06-27 15:26:14 +02:00
title_level_item - > setText ( 0 , visible_title ) ;
2020-06-26 02:58:06 +02:00
title_level_item - > setData ( 0 , title_role , q_title ) ;
title_level_item - > setData ( 0 , node_level_role , node_level : : title_level ) ;
title_level_item - > setData ( 0 , persistance_role , true ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
ui - > patch_tree - > addTopLevelItem ( title_level_item ) ;
}
2020-06-27 15:26:14 +02:00
ASSERT ( title_level_item ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto & [ serial , app_versions ] : serials )
{
if ( app_versions . empty ( ) )
{
continue ;
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
const QString q_serial = QString : : fromStdString ( serial ) ;
2020-06-27 10:32:00 +02:00
const QString visible_serial = serial = = patch_key : : all ? tr_all_serials : q_serial ;
2020-06-19 19:00:06 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto & [ app_version , enabled ] : app_versions )
{
const QString q_app_version = QString : : fromStdString ( app_version ) ;
2020-06-27 10:32:00 +02:00
const QString q_version_suffix = app_version = = patch_key : : all ? ( QStringLiteral ( " - " ) + tr_all_versions ) : ( QStringLiteral ( " v. " ) + q_app_version ) ;
const QString q_serial_and_version = visible_serial + q_version_suffix ;
2020-06-19 19:00:06 +02:00
2020-06-26 18:31:42 +02:00
// Find out if there is a node item for this serial
QTreeWidgetItem * serial_level_item = gui : : utils : : find_child ( title_level_item , q_serial_and_version ) ;
2020-06-26 02:58:06 +02:00
2020-06-26 18:31:42 +02:00
// Add a node item for this serial if it doesn't exist yet
if ( ! serial_level_item )
2020-06-26 02:58:06 +02:00
{
2020-06-26 18:31:42 +02:00
serial_level_item = new QTreeWidgetItem ( ) ;
serial_level_item - > setText ( 0 , q_serial_and_version ) ;
serial_level_item - > setData ( 0 , title_role , q_title ) ;
serial_level_item - > setData ( 0 , serial_role , q_serial ) ;
serial_level_item - > setData ( 0 , app_version_role , q_app_version ) ;
serial_level_item - > setData ( 0 , node_level_role , node_level : : serial_level ) ;
serial_level_item - > setData ( 0 , persistance_role , true ) ;
title_level_item - > addChild ( serial_level_item ) ;
2020-06-26 02:58:06 +02:00
}
2020-06-27 15:26:14 +02:00
ASSERT ( serial_level_item ) ;
2020-06-26 02:58:06 +02:00
// Add a checkable leaf item for this patch
const QString q_description = QString : : fromStdString ( description ) ;
QString visible_description = q_description ;
const auto match_criteria = QList < QPair < int , QVariant > > ( ) < < QPair ( description_role , q_description ) < < QPair ( persistance_role , true ) ;
// Add counter to leafs if the name already exists due to different hashes of the same game (PPU, SPU, PRX, OVL)
2020-06-26 18:31:42 +02:00
if ( const auto matches = gui : : utils : : find_children_by_data ( serial_level_item , match_criteria , false ) ; matches . count ( ) > 0 )
2020-06-26 02:58:06 +02:00
{
if ( auto only_match = matches . count ( ) = = 1 ? matches [ 0 ] : nullptr )
{
only_match - > setText ( 0 , q_description + QStringLiteral ( " (1) " ) ) ;
}
visible_description + = QStringLiteral ( " ( " ) + QString : : number ( matches . count ( ) + 1 ) + QStringLiteral ( " ) " ) ;
}
QTreeWidgetItem * patch_level_item = new QTreeWidgetItem ( ) ;
patch_level_item - > setText ( 0 , visible_description ) ;
patch_level_item - > setCheckState ( 0 , enabled ? Qt : : CheckState : : Checked : Qt : : CheckState : : Unchecked ) ;
patch_level_item - > setData ( 0 , hash_role , q_hash ) ;
patch_level_item - > setData ( 0 , title_role , q_title ) ;
patch_level_item - > setData ( 0 , serial_role , q_serial ) ;
patch_level_item - > setData ( 0 , app_version_role , q_app_version ) ;
patch_level_item - > setData ( 0 , description_role , q_description ) ;
patch_level_item - > setData ( 0 , patch_group_role , q_patch_group ) ;
patch_level_item - > setData ( 0 , node_level_role , node_level : : patch_level ) ;
patch_level_item - > setData ( 0 , persistance_role , true ) ;
2020-06-26 18:31:42 +02:00
serial_level_item - > addChild ( patch_level_item ) ;
2020-06-26 02:58:06 +02:00
}
}
}
2020-06-02 10:39:24 +02:00
}
2020-06-19 13:46:56 +02:00
}
2020-06-26 02:58:06 +02:00
const auto match_criteria = QList < QPair < int , QVariant > > ( ) < < QPair ( persistance_role , true ) ;
2020-06-02 10:39:24 +02:00
2020-06-19 19:00:06 +02:00
for ( int i = ui - > patch_tree - > topLevelItemCount ( ) - 1 ; i > = 0 ; i - - )
2020-06-19 13:46:56 +02:00
{
if ( auto title_level_item = ui - > patch_tree - > topLevelItem ( i ) )
2020-06-02 10:39:24 +02:00
{
2020-06-19 19:00:06 +02:00
if ( ! title_level_item - > data ( 0 , persistance_role ) . toBool ( ) )
{
delete ui - > patch_tree - > takeTopLevelItem ( i ) ;
continue ;
}
2020-06-26 02:58:06 +02:00
gui : : utils : : remove_children ( title_level_item , match_criteria , true ) ;
2020-06-02 10:39:24 +02:00
}
}
2020-06-26 02:58:06 +02:00
gui : : utils : : sort_tree ( ui - > patch_tree , Qt : : SortOrder : : AscendingOrder , true ) ;
2020-06-27 15:26:14 +02:00
// Move "All titles" to the top
// NOTE: "All serials" is only allowed as the only node in "All titles", so there is no need to move it up
// NOTE: "All versions" will be above valid numerical versions through sorting anyway
if ( const auto all_title_items = ui - > patch_tree - > findItems ( tr_all_titles , Qt : : MatchExactly ) ; all_title_items . size ( ) > 0 )
{
auto item = all_title_items [ 0 ] ;
ASSERT ( item & & all_title_items . size ( ) = = 1 ) ;
if ( const int index = ui - > patch_tree - > indexOfTopLevelItem ( item ) ; index > = 0 )
{
ui - > patch_tree - > takeTopLevelItem ( index ) ;
ui - > patch_tree - > insertTopLevelItem ( 0 , item ) ;
}
}
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
void patch_manager_dialog : : save_config ( )
2020-06-02 10:39:24 +02:00
{
2020-06-11 15:12:44 +02:00
patch_engine : : save_config ( m_map , m_legacy_patches_enabled ) ;
2020-06-02 10:39:24 +02:00
}
void patch_manager_dialog : : filter_patches ( const QString & term )
{
// Recursive function to show all matching items and their children.
// @return number of visible children of item, including item
std : : function < int ( QTreeWidgetItem * , bool ) > show_matches ;
2020-06-26 18:31:42 +02:00
show_matches = [ this , & show_matches , search_text = term . toLower ( ) ] ( QTreeWidgetItem * item , bool parent_visible ) - > int
2020-06-02 10:39:24 +02:00
{
if ( ! item ) return 0 ;
2020-06-26 18:31:42 +02:00
const node_level level = static_cast < node_level > ( item - > data ( 0 , node_level_role ) . toInt ( ) ) ;
// Hide nodes that aren't in the game list
if ( m_show_owned_games_only )
{
if ( level = = node_level : : serial_level )
{
const std : : string serial = item - > data ( 0 , serial_role ) . toString ( ) . toStdString ( ) ;
const std : : string app_version = item - > data ( 0 , app_version_role ) . toString ( ) . toStdString ( ) ;
2020-06-27 14:41:07 +02:00
if ( serial ! = patch_key : : all & &
2020-06-28 15:16:09 +02:00
( m_owned_games . find ( serial ) = = m_owned_games . end ( ) | | ( app_version ! = patch_key : : all & & ! m_owned_games . at ( serial ) . contains ( app_version ) ) ) )
2020-06-26 18:31:42 +02:00
{
item - > setHidden ( true ) ;
return 0 ;
}
}
}
2020-06-02 10:39:24 +02:00
// Only try to match if the parent is not visible
parent_visible = parent_visible | | item - > text ( 0 ) . toLower ( ) . contains ( search_text ) ;
2020-06-26 18:31:42 +02:00
int visible_items = 0 ;
2020-06-02 10:39:24 +02:00
// Get the number of visible children recursively
for ( int i = 0 ; i < item - > childCount ( ) ; i + + )
{
visible_items + = show_matches ( item - > child ( i ) , parent_visible ) ;
}
2020-06-26 18:31:42 +02:00
if ( parent_visible )
{
// Only show the title node if it has visible children when filtering for game list entries
if ( ! m_show_owned_games_only | | level ! = node_level : : title_level | | visible_items > 0 )
{
visible_items + + ;
}
}
2020-06-02 10:39:24 +02:00
// Only show item if itself or any of its children is visible
item - > setHidden ( visible_items < = 0 ) ;
return visible_items ;
} ;
// Go through each top level item and try to find matches
for ( auto top_level_item : ui - > patch_tree - > findItems ( " .* " , Qt : : MatchRegExp ) )
{
show_matches ( top_level_item , false ) ;
}
}
2020-06-26 02:58:06 +02:00
void patch_manager_dialog : : update_patch_info ( const patch_manager_dialog : : gui_patch_info & info )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
ui - > label_hash - > setText ( info . hash ) ;
ui - > label_author - > setText ( info . author ) ;
ui - > label_notes - > setText ( info . notes ) ;
ui - > label_description - > setText ( info . description ) ;
ui - > label_patch_version - > setText ( info . patch_version ) ;
ui - > label_serial - > setText ( info . serial ) ;
ui - > label_title - > setText ( info . title ) ;
ui - > label_app_version - > setText ( info . app_version ) ;
2020-06-02 10:39:24 +02:00
}
void patch_manager_dialog : : on_item_selected ( QTreeWidgetItem * current , QTreeWidgetItem * /*previous*/ )
{
if ( ! current )
{
2020-06-26 02:58:06 +02:00
// Clear patch info if no item is selected
update_patch_info ( { } ) ;
2020-06-02 10:39:24 +02:00
return ;
}
2020-06-25 20:53:41 +02:00
const node_level level = static_cast < node_level > ( current - > data ( 0 , node_level_role ) . toInt ( ) ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
patch_manager_dialog : : gui_patch_info info { } ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
switch ( level )
{
case node_level : : patch_level :
{
// Get patch identifiers stored in item data
info . hash = current - > data ( 0 , hash_role ) . toString ( ) ;
const std : : string hash = info . hash . toStdString ( ) ;
const std : : string description = current - > data ( 0 , description_role ) . toString ( ) . toStdString ( ) ;
2020-06-25 20:53:41 +02:00
2020-06-26 02:58:06 +02:00
// Find the patch for this item and get its metadata
if ( m_map . find ( hash ) ! = m_map . end ( ) )
2020-06-25 20:53:41 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & container = m_map . at ( hash ) ;
2020-06-25 20:53:41 +02:00
2020-06-26 02:58:06 +02:00
if ( ! container . is_legacy & & container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
2020-06-25 20:53:41 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & found_info = container . patch_info_map . at ( description ) ;
info . author = QString : : fromStdString ( found_info . author ) ;
info . notes = QString : : fromStdString ( found_info . notes ) ;
info . description = QString : : fromStdString ( found_info . description ) ;
info . patch_version = QString : : fromStdString ( found_info . patch_version ) ;
2020-06-25 20:53:41 +02:00
}
}
2020-06-26 02:58:06 +02:00
[[fallthrough]] ;
}
case node_level : : serial_level :
{
2020-06-27 10:32:00 +02:00
const QString serial = current - > data ( 0 , serial_role ) . toString ( ) ;
info . serial = serial . toStdString ( ) = = patch_key : : all ? tr_all_serials : serial ;
const QString app_version = current - > data ( 0 , app_version_role ) . toString ( ) ;
info . app_version = app_version . toStdString ( ) = = patch_key : : all ? tr_all_versions : app_version ;
2020-06-26 02:58:06 +02:00
[[fallthrough]] ;
}
case node_level : : title_level :
2020-06-27 10:32:00 +02:00
{
const QString title = current - > data ( 0 , title_role ) . toString ( ) ;
info . title = title . toStdString ( ) = = patch_key : : all ? tr_all_titles : title ;
[[fallthrough]] ;
}
2020-06-26 02:58:06 +02:00
default :
{
break ;
}
2020-06-02 10:39:24 +02:00
}
update_patch_info ( info ) ;
}
void patch_manager_dialog : : on_item_changed ( QTreeWidgetItem * item , int /*column*/ )
{
if ( ! item )
{
return ;
}
// Get checkstate of the item
const bool enabled = item - > checkState ( 0 ) = = Qt : : CheckState : : Checked ;
// Get patch identifiers stored in item data
2020-06-26 02:58:06 +02:00
const node_level level = static_cast < node_level > ( item - > data ( 0 , node_level_role ) . toInt ( ) ) ;
2020-06-02 10:39:24 +02:00
const std : : string hash = item - > data ( 0 , hash_role ) . toString ( ) . toStdString ( ) ;
2020-06-26 02:58:06 +02:00
const std : : string title = item - > data ( 0 , title_role ) . toString ( ) . toStdString ( ) ;
const std : : string serial = item - > data ( 0 , serial_role ) . toString ( ) . toStdString ( ) ;
const std : : string app_version = item - > data ( 0 , app_version_role ) . toString ( ) . toStdString ( ) ;
2020-06-02 10:39:24 +02:00
const std : : string description = item - > data ( 0 , description_role ) . toString ( ) . toStdString ( ) ;
2020-06-26 02:58:06 +02:00
const std : : string patch_group = item - > data ( 0 , patch_group_role ) . toString ( ) . toStdString ( ) ;
// Uncheck other patches with the same patch_group if this patch was enabled
if ( const auto node = item - > parent ( ) ; node & & enabled & & ! patch_group . empty ( ) & & level = = node_level : : patch_level )
{
for ( int i = 0 ; i < node - > childCount ( ) ; i + + )
{
if ( const auto other = node - > child ( i ) ; other & & other ! = item )
{
const std : : string other_patch_group = other - > data ( 0 , patch_group_role ) . toString ( ) . toStdString ( ) ;
if ( other_patch_group = = patch_group )
{
other - > setCheckState ( 0 , Qt : : CheckState : : Unchecked ) ;
}
}
}
}
2020-06-02 10:39:24 +02:00
// Enable/disable the patch for this item and show its metadata
if ( m_map . find ( hash ) ! = m_map . end ( ) )
{
2020-06-19 13:46:56 +02:00
auto & container = m_map [ hash ] ;
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
if ( ! container . is_legacy & & container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
m_map [ hash ] . patch_info_map [ description ] . titles [ title ] [ serial ] [ app_version ] = enabled ;
on_item_selected ( item , nullptr ) ;
2020-06-02 10:39:24 +02:00
return ;
}
}
}
void patch_manager_dialog : : on_custom_context_menu_requested ( const QPoint & pos )
{
QTreeWidgetItem * item = ui - > patch_tree - > itemAt ( pos ) ;
if ( ! item )
{
return ;
}
2020-06-27 00:23:04 +02:00
const node_level level = static_cast < node_level > ( item - > data ( 0 , node_level_role ) . toInt ( ) ) ;
2020-06-26 18:31:42 +02:00
2020-06-27 00:23:04 +02:00
QMenu * menu = new QMenu ( this ) ;
2020-06-02 10:39:24 +02:00
2020-06-27 00:23:04 +02:00
if ( level = = node_level : : patch_level )
2020-06-19 16:43:17 +02:00
{
// Find the patch for this item and add menu items accordingly
const std : : string hash = item - > data ( 0 , hash_role ) . toString ( ) . toStdString ( ) ;
const std : : string description = item - > data ( 0 , description_role ) . toString ( ) . toStdString ( ) ;
if ( m_map . find ( hash ) ! = m_map . end ( ) )
{
const auto & container = m_map . at ( hash ) ;
if ( ! container . is_legacy & & container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
{
const auto info = container . patch_info_map . at ( description ) ;
2020-06-26 18:31:42 +02:00
QAction * open_filepath = new QAction ( tr ( " Show Patch File " ) ) ;
2020-06-19 16:43:17 +02:00
menu - > addAction ( open_filepath ) ;
connect ( open_filepath , & QAction : : triggered , this , [ info ] ( bool )
{
gui : : utils : : open_dir ( info . source_path ) ;
} ) ;
2020-06-19 19:47:51 +02:00
2020-06-27 00:23:04 +02:00
menu - > addSeparator ( ) ;
2020-06-19 19:47:51 +02:00
if ( info . source_path = = patch_engine : : get_imported_patch_path ( ) )
{
2020-06-26 18:31:42 +02:00
QAction * remove_patch = new QAction ( tr ( " Remove Patch " ) ) ;
2020-06-19 19:47:51 +02:00
menu - > addAction ( remove_patch ) ;
connect ( remove_patch , & QAction : : triggered , this , [ info , this ] ( bool )
{
const auto answer = QMessageBox : : question ( this , tr ( " Remove Patch? " ) ,
tr ( " Do you really want to remove the selected patch? \n This action is immediate and irreversible! " ) ) ;
if ( answer ! = QMessageBox : : StandardButton : : Yes )
{
return ;
}
if ( patch_engine : : remove_patch ( info ) )
{
patch_log . success ( " Successfully removed patch %s: %s from %s " , info . hash , info . description , info . source_path ) ;
refresh ( ) ; // Refresh before showing the dialog
QMessageBox : : information ( this , tr ( " Success " ) , tr ( " The patch was successfully removed! " ) ) ;
}
else
{
patch_log . error ( " Could not remove patch %s: %s from %s " , info . hash , info . description , info . source_path ) ;
refresh ( ) ; // Refresh before showing the dialog
QMessageBox : : warning ( this , tr ( " Failure " ) , tr ( " The patch could not be removed! " ) ) ;
}
} ) ;
2020-06-27 00:23:04 +02:00
menu - > addSeparator ( ) ;
2020-06-19 19:47:51 +02:00
}
2020-06-19 16:43:17 +02:00
}
}
}
2020-06-02 10:39:24 +02:00
2020-06-27 00:23:04 +02:00
if ( item - > childCount ( ) > 0 )
{
if ( item - > isExpanded ( ) )
{
QAction * collapse = new QAction ( tr ( " Collapse " ) ) ;
menu - > addAction ( collapse ) ;
connect ( collapse , & QAction : : triggered , this , [ & item ] ( bool )
{
item - > setExpanded ( false ) ;
} ) ;
if ( level < ( node_level : : patch_level - 1 ) )
{
menu - > addSeparator ( ) ;
QAction * expand_children = new QAction ( tr ( " Expand Children " ) ) ;
menu - > addAction ( expand_children ) ;
connect ( expand_children , & QAction : : triggered , this , [ & item ] ( bool )
{
for ( int i = 0 ; i < item - > childCount ( ) ; i + + )
{
item - > child ( i ) - > setExpanded ( true ) ;
}
} ) ;
QAction * collapse_children = new QAction ( tr ( " Collapse Children " ) ) ;
menu - > addAction ( collapse_children ) ;
connect ( collapse_children , & QAction : : triggered , this , [ & item ] ( bool )
{
for ( int i = 0 ; i < item - > childCount ( ) ; i + + )
{
item - > child ( i ) - > setExpanded ( false ) ;
}
} ) ;
}
}
else
{
QAction * expand = new QAction ( tr ( " Expand " ) ) ;
menu - > addAction ( expand ) ;
connect ( expand , & QAction : : triggered , this , [ & item ] ( bool )
{
item - > setExpanded ( true ) ;
} ) ;
}
menu - > addSeparator ( ) ;
}
QAction * expand_all = new QAction ( tr ( " Expand All " ) ) ;
menu - > addAction ( expand_all ) ;
connect ( expand_all , & QAction : : triggered , ui - > patch_tree , & QTreeWidget : : expandAll ) ;
QAction * collapse_all = new QAction ( tr ( " Collapse All " ) ) ;
menu - > addAction ( collapse_all ) ;
connect ( collapse_all , & QAction : : triggered , ui - > patch_tree , & QTreeWidget : : collapseAll ) ;
2020-06-02 10:39:24 +02:00
menu - > exec ( ui - > patch_tree - > viewport ( ) - > mapToGlobal ( pos ) ) ;
}
2020-06-11 15:12:44 +02:00
2020-06-12 21:09:08 +02:00
bool patch_manager_dialog : : is_valid_file ( const QMimeData & md , QStringList * drop_paths )
{
const QList < QUrl > list = md . urls ( ) ; // Get list of all the dropped file urls
if ( list . size ( ) ! = 1 ) // We only accept one file for now
{
return false ;
}
for ( auto & & url : list ) // Check each file in url list for valid type
{
const QString path = url . toLocalFile ( ) ; // Convert url to filepath
const QFileInfo info = path ;
if ( ! info . fileName ( ) . endsWith ( " patch.yml " ) )
{
return false ;
}
if ( drop_paths )
{
drop_paths - > append ( path ) ;
}
}
return true ;
}
void patch_manager_dialog : : dropEvent ( QDropEvent * event )
{
QStringList drop_paths ;
if ( ! is_valid_file ( * event - > mimeData ( ) , & drop_paths ) )
{
return ;
}
QMessageBox box ( QMessageBox : : Icon : : Question , tr ( " Patch Manager " ) , tr ( " What do you want to do with the patch file? " ) , QMessageBox : : StandardButton : : NoButton , this ) ;
QAbstractButton * button_yes = box . addButton ( tr ( " Import " ) , QMessageBox : : YesRole ) ;
QAbstractButton * button_no = box . addButton ( tr ( " Validate " ) , QMessageBox : : NoRole ) ;
box . exec ( ) ;
const bool do_import = box . clickedButton ( ) = = button_yes ;
const bool do_validate = do_import | | box . clickedButton ( ) = = button_no ;
if ( ! do_validate )
{
return ;
}
2020-06-19 19:47:51 +02:00
for ( const QString & drop_path : drop_paths )
2020-06-12 21:09:08 +02:00
{
const auto path = drop_path . toStdString ( ) ;
patch_engine : : patch_map patches ;
std : : stringstream log_message ;
if ( patch_engine : : load ( patches , path , true , & log_message ) )
{
patch_log . success ( " Successfully validated patch file %s " , path ) ;
if ( do_import )
{
2020-06-19 19:47:51 +02:00
static const std : : string imported_patch_yml_path = patch_engine : : get_imported_patch_path ( ) ;
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
log_message . clear ( ) ;
2020-06-19 21:48:59 +02:00
size_t count = 0 ;
size_t total = 0 ;
if ( patch_engine : : import_patches ( patches , imported_patch_yml_path , count , total , & log_message ) )
2020-06-12 21:09:08 +02:00
{
refresh ( ) ;
2020-06-19 21:48:59 +02:00
const QString msg = log_message . str ( ) . empty ( ) ? " - " : QString : : fromStdString ( log_message . str ( ) ) ;
if ( count = = 0 )
{
QMessageBox : : warning ( this , tr ( " Nothing to import " ) , tr ( " None of the found %0 patches were imported. \n \n Log: \n \n %2 " )
. arg ( total ) . arg ( msg ) ) ;
}
else
{
QMessageBox : : information ( this , tr ( " Import successful " ) , tr ( " Imported %0/%1 patches to: \n %2 \n \n Log: \n \n %3 " )
. arg ( count ) . arg ( total ) . arg ( QString : : fromStdString ( imported_patch_yml_path ) ) . arg ( msg ) ) ;
}
2020-06-12 21:09:08 +02:00
}
else
{
2020-06-19 21:48:59 +02:00
QMessageBox : : warning ( this , tr ( " Import failed " ) , tr ( " The patch file was not imported. \n \n Log: \n \n " ) . arg ( QString : : fromStdString ( log_message . str ( ) ) ) ) ;
2020-06-12 21:09:08 +02:00
}
}
else
{
QMessageBox : : information ( this , tr ( " Validation successful " ) , tr ( " The patch file passed the validation. " ) ) ;
}
}
else
{
patch_log . error ( " Errors found in patch file %s " , path ) ;
2020-06-19 21:48:59 +02:00
QMessageBox : : warning ( this , tr ( " Validation failed " ) , tr ( " Errors were found in the patch file. \n \n Log: \n \n %0 " ) . arg ( QString : : fromStdString ( log_message . str ( ) ) ) ) ;
2020-06-12 21:09:08 +02:00
}
}
}
2020-06-11 15:12:44 +02:00
void patch_manager_dialog : : on_legacy_patches_enabled ( int state )
{
m_legacy_patches_enabled = state = = Qt : : CheckState : : Checked ;
}
2020-06-12 21:09:08 +02:00
2020-06-26 18:31:42 +02:00
void patch_manager_dialog : : on_show_owned_games_only ( int state )
{
m_show_owned_games_only = state = = Qt : : CheckState : : Checked ;
filter_patches ( ui - > patch_filter - > text ( ) ) ;
}
2020-06-12 21:09:08 +02:00
void patch_manager_dialog : : dragEnterEvent ( QDragEnterEvent * event )
{
if ( is_valid_file ( * event - > mimeData ( ) ) )
{
event - > accept ( ) ;
}
}
void patch_manager_dialog : : dragMoveEvent ( QDragMoveEvent * event )
{
if ( is_valid_file ( * event - > mimeData ( ) ) )
{
event - > accept ( ) ;
}
}
void patch_manager_dialog : : dragLeaveEvent ( QDragLeaveEvent * event )
{
event - > accept ( ) ;
}