Web · Wiki · Activities · Blog · Lists · Chat · Meeting · Bugs · Git · Translate · Archive · People · Donate
1
# Copyright (C) 2006, Red Hat, Inc.
2
# Copyright (C) 2007, One Laptop Per Child
3
# Copyright (C) 2009, 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
from gettext import gettext as _
20
21
import gobject
22
import gtk
23
import pango
24
from xpcom.components import interfaces
25
from xpcom import components
26
27
from sugar.graphics.toolbutton import ToolButton
28
from sugar.graphics.menuitem import MenuItem
29
from sugar._sugarext import AddressEntry
30
try:
31
    from sugar.graphics.toolbarbox import ToolbarBox as ToolbarBase
32
    from sugar.activity.widgets import ActivityToolbarButton
33
    from sugar.activity.widgets import StopButton
34
    from sugar.activity import activity
35
    NEW_TOOLBARS = True
36
except ImportError:
37
    from gtk import Toolbar as ToolbarBase
38
    NEW_TOOLBARS = False
39
40
import filepicker
41
import places
42
43
_MAX_HISTORY_ENTRIES = 15
44
45
class WebEntry(AddressEntry):
46
    _COL_ADDRESS = 0
47
    _COL_TITLE = 1
48
49
    def __init__(self):
50
        gobject.GObject.__init__(self)
51
52
        self._address = None
53
        self._title = None
54
        self._search_view = self._search_create_view()
55
56
        self._search_window = gtk.Window(gtk.WINDOW_POPUP)
57
        self._search_window.add(self._search_view)
58
        self._search_view.show()
59
60
        self.connect('focus-in-event', self.__focus_in_event_cb)
61
        self.connect('populate-popup', self.__populate_popup_cb)
62
        self.connect('key-press-event', self.__key_press_event_cb)
63
        self.connect('enter-notify-event', self.__enter_notify_event_cb)
64
        self.connect('leave-notify-event', self.__leave_notify_event_cb)
65
        self._focus_out_hid = self.connect(
66
                    'focus-out-event', self.__focus_out_event_cb)
67
        self._change_hid = self.connect('changed', self.__changed_cb)
68
69
    def _set_text(self, text):
70
        """Set the text but block changes notification, so that we can
71
           recognize changes caused directly by user actions"""
72
        self.handler_block(self._change_hid)
73
        try:
74
            self.props.text = text
75
        finally:
76
            self.handler_unblock(self._change_hid)
77
        self.set_position(-1)
78
79
    def activate(self, uri):
80
        self._set_text(uri)
81
        self._search_popdown()
82
        self.emit('activate')
83
84
    def _set_address(self, address):
85
        self._address = address
86
87
    address = gobject.property(type=str, setter=_set_address)
88
89
    def _set_title(self, title):
90
        self._title = title
91
        if title is not None and not self.props.has_focus:
92
            self._set_text(title)
93
94
    title = gobject.property(type=str, setter=_set_title)
95
96
    def _search_create_view(self):
97
        view = gtk.TreeView()
98
        view.props.headers_visible = False
99
100
        view.connect('button-press-event', self.__view_button_press_event_cb)
101
102
        column = gtk.TreeViewColumn()
103
        view.append_column(column)
104
105
        cell = gtk.CellRendererText()
106
        cell.props.ellipsize = pango.ELLIPSIZE_END
107
        cell.props.ellipsize_set = True
108
        cell.props.font = 'Bold'
109
        column.pack_start(cell, True)
110
111
        column.set_attributes(cell, text=self._COL_TITLE)
112
113
        cell = gtk.CellRendererText()
114
        cell.props.ellipsize = pango.ELLIPSIZE_END
115
        cell.props.ellipsize_set = True
116
        cell.props.alignment = pango.ALIGN_LEFT
117
        column.pack_start(cell)
118
119
        column.set_attributes(cell, text=self._COL_ADDRESS)
120
121
        return view
122
123
    def _search_update(self):
124
        list_store = gtk.ListStore(str, str)
125
126
        for place in places.get_store().search(self.props.text):
127
            list_store.append([place.uri, place.title])
128
129
        self._search_view.set_model(list_store)
130
131
        return len(list_store) > 0
132
133
    def _search_popup(self):
134
        entry_x, entry_y = self.window.get_origin()
135
        entry_w, entry_h = self.size_request()
136
137
        x = entry_x + entry_h / 2
138
        y = entry_y + entry_h
