Skip to content

Commit 9ba9e4d

Browse files
committed
Allow multiple selection in the treeview.
- Clicking the checkbox amongst a selection will toggle all items. - Right-clicking within the selection will offer to ignore all selected updates. - The Packages notebook tab will show all packages included in the selection and their total size.
1 parent 0d031bf commit 9ba9e4d

1 file changed

Lines changed: 147 additions & 27 deletions

File tree

‎usr/lib/linuxmint/mintUpdate/mintUpdate.py‎

Lines changed: 147 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,8 @@ def __init__(self):
193193
self.is_lmde = False
194194
self.app_restart_required = False
195195
self.show_cinnamon_enabled = False
196+
self.column_upgrade = None
197+
self._bulk_toggle_paths = None
196198
self.settings.connect("changed", self._on_settings_changed)
197199
self._on_settings_changed(self.settings, None)
198200

@@ -237,10 +239,10 @@ def __init__(self):
237239
cr.connect("toggled", self.toggled)
238240
cr.set_property("activatable", True)
239241

240-
column_upgrade = Gtk.TreeViewColumn(_("Upgrade"), cr)
241-
column_upgrade.add_attribute(cr, "active", UPDATE_CHECKED)
242-
column_upgrade.set_sort_column_id(UPDATE_CHECKED)
243-
column_upgrade.set_resizable(True)
242+
self.column_upgrade = Gtk.TreeViewColumn(_("Upgrade"), cr)
243+
self.column_upgrade.add_attribute(cr, "active", UPDATE_CHECKED)
244+
self.column_upgrade.set_sort_column_id(UPDATE_CHECKED)
245+
self.column_upgrade.set_resizable(True)
244246

245247
column_name = Gtk.TreeViewColumn(_("Name"), Gtk.CellRendererText(), markup=UPDATE_DISPLAY_NAME)
246248
column_name.set_sort_column_id(UPDATE_DISPLAY_NAME)
@@ -270,7 +272,7 @@ def __init__(self):
270272

271273
self.treeview.set_search_equal_func(name_search_func)
272274
self.treeview.append_column(column_type)
273-
self.treeview.append_column(column_upgrade)
275+
self.treeview.append_column(self.column_upgrade)
274276
self.treeview.append_column(column_name)
275277
self.treeview.append_column(column_old_version)
276278
self.treeview.append_column(column_new_version)
@@ -281,10 +283,12 @@ def __init__(self):
281283
self.treeview.set_reorderable(False)
282284
self.treeview.show()
283285

286+
self.treeview.connect("button-press-event", self.treeview_button_pressed)
284287
self.treeview.connect("button-release-event", self.treeview_right_clicked)
285288
self.treeview.connect("row-activated", self.treeview_row_activated)
286289

287290
selection = self.treeview.get_selection()
291+
selection.set_mode(Gtk.SelectionMode.MULTIPLE)
288292
selection.connect("changed", self.display_selected_update)
289293
self.ui_notebook_details.connect("switch-page", self.switch_page)
290294
self.ui_window.connect("delete_event", self.close_window)
@@ -872,14 +876,69 @@ def show_welcome_page(self, widget=None):
872876
def treeview_row_activated(self, treeview, path, view_column):
873877
self.toggled(None, path)
874878

