<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="900" height="650" creationComplete="init();" xmlns:ui="com.adobe.arise.ui.*" viewSourceURL="srcview/index.html">
    <mx:Script>
        <![CDATA[
            import com.adobe.xml.syndication.generic.IItem;
            import mx.events.CloseEvent;
            import mx.controls.CheckBox;
            import mx.formatters.DateFormatter;
            import mx.events.ListEvent;
            import com.adobe.xml.syndication.generic.IItem;
            import flash.data.SQLConnection;
            import flash.data.SQLStatement;
            import flash.data.SQLResult;
            import flash.events.SQLEvent;
            import flash.filesystem.*;
            import com.adobe.xml.syndication.generic.*;
            import mx.collections.ArrayCollection;
            import com.adobe.arise.aggregator.*;
            import com.adobe.arise.database.*;
            import mx.controls.Alert;
            import com.adobe.arise.ui.Info;

            private var agg:Aggregator;
            private var conn:SQLConnection;
            private var sql:XML;
            private var dateFormatter:DateFormatter;
            private var timer:Timer;

            // NavigationBox data providers
            [Bindable] private var topics:ArrayCollection;
            [Bindable] private var authors:ArrayCollection;
            [Bindable] private var feeds:ArrayCollection;
            [Bindable] private var posts:ArrayCollection;
            [Bindable] private var views:ArrayCollection;

            // Icons
            [Bindable]
            [Embed(source="assets/face-smile.png")]
            private var smileIcon:Class;

            [Bindable]
            [Embed(source="assets/dialog-error.png")]
            private var errorIcon:Class;

            [Bindable]
            [Embed(source="assets/emblem-important.png")]
            private var warningIcon:Class;

            [Bindable]
            [Embed(source="assets/unread.png")]
            public var unreadIcon:Class;

            [Bindable]
            [Embed(source="assets/read.png")]
            public var readIcon:Class;

            /////////////////////////
            // Startup functions
            /////////////////////////

            private function init():void
            {
                // Initialize data providers
                topics = new ArrayCollection();
                authors = new ArrayCollection();
                feeds = new ArrayCollection();
                posts = new ArrayCollection();
                views = new ArrayCollection();
                
                dateFormatter = new DateFormatter();
                dateFormatter.formatString = "EEE MMM D at L:NN A";
                
                // Create the aggregator
                agg = new Aggregator();
                
                // Load database queries
                var sqlFile:File = File.applicationResourceDirectory.resolvePath("sql.xml");
                var sqlFileStream:FileStream = new FileStream();
                sqlFileStream.open(sqlFile, FileMode.READ);
                sql = new XML(sqlFileStream.readUTFBytes(sqlFileStream.bytesAvailable));
                sqlFileStream.close();

                // Initialize views
                views.addItemAt({"viewName":"All Unread", "count":0}, 0);
                views.addItemAt({"viewName":"Checked",    "count":0}, 1);

                // Initialize the database
                initializeDatabase();

                flash.net.registerClassAlias("flash.geom.Rectangle", flash.geom.Rectangle);
                flash.net.registerClassAlias("flash.geom.Point", flash.geom.Point);

                // Disabled for now due to a bounds bug.
                //this.restorePreferences();
                //this.addEventListener(Event.CLOSING, savePreferences);
            }

            private function start():void
            {
                refreshUI(true);
            }

            private function savePreferences(e:Event):void
            {
                var prefs:Object = new Object();
                prefs.bounds = this.bounds;
                var prefsFile:File = File.applicationStorageDirectory.resolvePath("prefs.obj");
                var fs:FileStream = new FileStream();
                fs.open(prefsFile, FileMode.WRITE);
                fs.writeObject(prefs);
                fs.close();
            }
            
            private function restorePreferences():void
            {
                var prefsFile:File = File.applicationStorageDirectory.resolvePath("prefs.obj");
                if (prefsFile.exists)
                {
                    var fs:FileStream = new FileStream();
                    fs.open(prefsFile, FileMode.READ);
                    var prefs:Object = fs.readObject();
                    fs.close();
                    this.bounds = Rectangle(prefs.bounds);
                }
                else // Defaults
                {
                }

                this.visible = true;
            }

            /////////////////////////
            // End startup functions
            /////////////////////////

            /**
             * Aggregates every hour.
             */
            private function resetTimer():void
            {
                if (this.timer == null)
                {
                    timer = new Timer(3600000, 0); // Every hour
                    timer.addEventListener(TimerEvent.TIMER, onRefreshAll);
                }
                timer.stop();
                timer.start();
            }
            
            private function addFeed():void
            {
                if (newFeedUrl.text.length == 0) return;
                newFeedDrawer.close();
                aggregateFeeds([newFeedUrl.text]);
            }
            
            private function onRefreshAll(e:Event = null):void
            {
                var feedUrls:Array = new Array();
                for each (var feed:Object in feeds)
                {
                    feedUrls.push(feed.url);
                }
                aggregateFeeds(feedUrls);
                resetTimer();
            }
            
            private function importFeeds():void
            {
                var opmlFile:File = File.desktopDirectory;
                opmlFile.addEventListener(Event.SELECT,
                    function(e:Event):void
                    {
                        var fs:FileStream = new FileStream();
                        fs.open(opmlFile, FileMode.READ);
                        var opmlStr:String = fs.readUTFBytes(fs.bytesAvailable);
                        fs.close();
                        var opml:XML;
                        try
                        {
                            opml = new XML(opmlStr);
                        }
                        catch (e:Error)
                        {
                            trace("Invalid OPML");
                            return;
                        }
                        var xmlUrls:ArrayCollection = new ArrayCollection();
                        for each (var xmlUrl:String in opml..@xmlUrl)
                        {
                            if (!xmlUrls.contains(xmlUrl))
                            {
                                xmlUrls.addItem(xmlUrl);
                            }
                        }
                        aggregateFeeds(xmlUrls.source);
                    });
                opmlFile.browseForOpen("Select an OPML file to import.");
            }
            
            private function aggregateFeeds(feedUrls:Array):void
            {
                if (feedUrls.length == 0)
                {
                    refreshUI();
                    return;
                }

                var feedUrl:String = feedUrls.pop();
                showStatus("Aggregating " + feedUrl);
                var aggResponder:AggregatorResponder = new AggregatorResponder();
                aggResponder.addEventListener(AggregatorEvent.FEED_EVENT,
                    function(e:AggregatorEvent):void
                    {
                        var feed:IFeed = e.data as IFeed;
                        if (!validateFeed(feedUrl, feed))
                        {
                            aggregateFeeds(feedUrls);
                            return;
                        }
                        var responder:DatabaseResponder = getDatabaseResponder();
                        var aggregateFeed:AggregateFeed = new AggregateFeed(responder, sql, conn, feedUrl, feed);
                        responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                            function (e:DatabaseEvent):void
                            {
                                aggregateFeeds(feedUrls);
                            });
                        aggregateFeed.execute();
                    });
                // TBD: handle aggregation errors
                aggResponder.addEventListener(AggregatorEvent.ERROR_EVENT,
                    function(e:AggregatorEvent):void
                    {
                        trace("aggregation failed for: " + feedUrl);
                        trace("error is: " + e.data);
                        aggregateFeeds(feedUrls);
                    });
                agg.getFeed(aggResponder, feedUrl);
            }

            private function validateFeed(feedUrl:String, feed:IFeed):Boolean
            {
                if (feed.metadata.title == null)
                {
                    trace("The feed ["+feedUrl+"] is invalid. Missing title.");
                    return false;
                }

                return true;
            }

            private function refreshUI(initialRefresh:Boolean = false):void
            {
                feeds.removeAll();
                authors.removeAll();
                topics.removeAll();
                
                hideStatus();

                // Feeds
                var feedResponder:DatabaseResponder = getDatabaseResponder();
                feedResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(feedEvent:DatabaseEvent):void
                    {
                        feeds.source = feedEvent.data as Array;
                        countUnreadFeeds();
                        if (initialRefresh)
                        {
                            onRefreshAll();
                        }
                    });
                var feedDBO:Feeds = new Feeds(feedResponder, sql, conn);
                feedDBO.getAll();

                // Authors
                var authorResponder:DatabaseResponder = getDatabaseResponder();
                authorResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(authorEvent:DatabaseEvent):void
                    {
                        authors.source = authorEvent.data as Array;
                        countUnreadAuthors();
                    });
                var authorDBO:Authors = new Authors(authorResponder, sql, conn);
                authorDBO.getUnique();

                // Topics
                var topicResponder:DatabaseResponder = getDatabaseResponder();
                topicResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(topicEvent:DatabaseEvent):void
                    {
                        topics.source = topicEvent.data as Array;
                        countUnreadTopics();
                    });
                var topicDBO:Topics = new Topics(topicResponder, sql, conn);
                topicDBO.getUnique();
                
                countUnreadAll();
                countChecked();
            }
            
            private function showStatus(msg:String):void
            {
                status = msg;
            }
            
            private function hideStatus():void
            {
                status = "";
            }
            
            /////////////////////////
            // Counting functions
            /////////////////////////

            private function refreshCounts():void
            {
                this.countUnreadAll();
                this.countUnreadAuthors();
                this.countUnreadFeeds();
                this.countUnreadTopics();
                this.countChecked();
            }
            
            private function countUnreadTopics():void
            {
                var topicResponder:DatabaseResponder = getDatabaseResponder();
                topicResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        var results:Object = e.data;
                        for each(var o:Object in topics)
                        {
                            if (results[o.topic] != null)
                            {
                                o.unread = results[o.topic];
                            }
                            else
                            {
                                o.unread = 0;                                
                            }
                            topics.itemUpdated(o, "unread");
                        }
                    });
                var topicDBO:Topics = new Topics(topicResponder, sql, conn);
                topicDBO.getUnread();
            }

            private function countUnreadAuthors():void
            {
                var authorResponder:DatabaseResponder = getDatabaseResponder();
                authorResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        var results:Object = e.data;
                        for each(var o:Object in authors)
                        {
                            if (results[o.author] != null)
                            {
                                o.unread = results[o.author];
                            }
                            else
                            {
                                o.unread = 0;                                
                            }
                            authors.itemUpdated(o, "unread");
                        }
                    });
                var authorDBO:Authors = new Authors(authorResponder, sql, conn);
                authorDBO.getUnread();                
            }

            private function countUnreadFeeds():void
            {
                var feedResponder:DatabaseResponder = getDatabaseResponder();
                feedResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        var results:Object = e.data;
                        for each(var o:Object in feeds)
                        {
                            if (results[o.name] != null)
                            {
                                o.unread = results[o.name];
                            }
                            else
                            {
                                o.unread = 0;                                
                            }
                            feeds.itemUpdated(o, "unread");
                        }
                    });
                var feedDBO:Feeds = new Feeds(feedResponder, sql, conn);
                feedDBO.getUnread();                
            }

            private function countChecked():void
            {
                var postResponder:DatabaseResponder = getDatabaseResponder();
                postResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        for each(var o:Object in views)
                        {
                            if (o.viewName == "Checked")
                            {
                                o.count = e.data as int;
                                views.itemUpdated(o, "count");
                                break;
                            }
                        }
                    });
                var postDBO:Posts = new Posts(postResponder, sql, conn);
                postDBO.countChecked();                
            }

            private function countUnreadAll():void
            {
                var postResponder:DatabaseResponder = getDatabaseResponder();
                postResponder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        for each(var o:Object in views)
                        {
                            if (o.viewName == "All Unread")
                            {
                                o.count = e.data as int;
                                views.itemUpdated(o, "count");
                                break;
                            }
                        }
                    });
                var postDBO:Posts = new Posts(postResponder, sql, conn);
                postDBO.countUnread();                
            }

            private function toggleSelection(e:Event):void
            {
                if (e.target != viewGrid) viewGrid.selectedItem = null;
                if (e.target != topicGrid) topicGrid.selectedItem = null;
                if (e.target != authorGrid) authorGrid.selectedItem = null;
                if (e.target != feedGrid) feedGrid.selectedItem = null;
            }

            private function getPostsByTopic(e:ListEvent):void
            {
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        posts.source = e.data as Array;
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.getByTopic(topicGrid.selectedItem.topic);
            }

            private function getPostsByAuthor(e:ListEvent):void
            {
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        posts.source = e.data as Array;
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.getByAuthor(authorGrid.selectedItem.author);
            }

            private function getPostsByFeed(e:ListEvent):void
            {
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        posts.source = e.data as Array;
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.getByFeed(feedGrid.selectedItem.id);
            }

            private function getPostsByView(e:ListEvent):void
            {
                var view:String = viewGrid.selectedItem.viewName as String;
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        posts.source = e.data as Array;
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                if (view == "All Unread")
                {
                    postDBO.getAllUnread();                
                }
                else if (view == "Checked")
                {
                    postDBO.getAllChecked();                
                }
            }

            private function getPostsBySearch():void
            {
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        posts.source = e.data as Array;
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.getBySearch(searchTerm.text);
            }

            private function onPostSelected():void
            {
                var selectedPost:Object = postGrid.selectedItem;
                if (selectedPost == null) return;

                renderContent();

                if (selectedPost.read == 0)
                {
                    changeReadFlag(true, selectedPost);
                }

                setUnreadButtonLabel();
            }

            public function onPostChecked(cb:CheckBox, postId:Number):void
            {
                if (postGrid.selectedItem == null) return;
                var selectedPost:Object = postGrid.selectedItem;
                selectedPost.checked = cb.selected;
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        countChecked();
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.changeChecked(cb.selected, postId);
            }

            private function onDeleteFeed():void
            {
                if (feedGrid.selectedItem == null) return;
                Alert.show("Are you sure you want to delete the selected feed and all it's posts?",
                            "Confirm",
                            Alert.OK|Alert.CANCEL,
                            null,
                            function(e:CloseEvent):void {
                                if (e.detail == Alert.OK)
                                {
                                    var responder:DatabaseResponder = getDatabaseResponder();
                                    responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                                        function(ee:DatabaseEvent):void
                                        {
                                            refreshUI();
                                        });
                                    var deleteFeed:DeleteFeed = new DeleteFeed(responder, sql, conn, feedGrid.selectedItem.id);
                                    deleteFeed.execute();
                                }
                            },
                            warningIcon);                
            }

            /////////////////////////
            // Read and unread functions
            /////////////////////////
            
            private function toggleRead():void
            {
                var selectedPost:Object = postGrid.selectedItem;
                if (selectedPost == null) return;
                if (selectedPost.read == 0)
                {
                    changeReadFlag(true, selectedPost);
                }
                else
                {
                    changeReadFlag(false, selectedPost);
                }
                setUnreadButtonLabel();
            }
            
            private function setUnreadButtonLabel():void
            {
                if (postGrid.selectedItem == null) return;
                unreadButton.label = (postGrid.selectedItem.read == 1) ? "Mark as Unread" : "Mark as Read";
            }

            private function markAllRead():void
            {
                if (posts.length == 0) return;
                var unreadPosts:Array = new Array();
                for each (var post:Object in posts)
                {
                    if (post.read == 0)
                    {
                        unreadPosts.push(post);
                    }
                }
                if (unreadPosts.length > 0)
                {
                    _markAllRead(unreadPosts);
                }
            }
            
            private function _markAllRead(unreadPosts:Array):void
            {
                if (unreadPosts.length == 0)
                {
                    refreshCounts();
                    setUnreadButtonLabel();
                    return;
                }

                var post:Object = unreadPosts.shift();

                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        post.read = 1;
                        posts.itemUpdated(post, "read");
                        _markAllRead(unreadPosts);
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.changeReadFlag(true, post.id);
            }
            
            private function changeReadFlag(read:Boolean, post:Object):void
            {
                post.read = (read) ? 1 : 0;
                posts.itemUpdated(post, "read");
                var responder:DatabaseResponder = getDatabaseResponder();
                responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        refreshCounts();
                    });
                var postDBO:Posts = new Posts(responder, sql, conn);
                postDBO.changeReadFlag(read, post.id);
            }
                        
            private function renderContent():void
            {
                if (postGrid.selectedItem == null) return;
                if (contentTabs.selectedChild == summaryTab)
                {
                    var responder:DatabaseResponder = getDatabaseResponder();
                    responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                        function(e:DatabaseEvent):void
                        {
                            if (e.data == null) return;
                            var contentArray:Array = e.data as Array;
                            if (contentArray.length == 0) return;
                            summaryHTML.htmlText = (contentArray[0].content == null || contentArray[0].content == "null") ? wrapContent("(empty)") : wrapContent(contentArray[0].content) ;
                        });
                    var postDBO:Posts = new Posts(responder, sql, conn);
                    postDBO.getContentById(postGrid.selectedItem.id);
                }
                else if(contentTabs.selectedChild == siteTab)
                {
                    if (siteHtml.location != postGrid.selectedItem.url)
                    {
                        this.showStatus("Loading " + postGrid.selectedItem.url);
                        siteHtml.location = postGrid.selectedItem.url;
                    }
                }
            }
            
            private function loadUrl():void
            {
                if (locationBar.text.length == 0) return;
                if (locationBar.text.search(/^http(s?):\/\/.+$/) == -1)
                {
                    locationBar.text = ("http://" + locationBar.text);
                }
                this.showStatus("Loading " + locationBar.text);
                siteHtml.location = locationBar.text;
            }
            
            private function onHtmlLoadComplete(e:Event):void
            {
                showStatus('Done');
                backButton.enabled = (siteHtml.htmlControl.historyLength > 0 && siteHtml.htmlControl.historyPosition > 0) ? true : false ;
                forwardButton.enabled = (siteHtml.htmlControl.historyLength > 0 && siteHtml.htmlControl.historyPosition != (siteHtml.htmlControl.historyLength - 1)) ? true : false ;
            }
            
            private function wrapContent(content:String):String
            {
                var str:String = "<html>";
                str += "<div style='font-family: Arial'>";
                str += content;
                str += "</div></html>";
                return str;
            }

            /////////////////////////
            // Database stuff
            /////////////////////////

            private function getDatabaseResponder():DatabaseResponder
            {
                var responder:DatabaseResponder = new DatabaseResponder();
                responder.addEventListener(DatabaseEvent.ERROR_EVENT,
                    function(e:DatabaseEvent):void
                    {
                        Alert.show("An unexpected database error occurred.  The error ID is: " + e.data,
                                    "Database Error",
                                    Alert.OK, null, null, errorIcon);                
                    });
                return responder;
            }
            
            private function initializeDatabase():void
            {
                conn = new SQLConnection();
                conn.addEventListener(SQLEvent.OPEN,
                    function (e:SQLEvent):void
                    {
                        var responder:DatabaseResponder = getDatabaseResponder();
                        responder.addEventListener(DatabaseEvent.RESULT_EVENT,
                            function(e:DatabaseEvent):void
                            {
                                start();
                            });
                        var initDB:InitializeDatabase = new InitializeDatabase(responder, sql, conn);
                        initDB.execute();
                    });
                var dbFile:File = File.applicationStorageDirectory.resolvePath("arise.db");
                conn.open(dbFile, true, true);
            }
            
            /////////////////////////
            // Formating and sorting functions
            /////////////////////////

            public function formatDate(d:Date):String
            {
                return dateFormatter.format(d);
            }
            
            private function topicCompare(a:Object, b:Object):int
            {
                return caseInsensitiveCompare(a.topic, b.topic);
            }

            private function authorCompare(a:Object, b:Object):int
            {
                return caseInsensitiveCompare(a.author, b.author);
            }

            private function feedCompare(a:Object, b:Object):int
            {
                return caseInsensitiveCompare(a.name, b.name);
            }
            
            private function caseInsensitiveCompare(a:String, b:String):int
            {
                if (a.toLowerCase().localeCompare(b.toLowerCase()) > 0)
                {
                    return 1;
                }
                else if(a.toLowerCase().localeCompare(b.toLowerCase()) < 0)
                {
                    return -1;
                }
                else
                {
                    return 0;
                }
            }
            
            private function onInfo():void
            {
                new Info().open();
            }
            
        ]]>
    </mx:Script>
    <mx:VBox width="100%" height="100%">
        <mx:ApplicationControlBar width="100%">
            <mx:Button label="Add Feed" click="newFeedUrl.text = ''; newFeedDrawer.open();"/>
            <mx:Button label="Import Feeds" click="importFeeds();"/>
            <mx:Button label="Refresh All" click="onRefreshAll();"/>
            <mx:Button label="Mark as Unread" id="unreadButton" enabled="{postGrid.selectedItem != null}" click="toggleRead();"/>
            <mx:Button label="Mark all as Read" enabled="{posts.length &gt; 0}" click="markAllRead();"/>
            <mx:Button label="Info" click="onInfo();"/>
            <mx:Spacer width="100%" />
            <mx:Label text="Search:" />
            <mx:TextInput id="searchTerm" width="200" enabled="true" change="getPostsBySearch();"/>
        </mx:ApplicationControlBar>
        <!-- left navigation -->
        <mx:HDividedBox width="100%" height="100%">
            <mx:VBox width="200" height="100%" verticalGap="0">
                <mx:DataGrid id="viewGrid" dataProvider="{views}" width="100%" height="68" change="toggleSelection(event);" itemClick="getPostsByView(event);">
                    <mx:columns>
                        <mx:DataGridColumn headerText="View" dataField="viewName" textAlign="left"/>
                        <mx:DataGridColumn headerText="New" dataField="count" textAlign="right" width="48"/>
                    </mx:columns>
                </mx:DataGrid>
                <mx:Accordion width="100%" height="100%" creationPolicy="all">
                    <mx:Canvas label="Feeds" width="100%" height="100%">
                        <mx:VBox width="100%" height="100%" verticalGap="0">
                            <mx:DataGrid id="feedGrid" dataProvider="{feeds}" width="100%" height="100%" change="toggleSelection(event);" itemClick="getPostsByFeed(event);">
                                <mx:columns>
                                    <mx:DataGridColumn headerText="Name" dataField="name" textAlign="left" sortCompareFunction="feedCompare"/>
                                    <mx:DataGridColumn headerText="New" dataField="unread" textAlign="right" width="48"/>
                                </mx:columns>
                            </mx:DataGrid>
                            <mx:HBox width="100%" horizontalGap="0" paddingBottom="2" paddingTop="2" paddingLeft="2" paddingRight="2">
                                <mx:Button label="New" width="50%" click="newFeedUrl.text = ''; newFeedDrawer.open();"/>
                                <mx:Button label="Delete" width="50%" enabled="{feedGrid.selectedItem != null}" click="onDeleteFeed();"/>
                            </mx:HBox>
                        </mx:VBox>
                    </mx:Canvas>
                    <mx:Canvas label="Topics" width="100%" height="100%">
                        <mx:DataGrid id="topicGrid" dataProvider="{topics}" width="100%" height="100%" change="toggleSelection(event);" itemClick="getPostsByTopic(event);">
                            <mx:columns>
                                <mx:DataGridColumn headerText="Name" dataField="topic" textAlign="left" sortCompareFunction="topicCompare"/>
                                <mx:DataGridColumn headerText="New" dataField="unread" textAlign="right" width="48"/>
                            </mx:columns>
                        </mx:DataGrid>
                    </mx:Canvas>
                    <mx:Canvas label="Authors" width="100%" height="100%">
                        <mx:DataGrid id="authorGrid" dataProvider="{authors}" width="100%" height="100%" change="toggleSelection(event);" itemClick="getPostsByAuthor(event);">
                            <mx:columns>
                                <mx:DataGridColumn headerText="Name" dataField="author" textAlign="left" sortCompareFunction="authorCompare"/>
                                <mx:DataGridColumn headerText="New" dataField="unread" textAlign="right" width="48"/>
                            </mx:columns>
                        </mx:DataGrid>
                    </mx:Canvas>
                </mx:Accordion>
            </mx:VBox>
            <!-- right side -->
            <mx:Canvas width="100%" height="100%">
                <mx:VDividedBox width="100%" height="100%">
                    <mx:DataGrid id="postGrid" width="100%" height="30%" dataProvider="{posts}" draggableColumns="false" allowMultipleSelection="false" change="onPostSelected();" doubleClickEnabled="true" itemDoubleClick="if(postGrid.selectedItem != null) {navigateToURL(new URLRequest(postGrid.selectedItem.url))}">
                        <mx:columns>
                            <mx:DataGridColumn textAlign="center" width="30" dataField="checked">
                                <mx:headerRenderer>
                                    <mx:Component>
                                        <mx:Box verticalAlign="middle" horizontalAlign="center">
                                            <mx:CheckBox enabled="false" labelPlacement="bottom"/>
                                        </mx:Box>
                                    </mx:Component>
                                </mx:headerRenderer>
                                <mx:itemRenderer>
                                    <mx:Component>
                                        <mx:CheckBox selected="{(data.checked) ? true : false}" click="outerDocument.onPostChecked(this, data.id)"/>
                                    </mx:Component>
                                </mx:itemRenderer>
                            </mx:DataGridColumn>
                            <mx:DataGridColumn width="22" dataField="read" textAlign="center">
                                <mx:headerRenderer>
                                    <mx:Component>
                                        <mx:Image source="{outerDocument.unreadIcon}"/>
                                    </mx:Component>
                                </mx:headerRenderer>
                                <mx:itemRenderer>
                                    <mx:Component>
                                        <mx:Image source="{(data.read == 0) ? outerDocument.unreadIcon : outerDocument.readIcon}"/>
                                    </mx:Component>
                                </mx:itemRenderer>
                            </mx:DataGridColumn>
                            <mx:DataGridColumn headerText="Post Title" textAlign="left" dataField="title">
                                <mx:itemRenderer>
                                    <mx:Component>
                                        <mx:Label text="{data.title}" fontWeight="{(data.read == 0) ? 'bold' : 'normal'}"/>
                                    </mx:Component>
                                </mx:itemRenderer>
                            </mx:DataGridColumn>
                            <mx:DataGridColumn headerText="Feed" textAlign="left" width="170" dataField="feed_name">
                                <mx:itemRenderer>
                                    <mx:Component>
                                        <mx:Label text="{data.feed_name}" fontWeight="{(data.read == 0) ? 'bold' : 'normal'}"/>
                                    </mx:Component>
                                </mx:itemRenderer>
                            </mx:DataGridColumn>
                            <mx:DataGridColumn headerText="Date" textAlign="left" width="140" dataField="post_date">
                                <mx:itemRenderer>
                                    <mx:Component>
                                        <mx:Label text="{outerDocument.formatDate(data.post_date)}" fontWeight="{(data.read == 0) ? 'bold' : 'normal'}"/>
                                    </mx:Component>
                                </mx:itemRenderer>
                            </mx:DataGridColumn>
                        </mx:columns>
                    </mx:DataGrid>
                    <mx:TabNavigator id="contentTabs" width="100%" height="70%" horizontalAlign="center" creationPolicy="all" change="renderContent();">
                        <mx:Canvas id="summaryTab" label="Post Summary" width="100%" height="100%">
                            <mx:HTML width="100%" height="100%" id="summaryHTML"/>
                        </mx:Canvas>
                        <mx:Canvas id="siteTab" label="Site" width="100%" height="100%">
                            <mx:VBox width="100%" height="100%">
                                <mx:HBox width="100%">
                                    <mx:Button label="Back" click="siteHtml.htmlControl.historyBack()" enabled="false" id="backButton"/>
                                    <mx:Button label="Forward" click="siteHtml.htmlControl.historyForward()" enabled="false" id="forwardButton"/>
                                    <mx:TextInput width="90%" id="locationBar" text="{siteHtml.location}"/>
                                    <mx:Button width="10%" label="Load" click="loadUrl();" enabled="{locationBar.text.length &gt; 0}"/>
                                </mx:HBox>
                                <mx:HTML width="100%" height="100%" id="siteHtml" complete="onHtmlLoadComplete(event)"/>
                            </mx:VBox>
                        </mx:Canvas>
                    </mx:TabNavigator>
                </mx:VDividedBox>
            </mx:Canvas>
        </mx:HDividedBox>
    </mx:VBox>
    <ui:TopDrawer id="newFeedDrawer" width="400" height="100">
        <mx:VBox width="100%" height="100%" horizontalAlign="center" verticalAlign="middle" verticalGap="20">
            <mx:Grid>
                <mx:GridRow>
                    <mx:GridItem>
                        <mx:Label fontWeight="bold" text="Feed URL:"/>
                    </mx:GridItem>
                    <mx:GridItem>
                        <mx:TextInput id="newFeedUrl" width="300"/>
                    </mx:GridItem>
                </mx:GridRow>
            </mx:Grid>
            <mx:HBox width="100%" horizontalAlign="center">
                <mx:Button label="Add" click="addFeed();" enabled="{newFeedUrl.text.search(/^http(s?):\/\/.+$/) != -1}"/>
                <mx:Button label="Cancel" click="newFeedDrawer.close();"/>
            </mx:HBox>
        </mx:VBox>
    </ui:TopDrawer>
</mx:WindowedApplication>