~usb-creator-hackers/usb-creator/trunk

« back to all changes in this revision

Viewing changes to usbcreator/frontends/kde/frontend.py

  • Committer: Benjamin Drung
  • Date: 2022-11-02 13:01:26 UTC
  • Revision ID: benjamin.drung@canonical.com-20221102130126-4z0xyivy5f37dp13
Move to https://code.launchpad.net/~usb-creator-hackers/usb-creator/+git/main

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
 
3
 
# Copyright (C) 2009 Roderick B. Greening <roderick.greening@gmail.com>
4
 
# Copyright (C) 2014 Harald Sitter <apachelogger@kubuntu.org>
5
 
#
6
 
# Based in part on work by:
7
 
#  David Edmundson <kde@davidedmundson.co.uk>
8
 
#  Canonical Ltd. USB Creator Team
9
 
#
10
 
# This program is free software: you can redistribute it and/or modify
11
 
# it under the terms of the GNU General Public License version 3,
12
 
# as published by the Free Software Foundation.
13
 
#
14
 
# This program is distributed in the hope that it will be useful,
15
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 
# GNU General Public License for more details.
18
 
#
19
 
# You should have received a copy of the GNU General Public License
20
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 
 
22
 
# TODO: When pykf5 becomes available qmessagebox should be ported back to kmessagebox
23
 
 
24
 
import sys
25
 
import os
26
 
 
27
 
from PyQt5 import uic
28
 
from PyQt5.QtCore import *
29
 
from PyQt5.QtGui import *
30
 
from PyQt5.QtWidgets import *
31
 
 
32
 
from usbcreator.frontends.kde.translate import translate
33
 
uic.properties.Properties._string = translate
34
 
import gettext
35
 
import logging
36
 
 
37
 
from usbcreator.frontends.base import Frontend
38
 
from usbcreator import misc
39
 
try:
40
 
    from queue import Queue
41
 
except ImportError:
42
 
    from Queue import Queue
43
 
 
44
 
queue = Queue()
45
 
def thread_wrap(func):
46
 
    '''Decorator for functions that will be called by another thread.'''
47
 
    def wrap(*args):
48
 
        queue.put(lambda: func(*args))
49
 
    return wrap
50
 
 
51
 
class KdeFrontend(Frontend, QObject):
52
 
    @classmethod
53
 
    def startup_failure(cls, message):
54
 
        #KMessageBox.error(None, message)
55
 
        QMessageBox.critical(None, '', message)
56
 
        sys.exit(1)
57
 
 
58
 
    @classmethod
59
 
    def DBusMainLoop(cls):
60
 
        from dbus.mainloop.qt import DBusQtMainLoop
61
 
        DBusQtMainLoop(set_as_default=True)
62
 
 
63
 
    def __init__(self, backend, img=None,
64
 
                 allow_system_internal=False):
65
 
        QObject.__init__(self)
66
 
 
67
 
        #our passed vars - keep them private
68
 
        self.__img = img
69
 
        self.__allow_system_internal = allow_system_internal
70
 
 
71
 
        # Perform some initialization
72
 
        self.__initPrivateVars()
73
 
        self.__initUI()
74
 
 
75
 
        #enable the backend
76
 
        self.__backend = backend
77
 
 
78
 
        # Connect to backend signals.
79
 
        self.__backend.source_added_cb = self.add_source
80
 
        self.__backend.target_added_cb = self.add_target
81
 
        self.__backend.source_removed_cb = self.remove_source
82
 
        self.__backend.target_removed_cb = self.remove_target
83
 
        self.__backend.failure_cb = self.failure
84
 
        self.__backend.success_cb = self.success
85
 
        self.__backend.install_progress_cb = self.progress
86
 
        self.__backend.install_progress_message_cb = self.progress_message
87
 
        self.__backend.retry_cb = self.retry
88
 
        self.__backend.target_changed_cb = self.update_target
89
 
 
90
 
        #add any file sources passed
91
 
        if self.__img is not None:
92
 
            self.__backend.add_image(misc.text_type(self.__img))
93
 
 
94
 
        downloadsDir = QDir(QStandardPaths.standardLocations(QStandardPaths.DownloadLocation)[0])
95
 
        isoFilter = []
