예제 #1
0
파일: searchbar.py 프로젝트: fqxp/quodlibet
    def __init__(self,
                 filename=None,
                 completion=None,
                 accel_group=None,
                 timeout=DEFAULT_TIMEOUT,
                 validator=Query.validator,
                 star=None):
        super(SearchBarBox, self).__init__(spacing=6)

        if filename is None:
            filename = os.path.join(quodlibet.get_user_dir(), "lists",
                                    "queries")

        combo = ComboBoxEntrySave(filename,
                                  count=8,
                                  validator=validator,
                                  title=_("Saved Searches"),
                                  edit_title=_(u"Edit saved searches…"))

        self.__deferred_changed = DeferredSignal(self.__filter_changed,
                                                 timeout=timeout,
                                                 owner=self)

        self.__combo = combo
        entry = combo.get_child()
        self.__entry = entry
        if completion:
            entry.set_completion(completion)

        self._star = star
        self._query = None
        self.__sig = combo.connect('text-changed', self.__text_changed)

        entry.connect('clear', self.__filter_changed)
        entry.connect('backspace', self.__text_changed)
        entry.connect('populate-popup', self.__menu)
        entry.connect('activate', self.__filter_changed)
        entry.connect('activate', self.__save_search)
        entry.connect('focus-out-event', self.__save_search)
        entry.connect('key-press-event', self.__key_pressed)

        entry.set_placeholder_text(_("Search"))
        entry.set_tooltip_text(
            _("Search your library, "
              "using free text or QL queries"))

        combo.enable_clear_button()
        self.pack_start(combo, True, True, 0)

        if accel_group:
            key, mod = Gtk.accelerator_parse("<Primary>L")
            accel_group.connect(key, mod, 0,
                                lambda *x: entry.mnemonic_activate(True))

        for child in self.get_children():
            child.show_all()
예제 #2
0
    def enable_window_tracking(self, config_prefix, size_suffix=""):
        """Enable tracking/saving of changes and restore size/pos/maximized.

        Make sure to call set_transient_for() before since position is
        restored relative to the parent in this case.

        config_prefix -- prefix for the config key
                         (prefix_size, prefix_position, prefix_maximized)
        size_suffix -- optional suffix for saving the size. For cases where the
                       window has multiple states with different content sizes.
                       (example: edit tags with one song or multiple)

        """

        self.__state = 0
        self.__name = config_prefix
        self.__size_suffix = size_suffix
        self.__save_size_pos_deferred = DeferredSignal(self.__do_save_size_pos,
                                                       timeout=50,
                                                       owner=self)
        self.connect('configure-event', self.__configure_event)
        self.connect('window-state-event', self.__window_state_changed)
        self.connect('notify::visible', self.__visible_changed)
        parent = self.get_transient_for()
        if parent:
            connect_destroy(parent, 'configure-event',
                            self.__parent_configure_event)
        self.__restore_window_state()
