Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorent Pigout <florent.pigout@gmail.com>2012-02-13 01:39:10 (GMT)
committer Florent Pigout <florent.pigout@gmail.com>2012-02-13 01:39:10 (GMT)
commitb19c7aae01240138101f2dba6a073586602114df (patch)
tree4dc69aad22839eec7e5c51097f10532332c72c5e
parent2cb1de02f8c99d67276eb02279e002df4f1cea06 (diff)
new generic to display `html` content in a sugar app instead using hulahop
-rw-r--r--atoidejouer/tools/storage.py6
-rw-r--r--atoidejouer/ui/screen/help.py35
-rw-r--r--lib/htmltextview.py591
-rw-r--r--static/data/html/help.html341
4 files changed, 783 insertions, 190 deletions
diff --git a/atoidejouer/tools/storage.py b/atoidejouer/tools/storage.py
index 0985360..904c56d 100644
--- a/atoidejouer/tools/storage.py
+++ b/atoidejouer/tools/storage.py
@@ -74,11 +74,15 @@ def get_image_path(filename, dir_='graphics'):
def get_html_path(page):
- # return path
return os.path.join(BUND, 'static', 'data', 'html',
'%s.html' % page)
+def get_html_img_path(img_name):
+ return os.path.join(BUND, 'static', 'data', 'html', 'img',
+ '%s.png' % img_name)
+
+
def get_pixbuf_from_data(data, image_type=None, size=None):
# load it
if gtk is None:
diff --git a/atoidejouer/ui/screen/help.py b/atoidejouer/ui/screen/help.py
index 7d5ec14..b592aff 100644
--- a/atoidejouer/ui/screen/help.py
+++ b/atoidejouer/ui/screen/help.py
@@ -1,12 +1,11 @@
# python import
import logging
-# ..
-from gettext import gettext as _
# gtk import
import gtk
-# from hulahop.webview import WebView
+# html simple viewer
+from lib.htmltextview import HtmlTextView
# atoidejouer import
from atoidejouer.tools import storage
@@ -15,18 +14,36 @@ from atoidejouer.tools import storage
logger = logging.getLogger('atoidejouer')
-class ScreenHelp(object):
+class ScreenHelp(gtk.ScrolledWindow):
def __init__(self, activity_):
- # WebView.__init__(self)
+ gtk.ScrolledWindow.__init__(self)
+ self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+ # init main box
+ self.__v_box = gtk.VBox(spacing=2)
+ self.__v_box.show()
+ self.add_with_viewport(self.__v_box)
# keep activity
self.activity = activity_
# load html content
- self.load_uri(storage.get_html_path('help'))
+ with open(storage.get_html_path('help')) as f:
+ for content in f.read().split('----'):
+ content = content.strip()
+ c_widget = None
+ if '\n' in content:
+ c_widget = HtmlTextView()
+ c_widget.display_html(content)
+ c_widget.show()
+ else:
+ i_path = storage.get_html_img_path(content)
+ logger.debug(i_path)
+ c_widget = gtk.Image()
+ c_widget.set_from_file(i_path)
+ c_widget.show()
+ self.__v_box.pack_start(c_widget, expand=False, fill=True)
def _show(self):
- pass
# show all
- # self.show()
+ self.show()
# update activity
- # self.activity.set_canvas(self)
+ self.activity.set_canvas(self)
diff --git a/lib/htmltextview.py b/lib/htmltextview.py
new file mode 100644
index 0000000..a717ea9
--- /dev/null
+++ b/lib/htmltextview.py
@@ -0,0 +1,591 @@
+### Copyright (C) 2005-2007 Gustavo J. A. M. Carneiro
+###
+### This library is free software; you can redistribute it and/or
+### modify it under the terms of the GNU Lesser General Public
+### License as published by the Free Software Foundation; either
+### version 2 of the License, or (at your option) any later version.
+###
+### This library 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
+### Lesser General Public License for more details.
+###
+### You should have received a copy of the GNU Lesser General Public
+### License along with this library; if not, write to the
+### Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+### Boston, MA 02111-1307, USA.
+
+'''
+A gtk.TextView-based renderer for XHTML-IM, as described in:
+ http://www.jabber.org/jeps/jep-0071.html
+'''
+import gobject
+import pango
+import gtk
+import xml.sax, xml.sax.handler
+import re
+import warnings
+from cStringIO import StringIO
+import urllib2
+import operator
+
+__all__ = ['HtmlTextView']
+
+whitespace_rx = re.compile("\\s+")
+allwhitespace_rx = re.compile("^\\s*$")
+
+## pixels = points * display_resolution
+display_resolution = 0.3514598*(gtk.gdk.screen_height() /
+ float(gtk.gdk.screen_height_mm()))
+
+
+def _parse_css_color(color):
+ '''_parse_css_color(css_color) -> gtk.gdk.Color'''
+ if color.startswith("rgb(") and color.endswith(')'):
+ r, g, b = [int(c)*257 for c in color[4:-1].split(',')]
+ return gtk.gdk.Color(r, g, b)
+ else:
+ return gtk.gdk.color_parse(color)
+
+
+# class HtmlEntityResolver(xml.sax.handler.EntityResolver):
+# def resolveEntity(publicId, systemId):
+# pass
+
+class HtmlHandler(xml.sax.handler.ContentHandler):
+
+ def __init__(self, textview, startiter):
+ xml.sax.handler.ContentHandler.__init__(self)
+ self.textbuf = textview.get_buffer()
+ self.textview = textview
+ self.iter = startiter
+ self.text = ''
+ self.styles = [] # a gtk.TextTag or None, for each span level
+ self.list_counters = [] # stack (top at head) of list
+ # counters, or None for unordered list
+
+ def _parse_style_color(self, tag, value):
+ color = _parse_css_color(value)
+ tag.set_property("foreground-gdk", color)
+
+ def _parse_style_background_color(self, tag, value):
+ color = _parse_css_color(value)
+ tag.set_property("background-gdk", color)
+ if gtk.gtk_version >= (2, 8):
+ tag.set_property("paragraph-background-gdk", color)
+
+
+ if gtk.gtk_version >= (2, 8, 5) or gobject.pygtk_version >= (2, 8, 1):
+
+ def _get_current_attributes(self):
+ attrs = self.textview.get_default_attributes()
+ self.iter.backward_char()
+ self.iter.get_attributes(attrs)
+ self.iter.forward_char()
+ return attrs
+
+ else:
+
+ ## Workaround http://bugzilla.gnome.org/show_bug.cgi?id=317455
+ def _get_current_style_attr(self, propname, comb_oper=None):
+ tags = [tag for tag in self.styles if tag is not None]
+ tags.reverse()
+ is_set_name = propname + "-set"
+ value = None
+ for tag in tags:
+ if tag.get_property(is_set_name):
+ if value is None:
+ value = tag.get_property(propname)
+ if comb_oper is None:
+ return value
+ else:
+ value = comb_oper(value, tag.get_property(propname))
+ return value
+
+ class _FakeAttrs(object):
+ __slots__ = ("font", "font_scale")
+
+ def _get_current_attributes(self):
+ attrs = self._FakeAttrs()
+ attrs.font_scale = self._get_current_style_attr("scale",
+ operator.mul)
+ if attrs.font_scale is None:
+ attrs.font_scale = 1.0
+ attrs.font = self._get_current_style_attr("font-desc")
+ if attrs.font is None:
+ attrs.font = self.textview.style.font_desc
+ return attrs
+
+
+ def __parse_length_frac_size_allocate(self, textview, allocation,
+ frac, callback, args):
+ callback(allocation.width*frac, *args)
+
+ def _parse_length(self, value, font_relative, callback, *args):
+ '''Parse/calc length, converting to pixels, calls callback(length, *args)
+ when the length is first computed or changes'''
+ if value.endswith('%'):
+ frac = float(value[:-1])/100
+ if font_relative:
+ attrs = self._get_current_attributes()
+ font_size = attrs.font.get_size() / pango.SCALE
+ callback(frac*display_resolution*font_size, *args)
+ else:
+ ## CSS says "Percentage values: refer to width of the closest
+ ## block-level ancestor"
+ ## This is difficult/impossible to implement, so we use
+ ## textview width instead; a reasonable approximation..
+ alloc = self.textview.get_allocation()
+ self.__parse_length_frac_size_allocate(self.textview, alloc,
+ frac, callback, args)
+ self.textview.connect("size-allocate",
+ self.__parse_length_frac_size_allocate,
+ frac, callback, args)
+
+ elif value.endswith('pt'): # points
+ callback(float(value[:-2])*display_resolution, *args)
+
+ elif value.endswith('em'): # ems, the height of the element's font
+ attrs = self._get_current_attributes()
+ font_size = attrs.font.get_size() / pango.SCALE
+ callback(float(value[:-2])*display_resolution*font_size, *args)
+
+ elif value.endswith('ex'): # x-height, ~ the height of the letter 'x'
+ ## FIXME: figure out how to calculate this correctly
+ ## for now 'em' size is used as approximation
+ attrs = self._get_current_attributes()
+ font_size = attrs.font.get_size() / pango.SCALE
+ callback(float(value[:-2])*display_resolution*font_size, *args)
+
+ elif value.endswith('px'): # pixels
+ callback(int(value[:-2]), *args)
+
+ else:
+ warnings.warn("Unable to parse length value '%s'" % value)
+
+ def __parse_font_size_cb(length, tag):
+ tag.set_property("size-points", length/display_resolution)
+ __parse_font_size_cb = staticmethod(__parse_font_size_cb)
+
+ def _parse_style_font_size(self, tag, value):
+ try:
+ scale = {
+ "xx-small": pango.SCALE_XX_SMALL,
+ "x-small": pango.SCALE_X_SMALL,
+ "small": pango.SCALE_SMALL,
+ "medium": pango.SCALE_MEDIUM,
+ "large": pango.SCALE_LARGE,
+ "x-large": pango.SCALE_X_LARGE,
+ "xx-large": pango.SCALE_XX_LARGE,
+ } [value]
+ except KeyError:
+ pass
+ else:
+ attrs = self._get_current_attributes()
+ tag.set_property("scale", scale / attrs.font_scale)
+ return
+ if value == 'smaller':
+ tag.set_property("scale", pango.SCALE_SMALL)
+ return
+ if value == 'larger':
+ tag.set_property("scale", pango.SCALE_LARGE)
+ return
+ self._parse_length(value, True, self.__parse_font_size_cb, tag)
+
+ def _parse_style_font_style(self, tag, value):
+ try:
+ style = {
+ "normal": pango.STYLE_NORMAL,
+ "italic": pango.STYLE_ITALIC,
+ "oblique": pango.STYLE_OBLIQUE,
+ } [value]
+ except KeyError:
+ warnings.warn("unknown font-style %s" % value)
+ else:
+ tag.set_property("style", style)
+
+ def __frac_length_tag_cb(length, tag, propname):
+ tag.set_property(propname, length)
+ __frac_length_tag_cb = staticmethod(__frac_length_tag_cb)
+
+ def _parse_style_margin_left(self, tag, value):
+ self._parse_length(value, False, self.__frac_length_tag_cb,
+ tag, "left-margin")
+
+ def _parse_style_margin_right(self, tag, value):
+ self._parse_length(value, False, self.__frac_length_tag_cb,
+ tag, "right-margin")
+
+ def _parse_style_font_weight(self, tag, value):
+ ## TODO: missing 'bolder' and 'lighter'
+ try:
+ weight = {
+ '100': pango.WEIGHT_ULTRALIGHT,
+ '200': pango.WEIGHT_ULTRALIGHT,
+ '300': pango.WEIGHT_LIGHT,
+ '400': pango.WEIGHT_NORMAL,
+ '500': pango.WEIGHT_NORMAL,
+ '600': pango.WEIGHT_BOLD,
+ '700': pango.WEIGHT_BOLD,
+ '800': pango.WEIGHT_ULTRABOLD,
+ '900': pango.WEIGHT_HEAVY,
+ 'normal': pango.WEIGHT_NORMAL,
+ 'bold': pango.WEIGHT_BOLD,
+ } [value]
+ except KeyError:
+ warnings.warn("unknown font-style %s" % value)
+ else:
+ tag.set_property("weight", weight)
+
+ def _parse_style_font_family(self, tag, value):
+ tag.set_property("family", value)
+
+ def _parse_style_text_align(self, tag, value):
+ try:
+ align = {
+ 'left': gtk.JUSTIFY_LEFT,
+ 'right': gtk.JUSTIFY_RIGHT,
+ 'center': gtk.JUSTIFY_CENTER,
+ 'justify': gtk.JUSTIFY_FILL,
+ } [value]
+ except KeyError:
+ warnings.warn("Invalid text-align:%s requested" % value)
+ else:
+ tag.set_property("justification", align)
+
+ def _parse_style_text_decoration(self, tag, value):
+ if value == "none":
+ tag.set_property("underline", pango.UNDERLINE_NONE)
+ tag.set_property("strikethrough", False)
+ elif value == "underline":
+ tag.set_property("underline", pango.UNDERLINE_SINGLE)
+ tag.set_property("strikethrough", False)
+ elif value == "overline":
+ warnings.warn("text-decoration:overline not implemented")
+ tag.set_property("underline", pango.UNDERLINE_NONE)
+ tag.set_property("strikethrough", False)
+ elif value == "line-through":
+ tag.set_property("underline", pango.UNDERLINE_NONE)
+ tag.set_property("strikethrough", True)
+ elif value == "blink":
+ warnings.warn("text-decoration:blink not implemented")
+ else:
+ warnings.warn("text-decoration:%s not implemented" % value)
+
+
+ ## build a dictionary mapping styles to methods, for greater speed
+ __style_methods = dict()
+ for style in ["background-color", "color", "font-family", "font-size",
+ "font-style", "font-weight", "margin-left", "margin-right",
+ "text-align", "text-decoration"]:
+ try:
+ method = locals()["_parse_style_%s" % style.replace('-', '_')]
+ except KeyError:
+ warnings.warn("Style attribute '%s' not yet implemented" % style)
+ else:
+ __style_methods[style] = method
+
+ def _get_style_tags(self):
+ return [tag for tag in self.styles if tag is not None]
+
+ def _begin_span(self, style, tag=None):
+ if style is None:
+ self.styles.append(tag)
+ return None
+ if tag is None:
+ tag = self.textbuf.create_tag()
+ for attr, val in [item.split(':', 1) for item in style.split(';')]:
+ attr = attr.strip().lower()
+ val = val.strip()
+ try:
+ method = self.__style_methods[attr]
+ except KeyError:
+ warnings.warn("Style attribute '%s' requested "
+ "but not yet implemented" % attr)
+ else:
+ method(self, tag, val)
+ self.styles.append(tag)
+
+ def _end_span(self):
+ self.styles.pop(-1)
+
+ def _insert_text(self, text):
+ tags = self._get_style_tags()
+ if tags:
+ self.textbuf.insert_with_tags(self.iter, text, *tags)
+ else:
+ self.textbuf.insert(self.iter, text)
+
+ def _flush_text(self):
+ if not self.text:
+ return
+ self._insert_text(self.text.replace('\n', ''))
+ self.text = ''
+
+ def _anchor_event(self, tag, textview, event, iter, href, type_):
+ if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1:
+ self.textview.emit("url-clicked", href, type_)
+ return True
+ return False
+
+ def characters(self, content):
+ if allwhitespace_rx.match(content) is not None:
+ return
+ #if self.text:
+ # self.text += ' '
+ self.text += whitespace_rx.sub(' ', content)
+
+ def startElement(self, name, attrs):
+ self._flush_text()
+ try:
+ style = attrs['style']
+ except KeyError:
+ style = None
+
+ tag = None
+ if name == 'a':
+ tag = self.textbuf.create_tag()
+ tag.set_property('foreground', '#0000ff')
+ tag.set_property('underline', pango.UNDERLINE_SINGLE)
+ try:
+ type_ = attrs['type']
+ except KeyError:
+ type_ = None
+ tag.connect('event', self._anchor_event, attrs['href'], type_)
+ tag.is_anchor = True
+
+ self._begin_span(style, tag)
+
+ if name == 'br':
+ pass # handled in endElement
+ elif name == 'p':
+ if not self.iter.starts_line():
+ self._insert_text("\n")
+ elif name == 'div':
+ if not self.iter.starts_line():
+ self._insert_text("\n")
+ elif name == 'span':
+ pass
+ elif name == 'ul':
+ if not self.iter.starts_line():
+ self._insert_text("\n")
+ self.list_counters.insert(0, None)
+ elif name == 'ol':
+ if not self.iter.starts_line():
+ self._insert_text("\n")
+ self.list_counters.insert(0, 0)
+ elif name == 'li':
+ if self.list_counters[0] is None:
+ li_head = unichr(0x2022)
+ else:
+ self.list_counters[0] += 1
+ li_head = "%i." % self.list_counters[0]
+ self.text = ' '*len(self.list_counters)*4 + li_head + ' '
+ elif name == 'img':
+ try:
+ ## Max image size = 10 MB (to try to prevent DoS)
+ mem = urllib2.urlopen(attrs['src']).read(10*1024*1024)
+ ## Caveat: GdkPixbuf is known not to be safe to load
+ ## images from network... this program is now potentially
+ ## hackable ;)
+ loader = gtk.gdk.PixbufLoader()
+ loader.write(mem); loader.close()
+ pixbuf = loader.get_pixbuf()
+ except Exception, ex:
+ pixbuf = None
+ try:
+ alt = attrs['alt']
+ except KeyError:
+ alt = "Broken image"
+ if pixbuf is not None:
+ tags = self._get_style_tags()
+ if tags:
+ tmpmark = self.textbuf.create_mark(None, self.iter, True)
+
+ self.textbuf.insert_pixbuf(self.iter, pixbuf)
+
+ if tags:
+ start = self.textbuf.get_iter_at_mark(tmpmark)
+ for tag in tags:
+ self.textbuf.apply_tag(tag, start, self.iter)
+ self.textbuf.delete_mark(tmpmark)
+ else:
+ self._insert_text("[IMG: %s]" % alt)
+ elif name == 'body':
+ pass
+ elif name == 'a':
+ pass
+ else:
+ warnings.warn("Unhandled element '%s'" % name)
+
+ def endElement(self, name):
+ self._flush_text()
+ if name == 'p':
+ if not self.iter.starts_line():
+ self._insert_text("\n")
+ elif name == 'div':
+ if not self.iter.starts_line():
+ self._insert_text("\n")
+ elif name == 'span':
+ pass
+ elif name == 'br':
+ self._insert_text("\n")
+ elif name == 'ul':
+ self.list_counters.pop()
+ elif name == 'ol':
+ self.list_counters.pop()
+ elif name == 'li':
+ self._insert_text("\n")
+ elif name == 'img':
+ pass
+ elif name == 'body':
+ pass
+ elif name == 'a':
+ pass
+ else:
+ warnings.warn("Unhandled element '%s'" % name)
+ self._end_span()
+
+
+class HtmlTextView(gtk.TextView):
+ __gtype_name__ = 'HtmlTextView'
+ __gsignals__ = {
+ 'url-clicked': (gobject.SIGNAL_RUN_LAST, None, (str, str)), # href, type
+ }
+
+ def __init__(self):
+ gtk.TextView.__init__(self)
+ self.set_wrap_mode(gtk.WRAP_CHAR)
+ self.set_editable(False)
+ self._changed_cursor = False
+ self.connect("motion-notify-event", self.__motion_notify_event)
+ self.connect("leave-notify-event", self.__leave_event)
+ self.connect("enter-notify-event", self.__motion_notify_event)
+ self.set_pixels_above_lines(3)
+ self.set_pixels_below_lines(3)
+
+ def __leave_event(self, widget, event):
+ if self._changed_cursor:
+ window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+ window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+ self._changed_cursor = False
+
+ def __motion_notify_event(self, widget, event):
+ x, y, _ = widget.window.get_pointer()
+ x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
+ tags = widget.get_iter_at_location(x, y).get_tags()
+ for tag in tags:
+ if getattr(tag, 'is_anchor', False):
+ is_over_anchor = True
+ break
+ else:
+ is_over_anchor = False
+ if not self._changed_cursor and is_over_anchor:
+ window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+ window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
+ self._changed_cursor = True
+ elif self._changed_cursor and not is_over_anchor:
+ window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
+ window.set_cursor(gtk.gdk.Cursor(gtk.gdk.XTERM))
+ self._changed_cursor = False
+ return False
+
+ def display_html(self, html):
+ buffer = self.get_buffer()
+ eob = buffer.get_end_iter()
+ ## this works too if libxml2 is not available
+ #parser = xml.sax.make_parser(['drv_libxml2'])
+ parser = xml.sax.make_parser()
+ # parser.setFeature(xml.sax.handler.feature_validation, True)
+ parser.setContentHandler(HtmlHandler(self, eob))
+ #parser.setEntityResolver(HtmlEntityResolver())
+ parser.parse(StringIO(html))
+
+ if not eob.starts_line():
+ buffer.insert(eob, "\n")
+
+if gobject.pygtk_version < (2, 8):
+ gobject.type_register(HtmlTextView)
+
+
+if __name__ == '__main__':
+ htmlview = HtmlTextView()
+ def url_cb(view, url, type_):
+ print "url-clicked", url, type_
+ htmlview.connect("url-clicked", url_cb)
+ htmlview.display_html('<div><span style="color: red; text-decoration:underline">Hello</span><br/>\n'
+ ' <img src="http://images.slashdot.org/topics/topicsoftware.gif"/><br/>\n'
+ ' <span style="font-size: 500%; font-family: serif">World</span>\n'
+ '</div>\n')
+ htmlview.display_html("<br/>")
+ htmlview.display_html("""
+ <p style='font-size:large'>
+ <span style='font-style: italic'>O<span style='font-size:larger'>M</span>G</span>,
+ I&apos;m <span style='color:green'>green</span>
+ with <span style='font-weight: bold'>envy</span>!
+ </p>
+ """)
+ htmlview.display_html("<br/>")
+ htmlview.display_html("""
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <p>As Emerson said in his essay <span style='font-style: italic; background-color:cyan'>Self-Reliance</span>:</p>
+ <p style='margin-left: 5px; margin-right: 2%'>
+ &quot;A foolish consistency is the hobgoblin of little minds.&quot;
+ </p>
+ </body>
+ """)
+ htmlview.display_html("<br/>")
+ htmlview.display_html("""
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <p style='text-align:center'>Hey, are you licensed to <a href='http://www.jabber.org/'>Jabber</a>?</p>
+ <p style='text-align:right'><img src='http://www.jabber.org/images/psa-license.jpg'
+ alt='A License to Jabber'
+ height='261'
+ width='537'/></p>
+ </body>
+ """)
+
+ htmlview.display_html("""
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <ul style='background-color:rgb(120,140,100)'>
+ <li> One </li>
+ <li> Two </li>
+ <li> Three </li>
+ </ul>
+ </body>
+ """)
+ htmlview.display_html("""
+ <body xmlns='http://www.w3.org/1999/xhtml'>
+ <ol>
+ <li> One </li>
+ <li> Two </li>
+ <li> Three </li>
+ </ol>
+ </body>
+ """)
+
+ htmlview.display_html("""
+<p>
+Can we add button to return in project root directory in filemanager ?
+</p>
+ """)
+
+ htmlview.display_html('<body><span style="font-family: monospace">|&#xA0;&#xA0;&#xA0;|</span></body>')
+
+ htmlview.show()
+ sw = gtk.ScrolledWindow()
+ sw.set_property("hscrollbar-policy", gtk.POLICY_AUTOMATIC)
+ sw.set_property("vscrollbar-policy", gtk.POLICY_AUTOMATIC)
+ sw.set_property("border-width", 0)
+ sw.add(htmlview)
+ sw.show()
+ frame = gtk.Frame()
+ frame.set_shadow_type(gtk.SHADOW_IN)
+ frame.show()
+ frame.add(sw)
+ w = gtk.Window()
+ w.add(frame)
+ w.set_default_size(400, 300)
+ w.show_all()
+ w.connect("destroy", lambda w: gtk.main_quit())
+ gtk.main()
diff --git a/static/data/html/help.html b/static/data/html/help.html
index 1358b3d..ed3306a 100644
--- a/static/data/html/help.html
+++ b/static/data/html/help.html
@@ -1,239 +1,220 @@
-<html>
-<head>
-</head>
-<body>
-
-<h1 id="guide-a-toi-de-jouer">Guide pour l'activit&eacute; &laquo; &Agrave; Toi de Jouer &raquo;</h1>
-
-<p>
-Avec l'application &laquo; &Agrave; Toi de Jouer &raquo; je peux <a href="#lire-une-histoire">lire</a>
-et <a href="#creer-une-histoire">cr&eacute;er</a> des histoires.
-</p>
-
+<html><head></head><body>
+<h1 id="guide-a-toi-de-jouer">Guide pour l'activité "A Toi de Jouer"</h1>
+<p>
+Avec l'application "A Toi de Jouer" je peux <a href="#lire-une-histoire">lire</a> et <a href="#creer-une-histoire">créer</a> des histoires.
+</p>
<h2 id="lire-une-histoire">Je lis une histoire</h2>
-
<p>
Pour lire une histoire, je dois ouvrir le Journal :
</p>
-
-<center><img src="img/lire-01-ouvrir-journal.png" alt="J'ouvre l'activit&eacute; Journal"/></center>
-
+</body></html>
+----
+lire-01-ouvrir-journal
+----
+<html><head></head><body>
<p>
-Depuis le Journal, je peux s&eacute;lectionner mon histoire et l'ouvrir avec &laquo; &Agrave; Toi de Jouer &raquo; :
+Depuis le Journal, je peux sélectionner mon histoire et l'ouvrir avec "A Toi de Jouer" :
</p>
-
-<center><img src="img/lire-02-ouvrir.png" alt="J'ouvre mon histoire"/></center>
-
+</body></html>
+----
+lire-02-ouvrir
+----
+<html><head></head><body>
<p>
-Depuis l'&eacute;cran de lecture &laquo; &Agrave; Toi de Jouer &raquo;, je peux lire mon histoire :
+Depuis l'écran de lecture "A Toi de Jouer", je peux lire mon histoire :
</p>
-
-<center><img src="img/lire-03-mon-histoire.png" alt="Je lis mon histoire"/></center>
-
-<a href="#guide-a-toi-de-jouer">Haut^</a>
-
-<h2 id="creer-une-histoire">Je cr&eacute;e une histoire</h2>
-
+</body></html>
+----
+lire-03-mon-histoire
+----
+<html><head></head><body>
+<h2 id="creer-une-histoire">Je crée une histoire</h2>
<p>
-Pour cr&eacute;er mon histoire je dois <a href="#ouvrir-atoidejouer">ouvrir &laquo; &Agrave; Toi de Jouer &raquo;</a>,
-<a href="#ajouter-des-graphiques">ajouter des graphiques</a>,
-<a href="#deplacer-mes-graphiques">d&eacute;placer mes graphiques</a>
-et <a href="#sauver-mon-histoire">sauvegarder mon histoire</a>.
+Pour créer mon histoire je dois <a href="#ouvrir-atoidejouer">ouvrir "A Toi de Jouer"</a>, <a href="#ajouter-des-graphiques">ajouter des graphiques</a>, <a href="#deplacer-mes-graphiques">déplacer mes graphiques</a> et <a href="#sauver-mon-histoire">sauvegarder mon histoire</a>.
</p>
-
-<h3 id="ouvrir-atoidejouer">Ma premi&egrave;re histoire</h3>
-
+<h3 id="ouvrir-atoidejouer">Ma première histoire</h3>
<p>
-Je peux cr&eacute;er une nouvelle histoire en cliquant avec le bouton de droite
-et en choisissant &laquo; commencer nouveau &raquo; dans le menu :
+Je peux créer une nouvelle histoire en cliquant avec le bouton de droite et en choisissant "commencer nouveau" dans le menu :
</p>
-
-<center><img src="img/creer-01-nouveau.png" alt="Je cr&eacute;e mon histoire"/></center>
-
+</body></html>
+----
+creer-01-nouveau
+----
+<html><head></head><body>
<p>
-J'arrive sur l'&eacute;cran d'&eacute;dition graphique d'&laquo; &Agrave; Toi de Jouer &raquo;:
+J'arrive sur l'écran d'édition graphique d'"A Toi de Jouer":
</p>
-
-<center><img src="img/creer-02-ecran.png" alt="L'&eacute;cran d'&eacute;dition graphique"/></center>
-
+</body></html>
+----
+creer-02-ecran
+----
+<html><head></head><body>
<p>
-Je peux maintenant ajouter mes premiers graphiques.
-Pour cela je clique sur le bouton &laquo; Ajouter &raquo;, en haut de l'&eacute;cran, pour aller vers la biblioth&egrave;que
-et choisir mes graphiques :
+Je peux maintenant ajouter mes premiers graphiques. Pour cela je clique sur le bouton "Ajouter", en haut de l'écran, pour aller vers la bibliothèque et choisir mes graphiques :
</p>
-
-<center><img src="img/creer-03-toolbar-ajouter.png" alt="Bouton ajouter"/></center>
-
-<a href="#guide-a-toi-de-jouer">Haut^</a>
-
+</body></html>
+----
+creer-03-toolbar-ajouter
+----
+<html><head></head><body>
<h3 id="ajouter-des-graphiques">J'ajoute des graphiques</h3>
-
-<p>
-Voici l'&eacute;cran qui me permet de cr&eacute;er de nouveaux groupes de graphiques afin de les ajouter &agrave; mon histoire :
-</p>
-
-<center><img src="img/creer-seq-01-ecran.png" alt="Ecran pour ajouter des graphiques"/></center>
-
-<p>
-Tout d'abord je dois cr&eacute;er un nouveau groupe, ou &laquo; s&eacute;quence &raquo;.
-Pour cela je clique sur le bouton &laquo; Nouvelle s&eacute;quence &raquo; en haut &agrave; gauche :
-</p>
-
-<center><img src="img/creer-seq-02-nouveau.png" alt="Je cr&eacute;e une nouvelle s&eacute;quence"/></center>
-
-<p>
-Je peux maintenant indiquer un nom pour ma s&eacute;quence dans le champs texte
-et sauvegarder cette s&eacute;quence en cliquant sur le bouton &laquo; Sauver la s&eacute;quence &raquo; en haut :
-</p>
-
-<center><img src="img/creer-seq-03-sauver.png" alt="Je sauve ma nouvelle s&eacute;quence"/></center>
-
<p>
-Les images disponibles pour ma s&eacute;quence sont dans l'onglet &laquo; Biblioth&egrave;que &raquo;.
-Je peux cliquer sur le bouton &laquo; Biblioth&egrave;que &raquo; pour voir toutes les images que je peux ajouter :
+Voici l'écran qui me permet de créer de nouveaux groupes de graphiques afin de les ajouter à mon histoire :
</p>
-
-<center><img src="img/creer-seq-04-panel-go-bibliotheque.png" alt="Je vais dans ma biblioth&egrave;que"/></center>
-
+</body></html>
+----
+creer-seq-01-ecran
+----
+<html><head></head><body>
<p>
-Dans la liste des images, je peux cliquer sur le bouton &laquo; + &raquo; d'une image pour l'ajouter &agrave; ma s&eacute;quence :
+Tout d'abord je dois créer un nouveau groupe, ou "séquence". Pour cela je clique sur le bouton "Nouvelle séquence" en haut à gauche :
</p>
-
-<center><img src="img/creer-seq-05-ajout-image.png" alt="J'ajoute une image dans ma s&eacute;quence "/></center>
-
+</body></html>
+----
+creer-seq-02-nouveau
+----
+<html><head></head><body>
<p>
-Si j'ai fait une erreur, je peux supprimer une image de ma s&eacute;quence
-en cliquant, en bas &agrave; droite, sur le bouton &laquo; - &raquo; &agrave; cot&eacute; de l'image :
+Je peux maintenant indiquer un nom pour ma séquence dans le champs texte et sauvegarder cette séquence en cliquant sur le bouton "Sauver la séquence" en haut :
</p>
-
-<center><img src="img/creer-seq-06-retirer-image.png" alt="Retirer une image dans ma s&eacute;quence"/></center>
-
+</body></html>
+----
+creer-seq-03-sauver
+----
+<html><head></head><body>
<p>
-Une fois que ma s&eacute;quence est pr&egrave;te, je peux l'ajouter &agrave; mon histoire
-en cliquant sur le bouton &laquo; Ajouter s&eacute;quence &raquo; en haut &agrave; droite
-et <a href="#ajouter-des-graphiques">pr&eacute;parer une autre s&eacute;quence</a> :
+Les images disponibles pour ma séquence sont dans l'onglet "Bibliothèque". Je peux cliquer sur le bouton "Bibliothèque" pour voir toutes les images que je peux ajouter :
</p>
-
-<center><img src="img/creer-seq-07-importer.png" alt="J'importe ma dans mon histoire"/></center>
-
+</body></html>
+----
+creer-seq-04-panel-go-bibliotheque
+----
+<html><head></head><body>
<p>
-Quand j'ai ajout&eacute; toutes mes s&eacute;quences &agrave; l'histoire,
-je peux retourner vers l'&eacute;cran d'&eacute;dition pour <a href="#deplacer-mes-graphiques">d&eacute;placer mes graphiques</a> avec le bouton &laquo; Retour &raquo; :
+Dans la liste des images, je peux cliquer sur le bouton "+" d'une image pour l'ajouter à ma séquence :
</p>
-
-<center><img src="img/creer-seq-08-retour.png" alt="Je retourne &agrave; l'&eacute;dition"/></center>
-
-<a href="#guide-a-toi-de-jouer">Haut^</a>
-
-<h3 id="deplacer-mes-graphiques">Je d&eacute;place mes graphiques</h3>
-
+</body></html>
+----
+creer-seq-05-ajout-image
+----
+<html><head></head><body>
<p>
-De retour &agrave; l'&eacute;cran d'&eacute;dition, on ne voit pas encore d'image : pour faire appara&icirc;tre ma premi&egrave;re image,
-je dois d'abord s&eacute;lectionner la s&eacute;quence &agrave; laquelle elle appartient en bas &agrave; droite :
+Si j'ai fait une erreur, je peux supprimer une image de ma séquence en cliquant, en bas à droite, sur le bouton "-" à coté de l'image :
</p>
-
-<center><img src="img/creer-04-timeline-select-1.png" alt="Je s&eacute;lectionne une s&eacute;quence"/></center>
-
+</body></html>
+----
+creer-seq-06-retirer-image
+----
+<html><head></head><body>
<p>
-Pour pr&eacute;parer mon animation, je peux ajouter des secondes
-en cliquant sur le bouton &laquo; Afficher apr&egrave;s 10 secondes &raquo;:
+Une fois que ma séquence est prète, je peux l'ajouter à mon histoire en cliquant sur le bouton "Ajouter séquence" en haut à droite et <a href="#ajouter-des-graphiques">préparer une autre séquence</a> :
</p>
-
-<center><img src="img/creer-05-ajout-temps-1.png" alt="J'ajoute du temps &agrave; mon histoire"/></center>
-
+</body></html>
+----
+creer-seq-07-importer
+----
+<html><head></head><body>
<p>
-Je peux v&eacute;rifier que j'ai de nouvelles secondes dans ma barre de temps
-et choisir une seconde pour faire appara&icirc;tre mon image :
+Quand j'ai ajouté toutes mes séquences à l'histoire, je peux retourner vers l'écran d'édition pour <a href="#deplacer-mes-graphiques">déplacer mes graphiques</a> avec le bouton "Retour" :
</p>
-
-<center><img src="img/creer-06-ajout-temps-2.png" alt="Le temps ajout&eacute;"/></center>
-
+</body></html>
+----
+creer-seq-08-retour
+----
+<html><head></head><body>
+<h3 id="deplacer-mes-graphiques">Je déplace mes graphiques</h3>
<p>
-Je peux maintenant s&eacute;lectionner un graphique &agrave; afficher pour cette seconde:
+De retour à l'écran d'édition, on ne voit pas encore d'image : pour faire apparaître ma première image, je dois d'abord sélectionner la séquence à laquelle elle appartient en bas à droite :
</p>
-
-<center><img src="img/creer-07-panel-image-1.png" alt="J'affiche ma premi&egrave;re image"/></center>
-
+</body></html>
+----
+creer-04-timeline-select-1
+----
+<html><head></head><body>
<p>
-Pour mon animation, je s&eacute;lectionne une autre seconde pour voir mon
-image :
-<em>Entre les 2 secondes, l'image restera affich&eacute;e</em>
+Pour préparer mon animation, je peux ajouter des secondes en cliquant sur le bouton "Afficher après 10 secondes":
</p>
-
-<center><img src="img/creer-08-timeline-select-3.png" alt="Je s&eacute;lectionne d'autres secondes"/></center>
-
+</body></html>
+----
+creer-05-ajout-temps-1
+----
+<html><head></head><body>
<p>
-Je peux choisir une autre image &agrave; afficher plus tard dans mon
-histoire :
+Je peux vérifier que j'ai de nouvelles secondes dans ma barre de temps et choisir une seconde pour faire apparaître mon image :
</p>
-
-<center><img src="img/creer-09-panel-image-2.png" alt="Je s&eacute;lectionne une autre image"/></center>
-
+</body></html>
+----
+creer-06-ajout-temps-2
+----
+<html><head></head><body>
<p>
-Quand j'ai ajout&eacute; mon image, elle peut &ecirc;tre cach&eacute;e par une autre.
-Je peux la faire passer par devant en cliquant sur le bouton &laquo; Calque
-devant &raquo; :
+Je peux maintenant sélectionner un graphique à afficher pour cette seconde:
</p>
-
-<center><img src="img/creer-10-panel-layer-1.png" alt="Mon image est cach&eacute;e"/></center>
-
+</body></html>
+----
+creer-07-panel-image-1
+----
+<html><head></head><body>
<p>
-Maintenant mon image appara&icirc;t devant :
+Pour mon animation, je sélectionne une autre seconde pour voir mon image : <em>Entre les 2 secondes, l'image restera affichée</em>
</p>
-
-<center><img src="img/creer-11-panel-layer-2.png" alt="Mon image est devant"/></center>
-
+</body></html>
+----
+creer-08-timeline-select-3
+----
+<html><head></head><body>
<p>
-Pour animer une image entre 2 secondes, je peux choisir une seconde au d&eacute;but de la ligne de temps
-et placer mon image &agrave; son point de d&eacute;part :
+Je peux choisir une autre image à afficher plus tard dans mon histoire :
</p>
-
-<center><img src="img/creer-12-scene-move-1.png" alt="Je place mon image au d&eacute;part"/></center>
-
+</body></html>
+----
+creer-09-panel-image-2
+----
+<html><head></head><body>
<p>
-Ensuite je s&eacute;lection une seconde pour la fin de l'animation
-et je fais glisser l'image &agrave; sa position d'arriv&eacute;e :
+Quand j'ai ajouté mon image, elle peut être cachée par une autre. Je peux la faire passer par devant en cliquant sur le bouton "Calque devant" :
</p>
-
-<center><img src="img/creer-13-scene-move-2.png" alt="Je d&eacute;place mon image &agrave; son arriv&eacute;e"/></center>
-
+</body></html>
+----
+creer-10-panel-layer-1
+----
+<html><head></head><body>
<p>
-Pour regarder le r&eacute;sultat de mon animation, je peux lire mon histoire
-en cliquant sur le bouton &laquo; Jouer l'histoire &raquo; en haut &agrave; gauche :
+Maintenant mon image apparaît devant :
</p>
-
-<center><img src="img/creer-14-lecture-2.png" alt="Je visualise mon animation"/></center>
-
+</body></html>
+----
+creer-11-panel-layer-2
+----
+<html><head></head><body>
<p>
-Pour <a href="#sauver-mon-histoire">sauvegarder mon histoire</a>,
-je dois aller dans l'&eacute;cran &laquo; Activit&eacute; &raquo;
-en cliquant sur le bouton en haut &agrave; gauche :
+Pour animer une image entre 2 secondes, je peux choisir une seconde au début de la ligne de temps et placer mon image à son point de départ :
</p>
-
-<center><img src="img/creer-15-toolbar-go-activity.png" alt="Je vais dans l'&eacute;cran &laquo; Activit&eacute; &raquo;"/></center>
-
-<a href="#guide-a-toi-de-jouer">Haut^</a>
-
-<h3 id="sauver-mon-histoire">Je sauvegarde mon histoire</h3>
-
+</body></html>
+----
+creer-12-scene-move-1
+----
+<html><head></head><body>
<p>
-Dans l'&eacute;cran activit&eacute; je peux choisir un nom pour mon histoire dans le champs texte en haut &agrave; gauche:
+Ensuite je sélection une seconde pour la fin de l'animation et je fais glisser l'image à sa position d'arrivée :
</p>
-
-<center><img src="img/activity-01-nom-histoire.png" alt="Je choisis un nome pour mon histoire"/></center>
-
+</body></html>
+----
+creer-13-scene-move-2
+----
+<html><head></head><body>
<p>
-Enfin je peux cliquer sur l'ic&ocirc;ne &laquo; Journal &raquo; pour faire la
-sauvegarde :
+Pour regarder le résultat de mon animation, je peux lire mon histoire en cliquant sur le bouton "Jouer l'histoire" en haut à gauche :
</p>
-
-<center><img src="img/activity-02-sauver.png" alt="Je sauvegarde mon histoire"/></center>
-
+</body></html>
+----
+creer-14-lecture-2
+----
+<html><head></head><body>
<p>
-Maintenant, je pourrai <a href="#lire-une-histoire">la lire plus tard depuis mon &laquo; Journal &raquo;</a>.
+Pour <a href="#sauver-mon-histoire">sauvegarder mon histoire</a>, je dois aller dans l'écran "Activité" en cliquant sur le bouton en haut à gauche :
</p>
-
-<a href="#guide-a-toi-de-jouer">Haut^</a>
-
</body>
</html>
+----
+creer-15-toolbar-go-activity