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

bleachbit / bleachbit / 22770330947

06 Mar 2026 03:37PM UTC coverage: 72.411% (+0.6%) from 71.855%
22770330947

push

github

az0
Disable cookie bulk actions while loading

It prevents saving an empty list

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

1199 existing lines in 26 files now uncovered.

6698 of 9250 relevant lines covered (72.41%)

0.72 hits per line

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

60.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."""
UNCOV
71
        assert isinstance(value, bool)
×
72
        assert isinstance(model, Gtk.TreeStore)
×
73
        cleaner_id = None
×
74
        i = path
×
UNCOV
75
        if isinstance(i, str):
×
76
            # type is either str or gtk.TreeIter
77
            i = model.get_iter(path)
×
UNCOV
78
        parent = model.iter_parent(i)
×
UNCOV
79
        if parent:
×
80
            # this is an option (child), not a cleaner (parent)
81
            cleaner_id = model[parent][2]
×
UNCOV
82
            option_id = model[path][2]
×
83
        if cleaner_id and value:
×
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)
×
87
            if warning and not options.get('expert_mode'):
×
88
                if hasattr(parent_window, 'show_infobar'):
×
UNCOV
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"
×
94
                return
×
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.
UNCOV
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:
×
UNCOV
109
                    options.remember_warning_preference(warning_key)
×
110
        model[path][1] = value
×
111
        model[path][4] = ""
×
112

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

139

140
class TreeInfoModel:
1✔
141
    """Model holds information to be displayed in the tree view"""
142

143
    def __init__(self):
1✔
144
        self.tree_store = Gtk.TreeStore(
1✔
145
            GObject.TYPE_STRING, GObject.TYPE_BOOLEAN, GObject.TYPE_PYOBJECT, GObject.TYPE_STRING, GObject.TYPE_STRING)
146
        if not self.tree_store:
1✔
UNCOV
147
            raise Exception("cannot create tree store")
×
148
        self.row_changed_handler_id = None
1✔
149
        self.refresh_rows()
1✔
150
        self.tree_store.set_sort_func(3, self.sort_func)
1✔
151
        self.tree_store.set_sort_column_id(3, Gtk.SortType.ASCENDING)
1✔
152

153
    def get_model(self):
1✔
154
        """Return the tree store"""
155
        return self.tree_store
1✔
156

157
    def on_row_changed(self, __treemodel, path, __iter):
1✔
158
        """Event handler for when a row changes"""
159
        parent = self.tree_store[path[0]][2]
1✔
160
        child = None
1✔
161
        if len(path) == 2:
1✔
162
            child = self.tree_store[path][2]
1✔
163
        value = self.tree_store[path][1]
1✔
164
        options.set_tree(parent, child, value)
1✔
165

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

194
    def sort_func(self, model, iter1, iter2, _user_data):
1✔
195
        """Sort the tree by the id
196

197
        Index 0 is the display name
198
        Index 2 is the ID (e.g., cookies, vacuum).
199

200
        Sorting by ID is functionally important, so that vacuuming is done
201
        last, even for other languages. See https://github.com/bleachbit/bleachbit/issues/441
202
        """
203
        value1 = model[iter1][2].lower()
1✔
204
        value2 = model[iter2][2].lower()
1✔
205
        if value1 == value2:
1✔
UNCOV
206
            return 0
×
207
        if value1 > value2:
1✔
UNCOV
208
            return 1
×
209
        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