<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:gd="http://schemas.google.com/g/2005" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;DU4FQ304fyp7ImA9WhRTGUg.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138</id><updated>2011-11-10T22:45:12.337+02:00</updated><category term="OOP in AS3" /><category term="blog related" /><category term="AS3 and XML" /><category term="Adobe AIR" /><category term="Flex 4" /><category term="beginner as3 tutorial" /><category term="AS3 and components" /><category term="SQL" /><category term="intermediate as3 tutorial" /><category term="Flash and HTML" /><category term="Physics" /><title>ActionScript 3 for food</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://kirill-poletaev.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>504</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/KirillPoletaev" /><feedburner:info uri="kirillpoletaev" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>KirillPoletaev</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;CEcCQHk6eip7ImA9WhRTGUw.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-7044984266012116662</id><published>2011-11-10T10:01:00.000+02:00</published><updated>2011-11-10T10:01:01.712+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-10T10:01:01.712+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 31</title><content type="html">In this tutorial we will start working on the undo/redo functionalities.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
In Flex 4, there's a handy class called UndoManager, which will help us in our journey to achieve the undo/redo features the users desire. To use this class, create an instance of it and apply it using an EditManager instance, which you set as the interactionManager property of the text area's textFlow property.&lt;br /&gt;
&lt;br /&gt;
In the init() function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// Undo management
undoManager = new UndoManager();
textArea.textFlow.interactionManager = new EditManager(undoManager);
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, let's declare 2 variables canUndo and canRedo, which are booleans and false by default:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;[Bindable]
private var canUndo:Boolean = false;
[Bindable]
private var canRedo:Boolean = false;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now let's add two menu items Undo and Redo to the XML under the edit menu, set their keys to z and y and their enabled properties to be bound to canUndo and canRedo:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Print" key="p" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" /&amp;gt;
&amp;lt;menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Find the two IconButton objects that play the role of the undo and redo buttons on the toolbar, set their enabled properties to canRedo and canUndo and click properties to call the doUndo() and doRedo() functions.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_undo.png')" toolTip="Undo" enabled="{canUndo}" click="doUndo();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_redo.png')" toolTip="Redo" enabled="{canRedo}" click="doRedo();" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Go to the menuSelect function and add 2 checks for the Undo and Redo labels, direct them to doUndo() and doRedo() functions:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now let's create the doUndo and doRedo functions. Inside them, call the undo and redo methods of the undoManager:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function doUndo():void {
undoManager.undo();
}

private function doRedo():void {
undoManager.redo();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, update the textArea object by adding a new function textChange() to be called on change event:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines(); textChange();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Here's the function itself:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function textChange():void{
canUndo = undoManager.canUndo();
canRedo = undoManager.canRedo();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As you can see, we just use it to update the canUndo and canRedo values.&lt;br /&gt;
&lt;br /&gt;
Here's the full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import flashx.textLayout.accessibility.TextAccImpl;
import flashx.textLayout.edit.EditManager;
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;

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 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();
private var undoManager:UndoManager;

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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
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();
textArea.textFlow.interactionManager = new EditManager(undoManager);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

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 &amp;gt; 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 + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;&amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -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();
}

private function doUndo():void {
undoManager.undo();
}

private function doRedo():void {
undoManager.redo();
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Print" key="p" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" /&amp;gt;
&amp;lt;menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines(); textChange();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&amp;lt;s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"&amp;gt;
&amp;lt;custom:layout&amp;gt;
&amp;lt;s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/&amp;gt;
&amp;lt;/custom:layout&amp;gt;
&amp;lt;/custom:CustomTabBar&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;/s:Scroller&amp;gt;
&amp;lt;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" /&amp;gt;
&amp;lt;mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3"&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page.png')" toolTip="New document" click="doNew();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/folder_page.png')" toolTip="Open" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk.png')" toolTip="Save" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk_multiple.png')" toolTip="Save all" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/printer.png')" toolTip="Print" click="doPrint();" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_undo.png')" toolTip="Undo" enabled="{canUndo}" click="doUndo();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_redo.png')" toolTip="Redo" enabled="{canRedo}" click="doRedo();" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/cut.png')" toolTip="Cut" click="doCut();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page_white_copy.png')" toolTip="Copy" click="doCopy();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/paste_plain.png')" toolTip="Paste" click="doPaste();" /&amp;gt;
&amp;lt;/mx:HBox&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;s:TextArea id="tempText" borderVisible="false" visible="false"/&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, the code works and all, but there are a few issues still. The first one is that if we paste or cut text, the undo/redo doesn't work. If we change tabs, the undo/redo messes up too. We'll fix that in future.&lt;br /&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-7044984266012116662?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/DVBczBMhVTg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/7044984266012116662/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-31.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/7044984266012116662?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/7044984266012116662?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/DVBczBMhVTg/creating-flex-air-text-editor-part-31.html" title="Creating a Flex AIR text editor: Part 31" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-31.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0ECQX09fSp7ImA9WhRTGE8.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-6728201124524478134</id><published>2011-11-09T10:01:00.000+02:00</published><updated>2011-11-09T10:01:00.365+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-09T10:01:00.365+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 30</title><content type="html">In this tutorial we will start working on the printing feature.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Go to the Declarations tags and add the Print menu item there:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Print" key="p" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Find the printing icon button on the toolbar and set its click property to a doPrint() function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:IconButton icon="@Embed('../lib/printer.png')" toolTip="Print" click="doPrint();" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now find the menuSelect function and add a check for the Print label here, which also calls the doPrint() function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The doPrint() function is where it gets complicated. For now, we are just going to have it create a FlexPrintJob object and pass a tempText text area object for it to print. Before we do that, we need to create the tempText object:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;s:TextArea id="tempText" borderVisible="false" visible="false"/&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The doPrint function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;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;
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import flashx.textLayout.accessibility.TextAccImpl;
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;

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;

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();

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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
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);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

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 &amp;gt; 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 + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;&amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -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;
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Print" key="p" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&amp;lt;s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"&amp;gt;
&amp;lt;custom:layout&amp;gt;
&amp;lt;s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/&amp;gt;
&amp;lt;/custom:layout&amp;gt;
&amp;lt;/custom:CustomTabBar&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;/s:Scroller&amp;gt;
&amp;lt;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" /&amp;gt;
&amp;lt;mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3"&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page.png')" toolTip="New document" click="doNew();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/folder_page.png')" toolTip="Open" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk.png')" toolTip="Save" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk_multiple.png')" toolTip="Save all" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/printer.png')" toolTip="Print" click="doPrint();" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_undo.png')" toolTip="Undo" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_redo.png')" toolTip="Redo" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/cut.png')" toolTip="Cut" click="doCut();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page_white_copy.png')" toolTip="Copy" click="doCopy();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/paste_plain.png')" toolTip="Paste" click="doPaste();" /&amp;gt;
&amp;lt;/mx:HBox&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;s:TextArea id="tempText" borderVisible="false" visible="false"/&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-6728201124524478134?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/zKu5KckACYw" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/6728201124524478134/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-30.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/6728201124524478134?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/6728201124524478134?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/zKu5KckACYw/creating-flex-air-text-editor-part-30.html" title="Creating a Flex AIR text editor: Part 30" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-30.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0UCQ3kzfyp7ImA9WhRTF04.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-9118325046550776487</id><published>2011-11-08T10:01:00.000+02:00</published><updated>2011-11-08T10:01:02.787+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-08T10:01:02.787+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 29</title><content type="html">In this tutorial we will add icon buttons to the tool bar of our application.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
This is what we'll have by the end of this tutorial:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://1.bp.blogspot.com/-6MVwVbDwFYY/TqCGNYtGqyI/AAAAAAAAASs/ztg3ZiBndco/s1600/kirpad7.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="514" src="http://1.bp.blogspot.com/-6MVwVbDwFYY/TqCGNYtGqyI/AAAAAAAAASs/ztg3ZiBndco/s640/kirpad7.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;
Firstly, we will turn our toolBar Box component into an HBox with a fixed height and padding values:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3"&amp;gt;

&amp;lt;/mx:HBox&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We are going to create buttons with icons in them now. All of the icons that are use are from the Silk icon set from FamFamFam, and they're 16x16. My buttons are going to be 20x20, so that I can display a transparent square around the icon when the user rolls it over.&lt;br /&gt;
&lt;br /&gt;
To create the buttons, I need to create a new class for my button - IconButton.&lt;br /&gt;
&lt;br /&gt;
Create a new document IconButton.mxml, use it to create the 'icon' property using metadata tags. In the root Button tags, set the skinClass property to skinIcon:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" skinClass="iconSkin"&amp;gt;

&amp;lt;fx:Metadata&amp;gt;
[Style(name="icon",type="*")]
&amp;lt;/fx:Metadata&amp;gt;

&amp;lt;/s:Button&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The iconSkin class is a separate document, create it - iconSkin.mxml. Here we need to first set the states for the button, then use the Metadata tags to set the HostComponent to the Button component (because this is a button skin) and draw whatever graphics you want. For me its a transparent on roll over square.&lt;br /&gt;
&lt;br /&gt;
Add a BitmapImage or an Image object to hold the icon - set its source to hostComponent.getStyle('icon') and set its position too:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:SparkSkin name="iconSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark" 
        minWidth="21" minHeight="21"
        alpha.disabled="0.5"&amp;gt;

    &amp;lt;s:states&amp;gt;
        &amp;lt;s:State name="up" /&amp;gt;
        &amp;lt;s:State name="over" /&amp;gt;
        &amp;lt;s:State name="down" /&amp;gt;
        &amp;lt;s:State name="disabled" /&amp;gt;
    &amp;lt;/s:states&amp;gt;
 
    &amp;lt;fx:Metadata&amp;gt;
        [HostComponent("spark.components.Button")]
    &amp;lt;/fx:Metadata&amp;gt;
 
    &amp;lt;s:Rect width="24" height="24" alpha.up="0" alpha.over="0.8" alpha.disabled="0"&amp;gt;
&amp;lt;s:stroke&amp;gt;
&amp;lt;s:SolidColorStroke color="#666666"/&amp;gt;
&amp;lt;/s:stroke&amp;gt;
&amp;lt;s:fill&amp;gt;
&amp;lt;s:SolidColor color="#ffffff"/&amp;gt;
&amp;lt;/s:fill&amp;gt;
&amp;lt;/s:Rect&amp;gt;
&amp;lt;s:BitmapImage source="{hostComponent.getStyle('icon')}" top="4" right="4" left="4" bottom="4" /&amp;gt;
 
