大稻埕煙火

November 8th, 2009

感謝小巧帶我去看煙火。坐在野餐墊上戰了七八場富饒之城後,我們順利地等到煙火。

特別喜愛像金色大煙火。沒有多餘的顏色,只有金與黑,如同一管巨大的毛筆沾著金粉墨水,緩緩地在天空黑布上抹下,不知怎麼如此均勻,彷彿盛開的花瓣,對稱的大圓。後來漸漸地垂下,像是金色的雨水沾著窗戶慢慢流下,而我們在窗沿邊拼命讚嘆,就像小孩兒第一次看見彩虹。
煙火是種表演藝術,一波又一波的花樣,前後呼應,剛剛流瀉而下的金雨,下一刻恍若退至幕前,另一道五彩煙花炸裂,一幅金葉藍花倏忽盛開。花開花落,數秒驟逝。

除了煙火本身之外,第一次體會到直接的輿論力量,後排的人們齊心吶喊請前排的民眾坐下,風行草偃,大家都能一望無際。感謝大家的公德心和同理心。

Adobe 官方推出拼字檢查元件( Squiggly ) for Flash/Flex

September 24th, 2009

念法:
Squiggly [ˈskwigli] DJ [ˈskwɪglɪ] KK

下載:
http://labs.adobe.com/downloads/squiggly.html
Demo:
http://labs.adobe.com/technologies/squiggly/demo/

使用: Flex Unit Test Framework

September 11th, 2009

因應 XP 開發手法的趨勢,Adobe也推出FlexUnit: Unit test framework。但我個人覺得,Flex主要是關連到UI的,而UI的測試,光靠FlexUnit不夠的,用人測最直接。然而,通常你找不到那個人。

下載地點:Adobe open source project: Flex unit

何謂Unit Test? 簡單地來說:就每個Method當成一個unit(單位),測試之。在XP的開發過程中,是測試導向開發,什麼是”測試導向”? 就是:每次要寫新功能的時候,不是先寫功能的運作方法,而是先寫『測試該功能的程式』,換言之,我們要以”通過測試”為方向,去開發新功能的程式。因此,我們的開發流程往往是: 先寫空白的功能程式,譬如一個空的方法: getSomeResult(),然後先寫測試碼(test case),像是: testGetSomeResult() 然後寫上我們”預期的”測試結果,立刻跑一次test runner,當然,不會過關,立刻回頭撰寫getSomeResult()內容,讓它正確通過test case,那麼getSomeResult()即開發完成 。(見下方的步驟)

這樣的好處,除了確保開發出來的功能可以通過測試;往後的重構(refactor)或是需求變更,都可以再次把unit test 列出來跑一次,確保原來的功能沒有毀掉。
使用步驟:

  1. 建立TestRunner
  2. 寫TestCase/TestSuite並加入TestRunner
  3. 跑TestRunner
  4. 看結果,修改原始碼,回到(2)

例子:

//這是要被測試的class method

public class MyApp
{
      public function getSomeResult( input:Number ):Number
      {
           //這裡放入真正的邏輯...
           return input/10;
       }
}
//這是測試code( Test Case 不會用在真的主程式裡 ) : MyTestCase.as
private var classToTestRef:MyApp;
public function testGetSomeResult():void
{
     classToTestRef = new MyApp();
       /*assertEquals : 意思是兩個參數的值結果應該要一樣,
    如果不一樣test 就失敗(fail),你會在TestRunner看到
      底下這個例子會過!
       */

     assertEquals(  100 , classToTestRef.getSomeResult( 1000 ) );
      //這個不會過
     assertEquals(  2, classToTestRef.getSomeResult( 1000 ) );
      //還有許多assertXXX可以用,請見官方文件
}

特別要強調的是,針對Event處理的測試要怎麼寫? 因為Event發出的時間往往有時間差,並不即時。FlexUnit體貼地加上了一個addAsync() 的方法,幫助我們測試event listeners。寫法如下:

addAsync( eventListener:Function/*放置assertion的listener*/ , timeout:int /*過了多少時間(ms)就算失敗?*/);
public function testVeryComplexCalulation():void
{
    //用addAsync() 把test case 延遲檢查:
    instanceToTest.addEventListener( Event.COMPLETE ,
    addAsync( onComplete , 1000 /*設定:1秒沒發生就算測試失敗*/) );  
    instanceToTest.run();
}

//在 event listener 裡面才寫 assertions:
private function onComplete( event:Event ):void
{
   assertNotNull( instanceToTest.result );
}

Event, EventDispatcher, Listener

August 13th, 2009

這三者的關係,可以用一個情境來解釋:
總機先生,電話機,通話內容。

總機先生是 Listener
電話是 EventDispatcher
通話內容是 Event

通話內容必須透過電話傳達出去,出去之後就由總機先生立刻接聽;此外,一通電話內容可以讓無限個總機接聽(想像把電話機設定成擴音模式)。這個模式的作用在於,讓總機先生在『適當的時間』,做『適當的事』,比如說,一接到找老闆的電話,就要先問:『您是哪位?』此時『一接到電話』就是Event 透過EventDispatcher 發出來了,又正好總機監聽著電話狀態,所以總機立刻根據電話內容(event),做了特定的事情(問:您是哪位?)。再舉個例子,像是接到愛慕的A氏,就回答:『哈囉你好嗎?』 這兩種回達都是一個Listener ,根據不同的事件,定義不同的listener。

Listener 在 AS3.0 的世界,規定要寫成:

function functionName( event:Event ):void

要監聽某個電話就要寫成:
myPhone.addEventListener( eventType , myListener );
其中eventType是個字串,代表事件的類別(老闆打的還是同事打的),myListener 就是呼應的動作。要特別強調的是,在AS的世界裡,當一個Event被dispatch出來之後,對應的listener幾乎『沒有時差』就發動,因此我們常遇到UIComponent繪製畫面不如預期,這時可在listener之中利用 callLater 來針對UIComponent操作,利用callLater的延遲是比較正統的作法。

AIR 跟 JS…

July 26th, 2009

當兩者糾纏在一起的時候,如果由AIR呼叫Javascript 會掛掉的話,可先試著用setTimeout,通常都可以解決。因為HTML DOM需要時間初始化,Javascript 才能被呼叫。

像是 setTimeout( domWindow.myJsFunction , 1000 );

強制結束DragManager

July 23rd, 2009
DragManagerImpl#endDrag();

見:DragManagerImpl.as
由於原本的DragManager實作了IDragManager,裡面並沒有公開endDrag()此signature,因此你必須轉型之後才能呼叫這個方法。

清除預設的DragEvent外觀效果

May 22nd, 2009

針對Flex 預設的Drag-Drop,List元件會貼心地幫你畫出indicator(預設是一條線),然而,一旦試著用 DragEvent#preventDefault()時,indicator 會殘留在畫面上,此時,可以利用

ListBase#hideDropFeedback(event:DragEvent)

即可清除掉indicator, highlight 等 DisplayObject.

==
To clean up Flex default DragDrop visualization, try ListBase#hideDropFeedback(event), Especially when implementing your own drag-drop event handlers. This method remove all highlight and indicator display objects.

英文單字單複數互轉(Actionscript Inflection Utility)

May 19th, 2009

模仿Rails的Inflection方法,用actionscript 改寫如下:

Inflection#singularize : 將複數轉單數

如:
Inflection.singularize( “cars” ); //cars => car

Inflection#pluralize : 將單數轉複數

如:
Inflection.pluralize( “apple” ); // apple => apples

原始碼如下:(source)

