Web · Wiki · Activities · Blog · Lists · Chat · Meeting · Bugs · Git · Translate · Archive · People · Donate
1
# Copyright (C) 2006, Red Hat, Inc.
2
# Copyright (C) 2009 Martin Langhoff, Simon Schampijer, Daniel Drake,
3
#                    Tomeu Vizoso
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 2 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program; if not, write to the Free Software
17
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19
import os
20
import logging
21
from gettext import gettext as _
22
23
import gobject
24
gobject.threads_init()
25
26
import gtk
27
import base64
28
import time
29
import shutil
30
import sqlite3
31
import cjson
32
import gconf
33
import locale
34
import cairo
35
from hashlib import sha1
36
37
# HACK: Needed by http://dev.sugarlabs.org/ticket/456
38
import gnome
39
gnome.init('Hulahop', '1.0')
40
41
from sugar.activity import activity
42
from sugar.graphics import style
43
import telepathy
44
import telepathy.client
45
from sugar.presence import presenceservice
46
from sugar.graphics.tray import HTray
47
from sugar import profile
48
from sugar.graphics.alert import Alert
49
from sugar.graphics.icon import Icon
50
from sugar import mime
51
52
# Attempt to import the new toolbar classes.  If the import fails,
53
# fall back to the old toolbar style.
54
try:
55
    from sugar.graphics.toolbarbox import ToolbarButton
56
    NEW_TOOLBARS = True
57
except ImportError:
58
    NEW_TOOLBARS = False
59
60
PROFILE_VERSION = 2
61
62
_profile_version = 0
63
_profile_path = os.path.join(activity.get_activity_root(), 'data/gecko')
64
_version_file = os.path.join(_profile_path, 'version')
65
66
if not NEW_TOOLBARS:
67
    _TOOLBAR_EDIT = 1
68
    _TOOLBAR_BROWSE = 2
69
70
if os.path.exists(_version_file):
71
    f = open(_version_file)
72
    _profile_version = int(f.read())
73
    f.close()
74
75
if _profile_version < PROFILE_VERSION:
76
    if not os.path.exists(_profile_path):
77
        os.mkdir(_profile_path)
78
79
    shutil.copy('cert8.db', _profile_path)
80
    os.chmod(os.path.join(_profile_path, 'cert8.db'), 0660)
81
82
    f = open(_version_file, 'w')
83
    f.write(str(PROFILE_VERSION))
84
    f.close()
85
86
87
def _seed_xs_cookie():
88
    ''' Create a HTTP Cookie to authenticate with the Schoolserver
89
    '''
90
    client = gconf.client_get_default()
91
    backup_url = client.get_string('/desktop/sugar/backup_url')
92
    if not backup_url:
93
        _logger.debug('seed_xs_cookie: Not registered with Schoolserver')
94
        return
95
96
    jabber_server = client.get_string(
97
        '/desktop/sugar/collaboration/jabber_server')
98
99
    pubkey = profile.get_profile().pubkey
100
    cookie_data = {'color': profile.get_color().to_string(),
101
                   'pkey_hash': sha1(pubkey).hexdigest()}
102
103
    db_path = os.path.join(_profile_path, 'cookies.sqlite')
104
    try:
105
        cookies_db = sqlite3.connect(db_path)
106
        c = cookies_db.cursor()
107
108
        c.execute('''CREATE TABLE IF NOT EXISTS
109
                     moz_cookies
110
                     (id INTEGER PRIMARY KEY,
111
                      name TEXT,
112
                      value TEXT,
113
                      host TEXT,
114
                      path TEXT,
115
                      expiry INTEGER,
116
                      lastAccessed INTEGER,
117
                      isSecure INTEGER,
118
                      isHttpOnly INTEGER)''')
119
120
        c.execute('''SELECT id
121
                     FROM moz_cookies
122
                     WHERE name=? AND host=? AND path=?''',
123
                  ('xoid', jabber_server, '/'))
124
125
        if c.fetchone():
126
            _logger.debug('seed_xs_cookie: Cookie exists already')
127
            return
128
129
        expire = int(time.time()) + 10 * 365 * 24 * 60 * 60
