diff --git a/data/gtk.glade b/data/gtk.glade new file mode 100644 index 0000000..999cabb --- /dev/null +++ b/data/gtk.glade @@ -0,0 +1,773 @@ + + + + + + 100 + 1000 + 150 + 1 + 10 + + + 1024 + 9999 + 6600 + 1 + 100 + + + False + + + + + + True + False + vertical + + + True + False + + + False + 10 + 10 + vertical + True + + + + + False + + + False + False + 0 + + + + + False + + + True + False + + + False + True + 0 + + + + + False + False + 0 + + + + + + + False + True + 0 + + + + + True + False + + + True + False + vertical + + + True + False + + + True + False + 5 + True + True + + + True + False + + + True + True + + + + + + + + + True + True + 0 + + + + + True + False + 5 + vertical + + + False + True + end + 1 + + + + + 0 + 0 + 8 + + + + + True + True + localhost + Host + + + + 1 + 1 + + + + + True + True + Image Directory + + + 1 + 7 + + + + + True + True + False + Password + password + + + 1 + 5 + + + + + True + True + server-port-adjustment + 6600 + + + 1 + 3 + + + + + True + False + start + Host: + + + 1 + 0 + + + + + True + False + start + Port: + + + 1 + 2 + + + + + True + False + start + Password: + + + 1 + 4 + + + + + True + False + start + Image Dir: + + + 1 + 6 + + + + + True + False + 0 + + + + + True + False + 0 + + + + + connection + Server + + + + + True + False + vertical + + + True + False + True + + + True + True + False + False + False + + + True + False + + + True + False + + + True + False + gtk-missing-image + + + + + + + + + True + True + 10 + 0 + + + + + True + True + start + vertical + 1 + False + + + + + True + True + 10 + end + 1 + + + + + True + True + 10 + 0 + + + + + True + False + 5 + 5 + 5 + True + True + + + True + False + Album + + + 0 + 0 + + + + + True + False + Date + + + 0 + 1 + + + + + True + False + Artist + + + 0 + 2 + + + + + False + True + 10 + 1 + + + + + cover + Cover + 1 + + + + + True + False + vertical + + + True + False + False + + + True + False + True + edit-clear + + + + False + True + + + + + + False + True + 0 + + + + + True + True + + + True + True + 0 + horizontal + 3 + 0 + 0 + 1 + 5 + + + + + + + True + True + 1 + + + + + playlist + Playlist + 2 + + + + + True + False + vertical + + + True + False + none + + + True + False + 0.5 + + + + + False + True + 0 + + + + + True + False + 0 + + + True + False + + + True + False + True + view-refresh + + + + False + True + + + + + + + + True + True + edit-find-symbolic + False + False + search library + + + + + + True + False + + + True + False + + + 100 + True + True + library-scale-adjustment + off + off + False + 0 + 0 + False + + + + + + + False + True + + + + + True + False + + + True + False + + + + + + False + True + + + + + True + False + True + view-sort-descending + True + + + + False + True + + + + + + end + 1 + + + + + + False + True + 1 + + + + + True + True + + + True + True + 0 + horizontal + 0 + 0 + 1 + 5 + + + + + + + + True + True + end + 2 + + + + + library + Library + 3 + + + + + True + True + end + 1 + + + + + + + headerbar + True + False + True + + + True + False + False + + + True + False + gtk-disconnect + + + + False + True + + + + + True + False + + + False + True + + + + + True + False + True + media-playback-start + + + + False + True + + + + + + + + True + False + panelstack + + + + + True + False + False + + + True + False + + + True + True + False + True + none + vertical + audio-volume-muted-symbolic + audio-volume-high-symbolic + audio-volume-low-symbolic + audio-volume-medium-symbolic + + + + + + True + True + center + center + none + + + + + True + True + center + center + none + + + + + + + False + True + + + + + + end + 1 + + + + + + + diff --git a/mcg/mcgGtk.py b/mcg/mcgGtk.py index d7498a0..7adb584 100755 --- a/mcg/mcgGtk.py +++ b/mcg/mcgGtk.py @@ -2,7 +2,7 @@ """MPDCoverGrid is a client for the Music Player Daemon, focused on albums instead of single tracks.""" -__version__ = "0.4" +__version__ = "0.5" import gi @@ -48,20 +48,22 @@ class Application(Gtk.Application): def __init__(self): Gtk.Application.__init__(self, application_id="de.coderkun.mcg", flags=Gio.ApplicationFlags.FLAGS_NONE) self._window = None + + + def do_startup(self): + Gtk.Application.do_startup(self) self._settings = Gio.Settings.new(Application.SETTINGS_BASE_KEY) - - # Signals - self.connect('startup', self.on_startup) - self.connect('activate', self.on_activate) - - - def on_startup(self, app): self.load_css() + # Create builder to load UI + self._builder = Gtk.Builder() + self._builder.add_from_file('data/gtk.glade') - def on_activate(self, app): + + def do_activate(self): + Gtk.Application.do_activate(self) if not self._window: - self._window = Window(self, Application.TITLE, self._settings) + self._window = Window(self, self._builder, Application.TITLE, self._settings) self._window.present() @@ -111,18 +113,17 @@ class Application(Gtk.Application): -class Window(Gtk.ApplicationWindow): - STYLE_CLASS_BG_TEXTURE = 'bg-texture' - STYLE_CLASS_NO_BG = 'no-bg' - STYLE_CLASS_NO_BORDER = 'no-border' +class Window(): _PANEL_INDEX_CONNECTION = 0 _PANEL_INDEX_COVER = 1 _PANEL_INDEX_PLAYLIST = 2 _PANEL_INDEX_LIBRARY = 3 - def __init__(self, app, title, settings): - Gtk.ApplicationWindow.__init__(self, title=title, application=app) + def __init__(self, app, builder, title, settings): + self._appwindow = builder.get_object('appwindow') + self._appwindow.set_application(app) + self._appwindow.set_title(title) self._settings = settings self._panels = [] self._mcg = mcg.Client() @@ -138,31 +139,21 @@ class Window(Gtk.ApplicationWindow): self._fullscreened = False # Panels - self._panels.append(ConnectionPanel()) - self._panels.append(CoverPanel()) - self._panels.append(PlaylistPanel()) - self._panels.append(LibraryPanel()) + self._panels.append(ConnectionPanel(builder)) + self._panels.append(CoverPanel(builder)) + self._panels.append(PlaylistPanel(builder)) + self._panels.append(LibraryPanel(builder)) # Widgets - self._main_box = Gtk.VBox() - self._main_box.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self.add(self._main_box) # InfoBar - self._infobar = InfoBar() - self._main_box.pack_start(self._infobar, False, True, 0) + self._infobar = InfoBar(builder) # Stack - self._stack = Gtk.Stack() - for panel in self._panels: - self._stack.add_titled(panel, panel.get_name(), panel.get_title()) - self._stack.set_homogeneous(True) - self._main_box.pack_end(self._stack, True, True, 0) + self._stack = builder.get_object('panelstack') # Header - self._header_bar = HeaderBar(self._stack) - self.set_titlebar(self._header_bar) + self._header_bar = HeaderBar(builder) # Properties self._header_bar.set_sensitive(False, False) - self.get_style_context().add_class(Window.STYLE_CLASS_BG_TEXTURE) self._panels[Window._PANEL_INDEX_CONNECTION].set_host(self._settings.get_string(Application.SETTING_HOST)) self._panels[Window._PANEL_INDEX_CONNECTION].set_port(self._settings.get_int(Application.SETTING_PORT)) if use_keyring: @@ -174,17 +165,14 @@ class Window(Gtk.ApplicationWindow): self._panels[Window._PANEL_INDEX_LIBRARY].set_sort_type(self._settings.get_boolean(Application.SETTING_SORT_TYPE)) # Signals - self.connect('size-allocate', self.on_resize) - self.connect('window-state-event', self.on_state) - self.connect('destroy', self.on_destroy) self._header_bar.connect_signal(HeaderBar.SIGNAL_STACK_SWITCHED, self.on_header_bar_stack_switched) self._header_bar.connect_signal(HeaderBar.SIGNAL_CONNECT, self.on_header_bar_connect) self._header_bar.connect_signal(HeaderBar.SIGNAL_PLAYPAUSE, self.on_header_bar_playpause) self._header_bar.connect_signal(HeaderBar.SIGNAL_SET_VOLUME, self.on_header_bar_set_volume) self._panels[Window._PANEL_INDEX_CONNECTION].connect_signal(ConnectionPanel.SIGNAL_CONNECTION_CHANGED, self.on_connection_panel_connection_changed) - self._panels[Window._PANEL_INDEX_PLAYLIST].connect_signal(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST, self.on_playlist_panel_clear_playlist) self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_TOGGLE_FULLSCREEN, self.on_cover_panel_toggle_fullscreen) self._panels[Window._PANEL_INDEX_COVER].connect_signal(CoverPanel.SIGNAL_SET_SONG, self.on_cover_panel_set_song) + self._panels[Window._PANEL_INDEX_PLAYLIST].connect_signal(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST, self.on_playlist_panel_clear_playlist) self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_UPDATE, self.on_library_panel_update) self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_PLAY, self.on_library_panel_play) self._panels[Window._PANEL_INDEX_LIBRARY].connect_signal(LibraryPanel.SIGNAL_ITEM_SIZE_CHANGED, self.on_library_panel_item_size_changed) @@ -199,21 +187,37 @@ class Window(Gtk.ApplicationWindow): self._settings.connect('changed::'+Application.SETTING_ITEM_SIZE, self.on_settings_item_size_changed) self._settings.connect('changed::'+Application.SETTING_SORT_ORDER, self.on_settings_sort_order_changed) self._settings.connect('changed::'+Application.SETTING_SORT_TYPE, self.on_settings_sort_type_changed) + handlers = { + 'on_appwindow_size_allocate': self.on_resize, + 'on_appwindow_window_state_event': self.on_state, + 'on_appwindow_destroy': self.on_destroy + } + handlers.update(self._header_bar.get_signal_handlers()) + handlers.update(self._infobar.get_signal_handlers()) + handlers.update(self._panels[Window._PANEL_INDEX_CONNECTION].get_signal_handlers()) + handlers.update(self._panels[Window._PANEL_INDEX_COVER].get_signal_handlers()) + handlers.update(self._panels[Window._PANEL_INDEX_PLAYLIST].get_signal_handlers()) + handlers.update(self._panels[Window._PANEL_INDEX_LIBRARY].get_signal_handlers()) + builder.connect_signals(handlers) # Actions - self.resize(int(self._size[0]), int(self._size[1])) + self._appwindow.resize(int(self._size[0]), int(self._size[1])) if self._maximized: - self.maximize() - self.show_all() - self._infobar.hide() - self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION]) + self._appwindow.maximize() + self._appwindow.show_all() + self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get()) if self._settings.get_boolean(Application.SETTING_CONNECTED): self._connect() + def present(self): + self._appwindow.present() + self._appwindow.resize(800, 600) + + def on_resize(self, widget, event): if not self._maximized: - self._size = (self.get_allocation().width, self.get_allocation().height) + self._size = (self._appwindow.get_allocation().width, self._appwindow.get_allocation().height) def on_state(self, widget, state): @@ -343,7 +347,7 @@ class Window(Gtk.ApplicationWindow): def on_settings_panel_changed(self, settings, key): panel_index = settings.get_int(key) - self._stack.set_visible_child(self._panels[panel_index]) + self._stack.set_visible_child(self._panels[panel_index].get()) def on_settings_item_size_changed(self, settings, key): @@ -366,7 +370,7 @@ class Window(Gtk.ApplicationWindow): def _connect(self): connection_panel = self._panels[Window._PANEL_INDEX_CONNECTION] - connection_panel.set_sensitive(False) + connection_panel.get().set_sensitive(False) self._header_bar.set_sensitive(False, True) if self._mcg.is_connected(): self._mcg.disconnect() @@ -383,7 +387,7 @@ class Window(Gtk.ApplicationWindow): def _connect_connected(self): self._header_bar.connected() self._header_bar.set_sensitive(True, False) - self._stack.set_visible_child(self._panels[self._settings.get_int(Application.SETTING_PANEL)]) + self._stack.set_visible_child(self._panels[self._settings.get_int(Application.SETTING_PANEL)].get()) def _connect_disconnected(self): @@ -392,8 +396,8 @@ class Window(Gtk.ApplicationWindow): self._header_bar.disconnected() self._header_bar.set_sensitive(False, False) self._save_visible_panel() - self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION]) - self._panels[Window._PANEL_INDEX_CONNECTION].set_sensitive(True) + self._stack.set_visible_child(self._panels[Window._PANEL_INDEX_CONNECTION].get()) + self._panels[Window._PANEL_INDEX_CONNECTION].get().set_sensitive(True) def _fullscreen(self, fullscreened_new): @@ -408,81 +412,62 @@ class Window(Gtk.ApplicationWindow): def _save_visible_panel(self): - panel_index_selected = self._panels.index(self._stack.get_visible_child()) - if(panel_index_selected > 0): + panels = [panel.get() for panel in self._panels] + panel_index_selected = panels.index(self._stack.get_visible_child()) + if panel_index_selected > 0: self._settings.set_int(Application.SETTING_PANEL, panel_index_selected) def _show_error(self, message): self._infobar.show_error(message) - self._infobar.show() -class HeaderBar(mcg.Base, Gtk.HeaderBar): +class HeaderBar(mcg.Base): SIGNAL_STACK_SWITCHED = 'stack-switched' - SIGNAL_CONNECT = 'connect' - SIGNAL_PLAYPAUSE = 'playpause' + SIGNAL_CONNECT = 'on_headerbar-connect_toggled' + SIGNAL_PLAYPAUSE = 'on_headerbar-playpause_toggled' SIGNAL_SET_VOLUME = 'set-volume' - def __init__(self, stack): + def __init__(self, builder): mcg.Base.__init__(self) - Gtk.HeaderBar.__init__(self) - self._stack = stack + self._buttons = {} - self._button_handlers = {} self._changing_volume = False self._setting_volume = False # Widgets - # StackSwitcher - self._stack_switcher = StackSwitcher() - self._stack_switcher.set_stack(self._stack) - self.set_custom_title(self._stack_switcher) - # Buttons left - self._left_toolbar = Gtk.Toolbar() - self._left_toolbar.set_show_arrow(False) - self._left_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self.pack_start(self._left_toolbar) - # Buttons left: Connection - self._buttons[HeaderBar.SIGNAL_CONNECT] = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_DISCONNECT) - self._left_toolbar.add(self._buttons[HeaderBar.SIGNAL_CONNECT]) - # Buttons left: Separator - self._left_toolbar.add(Gtk.SeparatorToolItem()) - # Buttons left: Playback - self._buttons[HeaderBar.SIGNAL_PLAYPAUSE] = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_MEDIA_PLAY) - self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_sensitive(False) - self._left_toolbar.add(self._buttons[HeaderBar.SIGNAL_PLAYPAUSE]) - # Buttons right - self._right_toolbar = Gtk.Toolbar() - self._right_toolbar.set_show_arrow(False) - self._right_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self.pack_end(self._right_toolbar) - # Buttons right: Volume - item = Gtk.ToolItem() - self._buttons[HeaderBar.SIGNAL_SET_VOLUME] = Gtk.VolumeButton() - self._buttons[HeaderBar.SIGNAL_SET_VOLUME].set_sensitive(False) - item.add(self._buttons[HeaderBar.SIGNAL_SET_VOLUME]) - self._right_toolbar.add(item) - - # Properties - self.set_show_close_button(True) + self._header_bar = builder.get_object('headerbar') + self._stack_switcher = StackSwitcher(builder) + self._buttons[HeaderBar.SIGNAL_CONNECT] = builder.get_object('headerbar-connect') + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE] = builder.get_object('headerbar-playpause') + self._buttons[HeaderBar.SIGNAL_SET_VOLUME] = builder.get_object('headerbar-volume') # Signals self._stack_switcher.connect_signal(StackSwitcher.SIGNAL_STACK_SWITCHED, self.on_stack_switched) - self._button_handlers[HeaderBar.SIGNAL_CONNECT] = self._buttons[HeaderBar.SIGNAL_CONNECT].connect('toggled', self._callback_from_widget, self.SIGNAL_CONNECT) - self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE] = self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].connect('toggled', self._callback_from_widget, self.SIGNAL_PLAYPAUSE) - self._buttons[HeaderBar.SIGNAL_SET_VOLUME].connect('value-changed', self.on_volume_changed) - self._buttons[HeaderBar.SIGNAL_SET_VOLUME].connect('button-press-event', self.on_volume_set_active, True) - self._buttons[HeaderBar.SIGNAL_SET_VOLUME].connect('button-release-event', self.on_volume_set_active, False) + self._button_handlers = { + 'on_headerbar-connect_toggled': self._callback_from_widget, + 'on_headerbar-playpause_toggled': self._callback_from_widget, + 'on_headerbar-volume_value_changed': self.on_volume_changed, + 'on_headerbar-volume_button_press_event': self.on_volume_press, + 'on_headerbar-volume_button_release_event': self.on_volume_release + } + + + def get(self): + return self._header_bar + + + def get_signal_handlers(self): + return self._button_handlers def set_sensitive(self, sensitive, connecting): for button_signal in self._buttons: self._buttons[button_signal].set_sensitive(sensitive) - self._stack_switcher.set_sensitive(sensitive) + self._stack_switcher.get().set_sensitive(sensitive) self._buttons[HeaderBar.SIGNAL_CONNECT].set_sensitive(not connecting) @@ -495,30 +480,58 @@ class HeaderBar(mcg.Base, Gtk.HeaderBar): self._callback(self.SIGNAL_SET_VOLUME, int(value*100)) - def on_volume_set_active(self, widget, event, active): + def on_volume_press(self, *args): + self.volume_set_active(None, None, True) + + + def on_volume_release(self, *args): + self.volume_set_active(None, None, False) + + + def volume_set_active(self, widget, event, active): self._changing_volume = active def connected(self): + self._buttons[HeaderBar.SIGNAL_CONNECT].handler_block_by_func( + self._button_handlers[HeaderBar.SIGNAL_CONNECT] + ) self._buttons[HeaderBar.SIGNAL_CONNECT].set_stock_id(Gtk.STOCK_CONNECT) - with self._buttons[HeaderBar.SIGNAL_CONNECT].handler_block(self._button_handlers[HeaderBar.SIGNAL_CONNECT]): - self._buttons[HeaderBar.SIGNAL_CONNECT].set_active(True) + self._buttons[HeaderBar.SIGNAL_CONNECT].set_active(True) + self._buttons[HeaderBar.SIGNAL_CONNECT].handler_unblock_by_func( + self._button_handlers[HeaderBar.SIGNAL_CONNECT] + ) def disconnected(self): + self._buttons[HeaderBar.SIGNAL_CONNECT].handler_block_by_func( + self._button_handlers[HeaderBar.SIGNAL_CONNECT] + ) self._buttons[HeaderBar.SIGNAL_CONNECT].set_stock_id(Gtk.STOCK_DISCONNECT) - with self._buttons[HeaderBar.SIGNAL_CONNECT].handler_block(self._button_handlers[HeaderBar.SIGNAL_CONNECT]): - self._buttons[HeaderBar.SIGNAL_CONNECT].set_active(False) + self._buttons[HeaderBar.SIGNAL_CONNECT].set_active(False) + self._buttons[HeaderBar.SIGNAL_CONNECT].handler_unblock_by_func( + self._button_handlers[HeaderBar.SIGNAL_CONNECT] + ) def set_play(self): - with self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_block(self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE]): - self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_active(True) + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_block_by_func( + self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE] + ) + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_active(True) + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_unblock_by_func( + self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE] + ) def set_pause(self): - with self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_block(self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE]): - self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_active(False) + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_block_by_func( + self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE] + ) + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].set_active(False) + self._buttons[HeaderBar.SIGNAL_PLAYPAUSE].handler_unblock_by_func( + self._button_handlers[HeaderBar.SIGNAL_PLAYPAUSE] + ) def set_volume(self, volume): @@ -532,141 +545,94 @@ class HeaderBar(mcg.Base, Gtk.HeaderBar): self._buttons[HeaderBar.SIGNAL_SET_VOLUME].set_visible(False) - def _callback_from_widget(self, widget, signal, *data): - self._callback(signal, *data) + def _callback_from_widget(self, widget): + if widget is self._buttons[HeaderBar.SIGNAL_CONNECT]: + self._callback(self.SIGNAL_CONNECT) + elif widget is self._buttons[HeaderBar.SIGNAL_PLAYPAUSE]: + self._callback(self.SIGNAL_PLAYPAUSE) -class InfoBar(Gtk.InfoBar): - _RESPONSE_CLOSE = 1 - - - def __init__(self): - Gtk.InfoBar.__init__(self) - +class InfoBar(): + def __init__(self, builder): # Widgets - self.add_button(Gtk.STOCK_CLOSE, InfoBar._RESPONSE_CLOSE) - self._message_label = Gtk.Label() - self._message_label.show() - self.get_content_area().add(self._message_label) + self._revealer = builder.get_object('server-info-revealer') + self._bar = builder.get_object('server-info-bar') + self._message_label = builder.get_object('server-info-label') - # Signals - self.connect('close', self.on_response, InfoBar._RESPONSE_CLOSE) - self.connect('response', self.on_response) + + def get_signal_handlers(self): + return { + 'on_server-info-bar_close': self.on_close, + 'on_server-info-bar_response': self.on_response + } + + + def on_close(self, *args): + self.hide() def on_response(self, widget, response): - if response == InfoBar._RESPONSE_CLOSE: - self.hide() + self.hide() + + + def hide(self): + self._revealer.set_reveal_child(False) def show_error(self, message): - self.set_message_type(Gtk.MessageType.ERROR) + self._bar.set_message_type(Gtk.MessageType.ERROR) self._message_label.set_text(message) + self._revealer.set_reveal_child(True) -class Panel(mcg.Base): - - - def __init__(self): - mcg.Base.__init__(self) - - - def get_name(self): - raise NotImplementedError() - - - def get_title(self): - raise NotImplementedError() - - - - -class ConnectionPanel(Panel, Gtk.VBox): +class ConnectionPanel(mcg.Base): SIGNAL_CONNECTION_CHANGED = 'connection-changed' - def __init__(self): - Panel.__init__(self) - Gtk.VBox.__init__(self) + def __init__(self, builder): + mcg.Base.__init__(self) self._services = Gtk.ListStore(str, str, int) self._profile = None # Widgets - hbox = Gtk.HBox() - self.pack_start(hbox, True, False, 0) - grid = Gtk.Grid() - grid.set_column_spacing(5) - grid.set_column_homogeneous(True) - hbox.pack_start(grid, True, False, 0) + self._panel = builder.get_object('server-panel') # Zeroconf - zeroconf_box = Gtk.HBox() - grid.add(zeroconf_box) - # Zeroconf list - self._zeroconf_list = Gtk.TreeView(self._services) - self._zeroconf_list.get_selection().set_mode(Gtk.SelectionMode.SINGLE) + self._zeroconf_list = builder.get_object('server-zeroconf-list') + self._zeroconf_list.set_model(self._services) renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn("Zeroconf", renderer, text=0) self._zeroconf_list.append_column(column) - zeroconf_box.pack_start(self._zeroconf_list, True, True, 0) - # Separator - separator = Gtk.Separator.new(Gtk.Orientation.VERTICAL) - zeroconf_box.pack_end(separator, False, False, 5) - # Connection grid - connection_grid = Gtk.Grid() - grid.attach_next_to(connection_grid, zeroconf_box, Gtk.PositionType.RIGHT, 1, 1) # Host - host_label = Gtk.Label("Host:") - host_label.set_alignment(0, 0.5) - connection_grid.add(host_label) - self._host_entry = Gtk.Entry() - self._host_entry.set_text("localhost") - connection_grid.attach_next_to(self._host_entry, host_label, Gtk.PositionType.BOTTOM, 1, 1) + self._host_entry = builder.get_object('server-host') # Port - port_label = Gtk.Label("Port:") - port_label.set_alignment(0, 0.5) - connection_grid.attach_next_to(port_label, self._host_entry, Gtk.PositionType.BOTTOM, 1, 1) - adjustment = Gtk.Adjustment(6600, 1024, 9999, 1, 10, 10) - self._port_spinner = Gtk.SpinButton() - self._port_spinner.set_adjustment(adjustment) - connection_grid.attach_next_to(self._port_spinner, port_label, Gtk.PositionType.BOTTOM, 1, 1) + self._port_spinner = builder.get_object('server-port') # Passwort - password_label = Gtk.Label("Password:") - password_label.set_alignment(0, 0.5) - connection_grid.attach_next_to(password_label, self._port_spinner, Gtk.PositionType.BOTTOM, 1, 1) - self._password_entry = Gtk.Entry() - self._password_entry.set_input_purpose(Gtk.InputPurpose.PASSWORD) - self._password_entry.set_visibility(False) - connection_grid.attach_next_to(self._password_entry, password_label, Gtk.PositionType.BOTTOM, 1, 1) + self._password_entry = builder.get_object('server-password') # Image dir - image_dir_label = Gtk.Label("Image Dir:") - image_dir_label.set_alignment(0, 0.5) - connection_grid.attach_next_to(image_dir_label, self._password_entry, Gtk.PositionType.BOTTOM, 1, 1) - self._image_dir_entry = Gtk.Entry() - connection_grid.attach_next_to(self._image_dir_entry, image_dir_label, Gtk.PositionType.BOTTOM, 1, 1) + self._image_dir_entry = builder.get_object('server-image-dir') # Zeroconf provider self._zeroconf_provider = ZeroconfProvider() self._zeroconf_provider.connect_signal(ZeroconfProvider.SIGNAL_SERVICE_NEW, self.on_new_service) - # Signals - self._zeroconf_list.get_selection().connect('changed', self.on_service_selected) - self._zeroconf_list.connect('focus-out-event', self.on_zeroconf_list_outfocused) - self._host_entry.connect('focus-out-event', self.on_host_entry_outfocused) - self._port_spinner.connect('value-changed', self.on_port_spinner_value_changed) - self._password_entry.connect('focus-out-event', self.on_password_entry_outfocused) - self._image_dir_entry.connect('focus-out-event', self.on_image_dir_entry_outfocused) + + def get(self): + return self._panel - def get_name(self): - return 'connection' - - - def get_title(self): - return "Server" + def get_signal_handlers(self): + return { + 'on_server-zeroconf-list-selection_changed': self.on_service_selected, + 'on_server-zeroconf-list_focus_out_event': self.on_zeroconf_list_outfocused, + 'on_server-host_focus_out_event': self.on_host_entry_outfocused, + 'on_server-port_value_changed': self.on_port_spinner_value_changed, + 'on_server-password_focus_out_event': self.on_password_entry_outfocused, + 'on_server-image-dir_focus_out_event': self.on_image_dir_entry_outfocused + } def on_new_service(self, service): @@ -745,64 +711,44 @@ class ConnectionPanel(Panel, Gtk.VBox): -class CoverPanel(Panel, Gtk.VBox): +class CoverPanel(mcg.Base): SIGNAL_TOGGLE_FULLSCREEN = 'toggle-fullscreen' SIGNAL_SET_SONG = 'set-song' - def __init__(self): - Panel.__init__(self) - Gtk.VBox.__init__(self) + def __init__(self, builder): + mcg.Base.__init__(self) + self._current_album = None self._cover_pixbuf = None self._timer = None self._properties = {} # Widgets - self._current_box = Gtk.Box(Gtk.Orientation.HORIZONTAL) - self._current_box.set_halign(Gtk.Align.FILL) - self._current_box.set_homogeneous(True) - self.pack_start(self._current_box, True, True, 10) + self._panel = builder.get_object('cover-panel') # Cover - self._cover_image = Gtk.Image() - self._cover_box = Gtk.EventBox() - self._cover_box.add(self._cover_image) - self._cover_scroll = Gtk.ScrolledWindow() - self._cover_scroll.add(self._cover_box) - self._current_box.pack_start(self._cover_scroll, True, True, 10) + self._cover_scroll = builder.get_object('cover-scroll') + self._cover_image = builder.get_object('cover-image') # Songs - self._songs_scale = Gtk.VScale() - self._songs_scale.set_halign(Gtk.Align.START) - self._songs_scale.set_vexpand(True) - self._songs_scale.set_digits(0) - self._songs_scale.set_draw_value(False) + self._songs_scale = builder.get_object('cover-songs') self._songs_scale.override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(0, 0, 0, 1)) - self._current_box.pack_end(self._songs_scale, True, True, 10) # Album Infos - self._info_grid = Gtk.Grid() - self._info_grid.set_halign(Gtk.Align.CENTER) - self._info_grid.set_row_spacing(5) - self._album_title_label = Gtk.Label() - self._info_grid.add(self._album_title_label) - self._album_date_label = Gtk.Label() - self._info_grid.attach_next_to(self._album_date_label, self._album_title_label, Gtk.PositionType.BOTTOM, 1, 1) - self._album_artist_label = Gtk.Label() - self._info_grid.attach_next_to(self._album_artist_label, self._album_date_label, Gtk.PositionType.BOTTOM, 1, 1) - self.pack_end(self._info_grid, False, True, 10) - - # Signals - self._cover_box.connect('button-press-event', self.on_cover_box_pressed) - self._cover_scroll.connect('size-allocate', self.on_cover_size_allocate) - self._songs_scale.connect('button-press-event', self.on_songs_start_change) - self._songs_scale.connect('button-release-event', self.on_songs_change) + self._album_title_label = builder.get_object('cover-album') + self._album_date_label = builder.get_object('cover-date') + self._album_artist_label = builder.get_object('cover-artist') - def get_name(self): - return 'cover' + def get(self): + return self._panel - def get_title(self): - return "Cover" + def get_signal_handlers(self): + return { + 'on_cover-box_button_press_event': self.on_cover_box_pressed, + 'on_cover-scroll_size_allocate': self.on_cover_size_allocate, + 'on_cover-songs_button_press_event': self.on_songs_start_change, + 'on_cover-songs_button_release_event': self.on_songs_change + } def on_cover_box_pressed(self, widget, event): @@ -977,13 +923,12 @@ class CoverPanel(Panel, Gtk.VBox): -class PlaylistPanel(Panel, Gtk.VBox): +class PlaylistPanel(mcg.Base): SIGNAL_CLEAR_PLAYLIST = 'clear-playlist' - def __init__(self): - Panel.__init__(self) - Gtk.VBox.__init__(self) + def __init__(self, builder): + mcg.Base.__init__(self) self._host = None self._item_size = 150 self._playlist = None @@ -991,48 +936,28 @@ class PlaylistPanel(Panel, Gtk.VBox): self._playlist_stop = threading.Event() self._icon_theme = Gtk.IconTheme.get_default() - # Toolbar - self._playlist_toolbar = Gtk.Toolbar() - self.pack_start(self._playlist_toolbar, False, True, 0) - # Toolbar: Clear Button - self._clear_playlist_button = Gtk.ToolButton(Gtk.STOCK_CLEAR) - self._playlist_toolbar.add(self._clear_playlist_button) + # Widgets + self._panel = builder.get_object('playlist-panel') + # Clear button + self._playlist_clear_button = builder.get_object('playlist-clear') # Playlist Grid: Model self._playlist_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) # Playlist Grid - self._playlist_grid = Gtk.IconView(self._playlist_grid_model) + self._playlist_grid = builder.get_object('playlist-iconview') + self._playlist_grid.set_model(self._playlist_grid_model) self._playlist_grid.set_pixbuf_column(0) self._playlist_grid.set_text_column(-1) self._playlist_grid.set_tooltip_column(1) - self._playlist_grid.set_margin(0) - self._playlist_grid.set_spacing(0) - self._playlist_grid.set_row_spacing(0) - self._playlist_grid.set_column_spacing(0) - self._playlist_grid.set_item_padding(5) - self._playlist_grid.set_reorderable(False) - self._playlist_grid.set_item_width(-1) - self._playlist_grid.set_selection_mode(Gtk.SelectionMode.SINGLE) - self._playlist_scroll = Gtk.ScrolledWindow() - self._playlist_scroll.add(self._playlist_grid) - self.pack_end(self._playlist_scroll, True, True, 0) - self.show_all(); - - # Properties - self._playlist_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self._playlist_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BORDER) - self._playlist_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self._playlist_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BORDER) - - # Signals - self._clear_playlist_button.connect('clicked' ,self._callback_from_widget, PlaylistPanel.SIGNAL_CLEAR_PLAYLIST) - def get_name(self): - return "playlist" + def get(self): + return self._panel - def get_title(self): - return "Playlist" + def get_signal_handlers(self): + return { + 'on_playlist-clear_clicked': self._callback_from_widget + } def set_item_size(self, item_size): @@ -1100,13 +1025,14 @@ class PlaylistPanel(Panel, Gtk.VBox): self.set_playlist(self._host, self._playlist) - def _callback_from_widget(self, widget, signal, *data): - self._callback(signal, *data) + def _callback_from_widget(self, widget): + if widget is self._playlist_clear_button: + self._callback(PlaylistPanel.SIGNAL_CLEAR_PLAYLIST) -class LibraryPanel(Panel, Gtk.VBox): +class LibraryPanel(mcg.Base): SIGNAL_UPDATE = 'update' SIGNAL_PLAY = 'play' SIGNAL_ITEM_SIZE_CHANGED = 'item-size-changed' @@ -1114,9 +1040,8 @@ class LibraryPanel(Panel, Gtk.VBox): SIGNAL_SORT_TYPE_CHANGED = 'sort-type-changed' - def __init__(self): - Panel.__init__(self) - Gtk.VBox.__init__(self) + def __init__(self, builder): + mcg.Base.__init__(self) self._buttons = {} self._albums = None self._host = "localhost" @@ -1131,53 +1056,30 @@ class LibraryPanel(Panel, Gtk.VBox): self._icon_theme = Gtk.IconTheme.get_default() # Widgets + self._panel = builder.get_object('library-panel') # Progress Bar - self._progress_bar = Gtk.ProgressBar() + self._progress_revealer = builder.get_object('library-progress-revealer') + self._progress_bar = builder.get_object('library-progress') # Toolbar - self._library_toolbar = Gtk.HeaderBar() - self.pack_start(self._library_toolbar, False, True, 0) - # Toolbar: buttons left - # Toolbar: buttons left: Update Button - self._buttons[LibraryPanel.SIGNAL_UPDATE] = Gtk.ToolButton(Gtk.STOCK_REFRESH) - self._library_toolbar.pack_start(self._buttons[LibraryPanel.SIGNAL_UPDATE]) - # Toolbar: Filter Entry - self._filter_entry = Gtk.SearchEntry() - self._filter_entry.set_placeholder_text("search library") - self._library_toolbar.set_custom_title(self._filter_entry) - # Toolbar: buttons right - self._right_toolbar = Gtk.Toolbar() - self._right_toolbar.set_show_arrow(False) - self._right_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self._library_toolbar.pack_end(self._right_toolbar) - # Toolbar: buttons right: Grid Scale - self._grid_scale = Gtk.HScale() - self._grid_scale.set_range(100, 1000) - self._grid_scale.set_round_digits(0) + # Filter entry + self._filter_entry = builder.get_object('library-filter') + # Grid scale + self._grid_scale = builder.get_object('library-grid-scale') self._grid_scale.set_value(self._item_size) - self._grid_scale.set_size_request(100, -1) - self._grid_scale.set_draw_value(False) - item = Gtk.ToolItem() - item.add(self._grid_scale) - self._right_toolbar.add(item) - # Toolbar: buttons right: Library Sort Menu + # Sort menu library_sort_store = Gtk.ListStore(str, str) library_sort_store.append([mcg.MCGAlbum.SORT_BY_ARTIST, "sort by artist"]) library_sort_store.append([mcg.MCGAlbum.SORT_BY_TITLE, "sort by title"]) library_sort_store.append([mcg.MCGAlbum.SORT_BY_YEAR, "sort by year"]) - self._library_sort_combo = Gtk.ComboBox.new_with_model(library_sort_store) + self._library_sort_combo = builder.get_object('library-sort') + self._library_sort_combo.set_model(library_sort_store) renderer_text = Gtk.CellRendererText() self._library_sort_combo.pack_start(renderer_text, True) self._library_sort_combo.add_attribute(renderer_text, "text", 1) self._library_sort_combo.set_id_column(0) self._library_sort_combo.set_active_id(self._sort_order) - item = Gtk.ToolItem() - item.add(self._library_sort_combo) - self._right_toolbar.add(item) - # Toolbar: buttons right: Library Sort Type - self._library_sort_type_button = Gtk.ToggleToolButton.new_from_stock(Gtk.STOCK_SORT_ASCENDING) - self._library_sort_type_button.set_active(True) - self._library_sort_type_button.set_stock_id(Gtk.STOCK_SORT_DESCENDING) - self._right_toolbar.add(self._library_sort_type_button) + # Sort type + self._library_sort_type_button = builder.get_object('library-sort-order') # Library Grid: Model self._library_grid_model = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str) self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order) @@ -1185,46 +1087,31 @@ class LibraryPanel(Panel, Gtk.VBox): self._library_grid_filter = self._library_grid_model.filter_new() self._library_grid_filter.set_visible_func(self.on_filter_visible) # Library Grid - self._library_grid = Gtk.IconView(self._library_grid_filter) -# self._library_grid.pack_end(text_renderer, False) -# self._library_grid.add_attribute(text_renderer, "markup", 0) + self._library_grid = builder.get_object('library-iconview') + self._library_grid.set_model(self._library_grid_filter) self._library_grid.set_pixbuf_column(0) self._library_grid.set_text_column(-1) self._library_grid.set_tooltip_column(1) - self._library_grid.set_margin(0) - self._library_grid.set_spacing(0) - self._library_grid.set_row_spacing(0) - self._library_grid.set_column_spacing(0) - self._library_grid.set_item_padding(5) - self._library_grid.set_reorderable(False) - self._library_grid.set_item_width(-1) - self._library_grid.set_selection_mode(Gtk.SelectionMode.SINGLE) - self._library_scroll = Gtk.ScrolledWindow() - self._library_scroll.add(self._library_grid) - self.pack_end(self._library_scroll, True, True, 0) - - # Properties - self._library_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self._library_toolbar.get_style_context().add_class(Window.STYLE_CLASS_NO_BORDER) - self._library_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BG) - self._library_grid.get_style_context().add_class(Window.STYLE_CLASS_NO_BORDER) - - # Signals - self._buttons[LibraryPanel.SIGNAL_UPDATE].connect('clicked', self._callback_from_widget, self.SIGNAL_UPDATE) - self._grid_scale.connect('change-value', self.on_grid_scale_change) - self._grid_scale.connect('button-release-event', self.on_grid_scale_changed) - self._library_sort_combo.connect("changed", self.on_library_sort_combo_changed) - self._library_sort_type_button.connect('clicked', self.on_library_sort_type_button_activated) - self._filter_entry.connect('search-changed', self.on_filter_entry_changed) - self._library_grid.connect('item-activated', self.on_library_grid_clicked) - def get_name(self): - return "library" + def get(self): + return self._panel - def get_title(self): - return "Library" + def get_signal_handlers(self): + return { + 'on_library-update_clicked': self.on_update_clicked, + 'on_library-grid-scale_change_value': self.on_grid_scale_change, + 'on_library-grid-scale_button_release_event': self.on_grid_scale_changed, + 'on_library-sort_changed': self.on_library_sort_combo_changed, + 'on_library-sort-order_clicked': self.on_library_sort_type_button_activated, + 'on_library-filter_search_changed': self.on_filter_entry_changed, + 'on_library-iconview_item_activated': self.on_library_grid_clicked + } + + + def on_update_clicked(self, widget): + self._callback(self.SIGNAL_UPDATE) def on_filter_visible(self, model, iter, data): @@ -1296,8 +1183,10 @@ class LibraryPanel(Panel, Gtk.VBox): def set_sort_order(self, sort_order): if self._sort_order != sort_order: - self._sort_order = sort_order - self._library_sort_combo.set_active_id(sort_order) + result = self._library_sort_combo.set_active_id(sort_order) + if self._sort_order != sort_order: + self._sort_order = sort_order + self._library_grid_model.set_sort_func(2, self.compare_albums, self._sort_order) def get_sort_order(self): @@ -1307,11 +1196,17 @@ class LibraryPanel(Panel, Gtk.VBox): def set_sort_type(self, sort_type): if self._sort_type != sort_type: if sort_type: - self._sort_type = Gtk.SortType.DESCENDING + sort_type_gtk = Gtk.SortType.DESCENDING + stock_id = Gtk.STOCK_SORT_DESCENDING self._library_sort_type_button.set_active(True) else: - self._sort_type = Gtk.SortType.ASCENDING + sort_type_gtk = Gtk.SortType.ASCENDING self._library_sort_type_button.set_active(False) + stock_id = Gtk.STOCK_SORT_ASCENDING + if self._sort_type != sort_type_gtk: + self._sort_type = sort_type_gtk + self._library_sort_type_button.set_stock_id(stock_id) + self._library_grid_model.set_sort_column_id(2, sort_type) def get_sort_type(self): @@ -1341,11 +1236,8 @@ class LibraryPanel(Panel, Gtk.VBox): self._library_lock.acquire() self._library_stop.clear() self._albums = albums - if len(self.get_children()) > 1: - GObject.idle_add(self.remove, self.get_children()[0]) + self._progress_revealer.set_reveal_child(True) GObject.idle_add(self._progress_bar.set_fraction, 0.0) - GObject.idle_add(self.pack_start, self._progress_bar, False, True, 0) - GObject.idle_add(self.show_all) self._library_grid.set_model(None) self._library_grid.freeze_child_notify() self._library_grid_model.clear() @@ -1385,10 +1277,7 @@ class LibraryPanel(Panel, Gtk.VBox): self._library_grid.thaw_child_notify() self._library_grid.set_item_width(-1) self._library_lock.release() - if len(self.get_children()) > 1: - GObject.idle_add(self.remove, self.get_children()[0]) - GObject.idle_add(self.pack_start, self._library_toolbar, False, True, 0) - GObject.idle_add(self.show_all) + self._progress_revealer.set_reveal_child(False) def _set_widget_grid_size(self, grid_widget, size, vertical): @@ -1451,25 +1340,18 @@ class LibraryPanel(Panel, Gtk.VBox): self.set_albums(self._host, self._albums) - def _callback_from_widget(self, widget, signal, *data): - self._callback(signal, *data) - - -class StackSwitcher(mcg.Base, Gtk.StackSwitcher): +class StackSwitcher(mcg.Base): SIGNAL_STACK_SWITCHED = 'stack-switched' - def __init__(self): + def __init__(self, builder): mcg.Base.__init__(self) - Gtk.StackSwitcher.__init__(self) + self._temp_button = None - - - def set_stack(self, stack): - super().set_stack(stack) - for child in self.get_children(): + self._stack_switcher = builder.get_object('header-panelswitcher') + for child in self._stack_switcher.get_children(): if type(child) is Gtk.RadioButton: child.connect('clicked', self.on_clicked) @@ -1482,6 +1364,10 @@ class StackSwitcher(mcg.Base, Gtk.StackSwitcher): self._callback(StackSwitcher.SIGNAL_STACK_SWITCHED, self) + def get(self): + return self._stack_switcher + + class ZeroconfProvider(mcg.Base):