예제 #3
0
    def _create_category_widget(self):
        scrolled_window = ScrolledWindow()
        scrolled_window.show()
        scrolled_window.set_shadow_type(Gtk.ShadowType.IN)
        self.view = view = RCMHintedTreeView()
        view.show()
        view.set_headers_visible(False)
        scrolled_window.set_policy(Gtk.PolicyType.NEVER,
                                   Gtk.PolicyType.AUTOMATIC)
        scrolled_window.add(view)
        model = Gtk.ListStore(int, str, str, str, bool)
        filters = self.filters
        for (i, (name, data)) in enumerate(filters):
            filter_type, icon, query, always = data
            enabled = always
            model.append(row=[filter_type, icon, name, query, enabled])

        def search_func(model, column, key, iter, data):
            return key.lower() not in model[iter][column].lower()

        view.set_search_column(self.ModelIndex.NAME)
        view.set_search_equal_func(search_func, None)

        column = Gtk.TreeViewColumn("Songs")
        column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
        renderpb = Gtk.CellRendererPixbuf()
        renderpb.props.xpad = 6
        renderpb.props.ypad = 6
        column.pack_start(renderpb, False)
        column.add_attribute(renderpb, "icon-name", self.ModelIndex.ICON_NAME)
        render = Gtk.CellRendererText()
        render.set_property('ellipsize', Pango.EllipsizeMode.END)

        def cdf(column, cell, model, iter_, user_data):
            on = (self.login_state == State.LOGGED_IN
                  or model[iter_][self.ModelIndex.ALWAYS_ENABLE])
            cell.set_sensitive(on)

        column.set_cell_data_func(render, cdf)
        column.set_cell_data_func(renderpb, cdf)

        view.append_column(column)
        column.pack_start(render, True)
        column.add_attribute(render, "text", self.ModelIndex.NAME)
        view.set_model(model)

        selection = view.get_selection()

        def select_func(sel, model, path, value):
            return (self.login_state == State.LOGGED_IN or
                    model[model.get_iter(path)][self.ModelIndex.ALWAYS_ENABLE])

        selection.set_select_function(select_func)
        selection.select_iter(model.get_iter_first())
        self._refresh_online_filters()
        self.__changed_sig = connect_destroy(selection, 'changed',
                                             DeferredSignal(self._on_select))
        return scrolled_window
예제 #4
0
    def enable_row_update(self, view, sw, column):
        connect_obj(view, 'draw', self.__update_visibility, view)

        connect_destroy(
            sw.get_vadjustment(), "value-changed", self.__stop_update, view)

        self.__pending_paths = []
        self.__update_deferred = DeferredSignal(
            self.__update_visible_rows, timeout=50, priority=GLib.PRIORITY_LOW)
        self.__column = column
        self.__first_expose = True
예제 #5
0
    def enable_tracking(self, player):
        """Follow the active playing song"""

        # for tracking wait a bit to not produce lots of http requests
        defer_set = DeferredSignal(self.set_song, owner=self, timeout=50)

        def next_handler(player, song):
            defer_set(song)

        id_ = player.connect("song-started", next_handler)
        self.connect("destroy", lambda *x: player.disconnect(id_))
예제 #6
0
    def __init__(self, filename=None, completion=None, accel_group=None,
                 timeout=DEFAULT_TIMEOUT, validator=Query.validator,
                 star=None):
        super(SearchBarBox, self).__init__(spacing=6)

        if filename is None:
            filename = os.path.join(
                quodlibet.get_user_dir(), "lists", "queries")

        combo = ComboBoxEntrySave(filename, count=8,
                                  validator=validator,
                                  title=_("Saved Searches"),
                                  edit_title=_(u"Edit saved searches…"))

        self.__deferred_changed = DeferredSignal(
            self.__filter_changed, timeout=timeout, owner=self)

        self.__combo = combo
        entry = combo.get_child()
        self.__entry = entry
        if completion:
            entry.set_completion(completion)

        self._star = star
        self._query = None
        self.__sig = combo.connect('text-changed', self.__text_changed)

        entry.connect('clear', self.__filter_changed)
        entry.connect('backspace', self.__text_changed)
        entry.connect('populate-popup', self.__menu)
        entry.connect('activate', self.__filter_changed)
        entry.connect('activate', self.__save_search)
        entry.connect('focus-out-event', self.__save_search)
        entry.connect('key-press-event', self.__key_pressed)

        entry.set_placeholder_text(_("Search"))
        entry.set_tooltip_text(_("Search your library, "
                                 "using free text or QL queries"))

        combo.enable_clear_button()
        self.pack_start(combo, True, True, 0)

        if accel_group:
            key, mod = Gtk.accelerator_parse("<Primary>L")
            accel_group.connect(key, mod, 0,
                    lambda *x: entry.mnemonic_activate(True))

        for child in self.get_children():
            child.show_all()