96
 
        isoFilter.append("*.iso")
97
 
        for iso in downloadsDir.entryList(isoFilter, QDir.Files):
98
 
            self.__backend.add_image(misc.text_type(downloadsDir.absoluteFilePath(iso)))
99
 
 
100
 
        def test_func(*a):
101
 
            if not queue.empty():
102
 
                func = queue.get_nowait()
103
 
                func()
104
 
                queue.task_done()
105
 
            return True
106
 
        self.queue_processor = self.add_timeout(500, test_func, None)
107
 
 
108
 
        self.__backend.detect_devices()
109
 
 
110
 
        self.update_loop = self.add_timeout(2000, self.__backend.update_free)
111
 
 
112
 
    def __initPrivateVars(self):
113
 
        """Initialize Private Variables"""
114
 
 
115
 
        # main window
116
 
        self.__mainWindow = QDialog()
117
 
 
118
 
        # ui file
119
 
        self.__mainWindow_ui = "usbcreator-kde.ui"
120
 
 
121
 
        # init Backend to None - easier to debug...
122
 
        self.__backend = None
123
 
 
124
 
        # Set by add_file_source_dialog, used to auto-select a manually added
125
 
        # image.
126
 
        self.__recently_added_image = None
127
 
 
128
 
    def __initUI(self):
129
 
        """Initialize the interface"""
130
 
 
131
 
        # Locate the ui for the main window and load it.
132
 
        if 'USBCREATOR_LOCAL' in os.environ:
133
 
            appdir = os.path.join(os.getcwd(), 'gui')
134
 
        else:
135
 
            file = QStandardPaths.locate(QStandardPaths.DataLocation, self.__mainWindow_ui)
136
 
            appdir = file[:file.rfind("/")]
137
 
        uic.loadUi(misc.text_type(appdir + "/" + self.__mainWindow_ui), self.__mainWindow)
138
 
 
139
 
        #hide sources if an argument was provided
140
 
        if self.__img != '':
141
 
            self.__mainWindow.ui_source_list.hide()
142
 
            self.__mainWindow.ui_add_source.hide()
143
 
            self.__mainWindow.source_label.hide()
144
 
 
145
 
        #disable the start button by default
146
 
        self.__mainWindow.ui_start_button.setEnabled(False)
147
 
 
148
 
        #add some buttons
149
 
        self.__mainWindow.ui_quit_button.setIcon(QIcon.fromTheme("application-exit"))
150
 
        self.__mainWindow.ui_start_button.setIcon(QIcon.fromTheme("dialog-ok-apply"))
151
 
        self.__mainWindow.ui_add_source.setIcon(QIcon.fromTheme("media-optical"))
152
 
 
153
 
        #set up signals
154
 
        self.__mainWindow.ui_add_source.clicked.connect(self.add_file_source_dialog)
155
 
        self.__mainWindow.ui_quit_button.clicked.connect(self.quit)
156
 
        self.__mainWindow.ui_start_button.clicked.connect(self.install)
157
 
        self.__mainWindow.ui_dest_list.currentItemChanged.connect(self.dest_selection_changed)
158
 
        self.__mainWindow.ui_source_list.currentItemChanged.connect(self.source_selection_changed)
159
 
 
160
 
        # FIXME: we need a custom delegate and elide the iso column on the left rather than the right
161
 
        #        otherwise long paths will take up the entire space while in fact the image name is the useful bit of information 90% of the time
162
 
 
163
 
        self.__mainWindow.ui_source_list.setSortingEnabled(True)
164
 
        self.__mainWindow.ui_source_list.sortByColumn(0, Qt.AscendingOrder)
165
 
        # Last column dictates width.
166
 
        # Size column gets fixed to whatever the contents is. Since this is
167
 
        # formatted size a la '1 TiB' it pretty much stays within a certain
168
 
        # margin of width.
169
 
        # Image and Version columns are scaled respectively to maximize space
170
 
        # usage.
171
 
        # Neither colum is resizable by the user, so additional tooltips are
172
 
        # enabled for the widgetitems (see add_source).
173
 
        self.__mainWindow.ui_source_list.header().setSectionResizeMode(0, QHeaderView.Stretch)
174
 
        self.__mainWindow.ui_source_list.header().setSectionResizeMode(1, QHeaderView.Stretch)
