Thursday, March 12, 2015
Creating More Responsive Applications with Client Handlers and Validators
When it comes to writing UI applications in Apps Script, we get a lot of requests to support event callbacks that are handled in the user’s browser. For example, if your application has a form, you may want to disable a button after it is clicked the first time. Until now, the only way to do that would be by using an event handler on the server to disable that button. Using Client Handlers, your application can now respond to events in the browser without the need to perform a round trip to Google Apps Script servers.
By cutting out the round trip to the server, your app can respond instantly to user input. Imagine, for example, you want to provide your users with instant feedback within your app when a user types text where a number is expected. Ideally, you would want to warn users as they type the value, instead of waiting until the form is submitted. Having a server event handler for each keystroke is definitely overkill for such a simple and common task. Luckily, these use cases are now supported with Apps Script’s new Client Handlers and validators!
Let’s take a look at some code.
Client Handlers
A Client Handler allows you to react to any event in a browser without connecting to the server. What you can do in response to an event is limited to a set of predefined common actions, but you have a lot of flexibility in making your app more responsive.
You can use Client Handlers in any UiApp regardless of whether you are embedding in a Spreadsheet or a Sites Page or publishing as a service. This simple application enables the user to click a button to display the classic “Hello world” message:
function doGet() {
var app = UiApp.createApplication();
var button = app.createButton("Say Hello");
// Create a label with the "Hello World!" text and hide it for now
var label = app.createLabel("Hello World!").setVisible(false);
// Create a new handler that does not require the server.
// We give the handler two actions to perform on different targets.
// The first action disables the widget that invokes the handler
// and the second displays the label.
var handler = app.createClientHandler()
.forEventSource().setEnabled(false)
.forTargets(label).setVisible(true);
// Add our new handler to be invoked when the button is clicked
button.addClickHandler(handler);
app.add(button);
app.add(label);
return app;
} The Client Handlers in the above example are set up in two steps:
- Create a Client Handler just as we would create the server handlers you all know and love.
- Define the target widget for this handler. The target widget is the widget on which the handler will take action. We set the handler’s target in one of two ways: (a) By using the
forTargetsmethod to define the target widget. (b) By using theforEventSourcemethod which lets widget wire itself to the client handler.
In the above example, we set the handler’s target to be the event source, so that it will apply to the button that is clicked. Finally, we define the action that the handler should take, in this case disabling the button using setEnabled(false). Aside from setEnabled, you can also change styles using setStyleAttribute, change text using setText, and so on. One Client Handler can perform multiple actions — just chain them together - and you can even change the target so that some actions apply to one set of widgets and some actions to another set. In our example, along with disabling the button, we set the handler to display the label when it is invoked, using setVisible.
Validators
Another new addition to Apps Script is support for validators in handlers. Validators allow handlers to check simple and complex conditions before they are invoked. For example, the following application adds two numbers given by the user, while using validators to make sure the server is only called if both of the text boxes contain numbers.
function doGet() {
var app = UiApp.createApplication();
// Create input boxes and button
var textBoxA = app.createTextBox().setId(textBoxA).setName(textBoxA);
var textBoxB = app.createTextBox().setId(textBoxB).setName(textBoxB);
var addButton = app.createButton("Add");
// Create a handler to call the adding function
// Two validations are added to this handler so that it will
// only invoke add if both textBoxA and textBoxB contain
// numbers
var handler = app.createServerClickHandler(add)
.validateNumber(textBoxA)
.validateNumber(textBoxB)
.addCallbackElement(textBoxA)
.addCallbackElement(textBoxB);
addButton.addClickHandler(handler)
app.add(textBoxA);
app.add(textBoxB);
app.add(addButton);
return app;
}
function add(e) {
var app = UiApp.getActiveApplication();
var result = parseFloat(e.parameter.textBoxA) + parseFloat(e.parameter.textBoxB);
var newResultLabel = app.createLabel("Result is: " + result);
app.add(newResultLabel);
return app;
} There’s a variety of validators to choose from that perform different tasks. You can verify the input to be a number, an integer, or an e-mail address. You can check for a specific length, or for any numerical value in a defined range. You can also use general regular expressions. Lastly, each validator has its negation.
Note that validators work with both client and server handlers.
Putting it all together
Of course, validators and Client Handlers work best together. For example, in our addition application above, the “Add” button should be disabled as long as the current input is not numeric. We would also like to let the user know why the button is disabled by displaying an error message. To do so, we combine the power of server handlers, Client Handlers, and validators in the following way:
function doGet() {
var app = UiApp.createApplication();
// Create input boxes and button.
var textBoxA = app.createTextBox().setId(textBoxA).setName(textBoxA);
var textBoxB = app.createTextBox().setId(textBoxB).setName(textBoxB);
var addButton = app.createButton("Add").setEnabled(false);
var label = app.createLabel("Please input two numbers");
// Create a handler to call the adding function.
// Two validations are added to this handler so that it will
// only invoke add if both textBoxA and textBoxB contain
// numbers.
var handler = app.createServerClickHandler(add)
.validateNumber(textBoxA)
.validateNumber(textBoxB)
.addCallbackElement(textBoxA)
.addCallbackElement(textBoxB);
// Create handler to enable the button well all input is legal
var onValidInput = app.createClientHandler()
.validateNumber(textBoxA)
.validateNumber(textBoxB)
.forTargets(addButton).setEnabled(true)
.forTargets(label).setVisible(false);
// Create handler to mark invalid input in textBoxA and disable the button
var onInvalidInput1 = app.createClientHandler()
.validateNotNumber(textBoxA)
.forTargets(addButton).setEnabled(false)
.forTargets(textBoxA).setStyleAttribute("color", "red")
.forTargets(label).setVisible(true);
// Create handler to mark the input in textBoxA as valid
var onValidInput1 = app.createClientHandler()
.validateNumber(textBoxA)
.forTargets(textBoxA).setStyleAttribute("color", "black");
// Create handler to mark invalid input in textBoxB and disable the button
var onInvalidInput2 = app.createClientHandler()
.validateNotNumber(textBoxB)
.forTargets(addButton).setEnabled(false)
.forTargets(textBoxB).setStyleAttribute("color", "red")
.forTargets(label).setVisible(true);
// Create handler to mark the input in textBoxB as valid
var onValidInput2 = app.createClientHandler()
.validateNumber(textBoxB)
.forTargets(textBoxB).setStyleAttribute("color", "black");
// Add all the handlers to be called when the user types in the text boxes
textBoxA.addKeyUpHandler(onInvalidInput1);
textBoxB.addKeyUpHandler(onInvalidInput2);
textBoxA.addKeyUpHandler(onValidInput1);
textBoxB.addKeyUpHandler(onValidInput2);
textBoxA.addKeyUpHandler(onValidInput);
textBoxB.addKeyUpHandler(onValidInput);
addButton.addClickHandler(handler);
app.add(textBoxA);
app.add(textBoxB);
app.add(addButton);
app.add(label);
return app;
}
function add(e) {
var app = UiApp.getActiveApplication();
var result = parseFloat(e.parameter.textBoxA) + parseFloat(e.parameter.textBoxB);
var newResultLabel = app.createLabel("Result is: " + result);
app.add(newResultLabel);
return app;
} All of these features can be used to create more advanced and responsive applications. Client handlers can be used to change several attributes for widgets, and validators can help you check a variety of different conditions from well formed email addresses to general regular expressions.
If youd like to chat about these new features or have other questions about Google Apps Script, please join several members of the Apps Script team in the Google Apps Developer Office Hours on Google+ Hangouts tomorrow, Wednesday November 16th at 10am PST. You can also ask questions at any time in the Apps Script forum.
![]() | Omer Strulovich profile Omer was an intern on the Google Docs team for the summer of 2011. He is now back to pursuing his master’s degree in the field of cryptography. |
Thursday, February 5, 2015
Creating a Flex AIR text editor Part 46
Find the snippetTree object and set its change property to a snippetTreeChange() event and pass the event object as the parameter:
<mx:Tree height="100%" width="100%" id="snippetTree" showRoot="false" labelField="@label" dataProvider="{snippetXML}" change="snippetTreeChange(event);" />
The function checks if the isBranch attribute of the object is false, if it is, then insert the snippet into textArea using insertText() method and deselect the item from the tree by setting the selectedIndex property to -1.
private function snippetTreeChange(evt:Event):void {
if (evt.currentTarget.selectedItem.@isBranch == false) {
insertText(evt.currentTarget.selectedItem.@snippetText);
evt.currentTarget.selectedIndex = -1;
}
}
Well also need to edit the onSelected function to set the isBranch attribute of non-category nodes too. Set them to false:
private function onSelected(evt:SQLResult):void {
var data:Array = evt.data;
// Temporary:
data = [
{id:0, snippetName:"Snip1", snippetText:"Test1", categoryName:"Cat1" },
{id:1, snippetName:"Snip2", snippetText:"Test2", categoryName:"Cat2" },
{id:2, snippetName:"Snip3", snippetText:"Test3", categoryName:"Cat1" },
{id:3, snippetName:"Snip4", snippetText:"Test4", categoryName:"Cat2" },
{id:4, snippetName:"Snip5", snippetText:"Test5", categoryName:"Cat1" },
{id:5, snippetName:"Snip6", snippetText:"Test6", categoryName:"Cat2" } ]
// end temporary
var categories:Array = [];
snippetXML = new XMLList(<root></root>);
if(data){
for (var i:int = 0; i < data.length; i++) {
// If category is not unique, add the item to the category
if (isUnique(i)) {
// If it is unique, create the category and add the item
var newCategory:XML = <category isBranch="true"/>;
newCategory.@label = data[i].categoryName;
var newUniqueNode:XML = <snippet/>;
newUniqueNode.@label = data[i].snippetName;
newUniqueNode.@snippetText = data[i].snippetText;
newUniqueNode.@id = data[i].id;
newUniqueNode.@isBranch = false;
newCategory.appendChild(newUniqueNode);
snippetXML[0].appendChild(newCategory);
categories.push(data[i].categoryName);
}
}
}
function isUnique(ind:int):Boolean {
var isU:Boolean = true;
for (var u:int = 0; u < categories.length; u++) {
if (data[ind].categoryName == categories[u]) {
isU = false;
var newNode:XML = <snippet/>;
newNode.@label = data[ind].snippetName;
newNode.@snippetText = data[ind].snippetText;
newNode.@id = data[ind].id;
newNode.@isBranch = false;
snippetXML[0].category[u].appendChild(newNode);
break;
}
}
return isU;
}
}
Now we will create a class similar to FontWindow, but call it SnippetWindow instead. Create an instance of this class caleld snippetWindow:
public var snippetWindow:SnippetWindow = new SnippetWindow();
Now we can create a doSnippet() method which will be called to open/reopen the window:
private function doSnippet(createNew:Boolean = false, newText:String = ""):void{
snippetWindow.open();
snippetWindow.activate();
snippetWindow.visible = true;
snippetWindow.setValues(snippetXML, createNew, newText);
}
As you can probably notice, the contents are similar to doFonts() function, which does basically the same but with the fontWindow object. We also use its setValues() function to set certain values, but this time we only pass 3 parameters - the XMLList object, a boolean determining if we need to create a new snippet after opening this window and the text for the new snippet. The last two parameters get passed to the doSnippet() function itself, and they are optional.
Now we call this function two times in our code - on the New snippet and on the Manage snippets buttons:
<s:Button width="100%" label="New snippet" click="doSnippet(true, textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition));" />
<s:Button width="100%" label="Manage snippets" click="doSnippet();" />
When we just want to open the window, we simply call this function. When we want to create a new snippet and put the text that the user has selected before pressing the button, we pass a true value and a string value of the text selection.
Now lets create the SnippetWindow class. Create a new file called SnippetWindow.mxml.
I am going to copy-paste the contents from FontWindow here and just edit it. I am going to get rid of everything font-related, and just leave the basic stuff. I am going to have the same 4 functions I had - init, onCLose, setValues and doApply. I dont even need to change any of these functions, except for setValues. Then Im going to remove all the contents and replace them with snippet bassed settings.
We nede to edit the setValues() function to handle the parameters and set the text of newNameInput and newTextInput text fields.
Heres how the class looks like in the end:
<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
title="Snippet management" type="utility" width="500" height="700"
creationComplete="init();" showStatusBar="false" alwaysInFront="true" resizable="false">
<fx:Script>
<![CDATA[
import flash.events.Event;
[Bindable]
private var treeData:XMLList;
private function init():void{
this.addEventListener(Event.CLOSING, onClose);
}
private function onClose(evt:Event):void {
this.visible = false;
evt.preventDefault();
}
public function setValues(xmlData:XMLList, createNewOnStart:Boolean = false, newText:String = ""):void {
treeData = xmlData;
newTextInput.text = newText;
newNameInput.text = "New snippet name here";
if (newText=="") {
newTextInput.text = "New snippet text here";
}
}
private function doApply():void{
dispatchEvent( new Event( Event.CHANGE ) );
this.visible = false;
}
]]>
</fx:Script>
<s:VGroup paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5">
<s:Label text="Snippet creation and management: " />
<s:TextInput id="newNameInput" text="New snippet name" width="490" />
<s:TextArea id="newTextInput" width="490" height="200" text="Snippet text" />
<s:HGroup>
<s:Button label="Add snippet" />
<s:Button label="Save changes" />
<s:Button label="Delete snippet" />
</s:HGroup>
<s:Label text="" />
<s:Label text="Category creation and management: " />
<s:TextInput id="newCategoryInput" text="New category name" width="490" />
<s:HGroup>
<s:Button label="Add category" />
<s:Button label="Save changes" />
<s:Button label="Delete category with its contents" />
</s:HGroup>
<s:Label text="" />
<mx:Tree id="snippetManageTree" dataProvider="{treeData}" width="490" height="300" showRoot="false" labelField="@label"/>
</s:VGroup>
</mx:Window>
The window can now be opened, closed, reopened. We can use the New snippet button to open this window with the text piece that weve selected in the text area and have it in the text input of the snippet already.
Thats all for now.
Heres the code for the main file:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:custom="*"
creationComplete="init();" title="Kirpad" showStatusBar="{pref_status}"
minWidth="400" minHeight="200" height="700" width="900">
<s:menu>
<mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" />
</s:menu>
<fx:Script>
<![CDATA[
import flash.data.SQLConnection;
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.net.SharedObject;
import flashx.textLayout.accessibility.TextAccImpl;
import flashx.textLayout.edit.EditManager;
import flashx.textLayout.edit.TextScrap;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.controls.TextArea;
import mx.events.FlexNativeMenuEvent;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.Configuration;
import flash.system.System;
import flash.desktop.Clipboard;
import flash.desktop.ClipboardFormats;
import flash.ui.Mouse;
import mx.events.CloseEvent;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.events.ContextMenuEvent;
import mx.events.ResizeEvent;
import mx.core.FlexGlobals;
import mx.printing.FlexPrintJob;
import mx.printing.FlexPrintJobScaleType;
import flashx.undo.UndoManager;
import flashx.textLayout.operations.UndoOperation;
import flash.data.SQLConnection;
import flash.events.SQLEvent;
import flash.data.SQLStatement;
import flash.data.SQLResult;
import flash.data.SQLMode;
import flash.net.Responder;
import XML;
import XMLList;
private var preferences:SharedObject = SharedObject.getLocal("kirpadPreferences");
[Bindable]
private var pref_wrap:Boolean = true;
[Bindable]
private var pref_status:Boolean = true;
[Bindable]
private var pref_toolbar:Boolean = true;
[Bindable]
private var pref_sidepane:Boolean = true;
[Bindable]
private var pref_linecount:Boolean = true;
[Bindable]
public var pref_fontsettings:Object = new Object();
private var initHeight:Number;
private var heightFixed:Boolean = false;
private var statusMessage:String;
[Bindable]
private var textHeight:Number;
[Bindable]
private var textWidth:Number;
[Bindable]
private var textY:Number;
[Bindable]
private var textX:Number;
[Bindable]
private var tabY:Number;
[Bindable]
private var sidePaneY:Number;
[Bindable]
private var sidePaneX:Number;
[Bindable]
private var sidePaneHeight:Number;
[Bindable]
private var sidePaneWidth:Number = 180;
[Bindable]
private var sideContentWidth:Number = 170;
[Bindable]
private var tabWidth:Number;
[Bindable]
private var lineCountWidth:Number = 40;
[Bindable]
private var lineNumbers:String = "1";
[Bindable]
private var lineDisplayedNum:int = 1;
[Bindable]
private var tabSelectedIndex:int = 0;
[Bindable]
private var canUndo:Boolean = false;
[Bindable]
private var canRedo:Boolean = false;
private var previousTextInOperation:String = "";
private var currentTextInOperation:String = "";
private var previousIndex:int = 0;
private var rightclickTabIndex:int = 0;
private var untitledNum:int = 0;
private var tabsToClose:int = 0;
private var closeAfterConfirm:Boolean = false;
public var fontWindow:FontWindow = new FontWindow();
public var snippetWindow:SnippetWindow = new SnippetWindow();
private var undoManager:UndoManager;
private var editManager:EditManager;
private var saveWait:Boolean = false;
private var saveAsQueue:Array = [];
private var connection:SQLConnection;
[Bindable]
private var snippetXML:XMLList;
private function init():void {
// Create a listener for every frame
addEventListener(Event.ENTER_FRAME, everyFrame);
// Set initHeight to the initial height value on start
initHeight = height;
// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = false;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = true;
preferences.data.linecount = true;
preferences.data.fontsettings = {fontfamily:"Lucida Console", fontsize:14, fontstyle:"normal", fontweight:"normal", fontcolor:0x000000, bgcolor:0xffffff};
preferences.flush();
}
// Set preferences loaded from local storage
pref_wrap = preferences.data.wrap;
pref_status = preferences.data.status;
pref_toolbar = preferences.data.toolbar;
pref_sidepane = preferences.data.sidepane;
pref_fontsettings = preferences.data.fontsettings;
pref_linecount = preferences.data.linecount;
// Allow insertion of tabs
var textFlow:TextFlow = textArea.textFlow;
var config:Configuration = Configuration(textFlow.configuration);
config.manageTabKey = true;
// Set status message
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Kirpad initialized";
updateStatus();
// Close all sub-windows if main window is closed
addEventListener(Event.CLOSING, onClose);
// Add listener for the event that is dispatched when new font settings are applied
fontWindow.addEventListener(Event.CHANGE, fontChange);
// Update real fonts with the data from the settings values
updateFonts();
// Create a listener for resizing
addEventListener(NativeWindowBoundsEvent.RESIZE, onResize);
// Context menu declaration for the tabbar control
var cm_close:ContextMenuItem = new ContextMenuItem("Close tab");
cm_close.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextClose);
var cm_closeother:ContextMenuItem = new ContextMenuItem("Close other tabs");
cm_closeother.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextCloseOther);
var cm_save:ContextMenuItem = new ContextMenuItem("Save");
cm_save.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextSave);
var cm_duplicate:ContextMenuItem = new ContextMenuItem("Duplicate");
cm_duplicate.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextDuplicate);
var cm:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother, cm_save, cm_duplicate];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);
// Context menu declaration for the tab management list control
sideList.contextMenu = cm;
sideList.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, listRightClick);
// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
// Undo management
undoManager = new UndoManager();
editManager = new EditManager(undoManager);
textArea.textFlow.interactionManager = editManager;
// Select first tab
tabChange();
// Database management
var dbFile:File = File.applicationStorageDirectory.resolvePath("database.db");
connection = new SQLConnection();
connection.addEventListener(SQLEvent.OPEN, onOpen);
connection.openAsync(dbFile, SQLMode.CREATE);
}
private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(void);
(evt.item.@label == "Open")?(doOpen()):(void);
(evt.item.@label == "Save")?(doSave(tabSelectedIndex)):(void);
(evt.item.@label == "Save As")?(doSaveAs(textArea.text, tabSelectedIndex)):(void);
(evt.item.@label == "Save All")?(doSaveAll()):(void);
(evt.item.@label == "Word wrap")?(pref_wrap = !pref_wrap):(void);
(evt.item.@label == "Cut")?(doCut()):(void);
(evt.item.@label == "Copy")?(doCopy()):(void);
(evt.item.@label == "Paste")?(doPaste()):(void);
(evt.item.@label == "Select all")?(doSelectall()):(void);
(evt.item.@label == "Status bar")?(pref_status = !pref_status):(void);
(evt.item.@label == "Tool bar")?(pref_toolbar = !pref_toolbar):(void);
(evt.item.@label == "Side pane")?(pref_sidepane = !pref_sidepane):(void);
(evt.item.@label == "Line count")?(pref_linecount = !pref_linecount):(void);
(evt.item.@label == "Font...")?(doFont()):(void);
(evt.item.@label == "Print")?(doPrint()):(void);
(evt.item.@label == "Undo")?(doUndo()):(void);
(evt.item.@label == "Redo")?(doRedo()):(void);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}
private function savePreferences():void {
preferences.data.wrap = pref_wrap;
preferences.data.status = pref_status;
preferences.data.toolbar = pref_toolbar;
preferences.data.fontsettings = pref_fontsettings;
preferences.data.sidepane = pref_sidepane;
preferences.data.linecount = pref_linecount;
preferences.flush();
}
private function doCut():void {
var selectedText:String = textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition);
System.setClipboard(selectedText);
insertText("");
}
private function doCopy():void {
var selectedText:String = textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition);
System.setClipboard(selectedText);
}
private function doPaste():void{
var myClip:Clipboard = Clipboard.generalClipboard;
var pastedText:String = myClip.getData(ClipboardFormats.TEXT_FORMAT) as String;
insertText(pastedText);
}
private function doSelectall():void {
textArea.selectAll();
}
private function insertText(str:String):void {
editManager.insertText(str);
}
private function cursorFix():void{
Mouse.cursor = "ibeam";
}
private function everyFrame(evt:Event):void {
if (!heightFixed && height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
if (sideList.selectedIndices.length==0) {
sideList.selectedIndex = tabSelectedIndex;
}
}
private function onResize(evt:ResizeEvent):void {
updateTextSize();
}
private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textX = (pref_linecount)?(lineCountWidth):(0);
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textWidth = (pref_sidepane)?(width - sidePaneWidth - textX):(width - textX);
tabWidth = textWidth + textX;
var tabbarScrollHeight:Number = (tabData.length * 170 > tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height + tabbarScrollHeight;
sidePaneY = textY - tabBar.height - tabbarScrollHeight;
sidePaneX = width - sidePaneWidth;
}
private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + " " + statusMessage;
}
private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("
");
var line:int = lines.length;
var col:int = lines[lines.length - 1].length + 1;
return "Ln " + line + ", Col " + col;
}
private function doFont():void{
fontWindow.open();
fontWindow.activate();
fontWindow.visible = true;
fontWindow.setValues(pref_fontsettings.fontsize, pref_fontsettings.fontfamily, pref_fontsettings.fontstyle, pref_fontsettings.fontweight, pref_fontsettings.fontcolor, pref_fontsettings.bgcolor);
}
private function onClose(evt:Event):void {
if(!closeAfterConfirm){
evt.preventDefault();
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 1; i < allWindows.length; i++)
{
allWindows[i].close();
}
// Check if there are any unsaved tabs
var needSaving:Boolean = false;
tabsToClose = 0;
for (var u:int = 0; u < tabData.length; u++) {
if (tabData[u].saved == false) {
needSaving = true;
tabsToClose++;
}
}
// If there are unsaved tabs, dont close window yet, set closeAfterConfirm to true and close all tabs
if (needSaving) {
closeAfterConfirm = true;
for (var t:int = 0; t < tabData.length; t++) {
closeTab(t);
}
}
if (!needSaving) {
FlexGlobals.topLevelApplication.close();
}
}
}
private function fontChange(evt:Event):void{
pref_fontsettings.fontfamily = fontWindow.fontCombo.selectedItem.fontName;
pref_fontsettings.fontsize = fontWindow.sizeStepper.value;
if (fontWindow.styleCombo.selectedIndex == 0) {
pref_fontsettings.fontstyle = "normal";
pref_fontsettings.fontweight = "normal";
}
if (fontWindow.styleCombo.selectedIndex == 1) {
pref_fontsettings.fontstyle = "italic";
pref_fontsettings.fontweight = "normal";
}
if (fontWindow.styleCombo.selectedIndex == 2) {
pref_fontsettings.fontstyle = "normal";
pref_fontsettings.fontweight = "bold";
}
if (fontWindow.styleCombo.selectedIndex == 3) {
pref_fontsettings.fontstyle = "italic";
pref_fontsettings.fontweight = "bold";
}
pref_fontsettings.fontcolor = fontWindow.colorPicker.selectedColor;
pref_fontsettings.bgcolor = fontWindow.bgColorPicker.selectedColor;
savePreferences();
updateFonts();
}
private function updateFonts():void{
textArea.setStyle("fontFamily", pref_fontsettings.fontfamily);
textArea.setStyle("fontSize", pref_fontsettings.fontsize);
textArea.setStyle("fontStyle", pref_fontsettings.fontstyle);
textArea.setStyle("fontWeight", pref_fontsettings.fontweight);
textArea.setStyle("color", pref_fontsettings.fontcolor);
textArea.setStyle("contentBackgroundColor", pref_fontsettings.bgcolor);
lineCount.setStyle("fontFamily", pref_fontsettings.fontfamily);
lineCount.setStyle("fontSize", pref_fontsettings.fontsize);
lineCount.setStyle("fontStyle", pref_fontsettings.fontstyle);
lineCount.setStyle("fontWeight", pref_fontsettings.fontweight);
lineCount.setStyle("color", pref_fontsettings.fontcolor);
lineCount.setStyle("contentBackgroundColor", pref_fontsettings.bgcolor);
}
private function onTabClose(evt:Event):void {
var tabWidth:Number = tabBar.width / tabData.length;
var cIndex:int = Math.floor(tabBar.mouseX / tabWidth);
tabSelectedIndex = cIndex;
tabChange();
closeTab(tabSelectedIndex);
}
private function onListClose(evt:Event):void {
tabSelectedIndex = sideList.selectedIndex;
tabChange();
closeTab(tabSelectedIndex);
}
private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
} else
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", "Confirmation", Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
tabsToClose--;
if (evt.detail == Alert.YES) {
tabSelectedIndex = index;
tabChange();
removeTab(index, true);
doSave(index, false);
}else {
removeTab(index);
}
}
}
private function removeTab(index:int, waitForSave:Boolean = false):void {
if(!closeAfterConfirm){
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false, location:""} );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
tabSelectedIndex = tabBar.selectedIndex;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
}
if (closeAfterConfirm && tabsToClose == 0 && waitForSave == false) {
FlexGlobals.topLevelApplication.close();
}
if (waitForSave) {
saveWait = true;
}
countLines();
updateTextSize();
undoManager.clearAll();
textChange();
}
private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false, location:""} );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}
public function tabChange(from:String = "none", ind:int = 0, createOperation:Boolean = true):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
if (from == "operation") {
tabSelectedIndex = ind;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
if (createOperation) {
var operation:TabOperation = new TabOperation(previousIndex, tabSelectedIndex, undoManager);
undoManager.pushUndo(operation);
}
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
textChange();
var savedSymbol:String = (tabData[tabSelectedIndex].saved)?(""):("*");
title = "Kirpad - " + tabData[tabSelectedIndex].title + savedSymbol;
}
private function tabContextClose(evt:ContextMenuEvent):void{
closeTab(rightclickTabIndex);
}
private function tabContextCloseOther(evt:ContextMenuEvent):void {
var len:int = tabData.length;
for (var i:int = 0; i < len; i++) {
if (i != rightclickTabIndex && tabData[i].saved) {
closeTab(i);
}
}
len = tabData.length;
for (var u:int = 0; u < len; u++) {
if (u != rightclickTabIndex) {
closeTab(u);
}
}
}
private function tabContextSave(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
doSave(rightclickTabIndex);
}
private function tabContextDuplicate(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + tabData[rightclickTabIndex].title + " duplicated";
updateStatus();
tabData.addItem( { title:"Copy of " + tabData[rightclickTabIndex].title, textData: tabData[rightclickTabIndex].textData, saved:false, location:""} );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}
private function tabRightClick(evt:MouseEvent):void {
var tabWidth:Number = tabBar.width / tabData.length;
var rcIndex:int = Math.floor(tabBar.mouseX / tabWidth);
rightclickTabIndex = rcIndex;
}
private function listRightClick(evt:MouseEvent):void {
var tabHeight:Number = 20;
var rcIndex:int = Math.floor((sideList.mouseY + sideList.scroller.verticalScrollBar.value) / tabHeight);
rightclickTabIndex = rcIndex;
}
private function onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 && !evt.shiftKey) {
if (tabData.length - tabSelectedIndex > 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 && evt.shiftKey) {
if (tabSelectedIndex > 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode >= 49 && evt.keyCode <= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length > num - 1) {
tabSelectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabSelectedIndex = tabData.length - 1;
tabChange();
}
}
}
private function closeSidePane():void{
pref_sidepane = !pref_sidepane
savePreferences();
updateTextSize();
}
private function countLines():void {
if (pref_linecount && !pref_wrap) {
var totalLines:int = textArea.text.split("
").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}
private function updateLineCount(total:int, difference:int, current:int):void {
if (difference > 0) {
for (var i:int = current + 1; i < (total+1); i++) {
lineNumbers += "
" + (i);
}
}
if (difference < 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u < -difference; u++) {
charsInTheEnd += ((current - u).toString().length + 1);
}
lineNumbers = lineCount.text.substring(0, lineCount.text.length - charsInTheEnd);
}
}
private function updateLineScroll():void{
lineCount.scroller.verticalScrollBar.value = textArea.scroller.verticalScrollBar.value;
}
private function doPrint():void {
var printJob:FlexPrintJob = new FlexPrintJob();
if (!printJob.start()) return;
tempText.visible = true;
tempText.setStyle("lineBreak", "toFit");
tempText.text = textArea.text;
tempText.width = printJob.pageWidth;
tempText.heightInLines = NaN;
tempText.setStyle("horizontalScrollPolicy", "off");
tempText.setStyle("verticalScrollPolicy", "off");
printJob.printAsBitmap = false;
printJob.addObject(tempText, "matchWidth");
printJob.send();
tempText.visible = false;
}
private function textChange():void{
canUndo = undoManager.canUndo();
canRedo = undoManager.canRedo();
focusManager.setFocus(textArea);
}
private function doUndo():void {
undoManager.undo();
textChange();
}
private function doRedo():void {
undoManager.redo();
textChange();
}
private function doOpen():void {
var file:File = new File();
file.browseForOpen("Open document", [new FileFilter("Text documents", "*.txt"), new FileFilter("All files", "*")]);
file.addEventListener(Event.SELECT, fileLoad);
function fileLoad(evt:Event):void {
loadFile(file);
}
}
private function loadFile(file:File):void {
if(fileDuplicateCheck(file.nativePath)){
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var str:String = stream.readUTFBytes(stream.bytesAvailable);
stream.close();
str = str.replace(File.lineEnding, "
");
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + file.name + " opened";
updateStatus();
tabData.addItem( { title:file.name, textData: str, saved:true, location:file.nativePath } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}else {
Alert.show("File " + file.name + " is already open","Error");
}
}
private function fileDuplicateCheck(loc:String):Boolean {
var toReturn:Boolean = true;
for (var i:int = 0; i < tabData.length; i++) {
if (tabData[i].location == loc) {
toReturn = false;
tabSelectedIndex = i;
tabChange();
break;
}
}
return toReturn;
}
private function saveUpdate():void {
if (tabData[tabSelectedIndex].saved) {
tabData[tabSelectedIndex].saved = false;
tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;
sideList.dataProvider = tabData;
var savedSymbol:String = (tabData[tabSelectedIndex].saved)?(""):("*");
title = "Kirpad - " + tabData[tabSelectedIndex].title + savedSymbol;
}
}
private function doSave(ind:int, updateIndexNeeded:Boolean = true):void {
if(!tabData[ind].saved){
if (tabData[ind].location != "") {
saveFile(textArea.text, tabData[ind].location, updateIndexNeeded);
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + tabData[ind].title + " saved";
updateStatus();
tabData[ind].saved = true;
}else{
doSaveAs(textArea.text, ind, updateIndexNeeded);
}
}
}
private function doSaveAll():void {
saveAsQueue = [];
for (var i:int = 0; i < tabData.length; i++) {
saveAsQueue.push(i);
}
doSaveQueue();
}
private function doSaveQueue():void {
if (saveAsQueue.length > 0) {
var ind:int = saveAsQueue[0];
saveAsQueue.splice(0, 1);
tabSelectedIndex = ind;
tabChange();
doSave(ind);
}
}
private function refreshData():void {
tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;
sideList.dataProvider = tabData;
}
private function doSaveAs(text:String, ind:int, updateIndexNeeded:Boolean = true):void {
var file:File = new File();
file.browseForSave("Save " + tabData[ind].title);
file.addEventListener(Event.SELECT, fileSave);
file.addEventListener(Event.CANCEL, fileCancel);
function fileSave(evt:Event):void {
if (file.name.length > 0) {
// See if user entered extension for the file (for example .txt)
// If not, add .txt by default
var extReg:RegExp = /.([a-z0-9]{2,})/i;
if (extReg.test(file.name)) {
saveFile(text, file.nativePath, updateIndexNeeded);
tabData[ind].location = file.nativePath;
tabData[ind].title = file.name;
tabData[ind].saved = true;
}else{
saveFile(text, file.nativePath + ".txt", updateIndexNeeded);
tabData[ind].location = file.nativePath + ".txt";
tabData[ind].title = file.name + ".txt";
tabData[ind].saved = true;
}
}else{
Alert.show("You need to enter a name for your file.", "Error");
}
}
function fileCancel(evt:Event):void {
doSaveQueue();
}
}
private function saveFile(text:String, location:String, updateIndexNeeded:Boolean = true):void {
var file:File = new File(location);
var stream:FileStream = new FileStream();
stream.open(file, FileMode.WRITE);
var str:String = text;
str = str.replace(/
/g, File.lineEnding);
stream.writeUTFBytes(str);
stream.close();
if (closeAfterConfirm && tabsToClose == 0 && saveWait) {
FlexGlobals.topLevelApplication.close();
}
saveWait = false;
if (updateIndexNeeded) {
refreshData();
}
doSaveQueue();
}
private function updateDirectoryPath():void{
directoryLabel.text = fileList.directory.nativePath;
}
private function fileListLoad():void{
var file:File = new File(fileList.selectedPath);
loadFile(file);
}
private function onOpen(evt:SQLEvent):void{
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "CREATE TABLE IF NOT EXISTS snippets (id INTEGER PRIMARY KEY AUTOINCREMENT, snippetName TEXT, snippetText TEXT, categoryName TEXT)";
stat.execute(-1, new Responder(selectItems));
}
private function selectItems(evt:SQLResult):void{
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT id, snippetName, snippetText, categoryName FROM snippets ORDER BY id";
stat.execute( -1, new Responder(onSelected));
}
private function onSelected(evt:SQLResult):void {
var data:Array = evt.data;
// Temporary:
data = [
{id:0, snippetName:"Snip1", snippetText:"Test1", categoryName:"Cat1" },
{id:1, snippetName:"Snip2", snippetText:"Test2", categoryName:"Cat2" },
{id:2, snippetName:"Snip3", snippetText:"Test3", categoryName:"Cat1" },
{id:3, snippetName:"Snip4", snippetText:"Test4", categoryName:"Cat2" },
{id:4, snippetName:"Snip5", snippetText:"Test5", categoryName:"Cat1" },
{id:5, snippetName:"Snip6", snippetText:"Test6", categoryName:"Cat2" } ]
// end temporary
var categories:Array = [];
snippetXML = new XMLList(<root></root>);
if(data){
for (var i:int = 0; i < data.length; i++) {
// If category is not unique, add the item to the category
if (isUnique(i)) {
// If it is unique, create the category and add the item
var newCategory:XML = <category isBranch="true"/>;
newCategory.@label = data[i].categoryName;
var newUniqueNode:XML = <snippet/>;
newUniqueNode.@label = data[i].snippetName;
newUniqueNode.@snippetText = data[i].snippetText;
newUniqueNode.@id = data[i].id;
newUniqueNode.@isBranch = false;
newCategory.appendChild(newUniqueNode);
snippetXML[0].appendChild(newCategory);
categories.push(data[i].categoryName);
}
}
}
function isUnique(ind:int):Boolean {
var isU:Boolean = true;
for (var u:int = 0; u < categories.length; u++) {
if (data[ind].categoryName == categories[u]) {
isU = false;
var newNode:XML = <snippet/>;
newNode.@label = data[ind].snippetName;
newNode.@snippetText = data[ind].snippetText;
newNode.@id = data[ind].id;
newNode.@isBranch = false;
snippetXML[0].category[u].appendChild(newNode);
break;
}
}
return isU;
}
}
private function snippetTreeChange(evt:Event):void {
if (evt.currentTarget.selectedItem.@isBranch == false) {
insertText(evt.currentTarget.selectedItem.@snippetText);
evt.currentTarget.selectedIndex = -1;
}
}
private function doSnippet(createNew:Boolean = false, newText:String = ""):void{
snippetWindow.open();
snippetWindow.activate();
snippetWindow.visible = true;
snippetWindow.setValues(snippetXML, createNew, newText);
}
]]>
</fx:Script>
<fx:Declarations>
<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As" key="s" controlKey="true" shiftKey="true" />
<menuitem label="Save All" key="s" controlKey="true" altKey="true" />
<menuitem type="separator"/>
<menuitem label="Print" key="p" controlKey="true" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" />
<menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" />
<menuitem type="separator"/>
<menuitem label="Cut" key="x" controlKey="true" />
<menuitem label="Copy" key="c" controlKey="true" />
<menuitem label="Paste" key="v" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Select all" key="a" controlKey="true" />
</menuitem>
<menuitem label="Settings">
<menuitem label="Word wrap" type="check" toggled="{pref_wrap}" />
<menuitem label="Font..."/>
</menuitem>
<menuitem label="View">
<menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" />
<menuitem label="Status bar" type="check" toggled="{pref_status}" />
<menuitem label="Line count" type="check" toggled="{pref_linecount}" />
<menuitem label="Side pane" type="check" toggled="{pref_sidepane}" />
</menuitem>
</root>
</fx:XML>
<mx:ArrayCollection id="tabData">
<fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" location="" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneData">
<fx:Object icon="@Embed(../lib/page.png)" tip="Tab management" />
<fx:Object icon="@Embed(../lib/folder_magnify.png)" tip="File browsing" />
<fx:Object icon="@Embed(../lib/book.png)" tip="Snippets" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneTabHeadings">
<fx:String>Tab management</fx:String>
<fx:String>File browsing</fx:String>
<fx:String>Snippets</fx:String>
</mx:ArrayCollection>
</fx:Declarations>
<s:Group width="100%" height="100%">
<s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?(toFit):(explicit)}" click="cursorFix(); updateStatus();" change="updateStatus(); countLines(); textChange(); saveUpdate();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" />
<s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}">
<s:Group>
<custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange(tabbar);" selectedIndex="{tabSelectedIndex}" labelField="saved">
<custom:layout>
<s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/>
</custom:layout>
</custom:CustomTabBar>
</s:Group>
</s:Scroller>
<s:TextArea id="lineCount" width="{lineCountWidth}" text="{lineNumbers}" visible="{pref_linecount}" height="{textHeight}" y="{textY}" editable="false" selectable="false" mouseEnabled="false" textAlign="right" verticalScrollPolicy="off" horizontalScrollPolicy="off" />
<mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3">
<custom:IconButton icon="@Embed(../lib/page.png)" toolTip="New document" click="doNew();" />
<custom:IconButton icon="@Embed(../lib/folder_page.png)" toolTip="Open" click="doOpen();" />
<custom:IconButton icon="@Embed(../lib/disk.png)" toolTip="Save" click="doSave(tabSelectedIndex);" />
<custom:IconButton icon="@Embed(../lib/disk_multiple.png)" toolTip="Save all" click="doSaveAll();" />
<custom:IconButton icon="@Embed(../lib/printer.png)" toolTip="Print" click="doPrint();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/arrow_undo.png)" toolTip="Undo" enabled="{canUndo}" click="doUndo();" />
<custom:IconButton icon="@Embed(../lib/arrow_redo.png)" toolTip="Redo" enabled="{canRedo}" click="doRedo();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/cut.png)" toolTip="Cut" click="doCut();" />
<custom:IconButton icon="@Embed(../lib/page_white_copy.png)" toolTip="Copy" click="doCopy();" />
<custom:IconButton icon="@Embed(../lib/paste_plain.png)" toolTip="Paste" click="doPaste();" />
</mx:HBox>
<mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off">
<s:Group>
<s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" top="1" />
<custom:IconButton icon="@Embed(../lib/bullet_go.png)" toolTip="Hide side pane" click="closeSidePane();" top="-4" right="12"/>
</s:Group>
<mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" />
<mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}">
<s:NavigatorContent id="tabs">
<custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange(sidelist);" tabClose="onListClose(event);" />
</s:NavigatorContent>
<s:NavigatorContent id="files">
<s:VGroup height="100%">
<s:HGroup width="100%">
<custom:IconButton icon="@Embed(../lib/arrow_up.png)" toolTip="Up" click="fileList.navigateUp();" enabled="{fileList.canNavigateUp}"/>
<s:Label width="140" id="directoryLabel" paddingTop="6" />
</s:HGroup>
<mx:FileSystemList width="{sidePaneWidth-10}" height="100%" id="fileList" directory="{File.desktopDirectory}" directoryChange="updateDirectoryPath();" creationComplete="updateDirectoryPath();" fileChoose="fileListLoad();" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent id="snippets">
<s:VGroup height="100%" width="100%">
<mx:Tree height="100%" width="100%" id="snippetTree" showRoot="false" labelField="@label" dataProvider="{snippetXML}" change="snippetTreeChange(event);" />
<s:Button width="100%" label="New snippet" click="doSnippet(true, textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition));" />
<s:Button width="100%" label="Manage snippets" click="doSnippet();" />
</s:VGroup>
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</s:Group>
<s:TextArea id="tempText" borderVisible="false" visible="false"/>
</s:WindowedApplication>
Thanks for reading!
