Last modified 16 years ago
Last modified on 09/09/09 16:24:16
Analysis
Overview
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 (org.sophie2.base.model.book.Book, org.sophie2.base.model.book.Page, org.sophie2.base.model.book.Frame, 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() { @Override 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);
Related
How to demo
(Provide instructions for demonstration of the task.)
Design
- 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.
- ACTION_RUN_SCRIPT
- Use this when activating a RunScriptLinkAction.
- Move RUN_SCRIPT from ScriptLogic
- 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); jsBook.setBook(bookRef);
- 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.
- Source code: branches/private/deni/scripting_actions/
- Tests:
- branches/private/deni/scripting_actions/modules/org.sophie2.extra.func.scripting/src/test/java/org/sophie2/extra/func/scripting/facade/JSBookTest.java
- branches/private/deni/scripting_actions/modules/org.sophie2.extra.func.scripting/src/test/java/org/sophie2/extra/func/scripting/links/RunScriptLinkActionTest.java
Implementation
- 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.
- Base abstract class BaseJSAdapter<T> that provides common functionality for JSBook, JSPage and JSFrame:
- Source code: branches/private/deni/scripting_actions/
Merged into the trunk in [4417].
Testing
(Place the testing results here.)
Comments
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]);
- Properties:
- 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.
- Properties: