mirror of
https://git.deluge-torrent.org/deluge
synced 2025-04-04 19:38:03 +03:00
When one tries to search in the TorrentView, then a little search window appears but does nothing. Ref: https://lazka.github.io/pgi-docs/Gtk-3.0/callbacks.html#Gtk.TreeViewSearchEqualFunc Closes: https://dev.deluge-torrent.org/ticket/3350 Closes: https://github.com/deluge-torrent/deluge/pull/467
831 lines
30 KiB
Python
831 lines
30 KiB
Python
#
|
|
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
|
|
#
|
|
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
|
|
# the additional special exception to link portions of this program with the OpenSSL library.
|
|
# See LICENSE for more details.
|
|
#
|
|
|
|
import logging
|
|
|
|
from gi.repository import GObject, Gtk
|
|
|
|
from deluge.common import decode_bytes
|
|
|
|
from .common import cmp, load_pickled_state_file, save_pickled_state_file
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class ListViewColumnState:
|
|
"""Class used for saving/loading column state."""
|
|
|
|
def __init__(self, name, position, width, visible, sort, sort_order):
|
|
self.name = name
|
|
self.position = position
|
|
self.width = width
|
|
self.visible = visible
|
|
self.sort = sort
|
|
self.sort_order = sort_order
|
|
|
|
|
|
class ListView:
|
|
"""ListView is used to make custom GtkTreeViews. It supports the adding
|
|
and removing of columns, creating a menu for a column toggle list and
|
|
support for 'status_field's which are used while updating the columns data.
|
|
"""
|
|
|
|
class ListViewColumn:
|
|
"""Holds information regarding a column in the ListView"""
|
|
|
|
def __init__(self, name, column_indices):
|
|
# Name is how a column is identified and is also the header
|
|
self.name = name
|
|
# Column_indices holds the indexes to the liststore_columns that
|
|
# this column utilizes. It is stored as a list.
|
|
self.column_indices = column_indices
|
|
# Column is a reference to the GtkTreeViewColumn object
|
|
self.column = None
|
|
# This is the name of the status field that the column will query
|
|
# the core for if an update is called.
|
|
self.status_field = None
|
|
# If column is 'hidden' then it will not be visible and will not
|
|
# show up in any menu listing; it cannot be shown ever.
|
|
self.hidden = False
|
|
# If this is set, it is used to sort the model
|
|
self.sort_func = None
|
|
self.sort_id = None
|
|
# Values needed to update TreeViewColumns
|
|
self.column_type = None
|
|
self.renderer = None
|
|
self.text_index = 0
|
|
self.value_index = 0
|
|
self.pixbuf_index = 0
|
|
self.data_func = None
|
|
|
|
class TreeviewColumn(Gtk.TreeViewColumn):
|
|
"""
|
|
TreeViewColumn does not signal right-click events, and we need them
|
|
This subclass is equivalent to TreeViewColumn, but it signals these events
|
|
|
|
Most of the code of this class comes from Quod Libet (http://www.sacredchao.net/quodlibet)
|
|
"""
|
|
|
|
__gsignals__ = {
|
|
'button-press-event': (GObject.SignalFlags.RUN_LAST, None, (object,))
|
|
}
|
|
|
|
def __init__(self, title=None, cell_renderer=None, **args):
|
|
"""Constructor, see Gtk.TreeViewColumn"""
|
|
Gtk.TreeViewColumn.__init__(self, title, cell_renderer, **args)
|
|
label = Gtk.Label(label=title)
|
|
self.set_widget(label)
|
|
label.show()
|
|
label.__realize = label.connect('realize', self.on_realize)
|
|
self.title = title
|
|
self.data_func = None
|
|
self.data_func_data = None
|
|
self.cell_renderer = None
|
|
|
|
def on_realize(self, widget):
|
|
widget.disconnect(widget.__realize)
|
|
del widget.__realize
|
|
button = widget.get_ancestor(Gtk.Button)
|
|
if button is not None:
|
|
button.connect('button-press-event', self.on_button_pressed)
|
|
|
|
def on_button_pressed(self, widget, event):
|
|
self.emit('button-press-event', event)
|
|
|
|
def set_cell_data_func_attributes(self, cell_renderer, func, func_data=None):
|
|
"""Store the values to be set by set_cell_data_func"""
|
|
self.data_func = func
|
|
self.data_func_data = func_data
|
|
self.cell_renderer = cell_renderer
|
|
|
|
def set_visible(self, visible):
|
|
Gtk.TreeViewColumn.set_visible(self, visible)
|
|
if self.data_func:
|
|
if not visible:
|
|
# Set data function to None to prevent unnecessary calls when column is hidden
|
|
self.set_cell_data_func(self.cell_renderer, None, func_data=None)
|
|
else:
|
|
self.set_cell_data_func(
|
|
self.cell_renderer, self.data_func, self.data_func_data
|
|
)
|
|
|
|
def set_col_attributes(self, renderer, add=True, **kw):
|
|
if add is True:
|
|
for attr, value in kw.items():
|
|
self.add_attribute(renderer, attr, value)
|
|
else:
|
|
self.set_attributes(renderer, **kw)
|
|
|
|
def __init__(self, treeview_widget=None, state_file=None):
|
|
log.debug('ListView initialized..')
|
|
|
|
if treeview_widget is not None:
|
|
# User supplied a treeview widget
|
|
self.treeview = treeview_widget
|
|
else:
|
|
self.treeview = Gtk.TreeView()
|
|
|
|
self.treeview.set_enable_search(True)
|
|
self.treeview.set_search_equal_func(self.on_keypress_search_by_name, None)
|
|
|
|
if state_file:
|
|
self.load_state(state_file)
|
|
|
|
self.liststore = None
|
|
self.model_filter = None
|
|
|
|
self.treeview.set_reorderable(False)
|
|
self.treeview.set_rubber_banding(True) # Enable mouse multi-row selection.
|
|
self.treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
|
|
|
|
# Dictionary of 'header' or 'name' to ListViewColumn object
|
|
self.columns = {}
|
|
# Column_index keeps track of the order of the visible columns.
|
|
self.column_index = []
|
|
# The column types for the list store.. this may have more entries than
|
|
# visible columns due to some columns utilizing more than 1 liststore
|
|
# column and some columns being hidden.
|
|
self.liststore_columns = []
|
|
# The GtkMenu that is created after every addition, removal or reorder
|
|
self.menu = None
|
|
# A list of menus that self.menu will be a submenu of everytime it is
|
|
# created.
|
|
self.checklist_menus = []
|
|
|
|
# Store removed columns state. This is needed for plugins that remove
|
|
# their columns prior to having the state list saved on shutdown.
|
|
self.removed_columns_state = []
|
|
|
|
# Since gtk TreeModelSort doesn't do stable sort, remember last sort order so we can
|
|
self.last_sort_order = {}
|
|
self.unique_column_id = None
|
|
self.default_sort_column_id = None
|
|
|
|
# Create the model filter and column
|
|
self.add_bool_column('filter', hidden=True)
|
|
|
|
def create_model_filter(self):
|
|
"""create new filter-model
|
|
must be called after listview.create_new_liststore
|
|
"""
|
|
model_filter = self.liststore.filter_new()
|
|
model_filter.set_visible_column(self.columns['filter'].column_indices[0])
|
|
self.model_filter = Gtk.TreeModelSort(model=model_filter)
|
|
self.model_filter.connect('sort-column-changed', self.on_model_sort_changed)
|
|
self.model_filter.connect('row-inserted', self.on_model_row_inserted)
|
|
self.treeview.set_model(self.model_filter)
|
|
self.set_sort_functions()
|
|
self.set_model_sort()
|
|
|
|
def set_model_sort(self):
|
|
column_state = self.get_sort_column_from_state()
|
|
if column_state:
|
|
self.treeview.get_model().set_sort_column_id(
|
|
column_state.sort, column_state.sort_order
|
|
)
|
|
# Using the default sort column
|
|
elif self.default_sort_column_id:
|
|
self.model_filter.set_sort_column_id(
|
|
self.default_sort_column_id, Gtk.SortType.ASCENDING
|
|
)
|
|
self.model_filter.set_default_sort_func(
|
|
self.generic_sort_func, self.get_column_index(_('Added'))[0]
|
|
)
|
|
|
|
def get_sort_column_from_state(self):
|
|
"""Find the first (should only be one) state with sort enabled"""
|
|
if self.state is None:
|
|
return None
|
|
for column_state in self.state:
|
|
if column_state.sort is not None and column_state.sort > -1:
|
|
return column_state
|
|
return None
|
|
|
|
def on_model_sort_changed(self, model):
|
|
if self.unique_column_id:
|
|
self.last_sort_order = {}
|
|
|
|
def record_position(model, path, _iter, data):
|
|
unique_id = model[_iter][self.unique_column_id]
|
|
self.last_sort_order[unique_id] = int(str(path))
|
|
|
|
model.foreach(record_position, None)
|
|
|
|
def on_model_row_inserted(self, model, path, _iter):
|
|
if self.unique_column_id:
|
|
self.last_sort_order.setdefault(
|
|
model[_iter][self.unique_column_id], len(model) - 1
|
|
)
|
|
|
|
def stabilize_sort_func(self, sort_func):
|
|
def stabilized(model, iter1, iter2, data):
|
|
result = sort_func(model, iter1, iter2, data)
|
|
if result == 0 and self.unique_column_id:
|
|
unique1 = model[iter1][self.unique_column_id]
|
|
unique2 = model[iter2][self.unique_column_id]
|
|
if unique1 in self.last_sort_order and unique2 in self.last_sort_order:
|
|
result = cmp(
|
|
self.last_sort_order[unique1], self.last_sort_order[unique2]
|
|
)
|
|
# If all else fails, fall back to sorting by unique column
|
|
if result == 0:
|
|
result = cmp(unique1, unique2)
|
|
|
|
return result
|
|
|
|
return stabilized
|
|
|
|
def generic_sort_func(self, model, iter1, iter2, data):
|
|
return cmp(model[iter1][data], model[iter2][data])
|
|
|
|
def set_sort_functions(self):
|
|
for column in self.columns.values():
|
|
sort_func = column.sort_func or self.generic_sort_func
|
|
self.model_filter.set_sort_func(
|
|
column.sort_id, self.stabilize_sort_func(sort_func), column.sort_id
|
|
)
|
|
|
|
def create_column_state(self, column, position=None):
|
|
if not position:
|
|
# Find the position
|
|
for index, c in enumerate(self.treeview.get_columns()):
|
|
if column.get_title() == c.get_title():
|
|
position = index
|
|
break
|
|
sort = None
|
|
if self.model_filter:
|
|
sort_id, order = self.model_filter.get_sort_column_id()
|
|
col_title = decode_bytes(column.get_title())
|
|
if self.get_column_name(sort_id) == col_title:
|
|
sort = sort_id
|
|
|
|
return ListViewColumnState(
|
|
column.get_title(),
|
|
position,
|
|
column.get_width(),
|
|
column.get_visible(),
|
|
sort,
|
|
int(column.get_sort_order()),
|
|
)
|
|
|
|
def save_state(self, filename):
|
|
"""Saves the listview state (column positions and visibility) to
|
|
filename."""
|
|
# A list of ListViewColumnStates
|
|
state = []
|
|
|
|
# Workaround for all zero widths after removing column on shutdown
|
|
if not any(c.get_width() for c in self.treeview.get_columns()):
|
|
return
|
|
|
|
# Get the list of TreeViewColumns from the TreeView
|
|
for counter, column in enumerate(self.treeview.get_columns()):
|
|
# Append a new column state to the state list
|
|
state.append(self.create_column_state(column, counter))
|
|
|
|
state += self.removed_columns_state
|
|
|
|
self.state = state
|
|
save_pickled_state_file(filename, state)
|
|
|
|
def load_state(self, filename):
|
|
"""Load the listview state from filename."""
|
|
self.state = load_pickled_state_file(filename)
|
|
|
|
def set_treeview(self, treeview_widget):
|
|
"""Set the treeview widget that this listview uses."""
|
|
self.treeview = treeview_widget
|
|
self.treeview.set_model(self.liststore)
|
|
return
|
|
|
|
def get_column_index(self, name):
|
|
"""Get the liststore column indices belonging to this column.
|
|
Will return a list.
|
|
"""
|
|
return self.columns[name].column_indices
|
|
|
|
def get_column_name(self, index):
|
|
"""Get the header name for a liststore column index"""
|
|
for key, value in self.columns.items():
|
|
if index in value.column_indices:
|
|
return key
|
|
|
|
def get_state_field_column(self, field):
|
|
"""Returns the column number for the state field"""
|
|
for column in self.columns:
|
|
if self.columns[column].status_field is None:
|
|
continue
|
|
|
|
for f in self.columns[column].status_field:
|
|
if field == f:
|
|
return self.columns[column].column_indices[
|
|
self.columns[column].status_field.index(f)
|
|
]
|
|
|
|
def on_menuitem_toggled(self, widget):
|
|
"""Callback for the generated column menuitems."""
|
|
# Get the column name from the widget
|
|
name = widget.get_child().get_text()
|
|
|
|
# Set the column's visibility based on the widgets active state
|
|
try:
|
|
self.columns[name].column.set_visible(widget.get_active())
|
|
except KeyError:
|
|
self.columns[decode_bytes(name)].column.set_visible(widget.get_active())
|
|
return
|
|
|
|
def on_treeview_header_right_clicked(self, column, event):
|
|
if event.button == 3:
|
|
self.menu.popup(None, None, None, None, event.button, event.get_time())
|
|
|
|
def register_checklist_menu(self, menu):
|
|
"""Register a checklist menu with the listview. It will automatically
|
|
attach any new checklist menu it makes to this menu.
|
|
"""
|
|
self.checklist_menus.append(menu)
|
|
|
|
def create_checklist_menu(self):
|
|
"""Creates a menu used for toggling the display of columns."""
|
|
menu = self.menu = Gtk.Menu()
|
|
# Iterate through the column_index list to preserve order
|
|
for name in self.column_index:
|
|
column = self.columns[name]
|
|
# If the column is hidden, then we do not want to show it in the
|
|
# menu.
|
|
if column.hidden is True:
|
|
continue
|
|
menuitem = Gtk.CheckMenuItem.new_with_label(column.name)
|
|
# If the column is currently visible, make sure it's set active
|
|
# (or checked) in the menu.
|
|
if column.column.get_visible() is True:
|
|
menuitem.set_active(True)
|
|
# Connect to the 'toggled' event
|
|
menuitem.connect('toggled', self.on_menuitem_toggled)
|
|
# Add the new checkmenuitem to the menu
|
|
menu.append(menuitem)
|
|
|
|
# Attach this new menu to all the checklist_menus
|
|
for _menu in self.checklist_menus:
|
|
_menu.set_submenu(menu)
|
|
_menu.show_all()
|
|
return menu
|
|
|
|
def create_new_liststore(self):
|
|
"""Creates a new GtkListStore based on the liststore_columns list"""
|
|
# Create a new liststore with added column and move the data from the
|
|
# old one to the new one.
|
|
new_list = Gtk.ListStore(*tuple(self.liststore_columns))
|
|
|
|
# This function is used in the liststore.foreach method with user_data
|
|
# being the new liststore and the columns list
|
|
def copy_row(model, path, row, user_data):
|
|
new_list, columns = user_data
|
|
new_row = new_list.append()
|
|
for column in range(len(columns)):
|
|
# Get the current value of the column for this row
|
|
value = model.get_value(row, column)
|
|
# Set the value of this row and column in the new liststore
|
|
new_list.set_value(new_row, column, value)
|
|
|
|
# Do the actual row copy
|
|
if self.liststore is not None:
|
|
self.liststore.foreach(copy_row, (new_list, self.columns))
|
|
|
|
self.liststore = new_list
|
|
|
|
def update_treeview_column(self, header, add=True):
|
|
"""Update TreeViewColumn based on ListView column mappings"""
|
|
column = self.columns[header]
|
|
tree_column = self.columns[header].column
|
|
|
|
if column.column_type == 'text':
|
|
if add:
|
|
tree_column.pack_start(column.renderer, True)
|
|
tree_column.set_col_attributes(
|
|
column.renderer, add=add, text=column.column_indices[column.text_index]
|
|
)
|
|
elif column.column_type == 'bool':
|
|
if add:
|
|
tree_column.pack_start(column.renderer, True)
|
|
tree_column.set_col_attributes(
|
|
column.renderer, active=column.column_indices[0]
|
|
)
|
|
elif column.column_type == 'func':
|
|
if add:
|
|
tree_column.pack_start(column.renderer, True)
|
|
indice_arg = column.column_indices[0]
|
|
if len(column.column_indices) > 1:
|
|
indice_arg = tuple(column.column_indices)
|
|
tree_column.set_cell_data_func(
|
|
column.renderer, column.data_func, indice_arg
|
|
)
|
|
elif column.column_type == 'progress':
|
|
if add:
|
|
tree_column.pack_start(column.renderer, True)
|
|
if column.data_func is None:
|
|
tree_column.set_col_attributes(
|
|
column.renderer,
|
|
add=add,
|
|
text=column.column_indices[column.text_index],
|
|
value=column.column_indices[column.value_index],
|
|
)
|
|
else:
|
|
tree_column.set_cell_data_func(
|
|
column.renderer, column.data_func, tuple(column.column_indices)
|
|
)
|
|
elif column.column_type == 'texticon':
|
|
if add:
|
|
tree_column.pack_start(column.renderer[column.pixbuf_index], False)
|
|
tree_column.pack_start(column.renderer[column.text_index], True)
|
|
tree_column.set_col_attributes(
|
|
column.renderer[column.text_index],
|
|
add=add,
|
|
text=column.column_indices[column.text_index],
|
|
)
|
|
if column.data_func is not None:
|
|
tree_column.set_cell_data_func(
|
|
column.renderer[column.pixbuf_index],
|
|
column.data_func,
|
|
column.column_indices[column.pixbuf_index],
|
|
)
|
|
return True
|
|
|
|
def remove_column(self, header):
|
|
"""Removes the column with the name 'header' from the listview"""
|
|
# Store a copy of this columns state in case it's re-added
|
|
state = self.create_column_state(self.columns[header].column)
|
|
self.removed_columns_state.append(state)
|
|
# Only remove column if column is associated with the treeview. This avoids
|
|
# warning on shutdown when GTKUI is closed before plugins try to remove columns
|
|
if self.columns[header].column.get_tree_view() is not None:
|
|
self.treeview.remove_column(self.columns[header].column)
|
|
# Get the column indices
|
|
column_indices = self.columns[header].column_indices
|
|
# Delete the column
|
|
del self.columns[header]
|
|
self.column_index.remove(header)
|
|
# Shift the column_indices values of those columns affected by the
|
|
# removal. Any column_indices > the one removed.
|
|
for column in self.columns.values():
|
|
if column.column_indices[0] > column_indices[0]:
|
|
# We need to shift this column_indices
|
|
for i, index in enumerate(column.column_indices):
|
|
column.column_indices[i] = index - len(column_indices)
|
|
# Update the associated TreeViewColumn
|
|
self.update_treeview_column(column.name, add=False)
|
|
|
|
# Remove from the liststore columns list
|
|
for index in sorted(column_indices, reverse=True):
|
|
del self.liststore_columns[index]
|
|
|
|
# Create a new liststore
|
|
self.create_new_liststore()
|
|
# Create new model for the treeview
|
|
self.create_model_filter()
|
|
|
|
# Re-create the menu
|
|
self.create_checklist_menu()
|
|
return
|
|
|
|
def add_column(
|
|
self,
|
|
header,
|
|
render,
|
|
col_types,
|
|
hidden,
|
|
position,
|
|
status_field,
|
|
sortid,
|
|
text=0,
|
|
value=0,
|
|
pixbuf=0,
|
|
function=None,
|
|
column_type=None,
|
|
sort_func=None,
|
|
tooltip=None,
|
|
default=True,
|
|
unique=False,
|
|
default_sort=False,
|
|
):
|
|
"""Adds a column to the ListView"""
|
|
# Add the column types to liststore_columns
|
|
column_indices = []
|
|
if isinstance(col_types, list):
|
|
for col_type in col_types:
|
|
self.liststore_columns.append(col_type)
|
|
column_indices.append(len(self.liststore_columns) - 1)
|
|
else:
|
|
self.liststore_columns.append(col_types)
|
|
column_indices.append(len(self.liststore_columns) - 1)
|
|
|
|
# Add to the index list so we know the order of the visible columns.
|
|
if position is not None:
|
|
self.column_index.insert(position, header)
|
|
else:
|
|
self.column_index.append(header)
|
|
|
|
# Create a new column object and add it to the list
|
|
column = self.TreeviewColumn(header)
|
|
self.columns[header] = self.ListViewColumn(header, column_indices)
|
|
self.columns[header].column = column
|
|
self.columns[header].status_field = status_field
|
|
self.columns[header].sort_func = sort_func
|
|
self.columns[header].sort_id = column_indices[sortid]
|
|
# Store creation details
|
|
self.columns[header].column_type = column_type
|
|
self.columns[header].renderer = render
|
|
self.columns[header].text_index = text
|
|
self.columns[header].value_index = value
|
|
self.columns[header].pixbuf_index = pixbuf
|
|
self.columns[header].data_func = function
|
|
|
|
if unique:
|
|
self.unique_column_id = column_indices[sortid]
|
|
if default_sort:
|
|
self.default_sort_column_id = column_indices[sortid]
|
|
|
|
# Create a new list with the added column
|
|
self.create_new_liststore()
|
|
|
|
# Happens only on columns added after the torrent list has been loaded
|
|
if self.model_filter:
|
|
self.create_model_filter()
|
|
|
|
if column_type is None:
|
|
return
|
|
|
|
self.update_treeview_column(header)
|
|
|
|
column.set_sort_column_id(self.columns[header].column_indices[sortid])
|
|
column.set_clickable(True)
|
|
column.set_resizable(True)
|
|
column.set_expand(False)
|
|
column.set_min_width(20)
|
|
column.set_reorderable(True)
|
|
column.set_visible(not hidden)
|
|
column.connect('button-press-event', self.on_treeview_header_right_clicked)
|
|
|
|
if tooltip:
|
|
column.get_widget().set_tooltip_markup(tooltip)
|
|
|
|
# Check for loaded state and apply
|
|
column_in_state = False
|
|
if self.state is not None:
|
|
for column_state in self.state:
|
|
if header == decode_bytes(column_state.name):
|
|
# We found a loaded state
|
|
column_in_state = True
|
|
if column_state.width > 0:
|
|
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
|
column.set_fixed_width(column_state.width)
|
|
|
|
column.set_visible(column_state.visible)
|
|
position = column_state.position
|
|
break
|
|
|
|
# Set this column to not visible if its not in the state and
|
|
# its not supposed to be shown by default
|
|
if not column_in_state and not default and not hidden:
|
|
column.set_visible(False)
|
|
|
|
if position is not None:
|
|
self.treeview.insert_column(column, position)
|
|
else:
|
|
self.treeview.append_column(column)
|
|
|
|
# Set hidden in the column
|
|
self.columns[header].hidden = hidden
|
|
self.columns[header].column = column
|
|
# Re-create the menu item because of the new column
|
|
self.create_checklist_menu()
|
|
|
|
return True
|
|
|
|
def add_text_column(
|
|
self,
|
|
header,
|
|
col_type=str,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
sortid=0,
|
|
column_type='text',
|
|
sort_func=None,
|
|
tooltip=None,
|
|
default=True,
|
|
unique=False,
|
|
default_sort=False,
|
|
):
|
|
"""Add a text column to the listview. Only the header name is required."""
|
|
render = Gtk.CellRendererText()
|
|
self.add_column(
|
|
header,
|
|
render,
|
|
col_type,
|
|
hidden,
|
|
position,
|
|
status_field,
|
|
sortid,
|
|
column_type=column_type,
|
|
sort_func=sort_func,
|
|
tooltip=tooltip,
|
|
default=default,
|
|
unique=unique,
|
|
default_sort=default_sort,
|
|
)
|
|
|
|
return True
|
|
|
|
def add_bool_column(
|
|
self,
|
|
header,
|
|
col_type=bool,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
sortid=0,
|
|
column_type='bool',
|
|
tooltip=None,
|
|
default=True,
|
|
):
|
|
"""Add a bool column to the listview"""
|
|
render = Gtk.CellRendererToggle()
|
|
self.add_column(
|
|
header,
|
|
render,
|
|
col_type,
|
|
hidden,
|
|
position,
|
|
status_field,
|
|
sortid,
|
|
column_type=column_type,
|
|
tooltip=tooltip,
|
|
default=default,
|
|
)
|
|
|
|
def add_func_column(
|
|
self,
|
|
header,
|
|
function,
|
|
col_types,
|
|
sortid=0,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
column_type='func',
|
|
sort_func=None,
|
|
tooltip=None,
|
|
default=True,
|
|
):
|
|
"""Add a function column to the listview. Need a header name, the
|
|
function and the column types."""
|
|
|
|
render = Gtk.CellRendererText()
|
|
self.add_column(
|
|
header,
|
|
render,
|
|
col_types,
|
|
hidden,
|
|
position,
|
|
status_field,
|
|
sortid,
|
|
column_type=column_type,
|
|
function=function,
|
|
sort_func=sort_func,
|
|
tooltip=tooltip,
|
|
default=default,
|
|
)
|
|
|
|
return True
|
|
|
|
def add_progress_column(
|
|
self,
|
|
header,
|
|
col_types=None,
|
|
sortid=0,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
function=None,
|
|
column_type='progress',
|
|
tooltip=None,
|
|
sort_func=None,
|
|
default=True,
|
|
):
|
|
"""Add a progress column to the listview."""
|
|
|
|
if col_types is None:
|
|
col_types = [float, str]
|
|
render = Gtk.CellRendererProgress()
|
|
self.add_column(
|
|
header,
|
|
render,
|
|
col_types,
|
|
hidden,
|
|
position,
|
|
status_field,
|
|
sortid,
|
|
function=function,
|
|
column_type=column_type,
|
|
value=0,
|
|
text=1,
|
|
tooltip=tooltip,
|
|
sort_func=sort_func,
|
|
default=default,
|
|
)
|
|
|
|
return True
|
|
|
|
def add_texticon_column(
|
|
self,
|
|
header,
|
|
col_types=None,
|
|
sortid=1,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
column_type='texticon',
|
|
function=None,
|
|
sort_func=None,
|
|
tooltip=None,
|
|
default=True,
|
|
default_sort=False,
|
|
):
|
|
"""Adds a texticon column to the listview."""
|
|
if col_types is None:
|
|
col_types = [str, str]
|
|
render1 = Gtk.CellRendererPixbuf()
|
|
render2 = Gtk.CellRendererText()
|
|
|
|
self.add_column(
|
|
header,
|
|
(render1, render2),
|
|
col_types,
|
|
hidden,
|
|
position,
|
|
status_field,
|
|
sortid,
|
|
column_type=column_type,
|
|
function=function,
|
|
pixbuf=0,
|
|
text=1,
|
|
tooltip=tooltip,
|
|
sort_func=sort_func,
|
|
default=default,
|
|
default_sort=default_sort,
|
|
)
|
|
|
|
return True
|
|
|
|
def on_keypress_search_by_name(self, model, column, key, _iter, *search_data):
|
|
torrent_name_col = self.columns[_('Name')].column_indices[1]
|
|
return not model[_iter][torrent_name_col].lower().startswith(key.lower())
|
|
|
|
def restore_columns_order_from_state(self):
|
|
if self.state is None:
|
|
# No state file exists, so, no reordering can be done
|
|
return
|
|
columns = self.treeview.get_columns()
|
|
|
|
def find_column(header):
|
|
for column in columns:
|
|
if column.get_title() == header:
|
|
return column
|
|
|
|
restored_columns = []
|
|
for col_state in self.state:
|
|
if col_state.name in restored_columns:
|
|
# Duplicate column in state!?!?!?
|
|
continue
|
|
elif not col_state.visible:
|
|
# Column is not visible, no need to reposition
|
|
continue
|
|
|
|
try:
|
|
column_at_position = columns[col_state.position]
|
|
except IndexError:
|
|
# Ignore extra columns from Plugins in col_state
|
|
continue
|
|
if col_state.name == column_at_position.get_title():
|
|
# It's in the right position
|
|
continue
|
|
column = find_column(col_state.name)
|
|
if not column:
|
|
log.debug(
|
|
'Could not find column matching "%s" on state.', col_state.name
|
|
)
|
|
# The cases where I've found that the column could not be found
|
|
# is when not using the english locale, ie, the default one, or
|
|
# when changing locales between runs.
|
|
# On the next load, all should be fine
|
|
continue
|
|
self.treeview.move_column_after(column, column_at_position)
|
|
# Get columns again to keep reordering since positions have changed
|
|
columns = self.treeview.get_columns()
|
|
restored_columns.append(col_state.name)
|
|
self.create_new_liststore()
|