예제 #7
0
    def enable_window_tracking(self, config_prefix, size_suffix=""):
        """Enable tracking/saving of changes and restore size/pos/maximized

        config_prefix -- prefix for the config key
                         (prefix_size, prefix_position, prefix_maximized)
        size_suffix -- optional suffix for saving the size. For cases where the
                       window has multiple states with different content sizes.
                       (example: edit tags with one song or multiple)

        """

        self.__state = 0
        self.__name = config_prefix
        self.__size_suffix = size_suffix
        self.__save_size_deferred = DeferredSignal(self.__do_save_size,
                                                   timeout=50,
                                                   owner=self)
        self.connect('configure-event', self.__configure_event)
        self.connect('window-state-event', self.__window_state_changed)
        self.connect('notify::visible', self.__visible_changed)
        self.__restore_window_state()
예제 #8
0
파일: searchbar.py 프로젝트: fqxp/quodlibet
class SearchBarBox(Gtk.HBox):
    """
        A search bar widget for inputting queries.

        signals:
            query-changed - a parsable query string
            focus-out - If the widget gets focused while being focused
                (usually for focusing the songlist)
    """

    __gsignals__ = {
        'query-changed': (GObject.SignalFlags.RUN_LAST, None, (object, )),
        'focus-out': (GObject.SignalFlags.RUN_LAST, None, ()),
    }

    DEFAULT_TIMEOUT = 400

    def __init__(self,
                 filename=None,
                 completion=None,
                 accel_group=None,
                 timeout=DEFAULT_TIMEOUT,
                 validator=Query.validator,
                 star=None):
        super(SearchBarBox, self).__init__(spacing=6)

        if filename is None:
            filename = os.path.join(quodlibet.get_user_dir(), "lists",
                                    "queries")

        combo = ComboBoxEntrySave(filename,
                                  count=8,
                                  validator=validator,
                                  title=_("Saved Searches"),
                                  edit_title=_(u"Edit saved searches…"))

        self.__deferred_changed = DeferredSignal(self.__filter_changed,
                                                 timeout=timeout,
                                                 owner=self)

        self.__combo = combo
        entry = combo.get_child()
        self.__entry = entry
        if completion:
            entry.set_completion(completion)

        self._star = star
        self._query = None
        self.__sig = combo.connect('text-changed', self.__text_changed)

        entry.connect('clear', self.__filter_changed)
        entry.connect('backspace', self.__text_changed)
        entry.connect('populate-popup', self.__menu)
        entry.connect('activate', self.__filter_changed)
        entry.connect('activate', self.__save_search)
        entry.connect('focus-out-event', self.__save_search)
        entry.connect('key-press-event', self.__key_pressed)

        entry.set_placeholder_text(_("Search"))
        entry.set_tooltip_text(
            _("Search your library, "
              "using free text or QL queries"))

        combo.enable_clear_button()
        self.pack_start(combo, True, True, 0)

        if accel_group:
            key, mod = Gtk.accelerator_parse("<Primary>L")
            accel_group.connect(key, mod, 0,
                                lambda *x: entry.mnemonic_activate(True))

        for child in self.get_children():
            child.show_all()

    def set_enabled(self, enabled=True):
        self.__entry.set_sensitive(enabled)

    def set_text(self, text):
        """Set the text without firing any signals"""

        self.__deferred_changed.abort()
        self._update_query_from(text)

        # deactivate all signals and change the entry text
        self.__inhibit()
        self.__entry.set_text(text)
        self.__uninhibit()

    def _update_query_from(self, text):
        # TODO: remove tight coupling to Query
        self._query = Query(text, star=self._star)

    def get_text(self):
        """Get the active text as unicode"""

        return self.__entry.get_text()

    def get_query(self, star=None):
        if star and star != self._star:
            self._star = star
            self._update_query_from(self.get_text())
        return self._query

    def changed(self):
        """Triggers a filter-changed signal if the current text
        is a parsable query
        """

        self.__filter_changed()

    def __inhibit(self):
        self.__combo.handler_block(self.__sig)

    def __uninhibit(self):
        self.__combo.handler_unblock(self.__sig)

    def __menu(self, entry, menu):
        sep = SeparatorMenuItem()
        sep.show()
        menu.prepend(sep)

        cb = ConfigCheckMenuItem(_("Search after _typing"),
                                 'settings',
                                 'eager_search',
                                 populate=True)
        cb.set_tooltip_text(
            _("Show search results after the user stops typing."))
        cb.show()
        menu.prepend(cb)

    def __mnemonic_activate(self, label, group_cycling):
        widget = label.get_mnemonic_widget()
        if widget.is_focus():
            self.emit('focus-out')
            return True

    def __save_search(self, entry, *args):
        # only save the query on focus-out if eager_search is turned on
        if (len(args) > 0 and args[0]
                and not config.getboolean('settings', 'eager_search')):
            return

        text = self.get_text().strip()
        if text and self._query and self._query.is_parsable:
            # Adding the active text to the model triggers a changed signal
            # (get_active is no longer -1), so inhibit
            self.__inhibit()
            self.__combo.prepend_text(text)
            self.__combo.write()
            self.__uninhibit()

    def __key_pressed(self, entry, event):
        if (is_accel(event, '<Primary>Return')
                or is_accel(event, '<Primary>KP_Enter')):
            # Save query on Primary+Return accel, even though the focus is kept
            self.__save_search(entry)
        return False

    def __filter_changed(self, *args):
        self.__deferred_changed.abort()
        text = self.get_text()
        self._update_query_from(text)
        if self._query.is_parsable:
            GLib.idle_add(self.emit, 'query-changed', text)

    def __text_changed(self, *args):
        if not self.__entry.is_sensitive():
            return
        # the combobox has an active entry selected -> no timeout
        # todo: we need a timeout when the selection changed because
        # of keyboard input (up/down arrows)
        if self.__combo.get_active() != -1:
            self.__filter_changed()
            return

        if not config.getboolean('settings', 'eager_search'):
            return

        self.__deferred_changed()