130
        c.execute('''INSERT INTO moz_cookies (name, value, host,
131
                                              path, expiry, lastAccessed,
132
                                              isSecure, isHttpOnly)
133
                     VALUES(?,?,?,?,?,?,?,?)''',
134
                  ('xoid', cjson.encode(cookie_data), jabber_server,
135
                   '/', expire, 0, 0, 0))
136
        cookies_db.commit()
137
        cookies_db.close()
138
    except sqlite3.Error:
139
        _logger.exception('seed_xs_cookie: could not write cookie')
140
    else:
141
        _logger.debug('seed_xs_cookie: Updated cookie successfully')
142
143
144
import hulahop
145
hulahop.set_app_version(os.environ['SUGAR_BUNDLE_VERSION'])
146
hulahop.startup(_profile_path)
147
148
from xpcom import components
149
150
151
def _set_accept_languages():
152
    ''' Set intl.accept_languages based on the locale
153
    '''
154
155
    lang = locale.getdefaultlocale()[0]
156
    if not lang:
157
        _logger.debug("Set_Accept_language: unrecognised LANG format")
158
        return
159
    lang = lang.split('_')
160
161
    # e.g. es-uy, es
162
    pref = lang[0] + "-" + lang[1].lower() + ", " + lang[0]
163
    cls = components.classes["@mozilla.org/preferences-service;1"]
164
    prefService = cls.getService(components.interfaces.nsIPrefService)
165
    branch = prefService.getBranch('')
166
    branch.setCharPref('intl.accept_languages', pref)
167
    logging.debug('LANG set')
168
169
from browser import TabbedView
170
from browser import Browser
171
from webtoolbar import PrimaryToolbar
172
from edittoolbar import EditToolbar
173
from viewtoolbar import ViewToolbar
174
import downloadmanager
175
import globalhistory
176
import filepicker
177
178
_LIBRARY_PATH = '/usr/share/library-common/index.html'
179
180
from model import Model
181
from sugar.presence.tubeconn import TubeConnection
182
from messenger import Messenger
183
from linkbutton import LinkButton
184
185
SERVICE = "org.laptop.WebActivity"
186
IFACE = SERVICE
187
PATH = "/org/laptop/WebActivity"
188
189
_logger = logging.getLogger('web-activity')
190
191
192
class WebActivity(activity.Activity):
193
    def __init__(self, handle):
194
        activity.Activity.__init__(self, handle)
195
196
        _logger.debug('Starting the web activity')
197
198
        self._force_close = False
199
        self._tabbed_view = TabbedView()
200
201
        _set_accept_languages()
202
        _seed_xs_cookie()
203
204
        # don't pick up the sugar theme - use the native mozilla one instead
205
        cls = components.classes['@mozilla.org/preferences-service;1']
206
        pref_service = cls.getService(components.interfaces.nsIPrefService)
207
        branch = pref_service.getBranch("mozilla.widget.")
208
        branch.setBoolPref("disable-native-theme", True)
209
210
        # HACK
211
        # Currently, the multiple tabs feature crashes the Browse activity
212
        # on cairo versions 1.8.10 or later. The exact cause for this
213
        # isn't exactly known. Thus, disable the multiple tabs feature
214
        # if we come across cairo versions >= 1.08.10
215
        # More information can be found here:
216
        # http://lists.sugarlabs.org/archive/sugar-devel/2010-July/025187.html
217
        self._disable_multiple_tabs = cairo.cairo_version() >= 10810
218
        if self._disable_multiple_tabs:
219
            logging.warning('Not enabling the multiple tabs feature due'
220
                ' to a bug in cairo/mozilla')
221
222
        self._tray = HTray()
223
        self.set_tray(self._tray, gtk.POS_BOTTOM)
224
        self._tray.show()
225
226
        self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self,
227
                    self._disable_multiple_tabs)
228
        self._edit_toolbar = EditToolbar(self)
229
        self._view_toolbar = ViewToolbar(self)
230
231
        self._primary_toolbar.connect('add-link', self._link_add_button_cb)
232
233
        self._primary_toolbar.connect('add-tab', self._new_tab_cb)
234
235
        self._primary_toolbar.connect('go-home', self._go_home_button_cb)
236
237
        if NEW_TOOLBARS:
238
            logging.debug('Using new toolbars')
