~ubuntu-branches/ubuntu/maverick/usb-creator/maverick

« back to all changes in this revision

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

  • Committer: Bazaar Package Importer
  • Author(s): Evan Dandrea, Evan Dandrea, Roderick B. Greening
  • Date: 2009-08-26 21:16:18 UTC
  • Revision ID: james.westby@ubuntu.com-20090826211618-d1p2wkg661375fz9
Tags: 0.2.3
[ Evan Dandrea ]
* Depend on python-qt4-dbus.  Thanks Daniel T. Chen (LP: #404553).
* New KDE icon.  Thanks Jonathan Riddell and Ken Wimer!
* Massively cleaned up the structure of the usb-creator code.
* Replaced the HAL backend with a DeviceKit-disks backend.
* Added a Windows frontend and backend (built outside the archive).
* Manage the install routine and progress feedback in separate threads,
  rather than a separate process.
* Replace dependency on parted and mtools with devicekit-disks.

[ Roderick B. Greening ]
* Update ui file name for KDE.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008-2009 Canonical Ltd.
 
2
 
 
3
# This program is free software: you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License version 3,
 
5
# as published by the Free Software Foundation.
 
6
#
 
7
# This program is distributed in the hope that it will be useful,
 
8
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
10
# GNU General Public License for more details.
 
11
#
 
12
# You should have received a copy of the GNU General Public License
 
13
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
14
 
 
15
import subprocess, sys
 
16
import os
 
17
 
 
18
import gettext
 
19
import locale
 
20
import pygtk
 
21
import gobject
 
22
import gtk
 
23
import gnomevfs
 
24
import logging
 
25
 
 
26
from usbcreator.frontends.base import Frontend
 
27
from usbcreator.misc import *
 
28
 
 
29
if 'USBCREATOR_LOCAL' in os.environ:
 
30
    ui_path = os.path.join(os.getcwd(), 'gui/usbcreator-gtk.ui')
 
31
    icon_path = os.path.join(os.getcwd(), 'desktop/usb-creator-gtk.png')
 
32
else:
 
33
    ui_path = '/usr/share/usb-creator/usbcreator-gtk.ui'
 
34
    icon_path = '/usr/share/pixmaps/usb-creator-gtk.png'
 
35
 
 
36
gtk.gdk.threads_init()
 
37
 
 
38
def thread_wrap(func):
 
39
    '''Decorator for functions that will be called by another thread.'''
 
40
    def wrap(*args):
 
41
        gtk.gdk.threads_enter()
 
42
        try:
 
43
            func(*args)
 
44
        finally:
 
45
            gtk.gdk.threads_leave()
 
46
    return wrap
 
47
 
 
48
class GtkFrontend(Frontend):
 
49
    @classmethod
 
50
    def startup_failure(cls, message):
 
51
        dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR,
 
52
            gtk.BUTTONS_CLOSE, message)
 
53
        dialog.run()
 
54
        dialog.destroy()
 
55
        
 
56
    @classmethod
 
57
    def DBusMainLoop(cls):
 
58
        from dbus.mainloop.glib import DBusGMainLoop
 
59
        DBusGMainLoop(set_as_default=True)
 
60
 
 
61
    def __init__(self, backend, img=None, persistent=True):
 
62
 
 
63
        self.all_widgets = set()
 
64
 
 
65
        self.builder = gtk.Builder()
 
66
        self.builder.set_translation_domain('usbcreator')
 
67
        self.builder.add_from_file(ui_path)
 
68
 
 
69
        for widget in self.builder.get_objects():
 
70
            # Taken from ubiquity:
 
71
            # We generally want labels to be selectable so that people can
 
72
            # easily report problems in them
 
73
            # (https://launchpad.net/bugs/41618), but GTK+ likes to put
 
74
            # selectable labels in the focus chain, and I can't seem to turn
 
75
            # this off in glade and have it stick. Accordingly, make sure
 
76
            # labels are unfocusable here.
 
77
            if isinstance(widget, gtk.Label):
 
78
                widget.set_property('can-focus', False)
 
79
            if issubclass(type(widget), gtk.Widget):
 
80
                self.all_widgets.add(widget)
 
81
                setattr(self, gtk.Widget.get_name(widget), widget)
 