예제 #9
0
class SearchBarBox(Gtk.HBox):
    """
        A search bar widget for inputting queries.

        signals:
            query-changed - a parsable query string
            focus-out - If the widget gets focused while being focused
                (usually for focusing the songlist)
    """

    __gsignals__ = {
        'query-changed': (
            GObject.SignalFlags.RUN_LAST, None, (object,)),
        'focus-out': (GObject.SignalFlags.RUN_LAST, None, ()),
        }

    timeout = 400

    def __init__(self, filename=None, completion=None, accel_group=None):
        super(SearchBarBox, self).__init__(spacing=6)

        if filename is None:
            filename = os.path.join(
                quodlibet.get_user_dir(), "lists", "queries")

        combo = ComboBoxEntrySave(filename, count=8,
                validator=QueryValidator, title=_("Saved Searches"),
                edit_title=_(u"Edit saved searches…"))

        self.__deferred_changed = DeferredSignal(
            self.__filter_changed, timeout=self.timeout, owner=self)

        self.__combo = combo
        entry = combo.get_child()
        self.__entry = entry
        if completion:
            entry.set_completion(completion)

        self.__sig = combo.connect('text-changed', self.__text_changed)

        entry.connect('clear', self.__filter_changed)
        entry.connect('backspace', self.__text_changed)
        entry.connect('populate-popup', self.__menu)
        entry.connect('activate', self.__filter_changed)
        entry.connect('activate', self.__save_search)
        entry.connect('focus-out-event', self.__save_search)

        entry.set_placeholder_text(_("Search"))
        entry.set_tooltip_text(_("Search your library, "
                                 "using free text or QL queries"))

        combo.enable_clear_button()
        self.pack_start(combo, True, True, 0)

        if accel_group:
            key, mod = Gtk.accelerator_parse("<ctrl>L")
            accel_group.connect(key, mod, 0,
                    lambda *x: entry.mnemonic_activate(True))

        for child in self.get_children():
            child.show_all()

    def set_text(self, text):
        """Set the text without firing any signals"""

        self.__deferred_changed.abort()

        # deactivate all signals and change the entry text
        self.__inhibit()
        self.__entry.set_text(text)
        self.__uninhibit()

    def get_text(self):
        """Get the active text as unicode"""

        return self.__entry.get_text().decode("utf-8")

    def changed(self):
        """Triggers a filter-changed signal if the current text
        is a parsable query
        """

        self.__filter_changed()

    def __inhibit(self):
        self.__combo.handler_block(self.__sig)

    def __uninhibit(self):
        self.__combo.handler_unblock(self.__sig)

    def __menu(self, entry, menu):
        sep = SeparatorMenuItem()
        sep.show()
        menu.prepend(sep)

        cb = ConfigCheckMenuItem(
            _("Search after _typing"), 'settings', 'eager_search',
            populate=True)
        cb.set_tooltip_text(
            _("Show search results after the user stops typing."))
        cb.show()
        menu.prepend(cb)

    def __mnemonic_activate(self, label, group_cycling):
        widget = label.get_mnemonic_widget()
        if widget.is_focus():
            self.emit('focus-out')
            return True

    def __save_search(self, entry, *args):
        # only save the query on focus-out if eager_search is turned on
        if args and not config.getboolean('settings', 'eager_search'):
            return

        text = self.get_text().strip()
        if text and Query.is_parsable(text):
            # Adding the active text to the model triggers a changed signal
            # (get_active is no longer -1), so inhibit
            self.__inhibit()
            self.__combo.prepend_text(text)
            self.__combo.write()
            self.__uninhibit()

    def __filter_changed(self, *args):
        self.__deferred_changed.abort()
        text = self.get_text()
        if Query.is_parsable(text):
            GLib.idle_add(self.emit, 'query-changed', text)

    def __text_changed(self, *args):
        # the combobox has an active entry selected -> no timeout
        # todo: we need a timeout when the selection changed because
        # of keyboard input (up/down arrows)
        if self.__combo.get_active() != -1:
            self.__filter_changed()
            return

        if not config.getboolean('settings', 'eager_search'):
            return

        self.__deferred_changed()
