• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bleachbit / bleachbit / 24935925314

25 Apr 2026 04:56PM UTC coverage: 73.757% (+1.3%) from 72.5%
24935925314

push

github

az0
Fix: parameter name in progress_cb()

1 of 1 new or added line in 1 file covered. (100.0%)

1089 existing lines in 29 files now uncovered.

7178 of 9732 relevant lines covered (73.76%)

0.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.94
/bleachbit/GuiTreeModels.py
1
# SPDX-License-Identifier: GPL-3.0-or-later
2
# Copyright (c) 2008-2026 Andrew Ziem.
3
#
4
# This work is licensed under the terms of the GNU GPL, version 3 or
5
# later.  See the COPYING file in the top-level directory.
6

7
from bleachbit import GuiBasic
1✔
8
from bleachbit.Cleaner import backends
1✔
9
from bleachbit.GUI import logger
1✔
10
from bleachbit.GtkShim import GObject, Gtk
1✔
11
from bleachbit.Language import get_text as _
1✔
12
from bleachbit.Options import options
1✔
13

14

15
class TreeDisplayModel:
1✔
16
    """Displays the info model in a view"""
17

18
    def make_view(self, model, parent, context_menu_event):
1✔
19
        """Create and return a TreeView object"""
20
        self.view = Gtk.TreeView.new_with_model(model)
1✔
21

22
        # hide headers
23
        # These commits disabled the header: 5d0b5b7ab,49ad443.
24
        self.view.set_headers_visible(False)
1✔
25

26
        # listen for right click (context menu)
27
        self.view.connect("button_press_event", context_menu_event)
1✔
28

29
        # first column: cleaner name
30
        renderer_name = Gtk.CellRendererText()
1✔
31
        # TRANSLATORS: Column header for the cleaner name.
32
        # Used in the tree view on the main screen.
33
        # 'Name' is a noun.
34
        column_name = Gtk.TreeViewColumn(_("Name"), renderer_name, text=0)
1✔
35
        self.view.append_column(column_name)
1✔
36
        self.view.set_search_column(0)
1✔
37

38
        # second column: alert icon
39
        renderer_alert = Gtk.CellRendererPixbuf()
1✔
40
        column_alert = Gtk.TreeViewColumn("", renderer_alert)
1✔
41
        column_alert.add_attribute(renderer_alert, "icon-name", 4)
1✔
42
        self.view.append_column(column_alert)
1✔
43

44
        # third column: checkbox
45
        renderer_active = Gtk.CellRendererToggle()
1✔
46
        renderer_active.set_property('activatable', True)
1✔
47
        renderer_active.connect('toggled', self.col1_toggled_cb, model, parent)
1✔
48
        # TRANSLATORS: Column header indicating whether the cleaner is active/enabled.
49
        # Used in the tree view on the main screen.
50
        column_active = Gtk.TreeViewColumn(_("Active"), renderer_active)
1✔
51
        column_active.add_attribute(renderer_active, "active", 1)
1✔
52
        self.view.append_column(column_active)
1✔
53

54
        # fourth column: size
55
        renderer_size = Gtk.CellRendererText()
1✔
56
        renderer_size.set_alignment(1.0, 0.0)
1✔
57
        # TRANSLATORS: Column header for the size of the cleaner.
58
        # Used in the tree view on the main screen.
59
        # 'Size' is a noun and refers to the amount of space that the cleaner
60
        # would clean (preview mode) or did clean (cleaning mode).
61
        column_size = Gtk.TreeViewColumn(_("Size"), renderer_size, text=3)
1✔
62
        column_size.set_alignment(1.0)
1✔
63
        self.view.append_column(column_size)
1✔
64

65
        # finish
66
        self.view.expand_all()
1✔
67
        return self.view
1✔
68

69
    def set_cleaner(self, path, model, parent_window, value):
1✔
70
        """Activate or deactivate option of cleaner."""
71
        assert isinstance(value, bool)
1✔
72
        assert isinstance(model, Gtk.TreeStore)
1✔
73
        cleaner_id = None
1✔
74
        i = path
1✔
75
        if isinstance(i, str):
1✔
76
            # type is either str or gtk.TreeIter
77
            i = model.get_iter(path)
1✔
78
        parent = model.iter_parent(i)
1✔
79
        if parent:
1✔
80
            # this is an option (child), not a cleaner (parent)
81
            cleaner_id = model[parent][2]
1✔
82
            option_id = model[path][2]
1✔
83
        if cleaner_id and value:
1✔
84
            # When enabling an option, present any warnings.
85
            # (When disabling an option, there is no need to present warnings.)
86
            warning = backends[cleaner_id].get_warning(option_id)
1✔
87
            if warning and not options.get('expert_mode'):
1✔
88
                if hasattr(parent_window, 'show_infobar'):
1✔
89
                    parent_window.show_infobar(
×
90
                        # TRANSLATORS: Warning shown in the infobar.
91
                        _("This option is protected. To bypass protection, enable expert mode."))
92
                # Show an alert icon by the option name.
93
                model[path][4] = "dialog-warning"
1✔
94
                return
1✔
95
            warning_key = 'cleaner:' + cleaner_id + ':' + option_id
×
96
            if warning and not options.get_warning_preference(warning_key):
×
97
                # TRANSLATORS: %(cleaner) may be Firefox, System, etc.
98
                # %(option) may be cache, logs, cookies, etc.
99
                option_name = _("%(cleaner)s - %(option)s") % {
×
100
                    'cleaner': model[parent][0],
101
                    'option': model[path][0],
102
                }
103
                confirmed, remember_choice = GuiBasic.warning_confirm_dialog(
×
104
                    parent_window, option_name, warning)