82
 
 
83
        gtk.window_set_default_icon_from_file(icon_path)
 
84
        self.builder.connect_signals (self, None)
 
85
        self.cancelbutton.connect('clicked', lambda x: self.warning_dialog.hide())
 
86
        self.exitbutton.connect('clicked', lambda x: self.quit())
 
87
        self.progress_cancel_button.connect('clicked', lambda x: self.warning_dialog.show())
 
88
        def format_value(scale, value):
 
89
            return format_mb_size(value)
 
90
        self.persist_value.set_adjustment(
 
91
            gtk.Adjustment(0, 0, 100, 1, 10, 0))
 
92
        self.persist_value.connect('format-value', format_value)
 
93
        # TODO evand 2009-08-26: DevKit isn't happy with our format code at the moment.
 
94
        self.format_dest.set_sensitive(False)
 
95
 
 
96
        # Connect to backend signals.
 
97
        self.backend = backend
 
98
        self.backend.source_added_cb = self.add_source
 
99
        self.backend.target_added_cb = self.add_target
 
100
        self.backend.source_removed_cb = self.remove_source
 
101
        self.backend.target_removed_cb = self.remove_target
 
102
        self.backend.failure_cb = self.failure
 
103
        self.backend.success_cb = self.success
 
104
        self.backend.install_progress_cb = self.progress
 
105
        self.backend.install_progress_message_cb = self.progress_message
 
106
        self.backend.retry_cb = self.retry
 
107
 
 
108
        self.setup_sources_treeview()
 
109
        self.setup_targets_treeview()
 
110
        self.persist_vbox.set_sensitive(False)
 
111
        self.window.show()
 
112
        #selection = self.source_treeview.get_selection()
 
113
        #selection.connect('changed', lambda x: self.backend.refresh_targets())
 
114
        #selection = self.dest_treeview.get_selection()
 
115
        #selection.connect('changed', self.dest_selection_changed)
 
116
        
 
117
        if img is not None:
 
118
            self.backend.add_image(img)
 
119
        
 
120
        if not persistent:
 
121
            self.persist_disabled.set_active(True)
 
122
        self.backend.detect_devices()
 
123
 
 
124
        gtk.gdk.threads_enter()
 
125
        gtk.main()
 
126
        gtk.gdk.threads_leave()
 
127
 
 
128
    def add_timeout(self, interval, func, *args):
 
129
        '''Add a new timer for function 'func' with optional arguments. Wraps a
 
130
        similar gobject call timeout_add.'''
 
131
 
 
132
        timer = gobject.timeout_add(interval, func, *args)
 
133
 
 
134
        return timer
 
135
    def delete_timeout(self, timer):
 
136
        '''Remove the specified timer. Wraps gobject source_remove call.'''
 
137
 
 
138
        return gobject.source_remove(timer)
 
139
 
 
140
    def add_source(self, source):
 
141
        logging.debug('add_source: %s' % str(source))
 
142
        model = self.source_treeview.get_model()
 
143
        model.append([source])
 
144
 
 
145
        sel = self.source_treeview.get_selection()
 
146
        m, i = sel.get_selected()
 
147
        if not i:
 
148
            sel.select_path(0)
 
149
    
 
150
    def add_target(self, target):
 
151
        logging.debug('add_target: %s' % str(target))
 
152
        model = self.dest_treeview.get_model()
 
153
        model.append([target])
 
154
 
 
155
        sel = self.dest_treeview.get_selection()
 
156
        m, i = sel.get_selected()
 
157
        if not i:
 
158
            sel.select_path(0)
 
159
 
 
160
    def remove_source(self, source):
 
161
        model = self.source_treeview.get_model()
 
162
        iterator = model.get_iter_first()
 
163
        to_delete = None
 
164
        while iterator is not None:
 
165
            if model.get_value(iterator, 0) == source:
 
166
                to_delete = iterator
 
167
            iterator = model.iter_next(iterator)
 
168
        if to_delete is not None:
 
169
            model.remove(to_delete)
 
170
        
 
171
        sel = self.source_treeview.get_selection()
 
172
        m, i = sel.get_selected()
 
173
        if not i:
 
174
            sel.select_path(0)
 
