Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLucian Branescu Mihaila <lucian.branescu@gmail.com>2009-11-29 14:41:03 (GMT)
committer Lucian Branescu Mihaila <lucian.branescu@gmail.com>2009-11-29 14:41:03 (GMT)
commitf44480de7cb18867f8ef86ca3353f4e2607de84a (patch)
tree30a1706b07c5dda9a713cec8042285fa659e6b58
parent8861244a96e20fcffa66c54c0d7660ab9e41accf (diff)
Add usercode.py. Fix bug with SSB creation
-rw-r--r--usercode.py404
-rw-r--r--webactivity.py12
-rw-r--r--webtoolbar.py28
3 files changed, 442 insertions, 2 deletions
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)