&amp;lt;/s:SparkSkin&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now that this is ready, we can populate our toolbar with icon buttons! Set each object's icon property to the url of the icon graphic, and toolTip to whatever the button represents. Also, for buttons like New, Cut, Copy and Paste we can already set the click property to the functions we already have:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3"&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page.png')" toolTip="New document" click="doNew();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/folder_page.png')" toolTip="Open" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk.png')" toolTip="Save" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk_multiple.png')" toolTip="Save all" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/printer.png')" toolTip="Print" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_undo.png')" toolTip="Undo" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_redo.png')" toolTip="Redo" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/cut.png')" toolTip="Cut" click="doCut();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page_white_copy.png')" toolTip="Copy" click="doCopy();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/paste_plain.png')" toolTip="Paste" click="doPaste();" /&amp;gt;
&amp;lt;/mx:HBox&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full main mxml file code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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;

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();

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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
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);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;amp;&amp;amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

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 &amp;gt; 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 + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 &amp;amp;&amp;amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;amp;&amp;amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;amp;&amp;amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -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;
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&amp;lt;s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"&amp;gt;
&amp;lt;custom:layout&amp;gt;
&amp;lt;s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/&amp;gt;
&amp;lt;/custom:layout&amp;gt;
&amp;lt;/custom:CustomTabBar&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;/s:Scroller&amp;gt;
&amp;lt;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" /&amp;gt;
&amp;lt;mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3"&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page.png')" toolTip="New document" click="doNew();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/folder_page.png')" toolTip="Open" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk.png')" toolTip="Save" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/disk_multiple.png')" toolTip="Save all" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/printer.png')" toolTip="Print" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_undo.png')" toolTip="Undo" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/arrow_redo.png')" toolTip="Redo" /&amp;gt;
&amp;lt;s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/cut.png')" toolTip="Cut" click="doCut();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/page_white_copy.png')" toolTip="Copy" click="doCopy();" /&amp;gt;
&amp;lt;custom:IconButton icon="@Embed('../lib/paste_plain.png')" toolTip="Paste" click="doPaste();" /&amp;gt;
&amp;lt;/mx:HBox&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-9118325046550776487?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/3qG-xnL8riI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/9118325046550776487/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-29.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/9118325046550776487?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/9118325046550776487?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/3qG-xnL8riI/creating-flex-air-text-editor-part-29.html" title="Creating a Flex AIR text editor: Part 29" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-6MVwVbDwFYY/TqCGNYtGqyI/AAAAAAAAASs/ztg3ZiBndco/s72-c/kirpad7.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-29.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE8CQXg5cCp7ImA9WhRTFkg.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-1552549741978666278</id><published>2011-11-07T10:01:00.000+02:00</published><updated>2011-11-07T10:01:00.628+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-07T10:01:00.628+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 28</title><content type="html">In this tutorial we will add a right click context menu to the tab list in the side pane.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Go to the init() function, and add the following lines of code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// Context menu declaration for the tab management list control
sideList.contextMenu = cm;
sideList.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, listRightClick);&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We apply the same context menu we have for tabbar to the list now and add a right click listener, setting a listRightClick function as the handler.&lt;br /&gt;
&lt;br /&gt;
The listRightClick function is similar to tabRightClick by its functionality - its goal is to set rightclickTabIndex variable to the index of the tab that the action is commenced on. In tabRightClick, the index is calculated using the width and mouse coordinate on the x axis on the tabbar control.&lt;br /&gt;
&lt;br /&gt;
In listRightClick, we basically need to do the same, except that instead of the tabbar control we now have the list control. Moreover, this list is scrollable. &lt;br /&gt;
&lt;br /&gt;
To find out the index, we first of all need to know the height of each tab. Let's set the tab height to a fixed number - 20.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function listRightClick(evt:MouseEvent):void {
var tabHeight:Number = 20;

}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Go to the CustomListItem.mxml now and find the updateDisplayList function. Inside it, change the 'h' variable value to 20:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;!-- http://blog.flexexamples.com/2010/01/27/creating-a-fancy-spark-list-control-item-renderer-in-flex-4/ --&amp;gt;
&amp;lt;s:ItemRenderer name="CustomListItemRenderer"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
        autoDrawBackground="true" &amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.MouseEvent;
import flash.events.Event;

override protected function updateDisplayList(w:Number, h:Number):void {
super.updateDisplayList(w, 20);
if(labelDisplay){
labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
}
}

private function labelClose(evt:MouseEvent):void {
evt.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

&amp;lt;s:BitmapImage source="@Embed('../lib/page.png')" top="2" left="2" /&amp;gt;
&amp;lt;s:Label id="labelDisplay" left="25" right="20" top="4" bottom="4" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/cross.png')" top="2" right="4" id="closeButton" click="labelClose(event)" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, we can go back to the main mxml file and calculate the index using the mouse coordinate, height of a tab and the amount of pixels that we scrolled through:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function listRightClick(evt:MouseEvent):void {
var tabHeight:Number = 20;
var rcIndex:int = Math.floor((sideList.mouseY + sideList.scroller.verticalScrollBar.value) / tabHeight);
rightclickTabIndex = rcIndex;
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code for main mxml file:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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;

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();

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;

preferences.data.firsttime = null;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
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);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

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 &amp;gt; tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
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 + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;&amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -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;
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&amp;lt;s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"&amp;gt;
&amp;lt;custom:layout&amp;gt;
&amp;lt;s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/&amp;gt;
&amp;lt;/custom:layout&amp;gt;
&amp;lt;/custom:CustomTabBar&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;/s:Scroller&amp;gt;
&amp;lt;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" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-1552549741978666278?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/Pk3NM9H0DcU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/1552549741978666278/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-28.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/1552549741978666278?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/1552549741978666278?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/Pk3NM9H0DcU/creating-flex-air-text-editor-part-28.html" title="Creating a Flex AIR text editor: Part 28" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-28.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEMCQHw9fCp7ImA9WhRTFUs.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-2266331588320523040</id><published>2011-11-06T10:01:00.000+02:00</published><updated>2011-11-06T10:01:01.264+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-06T10:01:01.264+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 27</title><content type="html">In this tutorial we will make our tabbar scrollable!&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
The problem with our current tab bar is that we can create as many tabs as we want, and since they have variable width, they will eventually become smaller and smaller. This means that if we have over 20 tahs opened, our tabs will just appear as small rectangles only big enough to display the close icon!&lt;br /&gt;
&lt;br /&gt;
To fix this, I added a scrollbar to the tabbar. In fact, I added the tabbar to the scroller. Here's what the application looks like:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-qJe4v3M19Dk/Tp3evAg9pXI/AAAAAAAAASk/4ANPM29rW8c/s1600/kirpad6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="514" src="http://3.bp.blogspot.com/-qJe4v3M19Dk/Tp3evAg9pXI/AAAAAAAAASk/4ANPM29rW8c/s640/kirpad6.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
Put the CustomTabBar object into a group and then into a Scroller object. Set the scroller's horizontal scroll policy to auto and vertical scroll policy to off. Give it tabbar's previous width and y values. For the tab bar itself, create inner layout tags and put a HorizontalLayout object inside. There, set the gap to -1 and columnWidth to 170 (the fixed with of each tab), set variableColumnWidth to false.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"&amp;gt;
&amp;lt;custom:layout&amp;gt;
&amp;lt;s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/&amp;gt;
&amp;lt;/custom:layout&amp;gt;
&amp;lt;/custom:CustomTabBar&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;/s:Scroller&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
When we do this, the height of the tabs for some reason rises to like 100 pixels. Fix this by going into CustomTab.mxml and updating the updateDisplayList function by passing a fixed height value of 22 to the function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;        override protected function updateDisplayList(w:Number, h:Number):void
        {
            super.updateDisplayList(w, 22);
            if (labelDisplay)
            {
                labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
            }
        }    
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
This way, the full CustomTab.mxml code becomes:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:ItemRenderer
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        width="100%"
        height="100"
        autoDrawBackground="false"
&amp;gt;   

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

    &amp;lt;fx:Script&amp;gt;
    &amp;lt;![CDATA[
    
import flash.events.Event;
        import mx.controls.Alert;
        import mx.events.CloseEvent;

        private var tab:*;
        override public function set data(value:Object):void
        {
            super.data = value;
            tab = value;
        }

        override protected function updateDisplayList(w:Number, h:Number):void
        {
            super.updateDisplayList(w, 22);
            if (labelDisplay)
            {
                labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
            }
        }              

        protected function labelClose_clickHandler(event:MouseEvent):void
        {
            event.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
        }

    ]]&amp;gt;
    &amp;lt;/fx:Script&amp;gt;
   
    &amp;lt;s:states&amp;gt;
        &amp;lt;s:State name="normal" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="selected" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="hovered" basedOn="{data.state}"/&amp;gt;
    &amp;lt;/s:states&amp;gt;

    &amp;lt;!-- background --&amp;gt;
    &amp;lt;s:Rect left="1" right="1" top="1" bottom="0"&amp;gt;
        &amp;lt;s:fill&amp;gt;
            &amp;lt;s:LinearGradient rotation="90"&amp;gt;
                &amp;lt;s:GradientEntry color="0xffffff" /&amp;gt;
                &amp;lt;s:GradientEntry
                    color="0xd8d8d8"
                    alpha="0.85"
                    color.selected="0xffffff"
                    alpha.selected="1.0"
                    color.hovered="0x929496"
                    alpha.hovered="0.85"
                /&amp;gt;
            &amp;lt;/s:LinearGradient&amp;gt;
        &amp;lt;/s:fill&amp;gt;
    &amp;lt;/s:Rect&amp;gt;

    &amp;lt;!-- border rectangle --&amp;gt;
    &amp;lt;s:Line left="0" right="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line right="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" right="0" bottom="0"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
                alpha.selected="0.0"
                color.selected="0xffffff"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;

&amp;lt;s:BitmapImage source="@Embed('../lib/page.png')" top="3" left="4" /&amp;gt;
   
    &amp;lt;s:Label
        id="labelDisplay"
        textAlign="left"
        verticalAlign="middle"
        maxDisplayedLines="1"
        horizontalCenter="0"
        verticalCenter="1"
        left="24"
        right="20"
        top="2"
        bottom="2"
    /&amp;gt;

&amp;lt;mx:Image source="@Embed('../lib/cross.png')" top="3" right="4" id="labelClose" click="labelClose_clickHandler(event)" useHandCursor="true" buttonMode="true"/&amp;gt;

&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Return to the main mxml file, change the TextArea object so that it doesn't display the focus border (by setting focusBorder to 0):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
We are going to update the updateTextSize() function, and after this we will need to call it every time changes are made to the tabs - in the removeTab and doNew functions:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Update the updateTextSize function so that it calculates the height and coordinates of certain elements, considering the scroll bar which may or may not appear (depending on how many tabs there are):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;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 &amp;gt; tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY - tabBar.height - tabbarScrollHeight;
sidePaneX = width - sidePaneWidth;
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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;

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();

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;

