Skip to content

Commit 1ad9eef

Browse files
authored
Merge pull request #142 from alejoe91/launcher
Implement launcher in desktop and web
2 parents b4c5e2c + 7ca6d53 commit 1ad9eef

File tree

10 files changed

+931
-82
lines changed

10 files changed

+931
-82
lines changed

spikeinterface_gui/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212

1313
from .version import version as __version__
1414

15-
from .main import run_mainwindow
15+
from .main import run_mainwindow, run_launcher
1616

spikeinterface_gui/backend_panel.py

Lines changed: 81 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,10 @@ def listen_setting_changes(view):
186186

187187
class PanelMainWindow:
188188

189-
def __init__(self, controller, layout_preset=None):
189+
def __init__(self, controller, layout_preset=None, layout=None):
190190
self.controller = controller
191191
self.layout_preset = layout_preset
192+
self.layout = layout
192193
self.verbose = controller.verbose
193194

194195
self.make_views()
@@ -251,7 +252,7 @@ def create_main_layout(self):
251252

252253
pn.extension("gridstack")
253254

254-
preset = get_layout_description(self.layout_preset)
255+
preset = get_layout_description(self.layout_preset, self.layout)
255256

256257
layout_zone = {}
257258
for zone, view_names in preset.items():
@@ -359,19 +360,89 @@ def update_visibility(self, event):
359360
view.notify_active_view_updated()
360361

361362

363+
def get_local_ip():
364+
"""
365+
Get the local IP address of the machine.
366+
"""
367+
import socket
368+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
369+
try:
370+
# Doesn't actually need to connect
371+
s.connect(('10.255.255.255', 1))
372+
ip = s.getsockname()[0]
373+
except Exception:
374+
ip = '127.0.0.1'
375+
finally:
376+
s.close()
377+
return ip
378+
379+
def find_free_port():
380+
"""
381+
Find a free port on the local machine.
382+
This is useful for starting a server without specifying a port.
362383
363-
def start_server(mainwindow, address="localhost", port=0):
384+
Returns
385+
-------
386+
int
387+
A free port number.
388+
"""
389+
import socket
390+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
391+
s.bind(('', 0)) # Bind to a free port assigned by the OS
392+
return s.getsockname()[1]
364393

365-
pn.config.sizing_mode = "stretch_width"
394+
def start_server(window_or_dict, address="localhost", port=0, **panel_kwargs):
395+
"""
396+
Start a Panel server with the main window layout.
397+
398+
Parameters
399+
----------
400+
window_or_dict : Panel window or dict
401+
The main window instance containing the layout to serve or a dictionary of
402+
windows to serve. If a dictionary is provided, it should contain the names
403+
of the views as keys and their corresponding Panel objects as values.
404+
address : str, optional
405+
The address to bind the server to. Defaults to "localhost".
406+
If "auto-ip" is specified, it will use the local IP address.
407+
port : int, optional
408+
The port to bind the server to. If 0, a free port will be found
409+
automatically. Defaults to 0.
410+
panel_kwargs : dict, optional
411+
Additional keyword arguments to pass to the Panel server.
412+
These can include options like `show`, `start`, `dev`, `autoreload`,
413+
and `websocket_origin`.
414+
"""
415+
if port == 0:
416+
port = find_free_port()
417+
print(f"Found available port: {port}")
366418

367-
# mainwindow.main_layout.servable()
368-
# TODO alessio : find automatically a port when port = 0
419+
if address == "auto-ip":
420+
address = get_local_ip()
369421

370422
if address != "localhost":
371423
websocket_origin = f"{address}:{port}"
372424
else:
373425
websocket_origin = None
374-
375-
server = pn.serve({"/": mainwindow.main_layout}, address=address, port=port,
376-
show=False, start=True, dev=True, autoreload=True,websocket_origin=websocket_origin,
377-
title="SpikeInterface GUI")
426+
427+
dev = panel_kwargs.get("dev", False)
428+
autoreload = panel_kwargs.get("autoreload", False)
429+
start = panel_kwargs.get("start", True)
430+
show = panel_kwargs.get("show", True)
431+
verbose = panel_kwargs.get("verbose", True)
432+
433+
if not isinstance(window_or_dict, dict):
434+
# If a single window is provided, convert it to a dictionary
435+
mainwindow = window_or_dict
436+
mainwindow.main_layout = mainwindow.main_layout if hasattr(mainwindow, 'main_layout') else mainwindow.layout
437+
window_dict = {"/": mainwindow.main_layout}
438+
else:
439+
# If a dictionary is provided, use it directly
440+
window_dict = window_or_dict
441+
442+
server = pn.serve(
443+
window_dict, address=address, port=port,
444+
show=show, start=start, dev=dev, autoreload=autoreload,
445+
websocket_origin=websocket_origin, verbose=verbose,
446+
title="SpikeInterface GUI"
447+
)
448+
return server, address, port, websocket_origin

spikeinterface_gui/backend_qt.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,15 @@ def listen_setting_changes(view):
135135

136136

137137
class QtMainWindow(QT.QMainWindow):
138-
def __init__(self, controller, parent=None, layout_preset=None):
138+
main_window_closed = QT.pyqtSignal(object)
139+
140+
def __init__(self, controller, parent=None, layout_preset=None, layout=None):
139141
QT.QMainWindow.__init__(self, parent)
140142

141143
self.controller = controller
142144
self.verbose = controller.verbose
143145
self.layout_preset = layout_preset
144-
146+
self.layout = layout
145147
self.make_views()
146148
self.create_main_layout()
147149

@@ -190,7 +192,7 @@ def create_main_layout(self):
190192

191193
self.setDockNestingEnabled(True)
192194

193-
preset = get_layout_description(self.layout_preset)
195+
preset = get_layout_description(self.layout_preset, self.layout)
194196

195197
widgets_zone = {}
196198
for zone, view_names in preset.items():
@@ -254,6 +256,11 @@ def create_main_layout(self):
254256
# make visible the first of each zone
255257
self.docks[view_name0].raise_()
256258

259+
# used by to tell the launcher this is closed
260+
def closeEvent(self, event):
261+
self.main_window_closed.emit(self)
262+
event.accept()
263+
257264

258265
class ViewWidget(QT.QWidget):
259266
def __init__(self, view_class, parent=None):

0 commit comments

Comments
 (0)