e8555c89 Andy McCall 1 month ago
1
#include " ImGuiMedia.hh"
2
3
#include " ImGuiCpp.hh"
4
#include " ImGuiManager.hh"
5
#include " ImGuiOpenFile.hh"
6
#include " ImGuiUtils.hh"
7
8
#include " CartridgeSlotManager.hh"
9
#include " CassettePlayer.hh"
10
#include " CassettePlayerCLI.hh"
11
#include " CassettePort.hh"
12
#include " DiskImageCLI.hh"
13
#include " DiskImageUtils.hh"
14
#include " DiskManipulator.hh"
15
#include " FilePool.hh"
16
#include " HD.hh"
17
#include " HardwareConfig.hh"
18
#include " IDECDROM.hh"
19
#include " MSXCliComm.hh"
20
#include " MSXCommandController.hh"
21
#include " MSXRomCLI.hh"
22
#include " Reactor.hh"
23
#include " RealDrive.hh"
24
#include " RomDatabase.hh"
25
#include " RomInfo.hh"
26
27
#include " StringOp.hh"
28
#include " format.hh"
29
#include " join.hh"
30
#include " one_of.hh"
31
#include " unreachable.hh"
32
33
#include < CustomFont.h>
34
#include < imgui.h>
35
#include < imgui_stdlib.h>
36
37
#include < algorithm>
38
#include < memory>
39
#include < ranges>
40
#include < utility>
41
42
namespace openmsx {
43
44
using namespace std:: literals;
45
46
void ImGuiMedia:: save ( ImGuiTextBuffer& buf )
47
{
48
savePersistent ( buf, * this , persistentElements ) ;
49
50
auto saveItem = [ & ] ( const MediaItem& item, zstring_view name) {
51
if ( item. name . empty ( ) ) return ;
52
buf. appendf ( " %s .name=%s \n " , name. c_str ( ) , item. name . c_str ( ) ) ;
53
for ( const auto & patch : item. ipsPatches ) {
54
buf. appendf ( " %s .patch=%s \n " , name. c_str ( ) , patch. c_str ( ) ) ;
55
}
56
if ( item. romType != RomType:: UNKNOWN) {
57
buf. appendf ( " %s .romType=%s \n " , name. c_str ( ) ,
58
std:: string ( RomInfo:: romTypeToName ( item. romType ) ) . c_str ( ) ) ;
59
}
60
} ;
61
auto saveGroup = [ & ] ( const ItemGroup& group, zstring_view name) {
62
saveItem ( group. edit , name ) ;
63
auto recentName = tmpStrCat ( name, " .recent" ) ;
64
for ( const auto & item : group. recent ) {
65
saveItem ( item, recentName ) ;
66
}
67
68
} ;
69
70
std:: string name;
71
name = " diska" ;
72
for ( const auto & info : diskMediaInfo) {
73
saveGroup ( info. groups [ SelectDiskType:: IMAGE] , tmpStrCat ( name, " .image" ) ) ;
74
saveGroup ( info. groups [ SelectDiskType:: DIR_AS_DISK] , tmpStrCat ( name, " .dirAsDsk" ) ) ;
75
76
77
if ( info. show ) buf. appendf ( " %s .show=1\n " , name. c_str ( ) ) ;
78
name. back ( ) + + ;
79
}
80
81
name = " carta" ;
82
for ( const auto & info : cartridgeMediaInfo) {
83
saveGroup ( info. groups [ SelectCartridgeType:: IMAGE] , tmpStrCat ( name, " .rom" ) ) ;
84
saveGroup ( info. groups [ SelectCartridgeType:: EXTENSION] , tmpStrCat ( name, " .extension" ) ) ;
85
86
if ( info. show ) buf. appendf ( " %s .show=1\n " , name. c_str ( ) ) ;
87
name. back ( ) + + ;
88
}
89
90
name = " hda" ;
91
for ( const auto & info : hdMediaInfo) {
92
saveGroup ( info, name ) ;
93
name. back ( ) + + ;
94
}
95
96
name = " cda" ;
97
for ( const auto & info : cdMediaInfo) {
98
saveGroup ( info, name ) ;
99
name. back ( ) + + ;
100
}
101
102
if ( cassetteMediaInfo. show ) buf. append ( " cassette.show=1\n " ) ;
103
saveGroup ( cassetteMediaInfo. group , " cassette" ) ;
104
105
saveGroup ( extensionMediaInfo, " extension" ) ;
106
saveGroup ( laserdiscMediaInfo, " laserdisc" ) ;
107
}
108
109
void ImGuiMedia:: loadLine ( std:: string_view name , zstring_view value )
110
{
111
auto get = [ & ] ( std:: string_view prefix, auto & array) -> std :: remove_reference_t < decltype ( array[ 0 ] ) > * {
112
if ( ( name. size ( ) >= ( prefix. size ( ) + 2 ) ) && name. starts_with ( prefix ) && ( name[ prefix. size ( ) + 1 ] == ' .' ) ) {
113
char c = name[ prefix. size ( ) ] ;
114
if ( ( ' a' <= c) && ( c < char ( ' a' + array. size ( ) ) ) ) {
115
return & array[ c - ' a' ] ;
116
}
117
}
118
return nullptr ;
119
} ;
120
auto loadItem = [ & ] ( MediaItem& item, std:: string_view suffix) {
121
if ( suffix == " name" ) {
122
item. name = value;
123
} else if ( suffix == " patch" ) {
124
item. ipsPatches . emplace_back ( value ) ;
125
} else if ( suffix == " romType" ) {
126
if ( auto type = RomInfo:: nameToRomType ( value ) ; type != RomType:: UNKNOWN) {
127
item. romType = type;
128
}
129
}
130
} ;
131
auto loadGroup = [ & ] ( ItemGroup& group, std:: string_view suffix) {
132
if ( suffix. starts_with ( " recent." ) ) {
133
if ( suffix == " recent.name" && ! group. recent . full ( ) ) {
134
group. recent . push_back ( MediaItem{ } ) ;
135
}
136
if ( ! group. recent . empty ( ) ) {
137
loadItem ( group. recent . back ( ) , suffix. substr ( 7 ) ) ;
138
}
139
} else {
140
loadItem ( group. edit , suffix ) ;
141
}
142
} ;
143
144
if ( loadOnePersistent ( name, value, * this , persistentElements ) ) {
145
146
} else if ( auto * disk = get ( " disk" , diskMediaInfo) ) {
147
using enum SelectDiskType;
148
auto suffix = name. substr ( 6 ) ;
149
if ( suffix. starts_with ( " image." ) ) {
150
loadGroup ( disk-> groups [ IMAGE] , suffix. substr ( 6 ) ) ;
151
} else if ( suffix. starts_with ( " dirAsDsk." ) ) {
152
loadGroup ( disk-> groups [ DIR_AS_DISK] , suffix. substr ( 9 ) ) ;
153
} else if ( suffix == " select" ) {
154
if ( auto i = StringOp:: stringTo < unsigned > ( value ) ) {
155
if ( * i < unsigned ( std:: to_underlying ( NUM ) ) ) {
156
disk-> select = SelectDiskType ( * i) ;
157
}
158
}
159
} else if ( suffix == " show" ) {
160
disk-> show = StringOp:: stringToBool ( value ) ;
161
}
162
} else if ( auto * cart = get ( " cart" , cartridgeMediaInfo) ) {
163
using enum SelectCartridgeType;
164
auto suffix = name. substr ( 6 ) ;
165
if ( suffix. starts_with ( " rom." ) ) {
166
loadGroup ( cart-> groups [ IMAGE] , suffix. substr ( 4 ) ) ;
167
} else if ( suffix. starts_with ( " extension." ) ) {
168
loadGroup ( cart-> groups [ EXTENSION] , suffix. substr ( 10 ) ) ;
169
} else if ( suffix == " select" ) {
170
if ( auto i = StringOp:: stringTo < unsigned > ( value ) ) {
171
if ( * i < unsigned ( std:: to_underlying ( NUM ) ) ) {
172
cart-> select = SelectCartridgeType ( * i) ;
173
}
174
}
175
} else if ( suffix == " show" ) {
176
cart-> show = StringOp:: stringToBool ( value ) ;
177
}
178
} else if ( auto * hd = get ( " hd" , hdMediaInfo) ) {
179
loadGroup ( * hd, name. substr ( 4 ) ) ;
180
} else if ( auto * cd = get ( " cd" , cdMediaInfo) ) {
181
loadGroup ( * cd, name. substr ( 4 ) ) ;
182
} else if ( name. starts_with ( " cassette." ) ) {
183
auto suffix = name. substr ( 9 ) ;
184
if ( suffix == " show" ) {
185
cassetteMediaInfo. show = StringOp:: stringToBool ( value ) ;
186
} else {
187
loadGroup ( cassetteMediaInfo. group , suffix ) ;
188
}
189
} else if ( name. starts_with ( " extension." ) ) {
190
loadGroup ( extensionMediaInfo, name. substr ( 10 ) ) ;
191
} else if ( name. starts_with ( " laserdisc." ) ) {
192
loadGroup ( laserdiscMediaInfo, name. substr ( 10 ) ) ;
193
}
194
}
195
196
void ImGuiMedia:: showMediaWindow ( std:: string_view mediaName )
197
{
198
auto getIndex = [ & ] ( std:: string_view prefix) {
199
if ( ( mediaName. size ( ) > prefix. size ( ) ) && mediaName. starts_with ( prefix ) ) {
200
char c = mediaName[ prefix. size ( ) ] ;
201
return c - ' a' ;
202
}
203
return - 1 ;
204
} ;
205
206
if ( auto diskIndex = getIndex ( " disk" ) ; diskIndex >= 0 ) {
207
diskMediaInfo[ diskIndex] . show = true ;
208
} else if ( auto cartIndex = getIndex ( " cart" ) ; cartIndex >= 0 ) {
209
cartridgeMediaInfo[ cartIndex] . show = true ;
210
211
212
} else if ( mediaName. starts_with ( " cassette" ) ) {
213
cassetteMediaInfo. show = true ;
214
215
}
216
}
217
218
static std:: string buildFilter ( std:: string_view description , std:: span< const std:: string_view> extensions )
219
{
220
auto formatExtensions = [ & ] -> std :: string {
221
if ( extensions. size ( ) <= 3 ) {
222
return join ( std:: views:: transform ( extensions,
223
[ ] ( const auto & ext) { return strCat ( " *." , ext) ; } ) ,
224
' ' ) ;
225
} else {
226
return join ( extensions, ' ,' ) ;
227
}
228
} ;
229
return strCat (
230
description, " (" , formatExtensions ( ) , " ){" ,
231
join ( std:: views:: transform ( extensions,
232
[ ] ( const auto & ext) { return strCat ( ' .' , ext) ; } ) ,
233
' ,' ) ,
234
" ,.gz,.zip}" ) ;
235
}
236
237
std:: string ImGuiMedia:: diskFilter ( )
238
{
239
return buildFilter ( " Disk images" , DiskImageCLI:: getExtensions ( ) ) ;
240
}
241
242
static std:: string romFilter ( )
243
{
244
return buildFilter ( " ROM images" , MSXRomCLI:: getExtensions ( ) ) ;
245
}
246
247
static std:: string cassetteFilter ( )
248
{
249
return buildFilter ( " Tape images" , CassettePlayerCLI:: getExtensions ( ) ) ;
250
}
251
252
static std:: string hdFilter ( )
253
{
254
return buildFilter ( " Hard disk images" , std:: array{ " dsk" sv} ) ;
255
}
256
257
static std:: string cdFilter ( )
258
{
259
return buildFilter ( " CDROM images" , std:: array{ " iso" sv} ) ;
260
}
261
262
template < std:: invocable< const std:: string& > DisplayFunc = std:: identity >
263
static TemporaryString display ( const ImGuiMedia:: MediaItem& item , DisplayFunc displayFunc = { } )
264
{
265
auto n = item. ipsPatches . size ( ) ;
266
return tmpStrCat (
267
displayFunc ( item. name ) ,
268
strCat_if ( item. romType != RomType:: UNKNOWN, " (" sv, RomInfo:: romTypeToName ( item. romType ) , ' )' ) ,
269
strCat_if ( n != 0 , " (+" sv, n, " patch" sv, strCat_if ( n != 1 , " es" sv ) , ' )' ) ) ;
270
}
271
272
static std:: string romTypeToolTipText ( const zstring_view name )
273
{
274
return strCat ( " In the console and commandline known as: " , name) ;
275
}
276
277
std:: vector< ImGuiMedia:: ExtensionInfo> & ImGuiMedia:: getAllExtensions ( )
278
{
279
if ( extensionInfo. empty ( ) ) {
280
extensionInfo = parseAllConfigFiles < ExtensionInfo> ( manager, " extensions" , { " Manufacturer" sv, " Product code" sv, " Name" sv} ) ;
281
}
282
return extensionInfo;
283
}
284
285
void ImGuiMedia:: resetExtensionInfo ( )
286
{
287
extensionInfo. clear ( ) ;
288
}
289
290
const std:: string& ImGuiMedia:: getTestResult ( ExtensionInfo& info )
291
{
292
if ( ! info. testResult ) {
293
info. testResult . emplace ( ) ;
294
if ( info. configName == one_of ( " advram" , " Casio_KB-7" , " Casio_KB-10" ) ) {
295
296
297
return info. testResult . value ( ) ;
298
}
299
300
auto & reactor = manager. getReactor ( ) ;
301
manager. executeDelayed ( [ & reactor, & info] ( ) mutable {
302
303
try {
304
std:: optional< MSXMotherBoard> mb;
305
mb. emplace ( reactor ) ;
306
307
308
mb-> getMSXCliComm ( ) . setSuppressMessages ( true ) ;
309
try {
310
mb-> loadMachine ( " C-BIOS_MSX1" ) ;
311
} catch ( MSXException& e1) {
312
313
314
try {
315
if ( const auto * current = reactor. getMotherBoard ( ) ) {
316
mb. emplace ( reactor ) ;
317
mb-> getMSXCliComm ( ) . setSuppressMessages ( true ) ;
318
mb-> loadMachine ( std:: string ( current-> getMachineName ( ) ) ) ;
319
} else {
320
throw e1;
321
}
322
} catch ( MSXException& ) {
323
324
throw e1;
325
}
326
}
327
auto ext = mb-> loadExtension ( info. configName , " any" ) ;
328
mb-> insertExtension ( info. configName , std:: move ( ext ) ) ;
329
assert ( info. testResult -> empty ( ) ) ;
330
} catch ( MSXException& e) {
331
info. testResult = e. getMessage ( ) ;
332
}
333
} ) ;
334
}
335
return info. testResult . value ( ) ;
336
}
337
338
339
ImGuiMedia:: ExtensionInfo* ImGuiMedia:: findExtensionInfo ( std:: string_view config )
340
{
341
auto & allExtensions = getAllExtensions ( ) ;
342
auto it = std:: ranges:: find ( allExtensions, config, & ExtensionInfo:: configName ) ;
343
return ( it != allExtensions. end ( ) ) ? std:: to_address ( it ) : nullptr ;
344
}
345
346
std:: string ImGuiMedia:: displayNameForExtension ( std:: string_view config )
347
{
348
const auto * info = findExtensionInfo ( config ) ;
349
return info ? info-> displayName
350
: std:: string ( config ) ;
351
}
352
353
std:: string ImGuiMedia:: displayNameForRom ( const std:: string& filename , bool compact )
354
{
355
auto & reactor = manager. getReactor ( ) ;
356
if ( auto sha1 = reactor. getFilePool ( ) . getSha1Sum ( filename ) ) {
357
const auto & database = reactor. getSoftwareDatabase ( ) ;
358
if ( const auto * romInfo = database. fetchRomInfo ( * sha1) ) {
359
if ( auto title = romInfo-> getTitle ( database. getBufferStart ( ) ) ;
360
! title. empty ( ) ) {
361
return std:: string ( title ) ;
362
}
363
}
364
}
365
return compact ? std:: string ( FileOperations:: getFilename ( filename ) )
366
: filename;
367
}
368
369
std:: string ImGuiMedia:: displayNameForHardwareConfig ( const HardwareConfig& config , bool compact )
370
{
371
if ( config. getType ( ) == HardwareConfig:: Type:: EXTENSION) {
372
return displayNameForExtension ( config. getConfigName ( ) ) ;
373
} else {
374
return displayNameForRom ( std:: string ( config. getRomFilename ( ) ) , compact) ;
375
}
376
}
377
378
std:: string ImGuiMedia:: displayNameForSlotContent ( const CartridgeSlotManager& slotManager , unsigned slotNr , bool compact )
379
{
380
if ( const auto * config = slotManager. getConfigForSlot ( slotNr ) ) {
381
return displayNameForHardwareConfig ( * config, compact) ;
382
}
383
return " Empty" ;
384
}
385
386
std:: string ImGuiMedia:: slotAndNameForHardwareConfig ( const CartridgeSlotManager& slotManager , const HardwareConfig& config )
387
{
388
auto slot = slotManager. findSlotWith ( config ) ;
389
return strCat ( strCat_if ( slot,
390
STRCAT_LAZY ( char ( ' A' + * slot) ) , " (" , STRCAT_LAZY ( slotManager. getPsSsString ( * slot) ) , " ): " )
391
. else_ ( " I/O-only: " ) ,
392
displayNameForHardwareConfig ( config ) ) ;
393
}
394
395
std:: string ImGuiMedia:: displayNameForDriveContent ( unsigned drive , bool compact )
396
{
397
auto cmd = makeTclList ( tmpStrCat ( " disk" , char ( ' a' + drive) ) ) ;
398
std:: string_view display;
399
if ( auto result = manager. execute ( cmd ) ) {
400
display = result-> getListIndexUnchecked ( 1 ) . getString ( ) ;
401
}
402
return display. empty ( ) ? " Empty"
403
: std:: string ( compact ? FileOperations:: getFilename ( display )
404
: display ) ;
405
}
406
407
void ImGuiMedia:: printExtensionInfo ( ExtensionInfo& info )
408
{
409
const auto & test = getTestResult ( info ) ;
410
bool ok = test. empty ( ) ;
411
if ( ok) {
412
im:: Table ( " ##extension-info" , 2 , [ & ] {
413
ImGui:: TableSetupColumn ( " description" , ImGuiTableColumnFlags_WidthFixed) ;
414
ImGui:: TableSetupColumn ( " value" , ImGuiTableColumnFlags_WidthStretch) ;
415
416
for ( const auto & [ desc, value] : info. configInfo ) {
417
if ( ImGui:: TableNextColumn ( ) ) {
418
ImGui:: TextUnformatted ( desc ) ;
419
}
420
if ( ImGui:: TableNextColumn ( ) ) {
421
im:: TextWrapPos ( ImGui:: GetFontSize ( ) * 35.0f , [ & ] {
422
ImGui:: TextUnformatted ( value ) ;
423
} ) ;
424
}
425
}
426
} ) ;
427
} else {
428
im:: StyleColor ( ImGuiCol_Text, getColor ( imColor:: ERROR ) , [ & ] {
429
im:: TextWrapPos ( ImGui:: GetFontSize ( ) * 35.0f , [ & ] {
430
ImGui:: TextUnformatted ( test ) ;
431
} ) ;
432
} ) ;
433
}
434
}
435
436
void ImGuiMedia:: extensionTooltip ( ExtensionInfo& info )
437
{
438
im:: ItemTooltip ( [ & ] {
439
printExtensionInfo ( info ) ;
440
} ) ;
441
}
442
443
bool ImGuiMedia:: drawExtensionFilter ( std:: string& filterType , std:: string& filterString , bool & filterOpen , int id )
444
{
445
auto filterDisplay = tmpStrCat (
446
" filter" ,
447
strCat_if ( ! filterType. empty ( ) || ! filterString. empty ( ) , ' :' ) ,
448
strCat_if ( ! filterType. empty ( ) , ' ' , filterType) ,
449
strCat_if ( ! filterString. empty ( ) , ' ' , filterString) ,
450
" ###filter" , id ) ;
451
bool newFilterOpen = filterOpen;
452
im:: TreeNode ( filterDisplay. c_str ( ) , & newFilterOpen, [ & ] {
453
displayFilterCombo ( filterType, " Type" , getAllExtensions ( ) ) ;
454
ImGui:: InputText ( ICON_IGFD_FILTER, & filterString ) ;
455
simpleToolTip ( " A list of substrings that must be part of the extension.\n "
456
" \n "
457
" For example: enter 'ko' to search for 'Konami' extensions. "
458
" Then refine the search by appending '<space>sc' to find the 'Konami SCC' extension." ) ;
459
} ) ;
460
bool changed = filterOpen != newFilterOpen;
461
filterOpen = newFilterOpen;
462
return changed;
463
}
464
465
void ImGuiMedia:: paintCurrent ( const TclObject& current , std:: string_view type )
466
{
467
if ( auto str = current. getString ( ) ; str. empty ( ) ) {
468
ImGui:: StrCat ( " Current: no " , type, " inserted" ) ;
469
} else {
470
ImGui:: StrCat ( " Current: " , current. getString ( ) ) ;
471
}
472
ImGui:: Separator ( ) ;
473
}
474
475
void ImGuiMedia:: paintRecent ( std:: string_view mediaName , ItemGroup& group ,
476
function_ref< std:: string( const std:: string& ) > displayFunc ,
477
const std:: function< void ( const std:: string& ) > & toolTip ,
478
std:: function< void ( ) > * actionToSet )
479
{
480
if ( group. recent . empty ( ) ) return ;
481
im:: Indent ( [ & ] {
482
im:: Menu ( strCat ( " Recent##" , mediaName) . c_str ( ) , [ & ] {
483
int count = 0 ;
484
for ( const auto & item : group. recent ) {
485
auto d = strCat ( display ( item, displayFunc ) , " ##" , count+ + ) ;
486
if ( ImGui:: MenuItem ( d. c_str ( ) ) ) {
487
bool delayed = actionToSet == nullptr ;
488
auto action = [ this , & group, item, mediaName, delayed] {
489
group. edit = item;
490
insertMedia ( mediaName, group. edit , delayed ) ;
491
} ;
492
if ( actionToSet) {
493
* actionToSet = action;
494
} else {
495
action ( ) ;
496
}
497
}
498
if ( toolTip) toolTip ( item. name ) ;
499
}
500
} ) ;
501
} ) ;
502
}
503
504
void ImGuiMedia:: showMenu ( MSXMotherBoard* motherBoard )
505
{
506
im:: Menu ( " Media" , motherBoard != nullptr , [ & ] {
507
auto & interp = manager. getInterpreter ( ) ;
508
509
enum class Status : uint8_t { NONE, ITEM, SEPARATOR } ;
510
using enum Status;
511
Status status = NONE;
512
513
auto endGroup = [ & ] {
514
if ( status == ITEM) status = SEPARATOR;
515
} ;
516
auto elementInGroup = [ & ] {
517
if ( status == SEPARATOR) {
518
ImGui:: Separator ( ) ;
519
}
520
status = ITEM;
521
} ;
522
523
524
elementInGroup ( ) ;
525
const auto & slotManager = motherBoard-> getSlotManager ( ) ;
526
bool anySlot = false ;
527
for ( auto i : xrange ( CartridgeSlotManager:: MAX_SLOTS ) ) {
528
if ( ! slotManager. slotExists ( i ) ) continue ;
529
anySlot = true ;
530
auto [ ps, ss] = slotManager. getPsSs ( i ) ;
531
std:: string extraInfo = ss == - 1 ? " " : strCat ( " (" , slotManager. getPsSsString ( i ) , " )" ) ;
532
auto displayName = strCat ( " Cartridge Slot " , char ( ' A' + i) , extraInfo) ;
533
ImGui:: MenuItem ( displayName. c_str ( ) , nullptr , & cartridgeMediaInfo[ i] . show ) ;
534
simpleToolTip ( [ & ] { return displayNameForSlotContent ( slotManager, i ) ; } ) ;
535
}
536
if ( ! anySlot) {
537
ImGui:: TextDisabledUnformatted ( " No cartridge slots present" ) ;
538
}
539
endGroup ( ) ;
540
541
542
elementInGroup ( ) ;
543
im:: Menu ( " Extensions" , [ & ] {
544
auto mediaName = " ext" sv;
545
auto & group = extensionMediaInfo;
546
im:: Menu ( " Insert" , [ & ] {
547
ImGui:: TextUnformatted ( " Select extension to insert in the first free slot" sv) ;
548
HelpMarker ( " Note that some extensions are I/O only and will not occupy any cartridge slot when inserted. "
549
" These can only be removed via the 'Media > Extensions > Remove' menu. "
550
" To insert (non I/O-only) extensions in a specific slot, use the 'Media > Cartridge Slot' menu." ) ;
551
drawExtensionFilter ( genericFilterType, genericFilterString, genericFilterOpen ) ;
552
553
auto & allExtensions = getAllExtensions ( ) ;
554
555
556
557
auto filteredExtensions = to_vector ( xrange ( allExtensions. size ( ) ) ) ;
558
559
bool anyNonWorking = [ & ] {
560
561
562
563
564
auto testNonWorking = [ ] ( const ExtensionInfo& info) {
565
return info. testResult
566
&& ! info. testResult -> empty ( ) ;
567
} ;
568
if ( hideNonWorking) {
569
570
std:: erase_if ( filteredExtensions, [ & ] ( auto idx) {
571
return testNonWorking ( allExtensions[ idx] ) ;
572
} ) ;
573
return filteredExtensions. size ( ) != allExtensions. size ( ) ;
574
} else {
575
576
return std:: ranges:: any_of ( allExtensions, testNonWorking ) ;
577
}
578
} ( ) ;
579
580
applyComboFilter ( " Type" , genericFilterType, allExtensions, filteredExtensions) ;
581
applyDisplayNameFilter ( genericFilterString, allExtensions, filteredExtensions ) ;
582
583
float width = 40.0f * ImGui:: GetFontSize ( ) ;
584
float height = ( 10.25f + ( anyNonWorking ? 1 : 0 ) ) * ImGui:: GetTextLineHeightWithSpacing ( ) ;
585
im:: ListBox ( " ##list" , { width, height} , [ & ] {
586
im:: ListClipper ( filteredExtensions. size ( ) , [ & ] ( int i) {
587
auto & ext = allExtensions[ filteredExtensions[ i] ] ;
588
bool ok = getTestResult ( ext ) . empty ( ) ;
589
im:: StyleColor ( ! ok, ImGuiCol_Text, getColor ( imColor:: ERROR ) , [ & ] {
590
if ( ImGui:: Selectable ( ext. displayName . c_str ( ) ) ) {
591
group. edit . name = ext. configName ;
592
insertMedia ( mediaName, group. edit ) ;
593
ImGui:: CloseCurrentPopup ( ) ;
594
}
595
extensionTooltip ( ext ) ;
596
} ) ;
597
} ) ;
598
} ) ;
599
if ( anyNonWorking) {
600
ImGui:: Checkbox ( " Hide non-working extensions" , & hideNonWorking) ;
601
HelpMarker ( " When non-working extensions are hidden, you can still use 'Machine > Test MSX hardware' to see more details about (non-)working extensions, instead of hovering the cursor on the red item." ) ;
602
}
603
} ) ;
604
605
paintRecent ( std:: string ( mediaName ) , group,
606
[ this ] ( const std:: string& config) {
607
return displayNameForExtension ( config ) ;
608
} ,
609
[ this ] ( const std:: string& e) {
610
if ( auto * info = findExtensionInfo ( e ) ) {
611
extensionTooltip ( * info) ;
612
}
613
} ) ;
614
615
ImGui:: Separator ( ) ;
616
617
const auto & extensions = motherBoard-> getExtensions ( ) ;
618
im:: Disabled ( extensions. empty ( ) , [ & ] {
619
im:: Menu ( " Remove" , [ & ] {
620
int count = 0 ;
621
for ( const auto & ext : extensions) {
622
auto name = strCat ( slotAndNameForHardwareConfig ( slotManager, * ext ) , " ##" , count+ + ) ;
623
if ( ImGui:: Selectable ( name. c_str ( ) ) ) {
624
manager. executeDelayed ( makeTclList ( " remove_extension" , ext-> getName ( ) ) ) ;
625
}
626
if ( auto * info = findExtensionInfo ( ext-> getConfigName ( ) ) ) {
627
extensionTooltip ( * info) ;
628
}
629
}
630
} ) ;
631
} ) ;
632
} ) ;
633
endGroup ( ) ;
634
635
636
elementInGroup ( ) ;
637
auto drivesInUse = RealDrive:: getDrivesInUse ( * motherBoard) ;
638
bool anyDrive = false ;
639
for ( auto i : xrange ( RealDrive:: MAX_DRIVES ) ) {
640
if ( ! ( * drivesInUse) [ i] ) continue ;
641
anyDrive = true ;
642
auto displayName = strCat ( " Disk Drive " , char ( ' A' + i) ) ;
643
ImGui:: MenuItem ( displayName. c_str ( ) , nullptr , & diskMediaInfo[ i] . show ) ;
644
simpleToolTip ( [ & ] { return displayNameForDriveContent ( i ) ; } ) ;
645
}
646
if ( ! anyDrive) {
647
ImGui:: TextDisabledUnformatted ( " No disk drives present" ) ;
648
}
649
endGroup ( ) ;
650
651
652
elementInGroup ( ) ;
653
if ( auto * player = motherBoard-> getCassettePort ( ) . getCassettePlayer ( ) ) {
654
ImGui:: MenuItem ( " Tape Deck" , nullptr , & cassetteMediaInfo. show ) ;
655
simpleToolTip ( [ & ] -> std :: string {
656
auto current = player-> getImageName ( ) . getResolved ( ) ;
657
return current. empty ( ) ? " Empty" : current;
658
} ) ;
659
} else {
660
ImGui:: TextDisabledUnformatted ( " No cassette port present" ) ;
661
}
662
endGroup ( ) ;
663
664
665
auto hdInUse = HD:: getDrivesInUse ( * motherBoard) ;
666
667
std:: string hdName = " hdX" ;
668
for ( auto i : xrange ( HD:: MAX_HD ) ) {
669
if ( ! ( * hdInUse) [ i] ) continue ;
670
hdName. back ( ) = char ( ' a' + i) ;
671
auto displayName = tmpStrCat ( " Hard Disk " , char ( ' A' + i) ) ;
672
if ( auto cmdResult = manager. execute ( TclObject ( hdName ) ) ) {
673
elementInGroup ( ) ;
674
im:: Menu ( displayName. c_str ( ) , [ & ] {
675
paintHardDiskMenuContent ( hdName, displayName, cmdResult-> getListIndex ( interp, 1 ) , * motherBoard ) ;
676
} ) ;
677
}
678
}
679
endGroup ( ) ;
680
681
682
auto cdInUse = IDECDROM:: getDrivesInUse ( * motherBoard) ;
683
for ( auto i : xrange ( IDECDROM:: MAX_CD ) ) {
684
if ( ! ( * cdInUse) [ i] ) continue ;
685
std:: string cdName = " cdX" ;
686
cdName. back ( ) = char ( ' a' + i) ;
687
auto displayName = strCat ( " CDROM Drive " , char ( ' A' + i) ) ;
688
if ( auto cmdResult = manager. execute ( TclObject ( cdName ) ) ) {
689
elementInGroup ( ) ;
690
im:: Menu ( displayName. c_str ( ) , [ & ] {
691
paintCDROMMenuContent ( cdName, displayName, cmdResult-> getListIndex ( interp, 1 ) ) ;
692
} ) ;
693
}
694
}
695
endGroup ( ) ;
696
697
698
static constexpr zstring_view mediaSlotName = " laserdiscplayer" ;
699
if ( auto cmdResult = manager. execute ( TclObject ( mediaSlotName ) ) ) {
700
elementInGroup ( ) ;
701
static constexpr zstring_view displayName = " LaserDisc Player" ;
702
im:: Menu ( displayName. c_str ( ) , [ & ] {
703
paintLaserDiscMenuContent ( mediaSlotName, displayName, cmdResult-> getListIndex ( interp, 1 ) ) ;
704
} ) ;
705
}
706
endGroup ( ) ;
707
} ) ;
708
709
if ( switchHdAction) {
710
if ( motherBoard) {
711
if ( motherBoard-> isPowered ( ) ) {
712
static constexpr auto confirmSwitchHdTitle = " Confirm power cycle" ;
713
ImGui:: OpenPopup ( confirmSwitchHdTitle ) ;
714
im:: PopupModal ( confirmSwitchHdTitle, nullptr , ImGuiWindowFlags_AlwaysAutoResize, [ & ] {
715
ImGui:: TextUnformatted (
716
" Switching hard disk image requires a power cycle of the MSX.\n "
717
" Are you sure you want to proceed?\n "
718
" \n " ) ;
719
720
bool close = false ;
721
if ( ImGui:: Button ( " Ok" ) ) {
722
motherBoard-> powerDown ( ) ;
723
switchHdAction ( ) ;
724
motherBoard-> powerUp ( ) ;
725
close = true ;
726
}
727
ImGui:: SameLine ( ) ;
728
if ( ImGui:: Button ( " Cancel" ) ) {
729
close = true ;
730
}
731
if ( close) {
732
switchHdAction = { } ;
733
ImGui:: CloseCurrentPopup ( ) ;
734
}
735
} ) ;
736
} else {
737
switchHdAction ( ) ;
738
switchHdAction = { } ;
739
}
740
} else {
741
switchHdAction = { } ;
742
}
743
}
744
}
745
746
void ImGuiMedia:: paintHardDiskMenuContent ( std:: string_view mediaSlotName , std:: string_view slotDisplayName , const TclObject& currentImage , const MSXMotherBoard& motherBoard )
747
{
748
paintCurrent ( currentImage, " hard disk" ) ;
749
auto & group = hdMediaInfo[ mediaSlotName. back ( ) - ' a' ] ;
750
if ( ImGui:: MenuItem ( " Select hard disk image..." ) ) {
751
manager. openFile -> selectFile (
752
strCat ( " Select image for " , slotDisplayName) ,
753
hdFilter ( ) ,
754
[ this , & group, mediaSlotName] ( const auto & fn) {
755
switchHdAction = [ this , & group, mediaSlotName, fn] {
756
group. edit . name = fn;
757
bool delayed = false ;
758
this -> insertMedia ( mediaSlotName, group. edit , delayed ) ;
759
} ;
760
} ,
761
currentImage. getString ( ) ) ;
762
}
763
if ( motherBoard. isPowered ( ) ) {
764
HelpMarker ( " Hard disk image cannot be switched while the MSX is powered on, "
765
" so a power cycle is required." ) ;
766
}
767
paintRecent ( std:: string ( mediaSlotName ) , group, std:: identity{ } , { } , & switchHdAction) ;
768
}
769
770
void ImGuiMedia:: paintCDROMMenuContent ( std:: string_view mediaSlotName , std:: string_view slotDisplayName , const TclObject& currentImage )
771
{
772
paintCurrent ( currentImage, " CDROM" ) ;
773
if ( ImGui:: MenuItem ( " Eject" , nullptr , false , ! currentImage. empty ( ) ) ) {
774
manager. executeDelayed ( makeTclList ( mediaSlotName, " eject" ) ) ;
775
}
776
auto & group = cdMediaInfo[ mediaSlotName. back ( ) - ' a' ] ;
777
if ( ImGui:: MenuItem ( " Insert CDROM image..." ) ) {
778
manager. openFile -> selectFile (
779
strCat ( " Select CDROM image for " , slotDisplayName) ,
780
cdFilter ( ) ,
781
[ this , & group, mediaSlotName] ( const auto & fn) {
782
group. edit . name = fn;
783
this -> insertMedia ( mediaSlotName, group. edit ) ;
784
} ,
785
currentImage. getString ( ) ) ;
786
}
787
paintRecent ( std:: string ( mediaSlotName ) , group) ;
788
}
789
790
void ImGuiMedia:: paintLaserDiscMenuContent ( std:: string_view mediaSlotName , std:: string_view slotDisplayName , const TclObject& currentImage )
791
{
792
paintCurrent ( currentImage, " laserdisc" ) ;
793
if ( ImGui:: MenuItem ( " Eject" , nullptr , false , ! currentImage. empty ( ) ) ) {
794
manager. executeDelayed ( makeTclList ( mediaSlotName, " eject" ) ) ;
795
}
796
if ( ImGui:: MenuItem ( " Insert LaserDisc image..." ) ) {
797
manager. openFile -> selectFile (
798
strCat ( " Select LaserDisc image for " , slotDisplayName) ,
799
buildFilter ( " LaserDisc images" , std:: array< std:: string_view, 1 > { " ogv" } ) ,
800
[ this , mediaSlotName] ( const auto & fn) {
801
laserdiscMediaInfo. edit . name = fn;
802
this -> insertMedia ( mediaSlotName, laserdiscMediaInfo. edit ) ;
803
} ,
804
currentImage. getString ( ) ) ;
805
}
806
paintRecent ( std:: string ( mediaSlotName ) , laserdiscMediaInfo) ;
807
}
808
809
void ImGuiMedia:: paint ( MSXMotherBoard* motherBoard )
810
{
811
if ( ! motherBoard) return ;
812
813
auto drivesInUse = RealDrive:: getDrivesInUse ( * motherBoard) ;
814
for ( auto i : xrange ( RealDrive:: MAX_DRIVES ) ) {
815
if ( ! ( * drivesInUse) [ i] ) continue ;
816
if ( diskMediaInfo[ i] . show ) {
817
diskMenu ( i ) ;
818
}
819
}
820
821
const auto & slotManager = motherBoard-> getSlotManager ( ) ;
822
for ( auto i : xrange ( CartridgeSlotManager:: MAX_SLOTS ) ) {
823
if ( ! slotManager. slotExists ( i ) ) continue ;
824
if ( cartridgeMediaInfo[ i] . show ) {
825
cartridgeMenu ( i ) ;
826
}
827
}
828
829
if ( cassetteMediaInfo. show ) {
830
if ( auto * player = motherBoard-> getCassettePort ( ) . getCassettePlayer ( ) ) {
831
cassetteMenu ( * player) ;
832
}
833
}
834
}
835
836
static TclObject getPatches ( const TclObject& cmdResult )
837
{
838
return cmdResult. getOptionalDictValue ( TclObject ( " patches" ) ) . value_or ( TclObject{ } ) ;
839
}
840
841
static void printPatches ( const TclObject& patches )
842
{
843
if ( ! patches. empty ( ) ) {
844
ImGui:: TextUnformatted ( " IPS patches:" sv) ;
845
im:: Indent ( [ & ] {
846
for ( const auto & patch : patches) {
847
ImGui:: TextUnformatted ( patch ) ;
848
}
849
} ) ;
850
}
851
}
852
853
bool ImGuiMedia:: selectRecent ( ItemGroup& group , function_ref< std:: string( const std:: string& ) > displayFunc , float width ) const
854
{
855
bool interacted = false ;
856
ImGui:: SetNextItemWidth ( - width) ;
857
const auto & style = ImGui:: GetStyle ( ) ;
858
auto textWidth = ImGui:: GetContentRegionAvail ( ) . x - ( 3.0f * style. FramePadding . x + ImGui:: GetFrameHeight ( ) + width) ;
859
auto preview = ImGui:: leftClip ( displayFunc ( group. edit . name ) , textWidth) ;
860
im:: Combo ( " ##recent" , preview. c_str ( ) , [ & ] {
861
int count = 0 ;
862
for ( const auto & item : group. recent ) {
863
auto d = strCat ( display ( item, displayFunc ) , " ##" , count+ + ) ;
864
if ( ImGui:: Selectable ( d. c_str ( ) ) ) {
865
group. edit = item;
866
interacted = true ;
867
}
868
}
869
} ) ;
870
interacted |= ImGui:: IsItemActive ( ) ;
871
return interacted;
872
}
873
874
static float calcButtonWidth ( std:: string_view text1 , const char * text2 )
875
{
876
const auto & style = ImGui:: GetStyle ( ) ;
877
float width = style. ItemSpacing . x + 2.0f * style. FramePadding . x + ImGui:: CalcTextSize ( text1 ) . x ;
878
if ( text2) {
879
width += style. ItemSpacing . x + 2.0f * style. FramePadding . x + ImGui:: CalcTextSize ( text2 ) . x ;
880
}
881
return width;
882
}
883
884
bool ImGuiMedia:: selectImage ( ItemGroup& group , const std:: string& title ,
885
function_ref< std:: string( ) > createFilter , zstring_view current ,
886
function_ref< std:: string( const std:: string& ) > displayFunc ,
887
const std:: function< void ( ) > & createNewCallback )
888
{
889
bool interacted = false ;
890
im:: ID ( " file" , [ & ] {
891
auto width = calcButtonWidth ( ICON_IGFD_FOLDER_OPEN, createNewCallback ? ICON_IGFD_ADD : nullptr ) ;
892
interacted |= selectRecent ( group, displayFunc, width ) ;
893
if ( createNewCallback) {
894
ImGui:: SameLine ( ) ;
895
if ( ImGui:: Button ( ICON_IGFD_ADD ) ) {
896
interacted = true ;
897
createNewCallback ( ) ;
898
}
899
simpleToolTip ( " Create new file" ) ;
900
}
901
ImGui:: SameLine ( ) ;
902
if ( ImGui:: Button ( ICON_IGFD_FOLDER_OPEN ) ) {
903
interacted = true ;
904
manager. openFile -> selectFile (
905
title,
906
createFilter ( ) ,
907
[ & ] ( const auto & fn) {
908
group. edit . name = fn;
909
group. edit . romType = RomType:: UNKNOWN;
910
} ,
911
current ) ;
912
}
913
simpleToolTip ( " Browse file" ) ;
914
} ) ;
915
return interacted;
916
}
917
918
bool ImGuiMedia:: selectDirectory ( ItemGroup& group , const std:: string& title , zstring_view current ,
919
const std:: function< void ( ) > & createNewCallback )
920
{
921
bool interacted = false ;
922
im:: ID ( " directory" , [ & ] {
923
auto width = calcButtonWidth ( ICON_IGFD_FOLDER_OPEN, createNewCallback ? ICON_IGFD_ADD : nullptr ) ;
924
interacted |= selectRecent ( group, std:: identity{ } , width ) ;
925
if ( createNewCallback) {
926
ImGui:: SameLine ( ) ;
927
if ( ImGui:: Button ( ICON_IGFD_ADD ) ) {
928
interacted = true ;
929
createNewCallback ( ) ;
930
}
931
simpleToolTip ( " Create new directory" ) ;
932
}
933
ImGui:: SameLine ( ) ;
934
if ( ImGui:: Button ( ICON_IGFD_FOLDER_OPEN ) ) {
935
interacted = true ;
936
manager. openFile -> selectDirectory (
937
title,
938
[ & ] ( const auto & fn) { group. edit . name = fn; } ,
939
current ) ;
940
}
941
simpleToolTip ( " Browse directory" ) ;
942
} ) ;
943
return interacted;
944
}
945
946
bool ImGuiMedia:: selectMapperType ( const char * label , RomType& romType )
947
{
948
bool interacted = false ;
949
bool isAutoDetect = romType == RomType:: UNKNOWN;
950
constexpr const char * autoStr = " auto detect" ;
951
std:: string current = isAutoDetect ? autoStr : std:: string ( RomInfo:: getDescription ( romType ) ) ;
952
im:: Combo ( label, current. c_str ( ) , [ & ] {
953
if ( ImGui:: Selectable ( autoStr, isAutoDetect ) ) {
954
interacted = true ;
955
romType = RomType:: UNKNOWN;
956
}
957
const auto & romTypeInfo = RomInfo:: getRomTypeInfo ( ) ;
958
for ( auto rt : RomInfo:: getSortedRomTypes ( ) ) {
959
const auto & romInfo = romTypeInfo[ rt] ;
960
bool selected = romType == rt;
961
if ( ImGui:: Selectable ( std:: string ( romInfo. description ) . c_str ( ) , selected) ) {
962
interacted = true ;
963
romType = rt;
964
}
965
simpleToolTip ( romTypeToolTipText ( romInfo. name ) ) ;
966
}
967
} ) ;
968
interacted |= ImGui:: IsItemActive ( ) ;
969
return interacted;
970
}
971
972
bool ImGuiMedia:: selectPatches ( MediaItem& item , int & patchIndex )
973
{
974
bool interacted = false ;
975
auto patchesTitle = tmpStrCat (
976
" IPS patches" ,
977
strCat_if ( ! item. ipsPatches . empty ( ) , " (" , item. ipsPatches . size ( ) , ' )' ) ,
978
" ###patches" ) ;
979
im:: TreeNode ( patchesTitle. c_str ( ) , [ & ] {
980
const auto & style = ImGui:: GetStyle ( ) ;
981
auto width = style. ItemSpacing . x + 2.0f * style. FramePadding . x + ImGui:: CalcTextSize ( " Remove" sv) . x ;
982
ImGui:: SetNextItemWidth ( - width) ;
983
im:: Group ( [ & ] {
984
im:: ListBox ( " ##" , [ & ] {
985
int count = 0 ;
986
for ( const auto & patch : item. ipsPatches ) {
987
auto preview = ImGui:: leftClip ( patch, ImGui:: GetContentRegionAvail ( ) . x ) ;
988
if ( ImGui:: Selectable ( strCat ( preview, " ##" , count ) . c_str ( ) , count == patchIndex) ) {
989
interacted = true ;
990
patchIndex = count;
991
}
992
+ + count;
993
}
994
} ) ;
995
} ) ;
996
ImGui:: SameLine ( ) ;
997
im:: Group ( [ & ] {
998
if ( ImGui:: Button ( " Add" ) ) {
999
interacted = true ;
1000
manager. openFile -> selectFile (
1001
" Select disk IPS patch" ,
1002
buildFilter ( " IPS patches" , std:: array< std:: string_view, 1 > { " ips" } ) ,
1003
[ & ] ( const std:: string& ips) {
1004
patchIndex = narrow < int > ( item. ipsPatches . size ( ) ) ;
1005
item. ipsPatches . push_back ( ips ) ;
1006
} ) ;
1007
}
1008
auto size = narrow < int > ( item. ipsPatches . size ( ) ) ;
1009
im:: Disabled ( patchIndex < 0 || patchIndex >= size, [ & ] {
1010
if ( ImGui:: Button ( " Remove" ) ) {
1011
interacted = true ;
1012
item. ipsPatches . erase ( item. ipsPatches . begin ( ) + patchIndex ) ;
1013
}
1014
im:: Disabled ( patchIndex == 0 , [ & ] {
1015
if ( ImGui:: ArrowButton ( " up" , ImGuiDir_Up) ) {
1016
std:: swap ( item. ipsPatches [ patchIndex] , item. ipsPatches [ patchIndex - 1 ] ) ;
1017
- - patchIndex;
1018
}
1019
} ) ;
1020
im:: Disabled ( patchIndex == ( size - 1 ) , [ & ] {
1021
if ( ImGui:: ArrowButton ( " down" , ImGuiDir_Down) ) {
1022
std:: swap ( item. ipsPatches [ patchIndex] , item. ipsPatches [ patchIndex + 1 ] ) ;
1023
+ + patchIndex;
1024
}
1025
} ) ;
1026
} ) ;
1027
} ) ;
1028
} ) ;
1029
return interacted;
1030
}
1031
1032
bool ImGuiMedia:: insertMediaButton ( std:: string_view mediaName , const ItemGroup& group , bool * showWindow )
1033
{
1034
bool clicked = false ;
1035
im:: Disabled ( group. edit . name . empty ( ) && ! group. edit . isEject ( ) , [ & ] {
1036
const auto & style = ImGui:: GetStyle ( ) ;
1037
auto width = 4.0f * style. FramePadding . x + style. ItemSpacing . x +
1038
ImGui:: CalcTextSize ( " Apply" sv) . x + ImGui:: CalcTextSize ( " Ok" sv) . x ;
1039
ImGui:: SetCursorPosX ( ImGui:: GetContentRegionAvail ( ) . x - width + style. WindowPadding . x ) ;
1040
clicked |= ImGui:: Button ( " Apply" ) ;
1041
ImGui:: SameLine ( ) ;
1042
if ( ImGui:: Button ( " Ok" ) ) {
1043
* showWindow = false ;
1044
clicked = true ;
1045
}
1046
if ( clicked) {
1047
insertMedia ( mediaName, group. edit ) ;
1048
}
1049
} ) ;
1050
return clicked;
1051
}
1052
1053
TclObject ImGuiMedia:: showDiskInfo ( std:: string_view mediaName , DiskMediaInfo& info )
1054
{
1055
TclObject currentTarget;
1056
auto cmdResult = manager. execute ( makeTclList ( " machine_info" , " media" , mediaName) ) ;
1057
if ( ! cmdResult) return currentTarget;
1058
1059
using enum SelectDiskType;
1060
auto selectType = [ & ] {
1061
auto type = cmdResult-> getOptionalDictValue ( TclObject ( " type" ) ) ;
1062
assert ( type ) ;
1063
auto s = type-> getString ( ) ;
1064
if ( s == " empty" ) {
1065
return EMPTY;
1066
} else if ( s == " ramdsk" ) {
1067
return RAMDISK;
1068
} else if ( s == " dirasdisk" ) {
1069
return DIR_AS_DISK;
1070
} else {
1071
assert ( s == " file" ) ;
1072
return IMAGE;
1073
}
1074
} ( ) ;
1075
std:: string_view typeStr = [ & ] {
1076
switch ( selectType) {
1077
case IMAGE: return " Disk image:" ;
1078
case DIR_AS_DISK: return " Dir as disk:" ;
1079
case RAMDISK: return " RAM disk" ;
1080
case EMPTY: return " No disk inserted" ;
1081
default : UNREACHABLE;
1082
}
1083
} ( ) ;
1084
bool detailedInfo = selectType == one_of ( DIR_AS_DISK, IMAGE ) ;
1085
auto currentPatches = getPatches ( * cmdResult) ;
1086
1087
bool copyCurrent = ImGui:: SmallButton ( " Current disk" ) ;
1088
HelpMarker ( " Press to copy current disk to 'Select new disk' section." ) ;
1089
1090
im:: Indent ( [ & ] {
1091
ImGui:: TextUnformatted ( typeStr ) ;
1092
if ( detailedInfo) {
1093
if ( auto target = cmdResult-> getOptionalDictValue ( TclObject ( " target" ) ) ) {
1094
currentTarget = * target;
1095
ImGui:: SameLine ( ) ;
1096
ImGui:: TextUnformatted ( ImGui:: leftClip ( currentTarget. getString ( ) ,
1097
ImGui:: GetContentRegionAvail ( ) . x ) ) ;
1098
}
1099
std:: string statusLine;
1100
auto add = [ & ] ( std:: string_view s) {
1101
if ( statusLine. empty ( ) ) {
1102
statusLine = s;
1103
} else {
1104
strAppend ( statusLine, " , " , s ) ;
1105
}
1106
} ;
1107
if ( auto ro = cmdResult-> getOptionalDictValue ( TclObject ( " readonly" ) ) ) {
1108
if ( ro-> getOptionalBool ( ) . value_or ( false ) ) {
1109
add ( " read-only" ) ;
1110
}
1111
}
1112
if ( auto doubleSided = cmdResult-> getOptionalDictValue ( TclObject ( " doublesided" ) ) ) {
1113
add ( doubleSided-> getOptionalBool ( ) . value_or ( true ) ? " double-sided" sv : " single-sided" sv ) ;
1114
}
1115
if ( auto size = cmdResult-> getOptionalDictValue ( TclObject ( " size" ) ) ) {
1116
add ( tmpStrCat ( size-> getOptionalInt ( ) . value_or ( 0 ) / 1024 , " kB" ) ) ;
1117
}
1118
if ( ! statusLine. empty ( ) ) {
1119
ImGui:: TextUnformatted ( statusLine ) ;
1120
}
1121
printPatches ( currentPatches ) ;
1122
}
1123
} ) ;
1124
if ( copyCurrent) {
1125
info. select = selectType;
1126
auto & edit = info. groups [ selectType] . edit ;
1127
edit. name = currentTarget. getString ( ) ;
1128
edit. ipsPatches = to_vector < std:: string> ( currentPatches ) ;
1129
}
1130
return currentTarget;
1131
}
1132
1133
void ImGuiMedia:: printDatabase ( const RomInfo& romInfo , const char * buf )
1134
{
1135
auto printRow = [ ] ( std:: string_view description, std:: string_view value) {
1136
if ( value. empty ( ) ) return ;
1137
if ( ImGui:: TableNextColumn ( ) ) {
1138
ImGui:: TextUnformatted ( description ) ;
1139
}
1140
if ( ImGui:: TableNextColumn ( ) ) {
1141
ImGui:: TextUnformatted ( value ) ;
1142
}
1143
} ;
1144
1145
printRow ( " Title" , romInfo. getTitle ( buf ) ) ;
1146
printRow ( " Year" , romInfo. getYear ( buf ) ) ;
1147
printRow ( " Company" , romInfo. getCompany ( buf ) ) ;
1148
printRow ( " Country" , romInfo. getCountry ( buf ) ) ;
1149
auto status = [ & ] {
1150
auto str = romInfo. getOrigType ( buf ) ;
1151
if ( romInfo. getOriginal ( ) ) {
1152
return tmpStrCat ( " Unmodified dump" , strCat_if ( ! str. empty ( ) , " (confirmed by " , str, ' )' ) ) ;
1153
} else {
1154
return tmpStrCat ( str ) ;
1155
}
1156
} ( ) ;
1157
printRow ( " Status" , status) ;
1158
printRow ( " Remark" , romInfo. getRemark ( buf ) ) ;
1159
if ( romInfo. getGenMSXid ( ) > 0 ) {
1160
if ( ImGui:: TableNextColumn ( ) ) {
1161
ImGui:: TextUnformatted ( " More info" ) ;
1162
}
1163
if ( ImGui:: TableNextColumn ( ) ) {
1164
ImGui:: TextLinkOpenURL ( " Generation MSX" , strCat ( " https://www.generation-msx.nl/software/" , romInfo. getGenMSXid ( ) ) . c_str ( ) ) ;
1165
}
1166
}
1167
}
1168
1169
void ImGuiMedia:: printRomInfo ( ImGuiManager& manager , const TclObject& mediaTopic , std:: string_view filename , RomType romType )
1170
{
1171
im:: Table ( " ##extension-info" , 2 , [ & ] {
1172
ImGui:: TableSetupColumn ( " description" , ImGuiTableColumnFlags_WidthFixed) ;
1173
ImGui:: TableSetupColumn ( " value" , ImGuiTableColumnFlags_WidthStretch) ;
1174
1175
if ( ImGui:: TableNextColumn ( ) ) {
1176
ImGui:: TextUnformatted ( " Filename" sv) ;
1177
}
1178
if ( ImGui:: TableNextColumn ( ) ) {
1179
ImGui:: TextUnformatted ( ImGui:: leftClip ( filename, ImGui:: GetContentRegionAvail ( ) . x ) ) ;
1180
}
1181
1182
const auto & database = manager. getReactor ( ) . getSoftwareDatabase ( ) ;
1183
const auto * romInfo = [ & ] -> const RomInfo* {
1184
if ( auto actual = mediaTopic. getOptionalDictValue ( TclObject ( " actualSHA1" ) ) ) {
1185
if ( const auto * info = database. fetchRomInfo ( Sha1Sum ( actual-> getString ( ) ) ) ) {
1186
return info;
1187
}
1188
}
1189
if ( auto original = mediaTopic. getOptionalDictValue ( TclObject ( " originalSHA1" ) ) ) {
1190
if ( const auto * info = database. fetchRomInfo ( Sha1Sum ( original-> getString ( ) ) ) ) {
1191
return info;
1192
}
1193
}
1194
return nullptr ;
1195
} ( ) ;
1196
if ( romInfo) {
1197
ImGuiMedia:: printDatabase ( * romInfo, database. getBufferStart ( ) ) ;
1198
}
1199
1200
std:: string mapperStr{ RomInfo:: getDescription ( romType ) } ;
1201
std:: string mapperTypeStr = romTypeToolTipText ( RomInfo:: romTypeToName ( romType ) ) ;
1202
if ( romInfo) {
1203
if ( auto dbType = romInfo-> getRomType ( ) ;
1204
dbType != RomType:: UNKNOWN && dbType != romType) {
1205
strAppend ( mapperStr, " (database: " , RomInfo:: getDescription ( dbType ) , ' )' ) ;
1206
strAppend ( mapperTypeStr, " (and the database mapper type as: " , RomInfo:: romTypeToName ( dbType ) , ' )' ) ;
1207
}
1208
}
1209
if ( ImGui:: TableNextColumn ( ) ) {
1210
ImGui:: TextUnformatted ( " Mapper" sv) ;
1211
}
1212
if ( ImGui:: TableNextColumn ( ) ) {
1213
ImGui:: TextUnformatted ( mapperStr ) ;
1214
simpleToolTip ( mapperTypeStr ) ;
1215
}
1216
} ) ;
1217
}
1218
1219
TclObject ImGuiMedia:: showCartridgeInfo ( std:: string_view mediaName , CartridgeMediaInfo& info , int slot )
1220
{
1221
TclObject currentTarget;
1222
auto cmdResult = manager. execute ( makeTclList ( " machine_info" , " media" , mediaName) ) ;
1223
if ( ! cmdResult) return currentTarget;
1224
1225
using enum SelectCartridgeType;
1226
auto selectType = [ & ] {
1227
if ( auto type = cmdResult-> getOptionalDictValue ( TclObject ( " type" ) ) ) {
1228
auto s = type-> getString ( ) ;
1229
if ( s == " extension" ) {
1230
return EXTENSION;
1231
} else {
1232
assert ( s == " rom" ) ;
1233
return IMAGE;
1234
}
1235
} else {
1236
return EMPTY;
1237
}
1238
} ( ) ;
1239
auto currentPatches = getPatches ( * cmdResult) ;
1240
1241
bool copyCurrent = ImGui:: SmallButton ( " Current cartridge" ) ;
1242
HelpMarker ( " Press to copy current cartridge to 'Select new cartridge' section." ) ;
1243
const auto & slotManager = manager. getReactor ( ) . getMotherBoard ( ) -> getSlotManager ( ) ;
1244
ImGui:: SameLine ( ) ;
1245
ImGui:: TextUnformatted ( tmpStrCat ( " (slot " , slotManager. getPsSsString ( slot ) , ' )' ) ) ;
1246
1247
RomType currentRomType = RomType:: UNKNOWN;
1248
im:: Indent ( [ & ] {
1249
if ( selectType == EMPTY) {
1250
ImGui:: TextUnformatted ( " No cartridge inserted" sv) ;
1251
} else if ( auto target = cmdResult-> getOptionalDictValue ( TclObject ( " target" ) ) ) {
1252
currentTarget = * target;
1253
if ( selectType == EXTENSION) {
1254
if ( auto * i = findExtensionInfo ( target-> getString ( ) ) ) {
1255
printExtensionInfo ( * i) ;
1256
}
1257
} else if ( selectType == IMAGE) {
1258
if ( auto mapper = cmdResult-> getOptionalDictValue ( TclObject ( " mappertype" ) ) ) {
1259
currentRomType = RomInfo:: nameToRomType ( mapper-> getString ( ) ) ;
1260
}
1261
printRomInfo ( manager, * cmdResult, target-> getString ( ) , currentRomType ) ;
1262
printPatches ( currentPatches ) ;
1263
}
1264
}
1265
} ) ;
1266
if ( copyCurrent) {
1267
info. select = selectType;
1268
auto & edit = info. groups [ selectType] . edit ;
1269
edit. name = currentTarget. getString ( ) ;
1270
edit. ipsPatches = to_vector < std:: string> ( currentPatches ) ;
1271
edit. romType = currentRomType;
1272
}
1273
return currentTarget;
1274
}
1275
1276
void ImGuiMedia:: diskMenu ( int i )
1277
{
1278
auto & info = diskMediaInfo[ i] ;
1279
auto mediaName = strCat ( " disk" , char ( ' a' + i) ) ;
1280
auto displayName = strCat ( " Disk Drive " , char ( ' A' + i) ) ;
1281
ImGui:: SetNextWindowSize ( gl:: vec2{ 29 , 22 } * ImGui:: GetFontSize ( ) , ImGuiCond_FirstUseEver ) ;
1282
im:: Window ( displayName. c_str ( ) , & info. show , [ & ] {
1283
auto current = showDiskInfo ( mediaName, info ) ;
1284
im:: Child ( " select" , { 0 , - ImGui:: GetFrameHeightWithSpacing ( ) } , [ & ] {
1285
using enum SelectDiskType;
1286
ImGui:: SeparatorText ( " Select new disk:" ) ;
1287
im:: Indent ( [ & ] {
1288
ImGui:: RadioButton ( " disk image" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( IMAGE ) ) ;
1289
im:: VisuallyDisabled ( info. select != IMAGE, [ & ] {
1290
im:: Indent ( [ & ] {
1291
auto & group = info. groups [ IMAGE] ;
1292
auto createNew = [ & ] {
1293
manager. openFile -> selectNewFile (
1294
" Select name for new blank disk image" ,
1295
" Disk images (*.dsk){.dsk}" ,
1296
[ & ] ( const auto & fn) {
1297
group. edit . name = fn;
1298
auto & diskManipulator = manager. getReactor ( ) . getDiskManipulator ( ) ;
1299
try {
1300
diskManipulator. create ( fn, MSXBootSectorType:: DOS2, { 1440 } ) ;
1301
} catch ( MSXException& e) {
1302
manager. printError ( " Couldn't create new disk image: " , e. getMessage ( ) ) ;
1303
}
1304
} ,
1305
current. getString ( ) ) ;
1306
} ;
1307
bool interacted = selectImage (
1308
group, strCat ( " Select disk image for " , displayName) , & diskFilter,
1309
current. getString ( ) , std:: identity{ } , createNew ) ;
1310
interacted |= selectPatches ( group. edit , group. patchIndex ) ;
1311
if ( interacted) info. select = IMAGE;
1312
} ) ;
1313
} ) ;
1314
ImGui:: RadioButton ( " dir as disk" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( DIR_AS_DISK ) ) ;
1315
im:: VisuallyDisabled ( info. select != DIR_AS_DISK, [ & ] {
1316
im:: Indent ( [ & ] {
1317
auto & group = info. groups [ DIR_AS_DISK] ;
1318
auto createNew = [ & ] {
1319
manager. openFile -> selectNewFile (
1320
" Select name for new empty directory" ,
1321
" " ,
1322
[ & ] ( const auto & fn) {
1323
group. edit . name = fn;
1324
try {
1325
FileOperations:: mkdirp ( fn ) ;
1326
} catch ( MSXException& e) {
1327
manager. printError ( " Couldn't create directory: " , e. getMessage ( ) ) ;
1328
}
1329
} ,
1330
current. getString ( ) ) ;
1331
} ;
1332
bool interacted = selectDirectory (
1333
group, strCat ( " Select directory for " , displayName) ,
1334
current. getString ( ) , createNew ) ;
1335
if ( interacted) info. select = DIR_AS_DISK;
1336
} ) ;
1337
} ) ;
1338
ImGui:: RadioButton ( " RAM disk" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( RAMDISK ) ) ;
1339
if ( ! current. empty ( ) ) {
1340
ImGui:: RadioButton ( " Eject" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( EMPTY ) ) ;
1341
}
1342
} ) ;
1343
} ) ;
1344
insertMediaButton ( mediaName, info. groups [ info. select ] , & info. show ) ;
1345
} ) ;
1346
}
1347
1348
void ImGuiMedia:: cartridgeMenu ( int cartNum )
1349
{
1350
auto & info = cartridgeMediaInfo[ cartNum] ;
1351
auto displayName = strCat ( " Cartridge Slot " , char ( ' A' + cartNum) ) ;
1352
ImGui:: SetNextWindowSize ( gl:: vec2{ 37 , 30 } * ImGui:: GetFontSize ( ) , ImGuiCond_FirstUseEver ) ;
1353
im:: Window ( displayName. c_str ( ) , & info. show , [ & ] {
1354
using enum SelectCartridgeType;
1355
auto cartName = strCat ( " cart" , char ( ' a' + cartNum) ) ;
1356
auto extName = strCat ( " ext" , char ( ' a' + cartNum) ) ;
1357
1358
auto current = showCartridgeInfo ( cartName, info, cartNum ) ;
1359
1360
ImGui:: SeparatorText ( " Select new cartridge:" ) ;
1361
im:: Indent ( [ & ] {
1362
im:: Child ( " select" , { 0 , - ImGui:: GetFrameHeightWithSpacing ( ) } , [ & ] {
1363
1364
ImGui:: RadioButton ( " ROM image" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( IMAGE ) ) ;
1365
im:: VisuallyDisabled ( info. select != IMAGE, [ & ] {
1366
im:: Indent ( [ & ] {
1367
auto & group = info. groups [ IMAGE] ;
1368
auto & item = group. edit ;
1369
bool interacted = selectImage (
1370
group, strCat ( " Select ROM image for " , displayName) , & romFilter, current. getString ( ) ) ;
1371
1372
const auto & style = ImGui:: GetStyle ( ) ;
1373
ImGui:: SetNextItemWidth ( - ( ImGui:: CalcTextSize ( " mapper-type" ) . x + style. ItemInnerSpacing . x ) ) ;
1374
interacted |= selectMapperType ( " mapper-type" , item. romType ) ;
1375
interacted |= selectPatches ( item, group. patchIndex ) ;
1376
if ( interacted) info. select = IMAGE;
1377
} ) ;
1378
} ) ;
1379
ImGui:: RadioButton ( " extension" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( EXTENSION ) ) ;
1380
im:: VisuallyDisabled ( info. select != EXTENSION, [ & ] {
1381
im:: Indent ( [ & ] {
1382
bool interacted = showExtensionSelector ( cartNum ) ;
1383
if ( interacted) info. select = EXTENSION;
1384
} ) ;
1385
} ) ;
1386
if ( ! current. empty ( ) ) {
1387
ImGui:: RadioButton ( " Eject" , std:: bit_cast < int * > ( & info. select ) , std:: to_underlying ( EMPTY ) ) ;
1388
}
1389
ImGui:: Checkbox ( " Reset MSX on changes" , & resetOnCartChanges) ;
1390
} ) ;
1391
if ( insertMediaButton ( info. select == EXTENSION ? extName : cartName,
1392
info. groups [ info. select ] , & info. show ) ) {
1393
if ( resetOnCartChanges) {
1394
manager. executeDelayed ( TclObject ( " reset" ) ) ;
1395
}
1396
}
1397
} ) ;
1398
} ) ;
1399
}
1400
1401
bool ImGuiMedia:: showExtensionSelector ( int cartNum , std:: optional< std:: string> itemToShowAsSelected )
1402
{
1403
auto & info = cartridgeMediaInfo[ cartNum] ;
1404
using enum SelectCartridgeType;
1405
auto & allExtensions = getAllExtensions ( ) ;
1406
auto & group = info. groups [ EXTENSION] ;
1407
auto & item = group. edit ;
1408
1409
bool interacted = drawExtensionFilter ( info. filterType , info. filterString , info. filterOpen , cartNum ) ;
1410
1411
auto drawExtensions = [ & ] {
1412
auto filteredExtensions = to_vector ( xrange ( allExtensions. size ( ) ) ) ;
1413
applyComboFilter ( " Type" , info. filterType , allExtensions, filteredExtensions) ;
1414
applyDisplayNameFilter ( info. filterString , allExtensions, filteredExtensions ) ;
1415
1416
im:: ListClipper ( filteredExtensions. size ( ) , [ & ] ( int i) {
1417
auto & ext = allExtensions[ filteredExtensions[ i] ] ;
1418
bool ok = getTestResult ( ext ) . empty ( ) ;
1419
im:: StyleColor ( ! ok, ImGuiCol_Text, getColor ( imColor:: ERROR ) , [ & ] {
1420
auto extName = strCat ( " ext" , char ( ' a' + cartNum) ) ;
1421
if ( ImGui:: Selectable ( ext. displayName . c_str ( ) , item. name == ( itemToShowAsSelected ? * itemToShowAsSelected : ext. configName ) ) ) {
1422
interacted = true ;
1423
item. name = ext. configName ;
1424
if ( itemToShowAsSelected. has_value ( ) ) {
1425
insertMedia ( extName, group. edit ) ;
1426
}
1427
}
1428
if ( ImGui:: IsMouseDoubleClicked ( ImGuiMouseButton_Left ) ) {
1429
insertMedia ( extName, group. edit ) ;
1430
}
1431
extensionTooltip ( ext ) ;
1432
} ) ;
1433
} ) ;
1434
} ;
1435
ImGui:: SetNextItemWidth ( - FLT_MIN) ;
1436
if ( info. filterOpen ) {
1437
im:: ListBox ( strCat ( " ##list" , cartNum) . c_str ( ) , [ & ] {
1438
drawExtensions ( ) ;
1439
} ) ;
1440
} else {
1441
im:: Combo ( strCat ( " ##extension" , cartNum) . c_str ( ) , displayNameForExtension ( itemToShowAsSelected ? * itemToShowAsSelected : item. name ) . c_str ( ) , [ & ] {
1442
drawExtensions ( ) ;
1443
} ) ;
1444
}
1445
1446
interacted |= ImGui:: IsItemActive ( ) ;
1447
return interacted;
1448
}
1449
1450
static void RenderPlay ( gl:: vec2 center , ImDrawList* drawList )
1451
{
1452
float half = 0.4f * ImGui:: GetTextLineHeight ( ) ;
1453
auto p1 = center + gl:: vec2 ( half, 0.0f ) ;
1454
auto p2 = center + gl:: vec2 ( - half, half) ;
1455
auto p3 = center + gl:: vec2 ( - half, - half) ;
1456
drawList-> AddTriangleFilled ( p1, p2, p3, getColor ( imColor:: TEXT ) ) ;
1457
}
1458
static void RenderRewind ( gl:: vec2 center , ImDrawList* drawList )
1459
{
1460
float size = 0.8f * ImGui:: GetTextLineHeight ( ) ;
1461
float half = size * 0.5f ;
1462
auto color = getColor ( imColor:: TEXT ) ;
1463
auto p1 = center + gl:: vec2 ( - size, 0.0f ) ;
1464
auto p2 = center + gl:: vec2 ( 0.0f , - half) ;
1465
auto p3 = center + gl:: vec2 ( 0.0f , half) ;
1466
drawList-> AddTriangleFilled ( p1, p2, p3, color ) ;
1467
gl:: vec2 offset{ size, 0.0f } ;
1468
p1 += offset;
1469
p2 += offset;
1470
p3 += offset;
1471
drawList-> AddTriangleFilled ( p1, p2, p3, color ) ;
1472
}
1473
static void RenderStop ( gl:: vec2 center , ImDrawList* drawList )
1474
{
1475
gl:: vec2 half{ 0.4f * ImGui:: GetTextLineHeight ( ) } ;
1476
drawList-> AddRectFilled ( center - half, center + half, getColor ( imColor:: TEXT ) ) ;
1477
}
1478
static void RenderRecord ( gl:: vec2 center , ImDrawList* drawList )
1479
{
1480
float radius = 0.4f * ImGui:: GetTextLineHeight ( ) ;
1481
drawList-> AddCircleFilled ( center, radius, getColor ( imColor:: TEXT ) ) ;
1482
}
1483
static void RenderEject ( gl:: vec2 center , ImDrawList* drawList )
1484
{
1485
float half = 0.4f * ImGui:: GetTextLineHeight ( ) ;
1486
auto p1 = center + gl:: vec2 ( 0.0f , - half) ;
1487
auto p2 = center + gl:: vec2 ( - half, 0.5f * half) ;
1488
auto p3 = center + gl:: vec2 ( half, 0.5f * half ) ;
1489
drawList-> AddTriangleFilled ( p1, p2, p3, getColor ( imColor:: TEXT ) ) ;
1490
drawList-> AddRectFilled ( p2 + gl:: vec2 ( - 0.1f * half, 0.4f * half) , p3 + gl:: vec2 ( 0.1f * half, half) , getColor ( imColor:: TEXT ) ) ;
1491
}
1492
1493
void ImGuiMedia:: cassetteMenu ( CassettePlayer& cassettePlayer )
1494
{
1495
ImGui:: SetNextWindowSize ( gl:: vec2{ 30 , 21 } * ImGui:: GetFontSize ( ) , ImGuiCond_FirstUseEver ) ;
1496
auto & info = cassetteMediaInfo;
1497
auto & group = info. group ;
1498
im:: Window ( " Tape Deck" , & info. show , [ & ] {
1499
ImGui:: TextUnformatted ( " Current tape" sv) ;
1500
auto current = cassettePlayer. getImageName ( ) . getResolved ( ) ;
1501
im:: Indent ( [ & ] {
1502
if ( current. empty ( ) ) {
1503
ImGui:: TextUnformatted ( " No tape inserted" sv) ;
1504
} else {
1505
ImGui:: TextUnformatted ( " Tape image:" sv) ;
1506
ImGui:: SameLine ( ) ;
1507
ImGui:: TextUnformatted ( ImGui:: leftClip ( current, ImGui:: GetContentRegionAvail ( ) . x ) ) ;
1508
}
1509
} ) ;
1510
1511
ImGui:: SeparatorText ( " Controls" ) ;
1512
im:: Indent ( [ & ] {
1513
auto status = cassettePlayer. getState ( ) ;
1514
auto size = ImGui:: GetFrameHeightWithSpacing ( ) ;
1515
if ( ButtonWithCustomRendering ( " ##Record" , { 2.0f * size, size} , status == CassettePlayer:: State:: RECORD, RenderRecord) ) {
1516
manager. openFile -> selectNewFile (
1517
" Select new wav file for record" ,
1518
" Tape images (*.wav){.wav}" ,
1519
[ & ] ( const auto & fn) {
1520
group. edit . name = fn;
1521
manager. executeDelayed ( makeTclList ( " cassetteplayer" , " new" , fn) ,
1522
[ & group] ( const TclObject& ) {
1523
1524
addRecentItem ( group. recent , group. edit ) ;
1525
} ) ;
1526
} ,
1527
current ) ;
1528
}
1529
simpleToolTip ( " Set to record mode" ) ;
1530
ImGui:: SameLine ( ) ;
1531
if ( ButtonWithCustomRendering ( " ##Play" , { 2.0f * size, size} , status == CassettePlayer:: State:: PLAY, RenderPlay) ) {
1532
manager. executeDelayed ( makeTclList ( " cassetteplayer" , " play" ) ) ;
1533
}
1534
simpleToolTip ( " Set to play mode" ) ;
1535
ImGui:: SameLine ( ) ;
1536
if ( ButtonWithCustomRendering ( " ##Rewind" , { 2.0f * size, size} , false , RenderRewind) ) {
1537
manager. executeDelayed ( makeTclList ( " cassetteplayer" , " rewind" ) ) ;
1538
}
1539
simpleToolTip ( " Rewind tape" ) ;
1540
ImGui:: SameLine ( ) ;
1541
if ( ButtonWithCustomRendering ( " ##Stop" , { 2.0f * size, size} , status == CassettePlayer:: State:: STOP, RenderStop) ) {
1542
1543
}
1544
simpleToolTip ( " Indicates STOP status" ) ;
1545
ImGui:: SameLine ( ) ;
1546
if ( ButtonWithCustomRendering ( " ##Eject" , { 2.0f * size, size} , false , RenderEject) ) {
1547
manager. executeDelayed ( makeTclList ( " cassetteplayer" , " eject" ) ) ;
1548
}
1549
simpleToolTip ( " Eject tape" ) ;
1550
1551
const auto & style = ImGui:: GetStyle ( ) ;
1552
ImGui:: SameLine ( 0.0f , 3.0f * style. ItemSpacing . x ) ;
1553
const auto & reactor = manager. getReactor ( ) ;
1554
const auto & motherBoard = reactor. getMotherBoard ( ) ;
1555
const auto now = motherBoard-> getCurrentTime ( ) ;
1556
auto length = cassettePlayer. getTapeLength ( now ) ;
1557
auto pos = cassettePlayer. getTapePos ( now ) ;
1558
1559
std:: array< char , 16 > format_buf;
1560
auto format = [ & format_buf] ( double time) {
1561
int t = narrow_cast < int > ( time ) ;
1562
int s = t % 60 ; t /= 60 ;
1563
int m = t % 60 ; t /= 60 ;
1564
if ( t) {
1565
1566
return format_to_zbuf ( format_buf,
1567
" {:02}:{:02}:{:02}" , t, m, s ) ;
1568
} else {
1569
1570
return format_to_zbuf ( format_buf,
1571
" {:02}:{:02}" , m, s ) ;
1572
}
1573
} ;
1574
1575
auto parse = [ ] ( std:: string_view str) -> std :: optional < unsigned > {
1576
auto [ head, seconds] = StringOp:: splitOnLast ( str, ' :' ) ;
1577
auto s = StringOp:: stringTo < unsigned > ( seconds ) ;
1578
if ( ! s) return { } ;
1579
unsigned result = * s;
1580
1581
if ( ! head. empty ( ) ) {
1582
auto [ hours, minutes] = StringOp:: splitOnLast ( head, ' :' ) ;
1583
auto m = StringOp:: stringTo < unsigned > ( minutes ) ;
1584
if ( ! m) return { } ;
1585
result += * m * 60 ;
1586
1587
if ( ! hours. empty ( ) ) {
1588
auto h = StringOp:: stringTo < unsigned > ( hours ) ;
1589
if ( ! h) return { } ;
1590
result += * h * 60 * 60 ;
1591
}
1592
}
1593
return result;
1594
} ;
1595
auto posStr = format ( pos ) ; assert ( posStr. data ( ) == format_buf. data ( ) ) ;
1596
ImGui:: SetNextItemWidth ( ImGui:: CalcTextSize ( posStr ) . x + 2.0f * style. FramePadding . x ) ;
1597
if ( ImGui:: InputText ( " ##pos" , format_buf. data ( ) , int ( format_buf. size ( ) ) , ImGuiInputTextFlags_EnterReturnsTrue) ) {
1598
if ( auto newPos = parse ( posStr ) ) {
1599
manager. executeDelayed ( makeTclList ( " cassetteplayer" , " setpos" , * newPos) ) ;
1600
}
1601
}
1602
simpleToolTip ( " Indicates the current position of the tape, but can be edited to change the position manual (like fast forward)" ) ;
1603
1604
ImGui:: SameLine ( ) ;
1605
ImGui:: StrCat ( " / " , format ( length ) ) ;
1606
1607
const auto & controller = motherBoard-> getMSXCommandController ( ) ;
1608
const auto & hotKey = reactor. getHotKey ( ) ;
1609
if ( auto * autoRun = dynamic_cast < BooleanSetting* > ( controller. findSetting ( " autoruncassettes" ) ) ) {
1610
Checkbox ( hotKey, " (try to) Auto Run" , * autoRun ) ;
1611
}
1612
if ( auto * mute = dynamic_cast < BooleanSetting* > ( controller. findSetting ( " cassetteplayer_ch1_mute" ) ) ) {
1613
Checkbox ( hotKey, " Mute tape audio" , * mute, [ ] ( const Setting& ) { return std:: string{ } ; } ) ;
1614
}
1615
bool enabled = cassettePlayer. isMotorControlEnabled ( ) ;
1616
bool changed = ImGui:: Checkbox ( " Motor control enabled" , & enabled) ;
1617
if ( changed) {
1618
manager. execute ( makeTclList ( " cassetteplayer" , " motorcontrol" , enabled ? " on" sv : " off" sv) ) ;
1619
}
1620
simpleToolTip ( " Enable or disable motor control. Disable in some rare cases where you don't want the motor of the player to be controlled by the MSX, e.g. for CD-Sequential." ) ;
1621
1622
} ) ;
1623
1624
im:: Child ( " select" , { 0 , - ImGui:: GetFrameHeightWithSpacing ( ) } , [ & ] {
1625
ImGui:: SeparatorText ( " Select new tape:" ) ;
1626
im:: Indent ( [ & ] {
1627
selectImage ( group, " Select tape image" , & cassetteFilter, current ) ;
1628
} ) ;
1629
} ) ;
1630
insertMediaButton ( " cassetteplayer" , group, & info. show ) ;
1631
} ) ;
1632
}
1633
1634
void ImGuiMedia:: insertMedia ( std:: string_view mediaName , const MediaItem& item , bool delayed )
1635
{
1636
TclObject cmd = makeTclList ( mediaName ) ;
1637
if ( item. isEject ( ) ) {
1638
cmd. addListElement ( " eject" ) ;
1639
} else {
1640
if ( item. name . empty ( ) ) return ;
1641
cmd. addListElement ( " insert" , item. name ) ;
1642
for ( const auto & patch : item. ipsPatches ) {
1643
cmd. addListElement ( " -ips" , patch) ;
1644
}
1645
if ( item. romType != RomType:: UNKNOWN) {
1646
cmd. addListElement ( " -romtype" , RomInfo:: romTypeToName ( item. romType ) ) ;
1647
}
1648
}
1649
1650
auto onOk = [ this , cmd] ( const TclObject& ) {
1651
1652
addRecent ( cmd ) ;
1653
} ;
1654
if ( delayed) {
1655
manager. executeDelayed ( cmd, onOk ) ;
1656
} else {
1657
if ( auto r = manager. execute ( cmd ) ) onOk ( * r) ;
1658
}
1659
}
1660
1661
void ImGuiMedia:: addRecent ( const TclObject& cmd )
1662
{
1663
auto n = cmd. size ( ) ;
1664
if ( n < 3 ) return ;
1665
if ( cmd. getListIndexUnchecked ( 1 ) . getString ( ) != " insert" ) return ;
1666
1667
auto * group = [ & ] {
1668
auto mediaName = cmd. getListIndexUnchecked ( 0 ) . getString ( ) ;
1669
if ( mediaName. starts_with ( " cart" ) ) {
1670
if ( int i = mediaName[ 4 ] - ' a' ; 0 <= i && i < int ( CartridgeSlotManager:: MAX_SLOTS) ) {
1671
return & cartridgeMediaInfo[ i] . groups [ SelectCartridgeType:: IMAGE] ;
1672
}
1673
} else if ( mediaName. starts_with ( " disk" ) ) {
1674
if ( int i = mediaName[ 4 ] - ' a' ; 0 <= i && i < int ( RealDrive:: MAX_DRIVES) ) {
1675
1676
1677
auto type = FileOperations:: isDirectory ( cmd. getListIndexUnchecked ( 2 ) . getString ( ) )
1678
? SelectDiskType:: DIR_AS_DISK : SelectDiskType:: IMAGE;
1679
return & diskMediaInfo[ i] . groups [ type] ;
1680
}
1681
} else if ( mediaName. starts_with ( " hd" ) ) {
1682
if ( int i = mediaName[ 2 ] - ' a' ; 0 <= i && i < int ( HD:: MAX_HD) ) {
1683
return & hdMediaInfo[ i] ;
1684
}
1685
} else if ( mediaName. starts_with ( " cd" ) ) {
1686
if ( int i = mediaName[ 2 ] - ' a' ; 0 <= i && i < int ( IDECDROM:: MAX_CD) ) {
1687
return & cdMediaInfo[ i] ;
1688
}
1689
} else if ( mediaName == " cassetteplayer" ) {
1690
return & cassetteMediaInfo. group ;
1691
} else if ( mediaName == " laserdiscplayer" ) {
1692
return & laserdiscMediaInfo;
1693
} else if ( mediaName == " ext" ) {
1694
return & extensionMediaInfo;
1695
}
1696
return static_cast < ItemGroup* > ( nullptr ) ;
1697
} ( ) ;
1698
if ( ! group) return ;
1699
1700
MediaItem item;
1701
item. name = cmd. getListIndexUnchecked ( 2 ) . getString ( ) ;
1702
decltype ( n ) i = 3 ;
1703
while ( i < n) {
1704
auto option = cmd. getListIndexUnchecked ( i ) ;
1705
+ + i;
1706
if ( option == " -ips" && i < n) {
1707
item. ipsPatches . emplace_back ( cmd. getListIndexUnchecked ( i ) . getString ( ) ) ;
1708
+ + i;
1709
}
1710
if ( option == " -romtype" && i < n) {
1711
item. romType = RomInfo:: nameToRomType ( cmd. getListIndexUnchecked ( i ) . getString ( ) ) ;
1712
+ + i;
1713
}
1714
}
1715
1716
addRecentItem ( group-> recent , item ) ;
1717
}
1718
1719
1720
}