Showing posts with label air. Show all posts
Showing posts with label air. Show all posts

Monday, February 2, 2015

Creating a Flex AIR text editor Part 61

In this tutorial we will add support for restricted characters for our snippet addon.

Because we use XML in our snippet module and the user is able to enter whatever he wants, we gotta make sure that if the user enters restricted characters like < > & " the program doesnt break and it works as expected.

I am going to introduce 3 function here. Two of them are used to encode and decode string values using the replace method. The third method will be used for the labelFunction properties of Tree components.

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

The symbolEncode and symbolDecode functions are used to replace restricted characters with combinations of symbols that will not harm the programs work flow. Note that when we encode, we manage the ampersand (&) symbol before all else, but when we decode, it is the last symbol thats replaced.

Now we basically have to go around our code to change lines where we apply the @label property of the XML or retreive from it.

First lets do it in the SnippetWindow.mxml file.

The first function that we need to update is addSnippetXML. Here, where we set the @label property of the snippet, we need to encode the text before applying it:

aSnippet.@label = symbolEncode(newNameInput.text);

We do the same in the addCategoryXML function:

aCategory.@label = symbolEncode(newCategoryInput.text);

In updateCategoryCombo, we decode the string values when we retreive them from the XML to display in the combo box:

categories.push({label:symbolDecode(cat.@label), ind:cat.@id});

In selectTree() function, theres a line that sets the value of newCategoryInput. Here we need to decode the data before getting it:

newCategoryInput.text = symbolDecode(snippetManageTree.selectedItem.@label);

Next is the loadSnippetText() function, here we do the same to apply data to newNameInput:

newNameInput.text = symbolDecode(snippetManageTree.selectedItem.@label);

In saveCategory() function, before applying text value to the XML, we encode it:

treeData.category[snippetManageTree.selectedItem.@categoryPosition].@label = symbolEncode(newCategoryInput.text);

In saveSnippet() there are two lines that need to be updated:

treeData.category[currentCategoryPos].snippet[currentPos].@label = symbolEncode(newNameInput.text);

treeData.snippet[currentPos].@label = symbolEncode(newNameInput.text);

Now we need to update our Tree component itself. We dont need to set labelField property no longer, we use labelFunction instead and direct it to labelDecode:

<mx:Tree id="snippetManageTree" dataProvider="{treeData}" width="490" height="300" showRoot="false" itemClick="selectTree();" labelFunction="labelDecode" />

Now, lets go to the main mxml file. Here, add the 3 functions too:

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

The three lines that need to be updated are all located in the createSnippetXML function:

aCategory.@label = symbolEncode(categoryData[i].categoryName);

aSnippet.@label = symbolEncode(prepareSnippets[u].snippetName);

aRootSnippet.@label = symbolEncode(snippetData[t].snippetName);

And update the Tree control here as well, get rid of the labelField function and set labelFunction instead:

<mx:Tree height="100%" width="100%" id="snippetTree" showRoot="false" labelFunction="labelDecode" dataProvider="{snippetXML}" change="snippetTreeChange(event);" />

And there you go. Full codes ahead!

SnippetWindow.mxml:

<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:custom="*"
title="Snippet management" type="utility" width="500" height="730"
creationComplete="init();" showStatusBar="false" alwaysInFront="true" resizable="false" backgroundColor="#dddddd">

<fx:Script>
<![CDATA[
import flash.data.SQLConnection;
import flash.data.SQLResult;
import flash.data.SQLStatement;
import flash.events.Event;
import flash.net.Responder;
import mx.controls.Alert;
import mx.core.FlexGlobals;

[Bindable]
public var treeData:XMLList;
private var snippetConnection:SQLConnection;
private var categoryConnection:SQLConnection;
[Bindable]
private var categories:Array = [];

private function init():void{
this.addEventListener(Event.CLOSING, onClose);
}

private function onClose(evt:Event):void {
this.visible = false;
evt.preventDefault();
}

public function setValues(xmlData:XMLList, conn1:SQLConnection, conn2:SQLConnection, createNewOnStart:Boolean = false, newText:String = ""):void {
snippetConnection = conn1;
categoryConnection = conn2;
treeData = xmlData;
newTextInput.text = newText;
newNameInput.text = "New snippet name here";
if (newText=="") {
newTextInput.text = "New snippet text here";
}
updateCategoryCombo();
}

private function addSnippet(name:String, text:String):void{
if (name != "" && text != "") {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = snippetConnection;
stat.text = "INSERT INTO snippets (snippetName, snippetText, categoryID, snippetPosition) VALUES (@snippetName, @snippetText, @categoryID, @snippetPosition);"
stat.parameters["@snippetName"] = name;
stat.parameters["@snippetText"] = text;
stat.parameters["@categoryID"] = categoryCombo.selectedItem.ind;
stat.parameters["@snippetPosition"] = totalItemsIn(categoryCombo.selectedItem.ind);
stat.execute(-1, new Responder(addSnippetXML));
}else {
Alert.show("The name and text of the snippet must not be blank!","Oops!");
}
}

private function addSnippetXML(evt:SQLResult):void {
if (treeData.category.length() == 0 && treeData..snippet.length() == 0) {
treeData = new XMLList(<root></root>);
FlexGlobals.topLevelApplication.snippetXML = treeData;
}
var aSnippet:XML = <snippet/>;
aSnippet.@id = evt.lastInsertRowID;
aSnippet.@label = symbolEncode(newNameInput.text);
aSnippet.@categoryID = categoryCombo.selectedItem.ind;
aSnippet.@snippetPosition = totalItemsIn(categoryCombo.selectedItem.ind);
aSnippet.@isBranch = false;
if(aSnippet.@categoryID!=-1){
treeData.category.(@id == categoryCombo.selectedItem.ind).appendChild(aSnippet);
}else {
treeData[0].appendChild(aSnippet);
}
}

private function addCategory(name:String):void {
if (name != "") {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = categoryConnection;
stat.text = "INSERT INTO categories (categoryName, categoryPosition) VALUES (@categoryName, @categoryPosition);"
stat.parameters["@categoryName"] = name;
stat.parameters["@categoryPosition"] = treeData.category.length();
stat.execute(-1, new Responder(addCategoryXML));
}else {
Alert.show("The name of the category must not be blank!","Oops!");
}
}

private function addCategoryXML(evt:SQLResult):void {
if (treeData.category.length() == 0 && treeData..snippet.length() == 0) {
treeData = new XMLList(<root></root>);
FlexGlobals.topLevelApplication.snippetXML = treeData;
}
var aCategory:XML = <category/>;
aCategory.@id = evt.lastInsertRowID;
aCategory.@label = symbolEncode(newCategoryInput.text);
aCategory.@categoryPosition = treeData.category.length();
aCategory.@isBranch = true;
if(treeData.category.length()>0){
var prevLastCategory:XML = treeData.category[treeData.category.length()-1];
treeData[0].insertChildAfter(prevLastCategory, aCategory);
} else
if(treeData[0].snippet.length()>0){
var firstSnippet:XML = treeData[0].snippet[0];
treeData[0].insertChildBefore(firstSnippet, aCategory);
} else {
treeData[0].appendChild(aCategory);
}
snippetManageTree.selectedItem = aCategory;
updateCategoryCombo();
selectTree();
}

private function updateCategoryCombo():void{
categories = [];
categories.push( { label:"No category", ind: -1 } );
for each (var cat:XML in treeData.category) {
categories.push({label:symbolDecode(cat.@label), ind:cat.@id});
}
categoryCombo.selectedIndex = 0;
}

private function totalItemsIn(id:int):int {
var toRet:int;
if(id>=0){
toRet = treeData.category.(@id == id).children().length();
}
if (id == -1) {
toRet = treeData.snippet.length();
}
return toRet;
}

private function selectTree():void {
bSaveSnippet.enabled = false;
bDeleteSnippet.enabled = false;
bSaveCategory.enabled = false;
bDeleteCategory.enabled = false;
upCategory.enabled = false;
downCategory.enabled = false;
upSnippet.enabled = false;
downSnippet.enabled = false;

if (snippetManageTree.selectedItem.@isBranch == true) {
bSaveCategory.enabled = true;
bDeleteCategory.enabled = true;
upCategory.enabled = canCategoryUp();
downCategory.enabled = canCategoryDown();
newCategoryInput.text = symbolDecode(snippetManageTree.selectedItem.@label);
categoryCombo.selectedIndex = Number(snippetManageTree.selectedItem.@categoryPosition) + 1;
} else {
bSaveSnippet.enabled = true;
bDeleteSnippet.enabled = true;
upSnippet.enabled = canSnippetUp();
downSnippet.enabled = canSnippetDown();
categoryCombo.selectedIndex = treeData.category.(@id == snippetManageTree.selectedItem.@categoryID).@categoryPosition + 1;
if (snippetManageTree.selectedItem.@categoryID == -1) {
categoryCombo.selectedIndex = 0;
}
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = snippetConnection;
stat.text = "SELECT snippetText FROM snippets WHERE id=" + snippetManageTree.selectedItem.@id;
stat.execute( -1, new Responder(loadSnippetText));
}
}

private function loadSnippetText(evt:SQLResult):void{
newNameInput.text = symbolDecode(snippetManageTree.selectedItem.@label);
newTextInput.text = evt.data[0].snippetText;
}

private function canCategoryUp():Boolean {
var toRet:Boolean = true;
if (snippetManageTree.selectedItem.@categoryPosition == 0) {
toRet = false;
}
return toRet;
}

private function canCategoryDown():Boolean {
var toRet:Boolean = true;
if (snippetManageTree.selectedItem.@categoryPosition == (treeData.category.length()-1)) {
toRet = false;
}
return toRet;
}

private function canSnippetUp():Boolean {
var toRet:Boolean = true;
if (snippetManageTree.selectedItem.@snippetPosition == 0) {
toRet = false;
}
return toRet;
}

private function canSnippetDown():Boolean {
var toRet:Boolean = true;
if (snippetManageTree.selectedItem.@categoryID != -1 && snippetManageTree.selectedItem.@snippetPosition == (treeData.category.(@id == snippetManageTree.selectedItem.@categoryID).children().length()-1)) {
toRet = false;
}
if (snippetManageTree.selectedItem.@snippetPosition == (treeData.snippet.length()-1)) {
toRet = false;
}
return toRet;
}

private function categoryMove(dir:String):void{
var currentPos:int = snippetManageTree.selectedItem.@categoryPosition;
var currentId:int = snippetManageTree.selectedItem.@id;
var newPos:int = (dir=="up")?(currentPos - 1):(currentPos + 1);

// Databases:

// Set the above categorys position to current one
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = categoryConnection;
stat.text = "UPDATE categories SET categoryPosition=@currentPos WHERE categoryPosition=@newPos"
stat.parameters["@newPos"] = newPos;
stat.parameters["@currentPos"] = currentPos;
stat.execute();

// Set the current categorys position to the one above
var stat2:SQLStatement = new SQLStatement();
stat2.sqlConnection = categoryConnection;
stat2.text = "UPDATE categories SET categoryPosition=@newPos WHERE id=@currentId"
stat2.parameters["@newPos"] = newPos;
stat2.parameters["@currentId"] = currentId;
stat2.execute();

// XML:

var copy:XML = treeData.category[currentPos].copy();
delete treeData.category[currentPos];
if(dir=="up"){
treeData.insertChildBefore(treeData.category[newPos], copy);
}else {
treeData.insertChildAfter(treeData.category[currentPos], copy);
}
copy.@categoryPosition = newPos;
treeData.category[currentPos].@categoryPosition = currentPos;
snippetManageTree.selectedItem = copy;
selectTree();
}

private function snippetMove(dir:String):void{
var currentPos:int = snippetManageTree.selectedItem.@snippetPosition;
var currentId:int = snippetManageTree.selectedItem.@id;
var newPos:int = (dir == "up")?(currentPos - 1):(currentPos + 1);
var currentCategoryID:int = snippetManageTree.selectedItem.@categoryID;
var currentCategoryPos:int = NaN;

if (currentCategoryID != -1) {
currentCategoryPos = treeData.category.(@id == currentCategoryID).@categoryPosition;
}

// Databases:

// Set the above categorys position to current one
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = snippetConnection;
stat.text = "UPDATE snippets SET snippetPosition=@currentPos WHERE snippetPosition=@newPos"
stat.parameters["@newPos"] = newPos;
stat.parameters["@currentPos"] = currentPos;
stat.execute();

// Set the current categorys position to the one above
var stat2:SQLStatement = new SQLStatement();
stat2.sqlConnection = snippetConnection;
stat2.text = "UPDATE snippets SET snippetPosition=@newPos WHERE id=@currentId"
stat2.parameters["@newPos"] = newPos;
stat2.parameters["@currentId"] = currentId;
stat2.execute();

// XML:

var copy:XML = new XML();

if(currentCategoryID != -1){
copy = treeData.category[currentCategoryPos].snippet[currentPos].copy();
delete treeData.category[currentCategoryPos].snippet[currentPos];
if(dir=="up"){
treeData.category[currentCategoryPos].insertChildBefore(treeData.category[currentCategoryPos].snippet[newPos], copy);
}else {
treeData.category[currentCategoryPos].insertChildAfter(treeData.category[currentCategoryPos].snippet[currentPos], copy);
}
copy.@snippetPosition = newPos;
treeData.category[currentCategoryPos].snippet[currentPos].@snippetPosition = currentPos;
}

if(currentCategoryID == -1){
copy = treeData.snippet[currentPos].copy();
delete treeData.snippet[currentPos];
if(dir=="up"){
treeData.insertChildBefore(treeData.snippet[newPos], copy);
}else {
treeData.insertChildAfter(treeData.snippet[currentPos], copy);
}
copy.@snippetPosition = newPos;
treeData.snippet[currentPos].@snippetPosition = currentPos;
}

snippetManageTree.selectedItem = copy;
selectTree();
}

private function saveCategory():void {
var currentID:int = snippetManageTree.selectedItem.@id;

var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = categoryConnection;
stat.text = "UPDATE categories SET categoryName=@categoryName WHERE id=" + currentID;
stat.parameters["@categoryName"] = newCategoryInput.text;
stat.execute();

treeData.category[snippetManageTree.selectedItem.@categoryPosition].@label = symbolEncode(newCategoryInput.text);
updateCategoryCombo();
selectTree();
}

private function saveSnippet():void {
var currentID:int = snippetManageTree.selectedItem.@id;

var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = snippetConnection;
stat.text = "UPDATE snippets SET snippetName=@snippetName, snippetText=@snippetText WHERE id=" + currentID;
stat.parameters["@snippetName"] = newNameInput.text;
stat.parameters["@snippetText"] = newTextInput.text;
stat.execute();

var currentPos:int = snippetManageTree.selectedItem.@snippetPosition;
var currentCategoryID:int = snippetManageTree.selectedItem.@categoryID;
var currentCategoryPos:int;

if (currentCategoryID != -1) {
currentCategoryPos = treeData.category.(@id == currentCategoryID).@categoryPosition;
treeData.category[currentCategoryPos].snippet[currentPos].@label = symbolEncode(newNameInput.text);
}else {
treeData.snippet[currentPos].@label = symbolEncode(newNameInput.text);
}

}

private function deleteSnippet():void {
var currentPos:int = snippetManageTree.selectedItem.@snippetPosition;
var currentId:int = snippetManageTree.selectedItem.@id;
var currentCategoryID:int = snippetManageTree.selectedItem.@categoryID;
var currentCategoryPos:int = NaN;

if (currentCategoryID != -1) {
currentCategoryPos = treeData.category.(@id == currentCategoryID).@categoryPosition;
}
var totalSnippetsBelow:int;

// Delete from the SQL database
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = snippetConnection;
stat.text = "DELETE FROM snippets WHERE id=" + currentId;
stat.execute();

// If in a category:
if (currentCategoryID != -1) {

// Delete from the XML
delete treeData.category[currentCategoryPos].snippet[currentPos];

// Increase snippetPosition of all snippets below
totalSnippetsBelow = treeData.category[currentCategoryPos].children().length() - currentPos;

for (var i:int = 0; i < totalSnippetsBelow; i++) {
treeData.category[currentCategoryPos].snippet[currentPos + i].@snippetPosition = currentPos + i;

var snipStat1:SQLStatement = new SQLStatement();
snipStat1.sqlConnection = snippetConnection;
snipStat1.text = "UPDATE snippets SET snippetPosition=@snippetPosition WHERE snippetPosition=" + (currentPos + i);
snipStat1.parameters["@snippetPosition"] = currentPos + i - 1;
snipStat1.execute();
}

}

// If in a root tags:
if (currentCategoryID == -1) {

// Delete from the XML
delete treeData.snippet[currentPos];

// Increase snippetPosition of all snippets below
totalSnippetsBelow = treeData.snippet.length() - currentPos;

for (var u:int = 0; u < totalSnippetsBelow; u++) {
treeData.snippet[currentPos + u].@snippetPosition = currentPos + u;

var snipStat2:SQLStatement = new SQLStatement();
snipStat2.sqlConnection = snippetConnection;
snipStat2.text = "UPDATE snippets SET snippetPosition=@snippetPosition WHERE snippetPosition=" + (currentPos + u);
snipStat2.parameters["@snippetPosition"] = currentPos + u - 1;
snipStat2.execute();
}

}

// Disable all selection enabled components

bSaveSnippet.enabled = false;
bDeleteSnippet.enabled = false;
bSaveCategory.enabled = false;
bDeleteCategory.enabled = false;
upCategory.enabled = false;
downCategory.enabled = false;
upSnippet.enabled = false;
downSnippet.enabled = false;
}

private function deleteCategory():void {
var currentID:int = snippetManageTree.selectedItem.@id;
var currentPos:int = snippetManageTree.selectedItem.@categoryPosition;
var totalCategoriesBelow:int;

// Delete category from database

var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = categoryConnection;
stat.text = "DELETE FROM categories WHERE id=" + currentID;
stat.execute();

// Delete all snippets belonging to this category

var stat2:SQLStatement = new SQLStatement();
stat2.sqlConnection = snippetConnection;
stat2.text = "DELETE FROM snippets WHERE categoryID=" + currentID;
stat2.execute( -1, new Responder(snippetsDeleted));

function snippetsDeleted(evt:SQLResult):void{

// Delete from the xml

delete treeData.category[currentPos];

// Increase categoryPosition of all categories below
totalCategoriesBelow = treeData.category.length() - currentPos;

for (var i:int = 0; i < totalCategoriesBelow; i++) {
treeData.category[currentPos + i].@categoryPosition = currentPos + i;

var stat3:SQLStatement = new SQLStatement();
stat3.sqlConnection = categoryConnection;
stat3.text = "UPDATE categories SET categoryPosition=@categoryPosition WHERE categoryPosition=" + (currentPos + i);
stat3.parameters["@categoryPosition"] = currentPos + i - 1;
stat3.execute();
}

// Disable all selection enabled components

bSaveSnippet.enabled = false;
bDeleteSnippet.enabled = false;
bSaveCategory.enabled = false;
bDeleteCategory.enabled = false;
upCategory.enabled = false;
downCategory.enabled = false;
upSnippet.enabled = false;
downSnippet.enabled = false;
updateCategoryCombo();

}

}

private function changeCategory():void{
if (bSaveSnippet.enabled) {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = snippetConnection;
stat.text = "SELECT * FROM snippets WHERE id=" + snippetManageTree.selectedItem.@id;
stat.execute(-1, new Responder(getSelectedSnippet));
}
}

private function getSelectedSnippet(evt:SQLResult):void {
deleteSnippet();
addSnippet(evt.data[0].label, evt.data[0].snippetText);
}

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);
}
]]>
</fx:Script>

<s:VGroup paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5">
<s:Label text="Snippet creation and management: " />
<s:HGroup>
<s:TextInput id="newNameInput" text="New snippet name" width="240" />
<mx:ComboBox id="categoryCombo" width="240" dataProvider="{categories}" change="changeCategory();" />
</s:HGroup>
<s:TextArea id="newTextInput" width="490" height="200" text="Snippet text" />
<s:HGroup>
<s:Button id="bAddSnippet" label="Add snippet" click="addSnippet(newNameInput.text, newTextInput.text);" />
<s:Button id="bSaveSnippet" label="Save changes" enabled="false" click="saveSnippet();" />
<s:Button id="bDeleteSnippet" label="Delete snippet" enabled="false" click="deleteSnippet();" />
<custom:IconButton id="upSnippet" icon="@Embed(../lib/arrow_up.png)" toolTip="Move up" enabled="false" click="snippetMove(up);" />
<custom:IconButton id="downSnippet" icon="@Embed(../lib/arrow_down.png)" toolTip="Move down" enabled="false" click="snippetMove(down);" />
</s:HGroup>

<s:Label text="" />

<s:Label text="Category creation and management: " />
<s:TextInput id="newCategoryInput" text="New category name" width="490" />
<s:HGroup>
<s:Button id="bAddCategory" label="Add category" click="addCategory(newCategoryInput.text);"/>
<s:Button id="bSaveCategory" label="Save changes" enabled="false" click="saveCategory();" />
<s:Button id="bDeleteCategory" label="Delete category with its contents" enabled="false" click="deleteCategory();" />
<custom:IconButton id="upCategory" icon="@Embed(../lib/arrow_up.png)" toolTip="Move up" enabled="false" click="categoryMove(up);" />
<custom:IconButton id="downCategory" icon="@Embed(../lib/arrow_down.png)" toolTip="Move down" enabled="false" click="categoryMove(down);" />
</s:HGroup>

<s:Label text="" />

<mx:Tree id="snippetManageTree" dataProvider="{treeData}" width="490" height="300" showRoot="false" itemClick="selectTree();" labelFunction="labelDecode" />
</s:VGroup>

</mx:Window>

Main mxml file:

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

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

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

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

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

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

[Bindable]
private var tabSelectedIndex:int = 0;

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

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

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

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

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

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

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

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

[Bindable]
public var snippetXML:XMLList;

private function init():void {
// Create a listener for every frame
addEventListener(Event.ENTER_FRAME, everyFrame);

// Set initHeight to the initial height value on start
initHeight = height;

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = false;
preferences.data.status = true;
preferences.data.toolbar = true;
preferences.data.sidepane = true;
preferences.data.linecount = true;
preferences.data.fontsettings = {fontfamily:"Lucida Console", fontsize:14, fontstyle:"normal", fontweight:"normal", fontcolor:0x000000, bgcolor:0xffffff};
preferences.flush();
}

// Set preferences loaded from local storage
pref_wrap = preferences.data.wrap;
pref_status = preferences.data.status;
pref_toolbar = preferences.data.toolbar;
pref_sidepane = preferences.data.sidepane;
pref_fontsettings = preferences.data.fontsettings;
pref_linecount = preferences.data.linecount;

// Allow insertion of tabs
var textFlow:TextFlow = textArea.textFlow;
var config:Configuration = Configuration(textFlow.configuration);
config.manageTabKey = true;

// Set status message
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Kirpad initialized";
updateStatus();

// Close all sub-windows if main window is closed
addEventListener(Event.CLOSING, onClose);

// Add listener for the event that is dispatched when new font settings are applied
fontWindow.addEventListener(Event.CHANGE, fontChange);

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

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

// Context menu declaration for the tabbar control
var cm_close:ContextMenuItem = new ContextMenuItem("Close tab");
cm_close.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextClose);
var cm_closeother:ContextMenuItem = new ContextMenuItem("Close other tabs");
cm_closeother.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextCloseOther);
var cm_save:ContextMenuItem = new ContextMenuItem("Save");
cm_save.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextSave);
var cm_duplicate:ContextMenuItem = new ContextMenuItem("Duplicate");
cm_duplicate.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, tabContextDuplicate);

var cm:ContextMenu = new ContextMenu();
cm.items = [cm_close, cm_closeother, cm_save, cm_duplicate];
cm.hideBuiltInItems();
tabBar.contextMenu = cm;
tabBar.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, tabRightClick);

// Context menu declaration for the tab management list control
sideList.contextMenu = cm;
sideList.addEventListener(MouseEvent.RIGHT_MOUSE_DOWN, listRightClick);

// Listen to keyboard
addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);

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

// Select first tab
tabChange();

// Database management
var dbFile:File = File.applicationStorageDirectory.resolvePath("database.db");
connection = new SQLConnection();
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);
savePreferences();
updateStatus();
if (pref_wrap) {
pref_linecount = false;
}
updateTextSize();
countLines();
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

savePreferences();
updateFonts();
}

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

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

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

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

private function closeTab(index:int):void {
if (tabData[index].saved) {
removeTab(index);
} else
if (!tabData[index].saved) {
Alert.show("Save " + tabData[index].title + " before closing?", "Confirmation", Alert.YES | Alert.NO, null, confirmClose);
}
function confirmClose(evt:CloseEvent):void {
tabsToClose--;
if (evt.detail == Alert.YES) {
tabSelectedIndex = index;
tabChange();
removeTab(index, true);
doSave(index, false);
}else {
removeTab(index);
}
}
}

private function removeTab(index:int, waitForSave:Boolean = false):void {
if(!closeAfterConfirm){
// if this is the last tab, create a new empty tab
if (tabData.length == 1) {
tabData.addItem( { title:"Untitled", textData:"", saved:false, location:""} );
}
statusMessage = "[ " + new Date().toLocaleTimeString() + " ] Tab closed: " + tabData[index].title;
updateStatus();
tabData.removeItemAt(index);
tabSelectedIndex = tabBar.selectedIndex;
previousIndex = tabSelectedIndex;
textArea.text = tabData[tabSelectedIndex].textData;
textArea.selectRange(tabData[tabSelectedIndex].selectedAnchor, tabData[tabSelectedIndex].selectedActive);
}
if (closeAfterConfirm && tabsToClose == 0 && waitForSave == false) {
FlexGlobals.topLevelApplication.close();
}
if (waitForSave) {
saveWait = true;
}
countLines();
updateTextSize();
undoManager.clearAll();
textChange();
}

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

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

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

private function tabContextCloseOther(evt:ContextMenuEvent):void {
var len:int = tabData.length;
for (var i:int = 0; i < len; i++) {
if (i != rightclickTabIndex && tabData[i].saved) {
closeTab(i);
}
}

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

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

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

private function tabRightClick(evt:MouseEvent):void {
var tabWidth:Number = tabBar.width / tabData.length;
var rcIndex:int = Math.floor(tabBar.mouseX / tabWidth);
rightclickTabIndex = rcIndex;
}

private function listRightClick(evt:MouseEvent):void {
var tabHeight:Number = 20;
var rcIndex:int = Math.floor((sideList.mouseY + sideList.scroller.verticalScrollBar.value) / tabHeight);
rightclickTabIndex = rcIndex;
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

sideList.dataProvider = tabData;
var savedSymbol:String = (tabData[tabSelectedIndex].saved)?(""):("*");
title = "Kirpad - " + tabData[tabSelectedIndex].title + savedSymbol;
}
}

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

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

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

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

sideList.dataProvider = tabData;
}

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

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

function fileCancel(evt:Event):void {
doSaveQueue();
}
}

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

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

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

private function onOpen(evt:SQLEvent):void {
// 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;
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;
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);
}
]]>
</fx:Script>

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

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

</s:WindowedApplication>

Thanks for reading!
Read more »

Thursday, January 29, 2015

Creating a Flex AIR Screenshot app Part 11

In todays tutorial well add some options to set export directory in our application.

What were going to add is the "Export destination" option. It basically consists of a label and a HGroup with a text input and a button in it. The text input field cannot be edited by the user, and displays the directory, where the final files are going to be saved. By pressing the "browse" button, the user calls out a window, which lets him select a folder, and the text field updates (the value also saves locally).

Create theese elements, set the text inputs text value boudn to pref_destination and the buttons click event handler to screenshotDestination():

<s:Label styleName="descriptionText2">Export destination:</s:Label>
<s:HGroup width="310">
<s:TextInput editable="false" width="100%" toolTip="Destination" text="{pref_destination}" />
<s:Button label="Browse" click="screenshotDestination();" />
</s:HGroup>

Now well add an option to create a folder automatically in the destination and put all the exported images inside of it. Well add a checkbox that the user can toggle and choose whether he wants the folder to be created, or just store the exported images in the selected destination. Set the checkboxs id to folderCheckbox and selected property bound to pref_folder (use two-way binding). Create a text input field and, most importantly, set its enabled property bound to the selected property of folderCheckbox.

<s:CheckBox id="folderCheckbox" label="Create new folder with exported images" styleName="descriptionText2" selected="@{pref_folder}" />
<s:TextInput id="folderField" width="100%" toolTip="Folder name" maxChars="200" enabled="{folderCheckbox.selected}"/>

You may have already noticed that I set the styleName property of several elements to descriptionText2 in the code above. Set every elements styleName that is displayed in the screenshotsettings page and displays any text to descriptionText2:

<s:NavigatorContent id="screenshotsettings">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText2">Select screenshot screen sizes:</s:Label>
<s:SkinnableContainer backgroundColor="#999999" width="310" height="18" >
<s:CheckBox toolTip="Use this screen size" x="4" id="set1checkbox" />
<s:Label id="contSize" styleName="settingText" x="22" y="3" />
</s:SkinnableContainer>
<custom:ScreenSetting id="set2" />
<custom:ScreenSetting id="set3" />
<custom:ScreenSetting id="set4" />
<custom:ScreenSetting id="set5" />
<custom:ScreenSetting id="set6" />
<custom:ScreenSetting id="set7" />

<s:Label/>

<s:Label styleName="descriptionText2">Export as:</s:Label>
<s:HGroup>
<s:RadioButton id="screenRadioJPEG" label="JPEG" groupName="screenshotFormat" change="formatChange(JPEG);" styleName="descriptionText2" />
<s:RadioButton id="screenRadioPNG" label="PNG" groupName="screenshotFormat" change="formatChange(PNG);" styleName="descriptionText2" />
</s:HGroup>
<s:Label styleName="descriptionText2">Quality:</s:Label>
<s:HSlider id="screenQualitySlider" width="310" minimum="1" maximum="100" liveDragging="true" enabled="{pref_format==JPEG}" value="@{pref_quality}" />

<s:Label/>

<s:Label styleName="descriptionText2">Export destination:</s:Label>
<s:HGroup width="310">
<s:TextInput editable="false" width="100%" toolTip="Destination" text="{pref_destination}" />
<s:Button label="Browse" click="screenshotDestination();" />
</s:HGroup>
<s:CheckBox id="folderCheckbox" label="Create new folder with exported images" styleName="descriptionText2" selected="@{pref_folder}" />
<s:TextInput id="folderField" width="100%" toolTip="Folder name" maxChars="200" enabled="{folderCheckbox.selected}"/>
<s:HGroup>
<s:Button label="Back" click="screenshotBack();" />
<s:Button label="Export" click="startExportScreenshot();" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>

The style is basically a white text, but smaller than descriptionText:

<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

.descriptionText{
fontSize: 24;
color: #fff;
}

.descriptionText2{
fontSize: 16;
color: #fff;
}

.settingText{
fontSize: 16;
color: #fff;
}

#headStep{
fontSize: 30;
fontWeight: bold;
color: #ffffbb;
}

#headDesc{
fontSize: 30;
color: #ffffff;
}
</fx:Style>

Since we added some space-taking content, increase the applications height value to 600:

<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:custom="*"
xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false"
width="550" height="600" creationComplete="init();">

Now declare 2 variables - pref_folder and pref_destination:

[Bindable]
private var pref_folder:Boolean;
[Bindable]
private var pref_destination:String;

Edit init() function to set the default values for these variables. The folders default value is true, the destination is File.documentsDirectory.nativePath. Apply these values to pref_folder and pref_destination after that:

private function init():void {
//preferences.data.firsttime = null;

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.screensizes = [
{ checked:true },
{ checked:true, w:1280, h:1024 },
{ checked:true, w:1280, h:800 },
{ checked:true, w:1024, h:768 },
{ checked:false, w:"", h:"" },
{ checked:false, w:"", h:"" },
{ checked:false, w:"", h:"" } ];
preferences.data.format = "JPEG";
preferences.data.quality = 100;
preferences.data.folder = true;
preferences.data.destination = File.documentsDirectory.nativePath;
preferences.flush();
}

// Set preferences loaded from local storage
pref_screensizes = preferences.data.screensizes;
pref_format = preferences.data.format;
pref_quality = preferences.data.quality;
pref_folder = preferences.data.folder;
pref_destination = preferences.data.destination;

addEventListener(FlexNativeWindowBoundsEvent.WINDOW_RESIZE, onResize);
}

Once again, were adding more preferences, so remember to set firsttime to null for the first time running this code.

Lets add that screenshotDestination function. The user browses for a directory and pref_destination is set to the selected directorys nativePath:

private function screenshotDestination():void {
var newDestination:File = new File(pref_destination);
newDestination.browseForDirectory("Select directory");
newDestination.addEventListener(Event.SELECT, destinationSelect);

function destinationSelect(evt:Event):void {
pref_destination = newDestination.nativePath;
}
}

Add 2 lines to update preferences with the new folder and destination values and save them in saveScreenshotSettings():

private function saveScreenshotSettings():void {
pref_screensizes[0].checked = set1checkbox.selected;

for (var i:int = 0; i < screenSettings.length; i++) {
pref_screensizes[i + 1].checked = screenSettings[i].checked;
pref_screensizes[i + 1].w = screenSettings[i].w;
pref_screensizes[i + 1].h = screenSettings[i].h;
}

if (screenRadioJPEG.selected == true) {
pref_format == "JPEG";
} else {
pref_format == "PNG";
}

preferences.data.screensizes = pref_screensizes;
preferences.data.format = pref_format;
preferences.data.quality = pref_quality;
preferences.data.folder = pref_folder;
preferences.data.destination = pref_destination;
preferences.flush();
}

Thats all for today.

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:custom="*"
xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false"
width="550" height="600" creationComplete="init();">


<fx:Declarations>
<mx:ArrayCollection id="headerTitles">
<fx:Object step="Step one:" description="load a web page." />
<fx:Object step="Loading..." description="please wait." />
<fx:Object step="Step two:" description="set your export preferences." />
<fx:Object step="Step two:" description="select the area you wish to crop." />
<fx:Object step="Step three:" description="set your export preferences for the cropped image." />
<fx:Object step="Final step:" description="please wait while your images are being expored." />
</mx:ArrayCollection>
</fx:Declarations>

<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

.descriptionText{
fontSize: 24;
color: #fff;
}

.descriptionText2{
fontSize: 16;
color: #fff;
}

.settingText{
fontSize: 16;
color: #fff;
}

#headStep{
fontSize: 30;
fontWeight: bold;
color: #ffffbb;
}

#headDesc{
fontSize: 30;
color: #ffffff;
}
</fx:Style>

<fx:Script>
<![CDATA[
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.filesystem.File;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import mx.controls.HTML;
import mx.core.FlexHTMLLoader;
import mx.events.FlexNativeWindowBoundsEvent;
import mx.controls.Alert;
import mx.graphics.codec.JPEGEncoder;
import mx.graphics.codec.PNGEncoder;
import mx.graphics.ImageSnapshot;
import spark.primitives.BitmapImage;

[Bindable]
private var urlString:String;
[Bindable]
private var canSelect:Boolean = true;
private var tempHTML:HTML = new HTML();

private var preferences:SharedObject = SharedObject.getLocal("kirshotPreferences");
[Bindable]
private var pref_screensizes:Array;
[Bindable]
private var pref_format:String;
[Bindable]
private var pref_quality:int;
[Bindable]
private var pref_folder:Boolean;
[Bindable]
private var pref_destination:String;

private var screenSettings:Array;

private function init():void {
//preferences.data.firsttime = null;

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.screensizes = [
{ checked:true },
{ checked:true, w:1280, h:1024 },
{ checked:true, w:1280, h:800 },
{ checked:true, w:1024, h:768 },
{ checked:false, w:"", h:"" },
{ checked:false, w:"", h:"" },
{ checked:false, w:"", h:"" } ];
preferences.data.format = "JPEG";
preferences.data.quality = 100;
preferences.data.folder = true;
preferences.data.destination = File.documentsDirectory.nativePath;
preferences.flush();
}

// Set preferences loaded from local storage
pref_screensizes = preferences.data.screensizes;
pref_format = preferences.data.format;
pref_quality = preferences.data.quality;
pref_folder = preferences.data.folder;
pref_destination = preferences.data.destination;

addEventListener(FlexNativeWindowBoundsEvent.WINDOW_RESIZE, onResize);
}

private function doBrowse():void{
var file:File = new File();
file.addEventListener(Event.SELECT, browseSelect);
file.browseForOpen("Load a webpage");

function browseSelect(evt:Event):void {
urlInput.text = file.nativePath;
}
}

private function goCrop():void {
stack.selectedChild = crop;
urlString = urlInput.text;
}

private function goScreenshot():void {
stack.selectedChild = screenshotloading;
urlString = urlInput.text;

addElement(tempHTML);
tempHTML.visible = false;
tempHTML.addEventListener(Event.COMPLETE, onTempLoad);
tempHTML.htmlLoader.load(new URLRequest(urlString));
}

private function onTempLoad(evt:Event):void {
stack.selectedChild = screenshotsettings;
}

private function cancelLoading():void {
tempHTML.removeEventListener(Event.COMPLETE, onTempLoad);
tempHTML.cancelLoad();
screenshotBack();
}

private function screenshotBack():void {
saveScreenshotSettings()
stack.selectedChild = loadpage;
removeElement(tempHTML);
}

private function changeState():void {
if (stack.selectedChild == loadpage) {
contentBox.setStyle("horizontalAlign", "center");
urlInput.text = urlString;
}
if (stack.selectedChild == crop) {
maximize();
canSelect = true;
contentBox.setStyle("horizontalAlign", "left");
cropHTML.htmlLoader.load(new URLRequest(urlString));
cropHTML.width = contentBox.width;
cropHTML.height = contentBox.height - 24;
cropStatus.text = "Loading";
cropStatus.setStyle("color", "#ffff00");
cropHTML.addEventListener(Event.COMPLETE, onCropLoad);
}
if (stack.selectedChild == screenshotsettings) {
screenSettings = [set2, set3, set4, set5, set6, set7];
contSize.text = "Full size (" + tempHTML.contentWidth + "x" + tempHTML.contentHeight + ")";
loadScreenshotSettings();
}
}

private function loadScreenshotSettings():void {
set1checkbox.selected = pref_screensizes[0].checked;

for (var i:int = 0; i < screenSettings.length; i++) {
screenSettings[i].checked = pref_screensizes[i + 1].checked;
screenSettings[i].w = pref_screensizes[i + 1].w;
screenSettings[i].h = pref_screensizes[i + 1].h;
}

if (pref_format == "JPEG") {
screenRadioJPEG.selected = true;
} else {
screenRadioPNG.selected = true;
}
}

private function saveScreenshotSettings():void {
pref_screensizes[0].checked = set1checkbox.selected;

for (var i:int = 0; i < screenSettings.length; i++) {
pref_screensizes[i + 1].checked = screenSettings[i].checked;
pref_screensizes[i + 1].w = screenSettings[i].w;
pref_screensizes[i + 1].h = screenSettings[i].h;
}

if (screenRadioJPEG.selected == true) {
pref_format == "JPEG";
} else {
pref_format == "PNG";
}

preferences.data.screensizes = pref_screensizes;
preferences.data.format = pref_format;
preferences.data.quality = pref_quality;
preferences.data.folder = pref_folder;
preferences.data.destination = pref_destination;
preferences.flush();
}

private function formatChange(newformat:String):void {
pref_format = newformat;
}

private function startExportScreenshot():void {
var canExport:Boolean = true;

for (var i:int = 0; i < screenSettings.length; i++) {
if (screenSettings[i].checked && ((screenSettings[i].w == "" || screenSettings[i].w == 0) || (screenSettings[i].h == "" || screenSettings[i].h == 0))) {
canExport = false;
}
}

if (canExport) {
Alert.show("Successful export");
}else {
Alert.show("One or more selected screen sizes are not entered or are invalid!", "Oops...");
}
}

private function screenshotDestination():void {
var newDestination:File = new File(pref_destination);
newDestination.browseForDirectory("Select directory");
newDestination.addEventListener(Event.SELECT, destinationSelect);

function destinationSelect(evt:Event):void {
pref_destination = newDestination.nativePath;
}
}

private function onCropLoad(evt:Event):void {
cropStatus.text = "Loaded";
cropStatus.setStyle("color", "#ffffff");
}

private function onResize(evt:Event):void {
if (stack.selectedChild == crop) {
cropHTML.width = contentBox.width;
cropHTML.height = contentBox.height - 24;
}
}
]]>
</fx:Script>

<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox backgroundColor="#333333" height="46" width="100%" paddingTop="10" paddingLeft="10">
<s:Label id="headStep" text="{headerTitles.getItemAt(stack.selectedIndex).step}" />
<s:Label id="headDesc" text="{headerTitles.getItemAt(stack.selectedIndex).description}" />
</mx:HBox>
<mx:Box backgroundColor="#666666" width="100%" height="100%" id="contentBox" horizontalAlign="center">
<mx:ViewStack id="stack" change="changeState();">
<s:NavigatorContent id="loadpage">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText">Enter the link to the page:</s:Label>
<s:HGroup>
<s:TextInput width="250" id="urlInput" text="http://" /><s:Button label="Browse local..." click="doBrowse();" />
</s:HGroup>
<s:HGroup>
<custom:ImageButton img="@Embed(../lib/b_screenshot.png)" over="@Embed(../lib/b_screenshot_over.png)" toolTip="Take screenshots" click="goScreenshot();" buttonMode="true" enabled="{urlInput.text!=}" />
<custom:ImageButton img="@Embed(../lib/b_cut.png)" over="@Embed(../lib/b_cut_over.png)" toolTip="Crop area" click="goCrop();" buttonMode="true" enabled="{urlInput.text!=}" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="screenshotloading">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText">The page is being loaded...</s:Label>
<s:Button label="Cancel" click="cancelLoading();" />
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="screenshotsettings">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText2">Select screenshot screen sizes:</s:Label>
<s:SkinnableContainer backgroundColor="#999999" width="310" height="18" >
<s:CheckBox toolTip="Use this screen size" x="4" id="set1checkbox" />
<s:Label id="contSize" styleName="settingText" x="22" y="3" />
</s:SkinnableContainer>
<custom:ScreenSetting id="set2" />
<custom:ScreenSetting id="set3" />
<custom:ScreenSetting id="set4" />
<custom:ScreenSetting id="set5" />
<custom:ScreenSetting id="set6" />
<custom:ScreenSetting id="set7" />

<s:Label/>

<s:Label styleName="descriptionText2">Export as:</s:Label>
<s:HGroup>
<s:RadioButton id="screenRadioJPEG" label="JPEG" groupName="screenshotFormat" change="formatChange(JPEG);" styleName="descriptionText2" />
<s:RadioButton id="screenRadioPNG" label="PNG" groupName="screenshotFormat" change="formatChange(PNG);" styleName="descriptionText2" />
</s:HGroup>
<s:Label styleName="descriptionText2">Quality:</s:Label>
<s:HSlider id="screenQualitySlider" width="310" minimum="1" maximum="100" liveDragging="true" enabled="{pref_format==JPEG}" value="@{pref_quality}" />

<s:Label/>

<s:Label styleName="descriptionText2">Export destination:</s:Label>
<s:HGroup width="310">
<s:TextInput editable="false" width="100%" toolTip="Destination" text="{pref_destination}" />
<s:Button label="Browse" click="screenshotDestination();" />
</s:HGroup>
<s:CheckBox id="folderCheckbox" label="Create new folder with exported images" styleName="descriptionText2" selected="@{pref_folder}" />
<s:TextInput id="folderField" width="100%" toolTip="Folder name" maxChars="200" enabled="{folderCheckbox.selected}"/>
<s:HGroup>
<s:Button label="Back" click="screenshotBack();" />
<s:Button label="Export" click="startExportScreenshot();" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="crop">
<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox backgroundColor="#999999" width="100%" height="24" verticalScrollPolicy="off" verticalAlign="middle" paddingLeft="2">
<s:Button label="Back" click="{cropHTML.htmlLoader.loadString(); stack.selectedChild = loadpage;}" />
<s:Button label="Export selection" enabled="false" />
<s:Label id="cropStatus" />
<s:Label text="{urlString}" />
</mx:HBox>
<mx:HTML id="cropHTML" horizontalScrollPolicy="on" verticalScrollPolicy="on" />
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="cropsettings">

</s:NavigatorContent>

<s:NavigatorContent id="export">

</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</s:VGroup>

</s:WindowedApplication>

Thanks for reading!
Read more »

Tuesday, January 27, 2015

KirSQLite Flex AIR Database Manager Part 9

In this tutorial we are going to add the ability to add new tables to a database, and to drop tables.

Find the VGroup that our Tree object is located in. Add a HGroup above the Tre with two buttons in it, labeled "New table" and "Drop table". Set their click event handlers to newTable() and dropTable(). Set their enabled properties to {table.selectedItems.length>0} and {isTableSelected}.

Set the id of our table to tableTree.

<s:VGroup width="200" height="100%" gap="0">
<s:HGroup>
<s:Button label="New table" click="newTable();" enabled="{tableTree.selectedItems.length>0}"/>
<s:Button label="Drop table" click="dropTable();" enabled="{isTableSelected}" />
</s:HGroup>
<mx:Tree id="tableTree" width="100%" height="100%" dataProvider="{dbData}" showRoot="false" labelField="@label" itemClick="tableSelect(event);"/>
</s:VGroup>

Well need to declare two variables - selectedDatabase and isTableSelected.

private var selectedDatabase:String = "";
[Bindable]
private var isTableSelected:Boolean = false;

The selectedDatabase variable must contain the name of the current database ("main", or "db"+index).

Go to tableSelect() function. Add another if statement that executes code if the selected item in the tree is a branch. Inside of the statement, set isTableSelected to false, and add code that sets selectedDatabase to the proper value, based on the "numid" attribute of the selected database.

In the second if statement, set isTableSelected to false and selectedDatabase to "databaseName" attribute of the selected tree item.

private function tableSelect(evt:Event):void {
if (evt.currentTarget.selectedItem.@isBranch) {
isTableSelected = false;
var dataname:String;
if (evt.currentTarget.selectedItem.@numid == 1) dataname = "main";
if (evt.currentTarget.selectedItem.@numid > 1) dataname = "db" + evt.currentTarget.selectedItem.@numid;
selectedDatabase = dataname;
}
if (evt.currentTarget.selectedItem.@isBranch == false) {
isTableSelected = true;
selectedDatabase = evt.currentTarget.selectedItem.@databaseName;
tableData = new ArrayCollection([]);
var newColumns:Array = [checkboxColumn];
connection.loadSchema(SQLTableSchema, evt.currentTarget.selectedItem.@label, evt.currentTarget.selectedItem.@databaseName);
var schema:SQLSchemaResult = connection.getSchemaResult();
for (var i:int = 0; i < schema.tables[0].columns.length; i++) {
var aColumn:AdvancedDataGridColumn = new AdvancedDataGridColumn(schema.tables[0].columns[i].name);
newColumns.push(aColumn);
}
tableGrid.columns = newColumns;
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT * FROM " + evt.currentTarget.selectedItem.@databaseName + "." + evt.currentTarget.selectedItem.@label;
stat.execute(-1, new Responder(tableSuccess, tableError));
}
function tableSuccess(evt:SQLResult):void {
if (evt.data != null) {
for (var item:Object in evt.data) {
var obj:Object = new Object();
for (var value:Object in evt.data[item]) {
obj[value] = evt.data[item][value];
}
tableData.addItem(obj);
}
}
}
function tableError(evt:SQLError):void {
Alert.show("Unable to read table data.", "Error");
}
}

Go to loadDataSchema() and set isTableSelected to false:

private function loadDataSchema(name:String):void {
if (name != "") {
connection.loadSchema(null, null, name, true, new Responder(schemaSuccess, schemaError));
function schemaSuccess(evt:SQLSchemaResult):void {
// Schema found! Now parsing:
var result:SQLSchemaResult = evt;
// Adding tables:
var nid:Number = (name=="main")?(1):(Number(name.replace("db", "")));
var dataNode:XMLList = dbData.db.(@numid == nid);
dataNode.setChildren(<placeholder/>);
delete dataNode.placeholder;
for (var i:int = 0; i < result.tables.length; i++) {
var newTable:XML = new XML(<tb/>);
newTable.@label = result.tables[i].name;
newTable.@isBranch = false;
newTable.@databaseName = name;
dataNode.appendChild(newTable);
}
}
function schemaError(evt:SQLError):void {
// Alert.show("Database is empty");
}
isTableSelected = false;
}
}

Now that the values of these two variables are set properly, we can use them to create and drop tables.

First create the newTable() function. Here, we will call out a pop up window called newTableWindow, where the user will be able to enter the name for the new table:

private function newTable():void {
PopUpManager.addPopUp(newTableWindow, this);
PopUpManager.centerPopUp(newTableWindow);
newTableWindow.title = "Create new table";
}

The newTableWindow object is a TitleWindow that is declared in the Declarations tags. Set its close event listener to closeNewTableWindow(), set showCloseButton to true, and inside of the TitleWindow add a TextInput with an id "newTableName" and button with a click event handler "createNewTable()":

<mx:TitleWindow id="newTableWindow" title="Create new table" close="closeNewTableWindow();" showCloseButton="true">
<s:VGroup>
<s:HGroup width="100%" verticalAlign="middle">
<s:Label>Table name: </s:Label>
<s:TextInput id="newTableName" />
</s:HGroup>
<s:Button click="createNewTable();" label="Create" width="100%" />
</s:VGroup>
</mx:TitleWindow>

The closeNewTableWindow() closes the window:

private function closeNewTableWindow():void{
PopUpManager.removePopUp(newTableWindow);
}

The createNewTable() function creates a new SQLStatement object with a query built using our existing data. It is important that we set both success and error event handlers using a responder for this query, and display the error details to the user if an error occured. If everything went smoothly, close the window and call loadDataSchema():

private function createNewTable():void {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "CREATE TABLE IF NOT EXISTS " + selectedDatabase + "." + newTableName.text + "(id INTEGER PRIMARY KEY AUTOINCREMENT, blankColumn TEXT)";
stat.execute( -1, new Responder(newTableSuccess, newTableError));
function newTableSuccess(evt:SQLResult):void {
closeNewTableWindow();
loadDataSchema(selectedDatabase);
}
function newTableError(evt:SQLError):void {
Alert.show("ERROR:" + evt.details, "Error");
}
}

The Drop Table feature is more simple. First we throw out an Alert, asking the user if he is sure that he wants to remove the table. If the answer is YES, we create a new SQLStatement that drops the table. Once again, if there was an error - tell the user the details of the error. Otherwise, call loadDataSchema():

private function dropTable():void {
Alert.show("Are you sure you want to completely delete this table?", "Drop table?", Alert.YES | Alert.NO, null, dropConfirm);
function dropConfirm(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "DROP TABLE " + selectedDatabase + "." + tableTree.selectedItem.@label;
stat.execute( -1, new Responder(dropTableSuccess, dropTableError));
}
}
function dropTableSuccess(evt:SQLResult):void {
loadDataSchema(selectedDatabase);
}
function dropTableError(evt:SQLError):void {
Alert.show("ERROR:" + evt.details, "Error");
}
}

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" showStatusBar="false">

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

<fx:Declarations>
<fx:XML id="windowMenu">
<root>
<menuitem label="Database">
<menuitem id="newdb" label="New" key="n" controlKey="true" />
<menuitem id="opendb" label="Open" key="o" controlKey="true" />
<menuitem label="Save" key="s" controlKey="true" />
<menuitem label="Save As.." key="s" controlKey="true" shiftKey="true" />
</menuitem>
<menuitem label="Table">
<menuitem label="Add" key="t" controlKey="true" />
<menuitem label="Edit" key="e" controlKey="true" />
<menuitem label="Delete"/>
</menuitem>
</root>
</fx:XML>
<fx:XMLList id="dbData">
</fx:XMLList>
<mx:ArrayCollection id="tableData">
</mx:ArrayCollection>
<mx:AdvancedDataGridColumn id="checkboxColumn" headerText=" " width="30" sortable="false" draggable="false" resizable="false" editable="false">
<mx:itemRenderer>
<fx:Component>
<mx:Box width="30" horizontalAlign="center">
<mx:CheckBox selected="@{data.sel}" />
</mx:Box>
</fx:Component>
</mx:itemRenderer>
</mx:AdvancedDataGridColumn>
<mx:TitleWindow id="newTableWindow" title="Create new table" close="closeNewTableWindow();" showCloseButton="true">
<s:VGroup>
<s:HGroup width="100%" verticalAlign="middle">
<s:Label>Table name: </s:Label>
<s:TextInput id="newTableName" />
</s:HGroup>
<s:Button click="createNewTable();" label="Create" width="100%" />
</s:VGroup>
</mx:TitleWindow>
</fx:Declarations>

<fx:Script>
<![CDATA[
import flash.data.SQLConnection;
import flash.data.SQLResult;
import flash.data.SQLSchema;
import flash.data.SQLSchemaResult;
import flash.data.SQLStatement;
import flash.errors.SQLError;
import flash.events.Event;
import flash.events.SQLEvent;
import flash.filesystem.File;
import flash.net.FileFilter;
import flash.net.Responder;
import mx.collections.ArrayCollection;
import mx.controls.advancedDataGridClasses.AdvancedDataGridColumn;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.FlexNativeMenuEvent;
import mx.managers.PopUpManager;

private var connection:SQLConnection = new SQLConnection();
private var selectedDatabase:String = "";
[Bindable]
private var isTableSelected:Boolean = false;

private function selectAllChange(evt:Event):void {
var i:int;
if (evt.currentTarget.selected) {
for (i = 0; i < tableData.length; i++) {
tableData[i].sel = true;
}
} else
if (!evt.currentTarget.selected) {
for (i = 0; i < tableData.length; i++) {
tableData[i].sel = false;
}
}
tableGrid.invalidateDisplayList();
tableGrid.invalidateList();
}

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@id == "newdb")?(newDatabase()):(void);
(evt.item.@id == "opendb")?(openDatabase()):(void);
}

private function newDatabase():void {
var file:File = File.desktopDirectory.resolvePath("Untitled");
file.addEventListener(Event.SELECT, newSelect);
file.browseForSave("Choose where to save the database");
var newDB:XML;
var statement:SQLStatement = new SQLStatement();
function newSelect(evt:Event):void {
if (file.exists) {
Alert.show("File already exists, cannot overwrite.", "Nope");
return;
}
file.nativePath += ".db";
var n:String = parseDatabase(file);
loadDataSchema(n);
}
}

private function openDatabase():void {
var file:File = new File();
file.browseForOpen("Open database", [new FileFilter("Databases", "*.db"), new FileFilter("All files", "*")]);
file.addEventListener(Event.SELECT, openSelect);

function openSelect(evt:Event):void {
var n:String = parseDatabase(file, true);
loadDataSchema(n);
}
}

private function loadDataSchema(name:String):void {
if (name != "") {
connection.loadSchema(null, null, name, true, new Responder(schemaSuccess, schemaError));
function schemaSuccess(evt:SQLSchemaResult):void {
// Schema found! Now parsing:
var result:SQLSchemaResult = evt;
// Adding tables:
var nid:Number = (name=="main")?(1):(Number(name.replace("db", "")));
var dataNode:XMLList = dbData.db.(@numid == nid);
dataNode.setChildren(<placeholder/>);
delete dataNode.placeholder;
for (var i:int = 0; i < result.tables.length; i++) {
var newTable:XML = new XML(<tb/>);
newTable.@label = result.tables[i].name;
newTable.@isBranch = false;
newTable.@databaseName = name;
dataNode.appendChild(newTable);
}
}
function schemaError(evt:SQLError):void {
// Alert.show("Database is empty");
}
isTableSelected = false;
}
}

private function parseDatabase(file:File, needCheck:Boolean = false):String {
var ret:String = "";
if (!needCheck || file.exists) {
if(!needCheck || notAlreadyOpen(file)){
var newDB:XML;
if (dbData.db.length() == 0) {
connection.open(file);
dbData = new XMLList(<root></root>);
newDB = <db/>
newDB.@label = file.name;
newDB.@numid = 1;
newDB.@isBranch = true;
newDB.@path = file.nativePath;
dbData[0].appendChild(newDB);
ret = "main";
}else
if (dbData.db.length() > 0) {
var newnum:int = dbData.db.length() + 1;
connection.attach("db"+newnum.toString(), file);
newDB = <db/>
newDB.@label = file.name;
newDB.@numid = newnum.toString();
newDB.@isBranch = true;
newDB.@path = file.nativePath;
dbData[0].appendChild(newDB);
ret = "db" + newnum.toString();
}}else {
Alert.show("Database already opened.", "Error");
}
}else {
Alert.show("File not found.", "Error");
}
return ret;
}

private function notAlreadyOpen(file:File):Boolean{
var r:Boolean = true;
for (var i:int = 0; i < dbData.db.length(); i++) {
if (file.nativePath == dbData.db[i].@path) {
r = false;
}
}
return r;
}

private function tableSelect(evt:Event):void {
if (evt.currentTarget.selectedItem.@isBranch) {
isTableSelected = false;
var dataname:String;
if (evt.currentTarget.selectedItem.@numid == 1) dataname = "main";
if (evt.currentTarget.selectedItem.@numid > 1) dataname = "db" + evt.currentTarget.selectedItem.@numid;
selectedDatabase = dataname;
}
if (evt.currentTarget.selectedItem.@isBranch == false) {
isTableSelected = true;
selectedDatabase = evt.currentTarget.selectedItem.@databaseName;
tableData = new ArrayCollection([]);
var newColumns:Array = [checkboxColumn];
connection.loadSchema(SQLTableSchema, evt.currentTarget.selectedItem.@label, evt.currentTarget.selectedItem.@databaseName);
var schema:SQLSchemaResult = connection.getSchemaResult();
for (var i:int = 0; i < schema.tables[0].columns.length; i++) {
var aColumn:AdvancedDataGridColumn = new AdvancedDataGridColumn(schema.tables[0].columns[i].name);
newColumns.push(aColumn);
}
tableGrid.columns = newColumns;
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "SELECT * FROM " + evt.currentTarget.selectedItem.@databaseName + "." + evt.currentTarget.selectedItem.@label;
stat.execute(-1, new Responder(tableSuccess, tableError));
}
function tableSuccess(evt:SQLResult):void {
if (evt.data != null) {
for (var item:Object in evt.data) {
var obj:Object = new Object();
for (var value:Object in evt.data[item]) {
obj[value] = evt.data[item][value];
}
tableData.addItem(obj);
}
}
}
function tableError(evt:SQLError):void {
Alert.show("Unable to read table data.", "Error");
}
}

private function newTable():void {
PopUpManager.addPopUp(newTableWindow, this);
PopUpManager.centerPopUp(newTableWindow);
newTableWindow.title = "Create new table";
}

private function dropTable():void {
Alert.show("Are you sure you want to completely delete this table?", "Drop table?", Alert.YES | Alert.NO, null, dropConfirm);
function dropConfirm(evt:CloseEvent):void {
if (evt.detail == Alert.YES) {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "DROP TABLE " + selectedDatabase + "." + tableTree.selectedItem.@label;
stat.execute( -1, new Responder(dropTableSuccess, dropTableError));
}
}
function dropTableSuccess(evt:SQLResult):void {
loadDataSchema(selectedDatabase);
}
function dropTableError(evt:SQLError):void {
Alert.show("ERROR:" + evt.details, "Error");
}
}

private function closeNewTableWindow():void{
PopUpManager.removePopUp(newTableWindow);
}

private function createNewTable():void {
var stat:SQLStatement = new SQLStatement();
stat.sqlConnection = connection;
stat.text = "CREATE TABLE IF NOT EXISTS " + selectedDatabase + "." + newTableName.text + "(id INTEGER PRIMARY KEY AUTOINCREMENT, blankColumn TEXT)";
stat.execute( -1, new Responder(newTableSuccess, newTableError));
function newTableSuccess(evt:SQLResult):void {
closeNewTableWindow();
loadDataSchema(selectedDatabase);
}
function newTableError(evt:SQLError):void {
Alert.show("ERROR:" + evt.details, "Error");
}
}
]]>
</fx:Script>

<s:HGroup gap="0" width="100%" height="100%">
<s:VGroup width="200" height="100%" gap="0">
<s:HGroup>
<s:Button label="New table" click="newTable();" enabled="{tableTree.selectedItems.length>0}"/>
<s:Button label="Drop table" click="dropTable();" enabled="{isTableSelected}" />
</s:HGroup>
<mx:Tree id="tableTree" width="100%" height="100%" dataProvider="{dbData}" showRoot="false" labelField="@label" itemClick="tableSelect(event);"/>
</s:VGroup>
<s:VGroup width="100%" height="100%" gap="0">
<mx:TabNavigator width="100%" height="100%" paddingTop="0">
<s:NavigatorContent label="Table contents">
<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox width="100%" height="30" paddingLeft="8" paddingTop="6">
<mx:CheckBox label="Select all" change="selectAllChange(event);" />
<s:Button label="Delete selected"/>
</mx:HBox>
<mx:AdvancedDataGrid id="tableGrid" width="100%" height="100%" dataProvider="{tableData}" editable="true">
<mx:columns>
<mx:AdvancedDataGridColumn dataField="" headerText="Data" editable="false" />
</mx:columns>
</mx:AdvancedDataGrid>
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent label="Edit columns">

</s:NavigatorContent>
<s:NavigatorContent label="Query">

</s:NavigatorContent>
</mx:TabNavigator>
</s:VGroup>
</s:HGroup>

</s:WindowedApplication>

Thanks for reading!
Read more »