389 lines
16 KiB
Python
389 lines
16 KiB
Python
#
|
|
# listview.py
|
|
#
|
|
# Copyright (C) 2007 Andrew Resch ('andar') <andrewresch@gmail.com>
|
|
#
|
|
# Deluge is free software.
|
|
#
|
|
# You may redistribute it and/or modify it under the terms of the
|
|
# GNU General Public License, as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option)
|
|
# any later version.
|
|
#
|
|
# deluge is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with deluge. If not, write to:
|
|
# The Free Software Foundation, Inc.,
|
|
# 51 Franklin Street, Fifth Floor
|
|
# Boston, MA 02110-1301, USA.
|
|
#
|
|
# In addition, as a special exception, the copyright holders give
|
|
# permission to link the code of portions of this program with the OpenSSL
|
|
# library.
|
|
# You must obey the GNU General Public License in all respects for all of
|
|
# the code used other than OpenSSL. If you modify file(s) with this
|
|
# exception, you may extend this exception to your version of the file(s),
|
|
# but you are not obligated to do so. If you do not wish to do so, delete
|
|
# this exception statement from your version. If you delete this exception
|
|
# statement from all source files in the program, then also delete it here.
|
|
|
|
import pygtk
|
|
pygtk.require('2.0')
|
|
import gtk
|
|
import gettext
|
|
|
|
import deluge.common
|
|
from deluge.log import LOG as log
|
|
|
|
# Cell data functions to pass to add_func_column()
|
|
def cell_data_speed(column, cell, model, row, data):
|
|
"""Display value as a speed, eg. 2 KiB/s"""
|
|
speed = int(model.get_value(row, data))
|
|
speed_str = deluge.common.fspeed(speed)
|
|
cell.set_property('text', speed_str)
|
|
|
|
def cell_data_size(column, cell, model, row, data):
|
|
"""Display value in terms of size, eg. 2 MB"""
|
|
size = long(model.get_value(row, data))
|
|
size_str = deluge.common.fsize(size)
|
|
cell.set_property('text', size_str)
|
|
|
|
def cell_data_peer(column, cell, model, row, data):
|
|
"""Display values as 'value1 (value2)'"""
|
|
column1, column2 = data
|
|
first = int(model.get_value(row, column1))
|
|
second = int(model.get_value(row, column2))
|
|
cell.set_property('text', '%d (%d)' % (first, second))
|
|
|
|
def cell_data_time(column, cell, model, row, data):
|
|
"""Display value as time, eg 1m10s"""
|
|
time = int(model.get_value(row, data))
|
|
if time < 0 or time == 0:
|
|
time_str = _("Infinity")
|
|
else:
|
|
time_str = deluge.common.ftime(time)
|
|
cell.set_property('text', time_str)
|
|
|
|
def cell_data_ratio(column, cell, model, row, data):
|
|
"""Display value as a ratio with a precision of 3."""
|
|
ratio = float(model.get_value(row, data))
|
|
if ratio == -1:
|
|
ratio_str = _("Unknown")
|
|
else:
|
|
ratio_str = "%.3f" % ratio
|
|
cell.set_property('text', ratio_str)
|
|
|
|
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
|
|
|
|
|
|
def __init__(self, treeview_widget=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.liststore = None
|
|
|
|
self.treeview.set_model(self.liststore)
|
|
self.treeview.set_rules_hint(True)
|
|
self.treeview.set_reorderable(True)
|
|
self.treeview.get_selection().set_mode(gtk.SELECTION_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 = []
|
|
|
|
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 if greater than 1 column.
|
|
"""
|
|
# Only return as list if needed
|
|
if len(self.columns[name].column_indices) > 1:
|
|
return self.columns[name].column_indices
|
|
else:
|
|
return self.columns[name].column_indices[0]
|
|
|
|
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
|
|
self.columns[name].column.set_visible(widget.get_active())
|
|
return
|
|
|
|
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 = 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(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
|
|
self.treeview.set_model(self.liststore)
|
|
|
|
return
|
|
|
|
def remove_column(self, header):
|
|
"""Removes the column with the name 'header' from the listview"""
|
|
# Start by removing this column from the treeview
|
|
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 effected 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 index in column.column_indices:
|
|
index = index - len(column_indices)
|
|
|
|
# Remove from the liststore columns list
|
|
for index in column_indices:
|
|
del self.liststore_columns[index]
|
|
|
|
# Create a new liststore
|
|
self.create_new_liststore()
|
|
|
|
# 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):
|
|
"""Adds a column to the ListView"""
|
|
# Add the column types to liststore_columns
|
|
column_indices = []
|
|
if type(col_types) is 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
|
|
self.columns[header] = self.ListViewColumn(header, column_indices)
|
|
|
|
|
|
self.columns[header].status_field = status_field
|
|
|
|
# Create a new list with the added column
|
|
self.create_new_liststore()
|
|
|
|
column = gtk.TreeViewColumn(header)
|
|
if column_type == "text":
|
|
column.pack_start(render)
|
|
column.add_attribute(render, "text",
|
|
self.columns[header].column_indices[text])
|
|
elif column_type == "func":
|
|
column.pack_start(render, True)
|
|
if len(self.columns[header].column_indices) > 1:
|
|
column.set_cell_data_func(render, function,
|
|
tuple(self.columns[header].column_indices))
|
|
else:
|
|
column.set_cell_data_func(render, function,
|
|
self.columns[header].column_indices[0])
|
|
elif column_type == "progress":
|
|
column.pack_start(render)
|
|
if function is None:
|
|
column.add_attribute(render, "text",
|
|
self.columns[header].column_indices[text])
|
|
column.add_attribute(render, "value",
|
|
self.columns[header].column_indices[value])
|
|
else:
|
|
column.set_cell_data_func(render, function,
|
|
tuple(self.columns[header].column_indices))
|
|
elif column_type == "texticon":
|
|
column.pack_start(render[pixbuf], False)
|
|
if function is not None:
|
|
column.set_cell_data_func(render[pixbuf], function,
|
|
self.columns[header].column_indices[pixbuf])
|
|
column.pack_start(render[text], True)
|
|
column.add_attribute(render[text], "text",
|
|
self.columns[header].column_indices[text])
|
|
elif column_type == None:
|
|
return
|
|
|
|
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(10)
|
|
column.set_reorderable(True)
|
|
column.set_visible(not hidden)
|
|
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"):
|
|
"""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)
|
|
|
|
return True
|
|
|
|
def add_func_column(self, header, function, col_types, sortid=0,
|
|
hidden=False, position=None, status_field=None,
|
|
column_type="func"):
|
|
"""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)
|
|
|
|
return True
|
|
|
|
def add_progress_column(self, header, col_types=[float, str],
|
|
sortid=0,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
function=None,
|
|
column_type="progress"):
|
|
"""Add a progress column to the listview."""
|
|
|
|
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)
|
|
|
|
return True
|
|
|
|
def add_texticon_column(self, header, col_types=[int, str],
|
|
sortid=0,
|
|
hidden=False,
|
|
position=None,
|
|
status_field=None,
|
|
column_type="texticon",
|
|
function=None):
|
|
"""Adds a texticon column to the listview."""
|
|
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)
|
|
|
|
return True
|