diff --git a/examples/tutorials/tutor.py b/examples/tutorials/tutor.py index 713fc1dad..52170949d 100644 --- a/examples/tutorials/tutor.py +++ b/examples/tutorials/tutor.py @@ -20,7 +20,7 @@ import os import sys -from traitsui.extras.tutor import Tutor +from traitsui.extras.demo import demo # Correct program usage information: usage = """ @@ -31,33 +31,22 @@ If omitted, 'root_dir' defaults to the current directory.""" -def main(home_dir, root_dir): +def main(root_dir): # Create a tutor and display the tutorial: - tutor = Tutor(home=os.path.dirname(home_dir)).trait_set( - path=root_dir) - if tutor.root is not None: - tutor.configure_traits() - else: - raise NameError("No traits tutorial found in %s" % root_dir) + path, name = os.path.splitext(root_dir) + demo(dir_name=root_dir) if __name__ == '__main__': # Validate the command line arguments: - if len(sys.argv) > 2: + if len(sys.argv) != 2: print(usage) sys.exit(1) - home_dir = os.path.dirname(sys.argv[0]) - - # Determine the root path to use for the tutorial files: - if len(sys.argv) == 2: - root_dir = sys.argv[1] - else: - root_dir = os.getcwd() - + root_dir = sys.argv[1] try: - main(home_dir, root_dir) + main(root_dir) except NameError as e: print(e) print(usage) diff --git a/traitsui/extras/demo.py b/traitsui/extras/demo.py index 7bee831d2..81bdb7052 100644 --- a/traitsui/extras/demo.py +++ b/traitsui/extras/demo.py @@ -21,8 +21,8 @@ from __future__ import absolute_import +import contextlib import glob -import io from io import StringIO import operator from os import listdir @@ -45,9 +45,11 @@ from configobj import ConfigObj +from pyface.api import ImageResource from traits.api import ( Any, Bool, + Button, cached_property, Code, Dict, @@ -59,9 +61,11 @@ Str, ) from traitsui.api import ( + CodeEditor, Handler, Heading, HGroup, + HSplit, HTMLEditor, Include, InstanceEditor, @@ -69,11 +73,14 @@ ObjectTreeNode, spring, Tabbed, + TitleEditor, TreeEditor, TreeNodeObject, UIInfo, + UItem, VGroup, - View + View, + VSplit, ) @@ -151,8 +158,7 @@ def parse_source(file_name): The source code, sans docstring. """ try: - with io.open(file_name, "r", encoding="utf-8") as fh: - source_code = fh.read() + source_code = _read_file(file_name) return extract_docstring_from_source(source_code) except Exception: # Print an error message instead of failing silently. @@ -163,10 +169,27 @@ def parse_source(file_name): return (error_msg, "") +def _read_file(path, mode='r', encoding='utf8'): + """ Returns the contents of a specified text file. + """ + with open(path, mode, encoding=encoding) as fh: + result = fh.read() + return result + + # ------------------------------------------------------------------------- # 'DemoFileHandler' class: # ------------------------------------------------------------------------- +@contextlib.contextmanager +def _set_stdout(std_out): + stdout, stderr = sys.stdout, sys.stderr + try: + sys.stdout = sys.stderr = std_out + yield std_out + finally: + sys.stdout, sys.stderr = stdout, stderr + class DemoFileHandler(Handler): @@ -174,80 +197,38 @@ class DemoFileHandler(Handler): # Trait definitions: # ------------------------------------------------------------------------- + #: Run the demo file + run_button = Button(image=ImageResource("run"), label="Run") + #: The current 'info' object (for use by the 'write' method): info = Instance(UIInfo) + def _run_button_changed(self): + demo_file = self.info.object + with _set_stdout(self): + demo_file.run_code() + def init(self, info): # Save the reference to the current 'info' object: self.info = info - - # Set up the 'print' logger: - df = info.object - df.log = "" - sys.stdout = sys.stderr = self - - # Read in the demo source file: - description, source = parse_source(df.path) - df.description = publish_html_str(description) - df.source = source - # Try to run the demo source file: - - # Append the path for the demo source file to sys.path, so as to - # resolve any local (relative) imports in the demo source file. - sys.path.append(dirname(df.path)) - - locals = df.parent.init_dic - locals["__name__"] = "___main___" - locals["__file__"] = df.path - sys.modules["__main__"].__file__ = df.path - try: - with io.open(df.path, "r", encoding="utf-8") as fp: - exec(compile(fp.read(), df.path, "exec"), locals, locals) - demo = self._get_object("modal_popup", locals) - if demo is not None: - demo = ModalDemoButton(demo=demo) - else: - demo = self._get_object("popup", locals) - if demo is not None: - demo = DemoButton(demo=demo) - else: - demo = self._get_object("demo", locals) - except Exception as excp: - demo = DemoError(msg=str(excp)) - - # Clean up sys.path - sys.path.remove(dirname(df.path)) - df.demo = demo + demo_file = info.object + with _set_stdout(self): + demo_file.init() def closed(self, info, is_ok): """ Closes the view. """ - info.object.demo = None - - # ------------------------------------------------------------------------- - # Get a specified object from the execution dictionary: - # ------------------------------------------------------------------------- - - def _get_object(self, name, dic): - object = dic.get(name) or dic.get(name.capitalize()) - if object is not None: - if isinstance(type(object), type): - try: - object = object() - except Exception: - pass - - if isinstance(object, HasTraits): - return object - - return None + demo_file = info.object + if hasattr(demo_file, 'demo'): + demo_file.demo = None # ------------------------------------------------------------------------- # Handles 'print' output: # ------------------------------------------------------------------------- def write(self, text): - self.info.object.log += text + demo_file = self.info.object + demo_file.log += text def flush(self): pass @@ -379,18 +360,13 @@ def has_children(self, node): # Gets the object's children: # ------------------------------------------------------------------------- - def get_children(self, node): + def get_children(self): """ Gets the object's children. """ raise NotImplementedError -class DemoFile(DemoTreeNodeObject): - - # ------------------------------------------------------------------------- - # Trait definitions: - # ------------------------------------------------------------------------- - +class DemoFileBase(DemoTreeNodeObject): #: Parent of this file: parent = Any @@ -409,22 +385,20 @@ class DemoFile(DemoTreeNodeObject): #: Description of what the demo does: description = HTML - #: Source code for the demo: - source = Code - - #: Demo object whose traits UI is to be displayed: - demo = Instance(HasTraits) - #: Log of all print messages displayed: log = Code _nice_name = Str + + def init(self): + self.log = "" + # ------------------------------------------------------------------------- # Implementation of the 'path' property: # ------------------------------------------------------------------------- def _get_path(self): - return join(self.parent.path, self.name + ".py") + return join(self.parent.path, self.name) # ------------------------------------------------------------------------- # Implementation of the 'nice_name' property: @@ -432,7 +406,8 @@ def _get_path(self): def _get_nice_name(self): if not self._nice_name: - self._nice_name = user_name_for(self.name) + name, ext = splitext(self.name) + self._nice_name = user_name_for(name) return self._nice_name def _set_nice_name(self, value): @@ -449,6 +424,95 @@ def has_children(self): """ return False + def get_children(self): + """ Gets the demo file's children. + """ + return [] + + +class DemoFile(DemoFileBase): + + #: Source code for the demo: + source = Code + + #: Demo object whose traits UI is to be displayed: + demo = Instance(HasTraits) + + def init(self): + super(DemoFile, self).init() + description, source = parse_source(self.path) + self.description = publish_html_str(description) + self.source = source + self.run_code() + + def run_code(self): + """ Runs the code associated with this demo file. + """ + try: + # Get the execution context dictionary: + locals = self.parent.init_dic + locals["__name__"] = "___main___" + locals["__file__"] = self.path + sys.modules["__main__"].__file__ = self.path + + exec(self.source, locals, locals) + + demo = self._get_object("modal_popup", locals) + if demo is not None: + demo = ModalDemoButton(demo=demo) + else: + demo = self._get_object("popup", locals) + if demo is not None: + demo = DemoButton(demo=demo) + else: + demo = self._get_object("demo", locals) + except Exception: + traceback.print_exc() + else: + self.demo = demo + + # ------------------------------------------------------------------------- + # Get a specified object from the execution dictionary: + # ------------------------------------------------------------------------- + + def _get_object(self, name, dic): + object = dic.get(name) or dic.get(name.capitalize()) + if object is not None: + if isinstance(type(object), type): + try: + object = object() + except Exception: + pass + + if isinstance(object, HasTraits): + return object + + return None + + +# HTML template for displaying an image file: +_image_template = """ + + + + + + +""" + + +class DemoContentFile(DemoFileBase): + def init(self): + super(DemoContentFile, self).init() + file_str = _read_file(self.path) + self.description = publish_html_str(file_str) + + +class DemoImageFile(DemoFileBase): + def init(self): + super(DemoImageFile, self).init() + self.description = _image_template.format(self.path) + class DemoPath(DemoTreeNodeObject): @@ -496,12 +560,32 @@ class DemoPath(DemoTreeNodeObject): #: Cached value of the nice_name property. _nice_name = Str + #: Dictionary mapping file extensions to callables + _file_factory = Dict + + def __file_factory_default(self): + return { + ".htm": DemoContentFile, + ".html": DemoContentFile, + ".jpeg": DemoImageFile, + ".jpg": DemoImageFile, + ".png": DemoImageFile, + ".py": DemoFile, + ".rst": DemoContentFile, + ".txt": DemoContentFile + } + # ------------------------------------------------------------------------- # Implementation of the 'path' property: # ------------------------------------------------------------------------- def _get_path(self): - return join(self.parent.path, self.name) + if self.parent is not None: + path = join(self.parent.path, self.name) + else: + path = self.name + + return path # ------------------------------------------------------------------------- # Implementation of the 'nice_name' property: @@ -586,6 +670,8 @@ def has_children(self): name, ext = splitext(name) if (ext == ".py") and (name != "__init__"): return True + elif ext in self._file_factory: + return True return False @@ -617,11 +703,13 @@ def get_children_from_datastructure(self): if isdir(cur_path): if self.has_py_files(cur_path): dirs.append(DemoPath(parent=self, name=name)) - elif self.use_files: - name, ext = splitext(name) - if (ext == ".py") and (name != "__init__"): - files.append(DemoFile(parent=self, name=name)) + if name != "__init__.py": + try: + demo_file = self._handle_file(name) + files.append(demo_file) + except KeyError: + pass sort_key = operator.attrgetter("name") dirs.sort(key=sort_key) @@ -668,25 +756,22 @@ def get_children_from_config(self): for filename in filenames: filename = join(self.path, filename) for name in glob.iglob(filename): - pathname, ext = splitext(name) - if (ext == ".py") and ( - basename(pathname) != "__init__" - ): - names.append(pathname) + if basename(name) != "__init__.py": + names.append(name) if len(names) > 1: config_dict = {} for name in names: - config_dict[basename(name)] = { - "files": name + ".py" - } + config_dict[basename(name)] = {"files": name} demoobj = DemoPath(parent=self, name="") demoobj.nice_name = keyword demoobj.config_dict = config_dict dirs.append(demoobj) elif len(names) == 1: - file = DemoFile(parent=self, name=names[0]) - file.nice_name = keyword - files.append(file) + try: + demo_file = self._handle_file(name) + files.append(demo_file) + except KeyError: + pass sort_key = operator.attrgetter("nice_name") dirs.sort(key=sort_key) @@ -712,62 +797,109 @@ def has_py_files(self, path): return False + def _handle_file(self, filename): + """ Process a file based on its extension. + """ + _, ext = splitext(filename) + file_factory = self._file_factory[ext] + demo_file = file_factory(parent=self, name=filename) + return demo_file # ------------------------------------------------------------------------- # Defines the demo tree editor: # ------------------------------------------------------------------------- -path_view = View( +demo_path_view = View( Tabbed( - Item( + UItem( "description", - label="Description", - show_label=False, style="readonly", editor=HTMLEditor(format_text=True), ), - Item("source", label="Source", show_label=False, style="custom"), - export="DockWindowShell", - id="tabbed", + UItem("source", style="custom"), + ), + id="demo_path_view", +) + +demo_file_view = View( + HSplit( + Tabbed( + UItem( + "description", + style="readonly", + editor=HTMLEditor(format_text=True), + ), + ), + VSplit( + VGroup( + Tabbed( + UItem("source", style="custom"), + ), + UItem( + "handler.run_button", + visible_when="source is not None" + ), + ), + Tabbed( + Item( + "log", + style="readonly", + editor=CodeEditor( + show_line_numbers=False, + selected_color=0xFFFFFF + ), + label="Output", + show_label=False + ), + UItem( + "demo", + style="custom", + resizable=True, + ), + ), + dock="horizontal" + ), ), - id="traitsui.demos.demo.path_view", - # dock = 'horizontal' + id="demo_file_view", + handler=demo_file_handler, ) -demo_view = View( +demo_content_view = View( Tabbed( - Item( + UItem( "description", - label="Description", - show_label=False, style="readonly", editor=HTMLEditor(format_text=True), ), - Item("source", label="Source", show_label=False, style="custom"), - Item( - "demo", - label="Demo", - show_label=False, - style="custom", - resizable=True, - # FIXME: - # visible_when doesn't work correctly yet (for wx atleast) - # for tabbed items. Needs more investigation. - visible_when="demo", - ), - Item("log", show_label=False, style="readonly"), - export="DockWindowShell", - id="tabbed", ), - id="traitsui.demos.demo.file_view", handler=demo_file_handler, ) + demo_tree_editor = TreeEditor( nodes=[ - ObjectTreeNode(node_for=[DemoPath], label="nice_name", view=path_view), - ObjectTreeNode(node_for=[DemoFile], label="nice_name", view=demo_view), - ] + ObjectTreeNode( + node_for=[DemoPath], + label="nice_name", + view=demo_path_view + ), + ObjectTreeNode( + node_for=[DemoFile], + label="nice_name", + view=demo_file_view + ), + ObjectTreeNode( + node_for=[DemoContentFile], + label="nice_name", + view=demo_content_view + ), + ObjectTreeNode( + node_for=[DemoImageFile], + label="nice_name", + view=demo_content_view + ), + ], + selected='selected_node' ) @@ -777,15 +909,79 @@ class Demo(HasPrivateTraits): # Trait definitions: # ------------------------------------------------------------------------- + #: Navifate to next node. + next_button = Button(image=ImageResource("next"), label="Next") + + #: Navigate to parent of selected node. + parent_button = Button(image=ImageResource("parent"), label="Parent") + + #: Navigate to previous node. + previous_button = Button(image=ImageResource("previous"), label="Previous") + #: Path to the root demo directory: path = Str #: Root path object for locating demo files: root = Instance(DemoPath) + #: Selected node of the demo path tree. + selected_node = Any + #: Title for the demo title = Str + _next_node = Property + + _previous_node = Property + + def _get__next_node(self): + next = None + node = self.selected_node + children = node.tno_get_children(node) + + if len(children) > 0: + next = children[0] + else: + parent = node.parent + while parent is not None: + siblings = parent.tno_get_children(parent) + index = siblings.index(node) + if index < (len(siblings) - 1): + next = siblings[index + 1] + break + + parent, node = parent.parent, parent + + return next + + def _get__previous_node(self): + previous = None + node = self.selected_node + parent = node.parent + if parent is not None: + siblings = parent.tno_get_children(parent) + index = siblings.index(node) + if index > 0: + previous = siblings[index - 1] + previous_children = previous.tno_get_children(previous) + while len(previous_children) > 0: + previous = previous_children[-1] + else: + previous = parent + + return previous + + def _next_button_changed(self): + self.selected_node = self._next_node + + def _parent_button_changed(self): + if self.selected_node is not None: + parent = self.selected_node.parent + self.selected_node = parent + + def _previous_button_changed(self): + self.selected_node = self._previous_node + # ------------------------------------------------------------------------- # Traits view definitions: # ------------------------------------------------------------------------- @@ -794,6 +990,33 @@ def default_traits_view(self): """ Constructs the default traits view.""" traits_view = View( + HGroup( + UItem( + "previous_button", + style="custom", + enabled_when="_previous_node is not None", + tooltip="Go to previous file" + ), + UItem( + "parent_button", + style="custom", + enabled_when="(selected_node is not None) and " + "(object.selected_node.parent is not None)", + tooltip="Go up one level" + ), + UItem( + "title", + springy=True, + editor=TitleEditor() + ), + UItem( + "next_button", + style="custom", + enabled_when="_next_node is not None", + tooltip="Go to next file" + ), + "_", + ), Item( name="root", id="root", @@ -808,15 +1031,6 @@ def default_traits_view(self): ) return traits_view - # ------------------------------------------------------------------------- - # Handles the 'root' trait being changed: - # ------------------------------------------------------------------------- - - def _root_changed(self, root): - """ Handles the 'root' trait being changed. - """ - root.parent = self - # ------------------------------------------------------------------------- # Utilities to convert rst strings/files to html. @@ -918,6 +1132,9 @@ def demo( path=path, title=title, root=DemoPath( - name=name, use_files=use_files, config_filename=config_filename + name=dir_name, + nice_name=user_name_for(name), + use_files=use_files, + config_filename=config_filename ), ).configure_traits() diff --git a/traitsui/extras/tutor.py b/traitsui/extras/tutor.py deleted file mode 100644 index d40d08152..000000000 --- a/traitsui/extras/tutor.py +++ /dev/null @@ -1,1601 +0,0 @@ -#------------------------------------------------------------------------- -# -# Copyright (c) 2007, Enthought, Inc. -# All rights reserved. -# -# This software is provided without warranty under the terms of the BSD -# license included in enthought/LICENSE.txt and may be redistributed only -# under the conditions described in the aforementioned license. The license -# is also available online at http://www.enthought.com/licenses/BSD.txt -# -# Thanks for using Enthought open source! -# -# Author: David C. Morrill -# Date: 03/30/2007 -# -# fixme: -# - Get custom tree view images. -# - Write a program to create a directory structure from a lesson plan file. -# -#------------------------------------------------------------------------- - -""" A framework for creating interactive Python tutorials. -""" - -#------------------------------------------------------------------------- -# Imports: -#------------------------------------------------------------------------- - -from __future__ import absolute_import, print_function - -import sys -from operator import itemgetter -import os -import re - -from string \ - import capwords - -from traits.api \ - import HasPrivateTraits, HasTraits, File, Directory, Instance, Int, Str, \ - List, Bool, Dict, Any, Property, Delegate, Button, cached_property - -from traitsui.api \ - import View, VGroup, HGroup, VSplit, HSplit, Tabbed, Item, Heading, \ - Handler, ListEditor, CodeEditor, EnumEditor, HTMLEditor, \ - TreeEditor, TitleEditor, ValueEditor, ShellEditor - -from traitsui.menu \ - import NoButtons - -from traitsui.tree_node \ - import TreeNode -from traitsui.extras.demo import ( - parse_source, publish_html_str, publish_html_file -) - -from pyface.image_resource \ - import ImageResource -from io import open - -try: - from traitsui.wx.extra.windows.ie_html_editor \ - import IEHTMLEditor - - from traitsui.wx.extra.windows.flash_editor \ - import FlashEditor - wx_available = True -except: - IEHTMLEditor = HTMLEditor - FlashEditor = ValueEditor # Set this to a random editor to prevent error. - wx_available = False - -#------------------------------------------------------------------------- -# Constants: -#------------------------------------------------------------------------- - -# The standard list editor used: -list_editor = ListEditor( - use_notebook=True, - deletable=False, - page_name='.title', - export='DockWindowShell', - dock_style='fixed' -) - -# The standard code snippet editor used: -snippet_editor = ListEditor( - use_notebook=True, - deletable=False, - page_name='.title', - export='DockWindowShell', - dock_style='tab', - selected='snippet' -) - -# Regular expressions used to match section directories: -dir_pat1 = re.compile(r'^(\d\d\d\d)_(.*)$') -dir_pat2 = re.compile(r'^(.*)_(\d+\.\d+)$') - -# Regular expression used to extract item titles from URLs: -url_pat1 = re.compile(r'^(.*)\[(.*)\](.*)$') # Normal - -# Is this running on the Windows platform? -is_windows = (sys.platform in ('win32', 'win64')) - -# HTML template for a default lecture: -DefaultLecture = """ - - - -