preferences.data.firsttime = null;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;amp;&amp;amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

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 &amp;gt; tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
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 + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;amp;&amp;amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;amp;&amp;amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -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;
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" /&amp;gt;
&amp;lt;s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"&amp;gt;
&amp;lt;custom:layout&amp;gt;
&amp;lt;s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/&amp;gt;
&amp;lt;/custom:layout&amp;gt;
&amp;lt;/custom:CustomTabBar&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;/s:Scroller&amp;gt;
&amp;lt;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" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-2266331588320523040?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/mPlFUzTZ_1E" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/2266331588320523040/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-27.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2266331588320523040?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2266331588320523040?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/mPlFUzTZ_1E/creating-flex-air-text-editor-part-27.html" title="Creating a Flex AIR text editor: Part 27" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-qJe4v3M19Dk/Tp3evAg9pXI/AAAAAAAAASk/4ANPM29rW8c/s72-c/kirpad6.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-27.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcCQXc4eCp7ImA9WhRTFEU.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-8871693373092185892</id><published>2011-11-05T10:01:00.000+02:00</published><updated>2011-11-05T10:01:00.930+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-05T10:01:00.930+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 26</title><content type="html">In this tutorial we are going to optimize our existing line numeration code, as well as synchronize line enumeration bar and text area scrolling.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
First go to the lineCount text area object, set its horizontal and vertical policies to off:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;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" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Find your everyFrame function. Here we can call an updateLineScroll() function. It will be a small function and will not eat up CPU or take lots of memory:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The updateLineScroll function synchronizes the scrolling of the lineCount text area and the textArea text area:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function updateLineScroll():void{
lineCount.scroller.verticalScrollBar.value = textArea.scroller.verticalScrollBar.value;
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Before, we had our code that enumerates the lines refresh completely after each change. This is very intensive ont he hardware, especially if there are a lot of lines. I managed to make the algorithm lighter and not as expensive. Instead of updating from blank, it just adds or substracts the needed numbers from the current value. Take a look:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function countLines():void {
if (pref_linecount &amp;&amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -difference; u++) {
charsInTheEnd += ((current - u).toString().length + 1);
}
lineNumbers = lineCount.text.substring(0, lineCount.text.length - charsInTheEnd);
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
If the difference between real total lines and displayed lines is positive, then we need to add a few lines to the bar. If the difference is negative, we need to take some of the lines away - I do that by calculating how many symbols I need to take away and then get rid of them using the substring function.&lt;br /&gt;
&lt;br /&gt;
That's all for today.&lt;br /&gt;
&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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;

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();

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;

preferences.data.firsttime = null;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
textX = (pref_linecount)?(lineCountWidth):(0);
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width - sidePaneWidth - textX):(width - textX);
tabWidth = textWidth + textX;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY-tabBar.height;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;&amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference &amp;gt; 0) {
for (var i:int = current + 1; i &amp;lt; (total+1); i++) {
lineNumbers += "\n" + (i);
}
}
if (difference &amp;lt; 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u &amp;lt; -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;
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{tabWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&amp;lt;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" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-8871693373092185892?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/qw7w7gGRKTo" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/8871693373092185892/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-26.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/8871693373092185892?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/8871693373092185892?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/qw7w7gGRKTo/creating-flex-air-text-editor-part-26.html" title="Creating a Flex AIR text editor: Part 26" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-26.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUECQH8-cCp7ImA9WhRTE0Q.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-5262244052290288266</id><published>2011-11-04T10:01:00.000+02:00</published><updated>2011-11-04T10:01:01.158+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-04T10:01:01.158+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 25</title><content type="html">In this tutorial we will start working on another element of the interface which will count and enumerate lines in the text area.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
This is what it is going to look like:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-RO1hJpHHaOk/TpxsIv5yFeI/AAAAAAAAASc/Et3nZ_yBzs8/s1600/kirpad5.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="516" src="http://2.bp.blogspot.com/-RO1hJpHHaOk/TpxsIv5yFeI/AAAAAAAAASc/Et3nZ_yBzs8/s640/kirpad5.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
The column is a text field of a fixed width (for now), which is populated by characters by first checking how many lines there are in total and how many lines are displayed in the counter right now. If these two values are different, then we need to update the line enumeration - recount the lines and repopulate the text field.&lt;br /&gt;
&lt;br /&gt;
This text field should also have the same font and size as the main text area, so that the lines match.&lt;br /&gt;
&lt;br /&gt;
Another important thing is that we have to make it possible to toggle this line counter bar through the native menu, moreover, the bar will only be visible when the word wrapping is turned off. When wrapping is on, there are no lines and no columns in the text, so we don't really have anything to show.&lt;br /&gt;
&lt;br /&gt;
By the end of this tutorial we will have a working, save-able and load-able line counting bar, which is updated when changing between tabs, when changing fonts, closing tabs, etc. However, it will not scroll and the width will be fixed, but these things will be improved in the next part.&lt;br /&gt;
&lt;br /&gt;
Let's declare the new variables that we will be using in this part.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;[Bindable]
private var pref_linecount:Boolean = true;

[Bindable]
private var textX:Number;

[Bindable]
private var tabWidth:Number;

[Bindable]
private var lineCountWidth:Number = 40;
[Bindable]
private var lineNumbers:String = "1";
[Bindable]
private var lineDisplayedNum:int = 1;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
The pref_linecount variable is needed to toggle the bar. The textX variable didn't exist before, because the x was always 0. Because it will not be so from now on, we need to include this variable. We used textWidth for tabBar width before, now we will use tabWidth, because the width of the tabbar and textarea will not always be the same.&lt;br /&gt;
&lt;br /&gt;
The lineCountWidth variable is the fixed width of the text area. The lineNumbers variable stores the text value for the bar text area. The lineDisplayedNum is a number that represents the number of lines that are shown on the bar right now.&lt;br /&gt;
&lt;br /&gt;
First let's add the native menu item for toggling the pref_linecount variable and manage saving it and loading it. The procedure is already familiar to us. First add it to the XML:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now update the preferences part in the init function. &lt;br /&gt;
&lt;br /&gt;
Note 1: the wrap and linecount variables must be different by default.&lt;br /&gt;
&lt;br /&gt;
Note 2: the first time running this code you will need to include "preferences.data.firsttime = null;" to reset the shared object.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;// 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;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Add this line to the menuSelect function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;(evt.item.@label == "Line count")?(pref_linecount = !pref_linecount):(void);
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Update savePreferences():&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;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();
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now let's update the menuSelect function again - let's add a conditional which checks if pref_wrap is true. If it is, set linecount to false. &lt;br /&gt;
&lt;br /&gt;
Also, call countLines() function (we'll create it later in the tutorial):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
The countLines() function will be used to update the bar. We need to call it every time we change the value of the text area.&lt;br /&gt;
&lt;br /&gt;
Now, let's update the updateTextSize() function. We need to manage the new textX variable, update the textWidth variable and manage tabWidth. The function after the changes:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Go to the removeTab() function and call the countLines() function in the end (while we're at it):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now let's create the text area bar as a component. &lt;br /&gt;
&lt;br /&gt;
First update the textArea text area so that it calls the countLines() function on change event plus what we already have, and set its x property to textX:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();"/&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now update the tabbar by settings its width to tabWidth instead of textWidth:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{tabWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Create a new TextArea object, give it an id of lineCount. Set its width to lineCountWidth, text to lineNumbers, visible to pref_linecount, height to textHeight, y to textY, editable, selectable and mouseEnabled to false and textAlign to right.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:TextArea id="lineCount" width="{lineCountWidth}" text="{lineNumbers}" visible="{pref_linecount}" height="{textHeight}" y="{textY}" editable="false" selectable="false" mouseEnabled="false" textAlign="right"/&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now - the countLines function we've been talking about! Here we need to check if the linecount bar is toggled on and if word wrapping is off. If the conditions are met, count total lines and compare them to the lines currently displayed on the bar. If they do not match, recount and reenumerate the lines using a updateLineCount() function, which simply fills the text area with text:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function countLines():void {
if (pref_linecount &amp;amp;&amp;amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateLineCount(totalLines);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int):void {
lineNumbers = "";
for (var i:int = 1; i &amp;lt; (total + 1); i++) {
lineNumbers += i + "\n";
}
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
As I said before, we want the font settings to be the same in the textArea and in the lineCount area:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;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);
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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;

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();

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;

preferences.data.firsttime = null;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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);
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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;amp;&amp;amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
textX = (pref_linecount)?(lineCountWidth):(0);
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width - sidePaneWidth - textX):(width - textX);
tabWidth = textWidth + textX;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY-tabBar.height;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;amp;&amp;amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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 &amp;amp;&amp;amp; !pref_wrap) {
var totalLines:int = textArea.text.split("\n").length;
if (totalLines != lineDisplayedNum) {
updateLineCount(totalLines);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int):void {
lineNumbers = "";
for (var i:int = 1; i &amp;lt; (total + 1); i++) {
lineNumbers += i + "\n";
}
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Line count" type="check" toggled="{pref_linecount}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus(); countLines();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{tabWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&amp;lt;s:TextArea id="lineCount" width="{lineCountWidth}" text="{lineNumbers}" visible="{pref_linecount}" height="{textHeight}" y="{textY}" editable="false" selectable="false" mouseEnabled="false" textAlign="right"/&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Woohoo! Looking back at the work done for the application, I can say that there's a lot done already. And there's much more to come. We don't even have basic save/load functions working, haha!&lt;br /&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-5262244052290288266?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/KmNLGjNMZg4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/5262244052290288266/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-25.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/5262244052290288266?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/5262244052290288266?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/KmNLGjNMZg4/creating-flex-air-text-editor-part-25.html" title="Creating a Flex AIR text editor: Part 25" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-RO1hJpHHaOk/TpxsIv5yFeI/AAAAAAAAASc/Et3nZ_yBzs8/s72-c/kirpad5.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-25.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DUUCQXs9fSp7ImA9WhRTE00.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-8901582688652628863</id><published>2011-11-03T10:01:00.002+02:00</published><updated>2011-11-03T10:01:00.565+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-03T10:01:00.565+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 24</title><content type="html">In this tutorial we will make the buttons on the button bar on the side pane functional.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
First of all, let's add tool tips to the buttons on the ToggleButtonBar element. Tool tips are the little message boxes that pop up near the cursor to explain what you are hovering over. Since our buttons just contain icons, it could be useful to have tool tips.&lt;br /&gt;
&lt;br /&gt;
Find the sidePaneData array and add 'tip' properties with your tool tip messages to the elements:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now go to your ToggleButtonBar control and set its toolTipField to tip:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
So far we have the custom List component on the side pane, but we want the contents to change depending on which button is selected in the button bar. For now, we will display a FileSystemTree as the File browsing content, and a list and some buttons for Snippets.&lt;br /&gt;
&lt;br /&gt;
Create a ViewStack object and 3 NavigatorContent objects inside. Fill them with content for the tab management, file browsing and snippets. Set the id of the view stack to sidePaneStack, and, most importantly, set the selectedIndex property to the selectedIndex of sidePaneButtons.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now find the Label object that displays "Tab management" by default. Change its text value to the values in an array called sidePaneTabHeadings. To find out the index of the element we want to read, use the selectedIndex property of the sidePaneButtons object:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Create this new array collection in the declarations:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
And there you have it. The button bar now works. Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 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 tabSelectedIndex:int = 0;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width - sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY-tabBar.height;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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();
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" tip="Tab management" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" tip="File browsing" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" tip="Snippets" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneTabHeadings"&amp;gt;
&amp;lt;fx:String&amp;gt;Tab management&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;File browsing&amp;lt;/fx:String&amp;gt;
&amp;lt;fx:String&amp;gt;Snippets&amp;lt;/fx:String&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{textWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" /&amp;gt;
&amp;lt;mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}"&amp;gt;
&amp;lt;s:NavigatorContent id="tabs"&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="files"&amp;gt;
&amp;lt;mx:FileSystemTree height="100%" width="100%" /&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;s:NavigatorContent id="snippets"&amp;gt;
&amp;lt;s:VGroup height="100%" width="100%"&amp;gt;
&amp;lt;mx:Tree height="100%" width="100%" /&amp;gt;
&amp;lt;s:Button width="100%" label="New snippet" /&amp;gt;
&amp;lt;s:Button width="100%" label="Manage snippets" /&amp;gt;
&amp;lt;/s:VGroup&amp;gt;
&amp;lt;/s:NavigatorContent&amp;gt;
&amp;lt;/mx:ViewStack&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-8901582688652628863?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/ewThdqkgDS0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/8901582688652628863/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-24.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/8901582688652628863?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/8901582688652628863?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/ewThdqkgDS0/creating-flex-air-text-editor-part-24.html" title="Creating a Flex AIR text editor: Part 24" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-24.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8CQX8_fip7ImA9WhRTEk8.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-2362084905362787407</id><published>2011-11-02T10:01:00.000+02:00</published><updated>2011-11-02T10:01:00.146+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-02T10:01:00.146+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 23</title><content type="html">In this tutorial we are going to add icons to our application!&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Here's what the program is going to look like by the end of this tutorial:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-wyObIT5dFsc/TpnFg1652HI/AAAAAAAAASU/6ri7bDwdu58/s1600/kirpad4.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="514" src="http://2.bp.blogspot.com/-wyObIT5dFsc/TpnFg1652HI/AAAAAAAAASU/6ri7bDwdu58/s640/kirpad4.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
The icons used here are from a free icons set called &lt;a href="http://www.famfamfam.com/lab/icons/"&gt;Silk icons by Famfamfam&lt;/a&gt;. You can use them as well!&lt;br /&gt;
&lt;br /&gt;
I downloaded the silk pack and then selected some icons I want to use in my appication and put them into the lib folder of my project.&lt;br /&gt;
&lt;br /&gt;
First, let's add the paper (page.png) icon and the close (cross.png) icon for each tab in the tabbar.&lt;br /&gt;
&lt;br /&gt;
Open CustomTab.mxml, add a BitmapImage (or an Image) object before the labelDisplay label, set its source to the page.png file (if you have icons in the lib folder, which is outside the src folder, which contains all mxml files, your path will look like mine):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:BitmapImage source="@Embed('../lib/page.png')" top="3" left="4" /&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Set the label's position so that it doesn't overlap with the image. I also set its text align to left because I like it better that way:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;    &amp;lt;s:Label
        id="labelDisplay"
        textAlign="left"
        verticalAlign="middle"
        maxDisplayedLines="1"
        horizontalCenter="0"
        verticalCenter="1"
        left="24"
        right="20"
        top="2"
        bottom="2"
    /&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now change the existing X button label to an Image object containing the cross icon:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;mx:Image source="@Embed('../lib/cross.png')" top="3" right="4" id="labelClose" click="labelClose_clickHandler(event)" useHandCursor="true" buttonMode="true"/&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Your CustomTab.mxml file now looks like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:ItemRenderer
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        width="100%"
        height="100"
        autoDrawBackground="false"
&amp;gt;   

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

    &amp;lt;fx:Script&amp;gt;
    &amp;lt;![CDATA[
    
import flash.events.Event;
        import mx.controls.Alert;
        import mx.events.CloseEvent;

        private var tab:*;
        override public function set data(value:Object):void
        {
            super.data = value;
            tab = value;
        }

        override protected function updateDisplayList(w:Number, h:Number):void
        {
            super.updateDisplayList(w,h);
            if (labelDisplay)
            {
                labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
            }
        }              

        protected function labelClose_clickHandler(event:MouseEvent):void
        {
            event.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
        }

    ]]&amp;gt;
    &amp;lt;/fx:Script&amp;gt;
   
    &amp;lt;s:states&amp;gt;
        &amp;lt;s:State name="normal" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="selected" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="hovered" basedOn="{data.state}"/&amp;gt;
    &amp;lt;/s:states&amp;gt;

    &amp;lt;!-- background --&amp;gt;
    &amp;lt;s:Rect left="1" right="1" top="1" bottom="0"&amp;gt;
        &amp;lt;s:fill&amp;gt;
            &amp;lt;s:LinearGradient rotation="90"&amp;gt;
                &amp;lt;s:GradientEntry color="0xffffff" /&amp;gt;
                &amp;lt;s:GradientEntry
                    color="0xd8d8d8"
                    alpha="0.85"
                    color.selected="0xffffff"
                    alpha.selected="1.0"
                    color.hovered="0x929496"
                    alpha.hovered="0.85"
                /&amp;gt;
            &amp;lt;/s:LinearGradient&amp;gt;
        &amp;lt;/s:fill&amp;gt;
    &amp;lt;/s:Rect&amp;gt;

    &amp;lt;!-- border rectangle --&amp;gt;
    &amp;lt;s:Line left="0" right="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line right="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" right="0" bottom="0"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
                alpha.selected="0.0"
                color.selected="0xffffff"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;

&amp;lt;s:BitmapImage source="@Embed('../lib/page.png')" top="3" left="4" /&amp;gt;
   
    &amp;lt;s:Label
        id="labelDisplay"
        textAlign="left"
        verticalAlign="middle"
        maxDisplayedLines="1"
        horizontalCenter="0"
        verticalCenter="1"
        left="24"
        right="20"
        top="2"
        bottom="2"
    /&amp;gt;

&amp;lt;mx:Image source="@Embed('../lib/cross.png')" top="3" right="4" id="labelClose" click="labelClose_clickHandler(event)" useHandCursor="true" buttonMode="true"/&amp;gt;

&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now, go to CustomListItem.mxml, we will add the icons here too. Basically do the same things you did with the previous file, just make sure it is positioned nicely:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:BitmapImage source="@Embed('../lib/page.png')" top="2" left="2" /&amp;gt;
&amp;lt;s:Label id="labelDisplay" left="25" right="20" top="4" bottom="4" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/cross.png')" top="2" right="4" id="closeButton" click="labelClose(event)" useHandCursor="true" buttonMode="true"/&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Full CustomListItem.mxml code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;!-- http://blog.flexexamples.com/2010/01/27/creating-a-fancy-spark-list-control-item-renderer-in-flex-4/ --&amp;gt;
&amp;lt;s:ItemRenderer name="CustomListItemRenderer"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
        autoDrawBackground="true" &amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.MouseEvent;
import flash.events.Event;

override protected function updateDisplayList(w:Number, h:Number):void {
super.updateDisplayList(w, h);
if(labelDisplay){
labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
}
}

private function labelClose(evt:MouseEvent):void {
evt.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

&amp;lt;s:BitmapImage source="@Embed('../lib/page.png')" top="2" left="2" /&amp;gt;
&amp;lt;s:Label id="labelDisplay" left="25" right="20" top="4" bottom="4" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/cross.png')" top="2" right="4" id="closeButton" click="labelClose(event)" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now, let's add icons to our side pane in the Main.mxml file.&lt;br /&gt;
&lt;br /&gt;
There's this group in the sidePan Box control which contains the 'Tab management' text and an X button label, remove the second label and replace it with an icon. I chose bullet_go.png for this one:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="Tab management" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now, let's add a ToggleButtonBar to our side pane. It will be used to change the side pane's content (Tab management, File browsing and Snippets). The icons that I chose for these buttons are page.png, folder_magnify.png and book.png. Before we add the icons, though, we need to add the button bar. Set its dataProvider to a sidePaneData array collection and iconField to icon.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;mx:ToggleButtonBar dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}"/&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now create the ArrayCollection in the Declarations tags. Add 3 objects with icon properties, set those properties to the embedded urls to the icons:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 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 tabSelectedIndex:int = 0;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;amp;&amp;amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width - sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY-tabBar.height;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;amp;&amp;amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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();
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;mx:ArrayCollection id="sidePaneData"&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/page.png')" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/folder_magnify.png')" /&amp;gt;
&amp;lt;fx:Object icon="@Embed('../lib/book.png')" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{textWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="Tab management" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;mx:Image source="@Embed('../lib/bullet_go.png')" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;mx:ToggleButtonBar dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}"/&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-2362084905362787407?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/OtPV9KlXkXc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/2362084905362787407/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-23.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2362084905362787407?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2362084905362787407?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/OtPV9KlXkXc/creating-flex-air-text-editor-part-23.html" title="Creating a Flex AIR text editor: Part 23" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-wyObIT5dFsc/TpnFg1652HI/AAAAAAAAASU/6ri7bDwdu58/s72-c/kirpad4.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/11/creating-flex-air-text-editor-part-23.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DkMCQX8-fip7ImA9WhRTEU4.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-362035224670259806</id><published>2011-11-01T10:01:00.000+02:00</published><updated>2011-11-01T10:01:00.156+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-11-01T10:01:00.156+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 22</title><content type="html">In this tutorial we will reposition the sidePane and tabBar a little, as well as add a close button for the side pane.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
This is how the application is going to look like by the end of this tutorial:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-Wjc3KdbGICw/TpigX6VFeSI/AAAAAAAAASM/5zLSjTQpAIo/s1600/kirpad3.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="515" src="http://3.bp.blogspot.com/-Wjc3KdbGICw/TpigX6VFeSI/AAAAAAAAASM/5zLSjTQpAIo/s640/kirpad3.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;br /&gt;
The width of the tab bar is now going to be the same as the width of the text area. When side pane is enabled, it is narrowed, when side pane is disabled - the text area is 100% wide. Same now goes to the tab bar, so let's set the width of the tabbar to textWidth:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{textWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}" /&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
We need to update the updateTextSize() function to reposition the side pane (move it higher):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width - sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY-tabBar.height;
sidePaneX = width - sidePaneWidth;
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Now let's create a close button for the side pane. It can be used to close the side pane (set the pref_sidepane variable to false). To reenable it after that, the user will have to do it using the native menu. &lt;br /&gt;
&lt;br /&gt;
Find the label that says "Tab management", create a group and put this label in it. Add another label there, stylize it to look like an X button and call a closeSidePane() function on click:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="Tab management" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;s:Label text="x" width="20" fontSize="18" fontWeight="bold" y="-2" x="{sidePaneWidth-25}" color="#333333" buttonMode="true" click="closeSidePane();" /&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;s:Label text="[side pane navigation here]" width="100%" /&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
The closeSidePane() function closes the side pane by toggling pref_sideplane, calling the savePreferences() and updateTextSize() functions.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;private function closeSidePane():void{
pref_sidepane = !pref_sidepane
savePreferences();
updateTextSize();
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Here's the full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;
&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 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 tabSelectedIndex:int = 0;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;amp;&amp;amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width - sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height;
sidePaneY = textY-tabBar.height;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;amp;&amp;amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;amp;&amp;amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;amp;&amp;amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; 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();
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="{textWidth}" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off"&amp;gt;
&amp;lt;s:Group&amp;gt;
&amp;lt;s:Label text="Tab management" width="{sidePaneWidth}" /&amp;gt;
&amp;lt;s:Label text="x" width="20" fontSize="18" fontWeight="bold" y="-2" x="{sidePaneWidth-25}" color="#333333" buttonMode="true" click="closeSidePane();" /&amp;gt;
&amp;lt;/s:Group&amp;gt;
&amp;lt;s:Label text="[side pane navigation here]" width="100%" /&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;
&lt;/div&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-362035224670259806?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/-bnD9FxeSbc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/362035224670259806/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-22.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/362035224670259806?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/362035224670259806?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/-bnD9FxeSbc/creating-flex-air-text-editor-part-22.html" title="Creating a Flex AIR text editor: Part 22" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-Wjc3KdbGICw/TpigX6VFeSI/AAAAAAAAASM/5zLSjTQpAIo/s72-c/kirpad3.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-22.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkcCQXo-fCp7ImA9WhRTEEk.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-2092789852469554093</id><published>2011-10-31T10:01:00.000+02:00</published><updated>2011-10-31T10:01:00.454+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-31T10:01:00.454+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 21</title><content type="html">In this tutorial we will add more functionality to the list control on the side pane - the user will now be able to select and remove items from the list.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
We have a function called tabChange(). Let's edit it a bit, add an optional parameter, which is a string value. This is needed because we can use the same function when the selection is updated using the tabbar, the list, and whenever we call the function from code.&lt;br /&gt;
&lt;br /&gt;
If we call it on change event of the tabbar, we can pass 'tabbar' value as the parameter, and then the tabSelectedIndex variable will be set to the selectedIndex property of the TabBar control. If we pass 'sidelist' (on change event of the list), we set tabSelectedIndex to selectedIndex of the List control.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Set the CustomTabBar's change event handler to tabChange('tabbar'):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, we will also use a custom event for the List control called tabClose. We will create it shortly, but now lets add a listener to it. Also, we are going to be using a custom component called CustomList instead of List (to manage the tabClose event too), so remember to use the CustomList component name and the custom namespace.&lt;br /&gt;
&lt;br /&gt;
Set the CustomList's change event handler to tabChange('sidelist'), and tabClose event handler to onListClose(event):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
When we close an item from the list, the item already gets selected automatically. We just need to close the tab with the index of the list's selected index:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onListClose(evt:Event):void {
tabSelectedIndex = sideList.selectedIndex;
tabChange();
closeTab(tabSelectedIndex);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, let's work on the CustomList component. Create a new file and call it CustomList.mxml:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:List xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"&amp;gt;
    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;
&amp;lt;/s:List&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As you can see, we declare the tabClose event here. Now go to your CustomListItem mxml file, we'll change it a bit.&lt;br /&gt;
&lt;br /&gt;
First thing is the init function - we are not going to use it. Remove it and remove its listener in the root tags - we will replace this function with this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;override protected function updateDisplayList(w:Number, h:Number):void {
super.updateDisplayList(w, h);
if(labelDisplay){
labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, we already have the labelClose function, but it is empty. Just dispatch the tabClose event from there:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function labelClose(evt:MouseEvent):void {
evt.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Also create the event in the metadata tags:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code for CustomListItem.mxml:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;!-- http://blog.flexexamples.com/2010/01/27/creating-a-fancy-spark-list-control-item-renderer-in-flex-4/ --&amp;gt;
&amp;lt;s:ItemRenderer name="CustomListItemRenderer"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        autoDrawBackground="true" &amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.MouseEvent;
import flash.events.Event;

override protected function updateDisplayList(w:Number, h:Number):void {
super.updateDisplayList(w, h);
if(labelDisplay){
labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
}
}

private function labelClose(evt:MouseEvent):void {
evt.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

&amp;lt;s:Label id="labelDisplay" left="4" right="20" top="4" bottom="4" /&amp;gt;
&amp;lt;s:Label id="closeButton" text="x" fontWeight="bold" right="4" top="2" fontSize="16" alpha="0.5" color="#444444" click="labelClose(event)" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code for main mxml file:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 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 tabSelectedIndex:int = 0;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width-sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight;
sidePaneY = textY;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

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);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange(from:String = "none"):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; num - 1) {
tabSelectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabSelectedIndex = tabData.length - 1;
tabChange();
}
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange('tabbar');" selectedIndex="{tabSelectedIndex}" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dfdfdf" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5"&amp;gt;
&amp;lt;s:Label text="Tab management" textAlign="center" width="100%" /&amp;gt;
&amp;lt;s:Label text="[navigation will be here]" textAlign="center" width="100%" /&amp;gt;
&amp;lt;custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange('sidelist');" tabClose="onListClose(event);"/&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-2092789852469554093?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/Q37CKt7yjIg" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/2092789852469554093/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-21.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2092789852469554093?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2092789852469554093?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/Q37CKt7yjIg/creating-flex-air-text-editor-part-21.html" title="Creating a Flex AIR text editor: Part 21" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-21.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0ECQX07fyp7ImA9WhdaGUs.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-6605481274103368499</id><published>2011-10-30T10:01:00.000+02:00</published><updated>2011-10-30T10:01:00.307+02:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-30T10:01:00.307+02:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 20</title><content type="html">In this tutorial we will add a List component to the side pane and make it display the tab data, as well be in synchronisation with the tab bar (have the same item selected as the tab bar does). In this tutorial we will also fix the tab closing problem (it only closed selected tabs when the user uses the "X" button, regardless of which tab the button was on).&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Add content to the sidePane box. 2 labels (for now) and List component, which gets data from the tabData array collection. Two important things to note: the itemRenderer of the List component is going to be a CustomListItem renderer, and the selectedIndex of the control is going to be set to tabSelectedIndex.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dfdfdf" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5"&amp;gt;
&amp;lt;s:Label text="Tab management" textAlign="center" width="100%" /&amp;gt;
&amp;lt;s:Label text="[navigation will be here]" textAlign="center" width="100%" /&amp;gt;
&amp;lt;s:List id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The tabSelectedIndex property is going to be used to hold the value of the selected item, because, remember, we technically have 2 tab bars now - one is a TabBar, and the other is a List.&lt;br /&gt;
&lt;br /&gt;
Set the CustomTabBar's selectedIndex property to this variable as well.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" selectedIndex="{tabSelectedIndex}" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Declare the variable, set it to 0 by default. Also declare the sideContentWidth variable we used for the list's width:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;[Bindable]
private var sideContentWidth:Number = 170;

[Bindable]
private var tabSelectedIndex:int = 0;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Let's create the CustomListItem item renderer now. Create a new file called CustomListItem.mxml.&lt;br /&gt;
&lt;br /&gt;
Here are its contents:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;!-- http://blog.flexexamples.com/2010/01/27/creating-a-fancy-spark-list-control-item-renderer-in-flex-4/ --&amp;gt;
&amp;lt;s:ItemRenderer name="CustomListItemRenderer"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        autoDrawBackground="true" creationComplete="init();"&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.MouseEvent;

private function init():void{
labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
}

private function labelClose(evt:MouseEvent):void{
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;s:Label id="labelDisplay" left="4" right="20" top="4" bottom="4" /&amp;gt;
&amp;lt;s:Label id="closeButton" text="x" fontWeight="bold" right="4" top="2" fontSize="16" alpha="0.5" color="#444444" click="labelClose(event)" useHandCursor="true" buttonMode="true"/&amp;gt;
&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As you can see, we basically add the X button and set the labelDisplay label's value to the title of the selected tab item (and a * if its saved property is false).&lt;br /&gt;
&lt;br /&gt;
Now, back to the main file. This is going to be an important moment now: we are now going to use tabSelectedIndex instead of separate selectedIndex property of the tab bar, so replace ALL the "tabBar.selectedIndex" in your code to "tabSelectedIndex".&lt;br /&gt;
&lt;br /&gt;
When you do that, there are a few changes that need to be made.&lt;br /&gt;
&lt;br /&gt;
First change in the tabChange function. You need to add a line which sets tabSelectedIndex to the selectedIndex of tabBar in the beginning of the function.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabChange():void {
tabSelectedIndex = tabBar.selectedIndex;
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Second change, you need to update the removeTab function - add a line to change the tabSelectedIndex value again:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now go to onTabClose function. Here we will finally add code to calculate which tab exactly is meant to be closed (up until now it only closed the one that was selected - sorry about that):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;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);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 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 tabSelectedIndex:int = 0;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width-sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight;
sidePaneY = textY;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

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 closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
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 &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabSelectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabSelectedIndex = tabBar.selectedIndex;
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabSelectedIndex &amp;gt; 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabSelectedIndex &amp;gt; 0) {
tabSelectedIndex--;
tabChange();
}
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; num - 1) {
tabSelectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabSelectedIndex = tabData.length - 1;
tabChange();
}
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" selectedIndex="{tabSelectedIndex}" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dfdfdf" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5"&amp;gt;
&amp;lt;s:Label text="Tab management" textAlign="center" width="100%" /&amp;gt;
&amp;lt;s:Label text="[navigation will be here]" textAlign="center" width="100%" /&amp;gt;
&amp;lt;s:List id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}"/&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Phew, it works, partially. You can do all the stuff you could do before, but now you know that we use one global variable for storing the selected index. Also, the List component on the side pane displays all the tabs and displays which tab is selected. You can't select or remove tabs from the List yet, though.&lt;br /&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-6605481274103368499?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/8MXXCyU-b8U" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/6605481274103368499/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-20.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/6605481274103368499?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/6605481274103368499?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/8MXXCyU-b8U/creating-flex-air-text-editor-part-20.html" title="Creating a Flex AIR text editor: Part 20" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-20.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0ECQHgycSp7ImA9WhdaGEo.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-5293103266013831141</id><published>2011-10-29T10:01:00.000+03:00</published><updated>2011-10-29T10:01:01.699+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-29T10:01:01.699+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 19</title><content type="html">In this tutorial we will add a side pane to our application.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
A side pane will be used for tab navigation (useful when you have a lot of tabs opened at once), file navigation and snippet management. The user will be able to change between these functions of the side pane, and it will also be toggleable.&lt;br /&gt;
&lt;br /&gt;
The thing we will do today is first create a pref_sidepane boolean value that can be toggled on and off through the native menu (and saved on local computer too), then create size and coordination variables that will be used to manage the position and size of the side pane.&lt;br /&gt;
&lt;br /&gt;
Firstly create the pref_sidepane variable.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;[Bindable]
private var pref_sidepane:Boolean = true;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the init() function, find the part where we set and load the preferences and update it:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As always, you will need to set preferences.data.firsttime = null before this piece of code for the first time after applying the changes fo debug reasons.&lt;br /&gt;
&lt;br /&gt;
Now we add the Side pane menu item in the declarations tags:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Update the menuSelect function to add a check for the side pane menu item selection:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
And update the savePreferences function too:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;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.flush();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now that we have that working and out of the way, we can get to creating and managing the size and position of the side pane itself. First declare a few variables:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;[Bindable]
private var textWidth:Number;
[Bindable]
private var sidePaneY:Number;
[Bindable]
private var sidePaneX:Number;
[Bindable]
private var sidePaneHeight:Number;
[Bindable]
private var sidePaneWidth:Number = 180;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As you can see, we created the coordinate and dimension variables for the side pane and the width variable for text area (that we didn't have before, but we need it now).&lt;br /&gt;
&lt;br /&gt;
The sidePaneWidth variable will only be set here and not changed in the future (for now, at least).&lt;br /&gt;
&lt;br /&gt;
Set the text area's width to textWidth:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now create a new box element, call it sidePane, set its width, height, y, x and visible properties to the variables we've created in this tutorial. Also set the backgroundColor of the box to an interface color, I chose it to be gray-ish.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dfdfdf" visible="{pref_sidepane}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now go to the updateTextSize() function, add lines to manage the new variables. The code might seem complex but it is pretty self explanatory:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width-sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight;
sidePaneY = textY;
sidePaneX = width - sidePaneWidth;
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Finally, let's set minimal dimensions for the main window. We do this by setting minWidth and minHeight values of the root tags. Also set the height and width properties to values of your preference, so that the window doesn't get minimal values on start.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;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"&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
And that is it!&lt;br /&gt;
&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 tabY:Number;
[Bindable]
private var sidePaneY:Number;
[Bindable]
private var sidePaneX:Number;
[Bindable]
private var sidePaneHeight:Number;
[Bindable]
private var sidePaneWidth:Number = 180;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = 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;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
updateTextSize();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
textWidth = (pref_sidepane)?(width-sidePaneWidth):(width);
focusManager.setFocus(textArea);
sidePaneHeight = textHeight;
sidePaneY = textY;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

private function onTabClose(evt:Event):void {
closeTab(tabBar.selectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
if (closeAfterConfirm &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabBar.selectedIndex &amp;gt; 1) {
tabBar.selectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabBar.selectedIndex &amp;gt; 0) {
tabBar.selectedIndex--;
tabChange();
}
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; num - 1) {
tabBar.selectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;menuitem label="Side pane" type="check" toggled="{pref_sidepane}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dfdfdf" visible="{pref_sidepane}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-5293103266013831141?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/i5QDInINjhE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/5293103266013831141/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-19.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/5293103266013831141?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/5293103266013831141?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/i5QDInINjhE/creating-flex-air-text-editor-part-19.html" title="Creating a Flex AIR text editor: Part 19" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-19.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0UCQHo4fip7ImA9WhdaF0U.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-7550329273404978246</id><published>2011-10-28T10:01:00.000+03:00</published><updated>2011-10-28T10:01:01.436+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-28T10:01:01.436+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 18</title><content type="html">In this tutorial we will do two things - firstly we will make it so that each untitled tab gets numbered, so that there won't be two same tabs. The second thing we're going to do is ask for confirmation for each unsaved tab's closing when the user tries to close the main application.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Firstly, declare a new variable untitledNum, which will be the number that we will be increasing and adding to the title of each new tab:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private var untitledNum:int = 0;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the doNew() function, add a line that adds 1 to the untitledNum variable and then add this variable to the title value:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, for the closing part.&lt;br /&gt;
&lt;br /&gt;
When the user closes the main window and has unsaved tabs opened, the default behavior of the closing functionality should be prevented and instead of that, the application should ask confirmation about each unsaved tab. When the last confirmation is completed, we can close the window safely.&lt;br /&gt;
&lt;br /&gt;
How do we find out which tab is the last, and when do we actually close the window? We can keep track of all the tabs by creating a variable which holds a number of unsaved tabs that need to be closed. As the user confirms each tab, 1 is substracted from that variable. Then, in the confirmation function, if this variable equals 0, this means that this was the last tab and we should close now. We can make sure when to close the window by creating a boolean variable closeAfterConfirm. If it is true and tabsToClose equals 0, we can close the window.&lt;br /&gt;
&lt;br /&gt;
Now, let's code this.&lt;br /&gt;
&lt;br /&gt;
Declare the two variables I mentioned:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private var tabsToClose:int = 0;
private var closeAfterConfirm:Boolean = false;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Update the onClose function by adding a loop to check if there are unsaved tabs and a loop to use closeTab() on all tabs if there are unsaved ones. If we don't need to save, close the window.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onClose(evt:Event):void {
if(!closeAfterConfirm){
evt.preventDefault();
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 1; i &amp;lt; 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 &amp;lt; 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 &amp;lt; tabData.length; t++) {
closeTab(t);
}
}
if (!needSaving) {
FlexGlobals.topLevelApplication.close();
}
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The closeTab function needs to be updated too. We need to remember to substract 1 from tabsToClose on confirmation, and we need to call removeTab() when the user pressed Yes or No. Later we will add a saving function to be called when the user presses Yes.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Finally, we update the removeTab function by creating a conditional that closes the main window if closeAfterConfirm is set to true and tabsToClose equals 0.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
if (closeAfterConfirm &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
And that's all!&lt;br /&gt;
&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

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();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 &amp;lt; 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 &amp;lt; 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 &amp;lt; 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);
}

private function onTabClose(evt:Event):void {
closeTab(tabBar.selectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
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) {
// TODO: call saving function here
removeTab(index);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
if (closeAfterConfirm &amp;&amp; tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabBar.selectedIndex &amp;gt; 1) {
tabBar.selectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabBar.selectedIndex &amp;gt; 0) {
tabBar.selectedIndex--;
tabChange();
}
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; num - 1) {
tabBar.selectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-7550329273404978246?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/2rv2T6l0a_8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/7550329273404978246/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-18.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/7550329273404978246?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/7550329273404978246?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/2rv2T6l0a_8/creating-flex-air-text-editor-part-18.html" title="Creating a Flex AIR text editor: Part 18" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-18.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8CQHwyfCp7ImA9WhdaFkQ.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-14663831958421136</id><published>2011-10-27T10:01:00.000+03:00</published><updated>2011-10-27T10:01:01.294+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-27T10:01:01.294+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 17</title><content type="html">In this tutorial we are going to add hotkeys to let the user navigate through tabs using keyboard.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
First of all, though, we will add a few lines to our existing code to update the status bar with status messages. Let's do this when we create a new tab (the doNew() function):&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
And also when we remove one:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
There we go. Now we actually can say that we have a use for the status messages area in our status bar :)&lt;br /&gt;
&lt;br /&gt;
Now let's add hotkeys to let the user navigate through the tabs using keyboard. There are a few combinations we are going to implement:&lt;br /&gt;
&lt;br /&gt;
Ctrl+Tab will open the next tab.&lt;br /&gt;
Ctrl+Shift+Tab will go to the previous tab.&lt;br /&gt;
Ctrl+(1-8) will go to the numbered tab (from 1 to 8).&lt;br /&gt;
Ctrl+9 will go to the last tab.&lt;br /&gt;
&lt;br /&gt;
Let's implement this now. Add a listener for the keyboard events in the init() function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
All the key combinations right now require the Ctrl key to be pressed. Let's make a conditional for that:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {

}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now we can write the conditionals for each of the combination. Here's the full function in the end:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabBar.selectedIndex &amp;gt; 1) {
tabBar.selectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabBar.selectedIndex &amp;gt; 0) {
tabBar.selectedIndex--;
tabChange();
}
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; num - 1) {
tabBar.selectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
It is important to call tabChange() every time we change the tab so that the data gets updated.&lt;br /&gt;
&lt;br /&gt;
Here's the full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

private var previousIndex:int = 0;
private var rightclickTabIndex:int = 0;

public var fontWindow:FontWindow = new FontWindow();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 {
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i &amp;lt; allWindows.length; i++)
{
allWindows[i].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);
}

private function onTabClose(evt:Event):void {
closeTab(tabBar.selectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", "Confirmation", Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
Alert.show("Perform save function here and then delete");
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

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 onKeyDown(evt:KeyboardEvent):void{
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 &amp;&amp; !evt.shiftKey) {
if (tabData.length - tabBar.selectedIndex &amp;gt; 1) {
tabBar.selectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 &amp;&amp; evt.shiftKey) {
if (tabBar.selectedIndex &amp;gt; 0) {
tabBar.selectedIndex--;
tabChange();
}
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode &amp;gt;= 49 &amp;&amp; evt.keyCode &amp;lt;= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length &amp;gt; num - 1) {
tabBar.selectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-14663831958421136?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/fzskTpY-X1c" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/14663831958421136/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-17.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/14663831958421136?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/14663831958421136?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/fzskTpY-X1c/creating-flex-air-text-editor-part-17.html" title="Creating a Flex AIR text editor: Part 17" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-17.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEMCQXg-fCp7ImA9WhdaFkw.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-2423002669314881588</id><published>2011-10-26T10:01:00.000+03:00</published><updated>2011-10-26T10:01:00.654+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-26T10:01:00.654+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 16</title><content type="html">In this tutorial we will add a right click context menu specially for the tabbar, with options like Close tab and Close other tabs.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
First of all, we will need to create the context menu with all its items. We do this by declaring variables in the init() function. We need to create 2 ContextMenuItem objects, "Close tab" and "Close other tabs", as well as add event listeners for both of them and set tabContextClose and tabContextCloseOther functions as handlers.&lt;br /&gt;
&lt;br /&gt;
After that we create a new ContextMenu object and set its items property to an array consisting of these two context menu items. We hide the default menu items using the hideBuiltInItem() method, then we apply this object to the contextMenu property of our tabBar. Finally, we add a right click listener for the tabBar itself (I'll explain later what for) and add a handler function called tabRightClick.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We need to listen to the right click event, because this way we can find out the index of the tab that we are going to perform the action on. We can calcluate the index knowing the width of each tab, number of tabs and the position of the mouse on the tabbar control. We can then story this index in a variable called rightclickTabIndex. Let's declare it first:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private var rightclickTabIndex:int = 0;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now the tabRightClick function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabRightClick(evt:MouseEvent):void {
var tabWidth:Number = tabBar.width / tabData.length;
var rcIndex:int = Math.floor(tabBar.mouseX / tabWidth);
rightclickTabIndex = rcIndex;
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The tabContextClose function just closes the tab using the closeTab function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabContextClose(evt:ContextMenuEvent):void{
closeTab(rightclickTabIndex);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the tabContextCloseOther function, we loop through the tabs, deleting all except the one that was right clicked on:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabContextCloseOther(evt:ContextMenuEvent):void {
var len:int = tabData.length;
for (var i:int = 0; i &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Here's the full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

private var previousIndex:int = 0;
private var rightclickTabIndex:int = 0;

public var fontWindow:FontWindow = new FontWindow();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 {
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i &amp;lt; allWindows.length; i++)
{
allWindows[i].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);
}

private function onTabClose(evt:Event):void {
closeTab(tabBar.selectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", "Confirmation", Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
Alert.show("Perform save function here and then delete");
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}

private function doNew():void{
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
updateStatus();
}

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 &amp;lt; len; i++) {
if (i != rightclickTabIndex) {
closeTab(i);
}
}
}

private function tabRightClick(evt:MouseEvent):void {
var tabWidth:Number = tabBar.width / tabData.length;
var rcIndex:int = Math.floor(tabBar.mouseX / tabWidth);
rightclickTabIndex = rcIndex;
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-2423002669314881588?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/8dO87LKMqaE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/2423002669314881588/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-16.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2423002669314881588?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/2423002669314881588?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/8dO87LKMqaE/creating-flex-air-text-editor-part-16.html" title="Creating a Flex AIR text editor: Part 16" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-16.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEcCQHk5cSp7ImA9WhdaFU8.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-4721445410432737668</id><published>2011-10-25T10:01:00.000+03:00</published><updated>2011-10-25T10:01:01.729+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-25T10:01:01.729+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 15</title><content type="html">In this tutorial we will work on tab closing.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Before we start, there's one more thing we need to include in the tabChange() function that we created in the previous part. When changing between tabs, the text data and selection data is updated, however, the status bar is not. Thus, if word wrapping is off, the caret position is not updated. We can fix this by just calling the updateStatus() function here:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
updateStatus();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, let's get to creating the close functionality for the tabs!&lt;br /&gt;
&lt;br /&gt;
We already have the onTabClose() function, which is called when the user closes a tab using the X button. Let's create a closeTab() function, which we can then use whenever we know the index of the tab we want to remove. For now the code will only remove the tab that is selected (regardless of which tab was closed). This will be fixed in the future tutorials.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onTabClose(evt:Event):void {
closeTab(tabBar.selectedIndex);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We pass the index integer value and then check if the tab was saved or not (is confirmation required or not). If it is saved, remove the tab from the tabData array using the removeTab() function that we will create shortly. If it is not saved, ask for a confirmation using the Alert class. The confirmation asks the user if they want to save the file before closing. If the user chooses to save, then (for now) we just send a debug message, we will later put a function that handles saving the file before closing it. Otherwise, just close the tab.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", tabData[index].label, Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
Alert.show("Perform save function here and then delete");
}else {
removeTab(index);
}
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, that removeTab function... it basically deletes the item from the tabData array using the removeItemAt() function and the index value that we pass to the function. However, there are 2 things we need to remember - the first thing is that we need to set the new selected tab of the tabBar and update the text data and selection. The second thing is that if it is the last tab that we are removing, we need to create a new empty tab in its place.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
And that's pretty much it for today's tutorial!&lt;br /&gt;
&lt;br /&gt;
Here's the full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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 mx.events.ResizeEvent;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

private var previousIndex:Number = 0;

public var fontWindow:FontWindow = new FontWindow();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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 {
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i &amp;lt; allWindows.length; i++)
{
allWindows[i].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);
}

private function onTabClose(evt:Event):void {
closeTab(tabBar.selectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", tabData[index].label, Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
Alert.show("Perform save function here and then delete");
}else {
removeTab(index);
}
}
}

private function removeTab(index:int):void {
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
}
tabData.removeItemAt(index);
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}

private function doNew():void{
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
updateStatus();
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We still have a long way to go with this application. See you in next part!&lt;br /&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-4721445410432737668?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/BTs96MczKms" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/4721445410432737668/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-15.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/4721445410432737668?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/4721445410432737668?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/BTs96MczKms/creating-flex-air-text-editor-part-15.html" title="Creating a Flex AIR text editor: Part 15" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-15.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0ECQn04fSp7ImA9WhdaFE4.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-1625517875202471160</id><published>2011-10-24T10:01:00.000+03:00</published><updated>2011-10-24T10:01:03.335+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-24T10:01:03.335+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 14</title><content type="html">In this tutorial we will make our application run faster by optimizing the existing code a little, and also add the ability to create new tabs and change/save text and selection data among the tabs.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Firstly, we will optimize what we already have.&lt;br /&gt;
&lt;br /&gt;
Go to your everyFrame function and take out the code that's responsible for updating the text size, it will look like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
}
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Turn that piece of code into a separate function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Create a resizing listener in the init function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// Create a listener for resizing
addEventListener(NativeWindowBoundsEvent.RESIZE, onResize);
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the onResize function, call the updateTextSize method we just created:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onResize(evt:ResizeEvent):void {
updateTextSize();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Update the everyFrame function to call updateTextSize() too when the height is fixed:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now all the lagging that there might've been is fixed.&lt;br /&gt;
&lt;br /&gt;
Next, let's work on the tabs. Rename the tabHeadings array collection to tabData, because it isn't going to be used just for headings.&lt;br /&gt;
&lt;br /&gt;
Change the collection to this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
This is what each tab will look like on the code-side - it is going to have a title property, a textData property to store the text, a saved boolean to determine if this text was saved after last edit, and two properties to hold values of the selection.&lt;br /&gt;
&lt;br /&gt;
Change the dataProvider for your CustomTabBar to tabData instead of tabHeadings, and add a handler for the change event - direct it to a tabChange function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Declare a new variable in the beginning of the code, call it previousIndex. This will hold the index of the previously selected tab. Make it 0 by default:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private var previousIndex:Number = 0;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the tabChange function, set the previously selected tab's data to whatever it is in the textArea (the data and the selection point positions), then update the previousIndex property and then feed the textArea the new data from the newly selected tab:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now we will create the ability to add a new tab.&lt;br /&gt;
&lt;br /&gt;
Create a new menu item first under the File menu:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the menuSelect function, add a line for this menu item, which will call a doNew() function when this button is clicked:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;(evt.item.@label == "New")?(doNew()):(void);
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
The doNew() function adds a new item to the array collection and calls the tabChange() function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function doNew():void{
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Also, now that we don't have the somedata property in our tabData objects, you need to update the onTabClose() function to display something else in order for the code to work:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onTabClose(evt:Event):void{
Alert.show("Tab being closed: " + evt.target.data.label);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Finally, go to the CustomTab.mxml file. Find the updateDisplayList function, update it so that it first checks whether the object's saved property is true or false. If it is true, display title, if it is false, display title with a "*" symbol in the end.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;override protected function updateDisplayList(w:Number, h:Number):void
        {
            super.updateDisplayList(w,h);
            if (labelDisplay)
            {
                labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
            }
        }   
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code for CustomTab.mxml:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:ItemRenderer
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        width="100%"
        height="100"
        autoDrawBackground="false"
&amp;gt;   

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

    &amp;lt;fx:Script&amp;gt;
    &amp;lt;![CDATA[
    
import flash.events.Event;
        import mx.controls.Alert;
        import mx.events.CloseEvent;

        private var tab:*;
        override public function set data(value:Object):void
        {
            super.data = value;
            tab = value;
        }

        override protected function updateDisplayList(w:Number, h:Number):void
        {
            super.updateDisplayList(w,h);
            if (labelDisplay)
            {
                labelDisplay.text = (data.saved)?(data.title):(data.title + "*");
            }
        }              

        protected function labelClose_clickHandler(event:MouseEvent):void
        {
            // prevent tab change
            event.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
        }

    ]]&amp;gt;
    &amp;lt;/fx:Script&amp;gt;
   
    &amp;lt;s:states&amp;gt;
        &amp;lt;s:State name="normal" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="selected" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="hovered" basedOn="{data.state}"/&amp;gt;
    &amp;lt;/s:states&amp;gt;

    &amp;lt;!-- background --&amp;gt;
    &amp;lt;s:Rect left="1" right="1" top="1" bottom="0"&amp;gt;
        &amp;lt;s:fill&amp;gt;
            &amp;lt;s:LinearGradient rotation="90"&amp;gt;
                &amp;lt;s:GradientEntry color="0xffffff" /&amp;gt;
                &amp;lt;s:GradientEntry
                    color="0xd8d8d8"
                    alpha="0.85"
                    color.selected="0xffffff"
                    alpha.selected="1.0"
                    color.hovered="0x929496"
                    alpha.hovered="0.85"
                /&amp;gt;
            &amp;lt;/s:LinearGradient&amp;gt;
        &amp;lt;/s:fill&amp;gt;
    &amp;lt;/s:Rect&amp;gt;

    &amp;lt;!-- border rectangle --&amp;gt;
    &amp;lt;s:Line left="0" right="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line right="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" right="0" bottom="0"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
                alpha.selected="0.0"
                color.selected="0xffffff"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
   
    &amp;lt;s:Label
        id="labelDisplay"
        textAlign="center"
        verticalAlign="middle"
        maxDisplayedLines="1"
        horizontalCenter="0"
        verticalCenter="1"
        left="10"
        right="20"
        top="2"
        bottom="2"
    /&amp;gt;

    &amp;lt;s:Label
        id="labelClose"
        text="x"
        fontWeight="bold"
        right="4"
        top="2"
        fontSize="20"
        alpha=".5" 
color="#444444"
        click="labelClose_clickHandler(event)"
        useHandCursor="true"
        buttonMode="true"
    /&amp;gt;
&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code for main mxml:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.NativeWindowBoundsEvent;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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.ResizeEvent;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

private var previousIndex:Number = 0;

public var fontWindow:FontWindow = new FontWindow();

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 = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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{
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i &amp;lt; allWindows.length; i++)
{
allWindows[i].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);
}

private function onTabClose(evt:Event):void{
Alert.show("Tab being closed: " + evt.target.data.label);
}

private function doNew():void{
tabData.addItem( { title:"Untitled", textData:"", saved:false } );
tabBar.selectedIndex = tabData.length - 1;
tabChange();
}

private function tabChange():void {
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
previousIndex = tabBar.selectedIndex;
textArea.text = tabData[tabBar.selectedIndex].textData;
textArea.selectRange(tabData[tabBar.selectedIndex].selectedAnchor, tabData[tabBar.selectedIndex].selectedActive);
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="New" key="n" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabData"&amp;gt;
&amp;lt;fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabData}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange();" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-1625517875202471160?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/duOBcvl7sSY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/1625517875202471160/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-14.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/1625517875202471160?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/1625517875202471160?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/duOBcvl7sSY/creating-flex-air-text-editor-part-14.html" title="Creating a Flex AIR text editor: Part 14" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-14.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUUCQH0zeSp7ImA9WhdaE0g.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-8484123539445026057</id><published>2011-10-23T10:01:00.000+03:00</published><updated>2011-10-23T10:01:01.381+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-23T10:01:01.381+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 13</title><content type="html">In this tutorial we will add close buttons to our tabs in the tabbar, dispatch and catch events when the user closes a tab and determine which tab exactly was closed by reading its unique personal data from the ArrayCollection.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
In this tutorial we're going to create a few custom components. In the root tags, add a new namespace, I called it custom.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;xmlns:custom="*"
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Change the existing TabBar line to this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabHeadings}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As you can see, it is now a CustomTabBar with a custom namespace. It has a custom itemRenderer which is a CustomTab class. Also, we listen to the tabClose event (which is custom) and call an onTabClose(event); method.&lt;br /&gt;
&lt;br /&gt;
Let's edit the existing ArrayCollection to add some custom random data that we can read, just to make sure the application works.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ArrayCollection id="tabHeadings"&amp;gt;
&amp;lt;fx:Object label="Tab one" somedata="1" /&amp;gt;
&amp;lt;fx:Object label="Tab two" somedata="2"/&amp;gt;
&amp;lt;fx:Object label="Tab three" somedata="3"/&amp;gt;
&amp;lt;fx:Object label="Tab four" somedata="4"/&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Add a new onTabClose function, make it Alert a message like this:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function onTabClose(evt:Event):void{
Alert.show("Tab being closed: " + evt.target.data.somedata);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now create a new CustomTabBar.mxml file, which will be our custom tabbar component. The root tags will be Spark TabBar. Here we will also use Metadata tags to declare an event called tabClose.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:TabBar xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"&amp;gt;
    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;
&amp;lt;/s:TabBar&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We have set the itemRenderer property to a CustomTab class, so create a new CustomTab.mxml.&lt;br /&gt;
&lt;br /&gt;
Here's the full code for that class:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;s:ItemRenderer
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        width="100%"
        height="100"
        autoDrawBackground="false"
&amp;gt;   

    &amp;lt;fx:Metadata&amp;gt;
        [Event(name="tabClose")]
    &amp;lt;/fx:Metadata&amp;gt;

    &amp;lt;fx:Script&amp;gt;
    &amp;lt;![CDATA[
    
import flash.events.Event;
        import mx.controls.Alert;
        import mx.events.CloseEvent;

        private var tab:*;
        override public function set data(value:Object):void
        {
            super.data = value;
            tab = value;
        }

        override protected function updateDisplayList(w:Number, h:Number):void
        {
            super.updateDisplayList(w,h);
            if (labelDisplay)
            {
                labelDisplay.text = data.label
            }
        }              

        protected function labelClose_clickHandler(event:MouseEvent):void
        {
            // prevent tab change
            event.stopImmediatePropagation();
dispatchEvent(new Event("tabClose", true));
        }

    ]]&amp;gt;
    &amp;lt;/fx:Script&amp;gt;
   
    &amp;lt;s:states&amp;gt;
        &amp;lt;s:State name="normal" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="selected" basedOn="{data.state}"/&amp;gt;
        &amp;lt;s:State name="hovered" basedOn="{data.state}"/&amp;gt;
    &amp;lt;/s:states&amp;gt;

    &amp;lt;!-- background --&amp;gt;
    &amp;lt;s:Rect left="1" right="1" top="1" bottom="0"&amp;gt;
        &amp;lt;s:fill&amp;gt;
            &amp;lt;s:LinearGradient rotation="90"&amp;gt;
                &amp;lt;s:GradientEntry color="0xffffff" /&amp;gt;
                &amp;lt;s:GradientEntry
                    color="0xd8d8d8"
                    alpha="0.85"
                    color.selected="0xffffff"
                    alpha.selected="1.0"
                    color.hovered="0x929496"
                    alpha.hovered="0.85"
                /&amp;gt;
            &amp;lt;/s:LinearGradient&amp;gt;
        &amp;lt;/s:fill&amp;gt;
    &amp;lt;/s:Rect&amp;gt;

    &amp;lt;!-- border rectangle --&amp;gt;
    &amp;lt;s:Line left="0" right="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line right="0" bottom="0" top="1"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
    &amp;lt;s:Line left="0" right="0" bottom="0"&amp;gt;
        &amp;lt;s:stroke&amp;gt;
            &amp;lt;s:SolidColorStroke
                weight="1"
                alpha="1.0"
                color="0x999999"
                alpha.selected="0.0"
                color.selected="0xffffff"
            /&amp;gt;
        &amp;lt;/s:stroke&amp;gt;
    &amp;lt;/s:Line&amp;gt;
   
    &amp;lt;s:Label
        id="labelDisplay"
        textAlign="center"
        verticalAlign="middle"
        maxDisplayedLines="1"
        horizontalCenter="0"
        verticalCenter="1"
        left="10"
        right="20"
        top="2"
        bottom="2"
    /&amp;gt;

    &amp;lt;s:Label
        id="labelClose"
        text="x"
        fontWeight="bold"
        right="4"
        top="2"
        fontSize="20"
        alpha=".5" 
        color="#444444"
        click="labelClose_clickHandler(event)"
        useHandCursor="true"
        buttonMode="true"
    /&amp;gt;
&amp;lt;/s:ItemRenderer&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
As you can see, we declare the tabClose event in the Metadata tags too.&lt;br /&gt;
&lt;br /&gt;
The labelClose_clickHandler function dispatches this event, that we listen to from the main file. The rest of the code is mainly drawing the tabs. The base for this class code was taken from the Adobe website.&lt;br /&gt;
&lt;br /&gt;
And you're done!&lt;br /&gt;
&lt;br /&gt;
Here's the main file full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

public var fontWindow:FontWindow = new FontWindow();

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;

preferences.data.firsttime = null;

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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();
}

private function menuSelect(evt:FlexNativeMenuEvent):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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
}
}
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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{
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i &amp;lt; allWindows.length; i++)
{
allWindows[i].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);
}

private function onTabClose(evt:Event):void{
Alert.show("Tab being closed: " + evt.target.data.somedata);
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="File"&amp;gt;
&amp;lt;menuitem label="Open" key="o" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Edit"&amp;gt;
&amp;lt;menuitem label="Cut" key="x" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Copy" key="c" controlKey="true" /&amp;gt;
&amp;lt;menuitem label="Paste" key="v" controlKey="true" /&amp;gt;
&amp;lt;menuitem type="separator"/&amp;gt;
&amp;lt;menuitem label="Select all" key="a" controlKey="true" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="Settings"&amp;gt;
&amp;lt;menuitem label="Word wrap" type="check" toggled="{pref_wrap}" /&amp;gt;
&amp;lt;menuitem label="Font..."/&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;menuitem label="View"&amp;gt;
&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&amp;lt;menuitem label="Status bar" type="check" toggled="{pref_status}" /&amp;gt;
&amp;lt;/menuitem&amp;gt;
&amp;lt;/root&amp;gt;
&amp;lt;/fx:XML&amp;gt;
&amp;lt;mx:ArrayCollection id="tabHeadings"&amp;gt;
&amp;lt;fx:Object label="Tab one" somedata="1" /&amp;gt;
&amp;lt;fx:Object label="Tab two" somedata="2"/&amp;gt;
&amp;lt;fx:Object label="Tab three" somedata="3"/&amp;gt;
&amp;lt;fx:Object label="Tab four" somedata="4"/&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&amp;lt;/fx:Declarations&amp;gt;

&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;custom:CustomTabBar id="tabBar" dataProvider="{tabHeadings}" width="100%" y="{tabY}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;

&amp;lt;/s:WindowedApplication&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now when you try to close a tab, it alerts you the value of the somedata property for this tab object.&lt;br /&gt;
&lt;br /&gt;
Thanks for reading!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2631617199049210138-8484123539445026057?l=kirill-poletaev.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/KirillPoletaev/~4/vgobfz29lEE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://kirill-poletaev.blogspot.com/feeds/8484123539445026057/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-13.html#comment-form" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/8484123539445026057?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/2631617199049210138/posts/default/8484123539445026057?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/KirillPoletaev/~3/vgobfz29lEE/creating-flex-air-text-editor-part-13.html" title="Creating a Flex AIR text editor: Part 13" /><author><name>Kirill Poletaev</name><uri>http://www.blogger.com/profile/10345000564393362213</uri><email>noreply@blogger.com</email></author><thr:total>0</thr:total><feedburner:origLink>http://kirill-poletaev.blogspot.com/2011/10/creating-flex-air-text-editor-part-13.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DE8CQXYycSp7ImA9WhdaEks.&quot;"><id>tag:blogger.com,1999:blog-2631617199049210138.post-6215514183361734085</id><published>2011-10-22T10:01:00.000+03:00</published><updated>2011-10-22T10:01:00.899+03:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-10-22T10:01:00.899+03:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Flex 4" /><category scheme="http://www.blogger.com/atom/ns#" term="Adobe AIR" /><category scheme="http://www.blogger.com/atom/ns#" term="SQL" /><title>Creating a Flex AIR text editor: Part 12</title><content type="html">In this tutorial we will place toolbar and tabbar areas in our application and manage all the sizes.&lt;br /&gt;
&lt;a name='more'&gt;&lt;/a&gt;&lt;br /&gt;
Firstly, create a new ArrayCollection called tabHeadings. We will use it to test the tabbar that we will put under the native menu.&lt;br /&gt;
&lt;br /&gt;
Add this in the declarations tags:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;mx:ArrayCollection id="tabHeadings"&amp;gt;
&amp;lt;fx:Object label="Tab one" /&amp;gt;
&amp;lt;fx:Object label="Tab two" /&amp;gt;
&amp;lt;fx:Object label="Tab three" /&amp;gt;
&amp;lt;fx:Object label="Tab four" /&amp;gt;
&amp;lt;/mx:ArrayCollection&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now, we are going to declare variables that we will need to manage sizes and positions of certain elements on screen depending on what is toggled on and what size the elements are. We'll need to add textHeight, textY and tabY variables.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;[Bindable]
private var textHeight:Number;
[Bindable]
private var textY:Number;
[Bindable]
private var tabY:Number;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Also, while we're at it, we can add a pref_toolbar variable, which will be responsible for saving and storing toolbar data (it will be toggleable).&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;[Bindable]
private var pref_toolbar:Boolean = true;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Go to the place where you create the TextArea object. Put it in a Group object and add a TabBar and a Box (the container for toolbar items) inside. Set the TextArea's height and y values to textHeight and textY. Set the TabBar's dataProvider to tabHeading and y to tabY. The Box control is our toolbar - set its height to whatever for now and set its visible property to pref_toolbar.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;s:Group width="100%" height="100%"&amp;gt;
&amp;lt;s:TextArea id="textArea" width="100%" height="{textHeight}" y="{textY}" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();"/&amp;gt;
&amp;lt;mx:TabBar id="tabBar" dataProvider="{tabHeadings}" width="100%" y="{tabY}" /&amp;gt;
&amp;lt;mx:Box id="toolBar" width="100%" backgroundColor="#dddddd" height="24" visible="{pref_toolbar}"&amp;gt;
&amp;lt;/mx:Box&amp;gt;
&amp;lt;/s:Group&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Now go to the everyFrame function, the one that is executed every frame. Here we can set calculate and set values for the position and size variables. Also, here we can use the setFocus() method of the focusManager class to always set focus to textArea:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
}
}
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
We need this so that the text area, for example, won't be placed too high, so that it overlaps tabs or toolbar, or too low.&lt;br /&gt;
&lt;br /&gt;
Now we need to make our toolbar toggleable.&lt;br /&gt;
&lt;br /&gt;
Go to the init() function, update preference related stuff:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
When you're debugging, set firsttime to null before the check so that the conditional function is executed and the toolbar variable gets its value:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;preferences.data.firsttime = null;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
In the menuSelect function, add a new line that handles toolbar menu toggle - set the pref_toolbar's value to its opposite value.&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;private function menuSelect(evt:FlexNativeMenuEvent):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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Update the savePreferences() function:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;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.flush();
}
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Go to the declarations tags, add a new menu item in the View menu, set its toggled value to pref_toolbar:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" /&amp;gt;
&lt;/pre&gt;&lt;/div&gt;&lt;br /&gt;
Full code:&lt;br /&gt;
&lt;br /&gt;
&lt;div class="code"&gt;&lt;pre&gt;&amp;lt;?xml version="1.0" encoding="utf-8"?&amp;gt;
&amp;lt;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"
   creationComplete="init();" title="Kirpad" showStatusBar="{pref_status}"&amp;gt;
   
&amp;lt;s:menu&amp;gt;
&amp;lt;mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" /&amp;gt;
&amp;lt;/s:menu&amp;gt;

&amp;lt;fx:Script&amp;gt;
&amp;lt;![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
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;

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]
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 textY:Number;
[Bindable]
private var tabY:Number;

public var fontWindow:FontWindow = new FontWindow();

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;

preferences.data.firsttime = null;

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = true;
preferences.data.status = true;
preferences.data.toolbar = 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_fontsettings = preferences.data.fontsettings;

// 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();
}

private function menuSelect(evt:FlexNativeMenuEvent):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 == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

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.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 {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions&amp;gt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions&amp;lt;0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed &amp;&amp; height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
}
}
tabY = (toolBar.visible)?(toolBar.height):(0);
textY = tabBar.height + tabY;
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + "\t" + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("\n");
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{
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i &amp;lt; allWindows.length; i++)
{
allWindows[i].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);
}
]]&amp;gt;
&amp;lt;/fx:Script&amp;gt;

&amp;lt;fx:Declarations&amp;gt;
&amp;lt;fx:XML id="windowMenu"&amp;gt;
&amp;lt;root&amp;gt;
&amp;lt;menuitem label="