175
 
        self.__mainWindow.ui_source_list.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
176
 
        self.__mainWindow.ui_dest_list.setSortingEnabled(True)
177
 
        self.__mainWindow.ui_dest_list.sortByColumn(0, Qt.AscendingOrder)
178
 
        # For destinations only stretch the device column.
179
 
        self.__mainWindow.ui_dest_list.header().setSectionResizeMode(0, QHeaderView.Stretch)
180
 
        self.__mainWindow.ui_dest_list.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
181
 
        self.__mainWindow.ui_dest_list.header().setSectionResizeMode(2, QHeaderView.ResizeToContents)
182
 
 
183
 
        #show the window
184
 
        self.__mainWindow.show()
185
 
 
186
 
    def __timeout_callback(self, func, *args):
187
 
        '''Private callback wrapper used by add_timeout'''
188
 
 
189
 
        timer = self.sender()
190
 
        active = func(*args)
191
 
        if not active:
192
 
            timer.stop()
193
 
 
194
 
    def __fail(self, message=None):
195
 
        '''Handle Failed Install Gracefully'''
196
 
 
197
 
        logging.exception('Installation failed.')
198
 
        self.progress_bar.hide()
199
 
        if not message:
200
 
            message = _('Installation failed.')
201
 
        #KMessageBox.error(self.__mainWindow, message)
202
 
        QMessageBox.critical(self.__mainWindow, '', message)
203
 
        sys.exit(1)
204
 
 
205
 
    def add_timeout(self, interval, func, *args):
206
 
        '''Add a new timer for function 'func' with optional arguments. Mirrors a
207
 
        similar gobject call timeout_add.'''
208
 
 
209
 
        # FIXME: now that we are part of a Qt object, we may be able to alter for builtin timers
210
 
        timer = QTimer()
211
 
        timer.timeout.connect(lambda: self.__timeout_callback(func, *args))
212
 
        timer.start(interval)
213
 
 
214
 
        return timer
215
 
 
216
 
    def delete_timeout(self, timer):
217
 
        '''Remove the specified timer'''
218
 
 
219
 
        if timer.isActive():
220
 
            return False
221
 
        timer.stop()
222
 
        return True
223
 
 
224
 
    def add_target(self, target):
225
 
        logging.debug('add_target: %s' % misc.text_type(target))
226
 
        new_item = QTreeWidgetItem(self.__mainWindow.ui_dest_list)
227
 
        new_item.setData(0,Qt.UserRole,target)
228
 
        # FIXME:
229
 
        # the new_item lines should be auto triggered onChange to the
230
 
        # TreeWidget when new_item is appended.
231
 
        new_item.setText(0,target)
232
 
        new_item.setIcon(0,QIcon.fromTheme("drive-removable-media-usb-pendrive"))
233
 
 
234
 
        item = self.__mainWindow.ui_dest_list.currentItem()
235
 
        if not item:
236
 
            item = self.__mainWindow.ui_dest_list.topLevelItem(0)
237
 
            if item:
238
 
                self.__mainWindow.ui_dest_list.setCurrentItem(item,True)
239
 
 
240
 
        # populate from device data
241
 
        if self.__backend is not None:
242
 
            dev = self.__backend.targets[target]
243
 
            pretty_name = "%s %s (%s)" % (dev['vendor'], dev['model'], dev['device'])
244
 
            new_item.setText(0,pretty_name)
245
 
            new_item.setToolTip(0, new_item.text(0))
246
 
            new_item.setText(1,dev['label'])
247
 
            new_item.setToolTip(1, new_item.text(1))
248
 
            new_item.setText(2,misc.format_size(dev['capacity']))
249
 
            new_item.setToolTip(2, new_item.text(2))
250
 
 
251
 
    def remove_target(self, target):
252
 
        for i in range(0,self.__mainWindow.ui_dest_list.topLevelItemCount()):
253
 
            item = self.__mainWindow.ui_dest_list.topLevelItem(i)
254
 
            if item.data(0,Qt.UserRole) == target:
255
 
                self.__mainWindow.ui_dest_list.takeTopLevelItem(i)
256
 
                break
257
 
 
258
 
        if not self.__mainWindow.ui_dest_list.currentItem():