139
        width = self.allocation.width - entry_h
140
        height = gtk.gdk.screen_height() / 3
141
142
        self._search_window.move(x, y)
143
        self._search_window.resize(width, height)
144
        self._search_window.show()
145
146
    def _search_popdown(self):
147
        self._search_window.hide()
148
149
    def __focus_in_event_cb(self, entry, event):
150
        self._set_text(self._address)
151
        self._search_popdown()
152
153
    def __focus_out_event_cb(self, entry, event):
154
        self._set_text(self._title)
155
        self._search_popdown()
156
157
    def __enter_notify_event_cb(self, entry, event):
158
        if not entry.props.has_focus:
159
            self._set_text(self._address)
160
161
    def __leave_notify_event_cb(self, entry, event):
162
        if not entry.props.has_focus:
163
            self._set_text(self._title)
164
165
    def __view_button_press_event_cb(self, view, event):
166
        model = view.get_model()
167
168
        path, col_, x_, y_ = view.get_path_at_pos(int(event.x), int(event.y))
169
        if path:
170
            uri = model[path][self._COL_ADDRESS]
171
            self.activate(uri)
172
173
    def __key_press_event_cb(self, entry, event):
174
        keyname = gtk.gdk.keyval_name(event.keyval)
175
176
        selection = self._search_view.get_selection()
177
        model, selected = selection.get_selected()
178
179
        if keyname == 'Up':
180
            if selected is None:
181
                selection.select_iter(model[-1].iter)
182
                self._set_text(model[-1][0])
183
            else:
184
                index = model.get_path(selected)[0]
185
                if index > 0:
186
                    selection.select_path(index - 1)
187
                    self._set_text(model[index - 1][0])
188
            return True
189
        elif keyname == 'Down':
190
            if selected is None:
191
                down_iter = model.get_iter_first()
192
            else:
193
                down_iter = model.iter_next(selected)
194
            if down_iter:
195
                selection.select_iter(down_iter)
196
                self._set_text(model.get(down_iter, 0)[0])
197
            return True
198
        elif keyname == 'Return':
199
            if selected is None:
200
                return False
201
            uri = model[model.get_path(selected)][self._COL_ADDRESS]
202
            self.activate(uri)
203
            return True
204
        elif keyname == 'Escape':
205
            self._search_window.hide()
206
            return True
207
        return False
208
209
    def __popup_unmap_cb(self, entry):
210
        self.handler_unblock(self._focus_out_hid)
211
212
    def __populate_popup_cb(self, entry, menu):
213
        self.handler_block(self._focus_out_hid)
214
        menu.connect('unmap', self.__popup_unmap_cb)
215
216
    def __changed_cb(self, entry):
217
        self._address = self.props.text
218
219
        if not self.props.text or not self._search_update():
220
            self._search_popdown()
221
        else:
222
            self._search_popup()
223
224
225
class PrimaryToolbar(ToolbarBase):
226
    __gtype_name__ = 'PrimaryToolbar'
227
228
    __gsignals__ = {
229
        'add-link': (gobject.SIGNAL_RUN_FIRST,
230
                     gobject.TYPE_NONE,
231
                     ([])),
232
        'add-tab': (gobject.SIGNAL_RUN_FIRST,
233
                     gobject.TYPE_NONE,
234
                     ([])),
235
        'go-home': (gobject.SIGNAL_RUN_FIRST,
236
                     gobject.TYPE_NONE,
237
                     ([])),
238
    }
239
240
    def __init__(self, tabbed_view, act, disable_multiple_tabs):
241
        ToolbarBase.__init__(self)
242
243
        self._activity = act
244
245
        self._tabbed_view = tabbed_view
246
247
        self._loading = False
248
249
        if NEW_TOOLBARS:
250
            toolbar = self.toolbar
251
            activity_button = ActivityToolbarButton(self._activity)
252
            toolbar.insert(activity_button, 0)
253
        else:
254
            toolbar = self
255
256
        self._go_home = ToolButton('go-home')
257
        self._go_home.set_tooltip(_('Home page'))
258
        self._go_home.connect('clicked', self._go_home_cb)
259
        toolbar.insert(self._go_home, -1)
260
        self._go_home.show()
261
262
        self._stop_and_reload = ToolButton('media-playback-stop')
263
        self._stop_and_reload.connect('clicked', self._stop_and_reload_cb)
264
        toolbar.insert(self._stop_and_reload, -1)
