2023-12-07 17:36:02 +00:00
// Copyright 2024 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
# include "DolphinQt/Debugger/BranchWatchDialog.h"
# include <algorithm>
# include <optional>
# include <utility>
# include <QApplication>
# include <QCheckBox>
# include <QClipboard>
# include <QGridLayout>
# include <QGroupBox>
# include <QHeaderView>
# include <QLineEdit>
# include <QMenu>
# include <QMenuBar>
# include <QPushButton>
# include <QShortcut>
# include <QSortFilterProxyModel>
# include <QStatusBar>
# include <QString>
# include <QTableView>
# include <QTimer>
# include <QToolBar>
# include <QVBoxLayout>
# include <QVariant>
# include <fmt/format.h>
# include "Common/Assert.h"
# include "Common/CommonFuncs.h"
# include "Common/CommonTypes.h"
# include "Common/FileUtil.h"
# include "Common/IOFile.h"
# include "Core/ConfigManager.h"
# include "Core/Core.h"
# include "Core/Debugger/BranchWatch.h"
# include "Core/Debugger/PPCDebugInterface.h"
# include "Core/PowerPC/Gekko.h"
# include "Core/PowerPC/PowerPC.h"
# include "Core/System.h"
# include "DolphinQt/Debugger/BranchWatchTableModel.h"
# include "DolphinQt/Debugger/CodeWidget.h"
2024-03-17 06:05:56 +00:00
# include "DolphinQt/Host.h"
2023-12-07 17:36:02 +00:00
# include "DolphinQt/QtUtils/DolphinFileDialog.h"
# include "DolphinQt/QtUtils/ModalMessageBox.h"
# include "DolphinQt/QtUtils/SetWindowDecorations.h"
# include "DolphinQt/Settings.h"
class BranchWatchProxyModel final : public QSortFilterProxyModel
{
friend BranchWatchDialog ;
public :
explicit BranchWatchProxyModel ( const Core : : BranchWatch & branch_watch , QObject * parent = nullptr )
: QSortFilterProxyModel ( parent ) , m_branch_watch ( branch_watch )
{
}
BranchWatchTableModel * sourceModel ( ) const
{
return static_cast < BranchWatchTableModel * > ( QSortFilterProxyModel : : sourceModel ( ) ) ;
}
void setSourceModel ( BranchWatchTableModel * source_model )
{
QSortFilterProxyModel : : setSourceModel ( source_model ) ;
}
// Virtual setSourceModel is forbidden for type-safety reasons. See sourceModel().
[[noreturn]] void setSourceModel ( QAbstractItemModel * source_model ) override { Crash ( ) ; }
bool filterAcceptsRow ( int source_row , const QModelIndex & source_parent ) const override ;
template < bool BranchWatchProxyModel : : * member >
void OnToggled ( bool enabled )
{
this - > * member = enabled ;
invalidateRowsFilter ( ) ;
}
template < QString BranchWatchProxyModel : : * member >
void OnSymbolTextChanged ( const QString & text )
{
this - > * member = text ;
invalidateRowsFilter ( ) ;
}
template < std : : optional < u32 > BranchWatchProxyModel : : * member >
void OnAddressTextChanged ( const QString & text )
{
bool ok = false ;
if ( const u32 value = text . toUInt ( & ok , 16 ) ; ok )
this - > * member = value ;
else
this - > * member = std : : nullopt ;
invalidateRowsFilter ( ) ;
}
void OnDelete ( QModelIndexList index_list ) ;
bool IsBranchTypeAllowed ( UGeckoInstruction inst ) const ;
void SetInspected ( const QModelIndex & index ) ;
private :
const Core : : BranchWatch & m_branch_watch ;
QString m_origin_symbol_name = { } , m_destin_symbol_name = { } ;
std : : optional < u32 > m_origin_min , m_origin_max , m_destin_min , m_destin_max ;
bool m_b = { } , m_bl = { } , m_bc = { } , m_bcl = { } , m_blr = { } , m_blrl = { } , m_bclr = { } ,
m_bclrl = { } , m_bctr = { } , m_bctrl = { } , m_bcctr = { } , m_bcctrl = { } ;
bool m_cond_true = { } , m_cond_false = { } ;
} ;
bool BranchWatchProxyModel : : filterAcceptsRow ( int source_row , const QModelIndex & ) const
{
const Core : : BranchWatch : : Selection : : value_type & value = m_branch_watch . GetSelection ( ) [ source_row ] ;
if ( value . condition )
{
if ( ! m_cond_true )
return false ;
}
else if ( ! m_cond_false )
return false ;
const Core : : BranchWatchCollectionKey & k = value . collection_ptr - > first ;
if ( ! IsBranchTypeAllowed ( k . original_inst ) )
return false ;
if ( m_origin_min . has_value ( ) & & k . origin_addr < m_origin_min . value ( ) )
return false ;
if ( m_origin_max . has_value ( ) & & k . origin_addr > m_origin_max . value ( ) )
return false ;
if ( m_destin_min . has_value ( ) & & k . destin_addr < m_destin_min . value ( ) )
return false ;
if ( m_destin_max . has_value ( ) & & k . destin_addr > m_destin_max . value ( ) )
return false ;
if ( ! m_origin_symbol_name . isEmpty ( ) )
{
if ( const QVariant & symbol_name_v = sourceModel ( ) - > GetSymbolList ( ) [ source_row ] . origin_name ;
! symbol_name_v . isValid ( ) | |
! symbol_name_v . value < QString > ( ) . contains ( m_origin_symbol_name , Qt : : CaseInsensitive ) )
return false ;
}
if ( ! m_destin_symbol_name . isEmpty ( ) )
{
if ( const QVariant & symbol_name_v = sourceModel ( ) - > GetSymbolList ( ) [ source_row ] . destin_name ;
! symbol_name_v . isValid ( ) | |
! symbol_name_v . value < QString > ( ) . contains ( m_destin_symbol_name , Qt : : CaseInsensitive ) )
return false ;
}
return true ;
}
void BranchWatchProxyModel : : OnDelete ( QModelIndexList index_list )
{
std : : transform ( index_list . begin ( ) , index_list . end ( ) , index_list . begin ( ) ,
[ this ] ( const QModelIndex & index ) { return mapToSource ( index ) ; } ) ;
sourceModel ( ) - > OnDelete ( std : : move ( index_list ) ) ;
}
static constexpr bool BranchSavesLR ( UGeckoInstruction inst )
{
DEBUG_ASSERT ( inst . OPCD = = 18 | | inst . OPCD = = 16 | |
( inst . OPCD = = 19 & & ( inst . SUBOP10 = = 16 | | inst . SUBOP10 = = 528 ) ) ) ;
// Every branch instruction uses the same LK field.
return inst . LK ;
}
bool BranchWatchProxyModel : : IsBranchTypeAllowed ( UGeckoInstruction inst ) const
{
const bool lr_saved = BranchSavesLR ( inst ) ;
switch ( inst . OPCD )
{
case 18 :
return lr_saved ? m_bl : m_b ;
case 16 :
return lr_saved ? m_bcl : m_bc ;
case 19 :
switch ( inst . SUBOP10 )
{
case 16 :
if ( ( inst . BO & 0b10100 ) = = 0b10100 ) // 1z1zz - Branch always
return lr_saved ? m_blrl : m_blr ;
return lr_saved ? m_bclrl : m_bclr ;
case 528 :
if ( ( inst . BO & 0b10100 ) = = 0b10100 ) // 1z1zz - Branch always
return lr_saved ? m_bctrl : m_bctr ;
return lr_saved ? m_bcctrl : m_bcctr ;
}
}
return false ;
}
void BranchWatchProxyModel : : SetInspected ( const QModelIndex & index )
{
sourceModel ( ) - > SetInspected ( mapToSource ( index ) ) ;
}
BranchWatchDialog : : BranchWatchDialog ( Core : : System & system , Core : : BranchWatch & branch_watch ,
2024-03-10 18:43:12 +00:00
PPCSymbolDB & ppc_symbol_db , CodeWidget * code_widget ,
QWidget * parent )
2023-12-07 17:36:02 +00:00
: QDialog ( parent ) , m_system ( system ) , m_branch_watch ( branch_watch ) , m_code_widget ( code_widget )
{
setWindowTitle ( tr ( " Branch Watch Tool " ) ) ;
setWindowFlags ( ( windowFlags ( ) | Qt : : WindowMinMaxButtonsHint ) & ~ Qt : : WindowContextHelpButtonHint ) ;
SetQWidgetWindowDecorations ( this ) ;
2024-03-10 18:43:12 +00:00
setLayout ( [ this , & ppc_symbol_db ] ( ) {
2024-03-02 15:53:23 +00:00
auto * main_layout = new QVBoxLayout ;
2023-12-07 17:36:02 +00:00
// Controls Toolbar (widgets are added later)
2024-03-02 15:53:23 +00:00
main_layout - > addWidget ( m_control_toolbar = new QToolBar ) ;
2023-12-07 17:36:02 +00:00
// Branch Watch Table
2024-03-10 18:43:12 +00:00
main_layout - > addWidget ( m_table_view = [ this , & ppc_symbol_db ] ( ) {
2023-12-07 17:36:02 +00:00
const auto & ui_settings = Settings : : Instance ( ) ;
m_table_proxy = new BranchWatchProxyModel ( m_branch_watch ) ;
2024-03-10 18:43:12 +00:00
m_table_proxy - > setSourceModel (
m_table_model = new BranchWatchTableModel ( m_system , m_branch_watch , ppc_symbol_db ) ) ;
2023-12-07 17:36:02 +00:00
m_table_proxy - > setSortRole ( UserRole : : SortRole ) ;
m_table_model - > setFont ( ui_settings . GetDebugFont ( ) ) ;
connect ( & ui_settings , & Settings : : DebugFontChanged , m_table_model ,
& BranchWatchTableModel : : setFont ) ;
2024-03-17 06:05:56 +00:00
connect ( Host : : GetInstance ( ) , & Host : : PPCSymbolsChanged , m_table_model ,
& BranchWatchTableModel : : UpdateSymbols ) ;
2023-12-07 17:36:02 +00:00
auto * const table_view = new QTableView ;
table_view - > setModel ( m_table_proxy ) ;
table_view - > setSortingEnabled ( true ) ;
table_view - > sortByColumn ( Column : : Origin , Qt : : AscendingOrder ) ;
table_view - > setSelectionMode ( QAbstractItemView : : ExtendedSelection ) ;
table_view - > setSelectionBehavior ( QAbstractItemView : : SelectRows ) ;
table_view - > setSizePolicy ( QSizePolicy : : Expanding , QSizePolicy : : Expanding ) ;
table_view - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
table_view - > setEditTriggers ( QAbstractItemView : : NoEditTriggers ) ;
table_view - > setCornerButtonEnabled ( false ) ;
table_view - > verticalHeader ( ) - > hide ( ) ;
QHeaderView * const horizontal_header = table_view - > horizontalHeader ( ) ;
horizontal_header - > restoreState ( // Restore column visibility state.
Settings : : GetQSettings ( )
. value ( QStringLiteral ( " branchwatchdialog/tableheader/state " ) )
. toByteArray ( ) ) ;
horizontal_header - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
horizontal_header - > setStretchLastSection ( true ) ;
horizontal_header - > setSectionsMovable ( true ) ;
horizontal_header - > setFirstSectionMovable ( true ) ;
connect ( table_view , & QTableView : : clicked , this , & BranchWatchDialog : : OnTableClicked ) ;
connect ( table_view , & QTableView : : customContextMenuRequested , this ,
& BranchWatchDialog : : OnTableContextMenu ) ;
connect ( horizontal_header , & QHeaderView : : customContextMenuRequested , this ,
& BranchWatchDialog : : OnTableHeaderContextMenu ) ;
connect ( new QShortcut ( QKeySequence ( Qt : : Key_Delete ) , this ) , & QShortcut : : activated , this ,
& BranchWatchDialog : : OnTableDeleteKeypress ) ;
return table_view ;
} ( ) ) ;
m_mnu_column_visibility = [ this ] ( ) {
static constexpr std : : array < const char * , Column : : NumberOfColumns > headers = {
QT_TR_NOOP ( " Instruction " ) , QT_TR_NOOP ( " Condition " ) , QT_TR_NOOP ( " Origin " ) ,
QT_TR_NOOP ( " Destination " ) , QT_TR_NOOP ( " Recent Hits " ) , QT_TR_NOOP ( " Total Hits " ) ,
QT_TR_NOOP ( " Origin Symbol " ) , QT_TR_NOOP ( " Destination Symbol " ) } ;
auto * const menu = new QMenu ( ) ;
for ( int column = 0 ; column < Column : : NumberOfColumns ; + + column )
{
QAction * action = menu - > addAction ( tr ( headers [ column ] ) , [ this , column ] ( bool enabled ) {
m_table_view - > setColumnHidden ( column , ! enabled ) ;
} ) ;
action - > setChecked ( ! m_table_view - > isColumnHidden ( column ) ) ;
action - > setCheckable ( true ) ;
}
return menu ;
} ( ) ;
// Menu Bar
2024-03-02 15:53:23 +00:00
main_layout - > setMenuBar ( [ this ] ( ) {
2023-12-07 17:36:02 +00:00
QMenuBar * const menu_bar = new QMenuBar ;
menu_bar - > setNativeMenuBar ( false ) ;
QMenu * const menu_file = new QMenu ( tr ( " &File " ) , menu_bar ) ;
menu_file - > addAction ( tr ( " &Save Branch Watch " ) , this , & BranchWatchDialog : : OnSave ) ;
menu_file - > addAction ( tr ( " Save Branch Watch &As... " ) , this , & BranchWatchDialog : : OnSaveAs ) ;
menu_file - > addAction ( tr ( " &Load Branch Watch " ) , this , & BranchWatchDialog : : OnLoad ) ;
menu_file - > addAction ( tr ( " Load Branch Watch &From... " ) , this , & BranchWatchDialog : : OnLoadFrom ) ;
m_act_autosave = menu_file - > addAction ( tr ( " A&uto Save " ) ) ;
m_act_autosave - > setCheckable ( true ) ;
connect ( m_act_autosave , & QAction : : toggled , this , & BranchWatchDialog : : OnToggleAutoSave ) ;
menu_bar - > addMenu ( menu_file ) ;
QMenu * const menu_tool = new QMenu ( tr ( " &Tool " ) , menu_bar ) ;
menu_tool - > setToolTipsVisible ( true ) ;
menu_tool - > addAction ( tr ( " Hide &Controls " ) , this , & BranchWatchDialog : : OnHideShowControls )
- > setCheckable ( true ) ;
QAction * const act_ignore_apploader =
menu_tool - > addAction ( tr ( " Ignore &Apploader Branch Hits " ) ) ;
act_ignore_apploader - > setToolTip (
tr ( " This only applies to the initial boot of the emulated software. " ) ) ;
act_ignore_apploader - > setChecked ( m_system . IsBranchWatchIgnoreApploader ( ) ) ;
act_ignore_apploader - > setCheckable ( true ) ;
connect ( act_ignore_apploader , & QAction : : toggled , this ,
& BranchWatchDialog : : OnToggleIgnoreApploader ) ;
menu_tool - > addMenu ( m_mnu_column_visibility ) - > setText ( tr ( " Column &Visibility " ) ) ;
menu_tool - > addAction ( tr ( " Wipe &Inspection Data " ) , this , & BranchWatchDialog : : OnWipeInspection ) ;
menu_tool - > addAction ( tr ( " &Help " ) , this , & BranchWatchDialog : : OnHelp ) ;
menu_bar - > addMenu ( menu_tool ) ;
return menu_bar ;
} ( ) ) ;
// Status Bar
2024-03-02 15:53:23 +00:00
main_layout - > addWidget ( m_status_bar = [ ] ( ) {
2023-12-07 17:36:02 +00:00
auto * const status_bar = new QStatusBar ;
status_bar - > setSizeGripEnabled ( false ) ;
return status_bar ;
} ( ) ) ;
// Tool Controls
m_control_toolbar - > addWidget ( [ this ] ( ) {
auto * const layout = new QGridLayout ;
layout - > addWidget ( m_btn_start_pause = new QPushButton ( tr ( " Start Branch Watch " ) ) , 0 , 0 ) ;
connect ( m_btn_start_pause , & QPushButton : : toggled , this , & BranchWatchDialog : : OnStartPause ) ;
m_btn_start_pause - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
m_btn_start_pause - > setCheckable ( true ) ;
layout - > addWidget ( m_btn_clear_watch = new QPushButton ( tr ( " Clear Branch Watch " ) ) , 1 , 0 ) ;
connect ( m_btn_clear_watch , & QPushButton : : pressed , this ,
& BranchWatchDialog : : OnClearBranchWatch ) ;
m_btn_clear_watch - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
layout - > addWidget ( m_btn_path_was_taken = new QPushButton ( tr ( " Code Path Was Taken " ) ) , 0 , 1 ) ;
connect ( m_btn_path_was_taken , & QPushButton : : pressed , this ,
& BranchWatchDialog : : OnCodePathWasTaken ) ;
m_btn_path_was_taken - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
layout - > addWidget ( m_btn_path_not_taken = new QPushButton ( tr ( " Code Path Not Taken " ) ) , 1 , 1 ) ;
connect ( m_btn_path_not_taken , & QPushButton : : pressed , this ,
& BranchWatchDialog : : OnCodePathNotTaken ) ;
m_btn_path_not_taken - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
auto * const group_box = new QGroupBox ( tr ( " Tool Controls " ) ) ;
group_box - > setLayout ( layout ) ;
group_box - > setAlignment ( Qt : : AlignHCenter ) ;
return group_box ;
} ( ) ) ;
// Spacer
m_control_toolbar - > addWidget ( [ ] ( ) {
auto * const widget = new QWidget ;
widget - > setSizePolicy ( QSizePolicy : : MinimumExpanding , QSizePolicy : : Preferred ) ;
return widget ;
} ( ) ) ;
// Branch Type Filter Options
m_control_toolbar - > addWidget ( [ this ] ( ) {
auto * const layout = new QGridLayout ;
const auto routine = [ this , layout ] ( const QString & text , const QString & tooltip , int row ,
int column , void ( BranchWatchProxyModel : : * slot ) ( bool ) ) {
QCheckBox * const check_box = new QCheckBox ( text ) ;
check_box - > setToolTip ( tooltip ) ;
layout - > addWidget ( check_box , row , column ) ;
connect ( check_box , & QCheckBox : : toggled , [ this , slot ] ( bool checked ) {
( m_table_proxy - > * slot ) ( checked ) ;
UpdateStatus ( ) ;
} ) ;
check_box - > setChecked ( true ) ;
} ;
// clang-format off
routine ( QStringLiteral ( " b " ) , tr ( " Branch " ) , 0 , 0 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_b > ) ;
routine ( QStringLiteral ( " bl " ) , tr ( " Branch (LR saved) " ) , 0 , 1 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bl > ) ;
routine ( QStringLiteral ( " bc " ) , tr ( " Branch Conditional " ) , 0 , 2 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bc > ) ;
routine ( QStringLiteral ( " bcl " ) , tr ( " Branch Conditional (LR saved) " ) , 0 , 3 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bcl > ) ;
routine ( QStringLiteral ( " blr " ) , tr ( " Branch to Link Register " ) , 1 , 0 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_blr > ) ;
routine ( QStringLiteral ( " blrl " ) , tr ( " Branch to Link Register (LR saved) " ) , 1 , 1 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_blrl > ) ;
routine ( QStringLiteral ( " bclr " ) , tr ( " Branch Conditional to Link Register " ) , 1 , 2 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bclr > ) ;
routine ( QStringLiteral ( " bclrl " ) , tr ( " Branch Conditional to Link Register (LR saved) " ) , 1 , 3 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bclrl > ) ;
routine ( QStringLiteral ( " bctr " ) , tr ( " Branch to Count Register " ) , 2 , 0 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bctr > ) ;
routine ( QStringLiteral ( " bctrl " ) , tr ( " Branch to Count Register (LR saved) " ) , 2 , 1 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bctrl > ) ;
routine ( QStringLiteral ( " bcctr " ) , tr ( " Branch Conditional to Count Register " ) , 2 , 2 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bcctr > ) ;
routine ( QStringLiteral ( " bcctrl " ) , tr ( " Branch Conditional to Count Register (LR saved) " ) , 2 , 3 , & BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_bcctrl > ) ;
// clang-format on
auto * const group_box = new QGroupBox ( tr ( " Branch Type " ) ) ;
group_box - > setLayout ( layout ) ;
group_box - > setAlignment ( Qt : : AlignHCenter ) ;
return group_box ;
} ( ) ) ;
// Origin and Destination Filter Options
m_control_toolbar - > addWidget ( [ this ] ( ) {
auto * const layout = new QGridLayout ;
2024-03-02 15:53:23 +00:00
const auto routine = [ this , layout ] ( const QString & placeholder_text , int row , int column ,
int width ,
2023-12-07 17:36:02 +00:00
void ( BranchWatchProxyModel : : * slot ) ( const QString & ) ) {
QLineEdit * const line_edit = new QLineEdit ;
layout - > addWidget ( line_edit , row , column , 1 , width ) ;
connect ( line_edit , & QLineEdit : : textChanged , [ this , slot ] ( const QString & text ) {
( m_table_proxy - > * slot ) ( text ) ;
UpdateStatus ( ) ;
} ) ;
2024-03-02 15:53:23 +00:00
line_edit - > setPlaceholderText ( placeholder_text ) ;
2023-12-07 17:36:02 +00:00
return line_edit ;
} ;
// clang-format off
routine ( tr ( " Origin Symbol " ) , 0 , 0 , 1 , & BranchWatchProxyModel : : OnSymbolTextChanged < & BranchWatchProxyModel : : m_origin_symbol_name > ) ;
routine ( tr ( " Origin Min " ) , 1 , 0 , 1 , & BranchWatchProxyModel : : OnAddressTextChanged < & BranchWatchProxyModel : : m_origin_min > ) - > setMaxLength ( 8 ) ;
routine ( tr ( " Origin Max " ) , 2 , 0 , 1 , & BranchWatchProxyModel : : OnAddressTextChanged < & BranchWatchProxyModel : : m_origin_max > ) - > setMaxLength ( 8 ) ;
routine ( tr ( " Destination Symbol " ) , 0 , 1 , 1 , & BranchWatchProxyModel : : OnSymbolTextChanged < & BranchWatchProxyModel : : m_destin_symbol_name > ) ;
routine ( tr ( " Destination Min " ) , 1 , 1 , 1 , & BranchWatchProxyModel : : OnAddressTextChanged < & BranchWatchProxyModel : : m_destin_min > ) - > setMaxLength ( 8 ) ;
routine ( tr ( " Destination Max " ) , 2 , 1 , 1 , & BranchWatchProxyModel : : OnAddressTextChanged < & BranchWatchProxyModel : : m_destin_max > ) - > setMaxLength ( 8 ) ;
// clang-format on
auto * const group_box = new QGroupBox ( tr ( " Origin and Destination " ) ) ;
group_box - > setLayout ( layout ) ;
group_box - > setAlignment ( Qt : : AlignHCenter ) ;
return group_box ;
} ( ) ) ;
// Condition Filter Options
m_control_toolbar - > addWidget ( [ this ] ( ) {
auto * const layout = new QVBoxLayout ;
layout - > setAlignment ( Qt : : AlignHCenter ) ;
const auto routine = [ this , layout ] ( const QString & text ,
void ( BranchWatchProxyModel : : * slot ) ( bool ) ) {
QCheckBox * const check_box = new QCheckBox ( text ) ;
layout - > addWidget ( check_box ) ;
connect ( check_box , & QCheckBox : : toggled , [ this , slot ] ( bool checked ) {
( m_table_proxy - > * slot ) ( checked ) ;
UpdateStatus ( ) ;
} ) ;
check_box - > setChecked ( true ) ;
return check_box ;
} ;
routine ( QStringLiteral ( " true " ) ,
& BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_cond_true > )
- > setToolTip ( tr ( " This will also filter unconditional branches. \n "
" To filter for or against unconditional branches, \n "
" use the Branch Type filter options. " ) ) ;
routine ( QStringLiteral ( " false " ) ,
& BranchWatchProxyModel : : OnToggled < & BranchWatchProxyModel : : m_cond_false > ) ;
auto * const group_box = new QGroupBox ( tr ( " Condition " ) ) ;
group_box - > setLayout ( layout ) ;
group_box - > setAlignment ( Qt : : AlignHCenter ) ;
return group_box ;
} ( ) ) ;
// Misc. Controls
m_control_toolbar - > addWidget ( [ this ] ( ) {
auto * const layout = new QVBoxLayout ;
layout - > addWidget ( m_btn_was_overwritten = new QPushButton ( tr ( " Branch Was Overwritten " ) ) ) ;
connect ( m_btn_was_overwritten , & QPushButton : : pressed , this ,
& BranchWatchDialog : : OnBranchWasOverwritten ) ;
m_btn_was_overwritten - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
layout - > addWidget ( m_btn_not_overwritten = new QPushButton ( tr ( " Branch Not Overwritten " ) ) ) ;
connect ( m_btn_not_overwritten , & QPushButton : : pressed , this ,
& BranchWatchDialog : : OnBranchNotOverwritten ) ;
m_btn_not_overwritten - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
layout - > addWidget ( m_btn_wipe_recent_hits = new QPushButton ( tr ( " Wipe Recent Hits " ) ) ) ;
connect ( m_btn_wipe_recent_hits , & QPushButton : : pressed , this ,
& BranchWatchDialog : : OnWipeRecentHits ) ;
m_btn_wipe_recent_hits - > setSizePolicy ( QSizePolicy : : Preferred , QSizePolicy : : Expanding ) ;
m_btn_wipe_recent_hits - > setEnabled ( false ) ;
auto * const group_box = new QGroupBox ( tr ( " Misc. Controls " ) ) ;
group_box - > setLayout ( layout ) ;
group_box - > setAlignment ( Qt : : AlignHCenter ) ;
return group_box ;
} ( ) ) ;
connect ( m_timer = new QTimer , & QTimer : : timeout , this , & BranchWatchDialog : : OnTimeout ) ;
connect ( & Settings : : Instance ( ) , & Settings : : EmulationStateChanged , this ,
& BranchWatchDialog : : OnEmulationStateChanged ) ;
connect ( m_table_proxy , & BranchWatchProxyModel : : layoutChanged , this ,
& BranchWatchDialog : : UpdateStatus ) ;
2024-03-02 15:53:23 +00:00
return main_layout ;
2023-12-07 17:36:02 +00:00
} ( ) ) ;
// FIXME: On Linux, Qt6 has recently been resetting column widths to their defaults in many
// unexpected ways. This affects all kinds of QTables in Dolphin's GUI, so to avoid it in
// this QTableView, I have deferred this operation. Any earlier, and this would be undone.
// SetQWidgetWindowDecorations was moved to before these operations for the same reason.
m_table_view - > setColumnWidth ( Column : : Instruction , 50 ) ;
m_table_view - > setColumnWidth ( Column : : Condition , 50 ) ;
m_table_view - > setColumnWidth ( Column : : OriginSymbol , 250 ) ;
m_table_view - > setColumnWidth ( Column : : DestinSymbol , 250 ) ;
// The default column width (100 units) is fine for the rest.
const auto & settings = Settings : : GetQSettings ( ) ;
restoreGeometry ( settings . value ( QStringLiteral ( " branchwatchdialog/geometry " ) ) . toByteArray ( ) ) ;
}
2024-03-05 22:02:45 +00:00
BranchWatchDialog : : ~ BranchWatchDialog ( )
2023-12-07 17:36:02 +00:00
{
2024-03-05 22:02:45 +00:00
SaveSettings ( ) ;
2023-12-07 17:36:02 +00:00
}
static constexpr int BRANCH_WATCH_TOOL_TIMER_DELAY_MS = 100 ;
static constexpr int BRANCH_WATCH_TOOL_TIMER_PAUSE_ONESHOT_MS = 200 ;
static bool TimerCondition ( const Core : : BranchWatch & branch_watch , Core : : State state )
{
return branch_watch . GetRecordingActive ( ) & & state > Core : : State : : Paused ;
}
2024-03-05 22:02:45 +00:00
void BranchWatchDialog : : hideEvent ( QHideEvent * event )
2023-12-07 17:36:02 +00:00
{
2024-03-05 22:02:45 +00:00
if ( m_timer - > isActive ( ) )
m_timer - > stop ( ) ;
QDialog : : hideEvent ( event ) ;
2023-12-07 17:36:02 +00:00
}
2024-03-05 22:02:45 +00:00
void BranchWatchDialog : : showEvent ( QShowEvent * event )
2023-12-07 17:36:02 +00:00
{
2024-03-28 18:35:13 +00:00
if ( TimerCondition ( m_branch_watch , Core : : GetState ( m_system ) ) )
2023-12-07 17:36:02 +00:00
m_timer - > start ( BRANCH_WATCH_TOOL_TIMER_DELAY_MS ) ;
2024-03-05 22:02:45 +00:00
QDialog : : showEvent ( event ) ;
2023-12-07 17:36:02 +00:00
}
void BranchWatchDialog : : OnStartPause ( bool checked )
{
if ( checked )
{
m_branch_watch . Start ( ) ;
m_btn_start_pause - > setText ( tr ( " Pause Branch Watch " ) ) ;
// Restart the timer if the situation calls for it, but always turn off single-shot.
m_timer - > setSingleShot ( false ) ;
2024-03-28 18:35:13 +00:00
if ( Core : : GetState ( m_system ) > Core : : State : : Paused )
2023-12-07 17:36:02 +00:00
m_timer - > start ( BRANCH_WATCH_TOOL_TIMER_DELAY_MS ) ;
}
else
{
m_branch_watch . Pause ( ) ;
m_btn_start_pause - > setText ( tr ( " Start Branch Watch " ) ) ;
// Schedule one last update in the future in case Branch Watch is in the middle of a hit.
2024-03-28 18:35:13 +00:00
if ( Core : : GetState ( m_system ) > Core : : State : : Paused )
2023-12-07 17:36:02 +00:00
m_timer - > setInterval ( BRANCH_WATCH_TOOL_TIMER_PAUSE_ONESHOT_MS ) ;
m_timer - > setSingleShot ( true ) ;
}
Update ( ) ;
}
void BranchWatchDialog : : OnClearBranchWatch ( )
{
{
const Core : : CPUThreadGuard guard { m_system } ;
m_table_model - > OnClearBranchWatch ( guard ) ;
AutoSave ( guard ) ;
}
m_btn_wipe_recent_hits - > setEnabled ( false ) ;
UpdateStatus ( ) ;
}
static std : : string GetSnapshotDefaultFilepath ( )
{
return fmt : : format ( " {}{}.txt " , File : : GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX ) ,
SConfig : : GetInstance ( ) . GetGameID ( ) ) ;
}
void BranchWatchDialog : : OnSave ( )
{
if ( ! m_branch_watch . CanSave ( ) )
{
ModalMessageBox : : warning ( this , tr ( " Error " ) , tr ( " There is nothing to save! " ) ) ;
return ;
}
Save ( Core : : CPUThreadGuard { m_system } , GetSnapshotDefaultFilepath ( ) ) ;
}
void BranchWatchDialog : : OnSaveAs ( )
{
if ( ! m_branch_watch . CanSave ( ) )
{
ModalMessageBox : : warning ( this , tr ( " Error " ) , tr ( " There is nothing to save! " ) ) ;
return ;
}
const QString filepath = DolphinFileDialog : : getSaveFileName (
this , tr ( " Save Branch Watch snapshot " ) ,
QString : : fromStdString ( File : : GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX ) ) ,
tr ( " Text file (*.txt);;All Files (*) " ) ) ;
if ( filepath . isEmpty ( ) )
return ;
Save ( Core : : CPUThreadGuard { m_system } , filepath . toStdString ( ) ) ;
}
void BranchWatchDialog : : OnLoad ( )
{
Load ( Core : : CPUThreadGuard { m_system } , GetSnapshotDefaultFilepath ( ) ) ;
}
void BranchWatchDialog : : OnLoadFrom ( )
{
const QString filepath = DolphinFileDialog : : getOpenFileName (
this , tr ( " Load Branch Watch snapshot " ) ,
QString : : fromStdString ( File : : GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX ) ) ,
tr ( " Text file (*.txt);;All Files (*) " ) , nullptr , QFileDialog : : Option : : ReadOnly ) ;
if ( filepath . isEmpty ( ) )
return ;
Load ( Core : : CPUThreadGuard { m_system } , filepath . toStdString ( ) ) ;
}
void BranchWatchDialog : : OnCodePathWasTaken ( )
{
{
const Core : : CPUThreadGuard guard { m_system } ;
m_table_model - > OnCodePathWasTaken ( guard ) ;
AutoSave ( guard ) ;
}
m_btn_wipe_recent_hits - > setEnabled ( true ) ;
UpdateStatus ( ) ;
}
void BranchWatchDialog : : OnCodePathNotTaken ( )
{
{
const Core : : CPUThreadGuard guard { m_system } ;
m_table_model - > OnCodePathNotTaken ( guard ) ;
AutoSave ( guard ) ;
}
UpdateStatus ( ) ;
}
void BranchWatchDialog : : OnBranchWasOverwritten ( )
{
2024-03-28 18:35:13 +00:00
if ( Core : : GetState ( m_system ) = = Core : : State : : Uninitialized )
2023-12-07 17:36:02 +00:00
{
ModalMessageBox : : warning ( this , tr ( " Error " ) , tr ( " Core is uninitialized. " ) ) ;
return ;
}
{
const Core : : CPUThreadGuard guard { m_system } ;
m_table_model - > OnBranchWasOverwritten ( guard ) ;
AutoSave ( guard ) ;
}
UpdateStatus ( ) ;
}
void BranchWatchDialog : : OnBranchNotOverwritten ( )
{
2024-03-28 18:35:13 +00:00
if ( Core : : GetState ( m_system ) = = Core : : State : : Uninitialized )
2023-12-07 17:36:02 +00:00
{
ModalMessageBox : : warning ( this , tr ( " Error " ) , tr ( " Core is uninitialized. " ) ) ;
return ;
}
{
const Core : : CPUThreadGuard guard { m_system } ;
m_table_model - > OnBranchNotOverwritten ( guard ) ;
AutoSave ( guard ) ;
}
UpdateStatus ( ) ;
}
void BranchWatchDialog : : OnWipeRecentHits ( )
{
m_table_model - > OnWipeRecentHits ( ) ;
}
void BranchWatchDialog : : OnWipeInspection ( )
{
m_table_model - > OnWipeInspection ( ) ;
}
void BranchWatchDialog : : OnTimeout ( )
{
Update ( ) ;
}
void BranchWatchDialog : : OnEmulationStateChanged ( Core : : State new_state )
{
if ( ! isVisible ( ) )
return ;
if ( TimerCondition ( m_branch_watch , new_state ) )
m_timer - > start ( BRANCH_WATCH_TOOL_TIMER_DELAY_MS ) ;
else if ( m_timer - > isActive ( ) )
m_timer - > stop ( ) ;
Update ( ) ;
}
void BranchWatchDialog : : OnHelp ( )
{
ModalMessageBox : : information (
this , tr ( " Branch Watch Tool Help (1/4) " ) ,
tr ( " Branch Watch is a code-searching tool that can isolate branches tracked by the emulated "
" CPU by testing candidate branches with simple criteria. If you are familiar with Cheat "
" Engine's Ultimap, Branch Watch is similar to that. \n \n "
" Press the \" Start Branch Watch \" button to activate Branch Watch. Branch Watch persists "
" across emulation sessions, and a snapshot of your progress can be saved to and loaded "
" from the User Directory to persist after Dolphin Emulator is closed. \" Save As... \" and "
" \" Load From... \" actions are also available, and auto-saving can be enabled to save a "
" snapshot at every step of a search. The \" Pause Branch Watch \" button will halt Branch "
" Watch from tracking further branch hits until it is told to resume. Press the \" Clear "
" Branch Watch \" button to clear all candidates and return to the blacklist phase. " ) ) ;
ModalMessageBox : : information (
this , tr ( " Branch Watch Tool Help (2/4) " ) ,
tr ( " Branch Watch starts in the blacklist phase, meaning no candidates have been chosen yet, "
" but candidates found so far can be excluded from the candidacy by pressing the \" Code "
" Path Not Taken \" , \" Branch Was Overwritten \" , and \" Branch Not Overwritten \" buttons. "
" Once the \" Code Path Was Taken \" button is pressed for the first time, Branch Watch will "
" switch to the reduction phase, and the table will populate with all eligible "
" candidates. " ) ) ;
ModalMessageBox : : information (
this , tr ( " Branch Watch Tool Help (3/4) " ) ,
tr ( " Once in the reduction phase, it is time to start narrowing down the candidates shown in "
" the table. Further reduce the candidates by checking whether a code path was or was not "
" taken since the last time it was checked. It is also possible to reduce the candidates "
" by determining whether a branch instruction has or has not been overwritten since it was "
" first hit. Filter the candidates by branch kind, branch condition, origin or destination "
" address, and origin or destination symbol name. \n \n "
" After enough passes and experimentation, you may be able to find function calls and "
" conditional code paths that are only taken when an action is performed in the emulated "
" software. " ) ) ;
ModalMessageBox : : information (
this , tr ( " Branch Watch Tool Help (4/4) " ) ,
tr ( " Rows in the table can be left-clicked on the origin, destination, and symbol columns to "
" view the associated address in Code View. Right-clicking the selected row(s) will bring "
" up a context menu. \n \n "
" If the origin column of a row selection is right-clicked, an action to replace the "
" branch instruction at the origin(s) with a NOP instruction (No Operation), and an action "
" to copy the address(es) to the clipboard will be available. \n \n "
" If the destination column of a row selection is right-clicked, an action to replace the "
" instruction at the destination(s) with a BLR instruction (Branch to Link Register) will "
" be available, but only if the branch instruction at every origin saves the link "
" register, and an action to copy the address(es) to the clipboard will be available. \n \n "
" If the origin / destination symbol column of a row selection is right-clicked, an action "
" to replace the instruction(s) at the start of the symbol with a BLR instruction will be "
" available, but only if every origin / destination symbol is found. \n \n "
" All context menus have the action to delete the selected row(s) from the candidates. " ) ) ;
}
void BranchWatchDialog : : OnToggleAutoSave ( bool checked )
{
if ( ! checked )
return ;
const QString filepath = DolphinFileDialog : : getSaveFileName (
2024-04-20 14:26:53 +00:00
// i18n: If the user selects a file, Branch Watch will save to that file.
// If the user presses Cancel, Branch Watch will save to a file in the user folder.
2023-12-07 17:36:02 +00:00
this , tr ( " Select Branch Watch snapshot auto-save file (for user folder location, cancel) " ) ,
QString : : fromStdString ( File : : GetUserPath ( D_DUMPDEBUG_BRANCHWATCH_IDX ) ) ,
tr ( " Text file (*.txt);;All Files (*) " ) ) ;
if ( filepath . isEmpty ( ) )
m_autosave_filepath = std : : nullopt ;
else
m_autosave_filepath = filepath . toStdString ( ) ;
}
void BranchWatchDialog : : OnHideShowControls ( bool checked )
{
if ( checked )
m_control_toolbar - > hide ( ) ;
else
m_control_toolbar - > show ( ) ;
}
void BranchWatchDialog : : OnToggleIgnoreApploader ( bool checked )
{
m_system . SetIsBranchWatchIgnoreApploader ( checked ) ;
}
void BranchWatchDialog : : OnTableClicked ( const QModelIndex & index )
{
const QVariant v = m_table_proxy - > data ( index , UserRole : : ClickRole ) ;
switch ( index . column ( ) )
{
case Column : : OriginSymbol :
case Column : : DestinSymbol :
if ( ! v . isValid ( ) )
return ;
[[fallthrough]] ;
case Column : : Origin :
case Column : : Destination :
m_code_widget - > SetAddress ( v . value < u32 > ( ) , CodeViewWidget : : SetAddressUpdate : : WithDetailedUpdate ) ;
return ;
}
}
void BranchWatchDialog : : OnTableContextMenu ( const QPoint & pos )
{
const QModelIndex index = m_table_view - > indexAt ( pos ) ;
if ( ! index . isValid ( ) )
return ;
QModelIndexList index_list = m_table_view - > selectionModel ( ) - > selectedRows ( index . column ( ) ) ;
QMenu * const menu = new QMenu ;
menu - > addAction ( tr ( " &Delete " ) , [ this , index_list ] ( ) { OnTableDelete ( std : : move ( index_list ) ) ; } ) ;
switch ( index . column ( ) )
{
case Column : : Origin :
{
QAction * const action = menu - > addAction ( tr ( " Insert &NOP " ) ) ;
2024-03-28 18:35:13 +00:00
if ( Core : : GetState ( m_system ) ! = Core : : State : : Uninitialized )
2023-12-07 17:36:02 +00:00
connect ( action , & QAction : : triggered ,
[ this , index_list ] ( ) { OnTableSetNOP ( std : : move ( index_list ) ) ; } ) ;
else
action - > setEnabled ( false ) ;
menu - > addAction ( tr ( " &Copy Address " ) , [ this , index_list = std : : move ( index_list ) ] ( ) {
OnTableCopyAddress ( std : : move ( index_list ) ) ;
} ) ;
break ;
}
case Column : : Destination :
{
QAction * const action = menu - > addAction ( tr ( " Insert &BLR " ) ) ;
const bool enable_action =
2024-03-28 18:35:13 +00:00
Core : : GetState ( m_system ) ! = Core : : State : : Uninitialized & &
2024-03-02 15:53:23 +00:00
std : : all_of ( index_list . begin ( ) , index_list . end ( ) , [ this ] ( const QModelIndex & idx ) {
const QModelIndex sibling = idx . siblingAtColumn ( Column : : Instruction ) ;
2023-12-07 17:36:02 +00:00
return BranchSavesLR ( m_table_proxy - > data ( sibling , UserRole : : ClickRole ) . value < u32 > ( ) ) ;
} ) ;
if ( enable_action )
connect ( action , & QAction : : triggered ,
[ this , index_list ] ( ) { OnTableSetBLR ( std : : move ( index_list ) ) ; } ) ;
else
action - > setEnabled ( false ) ;
menu - > addAction ( tr ( " &Copy Address " ) , [ this , index_list = std : : move ( index_list ) ] ( ) {
OnTableCopyAddress ( std : : move ( index_list ) ) ;
} ) ;
break ;
}
case Column : : OriginSymbol :
case Column : : DestinSymbol :
{
QAction * const action = menu - > addAction ( tr ( " Insert &BLR at start " ) ) ;
const bool enable_action =
2024-03-28 18:35:13 +00:00
Core : : GetState ( m_system ) ! = Core : : State : : Uninitialized & &
2024-03-02 15:53:23 +00:00
std : : all_of ( index_list . begin ( ) , index_list . end ( ) , [ this ] ( const QModelIndex & idx ) {
return m_table_proxy - > data ( idx , UserRole : : ClickRole ) . isValid ( ) ;
2023-12-07 17:36:02 +00:00
} ) ;
if ( enable_action )
connect ( action , & QAction : : triggered , [ this , index_list = std : : move ( index_list ) ] ( ) {
OnTableSetBLR ( std : : move ( index_list ) ) ;
} ) ;
else
action - > setEnabled ( false ) ;
break ;
}
}
menu - > exec ( m_table_view - > viewport ( ) - > mapToGlobal ( pos ) ) ;
}
void BranchWatchDialog : : OnTableHeaderContextMenu ( const QPoint & pos )
{
m_mnu_column_visibility - > exec ( m_table_view - > horizontalHeader ( ) - > mapToGlobal ( pos ) ) ;
}
void BranchWatchDialog : : OnTableDelete ( QModelIndexList index_list )
{
m_table_proxy - > OnDelete ( std : : move ( index_list ) ) ;
UpdateStatus ( ) ;
}
void BranchWatchDialog : : OnTableDeleteKeypress ( )
{
OnTableDelete ( m_table_view - > selectionModel ( ) - > selectedRows ( ) ) ;
}
void BranchWatchDialog : : OnTableSetBLR ( QModelIndexList index_list )
{
for ( const QModelIndex & index : index_list )
{
m_system . GetPowerPC ( ) . GetDebugInterface ( ) . SetPatch (
Core : : CPUThreadGuard { m_system } ,
m_table_proxy - > data ( index , UserRole : : ClickRole ) . value < u32 > ( ) , 0x4e800020 ) ;
m_table_proxy - > SetInspected ( index ) ;
}
// TODO: This is not ideal. What I need is a signal for when memory has been changed by the GUI,
// but I cannot find one. UpdateDisasmDialog comes close, but does too much in one signal. For
// example, CodeViewWidget will scroll to the current PC when UpdateDisasmDialog is signaled. This
// seems like a pervasive issue. For example, modifying an instruction in the CodeViewWidget will
// not reflect in the MemoryViewWidget, and vice versa. Neither of these widgets changing memory
// will reflect in the JITWidget, either. At the very least, we can make sure the CodeWidget
// is updated in an acceptable way.
m_code_widget - > Update ( ) ;
}
void BranchWatchDialog : : OnTableSetNOP ( QModelIndexList index_list )
{
for ( const QModelIndex & index : index_list )
{
m_system . GetPowerPC ( ) . GetDebugInterface ( ) . SetPatch (
Core : : CPUThreadGuard { m_system } ,
m_table_proxy - > data ( index , UserRole : : ClickRole ) . value < u32 > ( ) , 0x60000000 ) ;
m_table_proxy - > SetInspected ( index ) ;
}
// Same issue as OnSetBLR.
m_code_widget - > Update ( ) ;
}
void BranchWatchDialog : : OnTableCopyAddress ( QModelIndexList index_list )
{
auto iter = index_list . begin ( ) ;
if ( iter = = index_list . end ( ) )
return ;
QString text ;
text . reserve ( index_list . size ( ) * 9 - 1 ) ;
while ( true )
{
text . append ( QString : : number ( m_table_proxy - > data ( * iter , UserRole : : ClickRole ) . value < u32 > ( ) , 16 ) ) ;
if ( + + iter = = index_list . end ( ) )
break ;
text . append ( QChar : : fromLatin1 ( ' \n ' ) ) ;
}
QApplication : : clipboard ( ) - > setText ( text ) ;
}
2024-03-05 22:02:45 +00:00
void BranchWatchDialog : : SaveSettings ( )
{
auto & settings = Settings : : GetQSettings ( ) ;
settings . setValue ( QStringLiteral ( " branchwatchdialog/geometry " ) , saveGeometry ( ) ) ;
settings . setValue ( QStringLiteral ( " branchwatchdialog/tableheader/state " ) ,
m_table_view - > horizontalHeader ( ) - > saveState ( ) ) ;
}
2023-12-07 17:36:02 +00:00
void BranchWatchDialog : : Update ( )
{
if ( m_branch_watch . GetRecordingPhase ( ) = = Core : : BranchWatch : : Phase : : Blacklist )
UpdateStatus ( ) ;
m_table_model - > UpdateHits ( ) ;
}
void BranchWatchDialog : : UpdateStatus ( )
{
switch ( m_branch_watch . GetRecordingPhase ( ) )
{
case Core : : BranchWatch : : Phase : : Blacklist :
{
const std : : size_t candidate_size = m_branch_watch . GetCollectionSize ( ) ;
const std : : size_t blacklist_size = m_branch_watch . GetBlacklistSize ( ) ;
if ( blacklist_size = = 0 )
{
m_status_bar - > showMessage ( tr ( " Candidates: %1 " ) . arg ( candidate_size ) ) ;
return ;
}
m_status_bar - > showMessage ( tr ( " Candidates: %1 | Excluded: %2 | Remaining: %3 " )
. arg ( candidate_size )
. arg ( blacklist_size )
. arg ( candidate_size - blacklist_size ) ) ;
return ;
}
case Core : : BranchWatch : : Phase : : Reduction :
{
const std : : size_t candidate_size = m_branch_watch . GetSelection ( ) . size ( ) ;
if ( candidate_size = = 0 )
{
m_status_bar - > showMessage ( tr ( " Zero candidates remaining. " ) ) ;
return ;
}
const std : : size_t remaining_size = m_table_proxy - > rowCount ( ) ;
m_status_bar - > showMessage ( tr ( " Candidates: %1 | Filtered: %2 | Remaining: %3 " )
. arg ( candidate_size )
. arg ( candidate_size - remaining_size )
. arg ( remaining_size ) ) ;
return ;
}
}
}
void BranchWatchDialog : : Save ( const Core : : CPUThreadGuard & guard , const std : : string & filepath )
{
File : : IOFile file ( filepath , " w " ) ;
if ( ! file . IsOpen ( ) )
{
ModalMessageBox : : warning (
this , tr ( " Error " ) ,
tr ( " Failed to save Branch Watch snapshot \" %1 \" " ) . arg ( QString : : fromStdString ( filepath ) ) ) ;
return ;
}
m_table_model - > Save ( guard , file . GetHandle ( ) ) ;
}
void BranchWatchDialog : : Load ( const Core : : CPUThreadGuard & guard , const std : : string & filepath )
{
File : : IOFile file ( filepath , " r " ) ;
if ( ! file . IsOpen ( ) )
{
ModalMessageBox : : warning (
this , tr ( " Error " ) ,
tr ( " Failed to open Branch Watch snapshot \" %1 \" " ) . arg ( QString : : fromStdString ( filepath ) ) ) ;
return ;
}
m_table_model - > Load ( guard , file . GetHandle ( ) ) ;
m_btn_wipe_recent_hits - > setEnabled ( m_branch_watch . GetRecordingPhase ( ) = =
Core : : BranchWatch : : Phase : : Reduction ) ;
}
void BranchWatchDialog : : AutoSave ( const Core : : CPUThreadGuard & guard )
{
if ( ! m_act_autosave - > isChecked ( ) | | ! m_branch_watch . CanSave ( ) )
return ;
Save ( guard , m_autosave_filepath ? m_autosave_filepath . value ( ) : GetSnapshotDefaultFilepath ( ) ) ;
}