175
 
 
176
    def remove_target(self, target):
 
177
        model = self.dest_treeview.get_model()
 
178
        iterator = model.get_iter_first()
 
179
        to_delete = None
 
180
        while iterator is not None:
 
181
            if model.get_value(iterator, 0) == target:
 
182
                to_delete = iterator
 
183
            iterator = model.iter_next(iterator)
 
184
        if to_delete is not None:
 
185
            model.remove(to_delete)
 
186
        
 
187
        sel = self.dest_treeview.get_selection()
 
188
        m, i = sel.get_selected()
 
189
        if not i:
 
190
            sel.select_path(0)
 
191
 
 
192
    def get_source(self):
 
193
        '''Returns the UDI of the selected source image.'''
 
194
        sel = self.source_treeview.get_selection()
 
195
        m, i = sel.get_selected()
 
196
        if i:
 
197
            return m[i][0]
 
198
        else:
 
199
            logging.debug('No source selected.')
 
200
            return ''
 
201
 
 
202
    def get_target(self):
 
203
        '''Returns the UDI of the selected target disk or partition.'''
 
204
        sel = self.dest_treeview.get_selection()
 
205
        m, i = sel.get_selected()
 
206
        if i:
 
207
            return m[i][0]
 
208
        else:
 
209
            logging.debug('No target selected.')
 
210
            return ''
 
211
    
 
212
    def get_persistence(self):
 
213
        if self.persist_enabled.get_active() and \
 
214
            self.persist_enabled.state != gtk.STATE_INSENSITIVE:
 
215
            val = self.persist_value.get_value()
 
216
            return int(val)
 
217
        else:
 
218
            return 0
 
219
 
 
220
    def get_gnome_drive(self, udi):
 
221
        monitor = gnomevfs.VolumeMonitor()
 
222
        for drive in monitor.get_connected_drives():
 
223
            if drive.get_hal_udi() == udi:
 
224
                return drive
 
225
 
 
226
    def setup_sources_treeview(self):
 
227
        def column_data_func(layout, cell, model, iterator, column):
 
228
            if not self.backend:
 
229
                return
 
230
            udi = model[iterator][0]
 
231
            dev = self.backend.sources[udi]
 
232
            if column == 0:
 
233
                drive = self.get_gnome_drive(udi)
 
234
                if drive:
 
235
                    cell.set_property('text', drive.get_display_name())
 
236
                else:
 
237
                    cell.set_property('text', dev['device'])
 
238
            elif column == 1:
 
239
                cell.set_property('text', dev['label'])
 
240
            elif column == 2:
 
241
                cell.set_property('text', format_size(dev['size']))
 
242
 
 
243
        def pixbuf_data_func(column, cell, model, iterator):
 
244
            if not self.backend:
 
245
                return
 
246
            udi = model[iterator][0]
 
247
            drive = self.get_gnome_drive(udi)
 
248
            source_type = self.backend.sources[udi]['type']
 
249
            if drive:
 
250
                cell.set_property('icon-name', drive.get_icon())
 
251
            elif source_type == SOURCE_ISO:
 
252
                cell.set_property('stock-id', gtk.STOCK_CDROM)
 
253
            elif source_type == SOURCE_IMG:
 
254
                cell.set_property('stock-id', gtk.STOCK_HARDDISK)
 
255
            else:
 
256
                cell.set_property('stock-id', None)
 
257
 
 
258
        list_store = gtk.ListStore(str)
 
259
        self.source_treeview.set_model(list_store)
 
260
 
 
261
        cell_name = gtk.CellRendererText()
 
262
        cell_pixbuf = gtk.CellRendererPixbuf()
 
263
        column_name = gtk.TreeViewColumn(_('CD-Drive/Image'))
 
264
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
265
        column_name.pack_start(cell_pixbuf, expand=False)
 
266
        column_name.pack_start(cell_name, expand=True)
 
267
        self.source_treeview.append_column(column_name)
 
268
        column_name.set_cell_data_func(cell_name, column_data_func, 0)
 
269
        column_name.set_cell_data_func(cell_pixbuf, pixbuf_data_func)
 
270
 
 
271
        cell_version = gtk.CellRendererText()
 