package net.shiue.util
{
public class Inflection
    {
        public function Inflection()
        {
        }
       
        public static function pluralize( word:String ):String
        {
           
            if( isUncountable( word ) )
                return word;
           
            for each( var r:Replacer in plurals )
            {
                if( word.search( r.pattern ) >= 0 )
                    return word.replace( r.pattern , r.repalcePattern );
            }
           
            return word;
        }
       
        public static function singularize( word:String ):String
        {
           
            if( isUncountable( word ) )
                return word;
           
           
            for each( var r:Replacer in singulars )
            {
                if( word.search( r.pattern ) >= 0 )
                    return word.replace( r.pattern , r.repalcePattern );
            }
           
            return word;
        }
       
        private static var _plurals:Array;
       
        private static function get plurals():Array
        {
           
            if( _plurals == null )
            {
                _plurals = [];
               
                _plurals.push( new Replacer( /(quiz)$/i , "$1zes" ) );
                _plurals.push( new Replacer( /^(ox)$/i , "$1en" ) );
                _plurals.push( new Replacer( /([m|l])ouse$/i , "$1ice" ) );
                _plurals.push( new Replacer( /(matr|vert|ind)(?:ix|ex)$/i , "$1ices" ) );
                _plurals.push( new Replacer( /(x|ch|ss|sh)$/i , "$1es" ) );
                _plurals.push( new Replacer( /([^aeiouy]|qu)y$/i , "$1ies" ) );
                _plurals.push( new Replacer( /(hive)$/i , "$1s" ) );
                _plurals.push( new Replacer( /(?:([^f])fe|([lr])f)$/i , "$1$2ves" ) );
                _plurals.push( new Replacer( /sis$/i , "ses" ) );
                _plurals.push( new Replacer( /([ti])um$/i , "$1a" ) );
                _plurals.push( new Replacer( /(buffal|tomat)o$/i , "$1oes" ) );
                _plurals.push( new Replacer( /(bu)s$/i , "$1ses" ) );
                _plurals.push( new Replacer( /(alias|status)$/i , "$1es" ) );
                _plurals.push( new Replacer( /(octop|vir)us$/i , "$1i" ) );
                _plurals.push( new Replacer( /(ax|test)is$/i , "$1es" ) );
                _plurals.push( new Replacer( /s$/i , "s" ) );
                _plurals.push( new Replacer( /$/i , "s" ) );
               
            }
           
            return _plurals;
        }
       
        private static var _singulars:Array;
       
        private static function get singulars():Array
        {
            if( _singulars == null )
            {
                _singulars  = [];
               
                _singulars.push( new Replacer(/(database)s$/i , "$1") );
                _singulars.push( new Replacer(/(quiz)zes$/i , "$1") );
                _singulars.push( new Replacer(/(matr)ices$/i , "$1ix") );
                _singulars.push( new Replacer(/(vert|ind)ices$/i , "$1ex") );
                _singulars.push( new Replacer(/^(ox)en/i , "$1") );
                _singulars.push( new Replacer(/(alias|status)es$/i, "$1") );
                _singulars.push( new Replacer(/(octop|vir)i$/i, "$1us") );
                _singulars.push( new Replacer(/(cris|ax|test)es$/i, "$1is") );
                _singulars.push( new Replacer(/(shoe)s$/i, "$1") );
                _singulars.push( new Replacer(/(o)es$/i, "$1") );
                _singulars.push( new Replacer(/(bus)es$/i, "$1") );
                _singulars.push( new Replacer(/([m|l])ice$/i, "$1ouse") );
                _singulars.push( new Replacer(/(x|ch|ss|sh)es$/i, "$1") );
                _singulars.push( new Replacer(/(m)ovies$/i, "$1ovie") );
                _singulars.push( new Replacer(/(s)eries$/i, "$1eries") );
                _singulars.push( new Replacer(/([^aeiouy]|qu)ies$/i, "$1y") );
                _singulars.push( new Replacer(/([lr])ves$/i, "$1f") );
                _singulars.push( new Replacer(/(tive)s$/i, "$1") );
                _singulars.push( new Replacer(/(hive)s$/i, "$1") );
                _singulars.push( new Replacer(/([^f])ves$/i, "$1fe") );
                _singulars.push( new Replacer(/(^analy)ses$/i, "$1sis") );
                _singulars.push( new Replacer(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis") );
                _singulars.push( new Replacer(/([ti])a$/i, "$1um") );
                _singulars.push( new Replacer(/(n)ews$/i, "$1ews") );
                _singulars.push( new Replacer(/s$/i, "") );
               

            }
           
            return _singulars;
           
        }
       
        private static var _irregular:Array;
       
        private static function get irregular():Array
        {
            if( _irregular == null )
            {
                _irregular = [];
               
                _irregular.push( new Replacer(/person/i , "people") );
                _irregular.push( new Replacer(/man/i , "men") );
                _irregular.push( new Replacer(/woman/i , "women") );
                _irregular.push( new Replacer(/child/i , "children") );
                _irregular.push( new Replacer(/sex/i , "sexes") );
                _irregular.push( new Replacer(/move/i , "moves") );
                _irregular.push( new Replacer(/cow/i , "kine") );
            }
           
            return _irregular;
        }
           
        private static var _uncountable:Array;
       
        private static function get uncountable():Array
        {
            if( _uncountable == null )
            {
                _uncountable = [];
               
                _uncountable.push("equipment");
                _uncountable.push("information");
                _uncountable.push("rice");
                _uncountable.push("money");
                _uncountable.push("species");
                _uncountable.push("series");
                _uncountable.push("fish");
                _uncountable.push("sheep");
               
            }
           
            return _uncountable;
        }
       
        public static function isUncountable( word:String ):Boolean
        {
            return uncountable.indexOf( word )>=0;
        }

    }
   
   
}
   

    class Replacer
    {
        public function Replacer( pattern:RegExp , replacePattern:String )
        {
            this.pattern = pattern;
            this.repalcePattern = replacePattern;
        }
       
        public var pattern:RegExp;
        public var repalcePattern:String;
    }

關於Native Menu(Mac) 2

May 14th, 2009

上次提到的Mac Native Menu 監聽不到自訂menu item之問題有解答了。
問題關鍵在我的 ApplicationUpdaterUI :ApplicationUpdaterUI 在初始化完成後,會摧毀自訂item的事件發送,所以無法選擇,但是系統預設item依舊運作良好。換句話說,在 ApplicationUpdater 發出 UpdateEvent.INITIALIZED 之前,建立的native menu都會失效(看得到選不到)。 因此必須針對 ApplicationUpdaterUI 監聽事件,之後才加上自訂的menu item,如此才能圓滿達成自定menu item與native menu item 共存的成果。

以下例子: 簡單地在 Mac 的第一個menu item下加入一個”myItem”

var ui:ApplicationUpdaterUI = new ApplicationUpdater();
ui.addEventListener( UpdaterEvent.INITIALIZED , uiInited );
ui.initalize();

function uiInited( e:UpdaterEvent ):void
{
//取得 native menu, 唯有mac os才會有預設的native menu;
//windows下自己建立一個NativeMenu 實體即可
var m:NativeMenu  = NativeApplication.nativeApplication.menu;
//items[0] = 第1個menu
m.items[0].addItem( new NativeMenuItem("myItem") ).addEventListener( Event.SELECT , onSelect );

}

function onSelect( e:Event ):void
{
trace("hello there");
}

製作蘋果電腦的標題列外皮(Mac title bar skin)

May 12th, 2009

Mac的比較漂亮啦 !

剛剛研究出快速畫出Mac標題列的樣式,這麼做:
建立自己的ProgramaticSkin, 覆寫 updateDisplayList( w , h );

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList( unscaledWidth , unscaledHeight );
var g:Graphics = graphics;
g.clear();

var m:Matrix = verticalGradientMatrix( 0 , 0 , unscaledWidth , unscaledHeight );
drawRoundRect( 0 , 0 , unscaledWidth , unscaledHeight , { tl:6,tr:6,bl:0,br:0 } ,
          [ 0xC5C5C5 , 0x969696 ] , [1,1] ,m , GradientType.LINEAR , [0,255] );

}