259
 
            item = self.__mainWindow.ui_dest_list.topLevelItem(0)
260
 
            if item:
261
 
                self.__mainWindow.ui_dest_list.setCurrentItem(item,True)
262
 
 
263
 
    def add_source(self, source):
264
 
        logging.debug('add_source: %s' % misc.text_type(source))
265
 
        new_item = QTreeWidgetItem(self.__mainWindow.ui_source_list)
266
 
        new_item.setData(0,Qt.UserRole,source)
267
 
        # FIXME:
268
 
        # the new_item lines should be auto triggered onChange to the TreeWidget
269
 
        # when new_item is appended.
270
 
        new_item.setText(0,source)
271
 
        new_item.setIcon(0,QIcon.fromTheme("media-optical"))
272
 
 
273
 
        item = self.__mainWindow.ui_source_list.currentItem()
274
 
        if not item:
275
 
            item = self.__mainWindow.ui_source_list.topLevelItem(0)
276
 
            if item:
277
 
                self.__mainWindow.ui_source_list.setCurrentItem(item,True)
278
 
 
279
 
        # how does this all get added? here or elsewhere...
280
 
        # populate from device data
281
 
        if self.__backend is not None:
282
 
            new_item.setText(0,self.__backend.sources[source]['device'])
283
 
            new_item.setToolTip(0, new_item.text(0))
284
 
            # Strip as some derivates like to have whitespaces/newlines (e.g. netrunner)
285
 
            new_item.setText(1,self.__backend.sources[source]['label'].strip())
286
 
            new_item.setToolTip(1, new_item.text(1))
287
 
            new_item.setText(2,misc.format_size(self.__backend.sources[source]['size']))
288
 
            new_item.setToolTip(2, new_item.text(2))
289
 
 
290
 
        # Iff the new_item was recently added by add_file_source_dialog,
291
 
        # make it the current item.
292
 
        if (self.__recently_added_image != None and
293
 
            self.__backend.sources[source]['device'] == self.__recently_added_image):
294
 
            self.__mainWindow.ui_source_list.setCurrentItem(new_item,True)
295
 
            self.__recently_added_image = None
296
 
 
297
 
    def remove_source(self, source):
298
 
        for i in range(0,self.__mainWindow.ui_source_list.topLevelItemCount()):
299
 
            item = self.__mainWindow.ui_source_list.topLevelItem(i)
300
 
            if item.data(0,Qt.UserRole) == source:
301
 
                self.__mainWindow.ui_source_list.removeItemWidget(item,0)
302
 
                break
303
 
 
304
 
        if not self.__mainWindow.ui_source_list.currentItem():
305
 
            item = self.__mainWindow.ui_source_list.topLevelItem(0)
306
 
            if item:
307
 
                self.__mainWindow.ui_source_list.setCurrentItem(item,True)
308
 
 
309
 
    def get_source(self):
310
 
        '''Returns the UDI of the selected source image.'''
311
 
        item = self.__mainWindow.ui_source_list.currentItem()
312
 
        if item:
313
 
            # Must deal in unicode and not QString for backend
314
 
            source = misc.text_type(item.data(0,Qt.UserRole))
315
 
            return source
316
 
        else:
317
 
            logging.debug('No source selected.')
318
 
            return ''
319
 
 
320
 
    def get_target(self):
321
 
        '''Returns the UDI of the selected target disk or partition.'''
322
 
        item = self.__mainWindow.ui_dest_list.currentItem()
323
 
        if item:
324
 
            # Must deal in unicode and not QString for backend
325
 
            dest = misc.text_type(item.data(0,Qt.UserRole))
326
 
            return dest
327
 
        else:
328
 
            logging.debug('No target selected.')
329
 
            return ''
330
 
 
331
 
    def update_target(self, udi):
332
 
        for i in range(0,self.__mainWindow.ui_dest_list.topLevelItemCount()):
333
 
            item = self.__mainWindow.ui_dest_list.topLevelItem(i)
334
 
            if misc.text_type(item.data(0,Qt.UserRole)) == udi:
335
 
                # FIXME: pyqt5 entirely bypasses qt's signals and apparently fails
336
 
                #        to do so correctly so the following yields an error