272
        column_name = gtk.TreeViewColumn(_('OS Version'), cell_version)
 
273
        column_name.set_cell_data_func(cell_version, column_data_func, 1)
 
274
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
275
        self.source_treeview.append_column(column_name)
 
276
 
 
277
        cell_size = gtk.CellRendererText()
 
278
        column_name = gtk.TreeViewColumn(_('Size'), cell_size)
 
279
        column_name.set_cell_data_func(cell_size, column_data_func, 2)
 
280
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
281
        self.source_treeview.append_column(column_name)
 
282
 
 
283
        # Drag and drop support.
 
284
        # FIXME evand 2009-04-28: Anything can be dropped on the source
 
285
        # treeview.  Ideally, the user should only be able to drop ISO and IMG
 
286
        # files.
 
287
 
 
288
        def motion_cb(wid, context, x, y, time):
 
289
            context.drag_status(gtk.gdk.ACTION_COPY, time)
 
290
            return True
 
291
        
 
292
        def drop_cb(w, context, x, y, time):
 
293
            target_list = w.drag_dest_get_target_list()
 
294
            target = w.drag_dest_find_target(context, target_list)
 
295
            selection = w.drag_get_data(context, target)
 
296
            context.finish(True, True)
 
297
            return True
 
298
 
 
299
        def data_received_cb(w, context, x, y, selection, target_type, timestamp):
 
300
            # FIXME evand 2009-04-28: Use the GNOME VFS?  Test with a sshfs
 
301
            # nautilus window.
 
302
            file = selection.data.strip('\r\n\x00')
 
303
            if file.startswith('file://'):
 
304
                file = file[7:]
 
305
            elif file.startswith('file:'):
 
306
                file = file[5:]
 
307
            self.backend.add_image(file)
 
308
            
 
309
        self.source_treeview.drag_dest_set(gtk.gdk.ACTION_DEFAULT,
 
310
            [('text/uri-list', 0, 600)], gtk.gdk.ACTION_COPY)
 
311
        self.source_treeview.connect('drag_motion', motion_cb)
 
312
        self.source_treeview.connect('drag_drop', drop_cb)
 
313
        self.source_treeview.connect('drag-data-received', data_received_cb)
 
314
 
 
315
    def update_target(self, udi):
 
316
        m = self.dest_treeview.get_model()
 
317
        iterator = m.get_iter_first()
 
318
        while iterator is not None:
 
319
            if m.get_value(iterator, 0) == udi:
 
320
                m.row_changed(m.get_path(iterator), iterator)
 
321
                break
 
322
            iterator = m.iter_next(iterator)
 
323
 
 
324
    def dest_selection_changed(self, selection):
 
325
        '''The selected partition has changed and the bounds on the persistence
 
326
        slider need to be changed, or the slider needs to be disabled, to
 
327
        reflect the amount of free space on the partition.'''
 
328
 
 
329
        # XXX evand 2009-05-06: I'm tempted to move this into the backend,
 
330
        # should it get any more complicated, but right now most of the work
 
331
        # here is for the frontend.
 
332
 
 
333
        # TODO evand 2009-05-06: When we factor in the difference in size after
 
334
        # Ubuntu CD directories are removed (casper, pool, etc), we should add
 
335
        # it as a property of the partition, as we'll need to factor it in
 
336
        # here.
 
337
        self.persist_vbox.set_sensitive(False)
 
338
        self.persist_enabled_vbox.set_sensitive(False)
 
339
        
 
340
        if not self.backend:
 
341
            return
 
342
        model, iterator = selection.get_selected()
 
343
        if not iterator:
 
344
            return
 
345
        target_udi = model[iterator][0]
 
346
        source_udi = self.get_source()
 
347
        if not source_udi:
 
348
            return
 
349
        source = self.backend.sources[source_udi]
 
350
        if target_udi in self.backend.targets:
 
351
            # We're dealing with a partition, therefore we need to calculate
 
352
            # how much, if any, extra space can be used for the persistence
 
353
            # file.
 
354
            target = self.backend.targets[target_udi]
 
355
            persist_max = (target['free'] - source['size']) / 1024 / 1024
 
356
            if persist_max > MIN_PERSISTENCE:
 
357
                self.persist_vbox.set_sensitive(True)
 
