@@ -228,6 +228,18 @@ namespace
228
228
// Do nothing.
229
229
}
230
230
231
+ void sortMapInfos ( MapsFileInfoList & mapInfos )
232
+ {
233
+ const SaveFileSortingMethod sortType = Settings::Get ().getSaveFileSortingMethod ();
234
+ if ( sortType == SaveFileSortingMethod::FILENAME ) {
235
+ std::sort ( mapInfos.begin (), mapInfos.end (), Maps::FileInfo::CompareByFileName{} );
236
+ }
237
+ else {
238
+ assert ( sortType == SaveFileSortingMethod::TIMESTAMP );
239
+ std::sort ( mapInfos.begin (), mapInfos.end (), Maps::FileInfo::CompareByTimestamp{} );
240
+ }
241
+ }
242
+
231
243
MapsFileInfoList getSortedMapsFileInfoList ()
232
244
{
233
245
ListFiles files;
@@ -244,19 +256,92 @@ namespace
244
256
}
245
257
}
246
258
247
- std::sort ( mapInfos. begin (), mapInfos. end (), Maps::FileInfo::sortByFileName );
259
+ sortMapInfos ( mapInfos );
248
260
249
261
return mapInfos;
250
262
}
251
263
264
+ MapsFileInfoList::const_iterator findInMapInfos ( const MapsFileInfoList::const_iterator & begin, const MapsFileInfoList::const_iterator & end,
265
+ const std::string & lastChoice )
266
+ {
267
+ return std::find_if ( begin, end, [&lastChoice]( const Maps::FileInfo & info ) { return info.filename == lastChoice; } );
268
+ }
269
+
270
+ MapsFileInfoList::const_iterator findInMapInfos ( const MapsFileInfoList::const_iterator & begin, const MapsFileInfoList::const_iterator & end,
271
+ const std::string & lastChoice, const uint32_t lastChoiceTimestamp, const SaveFileSortingMethod sortingMethod )
272
+ {
273
+ switch ( sortingMethod ) {
274
+ case SaveFileSortingMethod::FILENAME: {
275
+ #ifdef WITH_DEBUG
276
+ assert ( std::is_sorted ( begin, end, Maps::FileInfo::CompareByFileName{} ) );
277
+ #endif
278
+
279
+ // With case-insensitive sorting, there may be several "identical" file names on the case-sensitive file system
280
+ const auto [beginSameFileName, endSameFileName] = std::equal_range ( begin, end, lastChoice, Maps::FileInfo::CompareByFileName{} );
281
+
282
+ if ( const MapsFileInfoList::const_iterator iter = findInMapInfos ( beginSameFileName, endSameFileName, lastChoice ); iter != endSameFileName ) {
283
+ return iter;
284
+ }
285
+
286
+ break ;
287
+ }
288
+ case SaveFileSortingMethod::TIMESTAMP: {
289
+ #ifdef WITH_DEBUG
290
+ assert ( std::is_sorted ( begin, end, Maps::FileInfo::CompareByTimestamp{} ) );
291
+ #endif
292
+
293
+ const auto [beginSameTimestamp, endSameTimestamp] = std::equal_range ( begin, end, lastChoiceTimestamp, Maps::FileInfo::CompareByTimestamp{} );
294
+
295
+ if ( const MapsFileInfoList::const_iterator iter = findInMapInfos ( beginSameTimestamp, endSameTimestamp, lastChoice ); iter != endSameTimestamp ) {
296
+ return iter;
297
+ }
298
+
299
+ break ;
300
+ }
301
+ default :
302
+ assert ( 0 );
303
+ break ;
304
+ }
305
+
306
+ return end;
307
+ }
308
+
309
+ MapsFileInfoList::const_iterator findInMapInfos ( const MapsFileInfoList::const_iterator & begin, const MapsFileInfoList::const_iterator & end,
310
+ const std::string & lastChoice, const SaveFileSortingMethod sortingMethod )
311
+ {
312
+ switch ( sortingMethod ) {
313
+ case SaveFileSortingMethod::FILENAME: {
314
+ #ifdef WITH_DEBUG
315
+ assert ( std::is_sorted ( begin, end, Maps::FileInfo::CompareByFileName{} ) );
316
+ #endif
317
+
318
+ // With case-insensitive sorting, there may be several "identical" file names on the case-sensitive file system
319
+ const auto [beginSameFileName, endSameFileName] = std::equal_range ( begin, end, lastChoice, Maps::FileInfo::CompareByFileName{} );
320
+
321
+ if ( const MapsFileInfoList::const_iterator iter = findInMapInfos ( beginSameFileName, endSameFileName, lastChoice ); iter != endSameFileName ) {
322
+ return iter;
323
+ }
324
+
325
+ break ;
326
+ }
327
+ case SaveFileSortingMethod::TIMESTAMP:
328
+ return findInMapInfos ( begin, end, lastChoice );
329
+ default :
330
+ assert ( 0 );
331
+ break ;
332
+ }
333
+
334
+ return end;
335
+ }
336
+
252
337
std::string selectFileListSimple ( const std::string & header, const std::string & lastfile, const bool isEditing )
253
338
{
254
339
// setup cursor
255
340
const CursorRestorer cursorRestorer ( true , Cursor::POINTER );
256
341
257
342
MapsFileInfoList lists = getSortedMapsFileInfoList ();
258
343
259
- const int32_t listHeightDeduction = 112 ;
344
+ const int32_t listHeightDeduction = 120 ;
260
345
const int32_t listAreaOffsetY = 3 ;
261
346
const int32_t listAreaHeightDeduction = 4 ;
262
347
@@ -272,20 +357,24 @@ namespace
272
357
fheroes2::StandardWindow background ( maxFileNameWidth + 204 , std::min ( display.height () - 100 , maxDialogHeight ), true , display );
273
358
274
359
const fheroes2::Rect dialogArea ( background.activeArea () );
275
- const fheroes2::Rect listRoi ( dialogArea.x + 24 , dialogArea.y + 37 , dialogArea.width - 75 , dialogArea.height - listHeightDeduction );
276
- const fheroes2::Rect textInputRoi ( listRoi.x , listRoi.y + listRoi.height + 12 , maxFileNameWidth + 8 , 21 );
277
- const int32_t dateTimeoffsetX = textInputRoi.x + textInputRoi.width ;
360
+ const fheroes2::Rect listRoi ( dialogArea.x + 24 , dialogArea.y + 57 , dialogArea.width - 75 , dialogArea.height - listHeightDeduction );
361
+ const fheroes2::Rect nameHeaderRoi ( listRoi.x , listRoi.y - 28 , maxFileNameWidth + 8 , 28 );
362
+ const fheroes2::Rect textInputRoi ( listRoi.x , listRoi.y + listRoi.height + 3 , maxFileNameWidth + 8 , 23 );
363
+ const int32_t dateTimeOffsetX = textInputRoi.x + textInputRoi.width ;
278
364
const int32_t dateTimeWidth = listRoi.width - textInputRoi.width ;
365
+ const fheroes2::Rect dateHeaderRoi ( dateTimeOffsetX, nameHeaderRoi.y , dateTimeWidth, nameHeaderRoi.height );
279
366
280
367
// We divide the save-files list: file name and file date/time.
281
368
background.applyTextBackgroundShading ( { listRoi.x , listRoi.y , textInputRoi.width , listRoi.height } );
282
369
background.applyTextBackgroundShading ( { listRoi.x + textInputRoi.width , listRoi.y , dateTimeWidth, listRoi.height } );
370
+ background.applyTextBackgroundShading ( nameHeaderRoi );
371
+ background.applyTextBackgroundShading ( dateHeaderRoi );
283
372
background.applyTextBackgroundShading ( textInputRoi );
284
373
// Make background for the selected file date and time.
285
- background.applyTextBackgroundShading ( { dateTimeoffsetX , textInputRoi.y , dateTimeWidth, textInputRoi.height } );
374
+ background.applyTextBackgroundShading ( { dateTimeOffsetX , textInputRoi.y , dateTimeWidth, textInputRoi.height } );
286
375
287
376
fheroes2::ImageRestorer textInputBackground ( display, textInputRoi.x , textInputRoi.y , textInputRoi.width , textInputRoi.height );
288
- fheroes2::ImageRestorer dateBackground ( display, dateTimeoffsetX , textInputRoi.y , dateTimeWidth, textInputRoi.height );
377
+ fheroes2::ImageRestorer dateBackground ( display, dateTimeOffsetX , textInputRoi.y , dateTimeWidth, textInputRoi.height );
289
378
const fheroes2::Rect textInputAndDateROI ( textInputRoi.x , textInputRoi.y , listRoi.width , textInputRoi.height );
290
379
291
380
// Prepare OKAY and CANCEL buttons and render their shadows.
@@ -304,7 +393,9 @@ namespace
304
393
305
394
listbox.SetAreaItems ( { listRoi.x , listRoi.y + 3 , listRoi.width - listAreaOffsetY, listRoi.height - listAreaHeightDeduction } );
306
395
307
- const bool isEvilInterface = Settings::Get ().isEvilInterfaceEnabled ();
396
+ Settings & settings = Settings::Get ();
397
+ const bool isEvilInterface = settings.isEvilInterfaceEnabled ();
398
+ const SaveFileSortingMethod fileSortingMethod = settings.getSaveFileSortingMethod ();
308
399
309
400
int32_t scrollbarOffsetX = dialogArea.x + dialogArea.width - 35 ;
310
401
background.renderScrollbarBackground ( { scrollbarOffsetX, listRoi.y , listRoi.width , listRoi.height }, isEvilInterface );
@@ -327,16 +418,10 @@ namespace
327
418
if ( !lastfile.empty () ) {
328
419
filename = System::GetStem ( lastfile );
329
420
charInsertPos = filename.size ();
421
+ const MapsFileInfoList::const_iterator it = findInMapInfos ( lists.cbegin (), lists.cend (), lastfile, fileSortingMethod );
330
422
331
- MapsFileInfoList::iterator it = lists.begin ();
332
- for ( ; it != lists.end (); ++it ) {
333
- if ( ( *it ).filename == lastfile ) {
334
- break ;
335
- }
336
- }
337
-
338
- if ( it != lists.end () ) {
339
- listbox.SetCurrent ( std::distance ( lists.begin (), it ) );
423
+ if ( it != lists.cend () ) {
424
+ listbox.SetCurrent ( std::distance ( lists.cbegin (), it ) );
340
425
}
341
426
else {
342
427
if ( !isEditing ) {
@@ -370,7 +455,68 @@ namespace
370
455
std::unique_ptr<fheroes2::TextInputField> textInput;
371
456
372
457
const fheroes2::Text title ( header, fheroes2::FontType::normalYellow () );
373
- title.drawInRoi ( dialogArea.x + ( dialogArea.width - title.width () ) / 2 , dialogArea.y + 16 , display, dialogArea );
458
+ title.drawInRoi ( dialogArea.x + ( dialogArea.width - title.width () ) / 2 , dialogArea.y + 9 , display, dialogArea );
459
+
460
+ const fheroes2::Text nameHeader ( _ ( " saveLoadDialog|Name" ), fheroes2::FontType::normalYellow () );
461
+ nameHeader.drawInRoi ( nameHeaderRoi.x , nameHeaderRoi.y + ( nameHeaderRoi.height - nameHeader.height () ) / 2 + 3 , nameHeaderRoi.width , display, nameHeaderRoi );
462
+
463
+ const fheroes2::Text dateHeader ( _ ( " saveLoadDialog|Date" ), fheroes2::FontType::normalYellow () );
464
+ dateHeader.drawInRoi ( dateHeaderRoi.x , dateHeaderRoi.y + ( dateHeaderRoi.height - dateHeader.height () ) / 2 + 3 , dateHeaderRoi.width , display, dateHeaderRoi );
465
+
466
+ // Draw radio buttons for toggling between sorting methods.
467
+ const int sortMarkIcnID = isEvilInterface ? ICN::CELLWIN_EVIL : ICN::CELLWIN;
468
+ const fheroes2::Sprite & markBackground = fheroes2::AGG::GetICN ( sortMarkIcnID, 4 );
469
+
470
+ const fheroes2::Rect markBackgroundNameRoi ( nameHeaderRoi.x + 5 , nameHeaderRoi.y + 6 , markBackground.width (), markBackground.height () );
471
+ const fheroes2::Rect markBackgroundDateRoi ( dateTimeOffsetX + 5 , nameHeaderRoi.y + 6 , markBackground.width (), markBackground.height () );
472
+ fheroes2::Blit ( markBackground, display, markBackgroundNameRoi );
473
+ fheroes2::Blit ( markBackground, display, markBackgroundDateRoi );
474
+
475
+ const fheroes2::Sprite & mark = fheroes2::AGG::GetICN ( sortMarkIcnID, 5 );
476
+
477
+ if ( fileSortingMethod == SaveFileSortingMethod::FILENAME ) {
478
+ fheroes2::Blit ( mark, display, { markBackgroundNameRoi.x + 3 , markBackgroundNameRoi.y + 3 , mark.width (), mark.height () } );
479
+ }
480
+ else {
481
+ fheroes2::Blit ( mark, display, { markBackgroundDateRoi.x + 3 , markBackgroundDateRoi.y + 3 , mark.width (), mark.height () } );
482
+ }
483
+
484
+ // Redraw sort radio buttons, sort file list in new method, and return whether to redraw the list.
485
+ auto switchFileSorting
486
+ = [&markBackgroundNameRoi, &markBackgroundDateRoi, &markBackground, &mark, &display, &listbox, &lists, &settings]( const bool doSortByDate ) {
487
+ const fheroes2::Rect roiMarkBackground = doSortByDate ? markBackgroundNameRoi : markBackgroundDateRoi;
488
+ const fheroes2::Rect roiMark = doSortByDate ? markBackgroundDateRoi : markBackgroundNameRoi;
489
+ fheroes2::Blit ( markBackground, display, roiMarkBackground );
490
+ fheroes2::Blit ( mark, display, { roiMark.x + 3 , roiMark.y + 3 , roiMark.width , roiMark.height } );
491
+
492
+ const int currentId = listbox.getCurrentId ();
493
+ std::string lastChoice{};
494
+ uint32_t lastChoiceTimestamp{};
495
+ if ( currentId >= 0 && static_cast <size_t >( currentId ) < lists.size () ) {
496
+ lastChoice = lists[currentId].filename ;
497
+ lastChoiceTimestamp = lists[currentId].timestamp ;
498
+ }
499
+
500
+ settings.setSaveFileSortingMethod ( doSortByDate ? SaveFileSortingMethod::TIMESTAMP : SaveFileSortingMethod::FILENAME );
501
+ sortMapInfos ( lists );
502
+
503
+ bool redrawNeeded = false ;
504
+
505
+ // Re-select the last selected file if any, unless we're typing in the list box
506
+ if ( !lastChoice.empty () ) {
507
+ const MapsFileInfoList::const_iterator it
508
+ = findInMapInfos ( lists.cbegin (), lists.cend (), lastChoice, lastChoiceTimestamp, settings.getSaveFileSortingMethod () );
509
+
510
+ if ( it != lists.cend () ) {
511
+ const int newId = static_cast <int >( std::distance ( lists.cbegin (), it ) );
512
+ if ( newId != currentId ) {
513
+ listbox.SetCurrent ( newId );
514
+ redrawNeeded = true ;
515
+ }
516
+ }
517
+ }
518
+ return redrawNeeded;
519
+ };
374
520
375
521
if ( isEditing ) {
376
522
// Render a button to open the Virtual Keyboard window.
@@ -383,14 +529,14 @@ namespace
383
529
384
530
if ( !listbox.isSelected () ) {
385
531
textInput->draw ( filename, static_cast <int32_t >( charInsertPos ) );
386
- redrawDateTime ( display, std::time ( nullptr ), dateTimeoffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalWhite () );
532
+ redrawDateTime ( display, std::time ( nullptr ), dateTimeOffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalWhite () );
387
533
}
388
534
}
389
535
390
536
if ( listbox.isSelected () ) {
391
537
// Render the saved file name, date and time.
392
538
redrawTextInputField ( filename, textInputRoi, display );
393
- redrawDateTime ( display, listbox.GetCurrent ().timestamp , dateTimeoffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalYellow () );
539
+ redrawDateTime ( display, listbox.GetCurrent ().timestamp , dateTimeOffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalYellow () );
394
540
}
395
541
396
542
display.render ( background.totalArea () );
@@ -412,7 +558,7 @@ namespace
412
558
}
413
559
414
560
if ( le.MouseClickLeft ( buttonCancel.area () ) || Game::HotKeyPressEvent ( Game::HotKeyEvent::DEFAULT_CANCEL ) ) {
415
- return {} ;
561
+ break ;
416
562
}
417
563
418
564
const int listId = listbox.getCurrentId ();
@@ -422,6 +568,7 @@ namespace
422
568
bool isListboxSelected = listbox.isSelected ();
423
569
424
570
bool needRedraw = ( listId != listbox.getCurrentId () );
571
+ bool listUpdated = false ;
425
572
426
573
if ( le.isKeyPressed ( fheroes2::Key::KEY_DELETE ) && isListboxSelected ) {
427
574
listbox.SetCurrent ( listId );
@@ -461,6 +608,14 @@ namespace
461
608
result = System::concatPath ( Game::GetSaveDir (), filename + Game::GetSaveFileExtension () );
462
609
}
463
610
}
611
+ else if ( le.MouseClickLeft ( nameHeaderRoi ) && settings.getSaveFileSortingMethod () != SaveFileSortingMethod::FILENAME ) {
612
+ listUpdated = true ;
613
+ needRedraw = switchFileSorting ( false );
614
+ }
615
+ else if ( le.MouseClickLeft ( dateHeaderRoi ) && settings.getSaveFileSortingMethod () != SaveFileSortingMethod::TIMESTAMP ) {
616
+ listUpdated = true ;
617
+ needRedraw = switchFileSorting ( true );
618
+ }
464
619
else if ( isEditing ) {
465
620
assert ( textInput != nullptr );
466
621
@@ -521,8 +676,15 @@ namespace
521
676
else if ( isEditing && le.isMouseRightButtonPressedInArea ( buttonVirtualKB->area () ) ) {
522
677
fheroes2::showStandardTextMessage ( _ ( " Open Virtual Keyboard" ), _ ( " Click to open the Virtual Keyboard dialog." ), Dialog::ZERO );
523
678
}
679
+ else if ( le.isMouseRightButtonPressedInArea ( nameHeaderRoi ) ) {
680
+ fheroes2::showStandardTextMessage ( _ ( " Sort by Name" ), _ ( " Click here if you wish to sort save files by their name." ), Dialog::ZERO );
681
+ }
682
+ else if ( le.isMouseRightButtonPressedInArea ( dateHeaderRoi ) ) {
683
+ fheroes2::showStandardTextMessage ( _ ( " Sort by Date" ), _ ( " Click here if you you wish to sort save files by their last modified date." ), Dialog::ZERO );
684
+ }
524
685
525
- const bool needRedrawListbox = listbox.IsNeedRedraw ();
686
+ // TODO: ListBox::SetCurrent() call should update needRedraw variable as it changes the internal UI view of the class.
687
+ const bool needRedrawListbox = listUpdated || listbox.IsNeedRedraw ();
526
688
527
689
if ( isEditing && !needRedraw && !isListboxSelected && textInput->eventProcessing () ) {
528
690
// Text input blinking cursor render is done in Save Game dialog when no file is selected
@@ -549,7 +711,7 @@ namespace
549
711
dateBackground.restore ();
550
712
551
713
redrawTextInputField ( filename, textInputRoi, display );
552
- redrawDateTime ( display, listbox.GetCurrent ().timestamp , dateTimeoffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalYellow () );
714
+ redrawDateTime ( display, listbox.GetCurrent ().timestamp , dateTimeOffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalYellow () );
553
715
}
554
716
else if ( isEditing ) {
555
717
// Empty last selected save file name so that we can replace the input field's name if we select the same save file again.
@@ -558,7 +720,7 @@ namespace
558
720
559
721
dateBackground.restore ();
560
722
textInput->draw ( filename, static_cast <int32_t >( charInsertPos ) );
561
- redrawDateTime ( display, std::time ( nullptr ), dateTimeoffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalWhite () );
723
+ redrawDateTime ( display, std::time ( nullptr ), dateTimeOffsetX , textInputRoi.y + 4 , fheroes2::FontType::normalWhite () );
562
724
}
563
725
}
564
726
@@ -571,6 +733,12 @@ namespace
571
733
}
572
734
}
573
735
736
+ const SaveFileSortingMethod lastFileSortingMethod = settings.getSaveFileSortingMethod ();
737
+
738
+ if ( lastFileSortingMethod != fileSortingMethod ) {
739
+ settings.Save ( Settings::configFileName );
740
+ }
741
+
574
742
return result;
575
743
}
576
744
}
0 commit comments