Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
c723247a
by Daniel Brötzmann at 2019-07-08T21:30:22Z
-
66e191a1
by Daniel Brötzmann at 2019-07-08T21:30:35Z
7 changed files:
- gajim/gtk/accounts.py
- gajim/gtk/dialogs.py
- gajim/gtk/filetransfer.py
- gajim/gui_interface.py
- gajim/history_manager.py
- gajim/message_window.py
- gajim/plugins/gui.py
Changes:
| ... | ... | @@ -34,7 +34,6 @@ from gajim.gtk.dialogs import NewConfirmationDialog |
| 34 | 34 |
from gajim.gtk.dialogs import ConfirmationDialogDoubleRadio
|
| 35 | 35 |
from gajim.gtk.dialogs import ErrorDialog
|
| 36 | 36 |
from gajim.gtk.dialogs import PassphraseDialog
|
| 37 |
-from gajim.gtk.dialogs import YesNoDialog
|
|
| 38 | 37 |
from gajim.gtk.const import Setting
|
| 39 | 38 |
from gajim.gtk.const import SettingKind
|
| 40 | 39 |
from gajim.gtk.const import SettingType
|
| ... | ... | @@ -134,12 +133,16 @@ class AccountsWindow(Gtk.ApplicationWindow): |
| 134 | 133 |
account, 'offline', _('Be right back.'))
|
| 135 | 134 |
GLib.timeout_add(500, login, account, show_before, status_before)
|
| 136 | 135 |
|
| 137 |
- YesNoDialog(
|
|
| 138 |
- _('Relogin now?'),
|
|
| 139 |
- _('If you want all the changes to apply instantly, '
|
|
| 140 |
- 'you must relogin.'),
|
|
| 141 |
- transient_for=self,
|
|
| 142 |
- on_response_yes=lambda *args: relog(account))
|
|
| 136 |
+ NewConfirmationDialog(
|
|
| 137 |
+ _('Re-Login'),
|
|
| 138 |
+ _('Re-Login now?'),
|
|
| 139 |
+ _('To apply all changes instantly, you have to re-login.'),
|
|
| 140 |
+ [DialogButton.make('Cancel',
|
|
| 141 |
+ text=_('_Later')),
|
|
| 142 |
+ DialogButton.make('OK',
|
|
| 143 |
+ text=_('_Re-Login'),
|
|
| 144 |
+ callback=lambda *args: relog(account))],
|
|
| 145 |
+ transient_for=self).show()
|
|
| 143 | 146 |
|
| 144 | 147 |
@staticmethod
|
| 145 | 148 |
def _get_relogin_settings(account):
|
| ... | ... | @@ -243,93 +243,6 @@ class ErrorDialog(HigDialog): |
| 243 | 243 |
self.popup()
|
| 244 | 244 |
|
| 245 | 245 |
|
| 246 |
-class YesNoDialog(HigDialog):
|
|
| 247 |
- """
|
|
| 248 |
- HIG compliant YesNo dialog
|
|
| 249 |
- """
|
|
| 250 |
- |
|
| 251 |
- def __init__(self, pritext, sectext='', checktext='', text_label=None,
|
|
| 252 |
- on_response_yes=None, on_response_no=None, type_=Gtk.MessageType.QUESTION,
|
|
| 253 |
- transient_for=None):
|
|
| 254 |
- self.user_response_yes = on_response_yes
|
|
| 255 |
- self.user_response_no = on_response_no
|
|
| 256 |
- if transient_for is None:
|
|
| 257 |
- transient_for = app.app.get_active_window()
|
|
| 258 |
- HigDialog.__init__(self, transient_for, type_, Gtk.ButtonsType.YES_NO, pritext,
|
|
| 259 |
- sectext, on_response_yes=self.on_response_yes,
|
|
| 260 |
- on_response_no=self.on_response_no)
|
|
| 261 |
- |
|
| 262 |
- vbox = self.get_content_area()
|
|
| 263 |
- if checktext:
|
|
| 264 |
- self.checkbutton = Gtk.CheckButton.new_with_mnemonic(checktext)
|
|
| 265 |
- vbox.pack_start(self.checkbutton, False, True, 0)
|
|
| 266 |
- else:
|
|
| 267 |
- self.checkbutton = None
|
|
| 268 |
- if text_label:
|
|
| 269 |
- label = Gtk.Label(label=text_label)
|
|
| 270 |
- vbox.pack_start(label, False, True, 0)
|
|
| 271 |
- buff = Gtk.TextBuffer()
|
|
| 272 |
- self.textview = Gtk.TextView.new_with_buffer(buff)
|
|
| 273 |
- frame = Gtk.Frame()
|
|
| 274 |
- frame.set_shadow_type(Gtk.ShadowType.IN)
|
|
| 275 |
- frame.add(self.textview)
|
|
| 276 |
- vbox.pack_start(frame, False, True, 0)
|
|
| 277 |
- else:
|
|
| 278 |
- self.textview = None
|
|
| 279 |
- self.set_modal(False)
|
|
| 280 |
- self.popup()
|
|
| 281 |
- |
|
| 282 |
- def on_response_yes(self, widget):
|
|
| 283 |
- if self.user_response_yes:
|
|
| 284 |
- if self.textview:
|
|
| 285 |
- buff = self.textview.get_buffer()
|
|
| 286 |
- start, end = buff.get_bounds()
|
|
| 287 |
- txt = self.textview.get_buffer().get_text(start, end, True)
|
|
| 288 |
- |
|
| 289 |
- if isinstance(self.user_response_yes, tuple):
|
|
| 290 |
- if self.textview:
|
|
| 291 |
- self.user_response_yes[0](self.is_checked(), txt,
|
|
| 292 |
- *self.user_response_yes[1:])
|
|
| 293 |
- else:
|
|
| 294 |
- self.user_response_yes[0](self.is_checked(),
|
|
| 295 |
- *self.user_response_yes[1:])
|
|
| 296 |
- else:
|
|
| 297 |
- if self.textview:
|
|
| 298 |
- self.user_response_yes(self.is_checked(), txt)
|
|
| 299 |
- else:
|
|
| 300 |
- self.user_response_yes(self.is_checked())
|
|
| 301 |
- self.call_cancel_on_destroy = False
|
|
| 302 |
- self.destroy()
|
|
| 303 |
- |
|
| 304 |
- def on_response_no(self, widget):
|
|
| 305 |
- if self.user_response_no:
|
|
| 306 |
- if self.textview:
|
|
| 307 |
- buff = self.textview.get_buffer()
|
|
| 308 |
- start, end = buff.get_bounds()
|
|
| 309 |
- txt = self.textview.get_buffer().get_text(start, end, True)
|
|
| 310 |
- |
|
| 311 |
- if isinstance(self.user_response_no, tuple):
|
|
| 312 |
- if self.textview:
|
|
| 313 |
- self.user_response_no[0](txt, *self.user_response_no[1:])
|
|
| 314 |
- else:
|
|
| 315 |
- self.user_response_no[0](*self.user_response_no[1:])
|
|
| 316 |
- else:
|
|
| 317 |
- if self.textview:
|
|
| 318 |
- self.user_response_no(txt)
|
|
| 319 |
- else:
|
|
| 320 |
- self.user_response_no()
|
|
| 321 |
- self.call_cancel_on_destroy = False
|
|
| 322 |
- self.destroy()
|
|
| 323 |
- |
|
| 324 |
- def is_checked(self):
|
|
| 325 |
- """
|
|
| 326 |
- Get active state of the checkbutton
|
|
| 327 |
- """
|
|
| 328 |
- if not self.checkbutton:
|
|
| 329 |
- return False
|
|
| 330 |
- return self.checkbutton.get_active()
|
|
| 331 |
- |
|
| 332 |
- |
|
| 333 | 246 |
class ConfirmationDialogDoubleCheck(ConfirmationDialog):
|
| 334 | 247 |
"""
|
| 335 | 248 |
HIG compliant confirmation dialog with 2 checkbuttons
|
| ... | ... | @@ -44,7 +44,6 @@ from gajim.gtk.dialogs import DialogButton |
| 44 | 44 |
from gajim.gtk.dialogs import NewConfirmationDialog
|
| 45 | 45 |
from gajim.gtk.dialogs import HigDialog
|
| 46 | 46 |
from gajim.gtk.dialogs import InformationDialog
|
| 47 |
-from gajim.gtk.dialogs import YesNoDialog
|
|
| 48 | 47 |
from gajim.gtk.dialogs import ErrorDialog
|
| 49 | 48 |
from gajim.gtk.filechoosers import FileSaveDialog
|
| 50 | 49 |
from gajim.gtk.filechoosers import FileChooserDialog
|
| ... | ... | @@ -329,14 +328,20 @@ class FileTransfersWindow: |
| 329 | 328 |
file_name = os.path.basename(file_props.file_name)
|
| 330 | 329 |
else:
|
| 331 | 330 |
file_name = file_props.name
|
| 332 |
- YesNoDialog(
|
|
| 333 |
- _('File transfer error'),
|
|
| 334 |
- _('The file %(file)s has been received, but it seems to have '
|
|
| 331 |
+ NewConfirmationDialog(
|
|
| 332 |
+ _('File Transfer Error'),
|
|
| 333 |
+ _('File Transfer Error'),
|
|
| 334 |
+ _('The file %s has been received, but it seems to have '
|
|
| 335 | 335 |
'been damaged along the way.\n'
|
| 336 |
- 'Do you want to download it again?') %
|
|
| 337 |
- {'file': file_name},
|
|
| 338 |
- on_response_yes=(on_yes, jid, file_props, account),
|
|
| 339 |
- type_=Gtk.MessageType.ERROR)
|
|
| 336 |
+ 'Do you want to download it again?') % file_name,
|
|
| 337 |
+ [DialogButton.make('Cancel',
|
|
| 338 |
+ text=_('_No')),
|
|
| 339 |
+ DialogButton.make('OK',
|
|
| 340 |
+ text=_('_Download Again'),
|
|
| 341 |
+ callback=on_yes,
|
|
| 342 |
+ args=[jid,
|
|
| 343 |
+ file_props,
|
|
| 344 |
+ account])]).show()
|
|
| 340 | 345 |
|
| 341 | 346 |
def show_file_send_request(self, account, contact):
|
| 342 | 347 |
send_callback = partial(self.send_file, account, contact)
|
| ... | ... | @@ -98,8 +98,9 @@ from gajim.gtk.notification import Notification |
| 98 | 98 |
from gajim.gtk.dialogs import ErrorDialog
|
| 99 | 99 |
from gajim.gtk.dialogs import WarningDialog
|
| 100 | 100 |
from gajim.gtk.dialogs import InformationDialog
|
| 101 |
+from gajim.gtk.dialogs import NewConfirmationDialog
|
|
| 102 |
+from gajim.gtk.dialogs import DialogButton
|
|
| 101 | 103 |
from gajim.gtk.dialogs import InputDialog
|
| 102 |
-from gajim.gtk.dialogs import YesNoDialog
|
|
| 103 | 104 |
from gajim.gtk.dialogs import PassphraseDialog
|
| 104 | 105 |
from gajim.gtk.dialogs import PlainConnectionDialog
|
| 105 | 106 |
from gajim.gtk.dialogs import SSLErrorDialog
|
| ... | ... | @@ -161,24 +162,35 @@ class Interface: |
| 161 | 162 |
|
| 162 | 163 |
@staticmethod
|
| 163 | 164 |
def handle_event_http_auth(obj):
|
| 164 |
- #('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
|
|
| 165 |
- def response(account, answer):
|
|
| 165 |
+ # ('HTTP_AUTH', account, (method, url, transaction_id, iq_obj, msg))
|
|
| 166 |
+ def _response(account, answer):
|
|
| 166 | 167 |
obj.conn.get_module('HTTPAuth').build_http_auth_answer(
|
| 167 | 168 |
obj.stanza, answer)
|
| 168 | 169 |
|
| 169 |
- def on_yes(is_checked, obj):
|
|
| 170 |
- response(obj, 'yes')
|
|
| 171 |
- |
|
| 172 | 170 |
account = obj.conn.name
|
| 171 |
+ pri_msg = _('HTTP (%(method)s) Authorization '
|
|
| 172 |
+ 'for %(url)s (ID: %(id)s)') % {
|
|
| 173 |
+ 'method': obj.method,
|
|
| 174 |
+ 'url': obj.url,
|
|
| 175 |
+ 'id': obj.iq_id}
|
|
| 173 | 176 |
sec_msg = _('Do you accept this request?')
|
| 174 | 177 |
if app.get_number_of_connected_accounts() > 1:
|
| 175 |
- sec_msg = _('Do you accept this request on account %s?') % account
|
|
| 178 |
+ sec_msg = _('Do you accept this request (account: %s)?') % account
|
|
| 176 | 179 |
if obj.msg:
|
| 177 | 180 |
sec_msg = obj.msg + '\n' + sec_msg
|
| 178 |
- YesNoDialog(_('HTTP (%(method)s) Authorization for '
|
|
| 179 |
- '%(url)s (ID: %(id)s)') % {'method': obj.method, 'url': obj.url,
|
|
| 180 |
- 'id': obj.iq_id}, sec_msg, on_response_yes=(on_yes, obj),
|
|
| 181 |
- on_response_no=(response, obj, 'no'))
|
|
| 181 |
+ |
|
| 182 |
+ NewConfirmationDialog(
|
|
| 183 |
+ _('Authorization'),
|
|
| 184 |
+ pri_msg,
|
|
| 185 |
+ sec_msg,
|
|
| 186 |
+ [DialogButton.make('Cancel',
|
|
| 187 |
+ text=_('_No'),
|
|
| 188 |
+ callback=_response,
|
|
| 189 |
+ args=[obj, 'yes']),
|
|
| 190 |
+ DialogButton.make('OK',
|
|
| 191 |
+ text=_('_Accept'),
|
|
| 192 |
+ callback=_response,
|
|
| 193 |
+ args=[obj, 'no'])]).show()
|
|
| 182 | 194 |
|
| 183 | 195 |
def handle_event_iq_error(self, event):
|
| 184 | 196 |
ctrl = self.msg_win_mgr.get_control(event.properties.jid.getBare(),
|
| ... | ... | @@ -424,16 +436,22 @@ class Interface: |
| 424 | 436 |
% obj.jid)
|
| 425 | 437 |
|
| 426 | 438 |
def show_unsubscribed_dialog(self, account, contact):
|
| 427 |
- def on_yes(is_checked, list_):
|
|
| 439 |
+ def _remove(list_):
|
|
| 428 | 440 |
self.roster.on_req_usub(None, list_)
|
| 429 | 441 |
list_ = [(contact, account)]
|
| 430 |
- YesNoDialog(
|
|
| 431 |
- _('Contact "%s" removed subscription from you') % contact.jid,
|
|
| 432 |
- _('You will always see them as offline.\nDo you want to '
|
|
| 433 |
- 'remove them from your contact list?'),
|
|
| 434 |
- on_response_yes=(on_yes, list_))
|
|
| 435 |
- # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
|
|
| 436 |
- # not show deny
|
|
| 442 |
+ |
|
| 443 |
+ NewConfirmationDialog(
|
|
| 444 |
+ _('Subscription Removed'),
|
|
| 445 |
+ _('Contact \'%s\' removed subscription from you') % contact.jid,
|
|
| 446 |
+ _('You will always see this contact as offline.\n'
|
|
| 447 |
+ 'Do you want to remove them from your contact list?'),
|
|
| 448 |
+ [DialogButton.make('Cancel',
|
|
| 449 |
+ text=_('_No')),
|
|
| 450 |
+ DialogButton.make('Remove',
|
|
| 451 |
+ callback=_remove(list_))]).show()
|
|
| 452 |
+ |
|
| 453 |
+ # FIXME: Per RFC 3921, we can "deny" ack as well, but the GUI does
|
|
| 454 |
+ # not show deny
|
|
| 437 | 455 |
|
| 438 | 456 |
def handle_event_unsubscribed_presence(self, obj):
|
| 439 | 457 |
#('UNSUBSCRIBED', account, jid)
|
| ... | ... | @@ -82,7 +82,6 @@ if is_standalone(): |
| 82 | 82 |
|
| 83 | 83 |
# pylint: disable=C0413
|
| 84 | 84 |
from gajim.common import helpers
|
| 85 |
-from gajim.gtk.dialogs import YesNoDialog
|
|
| 86 | 85 |
from gajim.gtk.dialogs import ErrorDialog
|
| 87 | 86 |
from gajim.gtk.dialogs import NewConfirmationDialog
|
| 88 | 87 |
from gajim.gtk.dialogs import DialogButton
|
| ... | ... | @@ -234,27 +233,27 @@ class HistoryManager: |
| 234 | 233 |
Gtk.main_quit()
|
| 235 | 234 |
return
|
| 236 | 235 |
|
| 237 |
- def on_yes(clicked):
|
|
| 236 |
+ def _on_yes():
|
|
| 238 | 237 |
self.cur.execute('VACUUM')
|
| 239 | 238 |
self.con.commit()
|
| 240 | 239 |
if is_standalone():
|
| 241 | 240 |
Gtk.main_quit()
|
| 242 | 241 |
|
| 243 |
- def on_no():
|
|
| 242 |
+ def _on_no():
|
|
| 244 | 243 |
if is_standalone():
|
| 245 | 244 |
Gtk.main_quit()
|
| 246 | 245 |
|
| 247 |
- dialog = YesNoDialog(
|
|
| 246 |
+ NewConfirmationDialog(
|
|
| 247 |
+ _('Database Cleanup'),
|
|
| 248 | 248 |
_('Do you want to clean up the database? '
|
| 249 | 249 |
'(STRONGLY NOT RECOMMENDED IF GAJIM IS RUNNING)'),
|
| 250 |
- _('Normally allocated database size will not be freed, '
|
|
| 251 |
- 'it will just become reusable. If you really want to reduce '
|
|
| 252 |
- 'database filesize, click YES, else click NO.'
|
|
| 253 |
- '\n\nIn case you click YES, please wait…'),
|
|
| 254 |
- on_response_yes=on_yes, on_response_no=on_no)
|
|
| 255 |
- dialog.set_title(_('Database Cleanup'))
|
|
| 256 |
- button_box = dialog.get_children()[0].get_children()[1]
|
|
| 257 |
- button_box.get_children()[0].grab_focus()
|
|
| 250 |
+ _('Normally, the allocated database size will not be freed, it '
|
|
| 251 |
+ 'will just become reusable. This operation may take a while.'),
|
|
| 252 |
+ [DialogButton.make('Cancel',
|
|
| 253 |
+ callback=_on_no),
|
|
| 254 |
+ DialogButton.make('Remove',
|
|
| 255 |
+ text=_('_Cleanup'),
|
|
| 256 |
+ callback=_on_yes)]).show()
|
|
| 258 | 257 |
|
| 259 | 258 |
def _fill_jids_listview(self):
|
| 260 | 259 |
# get those jids that have at least one entry in logs
|
| ... | ... | @@ -581,7 +580,7 @@ class HistoryManager: |
| 581 | 580 |
if paths_len == 0: # nothing is selected
|
| 582 | 581 |
return
|
| 583 | 582 |
|
| 584 |
- def on_ok(liststore, list_of_paths):
|
|
| 583 |
+ def on_ok():
|
|
| 585 | 584 |
# delete rows from db that match log_line_id
|
| 586 | 585 |
list_of_rowrefs = []
|
| 587 | 586 |
for path in list_of_paths: # make them treerowrefs (it's needed)
|
| ... | ... | @@ -40,7 +40,8 @@ from gajim import message_control |
| 40 | 40 |
from gajim.chat_control_base import ChatControlBase
|
| 41 | 41 |
from gajim.chat_control import ChatControl
|
| 42 | 42 |
|
| 43 |
-from gajim.gtk.dialogs import YesNoDialog
|
|
| 43 |
+from gajim.gtk.dialogs import DialogButton
|
|
| 44 |
+from gajim.gtk.dialogs import NewConfirmationCheckDialog
|
|
| 44 | 45 |
from gajim.gtk.util import get_icon_name
|
| 45 | 46 |
from gajim.gtk.util import resize_window
|
| 46 | 47 |
from gajim.gtk.util import move_window
|
| ... | ... | @@ -231,7 +232,7 @@ class MessageWindow: |
| 231 | 232 |
number_of_closed_control += 1
|
| 232 | 233 |
|
| 233 | 234 |
if number_of_closed_control > 1:
|
| 234 |
- def on_yes1(checked):
|
|
| 235 |
+ def _on_yes1(checked):
|
|
| 235 | 236 |
if checked:
|
| 236 | 237 |
app.config.set('confirm_close_multiple_tabs', False)
|
| 237 | 238 |
self.dont_warn_on_delete = True
|
| ... | ... | @@ -246,11 +247,16 @@ class MessageWindow: |
| 246 | 247 |
ctrl.minimize()
|
| 247 | 248 |
# destroy window
|
| 248 | 249 |
return False
|
| 249 |
- YesNoDialog(
|
|
| 250 |
+ |
|
| 251 |
+ NewConfirmationCheckDialog(
|
|
| 252 |
+ _('Close Tabs'),
|
|
| 250 | 253 |
_('You are going to close several tabs'),
|
| 251 | 254 |
_('Do you really want to close them all?'),
|
| 252 |
- checktext=_('_Do not ask me again'), on_response_yes=on_yes1,
|
|
| 253 |
- transient_for=self.window)
|
|
| 255 |
+ _('_Do not ask me again'),
|
|
| 256 |
+ [DialogButton.make('Cancel'),
|
|
| 257 |
+ DialogButton.make('OK',
|
|
| 258 |
+ callback=_on_yes1)],
|
|
| 259 |
+ transient_for=self.window).show()
|
|
| 254 | 260 |
return True
|
| 255 | 261 |
|
| 256 | 262 |
def on_yes(ctrl):
|
| ... | ... | @@ -39,7 +39,8 @@ from gajim.plugins.helpers import GajimPluginActivateException |
| 39 | 39 |
from gajim.plugins.plugins_i18n import _
|
| 40 | 40 |
|
| 41 | 41 |
from gajim.gtk.dialogs import WarningDialog
|
| 42 |
-from gajim.gtk.dialogs import YesNoDialog
|
|
| 42 |
+from gajim.gtk.dialogs import DialogButton
|
|
| 43 |
+from gajim.gtk.dialogs import NewConfirmationDialog
|
|
| 43 | 44 |
from gajim.gtk.filechoosers import ArchiveChooserDialog
|
| 44 | 45 |
from gajim.gtk.util import get_builder
|
| 45 | 46 |
from gajim.gtk.util import load_icon
|
| ... | ... | @@ -260,9 +261,9 @@ class PluginsWindow: |
| 260 | 261 |
dialog.popup()
|
| 261 | 262 |
|
| 262 | 263 |
def _on_plugin_exists(zip_filename):
|
| 263 |
- def on_yes(is_checked):
|
|
| 264 |
+ def _on_yes():
|
|
| 264 | 265 |
plugin = app.plugin_manager.install_from_zip(zip_filename,
|
| 265 |
- True)
|
|
| 266 |
+ True)
|
|
| 266 | 267 |
if not plugin:
|
| 267 | 268 |
show_warn_dialog()
|
| 268 | 269 |
return
|
| ... | ... | @@ -273,13 +274,24 @@ class PluginsWindow: |
| 273 | 274 |
model.remove(row.iter)
|
| 274 | 275 |
break
|
| 275 | 276 |
|
| 276 |
- iter_ = model.append([plugin, plugin.name, False,
|
|
| 277 |
- plugin.activatable, self.get_plugin_icon(plugin)])
|
|
| 277 |
+ iter_ = model.append([
|
|
| 278 |
+ plugin,
|
|
| 279 |
+ plugin.name,
|
|
| 280 |
+ False,
|
|
| 281 |
+ plugin.activatable,
|
|
| 282 |
+ self.get_plugin_icon(plugin)])
|
|
| 278 | 283 |
sel = self.installed_plugins_treeview.get_selection()
|
| 279 | 284 |
sel.select_iter(iter_)
|
| 280 | 285 |
|
| 281 |
- YesNoDialog(_('Plugin already exists'), sectext=_('Overwrite?'),
|
|
| 282 |
- on_response_yes=on_yes, transient_for=self.window)
|
|
| 286 |
+ NewConfirmationDialog(
|
|
| 287 |
+ _('Overwrite Plugin?'),
|
|
| 288 |
+ _('Plugin already exists'),
|
|
| 289 |
+ _('Do you want to overwrite the currently installed version?'),
|
|
| 290 |
+ [DialogButton.make('Cancel'),
|
|
| 291 |
+ DialogButton.make('Remove',
|
|
| 292 |
+ text=_('_Overwrite'),
|
|
| 293 |
+ callback=_on_yes)],
|
|
| 294 |
+ transient_for=self.window).show()
|
|
| 283 | 295 |
|
| 284 | 296 |
def _try_install(zip_filename):
|
| 285 | 297 |
try:
|