Showing posts with label text. Show all posts
Showing posts with label text. Show all posts

Tuesday, February 3, 2015

Creating a Flex AIR text editor Part 32

In this tutorial we will add support for undoing and redoing cut and paste operations.

The Cut and Paste operations are made from scratch in our application, so naturally the built in undo and redo functions dont work with them. No worries, though! The UndoManager class is very versatile, we can create our own operation classes for practically anything. Lets do this.

Create a new class file, InsertOperation.as. Its a class which implements the IOperation interface and has 3 functions - the constructor, performUndo() and performRedo(). When the operation is added to the flow, it stays there and when needed, its performUndo and performRedo functions are called, where we write the code that we want to be executed when the operation should be undone or redone.

Create 6 private variables - previousText, currentText, textArea, previousSelectedActive, previousSelectedAnchor and currentRange.

The variables are going to store the text values and the selection values of the previous and current (updated/fresh/new) text data, as well as a reference to the text area itself. In the constructor, we capture these values from the parameter and set them to the variables.

We use the performUndo() function to set the text areas text value to the previous text (the one that was there before the operation was executed) and select the previous selected range. On preformRedo(), we set the text value to the current value and select the new selection range.

package  {

import flashx.undo.IOperation;
import spark.components.TextArea;

public class InsertOperation implements IOperation {

private var previousText:String;
private var currentText:String;
private var textArea:TextArea;
private var previousSelectedActive:int;
private var previousSelectedAnchor:int;
private var currentRange:int;

public function InsertOperation(_previousText:String, _currentText:String, _textArea:TextArea, _previousSelectedActive:int, _previousSelectedAnchor:int, _currentRange:int) {
previousText = _previousText;
currentText = _currentText;
textArea = _textArea;
previousSelectedActive = _previousSelectedActive;
previousSelectedAnchor = _previousSelectedAnchor;
currentRange = _currentRange;
}

public function performUndo():void {
textArea.text = previousText;
textArea.selectRange(previousSelectedAnchor, previousSelectedActive);
}

public function performRedo():void {
textArea.text = currentText;
textArea.selectRange(currentRange, currentRange);
}
}
}

Go to the main file, find insertText function. Here we will add a few lines of code that will send an InsertOperation instance to the undoManager operation flow, while passing all the necessary data as the parameters. Also, call textChange() function in the end of insertText():

private function insertText(str:String):void {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions>0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions<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;
var newText:String = preText + str + postText;

var operation:InsertOperation = new InsertOperation(textArea.text, newText, textArea, textArea.selectionActivePosition, textArea.selectionAnchorPosition, newSelectRange);
undoManager.pushUndo(operation);

textArea.text = newText;
textArea.selectRange(newSelectRange, newSelectRange);
textChange();
}

We now need to update doUndo() and doRedo(). When we perform the Undo operation, we need to add the operation weve undone to the redo flow, and otherwise in the doRedo function:

private function doUndo():void {
undoManager.pushRedo(undoManager.peekUndo());
undoManager.undo();
textChange();
}

private function doRedo():void {
undoManager.pushUndo(undoManager.peekRedo());
undoManager.redo();
textChange();
}

However, this is only required to do with the new InsertOperation operation types, becuase otherwise it is done by default anyway. Because of this, we can now undo and redo cuts and pastes, but, guess what! The normal text changes cant be undone and redone like you think theyd be.

This is a pretty common thing in programming - if you have 10 bugs in the code, fix one, and now you have 15 bugs in the code...

For now, we can go ahead and remove the line that is responsible for setting our undo manager object as the interaction manager of the text area in the init() function, leaving just this:

// Undo management
undoManager = new UndoManager();

One final thing that well do today is set focus to the text area in the textChange() function, because it sometimes goes off:

private function textChange():void{
canUndo = undoManager.canUndo();
canRedo = undoManager.canRedo();
focusManager.setFocus(textArea);
}

Full code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:custom="*"
creationComplete="init();" title="Kirpad" showStatusBar="{pref_status}"
minWidth="400" minHeight="200" height="700" width="900">

<s:menu>
<mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" />
</s:menu>

<fx:Script>
<![CDATA[
import flash.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();
}

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>0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions<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;
var newText:String = preText + str + postText;

var operation:InsertOperation = new InsertOperation(textArea.text, newText, textArea, textArea.selectionActivePosition, textArea.selectionAnchorPosition, newSelectRange);
undoManager.pushUndo(operation);

textArea.text = newText;
textArea.selectRange(newSelectRange, newSelectRange);
textChange();
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed && height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textX = (pref_linecount)?(lineCountWidth):(0);
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textWidth = (pref_sidepane)?(width - sidePaneWidth - textX):(width - textX);
tabWidth = textWidth + textX;
var tabbarScrollHeight:Number = (tabData.length * 170 > tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height + tabbarScrollHeight;
sidePaneY = textY - tabBar.height - tabbarScrollHeight;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + " " + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("
");
var line:int = lines.length;
var col:int = lines[lines.length - 1].length + 1;

return "Ln " + line + ", Col " + col;
}

private function doFont():void{
fontWindow.open();
fontWindow.activate();
fontWindow.visible = true;
fontWindow.setValues(pref_fontsettings.fontsize, pref_fontsettings.fontfamily, pref_fontsettings.fontstyle, pref_fontsettings.fontweight, pref_fontsettings.fontcolor, pref_fontsettings.bgcolor);
}

private function onClose(evt:Event):void {
if(!closeAfterConfirm){
evt.preventDefault();
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 1; i < allWindows.length; i++)
{
allWindows[i].close();
}

// Check if there are any unsaved tabs
var needSaving:Boolean = false;
tabsToClose = 0;

for (var u:int = 0; u < tabData.length; u++) {
if (tabData[u].saved == false) {
needSaving = true;
tabsToClose++;
}
}

// If there are unsaved tabs, dont close window yet, set closeAfterConfirm to true and close all tabs
if (needSaving) {
closeAfterConfirm = true;
for (var t:int = 0; t < tabData.length; t++) {
closeTab(t);
}
}
if (!needSaving) {
FlexGlobals.topLevelApplication.close();
}
}
}

private function fontChange(evt:Event):void{
pref_fontsettings.fontfamily = fontWindow.fontCombo.selectedItem.fontName;
pref_fontsettings.fontsize = fontWindow.sizeStepper.value;

if (fontWindow.styleCombo.selectedIndex == 0) {
pref_fontsettings.fontstyle = "normal";
pref_fontsettings.fontweight = "normal";
}
if (fontWindow.styleCombo.selectedIndex == 1) {
pref_fontsettings.fontstyle = "italic";
pref_fontsettings.fontweight = "normal";
}
if (fontWindow.styleCombo.selectedIndex == 2) {
pref_fontsettings.fontstyle = "normal";
pref_fontsettings.fontweight = "bold";
}
if (fontWindow.styleCombo.selectedIndex == 3) {
pref_fontsettings.fontstyle = "italic";
pref_fontsettings.fontweight = "bold";
}

pref_fontsettings.fontcolor = fontWindow.colorPicker.selectedColor;
pref_fontsettings.bgcolor = fontWindow.bgColorPicker.selectedColor;

savePreferences();
updateFonts();
}

private function updateFonts():void{
textArea.setStyle("fontFamily", pref_fontsettings.fontfamily);
textArea.setStyle("fontSize", pref_fontsettings.fontsize);
textArea.setStyle("fontStyle", pref_fontsettings.fontstyle);
textArea.setStyle("fontWeight", pref_fontsettings.fontweight);
textArea.setStyle("color", pref_fontsettings.fontcolor);
textArea.setStyle("contentBackgroundColor", pref_fontsettings.bgcolor);

lineCount.setStyle("fontFamily", pref_fontsettings.fontfamily);
lineCount.setStyle("fontSize", pref_fontsettings.fontsize);
lineCount.setStyle("fontStyle", pref_fontsettings.fontstyle);
lineCount.setStyle("fontWeight", pref_fontsettings.fontweight);
lineCount.setStyle("color", pref_fontsettings.fontcolor);
lineCount.setStyle("contentBackgroundColor", pref_fontsettings.bgcolor);
}

private function onTabClose(evt:Event):void {
var tabWidth:Number = tabBar.width / tabData.length;
var cIndex:int = Math.floor(tabBar.mouseX / tabWidth);
tabSelectedIndex = cIndex;
tabChange();
closeTab(tabSelectedIndex);
}

private function onListClose(evt:Event):void {
tabSelectedIndex = sideList.selectedIndex;
tabChange();
closeTab(tabSelectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
}
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 && 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 < 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 && !evt.shiftKey) {
if (tabData.length - tabSelectedIndex > 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 && evt.shiftKey) {
if (tabSelectedIndex > 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode >= 49 && evt.keyCode <= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length > num - 1) {
tabSelectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabSelectedIndex = tabData.length - 1;
tabChange();
}
}
}

private function closeSidePane():void{
pref_sidepane = !pref_sidepane
savePreferences();
updateTextSize();
}

private function countLines():void {
if (pref_linecount && !pref_wrap) {
var totalLines:int = textArea.text.split("
").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference > 0) {
for (var i:int = current + 1; i < (total+1); i++) {
lineNumbers += "
" + (i);
}
}
if (difference < 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u < -difference; u++) {
charsInTheEnd += ((current - u).toString().length + 1);
}
lineNumbers = lineCount.text.substring(0, lineCount.text.length - charsInTheEnd);
}
}

private function updateLineScroll():void{
lineCount.scroller.verticalScrollBar.value = textArea.scroller.verticalScrollBar.value;
}

private function doPrint():void {
var printJob:FlexPrintJob = new FlexPrintJob();
if (!printJob.start()) return;
tempText.visible = true;
tempText.setStyle("lineBreak", "toFit");
tempText.text = textArea.text;
tempText.width = printJob.pageWidth;
tempText.heightInLines = NaN;
tempText.setStyle("horizontalScrollPolicy", "off");
tempText.setStyle("verticalScrollPolicy", "off");
printJob.printAsBitmap = false;
printJob.addObject(tempText, "matchWidth");
printJob.send();
tempText.visible = false;
}

private function textChange():void{
canUndo = undoManager.canUndo();
canRedo = undoManager.canRedo();
focusManager.setFocus(textArea);
}

private function doUndo():void {
undoManager.pushRedo(undoManager.peekUndo());
undoManager.undo();
textChange();
}

private function doRedo():void {
undoManager.pushUndo(undoManager.peekRedo());
undoManager.redo();
textChange();
}
]]>
</fx:Script>

<fx:Declarations>
<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Print" key="p" controlKey="true" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" />
<menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" />
<menuitem type="separator"/>
<menuitem label="Cut" key="x" controlKey="true" />
<menuitem label="Copy" key="c" controlKey="true" />
<menuitem label="Paste" key="v" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Select all" key="a" controlKey="true" />
</menuitem>
<menuitem label="Settings">
<menuitem label="Word wrap" type="check" toggled="{pref_wrap}" />
<menuitem label="Font..."/>
</menuitem>
<menuitem label="View">
<menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" />
<menuitem label="Status bar" type="check" toggled="{pref_status}" />
<menuitem label="Line count" type="check" toggled="{pref_linecount}" />
<menuitem label="Side pane" type="check" toggled="{pref_sidepane}" />
</menuitem>
</root>
</fx:XML>
<mx:ArrayCollection id="tabData">
<fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneData">
<fx:Object icon="@Embed(../lib/page.png)" tip="Tab management" />
<fx:Object icon="@Embed(../lib/folder_magnify.png)" tip="File browsing" />
<fx:Object icon="@Embed(../lib/book.png)" tip="Snippets" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneTabHeadings">
<fx:String>Tab management</fx:String>
<fx:String>File browsing</fx:String>
<fx:String>Snippets</fx:String>
</mx:ArrayCollection>
</fx:Declarations>