879+
def treeview_button_pressed(self, widget, event):
880+
if event.type != Gdk.EventType.BUTTON_PRESS:
881+
return False
882+
883+
if event.button == Gdk.BUTTON_SECONDARY:
884+
path_info = widget.get_path_at_pos(int(event.x), int(event.y))
885+
if path_info is not None:
886+
sel_paths = widget.get_selection().get_selected_rows()[1]
887+
if any(p.compare(path_info[0]) == 0 for p in sel_paths):
888+
return True
889+
return False
890+
891+
if event.button != Gdk.BUTTON_PRIMARY:
892+
return False
893+
if event.state & (Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK):
894+
return False
895+
path_info = widget.get_path_at_pos(int(event.x), int(event.y))
896+
if path_info is None:
897+
return False
898+
path, column, _x, _y = path_info
899+
if column is not self.column_upgrade:
900+
return False
901+
sel_paths = widget.get_selection().get_selected_rows()[1]
902+
if len(sel_paths) > 1 and any(p.compare(path) == 0 for p in sel_paths):
903+
self._bulk_toggle_paths = sel_paths
904+
return False
905+
875906
def toggled(self, renderer, path):
876907
model = self.treeview.get_model()
908+
if isinstance(path, str):
909+
path = Gtk.TreePath.new_from_string(path)
877910
iter = model.get_iter(path)
878-
if iter is not None:
879-
model.set_value(iter, UPDATE_CHECKED, (not model.get_value(iter, UPDATE_CHECKED)))
911+
if iter is None:
912+
return
913+
914+
new_value = not model.get_value(iter, UPDATE_CHECKED)
915+
916+
bulk_paths = self._bulk_toggle_paths
917+
self._bulk_toggle_paths = None
918+
919+
if bulk_paths is None:
920+
sel_paths = self.treeview.get_selection().get_selected_rows()[1]
921+
if len(sel_paths) > 1 and any(p.compare(path) == 0 for p in sel_paths):
922+
bulk_paths = sel_paths
923+
924+
if bulk_paths is None:
925+
model.set_value(iter, UPDATE_CHECKED, new_value)
926+
else:
927+
for p in bulk_paths:
928+
it = model.get_iter(p)
929+
if it is not None:
930+
model.set_value(it, UPDATE_CHECKED, new_value)
931+
GLib.idle_add(self._restore_selection, bulk_paths)
880932

881933
self.update_installable_state()
882934

935+
def _restore_selection(self, paths):
936+
sel = self.treeview.get_selection()
937+
sel.unselect_all()
938+
for p in paths:
939+
sel.select_path(p)
940+
return GLib.SOURCE_REMOVE
941+
883942
@_idle
884943
def set_textview_changes_text(self, text):
885944
self.textview_changes.set_text(text)
@@ -889,8 +948,12 @@ def display_selected_update(self, selection):
889948
self.textview_packages.set_text("")
890949
self.textview_description.set_text("")
891950
self.textview_changes.set_text("")
892-
(model, iter) = selection.get_selected()
893-
if iter is not None:
951+
model, paths = selection.get_selected_rows()
952+
if len(paths) > 1:
953+
self.display_multi_selection(model, paths)
954+
return
955+
if len(paths) == 1:
956+
iter = model.get_iter(paths[0])
894957
update = model.get_value(iter, UPDATE_OBJ)
895958
description = update.description.replace("\\n", "\n")
896959
desc_tab = self.ui_notebook_details.get_nth_page(TAB_DESC)
@@ -1170,11 +1233,10 @@ def _compute_refresh_interval(self):
11701233
+ self.settings.get_int(f"{prefix}refresh-days") * DAY)
11711234

11721235
def switch_page(self, notebook, page, page_num):
1173-
selection = self.treeview.get_selection()
1174-
(model, iter) = selection.get_selected()
1175-
if iter and page_num == 2 and not self.changelog_retriever_started:
1236+
model, paths = self.treeview.get_selection().get_selected_rows()
1237+
if len(paths) == 1 and page_num == 2 and not self.changelog_retriever_started:
11761238
# Changelog tab
1177-
update = model.get_value(iter, UPDATE_OBJ)
1239+
update = model.get_value(model.get_iter(paths[0]), UPDATE_OBJ)
11781240
self.retrieve_changelog(update)
11791241
self.changelog_retriever_started = True
11801242

@@ -1198,21 +1260,79 @@ def display_package_list(self, update, is_flatpak=False):
11981260
size_label, size_to_string(update.size))
11991261
self.textview_packages.set_text(packages)
12001262

