GTK UI search by torrent name filter as a "toolbar".

Now, instead of permanently having a search box to filter the visible torrents by name, we now, mimic a toolbar just for that, mapped to CTRL-F. There's also a menu item in the "View" menu and a toolbar icon to toggle it. Implemented "Match Case" for the search.
This commit is contained in:
Pedro Algarvio 2011-05-31 13:38:48 +01:00
parent 0ba51d0e51
commit ea438609bf
5 changed files with 436 additions and 480 deletions

View File

@ -80,8 +80,23 @@ def filter_one_keyword(torrent_ids, keyword):
def filter_by_name(torrent_ids, search_string):
all_torrents = component.get("TorrentManager").torrents
try:
search_string, match_case = search_string[0].split('::match')
except ValueError:
search_string = search_string[0]
match_case = False
if match_case is False:
search_string = search_string.lower()
for torrent_id in torrent_ids:
if search_string[0].lower() in all_torrents[torrent_id].filename.lower():
torrent_name = all_torrents[torrent_id].get_name()
if match_case is False:
torrent_name = all_torrents[torrent_id].get_name().lower()
else:
torrent_name = all_torrents[torrent_id].get_name()
if search_string in torrent_name:
yield torrent_id
def tracker_error_filter(torrent_ids, values):

View File

@ -225,6 +225,30 @@ class Torrent(object):
def get_options(self):
return self.options
def get_name(self):
if self.handle.has_metadata():
name = self.torrent_info.file_at(0).path.split("/", 1)[0]
if not name:
name = self.torrent_info.name()
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
elif self.magnet:
try:
keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')])
name = keys.get('dn')
if not name:
return self.torrent_id
name = unquote(name).replace('+', ' ')
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
except:
pass
return self.torrent_id
def set_owner(self, account):
self.owner = account
@ -685,32 +709,6 @@ class Torrent(object):
return self.torrent_info.comment()
return ""
def ti_name():
if self.handle.has_metadata():
name = self.torrent_info.file_at(0).path.split("/", 1)[0]
if not name:
name = self.torrent_info.name()
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
elif self.magnet:
try:
keys = dict([k.split('=') for k in self.magnet.split('?')[-1].split('&')])
name = keys.get('dn')
if not name:
return self.torrent_id
name = unquote(name).replace('+', ' ')
try:
return name.decode("utf8", "ignore")
except UnicodeDecodeError:
return name
except:
pass
return self.torrent_id
def ti_priv():
if self.handle.has_metadata():
return self.torrent_info.priv()
@ -742,7 +740,7 @@ class Torrent(object):
"file_progress": self.get_file_progress,
"files": self.get_files,
"is_seed": self.handle.is_seed,
"name": ti_name,
"name": self.get_name,
"num_files": ti_num_files,
"num_pieces": ti_num_pieces,
"pieces": ti_pieces_info,

View File