239
            
240
            self._edit_toolbar_button = ToolbarButton(
241
                    page=self._edit_toolbar,
242
                    icon_name='toolbar-edit')
243
            self._primary_toolbar.toolbar.insert(
244
                    self._edit_toolbar_button, 1)
245
246
            view_toolbar_button = ToolbarButton(
247
                    page=self._view_toolbar,
248
                    icon_name='toolbar-view')
249
            self._primary_toolbar.toolbar.insert(
250
                    view_toolbar_button, 2)
251
252
            self._primary_toolbar.show_all()
253
            self.set_toolbar_box(self._primary_toolbar)
254
        else:
255
            _logger.debug('Using old toolbars')
256
            
257
            toolbox = activity.ActivityToolbox(self)
258
259
            toolbox.add_toolbar(_('Edit'), self._edit_toolbar)
260
            self._edit_toolbar.show()
261
    
262
            toolbox.add_toolbar(_('Browse'), self._primary_toolbar)
263
            self._primary_toolbar.show()
264
       
265
            toolbox.add_toolbar(_('View'), self._view_toolbar)
266
            self._view_toolbar.show()       
267
268
            self.set_toolbox(toolbox)
269
            toolbox.show()
270
            
271
            self.toolbox.set_current_toolbar(_TOOLBAR_BROWSE)
272
273
        self.set_canvas(self._tabbed_view)
274
        self._tabbed_view.show()
275
276
        self.model = Model()
277
        self.model.connect('add_link', self._add_link_model_cb)
278
279
        self.connect('key-press-event', self._key_press_cb)
280
281
        if handle.uri:
282
            self._tabbed_view.current_browser.load_uri(handle.uri)
283
        elif not self._jobject.file_path:
284
            # TODO: we need this hack until we extend the activity API for
285
            # opening URIs and default docs.
286
            self._load_homepage()
287
288
        self.messenger = None
289
        self.connect('shared', self._shared_cb)
290
291
        # Get the Presence Service
292
        self.pservice = presenceservice.get_instance()
293
        try:
294
            name, path = self.pservice.get_preferred_connection()
295
            self.tp_conn_name = name
296
            self.tp_conn_path = path
297
            self.conn = telepathy.client.Connection(name, path)
298
        except TypeError:
299
            _logger.debug('Offline')
300
        self.initiating = None
301
302
        if self._shared_activity is not None:
303
            _logger.debug('shared: %s', self._shared_activity.props.joined)
304
305
        if self._shared_activity is not None:
306
            # We are joining the activity
307
            _logger.debug('Joined activity')
308
            self.connect('joined', self._joined_cb)
309
            if self.get_shared():
310
                # We've already joined
311
                self._joined_cb()
312
        else:
313
            _logger.debug('Created activity')
314
315
    def _new_tab_cb(self, gobject):
316
        self._load_homepage(new_tab=True)
317
318
    def _shared_cb(self, activity_):
319
        _logger.debug('My activity was shared')
320
        self.initiating = True
321
        self._setup()
322
323
        _logger.debug('This is my activity: making a tube...')
324
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(SERVICE,
325
                                                                    {})
326
327
    def _setup(self):
328
        if self._shared_activity is None:
329
            _logger.debug('Failed to share or join activity')
330
            return
331
332
        bus_name, conn_path, channel_paths = \
333
                self._shared_activity.get_channels()
334
335
        # Work out what our room is called and whether we have Tubes already
336
        room = None
337
        tubes_chan = None
338
        text_chan = None
339
        for channel_path in channel_paths:
340
            channel = telepathy.client.Channel(bus_name, channel_path)
341
            htype, handle = channel.GetHandle()
342
            if htype == telepathy.HANDLE_TYPE_ROOM:
343
                _logger.debug('Found our room: it has handle#%d "%s"',
344
                              handle,
345
                              self.conn.InspectHandles(htype, [handle])[0])
346
                room = handle
347
                ctype = channel.GetChannelType()
348
                if ctype == telepathy.CHANNEL_TYPE_TUBES:
349
                    _logger.debug('Found our Tubes channel at %s',
350
                                  channel_path)
351
                    tubes_chan = channel
352
                elif ctype == telepathy.CHANNEL_TYPE_TEXT:
353
                    _logger.debug('Found our Text channel at %s',
354
                                  channel_path)
355
                    text_chan = channel
356
357
        if room is None:
358
            _logger.debug("Presence service didn't create a room")
359
            return
360
        if text_chan is None:
361
            _logger.debug("Presence service didn't create a text channel")
362
            return
363
364
        # Make sure we have a Tubes channel - PS doesn't yet provide one
365
        if tubes_chan is None:
366
            _logger.debug("Didn't find our Tubes channel, requesting one...")
367
            tubes_chan = self.conn.request_channel(
368
                telepathy.CHANNEL_TYPE_TUBES, telepathy.HANDLE_TYPE_ROOM,
369
                room, True)
370
371
        self.tubes_chan = tubes_chan
372
        self.text_chan = text_chan
373
374
        tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( \
375
                'NewTube', self._new_tube_cb)
376
377
    def _list_tubes_reply_cb(self, tubes):
378
        for tube_info in tubes:
379
            self._new_tube_cb(*tube_info)
380
381
    def _list_tubes_error_cb(self, e):
382
        _logger.debug('ListTubes() failed: %s', e)
383
384
    def _joined_cb(self, activity_):
385
        if not self._shared_activity:
386
            return
387
388
        _logger.debug('Joined an existing shared activity')
389
390
        self.initiating = False
391
        self._setup()
392
393
        _logger.debug('This is not my activity: waiting for a tube...')
394
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
395
            reply_handler=self._list_tubes_reply_cb,
396
            error_handler=self._list_tubes_error_cb)
397
398
    def _new_tube_cb(self, identifier, initiator, type, service, params,
399
                     state):
400
        _logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
401
                      'params=%r state=%d', identifier, initiator, type,
402
                      service, params, state)
403
404
        if (type == telepathy.TUBE_TYPE_DBUS and
405
            service == SERVICE):
406
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
407
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(
408
                        identifier)
409
410
            self.tube_conn = TubeConnection(self.conn,
411
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
412
                identifier, group_iface=self.text_chan[
413
                    telepathy.CHANNEL_INTERFACE_GROUP])
414
415
            _logger.debug('Tube created')
416
            self.messenger = Messenger(self.tube_conn, self.initiating,
417
                                       self.model)
418
419
    def _load_homepage(self, new_tab=False):
420
        # If new_tab is True, open the homepage in a new tab.
421
        if new_tab:
422
            browser = Browser()
423
            self._tabbed_view._append_tab(browser)
424
        else:
425
            browser = self._tabbed_view.current_browser
426
427
        if os.path.isfile(_LIBRARY_PATH):
428
            browser.load_uri('file://' + _LIBRARY_PATH)
429
        else:
430
            default_page = os.path.join(activity.get_bundle_path(),
431
                                        "data/index.html")
432
            browser.load_uri(default_page)
433
434
    def _get_data_from_file_path(self, file_path):
435
        fd = open(file_path, 'r')
436
        try:
437
            data = fd.read()
438
        finally:
439
            fd.close()
440
        return data
441
442
    def read_file(self, file_path):
443
        if self.metadata['mime_type'] == 'text/plain':
444
            data = self._get_data_from_file_path(file_path)
445
            self.model.deserialize(data)
446
447
            for link in self.model.data['shared_links']:
448
                _logger.debug('read: url=%s title=%s d=%s' % (link['url'],
449
                                                              link['title'],
450
                                                              link['color']))
451
                self._add_link_totray(link['url'],
452
                                      base64.b64decode(link['thumb']),
453
                                      link['color'], link['title'],
454
                                      link['owner'], -1, link['hash'])
455
            logging.debug('########## reading %s', data)
456
            self._tabbed_view.set_session(self.model.data['history'])
457
            self._tabbed_view.set_current_page(self.model.data['current_tab'])
458
        elif self.metadata['mime_type'] == 'text/uri-list':
459
            data = self._get_data_from_file_path(file_path)
460
            uris = mime.split_uri_list(data)
461
            if len(uris) == 1:
462
                self._tabbed_view.props.current_browser.load_uri(uris[0])
463
            else:
464
                _logger.error('Open uri-list: Does not support'
465
                              'list of multiple uris by now.')