예제 #10
0
    def __init__(self, library, player):
        super().__init__(spacing=3)
        sw = ScrolledWindow()
        sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        sw.set_shadow_type(Gtk.ShadowType.IN)
        save_interval_secs = config.getint("autosave", "queue_interval")
        self.queue = PlayQueue(library, player, save_interval_secs)
        self.queue.props.expand = True
        sw.add(self.queue)

        add_css(self, ".ql-expanded title { margin-bottom: 5px; }")

        outer = ExpandBoxHack()

        left = Gtk.HBox(spacing=12)

        hb2 = Gtk.HBox(spacing=3)
        state_icon = PlaybackStatusIcon()
        state_icon.stop()
        state_icon.show()
        hb2.pack_start(state_icon, True, True, 0)
        name_label = Gtk.Label(label=_("_Queue"), use_underline=True)
        name_label.set_size_request(-1, 24)
        hb2.pack_start(name_label, True, True, 0)
        left.pack_start(hb2, False, True, 0)

        menu = Gtk.Menu()

        self.count_label = count_label = Gtk.Label()
        self.count_label.set_property("ellipsize", Pango.EllipsizeMode.END)
        self.count_label.set_width_chars(10)
        self.count_label.get_style_context().add_class("dim-label")
        left.pack_start(count_label, False, True, 0)

        outer.pack_start(left, True, True, 0)

        self.set_label_fill(True)

        clear_item = SmallImageButton(image=SymbolicIconImage(
            Icons.USER_TRASH, Gtk.IconSize.MENU),
                                      relief=Gtk.ReliefStyle.NONE,
                                      tooltip_text=_("Clear Queue"))
        clear_item.connect("clicked", self.__clear_queue)
        outer.pack_start(clear_item, False, False, 3)

        toggle = SmallImageToggleButton(
            image=SymbolicIconImage(Icons.SYSTEM_LOCK_SCREEN,
                                    Gtk.IconSize.MENU),
            relief=Gtk.ReliefStyle.NONE,
            tooltip_text=_(
                "Disable queue - the queue will be ignored when playing"))
        disabled = config.getboolean("memory", "queue_disable", False)
        toggle.props.active = disabled
        self.__queue_disable(disabled)
        toggle.connect('toggled',
                       lambda b: self.__queue_disable(b.props.active))
        outer.pack_start(toggle, False, False, 3)

        mode_menu = Gtk.Menu()

        norm_mode_item = RadioMenuItem(
            label=_("Ephemeral"),
            tooltip_text=_("Remove songs from the queue after playing them"),
            group=None)
        mode_menu.append(norm_mode_item)
        norm_mode_item.set_active(True)
        norm_mode_item.connect("toggled",
                               lambda _: self.__keep_songs_enable(False))

        keep_mode_item = RadioMenuItem(
            label=_("Persistent"),
            tooltip_text=_("Keep songs in the queue after playing them"),
            group=norm_mode_item)
        mode_menu.append(keep_mode_item)
        keep_mode_item.connect("toggled",
                               lambda b: self.__keep_songs_enable(True))
        keep_mode_item.set_active(
            config.getboolean("memory", "queue_keep_songs", False))

        mode_item = MenuItem(_("Mode"), Icons.SYSTEM_RUN)
        mode_item.set_submenu(mode_menu)
        menu.append(mode_item)

        rand_checkbox = ConfigCheckMenuItem(_("_Random"),
                                            "memory",
                                            "shufflequeue",
                                            populate=True)
        rand_checkbox.connect('toggled', self.__queue_shuffle)
        self.set_shuffled(rand_checkbox.get_active())
        menu.append(rand_checkbox)

        stop_checkbox = ConfigCheckMenuItem(_("Stop at End"),
                                            "memory",
                                            "queue_stop_at_end",
                                            populate=True)
        menu.append(stop_checkbox)

        button = SmallMenuButton(SymbolicIconImage(Icons.EMBLEM_SYSTEM,
                                                   Gtk.IconSize.MENU),
                                 arrow=True)
        button.set_relief(Gtk.ReliefStyle.NORMAL)
        button.show_all()
        button.hide()
        button.set_no_show_all(True)
        menu.show_all()
        button.set_menu(menu)

        outer.pack_start(button, False, False, 3)

        close_button = SmallImageButton(image=SymbolicIconImage(
            "window-close", Gtk.IconSize.MENU),
                                        relief=Gtk.ReliefStyle.NONE)

        close_button.connect("clicked", lambda *x: self.hide())

        outer.pack_start(close_button, False, False, 6)

        self.set_label_widget(outer)
        self.add(sw)
        self.connect('notify::expanded', self.__expand, button)
        self.connect('notify::expanded', self.__expand, button)

        targets = [("text/x-quodlibet-songs", Gtk.TargetFlags.SAME_APP,
                    DND_QL), ("text/uri-list", 0, DND_URI_LIST)]
        targets = [Gtk.TargetEntry.new(*t) for t in targets]

        self.drag_dest_set(Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY)
        self.connect('drag-motion', self.__motion)
        self.connect('drag-data-received', self.__drag_data_received)

        self.queue.model.connect_after('row-inserted',
                                       DeferredSignal(self.__check_expand),
                                       count_label)
        self.queue.model.connect_after('row-deleted',
                                       DeferredSignal(self.__update_count),
                                       count_label)

        self.__update_count(self.model, None, count_label)

        connect_destroy(player, 'song-started', self.__update_state_icon,
                        state_icon)
        connect_destroy(player, 'paused', self.__update_state_icon_pause,
                        state_icon, True)
        connect_destroy(player, 'unpaused', self.__update_state_icon_pause,
                        state_icon, False)

        connect_destroy(player, 'song-started', self.__song_started,
                        self.queue.model)
        connect_destroy(player, 'song-ended', self.__update_queue_stop,
                        self.queue.model)

        # to make the children clickable if mapped
        # ....no idea why, but works
        def hack(expander):
            label = expander.get_label_widget()
            if label:
                label.unmap()
                label.map()

        self.connect("map", hack)

        self.set_expanded(config.getboolean("memory", "queue_expanded"))
        self.notify("expanded")

        for child in self.get_children():
            child.show_all()