358
                self.persist_enabled_vbox.set_sensitive(True)
 
359
                self.persist_value.set_range(MIN_PERSISTENCE, persist_max)
 
360
 
 
361
    def setup_targets_treeview(self):
 
362
        def column_data_func(layout, cell, model, iterator, column):
 
363
            if not self.backend:
 
364
                return
 
365
            udi = model[iterator][0]
 
366
            dev = self.backend.targets[udi]
 
367
            drive = self.get_gnome_drive(udi)
 
368
            if column == 0:
 
369
                if drive:
 
370
                    cell.set_property('text', drive.get_display_name() )
 
371
                else:
 
372
                    cell.set_property('text', dev['device'])
 
373
            elif column == 1:
 
374
                cell.set_property('text', dev['label'])
 
375
            elif column == 2:
 
376
                cell.set_property('text', format_size(dev['capacity']))
 
377
            elif column == 3:
 
378
                cell.set_property('text', format_size(dev['free']))
 
379
 
 
380
        def pixbuf_data_func(column, cell, model, iterator):
 
381
            if not self.backend:
 
382
                return
 
383
            udi = model[iterator][0]
 
384
            status = self.backend.targets[udi]['status']
 
385
 
 
386
            if status == NEED_SPACE:
 
387
                cell.set_property('stock-id', gtk.STOCK_DIALOG_WARNING)
 
388
            elif status == CANNOT_USE:
 
389
                # TODO evand 2009-05-05: Implement disabled rows as a
 
390
                # replacement?
 
391
                cell.set_property('stock-id', gtk.STOCK_DIALOG_ERROR)
 
392
            else:
 
393
                drive = self.get_gnome_drive(udi)
 
394
                if drive:
 
395
                    cell.set_property('icon-name', drive.get_icon())
 
396
                else:
 
397
                    cell.set_property('stock-id', None)
 
398
 
 
399
        list_store = gtk.ListStore(str)
 
400
        self.dest_treeview.set_model(list_store)
 
401
 
 
402
        column_name = gtk.TreeViewColumn()
 
403
        column_name.set_title(_('Device'))
 
404
        cell_name = gtk.CellRendererText()
 
405
        cell_pixbuf = gtk.CellRendererPixbuf()
 
406
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
407
        column_name.pack_start(cell_pixbuf, expand=False)
 
408
        column_name.pack_start(cell_name, expand=True)
 
409
        self.dest_treeview.append_column(column_name)
 
410
        column_name.set_cell_data_func(cell_name, column_data_func, 0)
 
411
        column_name.set_cell_data_func(cell_pixbuf, pixbuf_data_func)
 
412
        
 
413
        cell_name = gtk.CellRendererText()
 
414
        column_name = gtk.TreeViewColumn(_('Label'), cell_name)
 
415
        column_name.set_cell_data_func(cell_name, column_data_func, 1)
 
416
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
417
        self.dest_treeview.append_column(column_name)
 
418
 
 
419
        cell_capacity = gtk.CellRendererText()
 
420
        column_name = gtk.TreeViewColumn(_('Capacity'), cell_capacity)
 
421
        column_name.set_cell_data_func(cell_capacity, column_data_func, 2)
 
422
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
423
        self.dest_treeview.append_column(column_name)
 
424
 
 
425
        cell_free = gtk.CellRendererText()
 
426
        column_name = gtk.TreeViewColumn(_('Free Space'), cell_free)
 
427
        column_name.set_cell_data_func(cell_free, column_data_func, 3)
 
428
        column_name.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
 
429
        self.dest_treeview.append_column(column_name)
 
430
 
 
431
    def add_file_source_dialog(self, *args):
 
432
        filename = ''
 
433
        chooser = gtk.FileChooserDialog(title=None,action=gtk.FILE_CHOOSER_ACTION_OPEN,
 
434
            buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,gtk.STOCK_OPEN,gtk.RESPONSE_OK))
 
435
        for p, n in (('*.iso', _('CD Images')), ('*.img', _('Disk Images'))):
 
436
            filter = gtk.FileFilter()
 
437
            filter.add_pattern(p)
 
438
            filter.set_name(n)
 
439
            chooser.add_filter(filter)
 
