Last modified 11 years ago Last modified on 09/09/09 16:24:16

Error: Macro BackLinksMenu(None) failed
compressed data is corrupt

Error: Macro TicketQuery(summary=SCRIPTING_ACTIONS_API_R0, format=table, col=summary|owner|status|type|component|priority|effort|importance, rows=description|analysis_owners|analysis_reviewers|analysis_score|design_owners|design_reviewers|design_score|implementation_owners|implementation_reviewers|implementation_score|test_owners|test_reviewers|test_score|) failed
current transaction is aborted, commands ignored until end of transaction block



The goal of this task is to expose the Sophie book's model and Sophie's logic in a user-friendly way to the user.

Generally, there are two use-cases for scripting, which imply some differences in the requirements:

  • Scripts used for automation
    • The result of their execution should be undoable, as each other change to the model
  • Scripts as link targets
    • They have limited scope - for example, user cannot create a new book with them
    • There's no need to be undoable

Task requirements

  • The user should have access to the current book and should be able to do the following actions with it:
    • get/set its title
    • get/set the page size (width and height)
    • get a list of all pages or a page with a given index
    • add a new page in it
    • remove a page
    • reorder the pages
    • get/set the current page
  • For every page the user should be able to:
    • get its name
    • get its index (but not set it)
    • get the bounding rectangle (a rectangle that contains all page elements)
    • [Optional for this revision] get/set the background style
    • [Optional for this revision] get/set the border style
    • get a list of all frames
    • add a new frame
    • remove a frame
  • For every frame the user should be able to:
    • get/set its size
    • get/set its z-order
    • get/set its content location
    • get/set its rotation angle
    • [Optional for this revision] get/set its background style
    • [Optional for this revision] get/set its border style
    • [Optional for this revision] get/set its paddings
  • [Optional] The user should also be able to work with groups.
  • Allow scripts to be defined as link actions.
    • The action settings panel should contain a combo box with all available scripts for current book.
  • Not all Java classes have to be exposed to Sophie scripts, because:
    • Some scripts can crash the application.
    • Some other scripts can allow crackers to execute malicious code.
  • When closing a book, close any open document windows that display scripts from current book. Generally, when closing a document window, close all children resources' document windows.

Task result

  • Source code

