From f70dcb5478f8ac88d1b6cc2119fce138ed858a7b Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 09:06:09 +0100 Subject: [PATCH 01/15] Switch to using AdwApplicationWindow As it automatically loads stylesheet and would allow nicer things later on --- data/css/oops.css | 6 - data/org.freedesktop.GnomeAbrt.gresource.xml | 2 +- data/ui/oops-window.ui | 563 ++++++++++--------- src/gnome-abrt | 9 +- src/gnome_abrt/views.py | 9 +- 5 files changed, 294 insertions(+), 295 deletions(-) diff --git a/data/css/oops.css b/data/css/oops.css index 73efbba3..62f58f0d 100644 --- a/data/css/oops.css +++ b/data/css/oops.css @@ -1,9 +1,3 @@ -GtkListBoxRow, row { - padding: 8px; - margin: 5px; - border-radius: 12px; -} - GtkListBoxRow:selected, row:selected { background-color: #007bff; /* Custom blue color */ color: #ffffff; /* White text color for better contrast */ diff --git a/data/org.freedesktop.GnomeAbrt.gresource.xml b/data/org.freedesktop.GnomeAbrt.gresource.xml index 8e95a7a4..87f6b97b 100644 --- a/data/org.freedesktop.GnomeAbrt.gresource.xml +++ b/data/org.freedesktop.GnomeAbrt.gresource.xml @@ -1,7 +1,7 @@ - css/oops.css + css/oops.css ui/oops-menus.ui ui/oops-window.ui diff --git a/data/ui/oops-window.ui b/data/ui/oops-window.ui index 6f60dc85..976a49c6 100644 --- a/data/ui/oops-window.ui +++ b/data/ui/oops-window.ui @@ -1,121 +1,124 @@ - menu_problem_item_model diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index 97e1d937..392c9eda 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -219,7 +219,7 @@ def get_selected_rows(self): return [lbr.get_problem() for lbr in self._lb.get_selected_rows()] -class ProblemRow(Gtk.ListBoxRow): +class ProblemRow(Adw.PreferencesRow): def __init__(self, problem_values): super().__init__() @@ -228,13 +228,6 @@ def __init__(self, problem_values): # Store the problem type as a property (this is what we will filter by) self.problem_type = problem_values[2].lower() # Store as lowercase for easier comparison - - #applying margins directly on the ListBoxRow - self.set_margin_top(5) - self.set_margin_bottom(5) - self.set_margin_start(5) - self.set_margin_end(5) - grid = Gtk.Grid.new() grid.set_column_spacing(12) @@ -291,12 +284,9 @@ class OopsWindow(Adw.ApplicationWindow): __gtype_name__ = 'OopsWindow' - header_bar = Gtk.Template.Child() - box_header_left = Gtk.Template.Child() - box_panel_left = Gtk.Template.Child() - detected_crashes_label = Gtk.Template.Child() crash_box = Gtk.Template.Child() search_entry = Gtk.Template.Child() + search_bar = Gtk.Template.Child() btn_search_icon = Gtk.Template.Child() lbl_reason = Gtk.Template.Child() lbl_summary = Gtk.Template.Child() @@ -315,9 +305,6 @@ class OopsWindow(Adw.ApplicationWindow): menu_multiple_problems = Gtk.Template.Child() vbx_links = Gtk.Template.Child() vbx_problem_messages = Gtk.Template.Child() - gd_problem_info = Gtk.Template.Child() - vbx_empty_page = Gtk.Template.Child() - gr_main_layout = Gtk.Template.Child() class SourceObserver: def __init__(self, wnd): @@ -413,48 +400,19 @@ def __init__(self, application, sources, controller): key_controller.connect("key-pressed", self._on_key_press_event) self.add_controller(key_controller) - self.search_entry.set_visible(False) # Ensure the search entry is hidden on load + self.search_bar.set_search_mode(False) # Ensure the search entry is hidden on load #function to set up the auto-completion for the search entry self.setup_search_completion() - # Ensure buttons are packed only once - if self.btn_delete.get_parent() is None: - self.header_bar.pack_end(self.btn_delete) - if self.btn_report.get_parent() is None: - self.header_bar.pack_end(self.btn_report) - self.box_header_left.set_hexpand(True) - self.header_bar.add_css_class('header-bar') self.btn_report.add_css_class('btn-report') - self.box_header_left.connect("notify::allocation", self.on_box_header_left_size_allocate) - self.gr_main_layout.connect("notify::position", self.on_paned_position_changed) - self.gr_main_layout.connect("notify::allocation", self.on_paned_size_allocate) - self.btn_search_icon.connect('clicked', self.on_search_icon_clicked) self.search_entry.connect('notify::text', self.on_search_entry_text_changed) - self.search_entry.connect('search-changed', self.on_se_problems_search_changed) gesture = Gtk.GestureClick.new() gesture.connect("pressed", self.problems_button_press_event) self.lb_problems.add_controller(gesture) self.lbl_reason.add_css_class('oops-reason') - self.detected_crashes_label.add_css_class('app-name-label') self.crash_box.add_css_class('crash-info-box') - #"map" event is emitted when the window is initialized - self.connect("map", self.on_window_map) - - #box_header_left and box_panel_left were not properly aligned or sized the same way on window initialization. - #This was likely because the GTK layout system sometimes doesn't properly propagate the size allocation across all widgets immediately on startup - #Even though both panels are inside a GtkPaned, the initial size calculation didn't seem to synchronize their widths correctly - #as a result, the box_header_left width remained smaller than box_panel_left. - #did this to solve the issue: Force Layout Recalculation by Adjusting the Pane and slight Separator Shift - def on_window_map(self, widget): - """This function triggers when the window is first shown""" - #after the window is initialized, adjust the paned position slightly to the right - current_position = self.gr_main_layout.get_position() - #slightly move the separator of the paned (move it 10 pixels to the right) - self.gr_main_layout.set_position(current_position + 10) - #move it back to the original position after a slight delay - GLib.idle_add(self.restore_paned_position, current_position) def setup_search_completion(self): """Manually set up a popover to show suggestions for the search entry""" @@ -498,11 +456,6 @@ def on_suggestion_selected(self, listbox, row): #apply the filter based on the selected suggestion self._filter.set_pattern(suggestion) - def restore_paned_position(self, original_position): - """Optional: restoring the original paned position after the adjustment - might delete later""" - self.gr_main_layout.set_position(original_position) - return False #returning False to remove the idle callback after execution - def _add_actions(self, application): action_entries = [ ('delete', self.on_gac_delete_activate,), @@ -830,12 +783,11 @@ def destroy_links(widget): child.unparent() child = child.get_next_sibling() - if not problem: - self.nb_problem_layout.set_visible_child(self.vbx_empty_page if self._source else self.vbx_no_source_page) + self.nb_problem_layout.set_visible_child_name("empty") return - self.nb_problem_layout.set_visible_child(self.gd_problem_info) + self.nb_problem_layout.set_visible_child_name("problem") app = problem['application'] @@ -948,20 +900,21 @@ def on_gac_report_activate(self, action, parameter, user_data): def on_search_entry_text_changed(self, search_entry, gparam): """Hides the search entry when it is cleared (cross button clicked).""" if not search_entry.get_text(): - search_entry.set_visible(False) # Hide the search entry if the text is empty + self.search_bar.set_search_mode(False) # Hide the search entry if the text is empty def on_search_icon_clicked(self, button): logging.debug("search icon clicked"); - if self.search_entry.get_visible(): + if self.search_bar.get_search_mode(): logging.debug("hiding search entry") - self.search_entry.set_visible(False) + self.search_bar.set_search_mode(False) else: logging.debug("showing search entry") - self.search_entry.set_visible(True) + self.search_bar.set_search_mode(True) self.search_entry.grab_focus() @handle_problem_and_source_errors def on_se_problems_search_changed(self, entry): + print(type(entry)) self._filter.set_pattern(entry.get_text()) @@ -1023,102 +976,4 @@ def problems_button_press_event(self, gesture, n_press, x, y): self.menu_problem_item.set_parent(self) self.menu_problem_item.popup_at_pointer(None) return None - - def get_box_header_left_offset(self): - box_header_left = self.box_header_left - box_panel_left = self.box_panel_left - paned = box_panel_left.get_parent() - if paned is None: - return None - - offset = box_header_left.translate_coordinates(paned, 0, 0)[0] - parent = box_header_left.get_parent() - if parent is not None: - if parent.get_direction() == Gtk.TextDirection.RTL: - offset = paned.get_allocation().width - offset - \ - box_header_left.get_allocation().width - - return offset - - def do_box_header_left_size_allocate(self, sender): - spacing = sender.get_spacing() - sum_width = -spacing - for child in sender.get_children(): - width = child.get_preferred_width()[0] - sum_width += width - sum_width += spacing - - - offset = self.get_box_header_left_offset() - if offset is None: - return GLib.SOURCE_REMOVE - - context = self.box_header_left.get_style_context() - state = context.get_state() - padding = context.get_padding(state) - minimum_width = sum_width + offset + \ - padding.right + padding.left - - self.box_panel_left.set_size_request(minimum_width, -1) - - return GLib.SOURCE_REMOVE - - - - def on_box_header_left_size_allocate(self, sender, allocation): - other = self.box_panel_left - if not sender.get_realized() or not other.get_realized(): - return - GLib.idle_add(self.do_box_header_left_size_allocate, sender) - - def update_box_header_left_size_from_paned(self, sender): - other = self.box_header_left - - if not sender.get_realized() or not other.get_realized(): - return GLib.SOURCE_REMOVE - - offset = self.get_box_header_left_offset() - if offset is None: - return GLib.SOURCE_REMOVE - - width = max(sender.get_position() - offset, 0) - self.box_header_left.set_size_request(width, -1) - - - self.box_header_left.queue_resize() - return GLib.SOURCE_REMOVE - - - def on_paned_position_changed(self, sender, data): - #temporarily disable resizing of the main window during pane adjustment - self.set_resizable(False) - - #a minimum width for the left pane (box_panel_left) - min_left_width = 280 - - #maximum width of left pane - max_left_width = 600 - - #current position of the pane - current_position = sender.get_position() - - #box_panel_left)is not resized smaller than the minimum width - if current_position < min_left_width: - sender.set_position(min_left_width) - elif current_position > max_left_width: - sender.set_position(max_left_width) - - self.update_box_header_left_size_from_paned(sender) - - #enable window resizing after the adjustment is complete - self.set_resizable(True) - - - - def on_paned_size_allocate(self, sender, allocation): - GLib.idle_add(self.update_box_header_left_size_from_paned, sender) - - - - def on_paned_map(self, sender): - self.on_paned_position_changed(sender, None) + \ No newline at end of file From 461754f7e3831f0e8dfa8b03dafe861daacc060f Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 12:00:57 +0100 Subject: [PATCH 09/15] Use AdwActionRow instead of hacked AdwPreferencesRow --- data/ui/oops-window.ui | 143 +++++++--------------------------------- src/gnome_abrt/views.py | 52 +++++---------- 2 files changed, 38 insertions(+), 157 deletions(-) diff --git a/data/ui/oops-window.ui b/data/ui/oops-window.ui index 30ed70aa..3c88313d 100644 --- a/data/ui/oops-window.ui +++ b/data/ui/oops-window.ui @@ -204,89 +204,24 @@ - - - - horizontal - 12 - - - start - Affected Component - False - True - - - - - kernel-core - True - end - True - end - - - - + + + Affected Component - - - - horizontal - 12 - - - Component Version - start - False - - - - - 2.0.13-2 - True - end - True - end - - - - + + + Component Version - - - - horizontal - 12 - - - Reported - start - False - True - - - - - vertical - 6 - end - True - - - cannot be reported - True - end - - - - - - + + Reported @@ -294,53 +229,19 @@ - - - - horizontal - 12 - - - First Detected - start - False - - - - - a month ago - True - end - True - - - - + + First Detected + - - - - horizontal - 12 - - - Times Detected - start - False - - - - - 0 - True - end - True - - - - + + Times Detected + diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index 392c9eda..ed126c35 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -291,19 +291,17 @@ class OopsWindow(Adw.ApplicationWindow): lbl_reason = Gtk.Template.Child() lbl_summary = Gtk.Template.Child() lbl_type_crash = Gtk.Template.Child() - lbl_app_name_value = Gtk.Template.Child() - lbl_app_version_value = Gtk.Template.Child() - lbl_detected_value = Gtk.Template.Child() + lbl_app_name = Gtk.Template.Child() + lbl_app_version = Gtk.Template.Child() + lbl_detected = Gtk.Template.Child() lbl_reported = Gtk.Template.Child() - lbl_reported_value = Gtk.Template.Child() - lbl_times_detected_value = Gtk.Template.Child() + lbl_times_detected = Gtk.Template.Child() lb_problems = Gtk.Template.Child() nb_problem_layout = Gtk.Template.Child() btn_delete = Gtk.Template.Child() btn_report = Gtk.Template.Child() menu_problem_item = Gtk.Template.Child() menu_multiple_problems = Gtk.Template.Child() - vbx_links = Gtk.Template.Child() vbx_problem_messages = Gtk.Template.Child() class SourceObserver: @@ -705,17 +703,13 @@ def _show_problem_links(self, submissions): return False link_added = False + links = "" for sbm in submissions: if problems.Problem.Submission.URL == sbm.rtype: title_escaped = GLib.markup_escape_text(sbm.title) - lnk = Gtk.Label.new(sbm.title) - lnk.set_use_markup(True) - lnk.set_markup(f"{title_escaped}") - lnk.set_halign(Gtk.Align.START) - lnk.set_wrap(True) - lnk.set_visible(True) - self.vbx_links.append(lnk) #jft + links += f"{title_escaped}" link_added = True + self.lbl_reported.set_subtitle(links) return link_added @@ -761,10 +755,6 @@ def _get_summary_for_problem_type(self, problem_type): @handle_problem_and_source_errors def _set_problem(self, problem): - def destroy_links(widget): - if widget != self.lbl_reported_value: - widget.unparent() - self.selected_problem = problem action_enabled = problem is not None @@ -772,12 +762,6 @@ def destroy_links(widget): self.lookup_action('delete').set_enabled(action_enabled) self.lookup_action('report').set_enabled(action_enabled and not problem['not-reportable']) - # Iterate through children and destroy them - child = self.vbx_links.get_first_child() - while child: - destroy_links(child) - child = child.get_next_sibling() - child = self.vbx_problem_messages.get_first_child() while child: child.unparent() @@ -819,26 +803,22 @@ def destroy_links(widget): self.lbl_summary.set_text(self._get_summary_for_problem_type(problem['type'])) # Translators: package name not available - self.lbl_app_name_value.set_text(problem['package_name'] or _("N/A")) + self.lbl_app_name.set_subtitle(problem['package_name'] or _("N/A")) # Translators: package version not available - self.lbl_app_version_value.set_text(problem['package_version'] or _("N/A")) - self.lbl_detected_value.set_text(humanize.naturaltime(datetime.datetime.now()-problem['date'])) - self.lbl_detected_value.set_tooltip_text(problem['date'].strftime(config.get_configuration()['D_T_FMT'])) + self.lbl_app_version.set_subtitle(problem['package_version'] or _("N/A")) + self.lbl_detected.set_subtitle(humanize.naturaltime(datetime.datetime.now()-problem['date'])) + self.lbl_detected.set_tooltip_text(problem['date'].strftime(config.get_configuration()['D_T_FMT'])) - self.lbl_times_detected_value.set_text(str(problem['count'])) + self.lbl_times_detected.set_subtitle(str(problem['count'])) - self.lbl_reported_value.set_visible(True) - self.lbl_reported.set_text(_("Reported")) + self.lbl_reported.set_subtitle(_("Reported")) if problem['not-reportable']: - self.lbl_reported_value.set_text(_('cannot be reported')) + self.lbl_reported.set_subtitle(_('cannot be reported')) self._show_problem_links(problem['submission']) self._show_problem_message(problem['not-reportable']) elif problem['is_reported']: if self._show_problem_links(problem['submission']): - self.lbl_reported.set_text(_("Reports")) - self.lbl_reported_value.set_visible(False) - if not any((s.name == "Bugzilla" for s in problem['submission'])): self._show_problem_message( _("This problem has been reported, but a Bugzilla ticket has not" @@ -850,11 +830,11 @@ def destroy_links(widget): # has been reported but we don't know where and when. # Probably a rare situation, usually if a problem is # reported we display a list of reports here. - self.lbl_reported_value.set_text(_('yes')) + self.lbl_reported.set_subtitle(_('yes')) else: # Translators: Displayed after 'Reported' if a problem # has not been reported. - self.lbl_reported_value.set_text(_('no')) + self.lbl_reported.set_subtitle(_('no')) def _get_selected(self, selection): return selection.get_selected_rows() From 9f3ceb78e330953d11d27028ce226cc23e1c5906 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 12:15:46 +0100 Subject: [PATCH 10/15] Various header bar button fixes --- data/ui/oops-window.ui | 40 ++++++++++++---------------------------- src/gnome_abrt/views.py | 5 ----- 2 files changed, 12 insertions(+), 33 deletions(-) diff --git a/data/ui/oops-window.ui b/data/ui/oops-window.ui index 3c88313d..80416e0b 100644 --- a/data/ui/oops-window.ui +++ b/data/ui/oops-window.ui @@ -24,7 +24,7 @@ - + start True Search @@ -85,44 +85,28 @@ - - _Report - end - center - Submit selected problem - win.report + - - horizontal - 6 - - - document-new-symbolic - 16 - - - - - Create Report... - - + + document-new-symbolic + _Report + True + center + center + Submit selected problem + win.report - + Delete selected problems win.delete True - end + center center user-trash-symbolic - - - user-trash-symbolic - - diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index ed126c35..a792079f 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -287,7 +287,6 @@ class OopsWindow(Adw.ApplicationWindow): crash_box = Gtk.Template.Child() search_entry = Gtk.Template.Child() search_bar = Gtk.Template.Child() - btn_search_icon = Gtk.Template.Child() lbl_reason = Gtk.Template.Child() lbl_summary = Gtk.Template.Child() lbl_type_crash = Gtk.Template.Child() @@ -298,8 +297,6 @@ class OopsWindow(Adw.ApplicationWindow): lbl_times_detected = Gtk.Template.Child() lb_problems = Gtk.Template.Child() nb_problem_layout = Gtk.Template.Child() - btn_delete = Gtk.Template.Child() - btn_report = Gtk.Template.Child() menu_problem_item = Gtk.Template.Child() menu_multiple_problems = Gtk.Template.Child() vbx_problem_messages = Gtk.Template.Child() @@ -402,8 +399,6 @@ def __init__(self, application, sources, controller): #function to set up the auto-completion for the search entry self.setup_search_completion() - self.btn_report.add_css_class('btn-report') - self.search_entry.connect('notify::text', self.on_search_entry_text_changed) gesture = Gtk.GestureClick.new() gesture.connect("pressed", self.problems_button_press_event) From 595ab23aab6677ad2ee31166ade5d4966b70ff2b Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 12:23:01 +0100 Subject: [PATCH 11/15] Remove unused functions --- src/gnome_abrt/views.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index a792079f..7f94818c 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -327,7 +327,6 @@ def changed(self, source, change_type=None, problem=None): elif change_type == problems.ProblemSource.CHANGED_PROBLEM: self.wnd._update_problem_in_storage(problem) - self.wnd._update_source_button(source) except errors.UnavailableSource as ex: self.wnd._disable_source(ex.source, ex.temporary) @@ -362,7 +361,6 @@ def __init__(self, application, sources, controller): self._source = None self._handling_source_click = False self._configure_sources(sources) - self._set_button_toggled(self._source.button, True) self._add_actions(application) @@ -483,15 +481,6 @@ def _configure_sources(self, sources): self._source = self._all_sources[0] - def _update_source_button(self, source): - name = format_button_source_name(source.name, source) - - def _set_button_toggled(self, button, state): - pass - - def _on_source_btn_clicked(self, btn, args): - pass - def _switch_source(self, source): """Sets the passed source as the selected source.""" @@ -521,7 +510,6 @@ def _disable_source(self, source, temporary): if source_index != -1: real_source = self._all_sources[source_index] - self._set_button_toggled(real_source.button, False) if not temporary: logging.debug("Disabling source") self._all_sources.pop(source_index) @@ -531,7 +519,6 @@ def _disable_source(self, source, temporary): if (not temporary or source_index != 0) and self._all_sources: self._source = self._all_sources[0] - self._set_button_toggled(self._source.button, True) else: self._source = None @@ -681,9 +668,6 @@ def _select_problem_by_id(self, problem_id): for source in self._all_sources: if problem_id in source.get_problems(): res, old_source = self._switch_source(source) - if res: - self._set_button_toggled(old_source.button, False) - self._set_button_toggled(source.button, True) break problem_row = self._find_problem_row(problem_id) From 018ee68c0a95b9d53b3bfffa8b8574c40c254ec6 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 12:55:39 +0100 Subject: [PATCH 12/15] Use ListModels for filling the ListBox As that is the proper way to do it, instead of hacking a lot of things around. In the next commit i will add more features wrapping the models to make it worth it --- src/gnome_abrt/views.py | 165 ++++++++++++++++++++++------------------ 1 file changed, 90 insertions(+), 75 deletions(-) diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index 7f94818c..e361bfdb 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -34,7 +34,7 @@ #pylint: disable=E0611 from gi.repository import Pango #pylint: disable=E0611 -from gi.repository import GLib +from gi.repository import GLib, GObject import humanize from gi.repository import GObject @@ -42,6 +42,41 @@ from gnome_abrt import problems, config, wrappers, errors from gnome_abrt.l10n import _, C_, GETTEXT_PROGNAME + +class Problem (GObject.Object): + __gtype_name__ = 'AbrtProblem' + + _inner: problems.Problem + + application = GObject.Property( + type=str, + flags=GObject.ParamFlags.READWRITE, + ) + + last_seen = GObject.Property( + type=str, + flags=GObject.ParamFlags.READWRITE, + ) + + crash_type = GObject.Property( + type=str, + flags=GObject.ParamFlags.READWRITE, + ) + + n_times = GObject.Property( + type=int, + flags=GObject.ParamFlags.READWRITE, + ) + + def __init__(self, application, last_seen, crash_type, n_times, inner): + super().__init__() + + self.application = application + self.last_seen = last_seen + self.crash_type = crash_type + self.n_times = n_times + self.inner = inner + class ProblemsFilter: def __init__(self, list_box, list_box_selection): @@ -73,7 +108,7 @@ def match(self, list_box_row): return False # taking problem type so that we can search by "@" - problem_type = list_box_row.problem_type + problem_type = list_box_row.problem.crash_type # handling special case for filtering by problem_type using "@" symbol if self._pattern.startswith("@"): @@ -95,7 +130,7 @@ def match(self, list_box_row): if not self._pattern: return True - problem = list_box_row.get_problem() + problem = list_box_row.problem for i in ['component', 'reason', 'executable', 'package']: if problem[i]: @@ -115,7 +150,7 @@ def match(self, list_box_row): if self._pattern in rid.lower(): return True - if self._pattern in problem.problem_id.lower(): + if self._pattern in problem.inner.problem_id.lower(): return True app = problem['application'] @@ -145,25 +180,26 @@ def problem_to_storage_values(problem): else: problem_type = _("Misbehavior") - return (name, + problem = Problem(name, humanize.naturaltime(datetime.datetime.now()-problem['date_last']), problem_type, - problem['count'], + int(problem['count']), problem) + return problem #pylint: disable=W0613 def time_sort_func(first_row, second_row, trash): - fst_problem = first_row.get_problem() - scn_problem = second_row.get_problem() + fst_problem = first_row.problem + scn_problem = second_row.problem # skip invalid problems which were marked invalid while sorting - if (fst_problem.problem_id in trash or - scn_problem.problem_id in trash): + if (fst_problem.inner.problem_id in trash or + scn_problem.inner.problem_id in trash): return 0 try: - lhs = fst_problem['date_last'].timetuple() - rhs = scn_problem['date_last'].timetuple() + lhs = fst_problem.inner['date_last'].timetuple() + rhs = scn_problem.inner['date_last'].timetuple() return time.mktime(rhs) - time.mktime(lhs) except errors.InvalidProblem as ex: trash.add(ex.problem_id) @@ -216,24 +252,23 @@ def unselect_all(self): self._lb.unselect_all() def get_selected_rows(self): - return [lbr.get_problem() for lbr in self._lb.get_selected_rows()] + return [lbr.problem for lbr in self._lb.get_selected_rows()] class ProblemRow(Adw.PreferencesRow): - def __init__(self, problem_values): + def __init__(self, problem: Problem): super().__init__() - self._problem = problem_values[4] - # Store the problem type as a property (this is what we will filter by) - self.problem_type = problem_values[2].lower() # Store as lowercase for easier comparison + + self.problem = problem grid = Gtk.Grid.new() grid.set_column_spacing(12) self.set_child(grid) - self._lbl_app = Gtk.Label.new(problem_values[0]) + self._lbl_app = Gtk.Label.new(self.problem.application) self._lbl_app.set_halign(Gtk.Align.START) self._lbl_app.set_hexpand(True) self._lbl_app.set_xalign(0.0) @@ -244,13 +279,13 @@ def __init__(self, problem_values): grid.attach_next_to(self._lbl_app, None, Gtk.PositionType.RIGHT, 1, 1) - self._lbl_date = Gtk.Label.new(problem_values[1]) + self._lbl_date = Gtk.Label.new(self.problem.last_seen) self._lbl_date.set_halign(Gtk.Align.END) self._lbl_date.add_css_class('dim-label') grid.attach_next_to(self._lbl_date, self._lbl_app, Gtk.PositionType.RIGHT, 1, 1) - self._lbl_type = Gtk.Label.new(problem_values[2]) + self._lbl_type = Gtk.Label.new(self.problem.crash_type) self._lbl_type.set_halign(Gtk.Align.START) self._lbl_type.set_hexpand(True) self._lbl_type.set_xalign(0.0) @@ -259,11 +294,11 @@ def __init__(self, problem_values): grid.attach_next_to(self._lbl_type, self._lbl_app, Gtk.PositionType.BOTTOM, 1, 1) - self._lbl_count = Gtk.Label.new(problem_values[3]) + self._lbl_count = Gtk.Label.new(str(self.problem.n_times)) self._lbl_count.set_halign(Gtk.Align.END) self._lbl_count.add_css_class('times-detected-label') #showing lbl_count if the count is greater than 1 - self._lbl_count.set_visible(int(problem_values[3]) > 1) + self._lbl_count.set_visible(self.problem.n_times > 1) grid.attach_next_to(self._lbl_count, self._lbl_type, Gtk.PositionType.RIGHT, 1, 1) @@ -274,9 +309,6 @@ def set_values(self, problem_values): self._lbl_count.set_text(problem_values[3]) self._problem = problem_values[4] - def get_problem(self): - return self._problem - #pylint: disable=R0902 @Gtk.Template(resource_path='/org/freedesktop/GnomeAbrt/ui/oops-window.ui') @@ -350,6 +382,11 @@ def __init__(self, application, sources, controller): if not sources: raise ValueError("The source list cannot be empty!") + def create_problem_row(problem): + return ProblemRow(problem) + + self._problems = Gio.ListStore.new(Problem.__gtype__) + self.lb_problems.bind_model(self._problems, create_problem_row) self._source_observer = OopsWindow.SourceObserver(self) self._source_observer.disable() @@ -532,7 +569,7 @@ def _find_problem_row_full(self, problem): i = 0 lb_row = self.lb_problems.get_row_at_index(i) while lb_row is not None: - if problem == lb_row.get_problem(): + if problem == lb_row.problem: break i += 1 @@ -546,17 +583,12 @@ def _find_problem_row(self, problem): def _add_problem_to_storage(self, problem): try: - values = problem_to_storage_values(problem) + problem = problem_to_storage_values(problem) + self._problems.append(problem) except errors.InvalidProblem: logging.debug("Exception: %s", traceback.format_exc()) return - self._append_problem_values_to_storage(values) - - def _append_problem_values_to_storage(self, problem_values): - problem_cell = ProblemRow(problem_values) - self.lb_problems.insert(problem_cell, -1) - self._clear_invalid_problems_trash() def _clear_invalid_problems_trash(self): # append methods trigger time_sort_func() where InvalidProblem @@ -618,26 +650,12 @@ def _reload_problems(self, source): prblms = source.get_problems() for p in prblms: try: - storage_problems.append(problem_to_storage_values(p)) + self._problems.append(problem_to_storage_values(p)) except errors.InvalidProblem: logging.debug("Exception: %s", traceback.format_exc()) old_selection = self._get_selected(self.lss_problems) - self._reloading = True - try: - child = self.lb_problems.get_first_child() - while child: - next_child = child.get_next_sibling() - self.lb_problems.remove(child) - child = next_child - - if storage_problems: - for p in storage_problems: - self._append_problem_values_to_storage(p) - finally: - self._reloading = False - if storage_problems: problem_row = None if old_selection: @@ -709,8 +727,8 @@ def _get_reason_for_problem_type(self, application, problem_type, human_type): if problem_type == 'vmcore': return _("Fatal system failure") - if application.name: - return _("{0} quit unexpectedly").format(application.name) + if application: + return _("{0} quit unexpectedly").format(application) # Translators: If application name is unknown, # display neutral header "'Type' problem has been detected". @@ -739,7 +757,7 @@ def _set_problem(self, problem): action_enabled = problem is not None self.lookup_action('delete').set_enabled(action_enabled) - self.lookup_action('report').set_enabled(action_enabled and not problem['not-reportable']) + self.lookup_action('report').set_enabled(action_enabled and not problem.inner['not-reportable']) child = self.vbx_problem_messages.get_first_child() while child: @@ -752,8 +770,6 @@ def _set_problem(self, problem): self.nb_problem_layout.set_visible_child_name("problem") - app = problem['application'] - #lbl_type_crash # I'm ensuring that before applying a new crash class, # the old ones (application-crash, system-crash, system-failure) are removed to avoid incorrect styling @@ -761,44 +777,43 @@ def _set_problem(self, problem): self.crash_box.remove_css_class('system-crash') self.crash_box.remove_css_class('system-failure') - problem_type_crash = problem['type'] - if problem_type_crash == "CCpp": + if problem.crash_type == "CCpp": # Translators: These are the problem types displayed in the problem # list under the application name - problem_type_crash = _("Application Crash") + problem.crash_type = _("Application Crash") self.crash_box.add_css_class('application-crash') - elif problem_type_crash == "vmcore": - problem_type_crash = _("System Crash") + elif problem.crash_type == "vmcore": + problem.crash_type = _("System Crash") self.crash_box.add_css_class('system-crash') - elif problem_type_crash == "Kerneloops": - problem_type_crash = _("System Failure") + elif problem.crash_type == "Kerneloops": + problem.crash_type = _("System Failure") self.crash_box.add_css_class('system-failure') else: - problem_type_crash = _("Misbehavior") + problem.crash_type = _("Misbehavior") self.crash_box.add_css_class('application-crash') - self.lbl_type_crash.set_text(problem_type_crash) + self.lbl_type_crash.set_text(problem.crash_type) - self.lbl_reason.set_text(self._get_reason_for_problem_type(app, problem['type'], problem['human_type'])) - self.lbl_summary.set_text(self._get_summary_for_problem_type(problem['type'])) + self.lbl_reason.set_text(self._get_reason_for_problem_type(problem.application, problem.inner['type'], problem.inner['human_type'])) + self.lbl_summary.set_text(self._get_summary_for_problem_type(problem.inner['type'])) # Translators: package name not available - self.lbl_app_name.set_subtitle(problem['package_name'] or _("N/A")) + self.lbl_app_name.set_subtitle(problem.inner['package_name'] or _("N/A")) # Translators: package version not available - self.lbl_app_version.set_subtitle(problem['package_version'] or _("N/A")) - self.lbl_detected.set_subtitle(humanize.naturaltime(datetime.datetime.now()-problem['date'])) - self.lbl_detected.set_tooltip_text(problem['date'].strftime(config.get_configuration()['D_T_FMT'])) + self.lbl_app_version.set_subtitle(problem.inner['package_version'] or _("N/A")) + self.lbl_detected.set_subtitle(humanize.naturaltime(datetime.datetime.now()-problem.inner['date'])) + self.lbl_detected.set_tooltip_text(problem.inner['date'].strftime(config.get_configuration()['D_T_FMT'])) - self.lbl_times_detected.set_subtitle(str(problem['count'])) + self.lbl_times_detected.set_subtitle(str(problem.inner['count'])) self.lbl_reported.set_subtitle(_("Reported")) - if problem['not-reportable']: + if problem.inner['not-reportable']: self.lbl_reported.set_subtitle(_('cannot be reported')) - self._show_problem_links(problem['submission']) - self._show_problem_message(problem['not-reportable']) - elif problem['is_reported']: - if self._show_problem_links(problem['submission']): - if not any((s.name == "Bugzilla" for s in problem['submission'])): + self._show_problem_links(problem.inner['submission']) + self._show_problem_message(problem.inner['not-reportable']) + elif problem.inner['is_reported']: + if self._show_problem_links(problem.inner['submission']): + if not any((s.name == "Bugzilla" for s in problem.inner['submission'])): self._show_problem_message( _("This problem has been reported, but a Bugzilla ticket has not" " been opened. Our developers may need more information to fix the problem.\n" From 4e6a033ac91ccd97565e40ff3ad57805837355b9 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 13:13:58 +0100 Subject: [PATCH 13/15] Use a FilterListModel instead of the custom Pattern matching impl --- src/gnome_abrt/views.py | 137 +++++++++++++--------------------------- 1 file changed, 43 insertions(+), 94 deletions(-) diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index e361bfdb..32994d20 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -77,89 +77,6 @@ def __init__(self, application, last_seen, crash_type, n_times, inner): self.n_times = n_times self.inner = inner -class ProblemsFilter: - - def __init__(self, list_box, list_box_selection): - self._pattern = "" - self._list_box = list_box - self._list_box.set_filter_func(lambda row, _: self.match(row), None) - self._list_box_selection = list_box_selection - - def set_pattern(self, pattern): - self._pattern = pattern.lower() - self._list_box.invalidate_filter() - - i = 0 - problem_row = self._list_box.get_row_at_index(i) - while problem_row is not None: - if self.match(problem_row): - self._list_box.select_row(problem_row) - break - - i += 1 - problem_row = self._list_box.get_row_at_index(i) - - if problem_row is None: - self._list_box_selection.unselect_all() - - def match(self, list_box_row): - # None matches the pattern - if list_box_row is None: - return False - - # taking problem type so that we can search by "@" - problem_type = list_box_row.problem.crash_type - - # handling special case for filtering by problem_type using "@" symbol - if self._pattern.startswith("@"): - search_type = self._pattern[1:].strip().lower() - - if search_type == "": - return True - - if search_type == "misbehavior": - return problem_type == "misbehavior" - elif search_type == "system": - return problem_type in ["system failure", "system crash"] - elif search_type == "application": - return problem_type == "application crash" - else: - return False - - # Empty string matches everything - if not self._pattern: - return True - - problem = list_box_row.problem - - for i in ['component', 'reason', 'executable', 'package']: - if problem[i]: - value = str(problem[i]).lower() - if self._pattern in value: - return True - - # Check Bug tracker ID - if problem['is_reported']: - for sbm in problem['submission']: - if problems.Problem.Submission.URL != sbm.rtype: - continue - - rid = str(sbm.data) - rid = rid.rstrip('/').rsplit('/', maxsplit=1)[-1] - rid = rid.rsplit('=', maxsplit=1)[-1] - if self._pattern in rid.lower(): - return True - - if self._pattern in problem.inner.problem_id.lower(): - return True - - app = problem['application'] - if app and app.name: - return self._pattern in app.name.lower() - - return False - - def problem_to_storage_values(problem): app = problem.get_application() @@ -386,7 +303,8 @@ def create_problem_row(problem): return ProblemRow(problem) self._problems = Gio.ListStore.new(Problem.__gtype__) - self.lb_problems.bind_model(self._problems, create_problem_row) + self._filter_model = Gtk.FilterListModel.new(self._problems, None) + self.lb_problems.bind_model(self._filter_model, create_problem_row) self._source_observer = OopsWindow.SourceObserver(self) self._source_observer.disable() @@ -407,9 +325,7 @@ def create_problem_row(problem): self.lb_problems.set_sort_func(time_sort_func, self._trash) self.lss_problems = ListBoxSelection(self.lb_problems, self.on_tvs_problems_changed) - self._filter = ProblemsFilter(self.lb_problems, - self.lss_problems) - + self.lb_problems.grab_focus() try: self._reload_problems(self._source) @@ -482,7 +398,12 @@ def on_suggestion_selected(self, listbox, row): self.search_entry.set_position(-1) #moves the cursor to the last position self.completion_popover.popdown() #apply the filter based on the selected suggestion - self._filter.set_pattern(suggestion) + suggested_crash_type = suggestion[1:].strip().lower() + + def crash_type_filter(obj1): + return obj1.crash_type == suggested_crash_type + + self._filter_model.set_filter(Gtk.CustomFilter.new(crash_type_filter)) def _add_actions(self, application): action_entries = [ @@ -615,8 +536,8 @@ def _remove_problem_from_storage(self, problem): if selected: for i in range(index, -1, -1): problem_row = self.lb_problems.get_row_at_index(i) - if self._filter.match(problem_row): - break + #if self._filter.match(problem_row): + # break if problem_row is not None: self.lb_problems.select_row(problem_row) @@ -665,12 +586,12 @@ def _reload_problems(self, source): if problem_row is None: problem_row = self.lb_problems.get_row_at_index(i) i = 1 - + """ while (problem_row is not None and not self._filter.match(problem_row)): problem_row = self.lb_problems.get_row_at_index(i) i += 1 - + """ if problem_row is not None: self.lb_problems.select_row(problem_row) return @@ -888,8 +809,36 @@ def on_search_icon_clicked(self, button): @handle_problem_and_source_errors def on_se_problems_search_changed(self, entry): - print(type(entry)) - self._filter.set_pattern(entry.get_text()) + text = entry.get_text() + + def text_filter(obj1): + problem = obj1.inner # the inner problem + for i in ['component', 'reason', 'executable', 'package']: + if problem[i]: + value = str(problem[i]).lower() + if text in value: + return True + + # Check Bug tracker ID + if problem['is_reported']: + for sbm in problem['submission']: + if problems.Problem.Submission.URL != sbm.rtype: + continue + + rid = str(sbm.data) + rid = rid.rstrip('/').rsplit('/', maxsplit=1)[-1] + rid = rid.rsplit('=', maxsplit=1)[-1] + if text in rid.lower(): + return True + + if text in problem.inner.problem_id.lower(): + return True + + app = problem['application'] + if app and app.name: + return text in app.name.lower() + + self._filter_model.set_filter(Gtk.CustomFilter.new(text_filter)) def _on_key_press_event(self, controller, keyval, keycode, state): From a95d31aa3179e92622a0b6429af29f5f1cc25c00 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 13:48:01 +0100 Subject: [PATCH 14/15] Drop MultipleSource abstractions Useless... --- src/gnome-abrt | 24 +------ src/gnome_abrt/controller.py.in | 19 +++--- src/gnome_abrt/dbus_problems.py | 5 -- src/gnome_abrt/problems.py | 91 ------------------------- src/gnome_abrt/views.py | 117 +++++--------------------------- 5 files changed, 29 insertions(+), 227 deletions(-) diff --git a/src/gnome-abrt b/src/gnome-abrt index 767fdfb1..dd079eb6 100755 --- a/src/gnome-abrt +++ b/src/gnome-abrt @@ -48,15 +48,7 @@ gnome_abrt.init() #pylint: disable=C0413 from gnome_abrt.views import OopsWindow #pylint: disable=C0413 -from gnome_abrt.controller import Controller -#pylint: disable=C0413 -from gnome_abrt.signals import glib_sigchld_signal_handler -#pylint: disable=C0413 -from gnome_abrt.problems import MultipleSources -#pylint: disable=C0413 from gnome_abrt.dbus_problems import get_standard_problems_source -#from gnome_abrt.dbus_problems import (get_standard_problems_source, -# get_foreign_problems_source) #pylint: disable=C0413 from gnome_abrt.errors import UnavailableSource #pylint: disable=C0413 @@ -81,7 +73,6 @@ class OopsApplication(Adw.Application): resource_base_path='/org/freedesktop/GnomeAbrt/', flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE) - self.all_sources = None self.gnome_settings = None # pylint: disable=W0105 ''' @@ -172,23 +163,14 @@ class OopsApplication(Adw.Application): conf.add_option("all_problems", default_value=False) self._bind_to_gsettings(conf) - sources = [] + # pylint: disable=W0105 try: - sources.append( - get_standard_problems_source()) + main_window = OopsWindow(self, get_standard_problems_source()) except UnavailableSource as ex: logging.warning(str(ex)) - - if not sources: raise UnavailableSource(None, None, message="No available problem source.") - - # pylint: disable=W0105 - self.all_sources = [("All Problems", MultipleSources(sources))] - - controller = Controller(self.all_sources, - glib_sigchld_signal_handler) - main_window = OopsWindow(self, self.all_sources, controller) + main_window.present() self.add_window(main_window) #pylint: disable=W0703 diff --git a/src/gnome_abrt/controller.py.in b/src/gnome_abrt/controller.py.in index 6e6829f9..0df0566c 100644 --- a/src/gnome_abrt/controller.py.in +++ b/src/gnome_abrt/controller.py.in @@ -25,8 +25,8 @@ from gnome_abrt import errors class Controller: - def __init__(self, sources, sigchld_assign=None): - self.sources = sources + def __init__(self, source, sigchld_assign=None): + self.source = source self.run_event_fn = self._first_event_run self._sigchld_assign = sigchld_assign @@ -44,17 +44,16 @@ class Controller: problem.delete() - def _refresh_sources(self): - for name, src in self.sources: - try: - src.refresh() - except errors.UnavailableSource: - logging.debug("Cannot refresh problem source {0}: {1}" - .format(name, traceback.format_exc())) + def _refresh_source(self): + try: + self.source.refresh() + except errors.UnavailableSource: + logging.debug("Cannot refresh problem source {0}: {1}" + .format(name, traceback.format_exc())) def _first_event_run(self, event, problem): if self._sigchld_assign is not None: - self._sigchld_assign(self._refresh_sources) + self._sigchld_assign(self._refresh_source) self.run_event_fn = self._run_event_on_problem self.run_event_fn(event, problem) diff --git a/src/gnome_abrt/dbus_problems.py b/src/gnome_abrt/dbus_problems.py index f6726d17..1a1483ef 100644 --- a/src/gnome_abrt/dbus_problems.py +++ b/src/gnome_abrt/dbus_problems.py @@ -38,11 +38,6 @@ def get_standard_problems_source(mainloop=None): return DBusProblemSource(StandardProblems, mainloop) -''' -def get_foreign_problems_source(mainloop=None): - return DBusProblemSource(ForeignProblems, mainloop) -''' - class DBusProblemSource(problems.CachedSource): class Driver: diff --git a/src/gnome_abrt/problems.py b/src/gnome_abrt/problems.py index 6136d2c5..075b3c0e 100644 --- a/src/gnome_abrt/problems.py +++ b/src/gnome_abrt/problems.py @@ -364,97 +364,6 @@ def get_submission(self): return self.submission - -class MultipleSources(ProblemSource): - - def __init__(self, sources): - super().__init__() - - if not sources: - raise ValueError("At least one source must be passed") - - self.sources = sources - - class SourceObserver: - def __init__(self, parent): - self.parent = parent - - # pylint: disable=W0613 - def changed(self, source, change_type=None, problem=None): - self.parent.notify(change_type, problem) - - for src in self.sources: - src.attach(SourceObserver(self)) - - self._disable_notify = False - - def __eq__(self, other): - # override __eq__ to be able to find component source's master source - # in a list of sources - if isinstance(other, ProblemSource): - # check if the other is a component - if other in self.sources: - return True - if not isinstance(other, MultipleSources): - # the other source is not a component and cannot be self because - # it is not an instance of MultipleSources - return False - - # fall back to built-in behaviour (self == other) - return NotImplemented - - def _pop_source(self, index): - self.sources.pop(index) - if not self.sources: - raise UnavailableSource(self, None) - - def get_items(self, problem_id, *args): - pass - - def _foreach_source(self, callback): - i = 0 - while i != len(self.sources): - try: - callback(self.sources[i]) - i += 1 - except UnavailableSource as ex: - logging.debug("%s", str(ex)) - if not ex.temporary: - self._pop_source(i) - else: - i += 1 - - def get_problems(self): - result = [] - - def extend_result(source): - return result.extend(source.get_problems()) - - self._foreach_source(extend_result) - - return result - - def chown_problem(self, problem_id): - pass - - def delete_problem(self, problem_id): - pass - - def notify(self, change_type=None, problem=None): - if self._disable_notify: - return - - super().notify(change_type, problem) - - def refresh(self): - self._disable_notify = True - try: - self._foreach_source(lambda source: source.refresh()) - finally: - self._disable_notify = False - - self.notify() - class CachedSource(ProblemSource): def __init__(self): diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index 32994d20..a95befde 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -42,6 +42,10 @@ from gnome_abrt import problems, config, wrappers, errors from gnome_abrt.l10n import _, C_, GETTEXT_PROGNAME +#pylint: disable=C0413 +from gnome_abrt.controller import Controller +#pylint: disable=C0413 +from gnome_abrt.signals import glib_sigchld_signal_handler class Problem (GObject.Object): __gtype_name__ = 'AbrtProblem' @@ -138,7 +142,6 @@ def wrapper_for_instance_function(oops_wnd, *args): oops_wnd._remove_problem_from_storage(ex.problem_id) except errors.UnavailableSource as ex: logging.debug(traceback.format_exc()) - oops_wnd._disable_source(ex.source, ex.temporary) return None @@ -265,20 +268,15 @@ def changed(self, source, change_type=None, problem=None): if not self._enabled: return - try: - if source == self.wnd._source: - if change_type is None: - self.wnd._reload_problems(source) - elif change_type == problems.ProblemSource.NEW_PROBLEM: - self.wnd._add_problem_to_storage(problem) - elif change_type == problems.ProblemSource.DELETED_PROBLEM: - self.wnd._remove_problem_from_storage(problem) - elif change_type == problems.ProblemSource.CHANGED_PROBLEM: - self.wnd._update_problem_in_storage(problem) - - except errors.UnavailableSource as ex: - self.wnd._disable_source(ex.source, ex.temporary) - + if source == self.wnd._source: + if change_type is None: + self.wnd._reload_problems(source) + elif change_type == problems.ProblemSource.NEW_PROBLEM: + self.wnd._add_problem_to_storage(problem) + elif change_type == problems.ProblemSource.DELETED_PROBLEM: + self.wnd._remove_problem_from_storage(problem) + elif change_type == problems.ProblemSource.CHANGED_PROBLEM: + self.wnd._update_problem_in_storage(problem) class OptionsObserver: def __init__(self, wnd): @@ -293,12 +291,9 @@ def option_updated(self, conf, option): self.wnd._set_problem(self.wnd.selected_problem) - def __init__(self, application, sources, controller): + def __init__(self, application, source): super().__init__(application=application) - if not sources: - raise ValueError("The source list cannot be empty!") - def create_problem_row(problem): return ProblemRow(problem) @@ -309,13 +304,11 @@ def create_problem_row(problem): self._source_observer.disable() self._reloading = False - self._controller = controller + self._controller = Controller(source, + glib_sigchld_signal_handler) self.selected_problem = None - self._all_sources = [] - self._source = None self._handling_source_click = False - self._configure_sources(sources) self._add_actions(application) @@ -327,10 +320,7 @@ def create_problem_row(problem): self.on_tvs_problems_changed) self.lb_problems.grab_focus() - try: - self._reload_problems(self._source) - except errors.UnavailableSource as ex: - self._disable_source(ex.source, ex.temporary) + self._reload_problems(source) self._options_observer = OopsWindow.OptionsObserver(self) conf = config.get_configuration() @@ -422,69 +412,6 @@ def _add_actions(self, application): application.set_accels_for_action('win.copy-id', ['c']) application.set_accels_for_action('win.search', ['f']) - def _configure_sources(self, sources): - for name, src in sources: - self._all_sources.append(src) - src.attach(self._source_observer) - - label = None - try: - label = format_button_source_name(name, src) - except errors.UnavailableSource: - logging.debug("Unavailable source: %s", name) - continue - - src.name = name - src.button = None - - self._source = self._all_sources[0] - - def _switch_source(self, source): - """Sets the passed source as the selected source.""" - - result = True - old_source = None - if source != self._source: - try: - self._reload_problems(source) - old_source = self._source - self._source = source - except errors.UnavailableSource as ex: - self._disable_source(source, ex.temporary) - result = False - - return (result, old_source) - - def _disable_source(self, source, temporary): - if self._source is None or not self._all_sources: - return - - # Some sources can be components of other sources. - # Problems are connected directly to the component sources, therefore - # exception's source is a component source, thus we have to find an - # instance of composite source which the unavailable component source - # belongs. - source_index = self._all_sources.index(source) - - if source_index != -1: - real_source = self._all_sources[source_index] - if not temporary: - logging.debug("Disabling source") - self._all_sources.pop(source_index) - - if source != self._source: - return - - if (not temporary or source_index != 0) and self._all_sources: - self._source = self._all_sources[0] - else: - self._source = None - - try: - self._reload_problems(self._source) - except errors.UnavailableSource as ex: - self._disable_source(ex.source, ex.temporary) - @handle_problem_and_source_errors def _find_problem_row_full(self, problem): i = 0 @@ -599,16 +526,6 @@ def _reload_problems(self, source): self._set_problem(None) def _select_problem_by_id(self, problem_id): - # The problem could come from a different source than the currently - # loaded source. If so, try to switch to problem's origin source and - # select the problem after that. - if (self._source is not None and - problem_id not in self._source.get_problems()): - for source in self._all_sources: - if problem_id in source.get_problems(): - res, old_source = self._switch_source(source) - break - problem_row = self._find_problem_row(problem_id) if problem_row is not None: From 3893020f7231d70a41edd7fcb700b665c75a97d3 Mon Sep 17 00:00:00 2001 From: Bilal Elmoussaoui Date: Tue, 18 Feb 2025 14:01:03 +0100 Subject: [PATCH 15/15] use a SortListModel Avoids a warning when using the set_sort_func --- src/gnome_abrt/views.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/gnome_abrt/views.py b/src/gnome_abrt/views.py index a95befde..d80be977 100644 --- a/src/gnome_abrt/views.py +++ b/src/gnome_abrt/views.py @@ -110,9 +110,7 @@ def problem_to_storage_values(problem): #pylint: disable=W0613 -def time_sort_func(first_row, second_row, trash): - fst_problem = first_row.problem - scn_problem = second_row.problem +def time_sort_func(fst_problem, scn_problem, trash): # skip invalid problems which were marked invalid while sorting if (fst_problem.inner.problem_id in trash or scn_problem.inner.problem_id in trash): @@ -297,9 +295,14 @@ def __init__(self, application, source): def create_problem_row(problem): return ProblemRow(problem) + # a set where invalid problems found while sorting of the problem list + # are stored + self._trash = set() + self._problems = Gio.ListStore.new(Problem.__gtype__) self._filter_model = Gtk.FilterListModel.new(self._problems, None) - self.lb_problems.bind_model(self._filter_model, create_problem_row) + self._sort_model = Gtk.SortListModel.new(self._filter_model, Gtk.CustomSorter.new(time_sort_func, self._trash)) + self.lb_problems.bind_model(self._sort_model, create_problem_row) self._source_observer = OopsWindow.SourceObserver(self) self._source_observer.disable() @@ -312,10 +315,6 @@ def create_problem_row(problem): self._add_actions(application) - # a set where invalid problems found while sorting of the problem list - # are stored - self._trash = set() - self.lb_problems.set_sort_func(time_sort_func, self._trash) self.lss_problems = ListBoxSelection(self.lb_problems, self.on_tvs_problems_changed)