466
        else:
467
            self._tabbed_view.props.current_browser.load_uri(file_path)
468
        self._load_urls()
469
470
    def _load_urls(self):
471
        if self.model.data['currents'] != None:
472
            first = True
473
            for current_tab in self.model.data['currents']:
474
                if first:
475
                    browser = self._tabbed_view.current_browser
476
                    first = False
477
                else:
478
                    browser = Browser()
479
                    self._tabbed_view._append_tab(browser)
480
                browser.load_uri(current_tab['url'])
481
482
    def write_file(self, file_path):
483
        if not self.metadata['mime_type']:
484
            self.metadata['mime_type'] = 'text/plain'
485
486
        if self.metadata['mime_type'] == 'text/plain':
487
488
            browser = self._tabbed_view.current_browser
489
490
            if not self._jobject.metadata['title_set_by_user'] == '1':
491
                if browser.props.title:
492
                    self.metadata['title'] = browser.props.title
493
494
            self.model.data['history'] = self._tabbed_view.get_session()
495
            current_tab = self._tabbed_view.get_current_page()
496
            self.model.data['current_tab'] = current_tab
497
498
            self.model.data['currents'] = []
499
            for n in range(0, self._tabbed_view.get_n_pages()):
500
                n_browser = self._tabbed_view.get_nth_page(n)
501
                if n_browser != None:
502
                    nsiuri = browser.progress.location
503
                    ui_uri = browser.get_url_from_nsiuri(nsiuri)
504
                    info = {'title': browser.props.title, 'url': ui_uri}
505
                    self.model.data['currents'].append(info)
506
507
            f = open(file_path, 'w')
508
            try:
509
                logging.debug('########## writing %s', self.model.serialize())
510
                f.write(self.model.serialize())
511
            finally:
512
                f.close()
513
514
    def _link_add_button_cb(self, button):
515
        self._add_link()
516
517
    def _go_home_button_cb(self, button):
518
        self._load_homepage()
519
520
    def _key_press_cb(self, widget, event):
521
        key_name = gtk.gdk.keyval_name(event.keyval)
522
        browser = self._tabbed_view.props.current_browser
523
524
        if event.state & gtk.gdk.CONTROL_MASK:
525
526
            if key_name == 'd':
527
                self._add_link()
528
            elif key_name == 'f':
529
                _logger.debug('keyboard: Find')
530
                if NEW_TOOLBARS:
531
                    self._edit_toolbar_button.set_expanded(True)
532
                else:
533
                    self.toolbox.set_current_toolbar(_TOOLBAR_EDIT)
534
                self._edit_toolbar.search_entry.grab_focus()
535
            elif key_name == 'l':
536
                _logger.debug('keyboard: Focus url entry')
537
                if not NEW_TOOLBARS:
538
                    self.toolbox.set_current_toolbar(_TOOLBAR_BROWSE)
539
                self._primary_toolbar.entry.grab_focus()
540
            elif key_name == 'minus':
541
                _logger.debug('keyboard: Zoom out')
542
                browser.zoom_out()
543
            elif key_name in ['plus', 'equal']:
544
                _logger.debug('keyboard: Zoom in')
545
                browser.zoom_in()
546
            elif key_name == 'Left':
547
                browser.web_navigation.goBack()
548
            elif key_name == 'Right':
549
                browser.web_navigation.goForward()
550
            elif key_name == 'r':
551
                flags = components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE
552
                browser.web_navigation.reload(flags)
553
            elif gtk.gdk.keyval_name(event.keyval) == "t":
554
                if not self._disable_multiple_tabs:
555
                    self._load_homepage(new_tab=True)
556
            else:
557
                return False
558
559
            return True
560
561
        return False
562
563
    def _add_link(self):
564
        ''' take screenshot and add link info to the model '''
565
566
        browser = self._tabbed_view.props.current_browser
567
        ui_uri = browser.get_url_from_nsiuri(browser.progress.location)
568
569
        for link in self.model.data['shared_links']:
570
            if link['hash'] == sha1(ui_uri).hexdigest():
571
                _logger.debug('_add_link: link exist already a=%s b=%s',
572
                              link['hash'], sha1(ui_uri).hexdigest())
573
                return