<s:Group width="100%" height="100%">
<s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?(toFit):(explicit)}" click="cursorFix(); updateStatus();" change="updateStatus(); countLines(); textChange();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" />
<s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}">
<s:Group>
<custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange(tabbar);" selectedIndex="{tabSelectedIndex}">
<custom:layout>
<s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/>
</custom:layout>
</custom:CustomTabBar>
</s:Group>
</s:Scroller>
<s:TextArea id="lineCount" width="{lineCountWidth}" text="{lineNumbers}" visible="{pref_linecount}" height="{textHeight}" y="{textY}" editable="false" selectable="false" mouseEnabled="false" textAlign="right" verticalScrollPolicy="off" horizontalScrollPolicy="off" />
<mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3">
<custom:IconButton icon="@Embed(../lib/page.png)" toolTip="New document" click="doNew();" />
<custom:IconButton icon="@Embed(../lib/folder_page.png)" toolTip="Open" />
<custom:IconButton icon="@Embed(../lib/disk.png)" toolTip="Save" />
<custom:IconButton icon="@Embed(../lib/disk_multiple.png)" toolTip="Save all" />
<custom:IconButton icon="@Embed(../lib/printer.png)" toolTip="Print" click="doPrint();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/arrow_undo.png)" toolTip="Undo" enabled="{canUndo}" click="doUndo();" />
<custom:IconButton icon="@Embed(../lib/arrow_redo.png)" toolTip="Redo" enabled="{canRedo}" click="doRedo();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/cut.png)" toolTip="Cut" click="doCut();" />
<custom:IconButton icon="@Embed(../lib/page_white_copy.png)" toolTip="Copy" click="doCopy();" />
<custom:IconButton icon="@Embed(../lib/paste_plain.png)" toolTip="Paste" click="doPaste();" />
</mx:HBox>
<mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off">
<s:Group>
<s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" />
<mx:Image source="@Embed(../lib/bullet_go.png)" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/>
</s:Group>
<mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" />
<mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}">
<s:NavigatorContent id="tabs">
<custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange(sidelist);" tabClose="onListClose(event);" />
</s:NavigatorContent>
<s:NavigatorContent id="files">
<mx:FileSystemTree height="100%" width="100%" />
</s:NavigatorContent>
<s:NavigatorContent id="snippets">
<s:VGroup height="100%" width="100%">
<mx:Tree height="100%" width="100%" />
<s:Button width="100%" label="New snippet" />
<s:Button width="100%" label="Manage snippets" />
</s:VGroup>
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</s:Group>
<s:TextArea id="tempText" borderVisible="false" visible="false"/>

</s:WindowedApplication>

Lets recap - we have working undo/redo functionality working for Cut and Paste commands. We still need to
a) add Undo/Redo functionality to normal text changes (such as typing text, going to new line, deleting text);
b) add Undo/Redo functionality to tab changes.

Thanks for reading!
Read more »

Monday, February 2, 2015

Creating a Flex AIR text editor Part 38

In this tutorial we will add a Save As feature.

We need to update the doSave() function, which will now call doSaveAs() function if the location property of the current tabData element is blank. Also, we will turn all the code that refreshes the data providers into a function called refreshData, and will not call it during doSave(), but instead of that, we will call it later in the saveFile() function.

Because of that, we need to pass the updateIndexNeeded variable as the third parameter for the saveFile function. For doSaveAs(), we need to pass the text value, index of the element in the array and the updateIndexNeeded variable:

private function doSave(ind:int, updateIndexNeeded:Boolean = true):void {
if (tabData[ind].location != "") {
saveFile(textArea.text, tabData[ind].location, updateIndexNeeded);
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + tabData[ind].title + " saved";
updateStatus();
tabData[ind].saved = true;
}else{
doSaveAs(textArea.text, ind, updateIndexNeeded);
}
}

private function refreshData():void {
tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;

sideList.dataProvider = tabData;
}

Update saveFile() function by adding the third parameter and a conditional, which calls refreshData() if the value of this variable is true:

private function saveFile(text:String, location:String, updateIndexNeeded:Boolean = true):void {
var file:File = new File(location);
var stream:FileStream = new FileStream();
stream.open(file, FileMode.WRITE);
var str:String = text;
str = str.replace(/
/g, File.lineEnding);
stream.writeUTFBytes(str);
stream.close();
if (updateIndexNeeded) {
refreshData();
}
}

Now lets write the doSaveAs() function, which receives 3 parameters. Here we will create a file object, call its browseForSave() method and on select event we check if the file has extension written to it by the user and if it doesnt - add .txt by default:

private function doSaveAs(text:String, ind:int, updateIndexNeeded:Boolean = true):void {
var file:File = new File();
file.browseForSave("Save file");
file.addEventListener(Event.SELECT, fileSave);

function fileSave(evt:Event):void {
if (file.name.length > 0) {
// See if user entered extension for the file (for example .txt)
// If not, add .txt by default
var extReg:RegExp = /.([a-z0-9]{2,})/i;
if (extReg.test(file.name)) {
saveFile(text, file.nativePath, updateIndexNeeded);
tabData[ind].location = file.nativePath;
tabData[ind].title = file.name;
tabData[ind].saved = true;
}else{
saveFile(text, file.nativePath + ".txt", updateIndexNeeded);
tabData[ind].location = file.nativePath + ".txt";
tabData[ind].title = file.name + ".txt";
tabData[ind].saved = true;
}
}else{
Alert.show("You need to enter a name for your file.", "Error");
}
}
}

Now lets update the menu XML and add the save as button. Set its both controlKey and shiftKey to true, so that the hotkey combination would be Ctrl+Shift+S:

<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As" key="s" controlKey="true" shiftKey="true" />
<menuitem type="separator"/>
<menuitem label="Print" key="p" controlKey="true" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" />
<menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" />
<menuitem type="separator"/>
<menuitem label="Cut" key="x" controlKey="true" />
<menuitem label="Copy" key="c" controlKey="true" />
<menuitem label="Paste" key="v" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Select all" key="a" controlKey="true" />
</menuitem>
<menuitem label="Settings">
<menuitem label="Word wrap" type="check" toggled="{pref_wrap}" />
<menuitem label="Font..."/>
</menuitem>
<menuitem label="View">
<menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" />
<menuitem label="Status bar" type="check" toggled="{pref_status}" />
<menuitem label="Line count" type="check" toggled="{pref_linecount}" />
<menuitem label="Side pane" type="check" toggled="{pref_sidepane}" />
</menuitem>
</root>
</fx:XML>

Now add a conditional for this button in the menuSelect function. Pass the text value and the selected index to the doSaveAs function that were calling:

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(void);
(evt.item.@label == "Open")?(doOpen()):(void);
(evt.item.@label == "Save")?(doSave(tabSelectedIndex)):(void);
(evt.item.@label == "Save As")?(doSaveAs(textArea.text, tabSelectedIndex)):(void);
(evt.item.@label == "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();
}

And thats it! Full code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:custom="*"
creationComplete="init();" title="Kirpad" showStatusBar="{pref_status}"
minWidth="400" minHeight="200" height="700" width="900">

<s:menu>
<mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" />
</s:menu>

<fx:Script>
<![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.net.SharedObject;
import flashx.textLayout.accessibility.TextAccImpl;
import flashx.textLayout.edit.EditManager;
import flashx.textLayout.edit.TextScrap;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.controls.TextArea;
import mx.events.FlexNativeMenuEvent;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.Configuration;
import flash.system.System;
import flash.desktop.Clipboard;
import flash.desktop.ClipboardFormats;
import flash.ui.Mouse;
import mx.events.CloseEvent;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.events.ContextMenuEvent;
import mx.events.ResizeEvent;
import mx.core.FlexGlobals;
import mx.printing.FlexPrintJob;
import mx.printing.FlexPrintJobScaleType;
import flashx.undo.UndoManager;
import flashx.textLayout.operations.UndoOperation;

private var preferences:SharedObject = SharedObject.getLocal("kirpadPreferences");
[Bindable]
private var pref_wrap:Boolean = true;
[Bindable]
private var pref_status:Boolean = true;
[Bindable]
private var pref_toolbar:Boolean = true;
[Bindable]
private var pref_sidepane:Boolean = true;
[Bindable]
private var pref_linecount:Boolean = true;
[Bindable]
public var pref_fontsettings:Object = new Object();

private var initHeight:Number;
private var heightFixed:Boolean = false;

private var statusMessage:String;
[Bindable]
private var textHeight:Number;
[Bindable]
private var textWidth:Number;
[Bindable]
private var textY:Number;
[Bindable]
private var textX:Number;
[Bindable]
private var tabY:Number;
[Bindable]
private var sidePaneY:Number;
[Bindable]
private var sidePaneX:Number;
[Bindable]
private var sidePaneHeight:Number;
[Bindable]
private var sidePaneWidth:Number = 180;
[Bindable]
private var sideContentWidth:Number = 170;
[Bindable]
private var tabWidth:Number;
[Bindable]
private var lineCountWidth:Number = 40;
[Bindable]
private var lineNumbers:String = "1";
[Bindable]
private var lineDisplayedNum:int = 1;

[Bindable]
private var tabSelectedIndex:int = 0;

[Bindable]
private var canUndo:Boolean = false;
[Bindable]
private var canRedo:Boolean = false;

private var previousTextInOperation:String = "";
private var currentTextInOperation:String = "";

private var previousIndex:int = 0;
private var rightclickTabIndex:int = 0;
private var untitledNum:int = 0;
private var tabsToClose:int = 0;
private var closeAfterConfirm:Boolean = false;

public var fontWindow:FontWindow = new FontWindow();

private var undoManager:UndoManager;
private var editManager:EditManager;

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();
editManager = new EditManager(undoManager);
textArea.textFlow.interactionManager = editManager;
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(void);
(evt.item.@label == "Open")?(doOpen()):(void);
(evt.item.@label == "Save")?(doSave(tabSelectedIndex)):(void);
(evt.item.@label == "Save As")?(doSaveAs(textArea.text, tabSelectedIndex)):(void);
(evt.item.@label == "Word wrap")?(pref_wrap = !pref_wrap):(void);
(evt.item.@label == "Cut")?(doCut()):(void);
(evt.item.@label == "Copy")?(doCopy()):(void);
(evt.item.@label == "Paste")?(doPaste()):(void);
(evt.item.@label == "Select all")?(doSelectall()):(void);
(evt.item.@label == "Status bar")?(pref_status = !pref_status):(void);
(evt.item.@label == "Tool bar")?(pref_toolbar = !pref_toolbar):(void);
(evt.item.@label == "Side pane")?(pref_sidepane = !pref_sidepane):(void);
(evt.item.@label == "Line count")?(pref_linecount = !pref_linecount):(void);
(evt.item.@label == "Font...")?(doFont()):(void);
(evt.item.@label == "Print")?(doPrint()):(void);
(evt.item.@label == "Undo")?(doUndo()):(void);
(evt.item.@label == "Redo")?(doRedo()):(void);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}

private function savePreferences():void {
preferences.data.wrap = pref_wrap;
preferences.data.status = pref_status;
preferences.data.toolbar = pref_toolbar;
preferences.data.fontsettings = pref_fontsettings;
preferences.data.sidepane = pref_sidepane;
preferences.data.linecount = pref_linecount;
preferences.flush();
}

private function doCut():void {
var selectedText:String = textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition);
System.setClipboard(selectedText);
insertText("");
}

private function doCopy():void {
var selectedText:String = textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition);
System.setClipboard(selectedText);
}

private function doPaste():void{
var myClip:Clipboard = Clipboard.generalClipboard;
var pastedText:String = myClip.getData(ClipboardFormats.TEXT_FORMAT) as String;
insertText(pastedText);
}

private function doSelectall():void {
textArea.selectAll();
}

private function insertText(str:String):void {
editManager.insertText(str);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed && height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
if (sideList.selectedIndices.length==0) {
sideList.selectedIndex = tabSelectedIndex;
}
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textX = (pref_linecount)?(lineCountWidth):(0);
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textWidth = (pref_sidepane)?(width - sidePaneWidth - textX):(width - textX);
tabWidth = textWidth + textX;
var tabbarScrollHeight:Number = (tabData.length * 170 > tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height + tabbarScrollHeight;
sidePaneY = textY - tabBar.height - tabbarScrollHeight;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = str + " " + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("
");
var line:int = lines.length;
var col:int = lines[lines.length - 1].length + 1;

return "Ln " + line + ", Col " + col;
}

private function doFont():void{
fontWindow.open();
fontWindow.activate();
fontWindow.visible = true;
fontWindow.setValues(pref_fontsettings.fontsize, pref_fontsettings.fontfamily, pref_fontsettings.fontstyle, pref_fontsettings.fontweight, pref_fontsettings.fontcolor, pref_fontsettings.bgcolor);
}

private function onClose(evt:Event):void {
if(!closeAfterConfirm){
evt.preventDefault();
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 1; i < allWindows.length; i++)
{
allWindows[i].close();
}

// Check if there are any unsaved tabs
var needSaving:Boolean = false;
tabsToClose = 0;

for (var u:int = 0; u < tabData.length; u++) {
if (tabData[u].saved == false) {
needSaving = true;
tabsToClose++;
}
}

// If there are unsaved tabs, dont close window yet, set closeAfterConfirm to true and close all tabs
if (needSaving) {
closeAfterConfirm = true;
for (var t:int = 0; t < tabData.length; t++) {
closeTab(t);
}
}
if (!needSaving) {
FlexGlobals.topLevelApplication.close();
}
}
}

private function fontChange(evt:Event):void{
pref_fontsettings.fontfamily = fontWindow.fontCombo.selectedItem.fontName;
pref_fontsettings.fontsize = fontWindow.sizeStepper.value;

if (fontWindow.styleCombo.selectedIndex == 0) {
pref_fontsettings.fontstyle = "normal";
pref_fontsettings.fontweight = "normal";
}
if (fontWindow.styleCombo.selectedIndex == 1) {
pref_fontsettings.fontstyle = "italic";
pref_fontsettings.fontweight = "normal";
}
if (fontWindow.styleCombo.selectedIndex == 2) {
pref_fontsettings.fontstyle = "normal";
pref_fontsettings.fontweight = "bold";
}
if (fontWindow.styleCombo.selectedIndex == 3) {
pref_fontsettings.fontstyle = "italic";
pref_fontsettings.fontweight = "bold";
}

pref_fontsettings.fontcolor = fontWindow.colorPicker.selectedColor;
pref_fontsettings.bgcolor = fontWindow.bgColorPicker.selectedColor;

savePreferences();
updateFonts();
}

private function updateFonts():void{
textArea.setStyle("fontFamily", pref_fontsettings.fontfamily);
textArea.setStyle("fontSize", pref_fontsettings.fontsize);
textArea.setStyle("fontStyle", pref_fontsettings.fontstyle);
textArea.setStyle("fontWeight", pref_fontsettings.fontweight);
textArea.setStyle("color", pref_fontsettings.fontcolor);
textArea.setStyle("contentBackgroundColor", pref_fontsettings.bgcolor);

lineCount.setStyle("fontFamily", pref_fontsettings.fontfamily);
lineCount.setStyle("fontSize", pref_fontsettings.fontsize);
lineCount.setStyle("fontStyle", pref_fontsettings.fontstyle);
lineCount.setStyle("fontWeight", pref_fontsettings.fontweight);
lineCount.setStyle("color", pref_fontsettings.fontcolor);
lineCount.setStyle("contentBackgroundColor", pref_fontsettings.bgcolor);
}

private function onTabClose(evt:Event):void {
var tabWidth:Number = tabBar.width / tabData.length;
var cIndex:int = Math.floor(tabBar.mouseX / tabWidth);
tabSelectedIndex = cIndex;
tabChange();
closeTab(tabSelectedIndex);
}

private function onListClose(evt:Event):void {
tabSelectedIndex = sideList.selectedIndex;
tabChange();
closeTab(tabSelectedIndex);
}

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
} else
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", "Confirmation", Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
tabsToClose--;
if (evt.detail == Alert.YES) {
doSave(index, false);
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, location:""} );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
tabSelectedIndex = tabBar.selectedIndex;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
if (closeAfterConfirm && tabsToClose == 0) {
FlexGlobals.topLevelApplication.close();
}
countLines();
updateTextSize();
undoManager.clearAll();
textChange();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false, location:""} );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

public function tabChange(from:String = "none", ind:int = 0, createOperation:Boolean = true):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
}
if (from == "operation") {
tabSelectedIndex = ind;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;
if (createOperation) {
var operation:TabOperation = new TabOperation(previousIndex, tabSelectedIndex, undoManager);
undoManager.pushUndo(operation);
}
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
updateStatus();
countLines();
textChange();
}

private function tabContextClose(evt:ContextMenuEvent):void{
closeTab(rightclickTabIndex);
}

private function tabContextCloseOther(evt:ContextMenuEvent):void {
var len:int = tabData.length;
for (var i:int = 0; i < len; i++) {
if (i != rightclickTabIndex) {
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 && !evt.shiftKey) {
if (tabData.length - tabSelectedIndex > 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 && evt.shiftKey) {
if (tabSelectedIndex > 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode >= 49 && evt.keyCode <= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length > num - 1) {
tabSelectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabSelectedIndex = tabData.length - 1;
tabChange();
}
}
}

private function closeSidePane():void{
pref_sidepane = !pref_sidepane
savePreferences();
updateTextSize();
}

private function countLines():void {
if (pref_linecount && !pref_wrap) {
var totalLines:int = textArea.text.split("
").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference > 0) {
for (var i:int = current + 1; i < (total+1); i++) {
lineNumbers += "
" + (i);
}
}
if (difference < 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u < -difference; u++) {
charsInTheEnd += ((current - u).toString().length + 1);
}
lineNumbers = lineCount.text.substring(0, lineCount.text.length - charsInTheEnd);
}
}

private function updateLineScroll():void{
lineCount.scroller.verticalScrollBar.value = textArea.scroller.verticalScrollBar.value;
}

private function doPrint():void {
var printJob:FlexPrintJob = new FlexPrintJob();
if (!printJob.start()) return;
tempText.visible = true;
tempText.setStyle("lineBreak", "toFit");
tempText.text = textArea.text;
tempText.width = printJob.pageWidth;
tempText.heightInLines = NaN;
tempText.setStyle("horizontalScrollPolicy", "off");
tempText.setStyle("verticalScrollPolicy", "off");
printJob.printAsBitmap = false;
printJob.addObject(tempText, "matchWidth");
printJob.send();
tempText.visible = false;
}

private function textChange():void{
canUndo = undoManager.canUndo();
canRedo = undoManager.canRedo();
focusManager.setFocus(textArea);
}

private function doUndo():void {
undoManager.undo();
textChange();
}

private function doRedo():void {
undoManager.redo();
textChange();
}

private function doOpen():void {
var file:File = new File();
file.browseForOpen("Open document", [new FileFilter("Text documents", "*.txt"), new FileFilter("All files", "*")]);
file.addEventListener(Event.SELECT, fileLoad);

function fileLoad(evt:Event):void {
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var str:String = stream.readUTFBytes(stream.bytesAvailable);
stream.close();
str = str.replace(File.lineEnding, "
");
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + file.name + " opened";
updateStatus();
tabData.addItem( { title:file.name, textData: str, saved:true, location:file.nativePath} );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}
}

private function saveUpdate():void {
if (tabData[tabSelectedIndex].saved) {
tabData[tabSelectedIndex].saved = false;

tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;

sideList.dataProvider = tabData;
}
}

private function doSave(ind:int, updateIndexNeeded:Boolean = true):void {
if (tabData[ind].location != "") {
saveFile(textArea.text, tabData[ind].location, updateIndexNeeded);
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + tabData[ind].title + " saved";
updateStatus();
tabData[ind].saved = true;
}else{
doSaveAs(textArea.text, ind, updateIndexNeeded);
}
}

private function refreshData():void {
tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;

sideList.dataProvider = tabData;
}

private function doSaveAs(text:String, ind:int, updateIndexNeeded:Boolean = true):void {
var file:File = new File();
file.browseForSave("Save file");
file.addEventListener(Event.SELECT, fileSave);

function fileSave(evt:Event):void {
if (file.name.length > 0) {
// See if user entered extension for the file (for example .txt)
// If not, add .txt by default
var extReg:RegExp = /.([a-z0-9]{2,})/i;
if (extReg.test(file.name)) {
saveFile(text, file.nativePath, updateIndexNeeded);
tabData[ind].location = file.nativePath;
tabData[ind].title = file.name;
tabData[ind].saved = true;
}else{
saveFile(text, file.nativePath + ".txt", updateIndexNeeded);
tabData[ind].location = file.nativePath + ".txt";
tabData[ind].title = file.name + ".txt";
tabData[ind].saved = true;
}
}else{
Alert.show("You need to enter a name for your file.", "Error");
}
}
}

private function saveFile(text:String, location:String, updateIndexNeeded:Boolean = true):void {
var file:File = new File(location);
var stream:FileStream = new FileStream();
stream.open(file, FileMode.WRITE);
var str:String = text;
str = str.replace(/
/g, File.lineEnding);
stream.writeUTFBytes(str);
stream.close();
if (updateIndexNeeded) {
refreshData();
}
}
]]>
</fx:Script>

<fx:Declarations>
<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As" key="s" controlKey="true" shiftKey="true" />
<menuitem type="separator"/>
<menuitem label="Print" key="p" controlKey="true" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" />
<menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" />
<menuitem type="separator"/>
<menuitem label="Cut" key="x" controlKey="true" />
<menuitem label="Copy" key="c" controlKey="true" />
<menuitem label="Paste" key="v" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Select all" key="a" controlKey="true" />
</menuitem>
<menuitem label="Settings">
<menuitem label="Word wrap" type="check" toggled="{pref_wrap}" />
<menuitem label="Font..."/>
</menuitem>
<menuitem label="View">
<menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" />
<menuitem label="Status bar" type="check" toggled="{pref_status}" />
<menuitem label="Line count" type="check" toggled="{pref_linecount}" />
<menuitem label="Side pane" type="check" toggled="{pref_sidepane}" />
</menuitem>
</root>
</fx:XML>
<mx:ArrayCollection id="tabData">
<fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" location="" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneData">
<fx:Object icon="@Embed(../lib/page.png)" tip="Tab management" />
<fx:Object icon="@Embed(../lib/folder_magnify.png)" tip="File browsing" />
<fx:Object icon="@Embed(../lib/book.png)" tip="Snippets" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneTabHeadings">
<fx:String>Tab management</fx:String>
<fx:String>File browsing</fx:String>
<fx:String>Snippets</fx:String>
</mx:ArrayCollection>
</fx:Declarations>

<s:Group width="100%" height="100%">
<s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?(toFit):(explicit)}" click="cursorFix(); updateStatus();" change="updateStatus(); countLines(); textChange(); saveUpdate();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" />
<s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}">
<s:Group>
<custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange(tabbar);" selectedIndex="{tabSelectedIndex}" labelField="saved">
<custom:layout>
<s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/>
</custom:layout>
</custom:CustomTabBar>
</s:Group>
</s:Scroller>
<s:TextArea id="lineCount" width="{lineCountWidth}" text="{lineNumbers}" visible="{pref_linecount}" height="{textHeight}" y="{textY}" editable="false" selectable="false" mouseEnabled="false" textAlign="right" verticalScrollPolicy="off" horizontalScrollPolicy="off" />
<mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3">
<custom:IconButton icon="@Embed(../lib/page.png)" toolTip="New document" click="doNew();" />
<custom:IconButton icon="@Embed(../lib/folder_page.png)" toolTip="Open" click="doOpen();" />
<custom:IconButton icon="@Embed(../lib/disk.png)" toolTip="Save" click="doSave(tabSelectedIndex);" />
<custom:IconButton icon="@Embed(../lib/disk_multiple.png)" toolTip="Save all" />
<custom:IconButton icon="@Embed(../lib/printer.png)" toolTip="Print" click="doPrint();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/arrow_undo.png)" toolTip="Undo" enabled="{canUndo}" click="doUndo();" />
<custom:IconButton icon="@Embed(../lib/arrow_redo.png)" toolTip="Redo" enabled="{canRedo}" click="doRedo();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/cut.png)" toolTip="Cut" click="doCut();" />
<custom:IconButton icon="@Embed(../lib/page_white_copy.png)" toolTip="Copy" click="doCopy();" />
<custom:IconButton icon="@Embed(../lib/paste_plain.png)" toolTip="Paste" click="doPaste();" />
</mx:HBox>
<mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off">
<s:Group>
<s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" />
<mx:Image source="@Embed(../lib/bullet_go.png)" top="-4" right="15" click="closeSidePane();" useHandCursor="true" buttonMode="true"/>
</s:Group>
<mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" />
<mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}">
<s:NavigatorContent id="tabs">
<custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange(sidelist);" tabClose="onListClose(event);" />
</s:NavigatorContent>
<s:NavigatorContent id="files">
<mx:FileSystemTree height="100%" width="100%" />
</s:NavigatorContent>
<s:NavigatorContent id="snippets">
<s:VGroup height="100%" width="100%">
<mx:Tree height="100%" width="100%" />
<s:Button width="100%" label="New snippet" />
<s:Button width="100%" label="Manage snippets" />
</s:VGroup>
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</s:Group>
<s:TextArea id="tempText" borderVisible="false" visible="false"/>

</s:WindowedApplication>

The problem with the code right now is that when you just have 1 tab open and you close the whole application and choose to save the file, you arent able to because the appication just closes. We will fix that next time.

Thanks for reading!
Read more »

Creating a Flex AIR text editor Part 85

In this tutorial we will make it possible to turn the "Keep previous tabs" feature on and off.

The first thing we need to do is create a new item in the XML object in the File submenu called Keep previous tabs. Set its type to check and toggled attribute bound to pref_keepprev:

<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem label="Recent">
</menuitem>
<menuitem type="separator"/>
<menuitem label="Rename..." enabled="{hasLocation}" />
<menuitem label="Delete file" enabled="{hasLocation}" />
<menuitem label="Reload file" enabled="{hasLocation}" />
<menuitem type="separator"/>
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As" key="s" controlKey="true" shiftKey="true" />
<menuitem label="Save All" key="s" controlKey="true" altKey="true" />
<menuitem type="separator"/>
<menuitem label="Print" key="p" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Keep previous tabs" type="check" toggled="{pref_keepprev}" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" />
<menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" />
<menuitem type="separator"/>
<menuitem label="Cut" key="x" controlKey="true" enabled="{textArea.selectionActivePosition!=textArea.selectionAnchorPosition}" />
<menuitem label="Copy" key="c" controlKey="true" enabled="{textArea.selectionActivePosition!=textArea.selectionAnchorPosition}" />
<menuitem label="Paste" key="v" controlKey="true" enabled="{canPaste}" />
<menuitem type="separator"/>
<menuitem label="Select all" key="a" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Find/Replace..." key="f" controlKey="true" />
</menuitem>
<menuitem label="Insert">
<menuitem label="From file..." />
<menuitem label="File details" enabled="{hasLocation}"/>
<menuitem label="Timestamp" />
</menuitem>
<menuitem label="Settings">
<menuitem label="Word wrap" type="check" toggled="{pref_wrap}" />
<menuitem label="Font..."/>
</menuitem>
<menuitem label="View">
<menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" />
<menuitem label="Status bar" type="check" toggled="{pref_status}" />
<menuitem label="Line count" type="check" toggled="{pref_linecount}" />
<menuitem label="Side pane" type="check" toggled="{pref_sidepane}" />
</menuitem>
<menuitem label="Encoding">
<menuitem label="UTF-8" type="check" toggled="{enc.getItemAt(0)}" ind="0" encoding="utf-8" />
<menuitem label="Unicode" type="check" toggled="{enc.getItemAt(1)}" ind="1" encoding="unicode" />
<menuitem label="ASCII" type="check" toggled="{enc.getItemAt(2)}" ind="2" encoding="us-ascii" />
</menuitem>
</root>
</fx:XML>

Now declare this pref_keepprev variable:

[Bindable]
private var pref_keepprev:Boolean = true;

Update the init() function to add lines that manage this variable:

// 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.recent = [];
preferences.data.previously = [];
preferences.data.keepprev = 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;
pref_previously = new ArrayCollection(preferences.data.previously);
pref_recent = new ArrayCollection(preferences.data.recent);
pref_keepprev = preferences.data.keepprev;

Also remember to set preferences.data.firsttime to null before running the whole code for the first time, comment it after the first time.

Update savePreferences() function:

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.data.previously = pref_previously.toArray();
preferences.data.recent = pref_recent.toArray();
preferences.data.keepprev = pref_keepprev;
preferences.flush();
}

Go to menuSelect() function and add a line which checks for the "Keep previous tabs" item to be selected and toggles pref_keepprev on selection:

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(void);
(evt.item.@label == "Open")?(doOpen()):(void);
(evt.item.@label == "Save")?(doSave(tabSelectedIndex)):(void);
(evt.item.@label == "Save As")?(doSaveAs(textArea.text, tabSelectedIndex)):(void);
(evt.item.@label == "Save All")?(doSaveAll()):(void);
(evt.item.@label == "Word wrap")?(pref_wrap = !pref_wrap):(void);
(evt.item.@label == "Cut")?(doCut()):(void);
(evt.item.@label == "Copy")?(doCopy()):(void);
(evt.item.@label == "Paste")?(doPaste()):(void);
(evt.item.@label == "Select all")?(doSelectall()):(void);
(evt.item.@label == "Status bar")?(pref_status = !pref_status):(void);
(evt.item.@label == "Tool bar")?(pref_toolbar = !pref_toolbar):(void);
(evt.item.@label == "Side pane")?(pref_sidepane = !pref_sidepane):(void);
(evt.item.@label == "Line count")?(pref_linecount = !pref_linecount):(void);
(evt.item.@label == "Font...")?(doFont()):(void);
(evt.item.@label == "Print")?(doPrint()):(void);
(evt.item.@label == "Undo")?(doUndo()):(void);
(evt.item.@label == "Redo")?(doRedo()):(void);
(evt.item.@label == "Find/Replace...")?(doFind()):(void);
(evt.item.@label == "Recent")?(openRecent()):(void);
("@encoding" in evt.item)?(doEncode(evt.item.@encoding, evt.item.@ind)):(void);
(evt.item.@label == "Rename...")?(openRename()):(void);
(evt.item.@label == "Delete file")?(doDelete()):(void);
(evt.item.@label == "Reload file")?(doReload()):(void);
(evt.item.@label == "From file...")?(doInsertFile()):(void);
(evt.item.@label == "File details")?(doFileDetails()):(void);
(evt.item.@label == "Timestamp")?(doTimestamp()):(void);
(evt.item.@label == "Keep previous tabs")?(pref_keepprev = !pref_keepprev):(void);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}

Now the variable can be toggled! Only one thing left to do - add a check in the openPrevious() function which checks this variables value and only executes the code inside of the function if the value is true:

private function openPrevious():void {
if(pref_keepprev){
for (var i:int = 0; i < pref_previously.length; i++) {
var file:File = new File(pref_previously[i]);
if (file.exists) {
loadFile(file);
}
}
refreshData();
tabSelectedIndex = 0;
tabChange();
undoManager.clearAll();
}
}

Full code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:custom="*"
creationComplete="init();" title="Kirpad" showStatusBar="{pref_status}"
minWidth="400" minHeight="200" height="700" width="900">

<s:menu>
<mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" />
</s:menu>

<fx:Script>
<![CDATA[
import flash.data.SQLConnection;
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.NativeWindowBoundsEvent;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.net.SharedObject;
import flashx.textLayout.accessibility.TextAccImpl;
import flashx.textLayout.edit.EditManager;
import flashx.textLayout.edit.TextScrap;
import flashx.textLayout.formats.TextLayoutFormat;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.controls.TextArea;
import mx.events.FlexNativeMenuEvent;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.Configuration;
import flash.system.System;
import flash.desktop.Clipboard;
import flash.desktop.ClipboardFormats;
import flash.ui.Mouse;
import mx.events.CloseEvent;
import flash.ui.ContextMenu;
import flash.ui.ContextMenuItem;
import flash.events.ContextMenuEvent;
import mx.events.ResizeEvent;
import mx.core.FlexGlobals;
import mx.printing.FlexPrintJob;
import mx.printing.FlexPrintJobScaleType;
import flashx.undo.UndoManager;
import flashx.textLayout.operations.UndoOperation;
import flash.data.SQLConnection;
import flash.events.SQLEvent;
import flash.data.SQLStatement;
import flash.data.SQLResult;
import flash.data.SQLMode;
import flash.net.Responder;
import XML;
import XMLList;
import mx.managers.PopUpManager;
import flashx.textLayout.operations.InsertTextOperation;
import flashx.textLayout.edit.SelectionState;
import flash.events.NativeDragEvent;
import flash.desktop.NativeDragManager;

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();
[Bindable]
private var pref_recent:ArrayCollection = new ArrayCollection();
[Bindable]
private var pref_previously:ArrayCollection = new ArrayCollection();
[Bindable]
private var pref_keepprev:Boolean = true;

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;
[Bindable]
private var canPaste:Boolean = false;

private var previousTextInOperation:String = "";
private var currentTextInOperation:String = "";

private var previousIndex:int = 0;
private var rightclickTabIndex:int = 0;
private var untitledNum:int = 0;
private var tabsToClose:int = 0;
private var closeAfterConfirm:Boolean = false;

public var fontWindow:FontWindow = new FontWindow();
public var snippetWindow:SnippetWindow = new SnippetWindow();

private var undoManager:UndoManager;
private var editManager:EditManager;

private var saveWait:Boolean = false;
private var saveAsQueue:Array = [];

public var connection:SQLConnection;
public var connection2:SQLConnection;

private var snippetData:Array;
private var categoryData:Array;

[Bindable]
public var snippetXML:XMLList;

private var searchResults:Array = [];
private var selectedResult:int = 0;

private var currentEncoding:String = "utf-8";
[Bindable]
private var enc:ArrayCollection = new ArrayCollection([true, false, false]);

private var cm:ContextMenu = new ContextMenu();
[Bindable]
private var hasLocation:Boolean = false;
[Bindable]
private var fontOpen:Boolean = false;
private var isCtrl:Boolean = false;

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.recent = [];
preferences.data.previously = [];
preferences.data.keepprev = 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;
pref_previously = new ArrayCollection(preferences.data.previously);
pref_recent = new ArrayCollection(preferences.data.recent);
pref_keepprev = preferences.data.keepprev;

// 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);
fontWindow.addEventListener(Event.CLOSING, fontClose);

// Update real fonts with the data from the settings values
updateFonts();

// Create a listener for resizing
addEventListener(NativeWindowBoundsEvent.RESIZE, onResize);

// Create drag drop listeners
addEventListener(NativeDragEvent.NATIVE_DRAG_ENTER, onDragIn);
addEventListener(NativeDragEvent.NATIVE_DRAG_DROP, onDragDrop);

// Context menu declaration for the tabbar control
var cm_close:ContextMenuItem = new ContextMenuItem("Close tab");
cm_close.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextClose);
var cm_closeother:ContextMenuItem = new ContextMenuItem("Close other tabs");
cm_closeother.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextCloseOther);
var cm_save:ContextMenuItem = new ContextMenuItem("Save");
cm_save.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextSave);
var cm_duplicate:ContextMenuItem = new ContextMenuItem("Duplicate");
cm_duplicate.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextDuplicate);
var cm_rename:ContextMenuItem = new ContextMenuItem("Rename");
cm_rename.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextRename);
var cm_delete:ContextMenuItem = new ContextMenuItem("Delete file");
cm_delete.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextDelete);
var cm_reload:ContextMenuItem = new ContextMenuItem("Reload file");
cm_reload.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextReload);

cm.items = [cm_close, cm_closeother, cm_save, cm_duplicate, cm_rename, cm_delete, cm_reload];
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);
addEventListener(KeyboardEvent.KEY_UP, onKeyUp);

// Listen to mouse wheel
addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel);

// Undo management
undoManager = new UndoManager();
editManager = new EditManager(undoManager);
textArea.textFlow.interactionManager = editManager;

// Open previously open tabs
openPrevious();

// Select first tab
tabChange();

// Database management
var dbFile:File = File.applicationStorageDirectory.resolvePath("database.db");
connection = new SQLConnection();
connection2 = new SQLConnection();
connection2.open(dbFile, SQLMode.CREATE);
connection.addEventListener(SQLEvent.OPEN, onOpen);
connection.openAsync(dbFile, SQLMode.CREATE);
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "New")?(doNew()):(void);
(evt.item.@label == "Open")?(doOpen()):(void);
(evt.item.@label == "Save")?(doSave(tabSelectedIndex)):(void);
(evt.item.@label == "Save As")?(doSaveAs(textArea.text, tabSelectedIndex)):(void);
(evt.item.@label == "Save All")?(doSaveAll()):(void);
(evt.item.@label == "Word wrap")?(pref_wrap = !pref_wrap):(void);
(evt.item.@label == "Cut")?(doCut()):(void);
(evt.item.@label == "Copy")?(doCopy()):(void);
(evt.item.@label == "Paste")?(doPaste()):(void);
(evt.item.@label == "Select all")?(doSelectall()):(void);
(evt.item.@label == "Status bar")?(pref_status = !pref_status):(void);
(evt.item.@label == "Tool bar")?(pref_toolbar = !pref_toolbar):(void);
(evt.item.@label == "Side pane")?(pref_sidepane = !pref_sidepane):(void);
(evt.item.@label == "Line count")?(pref_linecount = !pref_linecount):(void);
(evt.item.@label == "Font...")?(doFont()):(void);
(evt.item.@label == "Print")?(doPrint()):(void);
(evt.item.@label == "Undo")?(doUndo()):(void);
(evt.item.@label == "Redo")?(doRedo()):(void);
(evt.item.@label == "Find/Replace...")?(doFind()):(void);
(evt.item.@label == "Recent")?(openRecent()):(void);
("@encoding" in evt.item)?(doEncode(evt.item.@encoding, evt.item.@ind)):(void);
(evt.item.@label == "Rename...")?(openRename()):(void);
(evt.item.@label == "Delete file")?(doDelete()):(void);
(evt.item.@label == "Reload file")?(doReload()):(void);
(evt.item.@label == "From file...")?(doInsertFile()):(void);
(evt.item.@label == "File details")?(doFileDetails()):(void);
(evt.item.@label == "Timestamp")?(doTimestamp()):(void);
(evt.item.@label == "Keep previous tabs")?(pref_keepprev = !pref_keepprev):(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.data.previously = pref_previously.toArray();
preferences.data.recent = pref_recent.toArray();
preferences.data.keepprev = pref_keepprev;
preferences.flush();
}

private function doCut():void {
var selectedText:String = textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition);
System.setClipboard(selectedText);
insertText("");
}

private function doCopy():void {
var selectedText:String = textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition);
System.setClipboard(selectedText);
}

private function doPaste():void{
var myClip:Clipboard = Clipboard.generalClipboard;
var pastedText:String = myClip.getData(ClipboardFormats.TEXT_FORMAT) as String;
insertText(pastedText);
}

private function doSelectall():void {
textArea.selectAll();
}

private function insertText(str:String):void {
editManager.insertText(str);
}

private function cursorFix():void{
Mouse.cursor = "ibeam";
}

private function everyFrame(evt:Event):void {
if (!heightFixed && height==initHeight) {
height = initHeight - 20;
if (height != initHeight) {
heightFixed = true;
updateTextSize();
}
}
updateLineScroll();
if (sideList.selectedIndices.length==0) {
sideList.selectedIndex = tabSelectedIndex;
}
canPaste = Clipboard.generalClipboard.hasFormat(ClipboardFormats.TEXT_FORMAT);
}

private function onResize(evt:ResizeEvent):void {
updateTextSize();
}

private function updateTextSize():void {
tabY = (toolBar.visible)?(toolBar.height):(0);
textX = (pref_linecount)?(lineCountWidth):(0);
var statusHeight:Number = (pref_status)?(statusBar.height):(0);
textWidth = (pref_sidepane)?(width - sidePaneWidth - textX):(width - textX);
tabWidth = textWidth + textX;
var tabbarScrollHeight:Number = (tabData.length * 170 > tabWidth)?(15):(0);
textY = tabBar.height + tabY + tabbarScrollHeight;
textHeight = height - textY - statusHeight;
focusManager.setFocus(textArea);
sidePaneHeight = textHeight + tabBar.height + tabbarScrollHeight;
sidePaneY = textY - tabBar.height - tabbarScrollHeight;
sidePaneX = width - sidePaneWidth;
}

private function updateStatus():void {
var str:String = new String();
str = (pref_wrap)?("Word wrapping on"):(caretPosition());
status = "Encoding: " + currentEncoding + " " + str + " " + statusMessage;
}

private function caretPosition():String {
var pos:int = textArea.selectionActivePosition;
var str:String = textArea.text.substring(0, pos);
var lines:Array = str.split("
");
var line:int = lines.length;
var col:int = lines[lines.length - 1].length + 1;

return "Ln " + line + ", Col " + col;
}

private function doFont():void {
fontOpen = true;
fontWindow.open();
fontWindow.activate();
fontWindow.visible = true;
fontWindow.setValues(pref_fontsettings.fontsize, pref_fontsettings.fontfamily, pref_fontsettings.fontstyle, pref_fontsettings.fontweight, pref_fontsettings.fontcolor, pref_fontsettings.bgcolor);
}

private function onClose(evt:Event):void {
if(!closeAfterConfirm){
evt.preventDefault();
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 1; i < allWindows.length; i++)
{
allWindows[i].close();
}

// Check if there are any unsaved tabs
var needSaving:Boolean = false;
tabsToClose = 0;

for (var u:int = 0; u < tabData.length; u++) {
if (tabData[u].saved == false) {
needSaving = true;
tabsToClose++;
}
}

// If there are unsaved tabs, dont close window yet, set closeAfterConfirm to true and close all tabs
if (needSaving) {
closeAfterConfirm = true;
for (var t:int = 0; t < tabData.length; t++) {
closeTab(t);
}
}
if (!needSaving) {
removeEventListener(Event.CLOSING, onClose);
savePrevious();
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();
fontOpen = false;
}

private function fontClose(evt:Event):void {
fontOpen = false;
}

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);
updateSearch();
}

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].location == "" && tabData[index].textData == "" && tabData.length == 1) {
savePrevious();
FlexGlobals.topLevelApplication.close();
} else
if (tabData[index].location == "" && tabData[index].textData == "") {
tabsToClose--;
removeTab(index);
} else
if (tabData[index].saved) {
removeTab(index);
} else
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", "Confirmation", Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
tabsToClose--;
if (evt.detail == Alert.YES) {
tabSelectedIndex = index;
tabChange();
removeTab(index, true);
doSave(index, false);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int, waitForSave:Boolean = false):void {
if (tabData[index].location != "") {
addRecent(tabData[index]);
}
if(!closeAfterConfirm){
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false, location:""} );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
if (tabData.length > index+1) {
tabSelectedIndex = index;
}else
if (tabData.length <= index+1) {
tabSelectedIndex = tabData.length-2;
}
tabData.removeItemAt(index);
previousIndex = tabSelectedIndex;

// refresh tabBar tabs to clean component glitch
tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;

//textArea.text = tabData[tabSelectedIndex].textData;
changeTextTo(tabData[tabSelectedIndex].textData);
editManager.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
}
if (closeAfterConfirm && tabsToClose == 0 && waitForSave == false) {
savePrevious();
FlexGlobals.topLevelApplication.close();
}
if (waitForSave) {
saveWait = true;
}
countLines();
updateTextSize();
undoManager.clearAll();
textChange();
}

private function doNew():void {
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] New tab created";
updateStatus();
untitledNum++;
tabData.addItem( { title:"Untitled("+untitledNum+")", textData:"", saved:false, location:""} );
tabSelectedIndex = tabData.length - 1;
hasLocation = false;
tabChange();
updateTextSize();
}

public function tabChange(from:String = "none", ind:int = 0, createOperation:Boolean = true):void {
if (from == "tabbar") {
tabSelectedIndex = tabBar.selectedIndex;
sideList.selectedIndex = tabSelectedIndex;
}
if (from == "sidelist") {
tabSelectedIndex = sideList.selectedIndex;
tabBar.selectedIndex = tabSelectedIndex;
}
if (from == "operation") {
tabSelectedIndex = ind;
}
tabData[previousIndex].textData = textArea.text;
tabData[previousIndex].selectedActive = textArea.selectionActivePosition;
tabData[previousIndex].selectedAnchor = textArea.selectionAnchorPosition;

if (createOperation) {
var operation:TabOperation = new TabOperation(previousIndex, tabSelectedIndex, undoManager);
undoManager.pushUndo(operation);

changeTextTo(tabData[tabSelectedIndex].textData);

var operation2:TabOperation = new TabOperation(previousIndex, tabSelectedIndex, undoManager);
undoManager.pushUndo(operation2);
}

previousIndex = tabSelectedIndex;
updateStatus();
countLines();
textChange();
//changeTitle();
updateSearch();
hasLocation = (tabData.getItemAt(tabSelectedIndex).location == )?(false):(true);
}

private function changeTextTo(str:String):void {
var tempSaved:Boolean = tabData[tabSelectedIndex].saved;
editManager.selectAll();
var insertOp:InsertTextOperation = new InsertTextOperation(editManager.getSelectionState(), str);
editManager.doOperation(insertOp);
tabData[tabSelectedIndex].saved = tempSaved;
editManager.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
textArea.scrollToRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
changeTitle();
}

private function tabContextClose(evt:ContextMenuEvent):void{
closeTab(rightclickTabIndex);
}

private function tabContextCloseOther(evt:ContextMenuEvent):void {
var len:int = tabData.length-1;
for (var i:int = len; i >= 0; i--) {
if (i != rightclickTabIndex && (tabData[i].saved || (!tabData[i].saved && tabData[i].textData=="" && tabData[i].location==""))) {
closeTab(i);
}
}

len = tabData.length-1;
for (var u:int = 0; u < len; u++) {
if (u != rightclickTabIndex) {
closeTab(u);
}
}
}

private function tabContextSave(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
doSave(rightclickTabIndex);
}

private function tabContextDuplicate(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + tabData[rightclickTabIndex].title + " duplicated";
updateStatus();
tabData.addItem( { title:"Copy of " + tabData[rightclickTabIndex].title, textData: tabData[rightclickTabIndex].textData, saved:false, location:""} );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}

private function tabRightClick(evt:MouseEvent):void {
var tabWidth:Number = tabBar.width / tabData.length;
var rcIndex:int = Math.floor(tabBar.mouseX / tabWidth);
rightclickTabIndex = rcIndex;
if (tabData[rightclickTabIndex].location == "") {
cm.items[4].enabled = false;
cm.items[5].enabled = false;
cm.items[6].enabled = false;
}else {
cm.items[4].enabled = true;
cm.items[5].enabled = true;
cm.items[6].enabled = true;
}
}

private function listRightClick(evt:MouseEvent):void {
var tabHeight:Number = 20;
var rcIndex:int = Math.floor((sideList.mouseY + sideList.scroller.verticalScrollBar.value) / tabHeight);
rightclickTabIndex = rcIndex;
if (tabData[rightclickTabIndex].location == "") {
cm.items[4].enabled = false;
cm.items[5].enabled = false;
cm.items[6].enabled = false;
}else {
cm.items[4].enabled = true;
cm.items[5].enabled = true;
cm.items[6].enabled = true;
}
}

private function onKeyDown(evt:KeyboardEvent):void {
if (evt.keyCode == 17) {
isCtrl = true;
}
if (evt.ctrlKey) {
// Ctrl+TAB - next tab
if (evt.keyCode == 9 && !evt.shiftKey) {
if (tabData.length - tabSelectedIndex > 1) {
tabSelectedIndex++;
tabChange();
}
}
// Ctrl+Shift+TAB - previous tab
if (evt.keyCode == 9 && evt.shiftKey) {
if (tabSelectedIndex > 0) {
tabSelectedIndex--;
tabChange();
}
}
// Ctrl+number (1-8) - go to numbered tab
if (evt.keyCode >= 49 && evt.keyCode <= 56) {
var num:int = evt.keyCode - 48;
if (tabData.length > num - 1) {
tabSelectedIndex = num - 1;
tabChange();
}
}
// Ctrl+9 - go to last tab
if (evt.keyCode == 57) {
tabSelectedIndex = tabData.length - 1;
tabChange();
}
// Ctrl + - - zoom out
if (evt.keyCode == 109 && !fontOpen) {
doZoomOut();
}
// Ctrl + + - zoom in
if (evt.keyCode == 107 && !fontOpen) {
doZoomIn();
}
}
}

private function onKeyUp(evt:KeyboardEvent):void {
if (evt.keyCode==17) {
isCtrl = false;
}
}

private function onMouseWheel(evt:MouseEvent):void {
if (isCtrl && !fontOpen) {
if (evt.delta > 0) {
doZoomIn();
}
if (evt.delta < 0) {
doZoomOut();
}
}
}

private function closeSidePane():void {
if(searchInput != null){
searchInput.text = "";
}
updateSearch();
pref_sidepane = !pref_sidepane
savePreferences();
updateTextSize();
}

private function countLines():void {
if (pref_linecount && !pref_wrap) {
var totalLines:int = textArea.text.split("
").length;
if (totalLines != lineDisplayedNum) {
updateTextSize();
updateLineCount(totalLines, totalLines-lineDisplayedNum, lineDisplayedNum);
lineDisplayedNum = totalLines;
}
}
}

private function updateLineCount(total:int, difference:int, current:int):void {
if (difference > 0) {
for (var i:int = current + 1; i < (total+1); i++) {
lineNumbers += "
" + (i);
}
}
if (difference < 0) {
var charsInTheEnd:int = 0;
for (var u:int = 0; u < -difference; u++) {
charsInTheEnd += ((current - u).toString().length + 1);
}
lineNumbers = lineCount.text.substring(0, lineCount.text.length - charsInTheEnd);
}
}

private function updateLineScroll():void{
lineCount.scroller.verticalScrollBar.value = textArea.scroller.verticalScrollBar.value;
}

private function doPrint():void {
var printJob:FlexPrintJob = new FlexPrintJob();
if (!printJob.start()) return;
tempText.visible = true;
tempText.setStyle("lineBreak", "toFit");
tempText.text = textArea.text;
tempText.width = printJob.pageWidth;
tempText.heightInLines = NaN;
tempText.setStyle("horizontalScrollPolicy", "off");
tempText.setStyle("verticalScrollPolicy", "off");
printJob.printAsBitmap = false;
printJob.addObject(tempText, "matchWidth");
printJob.send();
tempText.visible = false;
}

private function textChange():void {
tabData[tabSelectedIndex].textData = textArea.text;
canUndo = undoManager.canUndo();
canRedo = undoManager.canRedo();
focusManager.setFocus(textArea);
}

private function doUndo():void {
if (getClass(undoManager.peekUndo()) == TabOperation) {
undoManager.undo();
var tempSaved:Boolean = tabData[tabSelectedIndex].saved;
undoManager.undo();
tabData[tabSelectedIndex].saved = tempSaved;
}
editManager.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
undoManager.undo();
textChange();
}

private function doRedo():void {
if (getClass(undoManager.peekRedo()) == TabOperation) {
undoManager.redo();
var tempSaved:Boolean = tabData[tabSelectedIndex].saved;
undoManager.redo();
tabData[tabSelectedIndex].saved = tempSaved;
}
editManager.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
undoManager.redo();
textChange();
}

private function getClass(obj:Object):Class {
return Class(getDefinitionByName(getQualifiedClassName(obj)));
}

private function doOpen():void {
var file:File = new File();
file.browseForOpen("Open document", [new FileFilter("Text documents", "*.txt"), new FileFilter("All files", "*")]);
file.addEventListener(Event.SELECT, fileLoad);

function fileLoad(evt:Event):void {
loadFile(file);
}
}

private function loadFile(file:File):void {
if(fileDuplicateCheck(file.nativePath)){
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var str:String = stream.readMultiByte(stream.bytesAvailable, currentEncoding);
stream.close();
str = str.replace(File.lineEnding, "
");
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + file.name + " opened";
updateStatus();
tabData.addItem( { title:file.name, textData: str, saved:true, location:file.nativePath, selectedAnchor:0, selectedActive:0} );
tabSelectedIndex = tabData.length - 1;
tabChange();
updateTextSize();
}else {
Alert.show("File " + file.name + " is already open","Error");
}
}

private function fileDuplicateCheck(loc:String):Boolean {
var toReturn:Boolean = true;
for (var i:int = 0; i < tabData.length; i++) {
if (tabData[i].location == loc) {
toReturn = false;
tabSelectedIndex = i;
tabChange();
break;
}
}
return toReturn;
}

private function saveUpdate():void {
if (tabData[tabSelectedIndex].saved) {
tabData[tabSelectedIndex].saved = false;

tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = tabSelectedIndex;

sideList.dataProvider = tabData;
changeTitle();
}
}

private function doSave(ind:int, updateIndexNeeded:Boolean = true):void {
if(!tabData[ind].saved){
if (tabData[ind].location != "") {
saveFile(textArea.text, tabData[ind].location, updateIndexNeeded);
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + tabData[ind].title + " saved";
updateStatus();
tabData[ind].saved = true;
}else{
doSaveAs(textArea.text, ind, updateIndexNeeded);
}
}
}

private function doSaveAll():void {
saveAsQueue = [];
for (var i:int = 0; i < tabData.length; i++) {
saveAsQueue.push(i);
}
doSaveQueue();
}

private function doSaveQueue():void {
if (saveAsQueue.length > 0) {
var ind:int = saveAsQueue[0];
saveAsQueue.splice(0, 1);
tabSelectedIndex = ind;
tabChange();
doSave(ind);
}
}

private function refreshData():void {
tabBar.dataProvider = new ArrayCollection([]);
tabBar.dataProvider = tabData;
tabBar.selectedIndex = 0;
tabBar.selectedIndex = tabSelectedIndex;

sideList.dataProvider = tabData;
}

private function doSaveAs(text:String, ind:int, updateIndexNeeded:Boolean = true):void {
var file:File = new File();
file.browseForSave("Save " + tabData[ind].title);
file.addEventListener(Event.SELECT, fileSave);
file.addEventListener(Event.CANCEL, fileCancel);

function fileSave(evt:Event):void {
if (file.name.length > 0) {
// See if user entered extension for the file (for example .txt)
// If not, add .txt by default
var extReg:RegExp = /.([a-z0-9]{2,})/i;
if (extReg.test(file.name)) {
tabData[ind].location = file.nativePath;
tabData[ind].title = file.name;
tabData[ind].saved = true;
saveFile(text, file.nativePath, updateIndexNeeded);
}else {
tabData[ind].location = file.nativePath + ".txt";
tabData[ind].title = file.name + ".txt";
tabData[ind].saved = true;
saveFile(text, file.nativePath + ".txt", updateIndexNeeded);
}
hasLocation = true;
}else{
Alert.show("You need to enter a name for your file.", "Error");
}
}

function fileCancel(evt:Event):void {
if (closeAfterConfirm && tabsToClose == 0 && saveWait) {
savePrevious();
FlexGlobals.topLevelApplication.close();
}
doSaveQueue();
}
}

private function saveFile(text:String, location:String, updateIndexNeeded:Boolean = true):void {
var file:File = new File(location);
var stream:FileStream = new FileStream();
stream.open(file, FileMode.WRITE);
var str:String = text;
//str = str.replace(/
/g, File.lineEnding);
stream.writeMultiByte(str, currentEncoding);
stream.close();
if (closeAfterConfirm && tabsToClose == 0 && saveWait) {
savePrevious();
FlexGlobals.topLevelApplication.close();
}
saveWait = false;
if (updateIndexNeeded) {
refreshData();
}
doSaveQueue();
changeTitle();
}

private function updateDirectoryPath():void{
directoryLabel.text = fileList.directory.nativePath;
}

private function fileListLoad():void{
var file:File = new File(fileList.selectedPath);
loadFile(file);
}

private function onOpen(evt:SQLEvent):void {
// create snippets table
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
//stat.text = "DELETE FROM snippets";
stat.text = "CREATE TABLE IF NOT EXISTS snippets (id INTEGER PRIMARY KEY AUTOINCREMENT, snippetName TEXT, snippetText TEXT, categoryID INTEGER, snippetPosition INTEGER)";
stat.execute( -1, new Responder(onCreateSnippets));
}

private function onCreateSnippets(evt:SQLResult):void {
// create category table
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection2;
//stat.text = "DELETE FROM categories";
stat.text = "CREATE TABLE IF NOT EXISTS categories (id INTEGER PRIMARY KEY AUTOINCREMENT, categoryName TEXT, categoryPosition INTEGER)";
stat.execute( -1, new Responder(onCreateCategory));
}

private function onCreateCategory(evt:SQLResult):void {
// select snippets
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT id, snippetName, categoryID, snippetPosition FROM snippets ORDER BY id";
stat.execute( -1, new Responder(onSelectedSnippets));
}

private function onSelectedSnippets(evt:SQLResult):void {
// save selected snippets to a variable
snippetData = evt.data;

// select categories
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection2;
stat.text = "SELECT id, categoryName, categoryPosition FROM categories ORDER BY id";
stat.execute( -1, new Responder(onSelectedCategories));
}

private function onSelectedCategories(evt:SQLResult):void {
// save selected categories to a variable
categoryData = evt.data;

// call function to start creating the XML
createSnippetXML();
}

private function createSnippetXML():void {
snippetXML = new XMLList();

// add root tags if there are any items in any database
if (snippetData || categoryData) {
snippetXML = new XMLList(<root></root>);
}

// Create variable to store snippets which will be added in a batch
var prepareSnippets:Array = [];

// loop through categories to add them and their content
if (categoryData) {
// Sort categories
categoryData.sortOn("categoryPosition", Array.NUMERIC);
for (var i:int = 0; i < categoryData.length; i++) {
prepareSnippets = [];
var aCategory:XML = <category/>
aCategory.@id = categoryData[i].id;
aCategory.@label = symbolEncode(categoryData[i].categoryName);
aCategory.@categoryPosition = categoryData[i].categoryPosition;
aCategory.@isBranch = true;
snippetXML[0].appendChild(aCategory);
if(snippetData){
for (var a:int = snippetData.length-1; a >= 0; a--) {
if (snippetData[a].categoryID == categoryData[i].id) {
prepareSnippets.push(snippetData[a]);
snippetData.splice(a, 1);
}
}
}

// sort and add snippets to this category
prepareSnippets.sortOn("snippetPosition", Array.NUMERIC);
for (var u:int = 0; u < prepareSnippets.length; u++) {
var aSnippet:XML = <snippet/>;
aSnippet.@id = prepareSnippets[u].id;
aSnippet.@label = symbolEncode(prepareSnippets[u].snippetName);
aSnippet.@categoryID = prepareSnippets[u].categoryID;
aSnippet.@snippetPosition = prepareSnippets[u].snippetPosition;
aSnippet.@isBranch = false;
aCategory.appendChild(aSnippet);
}

}}
// look for snippets located in root tags (not in a category)
if(snippetData){
snippetData.sortOn("snippetPosition", Array.NUMERIC);
for (var t:int = 0; t < snippetData.length; t++) {
var aRootSnippet:XML = <snippet/>;
aRootSnippet.@id = snippetData[t].id;
aRootSnippet.@label = symbolEncode(snippetData[t].snippetName);
aRootSnippet.@categoryID = snippetData[t].categoryID;
aRootSnippet.@snippetPosition = snippetData[t].snippetPosition;
aRootSnippet.@isBranch = false;
snippetXML[0].appendChild(aRootSnippet);
}
}

}

private function snippetTreeChange(evt:Event):void {
if (evt.currentTarget.selectedItem.@isBranch == false) {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT snippetText FROM snippets WHERE id=" + evt.currentTarget.selectedItem.@id;
stat.execute( -1, new Responder(insertSnippet));
evt.currentTarget.selectedIndex = -1;
}
}

private function insertSnippet(evt:SQLResult):void {
insertText(evt.data[0].snippetText);
}

private function doSnippet(createNew:Boolean = false, newText:String = ""):void{
snippetWindow.open();
snippetWindow.activate();
snippetWindow.visible = true;
snippetWindow.setValues(snippetXML, connection, connection2, createNew, newText);
}

private function symbolEncode(str:String):String {
str = str.replace(/&/g, &amp;);
str = str.replace(/"/g, &quot;);
str = str.replace(//g, &apos;);
str = str.replace(/</g, &lt;);
str = str.replace(/>/g, &gt;);
return str;
}

private function symbolDecode(str:String):String{
str = str.replace(/&quot;/g, ");
str = str.replace(/&apos;/g, "");
str = str.replace(/&lt;/g, <);
str = str.replace(/&gt;/g, >);
str = str.replace(/&amp;/g, &);
return str;
}

private function labelDecode(obj:XML):String{
return symbolDecode(obj.@label);
}

private function doFind():void {
pref_sidepane = true
sidePaneButtons.selectedIndex = 3;
savePreferences();
updateTextSize();
countLines();
}

private function changePanelTab():void {
if (sidePaneButtons.selectedIndex == 3) {
focusManager.setFocus(searchInput);
}else {
focusManager.setFocus(textArea);
resetSearch();
}
updateSearch();
}

private function updateSearch():void {
var allFormat:TextLayoutFormat = new TextLayoutFormat();
allFormat.color = pref_fontsettings.fontcolor;
textArea.setFormatOfRange(allFormat, 0, textArea.text.length);
if (searchInput != null) {
searchResults = [];
if (searchInput.text != "") {
var lastIndex:int = 0;
var selectFormat:TextLayoutFormat = new TextLayoutFormat();
selectFormat.color = 0xff0000;

var tempText:String = textArea.text;
var tempInput:String = searchInput.text;
if (!checkCase.selected) {
tempText = textArea.text.toLowerCase();
tempInput = searchInput.text.toLowerCase();
}
while (tempText.indexOf(tempInput, lastIndex) >= 0) {
var thisIndex:int = tempText.indexOf(tempInput, lastIndex);
searchResults.push(thisIndex);
lastIndex = thisIndex + 1;
if(checkHighlight.selected){
textArea.setFormatOfRange(selectFormat, thisIndex, thisIndex + searchInput.text.length);
}
}
moveResults(0);
resultText.text = "Matches found: " + searchResults.length;
}else{
resultText.text = "";
}
}
}

private function replaceSearch():void {
if (searchResults.length > 0) {
var flags:String = "g";
if (!checkCase.selected) {
flags += "i";
}
var pattern:RegExp = new RegExp(searchInput.text, flags);
var resultText:String = textArea.text.replace(pattern, replaceInput.text);
changeTextTo(resultText);
resetSearch();
}
}

private function resetSearch():void {
if(searchInput!=null){
searchInput.text = "";
updateSearch();
}
}

private function moveResults(ind:int):void {
if (searchResults.length > 0) {
focusManager.setFocus(textArea);
textArea.selectRange(searchResults[ind], searchResults[ind] + searchInput.text.length);
textArea.scrollToRange(searchResults[ind], searchResults[ind] + searchInput.text.length);
selectedResult = ind;
}
}

private function resultsUp():void{
if (searchResults.length-1 > selectedResult) {
moveResults(selectedResult + 1);
}else
if (searchResults.length-1 == selectedResult) {
moveResults(0);
}
}

private function resultsDown():void{
if (selectedResult > 0) {
moveResults(selectedResult - 1);
}else
if (selectedResult == 0) {
moveResults(searchResults.length-1);
}
}

private function addRecent(obj:Object):void {
for (var i:int = 0; i < pref_recent.length; i++) {
// remove if location is the same
if (pref_recent[i].location == obj.location) {
pref_recent.removeItemAt(i);
break;
}
}
pref_recent.addItemAt( { label:obj.title, location:obj.location }, 0);
if (pref_recent.length > 20) {
pref_recent.removeItemAt(pref_recent.length-1);
}
savePreferences();
}

private function selectRecent():void {
var location:String = pref_recent[recentList.selectedIndex].location;
if (new File(location).exists) {
loadFile(new File(location));
}else {
Alert.show("File not found at specified location.","Oops!");
}
}

private function doEncode(newEncoding:String, index:Number):void{
currentEncoding = newEncoding;
for (var i:int = 0; i < enc.length; i++) {
enc.setItemAt(false, i);
}
enc.setItemAt(true, index);
}

private function changeTitle():void {
// refresh tabs
refreshData();
// refresh title
var savedSymbol:String = (tabData[tabSelectedIndex].saved)?(""):("*");
title = "Kirpad - " + tabData[tabSelectedIndex].title + savedSymbol;
}

private function openRecent():void{
pref_sidepane = true;
sidePaneButtons.selectedIndex = 4;
}

private function tabContextRename(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
openRename();
}

private function openRename():void{
PopUpManager.addPopUp(renameWindow, this, true);
PopUpManager.centerPopUp(renameWindow);
renameWindow.title = "Rename " + tabData[tabSelectedIndex].title;
renameInput.text = tabData[tabSelectedIndex].title;
focusManager.setFocus(renameInput);
}

private function closeRename():void{
PopUpManager.removePopUp(renameWindow);
}

private function doRename():void {
var location:String = tabData[tabSelectedIndex].location;
var titleOld:String = tabData[tabSelectedIndex].title;
var titleNew:String = renameInput.text;
var locationNew:String = location.substring(0, location.length - titleOld.length) + titleNew;
var validName:RegExp = /^(?!^(PRN|AUX|CLOCK$|NUL|CON|COMd|LPTd|..*)(..+)?$)[^x00-x1f\?*:";|/]+$/;

if (validName.test(titleNew)) {

// remove existing file if it exists
if (new File(location).exists) {
new File(location).deleteFile();
}

// Update properties and
// see if user entered extension for the file (for example .txt)
// If not, add .txt by default
// Then create the new file
var extReg:RegExp = /.([a-z0-9]{2,})/i;
if (extReg.test(titleNew)) {
tabData[tabSelectedIndex].location = locationNew;
tabData[tabSelectedIndex].title = titleNew;
tabData[tabSelectedIndex].saved = true;
saveFile(tabData[tabSelectedIndex].textData, locationNew, true);
}else {
tabData[tabSelectedIndex].location = locationNew + ".txt";
tabData[tabSelectedIndex].title = titleNew + ".txt";
tabData[tabSelectedIndex].saved = true;
saveFile(tabData[tabSelectedIndex].textData, locationNew + ".txt", true);
}

statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + titleNew + " renamed";
updateStatus();
}else {
Alert.show("New file name is blank or contains restricted characters. File was no renamed.", "Error");
}
}

private function tabContextDelete(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
doDelete();
}

private function doDelete():void{
Alert.show("Delete " + tabData[tabSelectedIndex].location + " from computer?", "Confirmation", Alert.YES | Alert.NO, null, confirmDelete);

function confirmDelete(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
var file:File = new File(tabData[tabSelectedIndex].location);
if (file.exists) {
file.deleteFile();
closeTab(tabSelectedIndex);
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + file.name + " deleted";
updateStatus();
}else {
Alert.show("File not found at specified location!","Oops!");
}
}
}
}

private function tabContextReload(evt:ContextMenuEvent):void {
tabSelectedIndex = rightclickTabIndex;
tabChange();
doReload();
}

private function doReload():void{
var file:File = new File(tabData[tabSelectedIndex].location);
if (file.exists) {
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var str:String = stream.readMultiByte(stream.bytesAvailable, currentEncoding);
stream.close();
str = str.replace(File.lineEnding, "
");
tabData[tabSelectedIndex].textData = str;
tabData[tabSelectedIndex].saved = true;
//textArea.text = str;
changeTextTo(tabData[tabSelectedIndex].textData);
//tabChange();
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] " + file.name + " reloaded";
updateStatus();
}else {
Alert.show("File not found at specified location!","Oops!");
}
}

private function doInsertFile():void{
var file:File = new File();
file.browseForOpen("Open document", [new FileFilter("Text documents", "*.txt"), new FileFilter("All files", "*")]);
file.addEventListener(Event.SELECT, fileLoad);

function fileLoad(evt:Event):void {
var stream:FileStream = new FileStream();
stream.open(file, FileMode.READ);
var str:String = stream.readMultiByte(stream.bytesAvailable, currentEncoding);
stream.close();
str = str.replace(File.lineEnding, "
");
insertText(str);
}
}

private function doFileDetails():void {
var currentFile:File = new File(tabData[tabSelectedIndex].location);
var str:String = "";
str += "File: " + currentFile.nativePath.toString() + "
";
str += "Created: " + currentFile.creationDate.toLocaleString() + "
";
str += "Modified: " + currentFile.modificationDate.toLocaleString() + "
";
str += "Size: " + currentFile.size + " bytes";
insertText(str);
}

private function doTimestamp():void{
insertText(new Date().toLocaleString());
}

private function doZoomIn():void {
if(pref_fontsettings.fontsize<200){
pref_fontsettings.fontsize++;
}
textArea.setStyle("fontSize", pref_fontsettings.fontsize);
lineCount.setStyle("fontSize", pref_fontsettings.fontsize);
savePreferences();
}

private function doZoomOut():void {
if(pref_fontsettings.fontsize>2){
pref_fontsettings.fontsize--;
}
textArea.setStyle("fontSize", pref_fontsettings.fontsize);
lineCount.setStyle("fontSize", pref_fontsettings.fontsize);
savePreferences();
}

private function onDragIn(evt:NativeDragEvent):void {
NativeDragManager.acceptDragDrop(this);
}

private function onDragDrop(evt:NativeDragEvent):void {
var dragFile:File = new File(evt.clipboard.getData(ClipboardFormats.FILE_LIST_FORMAT)[0].nativePath);
loadFile(dragFile);
}

private function openPrevious():void {
if(pref_keepprev){
for (var i:int = 0; i < pref_previously.length; i++) {
var file:File = new File(pref_previously[i]);
if (file.exists) {
loadFile(file);
}
}
refreshData();
tabSelectedIndex = 0;
tabChange();
undoManager.clearAll();
}
}

private function savePrevious():void {
pref_previously.removeAll();
for (var i:int = 0; i < tabData.length; i++) {
if(tabData[i].location!=""){
pref_previously.addItem(tabData[i].location);
}
}
savePreferences();
}
]]>
</fx:Script>

<fx:Declarations>
<s:TitleWindow id="renameWindow" width="300" height="120" close="closeRename();" title="Rename file">
<s:VGroup x="10" y="10" width="300" height="350">
<s:Label width="280" text="Rename to:"/>
<s:TextInput width="280" id="renameInput" maxChars="255" />
<s:HGroup>
<s:Button label="OK" click="doRename(); closeRename();" />
<s:Button label="Cancel" click="closeRename();" />
</s:HGroup>
</s:VGroup>
</s:TitleWindow>
<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="New" key="n" controlKey="true" />
<menuitem label="Open" key="o" controlKey="true" />
<menuitem label="Recent">
</menuitem>
<menuitem type="separator"/>
<menuitem label="Rename..." enabled="{hasLocation}" />
<menuitem label="Delete file" enabled="{hasLocation}" />
<menuitem label="Reload file" enabled="{hasLocation}" />
<menuitem type="separator"/>
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As" key="s" controlKey="true" shiftKey="true" />
<menuitem label="Save All" key="s" controlKey="true" altKey="true" />
<menuitem type="separator"/>
<menuitem label="Print" key="p" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Keep previous tabs" type="check" toggled="{pref_keepprev}" />
</menuitem>
<menuitem label="Edit">
<menuitem label="Undo" key="z" controlKey="true" enabled="{canUndo}" />
<menuitem label="Redo" key="y" controlKey="true" enabled="{canRedo}" />
<menuitem type="separator"/>
<menuitem label="Cut" key="x" controlKey="true" enabled="{textArea.selectionActivePosition!=textArea.selectionAnchorPosition}" />
<menuitem label="Copy" key="c" controlKey="true" enabled="{textArea.selectionActivePosition!=textArea.selectionAnchorPosition}" />
<menuitem label="Paste" key="v" controlKey="true" enabled="{canPaste}" />
<menuitem type="separator"/>
<menuitem label="Select all" key="a" controlKey="true" />
<menuitem type="separator"/>
<menuitem label="Find/Replace..." key="f" controlKey="true" />
</menuitem>
<menuitem label="Insert">
<menuitem label="From file..." />
<menuitem label="File details" enabled="{hasLocation}"/>
<menuitem label="Timestamp" />
</menuitem>
<menuitem label="Settings">
<menuitem label="Word wrap" type="check" toggled="{pref_wrap}" />
<menuitem label="Font..."/>
</menuitem>
<menuitem label="View">
<menuitem label="Tool bar" type="check" toggled="{pref_toolbar}" />
<menuitem label="Status bar" type="check" toggled="{pref_status}" />
<menuitem label="Line count" type="check" toggled="{pref_linecount}" />
<menuitem label="Side pane" type="check" toggled="{pref_sidepane}" />
</menuitem>
<menuitem label="Encoding">
<menuitem label="UTF-8" type="check" toggled="{enc.getItemAt(0)}" ind="0" encoding="utf-8" />
<menuitem label="Unicode" type="check" toggled="{enc.getItemAt(1)}" ind="1" encoding="unicode" />
<menuitem label="ASCII" type="check" toggled="{enc.getItemAt(2)}" ind="2" encoding="us-ascii" />
</menuitem>
</root>
</fx:XML>
<mx:ArrayCollection id="tabData">
<fx:Object title="Untitled" textData="" saved="false" seletedActive="0" selectedAnchor="0" location="" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneData">
<fx:Object icon="@Embed(../lib/page.png)" tip="Tab management" />
<fx:Object icon="@Embed(../lib/folder_magnify.png)" tip="File browsing" />
<fx:Object icon="@Embed(../lib/book.png)" tip="Snippets" />
<fx:Object icon="@Embed(../lib/find.png)" tip="Search and replace" />
<fx:Object icon="@Embed(../lib/folder_page_white.png)" tip="Recent files" />
</mx:ArrayCollection>
<mx:ArrayCollection id="sidePaneTabHeadings">
<fx:String>Tab management</fx:String>
<fx:String>File browsing</fx:String>
<fx:String>Snippets</fx:String>
<fx:String>Search and replace</fx:String>
<fx:String>Recent files:</fx:String>
</mx:ArrayCollection>
</fx:Declarations>

<s:Group width="100%" height="100%">
<s:TextArea id="textArea" width="{textWidth}" height="{textHeight}" y="{textY}" x="{textX}" lineBreak="{(pref_wrap)?(toFit):(explicit)}" click="cursorFix(); updateStatus();" change="updateStatus(); countLines(); textChange(); saveUpdate(); resetSearch();" keyDown="updateStatus();" borderVisible="false" focusThickness="0" />
<s:Scroller horizontalScrollPolicy="auto" verticalScrollPolicy="off" width="{tabWidth}" y="{tabY}">
<s:Group>
<custom:CustomTabBar id="tabBar" dataProvider="{tabData}" itemRenderer="CustomTab" height="22" tabClose="onTabClose(event);" change="tabChange(tabbar);" selectedIndex="{tabSelectedIndex}" labelField="saved">
<custom:layout>
<s:HorizontalLayout gap="-1" columnWidth="170" variableColumnWidth="false"/>
</custom:layout>
</custom:CustomTabBar>
</s:Group>
</s:Scroller>
<s:TextArea id="lineCount" width="{lineCountWidth}" text="{lineNumbers}" visible="{pref_linecount}" height="{textHeight}" y="{textY}" editable="false" selectable="false" mouseEnabled="false" textAlign="right" verticalScrollPolicy="off" horizontalScrollPolicy="off" />
<mx:HBox id="toolBar" width="100%" backgroundColor="#dddddd" height="30" visible="{pref_toolbar}" paddingTop="2" paddingLeft="3">
<custom:IconButton icon="@Embed(../lib/page.png)" toolTip="New document" click="doNew();" />
<custom:IconButton icon="@Embed(../lib/folder_page.png)" toolTip="Open" click="doOpen();" />
<custom:IconButton icon="@Embed(../lib/disk.png)" toolTip="Save" click="doSave(tabSelectedIndex);" />
<custom:IconButton icon="@Embed(../lib/disk_multiple.png)" toolTip="Save all" click="doSaveAll();" />
<custom:IconButton icon="@Embed(../lib/printer.png)" toolTip="Print" click="doPrint();" />
<custom:IconButton icon="@Embed(../lib/find.png)" toolTip="Find/replace" click="doFind();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/arrow_undo.png)" toolTip="Undo" enabled="{canUndo}" click="doUndo();" />
<custom:IconButton icon="@Embed(../lib/arrow_redo.png)" toolTip="Redo" enabled="{canRedo}" click="doRedo();" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/cut.png)" toolTip="Cut" click="doCut();" enabled="{textArea.selectionActivePosition!=textArea.selectionAnchorPosition}" />
<custom:IconButton icon="@Embed(../lib/page_white_copy.png)" toolTip="Copy" click="doCopy();" enabled="{textArea.selectionActivePosition!=textArea.selectionAnchorPosition}" />
<custom:IconButton icon="@Embed(../lib/paste_plain.png)" toolTip="Paste" click="doPaste();" enabled="{canPaste}" />
<s:Label text="|" fontSize="18" color="#bbbbbb" paddingTop="4" />
<custom:IconButton icon="@Embed(../lib/zoom_in.png)" toolTip="Zoom in" click="doZoomIn();" enabled="{!fontOpen}" />
<custom:IconButton icon="@Embed(../lib/zoom_out.png)" toolTip="Zoom out" click="doZoomOut();" enabled="{!fontOpen}"/>
</mx:HBox>
<mx:Box id="sidePane" width="{sidePaneWidth}" y="{sidePaneY}" x="{sidePaneX}" height="{sidePaneHeight}" backgroundColor="#dddddd" visible="{pref_sidepane}" paddingTop="5" paddingLeft="5" horizontalScrollPolicy="off">
<s:Group>
<s:Label text="{sidePaneTabHeadings.getItemAt(sidePaneButtons.selectedIndex)}" width="{sidePaneWidth}" top="1" />
<custom:IconButton icon="@Embed(../lib/bullet_go.png)" toolTip="Hide side pane" click="closeSidePane();" top="-4" right="12"/>
</s:Group>
<mx:ToggleButtonBar id="sidePaneButtons" dataProvider="{sidePaneData}" iconField="icon" width="{sidePaneWidth-10}" toolTipField="tip" />
<mx:ViewStack id="sidePaneStack" height="100%" selectedIndex="{sidePaneButtons.selectedIndex}" change="changePanelTab();" >
<s:NavigatorContent id="tabs">
<custom:CustomList id="sideList" dataProvider="{tabData}" width="{sideContentWidth}" height="100%" itemRenderer="CustomListItem" selectedIndex="{tabSelectedIndex}" change="tabChange(sidelist);" tabClose="onListClose(event);" />
</s:NavigatorContent>
<s:NavigatorContent id="files">
<s:VGroup height="100%">
<s:HGroup width="100%">
<custom:IconButton icon="@Embed(../lib/arrow_up.png)" toolTip="Up" click="fileList.navigateUp();" enabled="{fileList.canNavigateUp}"/>
<s:Label width="140" id="directoryLabel" paddingTop="6" />
</s:HGroup>
<mx:FileSystemList width="{sidePaneWidth-10}" height="100%" id="fileList" directory="{File.desktopDirectory}" directoryChange="updateDirectoryPath();" creationComplete="updateDirectoryPath();" fileChoose="fileListLoad();" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent id="snippets">
<s:VGroup height="100%" width="100%">
<mx:Tree height="100%" width="100%" id="snippetTree" showRoot="false" labelFunction="labelDecode" dataProvider="{snippetXML}" change="snippetTreeChange(event);" />
<s:Button width="100%" label="New snippet" click="doSnippet(true, textArea.text.substring(textArea.selectionActivePosition, textArea.selectionAnchorPosition));" />
<s:Button width="100%" label="Manage snippets" click="doSnippet();" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent id="search">
<s:VGroup height="100%" width="100%">
<s:Label text="Find:"/>
<s:TextInput id="searchInput" width="100%" enter="updateSearch();" />
<s:Button id="searchButton" label="Search" width="100%" click="updateSearch();" />
<s:Label id="resultText" />
<s:HGroup>
<s:Button id="findPrevious" label="Previous" width="50%" click="resultsDown();" />
<s:Button id="findNext" label="Next" width="50%" click="resultsUp();" />
</s:HGroup>
<s:CheckBox label="Match case" id="checkCase" change="updateSearch()" />
<s:CheckBox label="Highlight all" id="checkHighlight" change="updateSearch()" />
<s:Label text=""/>
<s:Label text="Replace with:"/>
<s:TextInput id="replaceInput" width="100%" enter="replaceSearch()" />
<s:Button id="replaceButton" label="Replace all" width="100%" click="replaceSearch()" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent id="recent">
<s:VGroup height="100%" width="100%">
<s:Label text="Recently closed files:"/>
<s:List id="recentList" width="{sideContentWidth}" height="100%" dataProvider="{pref_recent}" labelField="label" change="selectRecent();" />
</s:VGroup>
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</s:Group>
<s:TextArea id="tempText" borderVisible="false" visible="false"/>

</s:WindowedApplication>

Thanks for reading!
Read more »