88from typing import Any
99
1010import pandas as pd
11- from PyQt5 .QtCore import QUrl , pyqtSignal
11+ from PyQt5 .QtCore import QUrl , QUrlQuery , pyqtSignal
1212from PyQt5 .QtWebChannel import QWebChannel
1313from PyQt5 .QtWebEngineWidgets import QWebEngineSettings , QWebEngineView
1414from PyQt5 .QtWidgets import QVBoxLayout , QWidget
@@ -32,8 +32,29 @@ class NicePoolWidget(QWidget):
3232 data_replaced = pyqtSignal ()
3333 error_occurred = pyqtSignal (str )
3434
35- def __init__ (self , parent : QWidget | None = None ) -> None :
35+ def __init__ (
36+ self ,
37+ parent : QWidget | None = None ,
38+ * ,
39+ theme : str = "dark" ,
40+ controls_collapsed : bool = False ,
41+ preset_editing_visible : bool = True ,
42+ ) -> None :
43+ """Create a NicePool host with display options applied before first render.
44+
45+ Args:
46+ parent: Optional parent widget.
47+ theme: Initial ``dark`` or ``light`` theme.
48+ controls_collapsed: Whether the controls panel starts collapsed.
49+ preset_editing_visible: Whether preset editing starts visible.
50+
51+ Raises:
52+ ValueError: If ``theme`` is not supported.
53+ RuntimeError: If the packaged browser assets are unavailable.
54+ """
3655 super ().__init__ (parent )
56+ if theme not in {"dark" , "light" }:
57+ raise ValueError ("theme must be 'dark' or 'light'" )
3758 static_directory = Path (__file__ ).resolve ().parent / "static"
3859 html_path = static_directory / "index.html"
3960 script_path = static_directory / "nicepool-pyqt5.js"
@@ -51,6 +72,7 @@ def __init__(self, parent: QWidget | None = None) -> None:
5172 self ._pending : dict [int , tuple [str , ResultCallback | None , ErrorCallback | None ]] = {}
5273
5374 self .web_view = QWebEngineView (self )
75+ self .web_view .hide ()
5476 self .web_view .settings ().setAttribute (
5577 QWebEngineSettings .JavascriptCanAccessClipboard ,
5678 True ,
@@ -67,7 +89,18 @@ def __init__(self, parent: QWidget | None = None) -> None:
6789 self ._bridge .message_received .connect (self ._receive_browser_message )
6890 self ._bridge .invalid_message .connect (self .error_occurred )
6991 self .web_view .loadFinished .connect (self ._load_finished )
70- self .web_view .load (QUrl .fromLocalFile (str (html_path )))
92+ page_url = QUrl .fromLocalFile (str (html_path ))
93+ query = QUrlQuery ()
94+ query .addQueryItem ("theme" , theme )
95+ query .addQueryItem (
96+ "controlsCollapsed" , "true" if controls_collapsed else "false"
97+ )
98+ query .addQueryItem (
99+ "presetEditingVisible" ,
100+ "true" if preset_editing_visible else "false" ,
101+ )
102+ page_url .setQuery (query )
103+ self .web_view .load (page_url )
71104
72105 @property
73106 def is_ready (self ) -> bool :
@@ -108,6 +141,11 @@ def _receive_browser_message(self, message: dict[str, Any]) -> None:
108141 return
109142 self ._ready = True
110143 self .ready_changed .emit (True )
144+ if not any (
145+ message ["name" ] in {"setData" , "initializeData" }
146+ for message in self ._queued_messages
147+ ):
148+ self .web_view .show ()
111149 self ._dispatch_next ()
112150 return
113151 if kind == "response" :
@@ -151,6 +189,7 @@ def _receive_event(self, message: dict[str, Any]) -> None:
151189 elif name == "themeChanged" :
152190 self .theme_changed .emit (str (detail ))
153191 elif name == "dataReset" :
192+ self .web_view .show ()
154193 self .data_reset .emit ()
155194 elif name == "dataReplaced" :
156195 self .data_replaced .emit ()
@@ -164,16 +203,28 @@ def set_dataframe(
164203 row_id_column : str ,
165204 schema : Sequence [Mapping [str , Any ]] | None = None ,
166205 pre_filter_columns : Sequence [str ] | None = None ,
206+ preset_definitions : Sequence [Mapping [str , Any ]] | None = None ,
207+ default_preset : str | None = None ,
167208 ) -> None :
168209 """Atomically replace NicePool data from a pandas DataFrame."""
210+ dataset = dataframe_to_dataset (
211+ dataframe ,
212+ row_id_column = row_id_column ,
213+ schema = schema ,
214+ pre_filter_columns = pre_filter_columns ,
215+ )
216+ if preset_definitions is None :
217+ if default_preset is not None :
218+ raise ValueError ("default_preset requires preset_definitions" )
219+ self ._send ("setData" , dataset )
220+ return
169221 self ._send (
170- "setData" ,
171- dataframe_to_dataset (
172- dataframe ,
173- row_id_column = row_id_column ,
174- schema = schema ,
175- pre_filter_columns = pre_filter_columns ,
176- ),
222+ "initializeData" ,
223+ {
224+ "dataset" : dataset ,
225+ "presetDefinitions" : [dict (item ) for item in preset_definitions ],
226+ "defaultPresetName" : default_preset ,
227+ },
177228 )
178229
179230 def set_records (
@@ -183,16 +234,28 @@ def set_records(
183234 row_id_column : str ,
184235 schema : Sequence [Mapping [str , Any ]] | None = None ,
185236 pre_filter_columns : Sequence [str ] | None = None ,
237+ preset_definitions : Sequence [Mapping [str , Any ]] | None = None ,
238+ default_preset : str | None = None ,
186239 ) -> None :
187240 """Atomically replace NicePool data from rectangular records."""
241+ dataset = records_to_dataset (
242+ records ,
243+ row_id_column = row_id_column ,
244+ schema = schema ,
245+ pre_filter_columns = pre_filter_columns ,
246+ )
247+ if preset_definitions is None :
248+ if default_preset is not None :
249+ raise ValueError ("default_preset requires preset_definitions" )
250+ self ._send ("setData" , dataset )
251+ return
188252 self ._send (
189- "setData" ,
190- records_to_dataset (
191- records ,
192- row_id_column = row_id_column ,
193- schema = schema ,
194- pre_filter_columns = pre_filter_columns ,
195- ),
253+ "initializeData" ,
254+ {
255+ "dataset" : dataset ,
256+ "presetDefinitions" : [dict (item ) for item in preset_definitions ],
257+ "defaultPresetName" : default_preset ,
258+ },
196259 )
197260
198261 def replace_dataframe (
0 commit comments