574
        buf = self._get_screenshot()
575
        timestamp = time.time()
576
        self.model.add_link(ui_uri, browser.props.title, buf,
577
                            profile.get_nick_name(),
578
                            profile.get_color().to_string(), timestamp)
579
580
        if self.messenger is not None:
581
            self.messenger._add_link(ui_uri, browser.props.title,
582
                                     profile.get_color().to_string(),
583
                                     profile.get_nick_name(),
584
                                     base64.b64encode(buf), timestamp)
585
586
    def _add_link_model_cb(self, model, index):
587
        ''' receive index of new link from the model '''
588
        link = self.model.data['shared_links'][index]
589
        self._add_link_totray(link['url'], base64.b64decode(link['thumb']),
590
                              link['color'], link['title'],
591
                              link['owner'], index, link['hash'])
592
593
    def _add_link_totray(self, url, buf, color, title, owner, index, hash):
594
        ''' add a link to the tray '''
595
        item = LinkButton(url, buf, color, title, owner, index, hash)
596
        item.connect('clicked', self._link_clicked_cb, url)
597
        item.connect('remove_link', self._link_removed_cb)
598
        # use index to add to the tray
599
        self._tray.add_item(item, index)
600
        item.show()
601
        if self._tray.props.visible is False:
602
            self._tray.show()
603
        self._view_toolbar.traybutton.props.sensitive = True
604
605
    def _link_removed_cb(self, button, hash):
606
        ''' remove a link from tray and delete it in the model '''
607
        self.model.remove_link(hash)
608
        self._tray.remove_item(button)
609
        if len(self._tray.get_children()) == 0:
610
            self._view_toolbar.traybutton.props.sensitive = False
611
612
    def _link_clicked_cb(self, button, url):
613
        ''' an item of the link tray has been clicked '''
614
        self._tabbed_view.props.current_browser.load_uri(url)
615
616
    def _pixbuf_save_cb(self, buf, data):
617
        data[0] += buf
618
        return True
619
620
    def get_buffer(self, pixbuf):
621
        data = [""]
622
        pixbuf.save_to_callback(self._pixbuf_save_cb, "png", {}, data)
623
        return str(data[0])
624
625
    def _get_screenshot(self):
626
        window = self._tabbed_view.props.current_browser.window
627
        width, height = window.get_size()
628
629
        screenshot = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, has_alpha=False,
630
                                    bits_per_sample=8, width=width,
631
                                    height=height)
632
        screenshot.get_from_drawable(window, window.get_colormap(), 0, 0, 0, 0,
633
                                     width, height)
634
635
        screenshot = screenshot.scale_simple(style.zoom(100),
636
                                                 style.zoom(80),
637
                                                 gtk.gdk.INTERP_BILINEAR)
638
639
        buf = self.get_buffer(screenshot)
640
        return buf
641
642
    def can_close(self):
643
        if self._force_close:
644
            return True
645
        elif downloadmanager.can_quit():
646
            return True
647
        else:
648
            alert = Alert()
649
            alert.props.title = _('Download in progress')
650
            alert.props.msg = _('Stopping now will cancel your download')
651
            cancel_icon = Icon(icon_name='dialog-cancel')
652
            alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), cancel_icon)
653
            stop_icon = Icon(icon_name='dialog-ok')
654
            alert.add_button(gtk.RESPONSE_OK, _('Stop'), stop_icon)
655
            stop_icon.show()
656
            self.add_alert(alert)
657
            alert.connect('response', self.__inprogress_response_cb)
658
            alert.show()
659
            self.present()
660
            return False
661
662
    def __inprogress_response_cb(self, alert, response_id):
663
        self.remove_alert(alert)
664
        if response_id is gtk.RESPONSE_CANCEL:
665
            logging.debug('Keep on')
666
        elif response_id == gtk.RESPONSE_OK:
667
            logging.debug('Stop downloads and quit')
668
            self._force_close = True
669
            downloadmanager.remove_all_downloads()
670
            self.close()
671
672
    def get_document_path(self, async_cb, async_err_cb):
673
        browser = self._tabbed_view.props.current_browser
674
        browser.get_source(async_cb, async_err_cb)
675
676
    def get_canvas(self):
677
        return self._tabbed_view