This section contains the following topics:

- - - -""" - -# HTML template for displaying a .wmv/.avi movie file: -WMVMovieTemplate = """ - - - -

- - - - - - - - -

- - -""" - -# HTML template for displaying a QuickTime.mov movie file: -QTMovieTemplate = """ - - - -

- - - - - -

- - -""" - -# HTML template for displaying an image file: -ImageTemplate = """ - - - - - - -""" - -# HTML template for playing an MP3 audio file: -MP3Template = """ - - - - -

 

- - -""" - -#------------------------------------------------------------------------- -# Returns the contents of a specified text file (or None): -#------------------------------------------------------------------------- - - -def read_file(path, mode='rU', encoding='utf8'): - """ Returns the contents of a specified text file (or None). - - """ - try: - with open(path, mode, encoding=encoding) as fh: - result = fh.read() - return result - except Exception: - return None - - -#------------------------------------------------------------------------- -# Creates a title from a specified string: -#------------------------------------------------------------------------- - -def title_for(title): - """ Creates a title from a specified string. - """ - return capwords(title.replace('_', ' ')) - -#------------------------------------------------------------------------- -# Returns a relative CSS style sheet path for a specified path and parent -# section: -#------------------------------------------------------------------------- - - -def css_path_for(path, parent): - """ Returns a relative CSS style sheet path for a specified path and parent - section. - """ - if os.path.isfile(os.path.join(path, 'default.css')): - return 'default.css' - - if parent is not None: - result = parent.css_path - if result != '': - if path != parent.path: - result = os.path.join('..', result) - - return result - - return '' - -#------------------------------------------------------------------------- -# 'StdOut' class: -#------------------------------------------------------------------------- - - -class StdOut(object): - """ Simulate stdout, but redirect the output to the 'output' string - supplied by some 'owner' object. - """ - - def __init__(self, owner): - self.owner = owner - - def write(self, data): - """ Adds the specified data to the output log. - """ - self.owner.output += data - - def flush(self): - """ Flushes all current data to the output log. - """ - pass - -#------------------------------------------------------------------------- -# 'NoDemo' class: -#------------------------------------------------------------------------- - - -class NoDemo(HasPrivateTraits): - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Heading('No demo defined for this lab.'), - resizable=True - ) - -#------------------------------------------------------------------------- -# 'DemoPane' class: -#------------------------------------------------------------------------- - - -class DemoPane(HasPrivateTraits): - """ Displays the contents of a Python lab's *demo* value. - """ - - #-- Trait Definitions ---------------------------------------------------- - - demo = Instance(HasTraits, factory=NoDemo) - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('demo', - id='demo', - show_label=False, - style='custom', - resizable=True - ), - id='enthought.tutor.demo', - resizable=True - ) - -#------------------------------------------------------------------------- -# 'ATutorialItem' class: -#------------------------------------------------------------------------- - - -class ATutorialItem(HasPrivateTraits): - """ Defines the abstract base class for each type of item (HTML, Flash, - text, code) displayed within the tutor. - """ - - #-- Traits Definitions --------------------------------------------------- - - # The title for the item: - title = Str - - # The path to the item: - path = File - - # The displayable content for the item: - content = Property - -#------------------------------------------------------------------------- -# 'ADescriptionItem' class: -#------------------------------------------------------------------------- - - -class ADescriptionItem(ATutorialItem): - """ Defines a common base class for all description items. - """ - - #-- Event Handlers ------------------------------------------------------- - - def _path_changed(self, path): - """ Sets the title for the item based on the item's path name. - """ - self.title = title_for(os.path.splitext(os.path.basename( - path))[0]) - -#------------------------------------------------------------------------- -# 'HTMLItem' class: -#------------------------------------------------------------------------- - - -class HTMLItem(ADescriptionItem): - """ Defines a class used for displaying a single HTML page within the tutor - using the default Traits HTML editor. - """ - - #-- Traits Definitions --------------------------------------------------- - - url = Str - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=HTMLEditor() - ) - ) - - #-- Event Handlers ------------------------------------------------------- - - def _url_changed(self, url): - """ Sets the item title when the 'url' is changed. - """ - match = url_pat1.match(url) - if match is not None: - title = match.group(2).strip() - else: - title = url.strip() - col = title.rfind('/') - if col >= 0: - title = os.path.splitext(title[col + 1:])[0] - - self.title = title - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_content(self): - """ Returns the item content. - """ - url = self.url - if url != '': - match = url_pat1.match(url) - if match is not None: - url = match.group(1) + match.group(3) - - return url - - return read_file(self.path) - - def _set_content(self, content): - """ Sets the item content. - """ - self._content = content - -#------------------------------------------------------------------------- -# 'HTMLStrItem' class: -#------------------------------------------------------------------------- - - -class HTMLStrItem(HTMLItem): - """ Defines a class used for displaying a single HTML text string within - the tutor using the default Traits HTML editor. - """ - - # Make the content a real trait rather than a property: - content = Str - -#------------------------------------------------------------------------- -# 'IEHTMLItem' class: -#------------------------------------------------------------------------- - - -class IEHTMLItem(HTMLItem): - """ Defines a class used for displaying a single HTML page within the tutor - using the Traits Internet Explorer HTML editor. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=IEHTMLEditor() - ) - ) - -#------------------------------------------------------------------------- -# 'IEHTMLStrItem' class: -#------------------------------------------------------------------------- - - -class IEHTMLStrItem(IEHTMLItem): - """ Defines a class used for displaying a single HTML text string within - the tutor using the Traits Internet Explorer HTML editor. - """ - - # Make the content a real trait rather than a property: - content = Str - -#------------------------------------------------------------------------- -# 'FlashItem' class: -#------------------------------------------------------------------------- - - -class FlashItem(HTMLItem): - """ Defines a class used for displaying a Flash-based animation or video - within the tutor. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=FlashEditor() - ) - ) - -#------------------------------------------------------------------------- -# 'TextItem' class: -#------------------------------------------------------------------------- - - -class TextItem(ADescriptionItem): - """ Defines a class used for displaying a text file within the tutor. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='readonly', - show_label=False, - editor=CodeEditor(show_line_numbers=False, - selected_color=0xFFFFFF) - ) - ) - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_content(self): - """ Returns the item content. - """ - return read_file(self.path) - -#------------------------------------------------------------------------- -# 'TextStrItem' class: -#------------------------------------------------------------------------- - - -class TextStrItem(TextItem): - """ Defines a class used for displaying a text file within the tutor. - """ - - # Make the content a real trait, rather than a property: - content = Str - -#------------------------------------------------------------------------- -# 'CodeItem' class: -#------------------------------------------------------------------------- - - -class CodeItem(ATutorialItem): - """ Defines a class used for displaying a Python source code fragment - within the tutor. - """ - - #-- Trait Definitions ---------------------------------------------------- - - # The displayable content for the item (override): - content = Str - - # The starting line of the code snippet within the original file: - start_line = Int - - # The currently selected line: - selected_line = Int - - # Should this section normally be hidden? - hidden = Bool - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('content', - style='custom', - show_label=False, - editor=CodeEditor(selected_line='selected_line') - ) - ) - -#------------------------------------------------------------------------- -# 'ASection' abstract base class: -#------------------------------------------------------------------------- - - -class ASection(HasPrivateTraits): - """ Defines an abstract base class for a single section of a tutorial. - """ - - #-- Traits Definitions --------------------------------------------------- - - # The title of the section: - title = Str - - # The path to this section: - path = Directory - - # The parent section of this section (if any): - parent = Instance('ASection') - - # Optional table of contents (can be used to define/locate the - # subsections): - toc = List(Str) - - # The path to the CSS style sheet to use for this section: - css_path = Property - - # The list of subsections contained in this section: - subsections = Property # List( ASection ) - - # This section can be executed: - is_runnable = Bool(True) - - # Should the Python code be automatically executed on start-up? - auto_run = Bool(False) - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_subsections(self): - """ Returns the subsections for this section: - """ - if len(self.toc) > 0: - self._load_toc() - else: - self._load_dirs() - - # Return the cached list of sections: - return self._subsections - - @cached_property - def _get_css_path(self): - """ Returns the path to the CSS style sheet for this section. - """ - return css_path_for(self.path, self.parent) - - #-- Private Methods ------------------------------------------------------ - - def _load_dirs(self): - """ Defines the section's subsections by analyzing all of the section's - sub-directories. - """ - # No value cached yet: - dirs = [] - path = self.path - - # Find every sub-directory whose name begins with a number of the - # form ddd, or ends with a number of the form _ddd.ddd (used for - # sorting them into the correct presentation order): - for name in os.listdir(path): - dir = os.path.join(path, name) - if os.path.isdir(dir): - match = dir_pat1.match(name) - if match is not None: - dirs.append((float(match.group(1)), - match.group(2), dir)) - else: - match = dir_pat2.match(name) - if match is not None: - dirs.append((float(match.group(2)), - match.group(1), dir)) - - # Sort the directories by their index value: - dirs.sort(key=itemgetter(0)) - - # Create the appropriate type of section for each valid directory: - self._subsections = [ - sf.section for sf in [ - SectionFactory(title=title_for(title), - parent=self).trait_set( - path=dir) - for index, title, dir in dirs - ] if sf.section is not None - ] - - def _load_toc(self): - """ Defines the section's subsections by finding matches for the items - defined in the section's table of contents. - """ - toc = self.toc - base_names = [item.split(':', 1)[0] for item in toc] - subsections = [None] * len(base_names) - path = self.path - - # Classify all file names that match a base name in the table of - # contents: - for name in os.listdir(path): - try: - base_name = os.path.splitext(os.path.basename(name))[0] - index = base_names.index(base_name) - if subsections[index] is None: - subsections[index] = [] - subsections[index].append(name) - except: - pass - - # Try to convert each group of names into a section: - for i, names in enumerate(subsections): - - # Only process items for which we found at least one matching file - # name: - if names is not None: - - # Get the title for the section from its table of contents - # entry: - parts = toc[i].split(':', 1) - if len(parts) == 1: - title = title_for(parts[0].strip()) - else: - title = parts[1].strip() - - # Handle an item with one file which is a directory as a normal - # section: - if len(names) == 1: - dir = os.path.join(path, names[0]) - if os.path.isdir(dir): - subsections[i] = SectionFactory(title=title, - parent=self).trait_set( - path=dir).section - continue - - # Otherwise, create a section from the list of matching files: - subsections[i] = SectionFactory(title=title, - parent=self, - files=names).trait_set( - path=path).section - - # Set the subsections to the non-None values that are left: - self._subsections = [subsection for subsection in subsections - if subsection is not None] - -#------------------------------------------------------------------------- -# 'Lecture' class: -#------------------------------------------------------------------------- - - -class Lecture(ASection): - """ Defines a lecture, which is a section of a tutorial with descriptive - information, but no associated Python code. Can be used to provide - course overviews, introductory sections, or lead-ins to follow-on - lessons or labs. - """ - - #-- Trait Definitions----------------------------------------------------- - - # The list of descriptive items for the lecture: - descriptions = List(ATutorialItem) - - # This section can be executed (override): - is_runnable = False - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - Item('descriptions', - style='custom', - show_label=False, - editor=list_editor - ), - id='enthought.tutor.lecture' - ) - -#------------------------------------------------------------------------- -# 'LabHandler' class: -#------------------------------------------------------------------------- - - -class LabHandler(Handler): - """ Defines the controller functions for the Lab view. - """ - - def init(self, info): - """ Handles initialization of the view. - """ - # Run the associated Python code if the 'auto-run' feature is enabled: - if info.object.auto_run: - info.object.run_code() - -#------------------------------------------------------------------------- -# 'Lab' class: -#------------------------------------------------------------------------- - - -class Lab(ASection): - """ Defines a lab, which is a section of a tutorial with only Python code. - This type of section might typically follow a lecture which introduced - the code being worked on in the lab. - """ - - #-- Trait Definitions----------------------------------------------------- - - # The set-up code (if any) for the lab: - setup = Instance(CodeItem) - - # The list of code items for the lab: - snippets = List(CodeItem) - - # The list of visible code items for the lab: - visible_snippets = Property(depends_on='visible', cached=True) - - # The currently selected snippet: - snippet = Instance(CodeItem) - - # Should normally hidden code items be shown? - visible = Bool(False) - - # The dictionary containing the items from the Python code execution: - values = Dict # Any( {} ) - - # The run Python code button: - run = Button(image=ImageResource('run'), height_padding=1) - - # User error message: - message = Str - - # The output produced while the program is running: - output = Str - - # The current demo pane (if any): - demo = Instance(DemoPane, ()) - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - VSplit( - VGroup( - Item('visible_snippets', - style='custom', - show_label=False, - editor=snippet_editor - ), - HGroup( - Item('run', - style='custom', - show_label=False, - tooltip='Run the Python code' - ), - '_', - Item('message', - springy=True, - show_label=False, - editor=TitleEditor() - ), - ), - ), - Tabbed( - Item('values', - id='values_1', - label='Shell', - editor=ShellEditor(share=True), - dock='tab', - export='DockWindowShell' - ), - Item('values', - id='values_2', - editor=ValueEditor(), - dock='tab', - export='DockWindowShell' - ), - Item('output', - style='readonly', - editor=CodeEditor(show_line_numbers=False, - selected_color=0xFFFFFF), - dock='tab', - export='DockWindowShell' - ), - Item('demo', - id='demo', - style='custom', - resizable=True, - dock='tab', - export='DockWindowShell' - ), - show_labels=False, - ), - id='splitter', - ), - id='enthought.tutor.lab', - handler=LabHandler - ) - - #-- Event Handlers ------------------------------------------------------- - - def _run_changed(self): - """ Runs the current set of snippet code. - """ - self.run_code() - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_visible_snippets(self): - """ Returns the list of code items that are currently visible. - """ - if self.visible: - return self.snippets - - return [snippet for snippet in self.snippets if (not snippet.hidden)] - - #-- Public Methods ------------------------------------------------------- - - def run_code(self): - """ Runs all of the code snippets associated with the section. - """ - # Reconstruct the lab code from the current set of code snippets: - start_line = 1 - module = '' - for snippet in self.snippets: - snippet.start_line = start_line - module = '%s\n\n%s' % (module, snippet.content) - start_line += (snippet.content.count('\n') + 2) - - # Reset any syntax error and message log values: - self.message = self.output = '' - - # Redirect standard out and error to the message log: - stdout, stderr = sys.stdout, sys.stderr - sys.stdout = sys.stderr = StdOut(self) - - try: - try: - # Get the execution context dictionary: - values = self.values - - # Clear out any special variables defined by the last run: - for name in ('demo', 'popup'): - if isinstance(values.get(name), HasTraits): - del values[name] - - # Execute the current lab code: - exec(module[2:], values, values) - - # fixme: Hack trying to update the Traits UI view of the dict. - self.values = {} - self.values = values - - # Handle a 'demo' value being defined: - demo = values.get('demo') - if not isinstance(demo, HasTraits): - demo = NoDemo() - self.demo.demo = demo - - # Handle a 'popup' value being defined: - popup = values.get('popup') - if isinstance(popup, HasTraits): - popup.edit_traits(kind='livemodal') - - except SyntaxError as excp: - # Convert the line number of the syntax error from one in the - # composite module to one in the appropriate code snippet: - line = excp.lineno - if line is not None: - snippet = self.snippets[0] - for s in self.snippets: - if s.start_line > line: - break - snippet = s - line -= (snippet.start_line - 1) - - # Highlight the line in error: - snippet.selected_line = line - - # Select the correct code snippet: - self.snippet = snippet - - # Display the syntax error message: - self.message = '%s in column %s of line %s' % ( - excp.msg.capitalize(), excp.offset, line) - else: - # Display the syntax error message without line # info: - self.message = excp.msg.capitalize() - except: - import traceback - traceback.print_exc() - finally: - # Restore standard out and error to their original values: - sys.stdout, sys.stderr = stdout, stderr - -#------------------------------------------------------------------------- -# 'Lesson' class: -#------------------------------------------------------------------------- - - -class Lesson(Lab): - """ Defines a lesson, which is a section of a tutorial with both descriptive - information and associated Python code. - """ - - #-- Trait Definitions----------------------------------------------------- - - # The list of descriptive items for the lesson: - descriptions = List(ATutorialItem) - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - HSplit( - Item('descriptions', - label='Lesson', - style='custom', - show_label=False, - dock='horizontal', - editor=list_editor - ), - VSplit( - VGroup( - Item('visible_snippets', - style='custom', - show_label=False, - editor=snippet_editor - ), - HGroup( - Item('run', - style='custom', - show_label=False, - tooltip='Run the Python code' - ), - '_', - Item('message', - springy=True, - show_label=False, - editor=TitleEditor() - ), - '_', - Item('visible', - label='View hidden sections' - ) - ), - label='Lab', - dock='horizontal' - ), - Tabbed( - Item('values', - id='values_1', - label='Shell', - editor=ShellEditor(share=True), - dock='tab', - export='DockWindowShell' - - ), - Item('values', - id='values_2', - editor=ValueEditor(), - dock='tab', - export='DockWindowShell' - ), - Item('output', - style='readonly', - editor=CodeEditor(show_line_numbers=False, - selected_color=0xFFFFFF), - dock='tab', - export='DockWindowShell' - ), - Item('demo', - id='demo', - style='custom', - resizable=True, - dock='tab', - export='DockWindowShell' - ), - show_labels=False, - ), - label='Lab', - dock='horizontal' - ), - id='splitter', - ), - id='enthought.tutor.lesson', - handler=LabHandler - ) - -#------------------------------------------------------------------------- -# 'Demo' class: -#------------------------------------------------------------------------- - - -class Demo(Lesson): - """ Defines a demo, which is a section of a tutorial with both descriptive - information and associated Python code which is executed but not - shown. - """ - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - HSplit( - Item('descriptions', - label='Lesson', - style='custom', - show_label=False, - dock='horizontal', - editor=list_editor - ), - Item('demo', - id='demo', - style='custom', - show_label=False, - resizable=True, - dock='horizontal', - export='DockWindowShell' - ), - id='splitter', - ), - id='enthought.tutor.demo', - handler=LabHandler - ) - -#------------------------------------------------------------------------- -# 'SectionFactory' class: -#------------------------------------------------------------------------- - - -class SectionFactory(HasPrivateTraits): - """ Defines a class that creates Lecture, Lesson or Lab sections (or None), - based on the content of a specified directory. None is returned if the - directory does not contain any recognized files. - """ - - #-- Traits Definitions --------------------------------------------------- - - # The path the section is to be created for: - path = Directory - - # The list of files contained in the section: - files = List(Str) - - # The parent of the section being created: - parent = Instance(ASection) - - # The section created from the path: - section = Instance(ASection) - - # The title for the section: - title = Str - - # The optional table of contents for the section: - toc = List(Str) - - # The list of descriptive items for the section: - descriptions = List(ADescriptionItem) - - # The list of code snippet items for the section: - snippets = List(CodeItem) - - # The path to the CSS style sheet for the section: - css_path = Property - - # Should the Python code be automatically executed on start-up? - auto_run = Bool(False) - - #-- Event Handlers ------------------------------------------------------- - - def _path_changed(self, path): - """ Creates the appropriate section based on the value of the path. - """ - # Get the list of files to process: - files = self.files - if len(files) == 0: - # If none were specified, then use all files in the directory: - files = os.listdir(path) - - # Process the description file (if any) first: - for name in files: - if os.path.splitext(name)[1] == '.desc': - self._add_desc_item(os.path.join(path, name)) - break - - # Try to convert each file into one or more 'xxxItem' objects: - toc = [item.split(':', 1)[0].strip() for item in self.toc] - for name in files: - file_name = os.path.join(path, name) - - # Only process the ones that are actual files: - if os.path.isfile(file_name): - - # Use the file extension to determine the file's type: - root, ext = os.path.splitext(name) - if (root not in toc) and (len(ext) > 1): - - # If we have a handler for the file type, invoke it: - method = getattr(self, '_add_%s_item' % ext[1:].lower(), - None) - if method is not None: - method(file_name) - - # Based on the type of items created (if any), create the corresponding - # type of section: - if len(self.descriptions) > 0: - if len(self.snippets) > 0: - if len([snippet for snippet in self.snippets - if (not snippet.hidden)]) > 0: - self.section = Lesson( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - descriptions=self.descriptions, - snippets=self.snippets, - auto_run=self.auto_run - ) - else: - self.section = Demo( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - descriptions=self.descriptions, - snippets=self.snippets, - auto_run=True - ) - else: - self.section = Lecture( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - descriptions=self.descriptions - ) - elif len(self.snippets) > 0: - self.section = Lab( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent, - snippets=self.snippets, - auto_run=self.auto_run - ) - else: - # No descriptions or code snippets were found. Create a lecture - # anyway: - section = Lecture( - title=self.title, - path=path, - toc=self.toc, - parent=self.parent - ) - - # If the lecture has subsections, then return the lecture and add - # a default item containing a description of the subsections of the - # lecture: - if len(section.subsections) > 0: - self._create_html_item(path=path, content=DefaultLecture % ('\n'.join( - ['
  • %s
  • ' % subsection.title - for subsection in section.subsections]))) - section.descriptions = self.descriptions - self.section = section - - #-- Property Implementations --------------------------------------------- - - def _get_css_path(self): - """ Returns the path to the CSS style sheet for the section. - """ - return css_path_for(self.path, self.parent) - - #-- Factory Methods for Creating Section Items Based on File Type -------- - - def _add_py_item(self, path): - """ Creates the code snippets for a Python source file. - """ - description, source = parse_source(path) - title = "Description" - self._add_description(description, title) - self.snippets.append(CodeItem( - title="Code", - path=path, - hidden=False, - content=source - )) - - def _add_txt_item(self, path): - """ Creates a description item for a normal text file. - """ - self.descriptions.append(TextItem(path=path)) - - def _add_htm_item(self, path): - """ Creates a description item for an HTML file. - """ - # Check if there is a corresponding .rst (restructured text) file: - dir, base_name = os.path.split(path) - rst = os.path.join(dir, os.path.splitext(base_name)[0] + '.rst') - - # If no .rst file exists, just add the file as a normal HTML file: - if not os.path.isfile(rst): - self._create_html_item(path=path) - - def _add_html_item(self, path): - """ Creates a description item for an HTML file. - """ - self._add_htm_item(path) - - def _add_url_item(self, path): - """ Creates a description item for a file containing URLs. - """ - data = read_file(path) - if data is not None: - for url in [line for line in data.split('\n') - if line.strip()[:1] not in ('', '#')]: - self._create_html_item(url=url.strip()) - - def _add_rst_item(self, path): - """ Creates a description item for a ReSTructured text file. - """ - if self.css_path != '': - css_path = os.path.join(self.path, self.css_path) - else: - css_path = path - - # Get the name of the HTML file we will write to: - dir, base_name = os.path.split(path) - html = os.path.join(dir, os.path.splitext(base_name)[0] + '.htm') - - # If the HTML file does not exist, or is older than the restructured - # text file, then let docutils convert it to HTML: - is_file = os.path.isfile(html) - if ((not is_file) or - (os.path.getmtime(path) > os.path.getmtime(html)) or - (os.path.getmtime(css_path) > os.path.getmtime(html))): - - # Delete the current HTML file (if any): - if is_file: - os.remove(html) - - # Let docutils create a new HTML file from the restructured text - # file: - publish_html_file(path, html, css_path) - - if os.path.isfile(html): - # If there is now a valid HTML file, use it: - self._create_html_item(path=html) - else: - # Otherwise, just use the original restructured text file: - self._add_txt_item(path) - - def _add_swf_item(self, path): - """ Creates a description item for a Flash file. - """ - if is_windows and wx_available: - self.descriptions.append(FlashItem(path=path)) - - def _add_mov_item(self, path): - """ Creates a description item for a QuickTime movie file. - """ - path2 = path.replace(':', '|') - self._create_html_item(path=path, - content=QTMovieTemplate % (path2, path2)) - - def _add_wmv_item(self, path): - """ Creates a description item for a Windows movie file. - """ - self._create_html_item(path=path, - content=WMVMovieTemplate % (path, path)) - - def _add_avi_item(self, path): - """ Creates a description item for an AVI movie file. - """ - self._add_wmv_item(path) - - def _add_jpg_item(self, path): - """ Creates a description item for a JPEG image file. - """ - self._create_html_item(path=path, - content=ImageTemplate % path) - - def _add_jpeg_item(self, path): - """ Creates a description item for a JPEG image file. - """ - self._add_jpg_item(path) - - def _add_png_item(self, path): - """ Creates a description item for a PNG image file. - """ - self._add_jpg_item(path) - - def _add_mp3_item(self, path): - """ Creates a description item for an mp3 audio file. - """ - self._create_html_item(path=path, - content=MP3Template % path) - - def _add_desc_item(self, path): - """ Creates a section title from a description file. - """ - # If we've already processed a description file, then we're done: - if len(self.toc) > 0: - return - - lines = [] - desc = read_file(path) - if desc is not None: - # Split the file into lines and save the non-empty, non-comment - # lines: - for line in desc.split('\n'): - line = line.strip() - if (len(line) > 0) and (line[0] != '#'): - lines.append(line) - - if len(lines) == 0: - # If the file didn't have anything useful in it, set a title based - # on the description file name: - self.title = title_for( - os.path.splitext(os.path.basename(path))[0]) - else: - # Otherwise, set the title and table of contents from the lines in - # the file: - self.title = lines[0] - self.toc = lines[1:] - - #-- Private Methods ------------------------------------------------------ - - def _add_description(self, content, title): - """ Converts a restructured text string to HTML and adds it as - description item. - """ - css_path = self.css_path - if css_path != '': - css_path = os.path.join(self.path, css_path) - - html = publish_html_str(content, css_path) - - # Choose the right HTML renderer: - if is_windows and wx_available: - item = IEHTMLStrItem(content=html, title=title) - else: - item = HTMLStrItem(content=html, title=title) - - # Add the resulting item to the descriptions list: - self.descriptions.append(item) - - def _create_html_item(self, **traits): - """ Creates a platform specific html item and adds it to the list of - descriptions. - """ - if is_windows and wx_available: - item = IEHTMLItem(**traits) - else: - item = HTMLItem(**traits) - - self.descriptions.append(item) - -#------------------------------------------------------------------------- -# Tutor tree editor: -#------------------------------------------------------------------------- - -tree_editor = TreeEditor( - nodes=[ - TreeNode( - children='subsections', - label='title', - rename=False, - copy=False, - delete=False, - delete_me=False, - insert=False, - auto_open=True, - auto_close=False, - node_for=[ASection], - icon_group='' - ) - ], - editable=False, - auto_open=1, - selected='section' -) - -#------------------------------------------------------------------------- -# 'Tutor' class: -#------------------------------------------------------------------------- - - -class Tutor(HasPrivateTraits): - """ The main tutorial class which manages the presentation and navigation - of the entire tutorial. - """ - - #-- Trait Definitions ---------------------------------------------------- - - # The path to the files distributed with the tutor: - home = Directory - - # The path to the root of the tutorial tree: - path = Directory - - # The root of the tutorial lesson tree: - root = Instance(ASection) - - # The current section of the tutorial being displayed: - section = Instance(ASection) - - # The next section: - next_section = Property(depends_on='section', cached=True) - - # The previous section: - previous_section = Property(depends_on='section', cached=True) - - # The previous section button: - previous = Button(image=ImageResource('previous'), height_padding=1) - - # The next section button: - next = Button(image=ImageResource('next'), height_padding=1) - - # The parent section button: - parent = Button(image=ImageResource('parent'), height_padding=1) - - # The reload tutor button: - reload = Button(image=ImageResource('reload'), height_padding=1) - - # The title of the current session: - title = Property(depends_on='section') - - #-- Traits View Definitions ---------------------------------------------- - - view = View( - VGroup( - HGroup( - Item('previous', - style='custom', - enabled_when='previous_section is not None', - tooltip='Go to previous section' - ), - Item('parent', - style='custom', - enabled_when='(section is not None) and ' - '(section.parent is not None)', - tooltip='Go up one level' - ), - Item('next', - style='custom', - enabled_when='next_section is not None', - tooltip='Go to next section' - ), - '_', - Item('title', - springy=True, - editor=TitleEditor() - ), - '_', - Item('reload', - style='custom', - tooltip='Reload the tutorial' - ), - show_labels=False - ), - '_', - HSplit( - Item('root', - label='Table of Contents', - editor=tree_editor, - dock='horizontal', - export='DockWindowShell' - ), - Item('section', - id='section', - label='Current Lesson', - style='custom', - resizable=True, - dock='horizontal' - ), - id='splitter', - show_labels=False - ) - ), - title='Python Tutor', - id='dmorrill.tutor.tutor:1.0', - buttons=NoButtons, - resizable=True, - width=0.8, - height=0.8 - ) - - #-- Event Handlers ------------------------------------------------------- - - def _path_changed(self, path): - """ Handles the tutorial root path being changed. - """ - self.init_tutor() - - def _next_changed(self): - """ Displays the next tutorial section. - """ - self.section = self.next_section - - def _previous_changed(self): - """ Displays the previous tutorial section. - """ - self.section = self.previous_section - - def _parent_changed(self): - """ Displays the parent of the current tutorial section. - """ - self.section = self.section.parent - - def _reload_changed(self): - """ Reloads the tutor from the original path specified. - """ - self.init_tutor() - - #-- Property Implementations --------------------------------------------- - - @cached_property - def _get_next_section(self): - """ Returns the next section of the tutorial. - """ - next = None - section = self.section - if len(section.subsections) > 0: - next = section.subsections[0] - else: - parent = section.parent - while parent is not None: - index = parent.subsections.index(section) - if index < (len(parent.subsections) - 1): - next = parent.subsections[index + 1] - break - - parent, section = parent.parent, parent - - return next - - @cached_property - def _get_previous_section(self): - """ Returns the previous section of the tutorial. - """ - previous = None - section = self.section - parent = section.parent - if parent is not None: - index = parent.subsections.index(section) - if index > 0: - previous = parent.subsections[index - 1] - while len(previous.subsections) > 0: - previous = previous.subsections[-1] - else: - previous = parent - - return previous - - def _get_title(self): - """ Returns the title of the current section. - """ - section = self.section - if section is None: - return '' - - return ('%s: %s' % (section.__class__.__name__, section.title)) - - #-- Public Methods ------------------------------------------------------- - - def init_tutor(self): - """ Initials the tutor by creating the root section from the specified - path. - """ - path = self.path - title = title_for(os.path.splitext(os.path.basename(path))[0]) - section = SectionFactory(title=title).trait_set(path=path).section - if section is not None: - self.section = self.root = section