diff options
-rw-r--r-- | globalhistory.py | 78 | ||||
-rw-r--r-- | places.py | 138 | ||||
-rwxr-xr-x | webactivity.py | 1 | ||||
-rwxr-xr-x | webtoolbar.py | 162 |
4 files changed, 378 insertions, 1 deletions
diff --git a/globalhistory.py b/globalhistory.py new file mode 100644 index 0000000..811de7e --- /dev/null +++ b/globalhistory.py @@ -0,0 +1,78 @@ +# Copyright (C) 2008, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from datetime import datetime + +from xpcom import components +from xpcom.components import interfaces +from xpcom.server.factory import Factory + +import places + +class GlobalHistory: + _com_interfaces_ = interfaces.nsIGlobalHistory, \ + interfaces.nsIGlobalHistory2, \ + interfaces.nsIGlobalHistory3 + + cid = '{2a53cf28-c48e-4a01-ba18-3d3fef3e2985}' + description = 'Sugar Global History' + + def __init__(self): + self._store = places.get_store() + + def addPage(self, url): + self.addURI(url, False, True, None) + + def isVisited(self, uri): + place = self._store.lookup_place(uri.spec) + return place != None + + def addURI(self, uri, redirect, toplevel, referrer): + place = self._store.lookup_place(uri.spec) + if place: + place.visits += 1 + place.last_visit = datetime.now() + self._store.update_place(place) + else: + place = places.Place(uri.spec) + self._store.add_place(place) + + def setPageTitle(self, uri, title): + place = self._store.lookup_place(uri.spec) + if place: + place.title = title + self._store.update_place(place) + + def addDocumentRedirect(self, old_channel, new_channel, flags, toplevel): + pass + + def getURIGeckoFlags(self, uri): + place = self._store.lookup_place(uri.spec) + if place: + return place.gecko_flags + else: + return 0 + + def setURIGeckoFlags(self, uri, flags): + place = self._store.lookup_place(uri.spec) + if place: + place.gecko_flags = flags + self._store.update_place(place) + +components.registrar.registerFactory(GlobalHistory.cid, + GlobalHistory.description, + '@mozilla.org/browser/global-history;2', + Factory(GlobalHistory)) diff --git a/places.py b/places.py new file mode 100644 index 0000000..1277835 --- /dev/null +++ b/places.py @@ -0,0 +1,138 @@ +# Copyright (C) 2008, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os +import sqlite3 +from datetime import datetime, timedelta + +from sugar.activity import activity + +_store = None + +class Place(object): + def __init__(self, uri=None): + self.uri = uri + self.title = None + self.bookmark = False + self.gecko_flags = 0 + self.visits = 0 + self.last_visit = datetime.now() + +class SqliteStore(object): + MAX_SEARCH_MATCHES = 20 + EXPIRE_DAYS = 30 + + def __init__(self): + db_path = os.path.join(activity.get_activity_root(), + 'data', 'places.db') + + self._connection = sqlite3.connect(db_path) + cursor = self._connection.cursor() + + cursor.execute('select * from sqlite_master where name == "places"') + if cursor.fetchone() == None: + cursor.execute("""create table places ( + uri text, + title text, + bookmark boolean, + gecko_flags integer, + visits integer, + last_visit timestamp + ); + """) + else: + self._cleanup() + + def search(self, text): + cursor = self._connection.cursor() + + try: + text = '%' + text + '%' + cursor.execute('select uri, title, bookmark, gecko_flags, ' \ + 'visits, last_visit from places ' \ + 'where uri like ? or title like ? ' \ + 'order by visits desc limit 0, ?', + (text, text, self.MAX_SEARCH_MATCHES)) + + result = [self._place_from_row(row) for row in cursor] + finally: + cursor.close() + + return result + + def add_place(self, place): + cursor = self._connection.cursor() + + try: + cursor.execute('insert into places (uri, title, bookmark, ' \ + 'gecko_flags, visits, last_visit) ' \ + 'values (?, ?, ?, ?, ?, ?)', \ + (place.uri, place.title, place.bookmark, + place.gecko_flags, place.visits, place.last_visit)) + self._connection.commit() + finally: + cursor.close() + + def lookup_place(self, uri): + cursor = self._connection.cursor() + + try: + cursor.execute('select uri, title, bookmark, gecko_flags,visits, ' \ + 'last_visit from places where uri=?', (uri,)) + + row = cursor.fetchone() + if row: + return self._place_from_row(row) + else: + return None + finally: + cursor.close() + + def update_place(self, place): + cursor = self._connection.cursor() + + try: + cursor.execute('update places set title=?, gecko_flags=?, ' + 'visits=?, last_visit=?, bookmark=? where uri=?', + (place.title, place.gecko_flags, place.visits, + place.last_visit, place.bookmark, place.uri)) + self._connection.commit() + finally: + cursor.close() + + def _place_from_row(self, row): + place = Place() + + place.uri, place.title, place.bookmark, place.gecko_flags, \ + place.visits, place.last_visit = row + + return place + + def _cleanup(self): + cursor = self._connection.cursor() + + try: + date = datetime.now() - timedelta(days=self.EXPIRE_DAYS) + cursor.execute('delete from places where last_visit < ?', (date,)) + self._connection.commit() + finally: + cursor.close() + +def get_store(): + global _store + if _store == None: + _store = SqliteStore() + return _store diff --git a/webactivity.py b/webactivity.py index 7a0d5df..8ca7043 100755 --- a/webactivity.py +++ b/webactivity.py @@ -71,6 +71,7 @@ import downloadmanager import sessionhistory import progresslistener import filepicker +import globalhistory _LIBRARY_PATH = '/usr/share/library-common/index.html' diff --git a/webtoolbar.py b/webtoolbar.py index 3d1877c..f3f8ccb 100755 --- a/webtoolbar.py +++ b/webtoolbar.py @@ -19,6 +19,7 @@ from gettext import gettext as _ import gobject import gtk +import pango from xpcom.components import interfaces from xpcom import components @@ -29,9 +30,168 @@ from sugar._sugarext import AddressEntry import sessionhistory import progresslistener import filepicker +import places _MAX_HISTORY_ENTRIES = 15 +class WebEntry(AddressEntry): + _COL_ADDRESS = 0 + _COL_TITLE = 1 + + def __init__(self): + gobject.GObject.__init__(self) + + self._address = None + self._title = None + self._search_view = self._search_create_view() + + self._search_window = gtk.Window(gtk.WINDOW_POPUP) + self._search_window.add(self._search_view) + self._search_view.show() + + self.connect('focus-in-event', self.__focus_in_event_cb) + self.connect('populate-popup', self.__populate_popup_cb) + self.connect('key-press-event', self.__key_press_event_cb) + self._focus_out_hid = self.connect( + 'focus-out-event', self.__focus_out_event_cb) + self._change_hid = self.connect('changed', self.__changed_cb) + + def _set_text(self, text): + """Set the text but block changes notification, so that we can + recognize changes caused directly by user actions""" + self.handler_block(self._change_hid) + try: + self.props.text = text + finally: + self.handler_unblock(self._change_hid) + + def activate(self, uri): + self._set_text(uri) + self._search_popdown() + self.emit('activate') + + def _set_address(self, address): + self._address = address + + address = gobject.property(type=str, setter=_set_address) + + def _set_title(self, title): + self._title = title + + title = gobject.property(type=str, setter=_set_title) + + def _search_create_view(self): + view = gtk.TreeView() + view.props.headers_visible = False + + view.connect('button-press-event', self.__view_button_press_event_cb) + + column = gtk.TreeViewColumn() + view.append_column(column) + + cell = gtk.CellRendererText() + cell.props.ellipsize = pango.ELLIPSIZE_END + cell.props.ellipsize_set = True + column.pack_start(cell, True) + + column.set_attributes(cell, text=self._COL_ADDRESS) + + cell = gtk.CellRendererText() + cell.props.ellipsize = pango.ELLIPSIZE_END + cell.props.ellipsize_set = True + cell.props.alignment = pango.ALIGN_LEFT + cell.props.font = 'Bold' + column.pack_start(cell) + + column.set_attributes(cell, text=self._COL_TITLE) + + return view + + def _search_update(self): + list_store = gtk.ListStore(str, str) + + for place in places.get_store().search(self.props.text): + list_store.append([place.uri, place.title]) + + self._search_view.set_model(list_store) + + return len(list_store) > 0 + + def _search_popup(self): + entry_x, entry_y = self.window.get_origin() + entry_w, entry_h = self.size_request() + + x = entry_x + y = entry_y + entry_h + width = self.allocation.width + height = gtk.gdk.screen_height() / 3 + + i = self._search_view.get_model().get_iter_first() + self._search_view.get_selection().select_iter(i) + + self._search_window.move(x, y) + self._search_window.resize(width, height) + self._search_window.show() + + def _search_popdown(self): + self._search_window.hide() + + def __focus_in_event_cb(self, entry, event): + self._set_text(self._address) + self._search_popdown() + + def __focus_out_event_cb(self, entry, event): + self._set_text(self._title) + self._search_popdown() + + def __view_button_press_event_cb(self, view, event): + model = view.get_model() + + path, col_, x_, y_ = view.get_path_at_pos(event.x, event.y) + if path: + uri = model[path][self._COL_ADDRESS] + self.activate(uri) + + def __key_press_event_cb(self, entry, event): + keyname = gtk.gdk.keyval_name(event.keyval) + + selection = self._search_view.get_selection() + model, selected = selection.get_selected() + if selected == None: + return False + + if keyname == 'Up': + index = model.get_path(selected)[0] + if index > 0: + selection.select_path(index - 1) + return True + elif keyname == 'Down': + next = model.iter_next(selected) + if next: + selection.select_iter(next) + return True + elif keyname == 'Return': + uri = model[model.get_path(selected)][self._COL_ADDRESS] + self.activate(uri) + return True + + return False + + def __popup_unmap_cb(self, entry): + self.handler_unblock(self._focus_out_hid) + + def __populate_popup_cb(self, entry, menu): + self.handler_block(self._focus_out_hid) + menu.connect('unmap', self.__popup_unmap_cb) + + def __changed_cb(self, entry): + self._address = self.props.text + + if not self.props.text or not self._search_update(): + self._search_popdown() + else: + self._search_popup() + class WebToolbar(gtk.Toolbar): __gtype_name__ = 'WebToolbar' @@ -67,7 +227,7 @@ class WebToolbar(gtk.Toolbar): self.insert(self._stop_and_reload, -1) self._stop_and_reload.show() - self.entry = AddressEntry() + self.entry = WebEntry() self.entry.connect('activate', self._entry_activate_cb) entry_item = gtk.ToolItem() |