@@ -92,6 +92,10 @@ public Main()
9292 tabControl . SelectedIndexChanged += TabControl_SelectedIndexChanged ;
9393 }
9494
95+ // Add keyboard shortcut for Select All (Ctrl+A)
96+ this . KeyPreview = true ;
97+ this . KeyDown += Main_KeyDown ;
98+
9599 // Initialize sidebar and header
96100 UpdateSidebarSelection ( ) ;
97101 UpdateStats ( ) ;
@@ -377,6 +381,137 @@ private void TabControl_SelectedIndexChanged(object sender, EventArgs e)
377381 leftSidebar . Controls . SetChildIndex ( sidebarBorder , leftSidebar . Controls . Count - 1 ) ;
378382 }
379383 }
384+
385+ private void Main_KeyDown ( object sender , KeyEventArgs e )
386+ {
387+ // Handle Ctrl+A to select all mods in current tab
388+ // Only handle if not in a text input control
389+ if ( e . Control && e . KeyCode == Keys . A )
390+ {
391+ var focusedControl = this . ActiveControl ;
392+ bool isTextInput = focusedControl is TextBox ||
393+ focusedControl is RichTextBox ||
394+ focusedControl is ComboBox ;
395+
396+ // Only handle Ctrl+A if not in a text input control
397+ if ( ! isTextInput )
398+ {
399+ e . Handled = true ;
400+ e . SuppressKeyPress = true ;
401+ SelectAllModsInCurrentTab ( ) ;
402+ }
403+ }
404+ // Handle ESCAPE to deselect all mods in current tab
405+ else if ( e . KeyCode == Keys . Escape )
406+ {
407+ var focusedControl = this . ActiveControl ;
408+ bool isTextInput = focusedControl is TextBox ||
409+ focusedControl is RichTextBox ||
410+ focusedControl is ComboBox ;
411+
412+ // Only handle ESCAPE if not in a text input control (let ESCAPE close dialogs, clear text, etc.)
413+ if ( ! isTextInput )
414+ {
415+ e . Handled = true ;
416+ e . SuppressKeyPress = true ;
417+ DeselectAllModsInCurrentTab ( ) ;
418+ }
419+ }
420+ }
421+
422+ private void SelectAllModsInCurrentTab ( )
423+ {
424+ if ( tabControl == null )
425+ return ;
426+
427+ bool isInstalledView = tabControl . SelectedIndex == 0 ; // 0 = Installed, 1 = Store
428+
429+ // Get the appropriate panel and selection collection
430+ Panel targetPanel = isInstalledView ? panelInstalled : panelStore ;
431+ var bulkSelection = isInstalledView ? _bulkSelectedModIds : _bulkSelectedStoreModIds ;
432+
433+ if ( targetPanel == null )
434+ return ;
435+
436+ // Get all selectable mod cards in the current tab
437+ var selectableCards = targetPanel . Controls . OfType < ModCard > ( )
438+ . Where ( card => card . IsSelectable && card . BoundMod != null )
439+ . ToList ( ) ;
440+
441+ if ( selectableCards . Count == 0 )
442+ return ;
443+
444+ // Select all cards
445+ foreach ( var card in selectableCards )
446+ {
447+ var mod = card . BoundMod ;
448+ if ( mod == null )
449+ continue ;
450+
451+ // Add to bulk selection collection
452+ if ( ! bulkSelection . Contains ( mod . Id ) )
453+ {
454+ bulkSelection . Add ( mod . Id ) ;
455+ }
456+
457+ // Update card UI (suppress event to avoid individual handling)
458+ card . SetSelected ( true , true ) ;
459+ }
460+
461+ // Update UI
462+ UpdateBulkActionToolbar ( isInstalledView ) ;
463+ if ( isInstalledView )
464+ {
465+ UpdateLaunchButtonsState ( ) ;
466+ }
467+ }
468+
469+ private void DeselectAllModsInCurrentTab ( )
470+ {
471+ if ( tabControl == null )
472+ return ;
473+
474+ bool isInstalledView = tabControl . SelectedIndex == 0 ; // 0 = Installed, 1 = Store
475+
476+ // Get the appropriate panel and selection collections
477+ Panel targetPanel = isInstalledView ? panelInstalled : panelStore ;
478+ var bulkSelection = isInstalledView ? _bulkSelectedModIds : _bulkSelectedStoreModIds ;
479+
480+ if ( targetPanel == null )
481+ return ;
482+
483+ // Get all selected mod cards in the current tab
484+ var selectedCards = targetPanel . Controls . OfType < ModCard > ( )
485+ . Where ( card => card . IsSelectable && card . IsSelected && card . BoundMod != null )
486+ . ToList ( ) ;
487+
488+ // Deselect all cards
489+ foreach ( var card in selectedCards )
490+ {
491+ card . SetSelected ( false , true ) ;
492+ }
493+
494+ // Clear selection collections
495+ if ( isInstalledView )
496+ {
497+ // For installed tab, clear both bulk and launch selections
498+ _bulkSelectedModIds . Clear ( ) ;
499+ _selectedModIds . Clear ( ) ;
500+ PersistSelectedMods ( ) ;
501+ }
502+ else
503+ {
504+ // For store tab, only clear bulk selection
505+ _bulkSelectedStoreModIds . Clear ( ) ;
506+ }
507+
508+ // Update UI
509+ UpdateBulkActionToolbar ( isInstalledView ) ;
510+ if ( isInstalledView )
511+ {
512+ UpdateLaunchButtonsState ( ) ;
513+ }
514+ }
380515
381516 private void UpdateSidebarSelection ( )
382517 {
@@ -2097,6 +2232,12 @@ private void RefreshModCards()
20972232 {
20982233 // Reuse existing card from installed panel
20992234 installedCard = existingInstalledCard ;
2235+ // Update the card's version if it has changed (e.g., after an update)
2236+ if ( mod . InstalledVersion != null && installedCard . SelectedVersion != mod . InstalledVersion )
2237+ {
2238+ installedCard . UpdateVersion ( mod . InstalledVersion ) ;
2239+ installedCard . CheckForUpdate ( ) ;
2240+ }
21002241 // Only check for updates if mod was actually installed/uninstalled (not just filtering)
21012242 // Skip CheckForUpdate during filter changes to improve performance
21022243 }
@@ -5113,8 +5254,16 @@ private async Task<bool> FindCompatibleVersionsAsync(List<Mod> mods, List<Versio
51135254
51145255 UpdateStatus ( "Compatible versions installed. You can now launch the mods." ) ;
51155256
5257+ // Invalidate pending updates cache since mods were updated/downgraded
5258+ _cachedPendingUpdatesCount = null ;
5259+
51165260 // Refresh the UI to show updated mod versions
5117- SafeInvoke ( RefreshModCardsDebounced ) ;
5261+ SafeInvoke ( ( ) =>
5262+ {
5263+ RefreshModDetectionCache ( force : true ) ;
5264+ RefreshModCardsDebounced ( ) ;
5265+ UpdateStats ( ) ; // Explicitly update stats to refresh pending updates count
5266+ } ) ;
51185267
51195268 return true ;
51205269 }
@@ -6663,58 +6812,12 @@ private async void btnBulkUninstallInstalled_Click(object sender, EventArgs e)
66636812
66646813 private void btnBulkDeselectAllInstalled_Click ( object sender , EventArgs e )
66656814 {
6666- // Clear both bulk and launch selections for installed tab
6667- var modsToDeselect = _bulkSelectedModIds . Union ( _selectedModIds ) . ToList ( ) ;
6668-
6669- foreach ( var modId in modsToDeselect )
6670- {
6671- if ( _modCards . TryGetValue ( modId , out var card ) && card . IsSelectable )
6672- {
6673- card . SetSelected ( false , true ) ;
6674- }
6675- else
6676- {
6677- // Also check panel controls if not in dictionary
6678- var cardInInstalled = panelInstalled . Controls . OfType < ModCard > ( )
6679- . FirstOrDefault ( c => c . BoundMod != null &&
6680- string . Equals ( c . BoundMod . Id , modId , StringComparison . OrdinalIgnoreCase ) ) ;
6681- if ( cardInInstalled != null && cardInInstalled . IsSelectable )
6682- {
6683- cardInInstalled . SetSelected ( false , true ) ;
6684- }
6685- }
6686- }
6687-
6688- _bulkSelectedModIds . Clear ( ) ;
6689- _selectedModIds . Clear ( ) ;
6690- PersistSelectedMods ( ) ;
6691-
6692- UpdateBulkActionToolbar ( true ) ;
6693- UpdateLaunchButtonsState ( ) ;
6815+ DeselectAllModsInCurrentTab ( ) ;
66946816 }
66956817
66966818 private void btnBulkDeselectAllStore_Click ( object sender , EventArgs e )
66976819 {
6698- foreach ( var modId in _bulkSelectedStoreModIds . ToList ( ) )
6699- {
6700- if ( _modCards . TryGetValue ( modId , out var card ) && card . IsSelectable )
6701- {
6702- card . SetSelected ( false , true ) ;
6703- }
6704- else
6705- {
6706- // Also check panel controls if not in dictionary
6707- var cardInStore = panelStore . Controls . OfType < ModCard > ( )
6708- . FirstOrDefault ( c => c . BoundMod != null &&
6709- string . Equals ( c . BoundMod . Id , modId , StringComparison . OrdinalIgnoreCase ) ) ;
6710- if ( cardInStore != null && cardInStore . IsSelectable )
6711- {
6712- cardInStore . SetSelected ( false , true ) ;
6713- }
6714- }
6715- }
6716- _bulkSelectedStoreModIds . Clear ( ) ;
6717- UpdateBulkActionToolbar ( false ) ;
6820+ DeselectAllModsInCurrentTab ( ) ;
67186821 }
67196822
67206823 private void btnSidebarLaunchVanilla_Click ( object sender , EventArgs e )
@@ -7709,7 +7812,15 @@ private async Task UpdateModsAsync(List<Mod> modsToUpdate)
77097812 }
77107813 }
77117814
7712- SafeInvoke ( RefreshModCards ) ;
7815+ // Invalidate pending updates cache since mods were updated
7816+ _cachedPendingUpdatesCount = null ;
7817+
7818+ SafeInvoke ( ( ) =>
7819+ {
7820+ RefreshModDetectionCache ( force : true ) ;
7821+ RefreshModCards ( ) ;
7822+ UpdateStats ( ) ; // Explicitly update stats to refresh pending updates count
7823+ } ) ;
77137824
77147825 UpdateStatus ( "Updates completed!" ) ;
77157826 }
0 commit comments