337
 
                #        even though it should work just fine (i.e. the signal exists
338
 
                #        on the cpp side...
339
 
                self.__mainWindow.ui_dest_list.itemChanged.emit(item, 0)
340
 
                break
341
 
        target = self.__backend.targets[udi]
342
 
        # Update install button state.
343
 
        status = target['status']
344
 
        source = self.__backend.get_current_source()
345
 
        if not source:
346
 
            return
347
 
        stype = self.__backend.sources[source]['type']
348
 
        if (status == misc.CAN_USE or
349
 
         (self.__mainWindow.ui_start_button.isEnabled() and stype == misc.SOURCE_IMG)):
350
 
            self.__mainWindow.ui_start_button.setEnabled(True)
351
 
        else:
352
 
            self.__mainWindow.ui_start_button.setEnabled(False)
353
 
        # Update the destination status message.
354
 
        if status == misc.CANNOT_USE:
355
 
            msg = _('The device is not large enough to hold this image.')
356
 
        else:
357
 
            msg = ''
358
 
        self.__mainWindow.ui_dest_status.setText(msg)
359
 
 
360
 
    def source_selection_changed(self, current_item, prev_item):
361
 
        '''The selected image has changed we need to refresh targets'''
362
 
        if not self.__backend:
363
 
            return
364
 
        if current_item is not None:
365
 
            udi = misc.text_type(current_item.data(0,Qt.UserRole))
366
 
        else:
367
 
            udi = None
368
 
        self.__backend.set_current_source(udi)
369
 
        item = self.__mainWindow.ui_dest_list.currentItem()
370
 
        self.dest_selection_changed(item, None)
371
 
 
372
 
    def dest_selection_changed(self, current_item, prev_item):
373
 
        if not self.__backend:
374
 
            return
375
 
 
376
 
        if current_item is None:
377
 
            return
378
 
 
379
 
        udi = misc.text_type(current_item.data(0,Qt.UserRole))
380
 
        self.update_target(udi)
381
 
 
382
 
    def add_file_source_dialog(self):
383
 
        self.__recently_added_image = None
384
 
        filename = ''
385
 
        # This here filter is for kfiledialog, no clue if it will ever make a return
386
 
        #filter = '*.iso|' + _('CD Images') + '\n*.img|' + _('Disk Images')
387
 
        filter = _('CD Images') + '(*.iso)' + ';;' + _('Disk Images') + '(*.img)'
388
 
 
389
 
        downloadPath = QStandardPaths.standardLocations(QStandardPaths.DownloadLocation)[0]
390
 
        openFileName = QFileDialog.getOpenFileName(self.__mainWindow,
391
 
                                                   '',
392
 
                                                   downloadPath,
393
 
                                                   filter)
394
 
        if openFileName:
395
 
            openFileName = openFileName[0]
396
 
        filename = misc.text_type(openFileName)
397
 
 
398
 
        if not filename:
399
 
            return
400
 
 
401
 
        # If the file is already in the model, simply mark it as selected.
402
 
        for i in range(0, self.__mainWindow.ui_source_list.topLevelItemCount()):
403
 
            item = self.__mainWindow.ui_source_list.topLevelItem(i)
404
 
            if item.text(0) == filename:
405
 
                self.__mainWindow.ui_source_list.setCurrentItem(item, True)
406
 
                return
407
 
        # Otherwise add it.
408
 
        self.__recently_added_image = filename
409
 
        self.__backend.add_image(filename)
410
 
 
411
 
    def install(self):
412
 
        source = self.get_source()
413
 
        target = self.get_target()
414
 
        if (source and target):
415
 
            msgbox = QMessageBox(self.__mainWindow)
416
 
            msgbox.setIcon(QMessageBox.Warning)
417
 
            msgbox.setText(_('Are you sure you want to write the disc image to the device?'))
418
 
            msgbox.setInformativeText(_('All existing data will be lost.'))
419
 
            msgbox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
420
 
            res = msgbox.exec_()
421
 
            if res != QMessageBox.Yes:
422
 
                return
423
 
 
424
 
            self.__mainWindow.hide()
425
 
            self.delete_timeout(self.update_loop)
426
 
 
427
 
            self.progress_bar = QProgressDialog("",_('Cancel'),0,100,self.__mainWindow)
