From b19c7aae01240138101f2dba6a073586602114df Mon Sep 17 00:00:00 2001 From: Florent Pigout Date: Mon, 13 Feb 2012 01:39:10 +0000 Subject: new generic to display `html` content in a sugar app instead using hulahop --- 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('
Hello
\n' + '
\n' + ' World\n' + '
\n') + htmlview.display_html("
") + htmlview.display_html(""" +

+ OMG, + I'm green + with envy! +

+ """) + htmlview.display_html("
") + htmlview.display_html(""" + +

As Emerson said in his essay Self-Reliance:

+

+ "A foolish consistency is the hobgoblin of little minds." +

+ + """) + htmlview.display_html("
") + htmlview.display_html(""" + +

Hey, are you licensed to Jabber?

+

A License to Jabber

+ + """) + + htmlview.display_html(""" + + + + """) + htmlview.display_html(""" + +
    +
  1. One
  2. +
  3. Two
  4. +
  5. Three
  6. +
+ + """) + + htmlview.display_html(""" +

+Can we add button to return in project root directory in filemanager ? +

+ """) + + htmlview.display_html('|   |') + + 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 @@ - - - - - -

Guide pour l'activité « À Toi de Jouer »

- -

-Avec l'application « À Toi de Jouer » je peux lire -et créer des histoires. -

- + +

Guide pour l'activité "A Toi de Jouer"

+

+Avec l'application "A Toi de Jouer" je peux lire et créer des histoires. +

Je lis une histoire

-

Pour lire une histoire, je dois ouvrir le Journal :

- -
J'ouvre l'activité Journal
- + +---- +lire-01-ouvrir-journal +---- +

-Depuis le Journal, je peux sélectionner mon histoire et l'ouvrir avec « À Toi de Jouer » : +Depuis le Journal, je peux sélectionner mon histoire et l'ouvrir avec "A Toi de Jouer" :

- -
J'ouvre mon histoire
- + +---- +lire-02-ouvrir +---- +

-Depuis l'écran de lecture « À Toi de Jouer », je peux lire mon histoire : +Depuis l'écran de lecture "A Toi de Jouer", je peux lire mon histoire :

- -
Je lis mon histoire
- -Haut^ - -

Je crée une histoire

- + +---- +lire-03-mon-histoire +---- + +

Je crée une histoire

-Pour créer mon histoire je dois ouvrir « À Toi de Jouer », -ajouter des graphiques, -déplacer mes graphiques -et sauvegarder mon histoire. +Pour créer mon histoire je dois ouvrir "A Toi de Jouer", ajouter des graphiques, déplacer mes graphiques et sauvegarder mon histoire.

- -

Ma première histoire

- +

Ma première histoire

-Je peux créer une nouvelle histoire en cliquant avec le bouton de droite -et en choisissant « commencer nouveau » 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 :

- -
Je crée mon histoire
- + +---- +creer-01-nouveau +---- +

-J'arrive sur l'écran d'édition graphique d'« À Toi de Jouer »: +J'arrive sur l'écran d'édition graphique d'"A Toi de Jouer":

- -
L'écran d'édition graphique
- + +---- +creer-02-ecran +---- +

-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 : +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 :

- -
Bouton ajouter
- -Haut^ - + +---- +creer-03-toolbar-ajouter +---- +

J'ajoute des graphiques

- -

-Voici l'écran qui me permet de créer de nouveaux groupes de graphiques afin de les ajouter à mon histoire : -

- -
Ecran pour ajouter des graphiques
- -

-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 : -

- -
Je crée une nouvelle séquence
- -

-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 : -

- -
Je sauve ma nouvelle séquence
-

-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 : +Voici l'écran qui me permet de créer de nouveaux groupes de graphiques afin de les ajouter à mon histoire :

- -
Je vais dans ma bibliothèque
- + +---- +creer-seq-01-ecran +---- +

-Dans la liste des images, je peux cliquer sur le bouton « + » d'une image pour l'ajouter à ma sé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 :

- -
J'ajoute une image dans ma séquence
- + +---- +creer-seq-02-nouveau +---- +

-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 : +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 :

- -
Retirer une image dans ma séquence
- + +---- +creer-seq-03-sauver +---- +

-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 préparer une autre séquence : +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 :

- -
J'importe ma dans mon histoire
- + +---- +creer-seq-04-panel-go-bibliotheque +---- +

-Quand j'ai ajouté toutes mes séquences à l'histoire, -je peux retourner vers l'écran d'édition pour déplacer mes graphiques avec le bouton « Retour » : +Dans la liste des images, je peux cliquer sur le bouton "+" d'une image pour l'ajouter à ma séquence :

- -
Je retourne à l'édition
- -Haut^ - -

Je déplace mes graphiques

- + +---- +creer-seq-05-ajout-image +---- +

-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 : +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 :

- -
Je sélectionne une séquence
- + +---- +creer-seq-06-retirer-image +---- +

-Pour préparer mon animation, je peux ajouter des secondes -en cliquant sur le bouton « Afficher après 10 secondes »: +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 préparer une autre séquence :

- -
J'ajoute du temps à mon histoire
- + +---- +creer-seq-07-importer +---- +

-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 : +Quand j'ai ajouté toutes mes séquences à l'histoire, je peux retourner vers l'écran d'édition pour déplacer mes graphiques avec le bouton "Retour" :

- -
Le temps ajouté
- + +---- +creer-seq-08-retour +---- + +

Je déplace mes graphiques

-Je peux maintenant sélectionner un graphique à 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 :

- -
J'affiche ma première image
- + +---- +creer-04-timeline-select-1 +---- +

-Pour mon animation, je sélectionne une autre seconde pour voir mon -image : -Entre les 2 secondes, l'image restera affichée +Pour préparer mon animation, je peux ajouter des secondes en cliquant sur le bouton "Afficher après 10 secondes":

- -
Je sélectionne d'autres secondes
- + +---- +creer-05-ajout-temps-1 +---- +

-Je peux choisir une autre image à 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 :

- -
Je sélectionne une autre image
- + +---- +creer-06-ajout-temps-2 +---- +

-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 » : +Je peux maintenant sélectionner un graphique à afficher pour cette seconde:

- -
Mon image est cachée
- + +---- +creer-07-panel-image-1 +---- +

-Maintenant mon image apparaît devant : +Pour mon animation, je sélectionne une autre seconde pour voir mon image : Entre les 2 secondes, l'image restera affichée

- -
Mon image est devant
- + +---- +creer-08-timeline-select-3 +---- +

-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 : +Je peux choisir une autre image à afficher plus tard dans mon histoire :

- -
Je place mon image au départ
- + +---- +creer-09-panel-image-2 +---- +

-Ensuite je sélection une seconde pour la fin de l'animation -et je fais glisser l'image à sa position d'arrivé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" :

- -
Je déplace mon image à son arrivée
- + +---- +creer-10-panel-layer-1 +---- +

-Pour regarder le résultat de mon animation, je peux lire mon histoire -en cliquant sur le bouton « Jouer l'histoire » en haut à gauche : +Maintenant mon image apparaît devant :

- -
Je visualise mon animation
- + +---- +creer-11-panel-layer-2 +---- +

-Pour sauvegarder mon histoire, -je dois aller dans l'écran « Activité » -en cliquant sur le bouton en haut à 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 :

- -
Je vais dans l'écran « Activité »
- -Haut^ - -

Je sauvegarde mon histoire

- + +---- +creer-12-scene-move-1 +---- +

-Dans l'écran activité je peux choisir un nom pour mon histoire dans le champs texte en haut à gauche: +Ensuite je sélection une seconde pour la fin de l'animation et je fais glisser l'image à sa position d'arrivée :

- -
Je choisis un nome pour mon histoire
- + +---- +creer-13-scene-move-2 +---- +

-Enfin je peux cliquer sur l'icône « Journal » 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 :

- -
Je sauvegarde mon histoire
- + +---- +creer-14-lecture-2 +---- +

-Maintenant, je pourrai la lire plus tard depuis mon « Journal ». +Pour sauvegarder mon histoire, je dois aller dans l'écran "Activité" en cliquant sur le bouton en haut à gauche :

- -Haut^ - +---- +creer-15-toolbar-go-activity -- cgit v0.9.1