1263+
def display_multi_selection(self, model, paths):
1264+
self.textview_description.set_text(
1265+
_("Select a single update to view its description and changelog."))
1266+
1267+
package_names = set()
1268+
total_size = 0
1269+
for p in paths:
1270+
update = model.get_value(model.get_iter(p), UPDATE_OBJ)
1271+
package_names.update(update.package_names)
1272+
total_size += update.size
1273+
1274+
prefix = "\n • "
1275+
count = len(package_names)
1276+
packages = "%s%s%s\n%s %s\n\n" % \
1277+
(gettext.ngettext("The selected updates affect the following installed package:",
1278+
"The selected updates affect the following installed packages:",
1279+
count),
1280+
prefix,
1281+
prefix.join(sorted(package_names)),
1282+
_("Total size:"), size_to_string(total_size))
1283+
self.textview_packages.set_text(packages)
1284+
1285+
self.ui_notebook_details.get_nth_page(TAB_PACKAGES).show()
1286+
self.ui_notebook_details.get_nth_page(TAB_CHANGELOG).hide()
1287+
self.changelog_retriever_started = False
1288+
12011289
def treeview_right_clicked(self, widget, event):
1202-
if event.button == 3:
1203-
(model, iter) = widget.get_selection().get_selected()
1204-
if iter is not None:
1205-
update = model.get_value(iter, UPDATE_OBJ)
1206-
menu = Gtk.Menu()
1207-
menuItem = Gtk.MenuItem.new_with_mnemonic(_("Ignore the current update for this package"))
1208-
menuItem.connect("activate", self.add_to_ignore_list, update.source_packages, True)
1209-
menu.append(menuItem)
1210-
menuItem = Gtk.MenuItem.new_with_mnemonic(_("Ignore all future updates for this package"))
1211-
menuItem.connect("activate", self.add_to_ignore_list, update.source_packages, False)
1212-
menu.append(menuItem)
1213-
menu.attach_to_widget (widget, None)
1214-
menu.show_all()
1215-
menu.popup(None, None, None, None, event.button, event.time)
1290+
if event.button != Gdk.BUTTON_SECONDARY:
1291+
return
1292+
path_info = widget.get_path_at_pos(int(event.x), int(event.y))
1293+
if path_info is None:
1294+
return
1295+
clicked_path = path_info[0]
1296+
model = widget.get_model()
1297+
selection = widget.get_selection()
1298+
sel_paths = selection.get_selected_rows()[1]
1299+
1300+
if any(p.compare(clicked_path) == 0 for p in sel_paths):
1301+
target_paths = sel_paths
1302+
else:
1303+
target_paths = [clicked_path]
1304+
selection.unselect_all()
1305+
selection.select_path(clicked_path)
1306+
1307+
source_packages = []
1308+
seen = set()
1309+
for p in target_paths:
1310+
update = model.get_value(model.get_iter(p), UPDATE_OBJ)
1311+
for pkg in update.source_packages:
1312+
if pkg not in seen:
1313+
seen.add(pkg)
1314+
source_packages.append(pkg)
1315+
1316+
count = len(target_paths)
1317+
ignore_current = gettext.ngettext(
1318+
"Ignore the current update for this package",
1319+
"Ignore the current updates for these packages",
1320+
count)
1321+
ignore_future = gettext.ngettext(
1322+
"Ignore all future updates for this package",
1323+
"Ignore all future updates for these packages",
1324+
count)
1325+
1326+
menu = Gtk.Menu()
1327+
menuItem = Gtk.MenuItem.new_with_mnemonic(ignore_current)
1328+
menuItem.connect("activate", self.add_to_ignore_list, source_packages, True)
1329+
menu.append(menuItem)
1330+
menuItem = Gtk.MenuItem.new_with_mnemonic(ignore_future)
1331+
menuItem.connect("activate", self.add_to_ignore_list, source_packages, False)
1332+
menu.append(menuItem)
1333+
menu.attach_to_widget(widget, None)
1334+
menu.show_all()
1335+
menu.popup(None, None, None, None, event.button, event.time)
12161336

12171337
def add_to_ignore_list(self, widget, source_packages, versioned):
12181338
blacklist = self.settings.get_strv("blacklisted-packages")

0 commit comments

Comments
 (0)