Example #1
0
    def __init__(self):
        super(BlaMainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
        self.set_resizable(True)
        self.connect("delete_event", self.__delete_event)
        self.enable_tracking(is_main_window=True)

        # Set up the fullscreen window.
        self.__fullscreen_window = gtk.Window()
        def map_(window):
            pass
        self.__fullscreen_window.connect("map", map_)
        self.__fullscreen_window.set_modal(True)
        self.__fullscreen_window.set_transient_for(self)
        self.__fullscreen_window.connect_object(
            "window_state_event", BlaMainWindow.__window_state_event, self)
        def key_press_event(window, event):
            if blagui.is_accel(event, "Escape"):
                window.child.emit("toggle_fullscreen")
            elif blagui.is_accel(event, "space"):
                player.play_pause()
            elif blagui.is_accel(event, "<Ctrl>Q"):
                blaplay.shutdown()
        self.__fullscreen_window.connect_object(
            "key_press_event", key_press_event, self.__fullscreen_window)
        # Realize the fullscreen window. If we don't do this here and reparent
        # the drawingarea to it later that in turn will get unrealized again,
        # causing bad X window errors.
        self.__fullscreen_window.realize()

        # Install a global mouse hook. If connected callbacks don't consume the
        # event by returning True this hook gets called for every widget in the
        # hierarchy that re-emits the event. We therefore cache the event's
        # timestamp to detect and ignore signal re-emissions.
        def button_press_hook(receiver, event):
            event_time = event.get_time()
            if event_time != self.__previous_event_time:
                self.__previous_event_time = event_time
                if event.button == 8:
                    player.previous()
                elif event.button == 9:
                    player.next()
            # This behaves like gobject.{timeout|idle}_add: if the callback
            # doesn't return True it's only called once. It does NOT prevent
            # signal callbacks from executing.
            return True
        self.__previous_event_time = -1
        gobject.add_emission_hook(self, "button_press_event",
                                  button_press_hook)

        # Main menu
        ui_manager = blaplay.bla.ui_manager
        self.add_accel_group(ui_manager.get_accel_group())

        actions = [
            # Menus and submenus
            ("File", None, "_File"),
            ("Edit", None, "_Edit"),
            ("Select", None, "S_elect"),
            ("Selection", None, "Se_lection"),
            ("NewPlaylistFrom", None, "_New playlist from"),
            ("PlayOrder", None, "_Order"),
            ("View", None, "_View"),
            ("Help", None, "_Help"),

            # Menu items
            ("OpenPlaylist", None, "Open playlist...", None, "",
             self.__open_playlist),
            ("AddFiles", None, "Add _files...", None, "",
             lambda *x: self.__add_tracks()),
            ("AddDirectories", None, "_Add directories...", None, "",
             lambda *x: self.__add_tracks(files=False)),
            ("SavePlaylist", None, "_Save playlist...", None, "",
             self.__save_playlist),
            ("Quit", gtk.STOCK_QUIT, "_Quit", "<Ctrl>Q", "",
             lambda *x: blaplay.shutdown()),
            ("Preferences", None, "Pre_ferences...", None, "", BlaPreferences),
            ("About", None, "_About...", None, "", BlaAbout)
        ]
        ui_manager.add_actions(actions)

        toggle_actions = [
            ("Statusbar", None, "St_atusbar", None, "",
             self.__toggle_statusbar, blacfg.getboolean("general",
                                                        "statusbar")),
            ("Visualization", None, "Visualization", None, "",
             self.__toggle_visualization,
             blacfg.getboolean("general", "show.visualization"))
        ]
        ui_manager.add_toggle_actions(toggle_actions)

        radio_actions = [
            ("OrderNormal", None, "_Normal", None, "", blaconst.ORDER_NORMAL),
            ("OrderRepeat", None, "_Repeat", None, "", blaconst.ORDER_REPEAT),
            ("OrderShuffle", None, "_Shuffle", None, "",
             blaconst.ORDER_SHUFFLE)
        ]
        # TODO: Emit "order_changed" signal in the on_change handler instead
        #       and let interested widgets handle this instead.
        ui_manager.add_radio_actions(
            radio_actions, value=blacfg.getint("general", "play.order"),
            on_change=BlaStatusbar.set_order)

        # This is the topmost box that holds all the other components.
        self.add(gtk.VBox())

        # Create instances of the main parts of the GUI.
        self.__toolbar = BlaToolbar()
        self.__browsers = BlaBrowsers()
        self.__visualization = BlaVisualization()
        self.__view = BlaView()
        self.__statusbar = BlaStatusbar()

        # Group browsers and visualization widget.
        self.__vbox_left = gtk.VBox(spacing=blaconst.WIDGET_SPACING)
        self.__vbox_left.pack_start(self.__browsers, expand=True)
        self.__vbox_left.pack_start(self.__visualization, expand=False)
        self.__vbox_left.show()

        # Pack the browser + view-widget into a gtk.HPane instance.
        hpane = gtk.HPaned()
        hpane.pack1(self.__vbox_left, resize=False, shrink=False)
        hpane.pack2(self.__view, resize=True, shrink=True)
        hpane.show()

        # Restore pane positions.
        def notify(pane, propspec, key):
            blacfg.set("general", key, str(pane.get_position()))
        for pane, side in [(hpane, "left"), (self.__view, "right")]:
            key = "pane.pos.%s" % side
            try:
                pane.set_position(blacfg.getint("general", key))
            except TypeError:
                pass
            pane.connect("notify", notify, key)

        # Create a vbox hpane and the statusbar. This allows for setting a
        # border around those items which excludes the menubar and the toolbar.
        vbox = gtk.VBox(spacing=blaconst.BORDER_PADDING)
        vbox.set_border_width(blaconst.BORDER_PADDING)
        vbox.pack_start(hpane)
        vbox.pack_start(self.__statusbar, expand=False)
        vbox.show()

        self.child.pack_start(ui_manager.get_widget("/Menu"), expand=False)
        self.child.pack_start(self.__toolbar, expand=False)
        self.child.pack_start(vbox)
        self.child.show()

        self.__tray = BlaTray()

        def update_title(*args):
            self.__update_title()
        player.connect("state_changed", update_title)
        library.connect("library_updated", update_title)
        self.__update_title()
Example #2
0
class BlaMainWindow(BlaBaseWindow):
    __is_fullscreen = False

    def __init__(self):
        super(BlaMainWindow, self).__init__(gtk.WINDOW_TOPLEVEL)
        self.set_resizable(True)
        self.connect("delete_event", self.__delete_event)
        self.enable_tracking(is_main_window=True)

        # Set up the fullscreen window.
        self.__fullscreen_window = gtk.Window()
        def map_(window):
            pass
        self.__fullscreen_window.connect("map", map_)
        self.__fullscreen_window.set_modal(True)
        self.__fullscreen_window.set_transient_for(self)
        self.__fullscreen_window.connect_object(
            "window_state_event", BlaMainWindow.__window_state_event, self)
        def key_press_event(window, event):
            if blagui.is_accel(event, "Escape"):
                window.child.emit("toggle_fullscreen")
            elif blagui.is_accel(event, "space"):
                player.play_pause()
            elif blagui.is_accel(event, "<Ctrl>Q"):
                blaplay.shutdown()
        self.__fullscreen_window.connect_object(
            "key_press_event", key_press_event, self.__fullscreen_window)
        # Realize the fullscreen window. If we don't do this here and reparent
        # the drawingarea to it later that in turn will get unrealized again,
        # causing bad X window errors.
        self.__fullscreen_window.realize()

        # Install a global mouse hook. If connected callbacks don't consume the
        # event by returning True this hook gets called for every widget in the
        # hierarchy that re-emits the event. We therefore cache the event's
        # timestamp to detect and ignore signal re-emissions.
        def button_press_hook(receiver, event):
            event_time = event.get_time()
            if event_time != self.__previous_event_time:
                self.__previous_event_time = event_time
                if event.button == 8:
                    player.previous()
                elif event.button == 9:
                    player.next()
            # This behaves like gobject.{timeout|idle}_add: if the callback
            # doesn't return True it's only called once. It does NOT prevent
            # signal callbacks from executing.
            return True
        self.__previous_event_time = -1
        gobject.add_emission_hook(self, "button_press_event",
                                  button_press_hook)

        # Main menu
        ui_manager = blaplay.bla.ui_manager
        self.add_accel_group(ui_manager.get_accel_group())

        actions = [
            # Menus and submenus
            ("File", None, "_File"),
            ("Edit", None, "_Edit"),
            ("Select", None, "S_elect"),
            ("Selection", None, "Se_lection"),
            ("NewPlaylistFrom", None, "_New playlist from"),
            ("PlayOrder", None, "_Order"),
            ("View", None, "_View"),
            ("Help", None, "_Help"),

            # Menu items
            ("OpenPlaylist", None, "Open playlist...", None, "",
             self.__open_playlist),
            ("AddFiles", None, "Add _files...", None, "",
             lambda *x: self.__add_tracks()),
            ("AddDirectories", None, "_Add directories...", None, "",
             lambda *x: self.__add_tracks(files=False)),
            ("SavePlaylist", None, "_Save playlist...", None, "",
             self.__save_playlist),
            ("Quit", gtk.STOCK_QUIT, "_Quit", "<Ctrl>Q", "",
             lambda *x: blaplay.shutdown()),
            ("Preferences", None, "Pre_ferences...", None, "", BlaPreferences),
            ("About", None, "_About...", None, "", BlaAbout)
        ]
        ui_manager.add_actions(actions)

        toggle_actions = [
            ("Statusbar", None, "St_atusbar", None, "",
             self.__toggle_statusbar, blacfg.getboolean("general",
                                                        "statusbar")),
            ("Visualization", None, "Visualization", None, "",
             self.__toggle_visualization,
             blacfg.getboolean("general", "show.visualization"))
        ]
        ui_manager.add_toggle_actions(toggle_actions)

        radio_actions = [
            ("OrderNormal", None, "_Normal", None, "", blaconst.ORDER_NORMAL),
            ("OrderRepeat", None, "_Repeat", None, "", blaconst.ORDER_REPEAT),
            ("OrderShuffle", None, "_Shuffle", None, "",
             blaconst.ORDER_SHUFFLE)
        ]
        # TODO: Emit "order_changed" signal in the on_change handler instead
        #       and let interested widgets handle this instead.
        ui_manager.add_radio_actions(
            radio_actions, value=blacfg.getint("general", "play.order"),
            on_change=BlaStatusbar.set_order)

        # This is the topmost box that holds all the other components.
        self.add(gtk.VBox())

        # Create instances of the main parts of the GUI.
        self.__toolbar = BlaToolbar()
        self.__browsers = BlaBrowsers()
        self.__visualization = BlaVisualization()
        self.__view = BlaView()
        self.__statusbar = BlaStatusbar()

        # Group browsers and visualization widget.
        self.__vbox_left = gtk.VBox(spacing=blaconst.WIDGET_SPACING)
        self.__vbox_left.pack_start(self.__browsers, expand=True)
        self.__vbox_left.pack_start(self.__visualization, expand=False)
        self.__vbox_left.show()

        # Pack the browser + view-widget into a gtk.HPane instance.
        hpane = gtk.HPaned()
        hpane.pack1(self.__vbox_left, resize=False, shrink=False)
        hpane.pack2(self.__view, resize=True, shrink=True)
        hpane.show()

        # Restore pane positions.
        def notify(pane, propspec, key):
            blacfg.set("general", key, str(pane.get_position()))
        for pane, side in [(hpane, "left"), (self.__view, "right")]:
            key = "pane.pos.%s" % side
            try:
                pane.set_position(blacfg.getint("general", key))
            except TypeError:
                pass
            pane.connect("notify", notify, key)

        # Create a vbox hpane and the statusbar. This allows for setting a
        # border around those items which excludes the menubar and the toolbar.
        vbox = gtk.VBox(spacing=blaconst.BORDER_PADDING)
        vbox.set_border_width(blaconst.BORDER_PADDING)
        vbox.pack_start(hpane)
        vbox.pack_start(self.__statusbar, expand=False)
        vbox.show()

        self.child.pack_start(ui_manager.get_widget("/Menu"), expand=False)
        self.child.pack_start(self.__toolbar, expand=False)
        self.child.pack_start(vbox)
        self.child.show()

        self.__tray = BlaTray()

        def update_title(*args):
            self.__update_title()
        player.connect("state_changed", update_title)
        library.connect("library_updated", update_title)
        self.__update_title()

    def set_fullscreen(self, da, parent):
        # TODO: when minimizing to tray during fullscreen, reparent the da so
        #       that when we call raise_window() again we won't be in
        #       fullscreen anymore

        # When parent is None we want to go into fullscreen mode.
        go_to_fullscreen = parent is None
        if go_to_fullscreen:
            self.__fullscreen_window.fullscreen()
            da.reparent(self.__fullscreen_window)
            self.__fullscreen_window.show_all()
        else:
            self.__fullscreen_window.unfullscreen()
            da.reparent(parent)
            self.__fullscreen_window.hide()
        self.set_maximized(go_to_fullscreen)

    def raise_window(self):
        self.present()
        if not blacfg.getboolean("general", "always.show.tray"):
            self.__tray.set_visible(False)
        self.__visualization.flush_buffers()

    def toggle_hide(self):
        self.__hide_windows(self.get_visible())

    def destroy_(self, *args):
        self.__tray.set_visible(False)
        self.hide()
        self.__fullscreen_window.hide()

    def __update_title(self):
        track = player.get_track()
        state = player.get_state()

        if state == blaconst.STATE_STOPPED or not track:
            title = "%s %s" % (blaconst.APPNAME, blaconst.VERSION)
            tooltip = "Stopped"
        else:
            if player.radio:
                title = track[TITLE] or "%s - %s" % (
                    blaconst.APPNAME, track["organization"])
            else:
                artist = track[ARTIST]
                title = track[TITLE] or "?"
                if artist and title:
                    title = "%s - %s" % (artist, title)
                else:
                    title = track.basename

            tooltip = title

        self.set_title(title)
        self.__tray.set_tooltip(tooltip)
        if not blacfg.getboolean("general", "tray.show.tooltip"):
            self.__tray.set_has_tooltip(False)

    def __hide_windows(self, yes):
        blaguiutils.set_visible(not yes)
        if yes:
            self.hide()
            self.__tray.set_visible(True)
        else:
            self.raise_window()

    def __window_state_event(self, event):
        self.__is_fullscreen = bool(
            event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN)

    @property
    def is_fullscreen(self):
        return self.__is_fullscreen

    def __delete_event(self, window, event):
        if blacfg.getboolean("general", "close.to.tray"):
            self.toggle_hide()
            return True
        blaplay.shutdown()
        return False

    def __toggle_visualization(self, event):
        self.__visualization.set_visible(event.get_active())

    def __toggle_statusbar(self, event):
        self.__statusbar.set_visible(event.get_active())

    def __set_file_chooser_directory(self, diag):
        directory = blacfg.getstring("general", "filechooser.directory")
        if not directory or not os.path.isdir:
            directory = os.path.expanduser("~")
        diag.set_current_folder(directory)

    def __open_playlist(self, window):
        diag = gtk.FileChooserDialog(
            "Select playlist", buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                                        gtk.STOCK_OPEN, gtk.RESPONSE_OK))
        diag.set_local_only(True)
        self.__set_file_chooser_directory(diag)

        response = diag.run()
        path = diag.get_filename()
        diag.destroy()

        if response == gtk.RESPONSE_OK and path:
            path = path.strip()
            if playlist_manager.open_playlist(path):
                self.__view.set_view(blaconst.VIEW_PLAYLISTS)
                blacfg.set("general", "filechooser.directory",
                           os.path.dirname(path))

    def __add_tracks(self, files=True):
        if files:
            action = gtk.FILE_CHOOSER_ACTION_OPEN
        else:
            action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
        diag = gtk.FileChooserDialog(
            "Select files", action=action,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
                     gtk.RESPONSE_OK))
        diag.set_select_multiple(True)
        diag.set_local_only(True)
        self.__set_file_chooser_directory(diag)

        response = diag.run()
        filenames = diag.get_filenames()
        diag.destroy()

        if response == gtk.RESPONSE_OK and filenames:
            filenames = map(str.strip, filenames)
            playlist_manager.add_to_current_playlist(filenames, resolve=True)
            blacfg.set("general", "filechooser.directory",
                       os.path.dirname(filenames[0]))

    def __save_playlist(self, window):
        diag = gtk.FileChooserDialog(
            "Save playlist", action=gtk.FILE_CHOOSER_ACTION_SAVE,
            buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE,
                     gtk.RESPONSE_OK))
        diag.set_do_overwrite_confirmation(True)
        self.__set_file_chooser_directory(diag)

        items = [
            ("M3U", "audio/x-mpegurl", "m3u"),
            ("PlS", "audio/x-scpls", "pls", ),
            ("XSPF", "application/xspf+xml", "xspf"),
            ("Decide by extension", None, None)
        ]
        for label, mime_type, extension in items:
            filt = gtk.FileFilter()
            filt.set_name(label)
            filt.add_pattern("*.%s" % extension)
            if mime_type:
                filt.add_mime_type(mime_type)
            diag.add_filter(filt)

        # Add combobox to the dialog to choose whether to save relative or
        # absolute paths in the playlist.
        box = diag.child
        hbox = gtk.HBox()
        cb = gtk.combo_box_new_text()
        hbox.pack_end(cb, expand=False, fill=False)
        box.pack_start(hbox, expand=False, fill=False)
        box.show_all()
        map(cb.append_text, ["Relative paths", "Absolute paths"])
        cb.set_active(0)

        def filter_changed(diag, filt):
            filt = diag.get_filter()
            if diag.list_filters().index(filt) == 2:
                sensitive = False
            else:
                sensitive = True
            cb.set_sensitive(sensitive)
        diag.connect("notify::filter", filter_changed)

        response = diag.run()
        path = diag.get_filename()

        if response == gtk.RESPONSE_OK and path:
            filt = diag.get_filter()
            type_ = items[diag.list_filters().index(filt)][-1]
            path = path.strip()
            if type_ is None:
                type_ = blautil.get_extension(path)
            playlist_manager.save(path, type_, cb.get_active() == 0)
            blacfg.set("general", "filechooser.directory",
                       os.path.dirname(path))

        diag.destroy()