import subprocess from typing import Any import gi gi.require_version("Gtk", "3.0") from gi.repository import GObject, Gtk from sublime.config import AppConfiguration, ServerConfiguration from sublime.server import Server from sublime.ui.common import EditFormDialog, IconButton class EditServerDialog(EditFormDialog): entity_name: str = "Server" initial_size = (450, 250) text_fields = [ ("Name", "name", False), ("Server address", "server_address", False), ("Local network address", "local_network_address", False), ("Local network SSID", "local_network_ssid", False), ("Username", "username", False), ("Password", "password", True), ] boolean_fields = [ ("Play queue sync enabled", "sync_enabled"), ("Do not verify certificate", "disable_cert_verify"), ] def __init__(self, *args, **kwargs): test_server = Gtk.Button(label="Test Connection to Server") test_server.connect("clicked", self.on_test_server_clicked) open_in_browser = Gtk.Button(label="Open in Browser") open_in_browser.connect("clicked", self.on_open_in_browser_clicked) self.extra_buttons = [(test_server, None), (open_in_browser, None)] super().__init__(*args, **kwargs) def on_test_server_clicked(self, event: Any): # Instantiate the server. server_address = self.data["server_address"].get_text() server = Server( name=self.data["name"].get_text(), hostname=server_address, username=self.data["username"].get_text(), password=self.data["password"].get_text(), disable_cert_verify=self.data["disable_cert_verify"].get_active(), ) # Try to ping, and show a message box with whether or not it worked. try: server.ping() dialog = Gtk.MessageDialog( transient_for=self, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, text="Connection to server successful.", ) dialog.format_secondary_markup( f"Connection to {server_address} successful." ) except Exception as err: dialog = Gtk.MessageDialog( transient_for=self, message_type=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, text="Connection to server unsuccessful.", ) dialog.format_secondary_markup( f"Connection to {server_address} resulted in the following " f"error:\n\n{err}" ) dialog.run() dialog.destroy() def on_open_in_browser_clicked(self, event: Any): subprocess.call(["xdg-open", self.data["server_address"].get_text()]) class ConfigureServersDialog(Gtk.Dialog): __gsignals__ = { "server-list-changed": ( GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,), ), "connected-server-changed": ( GObject.SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,), ), } def __init__(self, parent: Any, config: AppConfiguration): Gtk.Dialog.__init__( self, title="Configure Servers", transient_for=parent, flags=0, add_buttons=(), ) self.server_configs = config.servers self.selected_server_index = config.current_server_index self.set_default_size(500, 300) # Flow box to hold the server list and the buttons. flowbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) # Server List self.server_list = Gtk.ListBox(activate_on_single_click=False) self.server_list.connect( "selected-rows-changed", self.server_list_on_selected_rows_changed ) self.server_list.connect("row-activated", self.on_server_list_activate) flowbox.pack_start(self.server_list, True, True, 10) button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) # Tuples: (button, action function, pack side, requires_selection) # Add all of the buttons to the button box. self.buttons = [ ( IconButton("document-edit-symbolic", label="Edit...", relief=True,), lambda e: self.on_edit_clicked(False), "start", True, ), ( IconButton("list-add-symbolic", label="Add...", relief=True,), lambda e: self.on_edit_clicked(True), "start", False, ), ( IconButton("list-remove-symbolic", label="Remove", relief=True,), self.on_remove_clicked, "start", True, ), ( IconButton("window-close-symbolic", label="Close", relief=True,), lambda _: self.close(), "end", False, ), ( IconButton( "network-transmit-receive-symbolic", label="Connect", relief=True, ), self.on_connect_clicked, "end", True, ), ] for button_cfg in self.buttons: btn, action, pack_end, requires_selection = button_cfg if pack_end == "end": button_box.pack_end(btn, False, True, 5) else: button_box.pack_start(btn, False, True, 5) btn.connect("clicked", action) flowbox.pack_end(button_box, False, False, 0) # Add the flowbox to the dialog and show the dialog. content_area = self.get_content_area() content_area.pack_start(flowbox, True, True, 10) self.show_all() self.refresh_server_list() self.server_list_on_selected_rows_changed(None) def refresh_server_list(self): # Clear out the list. for el in self.server_list: self.server_list.remove(el) # Add all of the rows for each of the servers. for i, config in enumerate(self.server_configs): box = Gtk.Box() image = Gtk.Image(margin=5) if i == self.selected_server_index: image.set_from_icon_name( "network-transmit-receive-symbolic", Gtk.IconSize.SMALL_TOOLBAR, ) box.add(image) server_name_label = Gtk.Label(label=config.name) server_name_label.set_halign(Gtk.Align.START) box.add(server_name_label) self.server_list.add(box) # Show them, and select the current server. self.show_all() if self.selected_server_index is not None and self.selected_server_index >= 0: self.server_list.select_row( self.server_list.get_row_at_index(self.selected_server_index) ) def on_remove_clicked(self, event: Any): selected = self.server_list.get_selected_row() if selected: del self.server_configs[selected.get_index()] self.refresh_server_list() self.emit("server-list-changed", self.server_configs) def on_edit_clicked(self, add: bool): if add: dialog = EditServerDialog(self) else: selected_index = self.server_list.get_selected_row().get_index() dialog = EditServerDialog(self, self.server_configs[selected_index]) result = dialog.run() if result == Gtk.ResponseType.OK: # Create a new server configuration to use. new_config = ServerConfiguration( name=dialog.data["name"].get_text(), server_address=dialog.data["server_address"].get_text(), local_network_address=dialog.data["local_network_address"].get_text(), local_network_ssid=dialog.data["local_network_ssid"].get_text(), username=dialog.data["username"].get_text(), password=dialog.data["password"].get_text(), sync_enabled=dialog.data["sync_enabled"].get_active(), disable_cert_verify=dialog.data["disable_cert_verify"].get_active(), ) if add: self.server_configs.append(new_config) else: self.server_configs[selected_index] = new_config self.refresh_server_list() self.emit("server-list-changed", self.server_configs) dialog.destroy() def on_server_list_activate(self, *args): self.on_connect_clicked(None) def on_connect_clicked(self, event: Any): selected_index = self.server_list.get_selected_row().get_index() self.emit("connected-server-changed", selected_index) self.close() def server_list_on_selected_rows_changed(self, event: Any): # Update the state of the buttons depending on whether or not a row is # selected in the server list. has_selection = self.server_list.get_selected_row() for button, *_, requires_selection in self.buttons: button.set_sensitive(bool(not requires_selection or has_selection))