Thursday, October 20, 2011

Creating a Flex AIR text editor: Part 10

In this tutorial we will create a new preference variable in our SharedObject that is responsible for font settings, and make all the font components in the FontWindow.mxml document take the values from that variable when the window is opened.

Firstly, create a new bindable public variable called pref_fontsettings in the main mxml file, make it an object.

[Bindable]
public var pref_fontsettings:Object = new Object();

In the init() function, add a new line to the preferences.data.firsttime==null conditional, which will set default font settings on the first loadup:

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

Remember that before running the final code in the end of this tutorial, you'll need to add preferences.data.firsttime=null before this conditional to reset it (so that the default values actually get set!).

Now we set the local variable preferences to the ones from the shared object:

// Set preferences loaded from local storage
pref_wrap = preferences.data.wrap;
pref_status = preferences.data.status;
pref_fontsettings = preferences.data.fontsettings;

In the doFont() function, call the fontWindow's method setValues, which we pass our font setting values to:

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

Go to FontWindow.mxml, and create this setValues function, which receives all the values:

public function setValues(fsize:int, ffamily:String, fstyle:String, fweight:String, fcolor:Number, bgcolor:Number):void{

}

In this function, we will need to write code to update the components' values to the ones that are passed from the main file.

The NumericStepper and ColorPicker components are the easiest ones - simply set the values to .value and .selectedColor properties:

public function setValues(fsize:int, ffamily:String, fstyle:String, fweight:String, fcolor:Number, bgcolor:Number):void{
sizeStepper.value = fsize;
colorPicker.selectedColor = fcolor;
bgColorPicker.selectedColor = bgcolor;

}

The font style and font weight need to be checked to determine which font style should be selected - regular, italic, bold or bold italic. Perform a check like this:

public function setValues(fsize:int, ffamily:String, fstyle:String, fweight:String, fcolor:Number, bgcolor:Number):void{
sizeStepper.value = fsize;
colorPicker.selectedColor = fcolor;
bgColorPicker.selectedColor = bgcolor;
if (fstyle == "normal" && fweight == "normal") { styleCombo.selectedIndex = 0; }
if (fstyle == "italic" && fweight == "normal") { styleCombo.selectedIndex = 1; }
if (fstyle == "normal" && fweight == "bold") { styleCombo.selectedIndex = 2; }
if (fstyle == "italic" && fweight == "bold") { styleCombo.selectedIndex = 3; }

}

For the font family list component, we check all the elements of the allFonts array and compare its fontName property to the passed font name, then set the list's selectedIndex property to the for..loop's "i" variable:

public function setValues(fsize:int, ffamily:String, fstyle:String, fweight:String, fcolor:Number, bgcolor:Number):void{
sizeStepper.value = fsize;
colorPicker.selectedColor = fcolor;
bgColorPicker.selectedColor = bgcolor;
if (fstyle == "normal" && fweight == "normal") { styleCombo.selectedIndex = 0; }
if (fstyle == "italic" && fweight == "normal") { styleCombo.selectedIndex = 1; }
if (fstyle == "normal" && fweight == "bold") { styleCombo.selectedIndex = 2; }
if (fstyle == "italic" && fweight == "bold") { styleCombo.selectedIndex = 3; }
for (var i:int = 0; i < allFonts.length; i++ ) {
if (allFonts[i].fontName == ffamily) {
fontCombo.selectedIndex = i;
break;
}
}
}

Now in the fontCombo list component we need to add some code that will scroll to selected index on creationComplete event:

<mx:List id="fontCombo" dataProvider="{allFonts}" labelField="fontName" fontSize="12" rowCount="8" variableRowHeight="true" width="380" creationComplete="{fontCombo.scrollToIndex(fontCombo.selectedIndex);}">

Complete FontWindow.mxml file:

<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
xmlns:mx="library://ns.adobe.com/flex/mx"
title="Font settings" type="utility" width="400" height="550"
creationComplete="init();" showStatusBar="false" alwaysInFront="true" resizable="false">

<fx:Declarations>
<mx:ArrayCollection id="allFonts"
            source="{Font.enumerateFonts(true)}">
        <mx:sort>
            <mx:Sort>
                <mx:fields>
                    <mx:SortField name="fontName" />
                </mx:fields>
            </mx:Sort>
        </mx:sort>
    </mx:ArrayCollection>
<mx:ArrayCollection id="fontStyles">
<fx:Object label="Regular" fontstyle="normal" fontweight="normal" />
<fx:Object label="Italic" fontstyle="italic" fontweight="normal"/>
<fx:Object label="Bold" fontstyle="normal" fontweight="bold"/>
<fx:Object label="Bold Italic" fontstyle="italic" fontweight="bold"/>
</mx:ArrayCollection>
</fx:Declarations>

<fx:Script>
<![CDATA[
import flash.events.Event;
import mx.controls.Alert;
import mx.core.FlexGlobals;

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

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

public function setValues(fsize:int, ffamily:String, fstyle:String, fweight:String, fcolor:Number, bgcolor:Number):void{
sizeStepper.value = fsize;
colorPicker.selectedColor = fcolor;
bgColorPicker.selectedColor = bgcolor;
if (fstyle == "normal" && fweight == "normal") { styleCombo.selectedIndex = 0; }
if (fstyle == "italic" && fweight == "normal") { styleCombo.selectedIndex = 1; }
if (fstyle == "normal" && fweight == "bold") { styleCombo.selectedIndex = 2; }
if (fstyle == "italic" && fweight == "bold") { styleCombo.selectedIndex = 3; }
for (var i:int = 0; i < allFonts.length; i++ ) {
if (allFonts[i].fontName == ffamily) {
fontCombo.selectedIndex = i;
break;
}
}
}
]]>
</fx:Script>

<s:VGroup paddingLeft="5" paddingRight="5" paddingTop="5" paddingBottom="5">
<s:Label text="Font:" />

<mx:List id="fontCombo" dataProvider="{allFonts}" labelField="fontName" fontSize="12" rowCount="8" variableRowHeight="true" width="380" creationComplete="{fontCombo.scrollToIndex(fontCombo.selectedIndex);}">
        <mx:itemRenderer>
            <fx:Component>
                <mx:Label fontFamily="{data.fontName}" toolTip="{data.fontName}"/>
            </fx:Component>
        </mx:itemRenderer>
    </mx:List>

<s:Label text="Font style:" />

<mx:List id="styleCombo" dataProvider="{fontStyles}" labelField="label" fontSize="12" rowCount="4" variableRowHeight="true" width="380">
        <mx:itemRenderer>
            <fx:Component>
                <mx:Label fontFamily="{outerDocument.fontCombo.selectedItem.fontName}" toolTip="{data.label}" fontStyle="{data.fontstyle}" fontWeight="{data.fontweight}" />
            </fx:Component>
        </mx:itemRenderer>
    </mx:List>

<s:HGroup verticalAlign="middle">
<s:Label text="Font size:" /><mx:NumericStepper id="sizeStepper" minimum="2" maximum="72"/>
<s:Label text="Font color:" /><mx:ColorPicker id="colorPicker"/>
<s:Label text="Background color:" /><mx:ColorPicker id="bgColorPicker"/>
</s:HGroup>

<s:Label text="Sample:" />
<mx:Box backgroundColor="{bgColorPicker.selectedColor}" width="370" height="70" paddingLeft="5" paddingTop="5">
<s:Label fontFamily="{fontCombo.selectedItem.fontName}" fontSize="{sizeStepper.value}" 
color="{colorPicker.selectedColor}" fontStyle="{styleCombo.selectedItem.fontstyle}" 
fontWeight="{styleCombo.selectedItem.fontweight}" text="AaBbYyZz"/>
</mx:Box>

<s:Button label="Apply settings"/>
</s:VGroup>

</mx:Window>

Main document 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"
   creationComplete="init();" title="Kirpad" showStatusBar="{pref_status}">
   
<s:menu>
<mx:FlexNativeMenu dataProvider="{windowMenu}" showRoot="false" labelField="@label" keyEquivalentField="@key" itemClick="menuSelect(event);" />
</s:menu>

<fx:Script>
<![CDATA[
import flash.events.KeyboardEvent;
import flash.events.Event;
import flash.net.SharedObject;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.events.FlexNativeMenuEvent;
import flashx.textLayout.elements.TextFlow;
import flashx.textLayout.elements.Configuration;
import flash.system.System;
import flash.desktop.Clipboard;
import flash.desktop.ClipboardFormats;
import flash.ui.Mouse;

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

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

private var statusMessage:String;

public var fontWindow:FontWindow = new FontWindow();

private function init():void {

// Create a listener for every frame
addEventListener(Event.ENTER_FRAME, everyFrame);

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

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.wrap = true;
preferences.data.status = true;
preferences.data.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_fontsettings = preferences.data.fontsettings;

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

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

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

private function menuSelect(evt:FlexNativeMenuEvent):void {
(evt.item.@label == "Word wrap")?(pref_wrap = !pref_wrap):(void);
(evt.item.@label == "Cut")?(doCut()):(void);
(evt.item.@label == "Copy")?(doCopy()):(void);
(evt.item.@label == "Paste")?(doPaste()):(void);
(evt.item.@label == "Select all")?(doSelectall()):(void);
(evt.item.@label == "Status bar")?(pref_status = !pref_status):(void);
(evt.item.@label == "Font...")?(doFont()):(void);
savePreferences();
updateStatus();
}

private function savePreferences():void {
preferences.data.wrap = pref_wrap;
preferences.data.status = pref_status;
preferences.flush();
}

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

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

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

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

private function insertText(str:String):void {
var substrPositions:int = textArea.selectionActivePosition - textArea.selectionAnchorPosition;
var oldSel1:int = (substrPositions>0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var oldSel2:int = (substrPositions<0)?(textArea.selectionAnchorPosition):(textArea.selectionActivePosition);
var preText:String = textArea.text.substring(0, oldSel1);
var postText:String = textArea.text.substring(oldSel2);
var newSelectRange:int = preText.length + str.length;
textArea.text = preText + str + postText;
textArea.selectRange(newSelectRange, newSelectRange);
}

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

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

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

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

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

private function onClose(evt:Event):void{
var allWindows:Array = NativeApplication.nativeApplication.openedWindows;
for (var i:int = 0; i < allWindows.length; i++)
{
allWindows[i].close();
}
}
]]>
</fx:Script>

<fx:Declarations>
<fx:XML id="windowMenu">
<root>
<menuitem label="File">
<menuitem label="Open" key="o" controlKey="true" />
</menuitem>
<menuitem label="Edit">
<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="Status bar" type="check" toggled="{pref_status}" />
</menuitem>
</root>
</fx:XML>
</fx:Declarations>

<s:TextArea id="textArea" width="100%" height="100%" borderVisible="false" lineBreak="{(pref_wrap)?('toFit'):('explicit')}"  click="cursorFix(); updateStatus();" change="updateStatus();" keyDown="updateStatus();" />
</s:WindowedApplication>

Thanks for reading!

No comments:

Post a Comment