@ -38,14 +38,6 @@
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_addtorrent_activate"/>
<accelerator key="O" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<child internal-child="image">
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
@ -58,14 +50,6 @@
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_createtorrent_activate"/>
<accelerator key="N" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<child internal-child="image">
<widget class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-new</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
@ -83,14 +67,6 @@
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_quitdaemon_activate"/>
<accelerator key="Q" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
<child internal-child="image">
<widget class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-quit</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
@ -148,14 +124,6 @@
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_connectionmanager_activate"/>
<accelerator key="M" signal="activate" modifiers="GDK_CONTROL_MASK"/>
<child internal-child="image">
<widget class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-network</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
</widget>
@ -239,6 +207,18 @@
<property name="use_underline">True</property>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="find_menuitem">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_Find ...</property>
<property name="use_underline">True</property>
<signal name="activate" handler="on_find_menuitem_activate"/>
<accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
</widget>
</child>
<child>
<widget class="GtkMenuItem" id="menu_Sidebar">
<property name="visible">True</property>
@ -302,14 +282,6 @@
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_homepage_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-home</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
@ -324,14 +296,6 @@
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_faq_activate"/>
<accelerator key="F1" signal="activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image6">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-dialog-question</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
@ -344,15 +308,6 @@
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_community_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image7">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0.4699999988079071</property>
<property name="stock">gtk-info</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
@ -383,10 +338,6 @@
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<widget class="GtkToolbar" id="toolbar">
<property name="visible">True</property>
@ -426,6 +377,25 @@
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<widget class="GtkToolButton" id="toolbutton_filter">
<property name="visible">True</property>
<property name="sensitive">False</property>
<property name="can_focus">False</property>
<property name="tooltip" translatable="yes">Filter torrents by name.
This will filter torrents for the current selection on the sidebar.</property>
<property name="use_action_appearance">False</property>
<property name="label" translatable="yes">_Filter</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-find</property>
<signal name="clicked" handler="on_toolbutton_filter_clicked"/>
<accelerator key="f" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="homogeneous">True</property>
</packing>
</child>
<child>
<widget class="GtkSeparatorToolItem" id="separatortoolitem1">
<property name="visible">True</property>
@ -562,39 +532,8 @@
</child>
</widget>
<packing>
<property name="expand">True</property>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="search_torrents_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="tooltip" translatable="yes">Search torrents by name</property>
<property name="invisible_char">•</property>
<property name="truncate_multiline">True</property>
<property name="caps_lock_warning">False</property>
<property name="secondary_icon_stock">gtk-clear</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">True</property>
<property name="primary_icon_sensitive">False</property>
<property name="secondary_icon_sensitive">True</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Clear the search</property>
<signal name="changed" handler="on_search_torrents_entry_changed"/>
<signal name="icon_press" handler="on_search_torrents_entry_icon_press"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">5</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
@ -624,9 +563,108 @@
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<widget class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<widget class="GtkHBox" id="search_box">
<property name="can_focus">False</property>
<child>
<widget class="GtkButton" id="close_search_button">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip" translatable="yes">Close</property>
<property name="use_action_appearance">False</property>
<property name="relief">none</property>
<signal name="clicked" handler="on_close_search_button_clicked"/>
<child>
<widget class="GtkImage" id="close_search_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-close</property>
<property name="icon-size">2</property>
</widget>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Filter:</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">1</property>
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="search_torrents_entry">
<property name="width_request">350</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="has_tooltip">True</property>
<property name="tooltip" translatable="yes">Filter torrents by name.
This will filter torrents for the current selection on the sidebar.</property>
<property name="invisible_char">•</property>
<property name="truncate_multiline">True</property>
<property name="invisible_char_set">True</property>
<property name="caps_lock_warning">False</property>
<property name="secondary_icon_stock">gtk-clear</property>
<property name="primary_icon_activatable">True</property>
<property name="secondary_icon_activatable">True</property>
<property name="primary_icon_sensitive">False</property>
<property name="secondary_icon_sensitive">True</property>
<property name="secondary_icon_tooltip_text" translatable="yes">Clear the search</property>
<property name="secondary_icon_tooltip_markup" translatable="yes">Clear the search</property>
<signal name="changed" handler="on_search_torrents_entry_changed"/>
<signal name="icon_press" handler="on_search_torrents_entry_icon_press"/>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="padding">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="search_torrents_match">
<property name="label" translatable="yes">_Match Case</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="draw_indicator">True</property>
<signal name="toggled" handler="on_search_torrents_match_toggled"/>
</widget>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">1</property>
<property name="position">3</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<property name="shadow_type">out</property>
@ -639,9 +677,16 @@
</widget>
</child>
</widget>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="resize">True</property>
<property name="shrink">False</property>
<property name="shrink">True</property>
</packing>
</child>
</widget>
@ -684,152 +729,6 @@
</widget>
</child>
</widget>
<widget class="GtkMenu" id="menu_file_tab">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<widget class="GtkImageMenuItem" id="menuitem_open_file">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="on_menuitem_open_file_activate"/>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="menuitem3">
<property name="visible">True</property>
<property name="can_focus">False</property>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="menuitem_expand_all">
<property name="label" translatable="yes">_Expand All</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_expand_all_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-zoom-fit</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkSeparatorMenuItem" id="menuitem_priority_sep">
<property name="visible">True</property>
<property name="can_focus">False</property>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="menuitem_donotdownload">
<property name="label" translatable="yes">_Do Not Download</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_donotdownload_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-no</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="menuitem_normal">
<property name="label" translatable="yes">_Normal Priority</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_normal_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-yes</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="menuitem_high">
<property name="label" translatable="yes">_High Priority</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_high_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-go-up</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
<child>
<widget class="GtkImageMenuItem" id="menuitem_highest">
<property name="label" translatable="yes">Hi_ghest Priority</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_highest_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-goto-top</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
</widget>
<widget class="GtkMenu" id="menu_peer_tab">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<widget class="GtkImageMenuItem" id="menuitem4">
<property name="label" translatable="yes">_Add Peer</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip" translatable="yes">Add a peer by its IP</property>
<property name="use_action_appearance">False</property>
<property name="use_underline">True</property>
<property name="use_stock">False</property>
<signal name="activate" handler="on_menuitem_add_peer_activate"/>
<child internal-child="image">
<widget class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="stock">gtk-add</property>
<property name="icon-size">1</property>
</widget>
</child>
</widget>
</child>
</widget>
<widget class="GtkDialog" id="move_storage_dialog">
<property name="can_focus">False</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>

View File

@ -74,7 +74,9 @@ class ToolBar(component.Component):
"toolbutton_pause",
"toolbutton_resume",
"toolbutton_queue_up",
"toolbutton_queue_down"
"toolbutton_queue_down",
"toolbutton_filter",
"find_menuitem"
]
self.config.register_set_function("classic_mode", self._on_classic_mode, True)

View File

