@@ -525,7 +525,7 @@ def reset_look_ui(self):
525525 self .style_combo .set_active (len (self .styles .keys ()))
526526 self .ui_ready = True
527527
528- def on_customize_button_clicked (self , button ):
528+ def on_customize_button_clicked (self , widget ):
529529 self .set_button_chooser (self .icon_chooser , self .settings .get_string ("icon-theme" ), 'icons' , 'icons' , ICON_SIZE )
530530 self .set_button_chooser (self .cursor_chooser , self .settings .get_string ("cursor-theme" ), 'icons' , 'cursors' , 32 )
531531 self .set_button_chooser (self .theme_chooser , self .settings .get_string ("gtk-theme" ), 'themes' , 'gtk-3.0' , 35 )
@@ -640,11 +640,60 @@ def refresh_choosers(self):
640640 self .refresh_chooser (chooser , path_suffix , themes , callback )
641641 self .refreshing = False
642642
643+ def get_theme_category (self , theme_name , theme_type ):
644+ parts = theme_name .split ('-' )
645+
646+ if theme_type == 'cursor' :
647+ # For cursors - first part of the name
648+ return (parts [0 ], "Light" )
649+
650+ elif theme_type in ['gtk' , 'icon' ]:
651+ # Basic category is always the first part of the name
652+ base_category = parts [0 ]
653+
654+ # Exception: if the second part is a single letter, it's part of the category (e.g., Mint-X, Mint-Y)
655+ if len (parts ) >= 2 and len (parts [1 ]) == 1 :
656+ base_category = f"{ parts [0 ]} -{ parts [1 ]} "
657+
658+ # Determine variant (light/dark/darker)
659+ theme_lower = theme_name .lower ()
660+ if 'darker' in theme_lower :
661+ variant = "Darker"
662+ elif 'dark' in theme_lower :
663+ variant = "Dark"
664+ else :
665+ variant = "Light"
666+
667+ return (base_category , variant )
668+
669+ elif theme_type == 'cinnamon' :
670+ # For desktop - basic category is the first part of the name
671+ base_category = parts [0 ]
672+
673+ # Exception: if the second part is a single letter, it's part of the category
674+ if len (parts ) >= 2 and len (parts [1 ]) == 1 :
675+ base_category = f"{ parts [0 ]} -{ parts [1 ]} "
676+
677+ # Determine variant
678+ theme_lower = theme_name .lower ()
679+ if 'darker' in theme_lower :
680+ variant = "Darker"
681+ elif 'dark' in theme_lower :
682+ variant = "Dark"
683+ else :
684+ variant = "Light"
685+
686+ return (base_category , variant )
687+
688+ return ("Other" , "Light" )
689+
643690 def refresh_chooser (self , chooser , path_suffix , themes , callback ):
644691 inc = 1.0
645692 if len (themes ) > 0 :
646693 inc = 1.0 / len (themes )
647694
695+ variant_sort_order = {"Light" : 0 , "Darker" : 1 , "Dark" : 2 }
696+
648697 if path_suffix == 'icons' :
649698 cache_folder = GLib .get_user_cache_dir () + '/cs_themes/'
650699 icon_cache_path = os .path .join (cache_folder , 'icons' )
@@ -662,9 +711,95 @@ def refresh_chooser(self, chooser, path_suffix, themes, callback):
662711 icon_paths [theme_name ] = icon_path
663712
664713 dump = False
714+
715+ # Collect all themes with their categories and variants
716+ categorized_themes = []
665717 for theme in themes :
666- theme_path = None
718+ category , variant = self .get_theme_category (theme , 'icon' )
719+ categorized_themes .append ({'name' : theme , 'category' : category , 'variant' : variant })
720+
721+ # Count themes per category
722+ category_counts = {}
723+ for theme_info in categorized_themes :
724+ category_counts [theme_info ['category' ]] = category_counts .get (theme_info ['category' ], 0 ) + 1
725+
726+ # Separate single-item categories
727+ single_item_category_themes = []
728+ multi_item_category_themes = []
729+
730+ for theme_info in categorized_themes :
731+ if category_counts [theme_info ['category' ]] == 1 :
732+ single_item_category_themes .append (theme_info )
733+ else :
734+ multi_item_category_themes .append (theme_info )
735+
736+ # Sort both lists
737+ single_item_category_themes .sort (key = lambda x : (variant_sort_order .get (x ['variant' ], 3 ), x ['name' ]))
738+ multi_item_category_themes .sort (key = lambda x : (x ['category' ], variant_sort_order .get (x ['variant' ], 3 ), x ['name' ]))
739+
740+ # Reset column position at the start
741+ chooser .col = 0
742+ chooser .row = 0
743+
744+ # Display single-item category themes first, under an "Other Themes" label
745+ if single_item_category_themes :
746+ chooser .add_separator (_ ("Other Themes" ))
747+ # Add an additional blank separator after the category label
748+ chooser .add_separator ()
749+ for theme_info in single_item_category_themes :
750+ theme = theme_info ['name' ]
751+ theme_path = None
752+ if theme in icon_paths :
753+ for theme_folder in ICON_FOLDERS :
754+ possible_path = os .path .join (theme_folder , icon_paths [theme ])
755+ if os .path .exists (possible_path ):
756+ theme_path = possible_path
757+ break
758+
759+ if theme_path is None :
760+ icon_theme = Gtk .IconTheme ()
761+ icon_theme .set_custom_theme (theme )
762+ folder = icon_theme .lookup_icon ('folder' , ICON_SIZE , Gtk .IconLookupFlags .FORCE_SVG )
763+ if folder :
764+ theme_path = folder .get_filename ()
765+ for theme_folder in ICON_FOLDERS :
766+ if os .path .commonpath ([theme_folder , theme_path ]) == theme_folder :
767+ icon_paths [theme ] = os .path .relpath (theme_path , start = theme_folder )
768+ break
769+ dump = True
770+
771+ if theme_path is None :
772+ continue
667773
774+ if os .path .exists (theme_path ):
775+ chooser .add_picture (theme_path , callback , title = theme , id = theme )
776+ GLib .timeout_add (5 , self .increment_progress , (chooser , inc ))
777+
778+ # Add a blank separator if both single and multi-item categories exist
779+ if single_item_category_themes and multi_item_category_themes :
780+ chooser .add_separator ()
781+
782+ current_category = None
783+ current_variant = None
784+ # Display multi-item category themes
785+ for theme_info in multi_item_category_themes :
786+ category = theme_info ['category' ]
787+ variant = theme_info ['variant' ]
788+ theme = theme_info ['name' ]
789+
790+ if current_category != category :
791+ if current_category is not None :
792+ chooser .add_separator () # Blank separator between categories
793+ current_category = category
794+ current_variant = None # Reset variant when category changes
795+ chooser .add_separator (category ) # Category name separator
796+ # Add an additional blank separator after the category label
797+ chooser .add_separator ()
798+
799+ # Update current_variant, no separator here if it's just a variant change
800+ current_variant = variant
801+
802+ theme_path = None
668803 if theme in icon_paths :
669804 # loop through all possible locations until we find a match
670805 # (user folders should override system ones)
@@ -680,14 +815,12 @@ def refresh_chooser(self, chooser, path_suffix, themes, callback):
680815 folder = icon_theme .lookup_icon ('folder' , ICON_SIZE , Gtk .IconLookupFlags .FORCE_SVG )
681816 if folder :
682817 theme_path = folder .get_filename ()
683-
684818 # we need to get the relative path for storage
685819 for theme_folder in ICON_FOLDERS :
686820 if os .path .commonpath ([theme_folder , theme_path ]) == theme_folder :
687821 icon_paths [theme ] = os .path .relpath (theme_path , start = theme_folder )
688822 break
689-
690- dump = True
823+ dump = True
691824
692825 if theme_path is None :
693826 continue
@@ -701,24 +834,98 @@ def refresh_chooser(self, chooser, path_suffix, themes, callback):
701834 os .mkdir (cache_folder )
702835
703836 with open (icon_cache_path , 'w' ) as cache_file :
704- for theme_name , icon_path in icon_paths .items ():
705- cache_file .write ('%s:%s\n ' % (theme_name , icon_path ))
837+ for theme_name , icon_path_val in icon_paths .items (): # Renamed icon_path to avoid conflict
838+ cache_file .write ('%s:%s\\ n' % (theme_name , icon_path_val ))
706839
707840 else :
708841 if path_suffix == "cinnamon" :
709842 chooser .add_picture ("/usr/share/cinnamon/theme/thumbnail.png" , callback , title = "cinnamon" , id = "cinnamon" )
710843 if path_suffix in ["gtk-3.0" , "cinnamon" ]:
711- themes = sorted (themes , key = lambda t : (not t [1 ].startswith (GLib .get_home_dir ())))
844+ # Sort themes by user-installed first, then alphabetically
845+ themes = sorted (themes , key = lambda t : (not t [1 ].startswith (GLib .get_home_dir ()), t [0 ].lower ()))
846+
847+
848+ # Collect all themes with their categories and variants
849+ categorized_themes = []
850+ for theme_data in themes : # theme_data is a tuple (name, path)
851+ name = theme_data [0 ]
852+ path = theme_data [1 ]
853+ theme_type = 'gtk' if path_suffix == 'gtk-3.0' else 'cinnamon'
854+ category , variant = self .get_theme_category (name , theme_type )
855+ categorized_themes .append ({'name' : name , 'path' : path , 'category' : category , 'variant' : variant , 'original_tuple' : theme_data })
856+
857+ # Count themes per category
858+ category_counts = {}
859+ for theme_info in categorized_themes :
860+ category_counts [theme_info ['category' ]] = category_counts .get (theme_info ['category' ], 0 ) + 1
861+
862+ single_item_category_themes = []
863+ multi_item_category_themes = []
864+
865+ for theme_info in categorized_themes :
866+ if category_counts [theme_info ['category' ]] == 1 :
867+ single_item_category_themes .append (theme_info )
868+ else :
869+ multi_item_category_themes .append (theme_info )
870+
871+ # Sort both lists
872+ single_item_category_themes .sort (key = lambda x : (variant_sort_order .get (x ['variant' ], 3 ), x ['name' ].lower ()))
873+ multi_item_category_themes .sort (key = lambda x : (x ['category' ].lower (), variant_sort_order .get (x ['variant' ], 3 ), x ['name' ].lower ()))
874+
875+ # Reset column position at the start
876+ chooser .col = 0
877+ chooser .row = 0
878+
879+ # Display single-item category themes first, under an "Other Themes" label
880+ if single_item_category_themes :
881+ chooser .add_separator (_ ("Other Themes" ))
882+ # Add an additional blank separator after the category label
883+ chooser .add_separator ()
884+ for theme_info in single_item_category_themes :
885+ theme_name = theme_info ['name' ]
886+ theme_path_val = theme_info ['path' ] # Renamed theme_path to avoid conflict
887+ try :
888+ for path_option in ["%s/%s/%s/thumbnail.png" % (theme_path_val , theme_name , path_suffix ),
889+ "/usr/share/cinnamon/thumbnails/%s/%s.png" % (path_suffix , theme_name ),
890+ "/usr/share/cinnamon/thumbnails/%s/unknown.png" % path_suffix ]:
891+ if os .path .exists (path_option ):
892+ chooser .add_picture (path_option , callback , title = theme_name , id = theme_name )
893+ break
894+ except :
895+ chooser .add_picture ("/usr/share/cinnamon/thumbnails/%s/unknown.png" % path_suffix , callback , title = theme_name , id = theme_name )
896+ GLib .timeout_add (5 , self .increment_progress , (chooser , inc ))
897+
898+ # Add a blank separator if both single and multi-item categories exist
899+ if single_item_category_themes and multi_item_category_themes :
900+ chooser .add_separator ()
901+
902+ current_category = None
903+ current_variant = None
904+ # Display multi-item category themes
905+ for theme_info in multi_item_category_themes :
906+ category = theme_info ['category' ]
907+ variant = theme_info ['variant' ]
908+ theme_name = theme_info ['name' ]
909+ theme_path_val = theme_info ['path' ] # Renamed theme_path to avoid conflict
910+
911+ if current_category != category :
912+ if current_category is not None :
913+ chooser .add_separator () # Blank separator between categories
914+ current_category = category
915+ current_variant = None # Reset variant when category changes
916+ chooser .add_separator (category ) # Category name separator
917+ # Add an additional blank separator after the category label
918+ chooser .add_separator ()
919+
920+ # Update current_variant, no separator here if it's just a variant change
921+ current_variant = variant
712922
713- for theme in themes :
714- theme_name = theme [0 ]
715- theme_path = theme [1 ]
716923 try :
717- for path in ["%s/%s/%s/thumbnail.png" % (theme_path , theme_name , path_suffix ),
924+ for path_option in ["%s/%s/%s/thumbnail.png" % (theme_path_val , theme_name , path_suffix ),
718925 "/usr/share/cinnamon/thumbnails/%s/%s.png" % (path_suffix , theme_name ),
719926 "/usr/share/cinnamon/thumbnails/%s/unknown.png" % path_suffix ]:
720- if os .path .exists (path ):
721- chooser .add_picture (path , callback , title = theme_name , id = theme_name )
927+ if os .path .exists (path_option ):
928+ chooser .add_picture (path_option , callback , title = theme_name , id = theme_name )
722929 break
723930 except :
724931 chooser .add_picture ("/usr/share/cinnamon/thumbnails/%s/unknown.png" % path_suffix , callback , title = theme_name , id = theme_name )
0 commit comments