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

bleachbit / bleachbit / 22011198150

14 Feb 2026 04:31AM UTC coverage: 71.333% (-0.5%) from 71.882%
22011198150

push

github

az0
Fix NSIS escaping again

Error in the AppVeyer log
warning 6000: unknown variable/constant "\$\$\$\$\$\$\$\$\$\$\$\$\"{FOLDER}$\$\$\$\$\$\$\$\$\$\$\$\$\"." detected, ignoring (LangString MULTIUSER_INSTALLED_ALL_USERS:1067)

Follow up to 75956835c

This issue did not affect BleachBit 5.0.2 (stable).

6313 of 8850 relevant lines covered (71.33%)

0.71 hits per line

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

10.14
/bleachbit/GuiBasic.py
1
# vim: ts=4:sw=4:expandtab
2

3
# BleachBit
4
# Copyright (C) 2008-2025 Andrew Ziem
5
# https://www.bleachbit.org
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19

20
"""
21
Basic GUI code
22
"""
23

24
# standard library
25
import os
1✔
26

27
# local import
28
from bleachbit.GtkShim import Gtk, Gdk, GLib, require_gtk
1✔
29
from bleachbit.Language import get_text as _
1✔
30
from bleachbit.Options import options
1✔
31
if os.name == 'nt':
1✔
32
    from bleachbit import Windows
1✔
33

34
# Ensure GTK is available for this GUI module
35
require_gtk()
1✔
36

37

38
def browse_folder(parent, title, multiple, stock_button):
1✔
39
    """Ask the user to select a folder.  Return the full path or None."""
40

41
    if os.name == 'nt' and not os.getenv('BB_NATIVE'):
×
42
        ret = Windows.browse_folder(parent, title)
×
43
        return [ret] if multiple and not ret is None else ret
×
44

45
    # fall back to GTK+
46
    chooser = Gtk.FileChooserDialog(transient_for=parent,
×
47
                                    title=title,
48
                                    action=Gtk.FileChooserAction.SELECT_FOLDER)
49
    chooser.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL,
×
50
                        stock_button, Gtk.ResponseType.OK)
51
    chooser.set_default_response(Gtk.ResponseType.OK)
×
52
    chooser.set_select_multiple(multiple)
×
53
    chooser.set_current_folder(os.path.expanduser('~'))
×
54
    resp = chooser.run()
×
55
    if multiple:
×
56
        ret = chooser.get_filenames()
×
57
    else:
58
        ret = chooser.get_filename()
×
59
    chooser.hide()
×
60
    chooser.destroy()
×
61
    if Gtk.ResponseType.OK != resp:
×
62
        # user cancelled
63
        return None
×
64
    return ret
×
65

66

67
def browse_file(parent, title):
1✔
68
    """Prompt user to select a single file"""
69

70
    if os.name == 'nt' and not os.getenv('BB_NATIVE'):
×
71
        return Windows.browse_file(parent, title)
×
72

73
    chooser = Gtk.FileChooserDialog(title=title,
×
74
                                    transient_for=parent,
75
                                    action=Gtk.FileChooserAction.OPEN)
76
    chooser.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL,
×
77
                        _("_Open"), Gtk.ResponseType.OK)
78
    chooser.set_default_response(Gtk.ResponseType.OK)
×
79
    chooser.set_current_folder(os.path.expanduser('~'))
×
80
    resp = chooser.run()
×
81
    path = chooser.get_filename()
×
82
    chooser.destroy()
×
83

84
    if Gtk.ResponseType.OK != resp:
×
85
        # user cancelled
86
        return None
×
87

88
    return path
×
89

90

91
def browse_files(parent, title):
1✔
92
    """Prompt user to select multiple files to delete"""
93

94
    if os.name == 'nt' and not os.getenv('BB_NATIVE'):
×
95
        return Windows.browse_files(parent, title)
×
96

97
    chooser = Gtk.FileChooserDialog(title=title,
×
98
                                    transient_for=parent,
99
                                    action=Gtk.FileChooserAction.OPEN)
100
    chooser.add_buttons(_("_Cancel"), Gtk.ResponseType.CANCEL,
×
101
                        _("_Delete"), Gtk.ResponseType.OK)
102
    chooser.set_default_response(Gtk.ResponseType.OK)
×
103
    chooser.set_select_multiple(True)
×
104
    chooser.set_current_folder(os.path.expanduser('~'))
×
105
    resp = chooser.run()
×
106
    paths = chooser.get_filenames()
×
107
    chooser.destroy()
×
108

109
    if Gtk.ResponseType.OK != resp:
×
110
        # user cancelled
111
        return None
×
112

113
    return paths
×
114

115

116
def delete_confirmation_dialog(parent, mention_preview, shred_settings=False):
1✔
117
    """Return boolean whether OK to delete files."""
118
    dialog = Gtk.Dialog(title=_("Delete confirmation"), transient_for=parent,
×
119
                        modal=True,
120
                        destroy_with_parent=True)
121
    dialog.set_default_size(300, -1)
×
122

123
    vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,
×
124
                   homogeneous=False, spacing=10)
125

126
    if shred_settings:
×
127
        notice_text = _("This function deletes all BleachBit settings and then quits the application. Use this to hide your use of BleachBit or to reset its settings. The next time you start BleachBit, the settings will initialize to default values.")
×
128
        notice = Gtk.Label(label=notice_text)
×
129
        notice.set_line_wrap(True)
×
130
        vbox.pack_start(notice, False, True, 0)
×
131

132
    if mention_preview:
×
133
        question_text = _(
×
134
            "Are you sure you want to permanently delete files according to the selected operations?  The actual files that will be deleted may have changed since you ran the preview.")