265
        self._stop_and_reload.show()
266
267
        self.entry = WebEntry()
268
        self.entry.connect('activate', self._entry_activate_cb)
269
270
        entry_item = gtk.ToolItem()
271
        entry_item.set_expand(True)
272
        entry_item.add(self.entry)
273
        self.entry.show()
274
275
        toolbar.insert(entry_item, -1)
276
        entry_item.show()
277
278
        self._back = ToolButton('go-previous-paired')
279
        self._back.set_tooltip(_('Back'))
280
        self._back.props.sensitive = False
281
        self._back.connect('clicked', self._go_back_cb)
282
        toolbar.insert(self._back, -1)
283
        self._back.show()
284
285
        self._forward = ToolButton('go-next-paired')
286
        self._forward.set_tooltip(_('Forward'))
287
        self._forward.props.sensitive = False
288
        self._forward.connect('clicked', self._go_forward_cb)
289
        toolbar.insert(self._forward, -1)
290
        self._forward.show()
291
292
        if not disable_multiple_tabs:
293
            self._add_tab = ToolButton('tab-add')
294
            self._add_tab.set_tooltip(_('Add a tab'))
295
            self._add_tab.props.sensitive = True
296
            self._add_tab.connect('clicked', self._add_tab_cb)
297
            toolbar.insert(self._add_tab, -1)
298
            self._add_tab.show()
299
300
        self._link_add = ToolButton('emblem-favorite')
301
        self._link_add.set_tooltip(_('Bookmark'))
302
        self._link_add.connect('clicked', self._link_add_clicked_cb)
303
        toolbar.insert(self._link_add, -1)
304
        self._link_add.show()
305
306
        if NEW_TOOLBARS:
307
            stop_button = StopButton(self._activity)
308
            toolbar.insert(stop_button, -1)
309
310
        self._progress_listener = None
311
        self._history = None
312
        self._browser = None
313
314
        self._location_changed_hid = None
315
        self._loading_changed_hid = None
316
        self._progress_changed_hid = None
317
        self._session_history_changed_hid = None
318
        self._title_changed_hid = None
319
320
        gobject.idle_add(lambda:
321
                self._connect_to_browser(tabbed_view.props.current_browser))
322
323
        tabbed_view.connect_after('switch-page', self.__switch_page_cb)
324
325
    def __switch_page_cb(self, tabbed_view, page, page_num):
326
        self._connect_to_browser(tabbed_view.props.current_browser)
327
328
    def _connect_to_browser(self, browser):
329
        if self._progress_listener is not None:
330
            self._progress_listener.disconnect(self._location_changed_hid)
331
            self._progress_listener.disconnect(self._loading_changed_hid)
332
            self._progress_listener.disconnect(self._progress_changed_hid)
333
334
        self._progress_listener = browser.progress
335
        self._set_progress(self._progress_listener.progress)
336
        if self._progress_listener.location:
337
            self._set_address(self._progress_listener.location)
338
        else:
339
            self._set_address(None)
340
        self._set_loading(self._progress_listener.loading)
341
        self._update_navigation_buttons()
342
343
        self._location_changed_hid = self._progress_listener.connect(
344
                'notify::location', self.__location_changed_cb)
345
        self._loading_changed_hid = self._progress_listener.connect(
346
                'notify::loading', self.__loading_changed_cb)
347
        self._progress_changed_hid = self._progress_listener.connect(
348
                'notify::progress', self.__progress_changed_cb)
349
350
        if self._history is not None:
351
            self._history.disconnect(self._session_history_changed_hid)
352
353
        self._history = browser.history
354
        self._session_history_changed_hid = self._history.connect(
355
                'session-history-changed', self._session_history_changed_cb)
356
357
        if self._browser is not None:
358
            self._browser.disconnect(self._title_changed_hid)
359
360
        self._browser = browser
361
        self._set_title(self._browser.props.title)
362
363
        self._title_changed_hid = self._browser.connect(
364
                'notify::title', self._title_changed_cb)
365
366
    def _session_history_changed_cb(self, session_history, current_page_index):
367
        # We have to wait until the history info is updated.
368
        gobject.idle_add(self._reload_session_history, current_page_index)
369
370
    def __location_changed_cb(self, progress_listener, pspec):
371
        self._set_address(progress_listener.location)
372
        self._update_navigation_buttons()
373
        filepicker.cleanup_temp_files()
374
375
    def __loading_changed_cb(self, progress_listener, pspec):
