From aa13c9deb4b9569b4eccc194e991a2375d65c684 Mon Sep 17 00:00:00 2001 From: Gonzalo Odiard Date: Thu, 26 Dec 2013 13:02:29 -0300 Subject: [PATCH] Implement Activity Help feature A new shortcut (alt-shift-h) display a window with the help content available in Help activity. The document is based in the context, shell view or activity active. If not content is available nothing is shown. For activities with help content available, a menu is added in the palette in the activity icon at the top of the frame. Signed-off-by: Kalpa Welivitigoda Signed-off-by: Gonzalo Odiard --- extensions/globalkey/Makefile.am | 3 +- extensions/globalkey/viewhelp.py | 27 ++++ src/jarabe/view/Makefile.am | 3 +- src/jarabe/view/palettes.py | 12 ++ src/jarabe/view/viewhelp.py | 249 +++++++++++++++++++++++++++++++ 5 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 extensions/globalkey/viewhelp.py create mode 100644 src/jarabe/view/viewhelp.py diff --git a/extensions/globalkey/Makefile.am b/extensions/globalkey/Makefile.am index b6cbbd69c3..dd4424727e 100644 --- a/extensions/globalkey/Makefile.am +++ b/extensions/globalkey/Makefile.am @@ -4,4 +4,5 @@ sugar_PYTHON = \ __init__.py \ screenshot.py \ speech.py \ - viewsource.py + viewsource.py \ + viewhelp.py diff --git a/extensions/globalkey/viewhelp.py b/extensions/globalkey/viewhelp.py new file mode 100644 index 0000000000..f9d3679fd3 --- /dev/null +++ b/extensions/globalkey/viewhelp.py @@ -0,0 +1,27 @@ +# Copyright (C) 2013 Kalpa Welivitigoda +# +# 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 jarabe.view.viewhelp import setup_view_help +from jarabe.model import shell + +BOUND_KEYS = ['h'] + + +def handle_key_press(key): + shell_model = shell.get_model() + activity = shell_model.get_active_activity() + + setup_view_help(activity) diff --git a/src/jarabe/view/Makefile.am b/src/jarabe/view/Makefile.am index 20e330acbc..94c50ecafc 100644 --- a/src/jarabe/view/Makefile.am +++ b/src/jarabe/view/Makefile.am @@ -13,4 +13,5 @@ sugar_PYTHON = \ pulsingicon.py \ service.py \ tabbinghandler.py \ - viewsource.py + viewsource.py \ + viewhelp.py diff --git a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py index 2fa02ec192..1c79263012 100644 --- a/src/jarabe/view/palettes.py +++ b/src/jarabe/view/palettes.py @@ -36,6 +36,8 @@ from jarabe.model import shell from jarabe.view.viewsource import setup_view_source +from jarabe.view.viewhelp import setup_view_help +from jarabe.view.viewhelp import get_help_url_and_title from jarabe.journal import misc @@ -103,6 +105,12 @@ def setup_palette(self): menu_item.connect('activate', self.__view_source__cb) self.menu_box.append_item(menu_item) + help_url_and_title = get_help_url_and_title(self._home_activity) + if help_url_and_title: + menu_item = PaletteMenuItem(_('View Help'), 'toolbar-help') + menu_item.connect('activate', self.__view_help__cb) + self.menu_box.append_item(menu_item) + separator = PaletteMenuItemSeparator() self.menu_box.append_item(separator) separator.show() @@ -126,6 +134,10 @@ def __view_source__cb(self, menu_item): Gtk.get_current_event_time()) self.emit('done') + def __view_help__cb(self, menu_item): + setup_view_help(self._home_activity) + self.emit('done') + def __stop_activate_cb(self, menu_item): self._home_activity.stop() self.emit('done') diff --git a/src/jarabe/view/viewhelp.py b/src/jarabe/view/viewhelp.py new file mode 100644 index 0000000000..3d3bd1a5ab --- /dev/null +++ b/src/jarabe/view/viewhelp.py @@ -0,0 +1,249 @@ +# Copyright (C) 2013 Kalpa Welivitigoda +# +# 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 gettext import gettext as _ +import logging +import os +import json + +from gi.repository import Gtk +from gi.repository import GObject +from gi.repository import Gdk +from gi.repository import WebKit +from gi.repository import GdkX11 + +from sugar3 import env +from sugar3.graphics import style +from sugar3.graphics.toolbutton import ToolButton +from sugar3.bundle.activitybundle import ActivityBundle +from jarabe.model import shell + + +_logger = logging.getLogger('ViewHelp') + + +def _get_help_activity_path(): + path = os.path.join(env.get_user_activities_path(), 'Help.activity') + if os.path.exists(path): + return path + # if was installed by a distro package + path = '/usr/share/sugar/activities/Help.activity' + if os.path.exists(path): + return path + return None + + +def get_help_url_and_title(activity): + """ + Returns the help document name and the title to display, + or None if not content is available. + """ + bundle_path = activity.get_bundle_path() + if bundle_path is None: + shell_model = shell.get_model() + zoom_level = shell_model.zoom_level + if zoom_level == shell_model.ZOOM_MESH: + title = _('Mesh') + link_id = 'mesh_view' + elif zoom_level == shell_model.ZOOM_GROUP: + title = _('Group') + link_id = 'group_view' + elif zoom_level == shell_model.ZOOM_HOME: + title = _('Home') + link_id = 'home_view' + else: + title = _('Journal') + link_id = 'org.laptop.JournalActivity' + else: + # get activity name and window id + activity_bundle = ActivityBundle(bundle_path) + title = activity_bundle.get_name() + link_id = activity_bundle.get_bundle_id() + + # get the help file name for the activity + activity_path = _get_help_activity_path() + if activity_path is None: + return None + help_content_link = os.path.join(activity_path, 'helplink.json') + if not os.path.exists(help_content_link): + _logger.error('Help activity not installed or json file not found') + return None + + links = None + try: + with open(help_content_link) as json_file: + links = json.load(json_file) + except IOError: + _logger.error('helplink.json malformed, or can\'t be read') + + if links: + if link_id in links.keys(): + return (links[link_id], title) + + return None + + +def setup_view_help(activity): + if shell.get_model().has_modal(): + return + # check whether the execution was from an activity + bundle_path = activity.get_bundle_path() + if bundle_path is None: + window_xid = 0 + else: + # get activity name and window id + window_xid = activity.get_xid() + + url_and_title = get_help_url_and_title(activity) + + if url_and_title: + viewhelp = ViewHelp(url_and_title[1], url_and_title[0], window_xid) + viewhelp.show() + else: + _logger.error('Help content is not available for the activity') + + +class ViewHelp(Gtk.Window): + parent_window_xid = None + + def __init__(self, title, help_file, window_xid): + self.parent_window_xid = window_xid + + Gtk.Window.__init__(self) + box = Gtk.Box() + box.set_orientation(Gtk.Orientation.VERTICAL) + self.add(box) + box.show() + + self.set_decorated(False) + self.set_position(Gtk.WindowPosition.CENTER_ALWAYS) + self.set_border_width(style.LINE_WIDTH) + self.set_has_resize_grip(False) + + width = Gdk.Screen.width() - style.GRID_CELL_SIZE * 2 + height = Gdk.Screen.height() - style.GRID_CELL_SIZE * 2 + self.set_size_request(width, height) + + self.connect('realize', self.__realize_cb) + + toolbar = Toolbar(title) + box.pack_start(toolbar, False, False, 0) + toolbar.show() + toolbar.connect('stop-clicked', self.__stop_clicked_cb) + + webview = WebKit.WebView() + webview.set_full_content_zoom(True) + + scrolled_window = Gtk.ScrolledWindow() + scrolled_window.add(webview) + scrolled_window.show() + + box.pack_start(scrolled_window, True, True, 0) + + webview.show() + + language = self._get_current_language() + view_file = self._get_help_file(language, help_file) + webview.load_uri('file://' + view_file) + + def __stop_clicked_cb(self, widget): + self.destroy() + shell.get_model().pop_modal() + + def __realize_cb(self, widget): + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + window = self.get_window() + window.set_accept_focus(True) + display = Gdk.Display.get_default() + parent = GdkX11.X11Window.foreign_new_for_display( + display, self.parent_window_xid) + window.set_transient_for(parent) + shell.get_model().push_modal() + + def _get_current_language(self): + locale = os.environ.get('LANG') + return locale.split('.')[0].split('_')[0].lower() + + def _get_help_file(self, language, help_file): + activity_path = _get_help_activity_path() + # check if exist a page for the language selected + # if not, use the default page + path = os.path.join(activity_path, 'html', language, help_file) + if not os.path.isfile(path): + path = os.path.join(activity_path, 'html', help_file) + + # verify if the images and _static dir exists + # Help have only one directory for images and static content, + # and need links in other languages directories + # the links are created by the Help activity or the viewer + # in the first run. + html_path = path[:path.rfind('/')] + images_path = os.path.join(html_path, '_images') + if not os.path.exists(images_path): + os.symlink(os.path.join(activity_path, 'images'), + images_path) + static_path = os.path.join(html_path, '_static') + if not os.path.exists(static_path): + os.symlink(os.path.join(activity_path, 'html', '_static'), + static_path) + return path + + +class Toolbar(Gtk.Toolbar): + + __gsignals__ = { + 'stop-clicked': (GObject.SignalFlags.RUN_FIRST, None, ([])), + } + + def __init__(self, activity_name): + Gtk.Toolbar.__init__(self) + + title = 'Help: ' + activity_name + + self._add_separator(False) + + label = Gtk.Label() + label.set_markup('%s' % title) + label.set_alignment(0, 0.5) + self._add_widget(label) + + self._add_separator(True) + + stop = ToolButton(icon_name='dialog-cancel') + stop.set_tooltip(_('Close')) + stop.connect('clicked', self.__stop_clicked_cb) + self.insert(stop, -1) + stop.show() + + def __stop_clicked_cb(self, widget): + self.emit('stop-clicked') + + def _add_widget(self, widget): + tool_item = Gtk.ToolItem() + tool_item.add(widget) + widget.show() + self.insert(tool_item, -1) + tool_item.show() + + def _add_separator(self, expand=False): + separator = Gtk.SeparatorToolItem() + separator.props.draw = False + if expand: + separator.set_expand(True) + else: + separator.set_size_request(style.DEFAULT_SPACING, -1) + self.insert(separator, -1) + separator.show()