Implementation idea

  • Decide whether to expose the model classes (,,, etc.) or to write adapter classes.
    • The first approach will make scripts very powerful - the user will be able to do with them everything he/she can do with the application. In addition, it won't be necessary to update the facade every time we change something.
    • The second approach however will be more convenient for the user. It will also solve most of the security issues.
  • To filter the access to some Java classes:
    context.setClassShutter(new ClassShutter() {
    	public boolean visibleToScripts(String fullClassName) {
    		return fullClassName.startsWith("org.mozilla.javascript");
  • The following lines add a global variable out that is a JavaScript reflection of the System.out variable:
    Object wrappedOut = Context.javaToJS(System.out, scope);
    ScriptableObject.putProperty(scope, "out", wrappedOut);


How to demo

(Provide instructions for demonstration of the task.)


  • For the links:
    • Package org.sophie2.extra.func.scripting.links
    • Class RunScriptLinkAction extends LinkAction
      • script property
    • Class RunScriptActionProvider implements LinkActionProvider
      • Register it as an extension
    • Class RunScriptConfigurationPanel implements ActionConfigurationPanel extends BaseSwingVisualElement
      • BoundComboBox<Script> that displays all script resources in current book
      • The swing component should be a panel with the combo box.
      • Register it as an extension
    • Class RunScriptActionPersister extends Persister<Ref<RunScriptAction>, Storage>
      • Schema "link-action:run-script|storage|r3"
      • Register it as an extension
    • Enum org.sophie2.extra.func.scripting.logic.RunScriptLogic implements OperationDef
      • Move RUN_SCRIPT from ScriptLogic
        • Wrap the script execution in a new AutoChange and register it to the script.
        • Use this when activating a RunScriptLinkAction.
  • For the API:
    • Package org.sophie2.extra.func.scripting.facade
    • Class JSBook extends ScriptableObject
      • Override getClassName to return "Book". That means that script writers will use "Book" instead of "JSBook".
      • Create a method void setBook(ResourceRef book) which sets the "real" book that is adapted. This method is not exposed to users.
      • Create getters, setters and other methods according to the analysis.
        • For example, for the title write String jsGet_title() and void jsSet_title(String title). This will allow users to benefit from the easy JavaScript syntax: "book.title = 'Design Patterns'".
        • For page reordering fix the method Book.movePage(int, int) (add validation of indices).
    • Class JSPage extends ScriptableObject
      • getClassName returns "Page".
      • Create getters, setters and other methods according to the analysis.
    • Class JSFrame extends ScriptableObject
      • getClassName returns "Frame".
      • Create getters, setters and other methods according to the analysis.
    • Class JSApp extends ScriptableObject
      • getClassName returns "App".
      • Create method JSBook jsFunction_newBook() which creates a new default book and adds it to the list of open documents.
    • In RunScriptLogic:
      • Use ScriptableObject.defineClass(scope, JSBook.class); to expose a given ScriptableObject to JavaScript.
      • When executing a script from a document window, expose all adapter and facade classes. When the script is used as a link action, do not expose JSApp.
      • Use the following to expose a global variable book which represents the book where the script is created:
        Scriptable scriptableJsBook = context.newObject(scope, "Book");
        ScriptableObject.putProperty(scope, "book", scriptableJsBook); 
        JSBook jsBook = (JSBook) Context.jsToJava(scriptableJsBook, JSBook.class);
      • Expose the app too, if running a script from a document window.
      • Limit the scope of visible Java classes to only Rhino classes using context.setClassShutter.


  • Some design changes:
    • Base abstract class BaseJSAdapter<T> that provides common functionality for JSBook, JSPage and JSFrame:
      • void setAdaptedObject(T adaptedObject)
      • T getAdaptedObject()
    • Allow access to Imm* classes and Object too.
    • Execute a script that imports all Imm* classes before running the script.

Merged into the trunk in [4417].


(Place the testing results here.)


Basic User Documentation

  • Working with books:
    • A global variable book is available which represents the book where the script is inserted.
    • Users can create new books and later manipulate them.
      • Example: var b = app.newBook()
    • Book properties that can be read or changed: (this section could be organized as a table with four columns)
      • Property | Type | Description | Examples
      • title | string | Book's title | book.title = 'Demonstration Book'
      • pages | array of Page | All pages in the book | Getting the number of pages: book.pages.length
      • pageSize | ImmSize | Page's dimensions | Getting the page height: var height = book.pageSize.height; Setting new dimensions: book.pageSize = new ImmSize(600, 900);
    • Methods in Book:
      • Method | Description | Examples
      • function newPage() | Creates a new page in the book and returns it | Add new page after existing pages: book.newPage()}}}
      • function removePage(index) | Removes the page with the given index. The first page has an index 0 (not 1) | Delete last page: b.removePage(b.pages.length - 1)
      • function movePage(oldIndex, newIndex) | Moves the page with index oldIndex to index newIndex | Move the first page after the second: book.movePage(0, 1)
      • setCurrentPage(page) | Sets the given page as current |
  • Page:
    • Properties:
      • pageNumber | integer | Gets the number of the page. First page has a number 1. Page number cannot be changed. |
      • pageIndex | integer | Gets the index of the page. First page has an index 0. Page index cannot be changed. |
      • name | string | Gets the name of the page, for example "Page A" |
      • frames | array of Frame | All frames in the page | Getting the number of frames in 1st page: book.pages[0].frames.length
    • Methods:
      • newTextFrame() | Creates a new text frame in the page and returns it. |
      • removeFrame(frame) | Removes the given frame | Remove first frame in a page: page.removeFrame(page.frames[0]);
  • Frame
    • Properties:
      • name | string | Gets the name of the frame |
      • size | ImmSize | The outer size of the frame | Set the frame's size to be the same as the page's size (without moving it to the top-left corner): book.pages[0].frames[0].size = book.pageSize
      • zOrder | integer | The z-order of the frame. The bottom-most frame has z-order 0, the front-most has the biggest z-order | Swap a frame with the frame above it: frame.zOrder++
      • contentLocation | ImmPoint | The location (the coordinates) of the frame content |
      • rotatingAngle | double | The angle at which the frame is rotated. The value is in degrees. |
    • Methods:
      • function bringToFront() | moves the frame above all other page elements.
      • function bringToBack() | moves the frame below all other page elements.