135
    else:
136
        question_text = _(
×
137
            "Are you sure you want to permanently delete these files?")
138
    question = Gtk.Label(label=question_text)
×
139
    question.set_line_wrap(True)
×
140
    vbox.pack_start(question, False, True, 0)
×
141

142
    if options.get('expert_mode'):
×
143
        cb_popup = Gtk.CheckButton(label=_("Confirm before delete"))
×
144
        cb_popup.set_active(options.get('delete_confirmation'))
×
145
        vbox.pack_start(cb_popup, False, True, 0)
×
146

147
    dialog.get_content_area().pack_start(vbox, False, True, 0)
×
148
    dialog.get_content_area().set_spacing(10)
×
149

150
    dialog.add_button(_('_Delete'), Gtk.ResponseType.ACCEPT)
×
151
    dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
×
152
    dialog.set_default_response(Gtk.ResponseType.CANCEL)
×
153

154
    dialog.show_all()
×
155
    ret = dialog.run()
×
156
    if options.get('expert_mode'):
×
157
        options.set('delete_confirmation', cb_popup.get_active())
×
158
    dialog.destroy()
×
159
    return ret == Gtk.ResponseType.ACCEPT
×
160

161

162
def warning_confirm_dialog(parent, option_name, warning_text, show_checkbox=True):
1✔
163
    """Show a warning dialog when enabling an option.
164

165
    Returns tuple (confirmed: bool, remember_choice: bool).
166
    """
167
    dialog = Gtk.Dialog(title=_('Enable %(option)s') % {'option': option_name},
×
168
                        transient_for=parent,
169
                        modal=True,
170
                        destroy_with_parent=True)
171
    content = dialog.get_content_area()
×
172
    content.set_border_width(12)
×
173
    content.set_spacing(10)
×
174

175
    warning_label = Gtk.Label(label=warning_text)
×
176
    warning_label.set_line_wrap(True)
×
177
    warning_label.set_xalign(0.0)
×
178
    content.pack_start(warning_label, False, False, 0)
×
179

180
    remember_cb = Gtk.CheckButton(
×
181
        label=_('Remember my choice for %(option)s') % {'option': option_name})
182
    remember_cb.set_halign(Gtk.Align.START)
×
183
    if show_checkbox:
×
184
        content.pack_start(remember_cb, False, False, 0)
×
185

186
    cancel_button = dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
×
187
    enable_button = dialog.add_button(_("_Enable anyway"), Gtk.ResponseType.OK)
×
188
    enable_button.get_style_context().add_class('destructive-action')
×
189
    dialog.set_default_response(Gtk.ResponseType.CANCEL)
×
190
    cancel_button.grab_focus()
×
191

192
    # Disable enable button for a moment to prevent accident by double-clicking.
193
    enable_button.set_sensitive(False)
×
194

195
    def enable_button_after_delay():
×
196
        enable_button.set_sensitive(True)
×
197
        return False
×
198

199
    GLib.timeout_add(500, enable_button_after_delay)
×
200

201
    dialog.show_all()
×
202
    response = dialog.run()
×
203
    remember_choice = response == Gtk.ResponseType.OK and show_checkbox and remember_cb.get_active()
×
204
    dialog.destroy()
×
205

206
    return response == Gtk.ResponseType.OK, remember_choice
×
207

208

209
def message_dialog(parent, msg, mtype=Gtk.MessageType.ERROR, buttons=Gtk.ButtonsType.OK, title=None):
1✔
210
    """Convenience wrapper for Gtk.MessageDialog"""
211

212
    dialog = Gtk.MessageDialog(transient_for=parent,
×
213
                               modal=True,
214
                               destroy_with_parent=True,
215
                               message_type=mtype,
216
                               buttons=buttons,
217
                               text=msg)
218
    if title:
×
219
        dialog.set_title(title)
×
220
    resp = dialog.run()
×
221
    dialog.destroy()
×
222

223
    return resp
×
224

225

226
def open_url(url, parent_window=None, prompt=True):
1✔
227
    """Open an HTTP URL.  Try to run as non-root."""
228
    # drop privileges so the web browser is running as a normal process
229
    if os.name == 'posix' and os.getuid() == 0:
×
230
        msg = _(
×
231
            "Because you are running as root, please manually open this link in a web browser:\n%s") % url
232
        message_dialog(None, msg, Gtk.MessageType.INFO)
×
233
        return
×
234
    if prompt:
×
235
        # find hostname
236
        import re
×
237
        ret = re.search(r'^http(s)?://([a-z.]+)', url)
×
238
        if not ret:
×
239
            host = url
×
240
        else:
241
            host = ret.group(2)
×
242
        # TRANSLATORS: %s expands to www.bleachbit.org or similar
243
        msg = _("Open web browser to %s?") % host
×
244
        resp = message_dialog(parent_window,
×
245
                              msg,
246
                              Gtk.MessageType.QUESTION,
247
                              Gtk.ButtonsType.OK_CANCEL,
248
                              _('Confirm'))
249
        if Gtk.ResponseType.OK != resp:
×
250
            return
×
251
    # open web browser
252
    if os.name == 'nt':
×
253
        # in Gtk.show_uri() avoid 'glib.GError: No application is registered as
254
        # handling this file'
255
        import webbrowser
×
256
        webbrowser.open(url)
×
257
    elif (Gtk.get_major_version(), Gtk.get_minor_version()) < (3, 22):
×
258
        # Ubuntu 16.04 LTS ships with GTK 3.18
259
        Gtk.show_uri(None, url, Gdk.CURRENT_TIME)
×
260
    else:
261
        Gtk.show_uri_on_window(parent_window, url, Gdk.CURRENT_TIME)
×
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