376
        if progress_listener.loading:
377
            self._set_title(None)
378
        self._set_loading(progress_listener.loading)
379
        self._update_navigation_buttons()
380
381
    def __progress_changed_cb(self, progress_listener, pspec):
382
        self._set_progress(progress_listener.progress)
383
384
    def _set_progress(self, progress):
385
        self.entry.props.progress = progress
386
387
    def _set_address(self, uri):
388
        if uri and self._browser is not None:
389
            ui_uri = self._browser.get_url_from_nsiuri(uri)
390
        else:
391
            ui_uri = None
392
        self.entry.props.address = ui_uri
393
394
    def _set_title(self, title):
395
        self.entry.props.title = title
396
397
    def _show_stop_icon(self):
398
        self._stop_and_reload.set_icon('media-playback-stop')
399
400
    def _show_reload_icon(self):
401
        self._stop_and_reload.set_icon('view-refresh')
402
403
    def _update_navigation_buttons(self):
404
        browser = self._tabbed_view.props.current_browser
405
406
        can_go_back = browser.web_navigation.canGoBack
407
        self._back.props.sensitive = can_go_back
408
409
        can_go_forward = browser.web_navigation.canGoForward
410
        self._forward.props.sensitive = can_go_forward
411
412
    def _entry_activate_cb(self, entry):
413
        browser = self._tabbed_view.props.current_browser
414
        browser.load_uri(entry.props.text)
415
        browser.grab_focus()
416
417
    def _add_tab_cb(self, button):
418
        self.emit('add-tab')
419
420
    def _go_home_cb(self, button):
421
        self.emit('go-home')
422
423
    def _go_back_cb(self, button):
424
        browser = self._tabbed_view.props.current_browser
425
        browser.web_navigation.goBack()
426
427
    def _go_forward_cb(self, button):
428
        browser = self._tabbed_view.props.current_browser
429
        browser.web_navigation.goForward()
430
431
    def _title_changed_cb(self, embed, spec):
432
        self._set_title(embed.props.title)
433
434
    def _stop_and_reload_cb(self, button):
435
        browser = self._tabbed_view.props.current_browser
436
        if self._loading:
437
            browser.web_navigation.stop(interfaces.nsIWebNavigation.STOP_ALL)
438
        else:
439
            flags = interfaces.nsIWebNavigation.LOAD_FLAGS_NONE
440
            browser.web_navigation.reload(flags)
441
442
    def _set_loading(self, loading):
443
        self._loading = loading
444
445
        if self._loading:
446
            self._show_stop_icon()
447
            self._stop_and_reload.set_tooltip(_('Stop'))
448
        else:
449
            self._show_reload_icon()
450
            self._stop_and_reload.set_tooltip(_('Reload'))
451
452
    def _reload_session_history(self, current_page_index=None):
453
        browser = self._tabbed_view.props.current_browser
454
        session_history = browser.web_navigation.sessionHistory
455
        if current_page_index is None:
456
            current_page_index = session_history.index
457
458
        for palette in (self._back.get_palette(), self._forward.get_palette()):
459
            for menu_item in palette.menu.get_children():
460
                palette.menu.remove(menu_item)
461
462
        if current_page_index > _MAX_HISTORY_ENTRIES:
463
            bottom = current_page_index - _MAX_HISTORY_ENTRIES
464
        else:
465
            bottom = 0
466
        if  (session_history.count - current_page_index) > \
467
               _MAX_HISTORY_ENTRIES:
468
            top = current_page_index + _MAX_HISTORY_ENTRIES + 1
469
        else:
470
            top = session_history.count
471
472
        for i in range(bottom, top):
473
            if i == current_page_index:
474
                continue
475
476
            entry = session_history.getEntryAtIndex(i, False)
477
            menu_item = MenuItem(entry.title, text_maxlen=60)
478
            menu_item.connect('activate', self._history_item_activated_cb, i)
479
480
            if i < current_page_index:
481
                palette = self._back.get_palette()
482
                palette.menu.prepend(menu_item)
483
            elif i > current_page_index:
484
                palette = self._forward.get_palette()
485
                palette.menu.append(menu_item)
486
487
            menu_item.show()
488
489
    def _history_item_activated_cb(self, menu_item, index):
490
        browser = self._tabbed_view.props.current_browser
491
        browser.web_navigation.gotoIndex(index)
492
493
    def _link_add_clicked_cb(self, button):
494
        self.emit('add-link')