440
        # FIXME evand 2009-04-28: I think there's a bug open about this block
 
441
        # of code.  Looks fairly wonky.
 
442
        if 'SUDO_USER' in os.environ:
 
443
            folder = os.path.expanduser('~' + os.environ['SUDO_USER'])
 
444
        else:
 
445
            folder = os.path.expanduser('~')
 
446
        chooser.set_current_folder(folder)
 
447
        response = chooser.run()
 
448
        if response == gtk.RESPONSE_OK:
 
449
            filename = chooser.get_filename()
 
450
            chooser.destroy()
 
451
            self.backend.add_image(filename)
 
452
 
 
453
    def install(self, widget):
 
454
        source = self.get_source()
 
455
        target = self.get_target()
 
456
        persist = self.get_persistence()
 
457
        # TODO evand 2009-07-31: Make these the default values in the
 
458
        # GtkBuilder file.
 
459
        starting_up = _('Starting up...')
 
460
        self.progress_title.set_markup('<big><b>' + starting_up + '</b></big>')
 
461
        self.progress_info.set_text('')
 
462
        if source and target:
 
463
            self.install_window.show()
 
464
            self.window.hide()
 
465
            self.backend.install(source, target, persist)
 
466
 
 
467
    @thread_wrap
 
468
    def progress(self, complete, remaining, speed):
 
469
        self.progress_bar.set_fraction(complete / 100.0)
 
470
        if remaining and speed:
 
471
            # TODO evand 2009-07-24: Could use a time formatting function
 
472
            # like our human size function.
 
473
            mins = int(remaining / 60)
 
474
            secs = int(remaining % 60)
 
475
            text = _('%d%% complete (%dm%ss remaining)') % \
 
476
                    (complete, mins, secs)
 
477
            self.progress_info.set_text(text)
 
478
        else:
 
479
            self.progress_info.set_text(_('%d%% complete') % complete)
 
480
 
 
481
    @thread_wrap
 
482
    def progress_message(self, message):
 
483
        self.progress_title.set_markup('<big><b>' + message + '</b></big>')
 
484
 
 
485
    @thread_wrap
 
486
    def retry(self):
 
487
        # TODO evand 2009-07-28: Implement a retry dialog.
 
488
        raise NotImplementedError
 
489
    
 
490
    def quit(self, *args):
 
491
        self.backend.cancel_install()
 
492
        gtk.main_quit()
 
493
 
 
494
    @thread_wrap
 
495
    def failure(self, message=None):
 
496
        logging.critical('Installation failed.')
 
497
        # FIXME: evand 2009-07-28: Do we need this?
 
498
        self.warning_dialog.hide()
 
499
        self.install_window.hide()
 
500
        if message:
 
501
            self.failed_dialog_label.set_text(message)
 
502
        else:
 
503
            message = _('Installation failed.')
 
504
            self.failed_dialog_label.set_text(message)
 
505
        self.failed_dialog.run()
 
506
        gtk.main_quit()
 
507
    
 
508
    @thread_wrap
 
509
    def success(self):
 
510
        self.warning_dialog.hide()
 
511
        self.install_window.hide()
 
512
        self.finished_dialog.run()
 
513
        sys.exit(0)
 
514
 
 
515
    def notify(self, message):
 
516
        dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_WARNING,
 
517
            gtk.BUTTONS_CLOSE, message)
 
518
        dialog.run()
 
519
        dialog.destroy()
 
520
 
 
521
    def format_dest_clicked(self, *args):
 
522
        # FIXME evand 2009-04-30: This needs a big warning dialog.
 
523
        model, iterator = self.dest_treeview.get_selection().get_selected()
 
524
        if not iterator:
 
525
            return
 
526
        udi = model[iterator][0]
 
527
        self.backend.format(udi)
 
528
 
 
529
    def open_dest_folder(self, *args):
 
530
        model, iterator = self.dest_treeview.get_selection().get_selected()
 
531
        if not iterator:
 
532
            logging.error('Open button pressed but there was no selection.')
 
533
            return
 
534
        disk = model[iterator][0]
 
535
        self.backend.open_mountpoint(udi)
 
536
 
 
537
# vim: set ai et sts=4 tabstop=4 sw=4: