1414from typing import Optional , TYPE_CHECKING
1515from datetime import datetime
1616
17- from .theme import Theme
17+ from .theme import Theme , DARK , build_qss , set_role
1818from .tabs import (
1919 PoliciesTab , AgentsTab , HistoryTab , WalletTab , NetworkTab , LogTab
2020)
@@ -191,6 +191,9 @@ def __init__(self, core: "MultiClaw"):
191191 self .setup_signing_service ()
192192 self .setup_event_subscriptions ()
193193
194+ # Apply theme (default to light)
195+ self .apply_theme (self ._settings .get ("theme" , "light" ))
196+
194197 # Initialize status indicators
195198 self .update_status_indicators ()
196199
@@ -199,7 +202,7 @@ def __init__(self, core: "MultiClaw"):
199202 QTimer .singleShot (0 , self .hide if self ._settings .get ("minimize_to_tray" , False ) else self .showMinimized )
200203
201204 # Auto-start server if enabled
202- if self ._settings .get ("auto_start_server" , False ):
205+ if self ._settings .get ("auto_start_server" , True ):
203206 QTimer .singleShot (500 , self .network_tab .toggle_server )
204207
205208 def _get_agents_for_address (self , wallet_address : str ) -> list :
@@ -312,28 +315,17 @@ def update_status_indicators(self):
312315 """Update all three status indicators in the header."""
313316 # Wallet indicator - green only if we have wallets AND unlocked
314317 has_wallets = len (self .wallet_tab .get_wallet_list ()) > 0
315- if has_wallets and self .wallet_tab .is_unlocked :
316- self .wallet_indicator .setStyleSheet (f"color: { Theme .LIME } ; font-size: 12px;" )
317- self .wallet_label .setStyleSheet (f"color: { Theme .LIME } ;" )
318- else :
319- self .wallet_indicator .setStyleSheet (f"color: { Theme .RUST } ; font-size: 12px;" )
320- self .wallet_label .setStyleSheet (f"color: { Theme .RUST } ;" )
318+ wallet_on = has_wallets and self .wallet_tab .is_unlocked
319+ set_role (self .wallet_indicator , status = "on" if wallet_on else "off" )
320+ set_role (self .wallet_label , status = "on" if wallet_on else "off" )
321321
322322 # Server indicator
323- if self .server_running :
324- self .server_indicator .setStyleSheet (f"color: { Theme .LIME } ; font-size: 12px;" )
325- self .server_label .setStyleSheet (f"color: { Theme .LIME } ;" )
326- else :
327- self .server_indicator .setStyleSheet (f"color: { Theme .RUST } ; font-size: 12px;" )
328- self .server_label .setStyleSheet (f"color: { Theme .RUST } ;" )
323+ set_role (self .server_indicator , status = "on" if self .server_running else "off" )
324+ set_role (self .server_label , status = "on" if self .server_running else "off" )
329325
330326 # Signing indicator
331- if self .signing_enabled :
332- self .signing_indicator .setStyleSheet (f"color: { Theme .LIME } ; font-size: 12px;" )
333- self .signing_label .setStyleSheet (f"color: { Theme .LIME } ;" )
334- else :
335- self .signing_indicator .setStyleSheet (f"color: { Theme .RUST } ; font-size: 12px;" )
336- self .signing_label .setStyleSheet (f"color: { Theme .RUST } ;" )
327+ set_role (self .signing_indicator , status = "on" if self .signing_enabled else "off" )
328+ set_role (self .signing_label , status = "on" if self .signing_enabled else "off" )
337329
338330 def _load_settings (self ) -> dict :
339331 """Load GUI-specific settings from disk.
@@ -366,6 +358,16 @@ def _save_settings(self):
366358 except Exception as e :
367359 logging .getLogger (__name__ ).error (f"Failed to save GUI settings: { e } " )
368360
361+ def apply_theme (self , theme_name : str = "light" ):
362+ """Apply the selected theme (light or dark) to the application."""
363+ from .theme import LIGHT , DARK , build_qss
364+ palette = DARK if theme_name == "dark" else LIGHT
365+ app = QApplication .instance ()
366+ if app :
367+ app .setStyleSheet (build_qss (palette ))
368+ self ._settings ["theme" ] = theme_name
369+ self ._save_settings ()
370+
369371 def update_status (self ):
370372 """Update status bar."""
371373 if self .core .is_server_running ():
@@ -375,7 +377,7 @@ def update_status(self):
375377
376378 def create_header (self ) -> QFrame :
377379 header = QFrame ()
378- header .setStyleSheet ( f"background-color: { Theme . BLACK } ;" )
380+ header .setObjectName ( "header" ) # Styled via QSS #header selector
379381 header .setFixedHeight (100 )
380382
381383 header_layout = QHBoxLayout (header )
@@ -390,7 +392,7 @@ def create_header(self) -> QFrame:
390392 logo_label .setPixmap (scaled )
391393 else :
392394 logo_label .setText ("MULTICLAW" )
393- logo_label .setStyleSheet ( f"color: { Theme . LIME } ; font-weight: bold; font-size: 16px;" )
395+ logo_label .setObjectName ( "logo" ) # Styled via QSS #logo selector
394396
395397 header_layout .addWidget (logo_label , alignment = Qt .AlignmentFlag .AlignVCenter )
396398 header_layout .addStretch ()
@@ -403,12 +405,12 @@ def create_header(self) -> QFrame:
403405 wallet_row = QHBoxLayout ()
404406 wallet_row .setSpacing (4 )
405407 self .wallet_indicator = QLabel ("●" )
406- self .wallet_indicator .setStyleSheet ( f"color: { Theme . RUST } ; font-size: 12px; " )
408+ self .wallet_indicator .setProperty ( "status" , "off " )
407409 self .wallet_indicator .setFixedWidth (12 )
408410 wallet_row .addWidget (self .wallet_indicator )
409411 self .wallet_label = QLabel ("Wallet" )
410412 self .wallet_label .setFont (QFont (Theme .MONO_FONT , 9 ))
411- self .wallet_label .setStyleSheet ( f"color: { Theme . RUST } ; " )
413+ self .wallet_label .setProperty ( "status" , "off " )
412414 self .wallet_label .setCursor (Qt .CursorShape .PointingHandCursor )
413415 self .wallet_label .mousePressEvent = lambda e : self .on_wallet_indicator_clicked ()
414416 wallet_row .addWidget (self .wallet_label )
@@ -418,27 +420,27 @@ def create_header(self) -> QFrame:
418420 server_row = QHBoxLayout ()
419421 server_row .setSpacing (4 )
420422 self .server_indicator = QLabel ("●" )
421- self .server_indicator .setStyleSheet ( f"color: { Theme . RUST } ; font-size: 12px; " )
423+ self .server_indicator .setProperty ( "status" , "off " )
422424 self .server_indicator .setFixedWidth (12 )
423425 server_row .addWidget (self .server_indicator )
424426 self .server_label = QLabel ("Server" )
425427 self .server_label .setFont (QFont (Theme .MONO_FONT , 9 ))
426- self .server_label .setStyleSheet ( f"color: { Theme . RUST } ; " )
428+ self .server_label .setProperty ( "status" , "off " )
427429 self .server_label .setCursor (Qt .CursorShape .PointingHandCursor )
428430 self .server_label .mousePressEvent = lambda e : self .on_server_indicator_clicked ()
429431 server_row .addWidget (self .server_label )
430432 status_layout .addLayout (server_row )
431433
432- # Signing indicator
434+ # Signing indicator (starts enabled)
433435 signing_row = QHBoxLayout ()
434436 signing_row .setSpacing (4 )
435437 self .signing_indicator = QLabel ("●" )
436- self .signing_indicator .setStyleSheet ( f"color: { Theme . LIME } ; font-size: 12px; " )
438+ self .signing_indicator .setProperty ( "status" , "on " )
437439 self .signing_indicator .setFixedWidth (12 )
438440 signing_row .addWidget (self .signing_indicator )
439441 self .signing_label = QLabel ("Signing" )
440442 self .signing_label .setFont (QFont (Theme .MONO_FONT , 9 ))
441- self .signing_label .setStyleSheet ( f"color: { Theme . LIME } ; " )
443+ self .signing_label .setProperty ( "status" , "on " )
442444 self .signing_label .setCursor (Qt .CursorShape .PointingHandCursor )
443445 self .signing_label .mousePressEvent = lambda e : self .toggle_global_signing ()
444446 signing_row .addWidget (self .signing_label )
@@ -450,13 +452,7 @@ def create_header(self) -> QFrame:
450452 self .activity_log = QTextEdit ()
451453 self .activity_log .setReadOnly (True )
452454 self .activity_log .setFont (QFont (Theme .MONO_FONT , 9 ))
453- self .activity_log .setStyleSheet (f"""
454- QTextEdit {{
455- background-color: transparent;
456- border: none;
457- color: { Theme .LIME_DIM } ;
458- }}
459- """ )
455+ # Activity log is styled via QSS: QWidget#header QTextEdit
460456 self .activity_log .setVerticalScrollBarPolicy (Qt .ScrollBarPolicy .ScrollBarAlwaysOff )
461457 self .activity_log .setHorizontalScrollBarPolicy (Qt .ScrollBarPolicy .ScrollBarAlwaysOff )
462458 self .activity_log .setMinimumWidth (450 )
@@ -841,6 +837,7 @@ def show_settings(self):
841837 dialog = SettingsDialog (self ._settings , self )
842838 if dialog .exec () == QDialog .DialogCode .Accepted and dialog .has_changes ():
843839 new_settings = dialog .get_settings ()
840+ old_theme = self ._settings .get ("theme" , "light" )
844841 self ._settings .update (new_settings )
845842 self ._save_settings ()
846843
@@ -854,6 +851,11 @@ def show_settings(self):
854851 replay_window = new_settings .get ("replay_window_seconds" , 300 )
855852 self .core .set_max_request_age (replay_window )
856853
854+ # Apply theme if changed
855+ new_theme = new_settings .get ("theme" , "light" )
856+ if new_theme != old_theme :
857+ self .apply_theme (new_theme )
858+
857859 def register_agent (self ):
858860 """Open the agent registration dialog."""
859861 self .tabs .setCurrentWidget (self .agents_tab )
@@ -906,12 +908,12 @@ def show_about(self):
906908
907909 # Tagline
908910 tagline = QLabel ("MultiClaw, by Primer" )
909- tagline .setStyleSheet ( f"font-style: italic; " )
911+ tagline .setProperty ( "role" , "hint " )
910912 layout .addWidget (tagline )
911913
912914 # Version
913915 version = QLabel (f"Version { __version__ } " )
914- version .setStyleSheet ( f"color: { Theme . CHARCOAL } ; " )
916+ version .setProperty ( "role" , "muted " )
915917 layout .addWidget (version )
916918
917919 layout .addSpacing (4 )
@@ -992,7 +994,7 @@ def show_quick_start(self):
992994
993995 if hint :
994996 hint_label = QLabel (f" <i>{ hint } </i>" )
995- hint_label .setStyleSheet ( f"color: { Theme . CHARCOAL } ; " )
997+ hint_label .setProperty ( "role" , "muted " )
996998 layout .addWidget (hint_label )
997999
9981000 layout .addSpacing (12 )
0 commit comments