Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--globalhistory.py78
-rw-r--r--places.py138
-rwxr-xr-xwebactivity.py1
-rwxr-xr-xwebtoolbar.py162
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()