From f44480de7cb18867f8ef86ca3353f4e2607de84a Mon Sep 17 00:00:00 2001 From: Lucian Branescu Mihaila Date: Sun, 29 Nov 2009 14:41:03 +0000 Subject: Add usercode.py. Fix bug with SSB creation --- diff --git a/usercode.py b/usercode.py new file mode 100644 index 0000000..2bc43cc --- /dev/null +++ b/usercode.py @@ -0,0 +1,404 @@ +# Copyright (C) 2009, Lucian Branescu Mihaila +# +# 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 logging +from urlparse import urlparse +from gettext import gettext as _ + +import gobject +import gtk +import pango +import gtksourceview2 + +import xpcom +from xpcom import components +from xpcom.components import interfaces + +from sugar.activity import activity +from sugar.graphics import style +from sugar.graphics.icon import Icon + +SCRIPTS_PATH = os.path.join(activity.get_activity_root(), + 'data/userscripts') +STYLE_PATH = os.path.join(activity.get_activity_root(), + 'data/style.user.css') + +# make sure the userscript dir exists +if not os.path.isdir(SCRIPTS_PATH): + os.mkdir(SCRIPTS_PATH) +# make sure userstyle sheet exists +open(STYLE_PATH, 'w').close() + +class Dialog(gtk.Window): + def __init__(self, width=None, height=None): + self.width = width or int(gtk.gdk.screen_width()/2) + self.height = height or int(gtk.gdk.screen_height()/1.5) + + gtk.Window.__init__(self) + self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + self.set_default_size(self.width, self.height) + + def show(self): + self.show_all() + gtk.Window.show(self) + +class SourceDisplay(gtk.ScrolledWindow): + __gtype_name__ = 'SugarSourceDisplay' + + def __init__(self): + gtk.ScrolledWindow.__init__(self) + + self.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC + self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC + + self._buffer = gtksourceview2.Buffer() + self._buffer.set_highlight_syntax(True) + + self._source_view = gtksourceview2.View(self._buffer) + self._source_view.set_editable(False) + self._source_view.set_cursor_visible(True) + self._source_view.set_show_line_numbers(True) + self._source_view.set_show_right_margin(True) + self._source_view.set_right_margin_position(80) + #self._source_view.set_highlight_current_line(True) #FIXME: Ugly color + self._source_view.modify_font(_SOURCE_FONT) + self.add(self._source_view) + self._source_view.show() + + self._file_path = None + + def _set_file_path(self, file_path): + if file_path == self._file_path: + return + self._file_path = file_path + + if self._file_path is None: + self._buffer.set_text('') + return + + mime_type = mime.get_for_file(self._file_path) + logging.debug('Detected mime type: %r' % mime_type) + + language_manager = gtksourceview2.language_manager_get_default() + detected_language = None + for language_id in language_manager.get_language_ids(): + language = language_manager.get_language(language_id) + if mime_type in language.get_mime_types(): + detected_language = language + break + + if detected_language is not None: + logging.debug('Detected language: %r' % \ + detected_language.get_name()) + + self._buffer.set_language(detected_language) + self._buffer.set_text(open(self._file_path, 'r').read()) + + def _get_file_path(self): + return self._file_path + + file_path = property(_get_file_path, _set_file_path) + +class SourceEditor(SourceDisplay): + def __init__(self): + SourceDisplay.__init__(self) + + self._source_view.set_editable(True) + + def get_text(self): + start = self._buffer.get_start_iter() + end = self._buffer.get_end_iter() + return self._buffer.get_text(start, end) + + def set_text(self, text): + self._buffer.set_text(text) + + text = property(get_text, set_text) + + def write(self, path=None): + open(path or self.file_path, 'w').write(self.text) + logging.debug('@@@@@ %s %s %s' % (self.text, path, self.file_path)) + +class FileViewer(gtk.ScrolledWindow): + __gtype_name__ = 'SugarFileViewer' + + __gsignals__ = { + 'file-selected': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([str])), + } + + def __init__(self, path, initial_filename): + gtk.ScrolledWindow.__init__(self) + + self.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC + self.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC + self.set_size_request(style.GRID_CELL_SIZE * 3, -1) + + self._path = None + self._initial_filename = initial_filename + + self._tree_view = gtk.TreeView() + self.add(self._tree_view) + self._tree_view.show() + + self._tree_view.props.headers_visible = False + selection = self._tree_view.get_selection() + selection.connect('changed', self.__selection_changed_cb) + + cell = gtk.CellRendererText() + column = gtk.TreeViewColumn() + column.pack_start(cell, True) + column.add_attribute(cell, 'text', 0) + self._tree_view.append_column(column) + self._tree_view.set_search_column(0) + + self.set_path(path) + + def set_path(self, path): + self.emit('file-selected', None) + if self._path == path: + return + self._path = path + self._tree_view.set_model(gtk.TreeStore(str, str)) + self._add_dir_to_model(path) + + def _add_dir_to_model(self, dir_path, parent=None): + model = self._tree_view.get_model() + for f in os.listdir(dir_path): + if not f.endswith('.pyc'): + full_path = os.path.join(dir_path, f) + if os.path.isdir(full_path): + new_iter = model.append(parent, [f, full_path]) + self._add_dir_to_model(full_path, new_iter) + else: + current_iter = model.append(parent, [f, full_path]) + if f == self._initial_filename: + selection = self._tree_view.get_selection() + selection.select_iter(current_iter) + + def __selection_changed_cb(self, selection): + model, tree_iter = selection.get_selected() + if tree_iter is None: + file_path = None + else: + file_path = model.get_value(tree_iter, 1) + self.emit('file-selected', file_path) + +class ScriptFileViewer(FileViewer): + def __init__(self, path): + ls = os.listdir(path) + initial_filename = ls[0] if len(ls) > 0 else None + FileViewer.__init__(self, path, initial_filename) + + def get_selected_file(self): + selection = self._tree_view.get_selection() + model, tree_iter = selection.get_selected() + if tree_iter is None: + return None + else: + return model.get_value(tree_iter, 1) + + def remove_file(self, file_path): + model = self._tree_view.get_model() + for i in model: + if i[0] == os.path.basename(file_path): + model.remove(model.get_iter(i.path)) + break + +class StyleEditor(Dialog): + __gsignals__ = { + 'userstyle-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), + } + + def __init__(self): + Dialog.__init__(self) + + # layout + vbox = gtk.VBox() + + self._editor = SourceEditor() + self._editor.file_path = STYLE_PATH + vbox.pack_start(self._editor) + + # buttons + buttonbox = gtk.HBox() + + self._cancel_button = gtk.Button(label=_('Cancel')) + self._cancel_button.set_image(Icon(icon_name='dialog-cancel')) + self._cancel_button.connect('clicked', self.__cancel_button_cb) + buttonbox.pack_start(self._cancel_button) + + self._save_button = gtk.Button(label=_('Save')) + self._save_button.set_image(Icon(icon_name='dialog-ok')) + self._save_button.connect('clicked', self.__save_button_cb) + buttonbox.pack_start(self._save_button) + + vbox.pack_start(buttonbox, expand=False) + + self.add(vbox) + + def __save_button_cb(self, button): + self._editor.write() + self.emit('userstyle-changed') + self.destroy() + + def __cancel_button_cb(self, button): + self.destroy() + +class ScriptEditor(Dialog): + def __init__(self): + Dialog.__init__(self) + + # layout + hbox = gtk.HBox() + + self._fileview = ScriptFileViewer(SCRIPTS_PATH) + self._fileview.connect('file-selected', self.__file_selected_cb) + hbox.pack_start(self._fileview, expand=False) + + editbox = gtk.VBox() + self._editor = SourceEditor() + editbox.pack_start(self._editor) + + buttonbox = gtk.HBox() + + self._cancel_button = gtk.Button(label=_('Close')) + self._cancel_button.set_image(Icon(icon_name='dialog-cancel')) + self._cancel_button.connect('clicked', self.__cancel_button_cb) + buttonbox.pack_start(self._cancel_button) + + self._delete_button = gtk.Button(label=_('Delete')) + self._delete_button.set_image(Icon(icon_name='stock_delete')) + self._delete_button.connect('clicked', self.__delete_button_cb) + buttonbox.pack_start(self._delete_button) + + self._save_button = gtk.Button(label=_('Save')) + self._save_button.set_image(Icon(icon_name='dialog-ok')) + self._save_button.connect('clicked', self.__save_button_cb) + buttonbox.pack_start(self._save_button) + + editbox.pack_start(buttonbox, expand=False) + hbox.pack_start(editbox) + + self.add(hbox) + + self.__file_selected_cb(self._fileview, + self._fileview._initial_filename) + + def __save_button_cb(self, button): + self._editor.write() + + def __delete_button_cb(self, button): + file_path = self._fileview.get_selected_file() + + self._fileview.remove_file(file_path) + os.remove(file_path) + + def __cancel_button_cb(self, button): + self.destroy() + + def __file_selected_cb(self, view, file_path): + self._editor.file_path = self._fileview.get_selected_file() + +def add_script(location): + cls = components.classes[ \ + '@mozilla.org/embedding/browser/nsWebBrowserPersist;1'] + persist = cls.createInstance(interfaces.nsIWebBrowserPersist) + persist.persistFlags = interfaces.nsIWebBrowserPersist \ + .PERSIST_FLAGS_REPLACE_EXISTING_FILES + + cls = components.classes["@mozilla.org/network/io-service;1"] + uri = cls.getService(interfaces.nsIIOService).newURI(location, None, None) + + cls = components.classes["@mozilla.org/file/local;1"] + local_file = cls.createInstance(interfaces.nsILocalFile) + + file_name = os.path.basename(uri.path) + file_path = os.path.join(SCRIPTS_PATH, file_name) + local_file.initWithPath(file_path) + if not local_file.exists(): + local_file.create(0x00, 0644) + + logging.debug('Saving userscript %s -> %s' % \ + (uri.spec, file_path)) + + persist.saveURI(uri, None, None, None, None, local_file) + +def script_exists(location): + script_name = os.path.basename(urlparse(location).path) + + return os.path.isfile(os.path.join(SCRIPTS_PATH, script_name)) + + +class Injector(): + _com_interfaces_ = interfaces.nsIDOMEventListener + + def __init__(self, script_path): + self.script_path = script_path + + self._wrapped = xpcom.server.WrapObject(self, + interfaces.nsIDOMEventListener) + + def handleEvent(self, event): + self.head.appendChild(self.script) + + def attach_to(self, window): + # set up the script element to be injected + self.script = window.document.createElement('script') + self.script.type = 'text/javascript' + + # work around XSS security + text = str(open(self.script_path,'r').read()) + self.script.appendChild( window.document.createTextNode(text) ) + + # reference to head + self.head = window.document.getElementsByTagName('head').item(0) + + # actual attaching + window.addEventListener('load', self._wrapped, False) + + +class ScriptListener(gobject.GObject): + _com_interfaces_ = interfaces.nsIWebProgressListener + + __gsignals__ = { + 'userscript-found': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), + 'userscript-inject': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([str])), + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._wrapped = xpcom.server.WrapObject( \ + self, interfaces.nsIWebProgressListener) + + def onLocationChange(self, webProgress, request, location): + if location.spec.endswith('.user.js'): + self.emit('userscript-found', location.spec) + else: + # TODO load scripts according to domain regex + for i in os.listdir(SCRIPTS_PATH): + script_path = os.path.join(SCRIPTS_PATH, i) + self.emit('userscript-inject', script_path) + + def setup(self, browser): + browser.web_progress.addProgressListener(self._wrapped, + interfaces.nsIWebProgress.NOTIFY_LOCATION) \ No newline at end of file diff --git a/webactivity.py b/webactivity.py index 1a0f9f5..b27ad50 100644 --- a/webactivity.py +++ b/webactivity.py @@ -538,6 +538,18 @@ class WebActivity(activity.Activity): def _link_clicked_cb(self, button, url): ''' an item of the link tray has been clicked ''' self._tabbed_view.props.current_browser.load_uri(url) + + @property + def current_uri(self): + browser = self._tabbed_view.props.current_browser + + return browser.web_navigation.currentURI + + @property + def current_title(self): + browser = self._tabbed_view.props.current_browser + + return browser.props.uri def _pixbuf_save_cb(self, buf, data): data[0] += buf diff --git a/webtoolbar.py b/webtoolbar.py index 2e7794e..c936d27 100644 --- a/webtoolbar.py +++ b/webtoolbar.py @@ -28,6 +28,8 @@ from sugar.graphics.toolbutton import ToolButton from sugar.graphics.menuitem import MenuItem from sugar._sugarext import AddressEntry from sugar.graphics.toolbarbox import ToolbarBox +from sugar.graphics.alert import Alert +from sugar.graphics.icon import Icon from sugar.activity.widgets import ActivityToolbarButton from sugar.activity.widgets import StopButton from sugar.activity import activity @@ -313,10 +315,32 @@ class PrimaryToolbar(ToolbarBox): def __keep_offline_cb(self, button): self.emit('keep-offline') - def __create_ssb_cb(self, button): + def __create_ssb_cb(self, button): + title = self._activity.current_title + uri = self._activity.current_uri + + #favicon = self._activity.get_favicon() + + pattern = re.compile(r''' + (\w+) # first word + [ _-]* # any amount and type of spacing + (\w+)? # second word, may be absent + ''', re.VERBOSE) + first, second = re.search(pattern, title).groups() + + # CamelCase the two words + first = first.capitalize() + if second is not None: + second = second.capitalize() + name = first + ' ' + second + else: + name = first + + self._ssb = ssb.SSBCreator(name, uri) + # alert to show after creation alert = Alert() - alert.props.title = _('Activity Creation') + alert.props.title = _('Activity Creation') cancel_icon = Icon(icon_name='dialog-cancel') alert.add_button(gtk.RESPONSE_CANCEL, _('Cancel'), cancel_icon) -- cgit v0.9.1