@ -184,6 +184,86 @@ def seed_peer_column_sort(model, iter1, iter2, data):
return queue_peer_seed_sort_function(v2, v4)
return queue_peer_seed_sort_function(v1, v3)
class SearchBox(object):
def __init__(self, torrentview):
self.torrentview = torrentview
self.window = torrentview.window
self.visible = False
self.search_pending = None
self.search_box = self.window.main_glade.get_widget("search_box")
self.search_torrents_entry = self.window.main_glade.get_widget("search_torrents_entry")
self.close_search_button = self.window.main_glade.get_widget("close_search_button")
self.match_search_button = self.window.main_glade.get_widget("search_torrents_match")
self.window.main_glade.signal_autoconnect(self)
def show(self):
self.visible = True
self.search_box.show_all()
def hide(self):
self.visible = False
self.clear_search()
self.search_box.hide_all()
def clear_search(self):
if self.search_pending and self.search_pending.active():
self.search_pending.cancel()
self.search_torrents_entry.set_text("")
if self.torrentview.filter and 'name' in self.torrentview.filter:
self.torrentview.filter.pop('name', None)
self.search_pending = reactor.callLater(0.5, self.torrentview.update)
def set_search_filter(self):
if self.search_pending and self.search_pending.active():
self.search_pending.cancel()
if self.torrentview.filter and 'name' in self.torrentview.filter:
self.torrentview.filter.pop('name', None)
elif self.torrentview.filter is None:
self.torrentview.filter = {}
search_string = self.search_torrents_entry.get_text()
if not search_string:
self.clear_search()
else:
if self.match_search_button.get_active():
search_string += '::match'
self.torrentview.filter['name'] = search_string
def on_close_search_button_clicked(self, widget):
self.hide()
def on_find_menuitem_activate(self, widget):
if self.visible:
self.hide()
else:
self.show()
def on_toolbutton_filter_clicked(self, widget):
if self.visible:
self.hide()
else:
self.show()
def on_search_torrents_match_toggled(self, widget):
if self.search_torrents_entry.get_text():
self.set_search_filter()
self.search_pending = reactor.callLater(0.5, self.torrentview.update)
def on_search_torrents_entry_icon_press(self, entry, icon, event):
if icon != gtk.ENTRY_ICON_SECONDARY:
return
self.clear_search()
def on_search_torrents_entry_changed(self, widget):
self.set_search_filter()
self.search_pending = reactor.callLater(0.7, self.torrentview.update)
class TorrentView(listview.ListView, component.Component):
"""TorrentView handles the listing of torrents."""
def __init__(self):
@ -272,7 +352,6 @@ class TorrentView(listview.ListView, component.Component):
# Set filter to None for now
self.filter = None
self.search_pending = None
### Connect Signals ###
# Connect to the 'button-press-event' to know when to bring up the
@ -290,15 +369,6 @@ class TorrentView(listview.ListView, component.Component):
self.treeview.connect("key-press-event", self.on_key_press_event)
self.treeview.connect("columns-changed", self.on_columns_changed_event)
self.search_torrents_entry = self.window.main_glade.get_widget("search_torrents_entry")
self.search_torrents_entry.connect(
"icon-press", self.on_search_torrents_entry_icon_press
)
self.search_torrents_entry.connect(
"changed", self.on_search_torrents_entry_changed
)
client.register_event_handler("TorrentStateChangedEvent", self.on_torrentstatechanged_event)
client.register_event_handler("TorrentAddedEvent", self.on_torrentadded_event)
client.register_event_handler("TorrentRemovedEvent", self.on_torrentremoved_event)
@ -306,6 +376,8 @@ class TorrentView(listview.ListView, component.Component):
client.register_event_handler("SessionResumedEvent", self.on_sessionresumed_event)
client.register_event_handler("TorrentQueueChangedEvent", self.on_torrentqueuechanged_event)
self.search_box = SearchBox(self)
def start(self):
"""Start the torrentview"""
# We need to get the core session state to know which torrents are in
@ -332,7 +404,7 @@ class TorrentView(listview.ListView, component.Component):
self.liststore.clear()
self.prev_status = {}
self.filter = None
self.search_torrents_entry.set_text("")
self.search_box.hide()
def shutdown(self):
"""Called when GtkUi is exiting"""
@ -396,7 +468,7 @@ class TorrentView(listview.ListView, component.Component):
def update(self):
if self.got_state:
if self.search_pending is not None and self.search_pending.active():
if self.search_box.search_pending is not None and self.search_box.search_pending.active():
# An update request is scheduled, let's wait for that one
return
# Send a status request
@ -620,33 +692,3 @@ class TorrentView(listview.ListView, component.Component):
torrentmenu = component.get("MenuBar").torrentmenu
torrentmenu.popup(None, None, None, 3, event.time)
return True
def on_search_torrents_entry_icon_press(self, entry, icon, event):
if icon != gtk.ENTRY_ICON_SECONDARY:
return
if self.search_pending and self.search_pending.active():
self.search_pending.cancel()
entry.set_text("")
if self.filter and 'name' in self.filter:
self.filter.pop('name', None)
self.search_pending = reactor.callLater(0.7, self.update)
def on_search_torrents_entry_changed(self, widget):
search_string = widget.get_text().lower()
if self.search_pending and self.search_pending.active():
self.search_pending.cancel()
if not search_string:
if self.filter and 'name' in self.filter:
self.filter.pop('name', None)
self.search_pending = reactor.callLater(0.7, self.update)
return
if self.filter is None:
self.filter = {}
self.filter['name'] = search_string
self.search_pending = reactor.callLater(0.7, self.update)