105
                if not confirmed:
×
106
                    # user cancelled, so don't toggle option
107
                    return
×
108
                if remember_choice:
×
109
                    options.remember_warning_preference(warning_key)
×
110
        model[path][1] = value
1✔
111
        model[path][4] = ""
1✔
112

113
    def col1_toggled_cb(self, cell, path, model, parent_window):
1✔
114
        """Callback for toggling cleaners"""
115
        is_toggled_on = not model[path][1]  # Is the new state enabled?
1✔
116
        self.set_cleaner(path, model, parent_window, is_toggled_on)
1✔
117
        i = model.get_iter(path)
1✔
118
        parent = model.iter_parent(i)
1✔
119
        if parent and is_toggled_on:
1✔
120
            # If child is enabled, then also enable the parent.
121
            model[parent][1] = True
×
122
        # If all siblings were toggled off, then also disable the parent.
123
        if parent and not is_toggled_on:
1✔
124
            sibling = model.iter_nth_child(parent, 0)
×
125
            any_sibling_enabled = False
×
126
            while sibling:
×
127
                if model[sibling][1]:
×
128
                    any_sibling_enabled = True
×
129
                sibling = model.iter_next(sibling)
×
130
            if not any_sibling_enabled:
×
131
                model[parent][1] = False
×
132
        # If toggled and has children, then do the same for each child.
133
        child = model.iter_children(i)
1✔
134
        while child:
1✔
135
            self.set_cleaner(child, model, parent_window, is_toggled_on)
1✔
136
            child = model.iter_next(child)
1✔
137
        # If the parent was just enabled but all children were blocked
138
        # by expert mode, leave the parent unchecked.
139
        if not parent and is_toggled_on:
1✔
140
            child = model.iter_children(i)
1✔
141
            any_child_enabled = False
1✔
142
            while child:
1✔
143
                if model[child][1]:
1✔
UNCOV
144
                    any_child_enabled = True
×
145
                child = model.iter_next(child)
1✔
146
            if not any_child_enabled:
1✔
147
                model[i][1] = False
1✔
148
        return
1✔
149

150

151
class TreeInfoModel:
1✔
152
    """Model holds information to be displayed in the tree view"""
153

154
    def __init__(self):
1✔
155
        self.tree_store = Gtk.TreeStore(
1✔
156
            GObject.TYPE_STRING, GObject.TYPE_BOOLEAN, GObject.TYPE_PYOBJECT, GObject.TYPE_STRING, GObject.TYPE_STRING)
157
        if not self.tree_store:
1✔
UNCOV
158
            raise Exception("cannot create tree store")
×
159
        self.row_changed_handler_id = None
1✔
160
        self.refresh_rows()
1✔
161
        self.tree_store.set_sort_func(3, self.sort_func)
1✔
162
        self.tree_store.set_sort_column_id(3, Gtk.SortType.ASCENDING)
1✔
163

164
    def get_model(self):
1✔
165
        """Return the tree store"""
166
        return self.tree_store
1✔
167

168
    def on_row_changed(self, __treemodel, path, __iter):
1✔
169
        """Event handler for when a row changes"""
170
        parent = self.tree_store[path[0]][2]
1✔
171
        child = None
1✔
172
        if len(path) == 2:
1✔
173
            child = self.tree_store[path][2]
1✔
174
        value = self.tree_store[path][1]
1✔
175
        options.set_tree(parent, child, value)
1✔
176

177
    def refresh_rows(self):
1✔
178
        """Clear rows (cleaners) and add them fresh"""
179
        if self.row_changed_handler_id:
1✔
180
            self.tree_store.disconnect(self.row_changed_handler_id)
1✔
181
        self.tree_store.clear()
1✔
182
        hidden_cleaners = []
1✔
183
        for key in sorted(backends):
1✔
184
            if not any(backends[key].get_options()):
1✔
185
                # localizations has no options, so it should be hidden
186
                # https://github.com/az0/bleachbit/issues/110
187
                continue
1✔
188
            c_name = backends[key].get_name()
1✔
189
            c_id = backends[key].get_id()
1✔
190
            c_value = options.get_tree(c_id, None)
1✔
191
            if not c_value and options.get('auto_hide') and backends[key].auto_hide():
1✔
192
                hidden_cleaners.append(c_id)
1✔
193
                continue
1✔
194
            parent = self.tree_store.append(
1✔
195
                None, (c_name, c_value, c_id, "", ""))
196
            for (o_id, o_name) in backends[key].get_options():
1✔
197
                o_value = options.get_tree(c_id, o_id)
1✔
198
                self.tree_store.append(parent, (o_name, o_value, o_id, "", ""))
1✔
199
        if hidden_cleaners:
1✔
200
            logger.debug("automatically hid %d cleaners: %s", len(
1✔
201
                hidden_cleaners), ', '.join(hidden_cleaners))
202
        self.row_changed_handler_id = self.tree_store.connect("row-changed",
1✔
203
                                                              self.on_row_changed)
204

205
    def sort_func(self, model, iter1, iter2, _user_data):
1✔
206
        """Sort the tree by the id
207

208
        Index 0 is the display name
209
        Index 2 is the ID (e.g., cookies, vacuum).
210

211
        Sorting by ID is functionally important, so that vacuuming is done
212
        last, even for other languages. See https://github.com/bleachbit/bleachbit/issues/441
213
        """
214
        value1 = model[iter1][2].lower()
1✔
215
        value2 = model[iter2][2].lower()
1✔
216
        if value1 == value2:
1✔
UNCOV
217
            return 0
×
218
        if value1 > value2:
1✔
UNCOV
219
            return 1
×
220
        return -1
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc