Syzygy Python Programming is loading ...

Requires Javascript.
Syzygy Python Programming - easier cluster-based virtual reality
\nThis approach to writing Syzygy programs is in a sense the converse of that in the [[Recommended Programming Model]]. Under this approach, you do all of the computations simultaneously in the master and in the slaves--ignoring the existance of other instances of your program as much as possible--and setting things up in such a way that the computation should yield the same result in all instances of the program. Should. Did I mention that this is not the [[Recommended Programming Model]]? But yes, it is easier to implement.\n\nThe basic idea is that any point in time the state of a program depends on two or three elements:\n# The history of user input up to that point.\n# The sequence of pseudo-random numbers generated up to that point (if any).\n# Deterministic computations performed on the above.\n\nIt seems reasonable to assume that (3) will be the same on all the computers in your cluster, provided they are reasonably similar to one another. At any rate you might be comfortable making that assumption, particularly if all your computers are identical.\n\nWhat about user input? Well, the most input event state is automatically shared between master and slaves by the framework; provided you restrict your input processing to [[Polling the Framework's Input State]] (e.g. using framework.getAxis(), framework.getOnButton(), etc.), you'll get the same inputs in master and slaves on each frame. The only other thing that's needed is a way of ensuring that all instances of your program start processing input at the same time, so that they all have the same input history. As we'll see below, the framework provides a way to do that.\n\nWhat about random numbers? The framework provides access to a random-number generator with a seed that's shared between master and slaves. The drawback to using this is that it means you have to have access to a framework reference everywhere in your program. Another possible drawback is that it hasn't been tested a great deal yet. You can also, of course, use the Python random module and share the seed manually.\n\nHow do you use it? First, you need to call:\n{{{\nframework.usePredeterminedHarmony()\n}}}\n\nThis can be called either in your {{{__main__}}} block or in your {{{onStart()}}} method. In [[Standalone Mode]], this call has no effect. However, in [[Cluster Mode]] it causes your {{{onPreExchange()}}}, {{{onPostExchange()}}}, and {{{onDraw()}}} methods to be ignored until all of the slaves are up and running and have connected to the master. Your {{{onDisconnectDraw()}}} method is called instead, in master and slaves, to allow you to draw a splash screen if you want to. If you don't override {{{onDisconnectDraw()}}}, the screen will be blanked. Note that unlike {{{onDraw()}}}, if you override this method, you will have to manually clear the window and explicitly set any projection and modelview matrices. The PySZG module function {{{ar_defaultWindowInitCallback()}}} clears the window (you must set the clear color) and the depth buffer and enables the depth test.\n\nOnce all instances of your program are up and running, you want to ensure that all computations happen the same way in all the instances. Ideally you would be able to do everything (except rendering, of course) in {{{onPostExchange()}}}, which is called in master and slaves. The one exception, as mentioned above, is initialization of the random number seed, which should be done only in {{{onPreExchange()}}} in the master. If you're using the framework's random-number generator, you would do this by calling:\n{{{\nframework.setRandomSeed( <negative integer> )\n}}}\n\nSyzygy's random-number generator is based on the 'ran1' algorithm from //Numerical Recipes in C//, and is not recommended for sequences longer than about 10^^8^^ numbers.\n\nOnce you've set it, this seed is automatically shared with the slaves; if you're using some other random-number generator, you will have to manually share the seed (see [[Transferring Data from Master to Slaves]]). You generate a random float (single-precision, on (0,1), i.e. not including 0 or 1) by calling:\n{{{\nframework.randUniformFloat()\n}}}\n\nThat's all the random-number functionality that's currently available.
The Syzygy Python bindings were created using a tool called [[SWIG|http://www.swig.org]]. They constitute a thin layer of code on top of a set of C++ libraries. You get the simplicity and flexibility of Python with the speed of C++. The catch is that you don't have complete access to the internals of the Syzygy classes, so there are restrictions on your ability to modify behaviors.\nThis is particularly the case for sets of coupled classes, in which a piece of code in one Syzygy class is making calls to a method of a different Syzygy class. Let's say that there are Syzygy C++ classes {{{arFoo}}} and {{{arBar}}}. As part of its workings, {{{arFoo}}} makes a call to a method of an {{{arBar}}} instance; call the method {{{barNone()}}}.\n\nWhen we run SWIG, it obligingly generates Python 'shadow classes' {{{arFoo}}} and {{{arBar}}}, with methods that make calls to the corresponding methods of the C++ classes; so, for example, you may see a method called {{{barNone}}} in the Python {{{arBar}}} class that seems to do the same thing that the C++ {{{arBar::barNone()}}} method does.\n\nNow, suppose you want to change the behavior of {{{barNone()}}}. You create a Python sub-class of {{{arBar}}}, {{{MyBar}}}, and override {{{barNone()}}}. When you create an instance of the new class and call its {{{barNone()}}}, it does what you expect. That's Point One: If you're always going to be calling a method yourself from Python, then you can sub-class away. So everything's good, yes?\n\nNo. This is Point Two. The underlying C++ {{{arFoo}}} class doesn't know anything about Python, so in its code it still calls the original {{{barNone()}}} method of the C++ {{{arBar}}} class, completely ignoring your override. So how do you get the underlying C++ objects to recognize your Python code?\n\nAt the Python level, you can't. If you absolutely need to change the functionality of interlocking classes, then you'll need to ignore them and write your own set of Python classes to accomplish the same thing (if you can). However, there is a way we can fix this problem at the SWIG level, although it requires some extra work. In some cases we've already put in this extra work, in particular with regards to the <link>arPyMasterSlaveFramework and with regards to the <link>arPyInteractable. What we've done in these cases is to use SWIG to extend the C++ classes to save a pointer to a callable Python object, and to call that object at the appropriate time.\n\nThe upshot of all this is that you need to be cautious about sub-classing Syzygy classes. If the class name starts with 'arPy', then you can probably sub-class and override pretty freely. In other cases, you can sub-class if you only intend to access the new functionality directly from your own Python code.
It's generally best to start analyzing a Python program by looking at the top, then at the bottom.\n\n! Imported Modules\n\nAt the top (well, in this case, after a long comment about copyrights & such) we have a series of ''import'' statements, specifying what modules, classes and functions are to be imported from other files.\n\n<<tiddler "Teapots.py Import Statements">>\n\n! The {{{__main__}}} Block\n\nAt the bottom we have the {{{__main__}}} block.\n\n<<tiddler "Teapots.py __main__ Block">>\n\n! Event Callbacks\n\nAnalyzing the {{{__main__}}} block leads us to three GLUT event //callbacks//, functions that are called by GLUT in response to certain events.\n\n<<tiddler "Teapots.py Callbacks">>
This tiddler shows some more complex effects that can be obtained with cunning use of CSS. Not all of these will work properly on all browsers because of differences in CSS implementation, but they should fail gracefully.\n\nYou can have special formatting for a specific, named tiddler like this:\n{{{\n#tiddlerHelloThere .title {\nbackground-color: #99aaee;\n}\n}}}\n\nOr for the first displayed tiddler:\n{{{\ndiv.tiddler:first-child .title {\nfont-size: 28pt;\n}\n}}}\n\nOr just for the first line of every tiddler:\n{{{\n.viewer:first-line {\nbackground-color: #999999;\n}\n}}}\n\nOr just for the first letter of every tiddler:\n{{{\n.viewer:first-letter {\nfloat: left;\nfont-size: 28pt;\nfont-weight: bold;\n}\n}}}\n\nOr just for tiddlers tagged with a particular tag (note that this won't work for tags that contain spaces):\n{{{\ndiv[tags~="welcome"].viewer {\nbackground-color: #ffccaa;\n}\n\ndiv[tags~="features"].viewer {\nbackground-color: #88aaff;\n}\n}}}
How do we port the teapots.py program to Syzygy? It's pretty easy, actually, and the Syzygy version will do quite a bit more. Here's the [[complete source code of szgteapots1.py|python/teapots/szgteapots1.py]]; note that if you don't already have it you'll also need the [[teapots.py|python/teapots/teapots.py]] source code. The original contains lots of comments that I'm not going to include below.\n\n!Imports\nLet's start at the top, with some import statements:\n{{{\nfrom PySZG import *\nfrom OpenGL.GL import *\nimport sys\nimport teapots\n}}}\nThe first line imports the Syzygy Python bindings module {{{PySZG}}} (if you don't already have that, here's a [[zipped copy|python/pyszg.zip]]). Next we import the {{{OpenGL.GL}}} sub-module, which contains the OpenGL functions. We need the {{{sys}}} module for access to command-line arguments, which our application framework object needs in order to do its initialization.\n\nThe last line is the interesting one: {{{import teapots}}}. This is one of the very neat things about Python: a program can also be a module. We can import the original GLUT teapots program and make use of its functions and data.\n\n!The Framework Class\nNow, what about the {{{arPyMasterSlaveFramework}}} sub-class I keep referring to? Let's define that now. To show how little code it really contains, I'm going to leave out all the comments that are in the file, and the little bits that add extra functionality:\n{{{\nclass TeapotApp(arPyMasterSlaveFramework):\n def __init__(self):\n arPyMasterSlaveFramework.__init__(self)\n self.teapotData = teapots.teapotDataGlobal\n def onWindowStartGL( self, winInfo ):\n teapots.init() # call init() from teapots.py\n def onDraw( self, win, viewport ):\n glTranslatef(-8,-5,-10)\n for t in self.teapotData:\n teapots.renderTeapot( *t ) \n}}}\nSo what's going on here? We've defined a class named {{{TeapotApp}}} that is a sub-class of {{{arPyMasterSlaveFramework}}}, and then we've defined a few methods to replace or //override// the corresponding methods of the superclass.\n\nNote that in general, most of these methods that override superclass methods //call// the corresponding method of the superclass, e.g.:\n{{{\n def __init__(self):\n arPyMasterSlaveFramework.__init__(self)\n}}}\nThis is a very common idiom in Pythonic object-oriented programming: You want your class to do everything that its superclass does and then some, so you override the superclass' method and call it from the overriding method (and then do some other stuff).\n\nSo what else do we do in the overridden methods? Let's look at them on at a time:\n\nIn the {{{__init__()}}} method (which gets called when we construct an instance of our framework class), we get a reference to the {{{teapotDataGlobal}}} list from teapots.py and store it as a property of the framework. This isn't really necessary, it's just for aesthetic reasons.\n\nIn {{{onWindowStartGL()}}}, we call the {{{init()}}} function from teapots.py to do our OpenGL initialization. Aren't we lazy?\n\nAnd finally we render the teapots in {{{onDraw()}}}. This is almost the same as the {{{display}}} callback in the GLUT version (we call the {{{renderTeapot()}}} function from teapots.py in the same way), with the following differences:\n# The GLUT version used an orthographic projection matrix, whereas in virtual reality we almost always use perspective projection (you can read about the difference in the OpenGLProgrammingGuide). Furthermore, Syzygy by default places the origin of the coordinate system in the center of the floor, whereas many GLUT programs put the eye at the origin. So we need to translate or shift the teapots to have them show up in the window. Hence the {{{glTranslatef()}}} call.\n# Syzygy automatically clears the color and depth buffers by default in {{{onWindowInit()}}}, so we don't have to do that in {{{onDraw()}}}.\n\n!The {{{__main__}}} Block\nAnd that just leaves the {{{__main__}}} block, which we can basically copy from the [[Initialization and Cleanup]] section:\n{{{\nif __name__ == '__main__':\n app = TeapotApp()\n if not app.init(sys.argv):\n raise RuntimeError,'Unable to init application.'\n if not app.start():\n raise RuntimeError,'Unable to start application.'\n}}}\n\nAnd that's it: a working Syzygy version of teapots. Run it with {{{python szgteapots1.py}}}, and it should look like this:\n\n[img[Syzygy Teapots #1|images/szgteapots1.jpg]]\n\nThe obvious difference is the tracking Simulator Interface in the lower-right corner. If you don't see it, it almost certainly means that you're logged into a Syzygy server; you need to be logged out from the server to run Syzygy programs in [[Standalone Mode]]. Issue the {{{dlogout}}} command and try again.\n\nAbout the only thing you can do with this version of the program that you couldn't with the original is to move the head around. Drag the mouse in the window with the left button down to move it left/right and up/down and with the right button down to move left/right and forwards/backwards. For more information about the Simulator Interface, see [[How dictSkeleton.py Works]].\n\nNext we'll look at some very minor changes to a [[More Interesting Syzygy Version of Teapots]].
To make quoted bits of text stand out, you can use BlockQuotes within your [[tiddler]]s, like this:\n\nJeremyRuston said:\n<<<\nA TiddlyWiki is like a blog because it's divided up into neat little chunks, but it encourages you to read it by hyperlinking rather than sequentially: if you like, a non-linear blog analogue that binds the individual microcontent items into a cohesive whole.\n<<<\n\nLike BulletPoints and NumberedBulletPoints, you can have multiple levels of BlockQuotes. Just [[edit]] this tiddler to see how it's done.\n\n>level 1\n>level 1\n>>level 2\n>>level 2\n>>>level 3\n>>>level 3\n>>level 2\n>level 1\n
/***\n|''Name:''|BreadCrumbsPlugin|\n|''Version:''|2.0.10 (28-Apr-2006)|\n|''Author:''|AlanHecht|\n|''Adapted By:''|[[Jack]]|\n|''Type:''|Plugin|\n!Description\nThis plugin creates an area at the top of the tiddler area that displays "breadcrumbs" of where you've been. This is especially useful for ~TWs using ~SinglePageMode by Eric Schulman.\n!Usage\nJust install the plugin and tag with systemConfig. Optionally position the following div in your PageTemplate to control the positioning of the breadcrumbs menu:\n{{{\n<div id='breadCrumbs'></div>\n}}}\n!Revision History\n* Original by AlanHecht\n* 2.0 Made 2.0.x compatible by [[Jack]]\n* Made 2.0.10 compatible (onstart paramifier)\n!Code\n***/\n\n// // Use the following line to set the number of breadcrumbs to display before rotating them off the list.\n//{{{\nversion.extensions.breadCrumbs = {major: 2, minor: 0, revision: 10, date: new Date("Apr 28, 2006")};\nvar crumbsToShow = 7;\nvar breadCrumbs = [];\n\nvar onClickTiddlerLink_orig_breadCrumbs = onClickTiddlerLink;\nonClickTiddlerLink = function(e){\n onClickTiddlerLink_orig_breadCrumbs(e);\n breadcrumbsAdd(e);\n}\n\nvar restart_orig_breadCrumbs = restart;\nrestart = function() {\n restart_orig_breadCrumbs()\n breadCrumbs = [];\n breadcrumbsRefresh();\n}\n\nfunction breadcrumbsAdd(e) {\n var uniqueCrumb = true;\n var crumbIndex = 0;\n if (!e) var e = window.event;\n var target = resolveTarget(e);\n var thisCrumb="[["+resolveTarget(e).getAttribute("tiddlyLink")+"]]";\n var lastInactiveCrumb = breadCrumbs.length -(breadCrumbs.length < crumbsToShow ? breadCrumbs.length : crumbsToShow);\n for(t=lastInactiveCrumb; t<breadCrumbs.length; t++)\n if(breadCrumbs[t] == thisCrumb) {\n uniqueCrumb = false;\n crumbIndex = t+1;\n }\n if(uniqueCrumb)\n breadCrumbs.push(thisCrumb);\n else\n breadCrumbs = breadCrumbs.slice(0,crumbIndex);\n breadcrumbsRefresh(); \n}\n\nfunction breadcrumbsRefresh() {\n \n if (!document.getElementById("breadCrumbs")) {\n // Create breadCrumbs div\n var ca = document.createElement("div");\n ca.id = "breadCrumbs";\n ca.style.visibility= "hidden";\n var targetArea = document.getElementById("tiddlerDisplay");\n targetArea.parentNode.insertBefore(ca,targetArea);\n }\n\n var crumbArea = document.getElementById("breadCrumbs");\n crumbArea.style.visibility = "visible";\n removeChildren(crumbArea);\n createTiddlyButton(crumbArea,"Home",null,restart);\n// crumbArea.appendChild(document.createTextNode(" > "));\n \n var crumbLine = "";\n var crumbCount = breadCrumbs.length;\n var firstCrumb = crumbCount -(crumbCount < crumbsToShow ? crumbCount : crumbsToShow);\n for(t=firstCrumb; t<crumbCount; t++) {\n crumbLine += " > ";\n crumbLine += breadCrumbs[t];\n }\n wikify(crumbLine,crumbArea)\n}\n\n\n//}}}
This is the easiest way to transfer complicated objects from master to slaves, but it has some __severe__ drawbacks.\n\n'Pickling' is a Python method for storing arbitrary data in a file or string. In this data transfer method, the master calls on the standard Python cPickle module (a version of the pickle module written in C) to stuff your object into a String. The slaves receive the string and un-pickle it, restoring the original object. See the pickle module documentation with your Python distribution for restrictions on what can and can't be pickled.\n\nAll of the data transfer methods involve three steps at appropriate times. In your framework's {{{onStart()}}} method, call:\n{{{\n# 'self' is the framework object\nself.initObjectTransfer( 'my_object' )\n}}}\n\nWhere 'my_object' represents an arbitrary String label for one transfer object. Repeat as needed for each object that you will be transferring.\nIn your framework's {{{onPreExchange()}}} method (remember, called only in the master), call:\n{{{\nself.setObject( 'my_object', myObject )\n}}}\n\nWhere myObject is an object to be transferred to the slaves. Again, repeat for each object to be transferred. Finally, in {{{onPostExchange()}}}, call:\n{{{\nif not self.getMaster():\n myObject = self.getObject( 'my_object' )\n}}}\n\nRemember, {{{onPostExchange()}}} is called in master and slaves, but we only need to extract the transferred object in the slaves.\n\nLooks pretty simple, yes? Why would one ever use anything else?\n\nFor starters, there could be a speed issue. {{{setObject()}}} is pickling an object and {{{getObject()}}} is un-pickling it. There's overhead involved in parsing a data format that can support arbitrary Python objects. And you're basically constructing a new {{{myObject}}} on every frame. You can speed things up by adding special methods to the class of {{{myObject}}} that will allow its state to be pickled and un-pickled more efficiently, but then you're losing the simplicity advantage.\n\nThere are also compatibility issues. In our lab, we currently run a mix of Windows 2000 PCs with Python 2.2 installed and Xandros Linux PCs with Python 2.3. Data pickled on one platform can't always be un-pickled on the other.\nPerhaps most importantly, there's also a stability issue. Programs based on this method sometimes just won't run. They can often be fixed by using the Python pickle module as opposed to the C cPickle, but then performance suffers quite a bit.\n\nAnd finally, there are security issues if you don't control both ends of the transaction (yes, people have set up Syzygy clusters in which parts of the cluster were hundreds of miles apart). Pickle was not designed to be secure: in particular, with at least some versions of Python the included pickle module could be made to execute arbitrary code by passing it a specially constructed string to unpickle (see e.g. http://pyro.sourceforge.net/manual/9-security.html).\n\nHowever, the general idea of converting your objects to a string in Python and transferring that is definitely appealing. There are more secure alternatives: One could use the ''banana'' and ''jelly'' modules from the [[Twisted Network|http://www.twistedmatrix.com/]] package or the ''xml.pickle'' module from the [[Gnosis Utilities|http://freshmeat.net/projects/gnosisxml/]]. I'll include examples when I get around to it.
Creating BulletPoints is simple.\n* Just add an asterisk\n* at the beginning of a line.\n** If you want to create sub-bullets\n** start the line with two asterisks\n*** And if you want yet another level\n*** use three asterisks\n* Edit this tiddler to see how it's done\n* You can also do NumberedBulletPoints
In VR there are a number of relevant coordinate systems. First, there is the global coordinate system of your application; let's call this //Virtual World Coordinates//. Obviously, there are //Real World Coordinates//.\n\nHowever, within each of these global coordinate systems there are special, local coordinate frames that we might call Virtual and Real //User Coordinates//. The Real one is used to specify the physical locations of your screens and any tracked input devices, while the Virtual one contains their relative Virtual locations. If your Syzygy configuration has been done correctly, then these two coordinate systems should be congruent with one another and we can think of them as a single User Coordinate System. Note that by convention this is usually set up such that the origin initially corresponds to the origin of the global Virtual World coordinate system and to the center of the floor in the Real World--but that's just a convention. Also, if your apparatus has a natural 'forward' direction, this will normally correspond to the negative Z axis in the Virtual World; positive Y points up and positive X points to the right. These are conventions we inherited from OpenGL.\n\nThe default Syzygy units are feet. In particular, all things relevant to the User coordinate system must be specified in feet. Screen positions must be specified in feet and tracker drivers return positions in feet.\n\nYou can use alternative units for your global Virtual World coordinate system by passing a unit conversion factor to the master/slave framework using the ''setUnitConversion()'' method. Call it passing a Float containing the number of application units/foot.\n\nNote that you can use the OpenGL matrix stack to define additional local Virtual World coordinate systems, e.g. relative to the center and primary axes of an object (see [[Chapter 3: Viewing|http://www.rush3d.com/reference/opengl-redbook-1.1/chapter03.html]] in the [[OpenGL Programming Guide|http://www.rush3d.com/reference/opengl-redbook-1.1/]]). However, I recommend keeping this sort of thing to a minimum. It makes handling e.g. user interaction considerably more complex if the user and object are several levels apart in a coordinate system hierarchy.\n\nWithin this framework, normal navigation<link> in the virtual world consists of moving the Virtual User coordinate system around within the global Virtual World coordinate system. The screens and tracker inputs thus maintain the same relative relationships to one another. This is currently accomplished using the Navigation Matrix. This matrix is not directly accessible, it is modified by a set of framework and global methods. It is automatically transferred from master to slaves.
[[TiddlyWiki|http://www.tiddlywiki.com/]] is written by Jeremy Ruston, (jeremy [at] osmosoft [dot] com) and is released under a BSD-style license contained in the source code.\n\nParts of this file consist of third-party plugins and are governed by any notices contained in the individual tiddlers.\n\nThe following pertain only to the tiddlers contained within this file that are tagged with BookText:\n\nBy Jim Crowell, copyright © 2007 by the University of Illinois Board of Trustees.\n\nThe BookText tiddlers may be redistributed freely provided that:\n+ All of them (including this notice) are present in unmodified form.\n+ All accompanying files are included and links to them from within the tiddlers are functional.\n\nTHESE TIDDLERS ARE PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY\nEXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\nOF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT\nSHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\nINCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED\nTO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\nBUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\nANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH\nDAMAGE.\n
You can wrap any text in an HTML {{{<span>}}} or {{{<div>}}} with a specified CSS class. This allows custom CSS styling in a nice, modular way. By placing a rule in your StyleSheet tiddler like {{{.wrappingClass{color: #666; background: #bbb} }}} you can markup a piece of text in the tiddler using this code:\n\n{{{\n{{wrappingClass{Text that is now accentuated}}}\n}}}\n\n{{wrappingClass{Text that is now accentuated}}}\n\nBy default, the text is placed in a {{{<span>}}}. To use a {{{<div>}}} instead, insert a line break before the text:\n\n{{{\n{{wrappingClass{\nText that is now accentuated}}}\n}}}\n\n{{wrappingClass{\nText that is now accentuated}}}\n
The {{{arMasterSlaveDict}}} uses the framework methods for [[Transferring Arbitrary Sequences]] to transfer the current state of each object from master to slaves.\n\nAt the end of your framework's {{{onPreExchange()}}} method, you should call:\n{{{\nself.dict.packState( self ).\n}}}\n\nThis calls the {{{getState()}}} method of each object in the dictionary. Each object's {{{getState()}}} method must return a sequence of strings, integers, floats, or nested sequences of these types (as described in [[Making Classes Compatible with an arMasterSlaveDict]]. Each such sequence is stuck into a tuple with the object's classname and its key value (the value used to reference it in the {{{arMasterSlaveDict}}}.) These tuples are all glommed together into a big tuple, which is stuffed into the framework.\n\nIn the framework's {{{onPostExchange()}}} method you would typically call:\n{{{\nif not self.getMaster():\n self.dict.unpackState( self )\n}}}\n\nThis causes the dictionary to grab the transferred tuple from the framework. It loops through the individual objects' sub-tuples in the tuple, extracting the object's class, key, and state (which has been converted to a tuple). If the {{{arMasterSlaveDict}}} doesn't already contain an object with that key, one is constructed using the default class constructor or the class factory passed when you instantiated the {{{arMasterSlaveDict}}} (see [[Instantiating and Starting an arMasterSlaveDict]]). Or if an object with the appropriate key existed but its {{{__class__.__name__}}} property does not match the name passed from the master, it is deleted and a new object of the correct type is constructed. Then the keyed object's {{{setState()}}} method is called, passing in the tuple received from the corresponding object on the master. Finally, any objects whose keys were not contained in the received tuple are deleted (because they presumably have been deleted from the {{{arMasterSlaveDict}}} on the master).\n\nKnown issue: If the objects in your {{{arMasterSlaveDict}}} are instances of a sub-class of {{{arPyInteractable}}}, then deleting an object in the master will cause it to be removed from the container in the slaves, but it will not be garbage-collected (i.e. its memory will leak). See {{{clearCallbacks}}} discussion in [[dictSkeleton.py Source Code and Remarks]]. (clearCallbacks() won't be called in the slaves). The discussion of the effect on the {{{arEffector}}} is not relevant, as that only happens on the master.
/***\n|Name|DcTableOfContentsPlugin|\n|Created by|Doug Compton|\n|Source|http://www.zagware.com/tw/plugins.html#DcTableOfContentsPlugin|\n|Version|0.3.0 - 4/12/2006|\n<<showtoc>>\n!Description\nThis macro will insert a table of contents reflecting the headings that are used in a tiddler and will be automatically updated when you make changes. Each item in the table of contents can be clicked on to jump to that heading. It can be used either inside of select tiddlers or inside a system wide template.\n\nA parameter can be used to show the table of contents of a seperate tiddler, &lt;<showtoc tiddlerTitle>&gt;\n\nIt will also place a link beside each header which will jump the screen to the top of the current tiddler. This will only be displayed if the current tiddler is using the &lt;<showtoc>&gt; macro.\n\nThe appearance of the table of contents and the link to jump to the top can be modified using CSS. An example of this is given below.\n\n!Examples\nIf you had a tiddler with the following headings\n\n ! Heading1\n !! Heading2\n !! Heading3\n !!! Heading4\n ! Heading5\n\nthis table of contents would be automatically generated\n*Heading1\n**Heading2\n**Heading3\n***Heading4\n*Heading5\n\n!Changing how it looks\nTo modifiy the appearance, you can use CSS similiar to the below.\n//{{{\n.dcTOC ul {\n color: red;\n list-style-type: lower-roman;\n}\n.dcTOC a {\n color: green;\n border: none;\n}\n\n.dcTOC a:hover {\n background: white;\n border: solid 1px;\n}\n.dcTOCTop {\n font-size: 2em;\n color: green;\n}\n//}}}\n\n!Usage\n!!Only in select tiddlers\nThe table of contents above is an example of how to use this macro in a tiddler. Just insert &lt;<showtoc>&gt; in a tiddler on a line by itself.\n\nIt can also display the table of contents of another tiddler by using the macro with a parameter, &lt;<showtoc tiddlerTitle>&gt;\n!!On every tiddler\nIt can also be used in a template to have it show on every tiddler. An example ViewTemplate is shown below.\n\n//{{{\n<div class='toolbar' macro='toolbar -closeTiddler closeOthers +editTiddler permalink references jump'></div>\n<div class='title' macro='view title'></div>\n<div class='subtitle'>Created <span macro='view created date DD-MM-YY'></span>, updated <span macro='view modified date DD-MM-YY'></span></div>\n<div class='tagging' macro='tagging'></div>\n<div class='tagged' macro='tags'></div>\n<div class="toc" macro='showtoc'></div>\n<div class='viewer' macro='view text wikified'></div>\n<div class='tagClear'></div>\n//}}}\n\n!History\n!!0.3.0 - 04/12/2006\n*Added the ability to show the table of contents of a seperate tiddler.\n*Fixed an error when a heading had a ~WikiLink in it.\n!!0.2.0 - 04/10/2006\n*Added the [top] link on headings to jump to the top of the current tiddler.\n*The appearance can now be customized using CSS.\n*All event handlers now return false.\n!!0.1.0 - 04/07/2006\n*Initial version\n\n!Code\n***/\n//{{{\n\nversion.extensions.DcTableOfContentsPlugin= {\n major: 0, minor: 3, revision: 0,\n date: new Date(2006, 4, 12), \n type: 'macro',\n source: "http://www.zagware.com/tw/plugins.html#DcTableOfContentsPlugin"\n};\n\n// Replace heading formatter with our own\nfor (var n=0; n<config.formatters.length; n++) {\n var format = config.formatters[n];\n if (format.name == 'heading') {\n format.handler = function(w) {\n // following two lines is the default handler\n var e = createTiddlyElement(w.output, "h" + w.matchLength);\n w.subWikify(e, this.terminator);\n\n // Only show [top] if current tiddler is using showtoc\n if (w.tiddler.isTOCInTiddler == 1) {\n // Create to outter SPAN containing the default CSS values\n var span = createTiddlyElement(e, "span");\n span.setAttribute("style", "font-size: .5em; color: blue;");\n\n // Create the link to jump to the top\n createTiddlyButton(span, " [top]", "Go to top of tiddler", window.scrollToTop, "dcTOCTop", null, null);\n }\n }\n break;\n }\n}\n\n\nconfig.macros.showtoc = {\n handler: function(place, macroName, params, wikifier, paramString, tiddler) {\n var text = "";\n var title = "";\n var myTiddler = null;\n\n // Did they pass in a tiddler?\n if (params.length) {\n title = params[0];\n myTiddler = store.getTiddler(title);\n } else {\n myTiddler = tiddler;\n }\n\n if (myTiddler == null) {\n wikify("ERROR: Could not find " + title, place);\n return;\n }\n\n var lines = myTiddler .text.split("\sn");\n myTiddler.isTOCInTiddler = 1;\n\n // Create to SPAN so the TOC can be customized using CSS\n var span = createTiddlyElement(place, "span", null, "dcTOC");\n\n if (lines != null) {\n for (var x=0; x<lines.length; x++) {\n var line = lines[x];\n if (line.substr(0,1) == "!") {\n // Find first non ! char\n for (var i=0; i<line.length; i++) {\n if (line.substr(i, 1) != "!") {\n break;\n }\n }\n var desc = line.substring(i);\n // Remove WikiLinks\n desc = desc.replace(/\s[\s[/g, "");\n desc = desc.replace(/\s]\s]/g, "");\n\n text += line.substr(0, i).replace(/[!]/g, '*');\n text += '<html><a href="javascript:;" onClick="window.scrollToHeading(\s'' + title + '\s', \s'' + desc+ '\s', event)">' + desc+ '</a></html>\sn';\n }\n }\n }\n wikify(text, span);\n }\n}\n\nwindow.scrollToTop = function(evt) {\n if (! evt)\n var evt = window.event;\n\n var target = resolveTarget(evt);\n var tiddler = story.findContainingTiddler(target);\n\n if (! tiddler)\n return false;\n\n window.scrollTo(0, ensureVisible(tiddler));\n\n return false;\n}\n\nwindow.scrollToHeading = function(title, anchorName, evt) {\n var tiddler = null;\n\n if (! evt)\n var evt = window.event;\n\n if (title) {\n story.displayTiddler(store.getTiddler(title), title, null, false);\n tiddler = document.getElementById(story.idPrefix + title);\n } else {\n var target = resolveTarget(evt);\n tiddler = story.findContainingTiddler(target);\n }\n\n if (tiddler == null)\n return false;\n \n var children1 = tiddler.getElementsByTagName("h1");\n var children2 = tiddler.getElementsByTagName("h2");\n var children3 = tiddler.getElementsByTagName("h3");\n var children4 = tiddler.getElementsByTagName("h4");\n var children5 = tiddler.getElementsByTagName("h5");\n\n var children = new Array();\n children = children.concat(children1, children2, children3, children4, children5);\n\n for (var i = 0; i < children.length; i++) {\n for (var j = 0; j < children[i].length; j++) {\n var heading = children[i][j].innerHTML;\n\n // Remove all HTML tags\n while (heading.indexOf("<") >= 0) {\n heading = heading.substring(0, heading.indexOf("<")) + heading.substring(heading.indexOf(">") + 1);\n }\n\n // Cut off the code added in showtoc for TOP\n heading = heading.substr(0, heading.length-6);\n\n if (heading == anchorName) {\n var y = findPosY(children[i][j]);\n window.scrollTo(0,y);\n return false;\n }\n }\n }\n return false\n}\n\n//}}}\n
BookText\nTableOfContents\n
The {{{arMasterSlaveDict}}} also has a {{{draw()}}} method that takes a reference to the framework as an optional parameter. This iterates over all objects in the dictionary. It checks each object for an attribute named 'draw'; if it has one, it calls it, passing the framework as a parameter:\n{{{\nself.dict.draw( self )\n}}}\n\nNote that not all objects in the dictionary have to have a {{{draw()}}} method; any that don't will just be skipped. However, if an object has an attribute named 'draw' that isn't a method, your program will crash.
<!---\n| Name:|~TagglyTaggingEditTemplate |\n| Version:|1.1 (12-Jan-2006)|\n| Source:|http://simonbaird.com/mptw/#TagglyTaggingEditTemplate|\n| Purpose:|See TagglyTagging for more info|\n| Requires:|You need the CSS in TagglyTaggingStyles to make it look right|\n--->\n<!--{{{-->\n<div class="toolbar" macro="toolbar +saveTiddler closeOthers -cancelTiddler deleteTiddler"></div>\n<div class="title" macro="view title"></div>\n<div class="editLabel">Title</div><div class="editor" macro="edit title"></div>\n<div class="editLabel">Tags</div><div class="editor" macro="edit tags"></div>\n<div class="editorFooter"><span macro="message views.editor.tagPrompt"></span><span macro="tagChooser"></span></div>\n<div class="editor" macro="edit text"></div>\n<br/>\n<!--}}}-->
{{{\n[img[title|filename]]\n[img[filename]]\n[img[title|filename][link]]\n[img[filename][link]]\n}}}\nImages can be included by their filename or full URL. It's good practice to include a title to be shown as a tooltip, and when the image isn't available. An image can also link to another tiddler or or a URL\n[img[Romanesque broccoli|fractalveg.jpg][http://www.flickr.com/photos/jermy/10134618/]]\n{{{\n[img[Fractal vegetable|fractalveg.jpg]]\n[img[This is shown as a tooltip|http://example.com/image.jpg]]\n[img[http://example.com/image.jpg]]\n[img[http://example.com/image.jpg][ExampleDotCom]]\n}}}\nThe tooltip is optional.\n\n[<img[Forest|forest.jpg][http://www.flickr.com/photos/jermy/8749660/]][>img[Field|field.jpg][http://www.flickr.com/photos/jermy/8749285/]]You can also float images to the left or right: the forest is left aligned with {{{[<img[}}}, and the field is right aligned with {{{[>img[}}}.\n@@clear(left):clear(right):display(block):You can use CSS to clear the floats@@\n{{{\n[<img[A woody bit of Hampstead Heath|forest.jpg]]\n[>img[A field near Milton Keynes|field.jpg]]\n}}}
The following two callback methods are sometimes important but a bit tricky. They are called from a separate processing thread from the rest. If you use them, make sure any variables accessed both by them and by the main thread methods are protected by locks (and no, I'm not going into that in more detail here).\n\nIf you'll recall, I said in the [[Per-Frame Event Loop]] section that the continuous stream of input events are buffered and collected by the framework once per frame. However, if you really need access to input event //right when they arrive//, you can use the {{{onInputEvent()}}} method. It's called once for each event that arrives. See [[Syzygy Input Events]] for information about working with these events.\n\nFinally, when running in [[Cluster Mode]], you can send text messages to instances of your program. You can either send messages from one instance to another or from the command line using the //dmsg// command. These messages are received and passed to the framework's {{{onUserMessage()}}} method.
#displayArea {background-color: #ffccff; }\n#mainMenu {border: 1px solid #ffff88; }\n#commandPanel {background-color: #008800; }
Like most wikis, TiddlyWiki supports a range of simplified character formatting:\n| !To get | !Type this |h\n| ''Bold'' | {{{''Bold''}}} |\n| ==Strikethrough== | {{{==Strikethrough==}}} |\n| __Underline__ | {{{__Underline__}}} (that's two underline characters) |\n| //Italic// | {{{//Italic//}}} |\n| Superscript: 2^^3^^=8 | {{{2^^3^^=8}}} |\n| Subscript: a~~ij~~ = -a~~ji~~ | {{{a~~ij~~ = -a~~ji~~}}} |\n| @@highlight@@ | {{{@@highlight@@}}} |\n<<<\nThe highlight can also accept CSS syntax to directly style the text:\n@@color:green;green coloured@@\n@@background-color:#ff0000;color:#ffffff;red coloured@@\n@@text-shadow:black 3px 3px 8px;font-size:18pt;display:block;margin:1em 1em 1em 1em;border:1px solid black;Access any CSS style@@\n<<<\n\n//For backwards compatibility, the following highlight syntax is also accepted://\n{{{\n@@bgcolor(#ff0000):color(#ffffff):red coloured@@\n}}}\n@@bgcolor(#ff0000):color(#ffffff):red coloured@@
/***\n|Name|FontSizePlugin|\n|Created by|SaqImtiaz|\n|Location|http://lewcid.googlepages.com/lewcid.html#FontSizePlugin|\n|Version|1.0|\n|Requires|~TW2.x|\n!Description:\nResize tiddler text on the fly. The text size is remembered between sessions by use of a cookie.\nYou can customize the maximum and minimum allowed sizes.\n(only affects tiddler content text, not any other text)\n\nAlso, you can load a TW file with a font-size specified in the url.\nEg: http://lewcid.googlepages.com/lewcid.html#font:110\n\n!Demo:\nTry using the font-size buttons in the sidebar, or in the MainMenu above.\n\n!Installation:\nCopy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.\nThen put {{{<<fontSize "font-size:">>}}} in your SideBarOptions tiddler, or anywhere else that you might like.\n\n!Usage\n{{{<<fontSize>>}}} results in <<fontSize>>\n{{{<<fontSize font-size: >>}}} results in <<fontSize font-size:>>\n\n!Customizing:\nThe buttons and prefix text are wrapped in a span with class fontResizer, for easy css styling.\nTo change the default font-size, and the maximum and minimum font-size allowed, edit the config.fontSize.settings section of the code below.\n\n!Notes:\nThis plugin assumes that the initial font-size is 100% and then increases or decreases the size by 10%. This stepsize of 10% can also be customized.\n\n!History:\n*27-07-06, version 1.0 : prevented double clicks from triggering editing of containing tiddler.\n*25-07-06, version 0.9\n\n!Code\n***/\n\n//{{{\nconfig.fontSize={};\n\n//configuration settings\nconfig.fontSize.settings =\n{\n defaultSize : 100, // all sizes in %\n maxSize : 200,\n minSize : 40,\n stepSize : 10\n};\n\n//startup code\nvar fontSettings = config.fontSize.settings;\n\nif (!config.options.txtFontSize)\n {config.options.txtFontSize = fontSettings.defaultSize;\n saveOptionCookie("txtFontSize");}\nsetStylesheet(".tiddler .viewer {font-size:"+config.options.txtFontSize+"%;}\sn","fontResizerStyles");\nsetStylesheet("#contentWrapper .fontResizer .button {display:inline;font-size:105%; font-weight:bold; margin:0 1px; padding: 0 3px; text-align:center !important;}\sn .fontResizer {margin:0 0.5em;}","fontResizerButtonStyles");\n\n//macro\nconfig.macros.fontSize={};\nconfig.macros.fontSize.handler = function (place,macroName,params,wikifier,paramString,tiddler)\n{\n\n var sp = createTiddlyElement(place,"span",null,"fontResizer");\n sp.ondblclick=this.onDblClick;\n if (params[0])\n createTiddlyText(sp,params[0]);\n createTiddlyButton(sp,"+","increase font-size",this.incFont);\n createTiddlyButton(sp,"=","reset font-size",this.resetFont);\n createTiddlyButton(sp,"–","decrease font-size",this.decFont);\n}\n\nconfig.macros.fontSize.onDblClick = function (e)\n{\n if (!e) var e = window.event;\n e.cancelBubble = true;\n if (e.stopPropagation) e.stopPropagation();\n return false;\n}\n\nconfig.macros.fontSize.setFont = function ()\n{\n saveOptionCookie("txtFontSize");\n setStylesheet(".tiddler .viewer {font-size:"+config.options.txtFontSize+"%;}\sn","fontResizerStyles");\n}\n\nconfig.macros.fontSize.incFont=function()\n{\n if (config.options.txtFontSize < fontSettings.maxSize)\n config.options.txtFontSize = (config.options.txtFontSize*1)+fontSettings.stepSize;\n config.macros.fontSize.setFont();\n}\n\nconfig.macros.fontSize.decFont=function()\n{\n\n if (config.options.txtFontSize > fontSettings.minSize)\n config.options.txtFontSize = (config.options.txtFontSize*1) - fontSettings.stepSize;\n config.macros.fontSize.setFont();\n}\n\nconfig.macros.fontSize.resetFont=function()\n{\n\n config.options.txtFontSize=fontSettings.defaultSize;\n config.macros.fontSize.setFont();\n}\n\nconfig.paramifiers.font =\n{\n onstart: function(v)\n {\n config.options.txtFontSize = v;\n config.macros.fontSize.setFont();\n }\n};\n//}}}
/***\n|Name|FullScreenPlugin|\n|Created by|SaqImtiaz|\n|Location|http://tw.lewcid.org/#FullScreenPlugin|\n|Version|1.1|\n|Requires|~TW2.x|\n!Description:\nToggle between viewing tiddlers fullscreen and normally. Very handy for when you need more viewing space.\n\n!Demo:\nClick the ↕ button in the toolbar for this tiddler. Click it again to turn off fullscreen.\n\n!Installation:\nCopy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.\nEdit the ViewTemplate to add the fullscreen command to the toolbar.\n\n!History:\n*25-07-06: ver 1.1\n*20-07-06: ver 1.0\n\n!Code\n***/\n//{{{\nvar lewcidFullScreen = false;\n\nconfig.commands.fullscreen =\n{\n text:" ↕ ",\n tooltip:"Fullscreen mode"\n};\n\nconfig.commands.fullscreen.handler = function (event,src,title)\n{\n if (lewcidFullScreen == false)\n {\n lewcidFullScreen = true;\n setStylesheet('#sidebar, .header, #mainMenu{display:none;} #displayArea{margin:0em 0 0 0 !important;}',"lewcidFullScreenStyle");\n }\n else\n {\n lewcidFullScreen = false;\n setStylesheet(' ',"lewcidFullScreenStyle");\n }\n}\n\nconfig.macros.fullscreen={};\nconfig.macros.fullscreen.handler = function(place,macroName,params,wikifier,paramString,tiddler)\n{\n var label = params[0]||" ↕ ";\n var tooltip = params[1]||"Fullscreen mode";\n createTiddlyButton(place,label,tooltip,config.commands.fullscreen.handler);\n}\n\nvar lewcid_fullscreen_closeTiddler = Story.prototype.closeTiddler;\nStory.prototype.closeTiddler =function(title,animate,slowly)\n{\n lewcid_fullscreen_closeTiddler.apply(this,arguments);\n if (story.isEmpty() && lewcidFullScreen == true)\n config.commands.fullscreen.handler();\n}\n\n\nSlider.prototype.lewcidStop = Slider.prototype.stop;\nSlider.prototype.stop = function()\n{\n this.lewcidStop();\n if (story.isEmpty() && lewcidFullScreen == true)\n config.commands.fullscreen.handler();\n}\n//}}}
Input events are accessed in one of two ways: by [[Polling the Framework's Input State]], either directly or indirectly via an {{{arEffector}}}, or by [[Filtering Individual Input Events]].
You can divide a tiddler into\n----\nsections by typing four dashes on a line by themselves
As noted in [[A Note on Sub-classing]], we have created special sub-classes of certain Syzygy classes in the Python bindings that install their own methods as callbacks.\n\nThe way this is currently done causes a problem for the Python memory management. In Python, every object has an associated reference count, reflecting the number of references to that object that exist. When an object's reference count goes to zero, it is deleted.\n\nCurrently, the routines that take a reference to a callable object as a callback increment that object's reference count. This is the correct thing to do if the callable object is, say, a global function. It is not the correct thing to do if it is a reference to a method of the object for which the callback is being installed (which is the case in all of the arPy... classes). In these classes, the fact that the object now owns an extra reference to one or more of its own methods means that it will never be deleted until the extra reference or references are removed.\n\nIn practice, this is really only an issue for the arPyInteractable class; for example, it doesn't arise for the arPyMasterSlaveFramework because there's only ever one instance of it and it gets forcefully deleted when the application exits. And it's usually not going to be much of an issue even for the arPyInteractable, unless you're trying to create and delete thousands of them. If you're using a bleeding-edge version of Syzygy it may even have been fixed. Until then, there's a workaround in the form of the arPyInteractable's clearCallbacks() method
dictSkeleton is a simple Python program that illustrates several features of Syzygy. The Source Code is shown below.Save it to a file and make a copy of your StandaloneMode configuration file in the same directory, then open a command shell in that directory and type:\n{{{\npython dictSkeleton.py\n}}}\nAfter printing a number of messages that won't be very meaningful at this point, the program should open a window that looks like this (without the light-blue labels):\n\n[img[dictskel1|images/dictskel1.jpg]]\n\nThis is just about the simplest possible virtual world. All it contains is one yellow square hovering in the air seven feet in front of you. You are holding a gray stick in your hand, the virtual wand. You can grab the square with the tip of the virtual wand and wave it around. You can click a button to add new squares and another to delete them.\n\nHow do you do that? If you were running this program in ClusterMode and were holding an input device with a tracker attached, the base of the virtual wand would appear to be attached to your input device (as though you were holding the wand). Waving the tracked input device around would wave the virtual wand around.\n\nIn StandaloneMode we definitely don't have access to such devices, and you may not have access to one at all. In these cases, we have recourse the Simulator Interface. This is the widget in the lower-right corner of the window; it will appear every time a program is run in StandaloneMode.\n\n[img[dictskel2|images/dictskel2.jpg]]\n\nThe yellow sphere with two embedded green spheres represents your head. The thing just to the left of it represents a wand, an input device with buttons and a joystick control for interacting with virtual objects. The little magenta ball on the end of the red line indicates the direction it's pointing, the green line is the left-right axis, the blue line is the vertical axis. The numbered red circles in the lower-right corner represent buttons; by default, it assumes that you want to simulate a device with 8 buttons, numbered 0-7. It also assumes that your real mouse only has two buttons, which is why the circles appear in 4 rows of 2. The white dot to the left of the zero indicates that pressing the left mouse button will press button "0" and the right mouse button will press button "1"; pressing the space bar will cause the dot to cycle through the 4 rows.\n\n[img[dictskel3|images/dictskel3.jpg]]\n\nIf your mouse does have a center button, click it and the button aray will re-arrange itself as shown above.\n\nFinally, the white square with a green circle in it below the red buttons represents a joystick; this position of the circle within the square represents the direction of joystick displacement.\n\nThe simulator interface is modal. The line at the top that says "[1] Translate head" indicates the current mode. The [1] indicates that you press the "1" key (on the main part of the keyboard, not the numeric keypad) to get into the head translation mode. The modes are as follows:\n\n[1] Head translation: Hold down the left mouse button and drag the mouse to move the head left/right and up/down; hold down the right button to move the head left/right and forwards/backwards.\n\n[2] Head rotation: Hold down the left mouse button and drag the mouse to turn the head left/right; hold down the right button to turn it up/down.\n\n[3] Wand translation: Works like [1], except it moves the wand instead of the head.\n\n[4] Wand translation+buttons: Move the mouse without pressing any buttons to translate the wand left/right and up/down; press mouse buttons to press wand simulator buttons.\n\n[5] Wand rotation+buttons: Move the mouse without pressing any buttons to rotate the wand left/right and up/down; press mouse buttons to press wand simulator buttons.\n\n[6] Joystick: Hold down the left mouse button and drag to tilt the simulated joystick; the green circle will shift appropriately inside the white square. In many programs this will cause your viewpoint to fly around.\n\n[7] Simulator rotation: Hold down the left mouse button and drag left/right to rotate the view of the simulator as shown below.\n\n[img[dictskel4|images/dictskel4.jpg]]
Entities in HTML documents allow characters to be entered that can't easily be typed on an ordinary keyboard. They take the form of an ampersand (&), an identifying string, and a terminating semi-colon (;). There's a complete reference [[here|http://www.htmlhelp.com/reference/html40/entities/]]; some of the more common and useful ones are shown below. Also see [[Paul's Notepad|http://thepettersons.org/PaulsNotepad.html#GreekHtmlEntities%20HtmlEntitiesList%20LatinHtmlEntities%20MathHtmlEntities]] for a more complete list.\n\n|>|>|>|>|>|>| !HTML Entities |\n| &amp;nbsp; | &nbsp; | no-break space | &nbsp;&nbsp; | &amp;apos; | &apos; | single quote, apostrophe |\n| &amp;ndash; | &ndash; | en dash |~| &amp;quot; | &quot; | quotation mark |\n| &amp;mdash; | &mdash; | em dash |~| &amp;prime; | &prime; | prime; minutes; feet |\n| &amp;hellip; | &hellip; | horizontal ellipsis |~| &amp;Prime; | &Prime; | double prime; seconds; inches |\n| &amp;copy; | &copy; | Copyright symbol |~| &amp;lsquo; | &lsquo; | left single quote |\n| &amp;reg; | &reg; | Registered symbol |~| &amp;rsquo; | &rsquo; | right single quote |\n| &amp;trade; | &trade; | Trademark symbol |~| &amp;ldquo; | &ldquo; | left double quote |\n| &amp;dagger; | &dagger; | dagger |~| &amp;rdquo; | &rdquo; | right double quote |\n| &amp;Dagger; | &Dagger; | double dagger |~| &amp;laquo; | &laquo; | left angle quote |\n| &amp;para; | &para; | paragraph sign |~| &amp;raquo; | &raquo; | right angle quote |\n| &amp;sect; | &sect; | section sign |~| &amp;times; | &times; | multiplication symbol |\n| &amp;uarr; | &uarr; | up arrow |~| &amp;darr; | &darr; | down arrow |\n| &amp;larr; | &larr; | left arrow |~| &amp;rarr; | &rarr; | right arrow |\n| &amp;lArr; | &lArr; | double left arrow |~| &amp;rArr; | &rArr; | double right arrow |\n| &amp;harr; | &harr; | left right arrow |~| &amp;hArr; | &hArr; | double left right arrow |\n\nThe table below shows how accented characters can be built up by subsituting a base character into the various accent entities in place of the underscore ('_'):\n\n|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>|>| !Accented Characters |\n| grave accent | &amp;_grave; | &Agrave; | &agrave; | &Egrave; | &egrave; | &Igrave; | &igrave; | &Ograve; | &ograve; | &Ugrave; | &ugrave; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |\n| acute accent | &amp;_acute; | &Aacute; | &aacute; | &Eacute; | &eacute; | &Iacute; | &iacute; | &Oacute; | &oacute; | &Uacute; | &uacute; | &nbsp; | &nbsp; | &Yacute; | &yacute; | &nbsp; | &nbsp; |\n| circumflex accent | &amp;_circ; | &Acirc; | &acirc; | &Ecirc; | &ecirc; | &Icirc; | &icirc; | &Ocirc; | &ocirc; | &Ucirc; | &ucirc; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |\n| umlaut mark | &amp;_uml; | &Auml; | &auml; | &Euml; | &euml; | &Iuml; | &iuml; | &Ouml; | &ouml; | &Uuml; | &uuml; | &nbsp; | &nbsp; | &Yuml; | &yuml; | &nbsp; | &nbsp; |\n| tilde | &amp;_tilde; | &Atilde; | &atilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Otilde; | &otilde; | &nbsp; | &nbsp; | &Ntilde; | &ntilde; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |\n| ring | &amp;_ring; | &Aring; | &aring; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |\n| slash | &amp;_slash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Oslash; | &oslash; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; |\n| cedilla | &amp;_cedil; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &nbsp; | &Ccedil; | &ccedil; |
/***\n''Import Tiddlers Plugin for TiddlyWiki version 1.2.x, 2.0 and 2.1beta''\n^^author: Eric Shulman - ELS Design Studios\nsource: http://www.TiddlyTools.com/#ImportTiddlersPlugin\nlicense: [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]^^\n\nWhen many people share and edit copies of the same TiddlyWiki document, the ability to quickly collect all these changes back into a single, updated document that can then be redistributed to the entire group is very important. It can also be very extremely helpful when moving your own tiddlers from document to document (e.g., when upgrading to the latest version of TiddlyWiki, or 'pre-loading' your favorite stylesheets into a new 'empty' TiddlyWiki document.)\n\nThis plugin lets you selectively combine tiddlers from any two TiddlyWiki documents. An interactive control panel lets you pick a document to import from, and then select which tiddlers to import, with prompting for skip, rename, merge or replace actions when importing tiddlers that match existing titles. Automatically add tags to imported tiddlers so they are easy to find later on. Generates a detailed report of import 'history' in ImportedTiddlers.\n!!!!!Interactive interface\n<<<\n{{{<<importTiddlers>>}}}\ncreates "import tiddlers" link. click to show/hide import control panel\n\n{{{<<importTiddlers inline>>}}}\ncreates import control panel directly in tiddler content\n\n<<importTiddlers inline>>\n\nPress ''[browse]'' to select a TiddlyWiki document file to import. You can also type in the path/filename or a remote document URL (starting with http://)and press ''[open]''. //Note: There may be some delay to permit the browser time to access and load the document before updating the listbox with the titles of all tiddlers that are available to be imported.//\n\nSelect one or more titles from the listbox (hold CTRL or SHIFT while clicking to add/remove the highlight from individual list items). You can press ''[select all]'' to quickly highlight all tiddler titles in the list. Use the ''[-]'', ''[+]'', or ''[=]'' links to adjust the listbox size so you can view more (or less) tiddler titles at one time. When you have chosen the tiddlers you want to import and entered any extra tags, press ''[import]'' to begin copying them to the current TiddlyWiki document.\n\n''select: all, new, changes, or differences''\n\nYou can click on ''all'', ''new'', ''changes'', or ''differences'' to automatically select a subset of tiddlers from the list. This makes it very quick and easy to find and import just the updated tiddlers you are interested in:\n>''"all"'' selects ALL tiddlers from the import source document, even if they have not been changed.\n>''"new"'' selects only tiddlers that are found in the import source document, but do not yet exist in the destination document\n>''"changes"'' selects only tiddlers that exist in both documents but that are newer in the source document\n>''"differences"'' selects all new and existing tiddlers that are different from the destination document (even if destination tiddler is newer)\n\n''Import Tagging:''\n\nTiddlers that have been imported can be automatically tagged, so they will be easier to find later on, after they have been added to your document. New tags are entered into the "add tags" input field, and then //added// to the existing tags for each tiddler as it is imported.\n\n''Skip, Rename, Merge, or Replace:''\n\nWhen importing a tiddler whose title is identical to one that already exists, the import process pauses and the tiddler title is displayed in an input field, along with four push buttons: ''[skip]'', ''[rename]'', ''[merge]'' and ''[replace]''.\n\nTo bypass importing this tiddler, press ''[skip]''. To import the tiddler with a different name (so that both the tiddlers will exist when the import is done), enter a new title in the input field and then press ''[rename]''. Press ''[merge]'' to combine the content from both tiddlers into a single tiddler. Press ''[replace]'' to overwrite the existing tiddler with the imported one, discarding the previous tiddler content.\n\n//Note: if both the title ''and'' modification date/////time match, the imported tiddler is assumed to be identical to the existing one, and will be automatically skipped (i.e., not imported) without asking.//\n\n''Import Report History''\n\nWhen tiddlers are imported, a report is generated into ImportedTiddlers, indicating when the latest import was performed, the number of tiddlers successfully imported, from what location, and by whom. It also includes a list with the title, date and author of each tiddler that was imported.\n\nWhen the import process is completed, the ImportedTiddlers report is automatically displayed for your review. If more tiddlers are subsequently imported, a new report is //added// to ImportedTiddlers, above the previous report (i.e., at the top of the tiddler), so that a reverse-chronological history of imports is maintained.\n\nIf a cumulative record is not desired, the ImportedTiddlers report may be deleted at any time. A new ImportedTiddlers report will be created the next time tiddlers are imported.\n\nNote: You can prevent the ImportedTiddlers report from being generated for any given import activity by clearing the "create a report" checkbox before beginning the import processing.\n\n<<<\n!!!!!non-interactive 'load tiddlers' macro\n<<<\nUseful for automated installation/update of plugins and other tiddler content.\n\n{{{<<loadTiddlers "label:load tiddlers from %0" http://www.tiddlytools.com/example.html confirm>>}}}\n<<loadTiddlers "label:load tiddlers from %0" http://www.tiddlytools.com/example.html confirm>>\n\nSyntax:\n{{{<<loadTiddlers label:text prompt:text filter source quiet confirm>>}}}\n\n''label:text'' and ''prompt:text''\n>defines link text and tooltip (prompt) that can be clicked to trigger the load tiddler processing. If a label is NOT provided, then no link is created and loadTiddlers() is executed whenever the containing tiddler is rendered.\n''filter'' (optional) determines which tiddlers will be automatically selected for importing. Use one of the following keywords:\n>''"all"'' retrieves ALL tiddlers from the import source document, even if they have not been changed.\n>''"new"'' retrieves only tiddlers that are found in the import source document, but do not yet exist in the destination document\n>''"changes"'' retrieves only tiddlers that exist in both documents for which the import source tiddler is newer than the existing tiddler\n>''"updates"'' retrieves both ''new'' and ''changed'' tiddlers (this is the default action when none is specified)\n>''"tiddler:~TiddlerName"'' retrieves only the specific tiddler named in the parameter.\n>''"tag:text"'' retrieves only the tiddlers tagged with the indicated text.\n''source'' (required) is the location of the imported document. It can be either a local document path/filename in whatever format your system requires, or a remote web location (starting with "http://" or "https://")\n>use the keyword ''ask'' to prompt for a source location whenever the macro is invoked\n''"quiet"'' (optional)\n>supresses all status message during the import processing (e.g., "opening local file...", "found NN tiddlers..." etc). Note that if ANY tiddlers are actualy imported, a final information message will still be displayed (along with the ImportedTiddlers report), even when 'quiet' is specified. This ensures that changes to your document cannot occur without any visible indication at all.\n''"confirm"'' (optional)\n>adds interactive confirmation. A browser message box (OK/Cancel) is displayed for each tiddler that will be imported, so that you can manually bypass any tiddlers that you do not want to import.\n<<<\n!!!!!Installation\n<<<\ncopy/paste the following tiddlers into your document:\n''ImportTiddlersPlugin'' (tagged with <<tag systemConfig>>)\n\ncreate/edit ''SideBarOptions'': (sidebar menu items) \n^^Add "< < ImportTiddlers > >" macro^^\n\n''Quick Installation Tip #1:''\nIf you are using an unmodified version of TiddlyWiki (core release version <<version>>), you can get a new, empty TiddlyWiki with the Import Tiddlers plugin pre-installed (''[[download from here|TW+ImportExport.html]]''), and then simply import all your content from your old document into this new, empty document.\n<<<\n!!!!!Revision History\n<<<\n//wffl store.addNotification(null,refreshImportList); // \n\n''2006.08.16 [3.0.6]'' Use higher-level store.saveTiddler() instead of store.addTiddler() to avoid conflicts with ZW and other adaptations that hijack low-level tiddler handling. Also, in CreateImportPanel(), no longer register notify to "refresh listbox after every tiddler change" (left over from old 'auto-filtered' list handling). Thanks to Bob McElrath for report/solution.\n''2006.07.29 [3.0.5]'' added noChangeMsg to loadTiddlers processing. if not 'quiet' mode, reports skipped tiddlers.\n''2006.04.18 [3.0.4]'' in loadTiddlers.handler, fixed parsing of "prompt:" param. Also, corrected parameters mismatch in loadTiddlers() callback function definition (order of params was wrong, resulting in filters NOT being applied)\n''2006.04.12 [3.0.3]'' moved many display messages to macro properties for easier L10N translations via 'lingo' definitions.\n''2006.04.12 [3.0.2]'' additional refactoring of 'core candidate' code. Proposed API now defines "loadRemoteFile()" for XMLHttpRequest processing with built in fallback for handling local filesystem access, and readTiddlersFromHTML() to process the resulting source HTML content.\n''2006.04.04 [3.0.1]'' in refreshImportList(), when using [by tags], tiddlers without tags are now included in a new "untagged" psuedo-tag list section\n''2006.04.04 [3.0.0]'' Separate non-interactive {{{<<importTiddlers...>>}}} macro functionality for incorporation into TW2.1 core and renamed as {{{<<loadTiddlers>>}}} macro. New parameters for loadTiddlers: ''label:text'' and ''prompt:text'' for link creation, ''ask'' for filename/URL, ''tag:text'' for filtering, "confirm" for accept/reject of individual inbound tiddlers. Also, ImportedTiddlers report generator output has been simplified and "importReplace/importPublic" tags and associated "force" param (which were rarely, if ever, used) has been dropped.\n''2006.03.30 [2.9.1]'' when extracting store area from remote URL, look for "</body>" instead of "</body>\sn</html>" so it will match even if the "\sn" is absent from the source.\n''2006.03.30 [2.9.0]'' added optional 'force' macro param. When present, autoImportTiddlers() bypasses the checks for importPublic and importReplace. Based on a request from Tom Otvos.\n''2006.03.28 [2.8.1]'' in loadImportFile(), added checks to see if 'netscape' and 'x.overrideMimeType()' are defined (IE does *not* define these values, so we bypass this code)\nAlso, when extracting store area from remote URL, explicitly look for "</body>\sn</html>" to exclude any extra content that may have been added to the end of the file by hosting environments such as GeoCities. Thanks to Tom Otvos for finding these bugs and suggesting some fixes.\n''2006.02.21 [2.8.0]'' added support for "tiddler:TiddlerName" filtering parameter in auto-import processing\n''2006.02.21 [2.7.1]'' Clean up layout problems with IE. (Use tables for alignment instead of SPANs styled with float:left and float:right)\n''2006.02.21 [2.7.0]'' Added "local file" and "web server" radio buttons for selecting dynamic import source controls in ImportPanel. Default file control is replaced with URL text input field when "web server" is selected. Default remote document URL is defined in SiteURL tiddler. Also, added option for prepending SiteProxy URL as prefix to remote URL to mask cross-domain document access (requires compatible server-side script)\n''2006.02.17 [2.6.0]'' Removed "differences only" listbox display mode, replaced with selection filter 'presets': all/new/changes/differences. Also fixed initialization handling for "add new tags" so that checkbox state is correctly tracked when panel is first displayed.\n''2006.02.16 [2.5.4]'' added checkbox options to control "import remote tags" and "keep existing tags" behavior, in addition to existing "add new tags" functionality.\n''2006.02.14 [2.5.3]'' FF1501 corrected unintended global 't' (loop index) in importReport() and autoImportTiddlers()\n''2006.02.10 [2.5.2]'' corrected unintended global variable in importReport().\n''2006.02.05 [2.5.1]'' moved globals from window.* to config.macros.importTiddlers.* to avoid FireFox 1.5.0.1 crash bug when referencing globals\n''2006.01.18 [2.5.0]'' added checkbox for "create a report". Default is to create/update the ImportedTiddlers report. Clear the checkbox to skip this step.\n''2006.01.15 [2.4.1]'' added "importPublic" tag and inverted default so that auto sharing is NOT done unless tagged with importPublic\n''2006.01.15 [2.4.0]'' Added support for tagging individual tiddlers with importSkip, importReplace, and/or importPrivate to control which tiddlers can be overwritten or shared with others when using auto-import macro syntax. Defaults are to SKIP overwriting existing tiddlers with imported tiddlers, and ALLOW your tiddlers to be auto-imported by others.\n''2006.01.15 [2.3.2]'' Added "ask" parameter to confirm each tiddler before importing (for use with auto-importing)\n''2006.01.15 [2.3.1]'' Strip TW core scripts from import source content and load just the storeArea into the hidden IFRAME. Makes loading more efficient by reducing the document size and by preventing the import document from executing its TW initialization (including plugins). Seems to resolve the "Found 0 tiddlers" problem. Also, when importing local documents, use convertUTF8ToUnicode() to convert the file contents so support international characters sets.\n''2006.01.12 [2.3.0]'' Reorganized code to use callback function for loading import files to support event-driven I/O via an ASYNCHRONOUS XMLHttpRequest. Let's processing continue while waiting for remote hosts to respond to URL requests. Added non-interactive 'batch' macro mode, using parameters to specify which tiddlers to import, and from what document source. Improved error messages and diagnostics, plus an optional 'quiet' switch for batch mode to eliminate //most// feedback.\n''2006.01.11 [2.2.0]'' Added "[by tags]" to list of tiddlers, based on code submitted by BradleyMeck\n''2006.01.09 [2.1.1]'' When a URL is typed in, and then the "open" button is pressed, it generates both an onChange event for the file input and a click event for open button. This results in multiple XMLHttpRequest()'s which seem to jam things up quite a bit. I removed the onChange handling for file input field. To open a file (local or URL), you must now explicitly press the "open" button in the control panel.\n''2006.01.08 [2.1.0]'' IMPORT FROM ANYWHERE!!! re-write getImportedTiddlers() logic to either read a local file (using local I/O), OR... read a remote file, using a combination of XML and an iframe to permit cross-domain reading of DOM elements. Adapted from example code and techniques courtesy of Jonny LeRoy.\n''2006.01.06 [2.0.2]'' When refreshing list contents, fixed check for tiddlerExists() when "show differences only" is selected, so that imported tiddlers that don't exist in the current file will be recognized as differences and included in the list.\n''2006.01.04 [2.0.1]'' When "show differences only" is NOT checked, import all tiddlers that have been selected even when they have a matching title and date.\n''2005.12.27 [2.0.0]'' Update for TW2.0\nDefer initial panel creation and only register a notification function when panel first is created\n''2005.12.22 [1.3.1]'' tweak formatting in importReport() and add 'discard report' link to output\n''2005.12.03 [1.3.0]'' Dynamically create/remove importPanel as needed to ensure only one instance of interface elements exists, even if there are multiple instances of macro embedding. Also, dynamically create/recreate importFrame each time an external TW document is loaded for importation (reduces DOM overhead and ensures a 'fresh' frame for each document)\n''2005.11.29 [1.2.1]'' fixed formatting of 'detail info' in importReport()\n''2005.11.11 [1.2.0]'' added 'inline' param to embed controls in a tiddler\n''2005.11.09 [1.1.0]'' only load HTML and CSS the first time the macro handler is called. Allows for redundant placement of the macro without creating multiple instances of controls with the same ID's.\n''2005.10.25 [1.0.5]'' fixed typo in importReport() that prevented reports from being generated\n''2005.10.09 [1.0.4]'' combined documentation with plugin code instead of using separate tiddlers\n''2005.08.05 [1.0.3]'' moved CSS and HTML definitions into plugin code instead of using separate tiddlers\n''2005.07.27 [1.0.2]'' core update 1.2.29: custom overlayStyleSheet() replaced with new core setStylesheet()\n''2005.07.23 [1.0.1]'' added parameter checks and corrected addNotification() usage\n''2005.07.20 [1.0.0]'' Initial Release\n<<<\n!!!!!Credits\n<<<\nThis feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]]\n<<<\n!!!!!Code\n***/\n// // ''MACRO DEFINITION''\n//{{{\n// Version\nversion.extensions.importTiddlers = {major: 3, minor: 0, revision: 6, date: new Date(2006,8,16)};\n\n// IE needs explicit global scoping for functions/vars called from browser events\nwindow.onClickImportButton=onClickImportButton;\nwindow.refreshImportList=refreshImportList;\n\n// default cookie/option values\nif (!config.options.chkImportReport) config.options.chkImportReport=true;\n\nconfig.macros.importTiddlers = { };\nconfig.macros.importTiddlers = {\n label: "import tiddlers",\n prompt: "Copy tiddlers from another document",\n foundMsg: "Found %0 tiddlers in %1",\n countMsg: "%0 tiddlers selected for import",\n importedMsg: "Imported %0 of %1 tiddlers from %2",\n src: "", // path/filename or URL of document to import (retrieved from SiteUrl tiddler)\n proxy: "", // URL for remote proxy script (retrieved from SiteProxy tiddler)\n useProxy: false, // use specific proxy script in front of remote URL\n inbound: null, // hash-indexed array of tiddlers from other document\n newTags: "", // text of tags added to imported tiddlers\n addTags: true, // add new tags to imported tiddlers\n listsize: 8, // # of lines to show in imported tiddler list\n importTags: true, // include tags from remote source document when importing a tiddler\n keepTags: true, // retain existing tags when replacing a tiddler\n index: 0, // current processing index in import list\n sort: "" // sort order for imported tiddler listbox\n};\n\nconfig.macros.importTiddlers.handler = function(place,macroName,params) {\n if (!config.macros.loadTiddlers.handler)\n { alert("importTiddlers error: this plugin requires LoadTiddlersPlugin or TiddlyWiki 2.1+"); return; }\n if (!params[0]) // LINK TO FLOATING PANEL\n createTiddlyButton(place,this.label,this.prompt,onClickImportMenu);\n else if (params[0]=="inline") {// // INLINE TIDDLER CONTENT\n createImportPanel(place);\n document.getElementById("importPanel").style.position="static";\n document.getElementById("importPanel").style.display="block";\n }\n else config.macros.loadTiddlers.handler(place,macroName,params); // FALLBACK: PASS TO LOADTIDDLERS\n}\n//}}}\n\n// // ''INTERFACE DEFINITION''\n\n// // Handle link click to create/show/hide control panel\n//{{{\nfunction onClickImportMenu(e)\n{\n if (!e) var e = window.event;\n var parent=resolveTarget(e).parentNode;\n var panel = document.getElementById("importPanel");\n if (panel==undefined || panel.parentNode!=parent)\n panel=createImportPanel(parent);\n var isOpen = panel.style.display=="block";\n if(config.options.chkAnimate)\n anim.startAnimating(new Slider(panel,!isOpen,e.shiftKey || e.altKey,"none"));\n else\n panel.style.display = isOpen ? "none" : "block" ;\n e.cancelBubble = true;\n if (e.stopPropagation) e.stopPropagation();\n return(false);\n}\n//}}}\n\n// // Create control panel: HTML, CSS\n//{{{\nfunction createImportPanel(place) {\n var panel=document.getElementById("importPanel");\n if (panel) { panel.parentNode.removeChild(panel); }\n setStylesheet(config.macros.importTiddlers.css,"importTiddlers");\n panel=createTiddlyElement(place,"span","importPanel",null,null)\n panel.innerHTML=config.macros.importTiddlers.html;\n refreshImportList();\n var siteURL=store.getTiddlerText("SiteUrl"); if (!siteURL) siteURL="";\n document.getElementById("importSourceURL").value=siteURL;\n config.macros.importTiddlers.src=siteURL;\n var siteProxy=store.getTiddlerText("SiteProxy"); if (!siteProxy) siteProxy="SiteProxy";\n document.getElementById("importSiteProxy").value=siteProxy;\n config.macros.importTiddlers.proxy=siteProxy;\n return panel;\n}\n//}}}\n\n// // CSS\n//{{{\nconfig.macros.importTiddlers.css = '\s\n#importPanel {\s\n display: none; position:absolute; z-index:11; width:35em; right:105%; top:3em;\s\n background-color: #eee; color:#000; font-size: 8pt; line-height:110%;\s\n border:1px solid black; border-bottom-width: 3px; border-right-width: 3px;\s\n padding: 0.5em; margin:0em; -moz-border-radius:1em;\s\n}\s\n#importPanel a, #importPanel td a { color:#009; display:inline; margin:0px; padding:1px; }\s\n#importPanel table { width:100%; border:0px; padding:0px; margin:0px; font-size:8pt; line-height:110%; background:transparent; }\s\n#importPanel tr { border:0px;padding:0px;margin:0px; background:transparent; }\s\n#importPanel td { color:#000; border:0px;padding:0px;margin:0px; background:transparent; }\s\n#importPanel select { width:98%;margin:0px;font-size:8pt;line-height:110%;}\s\n#importPanel input { width:98%;padding:0px;margin:0px;font-size:8pt;line-height:110%}\s\n#importPanel .box { border:1px solid black; padding:3px; margin-bottom:5px; background:#f8f8f8; -moz-border-radius:5px;}\s\n#importPanel .topline { border-top:2px solid black; padding-top:3px; margin-bottom:5px; }\s\n#importPanel .rad { width:auto; }\s\n#importPanel .chk { width:auto; margin:1px;border:0; }\s\n#importPanel .btn { width:auto; }\s\n#importPanel .btn1 { width:98%; }\s\n#importPanel .btn2 { width:48%; }\s\n#importPanel .btn3 { width:32%; }\s\n#importPanel .btn4 { width:24%; }\s\n#importPanel .btn5 { width:19%; }\s\n#importPanel .importButton { padding: 0em; margin: 0px; font-size:8pt; }\s\n#importPanel .importListButton { padding:0em 0.25em 0em 0.25em; color: #000000; display:inline }\s\n#importCollisionPanel { display:none; margin:0.5em 0em 0em 0em; }\s\n';\n//}}}\n\n// // HTML \n//{{{\nconfig.macros.importTiddlers.html = '\s\n<!-- source and report -->\s\n<table><tr><td align=left>\s\n import from\s\n <input type="radio" class="rad" name="importFrom" value="file" CHECKED\s\n onClick="document.getElementById(\s'importLocalPanel\s').style.display=this.checked?\s'block\s':\s'none\s';\s\n document.getElementById(\s'importHTTPPanel\s').style.display=!this.checked?\s'block\s':\s'none\s'"> local file\s\n <input type="radio" class="rad" name="importFrom" value="http"\s\n onClick="document.getElementById(\s'importLocalPanel\s').style.display=!this.checked?\s'block\s':\s'none\s';\s\n document.getElementById(\s'importHTTPPanel\s').style.display=this.checked?\s'block\s':\s'none\s'"> web server\s\n</td><td align=right>\s\n <input type=checkbox class="chk" id="chkImportReport" checked\s\n onClick="config.options[\s'chkImportReport\s']=this.checked;"> create a report\s\n</td></tr></table>\s\n<!-- import from local file -->\s\n<div id="importLocalPanel" style="display:block;margin-bottom:5px;margin-top:5px;padding-top:3px;border-top:1px solid #999">\s\nlocal document path/filename:<br>\s\n<input type="file" id="fileImportSource" size=57 style="width:100%"\s\n onKeyUp="config.macros.importTiddlers.src=this.value"\s\n onChange="config.macros.importTiddlers.src=this.value;">\s\n</div><!--panel-->\s\n\s\n<!-- import from http server -->\s\n<div id="importHTTPPanel" style="display:none;margin-bottom:5px;margin-top:5px;padding-top:3px;border-top:1px solid #999">\s\n<table><tr><td align=left>\s\n remote document URL:<br>\s\n</td><td align=right>\s\n <input type="checkbox" class="chk" id="importUseProxy"\s\n onClick="config.macros.importTiddlers.useProxy=this.checked;\s\n document.getElementById(\s'importSiteProxy\s').style.display=this.checked?\s'block\s':\s'none\s'"> use a proxy script\s\n</td></tr></table>\s\n<input type="text" id="importSiteProxy" style="display:none;margin-bottom:1px" onfocus="this.select()" value="SiteProxy"\s\n onKeyUp="config.macros.importTiddlers.proxy=this.value"\s\n onChange="config.macros.importTiddlers.proxy=this.value;">\s\n<input type="text" id="importSourceURL" onfocus="this.select()" value="SiteUrl"\s\n onKeyUp="config.macros.importTiddlers.src=this.value"\s\n onChange="config.macros.importTiddlers.src=this.value;">\s\n</div><!--panel-->\s\n\s\n<table><tr><td align=left>\s\n select:\s\n <a href="JavaScript:;" id="importSelectAll"\s\n onclick="onClickImportButton(this)" title="select all tiddlers">\s\n &nbsp;all&nbsp;</a>\s\n <a href="JavaScript:;" id="importSelectNew"\s\n onclick="onClickImportButton(this)" title="select tiddlers not already in destination document">\s\n &nbsp;added&nbsp;</a> \s\n <a href="JavaScript:;" id="importSelectChanges"\s\n onclick="onClickImportButton(this)" title="select tiddlers that have been updated in source document">\s\n &nbsp;changes&nbsp;</a> \s\n <a href="JavaScript:;" id="importSelectDifferences"\s\n onclick="onClickImportButton(this)" title="select tiddlers that have been added or are different from existing tiddlers">\s\n &nbsp;differences&nbsp;</a> \s\n <a href="JavaScript:;" id="importToggleFilter"\s\n onclick="onClickImportButton(this)" title="show/hide selection filter">\s\n &nbsp;filter&nbsp;</a> \s\n</td><td align=right>\s\n <a href="JavaScript:;" id="importListSmaller"\s\n onclick="onClickImportButton(this)" title="reduce list size">\s\n &nbsp;&#150;&nbsp;</a>\s\n <a href="JavaScript:;" id="importListLarger"\s\n onclick="onClickImportButton(this)" title="increase list size">\s\n &nbsp;+&nbsp;</a>\s\n <a href="JavaScript:;" id="importListMaximize"\s\n onclick="onClickImportButton(this)" title="maximize/restore list size">\s\n &nbsp;=&nbsp;</a>\s\n</td></tr></table>\s\n<select id="importList" size=8 multiple\s\n onchange="setTimeout(\s'refreshImportList(\s'+this.selectedIndex+\s')\s',1)">\s\n <!-- NOTE: delay refresh so list is updated AFTER onchange event is handled -->\s\n</select>\s\n<input type=checkbox class="chk" id="chkAddTags" checked\s\n onClick="config.macros.importTiddlers.addTags=this.checked;">add new tags &nbsp;\s\n<input type=checkbox class="chk" id="chkImportTags" checked\s\n onClick="config.macros.importTiddlers.importTags=this.checked;">import source tags &nbsp;\s\n<input type=checkbox class="chk" id="chkKeepTags" checked\s\n onClick="config.macros.importTiddlers.keepTags=this.checked;">keep existing tags<br>\s\n<input type=text id="txtNewTags" size=15 onKeyUp="config.macros.importTiddlers.newTags=this.value" autocomplete=off>\s\n<div align=center>\s\n <input type=button id="importOpen" class="importButton" style="width:32%" value="open"\s\n onclick="onClickImportButton(this)">\s\n <input type=button id="importStart" class="importButton" style="width:32%" value="import"\s\n onclick="onClickImportButton(this)">\s\n <input type=button id="importClose" class="importButton" style="width:32%" value="close"\s\n onclick="onClickImportButton(this)">\s\n</div>\s\n<div id="importCollisionPanel">\s\n tiddler already exists:\s\n <input type=text id="importNewTitle" size=15 autocomplete=off">\s\n <div align=center>\s\n <input type=button id="importSkip" class="importButton" style="width:23%" value="skip"\s\n onclick="onClickImportButton(this)">\s\n <input type=button id="importRename" class="importButton" style="width:23%" value="rename"\s\n onclick="onClickImportButton(this)">\s\n <input type=button id="importMerge" class="importButton" style="width:23%" value="merge"\s\n onclick="onClickImportButton(this)">\s\n <input type=button id="importReplace" class="importButton" style="width:23%" value="replace"\s\n onclick="onClickImportButton(this)">\s\n </div>\s\n</div>\s\n';\n//}}}\n\n// // Control interactions\n//{{{\nfunction onClickImportButton(which)\n{\n // DEBUG alert(which.id);\n var theList = document.getElementById('importList');\n if (!theList) return;\n var thePanel = document.getElementById('importPanel');\n var theCollisionPanel = document.getElementById('importCollisionPanel');\n var theNewTitle = document.getElementById('importNewTitle');\n var count=0;\n switch (which.id)\n {\n case 'fileImportSource':\n case 'importOpen': // load import source into hidden frame\n importReport(); // if an import was in progress, generate a report\n config.macros.importTiddlers.inbound=null; // clear the imported tiddler buffer\n refreshImportList(); // reset/resize the listbox\n if (config.macros.importTiddlers.src=="") break;\n // Load document into hidden iframe so we can read it's DOM and fill the list\n loadRemoteFile(config.macros.importTiddlers.src, function(src,txt) {\n var tiddlers = readTiddlersFromHTML(txt);\n var count=tiddlers?tiddlers.length:0;\n displayMessage(config.macros.importTiddlers.foundMsg.format([count,src]));\n config.macros.importTiddlers.inbound=tiddlers;\n window.refreshImportList(0);\n });\n break;\n case 'importSelectAll': // select all tiddler list items (i.e., not headings)\n importReport(); // if an import was in progress, generate a report\n for (var t=0,count=0; t < theList.options.length; t++) {\n if (theList.options[t].value=="") continue;\n theList.options[t].selected=true;\n count++;\n }\n clearMessage(); displayMessage(config.macros.importTiddlers.countMsg.format([count]));\n break;\n case 'importSelectNew': // select tiddlers not in current document\n importReport(); // if an import was in progress, generate a report\n for (var t=0,count=0; t < theList.options.length; t++) {\n theList.options[t].selected=false;\n if (theList.options[t].value=="") continue;\n theList.options[t].selected=!store.tiddlerExists(theList.options[t].value);\n count+=theList.options[t].selected?1:0;\n }\n clearMessage(); displayMessage(config.macros.importTiddlers.countMsg.format([count]));\n break;\n case 'importSelectChanges': // select tiddlers that are updated from existing tiddlers\n importReport(); // if an import was in progress, generate a report\n for (var t=0,count=0; t < theList.options.length; t++) {\n theList.options[t].selected=false;\n if (theList.options[t].value==""||!store.tiddlerExists(theList.options[t].value)) continue;\n for (var i=0; i<config.macros.importTiddlers.inbound.length; i++) // find matching inbound tiddler\n { var inbound=config.macros.importTiddlers.inbound[i]; if (inbound.title==theList.options[t].value) break; }\n theList.options[t].selected=(inbound.modified-store.getTiddler(theList.options[t].value).modified>0); // updated tiddler\n count+=theList.options[t].selected?1:0;\n }\n clearMessage(); displayMessage(config.macros.importTiddlers.countMsg.format([count]));\n break;\n case 'importSelectDifferences': // select tiddlers that are new or different from existing tiddlers\n importReport(); // if an import was in progress, generate a report\n for (var t=0,count=0; t < theList.options.length; t++) {\n theList.options[t].selected=false;\n if (theList.options[t].value=="") continue;\n if (!store.tiddlerExists(theList.options[t].value)) { theList.options[t].selected=true; count++; continue; }\n for (var i=0; i<config.macros.importTiddlers.inbound.length; i++) // find matching inbound tiddler\n { var inbound=config.macros.importTiddlers.inbound[i]; if (inbound.title==theList.options[t].value) break; }\n theList.options[t].selected=(inbound.modified-store.getTiddler(theList.options[t].value).modified!=0); // changed tiddler\n count+=theList.options[t].selected?1:0;\n }\n clearMessage(); displayMessage(config.macros.importTiddlers.countMsg.format([count]));\n break;\n case 'importToggleFilter': // show/hide filter\n case 'importFilter': // apply filter\n alert("coming soon!");\n break;\n case 'importStart': // initiate the import processing\n importReport(); // if an import was in progress, generate a report\n config.macros.importTiddlers.index=0;\n config.macros.importTiddlers.index=importTiddlers(0);\n importStopped();\n break;\n case 'importClose': // unload imported tiddlers or hide the import control panel\n // if imported tiddlers not loaded, close the import control panel\n if (!config.macros.importTiddlers.inbound) { thePanel.style.display='none'; break; }\n importReport(); // if an import was in progress, generate a report\n config.macros.importTiddlers.inbound=null; // clear the imported tiddler buffer\n refreshImportList(); // reset/resize the listbox\n break;\n case 'importSkip': // don't import the tiddler\n var theItem = theList.options[config.macros.importTiddlers.index];\n for (var j=0;j<config.macros.importTiddlers.inbound.length;j++)\n if (config.macros.importTiddlers.inbound[j].title==theItem.value) break;\n var theImported = config.macros.importTiddlers.inbound[j];\n theImported.status='skipped after asking'; // mark item as skipped\n theCollisionPanel.style.display='none';\n config.macros.importTiddlers.index=importTiddlers(config.macros.importTiddlers.index+1); // resume with NEXT item\n importStopped();\n break;\n case 'importRename': // change name of imported tiddler\n var theItem = theList.options[config.macros.importTiddlers.index];\n for (var j=0;j<config.macros.importTiddlers.inbound.length;j++)\n if (config.macros.importTiddlers.inbound[j].title==theItem.value) break;\n var theImported = config.macros.importTiddlers.inbound[j];\n theImported.status = 'renamed from '+theImported.title; // mark item as renamed\n theImported.set(theNewTitle.value,null,null,null,null); // change the tiddler title\n theItem.value = theNewTitle.value; // change the listbox item text\n theItem.text = theNewTitle.value; // change the listbox item text\n theCollisionPanel.style.display='none';\n config.macros.importTiddlers.index=importTiddlers(config.macros.importTiddlers.index); // resume with THIS item\n importStopped();\n break;\n case 'importMerge': // join existing and imported tiddler content\n var theItem = theList.options[config.macros.importTiddlers.index];\n for (var j=0;j<config.macros.importTiddlers.inbound.length;j++)\n if (config.macros.importTiddlers.inbound[j].title==theItem.value) break;\n var theImported = config.macros.importTiddlers.inbound[j];\n var theExisting = store.getTiddler(theItem.value);\n var theText = theExisting.text+'\sn----\sn^^merged from: ';\n theText +='[['+config.macros.importTiddlers.src+'#'+theItem.value+'|'+config.macros.importTiddlers.src+'#'+theItem.value+']]^^\sn';\n theText +='^^'+theImported.modified.toLocaleString()+' by '+theImported.modifier+'^^\sn'+theImported.text;\n var theDate = new Date();\n var theTags = theExisting.getTags()+' '+theImported.getTags();\n theImported.set(null,theText,null,theDate,theTags);\n theImported.status = 'merged with '+theExisting.title; // mark item as merged\n theImported.status += ' - '+theExisting.modified.formatString("MM/DD/YYYY 0hh:0mm:0ss");\n theImported.status += ' by '+theExisting.modifier;\n theCollisionPanel.style.display='none';\n config.macros.importTiddlers.index=importTiddlers(config.macros.importTiddlers.index); // resume with this item\n importStopped();\n break;\n case 'importReplace': // substitute imported tiddler for existing tiddler\n var theItem = theList.options[config.macros.importTiddlers.index];\n for (var j=0;j<config.macros.importTiddlers.inbound.length;j++)\n if (config.macros.importTiddlers.inbound[j].title==theItem.value) break;\n var theImported = config.macros.importTiddlers.inbound[j];\n var theExisting = store.getTiddler(theItem.value);\n theImported.status = 'replaces '+theExisting.title; // mark item for replace\n theImported.status += ' - '+theExisting.modified.formatString("MM/DD/YYYY 0hh:0mm:0ss");\n theImported.status += ' by '+theExisting.modifier;\n theCollisionPanel.style.display='none';\n config.macros.importTiddlers.index=importTiddlers(config.macros.importTiddlers.index); // resume with THIS item\n importStopped();\n break;\n case 'importListSmaller': // decrease current listbox size, minimum=5\n if (theList.options.length==1) break;\n theList.size-=(theList.size>5)?1:0;\n config.macros.importTiddlers.listsize=theList.size;\n break;\n case 'importListLarger': // increase current listbox size, maximum=number of items in list\n if (theList.options.length==1) break;\n theList.size+=(theList.size<theList.options.length)?1:0;\n config.macros.importTiddlers.listsize=theList.size;\n break;\n case 'importListMaximize': // toggle listbox size between current and maximum\n if (theList.options.length==1) break;\n theList.size=(theList.size==theList.options.length)?config.macros.importTiddlers.listsize:theList.options.length;\n break;\n }\n}\n//}}}\n\n// // refresh listbox\n//{{{\nfunction refreshImportList(selectedIndex)\n{\n var theList = document.getElementById("importList");\n if (!theList) return;\n // if nothing to show, reset list content and size\n if (!config.macros.importTiddlers.inbound) \n {\n while (theList.length > 0) { theList.options[0] = null; }\n theList.options[0]=new Option('please open a document...',"",false,false);\n theList.size=config.macros.importTiddlers.listsize;\n return;\n }\n // get the sort order\n if (!selectedIndex) selectedIndex=0;\n if (selectedIndex==0) config.macros.importTiddlers.sort='title'; // heading\n if (selectedIndex==1) config.macros.importTiddlers.sort='title';\n if (selectedIndex==2) config.macros.importTiddlers.sort='modified';\n if (selectedIndex==3) config.macros.importTiddlers.sort='tags';\n if (selectedIndex>3) {\n // display selected tiddler count\n for (var t=0,count=0; t < theList.options.length; t++) count+=(theList.options[t].selected&&theList.options[t].value!="")?1:0;\n clearMessage(); displayMessage(config.macros.importTiddlers.countMsg.format([count]));\n return; // no refresh needed\n }\n\n // get the alphasorted list of tiddlers (optionally, filter out unchanged tiddlers)\n var tiddlers=config.macros.importTiddlers.inbound;\n tiddlers.sort(function (a,b) {if(a['title'] == b['title']) return(0); else return (a['title'] < b['title']) ? -1 : +1; });\n // clear current list contents\n while (theList.length > 0) { theList.options[0] = null; }\n // add heading and control items to list\n var i=0;\n var indent=String.fromCharCode(160)+String.fromCharCode(160);\n theList.options[i++]=new Option(tiddlers.length+' tiddler'+((tiddlers.length!=1)?'s are':' is')+' in the document',"",false,false);\n theList.options[i++]=new Option(((config.macros.importTiddlers.sort=="title" )?">":indent)+' [by title]',"",false,false);\n theList.options[i++]=new Option(((config.macros.importTiddlers.sort=="modified")?">":indent)+' [by date]',"",false,false);\n theList.options[i++]=new Option(((config.macros.importTiddlers.sort=="tags")?">":indent)+' [by tags]',"",false,false);\n // output the tiddler list\n switch(config.macros.importTiddlers.sort)\n {\n case "title":\n for(var t = 0; t < tiddlers.length; t++)\n theList.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);\n break;\n case "modified":\n // sort descending for newest date first\n tiddlers.sort(function (a,b) {if(a['modified'] == b['modified']) return(0); else return (a['modified'] > b['modified']) ? -1 : +1; });\n var lastSection = "";\n for(var t = 0; t < tiddlers.length; t++) {\n var tiddler = tiddlers[t];\n var theSection = tiddler.modified.toLocaleDateString();\n if (theSection != lastSection) {\n theList.options[i++] = new Option(theSection,"",false,false);\n lastSection = theSection;\n }\n theList.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);\n }\n break;\n case "tags":\n var theTitles = {}; // all tiddler titles, hash indexed by tag value\n var theTags = new Array();\n for(var t=0; t<tiddlers.length; t++) {\n var title=tiddlers[t].title;\n var tags=tiddlers[t].tags;\n if (!tags || !tags.length) {\n if (theTitles["untagged"]==undefined) { theTags.push("untagged"); theTitles["untagged"]=new Array(); }\n theTitles["untagged"].push(title);\n }\n else for(var s=0; s<tags.length; s++) {\n if (theTitles[tags[s]]==undefined) { theTags.push(tags[s]); theTitles[tags[s]]=new Array(); }\n theTitles[tags[s]].push(title);\n }\n }\n theTags.sort();\n for(var tagindex=0; tagindex<theTags.length; tagindex++) {\n var theTag=theTags[tagindex];\n theList.options[i++]=new Option(theTag,"",false,false);\n for(var t=0; t<theTitles[theTag].length; t++)\n theList.options[i++]=new Option(indent+indent+theTitles[theTag][t],theTitles[theTag][t],false,false);\n }\n break;\n }\n theList.selectedIndex=selectedIndex; // select current control item\n if (theList.size<config.macros.importTiddlers.listsize) theList.size=config.macros.importTiddlers.listsize;\n if (theList.size>theList.options.length) theList.size=theList.options.length;\n}\n//}}}\n\n// // re-entrant processing for handling import with interactive collision prompting\n//{{{\nfunction importTiddlers(startIndex)\n{\n if (!config.macros.importTiddlers.inbound) return -1;\n\n var theList = document.getElementById('importList');\n if (!theList) return;\n var t;\n // if starting new import, reset import status flags\n if (startIndex==0)\n for (var t=0;t<config.macros.importTiddlers.inbound.length;t++)\n config.macros.importTiddlers.inbound[t].status="";\n for (var i=startIndex; i<theList.options.length; i++)\n {\n // if list item is not selected or is a heading (i.e., has no value), skip it\n if ((!theList.options[i].selected) || ((t=theList.options[i].value)==""))\n continue;\n for (var j=0;j<config.macros.importTiddlers.inbound.length;j++)\n if (config.macros.importTiddlers.inbound[j].title==t) break;\n var inbound = config.macros.importTiddlers.inbound[j];\n var theExisting = store.getTiddler(inbound.title);\n // avoid redundant import for tiddlers that are listed multiple times (when 'by tags')\n if (inbound.status=="added")\n continue;\n // don't import the "ImportedTiddlers" history from the other document...\n if (inbound.title=='ImportedTiddlers')\n continue;\n // if tiddler exists and import not marked for replace or merge, stop importing\n if (theExisting && (inbound.status.substr(0,7)!="replace") && (inbound.status.substr(0,5)!="merge"))\n return i;\n // assemble tags (remote + existing + added)\n var newTags = "";\n if (config.macros.importTiddlers.importTags)\n newTags+=inbound.getTags() // import remote tags\n if (config.macros.importTiddlers.keepTags && theExisting)\n newTags+=" "+theExisting.getTags(); // keep existing tags\n if (config.macros.importTiddlers.addTags && config.macros.importTiddlers.newTags.trim().length)\n newTags+=" "+config.macros.importTiddlers.newTags; // add new tags\n inbound.set(null,null,null,null,newTags.trim());\n // set the status to 'added' (if not already set by the 'ask the user' UI)\n inbound.status=(inbound.status=="")?'added':inbound.status;\n // do the import!\n // OLD: store.addTiddler(in); store.setDirty(true);\n store.saveTiddler(inbound.title, inbound.title, inbound.text, inbound.modifier, inbound.modified, inbound.tags);\n store.fetchTiddler(inbound.title).created = inbound.created; // force creation date to imported value\n }\n return(-1); // signals that we really finished the entire list\n}\n//}}}\n\n//{{{\nfunction importStopped()\n{\n var theList = document.getElementById('importList');\n var theNewTitle = document.getElementById('importNewTitle');\n if (!theList) return;\n if (config.macros.importTiddlers.index==-1)\n importReport(); // import finished... generate the report\n else\n {\n // DEBUG alert('import stopped at: '+config.macros.importTiddlers.index);\n // import collision... show the collision panel and set the title edit field\n document.getElementById('importCollisionPanel').style.display='block';\n theNewTitle.value=theList.options[config.macros.importTiddlers.index].value;\n }\n}\n//}}}\n\n// // ''REPORT GENERATOR''\n//{{{\nfunction importReport(quiet)\n{\n if (!config.macros.importTiddlers.inbound) return;\n // DEBUG alert('importReport: start');\n\n // if import was not completed, the collision panel will still be open... close it now.\n var panel=document.getElementById('importCollisionPanel'); if (panel) panel.style.display='none';\n\n // get the alphasorted list of tiddlers\n var tiddlers = config.macros.importTiddlers.inbound;\n // gather the statistics\n var count=0;\n for (var t=0; t<tiddlers.length; t++)\n if (tiddlers[t].status && tiddlers[t].status.trim().length && tiddlers[t].status.substr(0,7)!="skipped") count++;\n\n // generate a report\n if (count && config.options.chkImportReport) {\n // get/create the report tiddler\n var theReport = store.getTiddler('ImportedTiddlers');\n if (!theReport) { theReport= new Tiddler(); theReport.title = 'ImportedTiddlers'; theReport.text = ""; }\n // format the report content\n var now = new Date();\n var newText = "On "+now.toLocaleString()+", "+config.options.txtUserName\n newText +=" imported "+count+" tiddler"+(count==1?"":"s")+" from\sn[["+config.macros.importTiddlers.src+"|"+config.macros.importTiddlers.src+"]]:\sn";\n if (config.macros.importTiddlers.addTags && config.macros.importTiddlers.newTags.trim().length)\n newText += "imported tiddlers were tagged with: \s""+config.macros.importTiddlers.newTags+"\s"\sn";\n newText += "<<<\sn";\n for (var t=0; t<tiddlers.length; t++) if (tiddlers[t].status) newText += "#[["+tiddlers[t].title+"]] - "+tiddlers[t].status+"\sn";\n newText += "<<<\sn";\n newText += "<html><input type=\s"button\s" href=\s"javascript:;\s" ";\n newText += "onclick=\s"story.closeTiddler('"+theReport.title+"'); store.deleteTiddler('"+theReport.title+"');\s" ";\n newText += "value=\s"discard report\s"></html>";\n // update the ImportedTiddlers content and show the tiddler\n theReport.text = newText+((theReport.text!="")?'\sn----\sn':"")+theReport.text;\n theReport.modifier = config.options.txtUserName;\n theReport.modified = new Date();\n // OLD: store.addTiddler(theReport);\n store.saveTiddler(theReport.title, theReport.title, theReport.text, theReport.modifier, theReport.modified, theReport.tags);\n if (!quiet) { story.displayTiddler(null,theReport.title,1,null,null,false); story.refreshTiddler(theReport.title,1,true); }\n }\n\n // reset status flags\n for (var t=0; t<config.macros.importTiddlers.inbound.length; t++) config.macros.importTiddlers.inbound[t].status="";\n\n // refresh display if tiddlers have been loaded\n if (count) { store.setDirty(true); store.notifyAll(); }\n\n // always show final message when tiddlers were actually loaded\n if (count) displayMessage(config.macros.importTiddlers.importedMsg.format([count,tiddlers.length,config.macros.importTiddlers.src]));\n}\n//}}}\n\n/***\n!!!!!TW 2.1beta Core Code Candidate\n//The following section is a preliminary 'code candidate' for incorporation of non-interactive 'load tiddlers' functionality into TW2.1beta. //\n***/\n//{{{\n// default cookie/option values\nif (!config.options.chkImportReport) config.options.chkImportReport=true;\n\nconfig.macros.loadTiddlers = {\n label: "",\n prompt: "add/update tiddlers from '%0'",\n askMsg: "Please enter a local path/filename or a remote URL",\n openMsg: "Opening %0",\n openErrMsg: "Could not open %0 - error=%1",\n readMsg: "Read %0 bytes from %1",\n foundMsg: "Found %0 tiddlers in %1",\n nochangeMsg: "'%0' is up-to-date... skipped.",\n loadedMsg: "Loaded %0 of %1 tiddlers from %2"\n};\n\nconfig.macros.loadTiddlers.handler = function(place,macroName,params) {\n var label=(params[0] && params[0].substr(0,6)=='label:')?params.shift().substr(6):this.label;\n var prompt=(params[0] && params[0].substr(0,7)=='prompt:')?params.shift().substr(7):this.prompt;\n var filter="updates";\n if (params[0] && (params[0]=='all' || params[0]=='new' || params[0]=='changes' || params[0]=='updates'\n || params[0].substr(0,8)=='tiddler:' || params[0].substr(0,4)=='tag:'))\n filter=params.shift();\n var src=params.shift(); if (!src || !src.length) return; // filename is required\n var quiet=(params[0]=="quiet"); if (quiet) params.shift();\n var ask=(params[0]=="confirm"); if (ask) params.shift();\n var force=(params[0]=="force"); if (force) params.shift();\n if (label.trim().length) {\n // link triggers load tiddlers from another file/URL and then applies filtering rules to add/replace tiddlers in the store\n createTiddlyButton(place,label.format([src]),prompt.format([src]), function() {\n if (src=="ask") src=prompt(config.macros.loadTiddlers.askMsg);\n loadRemoteFile(src,loadTiddlers,quiet,ask,filter,force);\n })\n }\n else {\n // load tiddlers from another file/URL and then apply filtering rules to add/replace tiddlers in the store\n if (src=="ask") src=prompt(config.macros.loadTiddlers.askMsg);\n loadRemoteFile(src,loadTiddlers,quiet,ask,filter,force);\n }\n}\n\nfunction loadTiddlers(src,html,quiet,ask,filter,force)\n{\n var tiddlers = readTiddlersFromHTML(html);\n var count=tiddlers?tiddlers.length:0;\n if (!quiet) displayMessage(config.macros.loadTiddlers.foundMsg.format([count,src]));\n var count=0;\n if (tiddlers) for (var t=0;t<tiddlers.length;t++) {\n var inbound = tiddlers[t];\n var theExisting = store.getTiddler(inbound.title);\n if (inbound.title=='ImportedTiddlers')\n continue; // skip "ImportedTiddlers" history from the other document...\n\n // apply the all/new/changes/updates filter (if any)\n if (filter && filter!="all") {\n if ((filter=="new") && theExisting) // skip existing tiddlers\n continue;\n if ((filter=="changes") && !theExisting) // skip new tiddlers\n continue;\n if ((filter.substr(0,4)=="tag:") && inbound.tags.find(filter.substr(4))==null) // must match specific tag value\n continue;\n if ((filter.substr(0,8)=="tiddler:") && inbound.title!=filter.substr(8)) // must match specific tiddler name\n continue;\n if (!force && store.tiddlerExists(inbound.title) && ((theExisting.modified.getTime()-inbound.modified.getTime())>=0))\n { if (!quiet) displayMessage(config.macros.loadTiddlers.nochangeMsg.format([inbound.title])); continue; }\n }\n // get confirmation if required\n if (ask && !confirm((theExisting?"Update":"Add")+" tiddler '"+inbound.title+"'\snfrom "+src))\n { tiddlers[t].status="skipped - cancelled by user"; continue; }\n // DO IT!\n // OLD: store.addTiddler(in);\n store.saveTiddler(inbound.title, inbound.title, inbound.text, inbound.modifier, inbound.modified, inbound.tags);\n store.fetchTiddler(inbound.title).created = inbound.created; // force creation date to imported value\n tiddlers[t].status=theExisting?"updated":"added"\n count++;\n }\n if (count) {\n // refresh display\n store.setDirty(true);\n store.notifyAll();\n // generate a report\n if (config.options.chkImportReport) {\n // get/create the report tiddler\n var theReport = store.getTiddler('ImportedTiddlers');\n if (!theReport) { theReport= new Tiddler(); theReport.title = 'ImportedTiddlers'; theReport.text = ""; }\n // format the report content\n var now = new Date();\n var newText = "On "+now.toLocaleString()+", "+config.options.txtUserName+" loaded "+count+" tiddlers from\sn[["+src+"|"+src+"]]:\sn";\n newText += "<<<\sn";\n for (var t=0; t<tiddlers.length; t++) if (tiddlers[t].status) newText += "#[["+tiddlers[t].title+"]] - "+tiddlers[t].status+"\sn";\n newText += "<<<\sn";\n newText += "<html><input type=\s"button\s" href=\s"javascript:;\s" ";\n newText += "onclick=\s"story.closeTiddler('"+theReport.title+"'); store.deleteTiddler('"+theReport.title+"');\s" ";\n newText += "value=\s"discard report\s"></html>";\n // update the ImportedTiddlers content and show the tiddler\n theReport.text = newText+((theReport.text!="")?'\sn----\sn':"")+theReport.text;\n theReport.modifier = config.options.txtUserName;\n theReport.modified = new Date();\n // OLD: store.addTiddler(theReport);\n store.saveTiddler(theReport.title, theReport.title, theReport.text, theReport.modifier, theReport.modified, theReport.tags);\n if (!quiet) { story.displayTiddler(null,theReport.title,1,null,null,false); story.refreshTiddler(theReport.title,1,true); }\n }\n }\n // always show final message when tiddlers were actually loaded\n if (!quiet||count) displayMessage(config.macros.loadTiddlers.loadedMsg.format([count,tiddlers.length,src]));\n}\n\nfunction loadRemoteFile(src,callback,quiet,ask,filter,force) {\n if (src==undefined || !src.length) return null; // filename is required\n if (!quiet) clearMessage();\n if (!quiet) displayMessage(config.macros.loadTiddlers.openMsg.format([src]));\n if (src.substr(0,4)!="http" && src.substr(0,4)!="file") { // if not a URL, fallback to read from local filesystem\n var txt=loadFile(src);\n if ((txt==null)||(txt==false)) // file didn't load\n { if (!quiet) displayMessage(config.macros.loadTiddlers.openErrMsg.format([src,"(unknown)"])); }\n else {\n if (!quiet) displayMessage(config.macros.loadTiddlers.readMsg.format([txt.length,src]));\n if (callback) callback(src,convertUTF8ToUnicode(txt),quiet,ask,filter,force);\n }\n }\n else {\n var x; // get an request object\n try {x = new XMLHttpRequest()} // moz\n catch(e) {\n try {x = new ActiveXObject("Msxml2.XMLHTTP")} // IE 6\n catch (e) {\n try {x = new ActiveXObject("Microsoft.XMLHTTP")} // IE 5\n catch (e) { return }\n }\n }\n // setup callback function to handle server response(s)\n x.onreadystatechange = function() {\n if (x.readyState == 4) {\n if (x.status==0 || x.status == 200) {\n if (!quiet) displayMessage(config.macros.loadTiddlers.readMsg.format([x.responseText.length,src]));\n if (callback) callback(src,x.responseText,quiet,ask,filter,force);\n }\n else {\n if (!quiet) displayMessage(config.macros.loadTiddlers.openErrMsg.format([src,x.status]));\n }\n }\n }\n // get privileges to read another document's DOM via http:// or file:// (moz-only)\n if (typeof(netscape)!="undefined") {\n try { netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); }\n catch (e) { if (!quiet) displayMessage(e.description?e.description:e.toString()); }\n }\n // send the HTTP request\n try {\n var url=src+(src.indexOf('?')<0?'?':'&')+'nocache='+Math.random();\n x.open("GET",src,true);\n if (x.overrideMimeType) x.overrideMimeType('text/html');\n x.send(null);\n }\n catch (e) {\n if (!quiet) {\n displayMessage(config.macros.loadTiddlers.openErrMsg.format([src,"(unknown)"]));\n displayMessage(e.description?e.description:e.toString());\n }\n }\n }\n}\n\nfunction readTiddlersFromHTML(html)\n{\n // extract store area from html \n var start=html.indexOf('<div id="storeArea">');\n var end=html.indexOf('</body>',start);\n var sa="<html><body>"+html.substring(start,end)+"</body></html>";\n\n // load html into iframe document\n var f=document.getElementById("loaderFrame"); if (f) document.body.removeChild(f);\n f=document.createElement("iframe"); f.id="loaderFrame";\n f.style.width="0px"; f.style.height="0px"; f.style.border="0px";\n document.body.appendChild(f);\n var d=f.document;\n if (f.contentDocument) d=f.contentDocument; // For NS6\n else if (f.contentWindow) d=f.contentWindow.document; // For IE5.5 and IE6\n d.open(); d.writeln(sa); d.close();\n\n // read tiddler DIVs from storeArea DOM element \n var sa = d.getElementById("storeArea");\n if (!sa) return null;\n sa.normalize();\n var nodes = sa.childNodes;\n if (!nodes || !nodes.length) return null;\n var tiddlers = [];\n for(var t = 0; t < nodes.length; t++) {\n var title = null;\n if(nodes[t].getAttribute)\n title = nodes[t].getAttribute("tiddler");\n if(!title && nodes[t].id && (nodes[t].id.substr(0,5) == "store"))\n title = nodes[t].id.substr(5);\n if(title && title != "")\n tiddlers.push((new Tiddler()).loadFromDiv(nodes[t],title));\n }\n return tiddlers;\n}\n//}}}
!Initialization\nFirst, there are certain things you have to do when the program is launched (generally in your {{{__main__}}} block). If you're taking a proper object-oriented approaching in writing your program, you wil have created your own sub-class of the arPyMasterSlaveFramework class. Let's say you've called your sub-class {{{MyFramework}}}. Your {{{__main__}}} block will almost always look exactly like this:\n{{{\nif __name__ == '__main__':\n fw = MyFramework() # Create the framework object\n if not fw.init( sys.argv ): # Try to intialize it\n raise RuntimeError, 'The application failed to initialize.'\n if not fw.start(): # Try to start it (entering the event loop. Should never return).\n raise RuntimeError, 'The application failed to start.'\n}}}\nYou first construct an instance of your framework class and call the instance's init() method. The system variable {{{sys.argv}}} contains the command-line parameters that your program was launched with. The framework's {{{init()}}} method returns {{{True}}} or {{{False}}} to indicate success or failure.\n\nAt the end of your {{{__main__}}} block, you must call the framework's {{{start()}}} method. This function never returns. Instead, the program enters the Syzygy event-processing loop. Before it enters the event loop, however, it does a few things:\n# It calls the {{{MyFramework}}} class' {{{onStart()}}} method. This gets called exactly once and is where you would do application-global initialization. It gets called //before// any window is created, so you //can't// do OpenGL initialization here; if you try to, your program will probably crash.\n# It creates one or more windows to render your virtual world into, depending on how you've configured Syzygy; and\n# It calls the {{{MyFramework}}} class' {{{onWindowStartGL()}}} method. This one gets called once for each window that is created, and is where you do OpenGL initialization.\n\n!Cleanup\nWhen your program exits, it automagically calls the {{{MyFramework}}} class' {{{onExit()}}} method, where you can do any necessary cleanup (actually, this is hardly ever necessary.\n\nNext: The [[Per-Frame Event Loop]],\n
The {{{arMasterSlaveDict()}}} constructor takes two arguments, a name for the {{{arMasterSlaveDict}}} and a list or tuple containing class information.\n\nThe name is arbitrary, but should not contain whitespace; it's used in calling the framework's methods for [[Transferring Arbitrary Sequences]]].\n\nThe class information list is used for two purposes: First, whenever you try to insert an object into the {{{arMasterSlaveDict}}}, a key computed from it's class' {{{__module__}}} and {{{__name__}}} attributes is checked against the items in the list; if not present, a {{{TypeError}}} exception is raised. Second, if you insert an object into the dictionary in the master instance of your application, the class factory is used to construct an instance of the same class in the slaves.You have to provide an entry for each class of object that you intent to insert in this container. Each entry must be either (1) a reference to the class itself, or (2) a tuple containing a reference to the class and a //class factory// function. The class factory should be a reference to a callable object that takes no parameters and returns an object of the specified class. If the class factory item is not present, then the class itself will be used as the factory; it will be called without arguments (again, see below), so in that case the class must provide a zero-argument constructor ({{{__init__(self)}}}). Here are three examples; in all of them, I've made the {{{arMasterSlaveDict}}} a property of the framework object, so the following code would go in the framework's {{{__init__()}}} method::\n\n1) You've defined class {{{Foo}}}, either in the current file or in a module Bar that you've imported using {{{from Bar import *}}}. Now the class object {{{Foo}}} is in the global namespace. So you could call:\n{{{\nself.dict = arMasterSlaveDict( 'mydict', [Foo] ).\n}}}\nIn this case the class itself is used as the factory, i.e. {{{Foo()}}} is called to generate new instances in the slaves. A constructor that can be called without arguments (a default constructor) must be provided by the class.\n\n2) You've defined class {{{Foo}}} in module {{{Bar}}} and imported it using {{{import Bar}}}. Now {{{Foo}}} is not in the global namespace, it's in the {{{Bar}}} namespace. So you would call:\n{{{\nself.dict = arMasterSlaveDict( 'mydict', [Bar.Foo] ).\n}}}\nNow, {{{Bar.Foo()}}}--again, the class itself--is called to create new instances in the slaves. And again, the class must provide a default constructor.\n\n3) You want to pass a parameter to the constructor of {{{Foo}}}, e.g. a reference to the framework object. So you define a class factory framework method {{{newFoo()}}}:\n{{{\ndef newFoo(self): return Foo(self)\n}}}\nand in the framework's {{{__init__()}}}, call:\n{{{\nself.dict = arMasterSlaveDict( 'mydict', [(Foo,self.newFoo)] ).\n}}}\nNow the reference to {{{framework.newFoo}}} is called to generate new instances in slaves.\n\nStarting the {{{arMasterSlaveDict}}}: After you have instantiated it (most likely in the framework's {{{__init__()}}}, you need to call the {{{arMasterSlaveDict}}}'s {{{start()}}} method in the framework's {{{onStart()}}} method, passing the framework as a parameter:\n{{{\nself.dict.start( self ).\n}}}\n\n! About class methods\n\nA quick digression for the inexperienced into Python references to methods of objects. It's a bit confusing, because when you define the method it has that {{{self}}} argument (e.g. {{{def newFoo(self):}}}, but you don't pass it {{{self}}} when you call it (you don't call it using {{{newFoo(self)}}}), you call it using {{{self.newFoo()}}} if you're calling it from within another framework method or e.g. {{{framework.newFoo()}}} if you're calling it from outside the framework.\n\nThe trick, as I understand it, is that in Python, a class method is a special type of callable object that maintains a hidden reference to an instance of the class. That hidden reference is normally assigned when you construct an instance of the class. In the example above, we've created a sub-class of {{{arPyMasterSlaveFramework}}} (call it {{{MyFramework}}}) that has a {{{newFoo}}} method. Now, when we instantiate the framework using:\n{{{\nfw = MyFramework(),\n}}}\nwhat happens (among other things) is that a callable object using your {{{newFoo}}} code is also created. It's given a reference to {{{fw}}}, which it hides away, and {{{fw}}} is also given a refence to it, which is than accessible using {{{fw.newFoo}}}. Now, whenever we call {{{fw.newFoo()}}}, it tacks its hidden reference to fw onto the front of the parameter list and then calls whatever code you've defined for it.\n\nA rather nifty feature of all this is that you can pass references to class methods around willy-nilly and call them from very distant places in your code without having to know anything about the object that they are methods of. For example, you can now assign:\n{{{\nX = fw.newFoo,\n}}}\nand because the object {{{fw.newFoo}}} contains a hidden reference to {{{fw}}}, you don't need to keep track of {{{fw}}} yourself. You can now pass {{{X}}} around in your code and call it (using {{{X()}}}) wherever you want to, and it will still know to paste its reference to fw to the beginning of the argument list.\n\nYou can also use the use the standard ''new'' module to create a brand new method that wasn't part of the class definition and attach it to an instance of the class. Toto, this isn't C++ anymore...
[img[images/full_mark_horz_pro.gif]] \n\n[img[ISL Logo|images/ISL_logo.jpg]]\n\nThe [[Integrated Systems Laboratory|http://www.isl.uiuc.edu]] is a [[Beckman Institute|http://www.beckman.uiuc.edu/]] facility at the [[University of Illinois|http://www.uiuc.edu/]] for advancing scientific understanding of human-computer interactions. We run the [[Cube|http://www.isl.uiuc.edu/Labs/room_b650.htm]], the older [[CAVE|http://www.isl.uiuc.edu/cave_update.htm]], a [[driving simulator|http://www.isl.uiuc.edu/Labs/room_b668.htm]], and a motion capture suite. You know, cool stuff.
Macros let you write tiddlers containing more exotic objects than just text. See also [[TiddlyWiki Markup]]. Here are the built-in macros:\n\n|!Macro|!Description|!Syntax|\n|allTags|List all the tags used in the current TiddlyWiki file<<br>>Each entry is a button that pops up the list of tiddlers for that tag<<br>><<slider sliderID [[Internal Macros/tags]] 'Click to show example output'>>|{{{<<allTags>>}}}|\n|br|Force a line break|{{{<<br>>}}}|\n|closeAll|Displays a button to close all displayed Tiddlers<<br>><<closeAll>>|{{{<<closeAll>>}}}|\n|gradient|<<gradient [horiz|vert] #bbbbbb #eeeeee #ffffff>>Produces a horizontal or vertical background gradient fill>><<br>>There can be 2 or more colours in the format: #rrggbb (hex), or RGB(r,g,b) (CSS)<<br>>Other CSS formatting can also be added, e.g. {{{<<gradient vert #000000 #660000 #aa2222>>color:#ffffff;font-size:12pt;Darkness>>}}}|{{{<<gradient [horiz|vert] #bbbbbb #eeeeee #ffffff>>Some text here>>}}}|\n|list all|List all Tiddlers in a Tiddler|{{{<<list all>>}}}|\n|list missing|List all missing tiddlers|{{{<<list missing>>}}}|\n|list orphans|List all orphaned tiddlers|{{{<<list orphans>>}}}|\n|newJournal|Displays a button to create new date & Time stamped Tiddler (Date/time format optional)<<br>><<newJournal "DD MMM YYYY, hh:mm">> <<br>>You can also add optional tag names after the date format: <<newJournal "DD MMM YYYY, hh:mm" tag1 TagTwo>> |{{{<<newJournal [DateFormatString]>>}}} <<br>> {{{<<newJournal "DD MMM YYYY, hh:mm" tag1 TagTwo>>}}} |\n|newTiddler|Displays a button to create new Tiddler<<br>><<newTiddler>>|{{{<<newTiddler>>}}}|\n|permaview|Displays a button to change the URL link for all open Tiddlers - or the containing tiddler if used in the command bar (See the ViewTemplate)<<br>><<permaview>>|{{{<<permaview>>}}}|\n|saveChanges |Button to save all TiddlyWiki changes (or the current tiddler if used in the command bar (see EditTemplate)<<br>><<saveChanges>>|{{{<<saveChanges>>}}}|\n|search|Display a Search box<<br>><<search>>|{{{<<search>>}}}|\n|slider|Display a Slider (a collapsable display of another tiddler)<<br>>See the allTags entry for an example. Note: Put quotes around the label if needing spaces<<br>>where: ''ID''=cookie name to be used to save the state of the slider, ''Tiddler''=name of the tiddler to include in the slider, ''Label''=label text of the slider button, ''tooltip''=text of the buttons tooltip|{{{<<slider ID Tiddler [Label] [toolTip]>>}}}|\n|sparkline|Produces a sparkline graphic<<br>>e.g. <<sparkline 163 218 231 236 232 266 176 249 289 1041 1835 2285 3098 2101 1755 3283 3353 3335 2898 2224 1404 1354 1825 1839 2142 1942 1784 1145 979 1328 1611>>|{{{<<sparkline num1 num2 ... numN>>}}}|\n|tabs|Display Tabbed content (contents of tab provided by another tiddler)|{{{<<tabs indentifier tabLabel tabName Tiddler>>}}}|\n|tag|Display a Tag ~PopUp<<br>><<tag _Config>>|{{{<<tag tagName>>}}}|\n|tagChooser|Used in EditTemplate to add tags to the tags field. Doesn't actually add anything unless in edit mode (though it does show the list)<<br>><<tagChooser>>|{{{<<tagChooser>>}}}|\n|tagging|<<tiddler [[Internal Macros/tagging]]>>|{{{<<tagging [TiddlerTitle]>>}}}|\n|tiddler|Display contents of another tiddler inline|{{{<<tiddler Tiddler>>}}}|\n|timeline|Display a timeline list of tiddlers<<br>>where the sortfield is the sort order ("modified" or "created") and maxentries is the maximum number of entries|{{{<<timeline [sortfield] [maxentries]>>}}}|\n|today|Display Today's Date<<br>>e.g. <<today>>|{{{<<today [DateFormatString]>>}}}|\n|version|Display TiddlyWiki's version<<br>>e.g. <<version>>|{{{<<version>>}}}|\n\n!DateFormatString\nSeveral Macros including the today macro take a DateFormatString as an optional argument. This string can be a combination of ordinary text, with some special characters that get substituted by parts of the date:\n* DDD - day of week in full (eg, "Monday")\n* DD - day of month, 0DD - adds a leading zero\n* MMM - month in full (eg, "July")\n* MM - month number, 0MM - adds leading zero\n* YYYY - full year, YY - two digit year\n* hh - hours\n* mm - minutes\n* ss - seconds\n!Notes\nIf you need to supply a parameter that should be evaluated (e.g. a JavaScript variable), enclose the parameter in {{{{{}}} and {{{}}}}} rather than quotes. Note however, that the scope used in the evaluation is {{{global}}} rather than {{{local}}}. In other words, the evaluation is done ''before'' the parameter is passed to the macro/plugin so it cannot access any of the variables or functions defined within the macro/plugin.\n!Commands supported by the toolbar macro\n{{{\nconfig.commands = {\n closeTiddler: {text: "close", tooltip: "Close this tiddler"},\n closeOthers: {text: "close others", tooltip: "Close all other tiddlers"},\n editTiddler: {text: "edit", tooltip: "Edit this tiddler", readOnlyText: "view", readOnlyTooltip: "View the source of this tiddler"},\n saveTiddler: {text: "done", tooltip: "Save changes to this tiddler", readOnlyText: "done", readOnlyTooltip: "View this tiddler normally"},\n cancelTiddler: {text: "cancel", tooltip: "Undo changes to this tiddler", hideReadOnly: true},\n deleteTiddler: {text: "delete", tooltip: "Delete this tiddler", warning: "Are you sure you want to delete '%0'?", hideReadOnly: true},\n permalink: {text: "permalink", tooltip: "Permalink for this tiddler"},\n references: {text: "references", tooltip: "Show tiddlers that link to this one", popupNone: "No references"},\n jump: {text: "jump", tooltip: "Jump to another open tiddler"}\n };\n}}}\n(Julian Knight, 2006-04-06)\n<part tagging hidden>\nProduces a list (NB: <ul> ''not'' a popup) of links to tiddlers that carry the specified tag. If no tag is specified, it looks for tiddlers tagged with the name of the current tiddler.\nIn HTML, the list is formatted like so:\n{{{\n<ul>\n<li class="listTitle">List title label</li>\n<li><a class="tiddlyLink ..." href="javascript:;" onclick="..."\n refresh="link" tiddlyLink="ExampleOne">ExampleOne</a></li>\n</ul>\n}}}\n</part>\n<part tags hidden>\n<<allTags>>\n</part>
This book is written with [[TiddlyWiki|http://www.tiddlywiki.com/]], a sort of nonlinear notebook-in-a-webpage. It consists of lots of chunks of Javascript code and several chunks of text; both kinds of chunks are called //tiddlers//.\n\nFor the forseeable future, the most recent version will be available online at [[https://netfiles.uiuc.edu/jimc/www/SyzygyPythonProgramming/|https://netfiles.uiuc.edu/jimc/www/SyzygyPythonProgramming/]].\n\nContrariwise, if you're reading it on-line you can download a local zipped copy from\n[[https://netfiles.uiuc.edu/jimc/www/SyzygyPythonProgramming.zip|https://netfiles.uiuc.edu/jimc/www/SyzygyPythonProgramming.zip]].\n\nText in blue indicates a link that you click to open. Underlined blue text (e.g. [[TiddlyWiki|http://www.tiddlywiki.com/]]) are ordinary web links that open another file. Bold blue text (e.g. [[What is Syzygy?]]) indicates links to other tiddlers within this file, in which case clicking the link will open the other tiddler inside this webpage; by default all previously open tiddlers will slide down and the new one will open at the top of the page. You can have as many tiddlers open at a time as you want.\n\nBlue italicized text indicates either a link to content that doesn't exist yet (e.g. [[Master/Slave Framework]]) or a word that TiddlyWiki thinks should be a link but isn't (e.g. TiddlyWiki). Please ignore these.\n\nWhen you move the mouse over a tiddler, a bunch of text buttons appear in its upper-right corner, "close", "close others", etc. Clicking "close" will hide the tiddler. If you have multiple tiddlers open, the "jump" button will pull up a menu allowing you to bring any of them to the top of the window. The button that looks like a double-ended arrow will make the current tiddler take over the whole window (pressing it again reverses the process).\n\nYou can pull up the TableOfContents at any time using the first link in the list at left.\n\nIf you want to learn more, go to the [[TiddlyWiki website|http://www.tiddlywiki.com/]].\n\nOK, so [[What is Syzygy?]]\n\n
IntegratedSystemsLab psychology experiment programmer.\n\nPhone: 5-5523\nE-mail:\n[img[Jim's email address|images/JimAddress.jpg]]
[[Macros]] let you write tiddlers containing more exotic objects than just text. Here are some of the built-in macros (also see the GradientMacro and [[Sparklines]]):\n\nToday's date:\n{{{\n<<today>>\n}}}\nwill result in: <<today>>\n\nTag popup:\n{{{\n<<tag features>>\n}}}\nwill result in <<tag features>>\n\nNew journal entry:\n{{{\n<<newJournal "DD MMM YYYY, hh:mm">>\n}}}\nwill result in the button <<newJournal "DD MMM YYYY, hh:mm">>\nThe first parameter is DateFormatString\n\nTiddler inclusion:\n{{{\n<<tiddler MicroContent>>\n}}}\nwill insert the text of the tiddler MicroContent. //Note that there is no protection at the moment against inadvertantly setting up endless loops//\n<<tiddler MicroContent>>\n\nSlider:\n{{{\n<<slider chkTestSlider OptionsPanel options "Change TiddlyWiki advanced options">>\n}}}\nResults in this button <<slider chkTestSlider OptionsPanel options "Change TiddlyWiki advanced options">>\nThe parameters are:\n* cookie name to be used to save the state of the slider\n* name of the tiddler to include in the slider\n* title text of the slider\n* tooltip text of the slider\n
[img[University of Illinois at Urbana-Champaign|images/i_mark_pro.gif][http://www.uiuc.edu/]]\nTableOfContents\nBookText\n[[TiddlyWiki Markup]]\n----\n{{small{\n<<search>>\n<<closeAll>>\n<<permaview>>\n<<newTiddler>>\n<<saveChanges>>\n[[edit menu|MainMenu]]\n[[manage tiddlers|SideBarTabs]]\n[[options|OptionsPanel]]\n<<fontSize font-size: >>\n}}}\n----\n{{\nsmaller {\nWritten by JimCrowell\nIntegratedSystemsLab\n}}}
In order to work with the {{{arMasterSlaveDict}}}'s easy master->slave synchronization methods, each class that you want it to be able to hold must implement two methods: {{{getState()}}} and {{{setState()}}}.\n\n{{{getState()}}} needs to return an instance of one of the [[Python Sequence Types]] defining all of the state information needed to differentiate instances of the class. For a discussion of the constraints that this sequence has to satisfy, see [[Transferring Arbitrary Sequences]]. Briefly, the sequence can contain only Ints, Floats, Strings, or nested sequences of Ints, Floats, and/or Strings.\n\n{{{setState()}}} needs to accept a tuple with the same structure as that returned by {{{getState()}}} (again, see [[Transferring Arbitrary Sequences]) and from it make the state of the current object identical to that of the object that generated the original sequence.\n\nFor example, say that what distinguishes the objects of class {{{Foo}}} from one another are their visibilities ({{{True}}} or {{{False}}}) and their [[Placement Matrices]], implemented as {{{arMatrix4}}}s. Then you might define:\n{{{\ndef getState(self):\n return (self.getVisible(), self.getMatrix().toTuple()).\n}}}\nThis will return a tuple containing one Int (from {{{getVisible()}}}) and a nested tuple containing 16 Floats (the matrix). You should then define {{{setState()}}} to accept the same sequence and put the data in the appropriate places, e.g.:\n{{{\ndef setState(self, stateTuple):\n self.setVisible( stateTuple[0] )\n self.setMatrix( arMatrix4( stateTuple[1] ) ).\n}}}\nYou will need to create a similar pair of methods for each class that you want the {{{arMasterSlaveDict}}} to hold.
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'>\n\n<style type="text/css">#contentWrapper {display:none;}</style><div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Tahoma; background-color:#eee;"><b>Syzygy Python Programming</b> is loading<blink> ...</blink><br><br><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>
{{{Monospaced text}}} is supported - edit this tiddler to see the syntax.\n\nYou can also have monospaced blocks (useful for source code):\n\n{{{\nvar posTop = findPosY(e);\nvar posBot = posTop + e.offsetHeight;\nvar winTop = findScrollY();\nvar winHeight = findWindowHeight();\nvar winBot = winTop + winHeight;\nif(posTop < winTop)\nreturn(posTop);\nelse if(posBot > winBot)\n{\nif(e.offsetHeight < winHeight)\nreturn(posTop - (winHeight - e.offsetHeight));\nelse\nreturn(posTop);\n}\nelse\nreturn(winTop);\n}}}\n
Our [[Basic Syzygy Version of Teapots]] certainly worked, but it omitted perhaps the single most distinguishing feature of a Syzygy master/slave program: the transfer of data from master to slaves. In the basic version that wasn't necessary, because the application data never actually changed. We could change the head position by dragging the mouse around, but that sort of input-related information is automatically transferred by the framework.\n\nIn order to look at this data transfer procedure, we're going to have to inject some change into the application's state. We'll put in a facility for randomly re-arranging the teapots (get the [[complete source code|python/teapots/szgteapots2.py]]; note that if you don't already have them you'll also need the [[szgteapots1.py|python/teapots/szgteapots1.py]] and [[teapots.py|python/teapots/teapots.py]] sources):\n\n! Imports\n\nThe sequence of module import statements is just a bit different in this version:\n{{{\nfrom PySZG import *\nimport sys\nimport random\nimport szgteapots1\n}}}\n\nWe don't have to import the {{{OpenGL.GL}}} module because we used all the OpenGL functions that we needed in the previous version of the program, and we're importing that instead. The only new module is {{{random}}}, which as you might guess is all about generating random numbers. We'll use that for randomly shuffling the teapot positions.\n\nNow we define our framework class, and following our tradition of laziness we'll sub-class our previous framework,override a bunch more methods of {{{arPyMasterSlaveFramework}}}, and add one new method of our own:\n{{{\nclass TeapotApp(szgteapots1.TeapotApp):\n\n def onStart( self, szgClient ):\n arPyMasterSlaveFramework.onStart( self, szgClient )\n self.initSequenceTransfer('teapots')\n\n def onPreExchange( self ):\n arPyMasterSlaveFramework.onPreExchange(self)\n if self.getOnButton(0):\n self.shufflePositions()\n self.setSequence( 'teapots', self.teapotData )\n\n def onPostExchange( self ):\n arPyMasterSlaveFramework.onPostExchange(self)\n if not self.getMaster():\n self.teapotData = self.getSequence( 'teapots' )\n\n def shufflePositions( self ):\n pos = [i[0:2] for i in self.teapotData]\n random.shuffle( pos )\n for i in range(len(pos)):\n self.teapotData[i][0:2] = pos[i]\n}}}\n\nThe new method is {{{shufflePositions()}}}. Remember that {{{self.teapotData}}} is a reference to the {{{teapotDataGlobal}}} variable of the original GLUT program. It's a list of lists, with each sub-list containing a bunch of parameters for each teapot.\n{{{shufflePositions()}}} makes a new list of lists in which each sub-list is just the first two items from each teapot; these represent the teapot's position in the spatial array. {{{random.shuffle()}}} randomly re-orders this new list in place, and the following loop puts the shuffled positions back into the original {{{self.teapotData}}} list.\n\nSo how do we apply this shuffling method, and how do we transfer the shuffled data from master to slaves? That's what the first three methods are about.\n\nIn {{{onStart()}}}, we register a data transfer from master to slaves using the method described in [[Transferring Arbitrary Sequences]].\n\nIn {{{onPreExchange()}}}, we do two things:\n# We check to see whether the user has pressed button #0. If so, we do the re-shuffling of the teapots.\n# We stuff the teapot data into the framework for transfer from master to slaves. As explained in more detail in [[Transferring Arbitrary Sequences]], this method allows you to transfer arbitrarily-nested sequences of numbers and strings. You can read [[Python Sequence Types]] for a discussion of sequences, but the important thing is that our teapotData, being a list of lists of numbers, qualifies for transfer using this method without any modification. Lucky, that.\n\nIn {{{onPostExchange()}}}, we first check to see whether we're running as a master or a slave. If a master, do nothing; if a slave, get the teapot data from the master out of the framework. Note that after all of this, self.teapotData won't be quite the same on the master as on the slaves. On the master it's a list of lists; however, as described in [[Transferring Arbitrary Sequences]], on the slaves the numbers will all be in the same arrangement but it will instead be a tuple of tuples. The main difference between lists and tuples is that a tuple is immutable, i.e the values can't be changed or re-arranged. But we don't want to change them on the slaves, we just want to draw them, so that's fine.\n\nNow just for yucks we're going to add one more capability, the ability to fly around by pointing a wand and moving a joystick. We add one more line to the {{{onPreExchange()}}} method and override the previous {{{onDraw()}}} method, adding one new line there as well:\n{{{\n def onPreExchange( self ):\n arPyMasterSlaveFramework.onPreExchange(self)\n self.navUpdate() # <=== Added this\n if self.getOnButton(0):\n self.shufflePositions()\n self.setSequence( 'teapots', self.teapotData )\n\n def onDraw( self, win, viewport ):\n self.loadNavMatrix() # <=== And this\n szgteapots1.TeapotApp.onDraw( self, win, viewport )\n}}}\n\nSo what do these two lines do? Most of the work is in the first one. Syzygy assumes that you have a gamepad or joystick that's attached to a 6DOF position/orientation tracker, so it can know which way the gamepad is pointing. It checks to see if you've moved (tilted) the joystick. If so, it combines the direction of tilt with the direction that the tracking device says that you're pointing and moves you in that direction. More specifically, it modifies a //navigation matrix// that is automatically shared from master to slaves. The second line, in {{{onDraw()}}}, just loads that navigation matrix onto the OpenGL matrix stack, so that it affects any subsequent rendering.\n\nThe upshot of all of this is that you can point your gamepad in any direction, tilt the joystick forwards, and fly in the direction you're pointing.\n\nIf you run this program ({{{python szgteapots2.py}}}), it initially looks exactly as before. However you can now:\n# Press the '6' key and drag the mouse around to fly.\n# Press the '4' key on your main keyboard (not the numeric keypad) and press the left mouse button to reshuffle the teapots. You'll have to set up a Syzygy cluster in order to see the data transfer in operation.\n
config.options.chkAnimate=false;\nconfig.options.txtUserName='JimCrowell';\nconfig.options.chkTopOfPageMode=true;
body {\n background: #fffaae;\n color: #000;\n}\n\n.tiddler {\n background: #fffaae;\n padding: 1em 1em 0.5em 1em;\n margin-bottom: 1em;\n border: none;\n}\n\n.viewer .button {\n background: #e4ff70;\n color: #000;\n border: none;\n}\n\n.viewer .button:hover {\n background: #228b22;\n color: #fffaae;\n}\n\n.title {\ntext-align: right;\nbackground: #e4ff70;\n -moz-border-radius: 0.5em;\npadding: 0.2em;\n}\n\n#jsMath_button {\ndisplay: none;\n}\n\n/* navigator always visible\n.pageFooterOff #navigator{\n visibility: visible;\n}*/\n\n/* remove clock \n.slideClock{\n display: none;\n}*/
Within a CustomStyleSheet, you can include the text of another tiddler by including it in double square brackets. For example, if the tiddler MyFavouriteColour contains {{{#ff763e}}}, and the StyleSheet tiddler contained:\n\n{{{\n#mainMenu {background-color:[[MyFavouriteColour]];}\n#sidebarOptions {background-color: [[MyFavouriteColour]];}\n#sidebarTabs {background-color: [[MyFavouriteColour]];}\n}}}\n\nThen, the effect is that each CSS declaration will be set to {{{background-color: #ff763e;}}}. The benefit is that if your favourite colour should change, it's only got to be modified in one place.\n\nOf course, you can use this mechanism to redirect any part of a stylesheet, not just colours. And you can nest references for more complex effects.
/***\n|Name|NewHereCommand|\n|Source|http://simonbaird.com/mptw/#NewHereCommand|\n|Version|1.0|\n\nCode originally by ArphenLin. Small tweak by SimonBaird\nhttp://aiddlywiki.sourceforge.net/NewHere_demo.html#NewHereCommand\nTo use this you must edit your ViewTemplate and add newHere to the toolbar div, eg\n{{{<div class='toolbar' macro='toolbar ... newHere'></div>}}}\n***/\n//{{{\n\nconfig.commands.newHere = {\n text: 'new here',\n tooltip: 'Create a new tiddler tagged as this tiddler',\n handler: function(e,src,title) {\n if (!readOnly) {\n clearMessage();\n var t=document.getElementById('tiddler'+title);\n story.displayTiddler(t,config.macros.newTiddler.title,DEFAULT_EDIT_TEMPLATE);\n story.setTiddlerTag(config.macros.newTiddler.title, title, 0);\n story.focusTiddler(config.macros.newTiddler.title,"title");\n return false;\n }\n }\n};\n\nconfig.commands.newJournalHere = {\n //text: 'new journal here', // too long\n text: 'new journal',\n dataFormat: 'YYYY-0MM-0DD 0hh:0mm', // adjust to your preference\n tooltip: 'Create a new journal tiddler tagged as this tiddler',\n handler: function(e,src,title) {\n if (!readOnly) {\n clearMessage();\n var now = new Date();\n var t=document.getElementById('tiddler'+title);\n var newtitle = now.formatString(this.dataFormat)\n story.displayTiddler(t,newtitle,DEFAULT_EDIT_TEMPLATE);\n story.setTiddlerTag(newtitle, title, 0);\n story.focusTiddler(newtitle,"title");\n return false;\n }\n }\n};\n\n\n//}}}
To make a tiddler that doesn't have a WikiWord as its name, you can enclose the name in [[double square brackets]] - edit this tiddler to see an example. After saving the tiddler you can then click on the link to create the new tiddler. NonWikiWordLinks permits tiddlers to be created with names that are made from character sets that don't have upper and lower case.
It's easy to create NumberedBulletPoints.\n# Use a single '#' at the start of each line\n# and the tiddler will automatically\n# start numbering your list.\n## If you want a sub-list\n## within any bullets\n## add two '#'s at the start of the lines.\n# When you go back to a single '#'\n# the main numbered list will start up\n# where it left off.\n\nIt's just as simple to do normal BulletPoints.
Also called the Red Book. You can read a somewhat out-of-date edition online at [[http://www.rush3d.com/reference/opengl-redbook-1.1/|http://www.rush3d.com/reference/opengl-redbook-1.1/]]
/***\n|Name|OpenTiddlersMacro|\n|Created by|SaqImtiaz|\n|Location|http://tw.lewcid.org/#OpenTiddlersMacro|\n|Version|0.2 |\n|Requires|~TW2.08+|\n!Description:\n*Allows creation of tiddlyLinks that open multiple tiddlers.\n*Also useful for creating links to shadowTiddlers, which if normally created are not in bold.\n\n!Usage\n{{{<<openTiddlers text:"TextForLink" tiddlers:"Tiddler1 Tiddler2 [[Tiddler with spaces]] Tiddler4">>}}}\n\n!Example:\n{{{<<openTiddlers text:"This link opens multiple tiddlers" tiddlers:"Project Saq">>}}}\n<<openTiddlers text:"This link opens multiple tiddlers" tiddlers:"Project Saq">>\n\n!History\n*30-04-06, version 0.2, modifed and rename following feedback from Eric.\n*29-04-06, version 0.1, working.\n\n!To Do:\n*option to close other tiddlers\n*option to open in edit template\n\n!Code\n***/\n//{{{\nwindow.onClickMultiLink= function(e){\n story.displayTiddlers(this,this.getAttribute("tiddlerstring").readBracketedList());\n return(false);\n}\n\nconfig.macros.openTiddlers={};\nconfig.macros.openTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler){\n var nAV = paramString.parseParams('test', null, true);\n var text = nAV[0].text[0];\n var tiddlerstring = nAV[0].tiddlers[0];\n var btn= createTiddlyButton(place,text,null,onClickMultiLink,"tiddlyLinkExisting");\n btn.setAttribute("tiddlerstring",tiddlerstring);\n}\n//}}}
Once your program is in the event loop there are events that occur in a fixed sequence, once for each rendering of your virtual world (each frame, if you like, although this may or may not be the same as one video frame of your monitor).\n\n1. The framework collects any new input events that have arrived from device drivers or the [[inputsimulator]] program. These would be e.g. button presses, joystick angles, or tracker postion/orientation or //placement// matrices. These events arrive in a continuous stream throughout the event loop and are buffered for collection all at once.\n\n2. Your framework's {{{onPreExchange()}}} method is called //only in the master instance of the program//. As discussed in [[Programming Models]], this is the best place to do any state changes based on either (a) user input or (b) random variables. This is also where you would pack any transfer variables into the framework.\n\n3. The transfer variables are transferred from the master to the slaves. So are the current input state (i.e. the most recent values of all input events) and the navigation matrix (see [[Navigation]]).\n\n4. The framework's {{{onPostExchange()}}} method is called in both the master and the slaves. Here you would normally unpack the transfer variables and perhaps perform additional computation.\n\n5. For each window your program has opened, the framework's {{{onWindowInit()}}} method is called. This is for doing things like clearing the window. If you don't override it, the default behavior is to enable the OpenGL depth test, set the color mask for drawing to all four color planes (R,G,B and alpha), and clear the color and depth buffers.\n\n6. For each viewport the framework's {{{onDraw()}}} method is called. This is where you render your virtual world. That's all you should do; if you feel an inclination to do any computation here, put it in {{{onPreExchange()}}} or {{{onPostExchange()}}} instead. In particular, keep in mind that this method may be called more than once/frame, if e.g. your application is running on a computer with a stereoscopic display or the user has configured Syzygy for multiple viewports or windows on a single computer.\n\nThe discussion in 5 and 6 is a bit misleading about the order. What actually happens is that, for Window A, the {{{onWindowInit()}}} method is called. Then {{{onDraw()}}} is called for each viewport defined in Window A. Then {{{onWindowInit()}}} is called for Window B, and so on.\n\n7. Ordinary window-related events, such as keypresses and window-resize events, are processed by calling the framework's {{{onKey()}}} and {{{onWindowEvent()}}} methods. The former allows you to process key-presses, and is mainly used for controlling programs in Standalone Mode; the latter, for example, allows you to determine how many pixels wide a window is.\n\nAnd then it all repeats.\n\nAs stated above, {{{onPreExchange()}}} is only called in the master.The rest of the callback methods mentioned here are called in both the master and in the slaves.\n\nExceptions:\n\nNone of the methods above are called in a slave that doesn't have a connection to the master (which could happend when it first starts up--the master might not have started yet--or if the master crashes). Instead, the {{{onDisconnectDraw()}}} method is called; by default, this just blanks the screen, but you could override it to e.g. draw a splash screen. Note that unlike {{{onDraw()}}}, if you override this method, you will have to manually clear the window and explicitly set any projection and modelview matrices. The PySZG module function {{{ar_defaultWindowInitCallback()}}} clears the window (you must set the clear color) and the depth buffer and enables the depth test.\n\nIf you are using the [["Predetermined Harmony" Programming Model]], none of these methods are called in either the master or the slaves until all expected slaves (i.e. as defined by your //virtual computer// definition) have connected to the master. As above, the {{{onDisconnectDraw()}}} method is called in all instances of the application until this occurs.\n\nNext: [[Events in Other Threads]].
4x4 matrices are heavily used in 3-D graphics in general and in Syzygy and OpenGL in particular. A complete discussion of their use is beyond the scope of this book; see [[Homogeneous Coordinates and Transformation Matrices|http://www.rush3d.com/reference/opengl-redbook-1.1/appendixg.html]] for a description of how different kinds of transformations can be represented as matrices and [[Chapter 3: Viewing|http://www.rush3d.com/reference/opengl-redbook-1.1/chapter03.html]] in the [[OpenGL Programming Guide|http://www.rush3d.com/reference/opengl-redbook-1.1/]] for a complete discussion of how to work with transformations.\n\nI do want to quickly go over a small subset of this information here, however. In virtual reality many instances crop up in which we have to deal with positions and orientations of objects, because that's what tracking devices usually tell us about. Syzygy stores and transfers this information in the form of a matrix, implemented as an [[arMatrix4]], and we refer to such a matrix as a //placement matrix// (placement = position + orientation).\n\nA number of Syzygy classes (the [[arPyMasterSlaveFramework]], the [[arEffector]], and the [[arPyInteractable]] in particular) have ''getMatrix()'' and sometimes ''setMatrix()'' methods for accessing placement matrices, and Syzygy provides a large number of [[Math Functions]] for manipulating them and other kinds of 4x4 matrices.\n\nYou can extract the position and orientation information from a placement matrix and use them separately, but it is generally more useful to use the matrix to define an object-centered coordinate system. Once you have applied an object's placement matrix to the current OpenGL modeling transformation (see below), any subsequent drawing commands take effect in a coordinate sytem that moves around with the object. For an example, look at the ''draw()'' method of either the ColoredSquare or RodEffector classes in [[dictSkeleton.py|python/dictSkeleton.py]]. In each case, the method begins by loading a placement matrix and continues with a set of drawing commands. The drawing commands can be fixed in spatial terms, yet the whole object moves around as the placement matrix changes. When the object is in the 'neutral' position, assuming the tracking device parameters have been specified correctly, the positive X axis should normally point to the right, the positive Y axis should point upwards, and the negative Z axis should point forwards (that's the OpenGL consortium's idea of how things should work, not ours). For a more complete discussion, see [[Chapter 3: Viewing|http://www.rush3d.com/reference/opengl-redbook-1.1/chapter03.html]] in the [[OpenGL Programming Guide|http://www.rush3d.com/reference/opengl-redbook-1.1/]].\n\n[[PyOpenGL|http://pyopengl.sourceforge.net/]], the Python interface to OpenGL, has the function glMultMatrixf() for applying a matrix. It seems to be happy to accept any [[Python Sequence Types]] containing 16 Floats in down-the-columns-first order, i.e. given sequence S, it will interpret it as the matrix:\n\n\s[ \sleft( \sbegin{array}{cccc}\nS_0 & S_4 & S_8 & S_{12} \s\s\nS_1 & S_5 & S_9 & S_{13} \s\s\nS_2 & S_6 & S_{10} & S_{14} \s\s\nS_3 & S_7 & S_{11} & S_{15}\n\send{array} \sright)\s]\n\nAn [[arMatrix4]] does not qualify as a Python sequence, so in order to use one in e.g. an OpenGL function you will have to convert it to a tuple or list, e.g. in your framework's ''onDraw()'' method you might call:\n{{{\nglMultMatrixf( self.getMatrix(1).toTuple() )\n}}}\n''self.getMatrix(1)'' returns the framework's matrix event #1 (see Polling the Framework's Input State) as an arMatrix4, and the [[arMatrix4]]'s toTuple() method converts it to a 16-element tuple of Floats.\nNote that an [[arMatrix4]] can represent any 4x4 matrix, not just a placement matrix.
Format blocks of CSS definitions as:\n{{{\n/*{{{*/\ndiv {color: #ff0000;}\n/*}}}*/\n}}}\nIt will be displayed as:\n/*{{{*/\ndiv {color: #ff0000;}\n/*}}}*/
The master-slave framework has the following methods for determining the current state of an input event:\n{{{\nframework.getMatrix( index )\nframework.getAxis( index )\nframework.getButton( index )\n}}}\nThese three methods return the last-received value for the event # {{{index}}} of the appropriate type. Note that if input events are arriving faster than once/frame for a given event index, then you will miss some. This is unlikely unless your application runs very slowly; it is most likely to a problem with button events as it's much more common to cause your application to change state in a qualitative fashion based on changes in a button event (e.g., a box opens when you press button #0) than for the other two event types. The other issue with button events is that you're much more likely to be interested in changes in the event value (button just pressed or button just released) than in its ongoing state, which is why the following two methods are provided:\n{{{\nframework.getOnButton( index )\nframework.getOffButton( index )\n}}}\n\nThese two routines compare the current state of a button with its immediately preceding state. {{{getOnButton()}}} returns {{{True}}} if the current value of the button is 1 and the preceding value was 0, {{{getOffButton()}}} the reverse. Note that these two methods use the immediately preceding value in the actual event stream, not the value from the previous frame, so you're less likely to miss events with these two methods than with the preceding three.\n\nThe {{{arEffector}}} class has the same set of methods, although depending on the parameters you specified in its constructor, the same index may refer to a different button.
Syzygy programs use OpenGL for rendering graphics. We assume that most people who want to use Syzygy will be somewhat familiar with OpenGL; if not, you'll want to find a copy of the OpenGLProgrammingGuide in order to figure out how to render anything.\n\nAny graphics program needs access to a set of tools for creating and managing windows and for gathering user input. That's what Syzygy is, really. However, most people who learn OpenGL learn it it in the context of a much simpler toolkit, GLUT (short for GL Utility Toolkit)???, and that's the toolkit the examples in the OpenGLProgrammingGuide are based on. Furthermore, earlier versions of Syzygy were partially based on GLUT, so the two are similar in some respects.\n\nIn this chapter we'll analyze a simple GLUT program, [[teapots.py|python/teapots/teapots.py]] and show how to make it work with Syzygy. All the GLUT version does is to draw an array of teapots. Save and run it by typing {{{python teapots.py}}} and you'll get a window like this one:\n\n[img[GLUT Teapots|images/teapots.jpg]]\n\nWe'll start by [[Analyzing the GLUT Version of Teapots]].\n\nAfter that, we'll go on to create a [[Basic Syzygy Version of Teapots]] that does pretty much exactly what the GLUT version does--except, if you run it in [[Cluster Mode]] in VR you could walk around an look at it from different viewpoints.\n\nThen we'll add some more advanced--but still quite basic--Syzygy capabilities, such as navigation and the ability to change the teapot colors in the master instance of the application and have that change propagate to the slaves. All of this in a [[More Interesting Syzygy Version of Teapots]].\n
You can now link to [[external sites|http://www.osmosoft.com]] or [[ordinary tiddlers|TiddlyWiki]] with ordinary words, without the messiness of the full URL appearing. Edit this tiddler to see how.\n\nYou can also LinkToFolders.
Writing Syzygy programs can be a bit tricky, especially for beginning programmers. Getting things working in [[Standalone Mode]] is not too difficult (no more so than writing OpenGL programs using other frameworks, anyway). However, once you start playing around with PC clusters you run into the issue of keeping the various instances of your application synchronized.\n\nFunctional Syzygy master/slave programs generally fall into one of two categories or programming models. Our [[Recommended Programming Model]] is the most robust, but it is definitely more difficult, in that it requires you to explicitly identify all of the variables that define your scene and that may change over the course of the program's execution. On each frame, you compute these variables on the master and then transfer them to the slaves; then master and slaves use them to render the scene.\nHowever, sometimes this approach may not be practical. For example, your scene may depend on a large, time-varying data set, too big to transfer from master to slaves on every frame. Or you might just be lazy. In these cases, you can use the [["Predetermined Harmony" Programming Model]]. In this model, all computations are done in parallel by the master and slaves; the only state that has to be transferred from master to slaves are seeds for random-number generation. However, this only works if all of the programs start running at the same time, so the framework has some built-in checks to try and ensure that this happens.
In this manual you'll see a number of references to 'sequence types'; there are a number of Syzygy methods that will accept any sequence type. Python includes a number of [[data types that qualify as 'sequences'|http://www.python.org/doc/2.4.2/lib/typesseq.html]]; basically, it means that you can insert or extract items in them using the square-brackets operator with a numerical index, e.g. 'foo[2] = 7'. The most generic of these is the //List//. A List is an ordered sequence of Python objects or references to Python objects. For example:\n{{{\n[1, 2., 'foo', [1, 0]]\n}}}\nrepresents a List containing an Int, a Float, a String, and another List. Closely related to the List is the Tuple. Tuples are delimited by '(' and ')' instead of '[' and ']', e.g.:\n{{{\n(1, 2., 'foo', [1, 0])\n}}}\nThe functional difference is that a Tuple is immutable (read-only); however, this restriction allows its data to be accessed a bit more quickly. Note also that a tuple containing exactly one item must be written like so:\n{{{\n('foo',)\n}}}\nwith a comma after the item.\n\nAnother useful sequence type is an array created by one of the Numerical Python modules (numpy or its successor numarray). These are generally constrained such that all elements are of the same data type. For example, to create a 4x4 matrix using numarray, you might do the following:\n{{{\nimport numarray\nmyArray = numarray.array([[1,2,3,4],[5,6,7,8],[9,0,1,2],[3,4,5,6]])\n}}}\nThis creates a 4-element sequence of 4-element sequences of Floats.
/***\n| Name:|QuickOpenTagPlugin|\n| Purpose:|Makes tag links into a Taggly style open tag plus a normal style drop down menu|\n| Creator:|SimonBaird|\n| Source:|http://simonbaird.com/mptw/#QuickOpenTagPlugin|\n| Requires:|TW 2.x|\n| Version|1.1.1 (19-May-06)|\n\n!History\n* Version 1.1.1 (19/05/2006)\n** Added a little more CSS so the tags look good in standard main menus and normal tiddlers\n* Version 1.1 (07/02/2006)\n** Fix Firefox 1.5.0.1 crashes\n** Updated by ~BidiX[at]~BidiX.info\n* Version 1.0 (?/01/2006)\n** First release\n\n***/\n//{{{\n\n//⊻ ⊽ ⋁ ▼ \n\nwindow.createTagButton_orig_mptw = createTagButton;\nwindow.createTagButton = function(place,tag,excludeTiddler) {\n var sp = createTiddlyElement(place,"span",null,"quickopentag");\n createTiddlyLink(sp,tag,true,"button");\n var theTag = createTiddlyButton(sp,config.macros.miniTag.dropdownchar,config.views.wikified.tag.tooltip.format([tag]),onClickTag);\n theTag.setAttribute("tag",tag);\n if(excludeTiddler)\n theTag.setAttribute("tiddler",excludeTiddler);\n return(theTag);\n};\n\nconfig.macros.miniTag = {handler:function(place,macroName,params,wikifier,paramString,tiddler) {\n var tagged = store.getTaggedTiddlers(tiddler.title);\n if (tagged.length > 0) {\n var theTag = createTiddlyButton(place,config.macros.miniTag.dropdownchar,config.views.wikified.tag.tooltip.format([tiddler.title]),onClickTag);\n theTag.setAttribute("tag",tiddler.title);\n theTag.className = "miniTag";\n }\n}};\n\nconfig.macros.miniTag.dropdownchar = (document.all?"▼":"▾"); // the fat one is the only one that works in IE\n\nconfig.macros.allTags.handler = function(place,macroName,params)\n{\n var tags = store.getTags();\n var theDateList = createTiddlyElement(place,"ul",null,null,null);\n if(tags.length === 0)\n createTiddlyElement(theDateList,"li",null,"listTitle",this.noTags);\n for (var t=0; t<tags.length; t++)\n {\n var theListItem =createTiddlyElement(theDateList,"li",null,null,null);\n var theLink = createTiddlyLink(theListItem,tags[t][0],true);\n var theCount = " (" + tags[t][1] + ")";\n theLink.appendChild(document.createTextNode(theCount));\n\n var theDropDownBtn = createTiddlyButton(theListItem," "+config.macros.miniTag.dropdownchar,this.tooltip.format([tags[t][0]]),onClickTag);\n theDropDownBtn.setAttribute("tag",tags[t][0]);\n }\n};\n\n\n// probably could redo these styles a bit cleaner..\nsetStylesheet(\n ".tagglyTagged .quickopentag, .tagged .quickopentag \sn"+\n " { margin-right:1.2em; border:1px solid #eee; padding:2px; padding-right:0px; padding-left:1px; }\sn"+\n ".quickopentag .tiddlyLink { padding:2px; padding-left:3px; }\sn"+\n ".quickopentag a.button { padding:1px; padding-left:2px; padding-right:2px;}\sn"+\n// extra specificity to make it work?\n "#displayArea .viewer .quickopentag a.button, \sn"+\n "#displayArea .viewer .quickopentag a.tiddyLink, \sn"+\n "#mainMenu .quickopentag a.tiddyLink, \sn"+\n "#mainMenu .quickopentag a.tiddyLink \sn"+\n " { border:0px solid black; }\sn"+\n "#displayArea .viewer .quickopentag a.button, \sn"+\n "#mainMenu .quickopentag a.button \sn"+\n "{ margin-left:0px; padding-left:2px; }\sn"+\n "#displayArea .viewer .quickopentag a.tiddlyLink, \sn"+\n "#mainMenu .quickopentag a.tiddlyLink \sn"+\n " { margin-right:0px; padding-right:0px; padding-left:0px; margin-left:0px; }\sn"+\n "a.miniTag {font-size:150%;} \sn"+\n "#mainMenu .quickopentag a.button \sn"+\n "{ margin-left:0px; padding-left:2px; margin-right:0px; padding-right:0px; }\sn"+ // looks better in right justified main menus\n "",\n"QuickOpenTagStyles");\n\n//}}}\n\n/***\n<html>&#x22bb; &#x22bd; &#x22c1; &#x25bc; &#x25be;</html>\n***/\n
This is the approach to writing Syzygy programs that I've settled on as being safest and most robust. The basic idea is as follows: The way to ensure that everyone ends up getting the same result for a computation is to have only one instance of the program perform the computation and then share the result.\n\nTaken to its logical extreme, this notion implies that:\n\n# All computation should be done in the {{{onPreExchange()}}} method, which is called only in the master.\n# All variables need to render the scene should be transferred from master to slaves on each frame (packed in the master at the end of {{{onPreExchange()}}} and unpacked in the slaves in {{{onPostExchange()}}}--and this is all that should be done in onPostExchange()).\n# The scene is then rendered identically in master and slave in {{{onDraw()}}}. None of the transferred variables may be changed in this method.\n\nOf course, this is a bit ridiculous (logical extreme, remember), but it makes a good starting point. More practically:\nYou should try to do as much computation as possible in {{{onPreExchange()}}}. In particular, any processing of user input and any computations based on random numbers should occur here.\n\nYou may occasionally want to move a particular calculation into {{{onPostExchange()}}}; for example, if a large array is calculated in a straightforward manner from a small set of variables, by all means transfer the variables and compute the array in {{{onPostExchange()}}}.\n\nYou should never be doing any computation that modifies your state variables in {{{onDraw()}}}. {{{onDraw()}}} can be called multiple times/frame (it gets called once to draw each viewport, and besides the obvious usage of viewports to divide up a window into separate views, in Syzygy if you're doing stereo rendering you're also using two viewports for that...), so it should just be for rendering. As much as possible, you only want to be making OpenGL calls (or calling other methods that just make OpenGL calls) in this method.\n\nNote that the the example programs in [[Porting Teapots from GLUT]] and [[A Simple Master/Slave Application]] both adhere to this programming model.\n\nWhen writing a program according to this model, you'll generally start out by dividing your attention between {{{onPreExchange()}}} and {{{onDraw()}}}. Computations go in the former, rendering (OpenGL commands) in the latter. This is all you need to get your program running in Standalone Mode. When you're ready to try running it in Cluster Mode, you inspect your {{{onDraw()}}} method, determine which variables it requires, and think about how to best transfer those from master to slaves at the end of {{{onPreExchange()}}} and the beginning of {{{onPostExchange()}}}. At this point you may also identify certain computations that are more practically done in {{{onPostExchange()}}}, as described above.
/***\n| Name:|RenameTagsPlugin|\n| Purpose:|Allows you to easily rename tags|\n| Creator:|SimonBaird|\n| Source:|http://simonbaird.com/mptw/#RenameTagsPlugin|\n| Version:|1.0.1 (5-Mar-06)|\n\n!Description\nIf you rename a tiddler/tag that is tagging other tiddlers this plugin will ask you if you want to rename the tag in each tiddler where it is used. This is essential if you use tags and ever want to rename them. To use it, open the tag you want to rename as a tiddler (it's the last option in the tag popup menu), edit it, rename it and click done. You will asked if you want to rename the tag. Click OK to rename the tag in the tiddlers that use it. Click Cancel to not rename the tag.\n\n!Example\nTry renaming [[Plugins]] or [[CSS]] on this site.\n\n!History\n* 1.0.1 (5-Mar-06) - Added feature to allow renaming of tags without side-effect of creating a tiddler\n* 1.0.0 (5-Mar-06) - First working version\n\n!Code\n***/\n//{{{\n\nversion.extensions.RenameTagsPlugin = {\n major: 1, minor: 0, revision: 0,\n date: new Date(2006,3,5),\n source: "http://simonbaird.com/mptw/#RenameTagsPlugin"\n};\n\nconfig.macros.RenameTagsPlugin = {};\nconfig.macros.RenameTagsPlugin.prompt = "Rename the tag '%0' to '%1' in %2 tidder%3?";\n\n// these are very useful, perhaps they should be in the core\nif (!store.addTag) {\n store.addTag = function(title,tag) {\n var t=this.getTiddler(title); if (!t || !t.tags) return;\n t.tags.push(tag);\n };\n};\n\nif (!store.removeTag) {\n store.removeTag = function(title,tag) {\n var t=this.getTiddler(title); if (!t || !t.tags) return;\n if (t.tags.find(tag)!=null) t.tags.splice(t.tags.find(tag),1);\n };\n};\n\nstore.saveTiddler_orig_tagrename = store.saveTiddler;\nstore.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags) {\n if (title != newTitle && this.getTaggedTiddlers(title).length > 0) {\n // then we are renaming a tag\n var tagged = this.getTaggedTiddlers(title);\n if (confirm(config.macros.RenameTagsPlugin.prompt.format([title,newTitle,tagged.length,tagged.length>1?"s":""]))) {\n for (var i=0;i<tagged.length;i++) {\n store.removeTag(tagged[i].title,title);\n store.addTag(tagged[i].title,newTitle);\n // if tiddler is visible refresh it to show updated tag\n story.refreshTiddler(tagged[i].title,false,true);\n }\n }\n if (!this.tiddlerExists(title) && newBody == "") {\n // dont create unwanted tiddler\n return null;\n }\n }\n return this.saveTiddler_orig_tagrename(title,newTitle,newBody,modifier,modified,tags);\n}\n\n//}}}\n\n
Like GLUT, Syzygy master/slave programs are based on an event loop: The programmer defines functions to be called when certain events occur. The similarity is not accidental, as Syzygy was originally based on GLUT (it isn't anymore). However, the Syzygy event loop differs from GLUT's in a number of ways.\n\nFirst, a number of the Syzygy event callbacks are more directly related to the frame-rendering cycle, and in a very strictly defined way. That is, in Syzygy you are always either preparing to render a frame or rendering one, and there is a strict sequence of callbacks for each video frame. There is no equivalent of the GLUT 'idle' event callback, because a Syzygy program is //never// idle.\n\nSecond, Syzygy is in some ways a bit more complex in that a given program can run in two distinct modes, as a //master// (exactly one per cluster) or as a a //slave// (from zero to many per cluster). The sequence of events is different for master and slave instances of a program. For a discussion of the functional differences between the two, see the section on [[Programming Models]]; in this section we will only discuss the difference in the sequence of events.\n\nAlso, because the Syzygy libraries are multi-threaded (i.e. are performing multiple tasks in parallel), a few events can occur at any time and their callbacks are called in a separate thread from the main frame-linked loop (see [[Events in Other Threads]]). If you want to use these, you'll have to know a bit about multi-threaded programming and Python threads and locks.\n\nFinally, in Syzygy you're encouraged to take an object-oriented approach to programming in Python. Instead of defining a global function and passing it to a callback-setting function, as you do in GLUT, you define a framework or application class that is a sub-class of the arPyMasterSlaveFramework class. This class installs certain of its own methods as callbacks; for example, the framework's {{{onDraw()}}} method is the equivalent of the GLUT display callback and is automatically called to render the scene. However, the arPyMasterSlaveFramework's {{{onDraw()}}} method doesn't actually do anything. When you create your arPyMasterSlaveFramework sub-class, you define an {{{onDraw()}}} method for it that replaces the superclass' method and renders your scene. Wait, it will hopefully become clearer later.\n\nThe sequence of events in a Syzygy program can be subdivided into three categories:\n!Initialization and Cleanup: Things that happen (generally only once) when your program starts up and when it exits.\n<<tiddler "Initialization and Cleanup">>\n\n!Per-Frame Event Loop: Events that occur once for each frame or rendering of your scene in a rigid sequence.\n<<tiddler "Per-Frame Event Loop">>\n\n!Events in Other Threads: Callbacks that can be called at any time in a separate thread from the rest (and hence must be used a bit cautiously).\n<<tiddler "Events in Other Threads">>
(Normally-hidden tiddlers)\n\n<<list shadowed>>
/***\n|''Name:''|SinglePageModePlugin|\n|''Source:''|http://www.TiddlyTools.com/#SinglePageModePlugin|\n|''Author:''|Eric Shulman - ELS Design Studios|\n|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|\n|''~CoreVersion:''|2.0.10|\n\nNormally, as you click on the links in TiddlyWiki, more and more tiddlers are displayed on the page. The order of this tiddler display depends upon when and where you have clicked. Some people like this non-linear method of reading the document, while others have reported that when many tiddlers have been opened, it can get somewhat confusing.\n\n!!!!!Usage\n<<<\nSinglePageMode allows you to configure TiddlyWiki to navigate more like a traditional multipage web site with only one item displayed at a time. When SinglePageMode is enabled, the title of the current tiddler is automatically displayed in the browser window's titlebar and the browser's location URL is updated with a 'permalink' for the current tiddler so that it is easier to create a browser 'bookmark' for the current tiddler.\n\nEven when SinglePageMode is disabled (i.e., displaying multiple tiddlers is permitted), you can reduce the potential for confusion by enable TopOfPageMode, which forces tiddlers to always open at the top of the page instead of being displayed following the tiddler containing the link that was clicked.\n<<<\n!!!!!Configuration\n<<<\nWhen installed, this plugin automatically adds checkboxes in the AdvancedOptions tiddler so you can enable/disable the plugin behavior. For convenience, these checkboxes are also included here:\n\n<<option chkSinglePageMode>> Display one tiddler at a time\n<<option chkTopOfPageMode>> Always open tiddlers at the top of the page\n<<<\n!!!!!Installation\n<<<\nimport (or copy/paste) the following tiddlers into your document:\n''SinglePageModePlugin'' (tagged with <<tag systemConfig>>)\n^^documentation and javascript for SinglePageMode handling^^\n\nWhen installed, this plugin automatically adds checkboxes in the ''shadow'' AdvancedOptions tiddler so you can enable/disable this behavior. However, if you have customized your AdvancedOptions, you will need to ''manually add these checkboxes to your customized tiddler.''\n<<<\n!!!!!Revision History\n<<<\n''2006.07.04 [2.2.1]'' in hijack for displayTiddlers(), suspend TPM as well as SPM so that DefaultTiddlers displays in the correct order.\n''2006.06.01 [2.2.0]'' added chkTopOfPageMode (TPM) handling\n''2006.02.04 [2.1.1]'' moved global variable declarations to config.* to avoid FireFox 1.5.0.1 crash bug when assigning to globals\n''2005.12.27 [2.1.0]'' hijack displayTiddlers() so that SPM can be suspended during startup while displaying the DefaultTiddlers (or #hash list). Also, corrected initialization for undefined SPM flag to "false", so default behavior is to display multiple tiddlers\n''2005.12.27 [2.0.0]'' Update for TW2.0\n''2005.11.24 [1.1.2]'' When the back and forward buttons are used, the page now changes to match the URL. Based on code added by Clint Checketts\n''2005.10.14 [1.1.1]'' permalink creation now calls encodeTiddlyLink() to handle tiddler titles with spaces in them\n''2005.10.14 [1.1.0]'' added automatic setting of window title and location bar ('auto-permalink'). feature suggestion by David Dickens.\n''2005.10.09 [1.0.1]'' combined documentation and code in a single tiddler\n''2005.08.15 [1.0.0]'' Initial Release\n<<<\n!!!!!Credits\n<<<\nThis feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]].\nSupport for BACK/FORWARD buttons adapted from code developed by Clint Checketts\n<<<\n!!!!!Code\n***/\n//{{{\nversion.extensions.SinglePageMode= {major: 2, minor: 2, revision: 1, date: new Date(2006,7,3)};\n\nif (config.options.chkSinglePageMode==undefined) config.options.chkSinglePageMode=false;\nconfig.shadowTiddlers.AdvancedOptions += "\sn<<option chkSinglePageMode>> Display one tiddler at a time";\n\nif (config.options.chkTopOfPageMode==undefined) config.options.chkTopOfPageMode=false;\nconfig.shadowTiddlers.AdvancedOptions += "\sn<<option chkTopOfPageMode>> Always open tiddlers at the top of the page";\n\nconfig.SPMTimer = 0;\nconfig.lastURL = window.location.hash;\nfunction checkLastURL()\n{\n if (!config.options.chkSinglePageMode)\n { window.clearInterval(config.SPMTimer); config.SPMTimer=0; return; }\n if (config.lastURL == window.location.hash)\n return;\n var tiddlerName = convertUTF8ToUnicode(decodeURI(window.location.hash.substr(1)));\n tiddlerName=tiddlerName.replace(/\s[\s[/,"").replace(/\s]\s]/,""); // strip any [[ ]] bracketing\n if (tiddlerName.length) story.displayTiddler(null,tiddlerName,1,null,null);\n}\n\nif (Story.prototype.SPM_coreDisplayTiddler==undefined) Story.prototype.SPM_coreDisplayTiddler=Story.prototype.displayTiddler;\nStory.prototype.displayTiddler = function(srcElement,title,template,animate,slowly)\n{\n if (config.options.chkSinglePageMode) {\n window.location.hash = encodeURIComponent(String.encodeTiddlyLink(title));\n config.lastURL = window.location.hash;\n document.title = wikifyPlain("SiteTitle") + " - " + title;\n story.closeAllTiddlers();\n if (!config.SPMTimer) config.SPMTimer=window.setInterval(function() {checkLastURL();},1000);\n }\n if (config.options.chkTopOfPageMode) { story.closeTiddler(title); window.scrollTo(0,0); srcElement=null; }\n this.SPM_coreDisplayTiddler(srcElement,title,template,animate,slowly)\n}\n\nif (Story.prototype.SPM_coreDisplayTiddlers==undefined) Story.prototype.SPM_coreDisplayTiddlers=Story.prototype.displayTiddlers;\nStory.prototype.displayTiddlers = function(srcElement,titles,template,unused1,unused2,animate,slowly)\n{\n // suspend single-page mode when displaying multiple tiddlers\n var saveSPM=config.options.chkSinglePageMode; config.options.chkSinglePageMode=false;\n var saveTPM=config.options.chkTopOfPageMode; config.options.chkTopOfPageMode=false;\n this.SPM_coreDisplayTiddlers(srcElement,titles,template,unused1,unused2,animate,slowly);\n config.options.chkSinglePageMode=saveSPM; config.options.chkTopOfPageMode=saveTPM;\n}\n//}}}
easier cluster-based virtual reality
Syzygy Python Programming
<<slideShow>> - A simple slide show that keeps the TW style \n<<slideShow style:'MySSStyleSheet' clock:'+'>> - A themed slide show with a clock showing the presentation elapsed time\n<<slideShow repeat clock:'-20'>> - A looping slide show with a 20 minutes countdown clock\n<<slideShow slidePause:1000>> - A timed slideshow that runs once\n<<slideShow slidePause:1000 repeat>> - A timed looping slideshow\n-s-\n!The [[SlideShowPlugin]]\nPress F11 to go fullscreen and adjust the font sizes with Ctrl++ Ctrl+- (or Ctrl+mousewheel).\n\nThis plugin was developed by Paulo Soares and Clint Checketts.\n{{Comment{This block is not shown in the slide show.\n@@Don't show me!!!@@}}}\n-s-\n!How slides are separated\nIn a tiddler, you start each slide with the markup {{{-s-}}}\n-s-\nSlides don't have to have titles like this poor one\n-s-\n!A slide with subsections and a long title\nCheck to TOC below to see how this slide title is abbreviated.\n!!Section 1\nThis is a section\n!!!Subsection 1.1\nThis is a subsection\n!!!Subsection 1.2\nThis is another subsection\n!!!!Subsubsection 1.2.1\nThis is a subsubsection\n-s-\n!Using the keyboard\nThe following keys are defined:\n*Left arrow - previous overlay\n*Down arrow - previous slide\n*Right arrow - next overlay\n*Up arrow - next slide\n*Home - first slide\n*End - last slide\n*Escape - exit slide show\n*Spacebar - pause/resume slide show in auto advance mode\n-s-\n!Slide show parameters\n*The slide show can be themed by providing a ~StyleSheet ({{{<<slideShow style:'MyStyleSheet'>>}}})\n*By default, there is a clock at bottom of the browser window that displays the current time. This clock can also show the presentation elapsed time with {{{<<slideShow clock:'+'>>}}} or a countdown clock with {{{<<slideShow clock:'-20'>>}}} (for 20 minutes). In these two cases, if you click on the clock display it will be restarted\n*The slide show can be set to loop ({{{<<slideShow repeat>>}}})\n*You can set it so each slide changes after X milliseconds ({{{<<slideShow slidePause:X>>}}}) (auto advance mode)\n*Use auto start mode to begin the slideshow the moment the tiddler is opened ({{{<<slideShow autostart>>>}}})\n*You can disable overlays with {{{<<slideShow noOverlays>>}}}\n*These parameters can be mixed and matched in any order: {{{<<slideShow slidePause:1000 repeat>>}}} is the same as {{{<<slideShow repeat slidePause:1000>>}}}\n-s-\n!Overlays\nTo see how incremental display works use the left and right mouse buttons.\n{{Overlay1{You can}}} {{Overlay2{present things}}} {{Overlay1{in an arbitrary order!!!}}}\n{{Overlay3{Its a bit harder with lists but it works:}}}\n<html>\n<ol>\n<li class="Overlay4">First item</li>\n<li class="Overlay5">Second item</li>\n<li class="Overlay4">Last item</li>\n</ol>\n</html>\n{{Comment{You can hide comments on a slide that won't display in the slide show}}}
<!--{{{-->\n<div id='displayArea'>\n<div id='tiddlerDisplay'></div>\n</div>\n<!--}}}-->
/***\n|''Name:''|SlideShowPlugin|\n|''Description:''|Creates a simple slide show type display|\n|''Version:''|1.5.1|\n|''Date:''|Nov 10, 2006|\n|''Source:''|http://www.math.ist.utl.pt/~psoares/addons.html|\n|''Author:''|Paulo Soares (psoares (at) math (dot) ist (dot) utl (dot) pt) and [[Clint Checketts|http://www.checkettsweb.com]]|\n|''License:''|[[BSD open source license]]|\n|''~CoreVersion:''|2.1.0|\n|''Browser:''|Firefox 1.0.4+; Firefox 1.5; InternetExplorer 6.0|\n<<tiddler SlideShowPluginDoc>>\n!Code\n***/\n//{{{\nconfig.macros.slideShow = {label: "slide show", maxTOCLength: 30};\nconfig.macros.slideShow.messages = {gotoLabel: "Go to slide:"};\nconfig.views.wikified.slideShow = {text: "slide show", tooltip: "Start slide show"};\nconfig.views.wikified.slideShow.quit = {text: "end", tooltip: "Quit the slide show"};\nconfig.views.wikified.slideShow.firstSlide = {text: "<<", tooltip: "first slide"};\nconfig.views.wikified.slideShow.previousSlide = {text: "<", tooltip: "previous slide"};\nconfig.views.wikified.slideShow.nextSlide = {text: ">", tooltip: "next slide"};\nconfig.views.wikified.slideShow.lastSlide = {text: ">>", tooltip: "last slide"};\nconfig.views.wikified.slideShow.resetClock = {text: " ", tooltip: "reset"};\n\nconfig.formatters.push( {\n name: "SlideSeparator",\n match: "^-s-+$\s\sn?",\n handler: function(w)\n {\n createTiddlyElement(w.output,"hr",null,'slideSeparator');\n }\n}\n)\n\nfunction changeStyleSheet(tiddlerName) {\n if (tiddlerName == null) tiddlerName = "StyleSheet";\n setStylesheet(store.getRecursiveTiddlerText("StyleSheetColors"),"StyleSheetColors");\n setStylesheet(store.getRecursiveTiddlerText("StyleSheetLayout"),"StyleSheetLayout");\n var theCSS = store.getRecursiveTiddlerText(tiddlerName,"");\n setStylesheet(theCSS,"StyleSheet");\n}\n\n//Excellent (and versatile) reparser created by Paul Petterson for parsing the paramString in a macro\nfunction reparse( params ) {\n var re = /([^:\ss]+)(?:\s:((?:\sd+)|(?:["'](?:[^"']+)["']))|\ss|$)/g;\n var ret = new Array() ;\n var m ;\n while( (m = re.exec( params )) != null ) ret[ m[1] ] = m[2]?m[2]:true ;\n return ret ;\n}\n\nfunction getElementsByClass(searchClass,node,tag) {\n var classElements = new Array();\n if ( node == null ) node = document;\n if ( tag == null ) tag = '*';\n var els = node.getElementsByTagName(tag);\n var elsLen = els.length;\n var pattern = new RegExp("(^|\s\ss)"+searchClass+"(\s\ss|$)");\n var j=0;\n for (var i = 0; i < elsLen; i++) {\n if ( pattern.test(els[i].className) ) {\n classElements[j] = els[i];\n j++;\n }\n }\n return classElements;\n}\n\n// 'keys' code adapted from S5 which in turn was adapted from MozPoint (http://mozpoint.mozdev.org/)\nfunction keys(key) {\n if (document.getElementById('contentWrapper').className == "slideShowMode"){\n if (!key) {\n key = event;\n key.which = key.keyCode;\n }\n switch (key.which) {\n case 32: // spacebar\n if(time>0){\n if(autoAdvance){\n clearInterval(autoAdvance);\n autoAdvance = null;\n } else {\n autoAdvance=setInterval("GoToSlide(1)", time);\n }\n }\n break;\n case 34: // page down\n case 39: // rightkey\n GoToSlide("n");\n break;\n case 40: // downkey\n GoToSlide(-1);\n break;\n case 33: // page up\n case 37: // leftkey\n GoToSlide("p");\n break;\n case 38: // upkey\n GoToSlide(1);\n break;\n case 36: // home\n GoToSlide("f");\n break;\n case 35: // end\n GoToSlide("l");\n break;\n case 27: // escape\n endSlideShow();\n break;\n }\n\n }\n return false;\n}\n\nfunction clicker(e) {\n if (!e) var e = window.event;\n var target = resolveTarget(e);\n //Whenever something is clicked that won't advance the slide make sure that the table of contents gets hidden\n if (target.getAttribute('href') != null || isParentOrSelf(target, 'toc') || isParentOrSelf(target,'embed') || isParentOrSelf(target,'object') || isParentOrSelf(target, 'pageFooter') || isParentOrSelf(target, 'navigator')){\n //Don't hide the TOC if the indexNumbers (which trigger the index) is clicked\n if(isParentOrSelf(target,'indexNumbers') || isParentOrSelf(target,'jumpInput')){\n return true;\n }\n showHideTOC('none');\n return true;\n }\n \n //Advance a slide if the TOC is visible otherwise make sure that the TOC gets hidden\n if ((!e.which && e.button == 1) || e.which == 1) {\n if (document.getElementById('toc').style.display != 'block'){\n GoToSlide("n");\n } else {\n showHideTOC('none');\n }\n }\n \n if ((!e.which && e.button == 2) || e.which == 3) {\n if (document.getElementById('toc').style.display != 'block'){\n GoToSlide("p");\n } else {\n showHideTOC('none');\n }\n return false;\n }\n}\n\nfunction isParentOrSelf(element, id) {\n if (element == null || element.nodeName=='BODY') return false;\n else if (element.id == id) return true;\n else return isParentOrSelf(element.parentNode, id);\n}\n\nGoToSlide=function(step) {\n var new_pos;\n var slideHolder = document.getElementById('slideContainer');\n //The parse float ensures that the attribute is returned as a number and not a string.\n var cur_pos = parseFloat(slideHolder.getAttribute('currentslide'));\n var numberSlides = parseFloat(slideHolder.getAttribute('numberSlides'));\n switch (step) {\n case "f":\n new_pos=0;\n break;\n case "l":\n new_pos=numberSlides-1;\n break;\n case "n":\n var numberOverlays = parseFloat(slideHolder.childNodes[cur_pos].getAttribute('numberOverlays'));\n var currentOverlay = parseFloat(slideHolder.getAttribute('currentOverlay'));\n if(numberOverlays==0 || currentOverlay==numberOverlays){\n new_pos=cur_pos+1;\n } else {\n var className="Overlay"+currentOverlay;\n var overlay=getElementsByClass(className,slideHolder.childNodes[cur_pos]);\n for(var i=0; i<overlay.length; i++) {overlay[i].className=className+' previousOverlay';}\n currentOverlay++;\n slideHolder.setAttribute('currentOverlay',currentOverlay);\n className="Overlay"+currentOverlay;\n overlay=getElementsByClass(className,slideHolder.childNodes[cur_pos]);\n for(i=0; i<overlay.length; i++) {overlay[i].className=className+' currentOverlay';}\n return false;\n }\n break;\n case "p":\n var numberOverlays = parseFloat(slideHolder.childNodes[cur_pos].getAttribute('numberOverlays'));\n var currentOverlay = parseFloat(slideHolder.getAttribute('currentOverlay'));\n if(numberOverlays==0 || currentOverlay==0){\n new_pos=cur_pos-1;\n } else {\n var className="Overlay"+currentOverlay;\n var overlays=getElementsByClass(className,slideHolder.childNodes[cur_pos]);\n for(var i=0; i<overlays.length; i++) {overlays[i].className=className+' nextOverlay';}\n currentOverlay--;\n className="Overlay"+currentOverlay;\n overlays=getElementsByClass(className,slideHolder.childNodes[cur_pos]);\n for(i=0; i<overlays.length; i++) {overlays[i].className=className+' currentOverlay';}\n slideHolder.setAttribute('currentOverlay',currentOverlay);\n return false;\n }\n break;\n default:\n new_pos=cur_pos+step;\n }\n\n if(slideShowCircularMode && new_pos == numberSlides) new_pos=0;\n if(slideShowCircularMode && new_pos<0) new_pos=(numberSlides - 1);\n if(step!=0 && new_pos>=0 && new_pos<numberSlides) {\n slideHolder.childNodes[cur_pos].style.display='none';\n slideHolder.childNodes[new_pos].style.display='block';\n slideHolder.setAttribute('currentslide',new_pos);\n var numberOverlays = parseFloat(slideHolder.childNodes[new_pos].getAttribute('numberOverlays'));\n if(step=="p"){\n var currentOverlay=numberOverlays;\n var state=' previousOverlay';\n } else {\n var currentOverlay=0;\n var state=' nextOverlay';\n }\n slideHolder.setAttribute('currentOverlay',currentOverlay);\n if(numberOverlays>0) {\n for(var i=1; i<=numberOverlays; i++){\n var className="Overlay"+i;\n var overlays=getElementsByClass(className,slideHolder.childNodes[new_pos]);\n for(var j=0; j<overlays.length; j++) {overlays[j].className=className+state;}\n }\n if(step=="p"){\n var className="Overlay"+numberOverlays;\n var overlays=getElementsByClass(className,slideHolder.childNodes[new_pos]);\n for(var j=0; j<overlays.length; j++) {overlays[j].className=className+' currentOverlay';}\n }\n }\n new_pos++;\n var indexNumbers = document.getElementById('indexNumbers');\n indexNumbers.firstChild.data = new_pos+'/'+numberSlides;\n if((new_pos==numberSlides) && !slideShowCircularMode && autoAdvance) clearInterval(autoAdvance);\n return true;\n }\n return false;\n}\n\nfunction tocShowSlide(e) {\n if (!e) var e = window.event;\n var target = resolveTarget(e);\n var slide = target.getAttribute('slideNumber');\n var cur_pos = document.getElementById('slideContainer').getAttribute('currentslide');\n var step = slide-cur_pos;\n if(step!=0) GoToSlide(step);\n showHideTOC('none');\n return;\n}\n\n//Toggle the display of the table of contents\nfunction showHideTOC(display){\n var toc = document.getElementById('toc');\n //Reset the input box\n document.getElementById('jumpInput').value = "";\n\n if (display == null || display.length == null){\n if (toc.style.display == 'none' || toc.style.display == ''){\n toc.style.display = 'block';\n document.getElementById('jumpInput').focus();\n } else {\n toc.style.display = 'none';\n }\n } else {\n toc.style.display = display;\n if (display == 'block')\n document.getElementById('jumpInput').focus();\n }\n}\n\nfunction makeSignature(title,params){\n var signature = title+store.getTiddler(title).modified;\n if(params['style']) signature += params['style'];\n if(params['repeat']) signature += "repeat";\n if(params['slidePause'] > 0) signature += params['slidePause'];\n if(params['autostart']) signature += "autostart";\n if(params['clock']) signature += params['clock'];\n if(params['noOverlays']) signature += "noOverlays";\n return signature;\n}\n\nfunction padZero(x){\n return (x>=10 || x<0 ? "" : "0")+x;\n}\n\nsetClock=function(){\n var actualTime = new Date();\n var newTime = actualTime.getTime() - clockStartTime;\n newTime = clockMultiplier*newTime+clockInterval+clockCorrection;\n actualTime.setTime(newTime);\n newTime = padZero(actualTime.getHours()) + ":" + padZero(actualTime.getMinutes())+ ":" + padZero(actualTime.getSeconds());\n var clock = document.getElementById('slideClock');\n clock.firstChild.nodeValue = newTime;\n}\n\nresetClock=function(){\n var time = new Date(0);\n if(clockStartTime>time){\n var startTime = new Date();\n clockStartTime=startTime.getTime();\n }\n}\n\nvar title;\nvar place;\nvar autoAdvance=null;\nvar autoStart=null;\nvar slideClock=null;\nvar noOverlays=false;\nvar time = 0;\nvar slideShowCircularMode;\nvar slideShowStyleSheet;\nvar slideShowParams;\nvar clockMultiplier;\nvar clockInterval;\nvar clockCorrection=0;\nvar clockStartTime;\nvar openTiddlers;\n\nconfig.macros.slideShow.handler = function(aPlace,macroName,params,wikifier,paramString,tiddler){\n if(tiddler instanceof Tiddler){\n var lingo = config.views.wikified.slideShow;\n var autostart = false;\n if (!e) var e = window.event;\n \n place = aPlace;\n title = tiddler.title;\n params = reparse(paramString);\n var onclick = function(){config.macros.slideShow.onClickSlideShow(params);};\n createTiddlyButton(aPlace,lingo.text,lingo.tooltip,onclick);\n \n var slideShowHolder = document.getElementById('slideShowWrapper');\n //If no show exist previously, create it\n if(params['autostart']){\n if(slideShowHolder != null){\n var signature = slideShowHolder.getAttribute('showSignature');\n if(signature.indexOf("autostart")==-1) autostart = true;\n } else {autostart = true;}\n if(autostart){\n slideShowParams = params;\n setTimeout(config.macros.slideShow.onClickSlideShow,100);\n }\n }\n }\n}\n\nvar disableFunction = function(e){return false;}\nvar enableFunction = function(e){}\n\nconfig.macros.slideShow.onClickSlideShow = function(newParams) {\n if(typeof(newParams)=="number") newParams=slideShowParams;\n openTiddlers = new Array;\n var viewer=document.getElementById('tiddlerDisplay');\n for(var i=0; i<viewer.childNodes.length; i++){\n var name = viewer.childNodes[i].getAttribute('tiddler');\n openTiddlers.push(name);\n }\n document.oncontextmenu = disableFunction;\n clockMultiplier = 1;\n clockInterval = 0;\n var startTime = new Date(0);\n slideShowCircularMode = false;\n time = 0;\n slideShowStyleSheet = null;\n if(newParams['style']){\n slideShowStyleSheet = eval(newParams['style']);\n } \n if(newParams['repeat']){\n slideShowCircularMode = true;\n }\n if(newParams['slidePause'] > 0){\n time = newParams['slidePause'];\n }\n if(newParams['clock']){\n clockCorrection=startTime.getTimezoneOffset()*60000;\n startTime = new Date();\n var clockType= eval(newParams['clock']);\n if(clockType != '+') {\n clockMultiplier = -1;\n clockInterval = -clockType*60000;\n }\n }\n clockStartTime=startTime.getTime();\n if(newParams['noOverlays']){\n noOverlays = true;\n }\n var contentWrapper = document.getElementById('contentWrapper');\n if (contentWrapper.className != "slideShowMode"){\n clearMessage();\n //Attach the key and mouse listeners\n document.onkeyup = keys;\n //document.onmouseup = clicker;\n \n var slideShowHolder = document.getElementById('slideShowWrapper');\n story.refreshTiddler(title,"SlideShowViewTemplate",true);\n //If no show exist previously, create it\n if(slideShowHolder == null){\n createSlides(newParams);\n //If there was once waiting in the background and it matches the one we just started, resume it\n } else if (slideShowHolder.getAttribute('showSignature') == makeSignature(title,newParams)){\n \n //Remove dblClick on edit function\n var theTiddler = document.getElementById("tiddler"+title);\n theTiddler.ondblclick = function() {};\n\n // Grab the 'viewer' element and give it a signature so the show can be resumed if stopped\n var tiddlerElements = theTiddler.childNodes;\n var viewer;\n for (var i = 0; i < tiddlerElements.length; i++){\n if (tiddlerElements[i].className == "viewer") viewer = tiddlerElements[i];\n }\n theTiddler.insertBefore(slideShowHolder,viewer);\n theTiddler.removeChild(viewer);\n slideShowHolder.style.display = 'block';\n document.getElementById("pageFooter").className = "pageFooterOff";\n \n //If the show we started it totally new than the resumable one, create the new one and kill the resumable one\n } else {\n slideShowHolder.parentNode.removeChild(slideShowHolder);\n createSlides(newParams);\n }\n slideClock=setInterval("setClock()", 1000); \n if(time>0) autoAdvance=setInterval("GoToSlide(1)", time); \n story.closeAllTiddlers(title);\n toggleSlideStyles();\n } else {\n endSlideShow();\n }\n return ;\n \n}\n\nfunction endSlideShow(){\n //Set aside show so it can be resumed later\n var showHolder = document.getElementById('slideShowWrapper');\n showHolder.style.display = 'none';\n document.getElementById('contentWrapper').parentNode.appendChild(showHolder);\n document.oncontextmenu = enableFunction;\n if(autoAdvance) clearInterval(autoAdvance);\n if(slideClock) clearInterval(slideClock);\n story.refreshTiddler(title,null,true);\n story.closeAllTiddlers();\n toggleSlideStyles();\n story.displayTiddlers(null,openTiddlers,DEFAULT_VIEW_TEMPLATE);\n document.onmouseup = function(){};\n}\n\nfunction isInteger(s){\n var i;\n for (i = 0; i < s.length; i++){\n // Check that current character is number.\n var c = s.charAt(i);\n if (((c < "0") || (c > "9"))) return false;\n }\n // All characters are numbers.\n return true;\n}\n\nfunction jumpInputToSlide(e){\n if (!e) {\n e = window.event;\n e.which = e.keyCode;\n }\n if(e.which==13){\n var jumpInput= document.getElementById("jumpInput").value;\n if(isInteger(jumpInput)){\n var step=jumpInput-document.getElementById('slideContainer').getAttribute('currentslide')-1;\n if (GoToSlide(step)){\n showHideTOC('none'); \n }\n }\n }\n return;\n}\n\n//Used to shorten the TOC fields\nfunction abbreviateLabel(label){\n var maxTOCLength = config.macros.slideShow.maxTOCLength;\n if(label.length>maxTOCLength) {\n var temp = new Array();\n temp = label.split(' ');\n label = temp[0];\n for(var j=1; j<temp.length; j++){\n if((label.length+temp[j].length)<=maxTOCLength){\n label += " " + temp[j];\n } else {\n label += " ...";\n break;\n }\n }\n }\n return label;\n}\n\ncreateSlides = function(newParams){\n var lingo = config.views.wikified.slideShow;\n\n //Remove dblClick on edit function\n var theTiddler = document.getElementById("tiddler"+title);\n theTiddler.ondblclick = function() {};\n\n // Grab the 'viewer' element and give it a signature so the show can be resumed if stopped\n var tiddlerElements = theTiddler.childNodes;\n var viewer;\n for (var i = 0; i < tiddlerElements.length; i++){\n if (tiddlerElements[i].className == "viewer") viewer = tiddlerElements[i];\n }\n viewer.id = 'slideShowWrapper';\n viewer.setAttribute("showSignature",makeSignature(title,newParams));\n\n //Hide the text that comes before the first H1 element (I think I may put this into a cover page type thing)\n while(viewer.childNodes.length > 0 && viewer.firstChild.nodeName.toUpperCase() != "HR" && viewer.firstChild.className!="slideSeparator") {\n viewer.removeChild(viewer.firstChild);\n }\n \n //Cycle through the content and each time you hit an H1 begin a new slide div\n var slideNumber = 0;\n var slideHolder = document.createElement('DIV');\n slideHolder.id = "slideContainer";\n \n while(viewer.childNodes.length > 0){\n //Create a new slide a append it to the slide holder\n if (viewer.firstChild.nodeName.toUpperCase() == "HR" && viewer.firstChild.className=="slideSeparator"){\n slideNumber++;\n var slide = document.createElement('DIV');\n slide.id = "slideNumber"+slideNumber;\n slide.className = "slide";\n if (slideNumber > 1) {\n slideHolder.setAttribute('currentslide',0);\n slide.style.display='none';\n } else {\n slide.style.display='block';\n }\n slideHolder.appendChild(slide); \n viewer.removeChild(viewer.firstChild);\n } else {\n if(viewer.firstChild.nodeName=="SPAN" && viewer.firstChild.className=="" && viewer.firstChild.hasChildNodes()) {\n var anchor=viewer.firstChild.nextSibling;\n for (var ii=0;ii<viewer.firstChild.childNodes.length;ii++) {\n var clone=viewer.firstChild.childNodes[ii].cloneNode(true);\n viewer.insertBefore(clone,anchor);\n }\n viewer.removeChild(viewer.firstChild);\n } else {\n slide.appendChild(viewer.firstChild);\n }\n }\n }\n \n //Stick the slides back into the viewer\n viewer.appendChild(slideHolder);\n slideHolder.setAttribute('numberSlides',slideNumber);\n \n //Create the navigation bar\n var pagefooter = createTiddlyElement(viewer,"DIV","pageFooter","pageFooterOff");\n var navigator = createTiddlyElement(pagefooter,"SPAN","navigator");\n\n //Make it so that when the footer is hovered over the class will change to make it visible\n pagefooter.onmouseover = function () {pagefooter.className = "pageFooterOn"};\n pagefooter.onmouseout = function () {pagefooter.className = "pageFooterOff"};\n\n //Create the control button for the navigation \n var onClickQuit = function(){endSlideShow();};\n createTiddlyButton(navigator,lingo.quit.text,lingo.quit.tooltip,onClickQuit);\n createTiddlyButton(navigator,lingo.firstSlide.text,lingo.firstSlide.tooltip,first_slide);\n createTiddlyButton(navigator,lingo.previousSlide.text,lingo.previousSlide.tooltip,previous_slide);\n createTiddlyButton(navigator,lingo.nextSlide.text,lingo.nextSlide.tooltip,next_slide);\n createTiddlyButton(navigator,lingo.lastSlide.text,lingo.lastSlide.tooltip,last_slide); \n createTiddlyButton(navigator,lingo.resetClock.text,lingo.resetClock.tooltip,resetClock,"button","slideClock"); \n\n var indexNumbers = createTiddlyElement(pagefooter,"SPAN","indexNumbers","indexNumbers","1/"+slideNumber)\n indexNumbers.onclick = showHideTOC;\n var toc = createTiddlyElement(pagefooter,"UL","toc");\n var ovl=1;\n for (var i=0;i<slideHolder.childNodes.length;i++) {\n if(!noOverlays) {\n var ovl=1;\n while(1){\n var className="Overlay"+ovl;\n var overlays=getElementsByClass(className,slideHolder.childNodes[i]);\n if(overlays.length>0){\n for(var j=0; j<overlays.length; j++) {overlays[j].className+=' nextOverlay';}\n ovl++;\n } else {break;}\n }\n }\n slideHolder.childNodes[i].setAttribute("numberOverlays",ovl-1);\n slideHolder.setAttribute("currentOverlay",0);\n\n //Loop through each slide and check the header's content\n var tocLabel = null; \n for (var j=0;j<slideHolder.childNodes[i].childNodes.length;j++) {\n var node = slideHolder.childNodes[i].childNodes[j];\n if(node.nodeName=="H1" || node.nodeName=="H2" || node.nodeName=="H3" || node.nodeName=="H4") {\n var htstring = node.innerHTML;\n var stripped = htstring.replace(/(<([^>]+)>)/ig,"");\n tocLabel = abbreviateLabel(stripped);\n var tocLevel="tocLevel"+node.nodeName.charAt(1);\n var tocItem = createTiddlyElement(toc,"LI",null,tocLevel);\n var tocLink = createTiddlyElement(tocItem,"A",null,"tocItem",tocLabel);\n tocLink.setAttribute("slideNumber",i);\n tocLink.onclick=tocShowSlide;\n }\n }\n }\n \n\n //Input box to jump to s specific slide\n var tocItem = createTiddlyElement(toc,"LI",null,"tocJumpItem",config.macros.slideShow.messages.gotoLabel);\n var tocJumpInput = createTiddlyElement(tocItem,"INPUT","jumpInput");\n tocJumpInput.type="text";\n tocJumpInput.onkeyup=jumpInputToSlide;\n}\n\nvar next_slide= function(e){GoToSlide(1);}\nvar first_slide= function(e){GoToSlide("f");}\nvar previous_slide= function(e){GoToSlide(-1);}\nvar last_slide= function(e){GoToSlide("l");}\n\nfunction toggleSlideStyles(){\n var contentWrapper = document.getElementById('contentWrapper');\n if (contentWrapper.className == "slideShowMode"){\n contentWrapper.className = "";\n window.applyPageTemplate();\n if(slideShowStyleSheet) changeStyleSheet();\n } else{\n contentWrapper.className = "slideShowMode";\n window.applyPageTemplate("SlideShowPageTemplate");\n if(slideShowStyleSheet) changeStyleSheet(slideShowStyleSheet);\n }\n}\n\nsetStylesheet("/***\sn!Slide Mode Styles\sn***/\sn/*{{{*/\sn#contentWrapper.slideShowMode #slideContainer{\sn display: block;\sn}\sn\sn#contentWrapper.slideShowMode .Comment{\sn display: none;\sn}\sn\sn#contentWrapper.slideShowMode .nextOverlay{\sn visibility: hidden;\sn}\sn\sn#contentWrapper.slideShowMode .currentOverlay{\sn visibility: visible;\sn}\sn\sn#contentWrapper.slideShowMode .previousOverlay{\sn visibility: visible;\sn}\sn\sn#jump{\sn text-align: right;\sn}\sn\sn.pageFooterOff #navigator{\sn visibility: hidden;\sn}\sn\sn.pageFooterOn #navigator{\sn visibility: visible;\sn}\sn\sn#contentWrapper.slideShowMode #slideClock{\sn cursor: pointer; margin: 0 5px 0 5px; border: 1px solid #db4\sn}\sn\sn#contentWrapper.slideShowMode,\sn #contentWrapper.slideShowMode #displayArea{\sn width: 100%;\sn font-size: 1.5em;\sn margin: 0 !important;\sn padding: 0;\sn}\sn\sn#slideContainer{\sn display: none;\sn}\sn\sn.indexNumbers{\sn cursor: pointer;\sn}\sn\sn#navigator{\sn visibility: hidden;\sn bottom: 0;\sn}\sn\sn#toc{\sn display: none;\sn position: absolute;\sn font-size: .75em;\sn bottom: 2em;\sn right: 0;\sn background: #fff;\sn border: 1px solid #000;\sn text-align: left;\sn}\sn\snul#toc, #toc li{\sn margin: 0;\sn padding: 0;\sn list-style: none;\sn line-height: 1em;\sn}\sn\sn.tocJumpItem{\sn margin-right: 2em;\sn}\sn\sn.tocJumpItem input{\snmargin-right: 1em;\sn border: 0;\sn}\sn\sn#toc a,\sn#toc a.button{\sn display: block;\sn padding: .1em;\sn}\sn\sn#toc .tocLevel1{\snfont-size: .8em;\sn}\sn\sn#toc .tocLevel2{\sn margin-left: 1em;\sn font-size: .75em;\sn}\sn\sn#toc .tocLevel3{\sn margin-left: 2em;\snfont-size: .75em;\sn}\sn\sn#toc .tocLevel4{\sn margin-left: 3em;\snfont-size: .65em;\sn}\sn\sn#toc a{\sn cursor: pointer;\sn}\sn\snh1{\sn min-height: 1em;\sn}\sn\sn.slide h1{\sn min-height: 0;\sn}\sn\sn/* The '&gt;' selector is ignored by IE6 and earlier so the proper rules are given */\sn#pageFooter{\sn position: fixed;\sn bottom: 2px;\sn right: 2px;\sn width: 100%;\sn text-align: right;\sn}\sn\sn/* This is a hack to trick IE6 and earlier to put the navbar on the bottom of the page */\sn* html #pageFooter {\sn position: absolute;\sn width: 100%;\sn text-align: right;\sn right: auto; bottom: auto;\sn left: expression( ( -20 - pageFooter.offsetWidth + ( document.documentElement.clientWidth ? document.documentElement.clientWidth : document.body.clientWidth ) + ( ignoreMe2 = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft ) ) + 'px' );\sn top: expression( ( -10 - pageFooter.offsetHeight + ( document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight ) + ( ignoreMe = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ) ) + 'px' );\sn}\sn\sn\sn\sn/*}}}*/","slideShowStyles");\n//}}}
!Description\nThis plugin turns a TiddlyWiki tiddler into a simple slide show type display. Most features that are usually found in presentation software are available. It should work in a way that does not interfere with TiddlyWiki. When you close the slide show you get back to your good old TW. \n\nThis plugin has been tested in Firefox and Internet Explorer. Let me know if something seems broken.\n!Usage\nTo use this plugin you //must// be using TiddlyWiki 2.0. Some optional features (as the incremental display) require version 2.0.8 or higher. To install the plugin copy the tiddlers SlideShowPlugin, SlideShowPageTemplate and SlideShowViewTemplate to your TW, label the first one with the //systemConfig// tag, save the TW and refresh the browser.\n\nTo make a slide show simply drop {{{<<slideShow>>}}} at the beginning of a tiddler and use {{{--s--}}} to start each slide. \n\nIf you move your mouse over the bottom of the browser window you will see a few navigation buttons, a clock and a table of contents that shows up when you click the slide number.\n\nAny block of text marked as {{{{{Comment{For my eyes only!}}}}}} will not be displayed in the slide show.\n\nSee these and other features in this [[SlideShowExample]].\n!Incremental display\nA succession of overlays (or layers) can be defined in each slide by marking blocks of text with {{{{{Overlay1{...some text...}}}}}}, {{{{{Overlay2{...some text...}}}}}}, {{{{{Overlay3{...some text...}}}}}}, ...\n\nTo costumize the way overlays are shown you can redefine the following CSS classes\n*contentWrapper.slideShowMode .previousOverlay \n*contentWrapper.slideShowMode .currentOverlay \n*contentWrapper.slideShowMode .nextOverlay \nin a ~StyleSheet. The default style simply hides the next overlays and shows the current and the previous ones as normal text.\n!Slide show parameters\n*The slide show can be themed by providing a ~StyleSheet ({{{<<slideShow style:'MyStyleSheet'>>}}})\n*By default, there is a clock at bottom of the browser window that displays the current time. This clock can also show the presentation elapsed time with {{{<<slideShow clock:'+'>>}}} or a countdown clock with {{{<<slideShow clock:'-20'>>}}} (for 20 minutes). In these two cases, if you click on the clock display it will be restarted\n*The slide show can be set to loop ({{{<<slideShow repeat>>}}})\n*You can set it so each slide changes after X milliseconds ({{{<<slideShow slidePause:X>>}}}) (auto advance mode)\n*Use auto start mode to begin the slideshow the moment the tiddler is opened ({{{<<slideShow autostart>>>}}})\n*You can disable overlays with {{{<<slideShow noOverlays>>}}}\n*These parameters can be mixed and matched in any order: {{{<<slideShow slidePause:1000 repeat>>}}} is the same as {{{<<slideShow repeat slidePause:1000>>}}}\n!Slide show navigation\nYou can navigate through a slide show using the keyboard or the mouse. To quickly move to titled sections you can use the table of contents. \n!!Mouse navigation\nLeft (right) clicking on a slide jumps to the next (previous) overlay. To move to the beginning of the next or previous slide you must use the navigation bar at the bottom of the browser's window. If there are no overlays defined both operations are equivalent.\n!!Keyboard navigation\nThe following keys are defined:\n*Left arrow - previous overlay\n*Down arrow - previous slide\n*Right arrow - next overlay\n*Up arrow - next slide\n*Home - first slide\n*End - last slide\n*Escape - exit slide show\n*Spacebar - pause/resume slide show in auto advance mode\n!Revision history\n*1.5.1 10/11/2006\n**added SlideShowPageTemplate and SlideShowViewTemplate. This way, the plugin no longer requires a standard TW layout. Thanks to Andrew Lister for the idea.\n*1.5.0 18/09/2006\n**fixed restoring stylesheet on exit\n**changed (again!) the way how slides are separated (slide shows prepared for previous versions must be fixed)\n*1.4.0 20/04/2006\n**changed the way how slides are separated (slide shows prepared for previous versions must be fixed)\n**now works with content included with the {{{<<tiddler>>}}} macro\n**added incremental display (overlays)\n**improved documentation\n**assorted small fixes\n*1.3.1 10/03/2006\n**removed empty slide titles\n**fixed wrong numberSlides when slides have div's\n**fixed wrong time in Windows\n*1.3.0 26/02/2006\n**restore open tiddlers on exit\n**fixed problem with markup in headers (should work with NestedSlidersPlugin)\n**added slide comments (blocks of text in the tiddler that don't show up in the presentation)\n*1.2.1 28/01/2006\n**pause timed slideshow with spacebar\n**added clock with 3 different modes\n**fixed bugs with style and abbreviation options\n**general cleanup\n*1.2.0 07/01/2006\n**added a resume feature\n**added themes support\n*1.1.5 14/12/2005\n**added mouse support\n**cleaned up navbar generation\n*1.1.0 12/12/2005\n**added support for IE\n**added key listeners\n*1.0.0 11/12/2005\n**initial release\n!Todo\n*Time code is still very hackerish and unreliable.
<!--{{{-->\n<div class='title' macro='view title'></div>\n<div class='viewer' macro='view text wikified'></div>\n<!--}}}-->
<<slideShow>> - A simple slide show that keeps the TW style\n-s-\n<<tiddler "Porting Teapots from GLUT">>\n-s-\n<<tiddler "Analyzing the GLUT Version of Teapots">>\n-s-\n<<tiddler "Sequence of Events">>\n-s-\n<<tiddler "Basic Syzygy Version of Teapots">>\n-s-\n<<tiddler "More Interesting Syzygy Version of Teapots">>\n
Normally in OpenGL you specify your viewing situation (field of view, etc.) by loading a projection matrix. In Syzygy, however, this is done for you; you specify the parameters of your screen or screens in the [[Syzygy Database]] and Syzygy loads the appropriate projection matrix, taking your current head position into account.
/***\n\n''Inspired by [[TiddlyPom|http://www.warwick.ac.uk/~tuspam/tiddlypom.html]]''\n\n|Name|SplashScreenPlugin|\n|Created by|SaqImtiaz|\n|Location|http://tw.lewcid.org/#SplashScreenPlugin|\n|Version|0.21 |\n|Requires|~TW2.08+|\n!Description:\nProvides a simple splash screen that is visible while the TW is loading.\n\n!Installation\nCopy the source text of this tiddler to your TW in a new tiddler, tag it with systemConfig and save and reload. The SplashScreen will now be installed and will be visible the next time you reload your TW.\n\n!Customizing\nOnce the SplashScreen has been installed and you have reloaded your TW, the splash screen html will be present in the MarkupPreHead tiddler. You can edit it and customize to your needs.\n\n!History\n* 20-07-06 : version 0.21, modified to hide contentWrapper while SplashScreen is displayed.\n* 26-06-06 : version 0.2, first release\n\n!Code\n***/\n//{{{\nvar old_lewcid_splash_restart=restart;\n\nrestart = function()\n{ if (document.getElementById("SplashScreen"))\n document.getElementById("SplashScreen").style.display = "none";\n if (document.getElementById("contentWrapper"))\n document.getElementById("contentWrapper").style.display = "block";\n \n old_lewcid_splash_restart();\n \n if (splashScreenInstall)\n {if(config.options.chkAutoSave)\n {saveChanges();}\n displayMessage("TW SplashScreen has been installed, please save and refresh your TW.");\n }\n}\n\n\nvar oldText = store.getTiddlerText("MarkupPreHead");\nif (oldText.indexOf("SplashScreen")==-1)\n {var siteTitle = store.getTiddlerText("SiteTitle");\n var splasher='\sn\sn<style type="text/css">#contentWrapper {display:none;}</style><div id="SplashScreen" style="border: 3px solid #ccc; display: block; text-align: center; width: 320px; margin: 100px auto; padding: 50px; color:#000; font-size: 28px; font-family:Tahoma; background-color:#eee;"><b>'+siteTitle +'</b> is loading<blink> ...</blink><br><br><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>';\n if (! store.tiddlerExists("MarkupPreHead"))\n {var myTiddler = store.createTiddler("MarkupPreHead");}\n else\n {var myTiddler = store.getTiddler("MarkupPreHead");}\n myTiddler.set(myTiddler.title,oldText+splasher,config.options.txtUserName,null,null);\n store.setDirty(true);\n var splashScreenInstall = true;\n}\n//}}}
/***\nModifications (extensions) to the default TiddlyWiki stylesheet.\n***/\n/*{{{*/\n.wrappingClass {color: #666; background: #bbb;}\n/*}}}*/\n\n/*{{{*/\n#sidebar{ \n display:none;\n}\n#displayArea {\n margin: 1em 1em 0em 14em;\n}\n.smaller {\n font-size:80%\n} \n.small {\n font-size:90%;\n}\n\n#editor {\n height: 600px;\n}\n/*}}}*/\n
!Header 1\n!!Header 2\n!!!Header 3\n!!!!Header 4\n!!!!!Header 5\n
Sysygy maintains a database for storing useful bits of information in a way that your program can easily get at. Some of this information is used by the Syzygy libraries to configure your application; you can also use it for storing your own application parameters.\n\nIn [[Cluster Mode]], this database is maintained by the [[Syzygy Server]] and accessed from within your application by means of the [[arSZGClient]] object. In [[Standalone Mode]], the information is read in from a text file.\n\nInformation in the database is stored as text, although your program can access certain kinds of text strings as numbers. To take an obvious example, if the value of a certain parameter is set to "3", you can read it as an integer. Less obviously, if the parameter is set to "0/0/-.5" (numbers separated by forward slashes), then you can read it as either a list of floating-point numbers, or if it contains three numbers as an [[arVector3]] (a Syzygy class representing a 3-element vector).\n\nA given piece of information in the database is keyed by: (1) user; (2) computer name; (3) parameter group; and (4) parameter name.\n\nWhen you're running in [[Cluster Mode]], you set your Syzygy user name using the //dlogin// command. The user name then remains in effect for any Syzygy commands issued from that account on that computer until a new //dlogin// command is issued; any requests for database parameters automatically get your Syzygy user name tacked on. This means that in order to set a particular database parameter, you have to specify the computer it affects, the group name, and the parameter name. Group names are by convention all in upper case, and the groups used for configuration by the Syzygy libraries all have names beginning with "SZG_". In the documentation, we usually refer to database parameters as follows: "<group name>/<parameter name>". For example, if you came across the phrase "set the database parameter SZG_PYTHON/path on each computer to 'foo'", then "SZG_PYTHON" is the name of the parameter group and "path" is the parameter name.\n\nYou generally define all of your database parameters at once in a text file and load them into the database using the //dbatch// command.\n\nIn [[Standalone Mode]], the user and the computer name are both assumed to be "NULL". Your program will automatically look for a file named "szg_parameters.txt" containing your database parameters.\nWhen your program wants to access a database parameter, it generally shouldn't need to specify a user name or a computer name. It knows what user name it was launched under, and it knows what computer it is running on. You will almost always have it ask for a parameter using only the group name and the parameter name.\n\nI'll go into more detail about loading your database parameters for the different running modes in the appropriate sections; see [[Syzygy Database in Standalone Mode]] and [[Syzygy Database in Cluster Mode]].
A Syzygy input event consists of an event type, an index (an unsigned integer) and a value. Syzygy currently supports three types of input events:\n\n# ''Matrix'' events. The value is a 4x4 placement matrix, an {{{arMatrix4}}}. These typically represent positions and orientations (placements) reported by tracking devices; see [[Placement Matrices]]. Matrix #0 is normally interpreted as the placement of the user's head and matrix #1 is the placement of the primary interaction device.\n# ''Axis'' events. These are floating-point numbers (single-precision floats), one/event. They most commonly represent inputs from joysticks (horizontal and vertical joystick displacement, for example); in this case, by Syzygy convention they should be scaled to fit between +- 1.0. They are most commonly used for navigation, i.e. driving around in the virtual world. They can be used for other purposes, however; for example, we have used them to transmit human experimental subject reaction times from a timer to the application.\n# ''Button'' events. These are single integers, by convention having values of 0 or 1, usually representing the state (pressed/not pressed) of a button.\n\nWithin a given event type, different event sources are distinguished by the event index. Event indices are unsigned, i.e. start at zero and go up from there. The numbers and types of input events a given physical device provides (the device's signature) are specified by the device driver software.\n\nIf you are running your application in [[Standalone Mode]], then you only have one input device available: the [[Input Simulator]]. This device provides two matrix events, #0 and #1, specifying the placement of the user's head and of a tracked gamepad. It also provides two axis events. By default, it provides eight button events, #0-#7, but the number of buttons can be changed by setting a parameter in the [[Syzygy Database]].\n\nIn [[Cluster Mode]] you can have more than one input device. If so, your application will see them combined into a single virtual input device. Their event indices are piled on top of one another, with the order of piling determined by your cluster configuration. Suppose, for example, your system has three input devices: Device #0 is a tracker with three position/orientation sensors (i.e. three placement matrices), device #1 is a gamepad with a 2-D joystick (two axes) and six buttons, and device #2 is a simple pinch-style dataglove with a button on each finger (four buttons). As far as your application is concerned, it sees a single input device with three matrix events, two axis events, and ten button events; button events 0-5 represent to the gamepad buttons and 6-9 the dataglove buttons.
Type the text for 'New Tiddler'
# [[Introduction]]\n## [[What is Syzygy?]]\n# An Example: [[Porting Teapots from GLUT]]\n## [[Analyzing the GLUT Version of Teapots]]\n## [[Sequence of Events]] in a Syzygy Program.\n## [[Basic Syzygy Version of Teapots]]\n## [[More Interesting Syzygy Version of Teapots]]\n# [[Python Sequence Types]]\n# Syzgygy Concepts\n## [[Syzygy Database]]\n## [[Coordinate Systems and Units]]\n## [[Specifying Viewing Parameters]]\n## [[Math Classes]]\n### [[Vectors as Positions and Directions]]\n### [[Placement Matrices]]\n## [[Sequence of Events]] in a Syzygy Program.\n### [[Initialization and Cleanup]]\n### [[Per-Frame Event Loop]]\n### [[Events in Other Threads]]\n## [[Transferring Data from Master to Slaves]]\n### [[Brute Force: Transferring Pickled Objects]]\n### [[Transferring Arbitrary Sequences]]\n### [[Using an arMasterSlaveDict]]\n#### [[Making Classes Compatible with an arMasterSlaveDict]]\n#### [[Instantiating and Starting an arMasterSlaveDict]]\n#### [[Working with an arMasterSlaveDict]]\n#### [[Data Transfer with an arMasterSlaveDict]]\n#### [[Drawing Objects in an arMasterSlaveDict]]\n### [[Transferring Homogeneous Lists]]\n### [[Transferring Structs]]\n# [[Getting User Input]]\n## [[Syzygy Input Events]]\n## [[Polling the Framework's Input State]]\n# [[Programming Models]]\n## [[Recommended Programming Model]]\n## [["Predetermined Harmony" Programming Model]]\n# CopyRight
/***\n''Table of Contents Plugin for TiddlyWiki version 1.2.x and 2.0''\n^^author: Eric Shulman - ELS Design Studios\nsource: http://www.TiddlyTools.com/#TableOfContentsPlugin\nlicense: [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]^^\n\nWhen there are many tiddlers in a document, the standard 'tabbed list of tiddlers' in the right-hand sidebar can become very long, occupying a lot of page space and requiring a lot scrolling in order to locate and select a tiddler.\n\nThe TableOfContentsPlugin addresses this problem by replacing the standard tabbed list display with a single listbox/droplist control that uses a very small amount of page space, regardless of the number of tiddlers in the document.\n\n!!!!!Usage\n<<<\nTo view a tiddler, simply select (or double-click) its title from the listbox and it will automatically be displayed on the page. The listbox also includes special 'command' items that determine the order and type of tiddlers that are shown in the list:\n\n''[by title]'' displays all tiddlers in the document.\n''[by date/author/tags]'' displays indented sections, sorted accordingly, with headings (indicated by a '+') that can be expanded, one at a time, to view the list of tiddlers in that section.\n''[missing]'' displays tiddlers that have been referenced within the document but do not yet exist.\n''[orphans]'' displays tiddlers that do exist in the document but have not been referenced by a link from anywhere else within the document.\n''[system]'' displays special tiddlers that are used by TiddlyWiki to configure built-in features and add-on macros/extensions. The list includes all tiddlers tagged with <<tag systemTiddlers>> ("templates") or <<tag systemConfig>> (plug-in extensions), plus any 'shadow tiddlers' (built-in default systemTiddlers) that have not been over-ridden by custom tiddler definitions.\n\nThe current list ''display setting is indicated by an arrow (">")'' symbol to the left of command item. Selecting a command item causes the listbox to be reloaded with the appropriate contents and sort order.\n\nWhen you are viewing a list of tiddlers by date/author/tags, ''shift-clicking'' on a section heading or command item causes the listbox display to ''toggle between "show-one-section-at-a-time" and "expand-all-sections"'' display options, allowing you to quickly see all tiddler titles at once without having to view individual indented sections one at a time.\n\nThe ''size of the listbox can be adjusted'' so you can view more (or less) tiddler titles at one time. Select ''[-]'' to reduce the size by one line, ''[+]'' to increase the size by one line, or ''[=]'' to autosize the list to fit the current contents (toggles on/off). //Note: If the listbox is reduced to a single line, it displayed as a droplist instead of a fixed-sized listbox.// You can ''show/hide the entire listbox'' by selecting the "contents" label that appears above the listbox.\n<<<\n!!!!!Control Panel\n<<<\nBy default, tiddlers tagged with <<tag excludeLists>>, are omitted from the TableOfContents so that 'system' tiddlers (e.g. MainMenu, SiteTitle, StyleSheet, etc.) can be hidden, reducing "information overload" and making it easier to select relevant tiddlers when reading your document. However, when //editing// your document, including these hidden tiddlers in the list can be more helpful, so that changes to the menus, titles, styles, etc. can be more quickly accomplished.\n----\n''To include hidden tiddlers in the TableOfContents display, select the following option:''\n<<option chkTOCIncludeHidden>> include hidden tiddlers in TableOfContents(ignores <<tag excludeLists>> tag)\n^^note: this setting is ignored when ''<<option chkHttpReadOnly>> HideEditingFeatures when viewed over HTTP'' is enabled^^\n<<<\n!!!!!Parameters\n<<<\nThe macro accepts optional parameters to control various features and functions:\n* ''label:text''\nReplace the default text ("contents") that appears above the TableOfContents listbox. //Note: to include spaces in the label text, you must enclose the entire parameter in quotes// (e.g., {{{"label:my list of tiddlers"}}})\n* ''sort:fieldtype''\nSets the initial display order for items in the listbox. 'fieldtype' is one of the following pre-defined keywords: ''title'', ''modified'' //(date)//, ''modifier'' //(author)//, ''tags'', ''missing'', ''orphans'', or ''system''\n* ''date:format''\nControls the formatting of dates in TableOfContents display. 'format' is a text-substitution template containing one or more of the following special notations.\n** DDD - day of week in full (eg, "Monday")\n** DD - day of month, 0DD - adds leading zero\n** MMM - month in full (eg, "July")\n** MM - month number, 0MM - adds leading zero\n** YYYY - full year, YY - two digit year\n** hh - hours\n** mm - minutes\n** ss - seconds\n//Note: to include spaces in the formatting template, you must enclose the entire parameter in quotes// (e.g., {{{"date:DDD, DD/MM/YY"}}})\n* ''size:nnn''\nSets the initial number of lines to display in the listbox. If this parameter is omitted or "size:1" is specified, a single-line droplist is created. When a size > 1 is provided, a standard, fixed-size scrollable listbox is created. You can use "size:0" or "size:auto" to display a varible-height listbox that automatically adjusts to fit the current list contents without scrolling.\n* ''width:nnn[cm|px|em|%]''\nSets the width of the listbox control. Overrides the built-in CSS width declaration (=100%). Use standard CSS width units (cm=centimeters, px=pixels, em=M-space, %=proportional to containing area). You can also use a ".TOCList" custom CSS class definition to override the built-in CSS declarations for the listbox.\n* ''hidelist''\nHides the listbox when the TableOfContents is first displayed. Initially, only the listbox label and size controls will appear. Clicking on the listbox label text will alternately show/hide the listbox display. //Note: this setting does not affect the content of the listbox, only whether or not it is initially visible.//\n* ''prompt''\nSets the non-selectable prompt text that is displayed as the first line of the listbox //(note: this feature is not supported by the listbox control on all browsers)//. Let's you include a short text message (such as "select a tiddler"), even when displaying a compact single-line droplist.\n* ''padding:nnn[cm|px|em|%]''\nOverrides default listbox control padding. Sets the CSS padding style.\n* ''margin:nnn[cm|px|em|%]''\nOverrides default listbox control spacing. Sets the CSS margin style.\n* ''inline''\nNormally, the TableOfContents plugin is contained inside a {{{<div>}}} element. This setting causes the plugin to use a {{{<span>}}} instead, allowing for more flexible 'inline' placement when embedded within other content.\n<<<\n!!!!!Examples\n<<<\n<<tableOfContents "label:all tiddlers" sort:title width:40% size:1>>\n<<tableOfContents "label:by date" sort:modified size:1 width:40%>>\n<<tableOfContents "label:tagged tiddlers" sort:tags size:1 width:40%>>\n<<tableOfContents "label:system tiddlers" sort:system size:1 width:40%>>\n<<<\n!!!!!Installation\n<<<\nimport (or copy/paste) the following tiddlers into your document:\n''TableOfContentsPlugin'' (tagged with <<tag systemConfig>>)\n^^documentation and javascript for TableOfContents handling^^\n\ncreate/edit ''SideBarOptions'': (sidebar menu items) \n^^Add {{{<<tableOfContents>>}}} macro^^\n\n<<<\n!!!!!Revision History\n<<<\n''2006.05.21 [2.2.7]'' added onkeyup handling for enter key (=view selected tiddler, equivalent to double-click)\n''2006.02.14 [2.2.6]'' FF1501 fix: add 'var r' and 'var k' to unintended global variable declarations in refreshTOCList() and getTOCListFromButton(). Thanks for report from AndreasHoefler.\n''2006.02.04 [2.2.5]'' add 'var' to unintended global variable declarations to avoid FireFox 1.5.0.1 crash bug when assigning to globals\n''2005.12.21 [2.2.2]'' in onClickTOCList() and onDblClickTOCList(), prevent mouse events from 'bubbling' up to other handlers\n''2005.10.30 [2.2.1]'' in refreshTOCList(), fixed calculation of "showHidden" to check for 'readOnly' (i.e., "via HTTP") flag. Based on a report from LyallPearce\n''2005.10.30 [2.2.0]'' hide tiddlers tagged with 'excludeLists' (with option to override, i.e., "include hidden tiddlers")\n''2005.10.09 [2.1.0]'' combined documentation and code in a single tiddler\nadded click toggle for expand-all vs. show-one-branch\n''2005.08.07 [2.0.0]'' major re-write to not use static ID values for listbox controls, so that multiple macro instances can exist without corrupting each other or the DOM. Moved HTML and CSS definitions into plugin code instead of using separate tiddlers. Added macro parameters for label, sort, date, size, width, hidelist and showtabs\n''2005.08.03 [1.0.3]'' added "showtabs" optional parameter\n''2005.07.27 [1.0.2]'' core update 1.2.29: custom overlayStyleSheet() replaced with new core setStylesheet(). Added show/hide toggle (click on 'contents' link)\n''2005.07.23 [1.0.1]'' added parameter checks and corrected addNotification() usage\n''2005.07.20 [1.0.0]'' Initial Release\n<<<\n!!!!!Credits\n<<<\nThis feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]]\n<<<\n!!!!!Code\n***/\n//{{{\nversion.extensions.tableOfContents = {major: 2, minor: 2, revision: 7, date: new Date(2006,5,21)};\n//}}}\n\n// // 1.2.x compatibility\n//{{{\nif (!window.story) window.story=window;\nif (!store.getTiddler) store.getTiddler=function(title){return store.tiddlers[title]}\nif (!store.addTiddler) store.addTiddler=function(tiddler){store.tiddlers[tiddler.title]=tiddler}\nif (!store.deleteTiddler) store.deleteTiddler=function(title){delete store.tiddlers[title]}\n//}}}\n\n//{{{\n// define defaults for cookie-based option values\nif (config.options.txtTOCSortBy==undefined) config.options.txtTOCSortBy="modified";\nif (config.options.txtTOCListSize==undefined) config.options.txtTOCListSize=19;\nif (config.options.chkTOCShow==undefined) config.options.chkTOCShow=true;\nif (config.options.chkTOCIncludeHidden==undefined) config.options.chkTOCIncludeHidden=false;\n\nconfig.shadowTiddlers.AdvancedOptions += "\sn<<option chkTOCIncludeHidden>> include hidden tiddlers in TableOfContents";\n\n// define macro "tableOfContents" to render controls\nconfig.macros.tableOfContents = { label: "contents" };\nconfig.macros.tableOfContents.cmdMax=7;\n\nconfig.macros.tableOfContents.css = '\s\n.TOC { padding:0.5em 1em 0.5em 1em; }\s\n.TOC a { padding:0em 0.25em 0em 0.25em; color:inherit; }\s\n.TOCList { width: 100%; font-size:8pt; margin:0em; }\s\n';\n\nconfig.macros.tableOfContents.html = '\s\n<div style="text-align:right">\s\n <span style="float:left">\s\n <a href="JavaScript:;" id="TOCMenu" style="padding: 0em;"\s\n onclick="onClickTOCMenu(this)" title="show/hide table of contents">%label%</a>\s\n </span>\s\n <a href="JavaScript:;" id="TOCSmaller" style="display:inline"\s\n onclick="resizeTOC(this)" title="reduce list size">&#150;</a>\s\n <a href="JavaScript:;" id="TOCLarger"style="display:inline"\s\n onclick="resizeTOC(this)" title="increase list size">+</a>\s\n <a href="JavaScript:;" id="TOCMaximize"style="display:inline"\s\n onclick="resizeTOC(this)" title="maximize/restore list size">=</a>\s\n</div>\s\n';\n\nconfig.macros.tableOfContents.handler = function(place,macroName,params) { \n var parsedParams = new Array();\n parsedParams['label']=this.label;\n parsedParams['inline']=false;\n while (params.length>0) {\n if (params[0]=="label:none")\n parsedParams['label']="";\n else if (params[0].substr(0,6)=="label:")\n parsedParams['label']=params[0].substr(6);\n if (params[0].substr(0,7)=="prompt:")\n parsedParams['prompt']=params[0].substr(7);\n if (params[0].substr(0,8)=="padding:")\n parsedParams['padding']=params[0].substr(8);\n if (params[0].substr(0,7)=="margin:")\n parsedParams['margin']=params[0].substr(7);\n if (params[0].substr(0,5)=="sort:")\n parsedParams['sortby']=params[0].substr(5);\n if (params[0].substr(0,5)=="date:")\n parsedParams['date']=params[0].substr(5);\n if ((params[0]=="size:auto")||(params[0]=="size:0"))\n parsedParams['autosize']=true;\n else if (params[0] && (params[0].substr(0,5)=="size:"))\n parsedParams['requestedSize']=params[0].substr(5);\n if (params[0].substr(0,6)=="width:")\n parsedParams['width']=params[0].substr(6);\n if (params[0]=="hidelist")\n parsedParams['hidelist']=true;\n if (params[0]=="inline")\n parsedParams['inline']=true;\n params.shift(); \n }\n setStylesheet(config.macros.tableOfContents.css,"tableOfContents");\n var newTOC=createTiddlyElement(place,parsedParams['inline']?"span":"div",null,"TOC",null)\n if (parsedParams['margin']) { newTOC.style.margin=parsedParams['margin']; }\n if (parsedParams['padding']) { newTOC.style.padding=parsedParams['padding']; }\n if (parsedParams['label']!="") newTOC.innerHTML=config.macros.tableOfContents.html.replace(/%label%/,parsedParams['label']);\n var newTOCList=createTOCList(newTOC,parsedParams)\n refreshTOCList(newTOCList);\n store.addNotification(null,reloadTOCLists); // reload listbox after every tiddler change\n}\n\n// IE needs explicit global scoping for functions/vars called from browser events\nwindow.onChangeTOCList=onChangeTOCList;\nwindow.onClickTOCList=onClickTOCList;\nwindow.onDblClickTOCList=onDblClickTOCList;\nwindow.reloadTOCLists=reloadTOCLists;\nwindow.refreshTOCList=refreshTOCList;\nwindow.onClickTOCMenu=onClickTOCMenu;\nwindow.resizeTOC=resizeTOC;\n \nfunction createTOCList(place,params)\n{\n var theList = createTiddlyElement(place,"select",null,"TOCList",params['prompt'])\n theList.onchange=onChangeTOCList;\n theList.onclick=onClickTOCList;\n theList.ondblclick=onDblClickTOCList;\n theList.onkeyup=onKeyUpTOCList;\n theList.style.display=config.options.chkTOCShow ? "block" : "none" ;\n theList.sortBy=config.options.txtTOCSortBy;\n theList.dateFormat="DD MMM YYYY";\n theList.requestedSize=config.options.txtTOCListSize;\n theList.expandall=false;\n if (params['sortby'])\n { theList.sortBy=params['sortby']; theList.noSortCookie=true; }\n if (params['date'])\n { theList.dateFormat=params['date']; }\n if (params['autosize'])\n { theList.autosize=true; theList.noSizeCookie=true; }\n if (params['requestedSize'])\n { theList.requestedSize=params['requestedSize']; theList.noSizeCookie=true; }\n if (params['width'])\n { theList.style.width=params['width']; }\n if (params['hidelist'])\n { theList.style.display ="none" ; theList.noShowCookie=true; }\n if (params['expandall'])\n { theList.expandall=true; }\n return theList;\n}\n\nfunction onChangeTOCList()\n{\n var thisTiddler=this.options[this.selectedIndex].value;\n if ((this.size==1)&&(thisTiddler!='')) story.displayTiddler(null,thisTiddler,1);\n refreshTOCList(this);\n return false;\n}\n\nfunction onClickTOCList(e)\n{\n\n if (!e) var e = window.event;\n if (this.size==1)\n return; // don't toggle display for droplist\n if (e.shiftKey)\n { this.expandall=!this.expandall; refreshTOCList(this);}\n e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation();\n return true;\n}\n\nfunction onDblClickTOCList(e)\n{\n if (!e) var e = window.event;\n var thisTiddler=this.options[this.selectedIndex].value;\n if (thisTiddler!='') story.displayTiddler(null,thisTiddler,1);\n e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation();\n return false;\n}\n\nfunction onKeyUpTOCList(e)\n{\n if (!e) var e = window.event;\n if (e.keyCode!=13) return true;\n var thisTiddler=this.options[this.selectedIndex].value;\n if (thisTiddler!='') story.displayTiddler(null,thisTiddler,1);\n e.cancelBubble = true; if (e.stopPropagation) e.stopPropagation();\n return false;\n}\n\nfunction reloadTOCLists()\n{\n var all=document.all? document.all : document.getElementsByTagName("*");\n for (var i=0; i<all.length; i++)\n if (all[i].className=="TOCList")\n { all[i].selectedIndex=-1; refreshTOCList(all[i]); }\n}\n\nfunction refreshTOCList(theList)\n{\n // DEBUG var starttime=new Date();\n var selectedIndex = theList.selectedIndex;\n if (selectedIndex==-1) selectedIndex=0;\n var sortBy = theList.sortBy;\n var showHidden = config.options.chkTOCIncludeHidden\n && !(config.options.chkHttpReadOnly && readOnly);\n\n if (selectedIndex==0) sortBy=theList.sortBy; // "nnn tiddlers" heading\n if (selectedIndex==1) sortBy='title';\n if (selectedIndex==2) sortBy='modified';\n if (selectedIndex==3) sortBy='modifier';\n if (selectedIndex==4) sortBy='tags';\n if (selectedIndex==5) sortBy='missing';\n if (selectedIndex==6) sortBy='orphans';\n if (selectedIndex==7) sortBy='system';\n if (selectedIndex>config.macros.tableOfContents.cmdMax)\n {\n if (theList.options[theList.selectedIndex].value=='')\n expandTOC(theList);\n return;\n }\n theList.sortBy = sortBy;\n if (!theList.noSortCookie)\n { config.options.txtTOCSortBy=sortBy; saveOptionCookie("txtTOCSortBy"); }\n\n // get the list of tiddlers and filter out 'hidden' tiddlers (i.e., tagged with "excludeLists")\n var tiddlers = [];\n switch (sortBy) {\n case "missing":\n tiddlers = store.getMissingLinks();\n break;\n case "tags":\n tiddlers = store.getTags();\n break;\n case "orphans":\n var titles = store.getOrphans();\n for (var t = 0; t < titles.length; t++)\n if (showHidden || store.getTiddler(titles[t]).tags.find("excludeLists")==null)\n tiddlers.push(titles[t]);\n break;\n case "system":\n var temp = store.getTaggedTiddlers("systemTiddlers");\n for (var t = 0; t < temp.length; t++)\n if (showHidden || temp[t].tags.find("excludeLists")==null)\n tiddlers.pushUnique(temp[t].title,true);\n var temp = store.getTaggedTiddlers("systemConfig");\n for (var t = 0; t < temp.length; t++)\n if (showHidden || temp[t].tags.find("excludeLists")==null)\n tiddlers.pushUnique(temp[t].title,true);\n for (var t in config.shadowTiddlers) tiddlers.pushUnique(t,true);\n tiddlers.sort();\n break;\n default:\n var temp = store.getTiddlers(sortBy);\n for (var t = 0; t < temp.length; t++)\n if (showHidden || temp[t].tags.find("excludeLists")==null)\n tiddlers.push(temp[t]);\n }\n\n // clear current list contents\n while (theList.length > 0) { theList.options[0] = null; }\n theList.saved=null;\n\n // add heading and control items to list\n var i=0;\n var theHeading=tiddlers.length+' tiddlers:';\n if (sortBy=='missing') theHeading=tiddlers.length+' missing tiddlers:';\n if (sortBy=='orphans') theHeading=tiddlers.length+' orphaned tiddlers:';\n if (sortBy=='tags') theHeading=tiddlers.length+' tags:';\n if (sortBy=='system') theHeading=tiddlers.length+' system tiddlers:';\n var indent=String.fromCharCode(160)+String.fromCharCode(160);\n var sel=">";\n theList.options[i++]=new Option(theHeading,'',false,false);\n theList.options[i++]=new Option(((sortBy=="title")?sel:indent)+' [by title]','',false,false);\n theList.options[i++]=new Option(((sortBy=="modified")?sel:indent)+' [by date]','',false,false);\n theList.options[i++]=new Option(((sortBy=="modifier")?sel:indent)+' [by author]','',false,false);\n theList.options[i++]=new Option(((sortBy=="tags")?sel:indent)+' [by tags]','',false,false);\n theList.options[i++]=new Option(((sortBy=="missing")?sel:indent)+' [missing]','',false,false);\n theList.options[i++]=new Option(((sortBy=="orphans")?sel:indent)+' [orphans]','',false,false);\n theList.options[i++]=new Option(((sortBy=="system")?sel:indent)+' [system]','',false,false);\n // output the tiddler list\n switch(sortBy)\n {\n case "title":\n for (var t = 0; t < tiddlers.length; t++)\n theList.options[i++] = new Option(tiddlers[t].title,tiddlers[t].title,false,false);\n break;\n case "modified":\n // sort descending for newest date first\n tiddlers.sort(function (a,b) {if(a['modified'] == b['modified']) return(0); else return (a['modified'] > b['modified']) ? -1 : +1; });\n // continue with same logic as for 'modifier'...\n case "modifier":\n var lastSection = "";\n for (var t = 0; t < tiddlers.length; t++)\n {\n var tiddler = tiddlers[t];\n var theSection = "";\n if (sortBy=="modified") theSection = tiddler.modified.formatString(theList.dateFormat);\n if (sortBy=="modifier") theSection = tiddler.modifier;\n if (theSection != lastSection)\n {\n theList.options[i++] = new Option('+ '+theSection,"",false,false);\n lastSection = theSection;\n }\n theList.options[i++] = new Option(indent+indent+tiddler.title,tiddler.title,false,false);\n }\n expandTOC(theList);\n break;\n case "tags":\n // tagged tiddlers, by tag\n var tagcount=0;\n var lastTag = null;\n for (var t = 0; t < tiddlers.length; t++) // actually a list of tags, not tiddlers...\n {\n var theTag = tiddlers[t][0];\n var tagged = new Array();\n var temp = store.getTaggedTiddlers(theTag);\n for(var r=0; r<temp.length; r++)\n if (showHidden || temp[r].tags.find("excludeLists")==null)\n tagged.push(temp[r]);\n if (tagged.length)\n {\n tagcount++;\n theList.options[i++]= new\n Option('+ '+theTag+" ("+tagged.length+")","",false,false);\n for(var r=0; r<tagged.length; r++)\n theList.options[i++] = new\n Option(indent+indent+tagged[r].title,tagged[r].title,false,false);\n }\n }\n // count untagged tiddlers\n var temp = store.getTiddlers("title");\n var c=0; for (var r=0; r<temp.length;r++) if (!temp[r].tags.length) c++;\n // create 'pseudo-tag' listing untagged tiddlers (if any)\n if (c>0)\n {\n theList.options[i++] = new Option("+ untagged ("+c+")","",false,false);\n for (var r=0; r<temp.length;r++) if (!temp[r].tags.length)\n theList.options[i++] = new\n Option(indent+indent+temp[r].title,temp[r].title,false,false);\n }\n theList.options[0].text=tagcount+' tags:';\n expandTOC(theList);\n break;\n case "missing":\n case "orphans":\n case "system":\n for (var t = 0; t < tiddlers.length; t++)\n theList.options[i++] = new Option(tiddlers[t],tiddlers[t],false,false);\n break;\n }\n theList.selectedIndex=selectedIndex; // select current control item\n theList.size = (theList.autosize)?theList.options.length:theList.requestedSize;\n // DEBUG var endtime=new Date();\n // DEBUG alert("refreshTOC() elapsed time: "+(endtime-starttime)+" msec");\n}\n\n// show/hide branch of TOCList based on current selection\nfunction expandTOC(theList)\n{\n var selectedIndex = theList.selectedIndex;\n if (selectedIndex==-1) selectedIndex=0;\n var sortBy = theList.sortBy;\n\n // don't collapse/expand list for alpha-sorted "flatlist" TOC contents\n if ((sortBy=="title")||(sortBy=="missing")||(sortBy=="orphans")||(sortBy=="system"))\n return;\n // or list control items\n if ((selectedIndex>0)&&(selectedIndex<=config.macros.tableOfContents.cmdMax))\n return;\n\n var theText = theList.options[selectedIndex].text;\n var theValue = theList.options[selectedIndex].value;\n // save fully expanded list contents (if not already saved)\n if (!theList.saved)\n {\n theList.saved = new Array();\n for (var i=0; i < theList.length; i++)\n {\n opt = theList.options[i];\n theList.saved[i] = new Option(opt.text, opt.value, opt.defaultSelected, opt.selected);\n }\n }\n // clear current list contents\n while (theList.length > 0) { theList.options[0] = null; }\n\n // put back all items \n if (theList.expandall)\n {\n var i=0;\n for (var t=0; t<theList.saved.length; t++)\n {\n var opt=theList.saved[t];\n theList.options[i++] = new Option(opt.text,opt.value,opt.defaultSelected,opt.selected);\n if (opt.text==theText) selectedIndex=i-1;\n }\n theList.selectedIndex = selectedIndex;\n theList.size = (theList.autosize)?theList.options.length:theList.requestedSize;\n return;\n }\n\n // put back heading items until item text matches current selected heading\n var i=0;\n for (var t=0; t<theList.saved.length; t++)\n {\n var opt=theList.saved[t];\n if (opt.value=='')\n theList.options[i++] = new Option(opt.text,opt.value,opt.defaultSelected,opt.selected);\n if (opt.text==theText)\n break;\n }\n selectedIndex=i-1; // this is the NEW index of the current selected heading\n // put back items with value!='' until value==''\n for ( t++; t<theList.saved.length; t++)\n {\n var opt=theList.saved[t];\n if (opt.value!='')\n theList.options[i++] = new Option(opt.text,opt.value,opt.defaultSelected,opt.selected);\n if (opt.value=='')\n break;\n }\n // put back remaining items with value==''\n for ( ; t<theList.saved.length; t++)\n {\n var opt=theList.saved[t];\n if (opt.value=='')\n theList.options[i++] = new Option(opt.text,opt.value,opt.defaultSelected,opt.selected);\n }\n theList.selectedIndex = selectedIndex;\n theList.size = (theList.autosize)?theList.options.length:theList.requestedSize;\n}\n\n// these functions process clicks on the 'control links' that are displayed above the listbox\nfunction getTOCListFromButton(which)\n{\n var theList = null;\n switch (which.id)\n {\n case 'TOCMenu':\n var theSiblings = which.parentNode.parentNode.parentNode.childNodes;\n var thePlace=which.parentNode.parentNode.parentNode.parentNode.parentNode.id;\n break;\n case 'TOCSmaller':\n case 'TOCLarger':\n case 'TOCMaximize':\n var theSiblings = which.parentNode.parentNode.childNodes;\n var thePlace=which.parentNode.parentNode.parentNode.parentNode.id;\n break;\n }\n for (var k=0; k<theSiblings.length; k++)\n if (theSiblings[k].className=="TOCList") { theList=theSiblings[k]; break; }\n // DEBUG if (theList) alert('found '+theList.className+' for '+which.id+' button in '+thePlace);\n return theList;\n}\n\nfunction onClickTOCMenu(which)\n{\n var theList=getTOCListFromButton(which);\n if (!theList) return;\n var opening = theList.style.display=="none";\n if(config.options.chkAnimate)\n anim.startAnimating(new Slider(theList,opening,false,"none"));\n else\n theList.style.display = opening ? "block" : "none" ;\n if (!theList.noShowCookie)\n { config.options.chkTOCShow = opening; saveOptionCookie("chkTOCShow"); }\n return(false);\n}\n\nfunction resizeTOC(which)\n{\n var theList=getTOCListFromButton(which);\n if (!theList) return;\n\n var size = theList.size;\n if (theList.style.display=="none") // make sure list is visible\n if(config.options.chkAnimate)\n anim.startAnimating(new Slider(theList,true,false,"none"));\n else\n theList.style.display = "block" ;\n switch (which.id)\n {\n case 'TOCSmaller': // decrease current listbox size\n if (theList.autosize) { theList.autosize=false; size=config.options.txtTOCListSize; }\n if (size==1) break;\n size -= 1; // shrink by one line\n theList.requestedSize = theList.size = size;\n break;\n case 'TOCLarger': // increase current listbox size\n if (theList.autosize) { theList.autosize=false; size=config.options.txtTOCListSize; }\n if (size>=theList.options.length) break;\n size += 1; // grow by one line\n theList.requestedSize = theList.size = size;\n break;\n case 'TOCMaximize': // toggle autosize\n theList.autosize = (theList.size!=theList.options.length);\n theList.size = (theList.autosize)?theList.options.length:theList.requestedSize;\n break;\n }\n if (!theList.noSizeCookie && !theList.autosize)\n { config.options.txtTOCListSize=size; saveOptionCookie("txtTOCListSize"); }\n}\n//}}}\n
*sample:\n|!th1111111111|!th2222222222|\n|>| colspan |\n| rowspan |left|\n|~| right|\n|bgcolor(#a0ffa0):colored| center |\n|caption|c\n*another sample: see PeriodicTable.\nFor advanced effects, you can control the CSS style of a table by adding a row like this:\n{{{\n|cssClass|k\n}}}\n
/***\n|Name|TagglyListPlugin|\n|Created by|SimonBaird|\n|Location|http://simonbaird.com/mptw/#TagglyListPlugin|\n|Version|1.1.2 25-Apr-06|\n|Requires|See TagglyTagging|\n\n!History\n* 1.1.2 (25-Apr-2006) embedded TagglyTaggingStyles. No longer need separated tiddler for styles.\n* 1.1.1 (6-Mar-2006) fixed bug with refreshAllVisible closing tiddlers being edited. Thanks Luke Blanshard.\n\n***/\n\n/***\n!Setup and config\n***/\n//{{{\n\nversion.extensions.TagglyListPlugin = {\n major: 1, minor: 1, revision: 2,\n date: new Date(2006,4,25),\n source: "http://simonbaird.com/mptw/#TagglyListPlugin"\n};\n\nconfig.macros.tagglyList = {};\nconfig.macros.tagglyListByTag = {};\nconfig.macros.tagglyListControl = {};\nconfig.macros.tagglyListWithSort = {};\nconfig.macros.hideSomeTags = {};\n\n// change this to your preference\nconfig.macros.tagglyListWithSort.maxCols = 6;\n\nconfig.macros.tagglyList.label = "Tagged as %0:";\n\n// the default sort options. set these to your preference\nconfig.macros.tagglyListWithSort.defaults = {\n sortBy:"title", // title|created|modified\n sortOrder: "asc", // asc|desc\n hideState: "show", // show|hide\n groupState: "nogroup", // nogroup|group\n numCols: 1\n};\n\n// these tags will be ignored by the grouped view\nconfig.macros.tagglyListByTag.excludeTheseTags = [\n "systemConfig",\n "TiddlerTemplates"\n];\n\nconfig.macros.tagglyListControl.tags = {\n title:"sortByTitle", \n modified: "sortByModified", \n created: "sortByCreated",\n asc:"sortAsc", \n desc:"sortDesc",\n hide:"hideTagged", \n show:"showTagged",\n nogroup:"noGroupByTag",\n group:"groupByTag",\n cols1:"list1Cols",\n cols2:"list2Cols",\n cols3:"list3Cols",\n cols4:"list4Cols",\n cols5:"list5Cols",\n cols6:"list6Cols",\n cols7:"list7Cols",\n cols8:"list8Cols",\n cols9:"list9Cols" \n}\n\n// note: should match config.macros.tagglyListControl.tags\nconfig.macros.hideSomeTags.tagsToHide = [\n "sortByTitle",\n "sortByCreated",\n "sortByModified",\n "sortDesc",\n "sortAsc",\n "hideTagged",\n "showTagged",\n "noGroupByTag",\n "groupByTag",\n "list1Cols",\n "list2Cols",\n "list3Cols",\n "list4Cols",\n "list5Cols",\n "list6Cols",\n "list7Cols",\n "list8Cols",\n "list9Cols"\n];\n\n\n//}}}\n/***\n\n!Utils\n***/\n//{{{\n// from Eric\nfunction isTagged(title,tag) {\n var t=store.getTiddler(title); if (!t) return false;\n return (t.tags.find(tag)!=null);\n}\n\n// from Eric\nfunction toggleTag(title,tag) {\n var t=store.getTiddler(title); if (!t || !t.tags) return;\n if (t.tags.find(tag)==null) t.tags.push(tag);\n else t.tags.splice(t.tags.find(tag),1);\n}\n\nfunction addTag(title,tag) {\n var t=store.getTiddler(title); if (!t || !t.tags) return;\n t.tags.push(tag);\n}\n\nfunction removeTag(title,tag) {\n var t=store.getTiddler(title); if (!t || !t.tags) return;\n if (t.tags.find(tag)!=null) t.tags.splice(t.tags.find(tag),1);\n}\n\n// from Udo\nArray.prototype.indexOf = function(item) {\n for (var i = 0; i < this.length; i++) {\n if (this[i] == item) {\n return i;\n }\n }\n return -1;\n};\nArray.prototype.contains = function(item) {\n return (this.indexOf(item) >= 0);\n}\n//}}}\n/***\n\n!tagglyList\ndisplays a list of tagged tiddlers. \nparameters are sortField and sortOrder\n***/\n//{{{\n\n// not used at the moment...\nfunction sortedListOfOtherTags(tiddler,thisTag) {\n var list = tiddler.tags.concat(); // so we are working on a clone..\n for (var i=0;i<config.macros.hideSomeTags.tagsToHide.length;i++) {\n if (list.find(config.macros.hideSomeTags.tagsToHide[i]) != null)\n list.splice(list.find(config.macros.hideSomeTags.tagsToHide[i]),1); // remove hidden ones\n }\n for (var i=0;i<config.macros.tagglyListByTag.excludeTheseTags.length;i++) {\n if (list.find(config.macros.tagglyListByTag.excludeTheseTags[i]) != null)\n list.splice(list.find(config.macros.tagglyListByTag.excludeTheseTags[i]),1); // remove excluded ones\n }\n list.splice(list.find(thisTag),1); // remove thisTag\n return '[[' + list.sort().join("]] [[") + ']]';\n}\n\nfunction sortHelper(a,b) {\n if (a == b) return 0;\n else if (a < b) return -1;\n else return +1;\n}\n\nconfig.macros.tagglyListByTag.handler = function (place,macroName,params,wikifier,paramString,tiddler) {\n\n var sortBy = params[0] ? params[0] : "title"; \n var sortOrder = params[1] ? params[1] : "asc";\n\n var result = store.getTaggedTiddlers(tiddler.title,sortBy);\n\n if (sortOrder == "desc")\n result = result.reverse();\n\n var leftOvers = []\n for (var i=0;i<result.length;i++) {\n leftOvers.push(result[i].title);\n }\n\n var allTagsHolder = {};\n for (var i=0;i<result.length;i++) {\n for (var j=0;j<result[i].tags.length;j++) {\n\n if ( \n result[i].tags[j] != tiddler.title // not this tiddler\n && config.macros.hideSomeTags.tagsToHide.find(result[i].tags[j]) == null // not a hidden one\n && config.macros.tagglyListByTag.excludeTheseTags.find(result[i].tags[j]) == null // not excluded\n ) {\n if (!allTagsHolder[result[i].tags[j]])\n allTagsHolder[result[i].tags[j]] = "";\n allTagsHolder[result[i].tags[j]] += "**[["+result[i].title+"]]\sn";\n\n if (leftOvers.find(result[i].title) != null)\n leftOvers.splice(leftOvers.find(result[i].title),1); // remove from leftovers. at the end it will contain the leftovers...\n }\n }\n }\n\n\n var allTags = [];\n for (var t in allTagsHolder)\n allTags.push(t);\n\n allTags.sort(function(a,b) {\n var tidA = store.getTiddler(a);\n var tidB = store.getTiddler(b);\n if (sortBy == "title") return sortHelper(a,b);\n else if (!tidA && !tidB) return 0;\n else if (!tidA) return -1;\n else if (!tidB) return +1;\n else return sortHelper(tidA[sortBy],tidB[sortBy]);\n });\n\n var markup = "";\n\n if (sortOrder == "desc") {\n allTags.reverse();\n }\n else {\n // leftovers first...\n for (var i=0;i<leftOvers.length;i++)\n markup += "*[["+leftOvers[i]+"]]\sn";\n } \n\n for (var i=0;i<allTags.length;i++)\n markup += "*[["+allTags[i]+"]]\sn" + allTagsHolder[allTags[i]];\n\n if (sortOrder == "desc") {\n // leftovers last...\n for (var i=0;i<leftOvers.length;i++)\n markup += "*[["+leftOvers[i]+"]]\sn";\n }\n\n wikify(markup,place);\n}\n\nconfig.macros.tagglyList.handler = function (place,macroName,params,wikifier,paramString,tiddler) {\n var sortBy = params[0] ? params[0] : "title"; \n var sortOrder = params[1] ? params[1] : "asc";\n var numCols = params[2] ? params[2] : 1;\n\n var result = store.getTaggedTiddlers(tiddler.title,sortBy);\n if (sortOrder == "desc")\n result = result.reverse();\n\n var listSize = result.length;\n var colSize = listSize/numCols;\n var remainder = listSize % numCols;\n\n var upperColsize;\n var lowerColsize;\n if (colSize != Math.floor(colSize)) {\n // it's not an exact fit so..\n lowerColsize = Math.floor(colSize);\n upperColsize = Math.floor(colSize) + 1;\n }\n else {\n lowerColsize = colSize;\n upperColsize = colSize;\n }\n\n var markup = "";\n var c=0;\n\n var newTaggedTable = createTiddlyElement(place,"table");\n var newTaggedBody = createTiddlyElement(newTaggedTable,"tbody");\n var newTaggedTr = createTiddlyElement(newTaggedBody,"tr");\n\n for (var j=0;j<numCols;j++) {\n var foo = "";\n var thisSize;\n\n if (j<remainder)\n thisSize = upperColsize;\n else\n thisSize = lowerColsize;\n\n for (var i=0;i<thisSize;i++) \n foo += ( "*[[" + result[c++].title + "]]\sn"); // was using splitList.shift() but didn't work in IE;\n\n var newTd = createTiddlyElement(newTaggedTr,"td",null,"tagglyTagging");\n wikify(foo,newTd);\n\n }\n\n};\n\n/* snip for later.....\n //var groupBy = params[3] ? params[3] : "t.title.substr(0,1)";\n //var groupBy = params[3] ? params[3] : "sortedListOfOtherTags(t,tiddler.title)";\n //var groupBy = params[3] ? params[3] : "t.modified";\n var groupBy = null; // for now. groupBy here is working but disabled for now.\n\n var prevGroup = "";\n var thisGroup = "";\n\n if (groupBy) {\n result.sort(function(a,b) {\n var t = a; var aSortVal = eval(groupBy); var aSortVal2 = eval("t".sortBy);\n var t = b; var bSortVal = eval(groupBy); var bSortVal2 = eval("t".sortBy);\n var t = b; var bSortVal2 = eval(groupBy);\n return (aSortVal == bSortVal ?\n (aSortVal2 == bSortVal2 ? 0 : (aSortVal2 < bSortVal2 ? -1 : +1)) // yuck\n : (aSortVal < bSortVal ? -1 : +1));\n });\n }\n\n if (groupBy) {\n thisGroup = eval(groupBy);\n if (thisGroup != prevGroup)\n markup += "*[["+thisGroup+']]\sn';\n markup += "**[["+t.title+']]\sn';\n prevGroup = thisGroup;\n }\n\n\n\n*/\n\n\n//}}}\n\n/***\n\n!tagglyListControl\nUse to make the sort control buttons\n***/\n//{{{\n\nfunction getSortBy(title) {\n var tiddler = store.getTiddler(title);\n var defaultVal = config.macros.tagglyListWithSort.defaults.sortBy;\n if (!tiddler) return defaultVal;\n var usetags = config.macros.tagglyListControl.tags;\n if (tiddler.tags.contains(usetags["title"])) return "title";\n else if (tiddler.tags.contains(usetags["modified"])) return "modified";\n else if (tiddler.tags.contains(usetags["created"])) return "created";\n else return defaultVal;\n}\n\nfunction getSortOrder(title) {\n var tiddler = store.getTiddler(title);\n var defaultVal = config.macros.tagglyListWithSort.defaults.sortOrder;\n if (!tiddler) return defaultVal;\n var usetags = config.macros.tagglyListControl.tags;\n if (tiddler.tags.contains(usetags["asc"])) return "asc";\n else if (tiddler.tags.contains(usetags["desc"])) return "desc";\n else return defaultVal;\n}\n\nfunction getHideState(title) {\n var tiddler = store.getTiddler(title);\n var defaultVal = config.macros.tagglyListWithSort.defaults.hideState;\n if (!tiddler) return defaultVal;\n var usetags = config.macros.tagglyListControl.tags;\n if (tiddler.tags.contains(usetags["hide"])) return "hide";\n else if (tiddler.tags.contains(usetags["show"])) return "show";\n else return defaultVal;\n}\n\nfunction getGroupState(title) {\n var tiddler = store.getTiddler(title);\n var defaultVal = config.macros.tagglyListWithSort.defaults.groupState;\n if (!tiddler) return defaultVal;\n var usetags = config.macros.tagglyListControl.tags;\n if (tiddler.tags.contains(usetags["group"])) return "group";\n else if (tiddler.tags.contains(usetags["nogroup"])) return "nogroup";\n else return defaultVal;\n}\n\nfunction getNumCols(title) {\n var tiddler = store.getTiddler(title);\n var defaultVal = config.macros.tagglyListWithSort.defaults.numCols; // an int\n if (!tiddler) return defaultVal;\n var usetags = config.macros.tagglyListControl.tags;\n for (var i=1;i<=config.macros.tagglyListWithSort.maxCols;i++)\n if (tiddler.tags.contains(usetags["cols"+i])) return i;\n return defaultVal;\n}\n\n\nfunction getSortLabel(title,which) {\n // TODO. the strings here should be definable in config\n var by = getSortBy(title);\n var order = getSortOrder(title);\n var hide = getHideState(title);\n var group = getGroupState(title);\n if (which == "hide") return (hide == "show" ? "−" : "+"); // 0x25b8;\n else if (which == "group") return (group == "group" ? "normal" : "grouped");\n else if (which == "cols") return "cols±"; // &plusmn;\n else if (by == which) return which + (order == "asc" ? "↓" : "↑"); // &uarr; &darr;\n else return which;\n}\n\nfunction handleSortClick(title,which) {\n var currentSortBy = getSortBy(title);\n var currentSortOrder = getSortOrder(title);\n var currentHideState = getHideState(title);\n var currentGroupState = getGroupState(title);\n var currentNumCols = getNumCols(title);\n\n var tags = config.macros.tagglyListControl.tags;\n\n // if it doesn't exist, lets create it..\n if (!store.getTiddler(title))\n store.saveTiddler(title,title,"",config.options.txtUserName,new Date(),null);\n\n if (which == "hide") {\n // toggle hide state\n var newHideState = (currentHideState == "hide" ? "show" : "hide");\n removeTag(title,tags[currentHideState]);\n if (newHideState != config.macros.tagglyListWithSort.defaults.hideState)\n toggleTag(title,tags[newHideState]);\n }\n else if (which == "group") {\n // toggle hide state\n var newGroupState = (currentGroupState == "group" ? "nogroup" : "group");\n removeTag(title,tags[currentGroupState]);\n if (newGroupState != config.macros.tagglyListWithSort.defaults.groupState)\n toggleTag(title,tags[newGroupState]);\n }\n else if (which == "cols") {\n // toggle num cols\n var newNumCols = currentNumCols + 1; // confusing. currentNumCols is an int\n if (newNumCols > config.macros.tagglyListWithSort.maxCols || newNumCols > store.getTaggedTiddlers(title).length)\n newNumCols = 1;\n removeTag(title,tags["cols"+currentNumCols]);\n if (("cols"+newNumCols) != config.macros.tagglyListWithSort.defaults.groupState)\n toggleTag(title,tags["cols"+newNumCols]);\n }\n else if (currentSortBy == which) {\n // toggle sort order\n var newSortOrder = (currentSortOrder == "asc" ? "desc" : "asc");\n removeTag(title,tags[currentSortOrder]);\n if (newSortOrder != config.macros.tagglyListWithSort.defaults.sortOrder)\n toggleTag(title,tags[newSortOrder]);\n }\n else {\n // change sortBy only\n removeTag(title,tags["title"]);\n removeTag(title,tags["created"]);\n removeTag(title,tags["modified"]);\n\n if (which != config.macros.tagglyListWithSort.defaults.sortBy)\n toggleTag(title,tags[which]);\n }\n\n store.setDirty(true); // save is required now.\n story.refreshTiddler(title,false,true); // force=true\n}\n\nconfig.macros.tagglyListControl.handler = function (place,macroName,params,wikifier,paramString,tiddler) {\n var onclick = function(e) {\n if (!e) var e = window.event;\n handleSortClick(tiddler.title,params[0]);\n e.cancelBubble = true;\n if (e.stopPropagation) e.stopPropagation();\n return false;\n };\n createTiddlyButton(place,getSortLabel(tiddler.title,params[0]),"Click to change sort options",onclick,params[0]=="hide"?"hidebutton":"button");\n}\n//}}}\n/***\n\n!tagglyListWithSort\nput it all together..\n***/\n//{{{\nconfig.macros.tagglyListWithSort.handler = function (place,macroName,params,wikifier,paramString,tiddler) {\n if (tiddler && store.getTaggedTiddlers(tiddler.title).length > 0)\n // todo make this readable\n wikify(\n "<<tagglyListControl hide>>"+\n (getHideState(tiddler.title) != "hide" ? \n '<html><span class="tagglyLabel">'+config.macros.tagglyList.label.format([tiddler.title])+' </span></html>'+\n "<<tagglyListControl title>><<tagglyListControl modified>><<tagglyListControl created>><<tagglyListControl group>>"+(getGroupState(tiddler.title)=="group"?"":"<<tagglyListControl cols>>")+"\sn" + \n "<<tagglyList" + (getGroupState(tiddler.title)=="group"?"ByTag ":" ") + getSortBy(tiddler.title)+" "+getSortOrder(tiddler.title)+" "+getNumCols(tiddler.title)+">>" // hacky\n // + \sn----\sn" +\n //"<<tagglyList "+getSortBy(tiddler.title)+" "+getSortOrder(tiddler.title)+">>"\n : ""),\n place,null,tiddler);\n}\n\nconfig.macros.tagglyTagging = { handler: config.macros.tagglyListWithSort.handler };\n\n\n//}}}\n/***\n\n!hideSomeTags\nSo we don't see the sort tags.\n(note, they are still there when you edit. Will that be too annoying?\n***/\n//{{{\n\n// based on tags.handler\nconfig.macros.hideSomeTags.handler = function(place,macroName,params,wikifier,paramString,tiddler) {\n var theList = createTiddlyElement(place,"ul");\n if(params[0] && store.tiddlerExists[params[0]])\n tiddler = store.getTiddler(params[0]);\n var lingo = config.views.wikified.tag;\n var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;\n createTiddlyElement(theList,"li",null,"listTitle",prompt.format([tiddler.title]));\n for(var t=0; t<tiddler.tags.length; t++)\n if (!this.tagsToHide.contains(tiddler.tags[t])) // this is the only difference from tags.handler...\n createTagButton(createTiddlyElement(theList,"li"),tiddler.tags[t],tiddler.title);\n\n}\n\n//}}}\n/***\n\n!Refresh everything when we save a tiddler. So the tagged lists never get stale. Is this too slow???\n***/\n//{{{\n\nfunction refreshAllVisible() {\n story.forEachTiddler(function(title,element) {\n if (element.getAttribute("dirty") != "true") \n story.refreshTiddler(title,false,true);\n });\n}\n\nstory.saveTiddler_orig_mptw = story.saveTiddler;\nstory.saveTiddler = function(title,minorUpdate) {\n var result = this.saveTiddler_orig_mptw(title,minorUpdate);\n refreshAllVisible();\n return result;\n}\n\nstore.removeTiddler_orig_mptw = store.removeTiddler;\nstore.removeTiddler = function(title) {\n this.removeTiddler_orig_mptw(title);\n refreshAllVisible();\n}\n\nconfig.shadowTiddlers.TagglyTaggingStyles = "/***\snTo use, add {{{[[TagglyTaggingStyles]]}}} to your StyleSheet tiddler, or you can just paste the CSS in directly. See also ViewTemplate, EditTemplate and TagglyTagging.\sn***/\sn/*{{{*/\sn.tagglyTagged li.listTitle { display:none;}\sn.tagglyTagged li { display: inline; font-size:90%; }\sn.tagglyTagged ul { margin:0px; padding:0px; }\sn.tagglyTagging { padding-top:0.5em; }\sn.tagglyTagging li.listTitle { display:none;}\sn.tagglyTagging ul { margin-top:0px; padding-top:0.5em; padding-left:2em; margin-bottom:0px; padding-bottom:0px; }\sn\sn/* .tagglyTagging .tghide { display:inline; } */\sn\sn.tagglyTagging { vertical-align: top; margin:0px; padding:0px; }\sn.tagglyTagging table { margin:0px; padding:0px; }\sn\sn\sn.tagglyTagging .button { display:none; margin-left:3px; margin-right:3px; }\sn.tagglyTagging .button, .tagglyTagging .hidebutton { color:#aaa; font-size:90%; border:0px; padding-left:0.3em;padding-right:0.3em;}\sn.tagglyTagging .button:hover, .hidebutton:hover { background:#eee; color:#888; }\sn.selected .tagglyTagging .button { display:inline; }\sn\sn.tagglyTagging .hidebutton { color:white; } /* has to be there so it takes up space. tweak if you're not using a white tiddler bg */\sn.selected .tagglyTagging .hidebutton { color:#aaa }\sn\sn.tagglyLabel { color:#aaa; font-size:90%; }\sn\sn.tagglyTagging ul {padding-top:0px; padding-bottom:0.5em; margin-left:1em; }\sn.tagglyTagging ul ul {list-style-type:disc; margin-left:-1em;}\sn.tagglyTagging ul ul li {margin-left:0.5em; }\sn\sn.editLabel { font-size:90%; padding-top:0.5em; }\sn/*}}}*/\sn";\n\nrefreshStyles("TagglyTaggingStyles");\n\n\n//}}}\n\n// // <html>&#x25b8;&#x25be;&minus;&plusmn;</html>
You can do this right here and now. Maybe you should print this out so it's easy to follow while working on screen. I presume you have some familiarity with TiddlyWiki basics. If you are having trouble following this then try this [[excellent tutorial|http://www.blogjones.com/TiddlyWikiTutorial.html]] first.\n\n!Getting started: Create some content\n* First hit "close others" to clear up your screen\n* Click "new tiddler" from the main menu.\n* Name the tiddler "~MyStuff" by typing into the title box.\n* Add some contents to the contents box, for example "This is my stuff"\n* Click the Done button to save the new tiddler. (We will return to this one later).\n* Let's say you just returned from a meeting and want to enter a couple of notes about it\n** Click "new tiddler"\n** Enter tiddler title as "Meeting with Leo - 12 Oct"\n** Add some meeting notes in the content box\n** Don't click Done just yet...\n\n!Tagging your content\n* Before you save the "Meeting with Leo..." tiddler let's add some tags\n** It's a meeting so let's give it a tag called "Meetings". Type Meetings in the tags box.\n** The meeting was with Leo so let's give it a tag called Leo. Type Leo in the tags box.\n** Let's say the meeting was about budget planning for example. Type Budget in the tags box.\n** So your tags box should look like this: {{{Meetings Leo Budget}}}\n*Now save the tiddler by clicking the Done button.\n\n!Using the tags\n*No big deal so far, right? Now we start to get into TagglyTagging territory.\n*Notice the tags appear above the title. They are italicised to indicate they don't exist yet (just like any other tiddler that doesn't exist yet).\n*Click on the Leo tag. You will open an empty tiddler called Leo.\n**Give it some content like "Leo Runcible, ext 1234. Likes cats". Or whatever.\n**Leo's a person so give him a tag of "People" by typing People in the tags box.\n**Save it by clicking Done.\n\n!Lets pick up the pace\n*Go back to your "Meeting with Leo..." tiddler.\n** Click on Meetings. Give Meetings a tag of ~MyStuff and save it.\n** Click on Budget. Give Budget a tag of Projects. Type something about the Budget project in the tiddler contents if you want. Save it.\n** Now above the Budget title click on the Projects tag. Give Projects a tag of ~MyStuff. Save that.\n* Now find your ~MyStuff tiddler. Look at the tagged tiddler list which appears at the bottom of the tiddler. It should be a list containing Meetings, People and Projects.\n* Close a few tiddlers and try navigating to your meeting tiddler from ~MyStuff using the tagged tiddler lists.\n\nThat's a quick look at the basics of TagglyTagging. Hopefully by now you have some idea about what TagglyTagging can do. Play around with it a little more if you like. Then continue to...\n\n!The New Here Button\n* Click on People.\n* Do you know any other people?\n* If so click, "new here" in the People tiddler\n** Notice that the tags box already contains the tag People. (This is what the new here button does. It creates a new tiddler with the tag already filled in).\n** Type someone's name and some notes on that person. Click Done to Save.\n* Add a couple more people for fun\n** Notice what happens to the "tagged tiddler" list at the bottom of the People tiddler.\n\n!Changing the structure of your data\nLet's look at how easy it is to change your structure. Suppose you decide that you want to have different types of Projects: Ongoing, Current and Future\n* Go to your Budget tiddler. Edit it and change the Projects tag to Ongoing Projects. Save.\n* Click the Ongoing Projects tag. Give that a tag of "Projects".\n* That's it. You're done. Well actually you haven't added the Ongoing and Future Projects but you can see that's not hard. Maybe click "new here" at the Projects tiddler. Or maybe just tag a project as Future Projects and do it from the bottom up.\n\n!Another example\n* Suppose your original meeting had an action item for you. Type it into the meeting notes as a wiki word or {{{[[}}}Do Something{{{]]}}}.\n* Now click on it and give it a tag of Todo. Type any extra information about how you're going to do it when it's due by etc into the contents. Put a reminder in there if you have ReminderPlugin installed.\n* Now make sure you can get to your Todos by tagging Todo tiddler as MyStuff.\n* For convenience let's put a link to MyStuff in your MainMenu. This will put all your new organised information at your fingertips at all times.\n\n!Sort Controls and Columns\n* If you mouse over a tagged tiddler list you should some buttons. Try them out. Note that if you save it remembers your choice.\n\n!Wrapup\nHopefully you can see that TagglyTagging gives you a powerful way to organise and structure your information. Don't forget that you can still use conventional wiki links to navigate around your tiddlers. TagglyTagging just gives you another way to do cool stuff with your TiddlyWiki.\n\n\n~~This tutorial is a draft. Feedback is welcome. Please [[contact|Contact]] me with comments and suggestions.~~\n
Callbacks are functions that the programmer writes and registers with GLUT to be called when particular events occur. They have to have a specific //signature//, i.e. they have to accept a specific sequence of parameters specified by GLUT. We'll start with the {{{keyboard()}}} function:\n{{{\ndef keyboard(key, x, y):\n if key == chr(27):\n sys.exit(0)\n}}}\n{{{keyboard()}}} gets three parameters, the value of the key pressed and the current x and y position of the mouse. In this case, all it's doing is checking to see whether the user has pressed the ''Esc'' key (ascii 27) and exiting if so.\n\nHere's the {{{reshape()}}} callback:\n{{{\ndef reshape(w, h):\n glViewport(0, 0, w, h)\n glMatrixMode(GL_PROJECTION)\n glLoadIdentity()\n if (w <= h):\n glOrtho(0.0, 16.0, 0.0, 16.0*h/w, -10.0, 10.0)\n else:\n glOrtho(0.0, 16.0*w/h, 0.0, 16.0, -10.0, 10.0)\n glMatrixMode(GL_MODELVIEW)\n}}}\nIt gets handed the new width and height of the window in pixels and uses that information to update the OpenGL viewport. The end result is a bit complicated, best you run the program and resize the window in various ways to see what happens.\n\nFinally, the {{{display()}}} callback. This gets called whenever GLUT decides that the contents of the window need to be re-drawn, e.g. if the window gets resized or brought forward from behind another window:\n{{{\ndef display():\n glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)\n for d in teapotDataGlobal:\n renderTeapot(*d)\n glFlush()\n}}}\nIt clears the screen, then calls the {{{renderTeapot()}}} function repeatedly with different parameters to draw the array of teapots. {{{teapotDataGlobal}}} is a list of lists of numbers; each sub-list in the main list contains the parameters for one call to renderTeapot. In the {{{for}}} loop, each sub-list gets assigned to {{{d}}}. The {{{renderTeapot(*d)}}} is a very useful bit of Python magic to know; the {{{*}}} means "map the items in this sequence variable onto the corresponding function arguments", i.e. the first item in {{{d}}} gets assigned to the first function argument and so on. This trick works with the [[Python Sequence Types]]. The function then calls {{{glFlush()}}} to ensure that all the rendering commands get sent down the rendering pipe to the graphics card (you can read about the rendering pipeline in the OpenGLProgrammingGuide).\n\nAnd that's really all there is to it. The GLUT event loop putters along behind the scenes. If the user presses a key or resizes the window, or for some reason the window content (the scene) needs to be re-drawn, the appropriate callback gets called by the event loop manager.\n\nHave a look at the [[Sequence of Events]] in a Syzygy master/slave program. Then we'll go on to create the [[Basic Syzygy Version of Teapots]]
{{{\nimport sys\n\ntry:\n from OpenGL.GLUT import *\n from OpenGL.GL import *\n from OpenGL.GLU import *\nexcept:\n print '''\nERROR: PyOpenGL not installed properly. \n '''\n sys.exit()\n}}}\nThese tell us that this program probably depends only on the ''sys'' module (which contains system-related functions, e.g. {{{sys.exit()}}} is how you tell Python to quit) and on the ''GLUT'', ''GL'', and ''GLU'' sub-modules of the OpenGL module. There could be more import statements scattered throughout the program, but in this case (and often) there aren't. The two different forms of the import statement used determine how the objects contained in the different modules are referred to: ''import sys'' means that e.g. the {{{exit()}}} function of the module must be referred to in code as {{{sys.exit()}}}, whereas the way the OpenGL modules are imported means that the functions they contain can be referred to without the module name. The former is generally considered the better practice, but when you're going to be using a //lot// of functions from a module the latter form is OK. Why is {{{import sys}}} better than {{{from sys import *}}}? In the latter case, if you import two modules this way that both define a function with the same name you're going to run into problems. That doesn't matter if you do it the preferred way.\n\nThe author of this program has chosen to wrap the OpenGL import statements in an exception-handling block, so that if Python can't find one of the modules it will print an error message and exit. Since this is roughly what Python would have done anyway, I think this was basically a waste of typing. As a general rule, I don't recommend writing your own error-handling code unless you know what you're doing and you really need for a certain kind of error to be handled in a particular way. Python's default method for handling errors is fairly informative.\n\nNext: The [[Teapots.py __main__ Block]].
The simple way to describe the {{{__main__}}} block is that it's the main body of the program, like the {{{main()}}} function of a C or C++ program. Which it sort of is, but it's more than that.\n\nOne of the great features of Python is that the same file can be both a program and a module. Say you've got a program that does something nice. Suddenly your boss, client, professor, or other source of grief demands a special, modified version, but you still need the original. Instead of making a complete copy of the original, you can create a new program that imports the original one as a module and just overrides the functions that need to be changed.\n\nMost of the code in the file will be executed in either case, whether it's run or imported. The exception is what I've been referring to as the {{{__main__}}} block, which is only executed when the file is run as a program. It's actually just an {{{if}}} clause; here's the code from [[teapots.py|python/teapots/teapots.py]]:\n{{{\nif __name__ == '__main__':\n # Main Loop \n glutInit(sys.argv)\n glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH)\n glutInitWindowSize(500, 600)\n glutInitWindowPosition(50,50)\n glutCreateWindow(sys.argv[0])\n init()\n glutReshapeFunc(reshape)\n glutDisplayFunc(display)\n glutKeyboardFunc(keyboard)\n glutMainLoop()\n}}}\nThe idea is that within the scope defined by the file, there is always a global variable {{{__name__}}}. If the [[teapots.py|python/teapots/teapots.py]] file has been imported, then {{{__name__}}} is 'teapots' (the file name without the '.py'); if it's been run, {{{__name___}}} is {{{'__main__'}}}. So this is where you want to put code that's specifically related to running this file as a program (as opposed to definitions of functions and classes that you might want to re-use in a different program. If the current file is just intended to be a module and not an application, this is a good place to put testing code.\n\nSo what's going on here? Except for {{{init()}}}, which must be defined somewhere in this file, these are all GLUT functions. They initialize GLUT, specify that a single-buffered, full-color display with a depth buffer is desired (look these up in the OpenGLProgrammingGuide), specify the window's initial size and position (in pixels starting from the upper left corner of the screen), and create a window. Then the {{{init()}}} function is called; it does a lot of OpenGL initialization, so it had to be called //after// the window was created; generally speaking, if you issue OpenGL commands before there's a window (specifically, a //graphics context//) for them to operate on, your program will crash.\n\nThe last four function calls are all related to an important concept in both GLUT and Syzygy: the event loop. The basic idea is that behind the scenes, GLUT repeatedly checks to see whether certain kinds of events--originating either from the user or the operating system--have occurred. The programmer registers //callback// functions to handle particular types of events. They're called callbacks because the program doesn't generally call these functions; instead, they're called by GLUT's event loop in response to the appropriate events. In the lines\n{{{\n glutReshapeFunc(reshape)\n glutDisplayFunc(display)\n glutKeyboardFunc(keyboard)\n}}}\nthe programmer has previously defined three functions named {{{reshape()}}}, {{{display()}}}, and {{{keyboard}}}, which are to be called respectively when (1) the window gets resized, either by the user, the program, or the operating system; (2) the window content needs to be rendered, i.e. this is the function that is responsible for rendering the scene; and (3) the user presses a key on the keyboard. Registering these three functions as GLUT callbacks allows the programmer to respond appropriately when these events occur. The display callback is the only one that's absolutely required.\n\nFinally, the call:\n{{{\n glutMainLoop()\n}}}\nstarts the GLUT event loop and never returns, so this has to be the last function call in the program. From then on, GLUT just keeps up its internal event loop until told to quit, at which point the program exits.\n\nNext: [[Teapots.py Callbacks]]
default tiddlers\n+DefaultTiddlers\n----\ndocument info\nSiteTitle SiteSubtitle\n
/***\n''TiddlerGroupsPlugin for TiddlyWiki version 2.x''\n^^author: Eric Shulman - ELS Design Studios\nsource: http://www.TiddlyTools.com/#TiddlerGroupsPlugin\nlicense: [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]^^\n\nQuickly view a set of tiddlers by selecting a "tiddler group" from a droplist control. Groups are defined in TiddlerGroupsList. Each group definition is separated by a "----" (horizontal rule), and consists of two lines of text: a description (for display in the droplist) and a space-separated list of tiddler titles (use {{{[[...]]}}} around titles containing spaces). When a tiddler title is preceded by a "+" (e.g., "+DefaultTiddlers"), it is replaced with a list of tiddlers that are ''//linked//'' from that tiddler.\n\nThe droplist shows all tiddler groups that are currently defined in the TiddlerGroupsList, followed by several viewing options:\n* ''fold grouped tiddlers'' - if a CollapsedTemplate tiddler is defined in the document, you can automatically fold the tiddlers as they are displayed to make it easier to locate desired content without excessive scrolling. This is especially useful if the group contains many tiddlers or tiddlers with lengthy content.\n* ''close other tiddlers'' - reduce information clutter by ensuring that only the tiddlers from the selected group are displayed.\n* ''open new window'' - view the grouped tiddlers in a separate copy of the current document (not including unsaved tiddler changes)\nThe droplist also includes commands to quickly ''add a group'' to the list (using the currently displayed tiddlers), or ''edit the list'' so you can manually add/remove/re-order the group definitions. Note: these commands are automatically hidden when TW is operating in 'readOnly' mode (i.e., when viewed via http:)\n!!!!!Example\n<<<\nsyntax: {{{<<tiddlerGroups>>}}}\n<<tiddlerGroups>>\n<<<\n!!!!!Installation\n<<<\nimport (or copy/paste) the following tiddlers into your document:\n''TiddlerGroupsPlugin'' (tagged with <<tag systemConfig>>)\n<<<\n!!!!!Revisions\n<<<\n''2006.07.18 [1.0.0]'' fixed expansion of "+TiddlerName" references\n''2006.06.11 [0.9.5]'' cleanup options handling. added 'close others' option. added 'open new window' option. use cookies for tracking options\n''2006.06.09 [0.8.1]'' added 'fold group when viewed' toggle option\n''2006.06.09 [0.7.0]'' added notification for auto-refresh when list definitions are changed\n''2006.06.08 [0.5.0]'' converted to plugin/macro\n''2006.06.02 [0.0.0]'' started (as inline javascript)\n<<<\n!!!!!Credits\n>This feature was developed by EricShulman from [[ELS Design Studios|http://www.elsdesign.com]]\n!!!!!Code\n***/\n//{{{\nversion.extensions.tiddlerGroups= {major: 1, minor: 0, revision: 0, date: new Date(2006,7,18)};\nif (config.options.chkGroupFold==undefined) config.options.chkGroupFold=false;\nif (config.options.chkGroupClose==undefined) config.options.chkGroupClose=true;\nif (config.options.chkGroupOpen==undefined) config.options.chkGroupOpen=false;\nconfig.macros.tiddlerGroups= { \n\n groupsList: "TiddlerGroupsList",\n indent: "\sxa0\sxa0\sxa0\sxa0",\n selectprompt: "select a group to view",\n optionsprompt: "viewing options",\n foldcmd: "[%0] fold group",\n closecmd: "[%0] close others",\n opencmd: "[%0] new window",\n addcmd: "add a group...",\n addprompt: "Please enter a group name for these tiddlers:\sn%0",\n editcmd: "edit the list...",\n\n handler:\n function(place,macroName,params) {\n var s=createTiddlyElement(place,"select",null,"tiddlerGroupsList"); s.size=1; s.onchange=this.onchange;\n setStylesheet(".tiddlerGroupsList { width:100%; font-size:8pt; }", "tiddlerGroupsListStyles");\n store.addNotification(this.groupsList,this.refresh); this.refresh();\n },\n\n refresh:\n function() { // called directly, AND triggered by notification events\n var indent=config.macros.tiddlerGroups.indent;\n var all=document.all? document.all : document.getElementsByTagName("select");\n for (var i=0; i<all.length; i++) {\n if (all[i].className!="tiddlerGroupsList") continue;\n var here=all[i];\n while (here.length) here.options[0]=null; // remove current list items\n here.options[here.length]=new Option(config.macros.tiddlerGroups.selectprompt,"",true,true);\n var list=store.getTiddlerText(config.macros.tiddlerGroups.groupsList);\n if (list && list.trim().length) {\n var parts=list.split("\sn----\sn");\n for (var p=0; p<parts.length; p++) {\n var lines=parts[p].split("\sn");\n var label=lines.shift(); // 1st line=display text\n var value=lines.shift(); // 2nd line=item value\n here.options[here.length]=new Option(indent+label,value,false,false);\n }\n }\n here.options[here.length]=new Option(config.macros.tiddlerGroups.optionsprompt,"",false,false);\n if (!config.options.chkGroupOpen) {\n if (store.tiddlerExists("CollapsedTemplate")) {\n var msg=config.macros.tiddlerGroups.foldcmd.format([config.options.chkGroupFold?"x":"\sxa0\sxa0"]);\n here.options[here.length]=new Option(indent+msg,"_fold",false,false);\n }\n var msg=config.macros.tiddlerGroups.closecmd.format([config.options.chkGroupClose?"x":"\sxa0\sxa0"]);\n here.options[here.length]=new Option(indent+msg,"_close",false,false);\n }\n var msg=config.macros.tiddlerGroups.opencmd.format([config.options.chkGroupOpen?"x":"\sxa0\sxa0"]);\n here.options[here.length]=new Option(indent+msg,"_open",false,false);\n if (!readOnly) {\n here.options[here.length]=new Option(config.macros.tiddlerGroups.addcmd,"_add",false,false);\n here.options[here.length]=new Option(config.macros.tiddlerGroups.editcmd,"_edit",false,false);\n }\n }\n },\n\n onchange:\n function() { // this == droplist control\n var v=this.value; if (!v.length) return;\n switch (v) {\n case '_fold':\n config.options.chkGroupFold=!config.options.chkGroupFold;\n saveOptionCookie('chkGroupFold'); config.macros.tiddlerGroups.refresh();\n break;\n case '_close':\n config.options.chkGroupClose=!config.options.chkGroupClose;\n saveOptionCookie('chkGroupClose'); config.macros.tiddlerGroups.refresh();\n break;\n case '_open':\n config.options.chkGroupOpen=!config.options.chkGroupOpen;\n saveOptionCookie('chkGroupOpen'); config.macros.tiddlerGroups.refresh();\n break;\n case '_add':\n var list=[]; story.forEachTiddler(function(title,element){list.push(String.encodeTiddlyLink(title))}); list=list.join(" ");\n var d=prompt(config.macros.tiddlerGroups.addprompt.format([list.readBracketedList().join(', ')]),'');\n if (!d || !d.trim().length) return;\n var t=store.getTiddler(config.macros.tiddlerGroups.groupsList); t.set(null,t.text+"\sn----\sn"+d+"\sn"+list);\n store.setDirty(true); story.refreshTiddler(config.macros.tiddlerGroups.groupsList,null,true);\n config.macros.tiddlerGroups.refresh(); this.value=list; // reload list and select newly added item\n break;\n case '_edit':\n story.displayTiddler(null,config.macros.tiddlerGroups.groupsList,2);\n break;\n default:\n var list=v.readBracketedList();\n var expandedlist=[];\n for (var i=0; i<list.length; i++)\n if (list[i].substr(0,1)!='+') expandedlist.push(list[i]);\n else {\n var sublist=store.getTiddlerText(list[i].substr(1)).readBracketedList();\n for (var j=0; j<sublist.length; j++) expandedlist.push(sublist[j]);\n }\n if (config.options.chkGroupOpen) {\n for (var i=0; i<expandedlist.length; i++)\n list[i]=String.encodeTiddlyLink(expandedlist[i]);\n expandedlist=expandedlist.join(" ");\n var url=document.location.protocol+"//"+document.location.host+document.location.pathname+"#";\n url+=encodeURIComponent(list);\n window.open(url,"_blank");\n break;\n }\n if (config.options.chkGroupClose) story.closeAllTiddlers();\n var template=(config.options.chkGroupFold && store.tiddlerExists("CollapsedTemplate"))?"CollapsedTemplate":null;\n story.displayTiddlers(null,expandedlist,template);\n break;\n }\n }\n}\n//}}}
Here is a reference for the markup "language" used. See also [[Internal Macros]] (especially for the {{{<<br>>}}} macro that throws a new line).\n!Basic Formatting\n|!Format|!Markup|!Example|\n|Bold|{{{''Bold''}}} (2 single quotes)|''Bold''|\n|Highlight|{{{@@Highlight@@}}}|@@Highlight@@|\n|CSS Extended Highlights|{{{@@some css;Highlight@@}}}<<br>>For backwards compatibility, the following highlight syntax is also accepted:{{{@@bgcolor(#ff0000):color(#ffffff):red coloured@@}}}|@@background-color:#ff0000;color:#ffffff;red coloured@@<<br>><<slider AtEg ./atEg 'Extended example ...'>>|\n|Custom CSS Class|<html><code>{{wrappingClass{Text that is now accentuated}}}</code></html><<br>>By default, the text is placed in a <span>. To use a <div> instead, insert a line break before the text (after the single {)<<br>>In the CSS: {{{.wrappingClass {color: red;} }}}|Add .wrappingClass to StyleSheet|\n|Italic|{{{//Italic//}}}|//Italic//|\n|Monospaced text|<html><code>{{{ ... }}}</code></html>|{{{Monospaced text}}}|\n|Monospaced block multiline|Put <html><code>{{{</code></html> and <html><code>}}}</code></html> on their own lines|<html><pre>{{{<br/>Monospaced<br/>Multi-line<br/>Block<br/>}}}</pre></html>|\n|Strikethough|{{{==Strikethrough==}}}|==Strikethrough==|\n|Subscript|{{{~~Subscript~~}}}|Text~~Subscript~~|\n|Superscript|{{{^^Superscript^^}}}|Text^^Superscript^^|\n|Underlined|{{{__Underline__}}}(2 underscores)|__Underscored__|\n|Any HTML|{{{<html><span>any</span><br /><b>valid</b> <em>xhtml</em></html>}}}|<html><span>any</span><br /><b>valid</b> <em>xhtml</em></html>|\n!"Document" Structure\n|!Format|!Markup|!Example|\n|Headings|{{{!Heading 1}}}<<br>>{{{!!Heading 2}}}|<html><h1>Heading 1</h1><h2>Heading 2</h2><h3>Heading 3</h3><h4>Heading 4</h4><h5>Heading 5</h5></html>|\n|Any HTML|{{{<html><p>any valid xhtml</p></html>}}}|<html><p>any valid xhtml</p></html>|\n|Block quotes|{{{>Blockquote}}}<<br>>Can be nested using multiple >|<html><blockquote>Blockquote<blockquote>Nested Blockquote</blockquote></blockquote></html>|\n|Blockquotes - Multiline|<html><tt><<<</tt><br/>multi-line<br/>blockquote<br/><tt><<<</tt></html> |<html><blockquote>multi-line<br/>blockquote</blockquote></html>|\n|Horizontal Rule|{{{----}}} (4 dashes on a line of their own)|<html><hr></html>|\n|Images|{{{[img[favicon.ico]]}}}<<br>>Note that image files are always external to the TW file|[img[http://www.tiddlywiki.com/favicon.ico]]|\n|Inline Comments|{{{/% .... %/}}}<<br>>Text between the markers will not be shown in view mode|Not shown: /% Not Shown %/|\n|Links|Any WikiWord (creates a link to a tiddler whether it exists or not).<<br>>Note that a WikiWord has to start with a capital letter and have a further mix of upper and lower case.|PageTemplate|\n|~|{{{[[Manual Link]]}}} (Especially for tiddlers with spaces in their titles)|[[Table of Contents]]|\n|~|{{{[[Pretty Link|Some Crafty Link]]}}}<<br>>Note: Makes an external link if the target does not yet exist (e.g. {{{[[Not Yet A Tiddler|NotYetATiddler]]}}})|[[Pretty Link|MainMenu]]<<br>>[[Not Yet A Tiddler|NotYetATiddler]]|\n|~|Automatic external link {{{http://www.knightnet.org.uk}}}|http://www.knightnet.org.uk|\n|~|Pretty external link<<br>>{{{[[My Home Page|http://www.knightnet.org.uk]]}}}|[[My Home Page|http://www.knightnet.org.uk]]|\n|~|OS Folder link<<br>>Windows Share: {{{file://///server/share}}}<<br>>Windows Local: {{{file:///c:/folder/file}}}<<br>>Un*x Local File: {{{file://folder/file}}}<<br>>Relative File: {{{folder/file}}}||\n|List - Bulleted|{{{* List entry}}}|<html><ul><li>Bullet List</li></ul></html>|\n|List - Numbered|{{{# List entry}}}|<html><ol><li>Numbered List</li></ol></html>|\n|List - Nested|Both list types can be nested by using multiple * or #<<br>>Note that * and # must be the first character of the line as with all block format markup.<<br>>{{{* 1st level}}}<<br>>{{{** 2nd level}}}|<<tiddler ./nestedListEg>>|\n|Tables| {{{|}}} |Column Seperator |\n|~| {{{!}}} |Header (Row or Column) |\n|~| {{{>}}} |Column Span |\n|~| {{{~}}} |Row Span |\n|~| {{{|Left |}}} |Left Align |\n|~| {{{| Right|}}} |Right Align|\n|~| {{{| Center |}}} |Center Align |\n|~| {{{|Caption|c}}} |Table Caption (Can be at top or bottom)|\n|~| {{{|Header|h}}} |Marks the row as being a header row (will be wrapped with a {{{<thead>}}} and so all entries are automatically formatted as per {{{|!}}} cells)|\n|~| {{{|Footer|f}}} |Marks the row as being a footer row (will be wrapped with a {{{<tfoot>}}}, no special formatting is pre-defined for this but can be added to your own CSS)|\n|~| {{{|CSSclass|k}}} |Applies a CSS class to the table to allow additional formatting (NB: only works if no whitespace after the k)|\n|~|>|Note that the additional CSS classes evenRow and oddRow are automatically applied to all rows of the table. But evenRow is applied to the "first" row as per JavaScript convention (it is the zero'th row which is strangely considered even!).|\n|~|>|To have a table with no borders at all. Use {{{|noBorder|k}}} with the CSS (in your StyleSheet tiddler):<<br>>{{{ .noBorder,.noBorder td,.noBorder th,.noBorder tr{border:0 !important} }}}|\n|Table Sample|<<tiddler ./tblMarkup>>|<<tiddler ./tblShow>>|\n!Notes\nYou can use the custom CSS formatter in combination with headers and lists to allow new lines within the entry. e.g.:\n{{{\n#{{block{\nBullet 1\nSome text in the same bullet\n(Note that "block" can be anything, it is the formatters CSS class name)\n }}}\n# Bullet 2 \n}}}\n#{{block{\nBullet 1\nSome text in the same bullet\n}}}\n# Bullet 2 \n\n(Julian Knight, 2006-05-11)\n<part atEg hidden>\n{{{\nThis is before the indented text\n@@display:block;margin-left:2em;This text will be indented...\n...and can even span across several lines...\n\n...or even include blank lines.\n@@This is after the indented text \n}}}\nThis is before the indented text\n@@display:block;margin-left:2em;This text will be indented...\n...and can even span across several lines...\n\n...or even include blank lines.\n@@This is after the indented text \n</part>\n<part tblMarkup hidden>\n{{{\n|table caption at top|c\n|header|header|h\n|text, more text, more text|text, more text, more text|\n|!heading|!heading|\n|>|colspan=2|\n|rowspan|left align |\n|~| center |\n|bgcolor(green):green| right|\n|footer|footer|f\n|table caption at bottom|c\n}}}\n</part>\n<part tblShow hidden>\n|table caption at top|c\n|header|header|h\n|text, more text, more text|text, more text, more text|\n|!heading|!heading|\n|>|colspan=2|\n|rowspan|left align |\n|~| center |\n|bgcolor(green):green| right|\n|footer|footer|f\n|table caption at bottom|c\n</part>\n<part nestedListEg hidden>\n* 1st Level\n** 2nd Level\n</part>
The data-transfer method I'm going to describe here can transfer any [[Python Sequence Types]], with two restrictions:\n# The sequences may only contain Ints, Floats, Strings, or other sequences.\n# When un-packed on the slaves, all sequences, regardless of their type in the master, will be re-constituted as Tuples.\n\nAs an example, let's suppose that you wanted to transfer two Ints, an arMatrix4, and a numarray 4x4 array from master to slaves. To initialize a transfer, in your framework's onStart() call:\n{{{\n# 'self' is the framework object\nself.initSequenceTransfer( 'my_sequence' )\n}}}\nYou might have in your onPreExchange():\n{{{\nfirstInt = 1\nsecondInt = 42\n \n# ar_translationMatrix() returns an arMatrix4, a Syzygy 4x4 matrix class\n# that will have to be converted to a Tuple or List for this to work (it has a method for that).\n# The following call will yield this matrix:\n# 1 0 0 0\n# 0 1 0 5\n# 0 0 1 0\n# 0 0 0 1\ntheMatrix = ar_translationMatrix(0,5,0)\n \n# We'll pass a 2-D numarray array as well, just for yucks.\ntheArray = numarray.array([[1,2,3,4],[5,6,7,8],[9,0,1,2],[3,4,5,6]])\n \n# Pack them all into a tuple...\nmySequence = (firstInt, secondInt, theMatrix.toTuple(), theArray)\n# ...and stuff it into the framework.\nself.setSequence( 'my_sequence', mySequence )\n}}}\n\nIf at this point you were to print mySequence, it would display roughly like this:\n{{{\n(1, 42, (1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 5., 0., 1.), \s\n array([[1,2,3,4],[5,6,7,8],[9,0,1,2],[3,4,5,6]]))\n}}}\nIn your onPostExchange(), you would do the following:\n{{{\nif not self.getMaster():\n mySequence = self.getSequence( 'my_sequence' )\n}}}\nIf at this point you were to print mySequence, it would look roughly like this:\n{{{\n(1, 42, (1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 5., 0., 1.), \s\n ((1.,2.,3.,4.),(5.,6.,7.,8.),(9.,0.,1.,2.),(3.,4.,5.,6.)))\n}}}\n\nNote that all sequences of any type are re-constituted as tuples, but the degree of nesting or structure is preserved. In particular, the 2-D numarray array is now a 4-element tuple of 4-element tuples of Floats.\n \nYou would unpack it as follows:\n{{{\nif not self.getMaster():\n firstInt = mySequence[0] # mySequence[0] = 1\n secondInt = mySequence[1] # mySequence[2] = 42\n # mySequence[2] = (1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 5., 0., 1.)\n theMatrix = arMatrix4( mySequence[2] )\n # mySequence[3] = ((1.,2.,3.,4.),(5.,6.,7.,8.),(9.,0.,1.,2.),(3.,4.,5.,6.))\n theArray = numarray.array( mySequence[3] )\n}}}\nAs you can see, although this method is not as powerful as [[Brute Force: Transferring Pickled Objects]], it still allows you to transfer data with a fair amount of structure to it (you can nest sequences to arbitrary depths). Because it is more constrained, it is apt to be [considerably] more reliable and quite a lot faster than pickling and un-pickling. On the other hand, it takes more work: It requires that you code the state of any objects you want to transfer from master to slaves in terms of nested sequences of Ints, Floats, and Strings.\n
In order for an application to run properly in Cluster Mode, all instances of it need to share certain information. You have to tell the application what information needs to be shared between master and slaves.\n\nThere are a number of different ways to do this. The easiest method (but also the slowest and least reliable) is to use the Python pickle functionality to convert your data into a string in the master, transfer the string to the slaves, and then unpickle it (yielding a copy of the original, if everything works properly). In theory, this works with arbitrary Python objects. See [[Brute Force: Transferring Pickled Objects]].\n\nA bit more work is involved in transferring arbitrarily nested sequences of Floats, Ints, and Strings. This is my favorite method, and not just because I hand-coded it. This method allows you to construct a Python sequence data type that contains meaningful sub-sequences within it; for example, you could insert a placement matrix as a 16-element list or as a numarray array and then extract it as a single object once it's been transferred to the slaves. See [[Transferring Arbitrary Sequences]].\n\nThe {{{arMasterSlaveDict}}} container class provides an easy and very powerful way to synchronize a dictionary of objects from a variety of classes that vary in number over time.A python dictionary is an associative array, i.e. you insert and extract objects from it using a key, e.g. {{{a[0] = 12}}} or {{{return a['foo']}}}. An {{{arMasterSlaveDict}}} is a dictionary of objects that can be keyed using Ints, Floats, or Strings. All items placed within it must belong to a specific set of classes that you register with the {{{arMasterSlaveDict}}} and that implement a certain interface for getting and setting state; the {{{arMasterSlaveDict}}} itself has two synchronization methods, one to be called in the master and the other in the slaves. See [[Using an arMasterSlaveDict]].\n\nAt the most basic level, the framework also has methods for transferring single Strings and homogeneous lists of Ints or Floats (i.e. a list that is all Ints or all Floats). This may be the most reliable method (because the C++ code underlying it is the simplest) [Note: After a bit more experience, I'd have to say I've had better luck with the two preceding methods] , but it requires considerably more work as you have to extract the data from your objects in the master, pack it into flat lists, and then unpack those lists in the slaves. See [[Transferring Homogeneous Lists]].\n\nFinally, there is a simple and limited data-transfer method based on the standard ''struct'' module. This module uses a format string (similar to printf's) to construct a string representation of a sequence of simple data values from a limited range of types. See [[Transferring Structs]].
This is the original method for transferring data from master to slaves, and the one most closely related to the underlying C++ code. It allows you to transfer single Strings or flat Lists of Ints or Floats.\n\nAs an example, say we want to transfer an {{{arMatrix4}}} and three Ints. We initialize the transfers in the framework's {{{onStart()}}}:\n{{{\n# 'self' is the framework object\nself.initIntTransfer( 'int_transfer' )\nself.initFloatTransfer( 'matrix_transfer' )\n}}}\nWe pack the variables into lists and send them on their way in {{{onPreExchange()}}}:\n{{{\nmyInt1 = 1\nmyInt2 = 2\nmyInt3 = 3\nmyMatrix = ar_translationMatrix(0,5,0)\nself.setIntList( 'int_transfer', [myInt1, myInt2, myInt3] )\nself.setFloatList( 'matrix_transfer', myMatrix.toList() )\n}}}\nAnd we unpack and re-constitute them in {{{onPostExchange()}}}:\n{{{\nif not self.getMaster():\n myInt1 = self.getIntListItem( 'int_transfer', 0 )\n myInt2 = self.getIntListItem( 'int_transfer', 1 )\n myInt3 = self.getIntListItem( 'int_transfer', 2 )\n myMatrix = arMatrix4( self.getFloatList( 'matrix_transfer' ) )\n}}}\nThis method of transferring data from master to slaves is undoubtedly the most laborious, as each transfer field must be a flat, homogeneous list. However, it also involves the thinnest layer of binding code on top of the underlying C++ Syzygy libraries, so it may be the most reliable [Note: After a bit more experience, I'd have to say I've had better luck with the two preceding methods]. If your application requires very little data to be transferred, or if the other methods are all causing problems, you might give this one a try.
Finally, there is a simple and limited data-transfer method based on the standard 'struct' module. This module uses a format string (similar to printf's) to construct a string representation of a sequence of simple data values from a limited range of types; see the struct module documentation for details.\n\nAs an example, let's suppose that you wanted to transfer two Ints and a Float from master to slaves. To initialize a transfer, in your framework's {{{onStart()}}} call:\n{{{\n# 'self' is the framework object\nself.initStructTransfer( 'my_struct' )\n}}}\nYou might have in your onPreExchange():\n{{{\nfirstInt = 1\nsecondInt = 42\nfloater = 12.3\n \n# specify format string for above variables: Two longs, one double, one string.\n# If I'd wanted them converted as single-precision variables (C ints and floats),\n# I'd have specified 'iifs'. Python uses longs and doubles, so encoding as ints\n# and floats might or might not discard information.\nformatString = 'lld'\n\n# takes one of the Python Sequence Types with elements matching the format string\nself.setStruct( 'my_struct', formatString, (firstInt, secondInt, floater) ) \n}}}\nThe constructed string representation looks like this:\n{{{\n'\sx01\sx00\sx00\sx00*\sx00\sx00\sx00\sx9a\sx99\sx99\sx99\sx99\sx99(@'\n}}}\nIn your onPostExchange(), you would do the following:\n{{{\nif not self.getMaster():\n myStructTuple = self.getStruct( 'my_struct' )\n}}}\nmyStructTuple is a tuple, like this:\n{{{\n(1, 42, 12.3)\n}}}\nNote that Strings can be transferred using this method, but it's a bit of a pain; they have to be broken into separate characters. For example, suppose you also wanted to transfer:\n{{{\nmyName = 'Oz, the great and terrible'\n}}}\nYou might do this in onPreExchange():\n{{{\nformatString = 'lld' + ('s'*len(myName))\nmyStructList = [firstInt, secondInt, floater] + [item for item in myName]\n# Now:\n# formatString == 'lldssssssssssssssssssssssssss'\n# myStructList == [1, 42, 12.300000000000001, 'O', 'z', ',', ' ', 't', 'h', 'e', ' ', \s\n# 'g', 'r', 'e', 'a', 't', ' ', 'a', 'n', 'd', ' ', 't', 'e', 'r', 'r', 'i', 'b', 'l', 'e']\n\nself.setStruct( 'my_struct', formatString, myStructList )\n \n# Generated struct representation is\n# '\sx01\sx00\sx00\sx00*\sx00\sx00\sx00\sx9a\sx99\sx99\sx99\sx99\sx99(@Oz, the great and terrible'\n}}}\nIn onPostExchange():\n{{{\nif not self.getMaster():\n myStructTuple = self.getStruct( 'my_struct' )\n \n# myStructTuple == (1, 42, 12.300000000000001, 'O', 'z', ',', ' ', 't', 'h', 'e', \s\n# ' ', 'g', 'r', 'e', 'a', 't', ' ', 'a', 'n', 'd', ' ', 't', 'e', 'r', 'r', 'i', 'b', 'l', 'e')\n}}}\n(As far as unpacking the String goes, you're on your own).\n
An interactable is an object capable of receiving input events and being touched or grabbed. These are instantiated in subclasses of the <link>arPyInteractable class.\n\nAn effector is a representation of a mobile physical input device, such as a gamepad or a dataglove with a tracking sensor attached. An effector has a tip or hot spot that is used in determining which object to interact with and in updating a dragged object's position and orientation; a given effector can only interact with one object at at time. A given physical input device can be represented by more than one effector if either multiple-object interaction is desired or different parts of the device (i.e. different input events, different buttons for example) are meant for use with different sets of objects. Effectors are represented by the <link> arEffector class.\n\nAn interaction selector is an algorithm for determining which (if any) of a set of interactable objects a given effector will interact with. It's basically a distance measurement, the idea being that the effector will select the interactable that is closest to it by some measure. These are represented by subclasses of the arInteractionSelector class.\nYou touch an interactable object with an effector by bringing the effector close to the object in the sense defined by the effector's interaction selector, causing the object to be selected for interaction by the effector. As stated above, you can only touch one object at a time with a given effector.\n\nGrabbing an object with an effector allows you to use the effector to drag the object around. You must first be touching the object with the effector in order to grab it.\n\nA triggering condition or grab condition is a condition on an effector's input event stream that must be satisfied for a it to grab a touched object. Usually a particular button of the effector will have to be pressed. These are represented by the arGrabCondition class.\n\nA drag behavior is an algorithm by which movement of an effector changes a grabbed interactable object's placement matrix (position and orientation). An example would be maintaining a fixed relationship with an effector over time as the effector is waved around, as though stuck to it with glue. These are instantiated in subclasses of the arDragBehavior class.\n\nPutting it all together: There are currently two methods for handling the interaction of these various classes, and they both work pretty much the same way. They're both written in Python, so if you like you can copy and paste them out of PySZG.py and create your own versions. One is the global function ar_processInteractionList(), which takes an arEffector and a reference to a list of arPyInteractable sub-class instances, and the other is the ar_processInteraction() method of the arMasterSlaveDict class (see Using an arMasterSlaveDict), which just takes an arEffector. In either case, the idea is roughly as follows:\n\nScan through the set of objects to determine which if any of them the effector is touching. If you have already grabbed an object with the effector, this scan is bypassed.\nIf you are\n \n \n
The {{{arMasterSlaveDict}}} is a sub-class of {{{IterableUserDict}}}, which is basically a sub-classable dictionary defined and implemented in the standard Python ''UserDict'' module. {{{arMasterSlaveDict}}} has some additional restrictions: Keys must be Ints, Strings, or Floats, and it does not support the {{{popitem()}}} and {{{copy()}}} methods. It also has extra functionality for simplifying master/slave synchronization and user interaction. Provided that the classes of all objects to be stored have been properly registered with the {{{arMasterSlaveDict}}} and their {{{getState()}}} and {{{setState()}}} methods have been defined appropriately, any changes made to the contents of the {{{arMasterSlaveDict}}} (either in terms of insertions or deletions or in terms of state changes of objects within the dictionary) in the master will be automagically reflected in the slaves. Thus, this class is particularly useful for handling collections of objects whose numbers vary over time, e.g. because the user can create and destroy them.\n\n[[Making Classes Compatible with an arMasterSlaveDict]]\n[[Instantiating and Starting an arMasterSlaveDict]]\n[[Working with an arMasterSlaveDict]]\n[[Data Transfer with an arMasterSlaveDict]]\n[[Drawing Objects in an arMasterSlaveDict]]\n
Three-element vectors are used to represent positions and directions in 3-space. In Syzygy, these are embodied in the [[arVector3]] class. It contains three single-precision floating-point numbers; note that Python Floats are double-precision. An [[arVector3]] can be constructed from any sequence of Ints and/or Floats, e.g.:\n{{{\nmyPos = arVector3( [0.1,0,12.3] )\n}}}\nThey can be constructed using three Floats, but in this case they do all have to be Floats:\n{{{\nmyPos = arVector3( 0.1, 0., 12.3 )\n}}}\nSee the [[arVector3]] and [[Math Functions]] reference sections for more information. I'll just mention one method here:\n{{{\nmyDir = myPos.normalize()\n}}}\nreturns a normalized version of the vector (same direction, length = 1), commonly used to represent a direction.
<!---\n| Name:|~TagglyTaggingViewTemplate |\n| Version:|1.2 (16-Jan-2006)|\n| Source:|http://simonbaird.com/mptw/#TagglyTaggingViewTemplate|\n| Purpose:|See TagglyTagging for more info|\n| Requires:|You need the CSS in TagglyTaggingStyles to make it look right|\n!History\n* 16-Jan-06, version 1.2, added tagglyListWithSort\n* 12-Jan-06, version 1.1, first version\n!Notes\nRemove the miniTag if you don't like it or you don't use QuickOpenTagPlugin\n--->\n<!--{{{-->\n<div class="toolbar" macro="toolbar -closeTiddler closeOthers +editTiddler permalink references jump newHere fullscreen"></div>\n<div class="tagglyTagged" macro="hideSomeTags"></div>\n<div><span class="title" macro="view title"></span><span class="miniTag" macro="miniTag"></span></div>\n<div class='subtitle'>Updated <span macro='view modified date [[DD-MMM-YY]]'></span></div>\n<div class="viewer" macro="view text wikified"></div>\n<div class="tagglyTagging" macro="tagglyTagging"></div>\n<!--}}}-->\n
Syzygy is a programming toolkit for writing virtual reality or other graphical applications.\n\nA virtual reality is a spatially-displayed set of data that the user can move around in and interact with. For best results, it requires a device to track the position and orientation of the user's head (so that the display can be updated correctly for the user's current viewpoint) and of a hand-held input device; Syzygy supports a variety of tracking devices (see [[Supported Trackers]]). A virtual world will also make a stronger impression if the display is stereoscopic, providing distinct views for the user's left and right eyes and yielding a strong percept of 3-D; Syzygy supports a number of methods of presenting virtual worlds stereoscopically (see [[Viewing Modes]]).\n\nSyzygy applications can run on a single computer ([[Standalone Mode]]), but Syzygy is especially designed for the creation of applications to run on clusters of networked computers ([[Cluster Mode]]). Programs or instances of the same program running on different computers in the cluster communicate with one another and share data. The cluster as a whole effectively becomes a single computer with multiple screens and input devices. The rendering of your virtual world is synchronized on all of the computers (to within a few milliseconds), so different screens in the cluster give you different simultaneous views on the virtual world. Writing a program to run on multiple computers requires a small amount of extra work.\n\nSyzygy runs on a variety of operating systems, e.g. Windows, Linux, MacOS X, and Irix (see [[Supported Computer Systems]] for details). A cluster can be heterogeneous, i.e. you can mix different operating systems. Installation varies somewhat between operating systems.\n\nThe Syzygy libraries themselves are written in C++. Syzygy applications can be written in either of two programming languages: C++ or [[Python|www.python.org]]. This book will focus on writing Syzygy applications in Python, which is easier to learn and to work with. Applications written in C++ run more quickly, but for many applications this is unimportant for two reasons: First, unless your virtual world is fairly complex, most of the computing is probably happening on the graphics card, so the language the program is written in doesn't matter much; Second, the Python code is generally making calls to the C++ libraries to do most of the work anyway.\n\nSyzygy can be used to write two different kinds of programs, using the provided application frameworks. An application framework is an object (in the object-oriented programming sense) that you build an application around, and Syzygy has two:\n\n* The Distributed Scene Graph Framework is easier to use, but much more limited. In this framework, a single copy of your program runs on one computer in the cluster. Your virtual world is contained within a special-purpose database, and you modify the world by sending commands to the database. Changes to the database are mirrored to a special-purpose rendering program (called szgrender) running on each computer of the cluster; this program draws the database for you. The main advantage of this approach is that most of the work of drawing your world and keeping the various copies of it on the various computers synchronized with one another is handled for you. The disadvantage is that your virtual world can only contain the types of objects that the database knows about, and you have little control over how they are rendered; you can't make them partially transparent, for example.\n\n*The [[Master/Slave Framework]] is a bit harder to use, but much more powerful. You manually specify how your virtual world is to be rendered, using the [[OpenGL|www.opengl.org]] graphics library. A copy of your program runs on each computer. One copy is designated as the master; the rest are slaves. You can specify that certain processing only occurs in the master copy. Data that you specify are then copied from the master to each of the slave copies. You have another opportunity to perform additional processing, either in all instances of the program or only in the slaves, and then your virtual world is rendered on each computer.\n\nThis book will focus primarily on the more flexible [[Master/Slave Framework]].
This discussion presumes that you are working according to the [[Recommended Programming Model]], in which most of your computation is done in the master instance of the application in the {{{onPreExchange()}}} method. It also presumes that the {{{arMasterSlaveDict}}} has been assigned to a property of your framework object ({{{self.dict}}}) in it's {{{__init__()}}} method.\n\nYou can interact with an {{{arMasterSlaveDict}}} in many of the same ways as a dictionary. You can insert or delete items by key (but the key must be Int, Float, or String):\n{{{\nself.dict[0] = Foo()\nself.dict['MainFoo'] = Foo()\ndel self.dict['MainFoo']\n}}}\nAs before, I'm using {{{Foo}}} to represent a class, {{{Foo()}}} creates an instance. You can also treat the {{{arMasterSlaveDict}}} a bit more like a list using the {{{push()}}} method:\n{{{\nself.dict.push( Foo() )\n}}}\nWhen you call the {{{push()}}} method, the item is actually being stored using a hidden integer key that is incremented after each usage. If the current value is already in use, it goes on to the next one.\n\nNote that in all of these cases, a {{{TypeError}}} exception will be raised if you did not provide an entry for class {{{Foo}}} in the class-information list that you passed to the {{{arMasterSlaveDict}}} constructor (see [[Instantiating and Starting an arMasterSlaveDict]]).\n\nYou can also delete an item by value, as shown here:\n{{{\nmainFoo = self.dict['MainFoo']\n...do stuff...\nself.dict.delValue( mainFoo )\n}}}\nBecause the objects in the {{{arMasterSlaveDict}}} are all instances of classes (as opposed to built-in types), 'values' extracted are all actually references to the objects in the container. Keep in mind that in Python, an object doesn't actually get deleted until all references to it are gone.\n\nYou can clear an arMasterSlaveDict completely using:\n{{{\nself.dict.clear()\n}}}\nYou can get a lists of keys, values, or (key, value) tuples:\n{{{\nk = self.dict.keys() # list of keys\nv = self.dict.values() # list of values\nitems = self.dict.items() # list of (key,value) tuples\n}}}\nYou can also iterate over the items in the dictionary:\n{{{\nfor k in self.data.iterkeys():\n ...do stuff...\n\nfor v in self.data.itervalues():\n ...do stuff...\n \nfor i in self.data.iteritems():\n ...do stuff...\n}}}\nYou could of course loop over e.g. the list returned by the {{{keys()}}} method. The advantage of using {{{iterkeys()}}} instead is that it doesn't actually construct a list to hold the keys and is therefore a bit more efficient, particulary with large dictionaries. By the way, in neither case should you try to add objects to or remove them from the {{{arMasterSlaveDict}}} in such a loop. Changing the state of individual objects in the dictionary is OK, however.\n\nFinally, if you are using an {{{arEffector}}} to handle user interaction with the objects in your dictionary, the class' {{{processInteraction()}}} method takes the place of the global {{{ar_processInteractionList()}}} method (see [[User Interaction Concepts and Classes]]):\n{{{\nself.dict.processInteraction( effector )\n}}}
The following are things that either must or may need to be executed as the program starts up.\n\nThe following is a piece of boiler-plate code that should go in the {{{__main__}}} block of every Syzygy Python program:\n{{{\nif __name__ == '__main__':\n framework = SkeletonFramework()\n if not framework.init(sys.argv):\n raise RuntimeError,'Unable to init framework.'\n print 'Framework inited.'\n # Never returns unless something goes wrong\n if not framework.start():\n raise RuntimeError,'Unable to start framework.'\n}}}\n\n!! Methods\n{{{\narPyMasterSlaveFramework.init( sys.argv )\n}}}\nWhat it says. 'sys.argv' is the standard Python way of accessing the program's command-line parameters. Returns True or False to indicate success or failure. Should be called in {{{__main__}}} block.\n{{{\narPyMasterSlaveFramework.start()\n}}}\nStarts any services (input, sound) and then starts the event-processing loop. The {{{onStart()}}} callback method is called from in here in order for the user to do any application-global initialization that requires access to the Syzygy database or that varies between masters and slaves. The {{{onWindowStartGL()}}} callback method is called once for each window that is created in order to do OpenGL initialization. Unlike GLUT, the number of windows and their arrangement is not specified in code, but rather by parameters stored in the [[Syzygy Database]]. See the Graphics Configuration chapter of the Syzygy documentation for details. \n\n{{{arPyMasterSlaveFramework.start()}}} should be called at the end of the {{{__main__}}} block, as it does not return.\n\nThe following are arPyMasterSlaveFramework methods that you may want to call in your {{{onStart()}}} callback method:\n{{{\narPyMasterSlaveFramework.setUnitConversion( Float appUnitsPerFoot )\n}}}\nTell the framework what units you are using. Syzygy database parameters related to e.g. screen position and size are specified in feet. If your application uses different units, you need to tell the framework what scale factor to use to convert from one to the other. The required scale factor is the number to multiply by to convert from feet to your units. For example, if you were using centimeters, then your scale factor would be 12 inches/foot * 2.54 cm/inch = 30.48 cm/foot.\n{{{\narPyMasterSlaveFramework.setClipPlanes( Float near, Float far )\n}}}\nSet the distances to the near and far OpenGL clipping distances, in the application's units. Your virtual world will be cut off (i.e. bits of it will not be rendered) outside of these clipping distances.\n{{{\narPyMasterSlaveFramework.setDataBundlePath( String bundlePathName, String bundleSubDirectory )\n}}}\nIn Cluster Mode, tell any separate rendering programs where to look for data.\n\nThis requires some explanation. Typically it's most convenient to bundle a Python Syzygy program with all of its textures, sounds, etc. in the same directory as the application. However, when a program is run in Cluster Mode, a completely separate program (SoundRender) is responsible for playing sounds. If it is a Distributed Scene Graph Framework application, then the rendering program szgrender needs to know where to find your program's texture files. This problem is further complicated by the fact that we can't assume that these other programs are running on the same computer or even under the same operating system as the main application. This method is how the program communicates this information.\n\nThe first parameter is the name of a Syzygy Database parameter group, usually "SZG_PYTHON" for python programs. As part of your cluster configuration, you will have specified a value in the Syzygy database for the variable SZG_PYTHON/path for each computer in the cluster. This path should include a parent directory to the one your program is in. The second parameter, 'bundleSubDirectory', is the sub-directory that your program and its data reside in.\n\nSuppose, for example, that all the Syzygy Python programs on your computer reside in the Windows directory C:\spyszg. The current application lives in the subdirectory myVR. You would probably set SZG_PYTHON/path on each computer to G:\spyszg and call this method as follows:\n{{{\nself.setDataBundlePath( 'SZG_PYTHON', 'myVR' )\n}}}\nAnd what if, say, SoundRender is running on a Mac? Well, either the Mac has to have access to the same directory, or you have to have a copy of the files in an analogous directory. Let's suppose that the Mac somehow has access to the same pyszg directory, and it's mounted as /home/public/pyszg. Then you would set the Syzygy database variable SZG_PYTHON/path on that computer to /home/public/pyszg, and everything should work.\n
! Initialization and Cleanup\n\n! Callback Methods\n\nonStart( self, SZGClient )\nonSlaveConnected( self, numConnected )\nonPreExchange( self )\nonPostExchange( self )\nonWindowInit( self )\nonReshape( self, width, height )\nonOverlay( self )\nonDraw( self )\nonPlay( self )\nonKey( self, key, mouseX, mouseY )\nonUserMessage( self, messageBody )\nonExit( self )\n\n! User Input\n\ngetDataPath()\ngetButton( Int index )\ngetAxis( Int index )\ngetMatrix( Int index )\ngetOnButton( Int index )\ngetOffButton( Int index )\ngetNumberButtons()\ngetNumberAxes()\ngetNumberMatrices()\ngetInputState()\nsetEventFilter( arPythonEventFilter filter )\ngetMidEyeMatrix()\nReturns the <link>placement matrix of the mid-point between the eyes, or of the viewing position if the <link>viewing mode is monocular. Returns an arMatrix4.\ngetMidEyePosition()\nReturns the position of the mid-point between the eyes, or of the viewing position if the <link>viewing mode is monocular. Returns an arVector3.\n\n! Data Transfer\n\ngetStandalone()\nReturns True or False depending one whether or not the application is running in standalone mode.\ngetMaster()\nReturns True if this is the master application instance, False if it's a slave.\ngetConnected()\nReturns True if this is either (1) the master or (2) a slave that has an active connection with the master.\ninitIntTransfer( String fieldName )\nsetIntList( String fieldName, List valueList )\nsetIntListItem( String fieldName, Int index, Int value)\ngetIntList( String fieldName )\ngetIntListItem( String fieldName, Int index )\n\ninitFloatTransfer( String fieldName )\nsetFloatList( String fieldName, List valueList )\nsetFloatListItem( String fieldName, Int index, Float value)\ngetFloatList( String fieldName )\ngetFloatListItem( String fieldName, Int index )\n\ninitStringTransfer( String fieldName )\nsetString( String fieldName, String value )\ngetString( String fieldName )\ngetStringSize( String fieldName )\n\ninitObjectTransfer( String fieldName )\nsetObject( String fieldName , Object value )\ngetObject( String fieldName )\n\n! Navigation\n\nbool setNavTransCondition( character axis,\n arInputEventType type,\n unsigned int index,\n float threshold )\n bool setNavRotCondition( char axis,\n arInputEventType type,\n unsigned int index,\n float threshold )\nsetNavTransSpeed( float speed )\nsetNavRotSpeed( float speed )\nownNavParam( const std::string& paramName )\nnavUpdate()\nloadNavMatrix()\nLoads the navigation matrix (actually, its inverse) onto the OpenGL modelview matrix stack.\n\n! Miscellaneous\n\ngetLabel()\nReturns the name of the application.\ngetSZGClient()\nReturns a reference to the framework's <link>arSZGClient. This is the object that handles communication with the <link>Syzygy server, <link>Syzygy database access, and the like.\nsetEyeSpacing( Float feet )\nSets the horizontal spacing between the two eyes in feet. Only effective on the master, as this information is automatically shared with the slaves. Note that in monocular <link>viewing modes, this has no effect. This piece of information is automatically read from the Syzygy database; you would only use this method if you wanted to override the database value with e.g. a value read from a file.\nsetFixedHeadMode( True/False )\nTurn <link>Fixed-head Mode on and off. Only effective on the master. This is usually set from the Syzygy database.\ngetUnitConversion();\ngetAppLauncher()\n\ngetNumberSlaveConnected()\nReturns the number of slaves that have connected to the master. Returns -1 if called on a slave.\ngetProjectionMatrix( Float eyeSign )\ngetModelviewMatrix( Float eyeSign )\nGet the current OpenGL projection and modelview matrices. Each returns a 4x4 matrix as an arMatrix4. 'eyeSign' indicates whether the matrix should be computed for the left (-1.) or right (1.) eye or for monocular viewing (0.).\ngetWindowSizeX()\ngetWindowSizeY()\nGet the pixel dimensions of the window.\ngetCurrentEye()\nReturns the current value of the eye sign (-1 = left, 1 = right, 0 = monocular viewing). Should only be used in the onDraw() method.\nsoundActive()\ninputActive()\nReturn True or False depending on whether or not there is an active connection to a sound output or an input device, respectively.\n\nvoid ar_defaultWindowInitCallback();\nvoid ar_defaultReshapeCallback( PyObject* widthObject, PyObject* heightObject );\n\n%{\nvoid ar_defaultWindowInitCallback() {\n glEnable(GL_DEPTH_TEST);\n glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );\n glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);\n}\n\nvoid ar_defaultReshapeCallback( PyObject* widthObject, PyObject* heightObject ) {\n if ((!PyInt_Check(widthObject))||(!PyInt_Check(heightObject))) {\n PyErr_SetString(PyExc_TypeError,"ar_defaultReshapeCallback error: glViewport params must be ints");\n }\n GLsizei width = (GLsizei)PyInt_AsLong( widthObject );\n GLsizei height = (GLsizei)PyInt_AsLong( heightObject );\n glViewport( 0, 0, width, height );\n}\n%}\n\n
[[dictSkeleton.py|python/pyskel/dictSkeleton.py]] demonstrates many features of a Syzygy application, quite possibly more than you'll want to deal with to start out with.\n\nThe application is built on a sub-class of arPyMasterSlaveFramework class. It uses an arMasterSlaveDict dictionary container to manage a collection of colored squares, to sychronize them between master and slaves, and to draw them. It uses a custom effector class to allow the user to create and destroy squares and to drag them around. It demonstrates the framework's navigation methods.\n\nFor more information about what happens when, see [[Sequence of Events]].\n
{{{\n#!/usr/bin/python\n# (bang-trick for running on Unixes -- replace with the path to your python, e.g. the output of 'which python', then you can execute the program without explicitly invoking python using ./dictSkeleton.py)\n# This program demonstrates many features of a Syzygy application, quite possibly more than you'll want to deal with to start out with.\n# The application is built on a sub-class of arPyMasterSlaveFramework class. It uses an arMasterSlaveDict dictionary container to manage a collection of colored squares, to sychronize them between master and slaves, and to draw them. It uses a custom effector class to allow the user to create and destroy squares and to drag them around. It demonstrates the framework's navigation methods.\n# For more information about what happens when, see Sequence of Events.\n\n# Import necessary modules. Note that it's important to import the Syzyg bindings (PySZG) before the OpenGL modules to ensure that the correct version of GLUT is loaded. I tend to just copy & paste all of these at the beginning of any program.\n\nfrom PySZG import *\nfrom OpenGL.GL import *\nfrom OpenGL.GLU import *\nfrom OpenGL.GLUT import *\n\n# Global Variables: just one, in this program, and it's actually a constant. I don't much like global variables.\n# This is the unit conversion factor. Syzygy's default units are feet (ugh), and that's how screen dimensions and sizes etc. have to be specified in the Syzygy Database. If you want to use other units, you need to tell the framework by passing a scale factor. The scale factor is the number of your application's units per foot. For example, the Atlantis demo included in the main repository (a port of a standard OpenGL programming demo) uses 1/2-millimeters. So the appropriate unit-conversion factor would be 12 (in./ft.) * 2.54 (cm./in.) * 10. (mm./cm.) * 2.\n# For this application, we'll just use feet.\nSKELETON_UNITS_PER_FOOT = 1.\n\n\n#### Class definitions & implementations. ####\n\n# We'll have just one one interactable object class, a 2-ft colored square that can be grabbed & dragged around. We'll also have an effector class for doing the grabbing. For a discussion of interactables and effectors, see User Interaction Concepts.\n\n# ColoredSquare: a yellow square that changes green when 'touched' (i.e. when the effector tip is within 1 ft.) and can be dragged around. arPyInteractable is the base class for Python interactable objects. When you create your own sub-class, you may want to override the following methods (besides __init__(), of course): onTouch(), which is called when an object is first selected for interaction; onProcessInteraction(), which is called on each frame in which the object is selected; onUntouch(), which is called when an object becomes deselected, and draw(), which you can either call directly yourself or, as in this case, have it called by an arMasterSlaveDict. If you're using an arMasterSlaveDict, you also need to add the getState() and setState() methods.\n\nclass ColoredSquare(arPyInteractable):\n def __init__(self, container=None):\n # Always call superclass init!!!\n arPyInteractable.__init__(self)\n # This is a reference to the arMasterSlaveDict holding this object (see Using an arMasterSlaveDict). We'll just hang onto a copy of the reference.\n self._container = container\n\n # onTouch() will get called each time this object is first selected for interaction ('touched'), i.e. in each frame in which this object is touched when it was not touched in the previous frame. setHighlight() is an arPyInteractable method, it can take any Int value; how you use the highlight state is up to you, it's used in draw() below.\n def onTouch( self, effector ):\n self.setHighlight( True )\n return True\n\n # onProcessInteraction() will get called on each frame in which this object is touched. Use the 'effector' argument (a reference to the arEffector touching this object) to get buttons states, etc. Note that if this object is being dragged by the effector (determined by the conditions specified in the effector's setDrag() method, below), that happens just before this is called.\n def onProcessInteraction( self, effector ):\n # button 2 deletes this square from the dictionary. Note that we use getOnButton() to detect the transition from unpressed to pressed, see Polling the Framework's Input State.\n if effector.getOnButton(2):\n # NOTE! self.clearCallbacks() is necessary when you want to delete a ColoredSquare. Why? Because the arPyInteractable uses a number of callbacks--methods that are called when certain events occur. When it is instantiated, it installs some of its own methods as callbacks. This means that it owns several references to itself, which in turn means that unless we manually clear the callbacks (clearCallbacks() sets all the callbacks to None), the object's reference count will never go to zero, it won't be deleted, and it won't tell the effector to release it (see the Python 2.4.1 C API Documentation for a discussion of reference counts). So for the moment, whenever you want to delete an arPyInteractable or instance of a sub-class thereof, you must call clearCallbacks() as well as getting rid of any explicit references you may have. Eventually, I'll probably re-write this stuff in Python, at which point this problem will go away.\n self.clearCallbacks()\n self._container.delValue(self)\n # button 3 toggles visibility (actually solid/wireframe)\n if effector.getOnButton(3):\n self.setVisible( not self.getVisible() )\n\n # onUntouch() will get called each time this object stops being touched.\n def onUntouch( self, effector ):\n self.setHighlight( False )\n return True\n\n # Draw the square, mostly straight OpenGL. \n def draw( self, framework=None ):\n # Use glPushMatrix() to make a duplicate of the currently-active OpenGL modeling matrix. We can modify that transformation as desired & then easily restore the previous state when we're done using glPopMatrix(), below. This is good programming practice when using OpenGL. Note that in a Syzygy program, all matrix operations by default affect the modelview matrix (see Chapter 3: Viewing in the OpenGL Programming Guide).\n glPushMatrix()\n # Apply our object's placement matrix. glMultMatrixf() seems to be happy with most Python Sequence Types; we'll pass a tuple (flat, i.e. 16 floats in a row, not 4x4). getMatrix() and setMatrix() are arPyInteractable methods for getting and setting this object's placement matrix (see Placement Matrices).\n glMultMatrixf( self.getMatrix().toTuple() )\n glScalef( 2., 2., 1./12. )\n if self.getVisible():\n # set one of two colors depending on if this object has been selected for interaction\n if self.getHighlight():\n glColor3f( 0,1,0 ) # green\n else:\n glColor3f( 1,1,0 ) # yellow\n # draw rectangular solid 2'x2'x1'\n glutSolidCube(1.)\n # superimpose slightly larger white wireframe\n glColor3f(1,1,1)\n glutWireCube(1.03)\n glPopMatrix() # Restore prior modeling matrix\n\n # getState() and setState() are required by the arMasterSlaveDict. All state that must be shared between master and slaves has to be packed into a tuple for each object. See Making Classes Compatible with an arMasterSlaveDict.\n def getState(self):\n return (self.getHighlight(),self.getVisible(),self.getMatrix().toTuple())\n\n def setState( self, stateTuple ):\n self.setHighlight( stateTuple[0] )\n self.setVisible( stateTuple[1] )\n self.setMatrix( arMatrix4(stateTuple[2]) )\n\n# End of ColoredSquare --------------------------------------------------------\n\n# RodEffector: an effector that uses input matrix 1 for its position/orientation and has 6 buttons starting at index 0. It is visually and functionally a 5-ft. rod with the hot spot at the tip (see User Interaction Concepts). arEffector is the base class for effectors.\nclass RodEffector( arEffector ):\n def __init__(self):\n # arEffector constructor arguments: (1) The index of the matrix event (see Syzygy Input Events) that this effector will use for its placement matrix (in this case, #1). (2) The number of buttons the effector will copy in from the framework (six). (3) The button event index from which to start copying (in this case, zero, i.e. it will copy button #0-#5). Note that if this parameter is non-zero, the effector button indices are shifted down relative to the framework. For example, in the current example, effector button #0 = framework button #0. Suppose instead that we had called arEffector.__init__(self,1,6,2,0,0); in that case, our effector would have copied framework buttons #2-#7, and effector button #0 would have corresponded to framework button #2.\n arEffector.__init__(self,1,6,0,0,0)\n\n # set effector hot-spot to 5 ft in front (in Syzygy, as in OpenGL, the -Z axis always points forwards).\n self.setTipOffset( arVector3(0,0,-5) )\n\n # Set to interact with closest object within 1 ft. of tip (see Available Interaction Selectors for alternative classes for selecting objects)\n self.setInteractionSelector( arDistanceInteractionSelector(1.) )\n\n # set to grab an object (that has already been selected for interaction using rule specified on previous line) when button 0 is pressed and held. The two arguments are a grab condition (an arGrabCondition) and a drag behavior (an arDragBehavior sub-class) The grab condition specifies that a grab will occur whenever the value of the specified button event is > 0.5 and be maintained until that condition is no longer met. The specified drag behavior allows the user to wave the object around with orientation change, i.e. when you twist the wand, the object rotates with it. See Available Grab Conditions for more information on grab conditions and Available Drag Behaviors for the existing drag behaviors.\n self.setDrag( arGrabCondition( AR_EVENT_BUTTON, 0, 0.5 ), arWandRelativeDrag() )\n\n # draw the effector as a long, skinny, gray rod.\n def draw(self):\n # Use glPushMatrix() to make a duplicate of the currently-active OpenGL modeling matrix. We can modify that transformation as desired & then easily restore the previous state when we're done using glPopMatrix(), below. This is good programming practice when using OpenGL. Note that in a Syzygy program, all matrix operations by default affect the modelview matrix (see Chapter 3: Viewing in the OpenGL Programming Guide ).\n glPushMatrix()\n # Apply our object's placement matrix. glMultMatrixf() seems to be happy with most Python Sequence Types; we'll pass a tuple (flat, i.e. 16 floats in a row, not 4x4). getMatrix() and setMatrix() are arPyInteractable methods for getting and setting this object's placement matrix (see Placement Matrices).\n glMultMatrixf( self.getCenterMatrix().toTuple() )\n # draw grey rectangular solid 2"x2"x5'\n glScalef( 2./12, 2./12., 5. )\n glColor3f( .5,.5,.5 )\n glutSolidCube(1.)\n # superimpose slightly larger black wireframe (makes it easier to see shape)\n glColor3f(0,0,0)\n glutWireCube(1.03)\n glPopMatrix() # Restore prior modeling matrix\n\n# End of RodEffector --------------------------------------------------------\n\n##### The application framework ####\n# This is our master/slave application framework sub-class; arPyMasterSlaveFramework is the base class. Method names beginning with 'on' (e.g. 'onStart') represent callbacks, i.e. those methods are called when specific events occur. See Sequence of Events.\n\nclass SkeletonFramework(arPyMasterSlaveFramework):\n def __init__(self):\n arPyMasterSlaveFramework.__init__(self)\n\n # arMasterSlaveDict is a dictionary of objects to be shared between master and slaves. Dictionary keys MUST be Ints, Floats, or Strings. The second constructor argument contains information about the classes the dictionary can contain, used to construct new objects in the slaves. In this particular instance, the value in the list is a reference to the ColoredSquare class (remember, in Python a class is just an object that generates instances when called with (). See Using an arMasterSlaveDict and Instantiating and Starting an arMasterSlaveDict for more information. Note that you can have multiple arMasterSlaveDicts with different names in one application, if desired.\n self.dictionary = arMasterSlaveDict( 'objects', [ColoredSquare] )\n\n # Create a ColoredSquare, passing a reference to the arMasterSlaveDict as a parameter \n theSquare = ColoredSquare( self.dictionary )\n\n # set square's initial placement (see Placement Matrices, arMatrix4 class, Math Functions)\n theSquare.setMatrix( ar_translationMatrix(0,5,-6) )\n\n # Insert it in the dictionary. 'push()' is a method I added to allow you to not have to explicitly deal with dictionary keys if you don't feel like it. It uses an implicit key that is automatically incremented each time you use push(), skipping already-used values.\n self.dictionary.push( theSquare )\n\n # Create our effector\n self.wand = RodEffector()\n\n # Tell the framework what units we're using.\n self.setUnitConversion( FEET_TO_LOCAL_UNITS )\n\n # Set near & far clipping planes for rendering. Objects or portions of objects outside of these planes don't get drawn.\n nearClipDistance = .1*FEET_TO_LOCAL_UNITS\n farClipDistance = 100.*FEET_TO_LOCAL_UNITS\n self.setClipPlanes( nearClipDistance, farClipDistance )\n\n\n #### Framework callback methods (see Main Execution Thread, Framework Callbacks) ####\n\n # onStart() is called in framework.start(), after the window has been created (so it's OK to do OpenGL initialization here), but before we enter the main event loop. 'client' is a reference to an arSZGClient, which is used to get information from the Syzygy Database.\n def onStart( self, client ):\n\n # Register the arMasterSlaveDict to be shared between master & slaves\n self.dictionary.start( self )\n\n # Setup navigation, so we can drive around with the joystick. Tilting the joystick by more than 20% along axis 1 (the vertical on ours) will cause translation along Z (forwards/backwards). This is actually the default behavior, so this line isn't necessary. These behaviors actually occur in the navUpdate() method, called in onPreExchange() below.\n self.setNavTransCondition( 'z', AR_EVENT_AXIS, 1, 0.2 ) \n\n # Tilting joystick left or right (axis 0) will rotate left/right around vertical axis (default is left/right translation)\n self.setNavRotCondition( 'y', AR_EVENT_AXIS, 0, 0.2 ) \n\n # Set translation & rotation speeds to 5 ft/sec & 30 deg/sec (defaults). Note that the translation speed is specified in application units.\n self.setNavTransSpeed( 5. )\n self.setNavRotSpeed( 30. )\n \n # OpenGL initialization\n glClearColor(0,0,0,0)\n return True\n\n\n # onPreExchange() is called before data is transferred from master to slaves. It is called only in the master. This is where anything having to do with processing user input or random variables should happen. Normally, most of the frame-by-frame work of the program should be done here.\n def onPreExchange( self ):\n\n # handle joystick-based navigation (drive around). This causes the input events specified in e.g. setNavTransCondition to modify a global navigation matrix. The navigation matrix is automagically transferred to the slaves.\n self.navUpdate()\n\n # update the input state (placement matrix & button & axis states, if any) of our effector.\n self.wand.updateState( self.getInputState() )\n\n # If button #1 is pressed, create a new square and add it to the dictionary. Note we could also have used self.getOnButton(1) (the framework's method), I just used the effector method here for uniformity (since it's what handles all other button presses).\n if self.wand.getOnButton(1):\n theSquare = ColoredSquare( self.dictionary )\n theSquare.setMatrix( self.wand.getMatrix() )\n self.dictionary.push( theSquare )\n\n # Handle any interaction with the squares (see User Interaction Concepts). Any grabbing/dragging/deletion of squares happens in here. Any objects in the dictionary that aren't instances of subclasses of arPyInteractable will be ignored.\n self.dictionary.processInteraction( self.wand )\n\n # Pack the state of the arMasterSlaveDict (and its contents) into the framework. This generates a sequence of messages, starting with construction and deletion messages for objects that have been inserted or deleted from the dictionary, and ending with state-change messages for each object currently in the dictionary, based on the sequence returned by each object's getState(). See Data Transfer with an arMasterSlaveDict.\n self.dictionary.packState( self )\n \n\n # onPostExchange() is called after transfer of data from master to slaves. Mostly used to synchronize slaves with master based on transferred data. Note that you normally Shouldn't be doing a whole lot of computation here; the exception would be if you have a large and complex state that can be computed from a relatively small number of parameters, you might compute those parameters in the master, transfer them to the slaves, & do the complex state computation in both master and slaves here.\n def onPostExchange( self ):\n # We don't need to do any of this stuff on the master.\n if not self.getMaster():\n \n # Update effector's input state. In the slaves we only really need the matrix to be updated, for drawing purposes.\n self.wand.updateState( self.getInputState() )\n\n # Unpack the message queue and use it to update the set of objects in the arMasterSlaveDict as well as the state of each (using its setState() method). Note that any ColoredSquare objects removed in the master will be removed in the slaves here, but will not be garbage-collected (deleted) because clearCallbacks is not called (see clearCallbacks, above). arMasterSlaveDict needs to be modified to allow this. In most applications it shouldn't be a problem, however.\n self.dictionary.unpackState( self )\n\n\n # onDraw callback method. This is where you render your virtual world. That's all you should do; if you feel an inclination to do any computation here, put it in onPreExchange() or onPostExchange() instead. In particular, keep in mind that this method may be called more than once/frame, if e.g. your application is running on a computer with a stereoscopic display or the user has configured Syzygy for multiple viewports or windows on a single computer. \n def onDraw( self):\n # Load the [inverse of the] navigation matrix onto the OpenGL modelview matrix stack.\n self.loadNavMatrix()\n \n # Draw any objects in the dictionary with a 'draw' attribute. (in this case, all of them).\n self.dictionary.draw()\n\n # Draw the effector.\n self.wand.draw()\n\n# End of SkeletonFramework -------------------------------------------------------\n\n\n# Finally, the main program.\nif __name__ == '__main__':\n # Create the framework object\n framework = SkeletonFramework()\n\n # Try to initialize it.\n if not framework.init(sys.argv):\n raise PySZGException,'Unable to init framework.'\n print 'Framework inited.'\n\n # Start the framework; this does some setup, calls you onStart() method, then enters the event loop. Never returns unless something goes wrong.\n if not framework.start():\n raise PySZGException,'Unable to start framework.'\n}}}