LUMIERA.clone/wiki/renderengine.html

14536 lines
818 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<script type="text/javascript">
//<![CDATA[
var version = {title: "TiddlyWiki", major: 2, minor: 2, revision: 3, date: new Date("Jun 17, 2007"), extensions: {}};
//]]>
</script>
<!--
TiddlyWiki created by Jeremy Ruston, (jeremy [at] osmosoft [dot] com)
Copyright (c) UnaMesa Association 2004-2007
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
Neither the name of the UnaMesa Association nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
-->
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
<!--PRE-HEAD-START-->
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>
<!--}}}-->
<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;">loading <b>Proc-Layer</b> devel doku<blink> ...</blink><br><br><span style="font-size: 14px; color:red;">Requires Javascript.</span></div>
<!--PRE-HEAD-END-->
<title> Engine - Building a Render Nodes Network from Objects in the Session </title>
<style type="text/css">
#saveTest {display:none;}
#messageArea {display:none;}
#copyright {display:none;}
#storeArea {display:none;}
#storeArea div {padding:0.5em; margin:1em 0em 0em 0em; border-color:#fff #666 #444 #ddd; border-style:solid; border-width:2px; overflow:auto;}
#shadowArea {display:none;}
#javascriptWarning {width:100%; text-align:center; font-weight:bold; background-color:#dd1100; color:#fff; padding:1em 0em;}
</style>
<!--POST-HEAD-START-->
<!--POST-HEAD-END-->
</head>
<body onload="main();" onunload="if(window.checkUnsavedChanges) checkUnsavedChanges(); if(window.scrubNodes) scrubNodes(document.body);">
<!--PRE-BODY-START-->
<!--PRE-BODY-END-->
<div id="copyright">
Welcome to TiddlyWiki created by Jeremy Ruston, Copyright &copy; 2007 UnaMesa Association
</div>
<noscript>
<div id="javascriptWarning">This page requires JavaScript to function properly.<br /><br />If you are using Microsoft Internet Explorer you may need to click on the yellow bar above and select 'Allow Blocked Content'. You must then click 'Yes' on the following security warning.</div>
</noscript>
<div id="saveTest"></div>
<div id="backstageCloak"></div>
<div id="backstageButton"></div>
<div id="backstageArea"><div id="backstageToolbar"></div></div>
<div id="backstage">
<div id="backstagePanel"></div>
</div>
<div id="contentWrapper"></div>
<div id="contentStash"></div>
<div id="shadowArea">
<div title="ColorPalette">
<pre>Background: #fff
Foreground: #000
PrimaryPale: #8cf
PrimaryLight: #18f
PrimaryMid: #04b
PrimaryDark: #014
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eee
TertiaryLight: #ccc
TertiaryMid: #999
TertiaryDark: #666
Error: #f88</pre>
</div>
<div title="StyleSheetColors">
<pre>/*{{{*/
body {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
a {color:[[ColorPalette::PrimaryMid]];}
a:hover {background-color:[[ColorPalette::PrimaryMid]]; color:[[ColorPalette::Background]];}
a img {border:0;}
h1,h2,h3,h4,h5,h6 {color:[[ColorPalette::SecondaryDark]]; background:transparent;}
h1 {border-bottom:2px solid [[ColorPalette::TertiaryLight]];}
h2,h3 {border-bottom:1px solid [[ColorPalette::TertiaryLight]];}
.button {color:[[ColorPalette::PrimaryDark]]; border:1px solid [[ColorPalette::Background]];}
.button:hover {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::SecondaryLight]]; border-color:[[ColorPalette::SecondaryMid]];}
.button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::SecondaryDark]];}
.header {background:[[ColorPalette::PrimaryMid]];}
.headerShadow {color:[[ColorPalette::Foreground]];}
.headerShadow a {font-weight:normal; color:[[ColorPalette::Foreground]];}
.headerForeground {color:[[ColorPalette::Background]];}
.headerForeground a {font-weight:normal; color:[[ColorPalette::PrimaryPale]];}
.tabSelected{color:[[ColorPalette::PrimaryDark]];
background:[[ColorPalette::TertiaryPale]];
border-left:1px solid [[ColorPalette::TertiaryLight]];
border-top:1px solid [[ColorPalette::TertiaryLight]];
border-right:1px solid [[ColorPalette::TertiaryLight]];
}
.tabUnselected {color:[[ColorPalette::Background]]; background:[[ColorPalette::TertiaryMid]];}
.tabContents {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::TertiaryPale]]; border:1px solid [[ColorPalette::TertiaryLight]];}
.tabContents .button {border:0;}
#sidebar {}
#sidebarOptions input {border:1px solid [[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel {background:[[ColorPalette::PrimaryPale]];}
#sidebarOptions .sliderPanel a {border:none;color:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:hover {color:[[ColorPalette::Background]]; background:[[ColorPalette::PrimaryMid]];}
#sidebarOptions .sliderPanel a:active {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::Background]];}
.wizard {background:[[ColorPalette::PrimaryPale]]; border:1px solid [[ColorPalette::PrimaryMid]];}
.wizard h1 {color:[[ColorPalette::PrimaryDark]]; border:none;}
.wizard h2 {color:[[ColorPalette::Foreground]]; border:none;}
.wizardStep {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];
border:1px solid [[ColorPalette::PrimaryMid]];}
.wizardStep.wizardStepDone {background::[[ColorPalette::TertiaryLight]];}
.wizardFooter {background:[[ColorPalette::PrimaryPale]];}
.wizardFooter .status {background:[[ColorPalette::PrimaryDark]]; color:[[ColorPalette::Background]];}
.wizard .button {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryLight]]; border: 1px solid;
border-color:[[ColorPalette::SecondaryPale]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryDark]] [[ColorPalette::SecondaryPale]];}
.wizard .button:hover {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Background]];}
.wizard .button:active {color:[[ColorPalette::Background]]; background:[[ColorPalette::Foreground]]; border: 1px solid;
border-color:[[ColorPalette::PrimaryDark]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryPale]] [[ColorPalette::PrimaryDark]];}
#messageArea {border:1px solid [[ColorPalette::SecondaryMid]]; background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]];}
#messageArea .button {color:[[ColorPalette::PrimaryMid]]; background:[[ColorPalette::SecondaryPale]]; border:none;}
.popupTiddler {background:[[ColorPalette::TertiaryPale]]; border:2px solid [[ColorPalette::TertiaryMid]];}
.popup {background:[[ColorPalette::TertiaryPale]]; color:[[ColorPalette::TertiaryDark]]; border-left:1px solid [[ColorPalette::TertiaryMid]]; border-top:1px solid [[ColorPalette::TertiaryMid]]; border-right:2px solid [[ColorPalette::TertiaryDark]]; border-bottom:2px solid [[ColorPalette::TertiaryDark]];}
.popup hr {color:[[ColorPalette::PrimaryDark]]; background:[[ColorPalette::PrimaryDark]]; border-bottom:1px;}
.popup li.disabled {color:[[ColorPalette::TertiaryMid]];}
.popup li a, .popup li a:visited {color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border: none;}
.popup li a:active {background:[[ColorPalette::SecondaryPale]]; color:[[ColorPalette::Foreground]]; border: none;}
.popupHighlight {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
.listBreak div {border-bottom:1px solid [[ColorPalette::TertiaryDark]];}
.tiddler .defaultCommand {font-weight:bold;}
.shadow .title {color:[[ColorPalette::TertiaryDark]];}
.title {color:[[ColorPalette::SecondaryDark]];}
.subtitle {color:[[ColorPalette::TertiaryDark]];}
.toolbar {color:[[ColorPalette::PrimaryMid]];}
.toolbar a {color:[[ColorPalette::TertiaryLight]];}
.selected .toolbar a {color:[[ColorPalette::TertiaryMid]];}
.selected .toolbar a:hover {color:[[ColorPalette::Foreground]];}
.tagging, .tagged {border:1px solid [[ColorPalette::TertiaryPale]]; background-color:[[ColorPalette::TertiaryPale]];}
.selected .tagging, .selected .tagged {background-color:[[ColorPalette::TertiaryLight]]; border:1px solid [[ColorPalette::TertiaryMid]];}
.tagging .listTitle, .tagged .listTitle {color:[[ColorPalette::PrimaryDark]];}
.tagging .button, .tagged .button {border:none;}
.footer {color:[[ColorPalette::TertiaryLight]];}
.selected .footer {color:[[ColorPalette::TertiaryMid]];}
.sparkline {background:[[ColorPalette::PrimaryPale]]; border:0;}
.sparktick {background:[[ColorPalette::PrimaryDark]];}
.error, .errorButton {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::Error]];}
.warning {color:[[ColorPalette::Foreground]]; background:[[ColorPalette::SecondaryPale]];}
.lowlight {background:[[ColorPalette::TertiaryLight]];}
.zoomer {background:none; color:[[ColorPalette::TertiaryMid]]; border:3px solid [[ColorPalette::TertiaryMid]];}
.imageLink, #displayArea .imageLink {background:transparent;}
.annotation {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; border:2px solid [[ColorPalette::SecondaryMid]];}
.viewer .listTitle {list-style-type:none; margin-left:-2em;}
.viewer .button {border:1px solid [[ColorPalette::SecondaryMid]];}
.viewer blockquote {border-left:3px solid [[ColorPalette::TertiaryDark]];}
.viewer table, table.twtable {border:2px solid [[ColorPalette::TertiaryDark]];}
.viewer th, .viewer thead td, .twtable th, .twtable thead td {background:[[ColorPalette::SecondaryMid]]; border:1px solid [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::Background]];}
.viewer td, .viewer tr, .twtable td, .twtable tr {border:1px solid [[ColorPalette::TertiaryDark]];}
.viewer pre {border:1px solid [[ColorPalette::SecondaryLight]]; background:[[ColorPalette::SecondaryPale]];}
.viewer code {color:[[ColorPalette::SecondaryDark]];}
.viewer hr {border:0; border-top:dashed 1px [[ColorPalette::TertiaryDark]]; color:[[ColorPalette::TertiaryDark]];}
.highlight, .marked {background:[[ColorPalette::SecondaryLight]];}
.editor input {border:1px solid [[ColorPalette::PrimaryMid]];}
.editor textarea {border:1px solid [[ColorPalette::PrimaryMid]]; width:100%;}
.editorFooter {color:[[ColorPalette::TertiaryMid]];}
#backstageArea {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::TertiaryMid]];}
#backstageArea a {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstageArea a:hover {background:[[ColorPalette::SecondaryLight]]; color:[[ColorPalette::Foreground]]; }
#backstageArea a.backstageSelTab {background:[[ColorPalette::Background]]; color:[[ColorPalette::Foreground]];}
#backstageButton a {background:none; color:[[ColorPalette::Background]]; border:none;}
#backstageButton a:hover {background:[[ColorPalette::Foreground]]; color:[[ColorPalette::Background]]; border:none;}
#backstagePanel {background:[[ColorPalette::Background]]; border-color: [[ColorPalette::Background]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]] [[ColorPalette::TertiaryDark]];}
.backstagePanelFooter .button {border:none; color:[[ColorPalette::Background]];}
.backstagePanelFooter .button:hover {color:[[ColorPalette::Foreground]];}
#backstageCloak {background:[[ColorPalette::Foreground]]; opacity:0.6; filter:'alpha(opacity:60)';}
/*}}}*/</pre>
</div>
<div title="StyleSheetLayout">
<pre>/*{{{*/
* html .tiddler {height:1%;}
body {font-size:.75em; font-family:arial,helvetica; margin:0; padding:0;}
h1,h2,h3,h4,h5,h6 {font-weight:bold; text-decoration:none;}
h1,h2,h3 {padding-bottom:1px; margin-top:1.2em;margin-bottom:0.3em;}
h4,h5,h6 {margin-top:1em;}
h1 {font-size:1.35em;}
h2 {font-size:1.25em;}
h3 {font-size:1.1em;}
h4 {font-size:1em;}
h5 {font-size:.9em;}
hr {height:1px;}
a {text-decoration:none;}
dt {font-weight:bold;}
ol {list-style-type:decimal;}
ol ol {list-style-type:lower-alpha;}
ol ol ol {list-style-type:lower-roman;}
ol ol ol ol {list-style-type:decimal;}
ol ol ol ol ol {list-style-type:lower-alpha;}
ol ol ol ol ol ol {list-style-type:lower-roman;}
ol ol ol ol ol ol ol {list-style-type:decimal;}
.txtOptionInput {width:11em;}
#contentWrapper .chkOptionInput {border:0;}
.externalLink {text-decoration:underline;}
.indent {margin-left:3em;}
.outdent {margin-left:3em; text-indent:-3em;}
code.escaped {white-space:nowrap;}
.tiddlyLinkExisting {font-weight:bold;}
.tiddlyLinkNonExisting {font-style:italic;}
/* the 'a' is required for IE, otherwise it renders the whole tiddler in bold */
a.tiddlyLinkNonExisting.shadow {font-weight:bold;}
#mainMenu .tiddlyLinkExisting,
#mainMenu .tiddlyLinkNonExisting,
#sidebarTabs .tiddlyLinkNonExisting {font-weight:normal; font-style:normal;}
#sidebarTabs .tiddlyLinkExisting {font-weight:bold; font-style:normal;}
.header {position:relative;}
.header a:hover {background:transparent;}
.headerShadow {position:relative; padding:4.5em 0em 1em 1em; left:-1px; top:-1px;}
.headerForeground {position:absolute; padding:4.5em 0em 1em 1em; left:0px; top:0px;}
.siteTitle {font-size:3em;}
.siteSubtitle {font-size:1.2em;}
#mainMenu {position:absolute; left:0; width:10em; text-align:right; line-height:1.6em; padding:1.5em 0.5em 0.5em 0.5em; font-size:1.1em;}
#sidebar {position:absolute; right:3px; width:16em; font-size:.9em;}
#sidebarOptions {padding-top:0.3em;}
#sidebarOptions a {margin:0em 0.2em; padding:0.2em 0.3em; display:block;}
#sidebarOptions input {margin:0.4em 0.5em;}
#sidebarOptions .sliderPanel {margin-left:1em; padding:0.5em; font-size:.85em;}
#sidebarOptions .sliderPanel a {font-weight:bold; display:inline; padding:0;}
#sidebarOptions .sliderPanel input {margin:0 0 .3em 0;}
#sidebarTabs .tabContents {width:15em; overflow:hidden;}
.wizard {padding:0.1em 1em 0em 2em;}
.wizard h1 {font-size:2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizard h2 {font-size:1.2em; font-weight:bold; background:none; padding:0em 0em 0em 0em; margin:0.4em 0em 0.2em 0em;}
.wizardStep {padding:1em 1em 1em 1em;}
.wizard .button {margin:0.5em 0em 0em 0em; font-size:1.2em;}
.wizardFooter {padding:0.8em 0.4em 0.8em 0em;}
.wizardFooter .status {padding:0em 0.4em 0em 0.4em; margin-left:1em;}
.wizard .button {padding:0.1em 0.2em 0.1em 0.2em;}
#messageArea {position:fixed; top:2em; right:0em; margin:0.5em; padding:0.5em; z-index:2000; _position:absolute;}
.messageToolbar {display:block; text-align:right; padding:0.2em 0.2em 0.2em 0.2em;}
#messageArea a {text-decoration:underline;}
.tiddlerPopupButton {padding:0.2em 0.2em 0.2em 0.2em;}
.popupTiddler {position: absolute; z-index:300; padding:1em 1em 1em 1em; margin:0;}
.popup {position:absolute; z-index:300; font-size:.9em; padding:0; list-style:none; margin:0;}
.popup .popupMessage {padding:0.4em;}
.popup hr {display:block; height:1px; width:auto; padding:0; margin:0.2em 0em;}
.popup li.disabled {padding:0.4em;}
.popup li a {display:block; padding:0.4em; font-weight:normal; cursor:pointer;}
.listBreak {font-size:1px; line-height:1px;}
.listBreak div {margin:2px 0;}
.tabset {padding:1em 0em 0em 0.5em;}
.tab {margin:0em 0em 0em 0.25em; padding:2px;}
.tabContents {padding:0.5em;}
.tabContents ul, .tabContents ol {margin:0; padding:0;}
.txtMainTab .tabContents li {list-style:none;}
.tabContents li.listLink { margin-left:.75em;}
#contentWrapper {display:block;}
#splashScreen {display:none;}
#displayArea {margin:1em 17em 0em 14em;}
.toolbar {text-align:right; font-size:.9em;}
.tiddler {padding:1em 1em 0em 1em;}
.missing .viewer,.missing .title {font-style:italic;}
.title {font-size:1.6em; font-weight:bold;}
.missing .subtitle {display:none;}
.subtitle {font-size:1.1em;}
.tiddler .button {padding:0.2em 0.4em;}
.tagging {margin:0.5em 0.5em 0.5em 0; float:left; display:none;}
.isTag .tagging {display:block;}
.tagged {margin:0.5em; float:right;}
.tagging, .tagged {font-size:0.9em; padding:0.25em;}
.tagging ul, .tagged ul {list-style:none; margin:0.25em; padding:0;}
.tagClear {clear:both;}
.footer {font-size:.9em;}
.footer li {display:inline;}
.annotation {padding:0.5em; margin:0.5em;}
* html .viewer pre {width:99%; padding:0 0 1em 0;}
.viewer {line-height:1.4em; padding-top:0.5em;}
.viewer .button {margin:0em 0.25em; padding:0em 0.25em;}
.viewer blockquote {line-height:1.5em; padding-left:0.8em;margin-left:2.5em;}
.viewer ul, .viewer ol {margin-left:0.5em; padding-left:1.5em;}
.viewer table, table.twtable {border-collapse:collapse; margin:0.8em 1.0em;}
.viewer th, .viewer td, .viewer tr,.viewer caption,.twtable th, .twtable td, .twtable tr,.twtable caption {padding:3px;}
table.listView {font-size:0.85em; margin:0.8em 1.0em;}
table.listView th, table.listView td, table.listView tr {padding:0px 3px 0px 3px;}
.viewer pre {padding:0.5em; margin-left:0.5em; font-size:1.2em; line-height:1.4em; overflow:auto;}
.viewer code {font-size:1.2em; line-height:1.4em;}
.editor {font-size:1.1em;}
.editor input, .editor textarea {display:block; width:100%; font:inherit;}
.editorFooter {padding:0.25em 0em; font-size:.9em;}
.editorFooter .button {padding-top:0px; padding-bottom:0px;}
.fieldsetFix {border:0; padding:0; margin:1px 0px 1px 0px;}
.sparkline {line-height:1em;}
.sparktick {outline:0;}
.zoomer {font-size:1.1em; position:absolute; overflow:hidden;}
.zoomer div {padding:1em;}
* html #backstage {width:99%;}
* html #backstageArea {width:99%;}
#backstageArea {display:none; position:relative; overflow: hidden; z-index:150; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageToolbar {position:relative;}
#backstageArea a {font-weight:bold; margin-left:0.5em; padding:0.3em 0.5em 0.3em 0.5em;}
#backstageButton {display:none; position:absolute; z-index:175; top:0em; right:0em;}
#backstageButton a {padding:0.1em 0.4em 0.1em 0.4em; margin:0.1em 0.1em 0.1em 0.1em;}
#backstage {position:relative; width:100%; z-index:50;}
#backstagePanel {display:none; z-index:100; position:absolute; margin:0em 3em 0em 3em; padding:1em 1em 1em 1em;}
.backstagePanelFooter {padding-top:0.2em; float:right;}
.backstagePanelFooter a {padding:0.2em 0.4em 0.2em 0.4em;}
#backstageCloak {display:none; z-index:20; position:absolute; width:100%; height:100px;}
.whenBackstage {display:none;}
.backstageVisible .whenBackstage {display:block;}
/*}}}*/</pre>
</div>
<div title="StyleSheetLocale">
<pre>/***
StyleSheet for use when a translation requires any css style changes.
This StyleSheet can be used directly by languages such as Chinese, Japanese and Korean which use a logographic writing system and need larger font sizes.
***/
/*{{{*/
body {font-size:0.8em;}
#sidebarOptions {font-size:1.05em;}
#sidebarOptions a {font-style:normal;}
#sidebarOptions .sliderPanel {font-size:0.95em;}
.subtitle {font-size:0.8em;}
.viewer table.listView {font-size:0.95em;}
.htmlarea .toolbarHA table {border:1px solid ButtonFace; margin:0em 0em;}
/*}}}*/</pre>
</div>
<div title="StyleSheetPrint">
<pre>/*{{{*/
@media print {
#mainMenu, #sidebar, #messageArea, .toolbar, #backstageButton {display: none ! important;}
#displayArea {margin: 1em 1em 0em 1em;}
/* Fixes a feature in Firefox 1.5.0.2 where print preview displays the noscript content */
noscript {display:none;}
}
/*}}}*/</pre>
</div>
<div title="PageTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
&lt;div class='headerShadow'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class='headerForeground'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
&lt;div id='sidebar'&gt;
&lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
&lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id='displayArea'&gt;
&lt;div id='messageArea'&gt;&lt;/div&gt;
&lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="ViewTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar closeTiddler closeOthers +editTiddler &gt; fields syncing permalink references jump'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='subtitle'&gt;&lt;span macro='view modifier link'&gt;&lt;/span&gt;, &lt;span macro='view modified date'&gt;&lt;/span&gt; (&lt;span macro='message views.wikified.createdPrompt'&gt;&lt;/span&gt; &lt;span macro='view created date'&gt;&lt;/span&gt;)&lt;/div&gt;
&lt;div class='tagging' macro='tagging'&gt;&lt;/div&gt;
&lt;div class='tagged' macro='tags'&gt;&lt;/div&gt;
&lt;div class='viewer' macro='view text wikified'&gt;&lt;/div&gt;
&lt;div class='tagClear'&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="EditTemplate">
<pre>&lt;!--{{{--&gt;
&lt;div class='toolbar' macro='toolbar +saveTiddler -cancelTiddler deleteTiddler'&gt;&lt;/div&gt;
&lt;div class='title' macro='view title'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit title'&gt;&lt;/div&gt;
&lt;div macro='annotations'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit text'&gt;&lt;/div&gt;
&lt;div class='editor' macro='edit tags'&gt;&lt;/div&gt;&lt;div class='editorFooter'&gt;&lt;span macro='message views.editor.tagPrompt'&gt;&lt;/span&gt;&lt;span macro='tagChooser'&gt;&lt;/span&gt;&lt;/div&gt;
&lt;!--}}}--&gt;</pre>
</div>
<div title="GettingStarted">
<pre>To get started with this blank TiddlyWiki, you'll need to modify the following tiddlers:
* SiteTitle &amp; SiteSubtitle: The title and subtitle of the site, as shown above (after saving, they will also appear in the browser title bar)
* MainMenu: The menu (usually on the left)
* DefaultTiddlers: Contains the names of the tiddlers that you want to appear when the TiddlyWiki is opened
You'll also need to enter your username for signing your edits: &lt;&lt;option txtUserName&gt;&gt;</pre>
</div>
<div title="OptionsPanel">
<pre>These InterfaceOptions for customising TiddlyWiki are saved in your browser
Your username for signing your edits. Write it as a WikiWord (eg JoeBloggs)
&lt;&lt;option txtUserName&gt;&gt;
&lt;&lt;option chkSaveBackups&gt;&gt; SaveBackups
&lt;&lt;option chkAutoSave&gt;&gt; AutoSave
&lt;&lt;option chkRegExpSearch&gt;&gt; RegExpSearch
&lt;&lt;option chkCaseSensitiveSearch&gt;&gt; CaseSensitiveSearch
&lt;&lt;option chkAnimate&gt;&gt; EnableAnimations
----
Also see AdvancedOptions</pre>
</div>
</div>
<!--POST-SHADOWAREA-->
<div id="storeArea">
<div title="Admin" modifier="CehTeh" modified="200706110324" created="200706080535" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200706110324">
<pre>PageTemplate
|&gt;|SiteTitle - SiteSubtitle|
|&gt;|MainMenu|
|DefaultTiddlers&lt;&lt;br&gt;&gt;&lt;&lt;br&gt;&gt;&lt;&lt;br&gt;&gt;ViewTemplate&lt;&lt;br&gt;&gt;&lt;&lt;br&gt;&gt;EditTemplate|SideBarOptions|
|~|OptionsPanel|
|~|SideBarTabs|
|~|AdvancedOptions|
|~|&lt;&lt;tiddler Configuration.SideBarTabs&gt;&gt;|
''StyleSheet:'' StyleSheetColors - StyleSheetLayout - StyleSheetPrint
ColorPalette
SiteUrl</pre>
</div>
<div title="Advice" modifier="Ichthyostega" modified="200911221732" created="200910311755" tags="Concepts def spec" changecount="4">
<pre>{{red{WIP 11/09}}}...//about to explicate a pattern which I'm aiming at within the design almost since the beginning//
Expecting Advice and giving Advice &amp;mdash; this collaboration ranges somewhere between messaging and dynamic properties, but cross-cutting the primary, often hierarchical relation of dependencies. Always happening at a certain //point of advice,// which creates a distinct, static nature different of being just a convention, on the other hand, Advice is deliberately kept optional and received synchronously, albeit possibly within an continuation.
!Specification
''Definition'': Advice is an optional, mediated collaboration between entities taking on the roles of advisor and advised, thereby passing a custom piece of advice data, managed by the advice support system. The possibility of advice is created by the advised entity by exposing a point of advice, while the advising entity can discover this advice possibility.
!!Collaborators
* the ''advised'' entity
* the ''advisor''
* ''point of advice''
* ''advice system''
* the ''binding''
* the ''advice''
The ''advised'' entity opens the collaboration by requsting an advice. The ''advice'' itself is a piece of data of a custom type, which needs to be //copyable.// Obviously, both the advised and the advisor need to share knowledge about the meaning of this advice data. (in a more elaborate version we might allow the advisor to provide a subclass of the advice interface type). The actual advice colaboration happens at a ''point of advice'', which needs to be derived first. To this end, the adviced puts up an request by providing his ''binding'', which is a pattern for matching. An entity willing to give advice //discovers//&amp;nbsp possible ''advice channels'' by putting up an advisor binding, which similarily is a pattern. The ''advice system'' as mediator resolves both sides, by matching (which in the most general case could be an unification). This process creates an ''advice point solution'' &amp;mdash; and in the most general case even multiple solutions. If we allow such, there needs to be a scheme for both sides to handle this (unexpected) multiplicity of advice points. Anyway, now the actual colaboration takes place by the advisor placing the piece of advice into the advice channel, causing it to be placed into the point of advice. After passing a certain (implementation defined) break point, the advice leaves the influence of the advisor and gets exposed to the advised entitie(s). Typically this involves copying the advice data into a location managed by the advice system. In the most simple case, the advised entity accesses the advice synchronously (an non-blocking). Of course, there could be a status flag to find out if there is new advice. Moreover, typically the advice data type is default constructible and thus there is always a basic form of advice available, thereby completely decoupling the advised entity from the timings related to this colaboration.
!!extensions
In a more elaborate scheme, the advised entiy could provide a signal to be invoked either in the thread context of the advisor (being still blocked in the advice providing call), or in a completely separate thread. A third solution would be to allow the advised entity to block until recieving new advice. Both of these more elaborate schemes would also allow to create an advice queue.
</pre>
</div>
<div title="AllocationCluster" modifier="Ichthyostega" modified="200810200127" created="200810180031" tags="def img" changecount="9">
<pre>Memory management facility for the low-level model (render nodes network). The model is organised into temporal segments, which are considered to be structurally constant and uniform. The objects within each segment are strongly interconnected, and thus each segment is being built in a single build process and is replaced or released as a whole. __~AllocationCluster__ implements memory management to support this usage pattern. He owns a number of object families of various types.[&gt;img[draw/AllocationCluster.png]]
* [[processing nodes|ProcNode]] &amp;mdash; probably with several subclasses (?)
* [[wiring descriptors|WiringDescriptor]]
* the input/output descriptor arrays used by the latter
To Each of those families we can expect an initially undetermined (but rather large) number of individual objects, which can be expected to be allocated within a short timespan and which are to be released cleanly on destruction of the AllocationCluster.
''Problem of calling the dtors''
Even if the low-level memory manager(s) may use raw storage, we require that the allocated object's destructors be called. This means keeping track at least of the number of objects allocated (without wasting too much memory for bookkeeping). Besides, as the objects are expected to be interconnected, it may be dangerous to destroy a given family of objects while another family of objects may rely on the former in its destructor. //If we happen do get into this situation,// we need to define a priority order on the types and assure the destruction sequence is respected.
&amp;rarr; see MemoryManagement
</pre>
</div>
<div title="Asset" modifier="Ichthyostega" modified="201002272329" created="200708100337" tags="def classes img" changecount="18">
<pre>Asset management is a subsystem on its own. Assets are &quot;things&quot; that can be loaded into a session, like Media, Clips, Effects, Transitions. It is the &quot;bookkeeping view&quot;, while the Objects in the Session relate to the &quot;manipulation and process view&quot;. Some Assets can be //loaded// and a collection of Assets is saved with each Session. Besides, there is a collection of basic Assets always available by default.
The Assets are important reference points holding the information needed to access external resources. For example, an Clip asset can reference a Media asset, which in turn holds the external filename from which to get the media stream. For Effects, the situation is similar. Assets thus serve two quite distinct purposes. One is to load, list, group search and browse them, and to provide an entry point to create new or get at existing MObject in the Session, while the other purpose is to provide attribute and property informations to the inner parts of the engine, while at the same time isolating and decoupling them from environmental details.
We can distinguish several different Kinds of Assets, each one with specific properties. While all these Kinds of Assets implement the basic Asset interface, they in turn are the __key abstractions__ of the asset management view. Mostly, their interfaces will be used directly, because they are quite different in behaviour. Thus it is common to see asset related operations being templated on the Asset Kind.
&amp;rarr; see also [[Creating and registering Assets|AssetCreation]]
[img[Asset Classess|uml/fig130309.png]]
!Media Asset
Some piece of Media Data accessible at some external Location and able to be processed by Lumiera. A Media File on Harddisk can be considered as the most basic form of Media Asset, with some important derived flavours, like a Placeholder for a currently unavailable Source, or Media available in different Resolutions or Formats.
* __outward interface operations__ include querying properties, creating an Clip MObject, controlling processing policy (low res proxy placeholders, interlacing and other generic pre- and postprocessing)
* __inward interface operations__ include querying filename, codec, offset and any other informations necessary for creating a source render node, getting additional processing policy decisions (handling of interlacing, aspect ratio).
&amp;rarr; MediaAsset
!Processing Asset
Some software component able to work on media data in the Lumiera Render engine Framework. This includes all sorts of loadable effects, as well as some of the standard, internal facilities (Mask, Projector). Note that Processing Assets typically provide some attachment Point or means of communication with GUI facilities.
* __outward interface operations__ include getting name and description, investigating the media types the processor is able to handle, cause the underlying module to be acutally loaded...
* __inward interface operations__ include resolving the actual processing function.
&amp;rarr; ProcAsset
!Structural Asset
Some of the building blocks providing the framework for the objects placed into the current Session. Notable examples are [[processing pipes|Pipe]] within the high-level-model, Viewer attachment points, Tracks, Sequences, Timelines etc.
* __outward interface operations__ include...
* __inward interface operations__ include...
&amp;rarr; StructAsset {{red{to be defined}}}
!Meta Asset
Some additional, virtual facilities created in the course of the editing process. Examples are Automation data sets, Labels and reference points, Meta Clips (nested sub-sequences)
* __outward interface operations__ include...
* __inward interface operations__ include...
&amp;rarr; MetaAsset {{red{to be defined}}}
!!!!still to be worked out..
is how to implement the relationship between [[MObject]]s and Assets. Do we use direct pointers, or do we prefer an ID + central registry approach? And how to handle the removal of an Asset.
&amp;rarr; see also [[analysis of mem management|ManagementAssetRelation]]
&amp;rarr; see also [[Creating Objects|ObjectCreation]], especially [[Assets|AssetCreation]]
//9/07: currently implementing it as follows: use a refcounting-ptr from Clip-~MObject to asset::Media while maintaining a dependency network between Asset objects. We'll see if this approach is viable//
</pre>
</div>
<div title="AssetCreation" modifier="Ichthyostega" modified="201002272321" created="200709040307" tags="operational impl" changecount="5">
<pre>Assets are created by a Factories returning smart pointers; the Asset creation is bound to specific use cases and //only available// for these specific situations. There is no generic Asset Factory.
For every Asset we generate a __Ident tuple__ and a long ID (hash) derived from this Ident tuple. The constructor of the abstract base class {{{Asset}}} takes care of this step and automatically registeres the new Asset object with the AssetManager. Typically, the factory methods for concrete Asset classes provide some shortcuts providing sensible default values for some of the Ident tuple data fields. They may take additional parameters &amp;mdash; for example the factory method for creating {{{asset::Media}}} takes a filename (and may at some point in the future aply &quot;magic&quot; based on examination of the file &amp;rarr; LoadingMedia)
Generally speaking, assets can be seen as the statical part or view of the session and model. They form a global scope and are tied to the [[model root|ModelRootMO]] &amp;mdash; which means, they're going to be serialised and de-serialised alongside with this model root scope. Especially the de-serialisation triggers (re)-creation of all assets associated with the session to be loaded.
{{red{TODO:}}} //there will be a special factory mechanism for this case, details pending definition as of 2/2010 //
</pre>
</div>
<div title="AssetManager" modifier="Ichthyostega" modified="201002272324" created="200709200300" tags="def" changecount="2">
<pre>The Asset Manager provides an Interface to an internal Database holding all Assets in the current Session and System state. It may be a real Database at some point (and for the moment it's a Hashtable). Each [[Asset]] is registered automatically with the Asset Manager; it can be queried either by it's //identification tuple// or by it's unique ID.
Conceptually, assets belong to the [[global or root scope|ModelRootMO]] of the session data model. A mechanism for serialising and de-serialising all assets alongside with the session is planned as of 2/2010
</pre>
</div>
<div title="AssetModelConnection" modifier="Ichthyostega" modified="201003200149" created="201003160243" tags="SessionLogic Model spec draft discuss" changecount="3">
<pre>Conceptually, Assets and ~MObjects represent different views onto the same entities. Assets focus on bookkeeping of the contents, while the media objects allow manipulation and EditingOperations. Usually, on the implementation side, such closely linked dual views require careful consideration.
!redundancy
Obviously there is the danger of getting each entity twice, as Asset and as ~MObject. While such dual entities could be OK in conjunction with much specialised processing, in the case of Lumiera's Proc-Layer most of the functionality is shifted to naming schemes, configuration and generic processing, leaving the actual objects almost empty and deprived of distinguishing properties. Thus, starting out from the required concepts, an attempt was made to join, reduce and straighten the design.
* type and channel configuration is concentrated to MediaAsset
* the accounting of structural elements in the model is done through StructAsset
* the object instance handling is done in a generic fashion by using placements and object references
* clips and labels appear as ~MObjects solely; on the asset side there is just an generic [[id tracking mechanism|TypedID]].
* tracks are completely deprived of processing functionality and become lightweight containers, also used as clip bins.
* timelines and sequences are implemented as façade to equivalent structures within the model
* this leaves us only with effects requiring both an object and asset implementation
</pre>
</div>
<div title="AttachedPlacementProblem" modifier="Ichthyostega" modified="200905310342" created="200801111305" tags="SessionLogic impl draft operational" changecount="17">
<pre>Placing an MObject relatively to another object such that it should be handled as //attached//&amp;nbsp; to the latter results in several design and implementation challenges. Actually, such an attachment creates a cluster of objects. The typical use case is that of an effect attached to a clip or processing pipe.
* attachment is not a globally fixed relation between objects, rather, it typically exists only for some limited time span (e.g. the duration of the basic clip the effect is attached to)
* the order of attachment is important and the attached placement may create a fork in the signal flow, so we need a way for specifying reproducibly how the resulting wiring should be
* when building, we access the information in reversed direction: we have the target object and need to query for all attachments
The first step towards an solution is to isolate the problem; obviously we don't need to store the objects differently, we just need //information about attached objects//&amp;nbsp; for some quite isolated tasks (namely for creating a GUI representation and for combining attached objects into a [[Pipe]] when building). Resorting to a query (function call) interface should turn the rest of the problem into an implementation detail. Thus
* for an __attachment head__ (= {{{Placement&lt;MObject&gt;}}} to which other objects have been attached) get the ordered list of attachments
* for an __attached placement__ (member of the cluster) get the placement of the corresponding attachment head
* retrieve and break the attachment when //deleting.//
!!Implementation notes
Attachment is managed within the participating placements, mostly by special [[locating pins|LocatingPin]]. Attachment doesn't necessarily nail down an attached object to a specific position, rather the behaviour depends on the type of the object and the locating pins actually involved, especially on their order and priority. For example, if an {{{Placement&lt;Effect&gt;}}} doesn't contain any locating pin defining a temporal position, then the attachment will result in the placement inheriting the temporal placement of the //attachment head// (i.e. the clip this effect has been attached to). But, if on the contrary the effect in question //does// have an additional locating pin, for example relative to another object or even to a fixed time position, this one will &quot;win&quot; and determine the start position of the effect &amp;mdash; it may even move the effect out of the time interval covered by the clip, in which case the attachment has no effect on the clip's processing pipe.
The attachment relation is hierarchical and has a clearly defined //active// and //passive// side: The attachment head is the parent node in a tree, but plays the role of the passive partner, to which the child nodes attach. But note, this does not mean we are limited to a single attachment head. Actually, each placement has a list of locating pins and thus can attach to several other placements. For example, a transition attaches to at least two local pipes (clips). {{red{TODO: unresolved design problem; seems to contradict the PlacementScope}}}
!!!!Relation to memory management
Attachment on itself does //not// keep an object alive. Rather, it's implemented by an opaque ID entry (&amp;rarr; PlacementRef), which can be resolved by the PlacementIndex. The existence of attachments should be taken into account when deleting an object, preferably removing any dangling attachments to prevent an exception to be thrown later on. On the other hand, contrary to the elements of the HighLevelModel, processing nodes in the render engine never depend on placements &amp;mdash; they always refer directly to the MObject instance or even the underlying asset. In the case of MObject instances, the pointer from within the engine will //share ownership// with the placement (remember: both are derived from {{{boost::shared_ptr}}}).
</pre>
</div>
<div title="Automation" modifier="Ichthyostega" modified="200906071813" created="200706250751" tags="def img" changecount="6">
<pre>Automation is treated as a function over time. It is always tied to a specific Parameter (which can thus be variable over the course of the timeline). All details //how// this function is defined are completely abstracted away. The Parameter uses a ParamProvider to get the value for a given Time (point). Typically, this will use linear or bezier interpolation over a set of keyframes internally. Parameters can be configured to have different value ranges and distribution types (on-off, stepped, continuous, bounded)
[img[how to implement Automation|uml/fig129669.png]]
</pre>
</div>
<div title="AutomationData" modifier="Ichthyostega" created="200805300105" tags="def automation" changecount="2">
<pre>While generally automation is treated as a function over time, defining and providing such a function requires some //Automation Data.// The actual layout and meaning of this data is deemed an implementation detail of the [[parameter provider|ParamProvider]] used, but nevertheless an automation data set has object characteristics within the session (high-level-model), allowing it to be attached, moved and [[placed|Placement]] by the user.</pre>
</div>
<div title="BasicBuildingOperations" modifier="Ichthyostega" modified="200805210230" created="200712040334" tags="design operational Builder img" changecount="24">
<pre>Starting out from the concepts of Objects, Placement to Tracks, render Pipes and connection properties (&amp;rarr; see [[here|TrackPipeSequence]]) within the session, we can identify the elementary operations occuring within the Builder. Overall, the Builder is organized as application of //visiting tools// to a collection of objects, so finally we have to consider some object kind appearing in the working function of the given builder tool, which holds at this moment some //context//. The job now is to organize this context such as to create a predictable build process from this //event driven// approach.
&amp;rarr;see also: BuilderPrimitives for the elementary situations used to cary out the building operations
!Builder working Situations
# any ''Clip'' (which at this point has been reduced already to a part of a simple elementary media stream &amp;rarr; see [[Fixture]])
## yields a source reading node
## which needs to be augmented by the underlying media's [[processing pattern|ProcPatt]]
##* thus inserting codec(s) and source transformations
##* effectively this is an application of effects
## at this point we have to process (and maybe generate on-the-fly) the [[source port of this clip|ClipSourcePort]]
##* the output of the source reading and preprocessing defined thus far is delivered as input to this port, which is done by a ~WiringRequest (see below)
##* as every port, it is the entry point to a [[processing pipe|Pipe]], thus the source port has a processing pattern, typically inserting the camera (transformation effect) at this point
## followed by the application of effects
##* separately for every effect chain rooted (placed) directly onto the clip
##* and regarding the chaining order
## next we have to assess the [[pipes|Pipe]] to which the clip has been placed
## producing a [[wiring request|WiringRequest]] for every pair {{{(chainEndpoint, pipe)}}}
# [&gt;img[draw/Proc.builder1.png]] attaching an ''Effect'' is actually always an //insertion operation// which is done by //prepending// to the previously built nodes. Effects may be placed as attached to clips and pipes, which causes them to be included in the processing chain at the given location. Effects may as well be placed at an absolute time, which means they are to be applied to every clip that happens to be at this time &amp;mdash; but this usecase will be reolved when creating the Fixture, causing the effect to be attached to the clips in question. The same holds true for Effects put on tracks.
# treating an ''wiring request'' means
## detecting possible and impossible connections
## deriving additional possible &quot;placement dimensions&quot; generated by executing such an connection (e.g. connecting a mono source to a spatial sound system bus creates panning possibilities)
##* deriving parameter sources for this additional degrees of freedom
##* fire off insertion of the necessary effects to satisfy this connection request and implement the additional &quot;placement dimensions&quot; (pan, layer order, overlay mode, MIDI channel selection...)
# processing the effects and further placements ''attached to a Pipe'' is handled identical to the processing done with all attachments to individual clips.
# ''Transitions'' are to be handled differently according to their placement (&amp;rarr; more on [[Transitions|TransitionsHandling]])
#* when placed normally to two (or N) clips, they are inserted at the exit node of the clip's complete effect chain.
#* otherwise, when placed to the source port(s) or when placed to some other pipes they are inserted at the exit side of those pipe's effect chains. (Note: this puts additional requirements on the transition processor, so not every transition can be placed this way)
After consuming all input objects and satisfying all wiring requests, the result is a set of [[exit nodes|ExitNode]] ready for pulling data. We call the network reachable from such an exit node a [[Processor]], together all processors of all segments and output data types comprise the render engine.
!!!dependencies
Pipes need to be there first, as everything else will be plugged (placed) to a pipe at some point. But, on the other hand, for the model as such, pipes are optional: We could create sequences with ~MObjects without configuring pipes (but won't be able then to build any render processor of course). Similarily, there is no direct relation between tracks and pipes. Each sequence is comprised of at least one root track, but this has no implications regarding any output pipe.
Effects can be attached only to already existing pipelines, starting out at some pipes entry port or the source port of some clip. Besides that, all further parts can be built in any order and independent of each other. This is made possible by using [[wiring requests|WiringRequest]], which can be resolved later on. So, as long as we start out with the tracks (to resolve any pipe they are placed to), and further, if we manage to get any effect placed to some clip-MO //after// setting up and treating the clip, we are fine and can do the building quasi event driven.
!!!building and resolving
Building the network for the individual objects thus creates a queue of wiring requests. Some of them may be immediately resolvable, but detecting this correctly can be nontrivial, and so it seems better to group all wiring requests based on the pipe and treat them groupwise. Because &amp;mdash; in the most general case &amp;mdash; connecting includes the use of transforming and joining nodes, which can create additional wiring requests (e.g. for automation parameter data connections). Finally, if the network is complete, we could perform [[optimisations|RenderNetworkOptimisation]]
</pre>
</div>
<div title="BetterTimelineMacro" modifier="Saq" modified="200701030924" created="200607280926" tags="lewcidExtension systemConfig" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200701030924">
<pre>/***
|Name|BetterTimelineMacro|
|Created by|SaqImtiaz|
|Location|http://tw.lewcid.org/#BetterTimelineMacro|
|Version|0.5 beta|
|Requires|~TW2.x|
!!!Description:
A replacement for the core timeline macro that offers more features:
*list tiddlers with only specfic tag
*exclude tiddlers with a particular tag
*limit entries to any number of days, for example one week
*specify a start date for the timeline, only tiddlers after that date will be listed.
!!!Installation:
Copy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.
Edit the ViewTemplate to add the fullscreen command to the toolbar.
!!!Syntax:
{{{&lt;&lt;timeline better:true&gt;&gt;}}}
''the param better:true enables the advanced features, without it you will get the old timeline behaviour.''
additonal params:
(use only the ones you want)
{{{&lt;&lt;timeline better:true onlyTag:Tag1 excludeTag:Tag2 sortBy:modified/created firstDay:YYYYMMDD maxDays:7 maxEntries:30&gt;&gt;}}}
''explanation of syntax:''
onlyTag: only tiddlers with this tag will be listed. Default is to list all tiddlers.
excludeTag: tiddlers with this tag will not be listed.
sortBy: sort tiddlers by date modified or date created. Possible values are modified or created.
firstDay: useful for starting timeline from a specific date. Example: 20060701 for 1st of July, 2006
maxDays: limits timeline to include only tiddlers from the specified number of days. If you use a value of 7 for example, only tiddlers from the last 7 days will be listed.
maxEntries: limit the total number of entries in the timeline.
!!!History:
*28-07-06: ver 0.5 beta, first release
!!!Code
***/
//{{{
// Return the tiddlers as a sorted array
TiddlyWiki.prototype.getTiddlers = function(field,excludeTag,includeTag)
{
var results = [];
this.forEachTiddler(function(title,tiddler)
{
if(excludeTag == undefined || tiddler.tags.find(excludeTag) == null)
if(includeTag == undefined || tiddler.tags.find(includeTag)!=null)
results.push(tiddler);
});
if(field)
results.sort(function (a,b) {if(a[field] == b[field]) return(0); else return (a[field] &lt; b[field]) ? -1 : +1; });
return results;
}
//this function by Udo
function getParam(params, name, defaultValue)
{
if (!params)
return defaultValue;
var p = params[0][name];
return p ? p[0] : defaultValue;
}
window.old_timeline_handler= config.macros.timeline.handler;
config.macros.timeline.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
var args = paramString.parseParams(&quot;list&quot;,null,true);
var betterMode = getParam(args, &quot;better&quot;, &quot;false&quot;);
if (betterMode == 'true')
{
var sortBy = getParam(args,&quot;sortBy&quot;,&quot;modified&quot;);
var excludeTag = getParam(args,&quot;excludeTag&quot;,undefined);
var includeTag = getParam(args,&quot;onlyTag&quot;,undefined);
var tiddlers = store.getTiddlers(sortBy,excludeTag,includeTag);
var firstDayParam = getParam(args,&quot;firstDay&quot;,undefined);
var firstDay = (firstDayParam!=undefined)? firstDayParam: &quot;00010101&quot;;
var lastDay = &quot;&quot;;
var field= sortBy;
var maxDaysParam = getParam(args,&quot;maxDays&quot;,undefined);
var maxDays = (maxDaysParam!=undefined)? maxDaysParam*24*60*60*1000: (new Date()).getTime() ;
var maxEntries = getParam(args,&quot;maxEntries&quot;,undefined);
var last = (maxEntries!=undefined) ? tiddlers.length-Math.min(tiddlers.length,parseInt(maxEntries)) : 0;
for(var t=tiddlers.length-1; t&gt;=last; t--)
{
var tiddler = tiddlers[t];
var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
if ((theDay&gt;=firstDay)&amp;&amp; (tiddler[field].getTime()&gt; (new Date()).getTime() - maxDays))
{
if(theDay != lastDay)
{
var theDateList = document.createElement(&quot;ul&quot;);
place.appendChild(theDateList);
createTiddlyElement(theDateList,&quot;li&quot;,null,&quot;listTitle&quot;,tiddler[field].formatString(this.dateFormat));
lastDay = theDay;
}
var theDateListItem = createTiddlyElement(theDateList,&quot;li&quot;,null,&quot;listLink&quot;,null);
theDateListItem.appendChild(createTiddlyLink(place,tiddler.title,true));
}
}
}
else
{
window.old_timeline_handler.apply(this,arguments);
}
}
//}}}</pre>
</div>
<div title="BindingMO" modifier="Ichthyostega" modified="201003130209" created="200905210144" tags="def design discuss SessionLogic" changecount="15">
<pre>Sometimes, two entities within the [[Session]] are deliberately associated, and this association has to carry some specific mappings between properties or facilities within the entities to be linked together. When this connection isn't just the [[Placement]] of an object, and isn't just a logical or structural relationship either &amp;mdash; then we create an explicit Binding object to be stored into the session.
* When connecting a [[Sequence]] to a certain [[Timeline]], we also establish a mapping between the possible media stream channels produced by the sequence and the real output slots found within the timeline.
* similarly, using a sequence within a [[meta-clip|VirtualClip]] requires to remember such a mapping.
* another example is the root [[scope|PlacementScope]], which (conceptually) is a link between the definition part of the Session and the graph of MObjects, which are the session's contents.
!Properties of a Binding
Binding is a relation entity, maintaining a link between parts of the session. This 1:1 relation may be part of a n:1 (or maybe even n:m?) relation. In addition to representing this link, a binding allows to maintain a set of ''mappings'' attached to this link.
This creates a typing related problem: In order to maintain the link and the mappings, in each case we need a common denominator to subsume the elements to be maintained.
* in the case of the basic link, this common denominator is given by the basic structure of the HighLevelModel: ~MObjects attached by Placements. Consequently, the link maintained through a binding is limited to linking together first-class entities within the model, and the implementation is tied into the existing mechanisms for associating Placements (&amp;rarr; PlacementScope and PlacementIndex)
* in the case of the attached mappings it seems we're best off limiting ourselves to symbolic mappings (ID mappings). Because, going beyond that would either create sub-groupings (based on varying target types to be mapped), or push the »everything is an object« approach beyond the beneficial level.
!Critique {{red{WIP 12/09}}}
Now I'm in doubt if there is any point in treating the &quot;binding&quot; in a abstract fashion. Is there any common denominator? It seems there is just a common conceptional pattern, but mapping this pattern onto distinct types (classes) seems unnecessary. Rather, we have three very similar kinds of ~MObjects:
* ModelRootMO
* BindingMO
* [[Track]]
While each of these actually represents a high-level concept (the session, the timeline(s) the sequence(s)) and moreover links to other parts and provides mappings, it seems there is no re-usable similarity at the code level.
//According to this reasoning, I should proceed rather with these three distinct entities and postpone any search for similarities to a later refactoring round.//
&amp;rarr; see also SessionInterface
!Problems and Questions {{red{WIP 3/10}}}
Meanwhile I've settled down on implementing the [[top-level entities|Timeline]] as façade assets, backed by a {{{Placement&lt;Binding&gt;}}}. But the addressing and mapping of media channels still remains unsettled. Moreover, what happens when binding the sequence into a VirtualClip?
* because a Binding was assumed to link two entities, I concluded that the binding is dependent on those entities
* but then the two cases don't match:
** Timeline is an asset, not an MObject. Having a Timeline MObject would clearly be redundant. But then, consequently, //binding has to provide the global pipes,// because we need the possibility to attach effects to them (by placing the effects into the binding's scope)
** to the contrary, a VirtualClip requires somehow a ClipMO, which then in turn would need a channel configuration
*** so either, we could rework the clip asset into such a channel configuration (it looks pretty much redundant otherwise)
*** or the binding could take on the role of the channel configuration (which would be more in-line with the first case)
** unfortunately, both solutions bring the binding rather on the level of an asset (a media or channel configuration).
* //multiplicity// is yet another open question:
** can multiple placements refer to the same binding?
** what would be the semantics or such an arrangement?
** if we decide against it, what happens if we nevertheless encounter this situation?
&amp;harr; related to the handling of MultichannelMedia, which likewhise needs to be re-adjusted meanwhile!
!Implementation
On the implementation side, we use a special kind of MObject, acting as an anchor and providing an unique identity. Like any ~MObject, actually a placement establishes the connection and the scope, and typically constitutes a nested scope (e.g. the scope of all objects //within// the sequence to be bound into a timeline)
</pre>
</div>
<div title="BindingScopeProblem" modifier="Ichthyostega" modified="200910181440" created="200910181435" tags="SessionLogic spec" changecount="4">
<pre>There is some flexibility in the HighLevelModel, allowing to attach the same [[Sequence]] onto multiple [[timelines|Timeline]] or even into a [[meta-clip|VirtualClip]]. Thus, while there is always an containment relation which can be used to define the current PlacementScope, we can't always establish an unique path from any given location up to the model root. In the most general case, we have to deal with a DAG, not a tree.
!solution idea
Transform the DAG into a tree by //focussing//&amp;nbsp; on the current situation and context. Have a state containing the //current path.// &amp;rarr; QueryFocus
!!detail decisions
!!!!how to represent scoping
We us a 2-layer approach: initially, containment is implemented through a registration in the PlacementIndex. Building on that, scope is layered on top as an abstraction, which uses the registered containment relation, but takes the current access path into account at the critical points (where a [[binding|BindingMO]] comes into play)
!!!!the basic containment tree
each Placement (with the exception of the root) gets registered as contained in yet another Placement. This registration is entirely a tree, and thus differs from the real scope nesting at the Sequence level: The scopes constituting Sequences and Timelines are registered as siblings, immediately below the root. This has some consequences
# Sequences as well as Timelines can be discovered as contents of the model root
# ScopePath digresses at Sequence level from the basic containment tree
</pre>
</div>
<div title="BuildProcess" modifier="Ichthyostega" modified="200906071813" created="200706190658" tags="Builder operational img" changecount="30">
<pre>All decisions on //how // the RenderProcess has to be carried out are concentrated in this rather complicated Builder Subsystem. The benefit of this approach is, besides decoupling of subsystems, to keep the actual performance-intensive video processing code as simple and transparent as possible. The price, in terms of increased complexity &amp;mdash; to pay in the Builder &amp;mdash; can be handled by making the Build Process generic to a large degree. Using a Design By Contract approach we can decompose the various decisions into small decision modules without having to trace the actual workings of the Build Process as a whole.
[&gt;img[Outline of the Build Process|uml/fig129413.png]]
The building itself will be broken down into several small tool application steps. Each of these steps has to be mapped to the MObjects found on the [[Timeline]]. Remember: the idea is that the so called &quot;[[Fixture]]&quot; contains only [[ExplicitPlacement]]s which in turn link to MObjects like Clips, Effects and [[Automation]]. So it is sufficient to traverse this list and map the build tools to the elements. Each of these build tools has its own state, which serves to build up the resulting Render Engine. So far I see two steps to be necessary:
* find the &quot;Segments&quot;, i.e. the locations where the overall configuration changes
* for each segment: generate a ProcNode for each found MObject and wire them accordingly
Note, //we still have to work out how exactly building, rendering and playback work// together with the backend-design. The build process as such doesn't overly depend on these decisions. It is easy to reconfigure this process. For example, it would be possible as well to build for each frame separately (as Cinelerra2 does), or to build one segment covering the whole timeline (and handle everything via [[Automation]]
&amp;rarr;see also: [[Builder Overview|Builder]]
&amp;rarr;see also: BasicBuildingOperations
&amp;rarr;see also: BuilderStructures
&amp;rarr;see also: BuilderMechanics
&amp;rarr;see also: PlanningBuildFixture
&amp;rarr;see also: PlanningSegementationTool
&amp;rarr;see also: PlanningNodeCreatorTool
[img[Colaborations in the Build Process|uml/fig128517.png]]
</pre>
</div>
<div title="BuildRenderNode" modifier="Ichthyostega" modified="200806030139" created="200805300137" tags="Builder impl" changecount="12">
<pre>Actually setting up and wiring a [[processing node|ProcNode]] involves several issues and is carried out at the lowest level of the build process.
It is closely related to &amp;rarr; [[the way nodes are operated|NodeOperationProtocol]] and the &amp;rarr; [[mechanics of the render process|RenderMechanics]]
!!!object creation
The Nodes are small polymorphic objects, carrying configuration data, but no state. They are [[specially allocated|ManagementRenderNodes]], and the object creation is accessible by means of the NodeFactory solely. They //must not be deallocated manually.// The decision of what concrete node type to create depends on the actual build situation and is worked out by the combination of [[mould|BuilderMould]] and [[processing pattern|ProcPatt]] at the current OperationPoint, issuing a call to one of NodeFactory's {{{operator()}}}
!!!node, plugin and processing function
Its a good idea to distinguish clearly between those concepts. A plugin is a piece of (possibly external) code we use to carry out operations. We have to //discover its properties and capabilities.// We don't have to discover anything regarding nodes, because we (Lumiera builder and renderengine) are creating, configuring and wiring them to fit the specific purpose. Both are to be distinguished from processing functions, which do the actual calculations on the media data. Every node typically encompasses at least one processing function, which may be an internal function in the node object, a library function from Lumiera or GAVL, or external code loaded from a plugin.
!!!node interfaces
As a consequence of this distinctions, in conjunction with a processing node, we have to deal with three different interfaces
* the __build interface__ is used by the builder to set up and wire the nodes. It can be full blown C++ (including templates)
* the __operation interface__ is used to run the calculations, which happens in cooperation of Proc-Layer and Backend. So a function-style interface is preferable.
* the __inward interface__ is accessed by the processing function in the course of the calculations to get at the necessary context, including in/out buffers and param values.
!!!wiring data connections
A node //knows its predecessors, but not its successors.// When being //pulled//&amp;nbsp; in operation, it can expect to get a frame provider for accessing the in/out buffer locations (some processing functions may be &quot;in-place capable&quot;, but that's only a special case of the former). At this point, the ''pull principle'' comes into play: the node may request input frames from the frame provider, passing its predecessors as a ''continuation''.
With regard to the build process, the wiring of data connections translates into providing the node with its predecessors and preconfiguring the possible continuations. While in the common case, a node has just one input/output and pulls from its predecessor a frame for the same timeline position, the general case can be more contrived. A node may process N buffers in parallel and may require several different time positions for it's input, even at a differing framerate. So the actual source specification is (predNode,time,frameType). The objective of the wiring done in the build process is to factor out the parts known in advance, while in the render process only the variable part need to be filled in. Or to put it differently: wiring builds a higher order function (time)-&gt;(continuation), where continuation can be invoked to get the desired input frame.
!!!wiring control conections
In many cases, the parameter values provided by these connections aren't frame based data, rather, the processing function needs a call interface to get the current value (value for a given time), which is provided by the parameter object. Here, the wiring needs to link to the suitable parameter instance, which is located within the high-level model (!). As an additional complication, calculating the actual parameter value may require a context data frame (typically for caching purposes to speed up the interpolation). While these parameter context data frames are completely opaque for the render node, they have to be passed in and out similar to the state needed by the node itself, and the wiring has to prepare for accessing these frames too.
</pre>
</div>
<div title="Builder" modifier="Ichthyostega" modified="201004032310" created="200706220317" tags="def overview" changecount="29">
<pre>The Builder takes some MObject/[[Placement]] information (called Timeline) and generates out of this a Render Engine configuration able to render this Objects. It does all decisions and retrieves the current configuration of all objects and plugins, so the Render Engine can just process them stright forward.
The Builder is the central part of the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]]
&lt;br/&gt;
As the builder [[has to create a render node network|BuilderModelRelation]] implementing most of the features and wiring possible with the various MObject kinds and placement types, it is a rather complicated piece of software. In order to keep it manageable, it is broken down into several specialized sub components:
* clients access builder functionality via the BuilderFacade
* the [[Proc-Layer-Controller|Controller]] initiates the BuildProcess and does the overall coordination of scheduling edit operations, rebuilding the fixture and triggering the Builder
* to carry out the building, we use several primary tools (SegmentationTool, NodeCreatorTool,...), together with a BuilderToolKit to be supplied by the [[tool factory|BuilderToolFactory]]
* //operating the Builder// can be viewed at from two different angles, either emphasizing the [[basic building operations|BasicBuildingOperations]] employed to assemble the render node network, or focussing rather at the [[mechanics|BuilderMechanics]] of cooperating parts while processing.
* besides, we can identify a small set of elementary situations we call [[builder primitives|BuilderPrimitives]], to be covered by the mentioned BuilderToolKit; by virtue of [[processing patterns|ProcPatt]] they form an [[interface to the rule based configuration|BuilderRulesInterface]].
* the actual building (i.e. the application of tools to the timeline) is done by the [[Assembler|BuilderAssembler]], which is basically a collection of functions (but has a small amount of global configuration state)
* any non-trivial wiring of render nodes, tracks, pipes and [[automation|Automation]] is done by the services of the [[connection manager|ConManager]]
</pre>
</div>
<div title="BuilderMechanics" modifier="Ichthyostega" modified="200810170220" created="200805210256" tags="Builder design operational" changecount="3">
<pre>The cooperation of several components creates a context of operation for the primary builder working tool, the [[node creator|PlanningNodeCreatorTool]]:
* the BuilderToolFactory acts as the &quot;builder for the builder tools&quot;, i.e. we can assume to be able to retrive all needed primary tools and elementary tools from this factory, completely configured and ready to use.
* the [[Assembler|BuilderAssembler]] has the ability to consume objects from the high level model and feed them to the node creator (which translates into a dispatch of individual operations suited to the objects to be treated). This involves some sort of scheduling or ordering of the operaions, which is the only means to direct the overall process such as to create a sensible and usable result. //This is an fundamental design decision:// the actual working tools have no hard wired knowledge of the &quot;right process&quot;, which makes the whole Builder highly configurable (&quot;open&quot;).
* the [[connection manager|ConManager]] on the contrary is a passive service provider. Fed with [[wiring requests|WiringRequest]], he can determine if a desired connection is possible, and what steps to take to implement it; the latter recursively creates further building requests to satisfy by the assembler, and possibly new wiring requests.
!!pattern of operation
The working pattern of this builder mechanics can be described as triggering, enqueuing, priorizing, recursing and exhausting. Without the priorizing part, it would be a depth-first call graph without any context state, forcing us to have all cross reference information available at every node or element to be treated. We prefer to avoid this overhead by ordering the operations into several phases and within these phases into correlated entities with the help of a ''weighting function'' and scheduling with a ''priority queue''
!!call chain
After preparing the tools with the context state of this build process, the assembler drives the visitation process in the right order. The functions embedded within the visitor (NodeCreatorTool) for treating specific kinds of objects in turn use the toolkit (=the fully configured tool factory) to get the mould(s) for the individual steps they need to carry out. This involves preparing the mould (with the high-level object currently in-the-works, a suitable processing pattern and additional references), followed by operating the mould. The latter &quot;plays&quot; the processing pattern in the context of the mould, which, especially with the help of the operation point, carries out the actual building and/or connecting step. While doing so, the node factory will be invoked, which in turn invokes the wiring factory and thus pre-determines the node's prospective mode of operation when later called for rendering.
</pre>
</div>
<div title="BuilderModelRelation" modifier="Ichthyostega" created="201004032311" tags="Builder design draft" changecount="1">
<pre>[&gt;img[Builder creating the Model|uml/fig132868.png]]</pre>
</div>
<div title="BuilderMould" modifier="Ichthyostega" modified="200805270325" created="200805260248" tags="def" changecount="2">
<pre>The [[Builder]] uses different kinds of tools for creating a network of render nodes from a given high-level model. When breaking down this (necessarily complex) process into small manageable chunks, we arrive at [[elementary building situations|BuilderPrimitives]]. For each of these there is a specialized tool. We denote these tools as &quot;moulds&quot; because they are a rather passive holder for the objects to be attached and wired up. They are shaped according to the basic form the connections have to follow for each of these basic situations:
* attaching an effect to a pipe
* combining pipes via a transition
* starting out a pipe from a source reader
* general connections from the exit node of a pipe to the port of another pipe
In all those cases, the active part is provided by [[processing patterns|ProcPatt]] &amp;mdash; sort of micro programs executed within the context of a given mould: the processing pattern defines the steps to take (in the standard/basic case this is just &quot;attach&quot;), while the mould holds and provides the location where these steps will operate. Actually, this location is represented as a OperationPoint, provided by the mould and abstracting the details of making multi-channel connections.
</pre>
</div>
<div title="BuilderPrimitives" modifier="Ichthyostega" modified="200805260332" created="200805210327" tags="impl spec Builder img" changecount="25">
<pre>While assembling and building up the render engines node network, a small number of primitive building situations is encountered repeatedly. The BuilderToolKit provides a &quot;[[mould|BuilderMould]]&quot; for each of these situations, typically involving parametrisation and the application of a [[processing pattern|ProcPatt]].
The ''Lifecycle'' of such a mould starts out by arming it with the object references involved into the next building step. After conducting this building step, the resulting render nodes can be found &amp;mdash; depending on the situation &amp;mdash; attached either to the same mould, or to another kind of mould, but in any case ready to be included in the next building step. Thus, //effectively//&amp;nbsp; the moulds are //used to handle the nodes being built,// due to the fact that the low-level model (nodes to be built) and the high-level model (objects directing what is to be built) are //never connected directly.//
!List of elementary building situations
!!!inserting an Effect or Plugin
[&gt;img[draw/builder-primitives1.png]]
The __~PipeMould__ is used to chain up the effects attached to a clip (=local pipe) or global pipe (=bus)
* participating: a Pipe and an Effect
* point of reference: current exit node of the pipe
* result: Effect appended at the pipe's exit node
* returns: ~PipeMould holding onto the new exit node
@@clear(right):display(block):@@
!!!attaching a transition
[&gt;img[draw/builder-primitives2.png]]
After having completed N pipe's node chains, a __~CombiningMould__ can be used to join them into a [[transition|TransitionsHandling]]
* participating: N pipe's exit nodes, transition
* point of reference: N exit nodes corresponding to (completed) pipes
* result: transition has been attached with the pipe's exit nodes, new wiring requests created attached to the transition's exit node(s)
* returns: ~WiringMould, connected with the created wiring request
Using this mould implicitly &quot;closes&quot; the involved pipes, which means that we give up any reference to the exit node and can't build any further effect attached to this pipes. Generally speaking, &quot;exit node&quot; isn't a special kind of node, rather it's a node we are currently holding on. Similarly, there is nothing directly correlated to a pipe within the render nodes network after we are done with building the part of the network corresponding to the pipe; the latter serves rather as a blueprint for building, but isn't an entity in the resulting low-level model.
Actually, there is {{red{planned}}} a more general (and complicated) kind of transition, which can be inserted into N data connections without joining them together into one single output, as the standard transitions do. The ~CombiningMould can handle this case too by just returning N wiring moulds as a result.
@@clear(right):display(block):@@
!!!building a source connection
[&gt;img[draw/builder-primitives3.png]]
The __~SourceChainMould__ is used as a starting point for any further building, as it results in a local pipe (=clip) rooted at the clip source port. This reflects the fact that the source readers (=media access points) are the //leaf nodes// in the node graph we are about to build.
* participating: source port of a clip, media access point, [[processing pattern|ProcPatt]]
* point of reference: //none//
* result: processing pattern has been //executed//, resulting in a chain of nodes from the source reader to the clip source port
* returns: ~PipeMould holding onto the new exit node (of a yet-empty pipe)
@@clear(right):display(block):@@
!!!wiring a general connection
Any wiring (outside the chain of effects within a pipe) is always done from exit nodes to the port of another pipe, requiring an [[wiring request|WiringRequest]] already checked and deemed resolvable. Within the __~WiringMould__ the actual wiring is conducted, possibly adding a summation node (called &quot;overlayer&quot; in case of video) and typically a fader element (the specific setup to be used is subject to configuration by processing patterns)
* participating: already verified connection request, providing a Pipe and an exit node; a processing pattern and a Placement
* points of reference: exit node and (optionally) starting point of a pipe's chain (in case there are already other connections)
* result: summation node prepended to the port of the pipe, processing pattern has been //executed// for building the connection from the exit node to the pipe's port, ParamProvider has been setup in [[accordance|PlacementDerivedDimension]] to the Placement.
* returns: ~PipeMould holding onto the destination pipe's exit node, ~WiringMould holding onto the port side of the same pipe, i.e. the destination where further connections will insert summation nodes. {{red{TODO how to handle the //empty//-case?}}}
[&gt;img[draw/builder-primitives4.png]]
@@clear(right):display(block):@@
</pre>
</div>
<div title="BuilderStructures" modifier="Ichthyostega" modified="200906071812" created="200706250734" tags="overview design Builder img" changecount="22">
<pre>* the MObjects implement //Buildable//
* each Buildable can &quot;receive&quot; a Tool object and apply it
* the different Tool objects are iterated/mapped onto the list of MObjects in the [[Timeline]]
* __Rationale__
** the MObject class hierarchy is rather fixed (it is unlikely the we will be adding much new MObject subclasses)
** so this design makes it easy to add new Tool subclasses, and within each Tool subclass, all operations on the different MObject classes are grouped together, so it is easy to see what is going on.
** a given Tool instance can carry state while being iterated, so we don't need any global (or object-global) variables to hold the result of the build process
This programming technique is often referred to as [[&quot;double dispatch&quot; or &quot;visitor&quot;|VisitorUse]]. We use a specialized library implementation of this pattern &amp;mdash; heavily inspired by the [[Loki library|http://loki-lib.sourceforge.net/]]. We use this approach not only for the builder, but also for carrying out operations on the objects in the session in a typesafe manner.
It is the low level foundation of the actual [[building operations|BasicBuildingOperations]] necessary to create render nodes starting from the given high level model.
[img[Entities cooperating in the Builder|uml/fig129285.png]]
!Colaborations
While building, the application of such a visiting tool (especially the [[NodeCreatorTool|PlanningNodeCreatorTool]]) is embedded into an execution context formed by the BuilderToolFactory providing our BuilderToolKit, the [[Assembler|BuilderAssembler]] and [[connection manager|ConManager]]. The colaboration of these parts can be seen as the [[mechanics of the builder|BuilderMechanics]] &amp;mdash; sort of the //outward view//, contrary to the //invard aspects// visible when focussing on how the nodes are put together.
[img[Colaborations in the Build Process|uml/fig128517.png]]
</pre>
</div>
<div title="BuilderToolKit" modifier="Ichthyostega" modified="200806211539" created="200805210308" tags="impl Builder" changecount="12">
<pre>Besides the primary working tool within the builder (namely the [[Node Creator Tool|PlanningNodeCreatorTool]]), on a lower level, we encounter several [[elementary building situations|BuilderPrimitives]] &amp;mdash; and for each of these elementary situations we can retrieve a suitable &quot;fitting tool&quot; or [[mould|BuilderMould]]. The palette of these moulds is called the ''tool kit'' of the builder. It is subject to configuration by rules.
!!addressing a mould
All mould instances are owned and managed by the [[tool factory|BuilderToolFactory]], and can be referred to by their type (PipeMould, CombiningMould, SourceChainMould, WiringMould) and a concrete object instance (of suitable type). The returned mould (instance) acts as a handle to stick together the given object instance (from the high-level model) with the corresponding point in the low-level node network under construction. As consequence of this approach, the tool factory instance holds a snapshot of the current building state, including all the active spots in the build process. As the latter is driven by objects from the high-level model appearing (in a sensible order &amp;rarr; see BuilderMechanics) within the NodeCreatorTool, new moulds will be created and fitted as necessary, and existing moulds will be exhausted when finished, until the render node network is complete.
!!configuring a mould
As each mould kind is different, it has a {{{prepare(...)}}} function with suitably typed parameters. The rest is intended to be self-configuring (for example, a ~CombiningMould will detect the actual kind of Transition and select the internal mode of operation), so that it's sufficient to just call {{{operate()}}}
!!sequence of operations
When {{{operate()}}} doesn't throw, the result is a list of //successor moulds// &amp;mdash; you shouldn't use the original mould after triggering its operation, because it may have been retracted as a result and reused for another purpose by the tool factory. It is not necessary to store these resulting moulds either (as they can be retrieved as described above), but they can be used right away for the next building step if applicable. In the state they are returned from a successful building step (mould operation = execution of a contained [[processing pattern|ProcPatt]]), they are usually already holding a reference to the part of the network just created and need to be configured only with the next high-level object (effect, placement, pipe, processing pattern or similar, depending on the concrete situation) in order to carry out the next step.
!!single connection step
at the lowest level within the builder there is the step of building a //connection.// This step is executed by the processing pattern with the help of the mould. Actually, making such a connection is more complicated, because in the standard case it will connect N media streams simultaneously (N=2 for stereo sound or 3D video, N=6 for 5.1 Surround, N=9 for 2nd order Ambisonics). These details are encapsulated within the OperationPoint, which is provided by the mould and exhibits a common interface for the processing pattern to express the connecting operation.
&amp;rarr;see also: BuilderPrimitives for the elementary working situations corresponding to each of these [[builder moulds|BuilderMould]]
</pre>
</div>
<div title="ColorPalette" modifier="Ichthyostega" modified="200807131329" created="200706190033" tags="excludeMissing" changecount="14">
<pre>Background: #fefefd
Foreground: #000
PrimaryPale: #8fb
PrimaryLight: #4dc9a7
PrimaryMid: #16877a
PrimaryDark: #0f3f56
SecondaryPale: #ffc
SecondaryLight: #fe8
SecondaryMid: #db4
SecondaryDark: #841
TertiaryPale: #eef
TertiaryLight: #ccd
TertiaryMid: #99a
TertiaryDark: #667
Error: #f88</pre>
</div>
<div title="Command" modifier="Ichthyostega" modified="200907212316" created="200906072020" tags="def SessionLogic draft" changecount="8">
<pre>Within Proc-Layer, a Command is the abstract representation of a single operation or a compound of operations mutating the HighLevelModel.
Thus, each command is a ''Functor'' and a ''Closure'' ([[command pattern|http://en.wikipedia.org/wiki/Command_pattern]]), allowing commands to be treated uniformly, enqueued in a [[dispatcher|ProcDispatcher]], logged to the SessionStorage and registered with the UndoManager.
Commands are //defined// using a [[fluent API|http://en.wikipedia.org/wiki/Fluent_interface]], just by providing apropriate functions. Additionally, the Closure necessary for executing a command is built by binding to a set of concrete parameters. After reaching this point, the state of the internal representation could be serialised by plain-C function calls, which is important for integration with the SessionStorage.
&amp;rarr; see CommandDefinition
&amp;rarr; see CommandHandling
&amp;rarr; see CommandLifecycle
&amp;rarr; see CommandUsage
</pre>
</div>
<div title="CommandDefinition" modifier="Ichthyostega" modified="200907220310" created="200906140124" tags="SessionLogic spec draft decision design" changecount="10">
<pre>Commands can be identified and accessed //by name// &amp;mdash; consequently there needs to be an internal command registry, including a link to the actual implementing function, thus allowing to re-establish the connection between command and implementing functions when de-serialising a persisted command. To create a command, we need to provide the following informations
* operation function actually implementing the command
* function to [[undo|UndoManager]] the effect of the command
* function to capture state to be used by UNDO.
* a set of actual parameters to bind into these functions (closure).
!Command definition object
The process of creating a command by providing these building blocks is governed by a ~CommandDef helper object. According to the [[fluent definition style|http://en.wikipedia.org/wiki/Fluent_interface]], the user is expected to invoke a chain of definition functions, finally leading to the internal registration of the completed command object, which then might be dispatched or persisted. For example
{{{
CommandDefinition (&quot;test.command1&quot;)
.operation (command1::operate) // provide the function to be executed as command
.captureUndo (command1::capture) // provide the function capturing Undo state
.undoOperation (command1::undoIt) // provide the function which might undo the command
.bind (obj, val1,val2) // bind to the actual command parameters (stores command internally)
.executeSync(); // convenience call, forwarding the Command to dispatch.
}}}
!Operation parameters
While generally there is //no limitation// on the number and type of parameters, the set of implementing functions and the {{{bind(...)}}} call are required to match. Inconsistencies will be detected by the compiler. In addition to taking the //same parameters as the command operation,// the {{{captureUndo()}}} function is required to return (by value) a //memento//&amp;nbsp; type, which, in case of invoking the {{{undo()}}}-function, will be provided as additional parameter. To summarise:
|!Function|&gt;|!ret(params)|
| operation| void |(P1,..PN)|
| captureUndo| MEM |(P1,..PN)|
| undoOperation| void |(P1,..PN,MEM)|
| bind| void |(P1,..PN)|
Usually, parameters should be passed //by value// &amp;mdash; with the exception of target object(s), which are typically bound as MObjectRef, causing them to be resolved at commad execution time (late binding).
</pre>
</div>
<div title="CommandHandling" modifier="Ichthyostega" modified="200910090044" created="200906072048" tags="SessionLogic spec draft decision design img" changecount="31">
<pre>Organising any ''mutating'' operations executable by the user (via GUI) by means of the [[command pattern|http://en.wikipedia.org/wiki/Command_pattern]] can be considered //state of the art//&amp;nbsp; today. First of all, it allows to discern the specific implementation operations to be called on one or several objects within the HighLevelModel from the operation requested by the user, the latter being rather a concept. A command can be labeled clearly, executed under controlled circumstances, allowing transactional behaviour.
!Defining commands
[&gt;img[Structure of Commands|uml/fig134021.png]] Basically, a command could contain arbitrary operations, but we'll assume that it causes a well defined mutation within the HighLevelModel, which can be ''undone''. Thus, when defining (&amp;rarr;[[syntax|CommandDefinition]]) a command, we need to specify not only a function doing the mutation, but also another function which might be called later to reverse the effect of the action. Besides, the action operates on a number of ''target'' objects and additionally may require a set of ''parameter'' values. These are to be stored explicitly within the command object, thus creating a ''closure'' &amp;mdash; the operation //must not//&amp;nbsp; rely on other hidden parameters (maybe with the exception of very generic singleton system services?).
Theoretically, defining the &quot;undo&quot; operation might utilise two different approaches:
* specifying an //inverse operation,// known to cancel out the effect of the command
* capturing a //state memento,// which can later be played back to restore the state found prior to executing the command.
While obviously the first solution is much simpler to implement on behalf of the command framework, the second solution has distinct advantages, especially in the context of an editing application: there might be rounding or calculation errors, the inverse might be difficult to define correctly, the effect of the operation might depend on circumstances, be random, or might even trigger a resolution operation to yield the final result. Thus the decision is within Lumiera Proc-Layer to //favour state capturing// &amp;mdash; but in a modified, semi-manual and not completely exclusive way.
!Undo state
While the usual »Memento« implementation might automatically capture the whole model (resulting in a lot of data to be stored and some uncertainty about the scope of the model to be captured), in Lumiera we rely instead on the client code to provide a ''capture function''&amp;nbsp;and a ''playback function'' alongside with the actual operation. To help with this task, we provide a set of standard handlers for common situations. This way, operations might capture very specific information, might provide an &quot;intelligent undo&quot; to restore a given semantic instead of just a fixed value &amp;mdash; and moreover the client code is free actually to employ the &quot;inverse operation&quot; model in special cases where it just makes more sense than capturing state.
!Handling of commands
A command may be [[defined|CommandDefinition]] completely from scratch, or it might just serve as a CommandPrototype with specific targets and parameters. The command could then be serialised and later be recovered and re-bound with the parameters, but usually it will be handed over to the ProcDispatcher, pending execution. When ''invoking'', the handling sequence is to [[log the command|SessionStorage]], then call the ''undo capture function'', followed from calling the actual ''operation function''. After success, the logging and [[undo registration|UndoManager]] is completed. In any case, finally the ''result signal'' (a functor previously stored within the command) is emitted. {{red{10/09 WIP: not clear if we acutally implement this concept}}}
By design, commands are single-serving value objects; executing an operation repeatedly requires creating a collection of command objects, one for each invocation. While nothing prevents you from invoking the command operation functor several times, each invocation will overwrite the undo state captrued by the previous invocation. Thus, each command instance should bes seen as the promise (or later the trace) of a single operation execution. In a similar vein, the undo capturing should be defined as to be self sufficient, so that invoking just the undo functor of a single command performes any necessary steps to restore the situation found before invoking the corresponding mutation functor &amp;mdash; of course only //with respect to the topic covered by this command.// So, while commands provide a lot of flexibility and allow to do a multitude of things, certainly there is an intended CommandLifecycle.
&amp;rarr; command [[definition|CommandDefinition]] and [[-lifecycle|CommandLifecycle]]
&amp;rarr; more on possible [[command usage scenarios|CommandUsage]]
&amp;rarr; more details regarding [[command implementation|CommandImpl]]
</pre>
</div>
<div title="CommandImpl" modifier="Ichthyostega" modified="200909291434" created="200909291424" tags="spec impl" changecount="9">
<pre>Commands are separated in a handle (the {{{control::Command}}}-object), to be used by the client code, and an implementation level, which is managed transparently behind the stages. Client code is assumed to build a CommandDefinition at some point, and from then on to access the command ''by ID'', yielding the command handle.
Binding of arguments, invocation and UNDO all are accessible through this frontend.
!Infrastructure
To support this handling scheme, some infrastructure is in place:
* a command registry maintains the ID &amp;harr; Command relation.
* indirectly, through a custom alloctaor, the registry is also involved into allocation of the command implementation frame
* this implementation frame combines
** an operation mutation and an undo mutation
** a closure, implemented through an argument holder
** an undo state capturing mechanism, based on a capturing function provided on definition
* performing the actual execution is delegated to a handling pattern object, accessed by name.
</pre>
</div>
<div title="CommandLifecycle" modifier="Ichthyostega" modified="200910090043" created="200907210135" tags="SessionLogic spec draft design img" changecount="19">
<pre>[&lt;img[Structure of Commands|uml/fig135173.png]]
While generally the command framework was designed to be flexible and allow a lot of different use cases, execution paths and to serve various goals, there is an ''intended lifecycle'' &amp;mdash; commands are expected to go through several distinct states.
The handling of a command starts out with a ''command ID'' provided by the client code. Command ~IDs are unique (human readable) identifiers and should be organised in a hierarchical fashion. When provided with an ID, the CommandRegistry tries to fetch an existing command definition. In case this fails, we enter the [[command definition stage|CommandDefinition]], which includes specifying functions to implement the operation, state capturing and UNDO. When all these informations are available, the entity is called a ''command definition''. Conceptually, it is comparable to a //class// or //meta object.//
By ''binding'' to specific operation arguments, the definition is //armed up//&amp;nbsp; and becomes a real ''command''. This is similar to creating an instance from a class. Behind the scenes, storage is allocated to hold the argument values and any state captured to create the ability to UNDO the command's effect later on.
A command is operated or executed by passing it to an ''execution pattern'' &amp;mdash; there is a multitude of possible execution patterns to choose from, depending on the situation.
{{red{WIP... details of ~ProcDispatcher not specified yet}}}
When a command has been executed (and maybe undone), it's best to leave it alone, because the UndoManager might hold a reference. Anyway, a ''clone of the command'' could be created, maybe bound with different arguments and treated separately from the original command.
!State predicates
* fetching an non-existent command raises an ~LUMIERA_ERROR_INVALID_COMMAND
* a command definition becomes //valid// ({{{bool true}}}) when all necessary functions are specified. Technically this coincides with the creation of a CommandImpl frame behind the scenes, which also causes the Command (frontend/handle object) to evaluate to {{{true}}} in bool context from then on.
* when, in addition to the above, the command arguments are bound, it becomes //executable.//
* after the (fist) execution, the command gets also //undo-able.//
State predicates are accessible through the Command (frontend); additionally there are static query functions in class {{{Command}}}
</pre>
</div>
<div title="CommandUsage" modifier="Ichthyostega" modified="200908091509" created="200907212338" tags="SessionLogic draft operational" changecount="8">
<pre>//for now (7/09) I'll use this page to collect ideas how commands might be used...//
* use a command for getting a log entry and an undo possibility automatically
* might define, bind and then execute a command at once
* might define it and bind it to a standard set of parameters, to be used as a prototype later.
* might just create the definition, leaving the argument binding to the actual call site
* execute it and check the success/failure result
* just enqueue it, without caring for successful execution
* place it into a command sequence bundle
* repeat the execution
!!!a command definition....
* can be created from scratch, by ID
* can be re-accessed, by ID
* can't be modified once defined (this is to prevent duplicate definitions with the same ID)
* but can be dropped, which doesn't influence already existing dependent command instances
* usually will be the starting point for creating an actual command by //binding//
!!!a command instance....
* normally emerges from a definition by binding arguments
* the first such binding will create a named command registration
* but subsequent accesses by ID or new bindings create an anonymous clone
* which then in turn might then be registered explicitly with a new ID
* anonymous command instances are managed by referral and ref-counting
!!!an execution pattern....
* can only be defined in code by a class definition, not at runtime
* subclasses the ~HandlingPattern interface and uses an predefined ID (enum).
* a singleton instance is created on demand, triggered by referring the pattern's ID
* is conceptually //stateless// &amp;mdash; of course there can be common configuration values
* is always invoked providing a concrete command instance to execute
* is configured into the command instance, to implement the command's invocation
* returns a duck-typed //result// object
</pre>
</div>
<div title="ConManager" modifier="Ichthyostega" modified="200810060300" created="200806050208" tags="def Builder" changecount="4">
<pre>The Connection Manager is a service for wiring connections and for querying information and deriving decisions regarding various aspects of data streams and the possibility of connections. The purpose of the Connection Manager is to isolate the [[Builder]], which is client of this information and decision services, from the often convoluted details of type information and organizing a connection.
!control connections
my intention was that it would be sufficient for the builder to pass an connection request, and the Connection Manager will handle the details of establishing a control/parameter link.
{{red{TODO: handling of parameter values, automation and control connections still need to be designed}}}
!data connections
Connecting data streams of differing type involves a StreamConversion. Mostly, this aspect is covered by the [[stream type system|StreamType]]. The intended implementation will rely partially on [[rules|ConfigRules]] to define automated conversions, while other parts need to be provided by hard wired logic. Thus, regarding data connections, the ConManager can be seen as a specialized Facade and will delegate to the &amp;rarr; [[stream type manager|STypeManager]]
* retrieve information about capabilities of a stream type given by ID
* decide if a connection is possible
* retrieve a //strategy// for implementing a connection
</pre>
</div>
<div title="Concepts" modifier="Ichthyostega" modified="200910312026" created="200910311729" tags="overview" changecount="5">
<pre>This index refers to the conceptual, more abstract and formally specified aspects of the Proc-Layer and Lumiera in general.
More often than not, these emerge from immediate solutions, being percieved as especially expressive, when taken on, yielding guidance by themselves. Some others, [[Placements|Placement]] and [[Advice]] to mention here, immediately substantiate the original vision.</pre>
</div>
<div title="ConfigQuery" modifier="Ichthyostega" modified="200804110335" created="200801181308" tags="def" changecount="5">
<pre>Configuration Queries are requests to the system to &quot;create or retrieve an object with //this and that // capabilities&quot;. They are resolved by a rule based system ({{red{planned feature}}}) and the user can extend the used rules for each Session. Syntactically, they are stated in ''prolog'' syntax as a conjunction (=logical and) of ''predicates'', for example {{{stream(mpeg), pipe(myPipe)}}}. Queries are typed to the kind of expected result object: {{{Query&lt;Pipe&gt; (&quot;stream(mpeg)&quot;)}}} requests a pipe excepting/delivering mpeg stream data &amp;mdash; and it depends on the current configuration what &quot;mpeg&quot; means. If there is any stream data producing component in the system, which advertises to deliver {{{stream(mpeg)}}}, and a pipe can be configured or connected with this component, then the [[defaults manager|DefaultsManagement]] will create/deliver a [[Pipe|PipeHandling]] object configured accordingly.
&amp;rarr; [[Configuration Rules system|ConfigRules]]
&amp;rarr; [[accessing and integrating configuration queries|ConfigQueryIntegration]]
</pre>
</div>
<div title="ConfigQueryIntegration" modifier="Ichthyostega" modified="201004030043" created="200804070247" tags="overview draft impl img" changecount="23">
<pre>* planning to embed a YAP Prolog engine
* currently just integrated by a table driven mock
* the baseline is a bit more clear by now (4/08)
&amp;rarr; see also ConfigRules
&amp;rarr; see also DefaultsManagement
!Use cases
[&lt;img[when to run config queries|uml/fig131717.png]]
The key idea is that there is a Rule Base &amp;mdash; partly contained in the session (building on a stock of standard rules supplied with the application). Now, whenever there is the need to get a new object, for adding it to the session or for use associated with another object &amp;mdash; then instead of creating it by a direct hard wired ctor call, we issue a ConfigQuery requesting an object of the given type with some //capabilities// defined by predicates. The same holds true when loading an existing session: some objects won't be loaded back blindly, rather they will be re-created by issuing the config queries again. Especially an important use case is (re)creating a [[processing pattern|ProcPatt]] to guide the wiring of a given media's processing pipeline.
At various places, instead of requiring a fixed set of capabilities, it is possible to request a &quot;default configured&quot; object instead, specifying just those capabilities we really need to be configured in a specific way. This is done by using the [[Defaults Manager|DefaultsManagement]] accessible on the [[Session]] interface. Such a default object query may either retrieve an already existing object instance, run further config queries, and finally result in the invocation of a factory for creating new objects &amp;mdash; guarded by rules to suit current necessities.
@@clear(left):display(block):@@
!Components and Relations
[&gt;img[participating classes|uml/fig131461.png]]
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
&lt;br/&gt;
Access point is the interface {{{ConfigRules}}}, which allowes to resolve a ConfigQuery resulting in an object with properties configured such as to fulfill the query. This whole subsystem employes quite some generic programming, because actually we don't deal with &quot;objects&quot;, but rather with similar instantiations of the same functionality for a collection of different object types. For the purpose of resolving these queries, the actual kind of object is not so much of importance, but on the caller sinde, of course we want to deal with the result of the queries in a typesafe manner.
Examples for //participating object kinds// are [[pipes|Pipe]], [[processing patterns|ProcPatt]], effect instances, [[tags|Tag]], [[labels|Label]], [[automation data sets|AutomationData]],...
@@clear(right):display(block):@@
For this to work, we need each of the //participating object types// to provide the implementation of a generic interface {{{TypeHandler}}}, which allows to access actual C/C++ implementations for the predicates usable on objects of this type within the Prolog rules. The implementation has to make sure that, alongside with each config query, there are additional //type constraints// to be regarded. For example, if the client code runs a {{{Query&lt;Pipe&gt;}}}, an additional //type guard// (implemented by a predicate {{{type(pipe)}}} has to be inserted, so only rules and facts in accordance with this type will be used for resolution.
!when querying for a [[&quot;default&quot;|DefaultsManagement]] object
[&lt;img[colaboration when issuing a defaults query|uml/fig131845.png]]
@@clear(left):display(block):@@</pre>
</div>
<div title="ConfigRules" modifier="Ichthyostega" modified="200910171607" created="200801171352" tags="overview spec Rules" changecount="14">
<pre>Many features can be implemented by specifically configuring and wiring some unspecific components. Rather than tie the client code in need of some given feature to these configuration internals, in Lumiera the client can //query // for some kind of object providing the //needed capabilities. // Right from start (summer 2007), Ichthyo had the intention to implement such a feature using sort of a ''declarative database'', e.g. by embedding a Prolog system. By adding rules to the basic session configuration, users should be able to customize the semi-automatic part of Lumiera's behaviour to great extent.
[[Configuration Queries|ConfigQuery]] are used at various places, when creating and adding new objects, as well when building or optimizing the render engine node network.
* Creating a [[pipe|PipeHandling]] queries for a default pipe or a pipe with a certain stream type
* Adding a new [[track|TrackHandling]] queries for some default placement configuration, e.g. the pipe it will be plugged to.
* when processing a [[wiring request|WiringRequest]], connection possibilities have to be evaluated.
* actually building such a connection may create additional degrees of freedom, like panning for sound or layering for video.
!anatomy of a Configuration Query
The query is given as a number of logic predicates, which are required to be true. Syntactically, it is a string in prolog syntax, e.g. {{{stream(mpeg)}}}, where &quot;stream&quot; is the //predicate, // meaning here &quot;the stream type is...?&quot; and &quot;mpeg&quot; is a //term // denoting an actual property, object, thing, number etc &amp;mdash; the actual kind of stream in the given example. Multible comma separated predicates are combined with logical &quot;and&quot;. Terms may be //variable // at start, which is denoted syntactically by starting them with a uppercase letter. But any variable term need to be //bound // to some known value while computing the solution to the query, otherwise the query fails. A failed query is treated as a local failure, which may cause some operation being aborted or just some other possibility being chosen.
Queries are represented by instantiations of the {{{Query&lt;TYPE&gt;}}} template, because their actual meaning is &quot;retrieve or create an object of TYPE, configured such that...!&quot;. At the C++ side, this ensures type safety and fosters programming against interfaces, while being implemented rule-wise by silently prepending the query with the predicate &quot;{{{object(TYPE)}}}&quot;
!executing a Configuration Query
Actually posing such an configuration query, for example to the [[Defaults Manager in the Sessison|DefaultsManagement]], may trigger several actions: First it is checked against internal object registries (depending on the target object type), which may cause the delivery of an already existing object (as reference, clone, or smart pointer). Otherwise, the system tries to figure out an viable configuration for a newly created object instance, possibly by issuing recursive queries. In the most general case this may silently impose additional decisions onto the //execution context // of the query &amp;mdash; by default the session.
!Implementation
At start and for debugging/testing, there is an ''dummy'' implementation using a map with predefined queries and answers. But for the real system, the idea is to embed a ''YAP Prolog'' engine to run the queries. This includes the task of defining and loading a set of custom predicates, so the rule system can interact with the object oriented execution environment, for example by transforming some capability predicate into virtual calls to a corresponding object interface. We need a way for objects to declare some capability predicates, together with a functor that can be executed on an object instance (and further parameters) in the cause of the evaluation of some configuration query. Type safety and diagnostics play an important role here, because effectively the rule base is a pool of code open for arbitray additions from the user session.
&amp;rarr; [[considerations for a Prolog based implementation|QueryImplProlog]]
&amp;rarr; [[accessing and integrating configuration queries|ConfigQueryIntegration]]
&amp;rarr; see {{{src/common/query/mockconfigrules.cpp}}} for the table with the hard wired (mock) answers
{{red{WARN}}} there is an interference with the (planned) Undo function: a totally correct implementation of &quot;Undo&quot; would need to remember and restore the internal state of the query system (similar to backtracking). But, more generally, such an correct implementation is not achievable, because we are never able to capture and control the whole state of a real world system doing such advanced things like video and sound processing. Seemingly we have to accept that after undoing an action, there is no guarantee we can re-do it the same way as it was first time.
</pre>
</div>
<div title="Controller" modifier="Ichthyostega" modified="200712090624" created="200706220319" tags="def" changecount="4">
<pre>Here, in the context of the Render Engine, the Controller component is responsible for triggering and coordinating the build process and for activating the backend and the Render Engine configuration created by the Builder to carry out the actual rendering. There is another Controller in the backend, the ~PlaybackController, which is in charge of the playback/rendering/display state of the application, and consequently will call this (Proc-Layer) Controller to get the necessary Render Engine.
!Facade
This is an very important external Interface, because it links together all three Layers of our current architecture. It can be used by the backend to initiate [[Render Processes (=StateProxy)|StateProxy]] and it will probably be used by the Dispatcher for GUI actions as well...
</pre>
</div>
<div title="CurrentSession" modifier="Ichthyostega" modified="200709272058" created="200709272057" tags="decision design" changecount="2">
<pre>The question is where to put all the state-like information [[associated with the current session|SessionOverview]]. Because this is certainly &quot;global&quot;, but may depend on the session or need to be configured differently when loading another session. At the moment (9/07) Ichthyo considers the following solution:
* represent all configuration as [[Asset]]s
* find a way {{red{TODO}}} how to reload the contents of the [[AssetManager]].
* completely hide the Session object behind a ''~PImpl'' smart pointer, so the session object can be switched when reloading.
* the [[Fixture]] acts as isolation layer, and all objects refered from the Fixture are refcounting smart pointers. So, even when the session gets switched, the old objects remain valid as long as needed.</pre>
</div>
<div title="DataFrame" modifier="Ichthyostega" created="200909291346" tags="def spec" changecount="1">
<pre>A ''frame of data'' is the central low-level abstraction when dealing with media data and media processing.</pre>
</div>
<div title="DefaultTiddlers" modifier="Ichthyostega" modified="200802031805" created="200706172308" changecount="6">
<pre>[[ProcLayer and Engine]]
</pre>
</div>
<div title="DefaultsImplementation" modifier="Ichthyostega" modified="200806030204" created="200802200043" tags="spec impl draft" changecount="12">
<pre>As detailed in the [[definition|DefaultsManagement]], {{{default(Obj)}}} is sort of a Joker along the lines &quot;give me a suitable Object and I don't care for further details&quot;. Actually, default objects are implemented by the {{{mobject::session::DefsManager}}}, which remembers and keeps track of anything labeled as &quot;default&quot;. This defaults manager is a singleton and can be accessed via the [[Session]] interface, meaning that the memory track regarding defaults is part of the session state. Accessing an object via the query for an default actually //tagges// this object (storing a weak ref in the ~DefsManager). Alongside with each object successfully queried via &quot;default&quot;, the degree of constriction is remembered, i.e. the number of additional conditions contained in the query. This enables us to search for default objects starting with the most unspecific.
!Skeleton
# ''search'': using the predicate {{{default(X)}}} enumerates existing objects of suitable type
#* candidates are delivered starting with the least constrained default
#* the argument is unified
#** if the rest of the query succeeds we've found our //default object// and are happy.
#** otherwise, if all enumerated solutions are exhausted without success, we enter
# ''default creation'': try to get an object fulfilling the conditions and remember this situation
#* we issue an ConfigQuery with the query terms //minus// the {{{default(X)}}} predicate
#* it depends on the circumstances how this query is handled. Typically the query resolution first searches existing objects and then creates a new instance to match the required capabilities. Usually, this process succeeds, but there can be configurations leading to failure.
#** failing the ~ConfigQuery is considered an (non-critical) exception (throws), as defaults queries are supposed to succeed
#** otherwise, the newly created object is remembered (tagged) as new default, together with the degree of constriction
!!!Implementation details
Taken precisely, the &quot;degree of constriction&quot; yields only a partial ordering &amp;mdash; but as the &quot;default&quot;-predicate is sort of a existential quantification anyways, its sole purpose is to avoid polluting the session with unnecessary default objects, and we don't need to care for absolute precision. A suitable approximation is to count the number of predicates terms in the query and use a (sorted) set (separate for each Type) to store weak refs to the the objects tagged as &quot;default&quot;
{{red{WARN}}} there is an interference with the (planned) Undo function. This is a general problem of the config queries; just ignoring this issue seems reasonable.
!!!Problems with the (preliminary) mock implementation
As we don't have a Prolog interpreter on board yet, we utilize a mock store with preconfigured answers. (see MockConfigQuery). As this preliminary solution is lacking the ability to create new objects, we need to resort to some trickery here (please look away). The overall logic is quite broken, because the system isn't capable to do any real resolution &amp;mdash; if we ignore this fact, the rest of the algorithm can be implemented, tested and used right now.
</pre>
</div>
<div title="DefaultsManagement" modifier="Ichthyostega" modified="200803212244" created="200801121708" tags="def spec" changecount="12">
<pre>For several components and properties there is an implicit default value or configuration; it is stored alongside with the session. The intention is that defaults never create an error, instead, they are to be extended silently on demand. Objects configured according to this defaults can be retrieved at the [[Session]] interface by a set of overloaded functions {{{Session::current-&gt;default(Query&lt;TYPE&gt; (&quot;query string&quot;))}}}, where the //query string // defines a capability query similar to what is employed for pipes, stream types, codecs etc. This query mechanism is implemented by [[configuration rules|ConfigRules]]
!!!!what is denoted by {{{default}}}?
{{{default(Obj)}}} is a predicate expressing that the object {{{Obj}}} can be considered the default setup under the given conditions. Using the //default// can be considered as a shortcut for actually finding a exact and unique solution. The latter would require to specify all sorts of detailed properties up to the point where only one single object can satisfy all conditions. On the other hand, leaving some properties unspecified would yield a set of solutions (and the user code issuing the query had to provide means for selecting one soltution from this set). Just falling back on the //default// means that the user code actually doesn't care for any additional properties (as long as the properties he //does// care for are satisfied). Nothing is said specifically on //how//&amp;nbsp; this default gets configured; actually there can be rules //somewhere,// and, additionally, anything encountered once while asking for a default can be re-used as default under similar circumstances.
&amp;rarr; [[implementing defaults|DefaultsImplementation]]</pre>
</div>
<div title="DesignDecisions" modifier="Ichthyostega" modified="200910312026" created="200801062209" tags="decision design discuss Concepts" changecount="25">
<pre>Along the way of working out various [[implementation details|ImplementationDetails]], decisions need to be made on how to understand the different facilities and entities and how to tackle some of the problems. This page is mainly a collection of keywords, summaries and links to further the discussion. And the various decisions should allways be read as proposals to solve some problem at hand...
''Everything is an object'' &amp;mdash; of course, that's a //no-brainer,// todays. Rather, important is what is not &quot;an object&quot;, meaning it can't be arranged arbitrarily
* we have one and only one global [[Session]] which directly contains a collection of multiple [[Sequences|Sequence]] and is associated with a globally managed collection of [[assets|Asset]]. We don't utilise scoped variables here (no &quot;mandantisation&quot;); if e.g. a media has been //opened,// it is just plain //globally known//&amp;nbsp; as asset.
* the [[knowledge base|ConfigRules]] is just available globally. Obviously, the session gets a chance to install rules into this knowledge base, but we don't stress ownership here.
* we have a [[Fixture]] which acts as isolation layer towards the render engine and is (re)built automatically.
We ''separate'' processing (rendering) and configuration (building). We have a [[Builder]] which creates a network of [[render nodes|ProcNode]], to be processed by //pulling data // from some [[Pipe]]
''Objects are [[placed|Placement]] rather'' than assembled, connected, wired, attached. This is more of a rule-based approach and gives us one central metaphor and abstraction, allowing us to treat everything in an uniform manner. You can place it as you like, and the builder tries to make sense out of it, silently disabling what doesn't make sense.
An [[Sequence]] is just a collection of configured and placed objects (and has no additional, fixed structure). [[Tracks|Track]] form a mere organisational grid, they are grouping devices not first-class entities (a track doesn't &quot;have&quot; a pipe or &quot;is&quot; a video track and the like; it can be configured to behave in such manner by using placements though). [[Pipes|Pipe]] are hooks for making connections and are the only facility to build processing chains. We have global pipes, and each clip is built around a lokal [[source port|ClipSourcePort]] &amp;mdash; and that's all. No special &quot;media viewer&quot; and &quot;arranger&quot;, no special role for media sources, no commitment to some fixed media stream types (video and audio). All of this is sort of pushed down to be configuration, represented as asset of some kind. For example, we have [[processing pattern|ProcPatt]] assets to represent the way of building the source network for reading from some media file (including codecs treated like effect plugin nodes)
Actual ''media data and handling'' is abstracted rigorously. Media is conceived as being stream-like data of distinct StreamType. When it comes to more low-level media handling, we build on the DataFrame abstraction. Media processing isn't the focus of Lumiera; we organise the processing but otherwise ''rely on media handling libraries.'' In a similar vein, multiplicity is understood as type variation. Consequently, we don't build an audio and video &quot;section&quot; and we don't even have audio tracks and video tracks. Lumiera uses tracks and clips, and clips build on media, but we're able to deal with multichannel mixed-typed media.
''State'' is rigorously ''externalized'' and operations are to be ''scheduled'', to simplify locking and error handling. State is either treated similar to media stream data (as addressable and cacheable data frame), or is represented as &quot;parameter&quot; to be served by some [[parameter provider|ParamProvider]]. Consequently, [[Automation]] is just another kind of parameter, i.e. a function &amp;mdash; how this function is calculated is an encapsulated implementation detail (we don't have &quot;bezier automation&quot;, and then maybe a &quot;linear automation&quot;, a &quot;mask automation&quot; and yet another way to handle transitions)
Deliberately there is an limitaion on the flexibility of what can be added to the system via Plugins. We allow configuration and parametrisation to be extended and we allow processing and data handling to be extended, but we disallow extensions to the fundamental structure of the system by plugins. They may provide new implementations for already known subsystems, but they can't introduce new subsystems not envisioned in the general design of the application.
* thus fixed assortments include: the possbile kinds of MObject and [[Asset]], the possible automation parameter data types, the supported kinds of plugin systems
* while plugins may extend: the supported kinds of media, the [[media handling libraries|MediaImplLib]] used to deal with those media, the session storage backends, the behaviour of placments
</pre>
</div>
<div title="DesignGoals" modifier="Ichthyostega" modified="200805300046" created="200706210557" tags="design" changecount="20">
<pre>This __proc-Layer__ and ~Render-Engine implementation started out as a design-draft by [[Ichthyo|mailto:Ichthyostega@web.de]] in summer 2007. The key idea of this design-draft is to use the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]] for the Render Engine, thus separating completely the //building// of the Render Pipeline from //running,// i.e. doing the actual Render. The Nodes in this Pipeline should process Video/Audio and do nothing else. No more decisions, tests and conditional operations when running the Pipeline. Move all of this out into the configuration of the pipeline, which is done by the Builder.
!Why doesn't the current Cinelerra-2 Design succeed?
The design of Cinelerra-2 basically follows a similar design, but __fails because of two reasons__
# too much differentiation is put into the class hierarchy instead of configuring Instances differently.&lt;br&gt;This causes overly heavy use of virtual functions and -- in order to ameliorate this -- falling back to hard wired branching
# far too much coupling and back-coupling to the internals of the »EDL«, forcing a overly rigid structure on the latter
!Try to learn from the Problems of the current Cinelerra-2 codebase
* build up an [[Node Abstraction|ProcNode]] powerful enough to express //all necessary Operations// without the need to recure on the actual object type
* need to redesign the internals of the Session in a far more open manner. Everything is an [[M-Object|MObjects]] which is [[placed|Placement]] in some manner
* strive at a StrongSeparation between Session and Render Engine, encapsulate and allways implement against interfaces
!Design Goals
As always, the main goal is //to cut down complexity// by the usual approach to separate into small manageable chunks.
To achieve this, here we try to separate ''Configuration'' from ''Processing''. Further, in Configuration we try to separate the ''high level view'' (users view when editing) from the ''low level view'' (the actual configuration effective for the calculations). Finally, we try to factor out and encapsulate ''State'' in order to make State explicit.
The main tool used to implement this separation is the [[Builder Pattern|http://en.wikipedia.org/wiki/Builder_pattern]]. Here especially we move all decisions and parametrization into the BuildProcess. The Nodes in the render pipeline should process Video/Audio and do nothing else. All decisions, tests and conditional operations are factored out of the render process and handled as configuration of the pipeline, which is done by the Builder. The actual color model and number of ports is configured in by a pre-built wiring descriptor. All Nodes are of equal footing with each other, able to be connected freely within the limitations of the necessary input and output. OpenGL and renderfarm support can be configured in as an alternate implementation of some operations together with an alternate signal flow (usable only if the whole Pipeline can be built up to support this changed signal flow), thus factoring out all the complexities of managing the data flow between core and hardware accelerated rendering. We introduce separate control data connections for the [[automation data|Automation]], separating the case of true multi-channel-effects from the case where one node just gets remote controlled by another node (or the case of two nodes using the same automation data).
Another pertinent theme is to make the basic building blocks simpler, while on the other hand gaining much more flexibility for combining these building blocks. For example we try to unfold any &quot;internal-multi&quot; effects into separate instances (e.g. the possibility of having an arbitrary number of single masks at any point of the pipeline instead of having one special masking facility encompassing multiple sub-masks. Similarly, we treat the Objects in the Session in a more uniform manner and gain the possibility to [[place|Placement]] them in various ways.
</pre>
</div>
<div title="DisplayFacade" modifier="Ichthyostega" modified="200904302316" created="200902080703" tags="spec" changecount="2">
<pre>LayerSeparationInterface provided by the GUI.
Access point especially for the playback. A render- or playback process uses the DisplayFacade to push media data up to the GUI for display within a viewer widget of full-screen display. This can be thought off as a callback mechanism. In order to use the DisplayFacade, client code needs a DisplayerSlot (handle), which needs to be set up by the UI first and will be provided when starting the render or playback process.
</pre>
</div>
<div title="DisplayService" modifier="Ichthyostega" created="200902080711" tags="def" changecount="1">
<pre>A service within the GUI to manage output of frames generated by the lower layers of the application.
*providing the actual implementation of the DisplayFacade
*creating and maintaining of [[displayer slots|DisplayerSlot]], connected to viewer widgets or similar
</pre>
</div>
<div title="DisplayerSlot" modifier="Ichthyostega" modified="200902080707" created="200902080705" tags="def" changecount="2">
<pre>An output port wired up to some display facility or viewer widget within the UI. For the client code, each slot is represented by a handle, which can be used to lock into this slot for an continuous output process. Managed by the DisplayService
</pre>
</div>
<div title="EDL" modifier="Ichthyostega" modified="201001252333" created="200706210610" tags="def" changecount="12">
<pre>''EDL'' is a short-hand for __E__dit __D__ecision __L__ist. The use of this term can be confusing; for the usual meaning see the definition in [[Wikipedia|http://en.wikipedia.org/wiki/Edit_decision_list]]
Cinelerra uses this term in a related manner but with a somewhat shifted focus: In Cinelerra the EDL is comprised of the whole set of clips and other media objects arranged onto the tracks by the user. It is the result of the user's //editing efforts.//
In this usage, the EDL in most cases will be almost synonymous to &amp;raquo;the session&amp;laquo;, just the latter emphasizes more the state aspect. While the Lumiera project started out using the same terminology, later on, when support for multiple &quot;containers&quot; within the session and for [[meta-clips|VirtualClip]] was determined to be of much importance, the new term &amp;raquo;[[Sequence]]&amp;laquo; was preferred.
</pre>
</div>
<div title="EditingOperations" modifier="Ichthyostega" modified="200710111508" created="200709251610" tags="design decision" changecount="5">
<pre>These are the tools provided to any client of the Proc layer for handling and manipulating the entities in the Session. When defining such operations, //the goal should be to arrive at some uniformity in the way things are done.// Ideally, when writing client code, one should be able to guess how to achieve some desired result.
!guiding principle
The approach taken to define any operation is based primarily on the ''~OO-way of doing things'': entities operate themselfs. You don't advise some manager, session or other &amp;raquo;god class&amp;laquo; to manipulate them. And, secondly, the scope of each operation will be as large as possible, but not larger. This often means performing quite some elementary operations &amp;mdash; sometimes a convenience shortcut provided by the higher levels of the application may come in handy &amp;mdash; and basically this gives rise to several different paths of doing the same thing, all of which need to be equivalent.
!main tasks
* you ''create a clip'' either from a source media or from another clip (copy, maybe clone?). The new clip always reflects the full size (and other properties) of the source used for creating.
* you can request a clip to ''resize'', which always relates to its current dimensions.
* you can ''place or attach'' the clip to some point or other entity by creating a [[Placement]] from the clip. (&amp;rarr; [[handling of Placements|PlacementHandling]])
* you can ''adjust'' a placement relative to some other placement, meta object (i.e. selection), label or media, and this may cause the placement to resize or even effectively remove the clip.
* you can perform ''adjustments'' on a Sequence as a whole.
All these operations propagate to directly dependant objects and may schedule global reconfigurations.
!state, locking, policies
While performing any manipulative task, the state of the object is considered inconsistent, but it is guaranteed to be consistent after any such operation, irrespective of the result. There is no automatic atomicity and, consequently each propagation is considered a separate operation.
!!parallelism
At the level of fine grained operations on individual entities, there is ''no support for parallelism''. Individual entities don't lock themselves. Tasks perform locking and are to be scheduled. Thus, we need an isolation layer towards all inherently multithreaded parts of the system, like the GUI or the renderengine.
This has some obvious and some subtle consequences. Of course, manipulating //tasks// will be queued and scheduled somewhere. But as we rely on object identity and reference semantics for the entities in the session, some value changes are visible to operations going on in parallel threads (e.g. rendering) and each operation on the session entities //has to be aware of this.//
Consequently, sometimes there needs to be done sort of a ''read-copy-update'', i.e. self-replacement by copy followed by manipulation of the new copy, while ongoing processes use the unaltered original object until they receive some sort of reset.
!!undo
Basically, each elementary operation has to record the informations necessary to be undone. It does so by registering a Memento with some central UndoManager facility. This Memento object contains a functor pre-bound with the needed parameter values. (Besides, the UndoManager is free to implement a second level of security by taking independent state snapshots).
{{red{to be defined in more detail later...}}}
</pre>
</div>
<div title="EffectHandling" modifier="Ichthyostega" modified="200810170025" created="200810162300" tags="design operational" changecount="3">
<pre>We have to deal with effects on various different levels. One thing is to decide if an effect is applicable, another question is to find out about variable and automatable parameters, find a module which can be used as an effect GUI and finally to access a processing function which can be executed on a buffer filled with suitable data.
The effect asset (which is a [[processing asset|ProcAsset]]) provides an unified interface for loading external processing modules and querying their properties. As usual with assets, this is the &quot;bookkeeping view&quot; to start with, but we can create a //processor// from such an asset, i.e. an instance of the processing facility which can be used and wired into the model. Such a processor is an MObject and can be placed into the session (high level model); moreover it holds a concrete wiring for the various control parameters and it has an active link to the effect GUI module. As every ~MObject, it could be placed multiple times to affect several pipes, but all those different Placements would behave as being linked together (with respect to the control parameter sources and the GUI)
When building the low-level model, the actual processing code is resolved and a processing function is installed into the processing node representing the effect. This step includes checking of actual [[media type information|StreamType]], which may result in selecting a specifically tailored implementation function. The parameter wiring on the other hand is //shared// between the effect ~MObject, the corresponding GUI module and all low-level processing nodes. Actually, parameters are more of a reference than actually being values: they provide a {{{getValue()}}} function, which also works with automation. This way, any parameter or automation changes are reflected immediately into the produced output.
Initially, only the parameter (descriptors) are present on the effect ~MObject, while the actual [[parameter providers|ParamProvider]] are created or wired (by the ConManager) on demand.
</pre>
</div>
<div title="Example1" modifier="Ichthyostega" modified="200906071812" created="200706220239" tags="example img" changecount="5">
<pre>The &amp;raquo;Timeline&amp;laquo; is a sequence of ~MObjects -- here clips -- together with an ExplicitPlacement, locating each clip at a given time and track. (Note: I simplified the time format and wrote frame numbers to make it more clear)
[img[Example1: Objects in the Session/Fixture|uml/fig128773.png]]
----
After beeing processed by the Builder, we get the following Render Engine configuration
{{red{note: please take this only as a &quot;big picture&quot;, the implementation details got a lot more complicated as of 6/08}}}
[img[Example1: generated Render Engine|uml/fig129029.png]]
</pre>
</div>
<div title="Example2" modifier="Ichthyostega" modified="200906071812" created="200706220251" tags="example img" changecount="4">
<pre>{{red{TODO: seemingly this example is slightly outdated, as the implementation of placements is now indirect via LocatingPin objects}}}
This Example showes the //high level// Sequence as well. This needs to be transformed into a Fixture by some facility still to be designed. Basically, each [[Placement]] needs to be queried for this to get the corresponding ExplicitPlacement. The difficult part is to handle possible Placement constraints, e.g. one clip can't be placed at a timespan covered by another clip on the same track. In the current Cinelerra2, all of this is done directly by the GUI actions.
The &amp;raquo;Timeline&amp;laquo; is a sequence of ~MObjects -- note: using the same Object instances -- but now with the calculated ExplicitPlacement, locating the clip at a given time and track. The effect is located absolutely in time as well, but because it is the same Instance, it has the pointer to the ~RelativePlacement, wich basically attaches the effect to the clip. This structure may look complicated, but is easy to process if we go &quot;backward&quot; and just rely on the information contained in the ExplicitPlacement.
[img[Example2: Clip with Effect and generated Fixture for this Sequence|uml/fig128901.png]]
----
After beeing processed by the Builder, we get a Render Engine configuration.&lt;br&gt;
It has to be segmented at least at every point with changes in the configuration, but some variations are possible, e.g. we could create a Render Engine for every Frame (as Cinelerra2 does) or we could optimize out some configurations (for example the effect extended beyond the end of the clip)
{{red{note: as of 6/08 this can be taken only as the &quot;big picture&quot;. Implementation will differ in details, and is more complicated than showed here}}}
[img[Example2: generated Render Engine|uml/fig129157.png]]
</pre>
</div>
<div title="Examples" modifier="MichaelPloujnikov" modified="200706271425" created="200706220233" tags="example" changecount="6">
<pre>!MObject assembly
To make the intended use of the classes more clear, consider the following two example Object graphs:
* a video clip and a audio clip placed (explicitly) on two tracks &amp;rarr;[[Example1]]
* a video clip placed relatively, with an attached HUE effect &amp;rarr;[[Example2]]
</pre>
</div>
<div title="ExitNode" modifier="Ichthyostega" created="200706220322" tags="def" changecount="1">
<pre>a special ProcNode which is used to pull the finished output of one Render Pipeline (Tree or Graph). This term is already used in the Cinelerra2 codebase. I am unsure at the moment if it is a distinct subclass or rahter a specially configured ProcNode (a general design rule tells us to err in favour of the latter if in doubt).
</pre>
</div>
<div title="ExplicitPlacement" modifier="MichaelPloujnikov" modified="200706271458" created="200706220304" tags="def" changecount="2">
<pre>A special kind (subclass) of [[Placement]]. As such it is always linked to a //Subject//, i.e. a MObject. In addition to the properties of a (unspecific) Placement, the ExplicitPlacement specifies a absolute time and track position for locating the Subject
</pre>
</div>
<div title="Factories" modifier="Ichthyostega" modified="201003160211" created="200708100401" tags="impl discuss excludeMissing rewrite" changecount="24">
<pre>We use Factories
* for centralizing [[memory management|MemoryManagement]]
* to support polymorphism (of course...)
!Requirements
* request the actual placement/allocation from the backend
* allways hand out a smart-pointer
* encapsulate / make configurable the smart-pointer type
* install a callback into the smart-pointer for destroying the resource.
* redirect the destroying request to the backend
!Implementation Questions
* how much genericity? (Ichthyo is rather inclined not to overdo this one. Often it is preferable to have repeated implementations follow a well known pattern, if this leads to short and simple implementations, while the complete general solution will be much more contrived).
* how to specify the actual type needed?
* how to implement the cases where a subtype needs to be selected (abstract factory pattern). Embody this into the Factory, pass it in as a Strategy or treat the Factory just as a simple service taking an explicit type-ID and providing the new object?
!!chosen design
My main goal is to have an easy-to-use interface for the implementer of individual classes using this factory mechanism. The intended use should mimic the standard use of operator new, and at the same time there should be a possibility to configure »real« Factory behaviour in.
For this reason I make Factory a Functor, so it can be incorporated as a member into another class, while still looking like a function call to the client code. The users of this factory template typically either parametrize it with existing smart-pointer types, or they may chose to create a specialized Factory derived from this Factory template. After typically hiding this configuration behind a typedef, the user adds a static field to the class intended to use the Factory and initializes this field with the concrete Factory (this may pass internal ~IDs to the constructor of the Factory and from there on to the underlying Allocator).
{{{
#include &quot;common/factory.hpp&quot;
class Product
{
int wow_;
public:
typedef lumiera::factory::RefcntFactory&lt;Product&gt; Factory;
static Factory create;
Product() : wow_(42) {} ;
};
/** storage for a static Factory instance
* for creating refcounting Ptrs to Product objects
*/
Product::Factory Product::create; // &lt;----note this is a ctor call
}}}
Now, the clients of {{{class Product}}} can create ref-counting pointers to Product-objects by doing a fully qualified {{{create()}}} functor call:
{{{
std::tr1::shared_ptr&lt;Product&gt; huii = Product::create (); // &lt;----will invoke the default Product() ctor
std::tr1::shared_ptr&lt;Product&gt; pfuii = huii;
}}}
Some further details
* the product class (or base class) cares for using an custom allocator via an overloaded {{{operator new(size_t)}}} if applicable.
* thus, when Functor or any derived class issues a new XX(), our custom Allocator gains control
* the Functor-behaviour relies on a custom {{{operator()}}} which can be overridden in derived classes to take various parameter lists.
* additionally, such a Factory class derived from Functor can do specific magic and e.g. create some subclass
* and, as the created smart-pointer is a template template parameter, such a custom Functor can create all sorts of Proxies, wrappers and the like
* a special case of this factory use is the [[Singleton]] factory, which is used a lot within the Proy-Layer code
</pre>
</div>
<div title="Fixture" modifier="Ichthyostega" modified="200712100439" created="200706220324" tags="def" changecount="4">
<pre>a specially configured sequence list
* all MObjects have their position, length and configuration set up ready for rendering.
* compound objects (e.g. multichannel clips) have been resolved to single non-compound basic objects.
* every MObject is associated with an ExplicitPlacement, which declares a fixed position (Time, Track)
* this ~ExplicitPlacements are contained in a ordered List called the Timeline
As the builder and thus render engine //only consults the fixture,// while all editing operations finally propagate to the fixture as well, we get an isolation layer between the high level part of the Proc layer (editing, object manipulation) and the render engine. Creating the Fixture can be seen as a preprocessing step to simplify the build process. For this reason, the process of [[(re)building the fixture|PlanningBuildFixture]] has been designed together with the [[Builder]]</pre>
</div>
<div title="ForwardIterator" modifier="Ichthyostega" modified="200912190027" created="200910312114" tags="Concepts def spec" changecount="17">
<pre>The situation focussed by this concept is when an API needs to expose a sequence of results, values or objects, instead of just yielding a function result value. As the naive solution of passing an pointer or array creates coupling to internals, it was superseded by the ~GoF [[Iterator pattern|http://en.wikipedia.org/wiki/Iterator]]. Iteration can be implemented by convention, polymorphically or by generic programming; we use the latter approach.
!Lumiera Forward Iterator concept
''Definition'': An Iterator is a self-contained token value, representing the promise to pull a sequence of data
* rather then deriving from an specific interface, anything behaving appropriately //is a Lumiera Forward Iterator.//
* the client finds a typedef at a suitable, nearby location. Objects of this type can be created, copied and compared.
* any Lumiera forward iterator can be in //exhausted// &amp;nbsp;(invalid) state, which can be checked by {{{bool}}} conversion.
* especially, default constructed iterators are fixed to that state. Non-exhausted iterators may only be obtained by API call.
* the exhausted state is final and can't be reset, meaning that any iterator is a disposable one-way-off object.
* when an iterator is //not//&amp;nbsp; in the exhausted state, it may be //dereferenced// ({{{*i}}}), yielding the &quot;current&quot; value
* moreover, iterators may be incremented ({{{++i}}}) until exhaustion.
!!Discussion
The Lumiera Forward Iterator concept is a blend of the STL iterators and iterator concepts found in Java, C#, Python and Ruby. The chosen syntax should look familiar to C++ programmers and indeed is compatible to STL containers and ranges. To the contrary, while a STL iterator can be thought off as being just a disguised pointer, the semantics of Lumiera Forward Iterators is deliberately reduced to a single, one-way-off forward iteration, they can't be reset, manipulated by any arithmetic, and the result of assigning to an dereferenced iterator is unspecified, as is the meaning of post-increment and stored copies in general. You //should not think of an iterator as denoting a position// &amp;mdash; just a one-way off promise to yield data.
Another notable difference to the STL iterators is the default ctor and the {{{bool}}} conversion. The latter allows using iterators painlessly within {{{for}}} and {{{while}}} loops; a default constructed iterator is equivalent to the STL container's {{{end()}}} value &amp;mdash; indeed any //container-like// object exposing Lumiera Forward Iteration is encouraged to provide such an {{{end()}}}-function, additionally enabling iteration by {{{std::for_each}}} (or Lumiera's even more convenient {{{util::for_each()}}}).
!!Implementation notes
''iter-adapter.hpp'' provides some helper templates for building Lumiera Forward Iterators.
* __~IterAdapter__ is the most flexible variant, intended for use by custom facilities. An ~IterAdapter maintains an internal back-link to a facilitiy exposing an iteration control API, which is accessed through free functions as extension point. This iteration control API is similar to C#, allowing to advance to the next result and to check the current iteration state.
* __~RangeIter__ wraps two existing iterators &amp;mdash; usually obtained from {{{begin()}}} and {{{end()}}} of an STL container embedded within the implementation. This allows for iterator chaining.
* __~PtrDerefIter__ works similar, but can be used on an STL container holding //pointers,// to be dereferenced automatically on access
Similar to the STL habits, Lumiera Forward Iterators should expose typedefs for {{{pointer}}}, {{{reference}}} and {{{value_type}}}.
Additionally, they may be used for resource management purposes by embedding a ref-counting facility, e.g. allowing to keep a snapshot or restult set around until it can't be accessed anymore.
</pre>
</div>
<div title="Frame" modifier="Ichthyostega" modified="200909291345" created="200706220332" tags="def" changecount="3">
<pre>This term has //two meanings, //so care has to be taken for not confusing them.
# in general use, a Frame means one full image of a video clip, i.e an array of rows of pixels. For interlaced footage, one Frame contains two halfimages, commonly called Fields. (Cinelerra2 confuses this terms)
# here in this design, we use Frame as an abstraction for a buffer of raw media data to be processed. If in doubt, we should label this &quot;DataFrame&quot;.
#* one video Dataframe contains a single video frame
#* one audio Dataframe contains a block of raw audio samples
#* one OpenGL Dataframe could contain raw texture data (but I am lacking expertise for this topic)
</pre>
</div>
<div title="FullScreenPlugin" modifier="CehTeh" modified="200706110313" created="200607241016" tags="systemConfig lewcidExtension" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200706110313">
<pre>/***
|Name|FullScreenPlugin|
|Created by|SaqImtiaz|
|Location|http://tw.lewcid.org/#FullScreenPlugin|
|Version|1.1|
|Requires|~TW2.x|
!Description:
Toggle between viewing tiddlers fullscreen and normally. Very handy for when you need more viewing space.
!Demo:
Click the ↕ button in the toolbar for this tiddler. Click it again to turn off fullscreen.
!Installation:
Copy the contents of this tiddler to your TW, tag with systemConfig, save and reload your TW.
Edit the ViewTemplate to add the fullscreen command to the toolbar.
!History:
*25-07-06: ver 1.1
*20-07-06: ver 1.0
!Code
***/
//{{{
var lewcidFullScreen = false;
config.commands.fullscreen =
{
text:&quot;&quot;,
tooltip:&quot;Fullscreen mode&quot;
};
config.commands.fullscreen.handler = function (event,src,title)
{
if (lewcidFullScreen == false)
{
lewcidFullScreen = true;
setStylesheet('#sidebar, .header, #mainMenu{display:none;} #displayArea{margin:0em 0 0 0 !important;}',&quot;lewcidFullScreenStyle&quot;);
}
else
{
lewcidFullScreen = false;
setStylesheet(' ',&quot;lewcidFullScreenStyle&quot;);
}
}
config.macros.fullscreen={};
config.macros.fullscreen.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
var label = params[0]||&quot;&quot;;
var tooltip = params[1]||&quot;Fullscreen mode&quot;;
createTiddlyButton(place,label,tooltip,config.commands.fullscreen.handler);
}
var lewcid_fullscreen_closeTiddler = Story.prototype.closeTiddler;
Story.prototype.closeTiddler =function(title,animate,slowly)
{
lewcid_fullscreen_closeTiddler.apply(this,arguments);
if (story.isEmpty() &amp;&amp; lewcidFullScreen == true)
config.commands.fullscreen.handler();
}
Slider.prototype.lewcidStop = Slider.prototype.stop;
Slider.prototype.stop = function()
{
this.lewcidStop();
if (story.isEmpty() &amp;&amp; lewcidFullScreen == true)
config.commands.fullscreen.handler();
}
//}}}</pre>
</div>
<div title="GAVL" modifier="Ichthyostega" created="200809220251" tags="def" changecount="1">
<pre>The ''Gmerlin Audio Video Library''. &amp;rarr; see [[homepage|http://gmerlin.sourceforge.net/gavl.html]]
Used within Lumiera as a foundation for working with raw video and audio media data
</pre>
</div>
<div title="GOPs" modifier="Ichthyostega" modified="200706220333" created="200706220301" tags="def" changecount="2">
<pre>__G__roup __of__ __P__ictures: several compressed video formats don't encode single frames. Normally, such formats are considered mere //delivery formates// but it was one of the key strenghts of Cinelrra from start to be able to do real non linear editing on such formats (like the ~MPEG2-ts unsed in HDV video). The problem of course is that the data backend needs to decode the whole GOP to be serve single raw video frames.
For this Lumiera design, we could consider making GOP just another raw media data frame type and integrate this decoding into the render pipeline, similar to an effect based on several source frames for every calculated output frame.
&amp;rarr;see in [[Wikipedia|http://en.wikipedia.org/wiki/Group_of_pictures]]
</pre>
</div>
<div title="GuiCommunication" modifier="Ichthyostega" modified="200812050555" created="200812050543" tags="GuiIntegration draft" changecount="2">
<pre>All communication between Proc-Layer and GUI has to be routed through the respective LayerSeparationInterfaces. Following a fundamental design decision within Lumiera, these interface are //intended to be language agnostic// &amp;mdash; forcing them to stick to the least common denominator. Which creates the additional problem of how to create a smooth integration without forcing the architecture into functional decomposition style. To solve this problem, we rely on the well known solution of using a __business facade__ and delegation proxies.
Thus, the Proc-Layer exposes (one or several) facade interfaces for the GUI to use it's functionality, and similarily the GUI provides a [[notification facade|GuiNotificationFacade]] for pushing back status information created as result of the edit operations, the build process and the render tasks.
!anatomy of the Proc/GUI interface
* the GuiFacade is used as a general lifecycle facade to start up the GUI and to set up the LayerSeparationInterfaces.
* GuiFacade is implemented by a class //in core// and exposes a notification proxy implementing GuiNotificationFacade, which actually is wired through the InterfaceSystem to forward to the corresponding implementation facade object within the GUI
* similarly, starting the fundamental subsystems within proc installs Interfaces into the InterfaceSystem and exposes them through proxy objects
</pre>
</div>
<div title="GuiFacade" modifier="Ichthyostega" created="200902080719" tags="def spec" changecount="1">
<pre>special LayerSeparationInterface which serves the main purpose to load the GuiStarterPlugin, thus bringing up the Lumiera GTK UI at application start.
</pre>
</div>
<div title="GuiIntegration" modifier="Ichthyostega" modified="200904242106" created="200812050532" tags="overview" changecount="3">
<pre>Considering how to interface to and integrate with the GUI Layer. Running the GUI is //optional,// but it requires to be [[started up|GuiStart]], installing the necessary LayerSeparationInterfaces. As an example how to integrate the GUI with the lower layers, in 1/2009 we created an PlayerDummy, which &quot;pulls&quot; dummy frames from the (not yet existing) engine and displays them within an XV viewer widget.
Probably the most important aspect regarding the GUI integration is how to get [[access to and operate on the Session|SessionInterface]]. More specifically, this includes [[referring to individual objects|MObjectRef]].</pre>
</div>
<div title="GuiNotificationFacade" modifier="Ichthyostega" created="200902080659" tags="spec" changecount="1">
<pre>LayerSeparationInterface provided by the GUI.
Access point for the lower layers to push information and state changes (aynchronously) to the GUI. Actually, most operations within Lumiera are initiated by the user through the GUI. In the course of such actions, the GUI uses the services of the lower layer and typically recieves an synchronous response. In some exceptional cases, these operations may cause additional changes to happen asynchronously from the GUI's perspective. For example, an edit operation might trigger a re-build of the low-level model, which then detects an error.
</pre>
</div>
<div title="GuiStart" modifier="Ichthyostega" modified="200902080720" created="200812050525" tags="GuiIntegration" changecount="9">
<pre>Starting up the GUI is optional and is considered part of the Application start/stop and lifecycle.
* main and AppState activate the lifecyle methods on the ~GuiSubsysDescriptor, accessible via the GuiFacade
* loading a GuiStarterPlugin actually
** loads the GUI (shared lib)
** creates instances of all the GUI services available through LayerSeparationInterfaces
*** GuiNotificationFacade
*** DisplayFacade
** Finally, the ~GTK-main() is invoked.
!public services
The GUI provides a small number of public services, callable through LayerSeparationInterfaces. Besides that, the main purpose of the GUI of course is user interaction. Effectively the behaviour of the whole system is driven by GUI events to a large extent. These events are executed within the event handling thread (~GTK-main-Thread) and may in turn invoke services of the lower layers, again through the respective LayerSeparationInterfaces.
But the question of special interest here is how the //public services// of the GUI are implemented and made accessible for the lower Layers. Layer isolation is an issue here. If implemented in a rigorous manner, no facility within one layer may invoke implementation code of another layer directly. In practice, this tends to put quite some additional burden on the implementer, without and obvious benefit. Thus we decided to lower the barrier somewhat: while we still require that all service invoking calls are written against an public LayerSeparationInterface, actually the GUI (shared lib) is //linked// against the respective shared libs of the lower layers, thus especially enabling the exchange of iterators, closures and functor objects.
Note that we retain strict isolation in the other direction: no part of the lower layers is allowed to call directly into the GUI. Thus it's especially interesting how access to some GUI public service from the lower layers works in detail.
* when the GUI plugin starts, instances of the Services implementing those public service interfaces are created.
* these service objects in turn hold an ~InstanceHandle, which cares to register and open the corresponding C Language Interface
* additionally this InstanceHandle is configured such as to create an &quot;facade proxy&quot; object, which is implemented within liblumieracommon.so
Now, when invoking an operation on some public interface, the code in the lower layers actually executes an implementation of this operation //on the facade proxy,// which in turn forwards the call through the CL interface into the GUI, where it is actually implemented by the corresponding service object instance.
</pre>
</div>
<div title="GuiStarterPlugin" modifier="Ichthyostega" created="200902080716" tags="def GuiIntegration" changecount="1">
<pre>A specially configured LumieraPlugin, which actually contains or loads the complete code of the (GTK)GUI, and additionally is linked dynamically against the application core lib. During the [[UI startup process|GuiStart]], loading of this Plugin is triggered from {{{main()}}}. Actually this causes spawning of the GTK event thread and execution of the GTK main loop.
</pre>
</div>
<div title="HighLevelModel" modifier="Ichthyostega" modified="201003210021" created="200808152311" tags="Model spec design discuss img" changecount="34">
<pre>While the low-level model holds the data used for carrying out the actual media data processing (=rendering), the high-level model is what the user works upon when performing edit operations through the GUI (or script driven in &amp;raquo;headless mode&amp;laquo;). Its building blocks and combination rules determine largely what structures can be created within the [[Session]].
On the whole, it is a collection of [[media objects|MObjects]] stuck together and arranged by [[placements|Placement]].
Basically, the structure of the high-level model is is a very open and flexible one &amp;mdash; every valid connection of the underlying object types is allowed &amp;mdash; but the transformation into a low-level node network for rendering follows certain patterns and only takes into account any objects reachable while processing the session data in accordance to these patterns. Taking into account the parameters and the structure of these objects visited when building, the low-level render node network is configured in detail.
The fundamental metaphor or structural pattern is to create processing ''pipes'', which are a linear chain of data processing modules, starting from an source port and providing an exit point. [[Pipes|Pipe]] are a //concept or pattern,// they don't exist as objects. Each pipe has an input side and an output side and is in itself something like a Bus treating a single [[media stream|StreamType]] (but this stream may still have an internal structure, e.g. several channels related to a spatial audio system). Other processing entities like effects and transitions can be placed (attached) at the pipe, resulting them to be appended to form this chain. Optionally, there may be a ''wiring plug'', requesting the exit point to be connected to another pipe. When omitted, the wiring will be figured out automatically.
Thus, when making an connection //to// a pipe, output data will be sent to the //source port// (input side) of the pipe, wheras when making a connection //from// a pipe, data from it's exit point will be routed to the destination. Incidentally, the low-level model and the render engine employ //pull-based processing,// but this is rather of no relevance for the high-level model.
[img[draw/high-level1.png]]
Normally, pipes are limited to a //strictly linear chain// of data processors (&quot;''effects''&quot;) working on a single data stream type, and consequently there is a single ''exit point'' which may be wired to an destination. As an exception to this rule, you may insert wire tap nodes (probe points), which explicitly may send data to an arbitrary input port; they are never wired automatically. It is possible to create cyclic connections by such arbitrary wiring, which will be detected by the builder and flagged as an error.
While pipes have a rather rigid and limited structure, it is allowed to make several connections to and from any pipe &amp;mdash; even connections requiring an stream type conversion. It is not even necessary to specify //any// output destination, because then the wiring will be figured out automatically by searching the context and finally using some general rule. Connecting multiple outputs to the input of another pipe automatically creates a ''mixing step'' (which optionally can be controlled by a fader). Several pipes may be joined together by a ''transition'', which in the general case simultaneously treats N media streams. Of course, the most common case is to combine two streams into one output, thereby also mixing them. Most available transition plugins belong to this category, but, as said, the model isn't limited to this simple case, and moreover it is possible to attach several overlapping transitions covering the same time interval.
Individual Media Objects are attached, located or joined together by ''Placements''. A [[Placement]] is a handle for a single MObject (implemented as a refcounting smart-ptr) and contains a list of placement specifications, called LocatingPin. Adding an placement to the session acts as if creating an //instance.// (it behaves like a clone in case of multiple placements of the same object). Besides absolute and relative placement, there is also the possibility of a placement to stick directly to another MObject's placement, e.g. for attaching an effect to a clip or to connect an automation data set to an effect. This //stick-to placement// creates sort of a loose clustering of objects: it will derive the position from the placement it is attached to. Note that while the length and the in/out points are a //property of the ~MObject,// it's actual location depends on how it is //placed// and thus can be maintained quite dynamically. Note further that effects can have an length on their own, thus by using these attachement mechaics, the wiring and configuration within the high-level model can be quite time dependant.
[&gt;img[draw/high-level2.png]]
Actually a ''clip'' is handled as if it was comprised of local pipe(s). In the example shown here, a two-channel clip has three effects attached, plus a wiring plug. Each of those attachments is used only if applicable to the media stream type the respective pipe will process. As the clip has two channels (e.g. video and audio), it will have two ''source ports'' pulling from the underlying media. Thus, as showed in the drawing to the right, by chaining up any attached effect applicable to the respective stream type defined by the source port, effectively each channel (sub)clip gets its own specifically adapted processing pipe.
@@clear(right):display(block):@@
!!Example of an complete Session
[img[draw/high-level3.png]]
The Session contains several independent [[sequences|Sequence]] plus an output bus section (''global Pipes'') attached to the [[Timeline]]. Each sequence holds a collection of ~MObjects placed within a ''tree of tracks''.
Within Lumiera, tracks are a rather passive means for organizing media objects, but aren't involved into the data processing themselves. The possibility of nesting tracks allows for easy grouping. Like the other objects, tracks are connected together by placements: A track holds the list of placements of its child tracks. Each sequence holds a single placement pointing to the root track.
As placements have the ability to cooperate and derive any missing placement specifications, this creates a hierarchical structure throughout the session, where parts on any level behave similar if applicable. For example, when a track is anchored to some external entity (label, sync point in sound, etc), all objects placed relatively to this track will adjust and follow automatically. This relation between the track tree and the individual objects is especially important for the wiring, which, if not defined locally within an ~MObject's placement, is derived by searching up this track tree and utilizing the wiring plug locating pins found there, if applicable. In the default configuration, the placement of an sequence's root track contains a wiring plug for video and another wiring plug for audio. This setup is sufficient for getting every object within this sequence wired up automatically to the correct global output pipe. Moreover, when adding another wiring plug to some sub track, we can intercept and reroute the connections of all objects creating output of this specific stream type within this track and on all child tracks.
Besides routing to a global pipe, wiring plugs can also connect to the source port of an ''meta-clip''. In this example session, the outputs of ~Seq-2 as defined by locating pins in it's root track's placement, are directed to the source ports of a [[meta-cllip|VirtualClip]] placed within ~Seq-1. Thus, within ~Seq-1, the contents of ~Seq-2 appear like a pseudo-media, from which the (meta) clip has been taken. They can be adorned with effects and processed further completely similar to a real clip.
Finally, this example shows an ''automation'' data set controlling some parameter of an effect contained in one of the global pipes. From the effect's POV, the automation is simply a ParamProvider, i.e a function yielding a scalar value over time. The automation data set may be implemented as a bézier curve, or by a mathematical function (e.g. sine or fractal pseudo random) or by some captured and interpolated data values. Interestingly, in this example the automation data set has been placed relatively to the meta clip (albeit on another track), thus it will follow and adjust when the latter is moved.
</pre>
</div>
<div title="ImplementationDetails" modifier="Ichthyostega" modified="201001070900" created="200708080322" tags="overview" changecount="38">
<pre>This wiki page is the entry point to detail notes covering some technical decisions, details and problems encountered in the course of the implementation of the Lumiera Renderengine, the Builder and the related parts.
* [[Packages, Interfaces and Namespaces|InterfaceNamespaces]]
* [[Memory Management Issues|MemoryManagement]]
* [[Creating and registering Assets|AssetCreation]]
* [[Creating new Objects|ObjectCreation]]
* [[Multichannel Media|MultichannelMedia]]
* [[Editing Operations|EditingOperations]]
* [[Handling of the current Session|CurrentSession]]
* [[collecting Ideas for Implementation Guidelines|ImplementationGuidelines]]
* [[using the Visitor pattern?|VisitorUse]] &amp;mdash; resulting in [[»Visiting-Tool« library implementation|VisitingToolImpl]]
* [[Handling of Tracks and render Pipes in the session|TrackPipeSequence]]. [[Handling of Tracks|TrackHandling]] and [[Pipes|PipeHandling]]
* [[getting default configured|DefaultsManagement]] Objects relying on [[rule-based Configuration Queries|ConfigRules]]
* [[integrating the Config Query system|ConfigQueryIntegration]]
* [[identifying the basic Builder operations|BasicBuildingOperations]] and [[planning the Implementation|PlanningNodeCreatorTool]]
* [[how to handle »attached placement«|AttachedPlacementProblem]]
* working out the [[basic building situations|BuilderPrimitives]] and [[mechanics of rendering|RenderMechanics]]
* how to classify and [[describe media stream types|StreamType]] and how to [[use them|StreamTypeUse]]
* considerations regarding [[identity and equality|ModelObjectIdentity]] of objects in the HighLevelModel
* the [[identification of frames and nodes|NodeFrameNumbering]]
* the relation of [[Project, Timelines and Sequences|TimelineSequences]]
* how to [[start the GUI|GuiStart]] and how to [[communicate|GuiCommunication]]
* build the first LayerSeparationInterfaces
* create an uniform pattern for [[passing and accessing object collections|ForwardIterator]]
* decide on SessionInterface and create [[Session datastructure layout|SessionDataMem]]
* shaping the GUI/~Proc-Interface, based on MObjectRef and the [[Command frontend|CommandHandling]]
* defining PlacementScope in order to allow for [[discovering session contents|Query]]</pre>
</div>
<div title="ImplementationGuidelines" modifier="Ichthyostega" modified="200910311727" created="200711210531" tags="Concepts design discuss" changecount="16">
<pre>!Observations, Ideas, Proposals
''this page is a scrapbook for collecting ideas'' &amp;mdash; please don't take anything noted here too literal. While writing code, I observe that I (ichthyo) follow certain informal guidelines, some of which I'd like to note down because they could evolve into general style guidelines for the Proc-Layer code.
* ''Inversion of Control'' is the leading design principle.
* but deliberately we stay just below the level of using Dependency Injection. Singletons and call-by-name are good enough. We're going to build //one// application, not any conceivable application.
* avoid doing anything non-local during the startup phase or shutdown phase of the application. consequently, always prefer using an explicit lumiera::Singleton&lt;T&gt; over using an static instance directly, thus yielding better control when the ctor and dtor will be invoked.
* write error handling code only if the error situation can be actually //handled// at this place. Otherwise, be prepared for exceptions just passing by and thus handle any resources by &quot;resource acquisition is initialisation&quot; (RAII). Remember: error handling defeats decoupling and encapsulation
* (almost) never {{{delete}}} an object directly, use {{{new}}} only when some smart pointer is at hand.
* when user/client code is intended to create objects, make the ctor protected and provide a factory member called {{{create}}} instead, returning a smart pointer
* similarly, when we need just one instance of a given service, make the ctor protected and provide a factory member called {{{instance}}}, to be implemented by the lumiera::[[Singleton]] factory.
* whenever possible, prefer this (lazy initialised [[Singleton]]) approach and avoid static initialisation magic
* avoid asuming anything that can't be enforced by types, interfaces or signatures; this means: be prepared for open possibilities
* prefer {{{const}}} and initialisation code over assignment and active changes (inspired by functional programming)
</pre>
</div>
<div title="InlineJavaScript" modifier="Jeremy" created="200603090618" tags="systemConfig" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200603090618">
<pre>/***
''InlineJavascriptPlugin for ~TiddlyWiki version 1.2.x and 2.0''
^^author: Eric Shulman - ELS Design Studios
source: http://www.TiddlyTools.com/#InlineJavascriptPlugin
license: [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]^^
Insert Javascript executable code directly into your tiddler content. Lets you ''call directly into TW core utility routines, define new functions, calculate values, add dynamically-generated TiddlyWiki-formatted output'' into tiddler content, or perform any other programmatic actions each time the tiddler is rendered.
!!!!!Usage
&lt;&lt;&lt;
When installed, this plugin adds new wiki syntax for surrounding tiddler content with {{{&lt;script&gt;}}} and {{{&lt;/script&gt;}}} markers, so that it can be treated as embedded javascript and executed each time the tiddler is rendered.
''Deferred execution from an 'onClick' link''
By including a label=&quot;...&quot; parameter in the initial {{{&lt;script&gt;}}} marker, the plugin will create a link to an 'onclick' script that will only be executed when that specific link is clicked, rather than running the script each time the tiddler is rendered.
''External script source files:''
You can also load javascript from an external source URL, by including a src=&quot;...&quot; parameter in the initial {{{&lt;script&gt;}}} marker (e.g., {{{&lt;script src=&quot;demo.js&quot;&gt;&lt;/script&gt;}}}). This is particularly useful when incorporating third-party javascript libraries for use in custom extensions and plugins. The 'foreign' javascript code remains isolated in a separate file that can be easily replaced whenever an updated library file becomes available.
''Defining javascript functions and libraries:''
Although the external javascript file is loaded while the tiddler content is being rendered, any functions it defines will not be available for use until //after// the rendering has been completed. Thus, you cannot load a library and //immediately// use it's functions within the same tiddler. However, once that tiddler has been loaded, the library functions can be freely used in any tiddler (even the one in which it was initially loaded).
To ensure that your javascript functions are always available when needed, you should load the libraries from a tiddler that will be rendered as soon as your TiddlyWiki document is opened. For example, you could put your {{{&lt;script src=&quot;...&quot;&gt;&lt;/script&gt;}}} syntax into a tiddler called LoadScripts, and then add {{{&lt;&lt;tiddler LoadScripts&gt;&gt;}}} in your MainMenu tiddler.
Since the MainMenu is always rendered immediately upon opening your document, the library will always be loaded before any other tiddlers that rely upon the functions it defines. Loading an external javascript library does not produce any direct output in the tiddler, so these definitions should have no impact on the appearance of your MainMenu.
''Creating dynamic tiddler content''
An important difference between this implementation of embedded scripting and conventional embedded javascript techniques for web pages is the method used to produce output that is dynamically inserted into the document:
* In a typical web document, you use the document.write() function to output text sequences (often containing HTML tags) that are then rendered when the entire document is first loaded into the browser window.
* However, in a ~TiddlyWiki document, tiddlers (and other DOM elements) are created, deleted, and rendered &quot;on-the-fly&quot;, so writing directly to the global 'document' object does not produce the results you want (i.e., replacing the embedded script within the tiddler content), and completely replaces the entire ~TiddlyWiki document in your browser window.
* To allow these scripts to work unmodified, the plugin automatically converts all occurences of document.write() so that the output is inserted into the tiddler content instead of replacing the entire ~TiddlyWiki document.
If your script does not use document.write() to create dynamically embedded content within a tiddler, your javascript can, as an alternative, explicitly return a text value that the plugin can then pass through the wikify() rendering engine to insert into the tiddler display. For example, using {{{return &quot;thistext&quot;}}} will produce the same output as {{{document.write(&quot;thistext&quot;)}}}.
//Note: your script code is automatically 'wrapped' inside a function, {{{_out()}}}, so that any return value you provide can be correctly handled by the plugin and inserted into the tiddler. To avoid unpredictable results (and possibly fatal execution errors), this function should never be redefined or called from ''within'' your script code.//
''Accessing the ~TiddlyWiki DOM''
The plugin provides one pre-defined variable, 'place', that is passed in to your javascript code so that it can have direct access to the containing DOM element into which the tiddler output is currently being rendered.
Access to this DOM element allows you to create scripts that can:
* vary their actions based upon the specific location in which they are embedded
* access 'tiddler-relative' information (use findContainingTiddler(place))
* perform direct DOM manipulations (when returning wikified text is not enough)
&lt;&lt;&lt;
!!!!!Examples
&lt;&lt;&lt;
an &quot;alert&quot; message box:
{{{
&lt;script&gt;alert('InlineJavascriptPlugin: this is a demonstration message');&lt;/script&gt;
}}}
&lt;script&gt;alert('InlineJavascriptPlugin: this is a demonstration message');&lt;/script&gt;
dynamic output:
{{{
&lt;script&gt;return (new Date()).toString();&lt;/script&gt;
}}}
&lt;script&gt;return (new Date()).toString();&lt;/script&gt;
wikified dynamic output:
{{{
&lt;script&gt;return &quot;link to current user: [[&quot;+config.options.txtUserName+&quot;]]&quot;;&lt;/script&gt;
}}}
&lt;script&gt;return &quot;link to current user: [[&quot;+config.options.txtUserName+&quot;]]&quot;;&lt;/script&gt;
dynamic output using 'place' to get size information for current tiddler
{{{
&lt;script&gt;
if (!window.story) window.story=window;
var title=story.findContainingTiddler(place).id.substr(7);
return title+&quot; is using &quot;+store.getTiddlerText(title).length+&quot; bytes&quot;;
&lt;/script&gt;
}}}
&lt;script&gt;
if (!window.story) window.story=window;
var title=story.findContainingTiddler(place).id.substr(7);
return title+&quot; is using &quot;+store.getTiddlerText(title).length+&quot; bytes&quot;;
&lt;/script&gt;
creating an 'onclick' button/link that runs a script
{{{
&lt;script label=&quot;click here&quot;&gt;
if (!window.story) window.story=window;
alert(&quot;Hello World!\nlinktext='&quot;+place.firstChild.data+&quot;'\ntiddler='&quot;+story.findContainingTiddler(place).id.substr(7)+&quot;'&quot;);
&lt;/script&gt;
}}}
&lt;script label=&quot;click here&quot;&gt;
if (!window.story) window.story=window;
alert(&quot;Hello World!\nlinktext='&quot;+place.firstChild.data+&quot;'\ntiddler='&quot;+story.findContainingTiddler(place).id.substr(7)+&quot;'&quot;);
&lt;/script&gt;
loading a script from a source url
{{{
&lt;script src=&quot;demo.js&quot;&gt;return &quot;loading demo.js...&quot;&lt;/script&gt;
&lt;script label=&quot;click to execute demo() function&quot;&gt;demo()&lt;/script&gt;
}}}
where http://www.TiddlyTools.com/demo.js contains:
&gt;function demo() { alert('this output is from demo(), defined in demo.js') }
&gt;alert('InlineJavascriptPlugin: demo.js has been loaded');
&lt;script src=&quot;demo.js&quot;&gt;return &quot;loading demo.js...&quot;&lt;/script&gt;
&lt;script label=&quot;click to execute demo() function&quot;&gt;demo()&lt;/script&gt;
&lt;&lt;&lt;
!!!!!Installation
&lt;&lt;&lt;
import (or copy/paste) the following tiddlers into your document:
''InlineJavascriptPlugin'' (tagged with &lt;&lt;tag systemConfig&gt;&gt;)
&lt;&lt;&lt;
!!!!!Revision History
&lt;&lt;&lt;
''2006.01.05 [1.4.0]''
added support 'onclick' scripts. When label=&quot;...&quot; param is present, a button/link is created using the indicated label text, and the script is only executed when the button/link is clicked. 'place' value is set to match the clicked button/link element.
''2005.12.13 [1.3.1]''
when catching eval error in IE, e.description contains the error text, instead of e.toString(). Fixed error reporting so IE shows the correct response text. Based on a suggestion by UdoBorkowski
''2005.11.09 [1.3.0]''
for 'inline' scripts (i.e., not scripts loaded with src=&quot;...&quot;), automatically replace calls to 'document.write()' with 'place.innerHTML+=' so script output is directed into tiddler content
Based on a suggestion by BradleyMeck
''2005.11.08 [1.2.0]''
handle loading of javascript from an external URL via src=&quot;...&quot; syntax
''2005.11.08 [1.1.0]''
pass 'place' param into scripts to provide direct DOM access
''2005.11.08 [1.0.0]''
initial release
&lt;&lt;&lt;
!!!!!Credits
&lt;&lt;&lt;
This feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]]
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.inlineJavascript= {major: 1, minor: 4, revision: 0, date: new Date(2006,1,5)};
config.formatters.push( {
name: &quot;inlineJavascript&quot;,
match: &quot;\\&lt;script&quot;,
lookahead: &quot;\\&lt;script(?: src=\\\&quot;((?:.|\\n)*?)\\\&quot;)?(?: label=\\\&quot;((?:.|\\n)*?)\\\&quot;)?\\&gt;((?:.|\\n)*?)\\&lt;/script\\&gt;&quot;,
handler: function(w) {
var lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart) {
if (lookaheadMatch[1]) { // load a script library
// make script tag, set src, add to body to execute, then remove for cleanup
var script = document.createElement(&quot;script&quot;); script.src = lookaheadMatch[1];
document.body.appendChild(script); document.body.removeChild(script);
}
if (lookaheadMatch[2] &amp;&amp; lookaheadMatch[3]) { // create a link to an 'onclick' script
// add a link, define click handler, save code in link (pass 'place'), set link attributes
var link=createTiddlyElement(w.output,&quot;a&quot;,null,&quot;tiddlyLinkExisting&quot;,lookaheadMatch[2]);
link.onclick=function(){try{return(eval(this.code))}catch(e){alert(e.description?e.description:e.toString())}}
link.code=&quot;function _out(place){&quot;+lookaheadMatch[3]+&quot;};_out(this);&quot;
link.setAttribute(&quot;href&quot;,&quot;javascript:;&quot;); link.setAttribute(&quot;title&quot;,&quot;&quot;); link.style.cursor=&quot;pointer&quot;;
}
else if (lookaheadMatch[3]) { // run inline script code
var code=&quot;function _out(place){&quot;+lookaheadMatch[3]+&quot;};_out(w.output);&quot;
code=code.replace(/document.write\(/gi,'place.innerHTML+=(');
try { var out = eval(code); } catch(e) { out = e.description?e.description:e.toString(); }
if (out &amp;&amp; out.length) wikify(out,w.output);
}
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
} )
//}}}
</pre>
</div>
<div title="InlineJavascriptPlugin" modifier="Ichthyostega" created="200708081511" tags="plugin excludeLists systemConfig" changecount="1">
<pre>/***
|''Name:''|InlineJavascriptPlugin|
|''Source:''|http://www.TiddlyTools.com/#InlineJavascriptPlugin|
|''Author:''|Eric Shulman - ELS Design Studios|
|''License:''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|''~CoreVersion:''|2.0.10|
Insert Javascript executable code directly into your tiddler content. Lets you ''call directly into TW core utility routines, define new functions, calculate values, add dynamically-generated TiddlyWiki-formatted output'' into tiddler content, or perform any other programmatic actions each time the tiddler is rendered.
!!!!!Usage
&lt;&lt;&lt;
When installed, this plugin adds new wiki syntax for surrounding tiddler content with {{{&lt;script&gt;}}} and {{{&lt;/script&gt;}}} markers, so that it can be treated as embedded javascript and executed each time the tiddler is rendered.
''Deferred execution from an 'onClick' link''
By including a label=&quot;...&quot; parameter in the initial {{{&lt;script&gt;}}} marker, the plugin will create a link to an 'onclick' script that will only be executed when that specific link is clicked, rather than running the script each time the tiddler is rendered.
''External script source files:''
You can also load javascript from an external source URL, by including a src=&quot;...&quot; parameter in the initial {{{&lt;script&gt;}}} marker (e.g., {{{&lt;script src=&quot;demo.js&quot;&gt;&lt;/script&gt;}}}). This is particularly useful when incorporating third-party javascript libraries for use in custom extensions and plugins. The 'foreign' javascript code remains isolated in a separate file that can be easily replaced whenever an updated library file becomes available.
''Display script source in tiddler output''
By including the keyword parameter &quot;show&quot;, in the initial {{{&lt;script&gt;}}} marker, the plugin will include the script source code in the output that it displays in the tiddler.
''Defining javascript functions and libraries:''
Although the external javascript file is loaded while the tiddler content is being rendered, any functions it defines will not be available for use until //after// the rendering has been completed. Thus, you cannot load a library and //immediately// use it's functions within the same tiddler. However, once that tiddler has been loaded, the library functions can be freely used in any tiddler (even the one in which it was initially loaded).
To ensure that your javascript functions are always available when needed, you should load the libraries from a tiddler that will be rendered as soon as your TiddlyWiki document is opened. For example, you could put your {{{&lt;script src=&quot;...&quot;&gt;&lt;/script&gt;}}} syntax into a tiddler called LoadScripts, and then add {{{&lt;&lt;tiddler LoadScripts&gt;&gt;}}} in your MainMenu tiddler.
Since the MainMenu is always rendered immediately upon opening your document, the library will always be loaded before any other tiddlers that rely upon the functions it defines. Loading an external javascript library does not produce any direct output in the tiddler, so these definitions should have no impact on the appearance of your MainMenu.
''Creating dynamic tiddler content''
An important difference between this implementation of embedded scripting and conventional embedded javascript techniques for web pages is the method used to produce output that is dynamically inserted into the document:
* In a typical web document, you use the document.write() function to output text sequences (often containing HTML tags) that are then rendered when the entire document is first loaded into the browser window.
* However, in a ~TiddlyWiki document, tiddlers (and other DOM elements) are created, deleted, and rendered &quot;on-the-fly&quot;, so writing directly to the global 'document' object does not produce the results you want (i.e., replacing the embedded script within the tiddler content), and completely replaces the entire ~TiddlyWiki document in your browser window.
* To allow these scripts to work unmodified, the plugin automatically converts all occurences of document.write() so that the output is inserted into the tiddler content instead of replacing the entire ~TiddlyWiki document.
If your script does not use document.write() to create dynamically embedded content within a tiddler, your javascript can, as an alternative, explicitly return a text value that the plugin can then pass through the wikify() rendering engine to insert into the tiddler display. For example, using {{{return &quot;thistext&quot;}}} will produce the same output as {{{document.write(&quot;thistext&quot;)}}}.
//Note: your script code is automatically 'wrapped' inside a function, {{{_out()}}}, so that any return value you provide can be correctly handled by the plugin and inserted into the tiddler. To avoid unpredictable results (and possibly fatal execution errors), this function should never be redefined or called from ''within'' your script code.//
''Accessing the ~TiddlyWiki DOM''
The plugin provides one pre-defined variable, 'place', that is passed in to your javascript code so that it can have direct access to the containing DOM element into which the tiddler output is currently being rendered.
Access to this DOM element allows you to create scripts that can:
* vary their actions based upon the specific location in which they are embedded
* access 'tiddler-relative' information (use findContainingTiddler(place))
* perform direct DOM manipulations (when returning wikified text is not enough)
&lt;&lt;&lt;
!!!!!Examples
&lt;&lt;&lt;
an &quot;alert&quot; message box:
&gt;&lt;script show&gt;
alert('InlineJavascriptPlugin: this is a demonstration message');
&lt;/script&gt;
dynamic output:
&gt;&lt;script show&gt;
return (new Date()).toString();
&lt;/script&gt;
wikified dynamic output:
&gt;&lt;script show&gt;
return &quot;link to current user: [[&quot;+config.options.txtUserName+&quot;]]&quot;;
&lt;/script&gt;
dynamic output using 'place' to get size information for current tiddler:
&gt;&lt;script show&gt;
if (!window.story) window.story=window;
var title=story.findContainingTiddler(place).id.substr(7);
return title+&quot; is using &quot;+store.getTiddlerText(title).length+&quot; bytes&quot;;
&lt;/script&gt;
creating an 'onclick' button/link that runs a script:
&gt;&lt;script label=&quot;click here&quot; show&gt;
if (!window.story) window.story=window;
alert(&quot;Hello World!\nlinktext='&quot;+place.firstChild.data+&quot;'\ntiddler='&quot;+story.findContainingTiddler(place).id.substr(7)+&quot;'&quot;);
&lt;/script&gt;
loading a script from a source url:
&gt;http://www.TiddlyTools.com/demo.js contains:
&gt;&gt;{{{function demo() { alert('this output is from demo(), defined in demo.js') } }}}
&gt;&gt;{{{alert('InlineJavascriptPlugin: demo.js has been loaded'); }}}
&gt;&lt;script src=&quot;demo.js&quot; show&gt;
return &quot;loading demo.js...&quot;
&lt;/script&gt;
&gt;&lt;script label=&quot;click to execute demo() function&quot; show&gt;
demo()
&lt;/script&gt;
&lt;&lt;&lt;
!!!!!Installation
&lt;&lt;&lt;
import (or copy/paste) the following tiddlers into your document:
''InlineJavascriptPlugin'' (tagged with &lt;&lt;tag systemConfig&gt;&gt;)
&lt;&lt;&lt;
!!!!!Revision History
&lt;&lt;&lt;
''2006.06.01 [1.5.1]'' when calling wikify() on script return value, pass hightlightRegExp and tiddler params so macros that rely on these values can render properly
''2006.04.19 [1.5.0]'' added 'show' parameter to force display of javascript source code in tiddler output
''2006.01.05 [1.4.0]'' added support 'onclick' scripts. When label=&quot;...&quot; param is present, a button/link is created using the indicated label text, and the script is only executed when the button/link is clicked. 'place' value is set to match the clicked button/link element.
''2005.12.13 [1.3.1]'' when catching eval error in IE, e.description contains the error text, instead of e.toString(). Fixed error reporting so IE shows the correct response text. Based on a suggestion by UdoBorkowski
''2005.11.09 [1.3.0]'' for 'inline' scripts (i.e., not scripts loaded with src=&quot;...&quot;), automatically replace calls to 'document.write()' with 'place.innerHTML+=' so script output is directed into tiddler content. Based on a suggestion by BradleyMeck
''2005.11.08 [1.2.0]'' handle loading of javascript from an external URL via src=&quot;...&quot; syntax
''2005.11.08 [1.1.0]'' pass 'place' param into scripts to provide direct DOM access
''2005.11.08 [1.0.0]'' initial release
&lt;&lt;&lt;
!!!!!Credits
&lt;&lt;&lt;
This feature was developed by EricShulman from [[ELS Design Studios|http:/www.elsdesign.com]]
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.inlineJavascript= {major: 1, minor: 5, revision: 1, date: new Date(2006,6,1)};
config.formatters.push( {
name: &quot;inlineJavascript&quot;,
match: &quot;\\&lt;script&quot;,
lookahead: &quot;\\&lt;script(?: src=\\\&quot;((?:.|\\n)*?)\\\&quot;)?(?: label=\\\&quot;((?:.|\\n)*?)\\\&quot;)?( show)?\\&gt;((?:.|\\n)*?)\\&lt;/script\\&gt;&quot;,
handler: function(w) {
var lookaheadRegExp = new RegExp(this.lookahead,&quot;mg&quot;);
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if(lookaheadMatch &amp;&amp; lookaheadMatch.index == w.matchStart) {
if (lookaheadMatch[1]) { // load a script library
// make script tag, set src, add to body to execute, then remove for cleanup
var script = document.createElement(&quot;script&quot;); script.src = lookaheadMatch[1];
document.body.appendChild(script); document.body.removeChild(script);
}
if (lookaheadMatch[4]) { // there is script code
if (lookaheadMatch[3]) // show inline script code in tiddler output
wikify(&quot;{{{\n&quot;+lookaheadMatch[0]+&quot;\n}}}\n&quot;,w.output);
if (lookaheadMatch[2]) { // create a link to an 'onclick' script
// add a link, define click handler, save code in link (pass 'place'), set link attributes
var link=createTiddlyElement(w.output,&quot;a&quot;,null,&quot;tiddlyLinkExisting&quot;,lookaheadMatch[2]);
link.onclick=function(){try{return(eval(this.code))}catch(e){alert(e.description?e.description:e.toString())}}
link.code=&quot;function _out(place){&quot;+lookaheadMatch[4]+&quot;};_out(this);&quot;
link.setAttribute(&quot;href&quot;,&quot;javascript:;&quot;); link.setAttribute(&quot;title&quot;,&quot;&quot;); link.style.cursor=&quot;pointer&quot;;
}
else { // run inline script code
var code=&quot;function _out(place){&quot;+lookaheadMatch[4]+&quot;};_out(w.output);&quot;
code=code.replace(/document.write\(/gi,'place.innerHTML+=(');
try { var out = eval(code); } catch(e) { out = e.description?e.description:e.toString(); }
if (out &amp;&amp; out.length) wikify(out,w.output,w.highlightRegExp,w.tiddler);
}
}
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
} )
//}}}</pre>
</div>
<div title="InstanceHandle" modifier="Ichthyostega" created="200902090320" changecount="2">
<pre>An RAII class, used to manage a [[facade interface between layers|LayerSeparationInterface]].
The InstanceHandle is created by the service implementation and will automatically
* either register and open a ''C Language Interface'', using Lumiera's InterfaceSystem
* or load and open a LumieraPlugin, also by using the InterfaceSystem
* and optionally also create and manage a facade proxy to allow transparent access for client code
&amp;rarr; see [[detailed description here|LayerSeparationInterfaces]]
</pre>
</div>
<div title="InterfaceNamespaces" modifier="Ichthyostega" modified="201003160209" created="200708080338" tags="impl decision draft" changecount="16">
<pre>Because we rely on strong decoupling and separation into self contained components, there is not much need for a common quasi-global namespace. Operations needing the cooperation of another subsystem will be delegated or even dispatched, consequently implementation code needs only the service acces points from &quot;direct cooperation partner&quot; subsystems. Hierarchical scopes besides classes are needed only when multiple subsystems share a set of common abstractions. Interface and Implementation use separate namespaces.
!common definitions
# surely there will be the need to use some macros (albeit code written in C++ can successfully avoid macros to some extent)
# there will be one global ''Application State'' representation (and some application services)
# we have some lib facilities, especially a common [[Time]] abstraction
For these we have one special (bilingual) header file __lumiera.h__, which places in its C++ part some declarations into __namespace lumiera__. These declarations should be pulled into the specific namespaces or (still better) into the implementation //on demand//. There is no nesting (we deliberately don't want an symbol appearing //automatically// in every part of the system).
!subsystem interface and facade
These large scale interfaces reside in special namespaces &quot;~XXX_interface&quot; (where XXX is the subsystem). The accompanning //definitions// depend on the implementation namespace and are placed in the top-level source folder of the corresponding subsystem.
&amp;rarr; Example: [[Interfaces/Namespaces of the Session Subsystem(s)|InterfacesSession]]
!contract checks and test code
From experiences with other middle scale projects, I prefer having the test code in a separate tree (because test code easily doubles the number of source files). But of course it should be placed into the same namespace as the code being checked, or (better?) into a nested namespace &quot;test&quot;. It is esp. desirable to have good coverage on the contracts of the subsystem interfaces and mayor components (while it is not always feasible or advisable to cover every implementation detail).
&amp;rarr; see also [[testsuite documentation in the main wiki|index.html#TestSuite]]
</pre>
</div>
<div title="InterfacesSession" modifier="Ichthyostega" modified="200906071811" created="200708080639" tags="img" changecount="8">
<pre>* Subdir src/proc contains Interface within namespace proc_interface
* Subdir src/proc/mobject contains commonly used entities (namespace mobject)
** nested namespace controller
** nested namespace builder
** nested namespace session
* Subdir src/proc/engine (namespace engine) uses directly the (local) interface components StateProxy and ParamProvider; triggering of the render process is initiated by the controller and can be requested via the controller facade. Normally, the playback/render controller located in the backend will just use this interface and won't be aware of the build process at all.
[img[Example: Interfaces/Namespaces of the ~Session-Subsystems|uml/fig130053.png]]
</pre>
</div>
<div title="LayerSeparationInterface" modifier="Ichthyostega" modified="200904302314" created="200902080635" tags="def" changecount="2">
<pre>A major //business interface// &amp;mdash; used by the layers for interfacing to each other; also to be invoked externally by scripts.
&amp;rarr; [[overfiew and technical details|LayerSeparationInterfaces]]
</pre>
</div>
<div title="LayerSeparationInterfaces" modifier="Ichthyostega" modified="200902090419" created="200812050619" tags="overview spec img" changecount="22">
<pre>Lumiera uses a 3-layered architecture. Separation between layers is crucial. Any communication between the layers regarding the normal operation of the application should //at least be initiated// through ''Layer abstraction interfaces'' (Facade Interfaces). This is a low-impact version of layering, because, aside from this triggering, direct cooperation of parts within the single Lumiera process is allowed, under the condition that is is implemented using additional abstractions (interfaces with implementation level granularity). We stick to the policy of //disallowing direct coupling of implementations located in different layers.//
[&gt;img[Anatomy of a Layer Separation Interface|uml/fig132869.png]]
The goal is for the interface to remain fairly transparent for the client and to get an automatic lifecycle management.
To implement such a structure, each layer separation interface actually is comprised of several parts:
* an C Language Interface (&quot;''CL Interface''&quot;) to be installed into the InterfaceSystem
* a ''facade interface'' defining the respective abstractions in terms of the client side impl. language (C or C++)
* a ''service implementation'' directly addressed by the implementation instantiated within the InterfaceSystem
* a ''facade proxy'' object on the client side, which usually is given inline alongside with the CL interface definition.
!opening and closing
Handling the lifecycle can be tricky, because client- and service-side need to carry out the opening and closing operations in sync and observing a specific call order to ensure calls get blocked already on the client side unless the whole interface compound is really up and running. To add to this complexity, plugins and built-in interfaces are handled differently regarding the question who is in charge of the lifecycle: interfaces are installed on the service side, whereas the loading of plugins is triggered from client side by requesting the plugin from the loader.
Anyway, interfaces are resources which best should be managed automatically. At least within the C++ part of the application we can ensure this by using the InstanceHandle template. This way the handling of plugins and interfaces can be unified and the opening and closing of the facade proxy happens automatically.
The general idea is, that each facade interface actually provides access to a specific service; there will always be a single implementation object somewhere, which can be thought of as acting as &quot;the&quot; service. This service-providing object will then contain the mentioned InstanceHandle; thus, its lifecycle becomes identical with the service lifecycle.
* when the service relies on a [[plugin|LumieraPlugin]], this service providing object (containing the InstanceHandle) needs to sit at the service accessing side (as the plugin may not yet be loaded and someone has to pull it up).
* otherwise, when the service is just an interface to an already loaded facility, the service providing object (containing the InstanceHandle) will live on the service providing side, not the client side. Then the ctor of the service providing object needs to be able to access an interface instance definition (&amp;rarr; InterfaceSystem); the only difference to the plugin case is to create the InstanceHandle with a different ctor, passing the interface and the implementing instance.
!outline of the major interfaces
|!Layer|&gt;|!Interface |
|GUI|GuiFacade|UI lifecycle &amp;rarr; GuiStart|
|~|GuiNotificationFacade|status/error messages, asynchronous object status change notifications, trigger shutdown|
|~|DisplayFacade|pushing frames to a display/viewer|
|Proc|SessionFacade|session lifecycle|
|~|EditFacade|edit operations, object mutations|
|~|PlayerDummy|player mockup, maybe move to backend?|
|//Lumiera's major interfaces//|c
</pre>
</div>
<div title="LoadingMedia" modifier="Ichthyostega" modified="200806030203" created="200709220005" tags="design spec" changecount="4">
<pre>Opening and accessing media files on disk poses several problems, most of which belong to the domain of Lumiera's data backend. Here, we focus on the questions related to making media data available to the session and the render engine. Each media will be represented by an MediaAsset object, which indeed could be a compound object (in case of MultichannelMedia). Building this asset object thus includes getting informations from the real file on disk. For delegating this to the backend, we use the following query interface:
* {{{queryFile(char* name)}}} requests accessing the file and yields some (opaque) handle when successful.
* {{{queryChannel(fHandle, int)}}} will then be issued in sequence with ascending index numbers, until it returns {{{NULL}}}.
* the returned struct (pointer) will provide the following information:
** some identifier which can be used to create a name for the corresponding media (channel) asset
** some identifier characterizing the access method (codec) needed to get at the media data. This should be rather a high level description of the media stream type, e.g. &quot;H264&quot;
** some (opaque) handle usable for accessing this specific stream. When the render engine later on pulls data for this channel, it will pass this handle down to the backend.
{{red{to be defined in more detail later...}}}
&amp;rarr; see &quot;~MediaAccessFacade&quot; for a (preliminary) interface definitioin
&amp;rarr; see &quot;~MediaAccessMock&quot; for a mock/test implementaion
</pre>
</div>
<div title="LocatingPin" modifier="Ichthyostega" created="200710181527" changecount="1">
<pre>Used to actually implement the various kinds of [[Placement]] of ~MObjects. ~LocatingPin is the root of a hierarchy of different kinds of placing, constraining and locating a Media Object. Basically, this is an instance of the ''state pattern'': The user sees one Placement object with value semantics, but when the properties of the Placement are changed, actually a ~LocatingPin object (or rather a chain of ~LocatingPins) is changed within the Placement. Subclasses of ~LocatingPin implement different placing/constraining behaviour:
* {{{FixedLocation}}} places a MObject to a fixed temporal position and track
* {{{RelativeLocation}}} is used to atach the MObject to some other anchor MObject
* //additional constraints, placement objectives, range restrictions, pattern rules will follow...//</pre>
</div>
<div title="MObject" modifier="Ichthyostega" modified="200905130114" created="200706220312" tags="def classes" changecount="3">
<pre>All sorts of &quot;things&quot; to be placed and manipulated by the user in the session. This interface abstracts the details and just supposes
* the media object has a duration
* it is allways //placed// in some manner, i.e. it is allways accessed via a [[Placement]]
* {{red{and what else?}}}
&amp;rarr; [[overview of the MObject hierarchy|MObjects]]</pre>
</div>
<div title="MObjectRef" modifier="Ichthyostega" modified="201001112314" created="200904242107" tags="SessionLogic GuiIntegration design img" changecount="27">
<pre>''The Problem of referring to an [[MObject]]'' stems from the object //as a concept// encompassing a wider scope then just the current implementation instance. If the object was just a runtime entity in memory, we could use a simple (language) reference or pointer. Actually, this isn't sufficient, as the object reference will pass LayerSeparationInterfaces, will be handed over to code not written in the same implementation language, will be included in an ''UNDO'' record for the UndoManager, and thus will need to be serialized and stored permanently within the SessionStorage.
Moreover [[MObject instances|MObject]] have a 2-level structure: the core object holds just the properties in a strict sense, i.e. the properties which the object //owns.// Any properties due to putting the object into a specific context, i.e. all relation properties are represented as [[Placement]] of the object. Thus, when viewed from the client side, a reference to a specific ~MObject //instance,// actually denotes a //specific//&amp;nbsp; Placement of this object into the Session.
!Requirements
* just the reference allone is sufficient to access the placement //and//&amp;nbsp; the core object.
* the reference needs to be valid even after internal restructuring in the object store.
* there must be a way to pass these references through serialisation/deserialisation
* we need a plain-C representation of the reference, which ideally should be incorporated into the complete implementation
* references should either be integrated into memory management, or at least it should be possible to detect dangling references
* it should be possible to make handling of the references completely transparent, if the implementation language supports doing so.
!Two models
For the implementation of the object references, as linked to the memory management in general, there seem to be the following approaches
* the handle is a simple table index. Consequently we'll need a garbage collection to deal with deleted objects. This model has several flavours
** using an generation ID to tag the index handle, keep an translation table on each GC, mapping old indices to new ones and raise an error when encountering an outdated handle
** use a specific datastructure allowing the handles to remain valid in case of cleanup/reorganisation. (Hashtable or similar)
** keep track of all index handles and rewrite them on GC
* handles are refcounting smart pointers. This solution is C++ specific, albeit more elegant and minimalistic. As all the main interfaces should be accessible via C bindings, we'd need to use a replacement mechanism on the LayerSeparationInterfaces, which could be mapped to or handled by an C++ smart-ptr. (We used a similar approach for the PlayerDummy design study)
Obviously, the second approach has quite some appeal &amp;mdash; but, in order to use it, we'd have to mitigate its drawbacks: it bears the danger of creating a second separate code path for C language based clients, presumably receiving lesser care, maintenance and stress testing. The mentioned solution worked out earlier this year for the player mockup (1/2009) tries at least partially to integrate the C functionality, as the actual implementation is derived from the C struct used as handle, thus allowing to use the same pointers for both kinds of interface, and in turn by doing the de-allocation by a call through the C dtor function, which is installed as deleter function with the boost::smart_ptr used as base class of the handle. Conceptually, the interface on this handle is related to the actual implementation refered to by the handle (handle == smart_ptr) as if the latter was a subclass of the former. But on the level of the implementation, there is no inheritance relation between the handle and the referent, and especially this allows to define the handle's interface in terms of abstract interface types usable on the client side, while the referent's interface operates on the types of the service implementation. Thus, the drawback of using a C language interface is turned into the advantage of completely separating implementation and client.
!Implementation concept
Presumably, none of the both models is usable as-is; rather we try to reconstruct the viable properties of both, starting out with the more elegant second model. Thus, basically the ''reference is a smart-ptr'' referring to the core object. Additionally, it incorporates a ''systematic ID denoting the location of the placement''. This ID without the smart-ptr part is used for the C-implementation, making the full handle implementation a shortcut for an access sequence, which first querries the placement from the Session, followed by dereferencing the placement to get at the core object. Thus, the implementation builds upon another abstraction, the &amp;rarr; PlacementRef, which in turn assumes for an index within the implementation of the [[session datastructure|SessionDataMem]] to track and retrieve the actual Placement.
[img[Structure of MObjectRef and PlacementRef|uml/fig136581.png]]
!using ~MObject references
~MObject references have a distinct lifecycle: usually, they are created //empty// (invalid ref, inactive state), followed by activating them by attachment to an existing placement within the session. Later on, the reference can be closed (detached, deactivated). Activation can be done either directly by a {{{Placement&lt;MO&gt;&amp;}}}, or indirectly by any {{{Placement::ID}}} tag or even plain LUID denoting a placement known to the PlacementIndex embedded within the [[Session]]. Activation can fail, because the validity of the reference is checked. In this respect, they behave exactly like PlacementRef, and indeed are implemented on top of the latter. From this point onward, the referred ~MObject is kept alive by the reference &amp;mdash; but note, this doesn't extend to the placement, which still may be modified or removed from the session without further notice. {{red{TODO investigate synchronisation guarantees or a locking mechanism}}} Thus, client code should never store direct references to the placement.
~MObject references have value semantics, i.e. they don't have an identity and can be copied freely. Dereferencing yields a direct (language) ref to the MObject, while the placement can be accessed by a separate function {{{getPlacement()}}}. Moreover, the MObjectRef instance provides a direct API to some of the most common query functions you could imagine to call on both the object and the placement (i.e. to find out about the start time, length, ....)
</pre>
</div>
<div title="MObjects" modifier="Ichthyostega" modified="200906071811" created="200706190636" tags="overview img" changecount="17">
<pre>The ~MObjects Subsystem contains everything related to the [[Session]] and the various Media Objects placed within. It is complemented by the Asset Management (see &amp;rarr; [[Asset]]). Examples for [[MObjects |MObject]](&amp;rarr; def) being:
* audio/video clips
* [[effects and plugins|EffectHandling]]
* special facilities like mask and projector
* [[Automation]] sets
* labels and other (maybe functional) markup
This Design strives to achieve a StrongSeparation between the low-level Structures used to carry out the actual rendering and the high level Entities living in the session and being manipulated by the user. In this high level view, the Objects are grouped and located by [[Placements|Placement]], providing a flexible and open way to express different groupings, locations and ordering constraints between the Media Objects.
&amp;rarr; EditingOperations
&amp;rarr; PlacementHandling
&amp;rarr; SessionOverview
[img[Classess related to the session|uml/fig128133.png]]
</pre>
</div>
<div title="MainMenu" modifier="Ichthyostega" modified="200802031758" created="200706172305" changecount="10">
<pre>''[[Lumiera|index.html]]''
[[Proc-Layer|ProcLayer and Engine]]
[[MObjects]]
[[Implementation|ImplementationDetails]]
[[Admin]]
&lt;&lt;fullscreen&gt;&gt;</pre>
</div>
<div title="ManagementAssetRelation" modifier="Ichthyostega" modified="200711220525" created="200708100337" tags="impl decision" changecount="12">
<pre>Problem is: when removing an Asset, all corresponding MObjects need to disappear. This means, besides the obvious ~Ref-Link (MObject referring to an asset) we need backlinks or a sort of registry. And still worse: we need to remove the affected MObject from the object network in the session and rebuild the Fixture...
&amp;rarr; for a general design discussion see [[Relation of Clip and Asset|RelationClipAsset]]
//Currently// Ichthyo considers the following approach:
* all references between MObjects and Assets are implemented as __refcounting__ boost::shared_ptr
* the opposite direction is also a __strong reference__, effectively keeping the clip-MO alive even if it is no longer in use in the session (note this is a cyclic dependency that needs to be actively broken on deletion). This design decision is based on logical considerations (&amp;rarr; see &quot;deletions, Model-2&quot; [[here|RelationClipAsset]]). This back-link is implemented by a Placement which is stored internally within the asset::Clip, it is needed for clean deletion of Assets, for GUI search functions and for adding the Clip to the session (again after been removed, or multiple times as if cloned).
* MObjects and Assets implement an {{{unlink()}}} function releasing any internal links causing circular dependencies. It is always implemented such as to drop //optional// associations while retaining those associations mandatory for fulfilling the objects contract.
* Instead of a delete, we call this unlink() function and let the shared_ptr handle the actual deletion. Thus, even if the object is already unlinked, it is still valid and usable as long as some other entity holds a smart-ptr. An ongoing render process for example can still use a clip asset and the corresponding media asset linked as parent, but this media asset's link to the dependant clip has already been cleared (and the media is no longer registered with the AssetManager of course).
* so the back-link from dependant asset up to the parent asset is mandatory for the child asset to be functional, but preferably it should be {{{const}}} (only used for information retrieval)
* the whole hierarchy has to be crafted accordingly, but this isn't much of a limitation
* we don't use a registry, rather we model the real dependencies by individual dependency links. So a MediaAsset gets links to all Clips created from this Asset and by traversing this tree, we can handle the deletion
* after the deletion, the Fixture needs to be rebuilt.
* but any render processes still can have pointers to the Asset to be removed, and the shared_ptr will ensure, that the referred objects stay alive as long as needed.
{{red{let's see if this approach works...}}}
</pre>
</div>
<div title="ManagementRenderNodes" modifier="Ichthyostega" modified="200810160131" created="200805280200" tags="impl decision" changecount="5">
<pre>Contrary to the &amp;rarr;[[Assets and MObjects|ManagementAssetRelation]], the usage pattern for [[render nodes|ProcNode]] is quite simple: All nodes are created together every time a new segment of the network is being build and will be used together until this segment is re-built, at which point they can be thrown away altogether. While it would be easy to handle the nodes automatically by smart-ptr (the creation is accessible only by use of the {{{NodeFactory}}} anyways), it //seems advisable to care for a bulk allocation/deallocation here.// The reason being not so much the amount of memory (which is expected to be moderate), but the fact the build process can be triggered repeatedly several times a second when tweaking the session, which could lead to fragmentation and memory pressure.
__10/2008__: the allocation mechanism can surely be improved later, but for now I am going for a simple implementation based on heap allocated objects owned by a vector or smart-ptrs. For each segment of the render nodes network, we have several families of objects, each of with will be maintained by a separate low-level memory manager (as said, for now implemented as vector of smart-ptrs). Together, they form an AllocationCluster; all objects contained in such a cluster will be destroyed together.
</pre>
</div>
<div title="MarkupPreHead" modifier="Ichthyostega" modified="200802030405" created="200706172303" changecount="2">
<pre>&lt;!--{{{--&gt;
&lt;link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/&gt;
&lt;!--}}}--&gt;
&lt;style type=&quot;text/css&quot;&gt;#contentWrapper {display:none;}&lt;/style&gt;&lt;div id=&quot;SplashScreen&quot; style=&quot;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;&quot;&gt;loading &lt;b&gt;Proc-Layer&lt;/b&gt; devel doku&lt;blink&gt; ...&lt;/blink&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-size: 14px; color:red;&quot;&gt;Requires Javascript.&lt;/span&gt;&lt;/div&gt;</pre>
</div>
<div title="MediaAsset" modifier="Ichthyostega" modified="201003140236" created="200709021530" tags="def classes img" changecount="10">
<pre>The Interface asset::Media is a //key abstraction// It ties together several concepts and enables to deal with them on the interfaces in a uniform manner. Besides, like every Asset kind, it belongs rather to the bookkeeping view: an asset::Media holds the specific properties and parametrisation of the media source it stands for. Regarding the __inward interface__ &amp;mdash; as used from within the [[model|HighLevelModel]] or the [[render nodes|ProcNode]], it is irrelevant if any given asset::Media object stands for a complete media source, just a clip taken from this source or if a placeholder version of the real media source is used instead.
[img[Asset Classess|uml/fig130437.png]]
{{red{NOTE 3/2010:}}} Considering to change that significantly. Especially considering to collapse clip-asset and clip-MO into a single entity with multiple inheritance
&amp;rarr; regarding MultichannelMedia (see the notes at bottom)
&amp;rarr; see also LoadingMedia
</pre>
</div>
<div title="MediaImplLib" modifier="Ichthyostega" modified="200809251942" created="200809220304" tags="def spec" changecount="5">
<pre>The Proc-Layer is designed such as to avoid unnecessary assumptions regarding the properties of the media data and streams. Thus, for anything which is not completely generic, we rely on an abstract [[type description|StreamTypeDescriptor]], which provides a ''Facade'' to an actual library implementation. This way, the fundamental operations can be invoked, like allocating a buffer to hold media data.
In the context of Lumiera and especially in the Proc-Layer, __media implementation library__ means
* a subsystem which allows to work with media data of a specific kind
* such as to provide the minimal set of operations
** allocating a frame buffer
** describing the type of the data within such a buffer
* and this subsystem or external library has been integrated to be used through Lumiera by writing adaptation code for accessing these basic operations through the [[implementation facade interface|StreamTypeImplFacade]]
* such a link to an type implementation is registered and maintained by the [[stream type manager|STypeManager]]
!Problem of the implementation data types
Because we deliberately won't make any asumptions about the implementation library (besides the ones imposed indirectly by the facade interface), we can't integrate the data types of the library first class into the type system. All we can do is to use marker types and rely on the builder to have checked the compatibility of the actual data beforehand.
It would be possible to circumvent this problem by requiring all supported implementation libraries to be known at compile time, because then the actual media implementation type could be linked to a facade type by generic programming. Indeed, Lumiera follows this route with regards to the possible kinds of MObject or [[Asset]] &amp;mdash; but to the contraty, for the problem in question here, being able to include support for a new media data type just by adding a plugin by far outweights the benefits of compile-time checked implementation type selection. So, as a consequence of this design decision we //note the possibility of the media file type discovery code to be misconfigured// and select the //wrong implementation library at runtime.// And thus the render engine needs to be prepared for the source reading node of any pipe to flounder completely, and protect the rest of the system accordingly
</pre>
</div>
<div title="MemoryManagement" modifier="Ichthyostega" modified="201003160208" created="200708100225" tags="impl decision rewrite" changecount="15">
<pre>Of course: Cinelerra currently leaks memory and crashes regularilly. For the newly written code, besides retaining the same level of performance, a main goal is to use methods and techniques known to support the writing of quality code. So, besides the MultithreadConsiderations, a solid strategy for managing the ownership of allocated memory blocks is necessary right from start.
!Problems
# Memory management needs to work correct in a //fault tolerant environment//. That means that we need to be prepared to //handle on a non-local scale// some sorts of error conditions (without aborting the application). To be more precise: some error condition arises locally, which leads to a local abort and just the disabling/failing of some subsystem without affecting the application as a whole. This can happen on a regular base (e.g. rendering fails) and thus is __no excuse for leaking memory__
# Some (not all) parts of the core application are non-deterministic. That means, we can't tie the memory management to any assumptions on behalf of the execution path
!C++ solution
First of all -- this doesn't concern //every// allocation. It rather means there are certain //dangerous areas// which need to be identified. Anyhow, instead of carrying inherent complexities of the problem into the solution, we should rather look for common solution pattern(s) which help factoring out complexity.
For the case here in question this seems to be the ''resource allocation is construction'' pattern. Which boils down to basically never using bare pointers when concerned with ownership. Instead, ownership should be handled by smart-pointers.
!!usage scenarios
# __existence is being used__: Objects just live for being referred to in a object network. In this case, use refcounting smart-pointers for every ref. (note: problem with cyclic refs)
# __entity bound ownership__: Objects can be tied to some long living entity in the program, which holds the smart-pointer
#* if the existence of these ref-holding entity can be //guaranteed// (as if by contract), then the other users can build a object network with conventional pointers
#* otherwise, when the ref-holding entity //can disappear// in a regular program state, we need weak-refs and checking (because by our postulate the controlled resource needs to be destructed immediately, otherwise we would have the first case, existence == being used)
!!!dangerous uses
* the render nodes &amp;rarr; [[detail analysis|ManagementRenderNodes]] {{red{TODO}}}
* the MObjects in the session &amp;rarr; [[detail analysis|ManagementMObjects]] {{red{TODO}}}
* Asset - MObject relationship. &amp;rarr; [[detail analysis|ManagementAssetRelation]] {{red{TODO}}}
!!!rather harmless
* Frames (buffers), because they belong to a given [[RenderProcess (=StateProxy)|StateProxy]] and are just passed in into the individual [[ProcNode]]s. This can be handled consistently with conventional methods.
* each StateProxy belongs to one top-level call to the [[Controller-Facade|Controller]]
* similar for the builder tools, which belong to a build process. Moreover, they are pooled and reused.
* the [[sequences|Sequence]] and the defined [[assets|Asset]] belong together to one [[Session]]. If the Session is closed, this means a internal shutdown of the whole ProcLayer, i.e. closing of all GUI representations and terminating all render processes. If these calles are implemented as blocking operations, we can assert that as long as any GUI representation or any render process is running, there is a valid session and model.
!using Factories
And, last but not least, doing large scale allocations is the job of the backend. Exceptions being long-lived objects, like the session or the sequences, which are created once and don't bear the danger of causing memory pressure. Besides, the ProcLayer code shouldn't issue &quot;new&quot; and &quot;delete&quot; when it comes in hand, rather it should use some centralized [[Factories]] for all allocation and freeing, so we can redirect these calls down to the backend, which may use pooling or special placement allocators or the like. The rationale is, for modern hardware/architectures, care has to be taken with heap allocations, esp. with many small objects and irregular usage patterns.
</pre>
</div>
<div title="Model" modifier="Ichthyostega" modified="201003210021" created="201003210020" tags="overview" changecount="2">
<pre>Lumiera's Proc-Layer is built around //two interconnected models,// mediated by the [[Builder]]. Basically, the &amp;rarr;[[Session]] is an external interface to the HighLevelModel, while the &amp;rarr;RenderEngine operates the structures of the LowLevelModel.</pre>
</div>
<div title="ModelDependencies" modifier="Ichthyostega" modified="201003220007" created="201003020150" tags="SessionLogic operational spec draft" changecount="31">
<pre>Our design of the models (both [[high-level|HighLevelModel]] and [[low-level|LowLevelModel]]) relies partially on dependent objects being kept consitently in sync. Currently (2/2010), __ichthyo__'s assessment is to consider this topic not important and pervasive enough to justify building a dedicated solution, like e.g. a central tracking and registration service. An important point to consider with this assesment is the fact that the session implementation is beeing kept mostly single-threaded. Thus, lacking one central place to handle this issue, care has to be taken to capture and treat all the relevant individual dependencies properly at the implementation level.
!known interdependencies
* the session API relies on two kinds of facade like assets: [[Timeline]] and [[Sequence]], linked to the BindingMO and Track objects within the model respectively.
* conceptually, the DefaultsManagement and the AssetManager count as being part of the [[global model scope|ModelRootMO]], but, due to their importance, these facilities are accessible through an singleton interface.
* currently as of 2/2010 the exact dependency of the automation calculation during the render process onto the automation definitions within the HighLevelModel remains to be specified.
!!Timelines and Sequences
While implemented as StructAsset, additionally we need to assure every instance gets linked to the relevant parts of the model and registered with the session. Contrast this with other kinds of assets, which may just remain enlisted, but never actually used.
;the Session
:...is linked 1:1 with timelines and sequences. Registration and deregistration is directly tied to creation and destruction.
: __created__ &amp;rArr; default timeline
: __destroy__ &amp;rArr; discard all timelines, discard all sequences
;Timeline
:acts as facade and is implemented by an root-attached BindingMO. Can't exist in isolation.
: __created__ &amp;rArr; create a dedicated new binding, either useing an existing sequence, or a newly created empty sequence
: __destroy__ &amp;rArr; remove binding, while the previously bound sequence remains in model.
;root-placed Binding
:while generally a Binding can exist in the model, when attached to root, a Timeline will be created
: __created__ &amp;rArr; an existing sequence might be given on creation, otherwhise a default configured sequence is created
: __destroy__ &amp;rArr; implemented by detaching from root (see below) prior to purging from the model.
: __attached__ to root &amp;rArr; invoke Timeline creation
: __detached__ from root &amp;rArr; will care to destroy the corresponding timeline
;Sequence
:is completely dependent on a root-scoped track, can optionally be bound, into one/multiple timelines/VirtualClip, or unbound
: __created__ &amp;rArr; mandates specification of an track-MO, ({{red{TODO}}}maybe/necessarily??) placed into root scope
: __destroy__ &amp;rArr; purge the corresponding track from model, including all contents
: __querying__ &amp;rArr; forwards to creating a root-placed track, unless the queried sequence exists already
;root-placed Track
:attachment of a track to root scope is detected magically and causes creation of a Sequence
: __attached__ to root &amp;rArr; invoke sequence creation
: __detached__ from root &amp;rArr; find and discard corresponding sequence {{red{TODO 3/2010: decide if we want this behaviour}}}
: irrespective if the track exists, is empty, or gets purged entirely, only the connection to root scope counts; thus relocating a track by [[Placement]] might cause its scope with all nested contents to become a sequence of its own or become part of another sequence. As sequences aren't required to be bound into a timeline, they may be present in the model as invisible, passive container
</pre>
</div>
<div title="ModelObjectIdentity" modifier="Ichthyostega" modified="201001072246" created="201001070905" tags="SessionLogic spec draft" changecount="18">
<pre>When it comes to addressing and distinguishing object instances, there are two different models of treatment, and usually any class can be related to one of these: An object with ''value semantics'' is completely defined through this &quot;value&quot;, and not distinguishable beyond that. Usually, value objects can be copied, handled and passed freely, without any ownership. To the contrary, an object with ''reference semantics'' has an unique identity, even if otherwise completely opaque. It is rather like a facility, &quot;living&quot; somewhere, often owned and managed by another object (or behaving special in some other way). Usually, client code deals with such objects through a reference token (which has value semantics). Care has to be taken with //mutable objects,// as any change might influence the object's identity. While this usually is acceptable for value objects, it is prohibited for objects with reference semantics. These are typically created by //factories// &amp;mdash; and this fabrication is the only process to define the identity.
!Assets
Each [[Asset]] holds an identification tuple; the hash derived from this constant tuple is used as ~Asset-ID.
* the {{{Asset::Ident}}} tuple contains the following information
*# a __name-ID__, which is a human understandable but sanitised word
*# a tree-like classification of the asset's __category__, comprised of
*#* asset kind {{{{AUDIO, VIDEO, EFFECT, CODEC, STRUCT, META}}}}
*#* a path in a virtual classification tree. Some &amp;raquo;folders&amp;laquo; have magic meanings
*# an __origin-ID__ to denote the origin, authorship or organisation, acting like a namespace
*# a __version number__.
Of these, the tuple {{{(org,category,name)}}} defines the unique asset identity, and is hashed into the asset-ID. At most one asset with a given ident-tuple (including version) can exist in the whole system. Any higher version is supposed to be fully backwards compatible to all previous versions. Zero is reserved for internal purposes. {{red{1/10: shouldn't we extend the version to (major,minor), to match the plug-in versioning?}}} Thus, the version can be incremented without changing the identity, but the system won't allow co-existence of multiple versions.
* Assets are ''equality'' comparable (even ''ordered''), based on their //identity// &amp;mdash; sans version.
* Categories are ''equality'' comparable value objects
!~MObjects
As of 1/10, MObjects are mostly placeholders or dummies, because the actual SessionLogic has still to be defined in detail.
The following properties can be considered as settled:
* reference semantics
* non-copyable, created by MObjectFactory, managed automatically
* each ~MObject is associated n:1 to an asset.
* besides that, it has an opaque instance identity, which is never made explicit.
* this identity springs from the way the object is created and manipulated. It is //persistent.//
* because of the ~MObject-identity's nature, there is //no point in comparing ~MObjects.//
* ~MObjects are always attached to the session by a placement, which creates kind-of an //instance//
* thus, because placements act as a subdivision of ~MObject identification, in practice always placements will be compared.
!Placements
[[Placements|Placement]] are somewhat special, as they mix value and reference semantics. First off, they are configuration values, copyable and smart-pointers, referring to a primary subject (clip, effect, track, label, binding,....). But, //by adding a placement to the session,// we create an unique instance-identity. This is implemented by copying the placement into the internal session store and thereby creating a new hash-ID, which is then registered within the PlacementIndex. Thus, a ''placement into the model'' has a distict identity.
* Placements are ''equality'' comparable, based on this instance identity (hash-ID)
* besides, there is an equivalence relation regarding the &quot;placement specification&quot; contained in the [[locating pins|LocatingPin]] of the Placement.
** they can be compared for ''equivalent definition'': the contained definitions are the same and in the same order
** alternatively, they can be checked for ''effective equivalence'': both placements to be compared resolve to the same position
note: the placement equality relation carries over to ~PlacementRef and ~MObjectRef
!Commands
{{red{WIP}}} For now, commands are denoted by an unique, human-readable ID, which is hard-coded in the source. We might add an LUID and a version numbering scheme later on.
Commands are used as ''prototype object'' &amp;mdash; thus we face special challenges regarding the identity, which haven't yet been addressed.
!References and Handles
These are used as token for dealing with other objects and have no identity of their own. PlacementRef tokens embody a copy of the referred placement's hash-ID. MObjectRef handles are built on top of the former, additionally holding a smart-ptr to the primary subject.
* these reference handles simply reflect the equality relation on placements, by virtue of comparing the hash-ID.
* besides, we could build upon the placement locating chain equivalence relations to define a semantic equivalence on ~MObjectRefs
</pre>
</div>
<div title="ModelRootMO" modifier="Ichthyostega" modified="200912110245" created="200912080307" tags="def" changecount="5">
<pre>A special kind of MObject, serving as a marker or entry point at the root of the HighLevelModel. As any ~MObject, it is attached to the model by a [[Placement]]. And in this special case, this placement froms the ''root scope'' of the model, thus containing any other PlacementScope (e.g. tracks, clips with effects,...)
This special ''session root object'' provides a link between the model part and the &amp;raquo;bookkeeping&amp;laquo; part of the session, i.e. the [[assets|Asset]]. It is created and maintained by the session (implementation level) &amp;mdash; allowing to store and load the asset definitions as contents of the model root element.
__Note__: nothing within the PlacementIndex requires the root object to be of a specific type; the index just assumes a {{{Placement&lt;MObject&gt;}}} (or subclass) to exist as root element. And indeed, for several unit tests we create an Index mock with a tree of dummy ~MObjects and temporarily shaddow the &quot;real&quot; PlacementIndex by this mock (&amp;rarr; see SessionServices for the API allowing to access this //mock index//- functionality)
</pre>
</div>
<div title="MultichannelMedia" modifier="Ichthyostega" modified="201003140238" created="200709200255" tags="design img" changecount="12">
<pre>Based on practical experiences, Ichthyo tends to consider Multichannel Media as the base case, while counting media files providing just one single media stream as exotic corner cases. This may seem counter intuitive at first sight; you should think of it as an attempt to avoid right from start some of the common shortcomings found in many video editors, especially
* having to deal with keeping a &quot;link&quot; between audio and video clips
* silly limitations on the supported audio setups (e.g. &quot;sound is mono, stereo or Dolby-5.1&quot;)
* unnecessary complexity when dealing with more realistic setups, esp. when working on dialogue scenes
* inability to edit stereoscopic (3D) video in a natural fashion
!Compound Media
[&gt;img[Outline of the Build Process|uml/fig131333.png]]
Basically, each [[media asset|MediaAsset]] is considered to be a compound of several elementary media (tracks), possibly of various different media kinds. Adding support for placeholders (''proxy clips'') at some point in future will add still more complexity (because then there will be even dependencies between some of these elementary media). To handle, edit and render compound media, we need to impose some structural limitations. But anyhow, we try to configure as much as possible already at the &quot;asset level&quot; and make the rest of the proc layer behave just according to the configuration given with each asset.
So, when creating a clip out of such a compound media asset, the clip has to be a compound of elementary clips mirroring the given media asset's structure. Besides, it should be possible to //detach// and //attach// elementary clips from a compound clip. On the other hand, the [[Fixture]] created from the current state of the session is explicit to a great extent. So, in the Fixture we deal only with elementary clips placed to absolute positions, and thus the builder will see only simple non-compound clips and translate them into the corresponding source reading nodes.
!Handling
* from a Media asset, we can get a [[Processing Pattern (ProcPatt)|ProcPatt]] describing how to build a render pipeline for this media
* we can create a Clip (MObject) from each Media, which will be linked back to the media asset internally.
* moreover, creating a Clip will create and register a Clip asset as well, and this Clip asset will be tied to the original Clip and will show up in some special Category
* media can be compound and the created Clips will mirror this compound structure
* we distinguish elementay (non-compound) Clips from compound clips by concrete subtype. {{red{really?? doesn't seem so much like a good idea to me anymore 1/10}}} The builder can only handle elementary clips, because he needs to build a separate pipeline for every output channel. So the work of splitting common effect stacks for clips with several channels needs to be done when calculating the [[Fixture]] for the current session. The Builder expects to be able to build the render nodes corresponding to each entity found in the Fixture one by one.
* the Builder gets at the ProcPatt (descriptor) of the underlying media for each clip and uses this description as a template to build the render pipeline. That is, the ProcPatt specifies the codec asset and maybe some additional effect assets (deinterlace, scale) necessary for feeding media data corresponding to this clip/media into the render nodes network.
!{{red{Reviewed 3/2010}}}
While the general approach and reasoning remains valid, a lot of the details looks dated meanwhile.
* it is //not//&amp;nbsp; true that the builder can be limited to the handling single processing chains. Some effects ineed need to be fed with mutliple channels &amp;mdash; most notably panners and compressors.
* consequently there is the need for an internal representation of the media StreamType.
* thus we can assume the presence of some kind of //type system//
* which transforms the individual &quot;stream&quot; into a entirely conceptual entity within the HighLevelModel
* moreover it is clear that the channel configuration needs to be flexible, and not automatically bound to the existence of a single media with that configuration.
* and last but not least, we handle nested sequences as virtual clips with virtual media.
&amp;rArr; conclusions
* while the asset related parts remain as specified, we get a distinct ChannelConfig asset instead of the clip asset (which seems to be redundant)
* either the ~ClipMO referres this ChannelConfig asset &amp;mdash; or in case of the VirtualClip a BindingMO takes this role. Clip Asset and MO could be joined into a single entity
* as the BindingMO is also used to implement the top-level timelines, the treatment of global and local pipes is united
</pre>
</div>
<div title="NodeConfiguration" modifier="Ichthyostega" modified="200909041807" created="200909041806" tags="spec Builder Rendering" changecount="2">
<pre>Various aspects of the individual [[render node|ProcNode]] are subject to configuration and may influence the output quality or the behaviour of the render process.
* the selection //what// actual implementation (plugin) to used for a formally defined &amp;raquo;[[Effect|EffectHandling]]&amp;laquo;
* the intermediary/common StreamType to use within a [[Pipe]]
* the render technology (CPU, hardware accelerated {{red{&amp;rarr; Future}}})
* the ScheduleStrategy (possibly subdividing the calculation of a single frame)
* if this node becomes a possible CachePoint or DataMigrationPoint in RenderFarm mode
* details of picking a suitable [[operation mode|RenderImplDetails]] of the node (e.g. utilitsing &quot;in-place&quot; calculation)
</pre>
</div>
<div title="NodeCreatorTool" modifier="Ichthyostega" modified="200909041744" created="200712100626" tags="def" changecount="3">
<pre>~NodeCreatorTool is a [[visiting tool|VisitorUse]] used as second step in the [[Builder]]. Starting out from a [[Fixture]], the builder first [[divides the Timeline into segments|SegmentationTool]] and then processes each segment with the ~NodeCreatorTool to build a render nodes network (Render Engine) for this part of the timeline. While visiting individual Objects and Placements, the NodeCreaterTool creates and wires the necessary [[nodes|ProcNode]]</pre>
</div>
<div title="NodeFrameNumbering" modifier="Ichthyostega" modified="200810160129" created="200810140254" tags="spec draft" changecount="3">
<pre>!Problem of Frame identification
!Problem of Node numbering
In the most general case the render network may be just a DAG (not just a tree). Especially, multiple exit points may lead down to the same node, and following each of this possible paths the node may be at a different depth on each. This rules out a simple counter starting from the exit level, leaving us with the possibility of either employing a rather convoluted addressing scheme or using arbitrary ID numbers.{{red{...which is what we do for now}}}
</pre>
</div>
<div title="NodeOperationProtocol" modifier="Ichthyostega" modified="200909031217" created="200806010251" tags="Rendering operational" changecount="16">
<pre>The [[nodes|ProcNode]] are wired to form a &quot;Directed Acyclic Graph&quot;; each node knows its predecessor(s), but not its successor(s). The RenderProcess is organized according to the ''pull principle'', thus we find an operation {{{pull()}}} at the core of this process. There is no such thing as an &quot;engine object&quot; calling nodes iteratively or table driven, rather, the nodes themselves issue recursive calls to their predecessor(s). For this to work, we need the nodes to adhere to a specific protocol:
# Node is pulled, with a StateProxy object as parameter (encapsulating the access to the frames or buffers)
# Node may now access current parameter values, using the state accessible via the StateProxy
# using it's //input-output and wiring descriptor,// the Node creates a StateAdapter wrapping the StateProxy for allocating buffers and accessing the required input
# StateAdapter might first try to get the output frames from the Cache in the Backend. In case of failure, a {{{process()}}} call is prepared by generating {{{pull()}}} call(s) for the input
# as late as possible, typically on return, these recursive pull-calls have allocated a buffer containing the input data.
# when input is ready prior to the {{{process()}}} call, output buffers will be allocated, either from the cache, or (if not caching) from the &quot;parent&quot; StateAdapter up the callstack.
# after all buffers are available, the StateAdapter issues the {{{process()}}} call back to the originating node, which now may dereference the frame pointers and do its calculations
# finally, when the {{{pull()}}} call returns, &quot;parent&quot; state originating the pull holds onto the buffers containing the calculated output result.
some points to note:
* the WiringDescriptor is {{{const}}} and precalculated while building (remember another thread may call in parallel)
* when a node is &quot;inplace-capable&quot;, input and output buffer may actually point to the same location
* but there is no guarantee for this to happen, because the cache may be involved (and we can't overwrite the contents of a cache frame)
* generally, a node may have N inputs and M output frames, which are expected to be processed in a single call
&amp;rarr; the [[&quot;mechanics&quot; of the render process|RenderMechanics]]
&amp;rarr; more fine grained [[implementation details|RenderImplDetails]]
</pre>
</div>
<div title="ObjectCreation" modifier="Ichthyostega" modified="201004031621" created="200709030139" tags="impl design" changecount="20">
<pre>We have to consider carefully how to handle the Creation of new class instances. Because, when done naively, it can defeat all efforts of separating subsystems, or &amp;mdash; the other extreme &amp;mdash; lead to a //switch-on-typeID// programming style. We strive at a solution somewhere in the middle by utilizing __Abstract Factories__ on Interface or key abstraction classes, but providing specialized overloads for the different use cases. So in each use case we have to decide if we want to create a instance of some general concept (Interface), or if we have a direct collaboration and thus need the Factory to provide a more specific sub-Interface or even a concrete type.
!Object creation use cases
!![[Assets|Asset]]
|!Action|&gt;|!creates |
|loading a media file|asset::Media, asset::Codec| |
|viewing media|asset::Sequence, session::Clip and Placement (on hold)| for the whole Media, if not already existent|
|mark selection as clip|session::Clip, Placement with unspec. LocatingPin| doesn't add to session|
|loading Plugin|asset::Effect| usually at program startup|
|create Session|asset::Sequence, asset::Timeline, asset::Pipe| |
&amp;rarr; [[creating and registering Assets|AssetCreation]]
&amp;rarr; [[loading media|LoadingMedia]]
!![[MObjects|MObject]]
|add media to sequence|session::Clip, Placement with unspecified LocatingPin| creating whole-media clip on-the-fly |
|add Clip to sequence|copy of Placement| creates intependent Placement of existing ~Clip-MO|
|attach Effect|session::Effect, Placement with RelativeLocation| |
|start using Automation|session::Auto, asset::Dataset, RelativeLocation Placement| |
!Invariants
when creating Objects, certain invariants have to be maintained. Because creating an Object can be considered an atomic operation and must not leave any related objects in an inconsistent state. Each of our interfaces implies some invariants:
* every Placement has a Subject it places
* MObjects are always created to be placed in some way or the other
* [[Assets|Asset]] manage a dependency graph. Creating a derived Object (e.g. a Clip from a Media) implies a new dependency. (&amp;rarr; [[memory management|ManagementAssetRelation]] relies on this)</pre>
</div>
<div title="OpenGL" modifier="Ichthyostega" modified="201003160208" created="200706220345" tags="decision discuss" changecount="4">
<pre>Cinelerra2 introduced OpenGL support for rendering previews. I must admit, I am very unhappy with this, because
* it just supports some hardware
* it makes building difficult
* it can't handle all color models Cinelerra is capable of
* it introduces a separate codepath including some complicated copying of video data into the textures (and back?)
* it can't be used for rendering
So my judgement would be: in contrary to a realtime/gaming application, for quality video editing it is not worth the effort implementing OpenGL support in all details and with all its complexity. I would accept ~OpenGL as an option, if it could be pushed down into a Library, so it can be handled and maintained transparently and doesnt bind our limited developer manpower.
But because I know the opinions on this topc are varying (users tend to be delighted if they hear &quot;~OpenGL&quot;, because it seems to be likted to the notion of &quot;speed&quot; and &quot;power&quot; todays) &amp;mdash; I try to integrate ~OpenGL as a possibility into this design of the Render Engine. But I insist on putting up the requirement that it //must not jeopardize the code structure.//
My proposed aproach is to treat OpenGL as a separate video raw data type, requiring separete and specialized [[Processing Nodes|ProcNode]] for all calculations. Thus the Builder could connect OpenGL nodes if it is possible to cover the render path in whole or partially or maybe even just for preview.
</pre>
</div>
<div title="OperationPoint" modifier="Ichthyostega" modified="200909041742" created="200805270334" tags="def impl Builder" changecount="8">
<pre>A low-level abstraction within the [[Builder]] &amp;mdash; it serves to encapsulate the details of making multi-channel connections between the render nodes: In some cases, a node can handle N channels internally, while in other cases we need to replicate the node N times and wire each channel individually. As it stands, the OperationPoint marks the ''borderline between high-level and low-level model'': it is invoked in terms of ~MObjects and other entities of the high-level view, but internally it manages to create ProcNode and similar entities of the low-level model.
The operation point is provided by the current BuilderMould and used by the [[processing pattern|ProcPatt]] executing within this mould and conducting the current build step. The operation point's interface allows //to abstract//&amp;nbsp; these details, as well as to //gain additional control//&amp;nbsp; if necessary (e.g. addressing only one of the channels). The most prominent build instruction used within the processing patterns (which is the instruction {{{&quot;attach&quot;}}}) relies on the aforementioned //approach of abstracted handling,// letting the operation point determine automatically how to make the connection.
This is possible because the operation point has been provided (by the mould) with informations about the media stream type to be wired, which, together with information accessible at the the [[render node interface|ProcNode]] and from the [[referred processing assets|ProcAsset]], with the help of the [[connection manager|ConManager]] allows to figure out what's possible and how to do the desired connections. Additionally, in the course of deciding about possible connections, the PathManager is consulted to guide strategic decisions regarding the [[render node configuration|NodeConfiguration]], possible type conversions and the rendering technology to employ.
</pre>
</div>
<div title="Overview" modifier="Ichthyostega" modified="200906071810" created="200706190300" tags="overview img" changecount="13">
<pre>The Lumiera Processing Layer is comprised of various subsystems and can be separated into a low-level and a high-level part. At the low-level end is the [[Render Engine|OverviewRenderEngine]] which basically is a network of render nodes cooperating closely with the Backend Layer in order to carry out the actual playback and media transforming calculations. Whereas on the high-level side we find several different [[Media Objects|MObjects]] that can be placed into the session, edited and manipulated. This is complemented by the [[Asset Management|Asset]], which is the &quot;bookkeeping view&quot; of all the different &quot;things&quot; within each [[Session|SessionOverview]].
There is rather strong separation between these two levels, and &amp;mdash; &lt;br/&gt;correspondingly you'll encounter the data held within the Processing Layer organized in two different views, the ''high-level-model'' and the ''low-level-model''
* from users (and GUI) perspective, you'll see a [[Session|SessionOverview]] with a timeline-like structure, where various [[Media Objects|MObjects]] are arranged and [[placed|Placement]]. By looking closer, you'll find that there are data connections and all processing is organized around processing chains or [[pipes|Pipe]], which can be either global (in the Session) or local (in real or [[virtual|VirtualClip]] clips)
* when dealing with the actual calculations in the Engine (&amp;rarr; see OverviewRenderEngine), you won't find any Tracks, Media Objects or Pipes &amp;mdash; rather you'll find a network of interconnected [[render nodes|ProcNode]] forming the low level model. Each structurally constant segment of the timeline corresponds to a separate node network providing an ExitNode corresponding to each of the global pipes; pulling frames from them means running the engine.
* it is the job of the [[Builder]] create and wire up this render nodes network when provided with a given hig-level-model. So, actually the builder (together with the so called [[Fixture]]) form an isolation layer in the middle, separating the //editing part&amp;nbsp;// from the //processing part.//
[img[Block Diagram|uml/fig128005.png]]
</pre>
</div>
<div title="OverviewRenderEngine" modifier="Ichthyostega" modified="200906071810" created="200706190647" tags="Rendering overview img" changecount="20">
<pre>Render Engine, [[Builder]] and [[Controller]] are closely related Subsystems. Actually, the [[Builder]] //creates// a newly configured Render Engine //for every// RenderProcess. Before doing so, it queries from the Session (or, to be more precise, from the [[Fixture]] within the current Session) all necessary Media Object Placement information. The [[Builder]] then derives from this information the actual assembly of [[Processing Nodes|ProcNode]] comprising the Render Engine. Thus:
* the source of the build process is a sequence of absolute (explicit) [[Placements|Placement]] called the [[Playlist]]
* the [[build process|BuildProcess]] is driven, configured and controlled by the [[Controller]] subsystem component. It encompasses the actual playback configuration and State of the System.
* the resulting Render Engine is a list of [[Processors]], each configured to calculate a segment of the timeline with uniform properties. Each of these Processors in turn is a graph of interconnected ProcNode.s.
see also: RenderEntities, [[two Examples (Object diagrams)|Examples]]
[img[Overview: Components of the Renderengine|uml/fig128261.png]]
</pre>
</div>
<div title="PageTemplate" modifier="Ichthyostega" modified="200706260500" created="200701131624" tags="MPTWTheme excludeMissing" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200706110330" changecount="1">
<pre>&lt;!--{{{--&gt;
&lt;div class='header' macro='gradient vert [[ColorPalette::PrimaryLight]] [[ColorPalette::PrimaryMid]]'&gt;
&lt;div class='headerShadow'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;div class='headerForeground'&gt;
&lt;span class='siteTitle' refresh='content' tiddler='SiteTitle'&gt;&lt;/span&gt;&amp;nbsp;
&lt;span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;!-- horizontal MainMenu --&gt;
&lt;div id='topMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt;
&lt;!-- original MainMenu menu --&gt;
&lt;!-- &lt;div id='mainMenu' refresh='content' tiddler='MainMenu'&gt;&lt;/div&gt; --&gt;
&lt;div id='sidebar'&gt;
&lt;div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'&gt;&lt;/div&gt;
&lt;div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id='displayArea'&gt;
&lt;div id='messageArea'&gt;&lt;/div&gt;
&lt;div id='tiddlerDisplay'&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;!--}}}--&gt;
</pre>
</div>
<div title="ParamProvider" modifier="Ichthyostega" modified="200810170040" created="200706220517" tags="def automation" changecount="12">
<pre>A ParamProvider is the counterpart for (one or many) [[Parameter]] instances. It implements the value access function made available by the Parameter object to its clients.
To give a concrete example:
* a Fade Plugin needs the actual fade value for Frame t=xxx
* the Plugin has a Parameter Object (from which we could query the information of this parameter being a continuous float function)
* this Parameter Object provides a getValue() function, which is internally linked (i.e. by configuration) to a //Parameter Provider//
* the actual object implementing the ParamProvider Interface could be a Automation MObject located somewhere in the session and would do bezier interpolation on a given keyframe set.
* Param providers are created on demand; while building the Render Engine configuration actually at work, the Builder would have to setup a link between the Plugin Parameter Object and the ParamProvider; he can do so, because he sees the link between the Automation MObject and the corresponding Effect MObject
!!ParamProvider ownership and lifecycle
Actually, ParamProvider is just an interface which is implemented either by a constant or an [[Automation]] function. In both cases, access is via direct reference, while the link to the ParamProvider is maintained by a smart-ptr, which &amp;mdash; in the case of automation may share ownership with the [[Placement]] of the automation data set.
&amp;rarr; see the class diagram for [[Automation]]
&amp;rarr; see EffectHandling
</pre>
</div>
<div title="Parameter" modifier="Ichthyostega" modified="200805300124" created="200706220505" tags="def automation" changecount="1">
<pre>Parameters are all possibly variable control values used within the Render Engine. Contrast this with configuration values, which are considered to be fixed and need an internal reset of the application (or session) state to take effect.
A ''Parameter Object'' provides a descriptor of the kind of parameter, together with a function used to pull the //actual value// of this parameter. Here, //actual// has a two-fold meaning:
* if called without a time specification, it is either a global (but variable) system or session parameter or a default value for automated Parameters. (the intention is to treat this cases uniformly)
* if called with a time specification, it is the query for an &amp;mdash; probably interpolated &amp;mdash; [[Automation]] value at this absolute time. The corresponding ParamProvider should fall back transparently to a default or session value if no time varying data is available
{{red{TODO: define how Automation works}}}
</pre>
</div>
<div title="PartTiddlerPlugin" modifier="PauloSoares" modified="200611082325" created="200601251216" tags="excludeLists plugin systemConfig" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200611082325">
<pre>/***
|&lt;html&gt;&lt;a name=&quot;Top&quot;/&gt;&lt;/html&gt;''Name:''|PartTiddlerPlugin|
|''Version:''|1.0.6 (2006-11-07)|
|''Source:''|http://tiddlywiki.abego-software.de/#PartTiddlerPlugin|
|''Author:''|UdoBorkowski (ub [at] abego-software [dot] de)|
|''Licence:''|[[BSD open source license]]|
|''TiddlyWiki:''|2.0|
|''Browser:''|Firefox 1.0.4+; InternetExplorer 6.0|
!Table of Content&lt;html&gt;&lt;a name=&quot;TOC&quot;/&gt;&lt;/html&gt;
* &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Description',null, event)&quot;&gt;Description, Syntax&lt;/a&gt;&lt;/html&gt;
* &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Applications',null, event)&quot;&gt;Applications&lt;/a&gt;&lt;/html&gt;
** &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('LongTiddler',null, event)&quot;&gt;Refering to Paragraphs of a Longer Tiddler&lt;/a&gt;&lt;/html&gt;
** &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Citation',null, event)&quot;&gt;Citation Index&lt;/a&gt;&lt;/html&gt;
** &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('TableCells',null, event)&quot;&gt;Creating &quot;multi-line&quot; Table Cells&lt;/a&gt;&lt;/html&gt;
** &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Tabs',null, event)&quot;&gt;Creating Tabs&lt;/a&gt;&lt;/html&gt;
** &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Sliders',null, event)&quot;&gt;Using Sliders&lt;/a&gt;&lt;/html&gt;
* &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Revisions',null, event)&quot;&gt;Revision History&lt;/a&gt;&lt;/html&gt;
* &lt;html&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Code',null, event)&quot;&gt;Code&lt;/a&gt;&lt;/html&gt;
!Description&lt;html&gt;&lt;a name=&quot;Description&quot;/&gt;&lt;/html&gt;
With the {{{&lt;part aPartName&gt; ... &lt;/part&gt;}}} feature you can structure your tiddler text into separate (named) parts.
Each part can be referenced as a &quot;normal&quot; tiddler, using the &quot;//tiddlerName//''/''//partName//&quot; syntax (e.g. &quot;About/Features&quot;). E.g. you may create links to the parts, use it in {{{&lt;&lt;tiddler...&gt;&gt;}}} or {{{&lt;&lt;tabs...&gt;&gt;}}} macros etc.
''Syntax:''
|&gt;|''&lt;part'' //partName// [''hidden''] ''&gt;'' //any tiddler content// ''&lt;/part&gt;''|
|//partName//|The name of the part. You may reference a part tiddler with the combined tiddler name &quot;//nameOfContainerTidder//''/''//partName//.|
|''hidden''|When defined the content of the part is not displayed in the container tiddler. But when the part is explicitly referenced (e.g. in a {{{&lt;&lt;tiddler...&gt;&gt;}}} macro or in a link) the part's content is displayed.|
|&lt;html&gt;&lt;i&gt;any&amp;nbsp;tiddler&amp;nbsp;content&lt;/i&gt;&lt;/html&gt;|&lt;html&gt;The content of the part.&lt;br&gt;A part can have any content that a &quot;normal&quot; tiddler may have, e.g. you may use all the formattings and macros defined.&lt;/html&gt;|
|&gt;|~~Syntax formatting: Keywords in ''bold'', optional parts in [...]. 'or' means that exactly one of the two alternatives must exist.~~|
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!Applications&lt;html&gt;&lt;a name=&quot;Applications&quot;/&gt;&lt;/html&gt;
!!Refering to Paragraphs of a Longer Tiddler&lt;html&gt;&lt;a name=&quot;LongTiddler&quot;/&gt;&lt;/html&gt;
Assume you have written a long description in a tiddler and now you want to refer to the content of a certain paragraph in that tiddler (e.g. some definition.) Just wrap the text with a ''part'' block, give it a nice name, create a &quot;pretty link&quot; (like {{{[[Discussion Groups|Introduction/DiscussionGroups]]}}}) and you are done.
Notice this complements the approach to first writing a lot of small tiddlers and combine these tiddlers to one larger tiddler in a second step (e.g. using the {{{&lt;&lt;tiddler...&gt;&gt;}}} macro). Using the ''part'' feature you can first write a &quot;classic&quot; (longer) text that can be read &quot;from top to bottom&quot; and later &quot;reuse&quot; parts of this text for some more &quot;non-linear&quot; reading.
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!!Citation Index&lt;html&gt;&lt;a name=&quot;Citation&quot;/&gt;&lt;/html&gt;
Create a tiddler &quot;Citations&quot; that contains your &quot;citations&quot;.
Wrap every citation with a part and a proper name.
''Example''
{{{
&lt;part BAX98&gt;Baxter, Ira D. et al: //Clone Detection Using Abstract Syntax Trees.//
in //Proc. ICSM//, 1998.&lt;/part&gt;
&lt;part BEL02&gt;Bellon, Stefan: //Vergleich von Techniken zur Erkennung duplizierten Quellcodes.//
Thesis, Uni Stuttgart, 2002.&lt;/part&gt;
&lt;part DUC99&gt;Ducasse, Stéfane et al: //A Language Independent Approach for Detecting Duplicated Code.//
in //Proc. ICSM//, 1999.&lt;/part&gt;
}}}
You may now &quot;cite&quot; them just by using a pretty link like {{{[[Citations/BAX98]]}}} or even more pretty, like this {{{[[BAX98|Citations/BAX98]]}}}.
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!!Creating &quot;multi-line&quot; Table Cells&lt;html&gt;&lt;a name=&quot;TableCells&quot;/&gt;&lt;/html&gt;
You may have noticed that it is hard to create table cells with &quot;multi-line&quot; content. E.g. if you want to create a bullet list inside a table cell you cannot just write the bullet list
{{{
* Item 1
* Item 2
* Item 3
}}}
into a table cell (i.e. between the | ... | bars) because every bullet item must start in a new line but all cells of a table row must be in one line.
Using the ''part'' feature this problem can be solved. Just create a hidden part that contains the cells content and use a {{{&lt;&lt;tiddler &gt;&gt;}}} macro to include its content in the table's cell.
''Example''
{{{
|!Subject|!Items|
|subject1|&lt;&lt;tiddler ./Cell1&gt;&gt;|
|subject2|&lt;&lt;tiddler ./Cell2&gt;&gt;|
&lt;part Cell1 hidden&gt;
* Item 1
* Item 2
* Item 3
&lt;/part&gt;
...
}}}
Notice that inside the {{{&lt;&lt;tiddler ...&gt;&gt;}}} macro you may refer to the &quot;current tiddler&quot; using the &quot;.&quot;.
BTW: The same approach can be used to create bullet lists with items that contain more than one line.
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!!Creating Tabs&lt;html&gt;&lt;a name=&quot;Tabs&quot;/&gt;&lt;/html&gt;
The build-in {{{&lt;&lt;tabs ...&gt;&gt;}}} macro requires that you defined an additional tiddler for every tab it displays. When you want to have &quot;nested&quot; tabs you need to define a tiddler for the &quot;main tab&quot; and one for every tab it contains. I.e. the definition of a set of tabs that is visually displayed at one place is distributed across multiple tiddlers.
With the ''part'' feature you can put the complete definition in one tiddler, making it easier to keep an overview and maintain the tab sets.
''Example''
The standard tabs at the sidebar are defined by the following eight tiddlers:
* SideBarTabs
* TabAll
* TabMore
* TabMoreMissing
* TabMoreOrphans
* TabMoreShadowed
* TabTags
* TabTimeline
Instead of these eight tiddlers one could define the following SideBarTabs tiddler that uses the ''part'' feature:
{{{
&lt;&lt;tabs txtMainTab
Timeline Timeline SideBarTabs/Timeline
All 'All tiddlers' SideBarTabs/All
Tags 'All tags' SideBarTabs/Tags
More 'More lists' SideBarTabs/More&gt;&gt;
&lt;part Timeline hidden&gt;&lt;&lt;timeline&gt;&gt;&lt;/part&gt;
&lt;part All hidden&gt;&lt;&lt;list all&gt;&gt;&lt;/part&gt;
&lt;part Tags hidden&gt;&lt;&lt;allTags&gt;&gt;&lt;/part&gt;
&lt;part More hidden&gt;&lt;&lt;tabs txtMoreTab
Missing 'Missing tiddlers' SideBarTabs/Missing
Orphans 'Orphaned tiddlers' SideBarTabs/Orphans
Shadowed 'Shadowed tiddlers' SideBarTabs/Shadowed&gt;&gt;&lt;/part&gt;
&lt;part Missing hidden&gt;&lt;&lt;list missing&gt;&gt;&lt;/part&gt;
&lt;part Orphans hidden&gt;&lt;&lt;list orphans&gt;&gt;&lt;/part&gt;
&lt;part Shadowed hidden&gt;&lt;&lt;list shadowed&gt;&gt;&lt;/part&gt;
}}}
Notice that you can easily &quot;overwrite&quot; individual parts in separate tiddlers that have the full name of the part.
E.g. if you don't like the classic timeline tab but only want to see the 100 most recent tiddlers you could create a tiddler &quot;~SideBarTabs/Timeline&quot; with the following content:
{{{
&lt;&lt;forEachTiddler
sortBy 'tiddler.modified' descending
write '(index &lt; 100) ? &quot;* [[&quot;+tiddler.title+&quot;]]\n&quot;:&quot;&quot;'&gt;&gt;
}}}
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!!Using Sliders&lt;html&gt;&lt;a name=&quot;Sliders&quot;/&gt;&lt;/html&gt;
Very similar to the build-in {{{&lt;&lt;tabs ...&gt;&gt;}}} macro (see above) the {{{&lt;&lt;slider ...&gt;&gt;}}} macro requires that you defined an additional tiddler that holds the content &quot;to be slid&quot;. You can avoid creating this extra tiddler by using the ''part'' feature
''Example''
In a tiddler &quot;About&quot; we may use the slider to show some details that are documented in the tiddler's &quot;Details&quot; part.
{{{
...
&lt;&lt;slider chkAboutDetails About/Details details &quot;Click here to see more details&quot;&gt;&gt;
&lt;part Details hidden&gt;
To give you a better overview ...
&lt;/part&gt;
...
}}}
Notice that putting the content of the slider into the slider's tiddler also has an extra benefit: When you decide you need to edit the content of the slider you can just doubleclick the content, the tiddler opens for editing and you can directly start editing the content (in the part section). In the &quot;old&quot; approach you would doubleclick the tiddler, see that the slider is using tiddler X, have to look for the tiddler X and can finally open it for editing. So using the ''part'' approach results in a much short workflow.
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!Revision history&lt;html&gt;&lt;a name=&quot;Revisions&quot;/&gt;&lt;/html&gt;
* v1.0.6 (2006-11-07)
** Bugfix: cannot edit tiddler when UploadPlugin by Bidix is installed. Thanks to José Luis González Castro for reporting the bug.
* v1.0.5 (2006-03-02)
** Bugfix: Example with multi-line table cells does not work in IE6. Thanks to Paulo Soares for reporting the bug.
* v1.0.4 (2006-02-28)
** Bugfix: Shadow tiddlers cannot be edited (in TW 2.0.6). Thanks to Torsten Vanek for reporting the bug.
* v1.0.3 (2006-02-26)
** Adapt code to newly introduced Tiddler.prototype.isReadOnly() function (in TW 2.0.6). Thanks to Paulo Soares for reporting the problem.
* v1.0.2 (2006-02-05)
** Also allow other macros than the &quot;tiddler&quot; macro use the &quot;.&quot; in the part reference (to refer to &quot;this&quot; tiddler)
* v1.0.1 (2006-01-27)
** Added Table of Content for plugin documentation. Thanks to RichCarrillo for suggesting.
** Bugfix: newReminder plugin does not work when PartTiddler is installed. Thanks to PauloSoares for reporting.
* v1.0.0 (2006-01-25)
** initial version
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!Code&lt;html&gt;&lt;a name=&quot;Code&quot;/&gt;&lt;/html&gt;
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;window.scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
***/
//{{{
//============================================================================
// PartTiddlerPlugin
// Ensure that the PartTiddler Plugin is only installed once.
//
if (!version.extensions.PartTiddlerPlugin) {
version.extensions.PartTiddlerPlugin = {
major: 1, minor: 0, revision: 6,
date: new Date(2006, 10, 7),
type: 'plugin',
source: &quot;http://tiddlywiki.abego-software.de/#PartTiddlerPlugin&quot;
};
if (!window.abego) window.abego = {};
if (version.major &lt; 2) alertAndThrow(&quot;PartTiddlerPlugin requires TiddlyWiki 2.0 or newer.&quot;);
//============================================================================
// Common Helpers
// Looks for the next newline, starting at the index-th char of text.
//
// If there are only whitespaces between index and the newline
// the index behind the newline is returned,
// otherwise (or when no newline is found) index is returned.
//
var skipEmptyEndOfLine = function(text, index) {
var re = /(\n|[^\s])/g;
re.lastIndex = index;
var result = re.exec(text);
return (result &amp;&amp; text.charAt(result.index) == '\n')
? result.index+1
: index;
}
//============================================================================
// Constants
var partEndOrStartTagRE = /(&lt;\/part&gt;)|(&lt;part(?:\s+)((?:[^&gt;])+)&gt;)/mg;
var partEndTagREString = &quot;&lt;\\/part&gt;&quot;;
var partEndTagString = &quot;&lt;/part&gt;&quot;;
//============================================================================
// Plugin Specific Helpers
// Parse the parameters inside a &lt;part ...&gt; tag and return the result.
//
// @return [may be null] {partName: ..., isHidden: ...}
//
var parseStartTagParams = function(paramText) {
var params = paramText.readMacroParams();
if (params.length == 0 || params[0].length == 0) return null;
var name = params[0];
var paramsIndex = 1;
var hidden = false;
if (paramsIndex &lt; params.length) {
hidden = params[paramsIndex] == &quot;hidden&quot;;
paramsIndex++;
}
return {
partName: name,
isHidden: hidden
};
}
// Returns the match to the next (end or start) part tag in the text,
// starting the search at startIndex.
//
// When no such tag is found null is returned, otherwise a &quot;Match&quot; is returned:
// [0]: full match
// [1]: matched &quot;end&quot; tag (or null when no end tag match)
// [2]: matched &quot;start&quot; tag (or null when no start tag match)
// [3]: content of start tag (or null if no start tag match)
//
var findNextPartEndOrStartTagMatch = function(text, startIndex) {
var re = new RegExp(partEndOrStartTagRE);
re.lastIndex = startIndex;
var match = re.exec(text);
return match;
}
//============================================================================
// Formatter
// Process the &lt;part ...&gt; ... &lt;/part&gt; starting at (w.source, w.matchStart) for formatting.
//
// @return true if a complete part section (including the end tag) could be processed, false otherwise.
//
var handlePartSection = function(w) {
var tagMatch = findNextPartEndOrStartTagMatch(w.source, w.matchStart);
if (!tagMatch) return false;
if (tagMatch.index != w.matchStart || !tagMatch[2]) return false;
// Parse the start tag parameters
var arguments = parseStartTagParams(tagMatch[3]);
if (!arguments) return false;
// Continue processing
var startTagEndIndex = skipEmptyEndOfLine(w.source, tagMatch.index + tagMatch[0].length);
var endMatch = findNextPartEndOrStartTagMatch(w.source, startTagEndIndex);
if (endMatch &amp;&amp; endMatch[1]) {
if (!arguments.isHidden) {
w.nextMatch = startTagEndIndex;
w.subWikify(w.output,partEndTagREString);
}
w.nextMatch = skipEmptyEndOfLine(w.source, endMatch.index + endMatch[0].length);
return true;
}
return false;
}
config.formatters.push( {
name: &quot;part&quot;,
match: &quot;&lt;part\\s+[^&gt;]+&gt;&quot;,
handler: function(w) {
if (!handlePartSection(w)) {
w.outputText(w.output,w.matchStart,w.matchStart+w.matchLength);
}
}
} )
//============================================================================
// Extend &quot;fetchTiddler&quot; functionality to also recognize &quot;part&quot;s of tiddlers
// as tiddlers.
var currentParent = null; // used for the &quot;.&quot; parent (e.g. in the &quot;tiddler&quot; macro)
// Return the match to the first &lt;part ...&gt; tag of the text that has the
// requrest partName.
//
// @return [may be null]
//
var findPartStartTagByName = function(text, partName) {
var i = 0;
while (true) {
var tagMatch = findNextPartEndOrStartTagMatch(text, i);
if (!tagMatch) return null;
if (tagMatch[2]) {
// Is start tag
// Check the name
var arguments = parseStartTagParams(tagMatch[3]);
if (arguments &amp;&amp; arguments.partName == partName) {
return tagMatch;
}
}
i += tagMatch[0].length;
}
}
// Return the part &quot;partName&quot; of the given parentTiddler as a &quot;readOnly&quot; Tiddler
// object, using fullName as the Tiddler's title.
//
// All remaining properties of the new Tiddler (tags etc.) are inherited from
// the parentTiddler.
//
// @return [may be null]
//
var getPart = function(parentTiddler, partName, fullName) {
var text = parentTiddler.text;
var startTag = findPartStartTagByName(text, partName);
if (!startTag) return null;
var endIndexOfStartTag = skipEmptyEndOfLine(text, startTag.index+startTag[0].length);
var indexOfEndTag = text.indexOf(partEndTagString, endIndexOfStartTag);
if (indexOfEndTag &gt;= 0) {
var partTiddlerText = text.substring(endIndexOfStartTag,indexOfEndTag);
var partTiddler = new Tiddler();
partTiddler.set(
fullName,
partTiddlerText,
parentTiddler.modifier,
parentTiddler.modified,
parentTiddler.tags,
parentTiddler.created);
partTiddler.abegoIsPartTiddler = true;
return partTiddler;
}
return null;
}
// Hijack the store.fetchTiddler to recognize the &quot;part&quot; addresses.
//
var oldFetchTiddler = store.fetchTiddler ;
store.fetchTiddler = function(title) {
var result = oldFetchTiddler.apply(this, arguments);
if (!result &amp;&amp; title) {
var i = title.lastIndexOf('/');
if (i &gt; 0) {
var parentName = title.substring(0, i);
var partName = title.substring(i+1);
var parent = (parentName == &quot;.&quot;)
? currentParent
: oldFetchTiddler.apply(this, [parentName]);
if (parent) {
return getPart(parent, partName, parent.title+&quot;/&quot;+partName);
}
}
}
return result;
};
// The user must not edit a readOnly/partTiddler
//
config.commands.editTiddler.oldIsReadOnlyFunction = Tiddler.prototype.isReadOnly;
Tiddler.prototype.isReadOnly = function() {
// Tiddler.isReadOnly was introduced with TW 2.0.6.
// For older version we explicitly check the global readOnly flag
if (config.commands.editTiddler.oldIsReadOnlyFunction) {
if (config.commands.editTiddler.oldIsReadOnlyFunction.apply(this, arguments)) return true;
} else {
if (readOnly) return true;
}
return this.abegoIsPartTiddler;
}
config.commands.editTiddler.handler = function(event,src,title)
{
var t = store.getTiddler(title);
// Edit the tiddler if it either is not a tiddler (but a shadowTiddler)
// or the tiddler is not readOnly
if(!t || !t.abegoIsPartTiddler)
{
clearMessage();
story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
story.focusTiddler(title,&quot;text&quot;);
return false;
}
}
// To allow the &quot;./partName&quot; syntax in macros we need to hijack
// the invokeMacro to define the &quot;currentParent&quot; while it is running.
//
var oldInvokeMacro = window.invokeMacro;
function myInvokeMacro(place,macro,params,wikifier,tiddler) {
var oldCurrentParent = currentParent;
if (tiddler) currentParent = tiddler;
try {
oldInvokeMacro.apply(this, arguments);
} finally {
currentParent = oldCurrentParent;
}
}
window.invokeMacro = myInvokeMacro;
// Scroll the anchor anchorName in the viewer of the given tiddler visible.
// When no tiddler is defined use the tiddler of the target given event is used.
window.scrollAnchorVisible = function(anchorName, tiddler, evt) {
var tiddlerElem = null;
if (tiddler) {
tiddlerElem = document.getElementById(story.idPrefix + tiddler);
}
if (!tiddlerElem &amp;&amp; evt) {
var target = resolveTarget(evt);
tiddlerElem = story.findContainingTiddler(target);
}
if (!tiddlerElem) return;
var children = tiddlerElem.getElementsByTagName(&quot;a&quot;);
for (var i = 0; i &lt; children.length; i++) {
var child = children[i];
var name = child.getAttribute(&quot;name&quot;);
if (name == anchorName) {
var y = findPosY(child);
window.scrollTo(0,y);
return;
}
}
}
} // of &quot;install only once&quot;
//}}}
/***
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2006 ([[www.abego-software.de|http://www.abego-software.de]])
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
Neither the name of abego Software nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS IS&quot; AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
&lt;html&gt;&lt;sub&gt;&lt;a href=&quot;javascript:;&quot; onclick=&quot;scrollAnchorVisible('Top',null, event)&quot;&gt;[Top]&lt;/sub&gt;&lt;/a&gt;&lt;/html&gt;
***/</pre>
</div>
<div title="PathManager" modifier="Ichthyostega" created="200909041748" tags="def Builder" changecount="1">
<pre>Facility guiding decisions regarding the strategy to employ for rendering or wiring up connections. The PathManager is querried through the OperationPoint, when executing the connection steps within the Build process.</pre>
</div>
<div title="Pipe" modifier="Ichthyostega" modified="200804110301" created="200801062110" tags="def decision" changecount="6">
<pre>Pipes play an central role within the Proc Layer, because for everything placed and handled within the session, the final goal is to get it transformed into data which can be retrieved at some pipe's exit port. Pipes are special facilities, rather like inventory, separate and not treated like all the other objects.
We don't distinguish between &quot;input&quot; and &quot;output&quot; ports &amp;mdash; rather, pipes are thought to be ''hooks for making connections to''. By following this line of thought, each pipe has an input side and an output side and is in itself something like a ''Bus'' or ''processing chain''. Other processing entities like effects and transitions can be placed (attached) at the pipe, resulting them to be appended to form this chain. Likewise, we can place [[wiring requests|WiringRequest]] to the pipe, meaning we want it connected so to send it's output to another destination pipe. The [[Builder]] may generate further wiring requests to fulfil the placement of other entities.
Thus //Pipes are the basic building blocks// of the whole render network. We distinguish ''global available'' Pipes, which are like the sum groups of a mixing console, and the ''lokal pipe'' or [[source ports|ClipSourcePort]] of the individual clips, which exist only within the duration of the corresponding clip. The design //limits the possible kinds of pipes // to these two types &amp;mdash; thus we can build local processing chains at clips and global processing chains at the global pipes of the session and that's all we can do. (because of the flexibility which comes with the concept of [[placements|Placement]], this is no real limitation)
The GUI can connect the viewer(s) to some pipe (and moreover can use [[probe points|ProbePoint]] placed like effects and connected to some pipe), and likewise, when starting a ''render'', we get the opportunity to specify the pipes to pull the data from. Pulling data from some pipe is the (only) way to activate the render nodes network reachable from this pipe.
&amp;rarr; [[Handling of Tracks|TrackHandling]]
&amp;rarr; [[Handling of Pipes|PipeHandling]]
</pre>
</div>
<div title="PipeHandling" modifier="Ichthyostega" modified="200912080210" created="200801101352" tags="spec" changecount="12">
<pre>!Identification
Pipes are distinct objects and can be identified by their asset ~IDs. Besides, as for all [[structural assets|StructAsset]] there are extended query capabilities, including a symbolic pipe-id and a media (stream) type id. Any pipe can accept and deliver exactly one media stream kind (which may be inherently structured though, e.g. spatial sound systems or stereoscopic video)
!creating pipes
Pipe assets are created automatically by being used and referred. Each [[Timeline]] holds a collection of global pipes, attached to the BindingMO, which is the representation or attachement point of the Timeline within the HighLevelModel ([[Session]]) ({{red{todo: implementation missing as of 11/09}}}), and further pipes can be created by using a new pipe reference in some placement. Moreover, every clip has an (implicit) [[source port|ClipSourcePort]], which will appear as pipe asset when first used (referred) while [[building|BuildProcess]]. Note that creating a new pipe implies using a [[processing pattern|ProcPatt]], which will be queried from the [[Defaults Manager|DefaultsManagement]] (resulting in the use of some preconfigured pattern or maybe the creation of a new ProcPatt object if necessary)
!removal
Deleting a Pipe is an advanced operation, because it includes finding and &quot;detaching&quot; all references, otherwise the pipe will leap back into existence immediately. Thus, global pipe entries in the Session and pipe references in [[locating pins|LocatingPin]] within any placement have to be removed, while clips using a given source port will be disabled. {{red{todo: implementation deferred}}}
!using Pipes
there is not much you can do directly with a pipe asset. It is an point of reference, after all. Any connection to some pipe is only temporarily done by a placement in some part of the timeline, so it isn't stored with the pipe. You can edit the (user visible) description an you can globally disable a pipe asset. The pipe's ID and media stream type of course are fixed, because any connection and referral (via the asset ID) is based on them. Later on, we should provide a {{{rewire(oldPipe, newPipe)}}} to search any ref to the {{{oldPipe}}} and try to rewrite it to use the {{{newPipe}}}, possibly with a new media stream type.
Pipes are integrated with the [[management of defaults|DefaultsManagement]]. For example, any pipe uses implicitly some [[processing pattern|ProcPatt]] &amp;mdash; it may default to the empty pattern. This feature enables to apply some standard wiring to the pipes (e.g a fader for audio, similar to the classic mixing consoles). This //is // a global property of the pipe, but &amp;mdash; contrary to the stream type &amp;mdash; this pattern may be switched
</pre>
</div>
<div title="Placement" modifier="Ichthyostega" modified="200910311753" created="200706220306" tags="Concepts def" changecount="11">
<pre>A Placement represents a //relation:// it is always linked to a //Subject// (this being a [[Media Object|MObject]]) and has the meaning to //place// this Subject in some manner, either relatively to other Media Objects, by some Constraint or simply absolute at (time, output). The latter case is especially important for the build process and thus represented by a special [[Sub-Interface ExplicitPlacement|ExplicitPlacement]]. Besides this simple cases, Placements can also express more specific kinds of &quot;locating&quot; an object, like placing a sound source at a pan position or placing a video clip at a given layer (above or below another video clip)
So basically placements represent a query interface: you can allways ask the placement to find out about the position of the related object in terms of (time, output), and &amp;mdash; depending on the specific object and situation &amp;mdash; also about these additional [[placement derived dimensions|PlacementDerivedDimension]] like sound pan or layer order or similar things which also fit into the general concept of &quot;placing&quot; an object.
The fact of being placed in the [[Session|SessionOverview]] is constitutive for all sorts of [[MObject]]s, without Placement they make no sense. Thus &amp;mdash; technically &amp;mdash; Placements act as ''smart pointers''. Of course, there are several kinds of Placements and they are templated on the type of MObject they are refering to. Placements can be //aggregated// to increasingly constrain the resulting &quot;location&quot; of the refered ~MObject. See &amp;rarr; [[handling of Placements|PlacementHandling]] for more details
!Placements as instance
Effectively, the placement of a given MObject into the Session acts as setting up an concrete instance of this object. This way, placements exhibit a dual nature. When viewed on themselves, like any reference or smart-pointer they behave like values. But, by adding a placement to the session, we again create a unique distinguishable entity with reference semantics: there could be multiple placements of the same object but with varying placement properties. Such a placement-bound-into-the-session is denoted by an generic placement-ID or (as we call it) &amp;rarr; PlacementRef; behind the scenes there is a PlacementIndex keeping track of those &quot;instances&quot; &amp;mdash; allowing us to hand out the PlacementRef (which is just an opaque id) to client code outside the Proc-Layer and generally use it as an shorthand, behaving as if it was an MObject instance
</pre>
</div>
<div title="PlacementDerivedDimension" modifier="Ichthyostega" modified="200805260223" created="200805260219" tags="def spec" changecount="3">
<pre>For any [[media object|MObject]] within the session, we can allways at least query the time (reference/start) point and the output destination from the [[Placement]], by which the object is being handled. But the simple act of placing an object in some way, can &amp;mdash; depending on the context &amp;mdash; create additional degrees of freedom. To list some important examples:
* placing a video clip overlapping with other clips on other tracks creates the possibility for the clip to be above another clip or to be combined in various other ways with the other clips at the same time position
* placing a mono sound object plugged to a stereophoic output destination creates the freedom to define the pan position
The Placement interface allows to query for these additional //parameter values derived from the fact of being placed.//
!defining additional dimensions
probably a LocatingPin but... {{red{TODO any details are yet unknown as of 5/08}}}
!querying additional dimensions
basically you resolve the Placement, yielding an ExplicitPlacement... {{red{TODO but any details of how additional dimensions are resolved is still undefined as of 5/08}}}</pre>
</div>
<div title="PlacementHandling" modifier="Ichthyostega" modified="200911271831" created="200710100124" tags="design impl" changecount="14">
<pre>[[Placement]]s are at the very core of all [[editing operations|EditingOperations]], because they act as handles (smart pointers) to access the [[media objects|MObject]] to be manipulated. Placements themselves are lightweight and can be handled with //value semantics//. But, when adding a Placement to the [[Session]], it gains an distinguishable identity and should be treated by reference from then on: changing the location properties of this placement has a tangible effect on the way the placed object appears in the context of the session. Besides the direct (language) references, there is a special PlacementRef type which builds on this registration of the placement within the session, can be represented as POD and thus passed over external interfaces. Many editing tasks include finding some Placement in the session and reference as parameter. By acting //on the Placement object,// we can change parameters of the way the media object is placed (e.g. adjust an offset), while by //dereferencing//&amp;nbsp; the Placement object, we access the &quot;real&quot; media object (e.g. for trimming its length). Placements are ''templated'' on the type of the actual ~MObject they refer to, thus defining the interface/methods usable on this object.
Actually, the way each Placement ties and locates its subject is implemented by one or several small LocatingPin objects, where subclasses of LocatingPin implement the various different methods of placing and resolving the final location. Notably, we can give a ~FixedLocation or we can atach to another ~MObject to get a ~RelativeLocation, etc. In the typical use case, these ~LocatingPins are added to the Placement, but never retrieved directly. Rather the Placement acts as a ''query interface'' for determining the location of the related object. Here, &quot;location&quot; can be thought of as encompassing multiple dimenstions at the same time. An object can be
* located at a specific point in time
* related to and plugged into a specific output or global bus
* defined to have a position within some [[context-dependant additional dimensions|PlacementDerivedDimension]] like
** the pan position, either on the stereophoic base, or within a fully periphoic (spatial) sound system
** the layer order and overlay mode for video (normal, additive, subtractive, masking)
** the stereoscopic window position (depth parameter) for 3D video
** channel and parameter selection for MIDI data
Placements have //value semantics,// i.e. we don't stress the identity of a placement object (~MObjects on the other hand //do have// a distinguishable identity): initially, you create a Placement parametrized to some specific kind by adding [[LocatingPin]]s (fixed, relative,...) and possibliy you use a subclass of {{{Placement&lt;MObject&gt;}}} to encode additional type information, say {{{Placement&lt;Clip&gt;}}}, but later on, you treat the placement polymorphically and don't care about its kind. The sole purpose of the placement's kind is to select some virtual function implementing the desired behaviour. There is no limitation to one single Placement per ~MObject, indeed we can have several different Placements of the same MObject (from a users point of view, these behave like being clones). Besides, we can ''aggregate'' additional [[LocatingPin]]s to one Placements, resulting in their properties and constraints being combined to yield the actual position of the referred ~MObject.
!design decisions
* the actual way of placing is implemented similar to the ''State Pattern'' by small embedded LocatingPin objects.
* these LocatingPin objects form a ''decorator'' like chain
* resolving into an ExplicitPlacement traverses this chain
* //overconstraining// a placement is not an error, we just stop traversing the chain (ignoring the remaining additional Placements) at the moment the position is completely defined.
* placements can be treated like values, but incorporate an identity tag for the purpose of registering with the session.
* we provide subclasses to be able to form collections of e.g. {{{Placement&lt;Effect&gt;}}}, but we don't stress polymorphism here. &amp;rarr; PlacementType
* Why was the question how to access a ~MObject subinterface a Problem?
*# we want the Session/Fixture to be a collection of Placements. This means, either we store pointers, or Placement needs to be //one// unique type!
*# but if Placement is //a single type//, then we can get only MObjects from a Placement.
*# then either we had to do everything by a visitor (which gets the concrete subtype dynamically), or we'd end up switching on type.
</pre>
</div>
<div title="PlacementIndex" modifier="Ichthyostega" modified="201001112323" created="200905090053" tags="SessionLogic spec impl" changecount="22">
<pre>An implementation facility used to keep track of individual Placements and their relations.
Especially, the [[Session]] maintains such an index, allowing to use the (opaque) PlacementRef tags for referring to a specific &quot;instance&quot; of an MObject, //placed// in a unique way into the current session. And, moreover, this index allows for one placement referring to another placement, so to implement a //relative// placement mode. Because there is an index behind the scenes, it is possible to actually access such a referral in the reverse direction, which is necessary for implementing the desired placement behaviour (if an object instance used as anchor is moved, all objects placed relatively to it have to move accordingly, which necessitates finding those other objects).
Besides searching, [[placement instances|Placement]] can be added to the index, thereby creating a copy managed by the backing data structure. Thus, the session's PlacementIndex is the backbone of the session data structure, and the session's contents are actually contained within it.
!rationale of the choosen implementation
What implementation approach to take for the index largely depends on the usage pattern. Generally speaking, the Lumiera [[Session]] is a collection of MObjects attached by [[Placement]]; any relations between these objects are established on a logical level, implemented as markers within the respective placements. For [[building the render nodes graph|BuildProcess]], a consolidated view of the session's effective contents is created (&quot;[[Fixture]]&quot;), then to be traversed while emitting the LowLevelModel. The GUI is expected to query the index to discover parts of the structure; the [[object reference tags|MObjectRef]] returned by these queries will be used as arguments to any mutating operation on the objects within the session.
Using a ''flat hashtable'' allows to access a Placement denoted by ID in O(1). This way we get the Placement, but nothing more. So, additionally we'd have to set up an data record holding additional information:
* the [[scope|PlacementScope]] containing this placement
* allowing to create a path &quot;up&quot; from this scope, which is used for resolving any queries
* (maybe/planned) relations to other placements
Alternatively, we could try to use a ''structure based index'', thereby avoiding the mentioned description record by folding any of the contained informations into the surrounding data structure:
* the scope would be obvious from the index, resp. from the path used to resolve this index
* any other informations, especially the relations, would be folded into the placement
* this way, the &quot;index&quot; could be reduced to being the session data structure itself.
//does a placement need to know it's own ID?//&amp;nbsp; Obviously, there needs to be a way to find out the ID for a given placement, especially in the following situations:
* for most of the operations above, when querying additional informations from index for a given placement
* to create a PlacementRef (this is a variant of the &quot;shared ptr from this&quot;-problem)
On second sight, this problem turns out to be more involved, because either we have to keep a second index table for the reverse lookup (memory address -&gt; ID), or have to tie the placement by a back-link when adding it to the index/session data structure, or (alternatively) it forces us to store a copy of the ID //within// the placement itself. The last possibility seems to create the least impact; but implementing it this way effectively gears the implementation towards a hashtable based approach.
!supported operations
The placement index is utilized by editing operations and by executing the build process. Besides these core operations it allows for resolving PlacementRef objects. This latter functionality is used by all kinds of relative placements and for dealing with them while building, but it is also used to resolve [[object reference tags|MObjectRef]], which possibly may have been handed out via an external API or may have crossed layer boundaries. From these use cases we derive the following main operations to support:
* find the actual [[Placement]] object for a given ID
* find the //scope//&amp;nbsp; a given placement resides in. More specifically, find the [[placement defining this scope|PlacementScope]]
* find (any/all) other placements referring to a given placement (&quot;within this scope&quot;)
* add a new placement to a scope given as parameter
* remove a placement from index
* (planned) move a placement to a different scope within the session
!!!Handling of Subtypes
While usually type relations don't carry over to smart-poitner like types, in case of Placement I used a special definition pattern to artificially create such type relations ({{{Placement&lt;session::Clip&gt;}}} is subclass of {{{Placement&lt;MObject&gt;}}}). Now, as we're going to copy and maintain Placements within the storage backing the index, the question is: do we actually store subtypes (all having the same size btw) or do we use a vtable based mechanism to recover the type information on access?
Actually, the handling of placement types quite flexible; the actual hierarchy of Placement types can be determined in the //usage context// &amp;mdash; it is not really stored within the placement, and there is no point in storing it within the index. Only the type of the //pointee//&amp;nbsp; can be checked with the help of Placement's vtable.
Thus, things just fall into place here, without the need of any additional implementation logic. The index stores {{{Placement&lt;MObject&gt;}}} instances. The usage context will provide a suitable meaning for more specifically typed placements, and as long as this is in line with the type relations on the pointee(s), as checked by the {{{Placement::isCompatible&lt;TY&gt;()}}} call, the placement relations will just work out right by the the cast happening automatically on results retrieval.
&amp;rarr; see PlacementType
!implementation
Consequently, we incorporate a random hash (implemented as {{{LUID}}}) into the individual placement, this way creating an distinguishable //placement identity,// which is //not retained on copying.// The actual ID tag is complemented by a compile time type (template parameter), thus allowing to pass on additional context information through API calls. Placements themselves use a vtable (and thus RTTI), allowing to re-discover the exact type at runtime. Any further relation information is contained within the placement's [[locating pins|LocatingPin]], thus, any further description records can be avoided by storing the placements immediately //within the index.// To summarise, the implementation is comprised of
* a main table resolving hash-ID to storage location
* information about the enclosing scope for each placement, stored within the main entry
* a reverse lookup table to find all placements contained within a given scope
* an instance holding and managing facility based on pooled allocation
</pre>
</div>
<div title="PlacementRef" modifier="Ichthyostega" modified="201001112314" created="200905090032" tags="def spec" changecount="13">
<pre>A generic reference mechanism for Placements, as added to the current session.
While this reference itself is not tied to the actual memory layout (meaning it's //not// a disguised pointer), the implementation relies on a [[placement index facility|PlacementIndex]] for tracking and retrieving the actual Placement implementation object. As a plus, this approach allows to create active interconnections between placements. We utilise this possibility to create a system of [[nested scopes|PlacementScope]]. The index facility allows to //reverse// the relation denoted by such a reference, inasmuch it is possible to retrieve all other placements referring to a given target placement. But for an (external) user, this link to an index implementation is kept transparent and implicit.
!implementation considerations
From the usage context it is clear that the PlacementRef needs to incorporate a simple ID as the only actual data in memory, so it can be downcasted to a POD and passed as such via LayerSeparationInterfaces. And, of course, this ID tag should be the one used by PlacementIndex for organising the Placement index entries, thus enabling the PlacementRef to be passed immediately to the underlying index for resolution. Thus, this design decision is interconnected with the implementation technique used for the index (&amp;rarr; PlacementIndex). From the requirement of the ID tag to be contained in a fixed sized storage, and also from the expected kinds of queries Ichthyo (5/09) choose to incorporate a {{{LUID}}} as a random hash immediately into the placement and build the ID tag on top of it.
!using placement references
Placement references can be created directly from a given placement, or just from an {{{Placement::ID}}} tag. Creation and dereferencing can fail, because the validity of the reference is checked with the index. This implies accessing the //current session// behind the scenes. Placement references have value semantics. Dereferencing searches the denoted Placement via index, yielding a direct (language) ref.
Placement references mimic the behaviour of a real placement, i.e. they proxy the placement API (actually the passive, query-related part of placement's API functions), while directly forwarding calls to the pointee (~MObejct or subclass) when using {{{operator-&gt;()}}}. They can be copied and especially allow a lot of assignments (from placement, placement-ID or even plain LUID), even including a dynamic downcast on the pointee. Bottom line is that a placement ref can pretty much be used in place of a language ref to a real placement, which is crucial for implementing MObjectRef.
</pre>
</div>
<div title="PlacementScope" modifier="Ichthyostega" modified="200911202127" created="200905120304" tags="SessionLogic spec img" changecount="21">
<pre>MObjects are attached into the [[Session]] by adding a [[Placement]]. Because this especially includes the possibility of //grouping or container objects,// e.g. [[sequences|Sequence]] or [[tracks|Track]] or [[meta-clips|VirtualClip]], any placement may optionally define and root a scope, and every placement is at least contained in one encompassing scope &amp;mdash; of course with the exception of the absolute top level, which can be thought off as being contained in a scope of handling rules.
Thus, while the [[sequences|Sequence]] act as generic container holding a pile of placments, actually there is a more fine grained structure based on the nesting of the tracks, which especially in Lumiera's HighLevelModel belong to the sequence (they aren't a property of the top level timeline as one might expect). Building upon these observations, we actually require each addition of a placement to specify a scope. Consequently, for each Placement at hand it is possible to determine an //containing scope,// which in turn is associated with some Placement of a top-level ~MObject for this scope. The latter is called the ''scope top''. An example would be the {{{Placement&lt;Track&gt;}}} acting as scope of all the clips placed onto this track. The //implementation//&amp;nbsp; of this tie-to-scope is provided by the same mechanism as utilised for relative placements, i.e. an directional placement relation. Actually, this relation is implemented by the PlacementIndex within the current [[Session]].
[&gt;img[Structure of Placment Scopes|draw/ScopeStructure1.png]]
!Kinds of scopes
There is only a limited number of situations constituting a scope
* conceptually, the very top level is a scope of general rules.
* the next level is the link of [[binding|BindingMO]] of a [[Sequence]] into either a (top-level) [[Timeline]] or as virtual media into a VirtualClip. It is implemented through a {{{Placement&lt;Binding&gt;}}}.
* each sequence has at least one (manadtory) top-level placement holding its root track
* tracks may contain nested sub tracks.
* clips and (track-level) effects likewise are associated with an enclosing track.
* an important special case of relative placement is when an object is [[attached|AttachedPlacementProblem]] to another leading object, like e.g. an effect modifying a clip
__note__: attaching a Sequence in multiple ways &amp;rarr; [[causes scoping problems|BindingScopeProblem]]
!Purpose of Placement scoping
Similar to the common mechanisms of object visibility in programming languages, placement scopes guide the search for and resolution of properties of placement. Any such property //not defined locally// within the placement is queried ascending through the sequence of nested scopes. Thus, global definitions can be shadowed by local ones.
</pre>
</div>
<div title="PlacementType" modifier="Ichthyostega" modified="200911271836" created="200911271742" changecount="5">
<pre>Placement is a smart-ptr. As such, usually smart-pointers are templated on the pointee type, but a type relation between different target types doesn't carry over into a type relation on the corresponding smart-pointers. Now, as a [[Placement]] or a PlacementRef often is used to designate a specific &quot;instance&quot; of an MObject placed into the current session, the type parametrisation plays a crucial role when it comes to processing the objects contained within the session. Because the session deliberately has not much additional structure, besides the structure created by [[scopes and aggregations|PlacementScope]] within the session's contents.
To this end, we're using a special definition pattern for Placements, so
* a placement can refer to a specific sub-Interface like Track, Clip, Effect
* a specialised placement can stand-in for the more generic type.
!generic handling
Thus, ~MObject and Placement&lt;~MObject&gt; relate to the &quot;everything is an object&quot; view of affairs. More specific placements are registered, searched and retrieved within the session through this generic interface. In a similar vein, ~PlacementRef&lt;~MObject&gt; and MObjectRef is used on LayerSeparationInterfaces. This works, because it is possible to re-discover the more fine-grained target type.
* ''active type rediscovery'' happens when a [[using visitors|VisitorUse]], which requires support by the pointee types (~MObject subclasses), so the visitor implementation is able to build a trampoline table to dispatch into a specifically typed context.
* ''passive type rediscovery'' is possible whenever the usage context //is already specifically typed.// Because in this case we can check the type (by RTTI) and filter out any placement not convertible to the type requested within the given context.
!downcasting and slicing
Deliberately, all Placements have the same runtime size. Handling them value-like under certain circumstances is intended and acceptable. Of course then slicing on the level of the Placement will happen. But because the Placement actually is a smart-pointer, the pointee remains unaffected, and can be used later to re-gain the fully typed context.
On the other hand, care has to be taken when ''downcasting'' a placement. When possible, this should be preceded by a {{{Placement::isCompatible&lt;TY&gt;()}}}-call, which checks based on the pointee's RTTI. Client code is encouraged to avoid explicit casting and rather rely on the provided facilities:
* invoking one of the templated access functions of the PlacementIndex
* using the QueryFocus to issue a specifically typed {{{ScopeQuery&lt;TY&gt;}}} (which yields an suitable iterator)
* create an specifically typed MObjectRef and bind it to some reference source (~Placement-ID, LUID, Placement instance within the session)
* implementing a visitor (~BuilderTool)
</pre>
</div>
<div title="PlanningBuildFixture" modifier="Ichthyostega" modified="200801061937" created="200712100445" tags="impl Builder draft" changecount="11">
<pre>//This page is a scrapbook for working out the implementation of how to (re)build the [[Fixture]]//
Structurally, (re)building the Fixture rather belongs to [[Session]], but it is implemented very similar to the render engine build process: by treating all ~MObjects found in the various [[sequences|Sequence]] with a common [[visiting tool|VisitorUse]], this tool collects a simplified view with everyting made explicit, which can be pulled of as Fixture, i.e. (special kind of sequence list) afterwards.
* there is a //gathering phase// and a //solving phase//, the gathering is done by visiting.
* during the gathering phase, there ''need to be a lock'' preventing any other edit operation.
* the solving is delegated to the individual ~Placements. It is effectively a {{{const}}} operation creating a ExplicitPlacement (copy)
* thus the Fixture contains these newly created ~ExplicitPlacements, refering to ~MObjects shared with the original Placements within the sequences
!!!prerequisites
* Session and sequences exist.
* Pipes exist and are configured
!!!postconditions
* the Fixture contains one sorted timeline of ExplicitPlacement instances
* Anything in this list is actually to be rendered
* {{red{TODO: how to store and group the effects?}}}
* any meta-clips or other funny things have been resolved to normal clips with placement
* any multichannel clips has been broken down to elementary clips {{red{TODO: what is &quot;elementary&quot;. e.g. stereo sound streams?}}}
* any globally or otherwise strangely placed effects have been attached either to a clip or to some pipe
* we have one unified list of tracks
&lt;&lt;tasksum start&gt;&gt;
&lt;&lt;taskadder below&gt;&gt;
&lt;&lt;task &gt;&gt; work out how to get the processing of effects chained to some clip right
&lt;&lt;task &gt;&gt; work out how to handle multichannel audio (and stereo video)
!gathering phase
!!preparing
&lt;&lt;task&gt;&gt;what data collections to build?
!!treating a Track
&lt;&lt;task&gt;&gt;work out how to refer to pipes and do other config
&lt;&lt;task&gt;&gt;get some uniqe identifier and get relevant properties
!!treating a {{{Placement&lt;Clip&gt;}}}
&lt;&lt;task&gt;&gt;check the direct enablement status
&lt;&lt;task&gt;&gt;asses the compound status, maybe process recursively
!!treating an {{{Placement&lt;Effect&gt;}}}
&lt;&lt;task&gt;&gt;find out the application point {{red{really?}}}
!solving phase
&lt;&lt;task&gt;&gt;trigger solving on all placements
&lt;&lt;task&gt;&gt;sort the resulting ~ExplicitPlacements
&lt;&lt;tasksum end&gt;&gt;
</pre>
</div>
<div title="PlanningNodeCreatorTool" modifier="Ichthyostega" modified="200810170221" created="200712090659" tags="impl Builder draft" changecount="2">
<pre>//This page is a scrapbook for working out the implementation of the builder//
* NodeCreatorTool is a [[visiting tool|VisitorUse]]
* the render engine to be built is contained as state within this tool object while it is passed around
!!!prerequisites
* Session and sequences exist.
* Pipes exist and are configured
* Fixture contains ExplicitPlacement for every MObject to be rendered, and nothing else
&lt;&lt;tasksum start&gt;&gt;
&lt;&lt;taskadder&gt;&gt;
!!preparing
We need a way of addressing existing [[pipes|Pipe]]. Besides, as the Pipes and Tracks are referred by the Placements we are processing, they are guaranteed to exist.
!!treating a Pipe
&lt;&lt;task&gt;&gt;get the [[processing pattern|ProcPatt]] of the pipe by accessing the underlying pipe asset.
&lt;&lt;task&gt;&gt;process this ProcPatt recursively
!!treating a processing pattern
&lt;&lt;task&gt;&gt;{{red{finally go ahead and define what a ProcPatt need to be...}}}
!!treating a {{{Placement&lt;Clip&gt;}}}
&lt;&lt;task&gt;&gt;get the ProcPatt of the underlying media (asset)
&lt;&lt;task&gt;&gt;process the ProcPatt recursively
&lt;&lt;task&gt;&gt;access the ClipSourcePort (which may be created on-the-fly)
&lt;&lt;task&gt;&gt;enqueue an WiringRequest for connecting the source pipeline to the source port
&lt;&lt;task&gt;&gt;process the clip's render pipe recursively (thus adding the camera etc.)
&lt;&lt;task&gt;&gt;enqueue an WiringRequest for any placement to some pipe for this clip.
* __note__: we suppose
** all wiring requests will be done after the processing of entities
** all effects placed to this clip will be processed after this clip (but before the wiring requests)
!!treating an {{{Placement&lt;Effect&gt;}}}
&lt;&lt;task&gt;&gt;{{red{how to assure that effecs are processed after clips/pipes??}}}
&lt;&lt;task&gt;&gt;find out the application point
&lt;&lt;task&gt;&gt;build a transforming node for the effect and insert it there
!!postprocessing
&lt;&lt;task&gt;&gt;sort and group the assembled list of [[wiring requests|WiringRequest]] by pipes
&lt;&lt;tasksum end&gt;&gt;
</pre>
</div>
<div title="PlayerDummy" modifier="Ichthyostega" modified="200906071810" created="200901300209" tags="GuiIntegration operational img" changecount="17">
<pre>__Joelholdsworth__ and __Ichthyo__ created this player mockup in 1/2009 to find out about the implementation details regarding integration and colaboration between the layers. There is no working render engine yet, thus we use a ~DummyImageGenerator for creating faked yuv frames to display. Within the GUI, there is a ~PlaybackController hooked up with the transport controls on the timeline pane.
# first everything was contained within ~PlaybackController, which spawns a thread for periodically creating those dummy frames
# then, a ~PlayerService was factored out, now implemented within Proc-Layer (probably to be relocated into the backend for the final version). A new LayerSeparationInterface called ''~DummyPlayer'' was created and set up as a [[Subsystem]] within main().
# the next step was to support multiple playback processes going on in parallel. Now, the ~PlaybackController holds an smart-handle to the ~PlayProcess currently generating output for this viewer, and invokes the transport control functions and the pull frame call on this handle.
# then, also the tick generation (and thus the handling of the thread which pulls the frames) was factored out and pushed down into the mentioned ~PlayProcess. For this to work, the ~PlaybackController now makes a display slot available on the public GUI DisplayFacade interface, so the ~PlayProcessImpl can push up the frames for display within the GUI
[img[Overview to the dummy player operation|draw/PlayerArch1.png]]
!when playing...
As a prerequisite, a viewer has to be prepared within the GUI. A XV video display widget is wired up to a sigc++ signal slot, using the Glib::Dispatcher to forward calls from the play process thread to the GTK main event loop thread. All of this wiring actually is encapsulated as a DisplayerSlot, created and registered with the DisplayService.
When starting playback, the display slot handle created by these preparations is used to create a ~PlayProcess on the ~DummyPlayer interface. Actually
* a ~PlayProcessImpl object is created down within the player implementation
* this uses the provided slot handle to actually //allocate// the display slot via the Display facade. Here, //allocating// means registering and preparing it for output by //one single// ~PlayProcess. For the latter, this allocation yields an actually opened display handle.
* moreover, the ~PlayProcessImpl aquires an TickService instance, which is still trotteling (not calling the periodic callback)
* probably, a real player at this point would initiate a rendering process, so he can fetch the actual output frames periodically.
* on the &quot;upper&quot; side of the ~DummyPlayer facade, an lib::Handle object is created to track and manage this ~PlayProcesImpl instance
The mentioned handle is returned to the ~PlaybackController within the GUI, which uses this handle for all further interactions with the Player. The handle is ref counting and has value semantics, so it can be stored away, passed as parameter and so on. All such handles corresponding to one ~PlayProcess form a family; when the last goes out of scope, the ~PlayProcess terminates and deallocates any resources. Conceptually, this corresponds to pushing the &quot;stop&quot; button. Handles can be deliberately disconnected by calling {{{handle.close()}}} &amp;mdash; this has the same effect as deleting a handle (when all are closed or deleted the process ends).
All the other play control operations are simply forwarded via the handle and the ~PlayProcessImpl. For example, &quot;pause&quot; corresponds to setting the tick frequency to 0 (thus temporarily discontinuing the tick callbacks). When allocating the display slot in the course of creating the ~PlayProcessImpl, the latter only accesses the Display facade. It can't access the display or viewer directly, because the GUI lives within an plugin; lower layers aren't allowed to call GUI implementation functions directly. Thus, within the Display facade a functor (proxy) is created to represent the output sink. This (proxy) Displayer can be used within the implementation of the perodic callback function. As usual, the implementation of the (proxy) Displayer can be inlined and doesn't create runtime overhead. Thus, each frame output call has to pass though two indirections: the function pointer in the Display facade interface, and the Glib::Dispatcher.
!rationale
There can be multiple viewer widgets, to be connected dynamically to multiple play-controllers. (the latter are associated with the timeline(s)). Any playback can require multiple playback processes to work in parallel. The playback controller(s) should not be concerned with managing the play processes, which in turn should neither care for the actual rendering, nor manage the display frequency and synchronisation issues. Moreover, the mentioned parts live in different layers and especially the GUI needs to remain separated from the core. And finally, in case of a problem within one play process, it should be able to unwind automatically, without interfering with other ongoing play processes.
</pre>
</div>
<div title="Playlist" modifier="Ichthyostega" modified="200706250727" created="200706220456" tags="def" changecount="3">
<pre>Playlist is a sequence of individual Render Engine Processors able to render a segment of the timeline. So, together these Processors are able to render the whole timeline (or part of the timeline if only a part has to be rendered).
//Note, we have yet to specify how exactly the building and rendering will work together with the backend. There are several possibilities how to structure the Playlist//
</pre>
</div>
<div title="ProblemsTodo" modifier="Ichthyostega" modified="200806030155" created="200708050524" tags="design discuss" changecount="16">
<pre>Open issues, Things to be worked out, Problems still to be solved...
!!Parameter Handling
The requirements are not quite clear; obviously Parameters are the foundation for getting automation right and for providing effect editing interfaces, so it seems to me we need some sort of introspection, i.e. Parameters need to be discovered, enumerated and described at runtime. (&amp;rarr; see [[tag:automation|automation]])
''Automation Type'': Directly connected is the problem of handling the //type// of parameters sensible, including the value type of automation data. My first (somewhat naive) approach was to &quot;make everything a double&quot;. But this soon leads into quite some of the same problems haunting the automation solution implemented in the current Cinelerra codebase. What makes the issue difficult is the fact we both need static diversity as well as dynamic flexibility. Usually, when combining hierarchies and templates, one has to be very careful; so I just note the problem down at the moment and will revisit it later, when I have a more clear understanding of the demands put onto the [[ProcNode]]s
!!Treatment of Time (points) and Intervals
At the moment we have no clear picture what is needed and what problems we may face in that domain.
From experience, mainly with other applications, we can draw the following conclusions
* drift and rounding errors are dangerous, because time in our context usually is understood as a fixed grid (Frames, samples...)
* fine grained time values easily get very large
* Cinelerra currently uses the approach of simply counting natural values for each media type separately. In an environment mixing several different media types freely, this seems a bit too simplistic (because it actually brings in the danger of rounding errors, just think at drop frame TC)
!!Organizing of Output Channels
How to handle the simultaneous rendering of several output streams (video, audio channels). Shall we treat the session as one entity containing different output channels, or should it rather be seen as a composite of several sub-sessions, each for only one output channel? This decision will be reflected in the overall structure of the network of render nodes: We could have a list of channel-output generating pipelines in each processor (for every segment), or we could have independently segmented lists of Processors for every output channel/type. The problem is, //it is not clear what approach to prefer at the moment// because we are just guessing.
!!Tracks, Channels, Layers
Closely related to this is the not-so-obvious problem how to understand the common global structures found in most audio and video editing applications. Mostly, they stem from imitating hardware recording and editing solutions, thus easing the transition for professionals grown up with analogue hardware based media. But as digital media are the de-facto standard nowadays, we could rethink some of this accidental complexity introduced by sticking to the hardware tool metaphor.
* is it really necessary to have fixed global tracks?
* is it really helpful to feed &quot;source tracks&quot; into global processing busses/channels?
Users accustomed with modern GUI applications typically expect that //everything is a object// and can be pulled around and manipulated individually. This seems natural at start, but raises the problem of providing a efficient workflow for handling larger projects and editing tasks. So, if we don't have a hard wired multitrack+bus architecture, we need some sort of templating to get the standard editing use case done efficiently.
!!Compound and Multiplicity
Simple relations can be hard wired. But, on the contrary, it would be as naive to define a Clip as having a Video track and two audio tracks, as it would be naive to overlook the problem of holding video and corresponding audio together. And, moreover, the default case has to be processed in a //straight forward// fashion, with as few tests and decisions as possible. So, basically each component participating in getting the core processing done has to mirror the structure pattern of the other parts, so that processing can be done without testing and forking. But this leaves us with the problem where to put the initial knowledge about the structural pattern used for building up the compound structures and &amp;mdash; especially &amp;mdash; the problem how to treat different kinds of structural patterns, how to detect the pattern to be applied and how to treat multiple instances of the same structural pattern.
One example of this problem is the [[handling of multichannel media|MultichannelMedia]]. Following the above reasoning, we end with having a [[&quot;structural processing pattern&quot;|ProcPatt]], typically one video stream with MPEG decoder and a pair of audio streams which need either to be routed to some &quot;left&quot; and &quot;right&quot; output pipes, or have to be passed through a panning filter accordingly. Now the problem is: //create a new instance of this structure for each new media, or detect which media to subsume under a existing pattern instance.//
!!Parallelism
We need to work out guidelines for dealing with operations going on simultaneously. Certainly, this will divide the application in several different regions. As always, the primary goal is to avoid multithread problems altogether. Typically, this can be achieved by making matters explicit: externalizing state, make the processing subsystems stateless, queue and schedule tasks, use isolation layers.
* the StateProxy is a key for the individual render processes state, which is managed in separate [[StateFrame]]s in the backend. The [[processing network|ProcNode]] is stateless.
* the [[Fixture]] provides an isolation layer between the renderengine and the Session / high-level model
* all EditingOperations are not threadsafe intentionally, because they are [[scheduled|ProcLayerScheduler]]
</pre>
</div>
<div title="Proc-Layer" modifier="Ichthyostega" created="200802031814" tags="def" changecount="1">
<pre>The current Lumiera architecture separates functionality into three Layers: __GUI__, __Proc__ and __Backend__.
While the Backend is responsible for Data access and management and for carrying out the computation intensive media opteratons, the middle Layer or ~Proc-Layer contains [[assets|Asset]] and [[Session]], i.e. the user-visible data model and provides configuration and behaviour for these entities. Besides, he is responsible for [[building and configuring|Builder]] the [[render engine|RenderEngine]] based on the current Session state.</pre>
</div>
<div title="ProcAsset" modifier="Ichthyostega" modified="201003140233" created="200709221343" tags="def classes img" changecount="3">
<pre>All Assets of kind asset::Proc represent //processing algorithms// in the bookkeeping view. They enable loading, browsing and maybe even parametrizing all the Effects, Plugins and Codecs available for use within the Lumiera Session.
Besides, they provide an __inward interface__ for the [[ProcNode]]s, enabling them to dispatch the actual processing call while rendering. Actually, this interface is always accessed via an ~Effect-MObject; mostly it is investigated and queried in the build process when creating the corresponding processor nodes. &amp;rarr; see EffectHandling for details
{{red{todo: the naming scheme??}}}
[img[Asset Classess|uml/fig131077.png]]
{{red{Note 3/2010}}} it is very unlikely we'll organise the processing nodes as a class hierarchy. Rather it looks like we'll get several submodules/special capabilities configured in within the Builder</pre>
</div>
<div title="ProcLayer" modifier="Ichthyostega" modified="200708100338" created="200708100333" tags="def" changecount="2">
<pre>The middle Layer of our current Architecture plan, i.e. the layer managing all processing and manipulation, while the actual data handling is done in the backend and the user interaction belongs to the GUI Layer.
&amp;rarr; see the [[Overview]]
</pre>
</div>
<div title="ProcLayer and Engine" modifier="Ichthyostega" modified="201003210253" created="200706190056" tags="overview" changecount="29">
<pre>The Render Engine is the part of the application doing the actual video calculations. Utilising system level services and retrieving raw audio and video data through [[Lumiera's Backend|backend.html]], its operations are guided by the objects and parameters edited by the user in [[the session|Session]]. Thus, the Lumiera Proc-Layer covers the (abstract) edit operations available to the user, the representation of [[&quot;editable things&quot;|MObjects]] and the translation of those into facilities allowing to [[drive the rendering|Rendering]].
&lt;&lt;&lt;
''Status'': started out as design draft in summer '07, Ichthyo is now in the middle of a implementing the fundamental parts in C++
* basic [[AssetManager]] working
* first attempts to [[integrate with the GUI|GuiIntegration]]
* bringing toghether the vital parts of the [[session implementation|SessionDataMem]]
* as a prerequisite, defined and implemented [[Proc-Layer Command frontend|CommandHandling]]
* near term goal is to get the foundation of the HighLevelModel roughly settled.
&lt;&lt;&lt;
!Summary
We have several kinds of &quot;things&quot; organized as [[assets|Asset]] in the AssetManager, like media, clips, effects, codecs, configuration templates. Within the context of the [[Session]], we can use these as [[&quot;Media Objects&quot;|MObjects]] &amp;mdash; especially, we can [[place|Placement]] them in various kinds within the session and relative to one another. Basically, this is the [[editing work|EditingOperations]] done by the user.
Now, from any given configuration within the session, we create sort or a frozen- and tied-down snapshot, here called [[&quot;Fixture&quot;|Fixture]], containing all currently active ~MObjects, broken down to elementary parts and made explicit if necessary. This Fixture acts as a isolation layer towards the Render Engine. We will hand it over to the [[Builder]], which in turn will transform it into a network of connected [[render nodes|ProcNode]]. This network //implements//&amp;nbsp; the [[Render Engine|OverviewRenderEngine]].
The system is ''open'' inasmuch every part mirrors the structure of corresponding parts in adjacent subsystems, and the transformation of any given structure from one subsystem (e.g. Asset) to another (e.g. Render Engine) is done with minimal &quot;magic&quot;. So the whole system should be able to handle completely new structures mostly by adding new configurations and components, without much need of rewriting basic workings.
!!see also
&amp;rarr; [[Overview]] of Subsystems and Components, and DesignGoals
&amp;rarr; [[An Introduction|WalkThrough]] discussing the central points of this design
&amp;rarr; [[Overview Session (high level model)|SessionOverview]]
&amp;rarr; [[Overview Render Engine (low level model)|OverviewRenderEngine]]
&amp;rarr; BuildProcess and RenderProcess
&amp;rarr; [[Two Examples|Examples]] (Object diagrams)
&amp;rarr; how [[Automation]] works
&amp;rarr; [[Problems|ProblemsTodo]] to be solved and notable [[design decisions|DesignDecisions]]
&amp;rarr; [[Concepts, Abstractions and Formalities|Concepts]]
&amp;rarr; [[Implementation Details|ImplementationDetails]] {{red{WIP}}}
</pre>
</div>
<div title="ProcNode" modifier="Ichthyostega" modified="200806211606" created="200706220409" tags="def spec" changecount="6">
<pre>A data processing node within the Render Engine. Its key feature is the possibility to pull from it one (freely addressable) [[Frame]] of calculated data. Further, each ~ProcNode has the ability to be wired with other nodes and [[Parameter Providers|ParamProvider]]
!! {{red{open questions}}}
* how to address a node
* how to type them
* how to discover the number and type of the ports
* how to discover the possible parameter ports
* how to define and query for additional capabilities
&amp;rarr; see also the [[open design process draft|http://www.pipapo.org/pipawiki/Lumiera/DesignProcess/DesignRenderNodesInterface]]
&amp;rarr; see [[mem management|ManagementRenderNodes]]
&amp;rarr; see RenderProcess
</pre>
</div>
<div title="ProcPatt" modifier="Ichthyostega" modified="200805260329" created="200709212315" tags="def design" changecount="9">
<pre>This special type of [[structural Asset|StructAsset]] represents information how to build some part of the render engine's processing nodes network. Processing patterns can be thought of as a blueprint or micro program for construction. Most notably, they are used for creating nodes reading, decoding and delivering source media material to the render network, and they are used for building the output connection via faders, summation or overlay nodes to the global pipes (busses). Each [[media Asset|MediaAsset]] has associated processing patterns describing the codecs and other transformations needed to get at the media data of this asset. (and because media assets are typically compound objects, the referred ~ProcPatt will be compound too). Similarily, for each stream kind, we can retrieve a processing pattern for making output connections. Obviously, the possibilities opened by using processing patterns go far beyond.
Technically, a processing pattern is a list of building instructions, which will be //executed// by the [[Builder]] on the render node network under construction. This implies the possibility to define further instruction kinds when needed in future; at the moment the relevant sorts of instructions are
* attach the given sequence of nodes to the specified point
* recursively execute a nested ~ProcPatt
More specifically, a sequence of nodes is given by a sequence of prototypical effect and codec assets (and from each of them we can create the corresponding render node). And the point to attach these nodes is given by an identifier &amp;mdash; in most cases just &quot;{{{current}}}&quot;, denoting the point the builder is currently working at, when treating some placed ~MObject which in turn yielded this processing pattern in question.
Like all [[structural assets|StructAsset]], ~ProcPatt employs a special naming scheme within the asset name field, which directly mirrors its purpose and allows to bind to existing processing pattern instances when needed. {{red{TODO: that's just the general idea, but really it will rather use some sort of tags. Yet undefined as of 5/08}}} The idea is letting all assets in need of a similar processing pattern refer to one shared ~ProcPatt instance. For example, within a MPEG video media asset, at some point there will be a ~ProcPatt labeled &quot;{{{stream(mpeg)}}}&quot;. In consequence, all MPEG video will use the same pattern of node wiring. And, of course, this pattern could be changed, either globally, or by binding a single clip to some other processing pattern (for making a punctual exception from the general rule)
!!defining Processing Patterns
The basic working set of processing patterns can be expected to be just there (hard wired or default configuration, mechanism for creating an sensible fallback). Besides, the idea is that new processing patterns can be added via rules in the session and then referred to by other rules controlling the build process. Any processing pattern is assembled by adding individual build instructions, or by including another (nested) processing pattern.
!!retrieving a suitable Processing Pattern
For a given situation, the necessary ProcPatt can be retrieved by issuing a [[configuration query|ConfigQuery]]. This query should include the needed capabilities in predicate form (technically this query is a Prolog goal), but it can leave out informations by just requesting &quot;the default&quot; &amp;rarr; see DefaultsManagement
!!how does this actually work?
Any processing pattern needs the help of a passive holder tool suited for a specific [[building situation|BuilderPrimitives]]; we call these holder tools [[building moulds|BuilderMould]]. Depending on the situation, the mould has been armed up by the builder with the involved objects to be connected and extended. So, just by issuing the //location ID// defined within the individual build instruction (in most cases simply {{{&quot;current&quot;}}}), the processing pattern can retrieve the actual render object to use for building from the mould it is executed in.
!!errors and misconfiguration
Viewed as a micro program, the processing patterns are ''weak typed'' &amp;mdash; thus providing the necessary flexibility within an otherwise strong typed system. Consequently, the builder assumes they are configured //the right way// &amp;mdash; and will just bail out when this isn't the case, marking the related part of the high-level model as erroneous.
&amp;rarr; see BuilderErrorHandling for details
</pre>
</div>
<div title="Processors" modifier="Ichthyostega" created="200706220412" tags="def" changecount="1">
<pre>a given Render Engine configuration is a list of Processors. Each Processor in turn contains a Graph of ProcNode.s to do the acutal data processing. In order to cary out any calculations, the Processor needs to be called with a StateProxy containing the state information for this RenderProcess
</pre>
</div>
<div title="Query" modifier="Ichthyostega" modified="200910210243" created="200910171621" tags="Rules spec draft discuss" changecount="9">
<pre>{{red{WIP as of 10/09}}}...//brainstorming about the first ideas towards a query subsystem//
!use case: discovering the contents of a container in the HighLevelModel
In the course of shaping the session API, __joel__ and __ichthyo__ realised that we're moving towards some sort of discovery or introspection. This gives rise to the quest for a //generic// pattern how to issue and run these discovery operations. The idea is to understand such a discovery as running a query &amp;mdash; using this specific problem to shape the foundation of a query subsystem to come.
* a ''query'' is a polymorphic, noncopyable, non-singleton type; a query instance corresponds to one distinctly issued query
* issuing a query yields a result set, which is hidden within the concrete query implementation.
* the transactional behaviour needs still to be defined: how to deal with concurrent modifications? COW?
* the query instance remains property of the entity exposing the query capability.
* client code gets a result iterator, which can be explored //only once until exhaustion.//
* the handed out result iterator is used to manage the allocation for the query result set by sideeffect (smart handle). &amp;rarr; Ticket #353
For decoupling the query invocation from the facility actually processing the query, we need to come up with common pattern. In 10/09, there is an immediate demand for such a solution pattern for implementing the QueryFocus and PlacementScope framework, which is crucial for contents discovery in general on the session interface. &amp;rarr; QueryResolver was shaped to deal with this situation, but has the potential to evolve into a general solution for issuing queries.
----
See also the notes on &amp;rarr; QueryImplProlog
</pre>
</div>
<div title="QueryFocus" modifier="Ichthyostega" modified="201002160259" created="200910140244" tags="def spec img" changecount="30">
<pre>When querying contents of the session or sub-containers within the session, the QueryFocus follows the current point-of-query. As such queries can be issued to explore the content of container-like objects holding other MObjects, the focus is always attached to a container, which also acts as [[scope|PlacementScope]] for the contained objects. QueryFocus is an implicit state (the current point of interrest). This sate especially remembers the path down from the root of the HighLevelModel, which was used to access the current scope. Because this path constitutes a hierarchy of scopes, it can be relevant for querying and resolving placement properties. (&amp;rarr; SessionStructureQuery)
!provided operations
* attach to a given scope-like object. Causes the current focus to //navigate//
* open a new focus, thereby pushing the existing focus onto a [[focus stack|QueryFocusStack]]
* return (pop) to the previous focus
* get the current scope, represented by the &quot;top&quot; Placement of this scope
* get the current ScopePath from root (session globals) down to the current scope
* (typed) content discovery query on the current scope
[&gt;img[Scope Locating|uml/fig136325.png]]
!!!relation to Scope
There is a tight integration with PlacementScope through the ScopeLocator, which establishes the //current focus.// But while the [[scope|PlacementScope]] just decorates the placement defining a scope (called //&amp;raquo;scope top&amp;laquo;//), QueryFocus is more of a //binding// &amp;mdash; it links or focusses the current state into a specific scope with a ScopePath in turn depending on this current state. Thus, while Scope is just a passive container allowing to locate and navigate, QueryFocus by virtue of this binding allows to [[Query]] at this current location.
!implementation notes
we provide a static access API, meaning that there is a singleton (the ScopeLocator) behind the scenes, which holds the mentioned scope stack. The current focus stack top, i.e. the current ScopePath is managed through an ref-counting handle embedded into each QueryFocus instance. Thus, effectively QueryFocus is an frontend object for accessing this state. Moreover, embedded into ScopeLocator, there is an link to the current session. But this link is kept opaque; it works by the current session exposing an [[query service|QueryResolver]], while QueryFocus doesn't rely on knowledge about the session, allowing the focus to be unit tested.
The stack of scopes must not be confused with the ScopePath. Each single frame on the stack can be seen and accessed as a QueryFocus and as such relates to a current ScopePath. The purpose of the stack is to make the scope handling mostly transparent; especially this stack allows to write dedicated query functions directed at a given object: they work by pushing and then navigating to the object to use as starting point for the query, i.e. the //current scope.//
!!!simplifications
The full implementation of this scope navigation is tricky, especially when it comes to determining the relation of two positions. It should be ''postponed'' and replaced by a ''dummy'' (no-op) implementation for the first integration round.
</pre>
</div>
<div title="QueryFocusStack" modifier="Ichthyostega" modified="200911220509" created="200910200158" tags="SessionLogic spec operational" changecount="8">
<pre>The ScopeLocator uses a special stack of ScopePath &amp;raquo;frames&amp;laquo; to maintain the //current focus.//
What is the ''current'' QueryFocus and why is it necessary? There is a state-dependent part involved, inasmuch the effective ScopePath depends on how the invoking client has navigated the //current location// down into the HighLevelModel structures. Especially, when a VirtualClip is involved, there can be discrepancies between the paths resulting when descending down through different paths. (See &amp;rarr; BindingScopeProblem).
Thus, doing something with the current location, and especially descending or querying adjacent scopes can modify this current path state. Thus we need a means of invoking a query in a way not interfering with the current path state, otherwise we wouldn't be able to provide side-effect free query operations accessible on individual objects within the model.
!maintaining the current QueryFocus
As long as client code is just interested to use the current query location, we can provide a handle referring to it. But when a query needs to be run without side effect on the current location, we //push//&amp;nbsp; it aside and start using a new QueryFocus on top, which starts out at a new initial location. Client code again gets a handle (smart-ptr) to this location, and additionally may access the new //current location.// When all references are out of scope and gone, we'll drop back to the focus put aside previously.
!implementation of ref-counting and clean-up
Actually, client code should use QueryFocus instances as frontend to access this &amp;raquo;current focus&amp;laquo;. Each ~QueryFocus instance incorporates a smart-ptr. But as in this case we're not managing objects allocated somewhere, we use an {{{boost::intrusive_ptr}}} and maintain the ref-count immediately within the target objects to be managed. These target objects are ScopePath instances and are living within the QueryFocusStack, which in turn in managed by the ScopeLocator singleton (see the UML diagram &amp;rarr;[[here|QueryFocus]]). We use an (hand-written) stack implementation to ensure the memory locations of these ScopePath &amp;raquo;frames&amp;laquo; remain valid (and also to help with providing strong exception guarantees). The stack is aware of these ref-count and takes it into account on performing the {{{pop_unused()}}} operation: any unused frame on top will be evicted, stopping at the first frame still in use (which may be even just the old top). This cleanup also happens automatically when accessing the current top, re-initialising an potentially empty stack with a default-constructed new frame if necessary. This way, just accessing the stack top always yields the ''current focus location'', which thereby is //defined as the most recently used focus location still referred.//
!concurrency
This concept deliberately ignores parallelism. But, as the current path state is already encapsulated (and ref-counting is in place), the only central access point is to reach the current scope. Instead of using a plain-flat singleton here, this access can easily be routed through thread local storage.
{{red{As of 10/09 it is not clear if there will be any concurrent access to this discovery API}}} &amp;mdash; but it seems not unlikely to happen...
</pre>
</div>
<div title="QueryImplProlog" modifier="Ichthyostega" modified="200910171607" created="200801202321" tags="draft design Rules" changecount="18">
<pre>//obviously, getting this one to work requires quite a lot of technical details to be planned and implemented.// This said...
The intention is to get much more readable (&quot;declarative&quot;) and changeable configuration as by programming the decision logic literately within the implementation of some object.
!Draft
As an example, specifying how a Track can be configured for connecting automatically to some &quot;mpeg&quot; bus (=pipe)
{{{
resolve(O, Cap) :- find(O), capabilities(Cap).
resolve(O, Cap) :- make(O), capabilities(Cap).
capabilities(Q) :- call(Q).
stream(T, mpeg) :- type(T, track), type(P, pipe), resolve(P, stream(P,mpeg)), place_to(P, T).
}}}
Then, running the goal {{{:-resolve(T, stream(T,mpeg)).}}} would search a Track object, try to retrieve a pipe object with stream-type=mpeg and associate the track with this pipe. This relies on a predicate &quot;stream(P,mpeg)&quot; implemented (natively) for the pipe object. So, &quot;Cap&quot; is the query issued from calling code &amp;mdash; here {{{stream(T,mpeg)}}}, the type guard {{{type(T, track)}}} will probably be handled or inserted automatically, while the predicate implementations for find/1, make/1, stream/2, and place_to/2 are to be provided by the target types.
* __The supporting system__ had to combine several code snippets into one rule system to be used for running queries, with some global base rules, rules injected by each individual participating object kind and finally user provided rules added by the current session. The actual query is bound to &quot;Cap&quot; (and consequently run as a goal by {{{call(Q)}}}). The implementation needs to provide a symbol table associating variable terms (like &quot;T&quot; or &quot;P&quot;) to C/C++ object types, enabling the participating object kinds to register their specific predicate implementations. This is crucial, because there can be no general scheme of object-provided predicates (for each object kind different predicates make sense, e.g. [[pipes|PipeHandling]] have other possibilities than [[wiring requests|WiringRequest]]). Basically, a query issues a Prolog goal, which in turn evaluates domain specific predicates provided by the participating objects and thus calls back into C/C++ code. The supporting system maintains the internal connection (via the &quot;type&quot; predicate) such that from Prolog viewpoint it looks as if we were binding Variables directly to object instances. (there are some nasty technical details because of the backtracking nature of Prolog evaluations which need to be hidden away)
* Any __participating object kind__ needs a way to declare domain specific predicates, thus triggering the registration of the necessary hooks within the supporting system. Moreover, it should be able to inject further prolog code (as shown in the example above with the {{{strem(T, mpeg)}}} predicate. For each of these new domain specific predicates, there needs to be a functor which can be invoked when the C implementation of the predicate is called from Prolog (in some cases even later, when the final solution is &quot;executed&quot;, e.g. a new instance has been created and now some properties need to be set).
!!a note on Plugins
In the design of the Lumiera Proc Layer done thus far, we provide //no possibility to introduce a new object kind// into the system via plugin interface. The system uses a fixed collection of classes intended to cover all needs (Clip, Effect, Track, Pipe, Label, Automation, ~Macro-Clips). Thus, plugins will only be able to provide new parametrisations of existing classes. This should not be any real limitation, because the whole system is designed to achieve most of its functionality by freely combining rather basic object kinds. As a plus, it plays nicely with any plain-C based plugin interface. For example, we will have C++ adapter classes for the most common sorts of effect plugin (pull system and synchronous frame-by-frame push with buffering) with a thin C adaptation layer for the specific external plugin systems used. Everything beyond this point can be considered &quot;condiguration data&quot; (including the actual plugin implementation to be loaded)
</pre>
</div>
<div title="QueryResolver" modifier="Ichthyostega" modified="200911090412" created="200910210300" tags="Rules spec draft img" changecount="26">
<pre>Within the Lumiera Proc-Layer, there is a general preference for issuing [[queries|Query]] over hard wired configuration (or even mere table based configuration). This leads to the demand of exposing a //possibility to issue queries// &amp;mdash; without actually disclosing much details of the facility implementing this service. For example, for shaping the general session interface (in 10/09), we need a means of exposing a hook to discover HighLevelModel contents, without disclosing how the model is actually organised internally (namely by using an PlacementIndex).
!Analysis of the problem
The situation can be decomposed as follows.[&gt;img[QueryResolver|uml/fig137733.png]]
* first off, we need a way to state //what kind of query we want to run.// This includes stipulations on the type of the expected result set contents
* as the requirement is to keep the facility actually implementing the query service hidden behind an interface, we're forced to erase specific type information and pass on an encapsulated version of the query
* providing an iterator for exploring the results poses the additional constraint of having an fairly generic iterator type, while still being able to communicate with the actual query implementation behind the interface.
!!!Difficulties
*the usage pattern is not clear &amp;mdash; mostly it's just //planned//
*# client might create a specific {{{Query&lt;TY&gt;}}} and demand resolution
*# client might create just a goal, which is then translated into a specific query mechanism behind the invocation interface
*# client issues a query and expect it just to be handled by //some//&amp;nbsp; suitable resolver
* thus it's difficult to determine, //what// part of the issued query needs automatic management. More specifically, is it possible for the client to dispose the query after issuing it, but keeping and exploring the iterator obtained as result of the query?
* and then there is the notorious problem of re-gaining the specifically typed context //behind//&amp;nbsp; the invocation interface. Especially, the facility processing the query needs to know both the expected result type and details about the concrete query and its parametrisation. &lt;br/&gt;&amp;rarr; TypedQueryProblem
!!!Entities and Operations
The //client// &amp;nbsp;(code using query-resolver.hpp) either wants a ''goal'' or ''query'' to be resolved; the former is just implicitly typed and usually given in predicate logic from ({{red{planned as of 11/09}}}), while the latter may be a specialised subclass templated to yield objects of a specific type as results. A ''query resolver'' is an (abstracted) entity capable of //resolving//&amp;nbsp; such a goal. Actually, behind the scenes there is somehow a registration of the concrete resolving facilities, which are asumed to decide about their ability of handling a given goal. Issuing a goal or query yields a ''resolution'' &amp;mdash; practically speaking, a set of indivitual solutions. These individual solution ''results'' can be explored by ''iteration'', thereby moving an embedded ''cursor'' through the ''result set''. Any result can be retrieved at most once &amp;mdash; after that, the resolution is ''exhausted'' and will be released automatically when the expolration iterator goes out of scope.
!!!Decisions
* while, in the use case currently at hand, the query instance is created by the client on the stack, the possibility of managing the queries internally is deliberately kept open. Because otherwise, we had to commit to a specific way of obtaining results, for example by assuming always to use an embedded STL iterator.
* we endorse that uttermost performance is less important than clean separation an extensibility. Thus we accept accessing the current position pointer through reference and we use a ref-counting mechanism alongside with the iterator to be handed out to the client
* the result set is not tied to the query &amp;mdash; at least not by design. The query can be discarded while further exploring the result set.
* for dealing with the TypedQueryProblem, we require the concrete resolving facilities to register with a system startup hook, to build a dispatcher table on the implementation side. This allows us to downcast to the concrete Cursor type on iteration and results retrieval.
</pre>
</div>
<div title="RSSReaderPlugin" modifier="Ichthyostega" created="200708081515" tags="systemConfig" changecount="1">
<pre>/***
|''Name:''|RSSReaderPlugin|
|''Description:''|This plugin provides a RSSReader for TiddlyWiki|
|''Version:''|1.1.1|
|''Date:''|Apr 21, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#RSSReaderPlugin|
|''Documentation:''|http://tiddlywiki.bidix.info/#RSSReaderPluginDoc|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''Credit:''|BramChen for RssNewsMacro|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
|''OptionalRequires:''|http://www.tiddlytools.com/#NestedSlidersPlugin|
***/
//{{{
version.extensions.RSSReaderPlugin = {
major: 1, minor: 1, revision: 1,
date: new Date(&quot;Apr 21, 2007&quot;),
source: &quot;http://TiddlyWiki.bidix.info/#RSSReaderPlugin&quot;,
author: &quot;BidiX&quot;,
coreVersion: '2.2.0'
};
config.macros.rssReader = {
dateFormat: &quot;DDD, DD MMM YYYY&quot;,
itemStyle: &quot;display: block;border: 1px solid black;padding: 5px;margin: 5px;&quot;, //useed '@@'+itemStyle+itemText+'@@'
msg:{
permissionDenied: &quot;Permission to read preferences was denied.&quot;,
noRSSFeed: &quot;No RSS Feed at this address %0&quot;,
urlNotAccessible: &quot; Access to %0 is not allowed&quot;
},
cache: [], // url =&gt; XMLHttpRequest.responseXML
desc: &quot;noDesc&quot;,
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
var desc = params[0];
var feedURL = params[1];
var toFilter = (params[2] ? true : false);
var filterString = (toFilter?(params[2].substr(0,1) == ' '? tiddler.title:params[2]):'');
var place = createTiddlyElement(place, &quot;div&quot;, &quot;RSSReader&quot;);
wikify(&quot;^^&lt;&lt;rssFeedUpdate &quot;+feedURL+&quot; [[&quot; + tiddler.title + &quot;]]&gt;&gt;^^\n&quot;,place);
if (this.cache[feedURL]) {
this.displayRssFeed(this.cache[feedURL], feedURL, place, desc, toFilter, filterString);
}
else {
var r = loadRemoteFile(feedURL,config.macros.rssReader.processResponse, [place, desc, toFilter, filterString]);
if (typeof r == &quot;string&quot;)
displayMessage(r);
}
},
// callback for loadRemoteFile
// params : [place, desc, toFilter, filterString]
processResponse: function(status, params, responseText, url, xhr) { // feedURL, place, desc, toFilter, filterString) {
if (window.netscape){
try {
if (document.location.protocol.indexOf(&quot;http&quot;) == -1) {
netscape.security.PrivilegeManager.enablePrivilege(&quot;UniversalBrowserRead&quot;);
}
}
catch (e) { displayMessage(e.description?e.description:e.toString()); }
}
if (xhr.status == httpStatus.NotFound)
{
displayMessage(config.macros.rssReader.noRSSFeed.format([url]));
return;
}
if (!status)
{
displayMessage(config.macros.rssReader.noRSSFeed.format([url]));
return;
}
if (xhr.responseXML) {
// response is interpreted as XML
config.macros.rssReader.cache[url] = xhr.responseXML;
config.macros.rssReader.displayRssFeed(xhr.responseXML, params[0], url, params[1], params[2], params[3]);
}
else {
if (responseText.substr(0,5) == &quot;&lt;?xml&quot;) {
// response exists but not return as XML -&gt; try to parse it
var dom = (new DOMParser()).parseFromString(responseText, &quot;text/xml&quot;);
if (dom) {
// parsing successful so use it
config.macros.rssReader.cache[url] = dom;
config.macros.rssReader.displayRssFeed(dom, params[0], url, params[1], params[2], params[3]);
return;
}
}
// no XML display as html
wikify(&quot;&lt;html&gt;&quot; + responseText + &quot;&lt;/html&gt;&quot;, params[0]);
displayMessage(config.macros.rssReader.msg.noRSSFeed.format([url]));
}
},
// explore down the DOM tree
displayRssFeed: function(xml, place, feedURL, desc, toFilter, filterString){
// Channel
var chanelNode = xml.getElementsByTagName('channel').item(0);
var chanelTitleElement = (chanelNode ? chanelNode.getElementsByTagName('title').item(0) : null);
var chanelTitle = &quot;&quot;;
if ((chanelTitleElement) &amp;&amp; (chanelTitleElement.firstChild))
chanelTitle = chanelTitleElement.firstChild.nodeValue;
var chanelLinkElement = (chanelNode ? chanelNode.getElementsByTagName('link').item(0) : null);
var chanelLink = &quot;&quot;;
if (chanelLinkElement)
chanelLink = chanelLinkElement.firstChild.nodeValue;
var titleTxt = &quot;!![[&quot;+chanelTitle+&quot;|&quot;+chanelLink+&quot;]]\n&quot;;
var title = createTiddlyElement(place,&quot;div&quot;,null,&quot;ChanelTitle&quot;,null);
wikify(titleTxt,title);
// ItemList
var itemList = xml.getElementsByTagName('item');
var article = createTiddlyElement(place,&quot;ul&quot;,null,null,null);
var lastDate;
var re;
if (toFilter)
re = new RegExp(filterString.escapeRegExp());
for (var i=0; i&lt;itemList.length; i++){
var titleElm = itemList[i].getElementsByTagName('title').item(0);
var titleText = (titleElm ? titleElm.firstChild.nodeValue : '');
if (toFilter &amp;&amp; ! titleText.match(re)) {
continue;
}
var descText = '';
descElem = itemList[i].getElementsByTagName('description').item(0);
if (descElem){
try{
for (var ii=0; ii&lt;descElem.childNodes.length; ii++) {
descText += descElem.childNodes[ii].nodeValue;
}
}
catch(e){}
descText = descText.replace(/&lt;br \/&gt;/g,'\n');
if (desc == &quot;asHtml&quot;)
descText = &quot;&lt;html&gt;&quot;+descText+&quot;&lt;/html&gt;&quot;;
}
var linkElm = itemList[i].getElementsByTagName(&quot;link&quot;).item(0);
var linkURL = linkElm.firstChild.nodeValue;
var pubElm = itemList[i].getElementsByTagName('pubDate').item(0);
var pubDate;
if (!pubElm) {
pubElm = itemList[i].getElementsByTagName('date').item(0); // for del.icio.us
if (pubElm) {
pubDate = pubElm.firstChild.nodeValue;
pubDate = this.formatDateString(this.dateFormat, pubDate);
}
else {
pubDate = '0';
}
}
else {
pubDate = (pubElm ? pubElm.firstChild.nodeValue : 0);
pubDate = this.formatDate(this.dateFormat, pubDate);
}
titleText = titleText.replace(/\[|\]/g,'');
var rssText = '*'+'[[' + titleText + '|' + linkURL + ']]' + '' ;
if ((desc != &quot;noDesc&quot;) &amp;&amp; descText){
rssText = rssText.replace(/\n/g,' ');
descText = '@@'+this.itemStyle+descText + '@@\n';
if (version.extensions.nestedSliders){
descText = '+++[...]' + descText + '===';
}
rssText = rssText + descText;
}
var story;
if ((lastDate != pubDate) &amp;&amp; ( pubDate != '0')) {
story = createTiddlyElement(article,&quot;li&quot;,null,&quot;RSSItem&quot;,pubDate);
lastDate = pubDate;
}
else {
lastDate = pubDate;
}
story = createTiddlyElement(article,&quot;div&quot;,null,&quot;RSSItem&quot;,null);
wikify(rssText,story);
}
},
formatDate: function(template, date){
var dateString = new Date(date);
// template = template.replace(/hh|mm|ss/g,'');
return dateString.formatString(template);
},
formatDateString: function(template, date){
var dateString = new Date(date.substr(0,4), date.substr(5,2) - 1, date.substr(8,2)
);
return dateString.formatString(template);
}
};
config.macros.rssFeedUpdate = {
label: &quot;Update&quot;,
prompt: &quot;Clear the cache and redisplay this RssFeed&quot;,
handler: function(place,macroName,params) {
var feedURL = params[0];
var tiddlerTitle = params[1];
createTiddlyButton(place, this.label, this.prompt,
function () {
if (config.macros.rssReader.cache[feedURL]) {
config.macros.rssReader.cache[feedURL] = null;
}
story.refreshTiddler(tiddlerTitle,null, true);
return false;});
}
};
//}}}
</pre>
</div>
<div title="RelationClipAsset" modifier="Ichthyostega" modified="200710212327" created="200710191541" tags="design decision img" changecount="7">
<pre>What is the Role of the asset::Clip and how exactly are Assets and (Clip)-MObjects related?
First of all: ~MObjects are the dynamic/editing/manipulation view, while Assets are the static/bookkeeping/searching/information view of the same entities. Thus, the asset::Clip contains the general configuration, the ref to the media and descriptive properties, while all parameters being &quot;manipulated&quot; belong to the session::Clip (MObject). Besides that, the practical purpose of asset::Clip is that you can save and remember some selection as a Clip (Asset), maybe even attach some informations or markup to it, and later be able to (re)create a editable representation in the Session (the GUI could implement this by allowing to drag from the asset::Clip GUI representation to the timeline window)
!!dependencies
The session::Clip (frequently called &quot;clip-MO&quot;, i.e. the MObject) //depends on the Asset.// It can't exist without the Asset, because the Asset is needed for rendering. The other direction is different: the asset::Clip knows that there is a dependant clip-MO, there could be //at most one// such clip-MO depending on the Asset, but the Asset can exist without the clip-MO (it gives the possibility to re-create the clip-MO).
!!deletions
When the Asset or the corresponding asset::Media is deleted, the dependant clip-MO has to disappear. And the opposite direction?
* Model-1: asset::Clip has a weak ref to the clip-MO. Consequently, the clip-MO can go out of scope and disappear, so the asset::Clip has to maintain the information of the clip's dimensions (source position and length) somewhere. Because of MultichannelMedia, this is not so simple as it may look at first sight.
* Model-2: asset::Clip holds a smart ptr to the clip-MO, thus effectively keeping it alive. __obviously the better choice__
In either case, we have to solve the ''problem of clip asset proliferation''
!!multiplicity and const-ness
The link between ~MObject and Asset should be {{{const}}}, so the clip can't change the media parameters. Because of separation of concerns, it would be desirable that the Asset can't //edit// the clip either (meaning {{{const}}} in the opposite direction as well). But unfortunately the asset::Clip is in power to delete the clip-MO and, moreover, handles out a smart ptr ([[Placement]]) referring to the clip-MO, which can (and should) be used to place the clip-MO within the session and to manipulate it consequently...
[&gt;img[Outline of the Build Process|uml/fig131333.png]]
At first sight the link between asset and clip-MO is a simple logical relation between entities, but it is not strictly 1:1 because typical media are [[multichannel|MultichannelMedia]]. Even if the media is compound, there is //only one asset::Clip//, because in the logical view we have only one &quot;clip-thing&quot;. On the other hand, in the session, we have a compound clip ~MObject comprised of several elementary clip objects, each of which will refer to its own sub-media (channel) within the compound media (and don't forget, this structure can be tree-like)
{{red{open question:}}} do the clip-MO's of the individual channels refer directly to asset::Media? does this mean the relation is different from the top level, where we have a relation to a asset::Clip??</pre>
</div>
<div title="RenderEngine" modifier="Ichthyostega" modified="200802031835" created="200802031820" tags="def" changecount="2">
<pre>Conceptually, the Render Engine is the core of the application. But &amp;mdash; surprisingly &amp;mdash; we don't even have a distinct »~RenderEngine« component in our design. Rather, the engine is formed by the cooperation of several components spread over two layers (Backend and Proc-Layer): The [[Builder]] creates a network of [[render nodes|ProcNode]], which is used by the Backend to pull individual Frames.
&amp;rarr; OverviewRenderEngine
</pre>
</div>
<div title="RenderEntities" modifier="Ichthyostega" modified="200906071810" created="200706190715" tags="Rendering classes img" changecount="10">
<pre>The [[Render Engine|Rendering]] only carries out the low-level and performance critical tasks. All configuration and decision concerns are to be handled by [[Builder]] and [[Controller]]. While the actual connection of the Render Nodes can be highly complex, basically each Segment of the Timeline with uniform characteristics is handled by one Processor, which is a graph of [[Processing Nodes|ProcNode]] discharging into a ExitNode. The Render Engine Components as such are //stateless// themselves; for the actual calculations they are combined with a StateProxy object generated by and connected internally to the [[Controller]], while at the same time holding the Data Buffers (Frames) for the actual calculations.
[img[Entities comprising the Render Engine|uml/fig128389.png]]
</pre>
</div>
<div title="RenderImplDetails" modifier="Ichthyostega" modified="200810140245" created="200806220211" tags="Rendering impl img" changecount="22">
<pre>Below are some notes regarding details of the actual implementation of the render process and processing node operation. In the description of the [[render node operation protocol|NodeOperationProtocol]] and the [[mechanics of the render process|RenderMechanics]], these details were left out deliberately.
!Layered structure of State
State can be seen as structured like an onion. All the [[StateAdapter]]s in one call stack are supposed to be within one layer: they all know of a &quot;current state&quot;, which in turn is a StateProxy (and thus may refer yet to another state, maybe accros the network or in the backend or whatever). The actual {{{process()}}} function &quot;within&quot; the individual nodes just sees a single StateAdapter and thus can be thought to be a layer below.
!Buffer identification
For the purpose of node operation, Buffers are identified by a //Buffer-handle,// which contains both the actual buffer pointer and an internal indes and classification of the source providing the buffer; the latter information is used for deallocation. Especially for calling the {{{process()}}} function (which is supposed to be plain C) the respective StateAdapter provides an array containing just the output and input buffer pointers
!Problem of multi-channel nodes
Some data processors simply require to work on multiple channels simultanously, while others work just on a single channel and will be replicated by the builder for each channel invoved. Thus, we are struck with the nasty situation that the node graph may go through some nodes spanning the chain of several channels. Now the decision is //not to care for this complexity within a single chain calculating a single channel.// We rely solely on the cache to avoid duplicated calculations. When a given node happens to produce multiple output buffers, we are bound to allocate them for the purpose of this nodes {{{process()}}} call, but we just &quot;let go&quot; the buffers not needed immediately for the channel acutally to be processed. For this to work, it is supposed that the builder has wired in a caching, and that the cache will hit when we touch the same node again for the other channels.
Closely related to this is the problem how to number and identify nodes and thus to be able to find calculated frames in cache (&amp;rarr; [[here|NodeFrameNumbering]])
!Configuration of the processing nodes
[&gt;img[uml/fig132357.png]]
Every node is actually decomposed into three parts
* an interface container of a ProcNode subclass
* an {{{const}}} WiringDescriptor, which is actually parametrized to a subtype encoding details of how to carry out the intended operation
* the Invocation state created on the stack for each {{{pull()}}} call. It is comprised of references to an StateAdapter object and the current overall process state, the WiringDescriptor, and finally a table of suitable buffer handles
Thus, the outer container can be changed polymorphically to support the different kinds of nodes (large-scale view). The actual wiring of the nodes is contained in the WiringDescriptor, including the {{{process()}}} function pointer. Additionally, this WiringDescriptor knows the actual type of the operation Strategy, and this actual type has been chosen by the builder such as to select details of the desired operation of this node, for example caching / no caching or maybe ~OpenGL rendering or the special case of a node pulling directly from a source reader. Most of this configuration is done by selecting the right template specialisation within the builder; thus in the critical path most of the calls can be inlined
!!!! composing the actual operation Strategy
As shown in the class diagram to the right, the actual implementation is assembled by chaining together the various policy classes governing parts of the node operation, like Caching, in-Place calculation capability, etc. (&amp;rarr; see [[here|WiringDescriptor]] for details). The rationale is that the variable part of the Invocation data is allocated at runtime directly on the stack, while a precisely tailored call sequence for &quot;calculating the predecessor nodes&quot; can be defined out of a bunch of simple building blocks. This helps avoiding &quot;spaghetti code&quot;, which would be especially dangerous because of the large number of different execution paths to get right. Additionally, a nice side effect of this implementation technique is that a good deal of the implementation is eligible to inlining.
We //do employ//&amp;nbsp; some virtual calls for the buffer management in order to avoid coupling the policy classes to the actual number of in/out buffers. (As of 6/2008, this is mainly a precaution to be able to control the number of generated template instances. If we ever get in the region of several hundred individual specialisations, we'd need to separate out the allocation of the &quot;buffer table&quot; into a hand-made stack-like buffer allocated from the heap.)
!Rules for buffer allocation and freeing
* only output buffers are allocated. It is //never necessary//&amp;nbsp; to allocate input buffers!
* buffers are to be allocated as late as possible, typically just before invoking {{{process()}}}
* buffers are allways allocated by calling to the preceeding StateAdapter in the callstack (&quot;parent stae&quot;), because of the possibility of writing the result to cache.
* {{{pull()}}} returns a handle for the single output requested by this call. Using this ID, the caller may retrieve the actual buffer holding the result from the &quot;current state&quot; StaeProxy.
* any other buffers filled with results in the course of the same {{{process()}}} call can be released immediately before returning from the {{{pull()}}}
* similar, and input buffers are to be released immediately after the {{{process()}}} call, but before returing from this {{{pull()}}}
* buffers are allways released by calling to the &quot;current state&quot; (which is a StateProxy), providing the buffer-ID to be released
@@clear(right):display(block):@@
</pre>
</div>
<div title="RenderMechanics" modifier="Ichthyostega" modified="200906071809" created="200806030230" tags="Rendering operational impl img" changecount="28">
<pre>While the render process, with respect to the dependencies, the builder and the processing function is sufficiently characterized by referring to the ''pull principle'' and by defining a [[protocol|NodeOperationProtocol]] each node has to adhere to &amp;mdash; for actually get it coded we have to care for some important details, especially //how to manage the buffers.// It may well be that the length of the code path necessary to invoke the individual processing functions is finally not so important, compared with the time spent at the inner pixel loop within these functions. But my guess is (as of 5/08), that the overall number of data moving and copying operations //will be//&amp;nbsp; of importance.
!requirements
* operations should be &quot;in place&quot; as much as possible
* because caching necessitates a copy, the points where this happens should be controllable.
* buffers should accommodate automatically to provide the necessary space without clipping the image.
* the type of the media data can change while passing through the network, and so does the type of the buffers.
On the other hand, the processing function within the individual node needs to be shielded from these complexities. It can expect to get just //N~~I~~// input buffers and //N~~O~~// output buffers of required type. And, moreover, as the decision how to organize the buffers certainly depends on non-local circumstances, it should be preconfigured while building.
!data flow
[&gt;img[uml/fig131973.png]]
Not everything can be preconfigured though. The pull principle opens the possibility for the node to decide on a per call base what predecessor(s) to pull (if any). This decision may rely on automation parameters, which thus need to be accessible prior to requesting the buffer(s). Additionally, in a later version we plan to have the node network calculate some control values for adjusting the cache and backend timings &amp;mdash; and of course at some point we'll want to utilize the GPU, resulting in the need to feed data from our processing buffers into some texture representation.
!buffer management
Besides the StateProxy representing the actual render process and holding a couple of buffer (refs), we employ a lightweight adapter object in between. It is used //for a single {{{pull()}}}-call// &amp;mdash; mapping the actual buffers to the input and output port numbers of the processing node and for dealing with the cache calls. While the StateProxy manages a pool of frame buffers, this interspersed adapter allows us to either use a buffer retrieved from the cache as an input, possibly use a new buffer located within the cache as output, or (in case no caching happens) to just use the same buffer as input and output for &quot;in-place&quot;-processing. The idea is that most of the configuration of this adapter object is prepared in the wiring step while building the node network.
The usage patern of the buffers can be stack-like when processing nodes require multiple input buffers. In the standard case, which also is the simplest case, a pair of buffers (or a single buffer for &quot;in-place&quot; capable nodes) suffices to calculate a whole chain of nodes. But &amp;mdash; as the recursive descent means depth-first processing &amp;mdash; in case multiple input buffers are needed, we may encounter a situation where some of these input buffers already contain processed data, while we have to descend into yet another predecessor node chain to pull the data for the remaining buffers. Care has to be taken //to allocate the buffers as late as possible,// otherwise we could end up holding onto a buffer almost for each node in the network. Effectively this translates into the rule to allocate output buffers only after all input buffers are ready and filled with data; thus we shouldn't allocate buffers when //entering// the recursive call to the predecessor(s), rather we have to wait until we are about to return from the downcall chain.
Besides, these considerations also show we need a means of passing on the current buffer usage pattern while calling down. This usage pattern not only includes a record of what buffers are occupied, but also the intended use of these occupied buffers, especially if they can be modified in-place, and at which point they may be released and reused.
__note__: this process outlined here and below is still an simplification. The actual implementation has some additional [[details to care for|RenderImplDetails]]
!!Example: calculating a 3 node chain
# Caller invokes calculation by pulling from exit node, providing the top-level StateProxy
# node1 (exit node) builds StateAdapter and calls retrieve() on it to get the desired output result
# this StateAdapter (ad1) knows he could get the result from Cache, so he tries, but it's a miss
# thus he pulls from the predecessor node2 according to the [[input descriptor|ProcNodeInputDescriptor]] of node1
# node2 builds its StateAdapter and calls retrieve()
# but because StateAdapter (ad2) is configured to directly forward the call down (no caching), it pulls from node3
# node3 builds its StateAdapter and calls retrieve()
# this StateAdapter (ad3) is configured to look into the Cache...
# this time producing a Cache hit
# now StateAdapter ad2 has input data, but needs a output buffer location, which re requests from its //parent state// (ad1)
# and, because ad1 is configured for Caching and is &quot;in-place&quot; capable, it's clear that this output buffer will be located within the cache
# thus the allocation request is forwarded to the cache, which provides a new &quot;slot&quot;
# now node2 has both a valid input and a usable output buffer, thus the process function can be invoked
# and after the result has been rendered into the output buffer, the input is no longer needed
# and can be &quot;unlocked&quot; in the Cache
# now the input data for node1 is available, and as node1 is in-place-capable, no further buffer allocation is necessary prior to calculating
# the finished result is now in the buffer (which happens to be also the input buffer and is actually located within the Cache)
# thus it can be marked as ready for the Cache, which may now provide it to other processes (but isn't allowed to overwrite it)
# finally, when the caller is done with the data, it signalles this to the top-level State object
# which forwards this information to the cache, which in turn may now do with the released Buffer as he sees fit.
[img[uml/fig132229.png]]
@@clear(right):display(block):@@
__see also__
&amp;rarr; the [[Entities involved in Rendering|RenderEntities]]
&amp;rarr; additional [[implementation details|RenderImplDetails]]
&amp;rarr; [[Memory management for render nodes|ManagementRenderNodes]]
&amp;rarr; the protocol [[how to operate the nodes|NodeOperationProtocol]]
</pre>
</div>
<div title="RenderProcess" modifier="Ichthyostega" modified="200806130009" created="200706190705" tags="Rendering operational" changecount="27">
<pre>For each segment (of the effective timeline), there is a Processor holding the exit node(s) of a processing network, which is a &quot;Directed Acyclic Graph&quot; of small, preconfigured, stateless [[processing nodes|ProcNode]]. This network is operated according to the ''pull principle'', meaning that the rendering is just initiated by &quot;pulling&quot; output from the exit node, causing a cascade of recursive downcalls. Each node knows its predecessor(s) an can pull the necessary input from there. Consequently, there is no centralized &quot;engine object&quot; which may invoke nodes iteratively or table driven &amp;mdash; rather, the rendering can be seen as a passive service provided for the backend, which may pull from the exit nodes at any time, in any order (?), and possibly multithreaded.
All State necessary for a given calculation process is encapsulated and accessible by a StateProxy object, which can be seen as the representation of &quot;the process&quot;. At the same time, this proxy provides the buffers holding data to be processed and acts as a gateway to the backend to handle the communication with the Cache. In addition to this //top-level State,// each calculation step includes a small [[state adapter object|StateAdapter]] (stack allocated), which is pre-configured by the builder and serves the purpose to isolate the processing function from the detals of buffer management.
__see also__
&amp;rarr; the [[Entities involved in Rendering|RenderEntities]]
&amp;rarr; the [[mechanics of rendering and buffer management|RenderMechanics]]
&amp;rarr; the protocol [[how to operate the nodes|NodeOperationProtocol]]
</pre>
</div>
<div title="Rendering" modifier="Ichthyostega" modified="200806010307" created="200806010248" tags="def overview" changecount="3">
<pre>The rendering of input sources to the desired output ports happens within the &amp;raquo;''Render Engine''&amp;laquo;, which can be seen as a collaboration of Proc-Layer, Backend together with external/library code for the actual data manipulation. In preparation of the RenderProcess, the [[Builder]] as wired up a network of [[processing nodes|ProcNode]] called the ''low-level model'' (in contrast to the high-level model of objects placed within the session). Generally, this network is a &quot;Directed Acyclic Graph&quot; starting at the //exit nodes// (output ports) and pointing down to the //source readers.// In Lumiera, rendering is organized according to the ''pull principle'': when a specific frame of rendered data is requested from an exit node, a recursive calldown happens, as each node asks his predecessor(s) for the necessary input frame(s). This may include pulling frames from various input sources and for several time points, thus pull rendering is more powerful (but also more difficult to understand) than push rendering, where the process would start out with a given source frame.
Rendering can be seen as a passive service available to the Backend, which remains in charge what to render and when. Render processes may be running in parallel without any limitations. All of the storage and data management falls into the realm of the Backend. The render nodes themselves are ''completely stateless'' &amp;mdash; if some state is necessary for carrying out the calculations, the backend will provide a //state frame// in addition to the data frames.</pre>
</div>
<div title="Rules" modifier="Ichthyostega" modified="200910171620" created="200910171618" tags="overview" changecount="5">
<pre>A distinct property of the Lumiera application is to rely on a rules based approach rather then on hard wired logic. When it comes to deciding and branching, a [[Query]] is issued, resulting either immediately in a {{{bool}}} result, or creating a //binding// for the variables used within the query. Commonly, there is more than one solution for a given query, allowing the result set to be enumerated.
!current state {{red{WIP as of 10/09}}}
We are still fighting to get the outline of the application settled down.
For now, the above remains in the status of a general concept and typical solution pattern: ''create query points instead of hard wiring things''.
Later on we expect a distinct __query subsystem__ to emerge, presumably embedding a YAP Prolog interpreter.</pre>
</div>
<div title="STypeManager" modifier="Ichthyostega" created="200809220230" changecount="1">
<pre>A facility allowing the Proc-Layer to work with abstracted [[media stream types|StreamType]], linking (abstract or opaque) [[type tags|StreamTypeDescriptor]] to an [[library|MediaImplLib]], which provides functionality for acutally dealing with data of this media stream type. Thus, the stream type manager is a kind of registry of all the external libraries which can be bridged and accessed by Lumiera (for working with media data, that is). The most basic set of libraries is instelled here automatically at application start, most notably the [[GAVL]] library for working with uncompressed video and audio data. //Later on, when plugins will introduce further external libraries, these need to be registered here too.//</pre>
</div>
<div title="ScopeLocator" modifier="Ichthyostega" modified="200911202035" created="200911192145" tags="def SessionLogic" changecount="10">
<pre>A link to relate a compound of [[nested placement scopes|PlacementScope]] to the //current// session and the //current//&amp;nbsp; [[focus for querying|QueryFocus]] and exploring the structure. ScopeLocator is a singleton service, allowing to ''explore'' a [[Placement]] as a scope, i.e. discover any other placements within this scope, and allowing to locate the position of this scope by navigating up the ScopePath finally to reach the root scope of the HighLevelModel.
In the general case, this user visible high-level-model of the [[objects|MObject]] within the session allows for more than tree-like associations, as a given [[Sequence]] might be bound into multiple [[timelines|Timeline]]. Effectively, this makes the ScopePath context dependent. The ScopeLocator is the point where the strictly tree-like hierarchy of placements is connected to this more elaborate scope and path structure. To this end, ScopeLocator maintaines a QueryFocusStack, to keep track of the current location in focus, in cooperation with the QueryFocus objects used by client code.
&amp;rarr; see BindingScopeProblem
&amp;rarr; see TimelineSequences
!!a note about concurrency
While there //is// a &quot;current state&quot; involved, the effect of concurrent access deliberately remains unspecified, because access is expected to be serialised on a higher level. If this assumption were to break, then probably the ScopeLocator would involve some thread local state.
</pre>
</div>
<div title="ScopePath" modifier="Ichthyostega" modified="201001070848" created="200911202124" tags="def spec" changecount="3">
<pre>The sequence of nested [[placement scopes|PlacementScope]] leading from the root (global) scope down to a specific [[Placement]] is called ''scope path''. Ascending this path yields all the scopes to search or query in proper order to be used when resolving some attribute of placement. Placements use visibility rules comparable to visibility of scoped definitions in common programming languages or in cascading style sheets, where a local definition can shadow a global one. In a similar way, properties not defined locally may be resolved by querying up the sequence of nested scopes.
A scope path is a sequence of scopes, where each scope is implemented by a PlacementRef pointing to the &amp;raquo;scope top&amp;laquo;, i.e. the placement in the session //constituting this scope.// Each Placement is registered with the session as belonging to a scope, and each placement can contain other placements and thus form a scope. Thus, the ''leaf'' of this path can be considered the current scope. In addition to some search and query functions, a scope path has the ability to ''navigate'' to a given target scope, which must be reachable by ascending and descending into the branches of the overall tree or DAG. Navigating changes the current path. ({{red{WIP 11/09}}} navigation to scopes outside the current path and the immediate children of the current leaf is left out for now. We'll need it later, when actually implementing [[meta-clips|VirtualClip]].)
!Operations
* the default scope path contains just the root (of the implicit PlacementIndex, i.e. usually the root of the model in the session)
* a scope path can be created starting from a given scope. This convenience shortcut uses the ScopeLocator to establish the position of the given start scope. This way, effectively the PlacementIndex within the current session is queried for parentship relations until reaching the root of the HighLevelModel.
* paths are ''copyable value objects'' without identity on their own
* there is a special //invalid//&amp;nbsp; path token, {{{ScopePath::INVALID}}}
* length, validity and empty check
* paths are equality comparable
* relations
** if a scope in question is contained in the path
** if a scope in question is at the leaf position of the path
** if a path in question is prefix (contained) in the given path
** if two paths share a common prefix
** if two paths are disjoint (only connected at root)
* navigation
** move up one step
** move up to the root
** navigate to a given scope
** clear a path (reset to default)
</pre>
</div>
<div title="Sequence" modifier="Ichthyostega" modified="201003020521" created="201001252327" tags="def" changecount="13">
<pre>A sequence is a collection of media objects, arranged onto a track tree. Sequences are the building blocks within the session. To be visible and editable, a session needs to be bound into a top-level [[Timeline]]. Alternatively, it may be used as a VirtualClip nested within another sequence.
The sequences within the session establish a //logical grouping//, allowing for lots of flexibility. Actually, we can have several sequences within one session, and these sequences can be linked together or not, they may be arranged in temporal order or may constitute a logical grouping of clips used simultaneously in compositional work etc. Multiple sequences can use the same or different tracks, and tracks as well are only an organisational (grouping) device. But at any time, we have exactly one [[Fixture]], derived automatically from all sequences and containing the content actually to be rendered.
&amp;rarr; see considerations about [[the role of Tracks and Pipes in conjunction with the sequences|TrackPipeSequence]]
!!Implementation and lifecycle
Actually, sequences are façade objects to quite some extent, delegating the implementation of the exposed functionality to the relevant placements and ~MObjects within the model. But they're not completely shallow; each sequence has an distinguishable identity and may hold additional meta-data. Thus, stressing this static aspect, sequences are implemented as StructAsset, attached to the [[model root|ModelRootMO]] through the AssetManager, simultaneously registered with the session, then accessed and owned by ref-counting smart-ptr.
A sequence is always tied to a root-placed track, it can't exist without such. When moving this track by changing it's [[Placement]], thus disconnecting it from the root scope, the corresponding sequence will be automatically removed from the session and discarded. On the other hand, sequences aren't required to be //bound// &amp;mdash; a sequence might just exist in the model (attached by its track placed into root scope) and thereby remain passive and invisible. Such an unbound sequence can't be edited, displayed in the GUI or rendered, it is only accessible as asset. Of course it can be re-activated by linking it to a new or existing timeline or VirtualClip.
&amp;rarr; see detailed [[discussion of dependent objects' behaviour|ModelDependencies]]
</pre>
</div>
<div title="Session" modifier="Ichthyostega" modified="200911071800" created="200712100525" tags="def SessionLogic" changecount="8">
<pre>The Session contains all informations, state and objects to be edited by the User. From a users view, the Session is synonymous to the //current Project//. It can be [[saved and loaded|SessionLifecycle]]. The individual Objects within the Session, i.e. Clips, Media, Effects, are contained in one (or several) collections within the Session, which we call [[Sequence]].
&amp;rarr; [[Session design overview|SessionOverview]]
!Session structure
The Session object is a singleton &amp;mdash; actually it is a »~PImpl«-Facade object (because the actual implementation object can be swapped for (re)loading Sessions).&lt;br/&gt;The Session is the access point to the HighLevelModel; it is comprised of
* a number of (independent) top-level [[time-lines|Timeline]]
* some [[sequences|Sequence]] to be used within these timelines
* a [[scope structure|PlacementScope]] backed by an index, and a current QueryFocus
* a set of ConfigRules to guide default behaviour {{red{planned as of 10/09}}}
* the ''Fixture'' with a possibility to [[(re)build it|PlanningBuildFixture]] {{red{just partially designed as of 01/09}}}
* the [[Asset subsystem|AssetManager]] is tightly integrated; besides, there are some SessionServices for internal use
&amp;rarr; see [[relation of timeline, sequences and objects|TimelineSequences]]
</pre>
</div>
<div title="SessionDataMem" modifier="Ichthyostega" modified="201003160216" created="200904252303" tags="impl design SessionLogic draft" changecount="8">
<pre>The [[Session]] is interconnected with the GUI, the SessionStorage, [[Builder]] and the CommandHandling. The HighLevelModel is an conceptual view of the session. All these dependencies are isolated from the actual data layout in memory, but the latter is shaped by the intended interactions.
{{red{WIP...}}}Currently as of 3/10, this is an ongoing implementation and planning effort
!Objects, Placements, References
Media objects are attached to the session by [[placements|Placement]]. A Placement within the session gets an distinguishable identity (&amp;rarr; ModelObjectIdentity) and behaves like being an instance of the attached object. Client code usually interacts with the compound of placement + ~MObject. In order to decouple this interaction from the actual implementation within the session, client code rather deals with //references.// These are implemented like a smart-ptr, but based on an opaque hash value, which is equivalent to the //object instance identity.//
&amp;rarr; MObjectRef
&amp;rarr; PlacementRef
!Index of placements attached to the session
For implementation, the HighLevelModel can be reduced to a compound of interconnected placements. These placement instances are owned and managed by the session; attaching a placement means copying it into the session, thereby creating a new placement-ID. A lookup mechanism for placements and placement relations (PlacementIndex) thus is the actual core of the session data structure; the actual object instances are maintained by a pooling custom allocator ({{red{planned as of 1/10}}}).
!Lifecycle
MObject lifetime is managed by reference counting; all placements and client side references to an MObject share ownership. The placement instances attached to the session are maintained by the index; thus, as long as an placement exists, the corresponding object automatically stays alive. Similarly, assets, as managed by shared-ptrs, stay alive when directly referenced, even after being dropped from the AssetManager. A bare PlacementRef on the other hand doesn't guarantee anything about the referred placement; when dereferencing this reference token, the index is accessed to re-establish a connection to the object, if possible. The full-fledged MObjectRef is built on top of such a reference token and additionally incorporates a smart-ptr. For the client code this means, that holding a ref ensures existence of the object, while the //placement// of this object still can get removed from the session.
!Updates and dependent objects
The session and the models rely on dependent objects beeing kept updated and consistent. This problem can't be solved in a generic fashion &amp;mdash; at least not without using a far more elaborate scheme (e.g. a transaction manager), which is beyond the scope here. Thus, care has to be taken on the implementation level, especially in conjunction with error handling and threading considerations
&amp;rarr; see [[details here...|ModelDependencies]]
</pre>
</div>
<div title="SessionInterface" modifier="Ichthyostega" modified="201003272138" created="200904242108" tags="SessionLogic GuiIntegration design draft discuss" changecount="48">
<pre>&quot;Session Interface&quot;, when used in a more general sense, denotes a compound of several interfaces and facilities, together forming the primary access point to the user visible contents and state of the editing project.
* the API of the session class
* the accompanying management interface (SessionManager API)
* LayerSeparationInterfaces allowing to access these interfaces from outside the Proc-Layer
* the primary public ~APIs exposed on the objects to be [[queried and retrieved|SessionStructureQuery]] via the session class API
** Timeline
** Sequence
** Placement
** Clip
** Track
** Effect
** Automation
* the [[command|CommandHandling]] interface, including the [[UNDO|UndoManager]] facility
!generic and explicit API
The HighLevelModel exposes two kinds of interfaces (which are interconnected and rely on each other): A generic, but somewhat low-level API, which is good for processing &amp;mdash; like e.g. for the builder or de-serialiser &amp;mdash; and a more explicit API providing access to some meaningful entities within the model. Indeed, the latter (explicit top level entities) can be seen as a ''façade interface'' to the generic structures:
* the [[Session]] object itself corresponds to the ModelRootMO
* the one (or multiple) [[Timeline]] objects correspond to the BindingMO instances attached immediately below the model root
* the [[sequences|Sequence]] bound into these timelines (by the ~BindingMOs) correspond to the top level [[Track]]-~MObjects within each of these sequences.
[&lt;img[Object relations on the session façade|draw/SessionFacade1.png]]
Thus, there is a convenient and meaningful access path through these façade objects, which of course actually is implemented by forwarding to the actual model elements (root, bindings, tracks)
Following this access path down from the session means using the ''dedicated'' API on the objects retrieved.
To the contrary, the ''generic'' API is related to a //current location (state),// the QueryFocus.
!purpose of these ~APIs
* to discover
** by ID
** by type (filter)
** all contained
* to add
* to destroy
!!exploring session contents
Typically, the list of timelines serves as starting point for exploring the model. Basically, any kind of object could be attached everywhere, but both the GUI and the Builder rely on assumptions regarding the [[overall model structure|HighLevelModel]] &amp;mdash; silently ignoring content not in line. This corresponds to the //dedicated API functions// on specific kinds of objects, which allow to retrieve content according to this assumed structure conveniently and with strong typing. From the timeline and the sequence linked to it you'll get the root track, and from there the sub-tracks and the clips located on them, which in turn may have attachments (effects, transitions, labels).
On the other hand, arbitrary structures can be retrieved using the //generic API:// Contents can be discovered on the QueryFocus, which automatically follows the //point of mutation,// but can be moved to any point using the {{{QueryFocus::attach(obj)}}} function.
!!queries and defaults
Queries can retrieve the immediate children, or the complete contents of a scope (depth-first). The results can be filtered by type. The intention is to extend this later to arbitrary logical queries, using some kind of resolution engine. Besides these queries, [[default configured objects|DefaultsManagement]] can be retrieved or defined through the defaults manager, which is accessible as a self-contained component on the public session API. Defaults can be used to establish a standard way of doing things on a per-project base.
{{red{WIP ... just emerging}}}
!!discovery and mutations
The discovery functions available on these ~APIs are wired such as to return suitably typed MObjectRef instances always. These are small value objects and can be used to invoke operations (both query and mutating) on the underlying object within the session. Raw placement references aren't exposed on these outward interfaces.
While this protects against accessing dangling references, it can't prevent clients from invoking any mutating operation directly on these references. It would be conceivable, by using proxies, to create and invoke commands automatically. But we rather don't want to go this route, because
* Lumiera is an application focussed on very specific usage, not a general purpose library or framework
* regarding CommandHandling, the //design decision was to require a dedicated (and hand written) undo functor.//
!!!!protection against accidental mutation
{{red{WIP}}}As of 2/10, I am considering to add a protection against invoking an raw mutation operation accidentally, and especially bypassing the command frontend and the ProcDispatcher. This would not only be annoying (no UNDO), but potentially dangerous, because all of the session internals are not threadsafe by design.
The considered solution would be to treat this situation as if an authorisation is required; this authorisation for mutation could be checked by a &amp;raquo;wormhole&amp;laquo;-like context access (&amp;rarr; aspect oriented programming). Of course, in our case we're not dealing with real access restrictions, just a safeguard: While command execution creates such an authorisation token automatically, a client actually wanting to invoke an mutation operations bypassing the command frontend, would need to set up such a token explicitly and manually.
!!adding and destroying
Objects can be added and destroyed directly on the top level session API. The actual memory management of the object instances works automatically, based on reference counts. (Even after purging an object from the session, it may still be indirectly in use by an ongoing render process).
When adding an object, a [[scope|PlacementScope]] needs to be specified. Thus it makes sense to provide {{{add()}}}-operations on the dedicated API of individual session parts, while the generic {{{attach()}}}-call on the top-level session API relies on the current QueryFocus to determine the location where to add an object. Besides, as we're always adding the [[Placement]] of an object into the session, this placement may specify an additional constraint or tie to a specific scope; resolving the placement thus may cause the object to move to another location
!!!{{red{Questions}}}
* what exactly is the relation of discovery and [[mutations|SessionMutation]]?
* what's the point of locating them on the same conceptual level?
* how to observe the requirement of ''dispatching'' mutations ([[Command]])?
* how to integrate the two possible search depths (children and all)?
* how is all of this related to the LayerSeparationInterfaces, here SessionFacade und EditFacade?
&lt;&lt;&lt;
__preliminary notes__: {{red{3/2010}}} Discovery functions accessible from the session API are always written such as to return ~MObjectRefs. These expose generic functions for modifying the structure: {{{attach(MObjectRef)}}} and {{{purge()}}}. The session API exposes variations of these functions. Actually, all these functions do dispatch the respective commands automatically. To the contrary, the raw functions for adding and removing placements are located on the PlacementIndex; they are accessible as SessionServices &amp;mdash; which are intended for Proc-Layer's internal use solely. This separation isn't meant to be airtight, just an reminder for proper use.
Currently, I'm planning to modify MObjectRef to return only a const ref to the underlying facilities by default. Then, there would be a subclass which is //mutation enabled.// But this subclass will check for the presence of a mutation-permission token &amp;mdash; which is exposed via thread local storage, but //only within a command dispatch.// Again, no attempt is made to make this barrier airtight. Indeed, for tests, the mutation-permission token can just be created in the local scope. After all, this is not conceived as an authorisation scheme, rather as a automatic sanity check. It's the liability of the client code to ensure any mutation is dispatched.
&lt;&lt;&lt;
</pre>
</div>
<div title="SessionLifecycle" modifier="Ichthyostega" modified="201001070440" created="200911070329" tags="SessionLogic spec" changecount="23">
<pre>The current [[Session]] is the root of any state found within Proc-Layer. Thus, events defining the session's lifecycle influence and synchronise the cooperative behaviour of the entities within the model, the ProcDispatcher, [[Fixture]] and any facility below.
* when ''starting'', on first access an empty session is created, which puts any related facility into a defined initial state.
* when ''closing'' the session, any dependent facilities are disabled, disconnected, halted or closed
* ''loading'' an existing session &amp;mdash; after closing the previous session &amp;mdash; sets up an empty (default) session and populates it with de-serialised content.
* when encountering a ''mutation point'', [[command processing|ProcDispatcher]] is temporarily halted to trigger off an BuildProcess.
!Role of the session manager
The SessionManager is responsible for conducting the session lifecycle. Accessible through the static interface {{{Session::current}}}, it exposes the actual session as a ~PImpl. Both session manager and session are indeed interfaces, backed by implementation classes belonging to ~Proc-Layer's internals. Loading, saving, resetting and closing are the primary public operations of the session manager, each causing the respective lifecycle event.
!Synchronising access to session's implementation facilities
Some other parts and subsystems within the ~Proc-Layer need specialised access to implementation facilities within the session. Informations about some conditions and configurations might be retrieved through [[querrying the session|Query]], and especially default configurations for many objects are [[bound to the session|DefaultsImplementation]]. The [[discovery of session contents|SessionStructureQuery]] relies on an [[index facility|PlacementIndex]] embedded within the session implementation. Moreover, some &quot;properties&quot; of the [[media objects|MObject]] are actually due to the respective object being [[placed|Placement]] in some way into the session; consequently, there might be an dependency on the actual [[location as visible to the placement|PlacementScope]], which in turn is constituted by [[querying the index|QueryFocus]].
Each of these facilities relies on a separate access point to session services, corresponding to distinct service interfaces. But &amp;mdash; on the implementation side &amp;mdash; all these services are provided by a (compound) SessionServices implementation object. This approach allows to switch the actual implementation of all these services simply by swapping the ~PImpl maintained by the session manager. A new implementation level service can thus be added to the ~SessionImpl just by hooking it into the ~SessionServices compound object. But note, this mechanism as such is ''not thread safe'', unless the //implementation// of the invoked functions is synchronised in some way to prevent switching to a new session implementation while another thread is still executing session implementation code.
Currently, the understanding is for some global mechanism to hold any command execution, script running and further object access by the GUI //prior//&amp;nbsp; to invoking any of the session management operations (loading, closing, resetting). An alternative would be to change the top-level access to the session ~PImpl to go through an accessor value object, to acquire some lock automatically before any access can happen. C++ ensures the lifespan of any temporaries to surpass evaluation of the enclosing expression, which would be sufficient to prevent another thread to pull away the session during that timespan. Of course, any value returned from such an session API call isn't covered by this protection. Usually, objects are handed out as MObjectRef, which in turn means to resolve them (automatically) on dereferentiation by another session API access. But while it seems we could get locking to work automatically this way, still such a technique seems risky and involved; a plain flat lock at top level seems to be more appropriate.
!Interface and lifecycle hooks
{{red{draft as of 11/09}}}
As detailed above, {{{Session::current}}} exposes the management / lifecycle API, and at the same time can be dereferenced into the primary [[session API|SessionInterface]]. An default configured ~SessionImpl instance will be built automatically, in case no session implementation instance exists on executing this dereferentiation.
!!!building (or loading) a session
# as a preparation step, a new implementation instance is created, alongside with any supporting facilities (especially the PlacementIndex)
# the basic default configuration is loaded into this new session instance
# when the new session is (technically) complete and usable, the switch on the ~PImpl happens
# the {{{ON_SESSION_START}}} LifecycleEvent is emitted
# content is loaded into the session, including hard wired content and any de-serialised data from persistent storage
# the {{{ON_SESSION_INIT}}} event is emitted
# additional initialisation, wiring and registration takes place; basically anything to make the session fully functional
# the session LayerSeparationInterface is opened and any further top-level blocking is released
# the {{{ON_SESSION_READY}}} event is emitted
!!!closing the session
# top-level facilities accessing the session (GUI, command processing, scripts) are blocked and the LayerSeparationInterface is closed
# any render processes are ensured to be terminated (or //disconnected// &amp;mdash; so the can't do any harm)
# the {{{ON_SESSION_END}}} event is emitted
# the command processing log is tagged
# the command queue(s) are emptied, discarding any commands not yet executed
# the PlacementIndex is cleared, effectively releasing any object &quot;instances&quot;
# the [[asset registry|AssetManager]] is cleared, thereby releasing any remaining external resource references
# destruction of session implementation instances
{{red{none of the above is implemented as of 11/09}}}
</pre>
</div>
<div title="SessionLogic" modifier="Ichthyostega" modified="201003210012" created="200904242110" tags="overview" changecount="21">
<pre>The Session contains all informations, state and objects to be edited by the User (&amp;rarr;[[def|Session]]).
As such, the SessionInterface is the main entrance point to Proc-Layer functionality, both for the primary EditingOperations and for playback/rendering processes. Proc-Layer state is rooted within the session and guided by the [[session's lifecycle events|SessionLifecycle]].
Implementation facilities within the Proc-Layer may access a somewhat richer [[session service API|SessionServices]].
Currently (as of 3/10), Ichthyo is working on getting a preliminary implementation of the [[Session in Memory|SessionDataMem]] settled.
!Session, Model and Engine
The session is a SubSystem and acts as a frontend to most of the Proc-Layer. But it doesn't contain much operational logic; its primary contents are the [[model|Model]], which is closely [[interconnected to the assets|AssetModelConnection]].
!Design and handling of Objects within the Session
Objects are attached and manipulated by [[placements|Placement]]; thus the organisation of these placements is part of the session data layout. Effectively, such a placement within the session behaves like an //instances// of a given object, and at the same time it defines the &quot;non-substantial&quot; properties of the object, e.g. its positions and relations. [[References|MObjectRef]] to these placement entries are handed out as parameters, both down to the [[Builder]] and from there to the render processes within the engine, but also to external parts within the GUI and in plugins. The actual implementation of these object references is built on top of the PlacementRef tags, thus relying on the PlacementIndex the session maintains to keep track of all placements and their relations. While &amp;mdash; using these references &amp;mdash; an external client can access the objects and structures within the session, any actual ''mutations'' should be done based on the CommandHandling: a single operation of a sequence of operations is defined as [[Command]], to be [[dispatched|ProcDispatcher]] as [[mutation operation|SessionMutation]]. Following this policy ensures integration with the&amp;nbsp;SessionStorage and provides (unlimited) [[UNDO|UndoManager]].
On the implementation level, there are some interdependencies to consider between the [[data layout|SessionDataMem]], keeping ModelDependencies updated and integrating with the BuildProcess. While the internals of the session are deliberately kept single-threaded, we can't make much assumptions regarding the ongoing render processes.
</pre>
</div>
<div title="SessionManager" modifier="Ichthyostega" modified="200911090408" created="200911071838" tags="SessionLogic def" changecount="2">
<pre>The session manager is responsible for maintaining session state as a whole and for conducting the session [[lifecycle|SessionLifecycle]]. The session manager API allows for saving, loading, closing and resetting the session. Accessible through the static interface {{{Session::current}}}, it exposes the actual session as a ~PImpl. Actually, both session and session manager are interfaces.
</pre>
</div>
<div title="SessionMutation" modifier="Ichthyostega" modified="201003020143" created="201002170332" tags="SessionLogic spec decision draft" changecount="3">
<pre>//Any modification of the session will pass through the [[command system|CommandHandling]].//
Thus any possible mutation comes in two flavours: a raw operation invoked directly on an object instance attached to the model, and a command taking an MObjectRef as parameter. The latter approach &amp;mdash; invoking any mutation through a command &amp;mdash; will pass the mutations trough the ProcDispatcher to ensure the're logged for [[UNDO|UndoManager]] and executed sequentially, which is important, because the session's internals are //not threadsafe by design.// Thus we're kind of enforcing the use of Commands: mutating operations include a check for a &amp;raquo;permission to mutate&amp;laquo;, which is automatically available within a command execution {{red{TODO as of 2/10}}}. Moreover, the session API and the corresponding LayerSeparationInterfaces expose MObjectRef instances, not raw (language) refs.
!!Questions to solve
* how to get from the raw mutation to the command?
* how to organise the naming and parametrisation of commands?
* should we provide the additional flexibility of a binding here?
* how to keep [[dependencies within the model|ModelDependencies]] up-to date?
!!who defines commands?
The answer is simple: the one who needs to know about their existence. Because basically commands are invoked //by-name// &amp;mdash; someone needs to be aware of that name and what it denotes. Thus, for any given mutation, there is a place where it's meaningful to specify the details //and// to subsume them under a meaningful term. An entity responsible for this place could then act as the provider of the command in question.
Interestingly, there seems to be an alternative answer to this question. We could locate the setup and definition of all commands into a central administrative facility. Everyone in need of a command then ought to know the name and retrieve this command. Sounds like bureaucracy.
</pre>
</div>
<div title="SessionOverview" modifier="Ichthyostega" modified="200911071816" created="200709272105" tags="design img" changecount="38">
<pre>&lt;&lt;&lt;
{{red{WARNING: Naming was discussed (11/08) and decided to be changed....}}}
* the term [[EDL]] was phased out in favour of ''Sequence''
* [[Session]] is largely synonymous to ''Project''
* there seems to be a new entity called [[Timeline]] which holds the global Pipes
&lt;&lt;&lt;
The [[Session]] (sometimes also called //Project// ) contains all informations and objects to be edited by the User. Any state within the Proc-Layer is directly or indirectly rooted in the session. It can be saved and loaded. The individual Objects within the Session, i.e. Clips, Media, Effects, are contained in one or multiple collections within the Session, which we call [[sequence(s)|Sequence]]. Moreover, the sesion contains references to all the Media files used, and it contains various default or user defined configuration, all being represented as [[Asset]]. At any given time, there is //only one current session// opened within the application. The [[lifecycle events|SessionLifecycle]] of the session define the lifecycle of ~Proc-Layer as a whole.
The Session is close to what is visible in the GUI. From a user's perspective, you'll find a [[Timeline]]-like structure, containing an [[Sequence]], where various Media Objects are arranged and placed. The available building blocks and the rules how they can be combined together form Lumiera's [[high-level data model|HighLevelModel]]. Basically, besides the [[media objects|MObjects]] there are data connections and all processing is organized around processing chains or [[pipes|Pipe]], which can be either global (in the Session) or local (in real or virtual clips).
!!!larger projects
For larger editing projects the simple structure of a session containing &quot;the&quot; timeline is not sufficient. Rather
* we may have several [[sequences|Sequence]], e.g. one for each scene. These sequences can be even layered or nested (compositional work).
* within one project, there may be multiple, //independant Timelines// &amp;mdash; each of which may have an associated Viewer or Monitor
Usually, when working with this stucture, you'll drill down starting from a timeline, trough a (top-level) sequence, down into a track, a clip, maybe even a embedded Sequence (VirtualClip), and from there even more down into a single attached effect. This constitutes a set of [[nested scopes|PlacementScope]]. Operations are to be [[dispatched|ProcDispatcher]] through a [[command system|CommandHandling]], including the target object [[by reference|MObjectRef]]. [[Timelines|Timeline]] on the other hand are always top-level objects and can't be combined further. You can render a single given timeline to output.
&amp;rarr; see [[Relation of Project, Timelines and Sequences|TimelineSequences]]
!!!the definitive state
With all the structural complexities possible within such a session, we need an isolation layer to provide __one__ definitive state where all configuration has been made explicit. Thus the session manages a special consolidated view (object list), called [[the Fixture|Fixture]], which can be seen as all currently active objects placed onto a single timeline.
!!!organisational devices
The possibility of having multiple Sequences helps organizing larger projects. Each [[Sequence]] is just a logical grouping; because all effective properties of any MObject within this sequence are defined by the ~MObject itself and the [[Placement]], by which the object is anchored to some time point, some track, can be connected to some pipe, or linked to another object. In a similar manner, [[Tracks|Track]] are just another organisational aid for grouping objects, disabling them and defining common output pipes.
!!!global pipes
[&gt;img[draw/Proc.builder1.png]] Any session should contain a number of global [[(destination) pipes|Pipe]], typically video out and audio out. The goal is, to get any content producing or transforming object in some way connected to one of these outputs, either //by [[placing|Placement]] it directly// to some pipe, or by //placing it to a track// and having the track refer to some pipe. Besides the global destination pipes, we can use internal pipes to form busses or subgroups, either on a global (session) level, or by using the processing pipe within a [[virtual clip|VirtualClip]], which can be placed freely within the sequence(s). Normally, pipes just gather and mix data, but of course any pipe can have an attached effect chain.
&amp;rarr; [[more on Tracks and Pipes within the Sequence|TrackPipeSequence]]
!!!default configuration
While all these possibilities may seem daunting, there is a simple default configuration loaded into any pristine new session:
It will contain a global video and audio out pipe, just one timeline holding a single sequence with a single track; this track will be configured with a fading device, to send any video and audio data encountered on enclosed objects to the global (master) pipes. So, by adding a clip with a simple absolute placement to this track and to some time position, the clip gets connected and rendered, after [[(re)building|PlanningBuildFixture]] the [[Fixture]] and passing the result to the [[Builder]] &amp;mdash; and using the resulting render nodes network (Render Engine).
&amp;rarr; [[anatomy of the high-level model|HighLevelModel]]
&amp;rarr; considerations regarding [[Tracks and Pipes within the session|TrackPipeSequence]]
&amp;rarr; see [[Relation of Project, Timelines and Sequences|TimelineSequences]]
</pre>
</div>
<div title="SessionServices" modifier="Ichthyostega" modified="200911090107" created="200911071825" tags="SessionLogic impl" changecount="7">
<pre>Within Lumiera's Proc-Layer, there are some implementation facilities and subsystems needing more specialised access to implementation services provided by the session. Thus, besides the public SessionInterface and the [[lifecycle and state management API|SessionManager]], there are some additional service interfaces exposed by the session through a special access mechanism. This mechanism needs to be special in order to assure clean transactional behaviour when the session is opened, closed, cleared or loaded. Of course, there is the additional requirement to avoid direct dependencies of the mentioned Proc internals on session implementation details.
!Accessing session services
For each of these services, there is an access interface, usually through an class with only static methods. Basically this means access //by name.//
On the //implementation side//&amp;nbsp; of this access interface class (i.e. within a {{{*.cpp}}} file separate from the client code), there is a (down-casting) access through the top-level session-~PImpl pointer, allowing to invoke functions on the ~SessionServices instance. Actually, this ~SessionServices instance is configured (statically) to stack up implementations for all the exposed service interfaces on top of the basic ~SessionImpl class. Thus, each of the individual service implementations is able to use the basic ~SessinImpl (becaus it inherits it) and the implementaion of the access functions (to the session service we're discussing here) is able to use this forwarding mechanism to get the actual implementation basically by one-liners. The upside of this (admittedly convoluted) technique is that we've gotten at runtime only a single indirection, which moreover is through the top-level session-~PImpl. The downside is that, due to the separation in {{{*.h}}} and {{{*.c}}} files, we can't use any specifically typed generic operations, which forces us to use type erasure in case we need such (an example being the content discovery queries utilised by all high-level model objects).</pre>
</div>
<div title="SessionStructureQuery" modifier="Ichthyostega" modified="201003160203" created="200910112322" tags="SessionLogic design draft" changecount="17">
<pre>The frontside interface of the session allows to query for contained objects; it is used to discover the structure and contents of the currently opened session/project. Access point is the public API of the Session class, which, besides exposing those queries, also provides functionality for adding and removing session contents.
!discovering structure
The session can be seen as an agglomeration of nested and typed containers.
Thus, at any point, we can explore the structure by asking for //contained objects of a specific type.// For example, at top level, it may be of interest to enumerate the [[timelines within this session|Timeline]] and to ennumerate the [[sequences|Sequence]]. And in turn, on a given Sequence, it would be of interest to explore the tracks, and also maybe to iterate over all clips within this sequence.
So, clearly, there are two flavours of such an contents exploration query: it could either be issued as an dedicated member function on the public API of the respective container object, e.g. {{{Track::getClips()}}} &amp;mdash; or it could be exposed as generic query function, relying on the implicit knowledge of the //current location//&amp;nbsp; rather.
!problem of context and access path
The (planned) session structure of Lumiera allows for quite some flexibility, which, of course comes at a price tag. Especially, as there can be multiple independent top level timelines, where a given sequence can be used simultaneously within multiple timelines and even as virtual media within a [[meta-clip|VirtualClip]], and moreover, as properties of any Placement are rather queried and discovered within the PlacementScope of this object &amp;mdash; consequently the discovered values may depend on //how you look at this object.// More specifically, it depends on the ''access path'' used to discover this object, because this path constitutes the actual scope visible to this object.
To give an example, let's assume a clip within a sequence, and this sequence is both linked to the top-level timeline, but also used within a meta-clip. (see the drawing &amp;rarr; [[here|PlacementScope]])
In this case, the sequence has an 1:n [[binding|BindingMO]]. A binding is (by definition) also a PlacementScope, and, incidentally, in the case of binding the sequence into a timeline, the binding also translates //logical// output designations into global pipes found within the timeline, while otherwise they get mapped onto &quot;channels&quot; of the virtual media used by the virtual clip. Thus, the absolute time position as well as the output connection of a given clip within this sequence //depends on how we look at this clip.// Does this clip apear as part of the global timeline, or did we discover it as contained within the meta-clip? &amp;rarr; see [[discussion of this scope-binding problem|BindingScopeProblem]]
!!solution requirements
The baseline of any solution to this problem is clear: at the point where the query is issued, a context information is necessary; this context yields an access path from top level down to the object to be queried, and this access path constitutes the effective scope this object can utilise for resolving the query.
!!introducing a QueryFocus
A secondary goal of the design here is to ease the use of the session query interface. Thus the proposal is to treat this context and access path as part of the current state. To do so, we can introduce a QueryFocus following the queries and remembering the access path; this focus should be maintained mostly automatically. It allows for stack-like organisation, to allow sub-queries without affecting the current focus, where the handling of such a temporary sub-focus is handled automatically by a scoped local (RAII) object. The focus follows the issued queries and re-binds as necessary.
!!using QueryFocus as generic query interface
Solving the problem this way has the nice side effect, that we get a quite natural location where to put an unspecific query interface: Just let the current QueryFocus expose the basic set of query API functions. Such an generic query interface can be seen as a complement to the query functions exposed on specific objects (&quot;get me the clips within this track&quot;) according to the structure. Because a generic interface especially allows for writing simple diagnostics and discovery code, with only a weak link to the actual session structure.
!Implementation strategy
The solution is being built starting from the generic part, as the actual session structure isn't hard coded into the session implementation, but rather created by convention.
The query API on specific objects (i.e. Session, Timeline, Sequence, Track and Clip) is then easily coded up on top, mostly with inlined one-liners of the kind
{{{
ITER getClips() { return QueryFocus::push(this).query&lt;Clip&gt;(); }
}}}
To make this work, QueryFocus exposes an static API (and especially the focus stack is a singleton). The //current// QueryFocus object can easily be re-bound to another point of interest (and ajusts the contained path info automatically), and the {{{push()}}}-function creates a local scoped object (which pops automatically).
And last but not least: the difficult part of this whole concept is encapsulated and ''can be left out for now''. Because, according to Lumiera's [[roadmap|http://issues.lumiera.org/roadmap]], meta-clips are to be postponed until well into beta! Thus, we can start with a trivial (no-op) implementation, but immediately gain the benefit of making the relevant parts of the session implementation aware of the problem.
{{red{WIP ... draft}}}</pre>
</div>
<div title="SideBarOptions" modifier="CehTeh" created="200706200048" changecount="1">
<pre>&lt;&lt;search&gt;&gt;&lt;&lt;closeAll&gt;&gt;&lt;&lt;permaview&gt;&gt;&lt;&lt;newTiddler&gt;&gt;&lt;&lt;saveChanges&gt;&gt;&lt;&lt;slider chkSliderOptionsPanel OptionsPanel &quot;options »&quot; &quot;Change TiddlyWiki advanced options&quot;&gt;&gt;</pre>
</div>
<div title="SiteSubtitle" modifier="Ichthyostega" modified="200802030406" created="200706190044" changecount="2">
<pre>Building a Render Nodes Network from Objects in the Session</pre>
</div>
<div title="SiteTitle" modifier="Ichthyostega" modified="200708080212" created="200706190042" changecount="2">
<pre>Engine</pre>
</div>
<div title="SplashScreen" modifier="just me" created="200706220430" changecount="1">
<pre>{{red{killme}}}</pre>
</div>
<div title="SplashScreenPlugin" modifier="Saq" modified="200607202048" created="200607191631" tags="lewcidExtension systemConfig" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200607202048">
<pre>/***
''Inspired by [[TiddlyPom|http://www.warwick.ac.uk/~tuspam/tiddlypom.html]]''
|Name|SplashScreenPlugin|
|Created by|SaqImtiaz|
|Location|http://tw.lewcid.org/#SplashScreenPlugin|
|Version|0.21 |
|Requires|~TW2.08+|
!Description:
Provides a simple splash screen that is visible while the TW is loading.
!Installation
Copy 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.
!Customizing
Once 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.
!History
* 20-07-06 : version 0.21, modified to hide contentWrapper while SplashScreen is displayed.
* 26-06-06 : version 0.2, first release
!Code
***/
//{{{
var old_lewcid_splash_restart=restart;
restart = function()
{ if (document.getElementById(&quot;SplashScreen&quot;))
document.getElementById(&quot;SplashScreen&quot;).style.display = &quot;none&quot;;
if (document.getElementById(&quot;contentWrapper&quot;))
document.getElementById(&quot;contentWrapper&quot;).style.display = &quot;block&quot;;
old_lewcid_splash_restart();
if (splashScreenInstall)
{if(config.options.chkAutoSave)
{saveChanges();}
displayMessage(&quot;TW SplashScreen has been installed, please save and refresh your TW.&quot;);
}
}
var oldText = store.getTiddlerText(&quot;MarkupPreHead&quot;);
if (oldText.indexOf(&quot;SplashScreen&quot;)==-1)
{var siteTitle = store.getTiddlerText(&quot;SiteTitle&quot;);
var splasher='\n\n&lt;style type=&quot;text/css&quot;&gt;#contentWrapper {display:none;}&lt;/style&gt;&lt;div id=&quot;SplashScreen&quot; style=&quot;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;&quot;&gt;&lt;b&gt;'+siteTitle +'&lt;/b&gt; is loading&lt;blink&gt; ...&lt;/blink&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;font-size: 14px; color:red;&quot;&gt;Requires Javascript.&lt;/span&gt;&lt;/div&gt;';
if (! store.tiddlerExists(&quot;MarkupPreHead&quot;))
{var myTiddler = store.createTiddler(&quot;MarkupPreHead&quot;);}
else
{var myTiddler = store.getTiddler(&quot;MarkupPreHead&quot;);}
myTiddler.set(myTiddler.title,oldText+splasher,config.options.txtUserName,null,null);
store.setDirty(true);
var splashScreenInstall = true;
}
//}}}</pre>
</div>
<div title="StateAdapter" modifier="Ichthyostega" modified="200807132344" created="200806261912" tags="Rendering impl def" changecount="10">
<pre>A small (in terms of storage) and specifically configured StateProxy object which is created on the stack for each individual {{{pull()}}} call. It is part of the invocation state of such a call and participates in the buffer management. Thus, in a calldown sequence of {{{pull()}}} calls we get a corresponding sequence of &quot;parent&quot; states. At each level, the &amp;rarr; WiringDescriptor of the respective node defines a Strategy how the call is passed on.</pre>
</div>
<div title="StateProxy" modifier="Ichthyostega" modified="200806010404" created="200706220352" tags="def" changecount="4">
<pre>An Object representing a //Render Process// and containing associated state information.
* it is created in the Controller subsystem while initiating the BuildProcess
* it is passed on to the generated Render Engine, which in turn passes it down to the individual Processors
* moreover, it contains methods to communicate with other state relevant parts of the system, thereby shielding the rendering code from any complexities of Thread communication if necessary. (thus the name Proxy)
* in a future version, it may also encapsulate the communication in a distributed render farm
</pre>
</div>
<div title="StreamConversion" modifier="Ichthyostega" modified="200810060308" created="200810020337" tags="design spec" changecount="4">
<pre>Conversion of a media stream into a stream of another type is done by a processor module (plugin). The problem of finding such a module is closely related to the StreamType and especially [[problems of querying|StreamTypeQuery]] for such. (The builder uses a special Facade, the ConManager, to access this functionality). There can be different kinds of conversions, and the existance or non-existance of such an conversion can influence the stream type classification.
* different //kinds of media// can be ''transformed'' into each other
* stream types //subsumed// by a given prototype should be ''lossless convertible'' and thus can be considered //equivalent.//
* besides, between different stream //implementation types,// there can be a ''rendering'' (lossy conversion) &amp;mdash; or no conversion at all.
</pre>
</div>
<div title="StreamPrototype" modifier="Ichthyostega" modified="200809120021" created="200808152042" tags="def spec" changecount="10">
<pre>The stream Prototype is part of the specification of a media stream's type. It is a semantic (or problem domain oriented) concept and should be distinguished from the actual implementation type of the media stream. The latter is provided by an [[library implementation|StreamTypeImplFacade]]. While there are some common predefined prototypes, mostly, they are defined within the concrete [[Session]] according to the user's needs.
Prototypes form an open (extensible) collection, though each prototype belongs to a specific media kind ({{{VIDEO, IMAGE, AUDIO, MIDI,...}}}).
The ''distinguishing property'' of a stream prototype is that any [[Pipe]] can process //streams of a specific prototype only.// Thus, two streams with different prototype can be considered &quot;something quite different&quot; from the users point of view, while two streams belonging to the same prototype can be considered equivalent (and will be converted automatically when their implementation types differ). Note this definition is //deliberately fuzzy,// because it depends on the actual situation of the project in question.
Consequently, as we can't get away with an fixed Enum of all stream prototypes, the implementation must rely on a query interface. The intention is to provide a basic set of rules for deciding queries about the most common stream prototypes; besides, a specific session may inject additional rules or utilize a completely different knowledge base. Thus, for a given StreamTypeDescriptor specifying a prototype
* we can get a [[default|DefaultsManagement]] implementation type
* we can get a default prototype to a given implementation type by a similar query
* we can query if a implementation type in question can be //subsumed// by this prototype
* we can determine if another prototype is //convertible//
!!Examples
NTSC and PAL video, video versus digitized film, HD video versus SD video, 3D versus flat video, cinemascope versus 4:3, stereophonic versus monaural, periphonic versus panoramic sound, Ambisonics versus 5.1, dolby versus linear PCM...
</pre>
</div>
<div title="StreamType" modifier="Ichthyostega" modified="201003160202" created="200808060244" tags="spec draft" changecount="16">
<pre>//how to classify and describe media streams//
Media data is supposed to appear structured as stream(s) over time. While there may be an inherent internal structuring, at a given perspective ''any stream is a unit and homogeneous''. In the context of digital media data processing, streams are always ''quantized'', which means they appear as a temporal sequence of data chunks called ''frames''.
! Terminology
* __Media__ is comprised of a set of streams or channels
* __Stream__ denotes a homogeneous flow of media data of a single kind
* __Channel__ denotes a elementary stream, which can't be further separated in the given context
* all of these are delivered and processed in a smallest unit called __Frame__. Each frame corresponds to a //time interval.//
* a __Buffer__ is a data structure capable of holding a Frame of media data.
* the __~Stream-Type__ describes the kind of media data contained in the stream
! Problem of Stream Type Description
Media types vary largely and exhibit a large number of different properties, which can't be subsumed under a single classification scheme. On the other hand we want to deal with media objects in a uniform and generic manner, because generally all kinds of media behave somewhat similar. But the twist is, these similarities disappear when describing media with logical precision. Thus we are forced into specialized handling and operations for each kind of media, while we want to implement a generic handling concept.
! Stream Type handling in the Proc-Layer
!! Identification
A stream type is denoted by a StreamTypeID, which is an identifier, acting as an unique key for accessing information related to the stream type. It corresponds to an StreamTypeDescriptor record, containing an &amp;mdash; //not necessarily complete// &amp;mdash; specification of the stream type, according to the classification detailed below.
!! Classification
Within the Proc-Layer, media streams are treated largely in a similar manner. But, looking closer, not everything can be connected together, while on the other hand there may be some classes of media streams which can be considered //equivalent// in most respects. Thus separating the distinction between various media streams into several levels seems reasonable...
* Each media belongs to a fundamental ''kind'' of media, examples being __Video__, __Image__, __Audio__, __MIDI__,... Media streams of different kind can be considered somewhat &quot;completely separate&quot; &amp;mdash; just the handling of each of those media kinds follows a common //generic pattern// augmented with specialisations. Basically, it is //impossible to connect// media streams of different kind. Under some circumstances there may be the possibility of a //transformation// though. For example, a still image can be incorporated into video, sound may be visualized, MIDI may control a sound synthesizer.
* Below the level of distinct kinds of media streams, within every kind we have an open ended collection of ''prototypes'', which, when compared directly, may each be quite distinct and different, but which may be //rendered//&amp;nbsp; into each other. For example, we have stereoscopic (3D) video and we have the common flat video lacking depth information, we have several spatial audio systems (Ambisonics, Wave Field Synthesis), we have panorama simulating sound systems (5.1, 7.1,...), we have common stereophonic and monaural audio. It is considered important to retain some openness and configurability within this level of distinction, which means this classification should better be done by rules then by setting up a fixed property table. For example, it may be desirable for some production to distinguish between digitized film and video NTSC and PAL, while in another production everything is just &quot;video&quot; and can be converted automatically. The most noticeable consequence of such a distinction is that any Bus or [[Pipe]] is always limited to a media stream of a single prototype. (&amp;rarr; [[more|StreamPrototype]])
* Besides the distinction by prototypes, there are the various media ''implementation types''. This classification is not necessarily hierarchically related to the prototype classification, while in practice commonly there will be some sort of dependency. For example, both stereophonic and monaural audio may be implemented as 96kHz 24bit PCM with just a different number of channel streams, but we may as well get a dedicated stereo audio stream with two channels multiplexed into a single stream. For dealing with media streams of various implementation type, we need //library// routines, which also yield a //type classification system.// Most notably, for raw sound and video data we use the [[GAVL]] library, which defines a classification system for buffers and streams.
* Besides the type classification detailed thus far, we introduce an ''intention tag''. This is a synthetic classification owned by Lumiera and used for internal wiring decisions. Currently (8/08), we recognize the following intention tags: __Source__, __Raw__, __Intermediary__ and __Target__. Only media streams tagged as __Raw__ can be processed.
!! Media handling requirements involving stream type classification
* set up a buffer and be able to create/retrieve frames of media data.
* determine if a given media data source and sink can be connected, and how.
* determine and enumerate the internal structure of a stream.
* discover processing facilities
&amp;rarr; see StreamTypeUse
&amp;rarr; [[querying types|StreamTypeQuery]]
</pre>
</div>
<div title="StreamTypeDescriptor" modifier="Ichthyostega" modified="200809130314" created="200808151505" tags="def" changecount="7">
<pre>A description and classification record usable to find out about the properties of a media stream. The stream type descriptor can be accessed using an unique StreamTypeID. The information contained in this descriptor record can intentionally be //incomplete,// in which case the descriptor captures a class of matching media stream types. The following information is maintained:
* fundamental ''kind'' of media: {{{VIDEO, IMAGE, AUDIO, MIDI,...}}}
* stream ''prototype'': this is the abstract high level media type, like NTSC, PAL, Film, 3D, Ambisonics, 5.1, monaural,...
* stream ''implementation type'' accessible by virtue of an StreamTypeImplFacade
* the ''intended usage category'' of this stream: {{{SOURCE, RAW, INTERMEDIARY, TARGET}}}.
&amp;rarr; see &amp;raquo;[[Stream Type|StreamType]]&amp;laquo; detailed specification
&amp;rarr; notes about [[using stream types|StreamTypeUse]]
&amp;rarr; more [[about prototypes|StreamPrototype]]</pre>
</div>
<div title="StreamTypeID" modifier="Ichthyostega" created="200808151510" tags="def" changecount="1">
<pre>This ID is an symbolic key linked to a StreamTypeDescriptor. The predicate {{{stream(ID)}}} specifies a media stream with the StreamType as detailed by the corresponding descriptor (which may contain complete or partial data defining the type).</pre>
</div>
<div title="StreamTypeImplConstraint" modifier="Ichthyostega" modified="200810020230" created="200809220248" tags="def" changecount="2">
<pre>A special kind of media stream [[implementation type|StreamTypeImplFacade]], which is not fully specified. As such, it is supposed there //actually is// an concrete implementation type, while only caring for some part or detail of this implementation to exhibit a specific property. For example, using an type constraint we can express the requirement of the actual implementation of a video stream to be based on ~RGB-float, or to enforce a fixed frame size in pixels.
An implementation constraint can //stand-in// for a completely specified implementation type (meaning it's a sub interface of the latter). But actually using it in this way may cause a call to the [[defaults manager|DefaultsImplementation]] to fill in any missing information. An example would be to call {{{createFrame()}}} on the type constraint object, which means being able to allocate memory to hold a data frame, with properties in compliance with the given type constraint. Of cousre, then we need to know all the properties of this stream type, which is where the defaults manager is queried. This allows session customisation to kick in, but may fail under certain cicumstances.
</pre>
</div>
<div title="StreamTypeImplFacade" modifier="Ichthyostega" modified="200809251940" created="200808151520" tags="def" changecount="7">
<pre>Common interface for dealing with the implementation of media stream data. From a high level perspective, the various kinds of media ({{{VIDEO, IMAGE, AUDIO, MIDI,...}}}) exhibit similar behaviour, while on the implementation level not even the common classification can be settled down to a complete general and useful scheme. Thus, we need separate library implementations for deailing with the various sorts of media data, all providing at least a set of basic operations:
* set up a buffer
* create or accept a frame
* get an tag describing the precise implementation type
* ...?
&amp;rarr; see also &amp;raquo;[[Stream Type|StreamType]]&amp;laquo;
//Note:// there is a sort-of &quot;degraded&quot; variant just requiring some &amp;rarr; [[implementation constraint|StreamTypeImplConstraint]] to hold
</pre>
</div>
<div title="StreamTypeQuery" modifier="Ichthyostega" modified="201002010142" created="200809280129" tags="spec draft" changecount="24">
<pre>Querying for media stream type information comes in various flavours
* you may want to find a structural object (pipe, output, processing patten) associated with / able to deal with a certain stream type
* you may need a StreamTypeDescriptor for an existing stream given as implementation data
* you may want to build or complete type information from partial specification.
Mostly, those queries involve the ConfigRules system in some way or the other. The [[prototype-|StreamPrototype]] and [[implementation type|StreamTypeImplFacade]]-interfaces themselves are mostly a facade for issuing appropriate queries. Some objects (especially [[pipes|Pipe]]) are tied to a certain stream type and thus store a direct link to type information. Others are just associated with a type by virtue of the DefaultsManagement.
The //problem// with this pivotal role of the config rules is that &amp;mdash; from a design perspective &amp;mdash; not much can be said specifically, besides //&quot;you may be able to find out...&quot;, &quot;...depends on the defaults and the session configuration&quot;.// This way, a good deal of crucial behaviour is pushed out of the core implementation (and it's quite intentionally being done this way). What can be done regarding the design of the core is mostly to setup a framework for the rules and determine possible ''query situations''.
!the kind of media
the information of the fundamental media kind (video, audio, text, MIDI,...) is assiciated with the prototype, for technical reasons. Prototype information is mandatory for each StreamType, and the impl facade provides a query function (because some implementation libraries, e.g. [[GAVL]], support multiple kinds of media).
!query for a prototype
__Situation__: given an implementation type, find a prototype to subsume it.
Required only for building a complete ~StreamType which isn't known at this point.
The general case of this query is //quite hairy,// because the solution is not necessary clear and unique. And, worse still, it is related to the semantics, requiring semantic information and tagging to be maintained somewhere. For example, while the computer can't &quot;know&quot; what stereopohinc audio is (only a human can, by listening to a stereophoic playback and deciding if it actually does convey a spatical sound image), in most cases we can overcome this problem by using the //heuristical rule// of assuming the prototype &quot;stereophonic&quot; when given two identically typed audio channels. This example also shows the necessity of ordering heuristic rules to be able to pick a best fit.
We can inject two different kinds of fallback solutions for this kind of query:
* we can always build a &quot;catch-all&quot; prototype just based on the kind of media (e.g. {{{prototype(video).}}}). This should match with lowest priority
* we can search for existing ~StreamTypes with the same impl type, or an impl type which is //equivalent convertible// (see &amp;rarr; StreamConversion).
The latter case can yield multiple solutions, which isn't any problem, because the match is limited to classes of equivalent stream implementation, which would be subsumed under the same prototype anyway. Even if the registry holds different prototypes linked to the same implementation type, they would be convertible and thus could //stand-in// for one another. Together this results in the implementation
# try to get a direct match to an existing impl type which has an associated (complete) ~StreamType, thus bypassing the ConfigRules system altogether
# run a {{{Query&lt;Prototype&gt;}}} for the given implementation type
# do the search within equivalence class as described above
# fall back to the media kind.
{{red{TODO: how to deal with the problem of hijacking a prototype?}}} &amp;rarr; see [[here|StreamTypeUse]]
!query for an implementation
__Situation 1__: given an partially specified ~StreamType (just an [[constraint|StreamTypeImplConstraint]])
__Situation 2__: find an implementation for a given prototype (without any further impl type guidlines)
Both cases have to go though the [[defaults manager|DefaultsManagement]] in some way, in order to give any default configuration a chance to kick in. This is //one of the most important use cases// of the defaults system: the ability to configure a default fromat for all streams with certain semantic classification. {{{prototype(video)}}} by default is RGBA 24bit non-interlaced for example.
But after having queried the defaults system, there remains the problem to build a new solution (which will then automatically become default for this case). To be more precise: invoking the defaults system (as implemented in Lumiera) means first searching through existing objects encountered as default, and then issuing an general query with the capabilities in question. This general query in turn is conducted by the query type handler and usually consists of first searching existing objects and then creating a new object to match the capabilities. But, as said, the details depend on the type (and are defined by the query handler installed for this type). Translated to our problem here in question, this means //we have to define the basic operations from which a type query handler can be built.// Thus, to start with, it's completely sufficient to wire a call to the DefaultsManagement and assume the current session configuration contains some rules to cover it. Plus being prepared for the query to fail (throw, that is).
Later on this could be augmented by providing some search mechanisms:
* search through existing stream type implementations (or a suitable pre filtered selection) and narrow down the possible result(s) by using the constraint as a filter. Obviously this requires support by the MediaImplLib facade for the implementation in question. (This covers __Situation 1__)
* relate a protoype in question to the other existing prototypes and use the convertibility / subsumption as a filter mechanism. Finally pick an existing impl type which is linked to one of the prototypes found thus far.
Essentially, we need a search mechanism for impltypes and prototypes. This search mechanism is best defined by rules itself, but needs some primitive operations on types, like ennumerating all registered types, filter those selections and match against a constraint.
!query for an (complete) StreamType
All situations discussed thus far can also occur wrapped into and triggered by a query for a complete type. Depending on what part is known, the missing bits will be queried.
Independent from these is __another Situation__ where we query for a type ''by ID''.
* a simple symbolic ID can be found by searching through all existing stream types (Operation supported by the type registry within STypeManager)
* a special ''classificating'' ID can be parsed into the components (media kind, prototype, impltype), resulting in sub searches for these.
{{red{not sure if we want to support queries by symboic ID}}}...problem is the impl type, because probably the library needs to support describing any implementation type by a string. Seemingly GAVL does, but requiring it for every lib?
</pre>
</div>
<div title="StreamTypeUse" modifier="Ichthyostega" modified="201002010151" created="200809130312" tags="draft operational discuss" changecount="22">
<pre>Questions regarding the use of StreamType within the Proc-Layer.
* what is the relation between Buffer and Frame?
* how to get the required size of a Buffer?
* who does buffer allocations and how?
Mostly, stream types are used for querying, either to decide if they can be connected, or to find usable processing modules.
Even building a stream type from partial information involves some sort of query.
&amp;rarr; more on [[media stream type queries|StreamTypeQuery]]
!creating stream types
seemingly stream types are created based on an already existing media stream (or a Frame of media data?). {{red{really?}}}
The other use case seems to be that of an //incomplete// stream type based on a [[Prototype|StreamPrototype]]
!Prototype
According to my current understanding, a prototype is merely a classification entity. But then &amp;mdash; how to bootstrap a Prototype?
And how to do the classification of an existing implementation type.
Besides, there is the problem of //hijacking a prototype:// when a specific implementation type gets tied to a rather generic protoype, like {{{protoype(video)}}}, how to comply to the rule of prototypes subsuming a class of equivalent implementations?
!Defaults and partial specification
A StreamType need not be defined completely. It is sufficient to specify the media kind and the Prototype. The implementation type may be just given as a constraint, thus defining some properties and leaving out others. When creating a frame buffer based upon such an //incomplete type,// [[defaults|DefaultsManagement]] are queried to fill in the missing parts.
Constraints are objects provided by the Lumiera core, but specialized to the internals of the actual implementation library.
For example there might be a constraint implementation to force a specific {{{gavl_pixelformat_t}}}.
!the ID problem
Basically I'd prefer the ~IDs to be real identifiers. So they can be used directly within rules. At least the Prototypes //can// have such a textual identifier. But the implementation type is problematic, and consequently the ID of the StreamType as well. Because the actual implementation should not be nailed down to a fixed set of possibilities. And, generally, we can't expect an implementation library to yield textual identifiers for each implementation type. //Is this really a problem? {{red{what are the use cases?}}}//
As far as I can see, in most cases this is no problem, as the type can be retrieved or derived from an existing media object. Thus, the only problematic case is when we need to persist the type information without being able to guarantee the persistence of the media object this type was derived from. For example this might be a problem when working with proxy media. But at least we should be able to create a constraint (partial type specification) to cover the important part of the type information, i.e. the part which is needed to re-create the model even when the original media isn't there any longer.
Thus, //constraints may be viewed as type constructing functors.//
--------------
!use cases
* pulling data from a media file
* connecting pipes and similar wiring problems
* describing the properties of an processor plugin
!! pulling data from a media file
To open the file, we need //type discovery code,// resulting in a handle to some library module for accessing the contents, which is in compliance with the Lumiera application. Thus, we can determine the possible return values of this type discovery code and provide code which wires up a corresponding StreamTypeImplFacade. Further, the {{{control::STypeManager}}} has the ability to build a complete or partial StreamType from
* an ~ImplFacade
* a Prototype
* maybe even from some generic textual ~IDs?
Together this allows to associate a StreamType to each media source, and thus to derive the Prototype governing the immediately connected [[Pipe]]
A pipe can by design handle data of one Prototype solely.
!! wiring problems
When deciding if a connection can be made, we can build up the type information starting out from the source. (this requires some work, but it's //possible,// generally speaking.). Thus, we can allways get an ~ImplType for the &quot;lower end&quot; of the connection, and at least a Prototype for the &quot;output side&quot; &amp;mdash; which should be enough to use the query functions provided by the stream type interfaces
!! describing properties
{{red{currently difficult to define}}} as of 9/2008, because the property description of plugins is not planned yet.
My Idea was to use [[type implementation constraints|StreamTypeImplConstraint]] for this, which are a special kind of ~ImplType
</pre>
</div>
<div title="StrongSeparation" modifier="Ichthyostega" modified="200907220311" created="200706220452" tags="design" changecount="6">
<pre>This design lays great emphasis on separating all those components and subsystems, which are considered not to have a //natural link// of their underlying concepts. This often means putting some additional constraints on the implementation, so basically we need to rely on the actual implementation to live up to this goal. In many cases it may seem to be more natural to &quot;just access the necessary information&quot;. But on the long run this coupling of not-directly related components makes the whole codebase monolithic and introduces lots of //accidental complexity.//
Instead, we should try to just connect the various subsystems via Interfaces and &amp;mdash; instead of just using some information, rather use some service to be located on an Interface to query other components for this information. The best approach of course is always to avoid the dependency altogether.
!Examples
* There is a separation between the __high level [[Session]] view__ and the [[Fixture]]: the latter only accesses the MObjects and the Placement Interfaces.
* same holds true for the Builder: it just uses the same Interfaces. The actual coupling is done rather //by type//, i.e. the Builder relies on several types of MObjects to exist and treats them via overloaded methods. He doesn't rely on a actual object structure layout in the session besides the requirement of having a [[Playlist]]
* the Builder itself is a separation layer. Neither do the Objects in the sessionL access directly [[Render Nodes|ProcNode]], nor do the latter call back into the session. Both connections seem to be necessary at first sight, but both can be avoided by using the Builder Pattern
* another separation exists between the Render Engine and the individual Nodes: The Render Engine doesn't need to know the details of the data types processed by the Nodes. It relies on the Builder having done the correct connections and just pulls out the calculated results. If there needs to be additional control information to be passed, then I would prefer to do a direct wiring of separate control connections to specialized components, which in turn could instruct the controller to change the rendering process.
* to shield the rendering code of all complexities of thread communication and synchronization, we use the StateProxy
</pre>
</div>
<div title="StructAsset" modifier="Ichthyostega" modified="201002280044" created="200709221353" tags="def classes img" changecount="18">
<pre>Structural Assets are intended mainly for internal use, but the user should be able to see and query them. They are not &quot;loaded&quot; or &quot;created&quot; directly, rather they //leap into existence // by creating or extending some other structures in the session, hence the name. Some of the structural Asset parametrisation can be modified to exert control on some aspects of the Proc Layer's (default) behaviour.
* [[Processing Patterns|ProcPatt]] encode information how to set up some parts of the render network to be created automatically: for example, when building a clip, we use the processing pattern how to decode and pre-process the actual media data.
* [[Tracks|Track]] are one of the dimensions used for organizing the session data. They serve as an Anchor to attach parametrisation of output pipe, overlay mode etc. By [[placing|Placement]] to a track, a media object inherits placement properties from this track.
* [[Pipes|Pipe]] form &amp;mdash; at least as visible to the user &amp;mdash; the basic building block of the render network, because the latter appears to be a collection of interconnected processing pipelines. (this is the //outward view; // in fact the render network consists of [[nodes|ProcNode]] and is [[built|Builder]] from the Pipes, clips, effects...)[&gt;img[Asset Classess|uml/fig131205.png]]
* [[Sequence]] assets act as a façade to the fundamental compound building blocks within the model, a sequence being a collection of clips placed onto a tree of tracks. Sequences, as well as the top-level tracks enclosed will be created automatically on demand. Of course you may create them deliberately. Without binding it to a timeline or meta-clip, a sequence remains invisible.
* [[Timeline]] assets are the top level structures to access the model; similar to the sequences, they act as façade to relevant parts of the model (BindingMO) and will be created on demand, alongside with a new session if necessary, bound to the new timeline. Likewise, they can be referred by their name-ID
!naming scheme
The Asset name field of structural Assets utilizes a special naming scheme, which allows to derive the name based on the capabilities of the structural asset. For example, by default all media clips with a given media stream type (e.g. H264) will use the same [[processing Pattern|ProcPatt]] for rendering. {{red{todo: work out the details of this naming scheme??}}}
!querying
Structural assets can be queried by specifying the specific type (Pipe, Track, ProcPatt) and a query goal. For example, you can {{{Query&lt;Pipe&gt; (&quot;stream(mpeg)&quot;)}}}, yielding the first pipe found which declares to have stream type &quot;mpeg&quot;. The access point for this querying facility is on the ~StructFactory, which (as usual within Lumiera) can be invoked as {{{Struct::create(Query&lt;...&gt; ...}}}. Given such a query, first an attempt is made to satisfy it by retrieving an existing object (which might bind variables as a side effect). On failure, a new structural asset of the requested type will be created to satisfy the given goal.
{{red{Note:}}} in the current implementation no real resolution engine is used (as of 2/2010); rather, we're just able to retrieve a hard-wired answer or create a new asset, simply pattern matching on parts of the query.
&amp;rarr; [[Assets in general|Asset]]
</pre>
</div>
<div title="StyleSheet" modifier="Ichthyostega" modified="200709040043" created="200701131624" tags="MPTWTheme excludeMissing" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200706090017" changecount="14">
<pre>/*{{{*/
/* a contrasting background so I can see where one tiddler ends and the other begins */
body {
background: [[ColorPalette::TertiaryLight]];
}
/* sexy colours and font for the header */
.headerForeground {
color: [[ColorPalette::PrimaryPale]];
}
.headerShadow, .headerShadow a {
color: [[ColorPalette::PrimaryMid]];
}
.headerForeground, .headerShadow {
padding: 1em 1em 0;
font-family: 'Trebuchet MS' sans-serif;
font-weight:bold;
}
.headerForeground .siteSubtitle {
color: [[ColorPalette::PrimaryLight]];
}
.headerShadow .siteSubtitle {
color: [[ColorPalette::PrimaryMid]];
}
/* make shadow go and down right instead of up and left */
.headerShadow {
left: 2px;
top: 3px;
}
/* prefer monospace for editing */
.editor textarea {
font-family: 'Consolas' monospace;
}
/* sexy tiddler titles */
.title {
font-size: 250%;
color: [[ColorPalette::PrimaryLight]];
font-family: 'Trebuchet MS' sans-serif;
}
/* more subtle tiddler subtitle */
.subtitle {
padding:0px;
margin:0px;
padding-left:0.5em;
font-size: 90%;
color: [[ColorPalette::TertiaryMid]];
}
.subtitle .tiddlyLink {
color: [[ColorPalette::TertiaryMid]];
}
/* a little bit of extra whitespace */
.viewer {
padding-bottom:3px;
}
/* don't want any background color for headings */
h1,h2,h3,h4,h5,h6 {
background: [[ColorPalette::Background]];
color: [[ColorPalette::Foreground]];
}
/* give tiddlers 3d style border and explicit background */
.tiddler {
background: [[ColorPalette::Background]];
border-right: 2px [[ColorPalette::TertiaryMid]] solid;
border-bottom: 2px [[ColorPalette::TertiaryMid]] solid;
margin-bottom: 1em;
padding-bottom: 2em;
}
/* make options slider look nicer */
#sidebarOptions .sliderPanel {
border:solid 1px [[ColorPalette::PrimaryLight]];
}
/* the borders look wrong with the body background */
#sidebar .button {
border-style: none;
}
/* displays the list of a tiddler's tags horizontally. used in ViewTemplate */
.tagglyTagged li.listTitle {
display:none
}
.tagglyTagged li {
display: inline; font-size:90%;
}
.tagglyTagged ul {
margin:0px; padding:0px;
}
/* this means you can put line breaks in SidebarOptions for readability */
#sidebarOptions br {
display:none;
}
/* undo the above in OptionsPanel */
#sidebarOptions .sliderPanel br {
display:inline;
}
/* horizontal main menu stuff */
#displayArea {
margin: 1em 15.7em 0em 1em; /* use the freed up space */
}
#topMenu br {
display: none;
}
#topMenu {
background: [[ColorPalette::PrimaryMid]];
color:[[ColorPalette::PrimaryPale]];
}
#topMenu {
padding:2px;
}
#topMenu .button, #topMenu .tiddlyLink, #topMenu a {
margin-left: 0.5em;
margin-right: 0.5em;
padding-left: 3px;
padding-right: 3px;
color: [[ColorPalette::PrimaryPale]];
font-size: 115%;
}
#topMenu .button:hover, #topMenu .tiddlyLink:hover {
background: [[ColorPalette::PrimaryDark]];
}
/* make it print a little cleaner */
@media print {
#topMenu {
display: none ! important;
}
/* not sure if we need all the importants */
.tiddler {
border-style: none ! important;
margin:0px ! important;
padding:0px ! important;
padding-bottom:2em ! important;
}
.tagglyTagging .button, .tagglyTagging .hidebutton {
display: none ! important;
}
.headerShadow {
visibility: hidden ! important;
}
.tagglyTagged .quickopentag, .tagged .quickopentag {
border-style: none ! important;
}
.quickopentag a.button, .miniTag {
display: none ! important;
}
}
/* *** Additions by Ichthyostega *** */
.red {
background: #ffcc99;
color: #ff2210;
padding: 0px 0.8ex;
}
.viewer th {
background: #91a6af;
}
/*}}}*/
</pre>
</div>
<div title="TabTimeline" modifier="Ichthyostega" modified="200806030148" created="200706191949" changecount="4">
<pre>&lt;&lt;timeline better:true maxDays:55 maxEntries:45&gt;&gt;</pre>
</div>
<div title="TaskMacroPlugin" modifier="Ichthyostega" modified="200905291912" created="200712100248" tags="systemConfig excludeMissing" changecount="3">
<pre>/***
|Name|TaskMacroPlugin|
|Author|&lt;&lt;extension TaskMacroPlugin author&gt;&gt;|
|Location|&lt;&lt;extension TaskMacroPlugin source&gt;&gt;|
|License|&lt;&lt;extension TaskMacroPlugin license&gt;&gt;|
|Version|&lt;&lt;extension TaskMacroPlugin versionAndDate&gt;&gt;|
!Description
A set of macros to help you keep track of time estimates for tasks.
Macros defined:
* {{{task}}}: Displays a task description and makes it easy to estimate and track the time spent on the task.
* {{{taskadder}}}: Displays text entry field to simplify the adding of tasks.
* {{{tasksum}}}: Displays a summary of tasks sandwiched between two calls to this macro.
* {{{extension}}}: A simple little macro that displays information about a TiddlyWiki plugin, and that will hopefully someday migrate to the TW core in some form.
Core overrides:
* {{{wikify}}}: when wikifying a tiddler's complete text, adds refresh information so the tiddler will be refreshed when it changes
* {{{config.refreshers}}}: have the built-in refreshers return true; also, add a new refresher (&quot;fullContent&quot;) that redisplays a full tiddler whenever it or any nested tiddlers it shows are changed
* {{{refreshElements}}}: now checks the return value from the refresher and only short-circuits the recursion if the refresher returns true
!Plugin Information
***/
//{{{
version.extensions.TaskMacroPlugin = {
major: 1, minor: 1, revision: 0,
date: new Date(2006,5-1,13),
author: &quot;LukeBlanshard&quot;,
source: &quot;http://labwiki.sourceforge.net/#TaskMacroPlugin&quot;,
license: &quot;http://labwiki.sourceforge.net/#CopyrightAndLicense&quot;
}
//}}}
/***
A little macro for pulling out extension info. Use like {{{&lt;&lt;extension PluginName datum&gt;&gt;}}}, where {{{PluginName}}} is the name you used for {{{version.extensions}}} and {{{datum}}} is either {{{versionAndDate}}} or a property of the extension description object, such as {{{source}}}.
***/
//{{{
config.macros.extension = {
handler: function( place, macroName, params, wikifier, paramString, tiddler ) {
var info = version.extensions[params[0]]
var datum = params[1]
switch (params[1]) {
case 'versionAndDate':
createTiddlyElement( place, &quot;span&quot;, null, null,
info.major+'.'+info.minor+'.'+info.revision+', '+info.date.formatString('DD MMM YYYY') )
break;
default:
wikify( info[datum], place )
break;
}
}
}
//}}}
/***
!Core Overrides
***/
//{{{
window.wikify_orig_TaskMacroPlugin = window.wikify
window.wikify = function(source,output,highlightRegExp,tiddler)
{
if ( tiddler &amp;&amp; tiddler.text === source )
addDisplayDependency( output, tiddler.title )
wikify_orig_TaskMacroPlugin.apply( this, arguments )
}
config.refreshers_orig_TaskMacroPlugin = config.refreshers
config.refreshers = {
link: function() {
config.refreshers_orig_TaskMacroPlugin.link.apply( this, arguments )
return true
},
content: function() {
config.refreshers_orig_TaskMacroPlugin.content.apply( this, arguments )
return true
},
fullContent: function( e, changeList ) {
var tiddlers = e.refreshTiddlers
if ( changeList == null || tiddlers == null )
return false
for ( var i=0; i &lt; tiddlers.length; ++i )
if ( changeList.find(tiddlers[i]) != null ) {
var title = tiddlers[0]
story.refreshTiddler( title, null, true )
return true
}
return false
}
}
function refreshElements(root,changeList)
{
var nodes = root.childNodes;
for(var c=0; c&lt;nodes.length; c++)
{
var e = nodes[c],type;
if(e.getAttribute)
type = e.getAttribute(&quot;refresh&quot;);
else
type = null;
var refresher = config.refreshers[type];
if ( ! refresher || ! refresher(e, changeList) )
{
if(e.hasChildNodes())
refreshElements(e,changeList);
}
}
}
//}}}
/***
!Global Functions
***/
//{{{
// Add the tiddler whose title is given to the list of tiddlers whose
// changing will cause a refresh of the tiddler containing the given element.
function addDisplayDependency( element, title ) {
while ( element &amp;&amp; element.getAttribute ) {
var idAttr = element.getAttribute(&quot;id&quot;), tiddlerAttr = element.getAttribute(&quot;tiddler&quot;)
if ( idAttr &amp;&amp; tiddlerAttr &amp;&amp; idAttr == story.idPrefix+tiddlerAttr ) {
var list = element.refreshTiddlers
if ( list == null ) {
list = [tiddlerAttr]
element.refreshTiddlers = list
element.setAttribute( &quot;refresh&quot;, &quot;fullContent&quot; )
}
list.pushUnique( title )
return
}
element = element.parentNode
}
}
// Lifted from Story.prototype.focusTiddler: just return the field instead of focusing it.
Story.prototype.findEditField = function( title, field )
{
var tiddler = document.getElementById(this.idPrefix + title);
if(tiddler != null)
{
var children = tiddler.getElementsByTagName(&quot;*&quot;)
var e = null;
for (var t=0; t&lt;children.length; t++)
{
var c = children[t];
if(c.tagName.toLowerCase() == &quot;input&quot; || c.tagName.toLowerCase() == &quot;textarea&quot;)
{
if(!e)
e = c;
if(c.getAttribute(&quot;edit&quot;) == field)
e = c;
}
}
return e
}
}
// Wraps the given event function in another function that handles the
// event in a standard way.
function wrapEventHandler( otherHandler ) {
return function(e) {
if (!e) var e = window.event
e.cancelBubble = true
if (e.stopPropagation) e.stopPropagation()
return otherHandler( e )
}
}
//}}}
/***
!Task Macro
Usage:
&gt; {{{&lt;&lt;task orig cur spent&gt;&gt;description}}}
All of orig, cur, and spent are optional numbers of days. The description goes through the end of the line, and is wikified.
***/
//{{{
config.macros.task = {
NASCENT: 0, // Task not yet estimated
LIVE: 1, // Estimated but with time remaining
DONE: 2, // Completed: no time remaining
bullets: [&quot;\u25cb&quot;, // nascent (open circle)
&quot;\u25ba&quot;, // live (right arrow)
&quot;\u25a0&quot;],// done (black square)
styles: [&quot;nascent&quot;, &quot;live&quot;, &quot;done&quot;],
// Translatable text:
lingo: {
spentTooBig: &quot;Spent time %0 can't exceed current estimate %1&quot;,
noNegative: &quot;Times may not be negative numbers&quot;,
statusTips: [&quot;Not yet estimated&quot;, &quot;To do&quot;, &quot;Done&quot;], // Array indexed by state (NASCENT/LIVE/DONE)
descClickTip: &quot; -- Double-click to edit task description&quot;,
statusClickTip: &quot; -- Double-click to mark task complete&quot;,
statusDoneTip: &quot; -- Double-click to adjust the time spent, to revive the task&quot;,
origTip: &quot;Original estimate in days&quot;,
curTip: &quot;Current estimate in days&quot;,
curTip2: &quot;Estimate in days&quot;, // For when orig == cur
clickTip: &quot; -- Click to adjust&quot;,
spentTip: &quot;Days spent on this task&quot;,
remTip: &quot;Days remaining&quot;,
curPrompt: &quot;Estimate this task in days, or adjust the current estimate by starting with + or -.\n\nYou may optionally also set or adjust the time spent by putting a second number after the first.&quot;,
spentPrompt: &quot;Enter the number of days you've spent on this task, or adjust the current number by starting with + or -.\n\nYou may optionally also set or adjust the time remaining by putting a second number after the first.&quot;,
remPrompt: &quot;Enter the number of days it will take to finish this task, or adjust the current estimate by starting with + or -.\n\nYou may optionally also set or adjust the time spent by putting a second number after the first.&quot;,
numbersOnly: &quot;Enter numbers only, please&quot;,
notCurrent: &quot;The tiddler has been modified since it was displayed, please redisplay it before doing this.&quot;
},
// The macro handler
handler: function( place, macroName, params, wikifier, paramString, tiddler )
{
var start = wikifier.matchStart, end = wikifier.nextMatch
var origStr = params.length &gt; 0? params.shift() : &quot;?&quot;
var orig = +origStr // as a number
var cur = params.length &gt; 1? +params.shift() : orig
var spent = params.length &gt; 0? +params.shift() : 0
if ( spent &gt; cur )
throw Error( this.lingo.spentTooBig.format([spent, cur]) )
if ( orig &lt; 0 || cur &lt; 0 || spent &lt; 0 )
throw Error( this.lingo.noNegative )
var rem = cur - spent
var state = isNaN(orig+rem)? this.NASCENT : rem &gt; 0? this.LIVE : this.DONE
var table = createTiddlyElement( place, &quot;table&quot;, null, &quot;task &quot;+this.styles[state] )
var tbody = createTiddlyElement( table, &quot;tbody&quot; )
var row = createTiddlyElement( tbody, &quot;tr&quot; )
var statusCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;status&quot;, this.bullets[state] )
var descCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;description&quot; )
var origCell = state==this.NASCENT || orig==cur? null
: createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric original&quot; )
var curCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric current&quot; )
var spentCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric spent&quot; )
var remCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric remaining&quot; )
var sums = config.macros.tasksum.tasksums
if ( sums &amp;&amp; sums.length ) {
var summary = [(state == this.NASCENT? NaN : orig), cur, spent]
summary.owner = tiddler
sums[0].push( summary )
}
// The description goes to the end of the line
wikifier.subWikify( descCell, &quot;$\\n?&quot; )
var descEnd = wikifier.nextMatch
statusCell.setAttribute( &quot;title&quot;, this.lingo.statusTips[state] )
descCell.setAttribute( &quot;title&quot;, this.lingo.statusTips[state]+this.lingo.descClickTip )
if (origCell) {
createTiddlyElement( origCell, &quot;div&quot;, null, null, orig )
origCell.setAttribute( &quot;title&quot;, this.lingo.origTip )
curCell.setAttribute( &quot;title&quot;, this.lingo.curTip )
}
else {
curCell.setAttribute( &quot;title&quot;, this.lingo.curTip2 )
}
var curDivContents = (state==this.NASCENT)? &quot;?&quot; : cur
var curDiv = createTiddlyElement( curCell, &quot;div&quot;, null, null, curDivContents )
spentCell.setAttribute( &quot;title&quot;, this.lingo.spentTip )
var spentDiv = createTiddlyElement( spentCell, &quot;div&quot;, null, null, spent )
remCell.setAttribute( &quot;title&quot;, this.lingo.remTip )
var remDiv = createTiddlyElement( remCell, &quot;div&quot;, null, null, rem )
// Handle double-click on the description by going
// into edit mode and selecting the description
descCell.ondblclick = this.editDescription( tiddler, end, descEnd )
function appTitle( el, suffix ) {
el.setAttribute( &quot;title&quot;, el.getAttribute(&quot;title&quot;)+suffix )
}
// For incomplete tasks, handle double-click on the bullet by marking the task complete
if ( state != this.DONE ) {
appTitle( statusCell, this.lingo.statusClickTip )
statusCell.ondblclick = this.markTaskComplete( tiddler, start, end, macroName, orig, cur, state )
}
// For complete ones, handle double-click on the bullet by letting you adjust the time spent
else {
appTitle( statusCell, this.lingo.statusDoneTip )
statusCell.ondblclick = this.adjustTimeSpent( tiddler, start, end, macroName, orig, cur, spent )
}
// Add click handlers for the numeric cells.
if ( state != this.DONE ) {
appTitle( curCell, this.lingo.clickTip )
curDiv.className = &quot;adjustable&quot;
curDiv.onclick = this.adjustCurrentEstimate( tiddler, start, end, macroName,
orig, cur, spent, curDivContents )
}
appTitle( spentCell, this.lingo.clickTip )
spentDiv.className = &quot;adjustable&quot;
spentDiv.onclick = this.adjustTimeSpent( tiddler, start, end, macroName, orig, cur, spent )
if ( state == this.LIVE ) {
appTitle( remCell, this.lingo.clickTip )
remDiv.className = &quot;adjustable&quot;
remDiv.onclick = this.adjustTimeRemaining( tiddler, start, end, macroName, orig, cur, spent )
}
},
// Puts the tiddler into edit mode, and selects the range of characters
// defined by start and end. Separated for leak prevention in IE.
editDescription: function( tiddler, start, end ) {
return wrapEventHandler( function(e) {
story.displayTiddler( null, tiddler.title, DEFAULT_EDIT_TEMPLATE )
var tiddlerElement = document.getElementById( story.idPrefix + tiddler.title )
window.scrollTo( 0, ensureVisible(tiddlerElement) )
var element = story.findEditField( tiddler.title, &quot;text&quot; )
if ( element &amp;&amp; element.tagName.toLowerCase() == &quot;textarea&quot; ) {
// Back up one char if the last char's a newline
if ( tiddler.text[end-1] == '\n' )
--end
element.focus()
if ( element.setSelectionRange != undefined ) { // Mozilla
element.setSelectionRange( start, end )
// Damn mozilla doesn't scroll to visible. Approximate.
var max = 0.0 + element.scrollHeight
var len = element.textLength
var top = max*start/len, bot = max*end/len
element.scrollTop = Math.min( top, (bot+top-element.clientHeight)/2 )
}
else if ( element.createTextRange != undefined ) { // IE
var range = element.createTextRange()
range.collapse()
range.moveEnd(&quot;character&quot;, end)
range.moveStart(&quot;character&quot;, start)
range.select()
}
else // Other? Too bad, just select the whole thing.
element.select()
return false
}
else
return true
} )
},
// Modifies a task macro call such that the task appears complete.
markTaskComplete: function( tiddler, start, end, macroName, orig, cur, state ) {
var macro = this, text = tiddler.text
return wrapEventHandler( function(e) {
if ( text !== tiddler.text ) {
alert( macro.lingo.notCurrent )
return false
}
if ( state == macro.NASCENT )
orig = cur = 0
// The second &quot;cur&quot; in the call below bumps up the time spent
// to match the current estimate.
macro.replaceMacroCall( tiddler, start, end, macroName, orig, cur, cur )
return false
} )
},
// Asks the user for an adjustment to the current estimate, modifies the macro call accordingly.
adjustCurrentEstimate: function( tiddler, start, end, macroName, orig, cur, spent, curDivContents ) {
var macro = this, text = tiddler.text
return wrapEventHandler( function(e) {
if ( text !== tiddler.text ) {
alert( macro.lingo.notCurrent )
return false
}
var txt = prompt( macro.lingo.curPrompt, curDivContents )
if ( txt != null ) {
var a = macro.breakInput( txt )
cur = macro.offset( cur, a[0] )
if ( a.length &gt; 1 )
spent = macro.offset( spent, a[1] )
macro.replaceMacroCall( tiddler, start, end, macroName, orig, cur, spent )
}
return false
} )
},
// Asks the user for an adjustment to the time spent, modifies the macro call accordingly.
adjustTimeSpent: function( tiddler, start, end, macroName, orig, cur, spent ) {
var macro = this, text = tiddler.text
return wrapEventHandler( function(e) {
if ( text !== tiddler.text ) {
alert( macro.lingo.notCurrent )
return false
}
var txt = prompt( macro.lingo.spentPrompt, spent )
if ( txt != null ) {
var a = macro.breakInput( txt )
spent = macro.offset( spent, a[0] )
var rem = cur - spent
if ( a.length &gt; 1 ) {
rem = macro.offset( rem, a[1] )
cur = spent + rem
}
macro.replaceMacroCall( tiddler, start, end, macroName, orig, cur, spent )
}
return false
} )
},
// Asks the user for an adjustment to the time remaining, modifies the macro call accordingly.
adjustTimeRemaining: function( tiddler, start, end, macroName, orig, cur, spent ) {
var macro = this
var text = tiddler.text
var rem = cur - spent
return wrapEventHandler( function(e) {
if ( text !== tiddler.text ) {
alert( macro.lingo.notCurrent )
return false
}
var txt = prompt( macro.lingo.remPrompt, rem )
if ( txt != null ) {
var a = macro.breakInput( txt )
var newRem = macro.offset( rem, a[0] )
if ( newRem &gt; rem || a.length &gt; 1 )
cur += (newRem - rem)
else
spent += (rem - newRem)
if ( a.length &gt; 1 )
spent = macro.offset( spent, a[1] )
macro.replaceMacroCall( tiddler, start, end, macroName, orig, cur, spent )
}
return false
} )
},
// Breaks input at spaces &amp; commas, returns array
breakInput: function( txt ) {
var a = txt.trim().split( /[\s,]+/ )
if ( a.length == 0 )
a = [NaN]
return a
},
// Adds to, subtracts from, or replaces a numeric value
offset: function( num, txt ) {
if ( txt == &quot;&quot; || typeof(txt) != &quot;string&quot; )
return NaN
if ( txt.match(/^[+-]/) )
return num + (+txt)
return +txt
},
// Does some error checking, then replaces the indicated macro
// call within the text of the given tiddler.
replaceMacroCall: function( tiddler, start, end, macroName, orig, cur, spent )
{
if ( isNaN(cur+spent) ) {
alert( this.lingo.numbersOnly )
return
}
if ( spent &lt; 0 || cur &lt; 0 ) {
alert( this.lingo.noNegative )
return
}
if ( isNaN(orig) )
orig = cur
if ( spent &gt; cur )
cur = spent
var text = tiddler.text.substring(0,start) + &quot;&lt;&lt;&quot; + macroName + &quot; &quot; +
orig + &quot; &quot; + cur + &quot; &quot; + spent + &quot;&gt;&gt;&quot; + tiddler.text.substring(end)
var title = tiddler.title
store.saveTiddler( title, title, text, config.options.txtUserName, new Date(), undefined )
//story.refreshTiddler( title, null, true )
if ( config.options.chkAutoSave )
saveChanges()
}
}
//}}}
/***
!Tasksum Macro
Usage:
&gt; {{{&lt;&lt;tasksum &quot;start&quot; [&quot;here&quot; [intro]]&gt;&gt;}}}
or:
&gt; {{{&lt;&lt;tasksum &quot;end&quot; [intro]&gt;&gt;}}}
Put one of the {{{&lt;&lt;tasksum start&gt;&gt;}}} lines before the tasks you want to summarize, and an {{{end}}} line after them. By default, the summary goes at the end; if you include {{{here}}} in the start line, the summary will go at the top. The intro argument, if supplied, replaces the default text introducing the summary.
***/
//{{{
config.macros.tasksum = {
// Translatable text:
lingo: {
unrecVerb: &quot;&lt;&lt;%0&gt;&gt; requires 'start' or 'end' as its first argument&quot;,
mustMatch: &quot;&lt;&lt;%0 end&gt;&gt; must match a preceding &lt;&lt;%0 start&gt;&gt;&quot;,
defIntro: &quot;Task summary:&quot;,
nascentSum: &quot;''%0 not estimated''&quot;,
doneSum: &quot;%0 complete (in %1 days)&quot;,
liveSum: &quot;%0 ongoing (%1 days so far, ''%2 days remaining'')&quot;,
overSum: &quot;Total overestimate: %0%.&quot;,
underSum: &quot;Total underestimate: %0%.&quot;,
descPattern: &quot;%0 %1. %2&quot;,
origTip: &quot;Total original estimates in days&quot;,
curTip: &quot;Total current estimates in days&quot;,
spentTip: &quot;Total days spent on tasks&quot;,
remTip: &quot;Total days remaining&quot;
},
// The macro handler
handler: function( place, macroName, params, wikifier, paramString, tiddler )
{
var sums = this.tasksums
if ( params[0] == &quot;start&quot; ) {
sums.unshift([])
if ( params[1] == &quot;here&quot; ) {
sums[0].intro = params[2] || this.lingo.defIntro
sums[0].place = place
sums[0].placement = place.childNodes.length
}
}
else if ( params[0] == &quot;end&quot; ) {
if ( ! sums.length )
throw Error( this.lingo.mustMatch.format([macroName]) )
var list = sums.shift()
var intro = list.intro || params[1] || this.lingo.defIntro
var nNascent=0, nLive=0, nDone=0, nMine=0
var totLiveSpent=0, totDoneSpent=0
var totOrig=0, totCur=0, totSpent=0
for ( var i=0; i &lt; list.length; ++i ) {
var a = list[i]
if ( a.length &gt; 3 ) {
nNascent += a[0]
nLive += a[1]
nDone += a[2]
totLiveSpent += a[3]
totDoneSpent += a[4]
totOrig += a[5]
totCur += a[6]
totSpent += a[7]
if ( a.owner == tiddler )
nMine += a[8]
}
else {
if ( a.owner == tiddler )
++nMine
if ( isNaN(a[0]) ) {
++nNascent
}
else {
if ( a[1] &gt; a[2] ) {
++nLive
totLiveSpent += a[2]
}
else {
++nDone
totDoneSpent += a[2]
}
totOrig += a[0]
totCur += a[1]
totSpent += a[2]
}
}
}
// If we're nested, push a summary outward
if ( sums.length ) {
var summary = [nNascent, nLive, nDone, totLiveSpent, totDoneSpent,
totOrig, totCur, totSpent, nMine]
summary.owner = tiddler
sums[0].push( summary )
}
var descs = [], styles = []
if ( nNascent &gt; 0 ) {
descs.push( this.lingo.nascentSum.format([nNascent]) )
styles.push( &quot;nascent&quot; )
}
if ( nDone &gt; 0 )
descs.push( this.lingo.doneSum.format([nDone, totDoneSpent]) )
if ( nLive &gt; 0 ) {
descs.push( this.lingo.liveSum.format([nLive, totLiveSpent, totCur-totSpent]) )
styles.push( &quot;live&quot; )
}
else
styles.push( &quot;done&quot; )
var off = &quot;&quot;
if ( totOrig &gt; totCur )
off = this.lingo.overSum.format( [Math.round(100.0*(totOrig-totCur)/totCur)] )
else if ( totCur &gt; totOrig )
off = this.lingo.underSum.format( [Math.round(100.0*(totCur-totOrig)/totOrig)] )
var top = (list.intro != undefined)
var table = createTiddlyElement( null, &quot;table&quot;, null, &quot;tasksum &quot;+(top?&quot;top&quot;:&quot;bottom&quot;) )
var tbody = createTiddlyElement( table, &quot;tbody&quot; )
var row = createTiddlyElement( tbody, &quot;tr&quot;, null, styles.join(&quot; &quot;) )
var descCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;description&quot; )
var description = this.lingo.descPattern.format( [intro, descs.join(&quot;, &quot;), off] )
wikify( description, descCell, null, tiddler )
var origCell = totOrig == totCur? null
: createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric original&quot;, totOrig )
var curCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric current&quot;, totCur )
var spentCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric spent&quot;, totSpent )
var remCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric remaining&quot;, totCur-totSpent )
if ( origCell )
origCell.setAttribute( &quot;title&quot;, this.lingo.origTip )
curCell .setAttribute( &quot;title&quot;, this.lingo.curTip )
spentCell.setAttribute( &quot;title&quot;, this.lingo.spentTip )
remCell .setAttribute( &quot;title&quot;, this.lingo.remTip )
// Discard the table if there are no tasks
if ( list.length &gt; 0 ) {
var place = top? list.place : place
var placement = top? list.placement : place.childNodes.length
if ( placement &gt;= place.childNodes.length )
place.appendChild( table )
else
place.insertBefore( table, place.childNodes[placement] )
}
}
else
throw Error( this.lingo.unrecVerb.format([macroName]) )
// If we're wikifying, and are followed by end-of-line, swallow the newline.
if ( wikifier &amp;&amp; wikifier.source.charAt(wikifier.nextMatch) == &quot;\n&quot; )
++wikifier.nextMatch
},
// This is the stack of pending summaries
tasksums: []
}
//}}}
/***
!Taskadder Macro
Usage:
&gt; {{{&lt;&lt;taskadder [&quot;above&quot;|&quot;below&quot;|&quot;focus&quot;|&quot;nofocus&quot;]...&gt;&gt;}}}
Creates a line with text entry fields for a description and an estimate. By default, puts focus in the description field and adds tasks above the entry fields. Use {{{nofocus}}} to not put focus in the description field. Use {{{below}}} to add tasks below the entry fields.
***/
//{{{
config.macros.taskadder = {
// Translatable text:
lingo: {
unrecParam: &quot;&lt;&lt;%0&gt;&gt; doesn't recognize '%1' as a parameter&quot;,
descTip: &quot;Describe a new task&quot;,
curTip: &quot;Estimate how much days the task will take&quot;,
buttonText: &quot;add task&quot;,
buttonTip: &quot;Add a new task with the description and estimate as entered&quot;,
notCurrent: &quot;The tiddler has been modified since it was displayed, please redisplay it before adding a task this way.&quot;,
eol: &quot;eol&quot;
},
// The macro handler
handler: function( place, macroName, params, wikifier, paramString, tiddler )
{
var above = true
var focus = false
while ( params.length &gt; 0 ) {
var p = params.shift()
switch (p) {
case &quot;above&quot;: above = true; break
case &quot;below&quot;: above = false; break
case &quot;focus&quot;: focus = true; break
case &quot;nofocus&quot;: focus = false; break
default: throw Error( this.lingo.unrecParam.format([macroName, p]) )
}
}
// If we're followed by end-of-line, swallow the newline.
if ( wikifier.source.charAt(wikifier.nextMatch) == &quot;\n&quot; )
++wikifier.nextMatch
var where = above? wikifier.matchStart : wikifier.nextMatch
var table = createTiddlyElement( place, &quot;table&quot;, null, &quot;task&quot; )
var tbody = createTiddlyElement( table, &quot;tbody&quot; )
var row = createTiddlyElement( tbody, &quot;tr&quot; )
var statusCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;status&quot; )
var descCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;description&quot; )
var curCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;numeric&quot; )
var addCell = createTiddlyElement( row, &quot;td&quot;, null, &quot;addtask&quot; )
var descId = this.generateId()
var curId = this.generateId()
var descInput = createTiddlyElement( descCell, &quot;input&quot;, descId )
var curInput = createTiddlyElement( curCell, &quot;input&quot;, curId )
descInput.setAttribute( &quot;type&quot;, &quot;text&quot; )
curInput .setAttribute( &quot;type&quot;, &quot;text&quot; )
descInput.setAttribute( &quot;size&quot;, &quot;40&quot;)
curInput .setAttribute( &quot;size&quot;, &quot;6&quot; )
descInput.setAttribute( &quot;autocomplete&quot;, &quot;off&quot; );
curInput .setAttribute( &quot;autocomplete&quot;, &quot;off&quot; );
descInput.setAttribute( &quot;title&quot;, this.lingo.descTip );
curInput .setAttribute( &quot;title&quot;, this.lingo.curTip );
var addAction = this.addTask( tiddler, where, descId, curId, above )
var addButton = createTiddlyButton( addCell, this.lingo.buttonText, this.lingo.buttonTip, addAction )
descInput.onkeypress = this.handleEnter(addAction)
curInput .onkeypress = descInput.onkeypress
addButton.onkeypress = this.handleSpace(addAction)
if ( focus || tiddler.taskadderLocation == where ) {
descInput.focus()
descInput.select()
}
},
// Returns a function that inserts a new task macro into the tiddler.
addTask: function( tiddler, where, descId, curId, above ) {
var macro = this, oldText = tiddler.text
return wrapEventHandler( function(e) {
if ( oldText !== tiddler.text ) {
alert( macro.lingo.notCurrent )
return false
}
var desc = document.getElementById(descId).value
var cur = document.getElementById(curId) .value
var init = tiddler.text.substring(0,where) + &quot;&lt;&lt;task &quot; + cur + &quot;&gt;&gt; &quot; + desc + &quot;\n&quot;
var text = init + tiddler.text.substring(where)
var title = tiddler.title
tiddler.taskadderLocation = (above? init.length : where)
try {
store.saveTiddler( title, title, text, config.options.txtUserName, new Date(), undefined )
//story.refreshTiddler( title, null, true )
}
finally {
delete tiddler.taskadderLocation
}
if ( config.options.chkAutoSave )
saveChanges()
} )
},
// Returns an event handler that delegates to two other functions: &quot;matches&quot; to decide
// whether to consume the event, and &quot;addTask&quot; to actually perform the work.
handleGeneric: function( addTask, matches ) {
return function(e) {
if (!e) var e = window.event
var consume = false
if ( matches(e) ) {
consume = true
addTask( e )
}
e.cancelBubble = consume;
if ( consume &amp;&amp; e.stopPropagation ) e.stopPropagation();
return !consume;
}
},
// Returns an event handler that handles enter keys by calling another event handler
handleEnter: function( addTask ) {
return this.handleGeneric( addTask, function(e){return e.keyCode == 13 || e.keyCode == 10} ) // Different codes for Enter
},
// Returns an event handler that handles the space key by calling another event handler
handleSpace: function( addTask ) {
return this.handleGeneric( addTask, function(e){return (e.charCode||e.keyCode) == 32} )
},
counter: 0,
generateId: function() {
return &quot;taskadder:&quot; + String(this.counter++)
}
}
//}}}
/***
!Stylesheet
***/
//{{{
var stylesheet = '\
.viewer table.task, table.tasksum {\
width: 100%;\
padding: 0;\
border-collapse: collapse;\
}\
.viewer table.task {\
border: none;\
margin: 0;\
}\
table.tasksum, .viewer table.tasksum {\
border: solid 2px #999;\
margin: 3px 0;\
}\
table.tasksum td {\
text-align: center;\
border: 1px solid #ddd;\
background-color: #ffc;\
vertical-align: middle;\
margin: 0;\
padding: 0;\
}\
.viewer table.task tr {\
border: none;\
}\
.viewer table.task td {\
text-align: center;\
vertical-align: baseline;\
border: 1px solid #fff;\
background-color: inherit;\
margin: 0;\
padding: 0;\
}\
td.numeric {\
width: 3em;\
}\
table.task td.numeric div {\
border: 1px solid #ddd;\
background-color: #ffc;\
margin: 1px 0;\
padding: 0;\
}\
table.task td.original div {\
background-color: #fdd;\
}\
table.tasksum td.original {\
background-color: #fdd;\
}\
table.tasksum td.description {\
background-color: #e8e8e8;\
}\
table.task td.status {\
width: 1.5em;\
cursor: default;\
}\
table.task td.description, table.tasksum td.description {\
width: auto;\
text-align: left;\
padding: 0 3px;\
}\
table.task.done td.status,table.task.done td.description {\
color: #ccc;\
}\
table.task.done td.current, table.task.done td.remaining {\
visibility: hidden;\
}\
table.task.done td.spent div, table.tasksum tr.done td.current,\
table.tasksum tr.done td.spent, table.tasksum tr.done td.remaining {\
background-color: #eee;\
color: #aaa;\
}\
table.task.nascent td.description {\
color: #844;\
}\
table.task.nascent td.current div, table.tasksum tr.nascent td.numeric.current {\
font-weight: bold;\
color: #c00;\
background-color: #def;\
}\
table.task.nascent td.spent, table.task.nascent td.remaining {\
visibility: hidden;\
}\
td.remaining {\
font-weight: bold;\
}\
.adjustable {\
cursor: pointer; \
}\
table.task input {\
display: block;\
width: 100%;\
font: inherit;\
margin: 2px 0;\
padding: 0;\
border: 1px inset #999;\
}\
table.task td.numeric input {\
background-color: #ffc;\
text-align: center;\
}\
table.task td.addtask {\
width: 6em;\
border-left: 2px solid white;\
vertical-align: middle;\
}\
'
setStylesheet( stylesheet, &quot;TaskMacroPluginStylesheet&quot; )
//}}}
</pre>
</div>
<div title="TextAreaPlugin" modifier="Jeremy" created="200601261745" tags="systemConfig" server.type="file" server.host="file:///home/ct/.homepage/home.html" server.page.revision="200601261745">
<pre>/***
''TextAreaPlugin for TiddlyWiki version 2.0''
^^author: Eric Shulman - ELS Design Studios
source: http://www.elsdesign.com/tiddlywiki/#TextAreaPlugin
license: [[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]^^
This plugin 'hijacks' the TW core function, ''Story.prototype.focusTiddler()'', so it can add special 'keyDown' handlers to adjust several behaviors associated with the textarea control used in the tiddler editor. Specifically, it:
* Adds text search INSIDE of edit fields.^^
Use ~CTRL-F for &quot;Find&quot; (prompts for search text), and ~CTRL-G for &quot;Find Next&quot; (uses previous search text)^^
* Enables TAB characters to be entered into field content^^
(instead of moving to next field)^^
* Option to set cursor at top of edit field instead of auto-selecting contents^^
(see configuration section for checkbox)^^
!!!!!Configuration
&lt;&lt;&lt;
&lt;&lt;option chkDisableAutoSelect&gt;&gt; place cursor at start of textarea instead of pre-selecting content
&lt;&lt;option chkTextAreaExtensions&gt;&gt; add control-f (find), control-g (find again) and allow TABs as input in textarea
&lt;&lt;&lt;
!!!!!Installation
&lt;&lt;&lt;
Import (or copy/paste) the following tiddlers into your document:
''TextAreaPlugin'' (tagged with &lt;&lt;tag systemConfig&gt;&gt;)
&lt;&lt;&lt;
!!!!!Revision History
&lt;&lt;&lt;
''2006.01.22 [1.0.1]''
only add extra key processing for TEXTAREA elements (not other edit fields).
added option to enable/disable textarea keydown extensions (default is &quot;standard keys&quot; only)
''2006.01.22 [1.0.0]''
Moved from temporary &quot;System Tweaks&quot; tiddler into 'real' TextAreaPlugin tiddler.
&lt;&lt;&lt;
!!!!!Code
***/
//{{{
version.extensions.textAreaPlugin= {major: 1, minor: 0, revision: 1, date: new Date(2006,1,23)};
//}}}
//{{{
if (!config.options.chkDisableAutoSelect) config.options.chkDisableAutoSelect=false; // default to standard action
if (!config.options.chkTextAreaExtensions) config.options.chkTextAreaExtensions=false; // default to standard action
// Focus a specified tiddler. Attempts to focus the specified field, otherwise the first edit field it finds
Story.prototype.focusTiddler = function(title,field)
{
var tiddler = document.getElementById(this.idPrefix + title);
if(tiddler != null)
{
var children = tiddler.getElementsByTagName(&quot;*&quot;)
var e = null;
for (var t=0; t&lt;children.length; t++)
{
var c = children[t];
if(c.tagName.toLowerCase() == &quot;input&quot; || c.tagName.toLowerCase() == &quot;textarea&quot;)
{
if(!e)
e = c;
if(c.getAttribute(&quot;edit&quot;) == field)
e = c;
}
}
if(e)
{
e.focus();
e.select(); // select entire contents
// TWEAK: add TAB and &quot;find&quot; key handlers
if (config.options.chkTextAreaExtensions) // add extra key handlers
addKeyDownHandlers(e);
// TWEAK: option to NOT autoselect contents
if (config.options.chkDisableAutoSelect) // set cursor to start of field content
if (e.setSelectionRange) e.setSelectionRange(0,0); // for FF
else if (e.createTextRange) { var r=e.createTextRange(); r.collapse(true); r.select(); } // for IE
}
}
}
//}}}
//{{{
function addKeyDownHandlers(e)
{
// exit if not textarea or element doesn't allow selections
if (e.tagName.toLowerCase()!=&quot;textarea&quot; || !e.setSelectionRange) return;
// utility function: exits keydown handler and prevents browser from processing the keystroke
var processed=function(ev) { ev.cancelBubble=true; if (ev.stopPropagation) ev.stopPropagation(); return false; }
// capture keypress in edit field
e.onkeydown = function(ev) { if (!ev) var ev=window.event;
// process TAB
if (!ev.shiftKey &amp;&amp; ev.keyCode==9) {
// replace current selection with a TAB character
var start=e.selectionStart; var end=e.selectionEnd;
e.value=e.value.substr(0,start)+String.fromCharCode(9)+e.value.substr(end);
// update insertion point, scroll it into view
e.setSelectionRange(start+1,start+1);
var linecount=e.value.split('\n').length;
var thisline=e.value.substr(0,e.selectionStart).split('\n').length-1;
e.scrollTop=Math.floor((thisline-e.rows/2)*e.scrollHeight/linecount);
return processed(ev);
}
// process CTRL-F (find matching text) or CTRL-G (find next match)
if (ev.ctrlKey &amp;&amp; (ev.keyCode==70||ev.keyCode==71)) {
// if ctrl-f or no previous search, prompt for search text (default to previous text or current selection)... if no search text, exit
if (ev.keyCode==70||!e.find||!e.find.length)
{ var f=prompt(&quot;find:&quot;,e.find?e.find:e.value.substring(e.selectionStart,e.selectionEnd)); e.focus(); e.find=f?f:e.find; }
if (!e.find||!e.find.length) return processed(ev);
// do case-insensitive match with 'wraparound'... if not found, alert and exit
var newstart=e.value.toLowerCase().indexOf(e.find.toLowerCase(),e.selectionStart+1);
if (newstart==-1) newstart=e.value.toLowerCase().indexOf(e.find.toLowerCase());
if (newstart==-1) { alert(&quot;'&quot;+e.find+&quot;' not found&quot;); e.focus(); return processed(ev); }
// set new selection, scroll it into view, and report line position in status bar
e.setSelectionRange(newstart,newstart+e.find.length);
var linecount=e.value.split('\n').length;
var thisline=e.value.substr(0,e.selectionStart).split('\n').length;
e.scrollTop=Math.floor((thisline-1-e.rows/2)*e.scrollHeight/linecount);
window.status=&quot;line: &quot;+thisline+&quot;/&quot;+linecount;
return processed(ev);
}
}
}
//}}}</pre>
</div>
<div title="TickService" modifier="Ichthyostega" created="200902080629" tags="def" changecount="1">
<pre>A service generating //periodic ticks// &amp;mdash; repetedly invoking a callback with a given frequency.
* defined 1/2009 as part of the PlayerDummy (design sketch)
* probably to be implemented later on based on Posix timers
* will probably be later on integrated into a synchronisation framework (display sync, audio sync, MTC master/slave...)
</pre>
</div>
<div title="TiddlyWiki" modifier="Ichthyostega" created="200706220430" changecount="1">
<pre>http://tiddlywiki.com/</pre>
</div>
<div title="Timeline" modifier="Ichthyostega" modified="201003020524" created="200706250721" tags="def" changecount="16">
<pre>Timeline is the top level element within the [[Session (Project)|Session]]. It is visible within a //timeline view// in the GUI and represents the effective (resulting) arrangement of media objects, to be rendered for output or viewed in a Monitor (viewer window). A timeline is comprised of:
* a time axis in abolute time ({{red{WIP 1/10}}}: not clear if this is an entity or just a conceptual definition)
* a PlayControler
* a list of global Pipes representing the possible outputs (master busses)
* //exactly one// top-level [[Sequence]], which in turn may contain further nested Sequences.
Please note especially that following this design //a timeline doesn't define tracks.// [[Tracks form a Tree|Track]] and are part of the individual sequences, together with the media objects placed to these tracks.
Within the Project, there may be ''multiple timelines'', to be viewed and rendered independently. But, being the top-level entities, multiple timelines may not be combined further. You can always just render (or view) one specific timeline. A given sequence may be referred directly or indirectly from multiple timelines though.
''Note'': in early drafts of the design (2007) there was an entity called &quot;Timeline&quot; within the [[Fixture]]. This entity seems superfluous and has been dropped. It never got any relevance in existing code and at most was used in some code comments.
!Façade and implementation
Actually, Timeline is both an interface and acts as façade. Its an interface, because we'll need &quot;timeline views&quot; ({{red{really? is that a reason to create a hierarchy right here, or shouldn't that be rather conceptual?}}}. It is a façade to the raw structures in the model, in this case a {{{Placement&lt;BindingMO&gt;}}} attached immediately below the [[root scope|ModelRootMO]]. The implementation of the timeline(s) is maintained as StructAsset within the AssetManager, managed by shared-ptr. It always depends on a [[Sequence]], which might be created automatically as empty container when referring to a timeline.
Besides building on the asset management, implementing Timeline (and Sequence) as StructAsset yields another benefit: ~StructAssets can be retrieved by query, allowing to specify more details of the configuration immediately on creation. //But on the short term, this approach causes problems:// there is no real inference engine integrated into Lumiera yet (as of 2/2010 the plan is to get an early alpha working end to end first). For now we're bound to use the {{{fake-configrules}}} and to rely on a hard wired simulation of the intended behaviour of a real query resolution. Just some special magic queries will work for now, but that's enough to get ahead.
</pre>
</div>
<div title="TimelineSequences" modifier="Ichthyostega" modified="201003160202" created="200811011836" tags="design decision img" changecount="15">
<pre>There is a three-level hierarchy: [[Project|Session]], [[Timeline]], [[Sequence]]. Each project can contain ''multiple timelines'', to be viewed and rendered independently. But, being the top-level entities, these timelines may not be combined further. You can always just render (or view) one specific timeline. Each of those timelines refers to a Sequence, which is a bunch of [[media objects|MObject]] placed to a tree of [[tracks|Track]]. Of course it is possible to use ~sub-sequences within the top-level sequence within a timeline to organize a movie into several scenes or chapters.
[&gt;img[Relation of Timelines, Sequences and MObjects within the Project|uml/fig132741.png]]
As stated in the [[definition|Timeline]], a timeline refers to exactly one sequence, and the latter defines a tree of [[tracks|Track]] and a bunch of media objects placed to these tracks. A Sequence may optionally also contain nested sequences as [[meta-clips|VirtualClip]]. Moreover, obviously several timelines (top-level entities) may refer to the same Sequence without problems.
This is because the top-level entities (Timelines) are not permitted to be combined further. You may play or render a given timeline, you may even play several timelines simultaneously in different monitor windows, and these different timelines may incorporate the same sequence in a different way. The Sequence just defines the relations between some objects and may be placed relatively to another object (clip, label,...) or similar reference point, or even anchored at an absolute time if desired. In a similar open fashion, within the track-tree of a sequence, we may define a specific signal routing, or we may just fall back to automatic output wiring.
!Attaching output
The Timeline owns a list of global [[pipes (busses)|Pipe]] which are used to collect output. If the track tree of a sequence doesn't contain specific routing advice, then connections will be done directly to these global pipes in order and by matching StreamType (i.e. typically video to video master, audio to stereo audio master). When a monitor (viewer window) is attached to this timeline, similar output connections are made from those global pipes, i.e. the video display will take the contents of the first video (master) bus, and the first stereo audio pipe will be pulled and sent to system audio out. The timeline owns a ''play control'' shared by all attached viewers and coordinating the rendering-for-viewing. Similarly, a render task may be attached to the timeline to pull the pipes needed for a given kind of generated output. The actual implementation of the play controller and the coordination of render tasks is located in the Backend, which uses the service of the Proc-Layer to pull the respective exit nodes of the render engine network.
!Timeline versus Timeline View
Actually, what the GUI creates and uses is the //view// of a given timeline. This makes no difference to start with, as the view is modelled to be a sub-concept of &quot;timeline&quot; and thus can stand-in. All different views of the //same// timeline also share one single play control instance, i.e. they all have one single playhead position. Doing it this way should be the default, because it's the least confusing. Anyway, it's also possible to create multiple //independent timelines// &amp;mdash; in an extreme case even so when referring to the same top-level sequence. This configuration gives the ability to play the same arrangement in parallel with multiple independent play controllers (and thus independent playhead positions)
To complement this possibilities, I'd propose to give the //timeline view// the possibility to be re-linked to a sub-sequence. This way, it would stay connected to the main play control, but at the same time show a sub-sequence //in the way it will be treated as embedded// within the top-level sequence. This would be the default operation mode when a meta-clip is opened (and showed in a separate tab with such a linked timeline view). The reason for this proposed handling is again to give the user the least surprising behaviour. Because, when &amp;mdash; on the contrary &amp;mdash; the sub-sequence would be opened as //separate timeline,// a different absolute time position and a different signal routing may result; doing such should be reserved for advanced use, e.g. when multiple editors cooperate on a single project and a sequence has to be prepared in isolation prior to being integrated in the global sequence (featuring the whole movie).
</pre>
</div>
<div title="Track" modifier="Ichthyostega" modified="200911202133" created="200801062320" tags="def design decision" changecount="6">
<pre>Tracks are just a structure used to organize the Media Objects within the Sequence. Tracks are associated allways to a specific Sequence and the Tracks of an Sequence form a //tree.// They can be considered to be an organizing grid, and besides that, they have no special meaning. They are grouping devices, not first-class entities. A track doesn't &quot;have&quot; a port or pipe or &quot;is&quot; a video track and the like; it can be configured to behave in such manner by using placements.
The ~Track-IDs are assets on their own, but they can be found within a given sequence. So, several sequences can share a single track or each sequence can hold tracks with their own, separate identity. (the latter is the default)
* Like most ~MObjects, tracks have a asset view: you can find a track asset (a track ID) in the asset manager.
* and they have an object view: there is an track MObject which can be [[placed|Placement]], thus defining properties of this track within one sequence, e.g. the starting point in time
Of course, we can place other ~MObjects relative to some track (that's the main reason why we want to have tracks). In this sense, the [[handling of Tracks|TrackHandling]] is somewhat special: the placements forming the tree of tracks can be accessed directly through the sequence, and a track acts as container, forming a scope to encompass all the objects &quot;on&quot; this track. Thus, the placement of a track defines properties of the track, which will be inherited (if necessary) by all ~MObjects placed to this track. For example, if placing (=plugging) a track to some global [[Pipe]], and if placing a clip to this track, without placing the clip directly to another pipe, the associated-to-pipe information of the track will be fetched by the builder when needed to make the output connection of the clip.
&amp;rarr; [[Handling of Tracks|TrackHandling]]
&amp;rarr; [[Handling of Pipes|PipeHandling]]
&amp;rarr; [[Anatomy of the high-level model|HighLevelModel]]
</pre>
</div>
<div title="TrackHandling" modifier="Ichthyostega" modified="201003070510" created="200804110013" tags="spec" changecount="35">
<pre>What //exactly&amp;nbsp;// is denoted by &amp;raquo;Track&amp;laquo; &amp;mdash; //basically&amp;nbsp;// a working area to group media objects placed at this track at various time positions &amp;mdash; varies depending on context:
* viewed as [[structural asset|StructAsset]], tracks are nothing but global identifiers (possibly with attached tags and description)
* regarding the structure //within each [[Sequence]],// tracks form a tree-like grid, the individual track being attached to this tree by a [[Placement]], thus setting up properties of placement (time reference origin, output connection, layer, pan) which will be inherited down to any objects located on this track and on child tracks, if not overridden more locally.
* with respect to //object identity,// a given track-ID can have an incarnation or manifestation as real track-object within several [[sequences|Sequence]] (meaning you could select, disable or choose for rendering all objects in any sequence placed onto this track). Moreover, the track-//object// and the //placement&amp;nbsp;// of this track within the tree of tracks of a given sequence are two distinguishable entities (meaning a given track &amp;mdash; with a couple of objects located on it &amp;mdash; could be placed differently several times within the same sequence {{red{really??}}}, for example with different start offset or with different layering, output mode or pan position)
{{red{3/2010: no!}}} &amp;mdash; it doesn't work this way. Relative placements attach to other placements, not ~MObjects. Thuse the clips in this example are within the scope of //one// placement. We could create //another// placement of the same track and attach it to to a differenc sequence, but this other placement wouldn't &quot;inherit&quot; the clips attached to the first one!
!Identification
Tracks thus represent a blend of several concepts, but depending on the context it is allways clear which aspect is meant. Seen as [[assets|Asset]], tracks are known by a unique track-ID, which can be either [[queried|ConfigQuery]], or directly refered to by use of the asset-ID (which is a globally known hash). Usually, all referrals are via track-ID, including when you [[place|Placement]] an object onto a track.
Under some cincumstances though, especially from within the [[Builder]], we refer to a {{{Placement&lt;Track&gt;}}} rather, denoting a specific instantiation located at a distinct node within the tree of tracks of a given sequence. These latter referrals are always done by direct object reference, e.g. while traversing the track tree (generally there is no way to refer to a placement by name {{red{Note 3/2010 meanwhile we have placementIDs and we have ~MObjectRef. TODO better wording}}}).
!creating tracks
Similar to [[pipes|Pipe]] and [[processing patterns|ProcPatt]], track-assets need not be created, but rather leap into existence on first referral. On the contrary, you need to explicitly create the {{{Placement&lt;Track&gt;}}} for attaching it to some node within the tree of tracks of an sequence. The public access point for creating such a placement is {{{MObject::create(trackID}}} (i.e. the ~MObjectFactory. Here, the {{{trackID}}} denotes the track-asset. This placement, as returned from the ~MObjectFactory isn't attached to the session yet; {{{Session::current-&gt;attach(trackPlacement)}}} performs this step by creating a copy managed by the PlacementIndex and attaching it at the current QueryFocus, which was assumed to point to a track previously. Usually, client code would use one of the provided convenience shortcuts for this rather involved call sequence:
* the interface of the track-~MObjects exposes a function for adding new child tracks.
* the session API contains a function to attach child tracks. In both cases, existing tracks can be referred by plain textual ID.
* any MObjectRef to an object within the session allows to attach another placement or ~MObjectRef
!removal
Deleting a Track is an operation with drastic consequences, as it will cause the removal of all child tracks and the deletion of //all object placements to this track,// which could cause the resepctive objects to go out of scope (being deleted automatically by the placements or other smart pointer classes in charge of them). If the removed track was the root track of a sequence, this sequence and any timeline or VirtualClip binding to it will be killed as well. Deleting of objects can be achieved by {{{MObjectRef::purge()}}} or {{{Session::purge(MObjectRef)}}}
!using Tracks
The '''Track Asset''' is a rather static object with limited capabilities. It's main purpose is to be a point of referral. Track assets have a description field and you may assign a list of [[tags|Tag]] to them (which could be used for binding ConfigRules). Note that track assets are globally known within the session, they can't be limited to just one [[Sequence]] (but you are allways free not to refer to some track from a given sequence). By virtue of this global nature, you can utilize the track assets to enable/disable a bunch of objects irrespective of what sequence they are located in, and probably it's a good idea to allow the selection of specific tracks for rendering.
Matters are quite different for the placement of a Track within the tree of tracks of a given sequence, and for placing some media object onto a given track. The track placement defines properties which will be inherited to all objects on this track and on all child tracks and thus plays a key role for wiring the objects up to some output pipe. Typically, the top level track of each sequence has a placement-to &quot;the&quot; video and &quot;the&quot; audio master pipe.
!!!!details to note
* Tracks are global, but the placement of a track is local within one sequence
* when objects are placed onto a track, this is done by referal to the global track asset ID. But because this placement of some media object is allways inherently contained within one sequence, the //meaning&amp;nbsp;// of such a placement is to connect to the properties of any track-placement of this given track //within this sequence.//
* thus tracks-as-ID appear as something global, but tracks-as-propperty-carrier appear to the user as something local and object-like.
* in an extreme case, you'll add two different placements of a track at different points within the track tree of an sequence. And because the objects placed onto a track refer to the global track-ID, every object &quot;on&quot; this track //within this sequence&amp;nbsp;// will show up two times independently and possibly with different inherited properties (output pipe, layering mode, pan, temporal position)
* an interesting configuration results from the fact that you can use an sequence as a [[&quot;meta clip&quot; or &quot;virtual clip&quot;|VirtualClip]] nested within another sequence. In this case, you'll probably configure the tracks of the &quot;inner&quot; sequence such as to send their output not to a global pipe but rather to the [[source ports|ClipSourcePort]] of the virtual clip (which are effectively local pipes). Thus, within the &quot;outer&quot; sequence, you could attach effects to the virutal clip, combine it with transitions and place it onto another track, and any missing properties of this latter placement are to be resolved within the &quot;outer&quot; sequence &lt;br/&gt;(it would be perfectly legal to construct a contrieved example when using the same track-ID within &quot;inner&quot; and the &quot;outer&quot; sequence. Because the Placement of this track will probably be different in the both sequences, the behaviour of this placement could be quite different in the &quot;inner&quot; and the &quot;outer&quot; sequence. All of this may seem weird when discussed here in a textual and logical manner, but when viewed within the context and meaning of the various entities of the application, it's rather the way you'd expect it to be: you work locally and things behave as defined locally)
* note further, the root of the tree of tracks within each sequence //is itself again a //{{{Placement&lt;Track&gt;}}}. There is no necessitiy for doing it this way, but it seemed more stright forward and logical to Ichthyo, as it allowes for an easy way of configuring some things (like ouput connections) as a default within one sequence. As every track can have a list of child tracks, you'll get the &quot;list of tracks&quot; you'd expect.
* a nice consequence of the latter is: if you create a new sequence, it automatically gets one top-level track to start with, and this track will get a default configured placement (according to what is defined as [[default|DefaultsManagement]] within the current ConfigRules) &amp;mdash; typically starting at t=0 and being plugged into the master video and master audio pipe
* nothing prevents us from putting several objects at the same temporal location within one track. If the builder can't derive any additional layering information (which could be provided by some other configuration rules), then //there is no layering precedence// &amp;mdash; simply the object encountered first (or last) wins.
* obviously, one wants the __edit function__ used to create such an overlapping placement&amp;nbsp; also to create an [[transition|TransitionsHandling]] between the overlapping objects. Meaning this edit function will automatically create an transition processor object and provide it with a placement such as to attach it to the region of overlap.
</pre>
</div>
<div title="TrackPipeSequence" modifier="Ichthyostega" modified="201003160201" created="200711300405" tags="design def decision Builder rewrite" changecount="28">
<pre>''towards a definition of »Track«''. We don't want to tie ourself to some naive and overly simplistic definition, just because it is convenient. For classical (analogue) media, tracks are physical entities dictated by the nature of the process by which the media works. Especially, Tape machines have read/writing heads, which creates fixed tracks to which to route the signals. This is a practical geometric necessity. For digital media, there is no such necessity. We are bound primarily by the editor's habits of working.
!!!Assessment of Properties
Media are used as Clips (contiguous chunks), they are a compound of several elementary streams, and they have a temporal extension (length). Indeed, the temporal dimension is the only fundamental property that can't be changed. Orthogonal to this dimension, we find one or more organisational dimensions forming a grid:
* a media stream may be sent to one of several possible output destinations (image or sound, subgroup busses, MIDI instruments)
* for any given output destination there may be variations in the //way of connecting// (overlay mode and layer, pan position, MIDI channel)
* besides, we often just want to stash away some clip without using it, e.g. as an alternative or for later referral
This is to say we have //several degrees of freedom// within this organisational grid. Just because some sound is located on this track doesn't mean he will be sent to a given output, rather the clip is located on this track //and// is connected to that output //and// &amp;mdash; supposed we have full-periphonic surround sound &amp;mdash; it is located 60° to the right and with 30° elevation. Combined with the (always contiguous) temporal dimension, this discrete grid is thus extruded to form something like discrete Tracks.
!!!do we really need Tracks?
Starting with the assumption &quot;everything is just connected processing nodes&quot;, Tracks may seem superfluous. The problem with this approach is: it doesn't scale well. While it is fine to be able to connect clips and effects as we see fit (indeed, we want to build such a system), it is clearly not feasible to wire every clip manually to the output ports or add a panner effect to each and every audio sample. Because while editing, most of the time things are done in a fairly regular and systematic manner. Preferably we use the tracks as //preconfigured group setup// and just //place media onto them;// any such [[Placement]] can do the necessary wiring semi-automatic (rule-based).
!!!the constant part
there seems to be some non time-varying part in each sequence, that doesn't fit well with the simple model &quot;objects on a timeline&quot;. Tracks seen as an organisational grid fall into this category: they are a global property of the given sequence. They could be associated to the Session as a whole, but effectively this would subvert the concept of having [[several sequences|SessionOverview]]. On the other hand,
[[pipes|Pipe]] for Video and Sound output are obviously a global property of the Session. There can be several global pipes forming a matrix of subgroup busses. We could add ports or pipes to tracks by default as well, but we don't do this, because, again, this would run counter to our attempt of treating tracks as merely organisational entities. We have special [[source ports|ClipSourcePort]] on individual clips though, and we will have ports on [[virtual clips|VirtualClip]] too.
!Design
[[Tracks|Track]] are just a structure used to organize the Media Objects within the session. They form a grid, and besides that, they have no special meaning. It seems convenient to make the tracks not just a list, but allow grouping (tree structure) right from start. __~MObjects__ are ''placed'' rather than wired. The wiring is derived from the __Placement__. Placing can happen in several dimensions:
* placing in time will define when to activate and show the object.
* placing onto a track associates the ~MObject with this track; the GUI will show it on this track and the track may be used to resolve other properties of the object.
* placing to a __Pipe__ brings the object in conjunction with this pipe for the build process. It will be considered when building the render network for this pipe. Source-like objects (clips and exit nodes of effect chains) will be connected to the pipe, while transforming objects (effects) are inserted at the pipe. (you may read &quot;placed to pipe X&quot; as &quot;plug into pipe X&quot;)
* depending on the nature of the pipe and the source, placing to some pipe may create additional degrees of freedom, demanding the object to be placed in this new, additional dimensions: Connecting to video out e.g. creates an overlay mode and a layer position which need to be specified, while connecting to a spatial sound system creates the necessity of a pan position. On the other hand, placing a mono clip onto a mono Pipe creates no additional degrees of freedom.
Placements are __resolved__ resulting in an ExplicitPlacement. In most cases this is just a gathering of properties, but as Placements can be incomplete and relative, there is room for real solving. The resolving mechanism tries to __derive missing properties__ from the __context__: When a clip isn't placed to some pipe but to a Track, than the Track and its parents will be inspected. If some of them has been placed to a pipe, the object will be connected to this pipe. Similar for layers and pan position. This is done by [[Placement]] and LocatingPin; as the [[Builder]] uses ~ExplicitPlacements, he isn't concerned with this resolving and uses just the data they deliver to drive the [[basic building operations|BasicBuildingOperations]]
&amp;rarr; [[Definition|Track]] and [[handling of Tracks|TrackHandling]]
&amp;rarr; [[Definition|Pipe]] and [[handling of Pipes|PipeHandling]]
</pre>
</div>
<div title="TransitionsHandling" modifier="Ichthyostega" modified="200805300100" created="200712080417" tags="def design" changecount="3">
<pre>Transitions combine the data from at least two processing chains and do this combining in a time varying fashion. So, any transition has
* N input connections
* either one or N output connections
* temporal coordinates (time, length)
* some control data connection to a ParamProvider, because in the most general case the controling curves are treated similar to [[automation data|AutomationData]]
!!!how much output ports?
The standard case of a transition is sort of mixing together two input streams, like e.g. a simple dissolve. For this to be of any use, this input streams need to be connected to the same ouput destination before and after the transition (with regards to the timeline), i.e. the inputs and the transition share placement to the same output pipe. In this case, when the transition starts, the direct connections can be suspended and the transition will switch in seamlessly.
Using transitions is a very basic task and thus needs viable support by the GUI. Handling of transitions need to be very convienient, because it is so important. Because of this, it is compelling to subsume a more complicated situation and treat this more complicated case similar. This is the case, when two (or N) elements have to be combined in the way of a transition, but their further processing in the processing chain //after// the transition needs to be different, maybe they belong to differnent subgroups, have to appear on different layers or with different pan positions. Of courese, the workaround is simple, at least &quot;simple&quot; from the programmers point of view. It is //not// simple from the editor's point of view the moment the editor has to do this simple thing (changing the wiring and add manualy synced fade curves to the individual parts) a dozend or even a hundred times in some larger project.
Because of this experience, ichthyo wants to support a more general case of transitions, which have N output connections, behave similar to their &quot;simple&quot; counterpart, but leave out the mixing step. As a plus, such transitions can be inserted at the source ports of N clips or between any intermediary or final output pipes as well. Any transition processor capable of handling this situation should provide some flag, in order to decide if he can be placed in such a manner. (wichin the builder, encountering a inconsistently placed transition is just an [[building error|BuildingError]])
</pre>
</div>
<div title="TypedID" modifier="Ichthyostega" modified="201004040114" created="201003200157" tags="Model Rules design draft" changecount="31">
<pre>//drafted service as of 4/10 &amp;mdash; &amp;rarr;[[implementation plans|TypedLookup]]//
A registration service to associate object identities, symbolic identifiers and types.
!Motivation
For maintaining persistent objects, generally an unique object ID is desirable. Within Lumiera, we employ 128 hash-~IDs (&amp;raquo;{{{LUID}}}&amp;laquo;). But hash-~IDs are difficult to handle for testing and configuration, as they aren't self explanatory for human readers. They're best used in a way avoiding textual representation.
Formal symmetry might be another motivation: we separate into objects and assets, where the latter represent the //bookkeeping view.// But there remain some cases where the asset side, the bookkeeping is void of any substantial functionality. Just for the sake of orthogonality it //would be preferable//&amp;nbsp; to maintain a registration (for scripted use, for overview and organisational activities by the user, for diagnostics and repair work in the saved session state). A mere data record suffices to fulfil these requirements &amp;mdash; while there are other kinds of asset exposing a substantial amount of functionality.
Both these motivations highlight a tension between having a single global namespace of unique ~IDs, and having type-bound sub namespaces of limited scope, as the latter allows for using human readable symbolic ~IDs. Usually, when it comes to writing rules and configuration or creating instances explicitly from code, the specific context of the situation allows or even requires to focus down upon a single and distinct type of objects immediately &amp;mdash; global uniqueness is not much of a concern here.
!desired functionality
A registration service backed by an index table can be used to //translate//&amp;nbsp; between these two seemingly contradictory usage view angles.
* re-accessing the specific type to deal with data known just by unique object ID.
* retrieving the unique implementation instance, given just a symbolic ID and the type to use.
* ensuring uniqueness of symbolic ~IDs //within one kind of entities.//
* enumerating all instances of a specific kind
* ability to handle instance registration and de-registration automatically
!!!usage scenarios
* automatically maintained lists of all clips, labels, tracks and sequences in the &amp;raquo;Asset&amp;laquo; section of the application
* addressing objects in script driven operations, just based on symbolic names, allowing for additional conventions
* implementing a predicate {{{isTypeXXX(Element)}}}, which is crucial for [[rules based configuration|ConfigQuery]].
!!!{{red{WIP}}}Analysis and discussion
Still some contradictions &amp;mdash; and rather seems helpful, not so much necessary.
We //already have an registration service,// both for Assets (AssetManager) and for Placements (PlacementIndex). These facilities maintain not only a raw ID &amp;harr; object association, but also structuring informations, albeit bound to more specific circumstances (the system of placement scopes, and the asset category). The lookup uniqueID &amp;rArr; object could be implemented by sequentially querying this small number of central registration facilities. Thus, still lacking is a ''system of sub index tables''.
As mentioned above, an ID &amp;harr; type association plays a crucial role when it comes to implementing any kind of rules based configuration. It would allow to bridge from our session objects to rules and resolution working entirely symbolic. (&amp;rarr; [[more|ConfigQueryIntegration]]). But, as of 3/2010 this is a //planned feature and not required to get the initial pipeline working.// Thus, according to the YAGNI principle, we shouldn't engage into any implementation details right now and just create the extension points.
The immediate need prompting to start development on this facility, is how to get sub-selections from the objects in the session and for certain kinds of asset &amp;mdash; especially how to deal with retrieving the referred track for the &amp;rarr; [[sequence and timeline handling|ModelDependencies]].
&lt;&lt;&lt;
So the plan is to use ''per type'' mapping tables for an association: ''symbolic-ID &amp;rarr; unique-ID''
There should be a configurable slot to ''attach an object reference'' &amp;mdash; extensions to be defined later
Just an ''registration scheme'' should be implemented right now, working completely automatic
&lt;&lt;&lt;
!!!!notes
* //code bloat// &amp;mdash; need a frontend and an untyped backend for the raw storage
* one table or many tables?
** obviously, one table is more space efficient
** but it would be redundant (both PlacementIndex and AssetManager //are// implemented with a large hashtable)
** moreover, ennumeration of all elements of a specific kind was one of the reasons to build this facility
* supporting 1:n ? (e.g. one track-ID for many track objects?). Rather not directly, because this defeats lookup by ID
see [[implementation planning|TypedLookup]]
</pre>
</div>
<div title="TypedLookup" modifier="Ichthyostega" modified="201004031616" created="201004031607" tags="Rules spec impl draft" changecount="14">
<pre>TypedID is a registration service to associate object identities, symbolic identifiers and types. It acts as frontend to the TypedLookup service within Proc-Layer, at the implementation level. While TypedID works within a strictly typed context, this type information is translated into an internal index on passing over to the implementation, which manages a set of tables containing base entries with an combined symbolic+hash ID, plus an opaque buffer. Thus, the strictly typed context is required to re-access the stored data. But the type information wasn't erased entirely, so this typed context can be re-gained with the help of an internal type index. All of this is considered implementation detail and may be subject to change without further notice; any access is assumed to happen through the TypedID frontend. Besides, there are two more specialised frontends.
!Front-ends
* TypedID uses static but templated access functions, plus an singleton instance to manage a ~PImpl pointing to the ~TypedLookup table
* the individual //registration groups// (see below) are automatically exposed as MetaAsset.
* TypeHandler is heavily used by the ConfigRules {{red{planned}}}; each participating primary type provides an implementation
!Tables and index
The Table consists of several registration groups, each of which contains a hashtable and deals with one specific type. Groups are created on demand, but there is initially one group holding the internal type index (translation table). There may be even sub-groups, usable to create clusterings within one group.
__Individual entries__ are comprised of a ''~EntryID'' as key (actually a ~BareEntryID, without the typing) and a payload, which //doesn't store data,// but informations necessary to ''lookup and access'' the registered object. Obviously, this information is type specific, and thus the ~TypedLookup implementation can't know how to deal with it directly. Rather, we store a ''functor'' in the payload of the type index group. This functor is directly linked to the TypeHandler, i.e. any type wanting to be a primary type within Lumiera, so as to be directly usable within the ConfigRules, needs to provide a suitable functor implementation through its ~TypeHandler. These functors are then invoked by the ~TypedID frontend, when it comes to re-accessing an registered entity by ID
!link for automatic registration
An entity can be linked with the TypedLookup system to be registered and deregistered automatically. This is achieved by mixing in the {{{TypedID::Link}}}. On creation, this will set up an EntryID for this individual instance and cause creation of an empty entry within the suitable registration group. As a side-effect, uniqueness of any symbolic-ID within one group (type) is enforced. Obviously, the dtor of this registration Link cares for de-registration automatically. Be forwarned though, by creating an unique identity, this mechanism will interfere with copying and cloning of the registered entity.
In most cases, the //actually usable instance// of an entity isn't identical to a implementation class (language) instance. Typically, there is a frontend, a smart-ptr, reference or similar, which in turn might link to another registration mechanism. This ''actual instance tag'' needs to be attached deliberately through the {{{TypedID::Link}}} to be stored into the payload of the registration group table entry. This involves invocation of the functor provided by the TypeHandler.
!basic usage patterns
* ''Assets'' are maintained by the AssetManager, which always holds a smart-ptr to the managed asset. Assets include explicit links to dependent assets. Thus, there is no point in interfering with lifecylce management, so we store just a ''weak reference'' here, which the access functor turns back into a smart-ptr, sharing ownership.
* Plain ''~MObjects'' are somewhat similar, but there is no active lifecycle management &amp;mdash; they are always tied either to a placement of linked from within the assets or render processes. When done, they just go out of scope. Thus we too use a ''weak reference'' here, assumed the respective entity has mixed in {{{TypedID::Link}}}
* Active ''Placements'' of an MObject behave like //object instances// within the model/session. They live within the PlacementIndex and cary an unique {{{LUID}}}-ID. Thus, it's sufficient to store this ''Placement-ID'', which can be used by the access functor to fetch the corresponding Placement from the session.
Obviously, the ~TypedLookup system is open for addition of completely separate and different types.
|&gt;| !Entity |!pattern |
|1:1| Track|Placement|
|~| Label|Placement|
|~| Sequence|Asset|
|~| StreamType|Asset|
|1:n| Tag|Asset|
|~| Clip|~MObject|
|~| Effect| ~MObejct|
|~| Transition| Asset(?)|
</pre>
</div>
<div title="TypedQueryProblem" modifier="Ichthyostega" modified="200910301629" created="200910231618" tags="Rules operational" changecount="10">
<pre>//the problem of processing specifically typed queries without tying query and QueryResolver.//&lt;br/&gt;This problem can be seen as a instance of the problematic situation encountered with most visitation schemes: we want entities and tools (visitors) to work together, without being forced to commit to a pre-defined table of operations. In the situation at hand here, we want //some entities// &amp;mdash; which we don't know &amp;mdash; to issue //some queries// &amp;mdash; which we don't know either. Obviously, such a problem can be solved only by controlling the scope of this incomplete knowledge, and the sequence in time of building it.
Thus, to re-state the problem more specifically, we want the //definition//&amp;nbsp; of the entities to be completely separate of those definitions concerning the details of the query resolution mechanism, so to be able to design, reason, verify and extend each one without being forced into concern about the details of the respective other side. So, the buildup of the combined structure has to be postponed &amp;mdash; assuring it //has//&amp;nbsp; happened at the point it's operations are required.
!Solution Stages
* in ''static scope'' (while compiling), just a {{{Query&lt;TY&gt;}}} gets mentioned.&lt;br/&gt;As this is the only chance for driving a specifically typed context, we need to prepare a registration mechanism, to allow for //remembering//&amp;nbsp; this context later.
* at the same time, but otherwise independently, compilation needs to drive the generation of possible query resolution mechanisms. As C++ only allows very limited influence on the compilation process, which is always based on a common visibility scope, and on the other hand doesn't allow to re-access the static level after the fact (no language-rooted introspection), we're forced to use an external tool, or to rely on manual specification. As we're building a rather specialised facility, not a general purpose library, the latter seems adequate, if done close to where the actual query resolution mechanism is implemented.
* during ''initialisation'', a complete list of all specifically typed scopes needs to be collected. This might be a flat list, but may well become structured in multiple dimensions later, when using multiple //kinds of query.// It is important to notice that this buildup of a complete list won't happen, unless triggered explicitly, so we need registration into a system lifecycle hook.
* on ''first use'' (which me must ensure to be //after//&amp;nbsp; initialisation) the list-of-typed queries will be hooked into the query resolving facility to build a dispatcher table there.
* the ''actual query'' just picks the respective type-ID for accessing the dispatcher table, making for a total of //two//&amp;nbsp; function-pointer indirections.
</pre>
</div>
<div title="VirtualClip" modifier="Ichthyostega" modified="201003130000" created="200804110321" tags="def" changecount="15">
<pre>A ''~Meta-Clip'' or ''Virtual Clip'' (both are synonymous) denotes a clip which doesn't just pull media streams out of a source media asset, but rather provides the results of rendering a complete sub-network. In all other respects it behaves exactly like a &quot;real&quot; clip, i.e. it has [[source ports|ClipSourcePort]], can have attached effects (thus forming a local render pipe) and can be placed and combined with other clips. Depending on what is wired to the source ports, we get two flavours:
* a __placeholder clip__ has no &quot;embedded&quot; content. Rather, by virtue of placements and wiring requests, the output of some other pipe somewhere in the session will be wired to the clip's source ports. Thus, pulling data from this clip will effectively pull from these source pipes wired to it.
* a __nested sequence__ is like the other sequences in the Session, just in this case any missing placement properties will be derived from the Virtual Clip, which is thought as to &quot;contain&quot; the objects of the nested sequence. Typically, this also [[configures the tracks|TrackHandling]] of the &quot;inner&quot; sequence such as to connect any output to the source ports of the Virtual Clip.
Like any &quot;real&quot; clip, Virtual Clips have a start offset and a length, which will simply translate into an offset of the frame number pulled from the Virtual Clip's source connection or embedded sequence, making it possible to cut, splice, trim and roll them as usual. This of course implies we can have several instances of the same virtual clip with different start offset and length placed differently. The only limitation is that we can't handle cyclic dependencies for pulling data (which has to be detected and flagged as an error by the builder)
</pre>
</div>
<div title="VisitingToolImpl" modifier="Ichthyostega" modified="200802031822" created="200801032003" tags="impl excludeMissing" changecount="21">
<pre>The ''Visitor Pattern'' is a special form of //double dispatch// &amp;mdash; selecting the function actually to be executed at runtime based both on the concrete type of some tool object //and // the target this tool is applied to. The rationale is to separate some specific implementation details from the basic infrastructure and the global interfaces, which can be limited to describe the fundamental properties and operations, while all details relevant only for some specific sub-problem can be kept together encapsulated in a tool implementation class. Typically, there is some iteration mechanism, allowing to apply these tools to all objects in a given container, a collection or object graph, without knowing the exact type of the target and tool objects. See the [[Visitor Pattern design discussion|VisitorUse]]
!Problems with Visitor
The visitor pattern is not very popular, because any implementation is tricky, difficult to understand and often puts quite some burden on the user code. Even Erich Gamma says that on his list of bottom-ten patterns, Visitor is at the very bottom. This may be due to the fact that this original visitor implementation (often called the ''~GoF visitor'') causes a cyclic dependency between the target objects and the visiting tool objects, and includes some repetitive code (which results in silent failure if forgotten). Robert Martin invented 1996 an interesting variation (commonly labled ''acyclic visitor''). By using a marker base class for each concrete target type to be treated by the visitor, and by applying a dynamic cast, we can get rid of the cyclic dependencies. The top level &quot;Visitor&quot; is reduced to a mere empty marker interface in this design, while the actual visiting capabilities for a concrete target object is discovered at application time by the aforementioned dynamic cast. Besides the runntime cost of such a cast, the catch is that now the user code has still more responsibilities, because the need to maintain consistently two parallel object hierarchies.
Long time there seemed to be not much room for improvement, at least before the advent of generic programming and the discovery of template metaprogramming. ''Loki'' (Alexandrescu, 2000) showed us how to write a library implementation which hides away the technicallities of the visitor pattern and automatically generates most of the repetitive code.
!Requirements
* cyclic dependencies should be avoided or at least restricted to some central, library related place.
* The responsibilities for user code should be as small as possible. Especially, we should minimize the necessity to do corresponding adjustments to separate code locations, e.g. the necessity to maintain parallel hierarchies.
* Visitor is about //double dispatch,// thus we can't avoid using some table lookup implementation &amp;mdash; more specifically we can't avoid using the cooperating classes vtables. We can expect at least two table lookups for each call dispatch. Besides that, the implementation should not be too wasteful...
* individual &quot;visiting tool&quot; implementation classes should be able to opt in or opt out on implementing functions treating some of the visitable subclasses.
* there should be a safe fallback mechanism backed by the visitable object's hierarchy relations. If some concrete visiting tool class decides not to implement a {{{treat(..)}}}-function for some concrete target type, the call should fall back to the next best match according to the target object's hierarchy, i.e. the next best {{{treat(..)}}}-function should be used.
The last requirement practically rules out the Loki acyclic visitor, because this implementation calls a general fallback function when an exact match based on the target object's type is not possible. Considering our use of the visitor pattern within the render engine builder, such a solution would not be of much use: Some specific builder tool may implement a {{{treat(CompoundClip&amp;)}}}-function, while most of the other builder tools just implement a {{{treat(Clip&amp;)}}}-function, thus handling any multichannel clip via the general clip interface. This is exactly the reason why we want to use visitor at first place. Isolate specific treatment, implement against interfaces.
!Implementation Notes
A good starting point for understanding our library implementation of the visitor pattern is {{{tests/components/common/visitingtoolconcept.cpp}}}, which contains a all-in-one-file proof of concept implementation, on which the real implementation ({{{&quot;common/visitor.hpp&quot;}}}) was based.
* similar to Loki, we use a {{{Visitable}}} base class and a {{{Tool}}} base class (we prefer the name &quot;Tool&quot; over &quot;Visitor&quot;, because it makes the intended use more clear).
* the objects in the {{{Visitable}}} hierarchy implement an {{{apply(Tool&amp;)}}}-function. This function needs to be implemented in a very specific manner, thus the {{{DEFINE_PROCESSABLE_BY}}} macro should be used when possible to insert the definition into a concrete {{{Visitable}}} class.
* similar to the acyclic visitor, the concrete visiting tool classes inherit from {{{Applicable&lt;TARGET, ...&gt;}}} marker base classes, where the template parameter {{{TARGET}}} is the concrete Visitable type this tool wants to treat, either directly by defining a {{{treat(ConcreteVisitable&amp;)}}}, or by falling back to some more general {{{treat(...)}}} function.
* consequently our implementation is //rather not acyclic// &amp;mdash; the concrete tool implementation depends on the full definition (header) of all concrete Visitables, but we avoid cyclic dependencies on the interface level. By using a typelist technique inspired by Loki, we can concentrate these dependencies in one single header file, which keeps things maintainable.
* we use the {{{Applicable&lt;TARGET, ...&gt;}}} marker base classes to drive the generation of Dispatcher classes, each of which holds a table of trampoline functions for carrying out the actual double dispatch at call time. Each concrete Visitable using the {{{DEFINE_PROCESSABLE_BY}}}-macro generates a separate Dispatcher table containing slots for each concrete tool implementation class. To store and access the index position for these &quot;call slots&quot;, we use a tag associated with the concrete visiting tool class, which can be retrieved by going though the tool's vtable
* __runtime cost__: the concrete tool's ctor stores the trampoline pointers (this could be optimized to be a &quot;once per class&quot; initialisation). Then, for each call, we have 2 virtual function calls and a lookup and call of the trampoline function, typically resulting in another virtual function call for resolving the {{{treat(..)}}} function on the concrete tool class
* __extension possibilities__: while this system may seem complicated, you should note that it was designed with special focus on extension and implementing against interfaces:
** not every Visitable subclass needs a separate Dispatcher. As a rule of thumb, only when some type needs to be treated separately within some concrete visiting tool (i.e. when there is the need of a {{{treat(MySpecialVisitable&amp;)}}}), then this class should use the {{{DEFINE_PROCESSABLE_BY}}}-macro and thus define it's own {{{apply()}}}-function and Dispatcher. In all other cases, it is sufficient just to extend some existing Visitable, which thus will act as an interface with regards to the visiting tools.
** because the possibility of utilizing virtual {{{treat(...)}}} functions, not every concrete visiting tool class needs to define a set of {{{Applicable&lt;...&gt;}}} base classes (and thus get a separate dispatcher slot). We need such only for each //unique set// of Applicables. All other concrete tools can extend existing tool implementations, sharing and partially extending the same set of virtual {{{treat()}}}-functions.
** when adding a new &quot;first class&quot; Visitable, i.e. a concrete target class that needs to be treated separately in some visiting tool, the user needs to include the {{{DEFINE_PROCESSABLE_BY}}} macro and needs to make sure that all existing &quot;first class&quot; tool implementation classes include the Applicable base class for this new type. In this respect, our implementation is clearly &quot;cyclic&quot;. (Generally speaking, the visitor pattern should not be used when the hierarchy of target objects is frequently extended and remoulded). But, when using the typelist facillity to define the Applicable base classes, we'll have one header file defining these collection of Applicables and thus we just need to add our new concrete Visitable to this header and recompile all tool implementation classes.
** when creating a new &quot;~Visitable-and-Tool&quot; hierarchy, the user should derive (or typedef) and parametrize the {{{Visitable}}}, {{{Tool}}} and {{{Applicable}}} templates, typically into a new namespace. An example can be seen in {{{proc/mobject/builder/buildertool.hpp}}}
</pre>
</div>
<div title="VisitorUse" modifier="Ichthyostega" modified="201003160159" created="200711280302" tags="impl decision" changecount="18">
<pre>Using the ''Visitor Design Pattern'' is somewhat controversial, mainly because this pattern is rather complicated, requires certain circumstances to be usefull, and &amp;mdash; especially when used in the original form described by Gamma et al &amp;mdash; puts quite some burden on the implementor. Contrary to some patterns commonly used today (like e.g. singleton or factory), visitor is by no way a popular pattern. Ichthyo thinks that until now it's potential stregths remain still to be discovered, due to obvious and well known weaknesses. This problems can be ameliorated a good deal by using template based library implementation techniques along the lines of the [[Loki library|http://loki-lib.sourceforge.net/]].
&amp;rarr; [[implementation deatails|VisitingToolImpl]]
!why bothering with visitor?
In the Lumiera Proc layer, Ichthyo uses the visitor pattern to overcome another notorious problem when dealing with more complex class hierarchies: either, the //interface// (root class) is so unspecific to be almost useless, or, in spite of having a useful contract, this contract will effectively be broken by some subclasses (&quot;problem of elliptical circles&quot;). Initially, when designing the classes, the problems aren't there (obviously, because they could be taken as design flaws). But then, under the pressure of real features, new types are added later on, which //need to be in this hierarchy// and at the same time //need to have this and that special behaviour// and here we go ...
Visitor helps us to circumvent this trap: the basic operations can be written against the top level interface, such as to include visiting some object collection internally. Now, on a case-by-case base, local operations can utilise a more specific sub interface or the given concrete type's public interface. So visitor helps to encapsulate specific technical details of cooperating objects within the concrete visiting tool implementation, while still forcing them to be implemented against some interface or sub-interface of the target objects.
!!well suited for using visitors
generally speaking, visitors are preferable when the underlying element type hierarchy is rather stable, but new operations are to be added frequently.
* Implementation of the builder. All sorts of special treatments of some MObject kinds can be added later
* Operating on the Object collection within the session. Effectively, this decouples the invoking interface on the session completely from the special operations to be carried out on individual objects.
To see an simple example of our &quot;visiting tool&quot;, have a look at {{{tests/components/common/visitingtooltest.cpp}}}
</pre>
</div>
<div title="WalkThrough" modifier="Ichthyostega" modified="200805300124" created="200706210625" tags="overview" changecount="41">
<pre>The Intention of this text is to help you understanding the design and to show some notable details.
!!!!Starting Point
Design is an experiment to find out how things are related. We can't //plan// a Design top down, rather we have to start at some point with some hypothesis and look how it works out. The point of origin for Ichthyo's design is the observation that the Render Engine needs some Separation of Concerns to get the complexity down. And especially, this design ''uses three different Levels'' or Layers within the Render Engine and Session.
* the __high level__ within the session uses uniformly treated MObjects which are assembled/glued together by a network of [[Placements|Placement]].&lt;br&gt; It is supposed that the GUI will present this and //only this view //to the user, giving him the ability to work with the objects
* the __builder level__ works on a stripped-down subset of this ~MObject network: it uses the //same Object instances// but only assembled by [[Explicit Placements|ExplicitPlacement]] which locate the objects //on a simple (track, time) grid.// It's the job of the builder to create out of this simplified Network the Configuration of [[Render Nodes|ProcNode]] needed to do the actual rendering
* the __engine level__ uses solely [[Render Pipeline Nodes (ProcNode)|ProcNode]], i.e. a Graph of interconnected processing nodes. The important constraint here is that //any decisions are ruled out//. The core Render Engine with all its nodes is lacking the ability to do any tests and checks and has no possibility to branch or reconfigure anything. (this is an especially important lesson I draw from studying the current Cinelerra source code)
!!!!Performance Considerations
* within the Engine the Render Nodes are containing the ''inner loop'', whose contents are to be executed hundred thousands to million times per frame. Every dispensable concern, which is not strictly necessary to get the job done, is worth the effort of factoring out here.
* performance pressure at the builder level is far lower, albeit still existent. Compared to the effort of calculating a single processing step, looping even over some hundred nodes and executing quite some logic is negligible. Danger bears on creating memory pressure or causing awkward execution patterns (in the backend) rather. So the main concern should be the ability of reconfiguring different aspects separately without much effort. If for example a given render strategy works out to create lots of deadlocks and waitstates in the backend, the design should account for the possibility to exchange it with another strategy without having to modify the inner workings of the build process.&lt;br&gt;On the other hand, I wouldn't be overly concerned to trigger the build process yet another time to get some specific problem solved. However, the possibility to share one Render configuration for, say, 20 sec of video, instead of triggering the build process 500 times for every frame in this timespan, would sure be worth considering if it's not overly complicated to achieve.
* contrary to this, the session level is harmless with respect to performance. Getting acceptable responsiveness on user interactions is sufficient. We could consider using high level languages here, for it is much more important being able to express and handle complicated object relationships with relative ease. The only (indirect) concern is to avoid generating memory pressure inadvertently. Edit actions generating memory peaks could interfere with an ongoing background render process. If we decide to use lots of relation objects or transient objects, we should use an object pool or still better an garbage collector.
!!!!Concepts and Interfaces
This design strives to build each level and subsystem around some central concepts, which are directly expressed as Interfaces. Commonly used Interfaces clamp the different layers.
* MObject gives an uniform view on all the various entities to be arranged in the session.
* all the arranging and relating of ~MObjects is abstracted as [[Placement]]. The contract of a Placement is that it always has a related Subject, that we can change the //way of placement&amp;nbsp;// by adding and removing [[&quot;locating pins&quot;|LocatingPin]], call some test methods on it (still to be defined), and, finally, that we can get an ExplicitPlacement from it.
* albeit being a special form of a Placement, the ExplicitPlacement is treated as a separate concept. With respect to edit operations within the session, it can stand for any sort of Placement. On the other hand the Builder takes a list of ~ExplicitPlacements as input for building up the Render Engine(s). This corresponds to the fact that the render process needs to organize the things to be done on a simple two dimensional grid of (output channel / time). The (extended) contract of an ~ExplicitPlacement provides us with this (output,time).
* on the lower end of the builder, everything is organized around the Concept of a ProcNode, which enables us to //pull// one (freely addressable) Frame of calculated data. Further, the ProcNode has the ability to be wired with other nodes and [[Parameter Providers|ParamProvider]]
* the various types of data to be processed are abstracted away under the notion of a [[Frame]]. Basically, a Frame is an Buffer containing an Array of raw data and it can be located by some generic scheme, including (at least) the absolute starting time (and probably some type or channel id).
* All sorts of (target domain) [[parameters|Parameter]] are treated uniformly. There is a distinction between Parameters (which //could// be variable) and Configuration (which is considered to be fixed). In this context, [[Automation]] just appears as a special kind of ParamProvider.
* and finally, the calculation //process// together with its current state is represented by a StateProxy. I call this a &quot;proxy&quot;, because it should encapsulate and hide all tedious details of communication, be it even asynchronous communication with some Controller or Dispatcher running in another Thread. In order to maintain a view on the current state of the render process, it could eventually be necessary to register as an observer somewhere or to send notifications to other parts of the system.
!!!!Handling Diversity
An important goal of this approach is to be able to push down the treatment of variations and special cases. We don't need to know what kind of Placement links one MObject to another, because it is sufficient for us to get an ExplicitPlacement. The Render Engine doesn't need to know if it is pulling audio Frames or video Frames or GOPs or OpenGL textures. It simply relies on the Builder wiring together the correct node types. And the Builder in turn does so by using some overloaded function of an iterator or visitor. At many instances, instead of doing decisions in-code or using hard wired defaults, a system of [[configuration rules|ConfigRules]] is invoked to get a suitable default as a solution (and, as a plus, this provides points of customisation for advanced users). At engine level, there is no need for the video processing node to test for the colormodel on every screen line, because the Builder has already wired up the fitting implementation routine. All of this helps reducing complexity and quite some misconceptions can be detected already by the compiler.
!!!!Explicit structural differences
In case it's not already clear: we don't have &quot;the&quot; Render Engine, rather we construct a Render Engine for each structurally differing part of the timeline. (please relate this to the current Cinelerra code base, which constructs and builds up the render pipeline for each frame separately). No need to call back from within the pipeline to find out if a given plugin is enabled or to see if there are any automation keyframes. We don't need to pose any constraints on the structuring of the objects in the session, besides the requirement to get an ExplicitPlacement for each. We could even loosen the use of the common metaphor of placing media sequences on fixed tracks, if we want to get at a more advanced GUI at some point in the future.
!!!!Stateless Subsystems
The &amp;raquo;current setup&amp;laquo; of the objects in the session is sort of a global state. Same holds true for the Controller, as the Engine can be at playback, it can run a background render or scrub single frames. But the whole complicated subsystem of the Builder and one given Render Engine configuration can be made ''stateless''. As a benefit of this we can run this subsystems multi-threaded without the need of any precautions (locking, synchronizing). Because all state information is just passed in as function parameters and lives in local variables on the stack, or is contained in the StateProxy which represents the given render //process// and is passed down as function parameter as well. (note: I use the term &quot;stateless&quot; in the usual, slightly relaxed manner; of course there are some configuration values contained in instance variables of the objects carrying out the calculations, but this values are considered to be constant over the course of the object usage).
</pre>
</div>
<div title="WiringDescriptor" modifier="Ichthyostega" modified="200807132352" created="200807132338" tags="Rendering operational impl spec" changecount="3">
<pre>Each [[processing node|ProcNode]] contains a stateless ({{{const}}}) descriptor detailing the inputs, outputs and predecessors. Moreover, this descriptor contains the configuration of the call sequence yielding the &amp;raquo;data pulled from predecessor(s)&amp;laquo;. The actual type of this object is composed out of several building blocks (policy classes) and placed by the builder as a template parameter on the WiringDescriptor of the individual ProcNode. This happens in the WiringFactory in file {{{nodewiring.cpp}}}, which consequently contains all the possible combinations (pre)generated at compile time.
!building blocks
* ''Caching'': whether the result frames of this processing step will be communicated to the Cache and thus could be fetched from there instead of actually calculating them.
* ''Process'': whether this node does any calculations on it's own or just pulls from a source
* ''Inplace'': whether this node is capable of processing the result &quot;in-place&quot;, thereby overwriting the input buffer
* ''Multiout'': whether this node produces multiple output channels/frames in one processing step
!!implementation
!!!!Caching
When a node participates in ''caching'', a result frame may be pulled immediately from cache instead of calculating it. Moreover, //any output buffer//&amp;nbsp; of this node will be allocated //within the cache.// Consequently, caching interferes with the ability of the next node to calculate &quot;in-Place&quot;. In the other case, when ''not using the cache'', the {{{pull()}}} call immediately starts out with calling down to the predecessor nodes, and the allocation of output buffer(s) is always delegated to the parent state (i.e. the StateProxy pulling results from this node).
Generally, buffer allocation requests from predecessor nodes (while being pulled by this node) will either be satisfied by using the &quot;current state&quot;, or treated as if they were our own output buffers when this node is in-Place capable.
!!!!Multiple Outputs
Some simplifications are possible in the default case of a node producing just ''one single output'' stream. Otherwise, we'd have to allocate multiple output buffers, and then, after processing, select the one needed as a result and deallocate the superfluous further buffers.
!!!!in-Place capability
If a node is capable of calculating the result by ''modifying it's input'' buffer(s), an important performance optimization is possible, because in a chain of in-place capable nodes, we don't need any buffer allocations. But, on the other hand, this optimization may collide with the caching, because a frame retrieved from cache must not be modified.
Without this optimization, in the base case each processing needs an input and an output. Exceptionally, we could think of special nodes which //require// to process in-place, in which case we'd need to provide it with a copy of the input buffer to work on.
!!!!Processing
If ''not processing'' we don't have any input buffers, instead we get our output buffers from an external source.
Otherwise, in the default case of actually ''processing'' out output, we have to organize input buffers, allocate output buffers, call the {{{process()}}} function of the WiringDescriptor and finally release the input buffers.
</pre>
</div>
<div title="WiringRequest" modifier="Ichthyostega" modified="200810060404" created="200810060344" tags="def spec" changecount="2">
<pre>{{red{This is an early draft as of 9/08}}}
Wiring requests rather belong to the realm of the high-level model, but play an important role in the build process, because the result of &quot;executing&quot; a wiring request will be to establish an actual low-level data connection. Wiring requests will be created automatically in the course of the build process, but they can be created manually and attached to the media objects in the high-level model as a ''wiring plug'', which is a special kind of LocatingPin (&amp;rarr; [[Placement]])
Wiring requests are small stateful value objects. They will be collected, sorted and processed ordered, such as to finish the building and get a completely wired network of processing nodes. Obviously, there needs to be some reference or smart pointer to the objects to be wired, but this information remains opaque.
&amp;rarr; ConManager
</pre>
</div>
<div title="automation" modifier="Ichthyostega" modified="200805300125" created="200805300057" tags="overview" changecount="8">
<pre>The purpose of automation is to vary a parameter of some data processing instance in the course of time while rendering. Thus, automation encompasses all the variability within the render network //which is not a structural change.//
!Parameters and Automation
[[Automation]] is treated as a function over time. Everything beyond this definition is considered an implementation detail of the [[parameter provider|ParamProvider]] used to yield the value. Thus automation is closely tied to the concept of a [[Parameter]], which also plays an important role in the communication with the GUI and while [[setting up and wiring the render nodes|BuildRenderNode]] in the course of the build process (&amp;rarr; see [[tag:Builder|Builder]])</pre>
</div>
<div title="def" modifier="Ichthyostega" created="200902080726" changecount="1">
<pre>Definition of commonly used terms and facilities...</pre>
</div>
</div>
<!--POST-STOREAREA-->
<!--POST-BODY-START-->
<!--POST-BODY-END-->
<script type="text/javascript">
//<![CDATA[
//
// Please note:
//
// * This code is designed to be readable but for compactness it only includes brief comments. You can see fuller comments
// in the project Subversion repository at http://svn.tiddlywiki.org/Trunk/core/
//
// * You should never need to modify this source code directly. TiddlyWiki is carefully designed to allow deep customisation
// without changing the core code. Please consult the development group at http://groups.google.com/group/TiddlyWikiDev
//
//--
//-- Configuration repository
//--
// Miscellaneous options
var config = {
numRssItems: 20, // Number of items in the RSS feed
animDuration: 400, // Duration of UI animations in milliseconds
cascadeFast: 20, // Speed for cascade animations (higher == slower)
cascadeSlow: 60, // Speed for EasterEgg cascade animations
cascadeDepth: 5 // Depth of cascade animation
};
// Adaptors
config.adaptors = {};
// Backstage tasks
config.tasks = {};
// Annotations
config.annotations = {};
// Custom fields to be automatically added to new tiddlers
config.defaultCustomFields = {};
// Messages
config.messages = {
messageClose: {},
dates: {},
tiddlerPopup: {}
};
// Options that can be set in the options panel and/or cookies
config.options = {
chkRegExpSearch: false,
chkCaseSensitiveSearch: false,
chkAnimate: true,
chkSaveBackups: true,
chkAutoSave: false,
chkGenerateAnRssFeed: false,
chkSaveEmptyTemplate: false,
chkOpenInNewWindow: true,
chkToggleLinks: false,
chkHttpReadOnly: true,
chkForceMinorUpdate: false,
chkConfirmDelete: true,
chkInsertTabs: false,
chkUsePreForStorage: true, // Whether to use <pre> format for storage
chkDisplayStartupTime: false,
txtBackupFolder: "",
txtMainTab: "tabTimeline",
txtMoreTab: "moreTabAll",
txtMaxEditRows: "30",
txtFileSystemCharSet: "UTF-8"
};
config.optionsDesc = {};
// List of notification functions to be called when certain tiddlers are changed or deleted
config.notifyTiddlers = [
{name: "StyleSheetLayout", notify: refreshStyles},
{name: "StyleSheetColors", notify: refreshStyles},
{name: "StyleSheet", notify: refreshStyles},
{name: "StyleSheetPrint", notify: refreshStyles},
{name: "PageTemplate", notify: refreshPageTemplate},
{name: "SiteTitle", notify: refreshPageTitle},
{name: "SiteSubtitle", notify: refreshPageTitle},
{name: "ColorPalette", notify: refreshColorPalette},
{name: null, notify: refreshDisplay}
];
// Default tiddler templates
var DEFAULT_VIEW_TEMPLATE = 1;
var DEFAULT_EDIT_TEMPLATE = 2;
config.tiddlerTemplates = {
1: "ViewTemplate",
2: "EditTemplate"
};
// More messages (rather a legacy layout that shouldn't really be like this)
config.views = {
wikified: {
tag: {}
},
editor: {
tagChooser: {}
}
};
// Backstage tasks
config.backstageTasks = ["save","sync","importTask","tweak","plugins"];
// Macros; each has a 'handler' member that is inserted later
config.macros = {
today: {},
version: {},
search: {sizeTextbox: 15},
tiddler: {},
tag: {},
tags: {},
tagging: {},
timeline: {},
allTags: {},
list: {
all: {},
missing: {},
orphans: {},
shadowed: {},
touched: {}
},
closeAll: {},
permaview: {},
saveChanges: {},
slider: {},
option: {},
options: {},
newTiddler: {},
newJournal: {},
sparkline: {},
tabs: {},
gradient: {},
message: {},
view: {},
edit: {},
tagChooser: {},
toolbar: {},
br: {},
plugins: {},
refreshDisplay: {},
importTiddlers: {},
sync: {},
annotations: {}
};
// Commands supported by the toolbar macro
config.commands = {
closeTiddler: {},
closeOthers: {},
editTiddler: {},
saveTiddler: {hideReadOnly: true},
cancelTiddler: {},
deleteTiddler: {hideReadOnly: true},
permalink: {},
references: {type: "popup"},
jump: {type: "popup"},
syncing: {type: "popup"},
fields: {type: "popup"}
};
// Browser detection... In a very few places, there's nothing else for it but to know what browser we're using.
config.userAgent = navigator.userAgent.toLowerCase();
config.browser = {
isIE: config.userAgent.indexOf("msie") != -1 && config.userAgent.indexOf("opera") == -1,
isGecko: config.userAgent.indexOf("gecko") != -1,
ieVersion: /MSIE (\d.\d)/i.exec(config.userAgent), // config.browser.ieVersion[1], if it exists, will be the IE version string, eg "6.0"
isSafari: config.userAgent.indexOf("applewebkit") != -1,
isBadSafari: !((new RegExp("[\u0150\u0170]","g")).test("\u0150")),
firefoxDate: /gecko\/(\d{8})/i.exec(config.userAgent), // config.browser.firefoxDate[1], if it exists, will be Firefox release date as "YYYYMMDD"
isOpera: config.userAgent.indexOf("opera") != -1,
isLinux: config.userAgent.indexOf("linux") != -1,
isUnix: config.userAgent.indexOf("x11") != -1,
isMac: config.userAgent.indexOf("mac") != -1,
isWindows: config.userAgent.indexOf("win") != -1
};
// Basic regular expressions
config.textPrimitives = {
upperLetter: "[A-Z\u00c0-\u00de\u0150\u0170]",
lowerLetter: "[a-z0-9_\\-\u00df-\u00ff\u0151\u0171]",
anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]",
anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff\u0150\u0170\u0151\u0171]"
};
if(config.browser.isBadSafari) {
config.textPrimitives = {
upperLetter: "[A-Z\u00c0-\u00de]",
lowerLetter: "[a-z0-9_\\-\u00df-\u00ff]",
anyLetter: "[A-Za-z0-9_\\-\u00c0-\u00de\u00df-\u00ff]",
anyLetterStrict: "[A-Za-z0-9\u00c0-\u00de\u00df-\u00ff]"
};
}
config.textPrimitives.sliceSeparator = "::";
config.textPrimitives.urlPattern = "[a-z]{3,8}:[^\\s:'\"][^\\s'\"]*(?:/|\\b)";
config.textPrimitives.unWikiLink = "~";
config.textPrimitives.wikiLink = "(?:(?:" + config.textPrimitives.upperLetter + "+" +
config.textPrimitives.lowerLetter + "+" +
config.textPrimitives.upperLetter +
config.textPrimitives.anyLetter + "*)|(?:" +
config.textPrimitives.upperLetter + "{2,}" +
config.textPrimitives.lowerLetter + "+))";
config.textPrimitives.cssLookahead = "(?:(" + config.textPrimitives.anyLetter + "+)\\(([^\\)\\|\\n]+)(?:\\):))|(?:(" + config.textPrimitives.anyLetter + "+):([^;\\|\\n]+);)";
config.textPrimitives.cssLookaheadRegExp = new RegExp(config.textPrimitives.cssLookahead,"mg");
config.textPrimitives.brackettedLink = "\\[\\[([^\\]]+)\\]\\]";
config.textPrimitives.titledBrackettedLink = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
config.textPrimitives.tiddlerForcedLinkRegExp = new RegExp("(?:" + config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
config.textPrimitives.tiddlerAnyLinkRegExp = new RegExp("("+ config.textPrimitives.wikiLink + ")|(?:" +
config.textPrimitives.titledBrackettedLink + ")|(?:" +
config.textPrimitives.brackettedLink + ")|(?:" +
config.textPrimitives.urlPattern + ")","mg");
config.glyphs = {
browsers: [
function() {return config.browser.isIE;},
function() {return true}
],
currBrowser: null,
codes: {
downTriangle: ["\u25BC","\u25BE"],
downArrow: ["\u2193","\u2193"],
bentArrowLeft: ["\u2190","\u21A9"],
bentArrowRight: ["\u2192","\u21AA"]
}
};
//--
//-- Shadow tiddlers
//--
config.shadowTiddlers = {
StyleSheet: "",
MarkupPreHead: "<!--{{{-->\n<link rel='alternate' type='application/rss+xml' title='RSS' href='index.xml'/>\n<!--}}}-->",
MarkupPostHead: "",
MarkupPreBody: "",
MarkupPostBody: "",
TabTimeline: '<<timeline>>',
TabAll: '<<list all>>',
TabTags: '<<allTags excludeLists>>',
TabMoreMissing: '<<list missing>>',
TabMoreOrphans: '<<list orphans>>',
TabMoreShadowed: '<<list shadowed>>',
AdvancedOptions: '<<options>>',
PluginManager: '<<plugins>>',
ImportTiddlers: '<<importTiddlers>>'
};
//--
//-- Translateable strings
//--
// Strings in "double quotes" should be translated; strings in 'single quotes' should be left alone
merge(config.options,{
txtUserName: "YourName"});
merge(config.tasks,{
save: {text: "save", tooltip: "Save your changes to this TiddlyWiki", action: saveChanges},
sync: {text: "sync", tooltip: "Synchronise changes with other TiddlyWiki files and servers", content: '<<sync>>'},
importTask: {text: "import", tooltip: "Import tiddlers and plugins from other TiddlyWiki files and servers", content: '<<importTiddlers>>'},
tweak: {text: "tweak", tooltip: "Tweak the appearance and behaviour of TiddlyWiki", content: '<<options>>'},
plugins: {text: "plugins", tooltip: "Manage installed plugins", content: '<<plugins>>'}
});
// Options that can be set in the options panel and/or cookies
merge(config.optionsDesc,{
txtUserName: "Username for signing your edits",
chkRegExpSearch: "Enable regular expressions for searches",
chkCaseSensitiveSearch: "Case-sensitive searching",
chkAnimate: "Enable animations",
chkSaveBackups: "Keep backup file when saving changes",
chkAutoSave: "Automatically save changes",
chkGenerateAnRssFeed: "Generate an RSS feed when saving changes",
chkSaveEmptyTemplate: "Generate an empty template when saving changes",
chkOpenInNewWindow: "Open external links in a new window",
chkToggleLinks: "Clicking on links to open tiddlers causes them to close",
chkHttpReadOnly: "Hide editing features when viewed over HTTP",
chkForceMinorUpdate: "Don't update modifier username and date when editing tiddlers",
chkConfirmDelete: "Require confirmation before deleting tiddlers",
chkInsertTabs: "Use the tab key to insert tab characters instead of moving between fields",
txtBackupFolder: "Name of folder to use for backups",
txtMaxEditRows: "Maximum number of rows in edit boxes",
txtFileSystemCharSet: "Default character set for saving changes (Firefox/Mozilla only)"});
merge(config.messages,{
customConfigError: "Problems were encountered loading plugins. See PluginManager for details",
pluginError: "Error: %0",
pluginDisabled: "Not executed because disabled via 'systemConfigDisable' tag",
pluginForced: "Executed because forced via 'systemConfigForce' tag",
pluginVersionError: "Not executed because this plugin needs a newer version of TiddlyWiki",
nothingSelected: "Nothing is selected. You must select one or more items first",
savedSnapshotError: "It appears that this TiddlyWiki has been incorrectly saved. Please see http://www.tiddlywiki.com/#DownloadSoftware for details",
subtitleUnknown: "(unknown)",
undefinedTiddlerToolTip: "The tiddler '%0' doesn't yet exist",
shadowedTiddlerToolTip: "The tiddler '%0' doesn't yet exist, but has a pre-defined shadow value",
tiddlerLinkTooltip: "%0 - %1, %2",
externalLinkTooltip: "External link to %0",
noTags: "There are no tagged tiddlers",
notFileUrlError: "You need to save this TiddlyWiki to a file before you can save changes",
cantSaveError: "It's not possible to save changes. Possible reasons include:\n- your browser doesn't support saving (Firefox, Internet Explorer, Safari and Opera all work if properly configured)\n- the pathname to your TiddlyWiki file contains illegal characters\n- the TiddlyWiki HTML file has been moved or renamed",
invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
backupSaved: "Backup saved",
backupFailed: "Failed to save backup file",
rssSaved: "RSS feed saved",
rssFailed: "Failed to save RSS feed file",
emptySaved: "Empty template saved",
emptyFailed: "Failed to save empty template file",
mainSaved: "Main TiddlyWiki file saved",
mainFailed: "Failed to save main TiddlyWiki file. Your changes have not been saved",
macroError: "Error in macro <<\%0>>",
macroErrorDetails: "Error while executing macro <<\%0>>:\n%1",
missingMacro: "No such macro",
overwriteWarning: "A tiddler named '%0' already exists. Choose OK to overwrite it",
unsavedChangesWarning: "WARNING! There are unsaved changes in TiddlyWiki\n\nChoose OK to save\nChoose CANCEL to discard",
confirmExit: "--------------------------------\n\nThere are unsaved changes in TiddlyWiki. If you continue you will lose those changes\n\n--------------------------------",
saveInstructions: "SaveChanges",
unsupportedTWFormat: "Unsupported TiddlyWiki format '%0'",
tiddlerSaveError: "Error when saving tiddler '%0'",
tiddlerLoadError: "Error when loading tiddler '%0'",
wrongSaveFormat: "Cannot save with storage format '%0'. Using standard format for save.",
invalidFieldName: "Invalid field name %0",
fieldCannotBeChanged: "Field '%0' cannot be changed",
loadingMissingTiddler: "Attempting to retrieve the tiddler '%0' from the '%1' server at:\n\n'%2' in the workspace '%3'"});
merge(config.messages.messageClose,{
text: "close",
tooltip: "close this message area"});
config.messages.backstage = {
open: {text: "backstage", tooltip: "Open the backstage area to perform authoring and editing tasks"},
close: {text: "close", tooltip: "Close the backstage area"},
prompt: "backstage: ",
decal: {
edit: {text: "edit", tooltip: "Edit the tiddler '%0'"}
}
};
config.messages.listView = {
tiddlerTooltip: "Click for the full text of this tiddler",
previewUnavailable: "(preview not available)"
};
config.messages.dates.months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November","December"];
config.messages.dates.days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
config.messages.dates.shortMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
config.messages.dates.shortDays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
// suffixes for dates, eg "1st","2nd","3rd"..."30th","31st"
config.messages.dates.daySuffixes = ["st","nd","rd","th","th","th","th","th","th","th",
"th","th","th","th","th","th","th","th","th","th",
"st","nd","rd","th","th","th","th","th","th","th",
"st"];
config.messages.dates.am = "am";
config.messages.dates.pm = "pm";
merge(config.messages.tiddlerPopup,{
});
merge(config.views.wikified.tag,{
labelNoTags: "no tags",
labelTags: "tags: ",
openTag: "Open tag '%0'",
tooltip: "Show tiddlers tagged with '%0'",
openAllText: "Open all",
openAllTooltip: "Open all of these tiddlers",
popupNone: "No other tiddlers tagged with '%0'"});
merge(config.views.wikified,{
defaultText: "The tiddler '%0' doesn't yet exist. Double-click to create it",
defaultModifier: "(missing)",
shadowModifier: "(built-in shadow tiddler)",
dateFormat: "DD MMM YYYY",
createdPrompt: "created"});
merge(config.views.editor,{
tagPrompt: "Type tags separated with spaces, [[use double square brackets]] if necessary, or add existing",
defaultText: "Type the text for '%0'"});
merge(config.views.editor.tagChooser,{
text: "tags",
tooltip: "Choose existing tags to add to this tiddler",
popupNone: "There are no tags defined",
tagTooltip: "Add the tag '%0'"});
merge(config.messages,{
sizeTemplates:
[
{unit: 1024*1024*1024, template: "%0\u00a0GB"},
{unit: 1024*1024, template: "%0\u00a0MB"},
{unit: 1024, template: "%0\u00a0KB"},
{unit: 1, template: "%0\u00a0B"}
]});
merge(config.macros.search,{
label: "search",
prompt: "Search this TiddlyWiki",
accessKey: "F",
successMsg: "%0 tiddlers found matching %1",
failureMsg: "No tiddlers found matching %0"});
merge(config.macros.tagging,{
label: "tagging: ",
labelNotTag: "not tagging",
tooltip: "List of tiddlers tagged with '%0'"});
merge(config.macros.timeline,{
dateFormat: "DD MMM YYYY"});
merge(config.macros.allTags,{
tooltip: "Show tiddlers tagged with '%0'",
noTags: "There are no tagged tiddlers"});
config.macros.list.all.prompt = "All tiddlers in alphabetical order";
config.macros.list.missing.prompt = "Tiddlers that have links to them but are not defined";
config.macros.list.orphans.prompt = "Tiddlers that are not linked to from any other tiddlers";
config.macros.list.shadowed.prompt = "Tiddlers shadowed with default contents";
config.macros.list.touched.prompt = "Tiddlers that have been modified locally";
merge(config.macros.closeAll,{
label: "close all",
prompt: "Close all displayed tiddlers (except any that are being edited)"});
merge(config.macros.permaview,{
label: "permaview",
prompt: "Link to an URL that retrieves all the currently displayed tiddlers"});
merge(config.macros.saveChanges,{
label: "save changes",
prompt: "Save all tiddlers to create a new TiddlyWiki",
accessKey: "S"});
merge(config.macros.newTiddler,{
label: "new tiddler",
prompt: "Create a new tiddler",
title: "New Tiddler",
accessKey: "N"});
merge(config.macros.newJournal,{
label: "new journal",
prompt: "Create a new tiddler from the current date and time",
accessKey: "J"});
merge(config.macros.options,{
wizardTitle: "Tweak advanced options",
step1Title: "These options are saved in cookies in your browser",
step1Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='false' name='chkUnknown'>Show unknown options</input>",
unknownDescription: "//(unknown)//",
listViewTemplate: {
columns: [
{name: 'Option', field: 'option', title: "Option", type: 'String'},
{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
{name: 'Name', field: 'name', title: "Name", type: 'String'}
],
rowClasses: [
{className: 'lowlight', field: 'lowlight'}
]}
});
merge(config.macros.plugins,{
wizardTitle: "Manage plugins",
step1Title: "Currently loaded plugins",
step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
skippedText: "(This plugin has not been executed because it was added since startup)",
noPluginText: "There are no plugins installed",
confirmDeleteText: "Are you sure you want to delete these plugins:\n\n%0",
removeLabel: "remove systemConfig tag",
removePrompt: "Remove systemConfig tag",
deleteLabel: "delete",
deletePrompt: "Delete these tiddlers forever",
listViewTemplate: {
columns: [
{name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
{name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
{name: 'Forced', field: 'forced', title: "Forced", tag: 'systemConfigForce', type: 'TagCheckbox'},
{name: 'Disabled', field: 'disabled', title: "Disabled", tag: 'systemConfigDisable', type: 'TagCheckbox'},
{name: 'Executed', field: 'executed', title: "Loaded", type: 'Boolean', trueText: "Yes", falseText: "No"},
{name: 'Startup Time', field: 'startupTime', title: "Startup Time", type: 'String'},
{name: 'Error', field: 'error', title: "Status", type: 'Boolean', trueText: "Error", falseText: "OK"},
{name: 'Log', field: 'log', title: "Log", type: 'StringList'}
],
rowClasses: [
{className: 'error', field: 'error'},
{className: 'warning', field: 'warning'}
]}
});
merge(config.macros.toolbar,{
moreLabel: "more",
morePrompt: "Reveal further commands"
});
merge(config.macros.refreshDisplay,{
label: "refresh",
prompt: "Redraw the entire TiddlyWiki display"
});
merge(config.macros.importTiddlers,{
readOnlyWarning: "You cannot import into a read-only TiddlyWiki file. Try opening it from a file:// URL",
wizardTitle: "Import tiddlers from another file or server",
step1Title: "Step 1: Locate the server or TiddlyWiki file",
step1Html: "Specify the type of the server: <select name='selTypes'><option value=''>Choose...</option></select><br>Enter the URL or pathname here: <input type='text' size=50 name='txtPath'><br>...or browse for a file: <input type='file' size=50 name='txtBrowse'><br><hr>...or select a pre-defined feed: <select name='selFeeds'><option value=''>Choose...</option></select>",
openLabel: "open",
openPrompt: "Open the connection to this file or server",
openError: "There were problems fetching the tiddlywiki file",
statusOpenHost: "Opening the host",
statusGetWorkspaceList: "Getting the list of available workspaces",
step2Title: "Step 2: Choose the workspace",
step2Html: "Enter a workspace name: <input type='text' size=50 name='txtWorkspace'><br>...or select a workspace: <select name='selWorkspace'><option value=''>Choose...</option></select>",
cancelLabel: "cancel",
cancelPrompt: "Cancel this import",
statusOpenWorkspace: "Opening the workspace",
statusGetTiddlerList: "Getting the list of available tiddlers",
step3Title: "Step 3: Choose the tiddlers to import",
step3Html: "<input type='hidden' name='markList'></input><br><input type='checkbox' checked='true' name='chkSync'>Keep these tiddlers linked to this server so that you can synchronise subsequent changes</input><br><input type='checkbox' name='chkSave'>Save the details of this server in a 'systemServer' tiddler called:</input> <input type='text' size=25 name='txtSaveTiddler'>",
importLabel: "import",
importPrompt: "Import these tiddlers",
confirmOverwriteText: "Are you sure you want to overwrite these tiddlers:\n\n%0",
step4Title: "Step 4: Importing %0 tiddler(s)",
step4Html: "<input type='hidden' name='markReport'></input>", // DO NOT TRANSLATE
doneLabel: "done",
donePrompt: "Close this wizard",
statusDoingImport: "Importing tiddlers",
statusDoneImport: "All tiddlers imported",
systemServerNamePattern: "%2 on %1",
systemServerNamePatternNoWorkspace: "%1",
confirmOverwriteSaveTiddler: "The tiddler '%0' already exists. Click 'OK' to overwrite it with the details of this server, or 'Cancel' to leave it unchanged",
serverSaveTemplate: "|''Type:''|%0|\n|''URL:''|%1|\n|''Workspace:''|%2|\n\nThis tiddler was automatically created to record the details of this server",
serverSaveModifier: "(System)",
listViewTemplate: {
columns: [
{name: 'Selected', field: 'Selected', rowName: 'title', type: 'Selector'},
{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
{name: 'Size', field: 'size', tiddlerLink: 'size', title: "Size", type: 'Size'},
{name: 'Tags', field: 'tags', title: "Tags", type: 'Tags'}
],
rowClasses: [
]}
});
merge(config.macros.sync,{
listViewTemplate: {
columns: [
{name: 'Selected', field: 'selected', rowName: 'title', type: 'Selector'},
{name: 'Tiddler', field: 'tiddler', title: "Tiddler", type: 'Tiddler'},
{name: 'Server Type', field: 'serverType', title: "Server type", type: 'String'},
{name: 'Server Host', field: 'serverHost', title: "Server host", type: 'String'},
{name: 'Server Workspace', field: 'serverWorkspace', title: "Server workspace", type: 'String'},
{name: 'Status', field: 'status', title: "Synchronisation status", type: 'String'},
{name: 'Server URL', field: 'serverUrl', title: "Server URL", text: "View", type: 'Link'}
],
rowClasses: [
],
buttons: [
{caption: "Sync these tiddlers", name: 'sync'}
]},
wizardTitle: "Synchronize with external servers and files",
step1Title: "Choose the tiddlers you want to synchronize",
step1Html: "<input type='hidden' name='markList'></input>", // DO NOT TRANSLATE
syncLabel: "sync",
syncPrompt: "Sync these tiddlers",
hasChanged: "Changed while unplugged",
hasNotChanged: "Unchanged while unplugged",
syncStatusList: {
none: {text: "...", color: "none"},
changedServer: {text: "Changed on server", color: '#80ff80'},
changedLocally: {text: "Changed while unplugged", color: '#80ff80'},
changedBoth: {text: "Changed while unplugged and on server", color: '#ff8080'},
notFound: {text: "Not found on server", color: '#ffff80'},
putToServer: {text: "Saved update on server", color: '#ff80ff'},
gotFromServer: {text: "Retrieved update from server", color: '#80ffff'}
}
});
merge(config.macros.annotations,{
});
merge(config.commands.closeTiddler,{
text: "close",
tooltip: "Close this tiddler"});
merge(config.commands.closeOthers,{
text: "close others",
tooltip: "Close all other tiddlers"});
merge(config.commands.editTiddler,{
text: "edit",
tooltip: "Edit this tiddler",
readOnlyText: "view",
readOnlyTooltip: "View the source of this tiddler"});
merge(config.commands.saveTiddler,{
text: "done",
tooltip: "Save changes to this tiddler"});
merge(config.commands.cancelTiddler,{
text: "cancel",
tooltip: "Undo changes to this tiddler",
warning: "Are you sure you want to abandon your changes to '%0'?",
readOnlyText: "done",
readOnlyTooltip: "View this tiddler normally"});
merge(config.commands.deleteTiddler,{
text: "delete",
tooltip: "Delete this tiddler",
warning: "Are you sure you want to delete '%0'?"});
merge(config.commands.permalink,{
text: "permalink",
tooltip: "Permalink for this tiddler"});
merge(config.commands.references,{
text: "references",
tooltip: "Show tiddlers that link to this one",
popupNone: "No references"});
merge(config.commands.jump,{
text: "jump",
tooltip: "Jump to another open tiddler"});
merge(config.commands.syncing,{
text: "syncing",
tooltip: "Control synchronisation of this tiddler with a server or external file",
currentlySyncing: "<div>Currently syncing via <span class='popupHighlight'>'%0'</span> to:</"+"div><div>host: <span class='popupHighlight'>%1</span></"+"div><div>workspace: <span class='popupHighlight'>%2</span></"+"div>", // Note escaping of closing <div> tag
notCurrentlySyncing: "Not currently syncing",
captionUnSync: "Stop synchronising this tiddler",
chooseServer: "Synchronise this tiddler with another server:",
currServerMarker: "\u25cf ",
notCurrServerMarker: " "});
merge(config.commands.fields,{
text: "fields",
tooltip: "Show the extended fields of this tiddler",
emptyText: "There are no extended fields for this tiddler",
listViewTemplate: {
columns: [
{name: 'Field', field: 'field', title: "Field", type: 'String'},
{name: 'Value', field: 'value', title: "Value", type: 'String'}
],
rowClasses: [
],
buttons: [
]}});
merge(config.shadowTiddlers,{
DefaultTiddlers: "GettingStarted",
MainMenu: "GettingStarted",
SiteTitle: "My TiddlyWiki",
SiteSubtitle: "a reusable non-linear personal web notebook",
SiteUrl: "http://www.tiddlywiki.com/",
SideBarOptions: '<<search>><<closeAll>><<permaview>><<newTiddler>><<newJournal "DD MMM YYYY">><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel "options »" "Change TiddlyWiki advanced options">>',
SideBarTabs: '<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>',
TabMore: '<<tabs txtMoreTab "Missing" "Missing tiddlers" TabMoreMissing "Orphans" "Orphaned tiddlers" TabMoreOrphans "Shadowed" "Shadowed tiddlers" TabMoreShadowed>>'});
merge(config.annotations,{
AdvancedOptions: "This shadow tiddler provides access to several advanced options",
ColorPalette: "These values in this shadow tiddler determine the colour scheme of the ~TiddlyWiki user interface",
DefaultTiddlers: "The tiddlers listed in this shadow tiddler will be automatically displayed when ~TiddlyWiki starts up",
EditTemplate: "The HTML template in this shadow tiddler determines how tiddlers look while they are being edited",
GettingStarted: "This shadow tiddler provides basic usage instructions",
ImportTiddlers: "This shadow tiddler provides access to importing tiddlers",
MainMenu: "This shadow tiddler is used as the contents of the main menu in the left-hand column of the screen",
MarkupPreHead: "This tiddler is inserted at the top of the <head> section of the TiddlyWiki HTML file",
MarkupPostHead: "This tiddler is inserted at the bottom of the <head> section of the TiddlyWiki HTML file",
MarkupPreBody: "This tiddler is inserted at the top of the <body> section of the TiddlyWiki HTML file",
MarkupPostBody: "This tiddler is inserted at the end of the <body> section of the TiddlyWiki HTML file immediately before the script block",
OptionsPanel: "This shadow tiddler is used as the contents of the options panel slider in the right-hand sidebar",
PageTemplate: "The HTML template in this shadow tiddler determines the overall ~TiddlyWiki layout",
PluginManager: "This shadow tiddler provides access to the plugin manager",
SideBarOptions: "This shadow tiddler is used as the contents of the option panel in the right-hand sidebar",
SideBarTabs: "This shadow tiddler is used as the contents of the tabs panel in the right-hand sidebar",
SiteSubtitle: "This shadow tiddler is used as the second part of the page title",
SiteTitle: "This shadow tiddler is used as the first part of the page title",
SiteUrl: "This shadow tiddler should be set to the full target URL for publication",
StyleSheetColours: "This shadow tiddler contains CSS definitions related to the color of page elements",
StyleSheet: "This tiddler can contain custom CSS definitions",
StyleSheetLayout: "This shadow tiddler contains CSS definitions related to the layout of page elements",
StyleSheetLocale: "This shadow tiddler contains CSS definitions related to the translation locale",
StyleSheetPrint: "This shadow tiddler contains CSS definitions for printing",
TabAll: "This shadow tiddler contains the contents of the 'All' tab in the right-hand sidebar",
TabMore: "This shadow tiddler contains the contents of the 'More' tab in the right-hand sidebar",
TabMoreMissing: "This shadow tiddler contains the contents of the 'Missing' tab in the right-hand sidebar",
TabMoreOrphans: "This shadow tiddler contains the contents of the 'Orphans' tab in the right-hand sidebar",
TabMoreShadowed: "This shadow tiddler contains the contents of the 'Shadowed' tab in the right-hand sidebar",
TabTags: "This shadow tiddler contains the contents of the 'Tags' tab in the right-hand sidebar",
TabTimeline: "This shadow tiddler contains the contents of the 'Timeline' tab in the right-hand sidebar",
ViewTemplate: "The HTML template in this shadow tiddler determines how tiddlers look"
});
//--
//-- Main
//--
var params = null; // Command line parameters
var store = null; // TiddlyWiki storage
var story = null; // Main story
var formatter = null; // Default formatters for the wikifier
config.parsers = {}; // Hashmap of alternative parsers for the wikifier
var anim = new Animator(); // Animation engine
var readOnly = false; // Whether we're in readonly mode
var highlightHack = null; // Embarrassing hack department...
var hadConfirmExit = false; // Don't warn more than once
var safeMode = false; // Disable all plugins and cookies
var installedPlugins = []; // Information filled in when plugins are executed
var startingUp = false; // Whether we're in the process of starting up
var pluginInfo,tiddler; // Used to pass information to plugins in loadPlugins()
// Whether to use the JavaSaver applet
var useJavaSaver = config.browser.isSafari || config.browser.isOpera;
// Starting up
function main()
{
var t9,t8,t7,t6,t5,t4,t3,t2,t1,t0 = new Date();
startingUp = true;
window.onbeforeunload = function(e) {if(window.confirmExit) return confirmExit();};
params = getParameters();
if(params)
params = params.parseParams("open",null,false);
store = new TiddlyWiki();
invokeParamifier(params,"oninit");
story = new Story("tiddlerDisplay","tiddler");
addEvent(document,"click",Popup.onDocumentClick);
saveTest();
loadOptionsCookie();
for(var s=0; s<config.notifyTiddlers.length; s++)
store.addNotification(config.notifyTiddlers[s].name,config.notifyTiddlers[s].notify);
t1 = new Date();
store.loadFromDiv("storeArea","store",true);
t2 = new Date();
loadShadowTiddlers();
t3 = new Date();
invokeParamifier(params,"onload");
t4 = new Date();
readOnly = (window.location.protocol == "file:") ? false : config.options.chkHttpReadOnly;
var pluginProblem = loadPlugins();
t5 = new Date();
formatter = new Formatter(config.formatters);
invokeParamifier(params,"onconfig");
t6 = new Date();
store.notifyAll();
t7 = new Date();
restart();
t8 = new Date();
if(pluginProblem) {
story.displayTiddler(null,"PluginManager");
displayMessage(config.messages.customConfigError);
}
for(var m in config.macros) {
if(config.macros[m].init)
config.macros[m].init();
}
if(!readOnly)
backstage.init();
t9 = new Date();
if(config.options.chkDisplayStartupTime) {
displayMessage("Load in " + (t2-t1) + " ms");
displayMessage("Loadshadows in " + (t3-t2) + " ms");
displayMessage("Loadplugins in " + (t5-t4) + " ms");
displayMessage("Notify in " + (t7-t6) + " ms");
displayMessage("Restart in " + (t8-t7) + " ms");
displayMessage("Total startup in " + (t9-t0) + " ms");
}
startingUp = false;
}
// Restarting
function restart()
{
invokeParamifier(params,"onstart");
if(story.isEmpty()) {
var defaultParams = store.getTiddlerText("DefaultTiddlers").parseParams("open",null,false);
invokeParamifier(defaultParams,"onstart");
}
window.scrollTo(0,0);
}
function saveTest()
{
var s = document.getElementById("saveTest");
if(s.hasChildNodes())
alert(config.messages.savedSnapshotError);
s.appendChild(document.createTextNode("savetest"));
}
function loadShadowTiddlers()
{
var shadows = new TiddlyWiki();
shadows.loadFromDiv("shadowArea","shadows",true);
shadows.forEachTiddler(function(title,tiddler){config.shadowTiddlers[title] = tiddler.text;});
delete shadows;
}
function loadPlugins()
{
if(safeMode)
return false;
var tiddlers = store.getTaggedTiddlers("systemConfig");
var toLoad = [];
var nLoaded = 0;
var map = {};
var nPlugins = tiddlers.length;
installedPlugins = [];
for(var i=0; i<nPlugins; i++) {
var p = getPluginInfo(tiddlers[i]);
installedPlugins[i] = p;
var n = p.Name;
if(n)
map[n] = p;
if(n = p.Source)
map[n] = p;
}
var visit = function(p) {
if(!p || p.done)
return;
p.done = 1;
var reqs = p.Requires;
if(reqs) {
reqs = reqs.readBracketedList();
for(var i=0; i<reqs.length; i++)
visit(map[reqs[i]]);
}
toLoad.push(p);
};
for(i=0; i<nPlugins; i++)
visit(installedPlugins[i]);
for(i=0; i<toLoad.length; i++) {
p = toLoad[i];
pluginInfo = p;
tiddler = p.tiddler;
if(isPluginExecutable(p)) {
if(isPluginEnabled(p)) {
p.executed = true;
var startTime = new Date();
try {
if(tiddler.text)
window.eval(tiddler.text);
nLoaded++;
} catch(ex) {
p.log.push(config.messages.pluginError.format([exceptionText(ex)]));
p.error = true;
}
pluginInfo.startupTime = String((new Date()) - startTime) + "ms";
} else {
nPlugins--;
}
} else {
p.warning = true;
}
}
return nLoaded != nPlugins;
}
function getPluginInfo(tiddler)
{
var p = store.getTiddlerSlices(tiddler.title,["Name","Description","Version","Requires","CoreVersion","Date","Source","Author","License","Browsers"]);
p.tiddler = tiddler;
p.title = tiddler.title;
p.log = [];
return p;
}
// Check that a particular plugin is valid for execution
function isPluginExecutable(plugin)
{
if(plugin.tiddler.isTagged("systemConfigForce"))
return verifyTail(plugin,true,config.messages.pluginForced);
if(plugin["CoreVersion"]) {
var coreVersion = plugin["CoreVersion"].split(".");
var w = parseInt(coreVersion[0]) - version.major;
if(w == 0 && coreVersion[1])
w = parseInt(coreVersion[1]) - version.minor;
if(w == 0 && coreVersion[2])
w = parseInt(coreVersion[2]) - version.revision;
if(w > 0)
return verifyTail(plugin,false,config.messages.pluginVersionError);
}
return true;
}
function isPluginEnabled(plugin)
{
if(plugin.tiddler.isTagged("systemConfigDisable"))
return verifyTail(plugin,false,config.messages.pluginDisabled);
return true;
}
function verifyTail(plugin,result,message)
{
plugin.log.push(message);
return result;
}
function invokeMacro(place,macro,params,wikifier,tiddler)
{
try {
var m = config.macros[macro];
if(m && m.handler)
m.handler(place,macro,params.readMacroParams(),wikifier,params,tiddler);
else
createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,config.messages.missingMacro]));
} catch(ex) {
createTiddlyError(place,config.messages.macroError.format([macro]),config.messages.macroErrorDetails.format([macro,ex.toString()]));
}
}
//--
//-- Paramifiers
//--
function getParameters()
{
var p = null;
if(window.location.hash) {
p = decodeURI(window.location.hash.substr(1));
if(config.browser.firefoxDate != null && config.browser.firefoxDate[1] < "20051111")
p = convertUTF8ToUnicode(p);
}
return p;
}
function invokeParamifier(params,handler)
{
if(!params || params.length == undefined || params.length <= 1)
return;
for(var t=1; t<params.length; t++) {
var p = config.paramifiers[params[t].name];
if(p && p[handler] instanceof Function)
p[handler](params[t].value);
}
}
config.paramifiers = {};
config.paramifiers.start = {
oninit: function(v) {
safeMode = v.toLowerCase() == "safe";
}
};
config.paramifiers.open = {
onstart: function(v) {
story.displayTiddler("bottom",v,null,false,null);
}
};
config.paramifiers.story = {
onstart: function(v) {
var list = store.getTiddlerText(v,"").parseParams("open",null,false);
invokeParamifier(list,"onstart");
}
};
config.paramifiers.search = {
onstart: function(v) {
story.search(v,false,false);
}
};
config.paramifiers.searchRegExp = {
onstart: function(v) {
story.prototype.search(v,false,true);
}
};
config.paramifiers.tag = {
onstart: function(v) {
var tagged = store.getTaggedTiddlers(v,"title");
for(var t=0; t<tagged.length; t++)
story.displayTiddler("bottom",tagged[t].title,null,false,null);
}
};
config.paramifiers.newTiddler = {
onstart: function(v) {
if(!readOnly) {
story.displayTiddler(null,v,DEFAULT_EDIT_TEMPLATE);
story.focusTiddler(v,"text");
}
}
};
config.paramifiers.newJournal = {
onstart: function(v) {
if(!readOnly) {
var now = new Date();
var title = now.formatString(v.trim());
story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE);
story.focusTiddler(title,"text");
}
}
};
config.paramifiers.readOnly = {
onconfig: function(v) {
var p = v.toLowerCase();
readOnly = p == "yes" ? true : (p == "no" ? false : readOnly);
}
};
//--
//-- Formatter helpers
//--
function Formatter(formatters)
{
this.formatters = [];
var pattern = [];
for(var n=0; n<formatters.length; n++) {
pattern.push("(" + formatters[n].match + ")");
this.formatters.push(formatters[n]);
}
this.formatterRegExp = new RegExp(pattern.join("|"),"mg");
}
config.formatterHelpers = {
createElementAndWikify: function(w)
{
w.subWikifyTerm(createTiddlyElement(w.output,this.element),this.termRegExp);
},
inlineCssHelper: function(w)
{
var styles = [];
config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
var s,v;
if(lookaheadMatch[1]) {
s = lookaheadMatch[1].unDash();
v = lookaheadMatch[2];
} else {
s = lookaheadMatch[3].unDash();
v = lookaheadMatch[4];
}
if (s=="bgcolor")
s = "backgroundColor";
styles.push({style: s, value: v});
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
config.textPrimitives.cssLookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = config.textPrimitives.cssLookaheadRegExp.exec(w.source);
}
return styles;
},
applyCssHelper: function(e,styles)
{
for(var t=0; t< styles.length; t++) {
try {
e.style[styles[t].style] = styles[t].value;
} catch (ex) {
}
}
},
enclosedTextHelper: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var text = lookaheadMatch[1];
if(config.browser.isIE)
text = text.replace(/\n/g,"\r");
createTiddlyElement(w.output,this.element,null,null,text);
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
},
isExternalLink: function(link)
{
if(store.tiddlerExists(link) || store.isShadowTiddler(link)) {
return false;
}
var urlRegExp = new RegExp(config.textPrimitives.urlPattern,"mg");
if(urlRegExp.exec(link)) {
return true;
}
if (link.indexOf(".")!=-1 || link.indexOf("\\")!=-1 || link.indexOf("/")!=-1){
return true;
}
return false;
}
};
//--
//-- Standard formatters
//--
config.formatters = [
{
name: "table",
match: "^\\|(?:[^\\n]*)\\|(?:[fhck]?)$",
lookaheadRegExp: /^\|([^\n]*)\|([fhck]?)$/mg,
rowTermRegExp: /(\|(?:[fhck]?)$\n?)/mg,
cellRegExp: /(?:\|([^\n\|]*)\|)|(\|[fhck]?$\n?)/mg,
cellTermRegExp: /((?:\x20*)\|)/mg,
rowTypes: {"c":"caption", "h":"thead", "":"tbody", "f":"tfoot"},
handler: function(w)
{
var table = createTiddlyElement(w.output,"table",null,"twtable");
var prevColumns = [];
var currRowType = null;
var rowContainer;
var rowCount = 0;
w.nextMatch = w.matchStart;
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
var nextRowType = lookaheadMatch[2];
if(nextRowType == "k") {
table.className = lookaheadMatch[1];
w.nextMatch += lookaheadMatch[0].length+1;
} else {
if(nextRowType != currRowType) {
rowContainer = createTiddlyElement(table,this.rowTypes[nextRowType]);
currRowType = nextRowType;
}
if(currRowType == "c") {
// Caption
w.nextMatch++;
if(rowContainer != table.firstChild)
table.insertBefore(rowContainer,table.firstChild);
rowContainer.setAttribute("align",rowCount == 0?"top":"bottom");
w.subWikifyTerm(rowContainer,this.rowTermRegExp);
} else {
this.rowHandler(w,createTiddlyElement(rowContainer,"tr",null,(rowCount&1)?"oddRow":"evenRow"),prevColumns);
rowCount++;
}
}
this.lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = this.lookaheadRegExp.exec(w.source);
}
},
rowHandler: function(w,e,prevColumns)
{
var col = 0;
var colSpanCount = 1;
var prevCell = null;
this.cellRegExp.lastIndex = w.nextMatch;
var cellMatch = this.cellRegExp.exec(w.source);
while(cellMatch && cellMatch.index == w.nextMatch) {
if(cellMatch[1] == "~") {
// Rowspan
var last = prevColumns[col];
if(last) {
last.rowSpanCount++;
last.element.setAttribute("rowspan",last.rowSpanCount);
last.element.setAttribute("rowSpan",last.rowSpanCount); // Needed for IE
last.element.valign = "center";
}
w.nextMatch = this.cellRegExp.lastIndex-1;
} else if(cellMatch[1] == ">") {
// Colspan
colSpanCount++;
w.nextMatch = this.cellRegExp.lastIndex-1;
} else if(cellMatch[2]) {
// End of row
if(prevCell && colSpanCount > 1) {
prevCell.setAttribute("colspan",colSpanCount);
prevCell.setAttribute("colSpan",colSpanCount); // Needed for IE
}
w.nextMatch = this.cellRegExp.lastIndex;
break;
} else {
// Cell
w.nextMatch++;
var styles = config.formatterHelpers.inlineCssHelper(w);
var spaceLeft = false;
var chr = w.source.substr(w.nextMatch,1);
while(chr == " ") {
spaceLeft = true;
w.nextMatch++;
chr = w.source.substr(w.nextMatch,1);
}
var cell;
if(chr == "!") {
cell = createTiddlyElement(e,"th");
w.nextMatch++;
} else {
cell = createTiddlyElement(e,"td");
}
prevCell = cell;
prevColumns[col] = {rowSpanCount:1,element:cell};
if(colSpanCount > 1) {
cell.setAttribute("colspan",colSpanCount);
cell.setAttribute("colSpan",colSpanCount); // Needed for IE
colSpanCount = 1;
}
config.formatterHelpers.applyCssHelper(cell,styles);
w.subWikifyTerm(cell,this.cellTermRegExp);
if(w.matchText.substr(w.matchText.length-2,1) == " ") // spaceRight
cell.align = spaceLeft ? "center" : "left";
else if(spaceLeft)
cell.align = "right";
w.nextMatch--;
}
col++;
this.cellRegExp.lastIndex = w.nextMatch;
cellMatch = this.cellRegExp.exec(w.source);
}
}
},
{
name: "heading",
match: "^!{1,6}",
termRegExp: /(\n)/mg,
handler: function(w)
{
w.subWikifyTerm(createTiddlyElement(w.output,"h" + w.matchLength),this.termRegExp);
}
},
{
name: "list",
match: "^(?:[\\*#;:]+)",
lookaheadRegExp: /^(?:(?:(\*)|(#)|(;)|(:))+)/mg,
termRegExp: /(\n)/mg,
handler: function(w)
{
var stack = [w.output];
var currLevel = 0, currType = null;
var listLevel, listType, itemType;
w.nextMatch = w.matchStart;
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
while(lookaheadMatch && lookaheadMatch.index == w.nextMatch) {
if(lookaheadMatch[1]) {
listType = "ul";
itemType = "li";
} else if(lookaheadMatch[2]) {
listType = "ol";
itemType = "li";
} else if(lookaheadMatch[3]) {
listType = "dl";
itemType = "dt";
} else if(lookaheadMatch[4]) {
listType = "dl";
itemType = "dd";
}
listLevel = lookaheadMatch[0].length;
w.nextMatch += lookaheadMatch[0].length;
var t;
if(listLevel > currLevel) {
for(t=currLevel; t<listLevel; t++) {
var target = (currLevel == 0) ? stack[stack.length-1] : stack[stack.length-1].lastChild;
stack.push(createTiddlyElement(target,listType));
}
} else if(listLevel < currLevel) {
for(t=currLevel; t>listLevel; t--)
stack.pop();
} else if(listLevel == currLevel && listType != currType) {
stack.pop();
stack.push(createTiddlyElement(stack[stack.length-1].lastChild,listType));
}
currLevel = listLevel;
currType = listType;
var e = createTiddlyElement(stack[stack.length-1],itemType);
w.subWikifyTerm(e,this.termRegExp);
this.lookaheadRegExp.lastIndex = w.nextMatch;
lookaheadMatch = this.lookaheadRegExp.exec(w.source);
}
}
},
{
name: "quoteByBlock",
match: "^<<<\\n",
termRegExp: /(^<<<(\n|$))/mg,
element: "blockquote",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "quoteByLine",
match: "^>+",
lookaheadRegExp: /^>+/mg,
termRegExp: /(\n)/mg,
element: "blockquote",
handler: function(w)
{
var stack = [w.output];
var currLevel = 0;
var newLevel = w.matchLength;
var t;
do {
if(newLevel > currLevel) {
for(t=currLevel; t<newLevel; t++)
stack.push(createTiddlyElement(stack[stack.length-1],this.element));
} else if(newLevel < currLevel) {
for(t=currLevel; t>newLevel; t--)
stack.pop();
}
currLevel = newLevel;
w.subWikifyTerm(stack[stack.length-1],this.termRegExp);
createTiddlyElement(stack[stack.length-1],"br");
this.lookaheadRegExp.lastIndex = w.nextMatch;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
var matched = lookaheadMatch && lookaheadMatch.index == w.nextMatch;
if(matched) {
newLevel = lookaheadMatch[0].length;
w.nextMatch += lookaheadMatch[0].length;
}
} while(matched);
}
},
{
name: "rule",
match: "^----+$\\n?",
handler: function(w)
{
createTiddlyElement(w.output,"hr");
}
},
{
name: "monospacedByLine",
match: "^\\{\\{\\{\\n",
lookaheadRegExp: /^\{\{\{\n((?:^[^\n]*\n)+?)(^\}\}\}$\n?)/mg,
element: "pre",
handler: config.formatterHelpers.enclosedTextHelper
},
{
name: "monospacedByLineForCSS",
match: "^/\\*\\{\\{\\{\\*/\\n",
lookaheadRegExp: /\/\*\{\{\{\*\/\n*((?:^[^\n]*\n)+?)(\n*^\/\*\}\}\}\*\/$\n?)/mg,
element: "pre",
handler: config.formatterHelpers.enclosedTextHelper
},
{
name: "monospacedByLineForPlugin",
match: "^//\\{\\{\\{\\n",
lookaheadRegExp: /^\/\/\{\{\{\n\n*((?:^[^\n]*\n)+?)(\n*^\/\/\}\}\}$\n?)/mg,
element: "pre",
handler: config.formatterHelpers.enclosedTextHelper
},
{
name: "monospacedByLineForTemplate",
match: "^<!--\\{\\{\\{-->\\n",
lookaheadRegExp: /<!--\{\{\{-->\n*((?:^[^\n]*\n)+?)(\n*^<!--\}\}\}-->$\n?)/mg,
element: "pre",
handler: config.formatterHelpers.enclosedTextHelper
},
{
name: "wikifyCommentForPlugin",
match: "^/\\*\\*\\*\\n",
termRegExp: /(^\*\*\*\/\n)/mg,
handler: function(w)
{
w.subWikifyTerm(w.output,this.termRegExp);
}
},
{
name: "wikifyCommentForTemplate",
match: "^<!---\\n",
termRegExp: /(^--->\n)/mg,
handler: function(w)
{
w.subWikifyTerm(w.output,this.termRegExp);
}
},
{
name: "macro",
match: "<<",
lookaheadRegExp: /<<([^>\s]+)(?:\s*)((?:[^>]|(?:>(?!>)))*)>>/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart && lookaheadMatch[1]) {
w.nextMatch = this.lookaheadRegExp.lastIndex;
invokeMacro(w.output,lookaheadMatch[1],lookaheadMatch[2],w,w.tiddler);
}
}
},
{
name: "prettyLink",
match: "\\[\\[",
lookaheadRegExp: /\[\[(.*?)(?:\|(~)?(.*?))?\]\]/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var e;
var text = lookaheadMatch[1];
if(lookaheadMatch[3]) {
// Pretty bracketted link
var link = lookaheadMatch[3];
e = (!lookaheadMatch[2] && config.formatterHelpers.isExternalLink(link)) ?
createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
} else {
// Simple bracketted link
e = createTiddlyLink(w.output,text,false,null,w.isStatic,w.tiddler);
}
createTiddlyText(e,text);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "unWikiLink",
match: config.textPrimitives.unWikiLink+config.textPrimitives.wikiLink,
handler: function(w)
{
w.outputText(w.output,w.matchStart+1,w.nextMatch);
}
},
{
name: "wikiLink",
match: config.textPrimitives.wikiLink,
handler: function(w)
{
if(w.matchStart > 0) {
var preRegExp = new RegExp(config.textPrimitives.anyLetterStrict,"mg");
preRegExp.lastIndex = w.matchStart-1;
var preMatch = preRegExp.exec(w.source);
if(preMatch.index == w.matchStart-1) {
w.outputText(w.output,w.matchStart,w.nextMatch);
return;
}
}
if(w.autoLinkWikiWords == true || store.isShadowTiddler(w.matchText)) {
var link = createTiddlyLink(w.output,w.matchText,false,null,w.isStatic,w.tiddler);
w.outputText(link,w.matchStart,w.nextMatch);
} else {
w.outputText(w.output,w.matchStart,w.nextMatch);
}
}
},
{
name: "urlLink",
match: config.textPrimitives.urlPattern,
handler: function(w)
{
w.outputText(createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch);
}
},
{
name: "image",
match: "\\[[<>]?[Ii][Mm][Gg]\\[",
lookaheadRegExp: /\[([<]?)(>?)[Ii][Mm][Gg]\[(?:([^\|\]]+)\|)?([^\[\]\|]+)\](?:\[([^\]]*)\])?\]/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var e = w.output;
if(lookaheadMatch[5]) {
var link = lookaheadMatch[5];
e = config.formatterHelpers.isExternalLink(link) ? createExternalLink(w.output,link) : createTiddlyLink(w.output,link,false,null,w.isStatic,w.tiddler);
addClass(e,"imageLink");
}
var img = createTiddlyElement(e,"img");
if(lookaheadMatch[1])
img.align = "left";
else if(lookaheadMatch[2])
img.align = "right";
if(lookaheadMatch[3])
img.title = lookaheadMatch[3];
img.src = lookaheadMatch[4];
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "html",
match: "<[Hh][Tt][Mm][Ll]>",
lookaheadRegExp: /<[Hh][Tt][Mm][Ll]>((?:.|\n)*?)<\/[Hh][Tt][Mm][Ll]>/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
createTiddlyElement(w.output,"span").innerHTML = lookaheadMatch[1];
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "commentByBlock",
match: "/%",
lookaheadRegExp: /\/%((?:.|\n)*?)%\//mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
},
{
name: "boldByChar",
match: "''",
termRegExp: /('')/mg,
element: "strong",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "italicByChar",
match: "//",
termRegExp: /(\/\/)/mg,
element: "em",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "underlineByChar",
match: "__",
termRegExp: /(__)/mg,
element: "u",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "strikeByChar",
match: "--(?!\\s|$)",
termRegExp: /((?!\s)--|(?=\n\n))/mg,
element: "strike",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "superscriptByChar",
match: "\\^\\^",
termRegExp: /(\^\^)/mg,
element: "sup",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "subscriptByChar",
match: "~~",
termRegExp: /(~~)/mg,
element: "sub",
handler: config.formatterHelpers.createElementAndWikify
},
{
name: "monospacedByChar",
match: "\\{\\{\\{",
lookaheadRegExp: /\{\{\{((?:.|\n)*?)\}\}\}/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
createTiddlyElement(w.output,"code",null,null,lookaheadMatch[1]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "styleByChar",
match: "@@",
termRegExp: /(@@)/mg,
handler: function(w)
{
var e = createTiddlyElement(w.output,"span");
var styles = config.formatterHelpers.inlineCssHelper(w);
if(styles.length == 0)
e.className = "marked";
else
config.formatterHelpers.applyCssHelper(e,styles);
w.subWikifyTerm(e,this.termRegExp);
}
},
{
name: "lineBreak",
match: "\\n|<br ?/?>",
handler: function(w)
{
createTiddlyElement(w.output,"br");
}
},
{
name: "rawText",
match: "\\\"{3}|<nowiki>",
lookaheadRegExp: /(?:\"{3}|<nowiki>)((?:.|\n)*?)(?:\"{3}|<\/nowiki>)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
createTiddlyElement(w.output,"span",null,null,lookaheadMatch[1]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
},
{
name: "mdash",
match: "--",
handler: function(w)
{
createTiddlyElement(w.output,"span").innerHTML = "&mdash;";
}
},
{
name: "htmlEntitiesEncoding",
match: "(?:(?:&#?[a-zA-Z0-9]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9a-fA-F]|1D[c-fC-F][0-9a-fA-F]|20[d-fD-F][0-9a-fA-F]|FE2[0-9a-fA-F])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[a-zA-Z0-9]{2,8};)",
handler: function(w)
{
createTiddlyElement(w.output,"span").innerHTML = w.matchText;
}
},
{
name: "customClasses",
match: "\\{\\{",
termRegExp: /(\}\}\})/mg,
lookaheadRegExp: /\{\{[\s]*([\w]+[\s\w]*)[\s]*\{(\n?)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch) {
var e = createTiddlyElement(w.output,lookaheadMatch[2] == "\n" ? "div" : "span",null,lookaheadMatch[1]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
w.subWikifyTerm(e,this.termRegExp);
}
}
}
];
//--
//-- Wikifier
//--
function getParser(tiddler,format)
{
if(tiddler) {
if(!format)
format = tiddler.fields["wikiformat"];
if(format) {
for(var i in config.parsers) {
if(format == config.parsers[i].format)
return config.parsers[i];
}
} else {
for(var i in config.parsers) {
if(tiddler.isTagged(config.parsers[i].formatTag))
return config.parsers[i];
}
}
}
return formatter;
}
function wikify(source,output,highlightRegExp,tiddler)
{
if(source && source != "") {
var wikifier = new Wikifier(source,getParser(tiddler),highlightRegExp,tiddler);
wikifier.subWikifyUnterm(output);
}
}
function wikifyStatic(source,highlightRegExp,tiddler,format)
{
var e = createTiddlyElement(document.body,"div");
e.style.display = "none";
var html = "";
if(source && source != "") {
var wikifier = new Wikifier(source,getParser(tiddler,format),highlightRegExp,tiddler);
wikifier.isStatic = true;
wikifier.subWikifyUnterm(e);
html = e.innerHTML;
removeNode(e);
}
return html;
}
function wikifyPlain(title,theStore,limit)
{
if(!theStore)
theStore = store;
if(theStore.tiddlerExists(title) || theStore.isShadowTiddler(title)) {
return wikifyPlainText(theStore.getTiddlerText(title),limit,tiddler);
} else {
return "";
}
}
function wikifyPlainText(text,limit,tiddler)
{
if(limit > 0)
text = text.substr(0,limit);
var wikifier = new Wikifier(text,formatter,null,tiddler);
return wikifier.wikifyPlain();
}
function highlightify(source,output,highlightRegExp,tiddler)
{
if(source && source != "") {
var wikifier = new Wikifier(source,formatter,highlightRegExp,tiddler);
wikifier.outputText(output,0,source.length);
}
}
function Wikifier(source,formatter,highlightRegExp,tiddler)
{
this.source = source;
this.output = null;
this.formatter = formatter;
this.nextMatch = 0;
this.autoLinkWikiWords = tiddler && tiddler.autoLinkWikiWords() == false ? false : true;
this.highlightRegExp = highlightRegExp;
this.highlightMatch = null;
this.isStatic = false;
if(highlightRegExp) {
highlightRegExp.lastIndex = 0;
this.highlightMatch = highlightRegExp.exec(source);
}
this.tiddler = tiddler;
}
Wikifier.prototype.wikifyPlain = function()
{
var e = createTiddlyElement(document.body,"div");
this.subWikify(e);
var text = getPlainText(e);
removeNode(e);
return text;
};
Wikifier.prototype.subWikify = function(output,terminator)
{
if(terminator)
this.subWikifyTerm(output,new RegExp("(" + terminator + ")","mg"));
else
this.subWikifyUnterm(output);
};
Wikifier.prototype.subWikifyUnterm = function(output)
{
// subWikify() can be indirectly recursive, so we need to save the old output pointer
var oldOutput = this.output;
this.output = output;
this.formatter.formatterRegExp.lastIndex = this.nextMatch;
var formatterMatch = this.formatter.formatterRegExp.exec(this.source);
while(formatterMatch) {
// Output any text before the match
if(formatterMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,formatterMatch.index);
// Set the match parameters for the handler
this.matchStart = formatterMatch.index;
this.matchLength = formatterMatch[0].length;
this.matchText = formatterMatch[0];
this.nextMatch = this.formatter.formatterRegExp.lastIndex;
for(var t=1; t<formatterMatch.length; t++) {
if(formatterMatch[t]) {
this.formatter.formatters[t-1].handler(this);
this.formatter.formatterRegExp.lastIndex = this.nextMatch;
break;
}
}
formatterMatch = this.formatter.formatterRegExp.exec(this.source);
}
if(this.nextMatch < this.source.length) {
this.outputText(this.output,this.nextMatch,this.source.length);
this.nextMatch = this.source.length;
}
this.output = oldOutput;
};
Wikifier.prototype.subWikifyTerm = function(output,terminatorRegExp)
{
// subWikify() can be indirectly recursive, so we need to save the old output pointer
var oldOutput = this.output;
this.output = output;
// Get the first matches for the formatter and terminator RegExps
terminatorRegExp.lastIndex = this.nextMatch;
var terminatorMatch = terminatorRegExp.exec(this.source);
this.formatter.formatterRegExp.lastIndex = this.nextMatch;
var formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
while(terminatorMatch || formatterMatch) {
if(terminatorMatch && (!formatterMatch || terminatorMatch.index <= formatterMatch.index)) {
if(terminatorMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,terminatorMatch.index);
this.matchText = terminatorMatch[1];
this.matchLength = terminatorMatch[1].length;
this.matchStart = terminatorMatch.index;
this.nextMatch = this.matchStart + this.matchLength;
this.output = oldOutput;
return;
}
if(formatterMatch.index > this.nextMatch)
this.outputText(this.output,this.nextMatch,formatterMatch.index);
this.matchStart = formatterMatch.index;
this.matchLength = formatterMatch[0].length;
this.matchText = formatterMatch[0];
this.nextMatch = this.formatter.formatterRegExp.lastIndex;
for(var t=1; t<formatterMatch.length; t++) {
if(formatterMatch[t]) {
this.formatter.formatters[t-1].handler(this);
this.formatter.formatterRegExp.lastIndex = this.nextMatch;
break;
}
}
terminatorRegExp.lastIndex = this.nextMatch;
terminatorMatch = terminatorRegExp.exec(this.source);
formatterMatch = this.formatter.formatterRegExp.exec(terminatorMatch ? this.source.substr(0,terminatorMatch.index) : this.source);
}
if(this.nextMatch < this.source.length) {
this.outputText(this.output,this.nextMatch,this.source.length);
this.nextMatch = this.source.length;
}
this.output = oldOutput;
};
Wikifier.prototype.outputText = function(place,startPos,endPos)
{
while(this.highlightMatch && (this.highlightRegExp.lastIndex > startPos) && (this.highlightMatch.index < endPos) && (startPos < endPos)) {
if(this.highlightMatch.index > startPos) {
createTiddlyText(place,this.source.substring(startPos,this.highlightMatch.index));
startPos = this.highlightMatch.index;
}
var highlightEnd = Math.min(this.highlightRegExp.lastIndex,endPos);
var theHighlight = createTiddlyElement(place,"span",null,"highlight",this.source.substring(startPos,highlightEnd));
startPos = highlightEnd;
if(startPos >= this.highlightRegExp.lastIndex)
this.highlightMatch = this.highlightRegExp.exec(this.source);
}
if(startPos < endPos) {
createTiddlyText(place,this.source.substring(startPos,endPos));
}
};
//--
//-- Macro definitions
//--
config.macros.today.handler = function(place,macroName,params)
{
var now = new Date();
var text;
if(params[0])
text = now.formatString(params[0].trim());
else
text = now.toLocaleString();
createTiddlyElement(place,"span",null,null,text);
};
config.macros.version.handler = function(place)
{
createTiddlyElement(place,"span",null,null,version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
};
config.macros.list.handler = function(place,macroName,params)
{
var type = params[0] ? params[0] : "all";
var list = document.createElement("ul");
place.appendChild(list);
if(this[type].prompt)
createTiddlyElement(list,"li",null,"listTitle",this[type].prompt);
var results;
if(this[type].handler)
results = this[type].handler(params);
for(var t = 0; t < results.length; t++) {
var li = document.createElement("li");
list.appendChild(li);
createTiddlyLink(li,typeof results[t] == "string" ? results[t] : results[t].title,true);
}
};
config.macros.list.all.handler = function(params)
{
return store.reverseLookup("tags","excludeLists",false,"title");
};
config.macros.list.missing.handler = function(params)
{
return store.getMissingLinks();
};
config.macros.list.orphans.handler = function(params)
{
return store.getOrphans();
};
config.macros.list.shadowed.handler = function(params)
{
return store.getShadowed();
};
config.macros.list.touched.handler = function(params)
{
return store.getTouched();
};
config.macros.allTags.handler = function(place,macroName,params)
{
var tags = store.getTags(params[0]);
var ul = createTiddlyElement(place,"ul");
if(tags.length == 0)
createTiddlyElement(ul,"li",null,"listTitle",this.noTags);
for(var t=0; t<tags.length; t++) {
var title = tags[t][0];
var info = getTiddlyLinkInfo(title);
var li =createTiddlyElement(ul,"li");
var btn = createTiddlyButton(li,title + " (" + tags[t][1] + ")",this.tooltip.format([title]),onClickTag,info.classes);
btn.setAttribute("tag",title);
btn.setAttribute("refresh","link");
btn.setAttribute("tiddlyLink",title);
}
};
config.macros.timeline.handler = function(place,macroName,params)
{
var field = params[0] ? params[0] : "modified";
var tiddlers = store.reverseLookup("tags","excludeLists",false,field);
var lastDay = "";
var last = params[1] ? tiddlers.length-Math.min(tiddlers.length,parseInt(params[1])) : 0;
for(var t=tiddlers.length-1; t>=last; t--) {
var tiddler = tiddlers[t];
var theDay = tiddler[field].convertToLocalYYYYMMDDHHMM().substr(0,8);
if(theDay != lastDay) {
var theDateList = document.createElement("ul");
place.appendChild(theDateList);
createTiddlyElement(theDateList,"li",null,"listTitle",tiddler[field].formatString(this.dateFormat));
lastDay = theDay;
}
var theDateListItem = createTiddlyElement(theDateList,"li",null,"listLink");
theDateListItem.appendChild(createTiddlyLink(place,tiddler.title,true));
}
};
config.macros.search.handler = function(place,macroName,params)
{
var searchTimeout = null;
var btn = createTiddlyButton(place,this.label,this.prompt,this.onClick);
var txt = createTiddlyElement(place,"input",null,"txtOptionInput");
if(params[0])
txt.value = params[0];
txt.onkeyup = this.onKeyPress;
txt.onfocus = this.onFocus;
txt.setAttribute("size",this.sizeTextbox);
txt.setAttribute("accessKey",this.accessKey);
txt.setAttribute("autocomplete","off");
txt.setAttribute("lastSearchText","");
if(config.browser.isSafari) {
txt.setAttribute("type","search");
txt.setAttribute("results","5");
} else {
txt.setAttribute("type","text");
}
};
// Global because there's only ever one outstanding incremental search timer
config.macros.search.timeout = null;
config.macros.search.doSearch = function(txt)
{
if(txt.value.length > 0) {
story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
txt.setAttribute("lastSearchText",txt.value);
}
};
config.macros.search.onClick = function(e)
{
config.macros.search.doSearch(this.nextSibling);
return false;
};
config.macros.search.onKeyPress = function(e)
{
if(!e) var e = window.event;
switch(e.keyCode) {
case 13: // Ctrl-Enter
case 10: // Ctrl-Enter on IE PC
config.macros.search.doSearch(this);
break;
case 27: // Escape
this.value = "";
clearMessage();
break;
}
if(this.value.length > 2) {
if(this.value != this.getAttribute("lastSearchText")) {
if(config.macros.search.timeout)
clearTimeout(config.macros.search.timeout);
var txt = this;
config.macros.search.timeout = setTimeout(function() {config.macros.search.doSearch(txt);},500);
}
} else {
if(config.macros.search.timeout)
clearTimeout(config.macros.search.timeout);
}
};
config.macros.search.onFocus = function(e)
{
this.select();
};
config.macros.tiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
params = paramString.parseParams("name",null,true,false,true);
var names = params[0]["name"];
var tiddlerName = names[0];
var className = names[1] ? names[1] : null;
var args = params[0]["with"];
var wrapper = createTiddlyElement(place,"span",null,className);
if(!args) {
wrapper.setAttribute("refresh","content");
wrapper.setAttribute("tiddler",tiddlerName);
}
var text = store.getTiddlerText(tiddlerName);
if(text) {
var stack = config.macros.tiddler.tiddlerStack;
if(stack.indexOf(tiddlerName) !== -1)
return;
stack.push(tiddlerName);
try {
var n = args ? Math.min(args.length,9) : 0;
for(var i=0; i<n; i++) {
var placeholderRE = new RegExp("\\$" + (i + 1),"mg");
text = text.replace(placeholderRE,args[i]);
}
config.macros.tiddler.renderText(wrapper,text,tiddlerName,params);
} finally {
stack.pop();
}
}
};
config.macros.tiddler.renderText = function(place,text,tiddlerName,params)
{
wikify(text,place,null,store.getTiddler(tiddlerName));
};
config.macros.tiddler.tiddlerStack = [];
config.macros.tag.handler = function(place,macroName,params)
{
createTagButton(place,params[0]);
};
config.macros.tags.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
params = paramString.parseParams("anon",null,true,false,false);
var theList = createTiddlyElement(place,"ul");
var title = getParam(params,"anon","");
if(title && store.tiddlerExists(title))
tiddler = store.getTiddler(title);
var sep = getParam(params,"sep"," ");
var lingo = config.views.wikified.tag;
var prompt = tiddler.tags.length == 0 ? lingo.labelNoTags : lingo.labelTags;
createTiddlyElement(theList,"li",null,"listTitle",prompt.format([tiddler.title]));
for(var t=0; t<tiddler.tags.length; t++) {
createTagButton(createTiddlyElement(theList,"li"),tiddler.tags[t],tiddler.title);
if(t<tiddler.tags.length-1)
createTiddlyText(theList,sep);
}
};
config.macros.tagging.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
params = paramString.parseParams("anon",null,true,false,false);
var theList = createTiddlyElement(place,"ul");
var title = getParam(params,"anon","");
if(title == "" && tiddler instanceof Tiddler)
title = tiddler.title;
var sep = getParam(params,"sep"," ");
theList.setAttribute("title",this.tooltip.format([title]));
var tagged = store.getTaggedTiddlers(title);
var prompt = tagged.length == 0 ? this.labelNotTag : this.label;
createTiddlyElement(theList,"li",null,"listTitle",prompt.format([title,tagged.length]));
for(var t=0; t<tagged.length; t++) {
createTiddlyLink(createTiddlyElement(theList,"li"),tagged[t].title,true);
if(t<tagged.length-1)
createTiddlyText(theList,sep);
}
};
config.macros.closeAll.handler = function(place)
{
createTiddlyButton(place,this.label,this.prompt,this.onClick);
};
config.macros.closeAll.onClick = function(e)
{
story.closeAllTiddlers();
return false;
};
config.macros.permaview.handler = function(place)
{
createTiddlyButton(place,this.label,this.prompt,this.onClick);
};
config.macros.permaview.onClick = function(e)
{
story.permaView();
return false;
};
config.macros.saveChanges.handler = function(place)
{
if(!readOnly)
createTiddlyButton(place,this.label,this.prompt,this.onClick,null,null,this.accessKey);
};
config.macros.saveChanges.onClick = function(e)
{
saveChanges();
return false;
};
config.macros.slider.onClickSlider = function(e)
{
if(!e) var e = window.event;
var n = this.nextSibling;
var cookie = n.getAttribute("cookie");
var isOpen = n.style.display != "none";
if(config.options.chkAnimate && anim && typeof Slider == "function")
anim.startAnimating(new Slider(n,!isOpen,null,"none"));
else
n.style.display = isOpen ? "none" : "block";
config.options[cookie] = !isOpen;
saveOptionCookie(cookie);
return false;
};
config.macros.slider.createSlider = function(place,cookie,title,tooltip)
{
var cookie = cookie ? cookie : "";
var btn = createTiddlyButton(place,title,tooltip,this.onClickSlider);
var panel = createTiddlyElement(null,"div",null,"sliderPanel");
panel.setAttribute("cookie",cookie);
panel.style.display = config.options[cookie] ? "block" : "none";
place.appendChild(panel);
return panel;
};
config.macros.slider.handler = function(place,macroName,params)
{
var panel = this.createSlider(place,params[0],params[2],params[3]);
var text = store.getTiddlerText(params[1]);
panel.setAttribute("refresh","content");
panel.setAttribute("tiddler",params[1]);
if(text)
wikify(text,panel,null,store.getTiddler(params[1]));
};
config.macros.option.genericCreate = function(place,type,opt,className,desc)
{
var typeInfo = config.macros.option.types[type];
var c = document.createElement(typeInfo.elementType);
if(typeInfo.typeValue)
c.setAttribute("type",typeInfo.typeValue);
c[typeInfo.eventName] = typeInfo.onChange;
c.setAttribute("option",opt);
if(className)
c.className = className;
else
c.className = typeInfo.className;
if(config.optionsDesc[opt])
c.setAttribute("title",config.optionsDesc[opt]);
place.appendChild(c);
if(desc != "no")
createTiddlyText(place,config.optionsDesc[opt] ? config.optionsDesc[opt] : opt);
c[typeInfo.valueField] = config.options[opt];
return c;
};
config.macros.option.genericOnChange = function(e)
{
var opt = this.getAttribute("option");
if(opt) {
var optType = opt.substr(0,3);
var handler = config.macros.option.types[optType];
if (handler.elementType && handler.valueField)
config.macros.option.propagateOption(opt,handler.valueField,this[handler.valueField],handler.elementType);
}
return true;
};
config.macros.option.types = {
'txt': {
elementType: "input",
valueField: "value",
eventName: "onkeyup",
className: "txtOptionInput",
create: config.macros.option.genericCreate,
onChange: config.macros.option.genericOnChange
},
'chk': {
elementType: "input",
valueField: "checked",
eventName: "onclick",
className: "chkOptionInput",
typeValue: "checkbox",
create: config.macros.option.genericCreate,
onChange: config.macros.option.genericOnChange
}
};
config.macros.option.propagateOption = function(opt,valueField,value,elementType)
{
config.options[opt] = value;
saveOptionCookie(opt);
var nodes = document.getElementsByTagName(elementType);
for(var t=0; t<nodes.length; t++) {
var optNode = nodes[t].getAttribute("option");
if(opt == optNode)
nodes[t][valueField] = value;
}
};
config.macros.option.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
params = paramString.parseParams("anon",null,true,false,false);
var opt = (params[1] && params[1].name == "anon") ? params[1].value : getParam(params,"name",null);
var className = (params[2] && params[2].name == "anon") ? params[2].value : getParam(params,"class",null);
var desc = getParam(params,"desc","no");
var type = opt.substr(0,3);
var h = config.macros.option.types[type];
if (h && h.create)
h.create(place,type,opt,className,desc);
};
config.macros.options.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
params = paramString.parseParams("anon",null,true,false,false);
var showUnknown = getParam(params,"showUnknown","no");
var wizard = new Wizard();
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var chkUnknown = wizard.getElement("chkUnknown");
chkUnknown.checked = showUnknown == "yes";
chkUnknown.onchange = this.onChangeUnknown;
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
wizard.setValue("listWrapper",listWrapper);
this.refreshOptions(listWrapper,showUnknown == "yes");
};
config.macros.options.refreshOptions = function(listWrapper,showUnknown)
{
var opts = [];
for(var n in config.options) {
var opt = {};
opt.option = "";
opt.name = n;
opt.lowlight = !config.optionsDesc[n];
opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
if(!opt.lowlight || showUnknown)
opts.push(opt);
}
opts.sort(function(a,b) {return a.name.substr(3) < b.name.substr(3) ? -1 : (a.name.substr(3) == b.name.substr(3) ? 0 : +1);});
var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
for(n=0; n<opts.length; n++) {
var type = opts[n].name.substr(0,3);
var h = config.macros.option.types[type];
if (h && h.create) {
h.create(opts[n].colElements['option'],type,opts[n].name,null,"no");
}
}
};
config.macros.options.onChangeUnknown = function(e)
{
var wizard = new Wizard(this);
var listWrapper = wizard.getValue("listWrapper");
removeChildren(listWrapper);
config.macros.options.refreshOptions(listWrapper,this.checked);
return false;
};
config.macros.newTiddler.createNewTiddlerButton = function(place,title,params,label,prompt,accessKey,newFocus,isJournal)
{
var tags = [];
for(var t=1; t<params.length; t++) {
if((params[t].name == "anon" && t != 1) || (params[t].name == "tag"))
tags.push(params[t].value);
}
label = getParam(params,"label",label);
prompt = getParam(params,"prompt",prompt);
accessKey = getParam(params,"accessKey",accessKey);
newFocus = getParam(params,"focus",newFocus);
var customFields = getParam(params,"fields","");
if(!customFields && !store.isShadowTiddler(title))
customFields = String.encodeHashMap(config.defaultCustomFields);
var btn = createTiddlyButton(place,label,prompt,this.onClickNewTiddler,null,null,accessKey);
btn.setAttribute("newTitle",title);
btn.setAttribute("isJournal",isJournal ? "true" : "false");
if(tags.length > 0)
btn.setAttribute("params",tags.join("|"));
btn.setAttribute("newFocus",newFocus);
btn.setAttribute("newTemplate",getParam(params,"template",DEFAULT_EDIT_TEMPLATE));
if(customFields !== "")
btn.setAttribute("customFields",customFields);
var text = getParam(params,"text");
if(text !== undefined)
btn.setAttribute("newText",text);
return btn;
};
config.macros.newTiddler.onClickNewTiddler = function()
{
var title = this.getAttribute("newTitle");
if(this.getAttribute("isJournal") == "true") {
var now = new Date();
title = now.formatString(title.trim());
}
var params = this.getAttribute("params");
var tags = params ? params.split("|") : [];
var focus = this.getAttribute("newFocus");
var template = this.getAttribute("newTemplate");
var customFields = this.getAttribute("customFields");
story.displayTiddler(null,title,template,false,null,null);
var tiddlerElem = document.getElementById(story.idPrefix + title);
story.addCustomFields(tiddlerElem,customFields);
var text = this.getAttribute("newText");
if(typeof text == "string")
story.getTiddlerField(title,"text").value = text.format([title]);
for(var t=0;t<tags.length;t++)
story.setTiddlerTag(title,tags[t],+1);
story.focusTiddler(title,focus);
return false;
};
config.macros.newTiddler.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
if(!readOnly) {
params = paramString.parseParams("anon",null,true,false,false);
var title = params[1] && params[1].name == "anon" ? params[1].value : this.title;
title = getParam(params,"title",title);
this.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"title",false);
}
};
config.macros.newJournal.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
if(!readOnly) {
params = paramString.parseParams("anon",null,true,false,false);
var title = params[1] && params[1].name == "anon" ? params[1].value : "";
title = getParam(params,"title",title);
config.macros.newTiddler.createNewTiddlerButton(place,title,params,this.label,this.prompt,this.accessKey,"text",true);
}
};
config.macros.sparkline.handler = function(place,macroName,params)
{
var data = [];
var min = 0;
var max = 0;
for(var t=0; t<params.length; t++) {
var v = parseInt(params[t]);
if(v < min)
min = v;
if(v > max)
max = v;
data.push(v);
}
if(data.length < 1)
return;
var box = createTiddlyElement(place,"span",null,"sparkline",String.fromCharCode(160));
box.title = data.join(",");
var w = box.offsetWidth;
var h = box.offsetHeight;
box.style.paddingRight = (data.length * 2 - w) + "px";
box.style.position = "relative";
for(var d=0; d<data.length; d++) {
var tick = document.createElement("img");
tick.border = 0;
tick.className = "sparktick";
tick.style.position = "absolute";
tick.src = "data:image/gif,GIF89a%01%00%01%00%91%FF%00%FF%FF%FF%00%00%00%C0%C0%C0%00%00%00!%F9%04%01%00%00%02%00%2C%00%00%00%00%01%00%01%00%40%02%02T%01%00%3B";
tick.style.left = d*2 + "px";
tick.style.width = "2px";
var v = Math.floor(((data[d] - min)/(max-min)) * h);
tick.style.top = (h-v) + "px";
tick.style.height = v + "px";
box.appendChild(tick);
}
};
config.macros.tabs.handler = function(place,macroName,params)
{
var cookie = params[0];
var numTabs = (params.length-1)/3;
var wrapper = createTiddlyElement(null,"div",null,cookie);
var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
tabset.setAttribute("cookie",cookie);
var validTab = false;
for(var t=0; t<numTabs; t++) {
var label = params[t*3+1];
var prompt = params[t*3+2];
var content = params[t*3+3];
var tab = createTiddlyButton(tabset,label,prompt,this.onClickTab,"tab tabUnselected");
tab.setAttribute("tab",label);
tab.setAttribute("content",content);
tab.title = prompt;
if(config.options[cookie] == label)
validTab = true;
}
if(!validTab)
config.options[cookie] = params[1];
place.appendChild(wrapper);
this.switchTab(tabset,config.options[cookie]);
};
config.macros.tabs.onClickTab = function(e)
{
config.macros.tabs.switchTab(this.parentNode,this.getAttribute("tab"));
return false;
};
config.macros.tabs.switchTab = function(tabset,tab)
{
var cookie = tabset.getAttribute("cookie");
var theTab = null;
var nodes = tabset.childNodes;
for(var t=0; t<nodes.length; t++) {
if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab) {
theTab = nodes[t];
theTab.className = "tab tabSelected";
} else {
nodes[t].className = "tab tabUnselected";
}
}
if(theTab) {
if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
removeNode(tabset.nextSibling);
var tabContent = createTiddlyElement(null,"div",null,"tabContents");
tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
var contentTitle = theTab.getAttribute("content");
wikify(store.getTiddlerText(contentTitle),tabContent,null,store.getTiddler(contentTitle));
if(cookie) {
config.options[cookie] = tab;
saveOptionCookie(cookie);
}
}
};
// <<gradient [[tiddler name]] vert|horiz rgb rgb rgb rgb... >>
config.macros.gradient.handler = function(place,macroName,params,wikifier)
{
var terminator = ">>";
var panel;
if(wikifier)
panel = createTiddlyElement(place,"div",null,"gradient");
else
panel = place;
panel.style.position = "relative";
panel.style.overflow = "hidden";
panel.style.zIndex = "0";
var t;
if(wikifier) {
var styles = config.formatterHelpers.inlineCssHelper(wikifier);
config.formatterHelpers.applyCssHelper(panel,styles);
}
var colours = [];
for(t=1; t<params.length; t++) {
var c = new RGB(params[t]);
if(c)
colours.push(c);
}
drawGradient(panel,params[0] != "vert",colours);
if(wikifier)
wikifier.subWikify(panel,terminator);
if(document.all) {
panel.style.height = "100%";
panel.style.width = "100%";
}
};
config.macros.message.handler = function(place,macroName,params)
{
if(params[0]) {
var m = config;
var p = params[0].split(".");
for(var t=0; t<p.length; t++) {
if(p[t] in m)
m = m[p[t]];
else
break;
}
createTiddlyText(place,m.toString().format(params.splice(1)));
}
};
config.macros.view.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
if((tiddler instanceof Tiddler) && params[0]) {
var value = store.getValue(tiddler,params[0]);
if(value != undefined) {
switch(params[1]) {
case undefined:
highlightify(value,place,highlightHack,tiddler);
break;
case "link":
createTiddlyLink(place,value,true);
break;
case "wikified":
wikify(value,place,highlightHack,tiddler);
break;
case "date":
value = Date.convertFromYYYYMMDDHHMM(value);
createTiddlyText(place,value.formatString(params[2] ? params[2] : config.views.wikified.dateFormat));
break;
}
}
}
};
config.macros.edit.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
var field = params[0];
var rows = params[1];
if((tiddler instanceof Tiddler) && field) {
story.setDirty(tiddler.title,true);
if(field != "text" && !rows) {
var e = createTiddlyElement(null,"input");
if(tiddler.isReadOnly())
e.setAttribute("readOnly","readOnly");
e.setAttribute("edit",field);
e.setAttribute("type","text");
var v = store.getValue(tiddler,field);
if(!v)
v = "";
e.value = v;
e.setAttribute("size","40");
e.setAttribute("autocomplete","off");
place.appendChild(e);
} else {
var wrapper1 = createTiddlyElement(null,"fieldset",null,"fieldsetFix");
var wrapper2 = createTiddlyElement(wrapper1,"div");
var e = createTiddlyElement(wrapper2,"textarea");
if(tiddler.isReadOnly())
e.setAttribute("readOnly","readOnly");
var v = store.getValue(tiddler,field);
if(!v)
v = "";
e.value = v;
var rows = rows ? rows : 10;
var lines = v.match(/\n/mg);
var maxLines = Math.max(parseInt(config.options.txtMaxEditRows),5);
if(lines != null && lines.length > rows)
rows = lines.length + 5;
rows = Math.min(rows,maxLines);
e.setAttribute("rows",rows);
e.setAttribute("edit",field);
place.appendChild(wrapper1);
}
}
};
config.macros.tagChooser.onClick = function(e)
{
if(!e) var e = window.event;
var lingo = config.views.editor.tagChooser;
var popup = Popup.create(this);
var tags = store.getTags();
if(tags.length == 0)
createTiddlyText(createTiddlyElement(popup,"li"),lingo.popupNone);
for(var t=0; t<tags.length; t++) {
var theTag = createTiddlyButton(createTiddlyElement(popup,"li"),tags[t][0],lingo.tagTooltip.format([tags[t][0]]),config.macros.tagChooser.onTagClick);
theTag.setAttribute("tag",tags[t][0]);
theTag.setAttribute("tiddler",this.getAttribute("tiddler"));
}
Popup.show();
e.cancelBubble = true;
if(e.stopPropagation) e.stopPropagation();
return false;
};
config.macros.tagChooser.onTagClick = function(e)
{
if(!e) var e = window.event;
var tag = this.getAttribute("tag");
var title = this.getAttribute("tiddler");
if(!readOnly)
story.setTiddlerTag(title,tag,0);
return false;
};
config.macros.tagChooser.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
if(tiddler instanceof Tiddler) {
var title = tiddler.title;
var lingo = config.views.editor.tagChooser;
var btn = createTiddlyButton(place,lingo.text,lingo.tooltip,this.onClick);
btn.setAttribute("tiddler",title);
}
};
// Create a toolbar command button
config.macros.toolbar.createCommand = function(place,commandName,tiddler,theClass)
{
if(typeof commandName != "string") {
var c = null;
for(var t in config.commands) {
if(config.commands[t] == commandName)
c = t;
}
commandName = c;
}
if((tiddler instanceof Tiddler) && (typeof commandName == "string")) {
var command = config.commands[commandName];
if(command.isEnabled ? command.isEnabled(tiddler) : this.isCommandEnabled(command,tiddler)) {
var text = command.getText ? command.getText(tiddler) : this.getCommandText(command,tiddler);
var tooltip = command.getTooltip ? command.getTooltip(tiddler) : this.getCommandTooltip(command,tiddler);
var cmd;
switch(command.type) {
case "popup":
cmd = this.onClickPopup;
break;
case "command":
default:
cmd = this.onClickCommand;
break;
}
var btn = createTiddlyButton(null,text,tooltip,cmd);
btn.setAttribute("commandName",commandName);
btn.setAttribute("tiddler",tiddler.title);
if(theClass)
addClass(btn,theClass);
place.appendChild(btn);
}
}
};
config.macros.toolbar.isCommandEnabled = function(command,tiddler)
{
var title = tiddler.title;
var ro = tiddler.isReadOnly();
var shadow = store.isShadowTiddler(title) && !store.tiddlerExists(title);
return (!ro || (ro && !command.hideReadOnly)) && !(shadow && command.hideShadow);
};
config.macros.toolbar.getCommandText = function(command,tiddler)
{
return tiddler.isReadOnly() && command.readOnlyText ? command.readOnlyText : command.text;
};
config.macros.toolbar.getCommandTooltip = function(command,tiddler)
{
return tiddler.isReadOnly() && command.readOnlyTooltip ? command.readOnlyTooltip : command.tooltip;
};
config.macros.toolbar.onClickCommand = function(e)
{
if(!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
var command = config.commands[this.getAttribute("commandName")];
return command.handler(e,this,this.getAttribute("tiddler"));
};
config.macros.toolbar.onClickPopup = function(e)
{
if(!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
var popup = Popup.create(this);
var command = config.commands[this.getAttribute("commandName")];
var title = this.getAttribute("tiddler");
var tiddler = store.fetchTiddler(title);
popup.setAttribute("tiddler",title);
command.handlePopup(popup,title);
Popup.show();
return false;
};
// Invoke the first command encountered from a given place that is tagged with a specified class
config.macros.toolbar.invokeCommand = function(place,theClass,event)
{
var children = place.getElementsByTagName("a");
for(var t=0; t<children.length; t++) {
var c = children[t];
if(hasClass(c,theClass) && c.getAttribute && c.getAttribute("commandName")) {
if(c.onclick instanceof Function)
c.onclick.call(c,event);
break;
}
}
};
config.macros.toolbar.onClickMore = function(e)
{
var e = this.nextSibling;
e.style.display = "inline";
removeNode(this);
return false;
};
config.macros.toolbar.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
for(var t=0; t<params.length; t++) {
var c = params[t];
switch(c) {
case '>':
var btn = createTiddlyButton(place,this.moreLabel,this.morePrompt,config.macros.toolbar.onClickMore);
addClass(btn,"moreCommand");
var e = createTiddlyElement(place,"span",null,"moreCommand");
e.style.display = "none";
place = e;
break;
default:
var theClass = "";
switch(c.substr(0,1)) {
case "+":
theClass = "defaultCommand";
c = c.substr(1);
break;
case "-":
theClass = "cancelCommand";
c = c.substr(1);
break;
}
if(c in config.commands)
this.createCommand(place,c,tiddler,theClass);
break;
}
}
};
config.macros.refreshDisplay.handler = function(place)
{
createTiddlyButton(place,this.label,this.prompt,this.onClick);
};
config.macros.refreshDisplay.onClick = function(e)
{
refreshAll();
return false;
};
config.macros.annotations.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
var title = tiddler ? tiddler.title : null;
var a = title ? config.annotations[title] : null;
if(!tiddler || !title || !a)
return;
var text = a.format([title]);
wikify(text,createTiddlyElement(place,"div",null,"annotation"),null,tiddler);
};
//--
//-- Menu and toolbar commands
//--
config.commands.closeTiddler.handler = function(event,src,title)
{
story.closeTiddler(title,true);
return false;
};
config.commands.closeOthers.handler = function(event,src,title)
{
story.closeAllTiddlers(title);
return false;
};
config.commands.editTiddler.handler = function(event,src,title)
{
clearMessage();
var tiddlerElem = document.getElementById(story.idPrefix + title);
var fields = tiddlerElem.getAttribute("tiddlyFields");
story.displayTiddler(null,title,DEFAULT_EDIT_TEMPLATE,false,null,fields);
story.focusTiddler(title,"text");
return false;
};
config.commands.saveTiddler.handler = function(event,src,title)
{
var newTitle = story.saveTiddler(title,event.shiftKey);
if(newTitle)
story.displayTiddler(null,newTitle);
return false;
};
config.commands.cancelTiddler.handler = function(event,src,title)
{
if(story.hasChanges(title) && !readOnly) {
if(!confirm(this.warning.format([title])))
return false;
}
story.setDirty(title,false);
story.displayTiddler(null,title);
return false;
};
config.commands.deleteTiddler.handler = function(event,src,title)
{
var deleteIt = true;
if (config.options.chkConfirmDelete)
deleteIt = confirm(this.warning.format([title]));
if (deleteIt) {
store.removeTiddler(title);
story.closeTiddler(title,true);
autoSaveChanges();
}
return false;
};
config.commands.permalink.handler = function(event,src,title)
{
var t = encodeURIComponent(String.encodeTiddlyLink(title));
if(window.location.hash != t)
window.location.hash = t;
return false;
};
config.commands.references.handlePopup = function(popup,title)
{
var references = store.getReferringTiddlers(title);
var c = false;
for(var r=0; r<references.length; r++) {
if(references[r].title != title && !references[r].isTagged("excludeLists")) {
createTiddlyLink(createTiddlyElement(popup,"li"),references[r].title,true);
c = true;
}
}
if(!c)
createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),this.popupNone);
};
config.commands.jump.handlePopup = function(popup,title)
{
story.forEachTiddler(function(title,element) {
createTiddlyLink(createTiddlyElement(popup,"li"),title,true,null,false,null,true);
});
};
config.commands.syncing.handlePopup = function(popup,title)
{
var tiddler = store.fetchTiddler(title);
if(!tiddler)
return;
var serverType = tiddler.getServerType();
var serverHost = tiddler.fields['server.host'];
var serverWorkspace = tiddler.fields['server.workspace'];
if(!serverWorkspace)
serverWorkspace = "";
if(serverType) {
var e = createTiddlyElement(popup,"li",null,"popupMessage");
e.innerHTML = config.commands.syncing.currentlySyncing.format([serverType,serverHost,serverWorkspace]);
} else {
createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.notCurrentlySyncing);
}
if(serverType) {
createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
var btn = createTiddlyButton(createTiddlyElement(popup,"li"),this.captionUnSync,null,config.commands.syncing.onChooseServer);
btn.setAttribute("tiddler",title);
btn.setAttribute("server.type","");
}
createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
createTiddlyElement(popup,"li",null,"popupMessage",config.commands.syncing.chooseServer);
var feeds = store.getTaggedTiddlers("systemServer","title");
for(var t=0; t<feeds.length; t++) {
var f = feeds[t];
var feedServerType = store.getTiddlerSlice(f.title,"Type");
if(!feedServerType)
feedServerType = "file";
var feedServerHost = store.getTiddlerSlice(f.title,"URL");
if(!feedServerHost)
feedServerHost = "";
var feedServerWorkspace = store.getTiddlerSlice(f.title,"Workspace");
if(!feedServerWorkspace)
feedServerWorkspace = "";
var caption = f.title;
if(serverType == feedServerType && serverHost == feedServerHost && serverWorkspace == feedServerWorkspace) {
caption = config.commands.syncing.currServerMarker + caption;
} else {
caption = config.commands.syncing.notCurrServerMarker + caption;
}
btn = createTiddlyButton(createTiddlyElement(popup,"li"),caption,null,config.commands.syncing.onChooseServer);
btn.setAttribute("tiddler",title);
btn.setAttribute("server.type",feedServerType);
btn.setAttribute("server.host",feedServerHost);
btn.setAttribute("server.workspace",feedServerWorkspace);
}
};
config.commands.syncing.onChooseServer = function(e)
{
var tiddler = this.getAttribute("tiddler");
var serverType = this.getAttribute("server.type");
if(serverType) {
store.addTiddlerFields(tiddler,{
'server.type': serverType,
'server.host': this.getAttribute("server.host"),
'server.workspace': this.getAttribute("server.workspace")
});
} else {
store.setValue(tiddler,'server',null);
}
return false;
};
config.commands.fields.handlePopup = function(popup,title)
{
var tiddler = store.fetchTiddler(title);
if(!tiddler)
return;
var fields = {};
store.forEachField(tiddler,function(tiddler,fieldName,value) {fields[fieldName] = value;},true);
var items = [];
for(var t in fields) {
items.push({field: t,value: fields[t]});
}
items.sort(function(a,b) {return a.field < b.field ? -1 : (a.field == b.field ? 0 : +1);});
if(items.length > 0)
ListView.create(popup,items,this.listViewTemplate);
else
createTiddlyElement(popup,"div",null,null,this.emptyText);
};
//--
//-- Tiddler() object
//--
function Tiddler(title)
{
this.title = title;
this.text = null;
this.modifier = null;
this.modified = new Date();
this.created = new Date();
this.links = [];
this.linksUpdated = false;
this.tags = [];
this.fields = {};
return this;
}
Tiddler.prototype.getLinks = function()
{
if(this.linksUpdated==false)
this.changed();
return this.links;
};
// Returns the fields that are inherited in string field:"value" field2:"value2" format
Tiddler.prototype.getInheritedFields = function()
{
var f = {};
for(i in this.fields) {
if(i=="server.host" || i=="server.workspace" || i=="wikiformat"|| i=="server.type") {
f[i] = this.fields[i];
}
}
return String.encodeHashMap(f);
};
// Increment the changeCount of a tiddler
Tiddler.prototype.incChangeCount = function()
{
var c = this.fields['changecount'];
c = c ? parseInt(c) : 0;
this.fields['changecount'] = String(c+1);
};
// Clear the changeCount of a tiddler
Tiddler.prototype.clearChangeCount = function()
{
if(this.fields['changecount']) {
delete this.fields['changecount'];
}
};
// Returns true if the tiddler has been updated since the tiddler was created or downloaded
Tiddler.prototype.isTouched = function()
{
var changeCount = this.fields['changecount'];
if(changeCount === undefined)
changeCount = 0;
return changeCount > 0;
};
// Format the text for storage in an RSS item
Tiddler.prototype.saveToRss = function(url)
{
var s = [];
s.push("<item>");
s.push("<title" + ">" + this.title.htmlEncode() + "</title" + ">");
s.push("<description>" + wikifyStatic(this.text,null,this).htmlEncode() + "</description>");
for(var t=0; t<this.tags.length; t++)
s.push("<category>" + this.tags[t] + "</category>");
s.push("<link>" + url + "#" + encodeURIComponent(String.encodeTiddlyLink(this.title)) + "</link>");
s.push("<pubDate>" + this.modified.toGMTString() + "</pubDate>");
s.push("</item>");
return s.join("\n");
};
// Change the text and other attributes of a tiddler
Tiddler.prototype.set = function(title,text,modifier,modified,tags,created,fields)
{
this.assign(title,text,modifier,modified,tags,created,fields);
this.changed();
return this;
};
// Change the text and other attributes of a tiddler without triggered a tiddler.changed() call
Tiddler.prototype.assign = function(title,text,modifier,modified,tags,created,fields)
{
if(title != undefined)
this.title = title;
if(text != undefined)
this.text = text;
if(modifier != undefined)
this.modifier = modifier;
if(modified != undefined)
this.modified = modified;
if(created != undefined)
this.created = created;
if(fields != undefined)
this.fields = fields;
if(tags != undefined)
this.tags = (typeof tags == "string") ? tags.readBracketedList() : tags;
else if(this.tags == undefined)
this.tags = [];
return this;
};
// Get the tags for a tiddler as a string (space delimited, using [[brackets]] for tags containing spaces)
Tiddler.prototype.getTags = function()
{
return String.encodeTiddlyLinkList(this.tags);
};
// Test if a tiddler carries a tag
Tiddler.prototype.isTagged = function(tag)
{
return this.tags.indexOf(tag) != -1;
};
// Static method to convert "\n" to newlines, "\s" to "\"
Tiddler.unescapeLineBreaks = function(text)
{
return text ? text.unescapeLineBreaks() : "";
};
// Convert newlines to "\n", "\" to "\s"
Tiddler.prototype.escapeLineBreaks = function()
{
return this.text.escapeLineBreaks();
};
// Updates the secondary information (like links[] array) after a change to a tiddler
Tiddler.prototype.changed = function()
{
this.links = [];
var t = this.autoLinkWikiWords() ? 0 : 1;
var tiddlerLinkRegExp = t==0 ? config.textPrimitives.tiddlerAnyLinkRegExp : config.textPrimitives.tiddlerForcedLinkRegExp;
tiddlerLinkRegExp.lastIndex = 0;
var formatMatch = tiddlerLinkRegExp.exec(this.text);
while(formatMatch) {
var lastIndex = tiddlerLinkRegExp.lastIndex;
if(t==0 && formatMatch[1] && formatMatch[1] != this.title) {
// wikiWordLink
if(formatMatch.index > 0) {
var preRegExp = new RegExp(config.textPrimitives.unWikiLink+"|"+config.textPrimitives.anyLetter,"mg");
preRegExp.lastIndex = formatMatch.index-1;
var preMatch = preRegExp.exec(this.text);
if(preMatch.index != formatMatch.index-1)
this.links.pushUnique(formatMatch[1]);
} else {
this.links.pushUnique(formatMatch[1]);
}
}
else if(formatMatch[2-t] && !config.formatterHelpers.isExternalLink(formatMatch[3-t])) // titledBrackettedLink
this.links.pushUnique(formatMatch[3-t]);
else if(formatMatch[4-t] && formatMatch[4-t] != this.title) // brackettedLink
this.links.pushUnique(formatMatch[4-t]);
tiddlerLinkRegExp.lastIndex = lastIndex;
formatMatch = tiddlerLinkRegExp.exec(this.text);
}
this.linksUpdated = true;
};
Tiddler.prototype.getSubtitle = function()
{
var theModifier = this.modifier;
if(!theModifier)
theModifier = config.messages.subtitleUnknown;
var theModified = this.modified;
if(theModified)
theModified = theModified.toLocaleString();
else
theModified = config.messages.subtitleUnknown;
return config.messages.tiddlerLinkTooltip.format([this.title,theModifier,theModified]);
};
Tiddler.prototype.isReadOnly = function()
{
return readOnly;
};
Tiddler.prototype.autoLinkWikiWords = function()
{
return !(this.isTagged("systemConfig") || this.isTagged("excludeMissing"));
};
Tiddler.prototype.generateFingerprint = function()
{
return "0x" + Crypto.hexSha1Str(this.text);
};
Tiddler.prototype.getServerType = function()
{
var serverType = null;
if(this.fields && this.fields['server.type'])
serverType = this.fields['server.type'];
if(!serverType)
serverType = this.fields['wikiformat'];
if(serverType && !config.adaptors[serverType])
serverType = null;
return serverType;
};
Tiddler.prototype.getAdaptor = function()
{
var serverType = this.getServerType();
if(serverType)
return new config.adaptors[serverType];
else
return null;
};
//--
//-- TiddlyWiki() object contains Tiddler()s
//--
function TiddlyWiki()
{
var tiddlers = {}; // Hashmap by name of tiddlers
this.tiddlersUpdated = false;
this.namedNotifications = []; // Array of {name:,notify:} of notification functions
this.notificationLevel = 0;
this.slices = {}; // map tiddlerName->(map sliceName->sliceValue). Lazy.
this.clear = function() {
tiddlers = {};
this.setDirty(false);
};
this.fetchTiddler = function(title) {
return tiddlers[title];
};
this.deleteTiddler = function(title) {
delete this.slices[title];
delete tiddlers[title];
};
this.addTiddler = function(tiddler) {
delete this.slices[tiddler.title];
tiddlers[tiddler.title] = tiddler;
};
this.forEachTiddler = function(callback) {
for(var t in tiddlers) {
var tiddler = tiddlers[t];
if(tiddler instanceof Tiddler)
callback.call(this,t,tiddler);
}
};
}
TiddlyWiki.prototype.setDirty = function(dirty)
{
this.dirty = dirty;
};
TiddlyWiki.prototype.isDirty = function()
{
return this.dirty;
};
TiddlyWiki.prototype.suspendNotifications = function()
{
this.notificationLevel--;
};
TiddlyWiki.prototype.resumeNotifications = function()
{
this.notificationLevel++;
};
// Invoke the notification handlers for a particular tiddler
TiddlyWiki.prototype.notify = function(title,doBlanket)
{
if(!this.notificationLevel) {
for(var t=0; t<this.namedNotifications.length; t++) {
var n = this.namedNotifications[t];
if((n.name == null && doBlanket) || (n.name == title))
n.notify(title);
}
}
};
// Invoke the notification handlers for all tiddlers
TiddlyWiki.prototype.notifyAll = function()
{
if(!this.notificationLevel) {
for(var t=0; t<this.namedNotifications.length; t++) {
var n = this.namedNotifications[t];
if(n.name)
n.notify(n.name);
}
}
};
// Add a notification handler to a tiddler
TiddlyWiki.prototype.addNotification = function(title,fn)
{
for(var i=0; i<this.namedNotifications.length; i++) {
if((this.namedNotifications[i].name == title) && (this.namedNotifications[i].notify == fn))
return this;
}
this.namedNotifications.push({name: title, notify: fn});
return this;
};
TiddlyWiki.prototype.removeTiddler = function(title)
{
var tiddler = this.fetchTiddler(title);
if(tiddler) {
this.deleteTiddler(title);
this.notify(title,true);
this.setDirty(true);
}
};
TiddlyWiki.prototype.tiddlerExists = function(title)
{
var t = this.fetchTiddler(title);
return t != undefined;
};
TiddlyWiki.prototype.isShadowTiddler = function(title)
{
return typeof config.shadowTiddlers[title] == "string";
};
TiddlyWiki.prototype.getTiddler = function(title)
{
var t = this.fetchTiddler(title);
if(t != undefined)
return t;
else
return null;
};
TiddlyWiki.prototype.getTiddlerText = function(title,defaultText)
{
var tiddler = this.fetchTiddler(title);
if(tiddler)
return tiddler.text;
if(!title)
return defaultText;
var pos = title.indexOf(config.textPrimitives.sliceSeparator);
if(pos != -1) {
var slice = this.getTiddlerSlice(title.substr(0,pos),title.substr(pos + config.textPrimitives.sliceSeparator.length));
if(slice)
return slice;
}
if(this.isShadowTiddler(title))
return config.shadowTiddlers[title];
if(defaultText != undefined)
return defaultText;
return null;
};
TiddlyWiki.prototype.slicesRE = /(?:[\'\/]*~?([\.\w]+)[\'\/]*\:[\'\/]*\s*(.*?)\s*$)|(?:\|[\'\/]*~?([\.\w]+)\:?[\'\/]*\|\s*(.*?)\s*\|)/gm;
// @internal
TiddlyWiki.prototype.calcAllSlices = function(title)
{
var slices = {};
var text = this.getTiddlerText(title,"");
this.slicesRE.lastIndex = 0;
do {
var m = this.slicesRE.exec(text);
if(m) {
if(m[1])
slices[m[1]] = m[2];
else
slices[m[3]] = m[4];
}
} while(m);
return slices;
};
// Returns the slice of text of the given name
TiddlyWiki.prototype.getTiddlerSlice = function(title,sliceName)
{
var slices = this.slices[title];
if(!slices) {
slices = this.calcAllSlices(title);
this.slices[title] = slices;
}
return slices[sliceName];
};
// Build an hashmap of the specified named slices of a tiddler
TiddlyWiki.prototype.getTiddlerSlices = function(title,sliceNames)
{
var r = {};
for(var t=0; t<sliceNames.length; t++) {
var slice = this.getTiddlerSlice(title,sliceNames[t]);
if(slice)
r[sliceNames[t]] = slice;
}
return r;
};
TiddlyWiki.prototype.getRecursiveTiddlerText = function(title,defaultText,depth)
{
var bracketRegExp = new RegExp("(?:\\[\\[([^\\]]+)\\]\\])","mg");
var text = this.getTiddlerText(title,null);
if(text == null)
return defaultText;
var textOut = [];
var lastPos = 0;
do {
var match = bracketRegExp.exec(text);
if(match) {
textOut.push(text.substr(lastPos,match.index-lastPos));
if(match[1]) {
if(depth <= 0)
textOut.push(match[1]);
else
textOut.push(this.getRecursiveTiddlerText(match[1],"[[" + match[1] + "]]",depth-1));
}
lastPos = match.index + match[0].length;
} else {
textOut.push(text.substr(lastPos));
}
} while(match);
return textOut.join("");
};
TiddlyWiki.prototype.setTiddlerTag = function(title,status,tag)
{
var tiddler = this.fetchTiddler(title);
if(tiddler) {
var t = tiddler.tags.indexOf(tag);
if(t != -1)
tiddler.tags.splice(t,1);
if(status)
tiddler.tags.push(tag);
tiddler.changed();
this.incChangeCount(title);
this.notify(title,true);
this.setDirty(true);
}
};
TiddlyWiki.prototype.addTiddlerFields = function(title,fields)
{
var tiddler = this.fetchTiddler(title);
if(!tiddler)
return;
merge(tiddler.fields,fields);
tiddler.changed();
this.incChangeCount(title);
this.notify(title,true);
this.setDirty(true);
};
TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields,clearChangeCount,created)
{
var tiddler = this.fetchTiddler(title);
if(tiddler) {
created = created ? created : tiddler.created; // Preserve created date
this.deleteTiddler(title);
} else {
created = created ? created : modified;
tiddler = new Tiddler();
}
tiddler.set(newTitle,newBody,modifier,modified,tags,created,fields);
this.addTiddler(tiddler);
if(clearChangeCount)
tiddler.clearChangeCount();
else
tiddler.incChangeCount();
if(title != newTitle)
this.notify(title,true);
this.notify(newTitle,true);
this.setDirty(true);
return tiddler;
};
// Reset the sync status of a freshly synced tiddler
TiddlyWiki.prototype.resetTiddler = function(title)
{
var tiddler = this.fetchTiddler(title);
if(tiddler) {
tiddler.clearChangeCount();
this.notify(title,true);
this.setDirty(true);
}
};
TiddlyWiki.prototype.incChangeCount = function(title)
{
var tiddler = this.fetchTiddler(title);
if(tiddler)
tiddler.incChangeCount();
};
TiddlyWiki.prototype.createTiddler = function(title)
{
var tiddler = this.fetchTiddler(title);
if(!tiddler) {
tiddler = new Tiddler();
tiddler.title = title;
this.addTiddler(tiddler);
this.setDirty(true);
}
return tiddler;
};
// Load contents of a TiddlyWiki from an HTML DIV
TiddlyWiki.prototype.loadFromDiv = function(src,idPrefix,noUpdate)
{
this.idPrefix = idPrefix;
var storeElem = (typeof src == "string") ? document.getElementById(src) : src;
if(!storeElem)
return;
var tiddlers = this.getLoader().loadTiddlers(this,storeElem.childNodes);
this.setDirty(false);
if(!noUpdate) {
for(var i = 0;i<tiddlers.length; i++)
tiddlers[i].changed();
}
};
// Load contents of a TiddlyWiki from a string
// Returns null if there's an error
TiddlyWiki.prototype.importTiddlyWiki = function(text)
{
var posDiv = locateStoreArea(text);
if(!posDiv)
return null;
var content = "<" + "html><" + "body>" + text.substring(posDiv[0],posDiv[1] + endSaveArea.length) + "<" + "/body><" + "/html>";
// Create the iframe
var iframe = document.createElement("iframe");
iframe.style.display = "none";
document.body.appendChild(iframe);
var doc = iframe.document;
if(iframe.contentDocument)
doc = iframe.contentDocument; // For NS6
else if(iframe.contentWindow)
doc = iframe.contentWindow.document; // For IE5.5 and IE6
// Put the content in the iframe
doc.open();
doc.writeln(content);
doc.close();
// Load the content into a TiddlyWiki() object
var storeArea = doc.getElementById("storeArea");
this.loadFromDiv(storeArea,"store");
// Get rid of the iframe
iframe.parentNode.removeChild(iframe);
return this;
};
TiddlyWiki.prototype.updateTiddlers = function()
{
this.tiddlersUpdated = true;
this.forEachTiddler(function(title,tiddler) {
tiddler.changed();
});
};
// Return all tiddlers formatted as an HTML string
TiddlyWiki.prototype.allTiddlersAsHtml = function()
{
return store.getSaver().externalize(store);
};
// Return an array of tiddlers matching a search regular expression
TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag)
{
var candidates = this.reverseLookup("tags",excludeTag,false);
var results = [];
for(var t=0; t<candidates.length; t++) {
if((candidates[t].title.search(searchRegExp) != -1) || (candidates[t].text.search(searchRegExp) != -1))
results.push(candidates[t]);
}
if(!sortField)
sortField = "title";
results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
return results;
};
// Return an array of all the tags in use. Each member of the array is another array where [0] is the name of the tag and [1] is the number of occurances
TiddlyWiki.prototype.getTags = function(excludeTag)
{
var results = [];
this.forEachTiddler(function(title,tiddler) {
for(var g=0; g<tiddler.tags.length; g++) {
var tag = tiddler.tags[g];
if(excludeTag) {
var t = store.fetchTiddler(tag);
if(t && t.isTagged(excludeTag))
return false;
}
var f = false;
for(var c=0; c<results.length; c++) {
if(results[c][0] == tag) {
f = true;
results[c][1]++;
}
}
if(!f)
results.push([tag,1]);
}
});
results.sort(function(a,b) {return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : (a[0].toLowerCase() == b[0].toLowerCase() ? 0 : +1);});
return results;
};
// Return an array of the tiddlers that are tagged with a given tag
TiddlyWiki.prototype.getTaggedTiddlers = function(tag,sortField)
{
return this.reverseLookup("tags",tag,true,sortField);
};
// Return an array of the tiddlers that link to a given tiddler
TiddlyWiki.prototype.getReferringTiddlers = function(title,unusedParameter,sortField)
{
if(!this.tiddlersUpdated)
this.updateTiddlers();
return this.reverseLookup("links",title,true,sortField);
};
// Return an array of the tiddlers that do or do not have a specified entry in the specified storage array (ie, "links" or "tags")
// lookupMatch == true to match tiddlers, false to exclude tiddlers
TiddlyWiki.prototype.reverseLookup = function(lookupField,lookupValue,lookupMatch,sortField)
{
var results = [];
this.forEachTiddler(function(title,tiddler) {
var f = !lookupMatch;
for(var lookup=0; lookup<tiddler[lookupField].length; lookup++) {
if(tiddler[lookupField][lookup] == lookupValue)
f = lookupMatch;
}
if(f)
results.push(tiddler);
});
if(!sortField)
sortField = "title";
results.sort(function(a,b) {return a[sortField] < b[sortField] ? -1 : (a[sortField] == b[sortField] ? 0 : +1);});
return results;
};
// Return the tiddlers as a sorted array
TiddlyWiki.prototype.getTiddlers = function(field,excludeTag)
{
var results = [];
this.forEachTiddler(function(title,tiddler) {
if(excludeTag == undefined || !tiddler.isTagged(excludeTag))
results.push(tiddler);
});
if(field)
results.sort(function(a,b) {return a[field] < b[field] ? -1 : (a[field] == b[field] ? 0 : +1);});
return results;
};
// Return array of names of tiddlers that are referred to but not defined
TiddlyWiki.prototype.getMissingLinks = function(sortField)
{
if(!this.tiddlersUpdated)
this.updateTiddlers();
var results = [];
this.forEachTiddler(function (title,tiddler) {
if(tiddler.isTagged("excludeMissing") || tiddler.isTagged("systemConfig"))
return;
for(var n=0; n<tiddler.links.length;n++) {
var link = tiddler.links[n];
if(this.fetchTiddler(link) == null && !this.isShadowTiddler(link))
results.pushUnique(link);
}
});
results.sort();
return results;
};
// Return an array of names of tiddlers that are defined but not referred to
TiddlyWiki.prototype.getOrphans = function()
{
var results = [];
this.forEachTiddler(function (title,tiddler) {
if(this.getReferringTiddlers(title).length == 0 && !tiddler.isTagged("excludeLists"))
results.push(title);
});
results.sort();
return results;
};
// Return an array of names of all the shadow tiddlers
TiddlyWiki.prototype.getShadowed = function()
{
var results = [];
for(var t in config.shadowTiddlers) {
if(typeof config.shadowTiddlers[t] == "string")
results.push(t);
}
results.sort();
return results;
};
// Return an array of tiddlers that have been touched since they were downloaded or created
TiddlyWiki.prototype.getTouched = function()
{
var results = [];
this.forEachTiddler(function(title,tiddler) {
if(tiddler.isTouched())
results.push(tiddler);
});
results.sort();
return results;
};
// Resolves a Tiddler reference or tiddler title into a Tiddler object, or null if it doesn't exist
TiddlyWiki.prototype.resolveTiddler = function(tiddler)
{
var t = (typeof tiddler == 'string') ? this.getTiddler(tiddler) : tiddler;
return t instanceof Tiddler ? t : null;
};
TiddlyWiki.prototype.getLoader = function()
{
if(!this.loader)
this.loader = new TW21Loader();
return this.loader;
};
TiddlyWiki.prototype.getSaver = function()
{
if(!this.saver)
this.saver = new TW21Saver();
return this.saver;
};
// Returns true if path is a valid field name (path),
// i.e. a sequence of identifiers, separated by '.'
TiddlyWiki.isValidFieldName = function(name)
{
var match = /[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)*/.exec(name);
return match && (match[0] == name);
};
// Throws an exception when name is not a valid field name.
TiddlyWiki.checkFieldName = function(name)
{
if(!TiddlyWiki.isValidFieldName(name))
throw config.messages.invalidFieldName.format([name]);
};
function StringFieldAccess(n,readOnly)
{
this.set = readOnly ?
function(t,v) {if(v != t[n]) throw config.messages.fieldCannotBeChanged.format([n]);} :
function(t,v) {if(v != t[n]) {t[n] = v; return true;}};
this.get = function(t) {return t[n];};
}
function DateFieldAccess(n)
{
this.set = function(t,v) {
var d = v instanceof Date ? v : Date.convertFromYYYYMMDDHHMM(v);
if(d != t[n]) {
t[n] = d; return true;
}
};
this.get = function(t) {return t[n].convertToYYYYMMDDHHMM();};
}
function LinksFieldAccess(n)
{
this.set = function(t,v) {
var s = (typeof v == "string") ? v.readBracketedList() : v;
if(s.toString() != t[n].toString()) {
t[n] = s; return true;
}
};
this.get = function(t) {return String.encodeTiddlyLinkList(t[n]);};
}
TiddlyWiki.standardFieldAccess = {
// The set functions return true when setting the data has changed the value.
"title": new StringFieldAccess("title",true),
// Handle the "tiddler" field name as the title
"tiddler": new StringFieldAccess("title",true),
"text": new StringFieldAccess("text"),
"modifier": new StringFieldAccess("modifier"),
"modified": new DateFieldAccess("modified"),
"created": new DateFieldAccess("created"),
"tags": new LinksFieldAccess("tags")
};
TiddlyWiki.isStandardField = function(name)
{
return TiddlyWiki.standardFieldAccess[name] != undefined;
};
// Sets the value of the given field of the tiddler to the value.
// Setting an ExtendedField's value to null or undefined removes the field.
// Setting a namespace to undefined removes all fields of that namespace.
// The fieldName is case-insensitive.
// All values will be converted to a string value.
TiddlyWiki.prototype.setValue = function(tiddler,fieldName,value)
{
TiddlyWiki.checkFieldName(fieldName);
var t = this.resolveTiddler(tiddler);
if(!t)
return;
fieldName = fieldName.toLowerCase();
var isRemove = (value === undefined) || (value === null);
var accessor = TiddlyWiki.standardFieldAccess[fieldName];
if(accessor) {
if(isRemove)
// don't remove StandardFields
return;
var h = TiddlyWiki.standardFieldAccess[fieldName];
if(!h.set(t,value))
return;
} else {
var oldValue = t.fields[fieldName];
if(isRemove) {
if(oldValue !== undefined) {
// deletes a single field
delete t.fields[fieldName];
} else {
// no concrete value is defined for the fieldName
// so we guess this is a namespace path.
// delete all fields in a namespace
var re = new RegExp('^'+fieldName+'\\.');
var dirty = false;
for(var n in t.fields) {
if(n.match(re)) {
delete t.fields[n];
dirty = true;
}
}
if(!dirty)
return;
}
} else {
// the "normal" set case. value is defined (not null/undefined)
// For convenience provide a nicer conversion Date->String
value = value instanceof Date ? value.convertToYYYYMMDDHHMMSSMMM() : String(value);
if(oldValue == value)
return;
t.fields[fieldName] = value;
}
}
// When we are here the tiddler/store really was changed.
this.notify(t.title,true);
if(!fieldName.match(/^temp\./))
this.setDirty(true);
};
// Returns the value of the given field of the tiddler.
// The fieldName is case-insensitive.
// Will only return String values (or undefined).
TiddlyWiki.prototype.getValue = function(tiddler,fieldName)
{
var t = this.resolveTiddler(tiddler);
if(!t)
return undefined;
fieldName = fieldName.toLowerCase();
var accessor = TiddlyWiki.standardFieldAccess[fieldName];
if(accessor) {
return accessor.get(t);
}
return t.fields[fieldName];
};
// Calls the callback function for every field in the tiddler.
// When callback function returns a non-false value the iteration stops
// and that value is returned.
// The order of the fields is not defined.
// @param callback a function(tiddler,fieldName,value).
TiddlyWiki.prototype.forEachField = function(tiddler,callback,onlyExtendedFields)
{
var t = this.resolveTiddler(tiddler);
if(!t)
return undefined;
for(var n in t.fields) {
var result = callback(t,n,t.fields[n]);
if(result)
return result;
}
if(onlyExtendedFields)
return undefined;
for(var n in TiddlyWiki.standardFieldAccess) {
if(n == "tiddler")
// even though the "title" field can also be referenced through the name "tiddler"
// we only visit this field once.
continue;
var result = callback(t,n,TiddlyWiki.standardFieldAccess[n].get(t));
if(result)
return result;
}
return undefined;
};
//--
//-- Story functions
//--
function Story(container,idPrefix)
{
this.container = container;
this.idPrefix = idPrefix;
this.highlightRegExp = null;
}
Story.prototype.forEachTiddler = function(fn)
{
var place = document.getElementById(this.container);
if(!place)
return;
var e = place.firstChild;
while(e) {
var n = e.nextSibling;
var title = e.getAttribute("tiddler");
fn.call(this,title,e);
e = n;
}
};
Story.prototype.displayTiddlers = function(srcElement,titles,template,animate,unused,customFields,toggle)
{
for(var t = titles.length-1;t>=0;t--)
this.displayTiddler(srcElement,titles[t],template,animate,unused,customFields);
};
Story.prototype.displayTiddler = function(srcElement,title,template,animate,unused,customFields,toggle)
{
var place = document.getElementById(this.container);
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem) {
if(toggle)
this.closeTiddler(title,true);
else
this.refreshTiddler(title,template,false,customFields);
} else {
var before = this.positionTiddler(srcElement);
tiddlerElem = this.createTiddler(place,before,title,template,customFields);
}
if(srcElement && typeof srcElement !== "string") {
if(config.options.chkAnimate && (animate == undefined || animate == true) && anim && typeof Zoomer == "function" && typeof Scroller == "function")
anim.startAnimating(new Zoomer(title,srcElement,tiddlerElem),new Scroller(tiddlerElem));
else
window.scrollTo(0,ensureVisible(tiddlerElem));
}
};
Story.prototype.positionTiddler = function(srcElement)
{
var place = document.getElementById(this.container);
var before = null;
if(typeof srcElement == "string") {
switch(srcElement) {
case "top":
before = place.firstChild;
break;
case "bottom":
before = null;
break;
}
} else {
var after = this.findContainingTiddler(srcElement);
if(after == null) {
before = place.firstChild;
} else if(after.nextSibling) {
before = after.nextSibling;
if(before.nodeType != 1)
before = null;
}
}
return before;
};
Story.prototype.createTiddler = function(place,before,title,template,customFields)
{
var tiddlerElem = createTiddlyElement(null,"div",this.idPrefix + title,"tiddler");
tiddlerElem.setAttribute("refresh","tiddler");
if(customFields)
tiddlerElem.setAttribute("tiddlyFields",customFields);
place.insertBefore(tiddlerElem,before);
var defaultText = null;
if(!store.tiddlerExists(title) && !store.isShadowTiddler(title))
defaultText = this.loadMissingTiddler(title,customFields,tiddlerElem);
this.refreshTiddler(title,template,false,customFields,defaultText);
return tiddlerElem;
};
Story.prototype.loadMissingTiddler = function(title,fields,tiddlerElem)
{
var tiddler = new Tiddler(title);
tiddler.fields = typeof fields == "string" ? fields.decodeHashMap() : (fields ? fields : {});
var serverType = tiddler.getServerType();
var host = tiddler.fields['server.host'];
var workspace = tiddler.fields['server.workspace'];
if(!serverType | !host)
return null;
var sm = new SyncMachine(serverType,{
start: function() {
return this.openHost(host,"openWorkspace");
},
openWorkspace: function() {
return this.openWorkspace(workspace,"getTiddler");
},
getTiddler: function() {
return this.getTiddler(title,"gotTiddler");
},
gotTiddler: function(tiddler) {
if(tiddler && tiddler.text) {
var downloaded = new Date();
if(!tiddler.created)
tiddler.created = downloaded;
if(!tiddler.modified)
tiddler.modified = tiddler.created;
store.saveTiddler(tiddler.title,tiddler.title,tiddler.text,tiddler.modifier,tiddler.modified,tiddler.tags,tiddler.fields,true,tiddler.created);
autoSaveChanges();
}
delete this;
return true;
},
error: function(message) {
displayMessage("Error loading missing tiddler from %0: %1".format([host,message]));
}
});
sm.go();
return config.messages.loadingMissingTiddler.format([title,serverType,host,workspace]);
};
Story.prototype.chooseTemplateForTiddler = function(title,template)
{
if(!template)
template = DEFAULT_VIEW_TEMPLATE;
if(template == DEFAULT_VIEW_TEMPLATE || template == DEFAULT_EDIT_TEMPLATE)
template = config.tiddlerTemplates[template];
return template;
};
Story.prototype.getTemplateForTiddler = function(title,template,tiddler)
{
return store.getRecursiveTiddlerText(template,null,10);
};
Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem) {
if(tiddlerElem.getAttribute("dirty") == "true" && !force)
return tiddlerElem;
template = this.chooseTemplateForTiddler(title,template);
var currTemplate = tiddlerElem.getAttribute("template");
if((template != currTemplate) || force) {
var tiddler = store.getTiddler(title);
if(!tiddler) {
tiddler = new Tiddler();
if(store.isShadowTiddler(title)) {
tiddler.set(title,store.getTiddlerText(title),config.views.wikified.shadowModifier,version.date,[],version.date);
} else {
var text = template=="EditTemplate" ?
config.views.editor.defaultText.format([title]) :
config.views.wikified.defaultText.format([title]);
text = defaultText ? defaultText : text;
var fields = customFields ? customFields.decodeHashMap() : null;
tiddler.set(title,text,config.views.wikified.defaultModifier,version.date,[],version.date,fields);
}
}
tiddlerElem.setAttribute("tags",tiddler.tags.join(" "));
tiddlerElem.setAttribute("tiddler",title);
tiddlerElem.setAttribute("template",template);
var me = this;
tiddlerElem.onmouseover = this.onTiddlerMouseOver;
tiddlerElem.onmouseout = this.onTiddlerMouseOut;
tiddlerElem.ondblclick = this.onTiddlerDblClick;
tiddlerElem[window.event?"onkeydown":"onkeypress"] = this.onTiddlerKeyPress;
var html = this.getTemplateForTiddler(title,template,tiddler);
tiddlerElem.innerHTML = html;
applyHtmlMacros(tiddlerElem,tiddler);
if(store.getTaggedTiddlers(title).length > 0)
addClass(tiddlerElem,"isTag");
else
removeClass(tiddlerElem,"isTag");
if(!store.tiddlerExists(title)) {
if(store.isShadowTiddler(title))
addClass(tiddlerElem,"shadow");
else
addClass(tiddlerElem,"missing");
} else {
removeClass(tiddlerElem,"shadow");
removeClass(tiddlerElem,"missing");
}
if(customFields)
this.addCustomFields(tiddlerElem,customFields);
forceReflow();
}
}
return tiddlerElem;
};
Story.prototype.addCustomFields = function(place,customFields)
{
var fields = customFields.decodeHashMap();
var w = document.createElement("div");
w.style.display = "none";
place.appendChild(w);
for(var t in fields) {
var e = document.createElement("input");
e.setAttribute("type","text");
e.setAttribute("value",fields[t]);
w.appendChild(e);
e.setAttribute("edit",t);
}
};
Story.prototype.refreshAllTiddlers = function()
{
var place = document.getElementById(this.container);
var e = place.firstChild;
if(!e)
return;
this.refreshTiddler(e.getAttribute("tiddler"),e.getAttribute("template"),true);
while((e = e.nextSibling) != null)
this.refreshTiddler(e.getAttribute("tiddler"),e.getAttribute("template"),true);
};
Story.prototype.onTiddlerMouseOver = function(e)
{
if(window.addClass instanceof Function)
addClass(this,"selected");
};
Story.prototype.onTiddlerMouseOut = function(e)
{
if(window.removeClass instanceof Function)
removeClass(this,"selected");
};
Story.prototype.onTiddlerDblClick = function(e)
{
if(!e) var e = window.event;
var theTarget = resolveTarget(e);
if(theTarget && theTarget.nodeName.toLowerCase() != "input" && theTarget.nodeName.toLowerCase() != "textarea") {
if(document.selection && document.selection.empty)
document.selection.empty();
config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
e.cancelBubble = true;
if(e.stopPropagation) e.stopPropagation();
return true;
} else {
return false;
}
};
Story.prototype.onTiddlerKeyPress = function(e)
{
if(!e) var e = window.event;
clearMessage();
var consume = false;
var title = this.getAttribute("tiddler");
var target = resolveTarget(e);
switch(e.keyCode) {
case 9: // Tab
if(config.options.chkInsertTabs && target.tagName.toLowerCase() == "textarea") {
replaceSelection(target,String.fromCharCode(9));
consume = true;
}
if(config.isOpera) {
target.onblur = function() {
this.focus();
this.onblur = null;
};
}
break;
case 13: // Ctrl-Enter
case 10: // Ctrl-Enter on IE PC
case 77: // Ctrl-Enter is "M" on some platforms
if(e.ctrlKey) {
blurElement(this);
config.macros.toolbar.invokeCommand(this,"defaultCommand",e);
consume = true;
}
break;
case 27: // Escape
blurElement(this);
config.macros.toolbar.invokeCommand(this,"cancelCommand",e);
consume = true;
break;
}
e.cancelBubble = consume;
if(consume) {
if(e.stopPropagation) e.stopPropagation(); // Stop Propagation
e.returnValue = true; // Cancel The Event in IE
if(e.preventDefault ) e.preventDefault(); // Cancel The Event in Moz
}
return !consume;
};
Story.prototype.getTiddlerField = function(title,field)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
var e = null;
if(tiddlerElem != null) {
var children = tiddlerElem.getElementsByTagName("*");
for(var t=0; t<children.length; t++) {
var c = children[t];
if(c.tagName.toLowerCase() == "input" || c.tagName.toLowerCase() == "textarea") {
if(!e)
e = c;
if(c.getAttribute("edit") == field)
e = c;
}
}
}
return e;
};
Story.prototype.focusTiddler = function(title,field)
{
var e = this.getTiddlerField(title,field);
if(e) {
e.focus();
e.select();
}
};
Story.prototype.blurTiddler = function(title)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem != null && tiddlerElem.focus && tiddlerElem.blur) {
tiddlerElem.focus();
tiddlerElem.blur();
}
};
Story.prototype.setTiddlerField = function(title,tag,mode,field)
{
var c = story.getTiddlerField(title,field);
var tags = c.value.readBracketedList();
tags.setItem(tag,mode);
c.value = String.encodeTiddlyLinkList(tags);
};
Story.prototype.setTiddlerTag = function(title,tag,mode)
{
Story.prototype.setTiddlerField(title,tag,mode,"tags");
};
Story.prototype.closeTiddler = function(title,animate,unused)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem != null) {
clearMessage();
this.scrubTiddler(tiddlerElem);
if(config.options.chkAnimate && animate && anim && typeof Slider == "function")
anim.startAnimating(new Slider(tiddlerElem,false,null,"all"));
else {
removeNode(tiddlerElem);
forceReflow();
}
}
};
Story.prototype.scrubTiddler = function(tiddlerElem)
{
tiddlerElem.id = null;
};
Story.prototype.setDirty = function(title,dirty)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem != null)
tiddlerElem.setAttribute("dirty",dirty ? "true" : "false");
};
Story.prototype.isDirty = function(title)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem != null)
return tiddlerElem.getAttribute("dirty") == "true";
return null;
};
Story.prototype.areAnyDirty = function()
{
var r = false;
this.forEachTiddler(function(title,element) {
if(this.isDirty(title))
r = true;
});
return r;
};
Story.prototype.closeAllTiddlers = function(exclude)
{
clearMessage();
this.forEachTiddler(function(title,element) {
if((title != exclude) && element.getAttribute("dirty") != "true")
this.closeTiddler(title);
});
window.scrollTo(0,ensureVisible(this.container));
};
Story.prototype.isEmpty = function()
{
var place = document.getElementById(this.container);
return place && place.firstChild == null;
};
Story.prototype.search = function(text,useCaseSensitive,useRegExp)
{
this.closeAllTiddlers();
highlightHack = new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
var matches = store.search(highlightHack,"title","excludeSearch");
var titles = [];
for(var t=0;t<matches.length;t++)
titles.push(matches[t].title);
this.displayTiddlers(null,titles);
highlightHack = null;
var q = useRegExp ? "/" : "'";
if(matches.length > 0)
displayMessage(config.macros.search.successMsg.format([titles.length.toString(),q + text + q]));
else
displayMessage(config.macros.search.failureMsg.format([q + text + q]));
};
Story.prototype.findContainingTiddler = function(e)
{
while(e && !hasClass(e,"tiddler"))
e = e.parentNode;
return e;
};
Story.prototype.gatherSaveFields = function(e,fields)
{
if(e && e.getAttribute) {
var f = e.getAttribute("edit");
if(f)
fields[f] = e.value.replace(/\r/mg,"");
if(e.hasChildNodes()) {
var c = e.childNodes;
for(var t=0; t<c.length; t++)
this.gatherSaveFields(c[t],fields);
}
}
};
Story.prototype.hasChanges = function(title)
{
var e = document.getElementById(this.idPrefix + title);
if(e != null) {
var fields = {};
this.gatherSaveFields(e,fields);
var tiddler = store.fetchTiddler(title);
if(!tiddler)
return false;
for(var n in fields) {
if(store.getValue(title,n) != fields[n])
return true;
}
}
return false;
};
Story.prototype.saveTiddler = function(title,minorUpdate)
{
var tiddlerElem = document.getElementById(this.idPrefix + title);
if(tiddlerElem != null) {
var fields = {};
this.gatherSaveFields(tiddlerElem,fields);
var newTitle = fields.title ? fields.title : title;
if(store.tiddlerExists(newTitle) && newTitle != title) {
if(!confirm(config.messages.overwriteWarning.format([newTitle.toString()])))
return null;
}
if(newTitle != title)
this.closeTiddler(newTitle,false);
tiddlerElem.id = this.idPrefix + newTitle;
tiddlerElem.setAttribute("tiddler",newTitle);
tiddlerElem.setAttribute("template",DEFAULT_VIEW_TEMPLATE);
tiddlerElem.setAttribute("dirty","false");
if(config.options.chkForceMinorUpdate)
minorUpdate = !minorUpdate;
if(!store.tiddlerExists(newTitle))
minorUpdate = false;
var newDate = new Date();
var extendedFields = store.tiddlerExists(newTitle) ? store.fetchTiddler(newTitle).fields : {};
for(var n in fields) {
if(!TiddlyWiki.isStandardField(n))
extendedFields[n] = fields[n];
}
var tiddler = store.saveTiddler(title,newTitle,fields.text,minorUpdate ? undefined : config.options.txtUserName,minorUpdate ? undefined : newDate,fields.tags,extendedFields);
autoSaveChanges(null,[tiddler]);
return newTitle;
}
return null;
};
Story.prototype.permaView = function()
{
var links = [];
this.forEachTiddler(function(title,element) {
links.push(String.encodeTiddlyLink(title));
});
var t = encodeURIComponent(links.join(" "));
if(t == "")
t = "#";
if(window.location.hash != t)
window.location.hash = t;
};
//--
//-- Backstage
//--
var backstage = {
area: null,
toolbar: null,
button: null,
showButton: null,
hideButton: null,
cloak: null,
panel: null,
panelBody: null,
panelFooter: null,
currTabName: null,
currTabElem: null,
content: null,
init: function() {
var cmb = config.messages.backstage;
this.area = document.getElementById("backstageArea");
this.toolbar = document.getElementById("backstageToolbar");
this.button = document.getElementById("backstageButton");
this.button.style.display = "block";
var t = cmb.open.text + " " + glyph("bentArrowLeft");
this.showButton = createTiddlyButton(this.button,t,cmb.open.tooltip,
function (e) {backstage.show(); return false;},null,"backstageShow");
t = glyph("bentArrowRight") + " " + cmb.close.text;
this.hideButton = createTiddlyButton(this.button,t,cmb.close.tooltip,
function (e) {backstage.hide(); return false;},null,"backstageHide");
this.cloak = document.getElementById("backstageCloak");
this.panel = document.getElementById("backstagePanel");
this.panelFooter = createTiddlyElement(this.panel,"div",null,"backstagePanelFooter");
this.panelBody = createTiddlyElement(this.panel,"div",null,"backstagePanelBody");
this.cloak.onmousedown = function(e) {
backstage.switchTab(null);
};
createTiddlyText(this.toolbar,cmb.prompt);
for(t=0; t<config.backstageTasks.length; t++) {
var taskName = config.backstageTasks[t];
var task = config.tasks[taskName];
var handler = task.action ? this.onClickCommand : this.onClickTab;
var text = task.text + (task.action ? "" : glyph("downTriangle"));
var btn = createTiddlyButton(this.toolbar,text,task.tooltip,handler,"backstageTab");
btn.setAttribute("task",taskName);
addClass(btn,task.action ? "backstageAction" : "backstageTask");
}
this.content = document.getElementById("contentWrapper");
if(config.options.chkBackstage)
this.show();
else
this.hide();
},
isVisible: function () {
return this.area ? this.area.style.display == "block" : false;
},
show: function() {
this.area.style.display = "block";
if(anim && config.options.chkAnimate) {
backstage.toolbar.style.left = findWindowWidth() + "px";
var p = [
{style: "left", start: findWindowWidth(), end: 0, template: "%0px"}
];
anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p));
} else {
backstage.area.style.left = "0px";
}
this.showButton.style.display = "none";
this.hideButton.style.display = "block";
config.options.chkBackstage = true;
saveOptionCookie("chkBackstage");
addClass(this.content,"backstageVisible");
},
hide: function() {
if(this.currTabElem) {
this.switchTab(null);
} else {
backstage.toolbar.style.left = "0px";
if(anim && config.options.chkAnimate) {
var p = [
{style: "left", start: 0, end: findWindowWidth(), template: "%0px"}
];
var c = function(element,properties) {backstage.area.style.display = "none";};
anim.startAnimating(new Morpher(backstage.toolbar,config.animDuration,p,c));
} else {
this.area.style.display = "none";
}
this.showButton.style.display = "block";
this.hideButton.style.display = "none";
config.options.chkBackstage = false;
saveOptionCookie("chkBackstage");
removeClass(this.content,"backstageVisible");
}
},
onClickCommand: function(e) {
var task = config.tasks[this.getAttribute("task")];
displayMessage(task);
if(task.action) {
backstage.switchTab(null);
task.action();
}
return false;
},
onClickTab: function(e) {
backstage.switchTab(this.getAttribute("task"));
return false;
},
// Switch to a given tab, or none if null is passed
switchTab: function(tabName) {
var tabElem = null;
var e = this.toolbar.firstChild;
while(e)
{
if(e.getAttribute && e.getAttribute("task") == tabName)
tabElem = e;
e = e.nextSibling;
}
if(tabName == backstage.currTabName)
return;
if(backstage.currTabElem) {
removeClass(this.currTabElem,"backstageSelTab");
}
if(tabElem && tabName) {
backstage.preparePanel();
addClass(tabElem,"backstageSelTab");
var task = config.tasks[tabName];
wikify(task.content,backstage.panelBody,null,null);
backstage.showPanel();
} else if(backstage.currTabElem) {
backstage.hidePanel();
}
backstage.currTabName = tabName;
backstage.currTabElem = tabElem;
},
isPanelVisible: function() {
return backstage.panel ? backstage.panel.style.display == "block" : false;
},
preparePanel: function() {
backstage.cloak.style.height = findWindowHeight() + "px";
backstage.cloak.style.display = "block";
removeChildren(backstage.panelBody);
return backstage.panelBody;
},
showPanel: function() {
backstage.panel.style.display = "block";
if(anim && config.options.chkAnimate) {
backstage.panel.style.top = (-backstage.panel.offsetHeight) + "px";
var p = [
{style: "top", start: -backstage.panel.offsetHeight, end: 0, template: "%0px"}
];
anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p),new Scroller(backstage.panel,false));
} else {
backstage.panel.style.top = "0px";
}
return backstage.panelBody;
},
hidePanel: function() {
backstage.currTabName = null;
backstage.currTabElem = null;
if(anim && config.options.chkAnimate) {
var p = [
{style: "top", start: 0, end: -(backstage.panel.offsetHeight), template: "%0px"},
{style: "display", atEnd: "none"}
];
var c = function(element,properties) {backstage.cloak.style.display = "none";};
anim.startAnimating(new Morpher(backstage.panel,config.animDuration,p,c));
} else {
backstage.panel.style.display = "none";
backstage.cloak.style.display = "none";
}
}
};
config.macros.backstage = {};
config.macros.backstage.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
var backstageTask = config.tasks[params[0]];
if(backstageTask)
createTiddlyButton(place,backstageTask.text,backstageTask.tooltip,function(e) {backstage.switchTab(params[0]); return false;});
};
//--
//-- ImportTiddlers macro
//--
config.macros.importTiddlers.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
if(readOnly) {
createTiddlyElement(place,"div",null,"marked",this.readOnlyWarning);
return;
}
var w = new Wizard();
w.createWizard(place,this.wizardTitle);
this.restart(w);
};
config.macros.importTiddlers.onCancel = function(e)
{
var wizard = new Wizard(this);
var place = wizard.clear();
config.macros.importTiddlers.restart(wizard);
return false;
};
config.macros.importTiddlers.restart = function(wizard)
{
wizard.addStep(this.step1Title,this.step1Html);
var s = wizard.getElement("selTypes");
for(var t in config.adaptors) {
var e = createTiddlyElement(s,"option",null,null,t);
e.value = t;
}
s = wizard.getElement("selFeeds");
var feeds = this.getFeeds();
for(t in feeds) {
e = createTiddlyElement(s,"option",null,null,t);
e.value = t;
}
wizard.setValue("feeds",feeds);
s.onchange = config.macros.importTiddlers.onFeedChange;
var fileInput = wizard.getElement("txtBrowse");
fileInput.onchange = config.macros.importTiddlers.onBrowseChange;
fileInput.onkeyup = config.macros.importTiddlers.onBrowseChange;
wizard.setButtons([{caption: this.openLabel, tooltip: this.openPrompt, onClick: config.macros.importTiddlers.onOpen}]);
};
config.macros.importTiddlers.getFeeds = function()
{
var feeds = {};
var tagged = store.getTaggedTiddlers("systemServer","title");
for(var t=0; t<tagged.length; t++) {
var title = tagged[t].title;
var serverType = store.getTiddlerSlice(title,"Type");
if(!serverType)
serverType = "file";
feeds[title] = {title: title,
url: store.getTiddlerSlice(title,"URL"),
workspace: store.getTiddlerSlice(title,"Workspace"),
workspaceList: store.getTiddlerSlice(title,"WorkspaceList"),
tiddlerFilter: store.getTiddlerSlice(title,"TiddlerFilter"),
serverType: serverType,
description: store.getTiddlerSlice(title,"Description")};
}
return feeds;
};
config.macros.importTiddlers.onFeedChange = function(e)
{
var wizard = new Wizard(this);
var selTypes = wizard.getElement("selTypes");
var fileInput = wizard.getElement("txtPath");
var feeds = wizard.getValue("feeds");
var f = feeds[this.value];
if(f) {
selTypes.value = f.serverType;
fileInput.value = f.url;
this.selectedIndex = 0;
wizard.setValue("feedName",f.serverType);
wizard.setValue("feedHost",f.url);
wizard.setValue("feedWorkspace",f.workspace);
wizard.setValue("feedWorkspaceList",f.workspaceList);
wizard.setValue("feedTiddlerFilter",f.tiddlerFilter);
}
return false;
};
config.macros.importTiddlers.onBrowseChange = function(e)
{
var wizard = new Wizard(this);
var fileInput = wizard.getElement("txtPath");
fileInput.value = "file://" + this.value;
var serverType = wizard.getElement("selTypes");
serverType.value = "file";
return false;
};
config.macros.importTiddlers.onOpen = function(e)
{
var wizard = new Wizard(this);
var fileInput = wizard.getElement("txtPath");
var url = fileInput.value;
var serverType = wizard.getElement("selTypes").value;
var adaptor = new config.adaptors[serverType];
wizard.setValue("adaptor",adaptor);
wizard.setValue("serverType",serverType);
wizard.setValue("host",url);
var context = {};
var ret = adaptor.openHost(url,context,wizard,config.macros.importTiddlers.onOpenHost);
if(ret !== true)
displayMessage(ret);
wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenHost);
return false;
};
config.macros.importTiddlers.onOpenHost = function(context,wizard)
{
var adaptor = wizard.getValue("adaptor");
if(context.status !== true)
displayMessage("Error in importTiddlers.onOpenHost: " + context.statusText);
var ret = adaptor.getWorkspaceList(context,wizard,config.macros.importTiddlers.onGetWorkspaceList);
if(ret !== true)
displayMessage(ret);
wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetWorkspaceList);
};
config.macros.importTiddlers.onGetWorkspaceList = function(context,wizard)
{
if(context.status !== true)
displayMessage("Error in importTiddlers.onGetWorkspaceList: " + context.statusText);
wizard.addStep(config.macros.importTiddlers.step2Title,config.macros.importTiddlers.step2Html);
var s = wizard.getElement("selWorkspace");
s.onchange = config.macros.importTiddlers.onWorkspaceChange;
for(var t=0; t<context.workspaces.length; t++) {
var e = createTiddlyElement(s,"option",null,null,context.workspaces[t].title);
e.value = context.workspaces[t].title;
}
var workspaceList = wizard.getValue("feedWorkspaceList");
if(workspaceList) {
var list = workspaceList.parseParams("workspace",null,false,true);
for(var n=1; n<list.length; n++) {
if(context.workspaces.findByField("title",list[n].value) == null) {
e = createTiddlyElement(s,"option",null,null,list[n].value);
e.value = list[n].value;
}
}
}
var workspace = wizard.getValue("feedWorkspace");
if(workspace) {
t = wizard.getElement("txtWorkspace");
t.value = workspace;
}
wizard.setButtons([{caption: config.macros.importTiddlers.openLabel, tooltip: config.macros.importTiddlers.openPrompt, onClick: config.macros.importTiddlers.onChooseWorkspace}]);
};
config.macros.importTiddlers.onWorkspaceChange = function(e)
{
var wizard = new Wizard(this);
var t = wizard.getElement("txtWorkspace");
t.value = this.value;
this.selectedIndex = 0;
return false;
};
config.macros.importTiddlers.onChooseWorkspace = function(e)
{
var wizard = new Wizard(this);
var adaptor = wizard.getValue("adaptor");
var workspace = wizard.getElement("txtWorkspace").value;
wizard.setValue("workspace",workspace);
var context = {};
var ret = adaptor.openWorkspace(workspace,context,wizard,config.macros.importTiddlers.onOpenWorkspace);
if(ret !== true)
displayMessage(ret);
wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusOpenWorkspace);
return false;
};
config.macros.importTiddlers.onOpenWorkspace = function(context,wizard)
{
if(context.status !== true)
displayMessage("Error in importTiddlers.onOpenWorkspace: " + context.statusText);
var adaptor = wizard.getValue("adaptor");
var ret = adaptor.getTiddlerList(context,wizard,config.macros.importTiddlers.onGetTiddlerList,wizard.getValue("feedTiddlerFilter"));
if(ret !== true)
displayMessage(ret);
wizard.setButtons([{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}],config.macros.importTiddlers.statusGetTiddlerList);
};
config.macros.importTiddlers.onGetTiddlerList = function(context,wizard)
{
if(context.status !== true)
displayMessage("Error in importTiddlers.onGetTiddlerList: " + context.statusText);
// Extract data for the listview
var listedTiddlers = [];
if(context.tiddlers) {
for(var n=0; n<context.tiddlers.length; n++) {
var tiddler = context.tiddlers[n];
listedTiddlers.push({
title: tiddler.title,
modified: tiddler.modified,
modifier: tiddler.modifier,
text: tiddler.text ? wikifyPlainText(tiddler.text,100) : "",
tags: tiddler.tags,
size: tiddler.text ? tiddler.text.length : 0,
tiddler: tiddler
});
}
}
listedTiddlers.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
// Display the listview
wizard.addStep(config.macros.importTiddlers.step3Title,config.macros.importTiddlers.step3Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
var listView = ListView.create(listWrapper,listedTiddlers,config.macros.importTiddlers.listViewTemplate);
wizard.setValue("listView",listView);
var txtSaveTiddler = wizard.getElement("txtSaveTiddler");
txtSaveTiddler.value = config.macros.importTiddlers.generateSystemServerName(wizard);
wizard.setButtons([
{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel},
{caption: config.macros.importTiddlers.importLabel, tooltip: config.macros.importTiddlers.importPrompt, onClick: config.macros.importTiddlers.doImport}
]);
};
config.macros.importTiddlers.generateSystemServerName = function(wizard)
{
var serverType = wizard.getValue("serverType");
var host = wizard.getValue("host");
var workspace = wizard.getValue("workspace");
var pattern = config.macros.importTiddlers[workspace ? "systemServerNamePattern" : "systemServerNamePatternNoWorkspace"];
return pattern.format([serverType,host,workspace]);
};
config.macros.importTiddlers.saveServerTiddler = function(wizard)
{
var txtSaveTiddler = wizard.getElement("txtSaveTiddler").value;
if(store.tiddlerExists(txtSaveTiddler)) {
if(!confirm(config.macros.importTiddlers.confirmOverwriteSaveTiddler.format([txtSaveTiddler])))
return;
store.suspendNotifications();
store.removeTiddler(txtSaveTiddler);
store.resumeNotifications();
}
var serverType = wizard.getValue("serverType");
var host = wizard.getValue("host");
var workspace = wizard.getValue("workspace");
var text = config.macros.importTiddlers.serverSaveTemplate.format([serverType,host,workspace]);
store.saveTiddler(txtSaveTiddler,txtSaveTiddler,text,config.macros.importTiddlers.serverSaveModifier,new Date(),["systemServer"]);
};
config.macros.importTiddlers.doImport = function(e)
{
var wizard = new Wizard(this);
if(wizard.getElement("chkSave").checked)
config.macros.importTiddlers.saveServerTiddler(wizard);
var chkSync = wizard.getElement("chkSync").checked;
wizard.setValue("sync",chkSync);
var listView = wizard.getValue("listView");
var rowNames = ListView.getSelectedRows(listView);
var adaptor = wizard.getValue("adaptor");
var overwrite = new Array();
var t;
for(t=0; t<rowNames.length; t++) {
if(store.tiddlerExists(rowNames[t]))
overwrite.push(rowNames[t]);
}
if(overwrite.length > 0) {
if(!confirm(config.macros.importTiddlers.confirmOverwriteText.format([overwrite.join(", ")])))
return false;
}
wizard.addStep(config.macros.importTiddlers.step4Title.format([rowNames.length]),config.macros.importTiddlers.step4Html);
for(t=0; t<rowNames.length; t++) {
var link = document.createElement("div");
createTiddlyLink(link,rowNames[t],true);
var place = wizard.getElement("markReport");
place.parentNode.insertBefore(link,place);
}
wizard.setValue("remainingImports",rowNames.length);
wizard.setButtons([
{caption: config.macros.importTiddlers.cancelLabel, tooltip: config.macros.importTiddlers.cancelPrompt, onClick: config.macros.importTiddlers.onCancel}
],config.macros.importTiddlers.statusDoingImport);
for(t=0; t<rowNames.length; t++) {
var context = {};
context.allowSynchronous = true;
var inbound = adaptor.getTiddler(rowNames[t],context,wizard,config.macros.importTiddlers.onGetTiddler);
}
return false;
};
config.macros.importTiddlers.onGetTiddler = function(context,wizard)
{
if(!context.status)
displayMessage("Error in importTiddlers.onGetTiddler: " + context.statusText);
var tiddler = context.tiddler;
store.suspendNotifications();
store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
if(!wizard.getValue("sync")) {
store.setValue(tiddler.title,'server',null);
}
store.resumeNotifications();
if(!context.isSynchronous)
store.notify(tiddler.title,true);
var remainingImports = wizard.getValue("remainingImports")-1;
wizard.setValue("remainingImports",remainingImports);
if(remainingImports == 0) {
if(context.isSynchronous) {
store.notifyAll();
refreshDisplay();
}
wizard.setButtons([
{caption: config.macros.importTiddlers.doneLabel, tooltip: config.macros.importTiddlers.donePrompt, onClick: config.macros.importTiddlers.onCancel}
],config.macros.importTiddlers.statusDoneImport);
autoSaveChanges();
}
};
//--
//-- Sync macro
//--
// Synchronisation handlers
config.syncers = {};
// Sync state.
var currSync = null;
// sync macro
config.macros.sync.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
if(!wikifier.isStatic)
this.startSync(place);
};
config.macros.sync.startSync = function(place)
{
if(currSync)
config.macros.sync.cancelSync();
currSync = {};
currSync.syncList = this.getSyncableTiddlers();
this.createSyncTasks();
this.preProcessSyncableTiddlers();
var wizard = new Wizard();
currSync.wizard = wizard;
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
currSync.listView = ListView.create(listWrapper,currSync.syncList,this.listViewTemplate);
this.processSyncableTiddlers();
wizard.setButtons([
{caption: this.syncLabel, tooltip: this.syncPrompt, onClick: this.doSync}
]);
};
config.macros.sync.getSyncableTiddlers = function ()
{
var list = [];
store.forEachTiddler(function(title,tiddler) {
var syncItem = {};
syncItem.serverType = tiddler.getServerType();
syncItem.serverHost = tiddler.fields['server.host'];
syncItem.serverWorkspace = tiddler.fields['server.workspace'];
syncItem.tiddler = tiddler;
syncItem.title = tiddler.title;
syncItem.isTouched = tiddler.isTouched();
syncItem.selected = syncItem.isTouched;
syncItem.syncStatus = config.macros.sync.syncStatusList[syncItem.isTouched ? "changedLocally" : "none"];
syncItem.status = syncItem.syncStatus.text;
if(syncItem.serverType && syncItem.serverHost)
list.push(syncItem);
});
list.sort(function(a,b) {return a.title < b.title ? -1 : (a.title == b.title ? 0 : +1);});
return list;
};
config.macros.sync.preProcessSyncableTiddlers = function()
{
for(var t=0; t<currSync.syncList.length; t++) {
si = currSync.syncList[t];
var ti = si.syncTask.syncMachine.generateTiddlerInfo(si.tiddler);
si.serverUrl = ti.uri;
}
};
config.macros.sync.processSyncableTiddlers = function()
{
for(var t=0; t<currSync.syncList.length; t++) {
si = currSync.syncList[t];
si.rowElement.style.backgroundColor = si.syncStatus.color;
}
};
config.macros.sync.createSyncTasks = function()
{
currSync.syncTasks = [];
for(var t=0; t<currSync.syncList.length; t++) {
var si = currSync.syncList[t];
var r = null;
for(var st=0; st<currSync.syncTasks.length; st++) {
var cst = currSync.syncTasks[st];
if(si.serverType == cst.serverType && si.serverHost == cst.serverHost && si.serverWorkspace == cst.serverWorkspace)
r = cst;
}
if(r == null) {
si.syncTask = this.createSyncTask(si);
currSync.syncTasks.push(si.syncTask);
} else {
si.syncTask = r;
r.syncItems.push(si);
}
}
};
config.macros.sync.createSyncTask = function(syncItem)
{
var st = {};
st.serverType = syncItem.serverType;
st.serverHost = syncItem.serverHost;
st.serverWorkspace = syncItem.serverWorkspace;
st.syncItems = [syncItem];
st.syncMachine = new SyncMachine(st.serverType,{
start: function() {
return this.openHost(st.serverHost,"openWorkspace");
},
openWorkspace: function() {
return this.openWorkspace(st.serverWorkspace,"getTiddlerList");
},
getTiddlerList: function() {
return this.getTiddlerList("gotTiddlerList");
},
gotTiddlerList: function(tiddlers) {
for(var t=0; t<st.syncItems.length; t++) {
var si = st.syncItems[t];
var f = tiddlers.findByField("title",si.title);
if(f !== null) {
if(tiddlers[f].fields['server.page.revision'] > si.tiddler.fields['server.page.revision']) {
si.syncStatus = config.macros.sync.syncStatusList[si.isTouched ? 'changedBoth' : 'changedServer'];
}
} else {
si.syncStatus = config.macros.sync.syncStatusList.notFound;
}
config.macros.sync.updateSyncStatus(si);
}
},
getTiddler: function(title) {
return this.getTiddler(title,"onGetTiddler");
},
onGetTiddler: function(tiddler) {
var syncItem = st.syncItems.findByField("title",tiddler.title);
if(syncItem !== null) {
syncItem = st.syncItems[syncItem];
store.saveTiddler(tiddler.title, tiddler.title, tiddler.text, tiddler.modifier, tiddler.modified, tiddler.tags, tiddler.fields, true, tiddler.created);
syncItem.syncStatus = config.macros.sync.syncStatusList.gotFromServer;
config.macros.sync.updateSyncStatus(syncItem);
}
},
putTiddler: function(tiddler) {
return this.putTiddler(tiddler,"onPutTiddler");
},
onPutTiddler: function(tiddler) {
var syncItem = st.syncItems.findByField("title",tiddler.title);
if(syncItem !== null) {
syncItem = st.syncItems[syncItem];
store.resetTiddler(tiddler.title);
syncItem.syncStatus = config.macros.sync.syncStatusList.putToServer;
config.macros.sync.updateSyncStatus(syncItem);
}
}
});
st.syncMachine.go();
return st;
};
config.macros.sync.updateSyncStatus = function(syncItem)
{
var e = syncItem.colElements["status"];
removeChildren(e);
createTiddlyText(e,syncItem.syncStatus.text);
syncItem.rowElement.style.backgroundColor = syncItem.syncStatus.color;
};
config.macros.sync.doSync = function(e)
{
var rowNames = ListView.getSelectedRows(currSync.listView);
for(var t=0; t<currSync.syncList.length; t++) {
var si = currSync.syncList[t];
if(rowNames.indexOf(si.title) != -1) {
config.macros.sync.doSyncItem(si);
}
}
return false;
};
config.macros.sync.doSyncItem = function(syncItem)
{
var r = true;
var sl = config.macros.sync.syncStatusList;
switch(syncItem.syncStatus) {
case sl.changedServer:
r = syncItem.syncTask.syncMachine.go("getTiddler",syncItem.title);
break;
case sl.notFound:
case sl.changedLocally:
case sl.changedBoth:
r = syncItem.syncTask.syncMachine.go("putTiddler",syncItem.tiddler);
break;
default:
break;
}
if(r !== true)
displayMessage("Error in doSyncItem: " + r);
};
config.macros.sync.cancelSync = function()
{
currSync = null;
};
function SyncMachine(serverType,steps)
{
this.serverType = serverType;
this.adaptor = new config.adaptors[serverType];
this.steps = steps;
}
SyncMachine.prototype.go = function(step,varargs)
{
if(!step)
step = "start";
var h = this.steps[step];
if(!h)
return null;
var a = [];
for(var t=1; t<arguments.length; t++)
a.push(arguments[t]);
var r = h.apply(this,a);
if(typeof r == "string")
this.invokeError(r);
return r;
};
SyncMachine.prototype.invokeError = function(message)
{
if(this.steps.error)
this.steps.error(message);
};
SyncMachine.prototype.openHost = function(host,nextStep)
{
var me = this;
return me.adaptor.openHost(host,null,null,function(context) {
if(typeof context.status == "string")
me.invokeError(context.status);
else
me.go(nextStep);
});
};
SyncMachine.prototype.getWorkspaceList = function(nextStep)
{
var me = this;
return me.adaptor.getWorkspaceList(null,null,function(context) {
if(typeof context.status == "string")
me.invokeError(context.status);
else
me.go(nextStep,context.workspaces);
});
};
SyncMachine.prototype.openWorkspace = function(workspace,nextStep)
{
var me = this;
return me.adaptor.openWorkspace(workspace,null,null,function(context) {
if(typeof context.status == "string")
me.invokeError(context.status);
else
me.go(nextStep);
});
};
SyncMachine.prototype.getTiddlerList = function(nextStep)
{
var me = this;
return me.adaptor.getTiddlerList(null,null,function(context) {
if(typeof context.status == "string")
me.invokeError(context.status);
else
me.go(nextStep,context.tiddlers);
});
};
SyncMachine.prototype.generateTiddlerInfo = function(tiddler)
{
return this.adaptor.generateTiddlerInfo(tiddler);
};
SyncMachine.prototype.getTiddler = function(title,nextStep)
{
var me = this;
return me.adaptor.getTiddler(title,null,null,function(context) {
if(typeof context.status == "string")
me.invokeError(context.status);
else
me.go(nextStep,context.tiddler);
});
};
SyncMachine.prototype.putTiddler = function(tiddler,nextStep)
{
var me = this;
return me.adaptor.putTiddler(tiddler,null,null,function(context) {
if(typeof context.status == "string")
me.invokeError(context.status);
else
me.go(nextStep,tiddler);
});
};
//--
//-- Manager UI for groups of tiddlers
//--
config.macros.plugins.handler = function(place,macroName,params,wikifier,paramString,tiddler)
{
var wizard = new Wizard();
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
listWrapper.setAttribute("refresh","macro");
listWrapper.setAttribute("macroName","plugins");
listWrapper.setAttribute("params",paramString);
this.refresh(listWrapper,paramString);
};
config.macros.plugins.refresh = function(listWrapper,params)
{
var wizard = new Wizard(listWrapper);
var selectedRows = [];
ListView.forEachSelector(listWrapper,function(e,rowName) {
if(e.checked)
selectedRows.push(e.getAttribute("rowName"));
});
removeChildren(listWrapper);
params = params.parseParams("anon");
var plugins = installedPlugins.slice(0);
var t,tiddler,p;
var configTiddlers = store.getTaggedTiddlers("systemConfig");
for(t=0; t<configTiddlers.length; t++) {
tiddler = configTiddlers[t];
if(plugins.findByField("title",tiddler.title) == null) {
p = getPluginInfo(tiddler);
p.executed = false;
p.log.splice(0,0,this.skippedText);
plugins.push(p);
}
}
for(t=0; t<plugins.length; t++) {
p = plugins[t];
p.size = p.tiddler.text ? p.tiddler.text.length : 0;
p.forced = p.tiddler.isTagged("systemConfigForce");
p.disabled = p.tiddler.isTagged("systemConfigDisable");
p.Selected = selectedRows.indexOf(plugins[t].title) != -1;
}
if(plugins.length == 0) {
createTiddlyElement(listWrapper,"em",null,null,this.noPluginText);
wizard.setButtons([]);
} else {
var listView = ListView.create(listWrapper,plugins,this.listViewTemplate,this.onSelectCommand);
wizard.setValue("listView",listView);
wizard.setButtons([
{caption: config.macros.plugins.removeLabel, tooltip: config.macros.plugins.removePrompt, onClick: config.macros.plugins.doRemoveTag},
{caption: config.macros.plugins.deleteLabel, tooltip: config.macros.plugins.deletePrompt, onClick: config.macros.plugins.doDelete}
]);
}
};
config.macros.plugins.doRemoveTag = function(e)
{
var wizard = new Wizard(this);
var listView = wizard.getValue("listView");
var rowNames = ListView.getSelectedRows(listView);
if(rowNames.length == 0) {
alert(config.messages.nothingSelected);
} else {
for(var t=0; t<rowNames.length; t++)
store.setTiddlerTag(rowNames[t],false,"systemConfig");
}
};
config.macros.plugins.doDelete = function(e)
{
var wizard = new Wizard(this);
var listView = wizard.getValue("listView");
var rowNames = ListView.getSelectedRows(listView);
if(rowNames.length == 0) {
alert(config.messages.nothingSelected);
} else {
if(confirm(config.macros.plugins.confirmDeleteText.format([rowNames.join(", ")]))) {
for(t=0; t<rowNames.length; t++) {
store.removeTiddler(rowNames[t]);
story.closeTiddler(rowNames[t],true);
}
}
}
};
//--
//-- Message area
//--
function getMessageDiv()
{
var msgArea = document.getElementById("messageArea");
if(!msgArea)
return null;
if(!msgArea.hasChildNodes())
createTiddlyButton(createTiddlyElement(msgArea,"div",null,"messageToolbar"),
config.messages.messageClose.text,
config.messages.messageClose.tooltip,
clearMessage);
msgArea.style.display = "block";
return createTiddlyElement(msgArea,"div");
}
function displayMessage(text,linkText)
{
var e = getMessageDiv();
if(!e) {
alert(text);
return;
}
if(linkText) {
var link = createTiddlyElement(e,"a",null,null,text);
link.href = linkText;
link.target = "_blank";
} else {
e.appendChild(document.createTextNode(text));
}
}
function clearMessage()
{
var msgArea = document.getElementById("messageArea");
if(msgArea) {
removeChildren(msgArea);
msgArea.style.display = "none";
}
return false;
}
//--
//-- Refresh mechanism
//--
config.refreshers = {
link: function(e,changeList)
{
var title = e.getAttribute("tiddlyLink");
refreshTiddlyLink(e,title);
return true;
},
tiddler: function(e,changeList)
{
var title = e.getAttribute("tiddler");
var template = e.getAttribute("template");
if(changeList && changeList.indexOf(title) != -1 && !story.isDirty(title))
story.refreshTiddler(title,template,true);
else
refreshElements(e,changeList);
return true;
},
content: function(e,changeList)
{
var title = e.getAttribute("tiddler");
var force = e.getAttribute("force");
if(force != null || changeList == null || changeList.indexOf(title) != -1) {
removeChildren(e);
wikify(store.getTiddlerText(title,title),e,null);
return true;
} else
return false;
},
macro: function(e,changeList)
{
var macro = e.getAttribute("macroName");
var params = e.getAttribute("params");
if(macro)
macro = config.macros[macro];
if(macro && macro.refresh)
macro.refresh(e,params);
return true;
}
};
function refreshElements(root,changeList)
{
var nodes = root.childNodes;
for(var c=0; c<nodes.length; c++) {
var e = nodes[c], type = null;
if(e.getAttribute && (e.tagName ? e.tagName != "IFRAME" : true))
type = e.getAttribute("refresh");
var refresher = config.refreshers[type];
var refreshed = false;
if(refresher != undefined)
refreshed = refresher(e,changeList);
if(e.hasChildNodes() && !refreshed)
refreshElements(e,changeList);
}
}
function applyHtmlMacros(root,tiddler)
{
var e = root.firstChild;
while(e) {
var nextChild = e.nextSibling;
if(e.getAttribute) {
var macro = e.getAttribute("macro");
if(macro) {
var params = "";
var p = macro.indexOf(" ");
if(p != -1) {
params = macro.substr(p+1);
macro = macro.substr(0,p);
}
invokeMacro(e,macro,params,null,tiddler);
}
}
if(e.hasChildNodes())
applyHtmlMacros(e,tiddler);
e = nextChild;
}
}
function refreshPageTemplate(title)
{
var stash = createTiddlyElement(document.body,"div");
stash.style.display = "none";
var display = document.getElementById("tiddlerDisplay");
var nodes,t;
if(display) {
nodes = display.childNodes;
for(t=nodes.length-1; t>=0; t--)
stash.appendChild(nodes[t]);
}
var wrapper = document.getElementById("contentWrapper");
if(!title)
title = "PageTemplate";
var html = store.getRecursiveTiddlerText(title,null,10);
wrapper.innerHTML = html;
applyHtmlMacros(wrapper);
refreshElements(wrapper);
display = document.getElementById("tiddlerDisplay");
removeChildren(display);
if(!display)
display = createTiddlyElement(wrapper,"div","tiddlerDisplay");
nodes = stash.childNodes;
for(t=nodes.length-1; t>=0; t--)
display.appendChild(nodes[t]);
removeNode(stash);
}
function refreshDisplay(hint)
{
if(typeof hint == "string")
hint = [hint];
var e = document.getElementById("contentWrapper");
refreshElements(e,hint);
if(backstage.isPanelVisible()) {
e = document.getElementById("backstage");
refreshElements(e,hint);
}
}
function refreshPageTitle()
{
document.title = getPageTitle();
}
function getPageTitle()
{
var st = wikifyPlain("SiteTitle");
var ss = wikifyPlain("SiteSubtitle");
return st + ((st == "" || ss == "") ? "" : " - ") + ss;
}
function refreshStyles(title,doc)
{
if(!doc)
doc = document;
setStylesheet(title == null ? "" : store.getRecursiveTiddlerText(title,"",10),title,doc);
}
function refreshColorPalette(title)
{
if(!startingUp)
refreshAll();
}
function refreshAll()
{
refreshPageTemplate();
refreshDisplay();
refreshStyles("StyleSheetLayout");
refreshStyles("StyleSheetColors");
refreshStyles("StyleSheet");
refreshStyles("StyleSheetPrint");
}
//--
//-- Options cookie stuff
//--
config.optionHandlers = {
'txt': {
get: function(name) {return encodeCookie(config.options[name].toString());},
set: function(name,value) {config.options[name] = decodeCookie(value);}
},
'chk': {
get: function(name) {return config.options[name] ? "true" : "false";},
set: function(name,value) {config.options[name] = value == "true";}
}
};
function loadOptionsCookie()
{
if(safeMode)
return;
var cookies = document.cookie.split(";");
for(var c=0; c<cookies.length; c++) {
var p = cookies[c].indexOf("=");
if(p != -1) {
var name = cookies[c].substr(0,p).trim();
var value = cookies[c].substr(p+1).trim();
var optType = name.substr(0,3);
if(config.optionHandlers[optType] && config.optionHandlers[optType].set)
config.optionHandlers[optType].set(name,value);
}
}
}
function saveOptionCookie(name)
{
if(safeMode)
return;
var c = name + "=";
var optType = name.substr(0,3);
if(config.optionHandlers[optType] && config.optionHandlers[optType].get)
c += config.optionHandlers[optType].get(name);
c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
document.cookie = c;
}
function encodeCookie(s)
{
return escape(manualConvertUnicodeToUTF8(s));
}
function decodeCookie(s)
{
s = unescape(s);
var re = /&#[0-9]{1,5};/g;
return s.replace(re,function($0) {return String.fromCharCode(eval($0.replace(/[&#;]/g,"")));});
}
//--
//-- Saving
//--
var saveUsingSafari = false;
var startSaveArea = '<div id="' + 'storeArea">'; // Split up into two so that indexOf() of this source doesn't find it
var endSaveArea = '</d' + 'iv>';
// If there are unsaved changes, force the user to confirm before exitting
function confirmExit()
{
hadConfirmExit = true;
if((store && store.isDirty && store.isDirty()) || (story && story.areAnyDirty && story.areAnyDirty()))
return config.messages.confirmExit;
}
// Give the user a chance to save changes before exitting
function checkUnsavedChanges()
{
if(store && store.isDirty && store.isDirty() && window.hadConfirmExit === false) {
if(confirm(config.messages.unsavedChangesWarning))
saveChanges();
}
}
function updateLanguageAttribute(s)
{
if(config.locale) {
var mRE = /(<html(?:.*?)?)(?: xml:lang\="([a-z]+)")?(?: lang\="([a-z]+)")?>/;
var m = mRE.exec(s);
if(m) {
var t = m[1];
if(m[2])
t += ' xml:lang="' + config.locale + '"';
if(m[3])
t += ' lang="' + config.locale + '"';
t += ">";
s = s.substr(0,m.index) + t + s.substr(m.index+m[0].length);
}
}
return s;
}
function updateMarkupBlock(s,blockName,tiddlerName)
{
return s.replaceChunk(
"<!--%0-START-->".format([blockName]),
"<!--%0-END-->".format([blockName]),
"\n" + store.getRecursiveTiddlerText(tiddlerName,"") + "\n");
}
function updateOriginal(original,posDiv)
{
if(!posDiv)
posDiv = locateStoreArea(original);
if(!posDiv) {
alert(config.messages.invalidFileError.format([localPath]));
return null;
}
var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
convertUnicodeToUTF8(store.allTiddlersAsHtml()) + "\n" +
original.substr(posDiv[1]);
var newSiteTitle = convertUnicodeToUTF8(getPageTitle()).htmlEncode();
revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
revised = updateLanguageAttribute(revised);
revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
return revised;
}
function locateStoreArea(original)
{
// Locate the storeArea div's
var posOpeningDiv = original.indexOf(startSaveArea);
var limitClosingDiv = original.indexOf("<"+"!--POST-STOREAREA--"+">");
if(limitClosingDiv == -1)
limitClosingDiv = original.indexOf("<"+"!--POST-BODY-START--"+">");
var posClosingDiv = original.lastIndexOf(endSaveArea,limitClosingDiv == -1 ? original.length : limitClosingDiv);
return (posOpeningDiv != -1 && posClosingDiv != -1) ? [posOpeningDiv,posClosingDiv] : null;
}
function autoSaveChanges(onlyIfDirty,tiddlers)
{
if(config.options.chkAutoSave)
saveChanges(onlyIfDirty,tiddlers);
}
// Save this tiddlywiki with the pending changes
function saveChanges(onlyIfDirty,tiddlers)
{
if(onlyIfDirty && !store.isDirty())
return;
clearMessage();
// Get the URL of the document
var originalPath = document.location.toString();
// Check we were loaded from a file URL
if(originalPath.substr(0,5) != "file:") {
alert(config.messages.notFileUrlError);
if(store.tiddlerExists(config.messages.saveInstructions))
story.displayTiddler(null,config.messages.saveInstructions);
return;
}
var localPath = getLocalPath(originalPath);
// Load the original file
var original = loadFile(localPath);
if(original == null) {
alert(config.messages.cantSaveError);
if(store.tiddlerExists(config.messages.saveInstructions))
story.displayTiddler(null,config.messages.saveInstructions);
return;
}
// Locate the storeArea div's
var posDiv = locateStoreArea(original);
if(!posDiv) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
saveBackup(localPath,original);
saveRss(localPath);
saveEmpty(localPath,original,posDiv);
saveMain(localPath,original,posDiv);
}
function saveBackup(localPath,original)
{
// Save the backup
if(config.options.chkSaveBackups) {
var backupPath = getBackupPath(localPath);
var backup = config.browser.isIE ? ieCopyFile(backupPath,localPath) : saveFile(backupPath,original);
if(backup)
displayMessage(config.messages.backupSaved,"file://" + backupPath);
else
alert(config.messages.backupFailed);
}
}
function saveRss(localPath)
{
if(config.options.chkGenerateAnRssFeed) {
var rssPath = localPath.substr(0,localPath.lastIndexOf(".")) + ".xml";
var rssSave = saveFile(rssPath,convertUnicodeToUTF8(generateRss()));
if(rssSave)
displayMessage(config.messages.rssSaved,"file://" + rssPath);
else
alert(config.messages.rssFailed);
}
}
function saveEmpty(localPath,original,posDiv)
{
if(config.options.chkSaveEmptyTemplate) {
var emptyPath,p;
if((p = localPath.lastIndexOf("/")) != -1)
emptyPath = localPath.substr(0,p) + "/empty.html";
else if((p = localPath.lastIndexOf("\\")) != -1)
emptyPath = localPath.substr(0,p) + "\\empty.html";
else
emptyPath = localPath + ".empty.html";
var empty = original.substr(0,posDiv[0] + startSaveArea.length) + original.substr(posDiv[1]);
var emptySave = saveFile(emptyPath,empty);
if(emptySave)
displayMessage(config.messages.emptySaved,"file://" + emptyPath);
else
alert(config.messages.emptyFailed);
}
}
function saveMain(localPath,original,posDiv)
{
var save;
try {
var revised = updateOriginal(original,posDiv);
save = saveFile(localPath,revised);
} catch (ex) {
showException(ex);
}
if(save) {
displayMessage(config.messages.mainSaved,"file://" + localPath);
store.setDirty(false);
} else {
alert(config.messages.mainFailed);
}
}
function getLocalPath(origPath)
{
var originalPath = convertUriToUTF8(origPath,config.options.txtFileSystemCharSet);
// Remove any location or query part of the URL
var argPos = originalPath.indexOf("?");
if(argPos != -1)
originalPath = originalPath.substr(0,argPos);
var hashPos = originalPath.indexOf("#");
if(hashPos != -1)
originalPath = originalPath.substr(0,hashPos);
// Convert file://localhost/ to file:///
if(originalPath.indexOf("file://localhost/") == 0)
originalPath = "file://" + originalPath.substr(16);
// Convert to a native file format
var localPath;
if(originalPath.charAt(9) == ":") // pc local file
localPath = unescape(originalPath.substr(8)).replace(new RegExp("/","g"),"\\");
else if(originalPath.indexOf("file://///") == 0) // FireFox pc network file
localPath = "\\\\" + unescape(originalPath.substr(10)).replace(new RegExp("/","g"),"\\");
else if(originalPath.indexOf("file:///") == 0) // mac/unix local file
localPath = unescape(originalPath.substr(7));
else if(originalPath.indexOf("file:/") == 0) // mac/unix local file
localPath = unescape(originalPath.substr(5));
else // pc network file
localPath = "\\\\" + unescape(originalPath.substr(7)).replace(new RegExp("/","g"),"\\");
return localPath;
}
function getBackupPath(localPath)
{
var backSlash = true;
var dirPathPos = localPath.lastIndexOf("\\");
if(dirPathPos == -1) {
dirPathPos = localPath.lastIndexOf("/");
backSlash = false;
}
var backupFolder = config.options.txtBackupFolder;
if(!backupFolder || backupFolder == "")
backupFolder = ".";
var backupPath = localPath.substr(0,dirPathPos) + (backSlash ? "\\" : "/") + backupFolder + localPath.substr(dirPathPos);
backupPath = backupPath.substr(0,backupPath.lastIndexOf(".")) + "." + (new Date()).convertToYYYYMMDDHHMMSSMMM() + ".html";
return backupPath;
}
function generateRss()
{
var s = [];
var d = new Date();
var u = store.getTiddlerText("SiteUrl");
// Assemble the header
s.push("<" + "?xml version=\"1.0\"?" + ">");
s.push("<rss version=\"2.0\">");
s.push("<channel>");
s.push("<title" + ">" + wikifyPlain("SiteTitle").htmlEncode() + "</title" + ">");
if(u)
s.push("<link>" + u.htmlEncode() + "</link>");
s.push("<description>" + wikifyPlain("SiteSubtitle").htmlEncode() + "</description>");
s.push("<language>en-us</language>");
s.push("<copyright>Copyright " + d.getFullYear() + " " + config.options.txtUserName.htmlEncode() + "</copyright>");
s.push("<pubDate>" + d.toGMTString() + "</pubDate>");
s.push("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
s.push("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
s.push("<generator>TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + "</generator>");
// The body
var tiddlers = store.getTiddlers("modified","excludeLists");
var n = config.numRssItems > tiddlers.length ? 0 : tiddlers.length-config.numRssItems;
for (var t=tiddlers.length-1; t>=n; t--)
s.push(tiddlers[t].saveToRss(u));
// And footer
s.push("</channel>");
s.push("</rss>");
// Save it all
return s.join("\n");
}
//--
//-- Filesystem code
//--
function convertUTF8ToUnicode(u)
{
if(window.netscape == undefined)
return manualConvertUTF8ToUnicode(u);
else
return mozConvertUTF8ToUnicode(u);
}
function manualConvertUTF8ToUnicode(utf)
{
var uni = utf;
var src = 0;
var dst = 0;
var b1, b2, b3;
var c;
while(src < utf.length) {
b1 = utf.charCodeAt(src++);
if(b1 < 0x80) {
dst++;
} else if(b1 < 0xE0) {
b2 = utf.charCodeAt(src++);
c = String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
uni = uni.substring(0,dst++).concat(c,utf.substr(src));
} else {
b2 = utf.charCodeAt(src++);
b3 = utf.charCodeAt(src++);
c = String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
uni = uni.substring(0,dst++).concat(c,utf.substr(src));
}
}
return uni;
}
function mozConvertUTF8ToUnicode(u)
{
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
} catch(ex) {
return manualConvertUTF8ToUnicode(u);
} // fallback
var s = converter.ConvertToUnicode(u);
var fin = converter.Finish();
return (fin.length > 0) ? s+fin : s;
}
function convertUnicodeToUTF8(s)
{
if(window.netscape == undefined)
return manualConvertUnicodeToUTF8(s);
else
return mozConvertUnicodeToUTF8(s);
}
function manualConvertUnicodeToUTF8(s)
{
var re = /[^\u0000-\u007F]/g ;
return s.replace(re,function($0) {return "&#" + $0.charCodeAt(0).toString() + ";";});
}
function mozConvertUnicodeToUTF8(s)
{
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
} catch(ex) {
return manualConvertUnicodeToUTF8(s);
} // fallback
var u = converter.ConvertFromUnicode(s);
var fin = converter.Finish();
if(fin.length > 0)
return u + fin;
else
return u;
}
function convertUriToUTF8(uri,charSet)
{
if(window.netscape == undefined || charSet == undefined || charSet == "")
return uri;
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var converter = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
} catch(ex) {
return uri;
}
return converter.convertURISpecToUTF8(uri,charSet);
}
function saveFile(fileUrl,content)
{
var r = null;
if(!r)
r = mozillaSaveFile(fileUrl,content);
if(!r)
r = ieSaveFile(fileUrl,content);
if(!r)
r = javaSaveFile(fileUrl,content);
return r;
}
function loadFile(fileUrl)
{
var r = null;
if((r == null) || (r == false))
r = mozillaLoadFile(fileUrl);
if((r == null) || (r == false))
r = ieLoadFile(fileUrl);
if((r == null) || (r == false))
r = javaLoadFile(fileUrl);
return r;
}
// Returns null if it can't do it, false if there's an error, true if it saved OK
function ieSaveFile(filePath,content)
{
try {
var fso = new ActiveXObject("Scripting.FileSystemObject");
} catch(ex) {
return null;
}
var file = fso.OpenTextFile(filePath,2,-1,0);
file.Write(content);
file.Close();
return true;
}
// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function ieLoadFile(filePath)
{
try {
var fso = new ActiveXObject("Scripting.FileSystemObject");
var file = fso.OpenTextFile(filePath,1);
var content = file.ReadAll();
file.Close();
} catch(ex) {
return null;
}
return content;
}
function ieCopyFile(dest,source)
{
try {
var fso = new ActiveXObject("Scripting.FileSystemObject");
fso.GetFile(source).Copy(dest);
} catch(ex) {
return false;
}
return true;
}
// Returns null if it can't do it, false if there's an error, true if it saved OK
function mozillaSaveFile(filePath,content)
{
if(window.Components) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filePath);
if(!file.exists())
file.create(0,0664);
var out = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream);
out.init(file,0x20|0x02,00004,null);
out.write(content,content.length);
out.flush();
out.close();
return true;
} catch(ex) {
return false;
}
}
return null;
}
// Returns null if it can't do it, false if there's an error, or a string of the content if successful
function mozillaLoadFile(filePath)
{
if(window.Components) {
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath(filePath);
if(!file.exists())
return null;
var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
inputStream.init(file,0x01,00004,null);
var sInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
sInputStream.init(inputStream);
return sInputStream.read(sInputStream.available());
} catch(ex) {
return false;
}
}
return null;
}
function javaUrlToFilename(url)
{
var f = "//localhost";
if(url.indexOf(f) == 0)
return url.substring(f.length);
var i = url.indexOf(":");
if(i > 0)
return url.substring(i-1);
return url;
}
function javaSaveFile(filePath,content)
{
try {
if(document.applets["TiddlySaver"])
return document.applets["TiddlySaver"].saveFile(javaUrlToFilename(filePath),"UTF-8",content);
} catch(ex) {
}
try {
var s = new java.io.PrintStream(new java.io.FileOutputStream(javaUrlToFilename(filePath)));
s.print(content);
s.close();
} catch(ex) {
return null;
}
return true;
}
function javaLoadFile(filePath)
{
try {
if(document.applets["TiddlySaver"])
return String(document.applets["TiddlySaver"].loadFile(javaUrlToFilename(filePath),"UTF-8"));
} catch(ex) {
}
var content = [];
try {
var r = new java.io.BufferedReader(new java.io.FileReader(javaUrlToFilename(filePath)));
var line;
while((line = r.readLine()) != null)
content.push(new String(line));
r.close();
} catch(ex) {
return null;
}
return content.join("\n");
}
//--
//-- Server adaptor for talking to static files
//--
function FileAdaptor()
{
this.host = null;
this.store = null;
return this;
}
FileAdaptor.NotLoadedError = "TiddlyWiki file has not been loaded";
FileAdaptor.serverType = 'file';
// Open the specified host/server
FileAdaptor.prototype.openHost = function(host,context,userParams,callback)
{
this.host = host;
if(!context)
context = {};
context.adaptor = this;
context.callback = callback;
context.userParams = userParams;
var ret = loadRemoteFile(host,FileAdaptor.openHostCallback,context);
return typeof(ret) == "string" ? ret : true;
};
FileAdaptor.openHostCallback = function(status,context,responseText,url,xhr)
{
var adaptor = context.adaptor;
context.status = status;
if(!status) {
context.statusText = "Error reading file: " + xhr.statusText;
} else {
// Load the content into a TiddlyWiki() object
adaptor.store = new TiddlyWiki();
if(!adaptor.store.importTiddlyWiki(responseText))
context.statusText = config.messages.invalidFileError.format([url]);
}
context.callback(context,context.userParams);
};
// Gets the list of workspaces on a given server
FileAdaptor.prototype.getWorkspaceList = function(context,userParams,callback)
{
if(!context)
context = {};
context.workspaces = [{title:"(default)"}];
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// Open the specified workspace
FileAdaptor.prototype.openWorkspace = function(workspace,context,userParams,callback)
{
if(!context)
context = {};
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
// Gets the list of tiddlers within a given workspace
FileAdaptor.prototype.getTiddlerList = function(context,userParams,callback)
{
if(!this.store)
return FileAdaptor.NotLoadedError;
if(!context)
context = {};
context.tiddlers = [];
this.store.forEachTiddler(function(title,tiddler)
{
var t = new Tiddler(title);
t.text = tiddler.text;
t.modified = tiddler.modified;
t.modifier = tiddler.modifier;
t.fields['server.page.revision'] = tiddler.modified.convertToYYYYMMDDHHMM();
t.tags = tiddler.tags;
context.tiddlers.push(t);
});
context.status = true;
window.setTimeout(function() {callback(context,userParams);},10);
return true;
};
FileAdaptor.prototype.generateTiddlerInfo = function(tiddler)
{
var info = {};
info.uri = tiddler.fields['server.host'] + "#" + tiddler.title;
return info;
};
// Retrieves a tiddler from a given workspace on a given server
FileAdaptor.prototype.getTiddler = function(title,context,userParams,callback)
{
if(!this.store)
return FileAdaptor.NotLoadedError;
if(!context)
context = {};
context.tiddler = this.store.fetchTiddler(title);
if(context.tiddler) {
context.tiddler.fields['server.type'] = FileAdaptor.serverType;
context.tiddler.fields['server.host'] = this.host;
context.tiddler.fields['server.page.revision'] = context.tiddler.modified.convertToYYYYMMDDHHMM();
}
context.status = true;
if(context.allowSynchronous) {
context.isSynchronous = true;
callback(context,userParams);
} else {
window.setTimeout(function() {callback(context,userParams);},10);
}
return true;
};
FileAdaptor.prototype.close = function()
{
delete this.store;
this.store = null;
};
config.adaptors[FileAdaptor.serverType] = FileAdaptor;
//--
//-- Remote HTTP requests
//--
function loadRemoteFile(url,callback,params)
{
return doHttp("GET",url,null,null,null,null,callback,params,null);
}
// HTTP status codes
var httpStatus = {
OK: 200,
ContentCreated: 201,
NoContent: 204,
Unauthorized: 401,
Forbidden: 403,
NotFound: 404,
MethodNotAllowed: 405
};
function doHttp(type,url,data,contentType,username,password,callback,params,headers)
{
// Get an xhr object
var x = getXMLHttpRequest();
if(!x)
return "Can't create XMLHttpRequest object";
// Install callback
x.onreadystatechange = function() {
if (x.readyState == 4 && callback && (x.status !== undefined)) {
if([0, httpStatus.OK, httpStatus.ContentCreated, httpStatus.NoContent].contains(x.status))
callback(true,params,x.responseText,url,x);
else
callback(false,params,null,url,x);
x.onreadystatechange = function(){};
x = null;
}
};
// Send request
if(window.Components && window.netscape && window.netscape.security && document.location.protocol.indexOf("http") == -1)
window.netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
try {
url = url + (url.indexOf("?") < 0 ? "?" : "&") + "nocache=" + Math.random();
x.open(type,url,true,username,password);
if (data)
x.setRequestHeader("Content-Type", contentType ? contentType : "application/x-www-form-urlencoded");
if (x.overrideMimeType)
x.setRequestHeader("Connection", "close");
if(headers) {
for(n in headers)
x.setRequestHeader(n,headers[n]);
}
x.setRequestHeader("X-Requested-With", "TiddlyWiki " + version.major + "." + version.minor + "." + version.revision + (version.beta ? " (beta " + version.beta + ")" : ""));
x.send(data);
} catch (ex) {
return exceptionText(ex);
}
return x;
}
function getXMLHttpRequest()
{
try {
var x = new XMLHttpRequest(); // Modern
} catch(ex) {
try {
x = new ActiveXObject("Msxml2.XMLHTTP"); // IE 6
} catch (ex2) {
return null;
}
}
return x;
}
//--
//-- TiddlyWiki-specific utility functions
//--
function createTiddlyButton(theParent,theText,theTooltip,theAction,theClass,theId,theAccessKey)
{
var theButton = document.createElement("a");
if(theAction) {
theButton.onclick = theAction;
theButton.setAttribute("href","javascript:;");
}
if(theTooltip)
theButton.setAttribute("title",theTooltip);
if(theText)
theButton.appendChild(document.createTextNode(theText));
if(theClass)
theButton.className = theClass;
else
theButton.className = "button";
if(theId)
theButton.id = theId;
if(theParent)
theParent.appendChild(theButton);
if(theAccessKey)
theButton.setAttribute("accessKey",theAccessKey);
return theButton;
}
function createTiddlyLink(place,title,includeText,theClass,isStatic,linkedFromTiddler,noToggle)
{
var text = includeText ? title : null;
var i = getTiddlyLinkInfo(title,theClass);
var btn = isStatic ? createExternalLink(place,store.getTiddlerText("SiteUrl",null) + "#" + title) : createTiddlyButton(place,text,i.subTitle,onClickTiddlerLink,i.classes);
btn.setAttribute("refresh","link");
btn.setAttribute("tiddlyLink",title);
if(noToggle)
btn.setAttribute("noToggle","true");
if(linkedFromTiddler) {
var fields = linkedFromTiddler.getInheritedFields();
if(fields)
btn.setAttribute("tiddlyFields",fields);
}
return btn;
}
function refreshTiddlyLink(e,title)
{
var i = getTiddlyLinkInfo(title,e.className);
e.className = i.classes;
e.title = i.subTitle;
}
function getTiddlyLinkInfo(title,currClasses)
{
var classes = currClasses ? currClasses.split(" ") : [];
classes.pushUnique("tiddlyLink");
var tiddler = store.fetchTiddler(title);
var subTitle;
if(tiddler) {
subTitle = tiddler.getSubtitle();
classes.pushUnique("tiddlyLinkExisting");
classes.remove("tiddlyLinkNonExisting");
classes.remove("shadow");
} else {
classes.remove("tiddlyLinkExisting");
classes.pushUnique("tiddlyLinkNonExisting");
if(store.isShadowTiddler(title)) {
subTitle = config.messages.shadowedTiddlerToolTip.format([title]);
classes.pushUnique("shadow");
} else {
subTitle = config.messages.undefinedTiddlerToolTip.format([title]);
classes.remove("shadow");
}
}
if(config.annotations[title])
subTitle = config.annotations[title];
return {classes: classes.join(" "),subTitle: subTitle};
}
function createExternalLink(place,url)
{
var theLink = document.createElement("a");
theLink.className = "externalLink";
theLink.href = url;
theLink.title = config.messages.externalLinkTooltip.format([url]);
if(config.options.chkOpenInNewWindow)
theLink.target = "_blank";
place.appendChild(theLink);
return theLink;
}
// Event handler for clicking on a tiddly link
function onClickTiddlerLink(e)
{
if(!e) e = window.event;
var theTarget = resolveTarget(e);
var theLink = theTarget;
var title = null;
var fields = null;
var noToggle = null;
do {
title = theLink.getAttribute("tiddlyLink");
fields = theLink.getAttribute("tiddlyFields");
noToggle = theLink.getAttribute("noToggle");
theLink = theLink.parentNode;
} while(title == null && theLink != null);
if(!fields && !store.isShadowTiddler(title))
fields = String.encodeHashMap(config.defaultCustomFields);
if(title) {
var toggling = e.metaKey || e.ctrlKey;
if(config.options.chkToggleLinks)
toggling = !toggling;
if(noToggle)
toggling = false;
story.displayTiddler(theTarget,title,null,true,null,fields,toggling);
}
clearMessage();
return false;
}
// Create a button for a tag with a popup listing all the tiddlers that it tags
function createTagButton(place,tag,excludeTiddler)
{
var theTag = createTiddlyButton(place,tag,config.views.wikified.tag.tooltip.format([tag]),onClickTag);
theTag.setAttribute("tag",tag);
if(excludeTiddler)
theTag.setAttribute("tiddler",excludeTiddler);
return theTag;
}
// Event handler for clicking on a tiddler tag
function onClickTag(e)
{
if(!e) var e = window.event;
var theTarget = resolveTarget(e);
var popup = Popup.create(this);
var tag = this.getAttribute("tag");
var title = this.getAttribute("tiddler");
if(popup && tag) {
var tagged = store.getTaggedTiddlers(tag);
var titles = [];
var li,r;
for(r=0;r<tagged.length;r++) {
if(tagged[r].title != title)
titles.push(tagged[r].title);
}
var lingo = config.views.wikified.tag;
if(titles.length > 0) {
var openAll = createTiddlyButton(createTiddlyElement(popup,"li"),lingo.openAllText.format([tag]),lingo.openAllTooltip,onClickTagOpenAll);
openAll.setAttribute("tag",tag);
createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
for(r=0; r<titles.length; r++) {
createTiddlyLink(createTiddlyElement(popup,"li"),titles[r],true);
}
} else {
createTiddlyText(createTiddlyElement(popup,"li",null,"disabled"),lingo.popupNone.format([tag]));
}
createTiddlyElement(createTiddlyElement(popup,"li",null,"listBreak"),"div");
var h = createTiddlyLink(createTiddlyElement(popup,"li"),tag,false);
createTiddlyText(h,lingo.openTag.format([tag]));
}
Popup.show();
e.cancelBubble = true;
if(e.stopPropagation) e.stopPropagation();
return false;
}
// Event handler for 'open all' on a tiddler popup
function onClickTagOpenAll(e)
{
if(!e) var e = window.event;
var tag = this.getAttribute("tag");
var tagged = store.getTaggedTiddlers(tag);
var titles = [];
for(var t=0; t<tagged.length; t++)
titles.push(tagged[t].title);
story.displayTiddlers(this,titles);
return false;
}
function onClickError(e)
{
if(!e) var e = window.event;
var popup = Popup.create(this);
var lines = this.getAttribute("errorText").split("\n");
for(var t=0; t<lines.length; t++)
createTiddlyElement(popup,"li",null,null,lines[t]);
Popup.show();
e.cancelBubble = true;
if(e.stopPropagation) e.stopPropagation();
return false;
}
function createTiddlyDropDown(place,onchange,options,defaultValue)
{
var sel = createTiddlyElement(place,"select");
sel.onchange = onchange;
for(var t=0; t<options.length; t++) {
var e = createTiddlyElement(sel,"option",null,null,options[t].caption);
e.value = options[t].name;
if(options[t].name == defaultValue)
e.selected = true;
}
return sel;
}
function createTiddlyPopup(place,caption,tooltip,tiddler)
{
if(tiddler.text) {
createTiddlyLink(place,caption,true);
var btn = createTiddlyButton(place,glyph("downArrow"),tooltip,onClickTiddlyPopup,"tiddlerPopupButton");
btn.tiddler = tiddler;
} else {
createTiddlyText(place,caption);
}
}
function onClickTiddlyPopup(e)
{
if(!e) var e = window.event;
var tiddler = this.tiddler;
if(tiddler.text) {
var popup = Popup.create(this,"div","popupTiddler");
wikify(tiddler.text,popup,null,tiddler);
Popup.show();
}
if(e) e.cancelBubble = true;
if(e && e.stopPropagation) e.stopPropagation();
return false;
}
function createTiddlyError(place,title,text)
{
var btn = createTiddlyButton(place,title,null,onClickError,"errorButton");
if(text) btn.setAttribute("errorText",text);
}
function merge(dst,src,preserveExisting)
{
for(p in src) {
if(!preserveExisting || dst[p] === undefined)
dst[p] = src[p];
}
return dst;
}
// Returns a string containing the description of an exception, optionally prepended by a message
function exceptionText(e,message)
{
var s = e.description ? e.description : e.toString();
return message ? "%0:\n%1".format([message,s]) : s;
}
// Displays an alert of an exception description with optional message
function showException(e,message)
{
alert(exceptionText(e,message));
}
function alertAndThrow(m)
{
alert(m);
throw(m);
}
function glyph(name)
{
var g = config.glyphs;
var b = g.currBrowser;
if(b == null) {
b = 0;
while(!g.browsers[b]() && b < g.browsers.length-1)
b++;
g.currBrowser = b;
}
if(!g.codes[name])
return "";
return g.codes[name][b];
}
//-
//- Animation engine
//-
function Animator()
{
this.running = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
this.timerID = 0; // ID of the timer used for animating
this.animations = []; // List of animations in progress
return this;
}
// Start animation engine
Animator.prototype.startAnimating = function() // Variable number of arguments
{
for(var t=0; t<arguments.length; t++)
this.animations.push(arguments[t]);
if(this.running == 0) {
var me = this;
this.timerID = window.setInterval(function() {me.doAnimate(me);},10);
}
this.running += arguments.length;
};
// Perform an animation engine tick, calling each of the known animation modules
Animator.prototype.doAnimate = function(me)
{
var a = 0;
while(a < me.animations.length) {
var animation = me.animations[a];
if(animation.tick()) {
a++;
} else {
me.animations.splice(a,1);
if(--me.running == 0)
window.clearInterval(me.timerID);
}
}
};
// Map a 0..1 value to 0..1, but slow down at the start and end
Animator.slowInSlowOut = function(progress)
{
return(1-((Math.cos(progress * Math.PI)+1)/2));
};
//--
//-- Morpher animation
//--
// Animate a set of properties of an element
function Morpher(element,duration,properties,callback)
{
this.element = element;
this.duration = duration;
this.properties = properties;
this.startTime = new Date();
this.endTime = Number(this.startTime) + duration;
this.callback = callback;
this.tick();
return this;
}
Morpher.prototype.assignStyle = function(element,style,value)
{
switch(style) {
case "-tw-vertScroll":
window.scrollTo(findScrollX(),value);
break;
case "-tw-horizScroll":
window.scrollTo(value,findScrollY());
break;
default:
element.style[style] = value;
break;
}
};
Morpher.prototype.stop = function()
{
for(var t=0; t<this.properties.length; t++) {
var p = this.properties[t];
if(p.atEnd !== undefined) {
this.assignStyle(this.element,p.style,p.atEnd);
}
}
if(this.callback)
this.callback(this.element,this.properties);
};
Morpher.prototype.tick = function()
{
var currTime = Number(new Date());
progress = Animator.slowInSlowOut(Math.min(1,(currTime-this.startTime)/this.duration));
for(var t=0; t<this.properties.length; t++) {
var p = this.properties[t];
if(p.start !== undefined && p.end !== undefined) {
var template = p.template ? p.template : "%0";
switch(p.format) {
case undefined:
case "style":
var v = p.start + (p.end-p.start) * progress;
this.assignStyle(this.element,p.style,template.format([v]));
break;
case "color":
break;
}
}
}
if(currTime >= this.endTime) {
this.stop();
return false;
}
return true;
};
//--
//-- Zoomer animation
//--
function Zoomer(text,startElement,targetElement,unused)
{
var e = createTiddlyElement(document.body,"div",null,"zoomer");
createTiddlyElement(e,"div",null,null,text);
var winWidth = findWindowWidth();
var winHeight = findWindowHeight();
var p = [
{style: 'left', start: findPosX(startElement), end: findPosX(targetElement), template: '%0px'},
{style: 'top', start: findPosY(startElement), end: findPosY(targetElement), template: '%0px'},
{style: 'width', start: Math.min(startElement.scrollWidth,winWidth), end: Math.min(targetElement.scrollWidth,winWidth), template: '%0px', atEnd: 'auto'},
{style: 'height', start: Math.min(startElement.scrollHeight,winHeight), end: Math.min(targetElement.scrollHeight,winHeight), template: '%0px', atEnd: 'auto'},
{style: 'fontSize', start: 8, end: 24, template: '%0pt'}
];
var c = function(element,properties) {removeNode(element);};
return new Morpher(e,config.animDuration,p,c);
}
//--
//-- Scroller animation
//--
function Scroller(targetElement,unused)
{
var p = [
{style: '-tw-vertScroll', start: findScrollY(), end: ensureVisible(targetElement)}
];
return new Morpher(targetElement,config.animDuration,p);
}
//--
//-- Slider animation
//--
// deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
function Slider(element,opening,unused,deleteMode)
{
element.style.overflow = 'hidden';
if(opening)
element.style.height = '0px'; // Resolves a Firefox flashing bug
element.style.display = 'block';
var left = findPosX(element);
var width = element.scrollWidth;
var height = element.scrollHeight;
var winWidth = findWindowWidth();
var p = [];
var c = null;
if(opening) {
p.push({style: 'height', start: 0, end: height, template: '%0px', atEnd: 'auto'});
p.push({style: 'opacity', start: 0, end: 1, template: '%0'});
p.push({style: 'filter', start: 0, end: 100, template: 'alpha(opacity:%0)'});
} else {
p.push({style: 'height', start: height, end: 0, template: '%0px'});
p.push({style: 'display', atEnd: 'none'});
p.push({style: 'opacity', start: 1, end: 0, template: '%0'});
p.push({style: 'filter', start: 100, end: 0, template: 'alpha(opacity:%0)'});
switch(deleteMode) {
case "all":
c = function(element,properties) {removeNode(element);};
break;
case "children":
c = function(element,properties) {removeChildren(element);};
break;
}
}
return new Morpher(element,config.animDuration,p,c);
}
//--
//-- Popup menu
//--
var Popup = {
stack: [] // Array of objects with members root: and popup:
};
Popup.create = function(root,elem,theClass)
{
Popup.remove();
var popup = createTiddlyElement(document.body,elem ? elem : "ol","popup",theClass ? theClass : "popup");
Popup.stack.push({root: root, popup: popup});
return popup;
};
Popup.onDocumentClick = function(e)
{
if (!e) var e = window.event;
var target = resolveTarget(e);
if(e.eventPhase == undefined)
Popup.remove();
else if(e.eventPhase == Event.BUBBLING_PHASE || e.eventPhase == Event.AT_TARGET)
Popup.remove();
return true;
};
Popup.show = function(unused1,unused2)
{
var curr = Popup.stack[Popup.stack.length-1];
this.place(curr.root,curr.popup);
addClass(curr.root,"highlight");
if(config.options.chkAnimate && anim && typeof Scroller == "function")
anim.startAnimating(new Scroller(curr.popup));
else
window.scrollTo(0,ensureVisible(curr.popup));
};
Popup.place = function(root,popup,offset)
{
if(!offset) var offset = {x:0, y:0};
var rootLeft = findPosX(root);
var rootTop = findPosY(root);
var rootHeight = root.offsetHeight;
var popupLeft = rootLeft + offset.x;
var popupTop = rootTop + rootHeight + offset.y;
var winWidth = findWindowWidth();
if(popup.offsetWidth > winWidth*0.75)
popup.style.width = winWidth*0.75 + "px";
var popupWidth = popup.offsetWidth;
if(popupLeft + popupWidth > winWidth)
popupLeft = winWidth - popupWidth;
popup.style.left = popupLeft + "px";
popup.style.top = popupTop + "px";
popup.style.display = "block";
}
Popup.remove = function()
{
if(Popup.stack.length > 0) {
Popup.removeFrom(0);
}
};
Popup.removeFrom = function(from)
{
for(var t=Popup.stack.length-1; t>=from; t--) {
var p = Popup.stack[t];
removeClass(p.root,"highlight");
removeNode(p.popup);
}
Popup.stack = Popup.stack.slice(0,from);
};
//--
//-- Wizard support
//--
function Wizard(elem)
{
if(elem) {
this.formElem = findRelated(elem,"wizard","className");
this.bodyElem = findRelated(this.formElem.firstChild,"wizardBody","className","nextSibling");
this.footElem = findRelated(this.formElem.firstChild,"wizardFooter","className","nextSibling");
} else {
this.formElem = null;
this.bodyElem = null;
this.footElem = null;
}
}
Wizard.prototype.setValue = function(name,value)
{
if(this.formElem)
this.formElem[name] = value;
};
Wizard.prototype.getValue = function(name)
{
return this.formElem ? this.formElem[name] : null;
};
Wizard.prototype.createWizard = function(place,title)
{
this.formElem = createTiddlyElement(place,"form",null,"wizard");
createTiddlyElement(this.formElem,"h1",null,null,title);
this.bodyElem = createTiddlyElement(this.formElem,"div",null,"wizardBody");
this.footElem = createTiddlyElement(this.formElem,"div",null,"wizardFooter");
};
Wizard.prototype.clear = function()
{
removeChildren(this.bodyElem);
};
Wizard.prototype.setButtons = function(buttonInfo,status)
{
removeChildren(this.footElem);
for(var t=0; t<buttonInfo.length; t++) {
createTiddlyButton(this.footElem,buttonInfo[t].caption,buttonInfo[t].tooltip,buttonInfo[t].onClick);
insertSpacer(this.footElem);
}
if(typeof status == "string") {
createTiddlyElement(this.footElem,"span",null,"status",status);
}
};
Wizard.prototype.addStep = function(stepTitle,html)
{
removeChildren(this.bodyElem);
var w = createTiddlyElement(this.bodyElem,"div");
createTiddlyElement(w,"h2",null,null,stepTitle);
var step = createTiddlyElement(w,"div",null,"wizardStep");
step.innerHTML = html;
applyHtmlMacros(step,tiddler);
};
Wizard.prototype.getElement = function(name)
{
return this.formElem.elements[name];
};
//--
//-- ListView gadget
//--
var ListView = {};
// Create a listview
ListView.create = function(place,listObject,listTemplate,callback,className)
{
var table = createTiddlyElement(place,"table",null,className ? className : "listView twtable");
var thead = createTiddlyElement(table,"thead");
var r = createTiddlyElement(thead,"tr");
for(var t=0; t<listTemplate.columns.length; t++) {
var columnTemplate = listTemplate.columns[t];
var c = createTiddlyElement(r,"th");
var colType = ListView.columnTypes[columnTemplate.type];
if(colType && colType.createHeader)
colType.createHeader(c,columnTemplate,t);
}
var tbody = createTiddlyElement(table,"tbody");
for(var rc=0; rc<listObject.length; rc++) {
rowObject = listObject[rc];
r = createTiddlyElement(tbody,"tr");
for(c=0; c<listTemplate.rowClasses.length; c++) {
if(rowObject[listTemplate.rowClasses[c].field])
addClass(r,listTemplate.rowClasses[c].className);
}
rowObject.rowElement = r;
rowObject.colElements = {};
for(var cc=0; cc<listTemplate.columns.length; cc++) {
c = createTiddlyElement(r,"td");
columnTemplate = listTemplate.columns[cc];
var field = columnTemplate.field;
colType = ListView.columnTypes[columnTemplate.type];
if(colType && colType.createItem)
colType.createItem(c,rowObject,field,columnTemplate,cc,rc);
rowObject.colElements[field] = c;
}
}
if(callback && listTemplate.actions)
createTiddlyDropDown(place,ListView.getCommandHandler(callback),listTemplate.actions);
if(callback && listTemplate.buttons) {
for(t=0; t<listTemplate.buttons.length; t++) {
var a = listTemplate.buttons[t];
if(a && a.name != "")
createTiddlyButton(place,a.caption,null,ListView.getCommandHandler(callback,a.name,a.allowEmptySelection));
}
}
return table;
};
ListView.getCommandHandler = function(callback,name,allowEmptySelection)
{
return function(e) {
var view = findRelated(this,"TABLE",null,"previousSibling");
var tiddlers = [];
ListView.forEachSelector(view,function(e,rowName) {
if(e.checked)
tiddlers.push(rowName);
});
if(tiddlers.length == 0 && !allowEmptySelection) {
alert(config.messages.nothingSelected);
} else {
if(this.nodeName.toLowerCase() == "select") {
callback(view,this.value,tiddlers);
this.selectedIndex = 0;
} else {
callback(view,name,tiddlers);
}
}
};
};
// Invoke a callback for each selector checkbox in the listview
ListView.forEachSelector = function(view,callback)
{
var checkboxes = view.getElementsByTagName("input");
var hadOne = false;
for(var t=0; t<checkboxes.length; t++) {
var cb = checkboxes[t];
if(cb.getAttribute("type") == "checkbox") {
var rn = cb.getAttribute("rowName");
if(rn) {
callback(cb,rn);
hadOne = true;
}
}
}
return hadOne;
};
ListView.getSelectedRows = function(view)
{
var rowNames = [];
ListView.forEachSelector(view,function(e,rowName) {
if(e.checked)
rowNames.push(rowName);
});
return rowNames;
};
ListView.columnTypes = {};
ListView.columnTypes.String = {
createHeader: function(place,columnTemplate,col)
{
createTiddlyText(place,columnTemplate.title);
},
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined)
createTiddlyText(place,v);
}
};
ListView.columnTypes.WikiText = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined)
wikify(v,place,null,null);
}
};
ListView.columnTypes.Tiddler = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined && v.title)
createTiddlyPopup(place,v.title,config.messages.listView.tiddlerTooltip,v);
}
};
ListView.columnTypes.Size = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined) {
var t = 0;
while(t<config.messages.sizeTemplates.length-1 && v<config.messages.sizeTemplates[t].unit)
t++;
createTiddlyText(place,config.messages.sizeTemplates[t].template.format([Math.round(v/config.messages.sizeTemplates[t].unit)]));
}
}
};
ListView.columnTypes.Link = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
var c = columnTemplate.text;
if(v != undefined)
createTiddlyText(createExternalLink(place,v),c ? c : v);
}
};
ListView.columnTypes.Date = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined)
createTiddlyText(place,v.formatString(columnTemplate.dateFormat));
}
};
ListView.columnTypes.StringList = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined) {
for(var t=0; t<v.length; t++) {
createTiddlyText(place,v[t]);
createTiddlyElement(place,"br");
}
}
}
};
ListView.columnTypes.Selector = {
createHeader: function(place,columnTemplate,col)
{
createTiddlyCheckbox(place,null,false,this.onHeaderChange);
},
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var e = createTiddlyCheckbox(place,null,listObject[field],null);
e.setAttribute("rowName",listObject[columnTemplate.rowName]);
},
onHeaderChange: function(e)
{
var state = this.checked;
var view = findRelated(this,"TABLE");
if(!view)
return;
ListView.forEachSelector(view,function(e,rowName) {
e.checked = state;
});
}
};
ListView.columnTypes.Tags = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var tags = listObject[field];
createTiddlyText(place,String.encodeTiddlyLinkList(tags));
}
};
ListView.columnTypes.Boolean = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
if(listObject[field] == true)
createTiddlyText(place,columnTemplate.trueText);
if(listObject[field] == false)
createTiddlyText(place,columnTemplate.falseText);
}
};
ListView.columnTypes.TagCheckbox = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var e = createTiddlyCheckbox(place,null,listObject[field],this.onChange);
e.setAttribute("tiddler",listObject.title);
e.setAttribute("tag",columnTemplate.tag);
},
onChange : function(e)
{
var tag = this.getAttribute("tag");
var tiddler = this.getAttribute("tiddler");
store.setTiddlerTag(tiddler,this.checked,tag);
}
};
ListView.columnTypes.TiddlerLink = {
createHeader: ListView.columnTypes.String.createHeader,
createItem: function(place,listObject,field,columnTemplate,col,row)
{
var v = listObject[field];
if(v != undefined) {
var link = createTiddlyLink(place,listObject[columnTemplate.tiddlerLink],false,null);
createTiddlyText(link,listObject[field]);
}
}
};
//--
//-- Augmented methods for the JavaScript Number(), Array(), String() and Date() objects
//--
// Clamp a number to a range
Number.prototype.clamp = function(min,max)
{
var c = this;
if(c < min)
c = min;
if(c > max)
c = max;
return c;
};
// Add indexOf function if browser does not support it
if(!Array.indexOf) {
Array.prototype.indexOf = function(item,from)
{
if(!from)
from = 0;
for(var i=from; i<this.length; i++) {
if(this[i] === item)
return i;
}
return -1;
};}
// Find an entry in a given field of the members of an array
Array.prototype.findByField = function(field,value)
{
for(var t=0; t<this.length; t++) {
if(this[t][field] == value)
return t;
}
return null;
};
// Return whether an entry exists in an array
Array.prototype.contains = function(item)
{
return this.indexOf(item) != -1;
};
// Adds, removes or toggles a particular value within an array
// value - value to add
// mode - +1 to add value, -1 to remove value, 0 to toggle it
Array.prototype.setItem = function(value,mode)
{
var p = this.indexOf(value);
if(mode == 0)
mode = (p == -1) ? +1 : -1;
if(mode == +1) {
if(p == -1)
this.push(value);
} else if(mode == -1) {
if(p != -1)
this.splice(p,1);
}
};
// Return whether one of a list of values exists in an array
Array.prototype.containsAny = function(items)
{
for(var i=0; i<items.length; i++) {
if (this.indexOf(items[i]) != -1)
return true;
}
return false;
};
// Return whether all of a list of values exists in an array
Array.prototype.containsAll = function(items)
{
for (var i = 0; i<items.length; i++) {
if (this.indexOf(items[i]) == -1)
return false;
}
return true;
};
// Push a new value into an array only if it is not already present in the array. If the optional unique parameter is false, it reverts to a normal push
Array.prototype.pushUnique = function(item,unique)
{
if(unique === false) {
this.push(item);
} else {
if(this.indexOf(item) == -1)
this.push(item);
}
};
Array.prototype.remove = function(item)
{
var p = this.indexOf(item);
if(p != -1)
this.splice(p,1);
};
// Get characters from the right end of a string
String.prototype.right = function(n)
{
return n < this.length ? this.slice(this.length-n) : this;
};
// Trim whitespace from both ends of a string
String.prototype.trim = function()
{
return this.replace(/^\s*|\s*$/g,"");
};
// Convert a string from a CSS style property name to a JavaScript style name ("background-color" -> "backgroundColor")
String.prototype.unDash = function()
{
var s = this.split("-");
if(s.length > 1) {
for(var t=1; t<s.length; t++)
s[t] = s[t].substr(0,1).toUpperCase() + s[t].substr(1);
}
return s.join("");
};
// Substitute substrings from an array into a format string that includes '%1'-type specifiers
String.prototype.format = function(substrings)
{
var subRegExp = /(?:%(\d+))/mg;
var currPos = 0;
var r = [];
do {
var match = subRegExp.exec(this);
if(match && match[1]) {
if(match.index > currPos)
r.push(this.substring(currPos,match.index));
r.push(substrings[parseInt(match[1])]);
currPos = subRegExp.lastIndex;
}
} while(match);
if(currPos < this.length)
r.push(this.substring(currPos,this.length));
return r.join("");
};
// Escape any special RegExp characters with that character preceded by a backslash
String.prototype.escapeRegExp = function()
{
var s = "\\^$*+?()=!|,{}[].";
var c = this;
for(var t=0; t<s.length; t++)
c = c.replace(new RegExp("\\" + s.substr(t,1),"g"),"\\" + s.substr(t,1));
return c;
};
// Convert "\" to "\s", newlines to "\n" (and remove carriage returns)
String.prototype.escapeLineBreaks = function()
{
return this.replace(/\\/mg,"\\s").replace(/\n/mg,"\\n").replace(/\r/mg,"");
};
// Convert "\n" to newlines, "\b" to " ", "\s" to "\" (and remove carriage returns)
String.prototype.unescapeLineBreaks = function()
{
return this.replace(/\\n/mg,"\n").replace(/\\b/mg," ").replace(/\\s/mg,"\\").replace(/\r/mg,"");
};
// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
String.prototype.htmlEncode = function()
{
return this.replace(/&/mg,"&amp;").replace(/</mg,"&lt;").replace(/>/mg,"&gt;").replace(/\"/mg,"&quot;");
};
// Convert "&amp;" to &, "&lt;" to <, "&gt;" to > and "&quot;" to "
String.prototype.htmlDecode = function()
{
return this.replace(/&lt;/mg,"<").replace(/&gt;/mg,">").replace(/&quot;/mg,"\"").replace(/&amp;/mg,"&");
};
// Convert a string to it's JSON representation by encoding control characters, double quotes and backslash. See json.org
String.prototype.toJSONString = function()
{
var m = {
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'"' : '\\"',
'\\': '\\\\'
};
var replaceFn = function(a,b) {
var c = m[b];
if(c)
return c;
c = b.charCodeAt();
return '\\u00' + Math.floor(c / 16).toString(16) + (c % 16).toString(16);
};
if(/["\\\x00-\x1f]/.test(this))
return '"' + this.replace(/([\x00-\x1f\\"])/g,replaceFn) + '"';
return '"' + this + '"';
};
// Parse a space-separated string of name:value parameters
// The result is an array of objects:
// result[0] = object with a member for each parameter name, value of that member being an array of values
// result[1..n] = one object for each parameter, with 'name' and 'value' members
String.prototype.parseParams = function(defaultName,defaultValue,allowEval,noNames,cascadeDefaults)
{
var parseToken = function(match,p) {
var n;
if(match[p]) // Double quoted
n = match[p];
else if(match[p+1]) // Single quoted
n = match[p+1];
else if(match[p+2]) // Double-square-bracket quoted
n = match[p+2];
else if(match[p+3]) // Double-brace quoted
try {
n = match[p+3];
if(allowEval)
n = window.eval(n);
} catch(ex) {
throw "Unable to evaluate {{" + match[p+3] + "}}: " + exceptionText(ex);
}
else if(match[p+4]) // Unquoted
n = match[p+4];
else if(match[p+5]) // empty quote
n = "";
return n;
};
var r = [{}];
var dblQuote = "(?:\"((?:(?:\\\\\")|[^\"])+)\")";
var sngQuote = "(?:'((?:(?:\\\\\')|[^'])+)')";
var dblSquare = "(?:\\[\\[((?:\\s|\\S)*?)\\]\\])";
var dblBrace = "(?:\\{\\{((?:\\s|\\S)*?)\\}\\})";
var unQuoted = noNames ? "([^\"'\\s]\\S*)" : "([^\"':\\s][^\\s:]*)";
var emptyQuote = "((?:\"\")|(?:''))";
var skipSpace = "(?:\\s*)";
var token = "(?:" + dblQuote + "|" + sngQuote + "|" + dblSquare + "|" + dblBrace + "|" + unQuoted + "|" + emptyQuote + ")";
var re = noNames ? new RegExp(token,"mg") : new RegExp(skipSpace + token + skipSpace + "(?:(\\:)" + skipSpace + token + ")?","mg");
var params = [];
do {
var match = re.exec(this);
if(match) {
var n = parseToken(match,1);
if(noNames) {
r.push({name:"",value:n});
} else {
var v = parseToken(match,8);
if(v == null && defaultName) {
v = n;
n = defaultName;
} else if(v == null && defaultValue) {
v = defaultValue;
}
r.push({name:n,value:v});
if(cascadeDefaults) {
defaultName = n;
defaultValue = v;
}
}
}
} while(match);
// Summarise parameters into first element
for(var t=1; t<r.length; t++) {
if(r[0][r[t].name])
r[0][r[t].name].push(r[t].value);
else
r[0][r[t].name] = [r[t].value];
}
return r;
};
// Process a string list of macro parameters into an array. Parameters can be quoted with "", '',
// [[]], {{ }} or left unquoted (and therefore space-separated). Double-braces {{}} results in
// an *evaluated* parameter: e.g. {{config.options.txtUserName}} results in the current user's name.
String.prototype.readMacroParams = function()
{
var p = this.parseParams("list",null,true,true);
var n = [];
for(var t=1; t<p.length; t++)
n.push(p[t].value);
return n;
};
// Process a string list of unique tiddler names into an array. Tiddler names that have spaces in them must be [[bracketed]]
String.prototype.readBracketedList = function(unique)
{
var p = this.parseParams("list",null,false,true);
var n = [];
for(var t=1; t<p.length; t++)
n.pushUnique(p[t].value,unique);
return n;
};
// Returns array with start and end index of chunk between given start and end marker, or undefined.
String.prototype.getChunkRange = function(start,end)
{
var s = this.indexOf(start);
if(s != -1) {
s += start.length;
var e = this.indexOf(end,s);
if(e != -1)
return [s,e];
}
};
// Replace a chunk of a string given start and end markers
String.prototype.replaceChunk = function(start,end,sub)
{
var r = this.getChunkRange(start,end);
return r ? this.substring(0,r[0]) + sub + this.substring(r[1]) : this;
};
// Returns a chunk of a string between start and end markers, or undefined
String.prototype.getChunk = function(start,end)
{
var r = this.getChunkRange(start,end);
if(r)
return this.substring(r[0],r[1]);
};
// Static method to bracket a string with double square brackets if it contains a space
String.encodeTiddlyLink = function(title)
{
return title.indexOf(" ") == -1 ? title : "[[" + title + "]]";
};
// Static method to encodeTiddlyLink for every item in an array and join them with spaces
String.encodeTiddlyLinkList = function(list)
{
if(list) {
var results = [];
for(var t=0; t<list.length; t++)
results.push(String.encodeTiddlyLink(list[t]));
return results.join(" ");
} else {
return "";
}
};
// Convert a string as a sequence of name:"value" pairs into a hashmap
String.prototype.decodeHashMap = function()
{
var fields = this.parseParams("anon","",false);
var r = {};
for(var t=1; t<fields.length; t++)
r[fields[t].name] = fields[t].value;
return r;
};
// Static method to encode a hashmap into a name:"value"... string
String.encodeHashMap = function(hashmap)
{
var r = [];
for(var t in hashmap)
r.push(t + ':"' + hashmap[t] + '"');
return r.join(" ");
};
// Static method to left-pad a string with 0s to a certain width
String.zeroPad = function(n,d)
{
var s = n.toString();
if(s.length < d)
s = "000000000000000000000000000".substr(0,d-s.length) + s;
return s;
};
String.prototype.startsWith = function(prefix)
{
return !prefix || this.substring(0,prefix.length) == prefix;
};
// Returns the first value of the given named parameter.
function getParam(params,name,defaultValue)
{
if(!params)
return defaultValue;
var p = params[0][name];
return p ? p[0] : defaultValue;
}
// Returns the first value of the given boolean named parameter.
function getFlag(params,name,defaultValue)
{
return !!getParam(params,name,defaultValue);
}
// Substitute date components into a string
Date.prototype.formatString = function(template)
{
var t = template.replace(/0hh12/g,String.zeroPad(this.getHours12(),2));
t = t.replace(/hh12/g,this.getHours12());
t = t.replace(/0hh/g,String.zeroPad(this.getHours(),2));
t = t.replace(/hh/g,this.getHours());
t = t.replace(/mmm/g,config.messages.dates.shortMonths[this.getMonth()]);
t = t.replace(/0mm/g,String.zeroPad(this.getMinutes(),2));
t = t.replace(/mm/g,this.getMinutes());
t = t.replace(/0ss/g,String.zeroPad(this.getSeconds(),2));
t = t.replace(/ss/g,this.getSeconds());
t = t.replace(/[ap]m/g,this.getAmPm().toLowerCase());
t = t.replace(/[AP]M/g,this.getAmPm().toUpperCase());
t = t.replace(/wYYYY/g,this.getYearForWeekNo());
t = t.replace(/wYY/g,String.zeroPad(this.getYearForWeekNo()-2000,2));
t = t.replace(/YYYY/g,this.getFullYear());
t = t.replace(/YY/g,String.zeroPad(this.getFullYear()-2000,2));
t = t.replace(/MMM/g,config.messages.dates.months[this.getMonth()]);
t = t.replace(/0MM/g,String.zeroPad(this.getMonth()+1,2));
t = t.replace(/MM/g,this.getMonth()+1);
t = t.replace(/0WW/g,String.zeroPad(this.getWeek(),2));
t = t.replace(/WW/g,this.getWeek());
t = t.replace(/DDD/g,config.messages.dates.days[this.getDay()]);
t = t.replace(/ddd/g,config.messages.dates.shortDays[this.getDay()]);
t = t.replace(/0DD/g,String.zeroPad(this.getDate(),2));
t = t.replace(/DDth/g,this.getDate()+this.daySuffix());
t = t.replace(/DD/g,this.getDate());
return t;
};
Date.prototype.getWeek = function()
{
var dt = new Date(this.getTime());
var d = dt.getDay();
if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week to calculate weekNo
var n = Math.floor((dt.getTime()-new Date(dt.getFullYear(),0,1)+3600000)/86400000);
return Math.floor(n/7)+1;
};
Date.prototype.getYearForWeekNo = function()
{
var dt = new Date(this.getTime());
var d = dt.getDay();
if (d==0) d=7;// JavaScript Sun=0, ISO Sun=7
dt.setTime(dt.getTime()+(4-d)*86400000);// shift day to Thurs of same week
return dt.getFullYear();
};
Date.prototype.getHours12 = function()
{
var h = this.getHours();
return h > 12 ? h-12 : ( h > 0 ? h : 12 );
};
Date.prototype.getAmPm = function()
{
return this.getHours() >= 12 ? config.messages.dates.pm : config.messages.dates.am;
};
Date.prototype.daySuffix = function()
{
return config.messages.dates.daySuffixes[this.getDate()-1];
};
// Convert a date to local YYYYMMDDHHMM string format
Date.prototype.convertToLocalYYYYMMDDHHMM = function()
{
return String.zeroPad(this.getFullYear(),4) + String.zeroPad(this.getMonth()+1,2) + String.zeroPad(this.getDate(),2) + String.zeroPad(this.getHours(),2) + String.zeroPad(this.getMinutes(),2);
};
// Convert a date to UTC YYYYMMDDHHMM string format
Date.prototype.convertToYYYYMMDDHHMM = function()
{
return String.zeroPad(this.getUTCFullYear(),4) + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2);
};
// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format
Date.prototype.convertToYYYYMMDDHHMMSSMMM = function()
{
return String.zeroPad(this.getUTCFullYear(),4) + String.zeroPad(this.getUTCMonth()+1,2) + String.zeroPad(this.getUTCDate(),2) + "." + String.zeroPad(this.getUTCHours(),2) + String.zeroPad(this.getUTCMinutes(),2) + String.zeroPad(this.getUTCSeconds(),2) + String.zeroPad(this.getUTCMilliseconds(),4);
};
// Static method to create a date from a UTC YYYYMMDDHHMM format string
Date.convertFromYYYYMMDDHHMM = function(d)
{
return new Date(Date.UTC(parseInt(d.substr(0,4),10),
parseInt(d.substr(4,2),10)-1,
parseInt(d.substr(6,2),10),
parseInt(d.substr(8,2),10),
parseInt(d.substr(10,2),10),0,0));
};
//--
//-- Crypto functions and associated conversion routines
//--
// Crypto "namespace"
function Crypto() {}
// Convert a string to an array of big-endian 32-bit words
Crypto.strToBe32s = function(str)
{
var be = Array();
var len = Math.floor(str.length/4);
var i, j;
for(i=0, j=0; i<len; i++, j+=4) {
be[i] = ((str.charCodeAt(j)&0xff) << 24)|((str.charCodeAt(j+1)&0xff) << 16)|((str.charCodeAt(j+2)&0xff) << 8)|(str.charCodeAt(j+3)&0xff);
}
while (j<str.length) {
be[j>>2] |= (str.charCodeAt(j)&0xff)<<(24-(j*8)%32);
j++;
}
return be;
};
// Convert an array of big-endian 32-bit words to a string
Crypto.be32sToStr = function(be)
{
var str = "";
for(var i=0;i<be.length*32;i+=8)
str += String.fromCharCode((be[i>>5]>>>(24-i%32)) & 0xff);
return str;
};
// Convert an array of big-endian 32-bit words to a hex string
Crypto.be32sToHex = function(be)
{
var hex = "0123456789ABCDEF";
var str = "";
for(var i=0;i<be.length*4;i++)
str += hex.charAt((be[i>>2]>>((3-i%4)*8+4))&0xF) + hex.charAt((be[i>>2]>>((3-i%4)*8))&0xF);
return str;
};
// Return, in hex, the SHA-1 hash of a string
Crypto.hexSha1Str = function(str)
{
return Crypto.be32sToHex(Crypto.sha1Str(str));
};
// Return the SHA-1 hash of a string
Crypto.sha1Str = function(str)
{
return Crypto.sha1(Crypto.strToBe32s(str),str.length);
};
// Calculate the SHA-1 hash of an array of blen bytes of big-endian 32-bit words
Crypto.sha1 = function(x,blen)
{
// Add 32-bit integers, wrapping at 32 bits
add32 = function(a,b)
{
var lsw = (a&0xFFFF)+(b&0xFFFF);
var msw = (a>>16)+(b>>16)+(lsw>>16);
return (msw<<16)|(lsw&0xFFFF);
};
// Add five 32-bit integers, wrapping at 32 bits
add32x5 = function(a,b,c,d,e)
{
var lsw = (a&0xFFFF)+(b&0xFFFF)+(c&0xFFFF)+(d&0xFFFF)+(e&0xFFFF);
var msw = (a>>16)+(b>>16)+(c>>16)+(d>>16)+(e>>16)+(lsw>>16);
return (msw<<16)|(lsw&0xFFFF);
};
// Bitwise rotate left a 32-bit integer by 1 bit
rol32 = function(n)
{
return (n>>>31)|(n<<1);
};
var len = blen*8;
// Append padding so length in bits is 448 mod 512
x[len>>5] |= 0x80 << (24-len%32);
// Append length
x[((len+64>>9)<<4)+15] = len;
var w = Array(80);
var k1 = 0x5A827999;
var k2 = 0x6ED9EBA1;
var k3 = 0x8F1BBCDC;
var k4 = 0xCA62C1D6;
var h0 = 0x67452301;
var h1 = 0xEFCDAB89;
var h2 = 0x98BADCFE;
var h3 = 0x10325476;
var h4 = 0xC3D2E1F0;
for(var i=0;i<x.length;i+=16) {
var j,t;
var a = h0;
var b = h1;
var c = h2;
var d = h3;
var e = h4;
for(j = 0;j<16;j++) {
w[j] = x[i+j];
t = add32x5(e,(a>>>27)|(a<<5),d^(b&(c^d)),w[j],k1);
e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
}
for(j=16;j<20;j++) {
w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
t = add32x5(e,(a>>>27)|(a<<5),d^(b&(c^d)),w[j],k1);
e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
}
for(j=20;j<40;j++) {
w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
t = add32x5(e,(a>>>27)|(a<<5),b^c^d,w[j],k2);
e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
}
for(j=40;j<60;j++) {
w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
t = add32x5(e,(a>>>27)|(a<<5),(b&c)|(d&(b|c)),w[j],k3);
e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
}
for(j=60;j<80;j++) {
w[j] = rol32(w[j-3]^w[j-8]^w[j-14]^w[j-16]);
t = add32x5(e,(a>>>27)|(a<<5),b^c^d,w[j],k4);
e=d; d=c; c=(b>>>2)|(b<<30); b=a; a = t;
}
h0 = add32(h0,a);
h1 = add32(h1,b);
h2 = add32(h2,c);
h3 = add32(h3,d);
h4 = add32(h4,e);
}
return Array(h0,h1,h2,h3,h4);
};
//--
//-- RGB colour object
//--
// Construct an RGB colour object from a '#rrggbb', '#rgb' or 'rgb(n,n,n)' string or from separate r,g,b values
function RGB(r,g,b)
{
this.r = 0;
this.g = 0;
this.b = 0;
if(typeof r == "string") {
if(r.substr(0,1) == "#") {
if(r.length == 7) {
this.r = parseInt(r.substr(1,2),16)/255;
this.g = parseInt(r.substr(3,2),16)/255;
this.b = parseInt(r.substr(5,2),16)/255;
} else {
this.r = parseInt(r.substr(1,1),16)/15;
this.g = parseInt(r.substr(2,1),16)/15;
this.b = parseInt(r.substr(3,1),16)/15;
}
} else {
var rgbPattern = /rgb\s*\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/;
var c = r.match(rgbPattern);
if(c) {
this.r = parseInt(c[1],10)/255;
this.g = parseInt(c[2],10)/255;
this.b = parseInt(c[3],10)/255;
}
}
} else {
this.r = r;
this.g = g;
this.b = b;
}
return this;
}
// Mixes this colour with another in a specified proportion
// c = other colour to mix
// f = 0..1 where 0 is this colour and 1 is the new colour
// Returns an RGB object
RGB.prototype.mix = function(c,f)
{
return new RGB(this.r + (c.r-this.r) * f,this.g + (c.g-this.g) * f,this.b + (c.b-this.b) * f);
};
// Return an rgb colour as a #rrggbb format hex string
RGB.prototype.toString = function()
{
return "#" + ("0" + Math.floor(this.r.clamp(0,1) * 255).toString(16)).right(2) +
("0" + Math.floor(this.g.clamp(0,1) * 255).toString(16)).right(2) +
("0" + Math.floor(this.b.clamp(0,1) * 255).toString(16)).right(2);
};
//--
//-- DOM utilities - many derived from www.quirksmode.org
//--
function drawGradient(place,horiz,colours)
{
for(var t=0; t<= 100; t+=2) {
var bar = document.createElement("div");
place.appendChild(bar);
bar.style.position = "absolute";
bar.style.left = horiz ? t + "%" : 0;
bar.style.top = horiz ? 0 : t + "%";
bar.style.width = horiz ? (101-t) + "%" : "100%";
bar.style.height = horiz ? "100%" : (101-t) + "%";
bar.style.zIndex = -1;
var f = t/100;
var p = f*(colours.length-1);
bar.style.backgroundColor = colours[Math.floor(p)].mix(colours[Math.ceil(p)],p-Math.floor(p)).toString();
}
}
function createTiddlyText(theParent,theText)
{
return theParent.appendChild(document.createTextNode(theText));
}
function createTiddlyCheckbox(theParent,caption,checked,onChange)
{
var cb = document.createElement("input");
cb.setAttribute("type","checkbox");
cb.onclick = onChange;
theParent.appendChild(cb);
cb.checked = checked;
cb.className = "chkOptionInput";
if(caption)
wikify(caption,theParent);
return cb;
}
function createTiddlyElement(theParent,theElement,theID,theClass,theText)
{
var e = document.createElement(theElement);
if(theClass != null)
e.className = theClass;
if(theID != null)
e.setAttribute("id",theID);
if(theText != null)
e.appendChild(document.createTextNode(theText));
if(theParent != null)
theParent.appendChild(e);
return e;
}
function addEvent(obj,type,fn)
{
if(obj.attachEvent) {
obj['e'+type+fn] = fn;
obj[type+fn] = function(){obj['e'+type+fn](window.event);};
obj.attachEvent('on'+type,obj[type+fn]);
} else {
obj.addEventListener(type,fn,false);
}
}
function removeEvent(obj,type,fn)
{
if(obj.detachEvent) {
obj.detachEvent('on'+type,obj[type+fn]);
obj[type+fn] = null;
} else {
obj.removeEventListener(type,fn,false);
}
}
function addClass(e,theClass)
{
var currClass = e.className.split(" ");
if(currClass.indexOf(theClass) == -1)
e.className += " " + theClass;
}
function removeClass(e,theClass)
{
var currClass = e.className.split(" ");
var i = currClass.indexOf(theClass);
while(i != -1) {
currClass.splice(i,1);
i = currClass.indexOf(theClass);
}
e.className = currClass.join(" ");
}
function hasClass(e,theClass)
{
if(e.className) {
if(e.className.split(" ").indexOf(theClass) != -1)
return true;
}
return false;
}
// Find the closest relative with a given property value (property defaults to tagName, relative defaults to parentNode)
function findRelated(e,value,name,relative)
{
name = name ? name : "tagName";
relative = relative ? relative : "parentNode";
if(name == "className") {
while(e && !hasClass(e,value)) {
e = e[relative];
}
} else {
while(e && e[name] != value) {
e = e[relative];
}
}
return e;
}
// Resolve the target object of an event
function resolveTarget(e)
{
var obj;
if(e.target)
obj = e.target;
else if(e.srcElement)
obj = e.srcElement;
if(obj.nodeType == 3) // defeat Safari bug
obj = obj.parentNode;
return obj;
}
// Return the content of an element as plain text with no formatting
function getPlainText(e)
{
var text = "";
if(e.innerText)
text = e.innerText;
else if(e.textContent)
text = e.textContent;
return text;
}
// Get the scroll position for window.scrollTo necessary to scroll a given element into view
function ensureVisible(e)
{
var posTop = findPosY(e);
var posBot = posTop + e.offsetHeight;
var winTop = findScrollY();
var winHeight = findWindowHeight();
var winBot = winTop + winHeight;
if(posTop < winTop) {
return posTop;
} else if(posBot > winBot) {
if(e.offsetHeight < winHeight)
return posTop - (winHeight - e.offsetHeight);
else
return posTop;
} else {
return winTop;
}
}
// Get the current width of the display window
function findWindowWidth()
{
return window.innerWidth ? window.innerWidth : document.documentElement.clientWidth;
}
// Get the current height of the display window
function findWindowHeight()
{
return window.innerHeight ? window.innerHeight : document.documentElement.clientHeight;
}
// Get the current horizontal page scroll position
function findScrollX()
{
return window.scrollX ? window.scrollX : document.documentElement.scrollLeft;
}
// Get the current vertical page scroll position
function findScrollY()
{
return window.scrollY ? window.scrollY : document.documentElement.scrollTop;
}
function findPosX(obj)
{
var curleft = 0;
while(obj.offsetParent) {
curleft += obj.offsetLeft;
obj = obj.offsetParent;
}
return curleft;
}
function findPosY(obj)
{
var curtop = 0;
while(obj.offsetParent) {
curtop += obj.offsetTop;
obj = obj.offsetParent;
}
return curtop;
}
// Blur a particular element
function blurElement(e)
{
if(e != null && e.focus && e.blur) {
e.focus();
e.blur();
}
}
// Create a non-breaking space
function insertSpacer(place)
{
var e = document.createTextNode(String.fromCharCode(160));
if(place)
place.appendChild(e);
return e;
}
// Remove all children of a node
function removeChildren(e)
{
while(e && e.hasChildNodes())
removeNode(e.firstChild);
}
// Remove a node and all it's children
function removeNode(e)
{
scrubNode(e);
e.parentNode.removeChild(e);
}
// Remove any event handlers or non-primitve custom attributes
function scrubNode(e)
{
var att = e.attributes;
if(att) {
for(var t=0; t<att.length; t++) {
var n = att[t].name;
if(n !== 'style' && (typeof e[n] === 'function' || (typeof e[n] === 'object' && e[n] != null))) {
try {
e[n] = null;
} catch(ex) {
}
}
}
}
var c = e.firstChild;
while(c) {
scrubNode(c);
c = c.nextSibling;
}
}
// Add a stylesheet, replacing any previous custom stylesheet
function setStylesheet(s,id,doc)
{
if(!id)
id = "customStyleSheet";
if(!doc)
doc = document;
var n = doc.getElementById(id);
if(doc.createStyleSheet) {
// Test for IE's non-standard createStyleSheet method
if(n)
n.parentNode.removeChild(n);
// This failed without the &nbsp;
doc.getElementsByTagName("head")[0].insertAdjacentHTML("beforeEnd","&nbsp;<style id='" + id + "'>" + s + "</style>");
} else {
if(n) {
n.replaceChild(doc.createTextNode(s),n.firstChild);
} else {
n = doc.createElement("style");
n.type = "text/css";
n.id = id;
n.appendChild(doc.createTextNode(s));
doc.getElementsByTagName("head")[0].appendChild(n);
}
}
}
// Force the browser to do a document reflow when needed to workaround browser bugs
function forceReflow()
{
if(config.browser.isGecko) {
setStylesheet("body {top:-1em;margin-top:1em;}");
setStylesheet("");
}
}
// Replace the current selection of a textarea or text input and scroll it into view
function replaceSelection(e,text)
{
if(e.setSelectionRange) {
var oldpos = e.selectionStart;
var isRange = e.selectionEnd > e.selectionStart;
e.value = e.value.substr(0,e.selectionStart) + text + e.value.substr(e.selectionEnd);
e.setSelectionRange(isRange ? oldpos : oldpos + text.length,oldpos + text.length);
var linecount = e.value.split('\n').length;
var thisline = e.value.substr(0,e.selectionStart).split('\n').length-1;
e.scrollTop = Math.floor((thisline - e.rows / 2) * e.scrollHeight / linecount);
} else if(document.selection) {
var range = document.selection.createRange();
if(range.parentElement() == e) {
var isCollapsed = range.text == "";
range.text = text;
if(!isCollapsed) {
range.moveStart('character', -text.length);
range.select();
}
}
}
}
// Returns the text of the given (text) node, possibly merging subsequent text nodes
function getNodeText(e)
{
var t = "";
while(e && e.nodeName == "#text") {
t += e.nodeValue;
e = e.nextSibling;
}
return t;
}
//--
//-- LoaderBase and SaverBase
//--
function LoaderBase() {}
LoaderBase.prototype.loadTiddler = function(store,node,tiddlers)
{
var title = this.getTitle(store,node);
if(title) {
var tiddler = store.createTiddler(title);
this.internalizeTiddler(store,tiddler,title,node);
tiddlers.push(tiddler);
}
};
LoaderBase.prototype.loadTiddlers = function(store,nodes)
{
var tiddlers = [];
for(var t = 0; t < nodes.length; t++) {
try {
this.loadTiddler(store,nodes[t],tiddlers);
} catch(ex) {
showException(ex,config.messages.tiddlerLoadError.format([this.getTitle(store,nodes[t])]));
}
}
return tiddlers;
};
function SaverBase() {}
SaverBase.prototype.externalize = function(store)
{
var results = [];
var tiddlers = store.getTiddlers("title");
for(var t = 0; t < tiddlers.length; t++)
results.push(this.externalizeTiddler(store,tiddlers[t]));
return results.join("\n");
};
//--
//-- TW21Loader (inherits from LoaderBase)
//--
function TW21Loader() {}
TW21Loader.prototype = new LoaderBase();
TW21Loader.prototype.getTitle = function(store,node)
{
var title = null;
if(node.getAttribute) {
title = node.getAttribute("title");
if(!title)
title = node.getAttribute("tiddler");
}
if(!title && node.id) {
var lenPrefix = store.idPrefix.length;
if (node.id.substr(0,lenPrefix) == store.idPrefix)
title = node.id.substr(lenPrefix);
}
return title;
};
TW21Loader.prototype.internalizeTiddler = function(store,tiddler,title,node)
{
var e = node.firstChild;
var text = null;
if(node.getAttribute("tiddler")) {
text = getNodeText(e).unescapeLineBreaks();
} else {
while(e.nodeName!="PRE" && e.nodeName!="pre") {
e = e.nextSibling;
}
text = e.innerHTML.replace(/\r/mg,"").htmlDecode();
}
var modifier = node.getAttribute("modifier");
var c = node.getAttribute("created");
var m = node.getAttribute("modified");
var created = c ? Date.convertFromYYYYMMDDHHMM(c) : version.date;
var modified = m ? Date.convertFromYYYYMMDDHHMM(m) : created;
var tags = node.getAttribute("tags");
var fields = {};
var attrs = node.attributes;
for(var i = attrs.length-1; i >= 0; i--) {
var name = attrs[i].name;
if (attrs[i].specified && !TiddlyWiki.isStandardField(name)) {
fields[name] = attrs[i].value.unescapeLineBreaks();
}
}
tiddler.assign(title,text,modifier,modified,tags,created,fields);
return tiddler;
};
//--
//-- TW21Saver (inherits from SaverBase)
//--
function TW21Saver() {}
TW21Saver.prototype = new SaverBase();
TW21Saver.prototype.externalizeTiddler = function(store,tiddler)
{
try {
var extendedAttributes = "";
var usePre = config.options.chkUsePreForStorage;
store.forEachField(tiddler,
function(tiddler,fieldName,value) {
// don't store stuff from the temp namespace
if(typeof value != "string")
value = "";
if (!fieldName.match(/^temp\./))
extendedAttributes += ' %0="%1"'.format([fieldName,value.escapeLineBreaks().htmlEncode()]);
},true);
var created = tiddler.created.convertToYYYYMMDDHHMM();
var modified = tiddler.modified.convertToYYYYMMDDHHMM();
var vdate = version.date.convertToYYYYMMDDHHMM();
var attributes = tiddler.modifier ? ' modifier="' + tiddler.modifier.htmlEncode() + '"' : "";
attributes += (usePre && modified == created) ? "" : ' modified="' + modified +'"';
attributes += (usePre && created == vdate) ? "" :' created="' + created + '"';
var tags = tiddler.getTags();
if(!usePre || tags)
attributes += ' tags="' + tags.htmlEncode() + '"';
return ('<div %0="%1"%2%3>%4</'+'div>').format([
usePre ? "title" : "tiddler",
tiddler.title.htmlEncode(),
attributes,
extendedAttributes,
usePre ? "\n<pre>" + tiddler.text.htmlEncode() + "</pre>\n" : tiddler.text.escapeLineBreaks().htmlEncode()
]);
} catch (ex) {
throw exceptionText(ex,config.messages.tiddlerSaveError.format([tiddler.title]));
}
};
//--
//-- Deprecated code
//--
// @Deprecated: Use createElementAndWikify and this.termRegExp instead
config.formatterHelpers.charFormatHelper = function(w)
{
w.subWikify(createTiddlyElement(w.output,this.element),this.terminator);
};
// @Deprecated: Use enclosedTextHelper and this.lookaheadRegExp instead
config.formatterHelpers.monospacedByLineHelper = function(w)
{
var lookaheadRegExp = new RegExp(this.lookahead,"mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var text = lookaheadMatch[1];
if(config.browser.isIE)
text = text.replace(/\n/g,"\r");
createTiddlyElement(w.output,"pre",null,null,text);
w.nextMatch = lookaheadRegExp.lastIndex;
}
};
// @Deprecated: Use <br> or <br /> instead of <<br>>
config.macros.br.handler = function(place)
{
createTiddlyElement(place,"br");
};
// Find an entry in an array. Returns the array index or null
// @Deprecated: Use indexOf instead
Array.prototype.find = function(item)
{
var i = this.indexOf(item);
return i == -1 ? null : i;
};
// Load a tiddler from an HTML DIV. The caller should make sure to later call Tiddler.changed()
// @Deprecated: Use store.getLoader().internalizeTiddler instead
Tiddler.prototype.loadFromDiv = function(divRef,title)
{
return store.getLoader().internalizeTiddler(store,this,title,divRef);
};
// Format the text for storage in an HTML DIV
// @Deprecated Use store.getSaver().externalizeTiddler instead.
Tiddler.prototype.saveToDiv = function()
{
return store.getSaver().externalizeTiddler(store,this);
};
// @Deprecated: Use store.allTiddlersAsHtml() instead
function allTiddlersAsHtml()
{
return store.allTiddlersAsHtml();
}
// @Deprecated: Use refreshPageTemplate instead
function applyPageTemplate(title)
{
refreshPageTemplate(title);
}
// @Deprecated: Use story.displayTiddlers instead
function displayTiddlers(srcElement,titles,template,unused1,unused2,animate,unused3)
{
story.displayTiddlers(srcElement,titles,template,animate);
}
// @Deprecated: Use story.displayTiddler instead
function displayTiddler(srcElement,title,template,unused1,unused2,animate,unused3)
{
story.displayTiddler(srcElement,title,template,animate);
}
// @Deprecated: Use functions on right hand side directly instead
var createTiddlerPopup = Popup.create;
var scrollToTiddlerPopup = Popup.show;
var hideTiddlerPopup = Popup.remove;
// @Deprecated: Use right hand side directly instead
var regexpBackSlashEn = new RegExp("\\\\n","mg");
var regexpBackSlash = new RegExp("\\\\","mg");
var regexpBackSlashEss = new RegExp("\\\\s","mg");
var regexpNewLine = new RegExp("\n","mg");
var regexpCarriageReturn = new RegExp("\r","mg");
//--
//-- End of scripts
//--
//]]>
</script>
<script type="text/javascript">
//<![CDATA[
if(useJavaSaver)
document.write("<applet style='position:absolute;left:-1px' name='TiddlySaver' code='TiddlySaver.class' archive='TiddlySaver.jar' width='1' height='1'></applet>");
//]]>
</script>
<!--POST-SCRIPT-START-->
<!--POST-SCRIPT-END-->
</body>
</html>