428
 
            #set title of progress window (same as gtk frontend)
429
 
            self.progress_bar.setWindowTitle(_('Installing'))
430
 
            #prevent progress bar from emitting reset on reaching max value (and auto closing)
431
 
            self.progress_bar.setAutoReset(False)
432
 
            #force immediate showing, rather than waiting...
433
 
            self.progress_bar.setMinimumDuration(0)
434
 
            #must disconnect the canceled() SIGNAL, otherwise the progress bar is actually destroyed
435
 
            self.progress_bar.canceled.disconnect(self.progress_bar.cancel)
436
 
            #now we connect our own signal to display a warning dialog instead
437
 
            self.progress_bar.canceled.connect(self.warning_dialog)
438
 
            starting_up = _('Starting up')
439
 
            self.progress_bar.setLabelText(starting_up)
440
 
            self.progress_bar.show()
441
 
            try:
442
 
                self.__backend.install(source, target,
443
 
                                       allow_system_internal=self.__allow_system_internal)
444
 
            except:
445
 
                self.__fail()
446
 
        else:
447
 
            message = _('You must select both source image and target device first.')
448
 
            self.notify(message)
449
 
 
450
 
    @thread_wrap
451
 
    def progress(self, complete):
452
 
        # Updating value cause dialog to re-appear from hidden (dunno why)
453
 
        if not self.progress_bar.isHidden():
454
 
            if complete > 100:
455
 
                complete = 100
456
 
            self.progress_bar.setValue(int(complete))
457
 
            if complete == 100:
458
 
                self.progress_bar.setLabelText(_('Finishing...'))
459
 
 
460
 
    @thread_wrap
461
 
    def progress_message(self, message):
462
 
        self.progress_bar.setLabelText(message)
463
 
 
464
 
    def quit(self, *args):
465
 
        self.__backend.cancel_install()
466
 
        sys.exit(0)
467
 
 
468
 
    @thread_wrap
469
 
    def failure(self, message=None):
470
 
        '''Install failed'''
471
 
        self.__fail(message)
472
 
 
473
 
    @thread_wrap
474
 
    def success(self):
475
 
        '''Install completed'''
476
 
        self.progress_bar.hide()
477
 
        text = _('The installation is complete.  You may now reboot your '
478
 
                 'computer with this device inserted to try or install '
479
 
                 'Ubuntu.')
480
 
 
481
 
        QMessageBox.information(self.__mainWindow, '', text)
482
 
        self.__backend.shutdown()
483
 
        sys.exit(0)
484
 
 
485
 
    @thread_wrap
486
 
    def retry(self, message):
487
 
        '''A retry dialog'''
488
 
 
489
 
        caption = _('Retry?')
490
 
 
491
 
        #res = KMessageBox.warningYesNo(self.__mainWindow,message,caption)
492
 
        res = QMessageBox.warning(self.__mainWindow, caption, message,
493
 
                                  QMessageBox.Yes, QMessageBox.No)
494
 
 
495
 
        #return res == KMessageBox.Yes
496
 
        return res == QMessageBox.Yes
497
 
 
498
 
    def notify(self,title):
499
 
        #KMessageBox.sorry(self.__mainWindow,title)
500
 
        QMessageBox.warning(self.__mainWindow, '', title)
501
 
 
502
 
    def warning_dialog(self):
503
 
        '''A warning dialog to show when progress dialog cancel is pressed'''
504
 
 
505
 
        caption = _('Quit the installation?')
506
 
        text = _('Do you really want to quit the installation now?')
507
 
 
508
 
        #hide the progress bar - install will still continue in bg
509
 
        self.progress_bar.hide()
510
 
 
511
 
        #res = KMessageBox.warningYesNo(self.__mainWindow,text,caption)
512
 
        res = QMessageBox.warning(self.__mainWindow, caption, text,
513
 
                                  QMessageBox.Yes, QMessageBox.No)
514
 
 
515
 
        #if res == KMessageBox.Yes:
516
 
        if res == QMessageBox.Yes:
517
 
            self.quit()
518
 
 
519
 
        #user chose not to quit, so re-show progress bar
520
 
        self.progress_bar.show()
521
 
 
522
 
    def format_dest_clicked(self):
523
 
        return
524