summaryrefslogtreecommitdiffstats
path: root/deluge/ui/gtk3/listview.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui/gtk3/listview.py')
-rw-r--r--deluge/ui/gtk3/listview.py831
1 files changed, 831 insertions, 0 deletions
diff --git a/deluge/ui/gtk3/listview.py b/deluge/ui/gtk3/listview.py
new file mode 100644
index 0000000..a80d795
--- /dev/null
+++ b/deluge/ui/gtk3/listview.py
@@ -0,0 +1,831 @@
+#
+# 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):
+ 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()