hawitter (empty) → 0.2
raw patch · 8 files changed
+999/−0 lines, 8 filesdep +HTTPdep +basedep +containerssetup-changed
Dependencies added: HTTP, base, containers, gconf, glade, gtk, json, network, old-locale, regex-compat, time, utf8-string
Files
- LICENSE +25/−0
- Main.hs +333/−0
- README.en +16/−0
- README.ja +16/−0
- Setup.hs +3/−0
- hawitter.cabal +24/−0
- hawitter.glade +472/−0
- hawitter_128sq.svg +110/−0
+ LICENSE view
@@ -0,0 +1,25 @@+Copyright (c) 2010 Daiki Handa+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. The name of the author may not be used to endorse or promote products+ derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+
+ Main.hs view
@@ -0,0 +1,333 @@+module Main where+import Control.Monad+import qualified Codec.Binary.UTF8.String as U+import Data.IORef+import Data.List+import Data.Maybe+import Data.Ratio+import Data.Time.Clock+import Data.Time.Format+import qualified Data.Map as M+import Graphics.UI.Gtk+import Graphics.UI.Gtk.Glade+import Network.URI+import Network.HTTP+import Network.HTTP.Auth+import Text.JSON+import Text.Regex+import System.IO+import System.Locale+import System.Gnome.GConf+import qualified Paths_hawitter+++main=do+ initGUI+ + si<-statusIconNewFromFile =<< Paths_hawitter.getDataFileName "hawitter_128sq.svg"+ statusIconSetVisible si True+ + gladeFile<-Paths_hawitter.getDataFileName "hawitter.glade"+ Just gxml<-xmlNewWithRootAndDomain gladeFile (Just "mainwindow") Nothing+ window<-xmlGetWidget gxml castToWindow "mainwindow" + + (mv,showTweets)<-newMessageView+ + swt<-xmlGetWidget gxml castToViewport "vptimeline"+ set swt [containerChild:=mv]+ + pTweets<-newIORef []+ pLastId<-newIORef Nothing+ pImageCache<-newIORef emptyImageCache+ let updateTL=update showTweets pTweets pLastId pImageCache+ + -- handlers+ let onAction name f=xmlGetWidget gxml castToMenuItem name >>= flip onActivateLeaf f+ + onAction "file-account" newAccountDialog+ onAction "file-quit" mainQuit+ onAction "timeline-post" newPostWindow+ onAction "timeline-refresh" updateTL+ onAction "help-about" showAboutDialog+ onDestroy window mainQuit+ timeoutAdd (updateTL >> return True) (60*1000)+ + updateTL+ widgetShowAll window+ mainGUI++-- [newest..oldest]+newMessageView :: IO (Widget,[Tweet] -> IO ())+newMessageView=do+ vb<-vBoxNew False 2+ let+ append x=boxPackStart vb x PackNatural 3+ f ts=do+ containerGetChildren vb >>= mapM_ (containerRemove vb)+ mapM_ (\t->allocTweet t >>= append) ts+ widgetShowAll vb+ return (castToWidget vb,f)++newAccountDialog=do+ gladeFile<-Paths_hawitter.getDataFileName "hawitter.glade"+ gconf<-gconfGetDefault+ Just gxml<-xmlNewWithRootAndDomain gladeFile (Just "accountdialog") Nothing+ + d<-xmlGetWidget gxml castToDialog "accountdialog"+ id<-xmlGetWidget gxml castToEntry "identry"+ ps<-xmlGetWidget gxml castToEntry "pswdentry"++ entrySetText id =<< gconfGetString gconf "/apps/hawitter/basic/user"+ entrySetText ps =<< gconfGetString gconf "/apps/hawitter/basic/pswd"+ + dialogRun d+ widgetDestroy d+ + entryGetText id >>= gconfSet gconf "/apps/hawitter/basic/user" . GConfValueString+ entryGetText ps >>= gconfSet gconf "/apps/hawitter/basic/pswd" . GConfValueString+ putStrLn "gconf configuration complete"++newPostWindow=do+ gladeFile<-Paths_hawitter.getDataFileName "hawitter.glade"+ Just gxml<-xmlNewWithRootAndDomain gladeFile (Just "postdialog") Nothing+ d<-xmlGetWidget gxml castToDialog "postdialog"+ lb<-xmlGetWidget gxml castToLabel "remaining"+ tv<-xmlGetWidget gxml castToTextView "tweetbody"+ + buf<-textViewGetBuffer tv+ onBufferChanged buf $ updateTweetInfo buf lb+ + + rid<-dialogRun d+ + + when (rid==ResponseUser 0) $ do+ st<-textBufferGetStartIter buf+ en<-textBufferGetEndIter buf+ txt<-textBufferGetText buf st en False+ + let txtU8=U.utf8Encode txt+ callJSON POST "/statuses/update" [("status",txtU8)]+ return ()+ widgetDestroy d++updateTweetInfo buf lb=do+ st<-textBufferGetStartIter buf+ en<-textBufferGetEndIter buf+ txt<-textBufferGetText buf st en False+ + labelSetText lb $ show (140-length txt)++" characters left"++showAboutDialog=do+ d<-aboutDialogNew+ aboutDialogSetName d "hawitter"+ aboutDialogSetVersion d "0.2"+ aboutDialogSetComments d "Hawitter is a twitter client for GTK, written in Haskell."+ aboutDialogSetAuthors d ["xanxys <xanxys@gmail.com>"]+ aboutDialogSetLogo d . Just =<< pixbufNewFromFile =<< Paths_hawitter.getDataFileName "hawitter_128sq.svg"+ dialogRun d+ widgetDestroy d++++-- URL -> hashtag -> users+-- URL may contain #+markupMessage :: String -> String+markupMessage=modifyWithRegex ruser mkUser . modifyWithRegex rhash mkHash . modifyWithRegex rurl mkLink+ where+ rurl=mkRegex "http://[-a-zA-Z0-9_./#]+"+ rhash=mkRegex "#[-a-zA-Z0-9_.]+"+ ruser=mkRegex "@[-a-zA-Z0-9_.]+"+ + mkLink url="<a href=\""++url++"\"><span underline=\"none\">"++url++"</span></a>"+ mkHash hash="<span color=\"brown\">"++hash++"</span>"+ mkUser user="<span color=\"aquamarine\">"++user++"</span>"++modifyWithRegex rx f s=case matchRegexAll rx s of+ Nothing -> s+ Just (pre,target,post,_) -> pre++f target++modifyWithRegex rx f post+++allocTweet :: Tweet -> IO Widget+allocTweet (Tweet icon (User id) (Source src) date msg)=do+ t0<-getCurrentTime+ + im<-imageNewFromPixbuf icon+ set im [miscYalign:=0]+ + -- begin vbCont+ lbUser<-labelNew Nothing+ let+ (cname,curl)=extractClientName src+ fromLink+ |null curl = "<span color=\"lightblue\" underline=\"none\">"++cname++"</span>"+ |otherwise = "<a href=\""++curl++"\"><span color=\"lightblue\" underline=\"none\">"++cname++"</span></a>"+ labelSetMarkup lbUser $ unwords ["<b>"++id++"</b>","-",showPastTime t0 date,"-","from",fromLink]+ labelSetLineWrap lbUser False+ labelSetSingleLineMode lbUser True+ set lbUser [miscXalign:=0]+ + lbMsg<-labelNew Nothing+ labelSetMarkup lbMsg $ markupMessage msg+ labelSetLineWrap lbMsg True+ labelSetSingleLineMode lbMsg False+ set lbMsg [miscXalign:=0]+ + vbCont<-vBoxNew False 1+ boxPackStart vbCont lbUser PackNatural 0+ boxPackStart vbCont lbMsg PackGrow 0+ + hbTweet<-hBoxNew False 3+ boxPackStart hbTweet im PackNatural 0+ boxPackStart hbTweet vbCont PackGrow 0+ + return $ castToWidget hbTweet+++-- example:+-- <a href="http://example.com" rel="nofollow">TwitterClient</a>+-- ->+-- ("TwitterClient","http://example.com")+extractClientName :: String -> (String,String)+extractClientName s=case matchRegex rx s of+ Just [u,n] -> (escapeBRs n,escapeBRs u)+ _ -> (escapeBRs s,"")+ where+ rx=mkRegex "href=\"([^\"]+)[^>]*>([^<]+)<"+ escapeBRs=concatMap f+ f '<'="<"+ f '>'=">"+ f x=[x]+ ++showPastTime now past+ |ss<60 = show ss++" seconds before"+ |ms<60 = show ms++" minutes before"+ |hs<24 = show hs++" hours before"+ |otherwise = formatTime defaultTimeLocale "%Y/%m/%d %H:%M:%S %Z" past+ where+ ss=ceiling $ now `diffUTCTime` past+ ms=ss `div` 60+ hs=ms `div` 60++data Tweet=Tweet Pixbuf User Source UTCTime String++newtype Source=Source String deriving(Show,Eq,Ord)+newtype User=User String deriving(Show,Eq,Ord)+++update showTweets ts li ic=do+ last_id<-readIORef li+ resp<-callJSON GET "/statuses/home_timeline" (maybe [] (\x->[("since_id",show x)]) last_id)+ case resp of+ Nothing -> print "couldn't fetch TL"+ Just rb -> updateWithTL showTweets ts li ic rb+++updateWithTL showTweets ts li ic (JSArray ss)=do+ unless (null ids) $ writeIORef li $ Just $ maximum ids++ imgCache<-updateImageCache (zip users icons) =<< readIORef ic+ writeIORef ic imgCache+ + let tweets=zipWith4 (\u m d s->Tweet (imgCache M.! u) u s d m) users msgs dates sources+ modifyIORef ts (tweets++)+ + readIORef ts >>= showTweets+ where+ ids=map (fromJSI . indexJSA "id") ss+ msgs=map (fromJSS . indexJSA "text") ss+ dates=map (readDate . fromJSS . indexJSA "created_at") ss+ users=map (User . fromJSS . indexJSA "screen_name" . indexJSA "user") ss+ icons=map (fromJSS . indexJSA "profile_image_url" . indexJSA "user") ss+ sources=map (Source . fromJSS . indexJSA "source") ss+ ++-- example: "Wed Nov 18 18:54:12 +0000 2009"+readDate :: String -> UTCTime+readDate=readTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y"+++emptyImageCache=M.empty++updateImageCache :: [(User,String)] -> M.Map User Pixbuf -> IO (M.Map User Pixbuf)+updateImageCache uss m=do+ imgs<-mapM pixbufNewFromURL $ map snd nus+ return $! M.fromList (zip (map fst nus) imgs) `M.union` m+ where nus=filter (flip M.notMember m . fst) $ nubBy (\x y->fst x==fst y) uss+++pixbufNewFromURL :: String -> IO Pixbuf+pixbufNewFromURL url=do+ r<-simpleHTTP (getRequest url)+ case r of+ Left x -> pixbufNew ColorspaceRgb False 8 73 73+ Right x -> do+ (path,h)<-openBinaryTempFile "/tmp" "hawitter"+ hPutStr h $ rspBody x+ hFlush h+ n<-pixbufNewFromFile path+ hClose h+ return n+ ++++indexJSA :: String -> JSValue -> JSValue+indexJSA key (JSObject o)=fromJust $ lookup key $ fromJSObject o++fromJSS :: JSValue -> String+fromJSS (JSString x)=fromJSString x++fromJSI :: JSValue -> Int+fromJSI (JSRational False x)=fromIntegral $ numerator x+ ++{-+oauthRequestToken=do+ Request (fromJust $ parseURI "http://twitter.com/oauth/request_token") GET [] ""+ request c (hmacsha1_signature c) + where+ c=Unauthenticated consumerKey consumerSecret+-} +++-- example:+-- callJSON GET "/account/rate_limit_status"+callJSON :: RequestMethod -> String -> [(String,String)] -> IO (Maybe JSValue)+callJSON met cmd args=do+ print $ "http://api.twitter.com/1"++cmd++".json"++"?"++urlEncodeVars args+ x<-callAPI met $ "http://api.twitter.com/1"++cmd++".json"++"?"++urlEncodeVars args+ case x of+ Nothing -> return Nothing+ Just y -> case decode y of+ Error e -> error e+ Ok x -> return $ Just x++-- example:+-- callAPI "http://api.twitter.com/1/account/rate_limit_status.json"+callAPI :: RequestMethod -> String -> IO (Maybe String)+callAPI met url=do+ gconf<-gconfGetDefault+ user<-gconfGetString gconf "/apps/hawitter/basic/user"+ pswd<-gconfGetString gconf "/apps/hawitter/basic/pswd"+ let clearUserData=mapM_ (gconfUnset gconf) ["/apps/hawitter/basic/user","/apps/hawitter/basic/pswd"]+ + let au=AuthBasic undefined user pswd undefined+ let rq=Request (fromJust $ parseURI url) met [] ""+ let rq'=insertHeader HdrAuthorization (withAuthority au rq) rq+ + if null user+ then return Nothing+ else do rs<-simpleHTTP rq'+ case rs of+ Left er -> error $ show er+ Right q -> case rspCode q of+ (2,0,0) -> return $ Just $ rspBody q+ (4,0,1) -> print "401" >> clearUserData >> return Nothing+ _ -> print rs >> return Nothing++gconfGetString gconf key=catch (liftM unpack $ gconfGet gconf key) (const $ return "")+ where unpack (GConfValueString s)=s+ +
+ README.en view
@@ -0,0 +1,16 @@+Hawitter 0.2++== Installation ==+It should be possible to run 'cabal install hawitter'.+++== Technical Notes ==+This application uses following gconf keys.+* /apps/hawitter/basic/user :: str+* /apps/hawitter/basic/pswd :: str++== Contact ==+If you encounter errors or have suggentions, contact me with:++* email: xanxys at gmail dot com+* twitter: @xanxys_
+ README.ja view
@@ -0,0 +1,16 @@+Hawitter 0.2++== インストール ==+'cabal install hawitter'で動くはず。動かない場合は連絡を。++== 技術的メモ ==+gconfの次のキーを使っています。+* /apps/hawitter/basic/user :: str+* /apps/hawitter/basic/pswd :: str++== 連絡先 ==+問題/提案がある場合は、++* email: xanxys at gmail dot com+* twitter: @xanxys_+* ブログの該当記事のコメント欄(初期バージョンのみ)
+ Setup.hs view
@@ -0,0 +1,3 @@+import Distribution.Simple+main=defaultMain+
+ hawitter.cabal view
@@ -0,0 +1,24 @@+name: hawitter+cabal-version: >=1.2+version: 0.2+author: Daiki Handa <xanxys@gmail.com>+maintainer: Daiki Handa <xanxys@gmail.com>+synopsis: A twitter client for GTK+. Alpha version.+license: BSD3+license-file: LICENSE+category: Web+description:+ THIS SOFTWARE IS STILL IN ALPHA STATE.+ A slick twitter client for GTK. Features syntax highlighting of tweets, URL linking/shortening and+ 'timeline modifier'.+ +build-type: Simple+data-files: hawitter.glade hawitter_128sq.svg README.en README.ja+tested-with: GHC==6.10.4+executable hawitter+ main-is: Main.hs+ build-depends:+ base>=3, base<5, containers>=0.2, regex-compat>=0.7, time>=1.1.4, old-locale>=1,+ gtk>=0.10, glade>=0.10, gconf>=0.10,+ network>=2.2, HTTP>=4000, json>=0.4, utf8-string>=0.3+
+ hawitter.glade view
@@ -0,0 +1,472 @@+<?xml version="1.0"?>+<glade-interface>+ <!-- interface-requires gtk+ 2.16 -->+ <!-- interface-naming-policy project-wide -->+ <widget class="GtkWindow" id="mainwindow">+ <property name="title" translatable="yes">hawitter</property>+ <property name="icon">hawitter_128sq.svg</property>+ <child>+ <widget class="GtkVBox" id="vbox1">+ <property name="visible">True</property>+ <property name="orientation">vertical</property>+ <child>+ <widget class="GtkMenuBar" id="menubar1">+ <property name="visible">True</property>+ <child>+ <widget class="GtkMenuItem" id="menuitem1">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_File</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu1">+ <property name="visible">True</property>+ <child>+ <widget class="GtkMenuItem" id="file-account">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Account</property>+ <property name="use_underline">True</property>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="file-quit">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Quit</property>+ <property name="use_underline">True</property>+ <accelerator key="q" signal="activate" modifiers="GDK_CONTROL_MASK"/>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem3">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Timeline</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu2">+ <property name="visible">True</property>+ <child>+ <widget class="GtkMenuItem" id="timeline-post">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Post</property>+ <property name="use_underline">True</property>+ <accelerator key="n" signal="activate" modifiers="GDK_CONTROL_MASK"/>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="timeline-refresh">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Refresh</property>+ <property name="use_underline">True</property>+ <accelerator key="F5" signal="activate"/>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ <child>+ <widget class="GtkMenuItem" id="menuitem4">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_Help</property>+ <property name="use_underline">True</property>+ <child>+ <widget class="GtkMenu" id="menu3">+ <property name="visible">True</property>+ <child>+ <widget class="GtkMenuItem" id="help-about">+ <property name="visible">True</property>+ <property name="label" translatable="yes">_About</property>+ <property name="use_underline">True</property>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GtkHBox" id="hbox1">+ <property name="visible">True</property>+ <child>+ <widget class="GtkLabel" id="label1">+ <property name="visible">True</property>+ <property name="label" translatable="yes">modifier:</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GtkEntry" id="entry1">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="invisible_char">●</property>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">1</property>+ </packing>+ </child>+ <child>+ <widget class="GtkScrolledWindow" id="swtimeline">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="hscrollbar_policy">never</property>+ <child>+ <widget class="GtkViewport" id="vptimeline">+ <property name="visible">True</property>+ <property name="resize_mode">queue</property>+ <child>+ <placeholder/>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="position">2</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+ <widget class="GtkDialog" id="accountdialog">+ <property name="border_width">5</property>+ <property name="title" translatable="yes">hawitter - account</property>+ <property name="resizable">False</property>+ <property name="modal">True</property>+ <property name="type_hint">normal</property>+ <property name="has_separator">False</property>+ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox1">+ <property name="visible">True</property>+ <property name="orientation">vertical</property>+ <property name="spacing">2</property>+ <child>+ <widget class="GtkVBox" id="vbox3">+ <property name="visible">True</property>+ <property name="orientation">vertical</property>+ <child>+ <widget class="GtkRadioButton" id="radiobutton2">+ <property name="label" translatable="yes">OAuth(recommended)</property>+ <property name="visible">True</property>+ <property name="sensitive">False</property>+ <property name="can_focus">True</property>+ <property name="receives_default">False</property>+ <property name="draw_indicator">True</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GtkAlignment" id="alignment2">+ <property name="visible">True</property>+ <property name="left_padding">20</property>+ <child>+ <widget class="GtkTable" id="table2">+ <property name="visible">True</property>+ <property name="sensitive">False</property>+ <property name="n_rows">4</property>+ <property name="n_columns">2</property>+ <property name="column_spacing">1</property>+ <property name="row_spacing">2</property>+ <child>+ <widget class="GtkLabel" id="label23">+ <property name="visible">True</property>+ <property name="xalign">1</property>+ <property name="label" translatable="yes">step 1:</property>+ </widget>+ <packing>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label4">+ <property name="visible">True</property>+ <property name="label" translatable="yes">follow the link and click accept</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label5">+ <property name="visible">True</property>+ <property name="xalign">1</property>+ <property name="label" translatable="yes">step 2:</property>+ </widget>+ <packing>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label6">+ <property name="visible">True</property>+ <property name="label" translatable="yes">click the 'save token' button</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="top_attach">2</property>+ <property name="bottom_attach">3</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label7">+ <property name="visible">True</property>+ <property name="label" translatable="yes">http://www.example.com/</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="top_attach">1</property>+ <property name="bottom_attach">2</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkAlignment" id="alignment3">+ <property name="visible">True</property>+ <property name="left_padding">50</property>+ <property name="right_padding">50</property>+ <child>+ <widget class="GtkButton" id="button3">+ <property name="label" translatable="yes">save token</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="top_attach">3</property>+ <property name="bottom_attach">4</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <placeholder/>+ </child>+ <child>+ <placeholder/>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ <child>+ <widget class="GtkRadioButton" id="radiobutton1">+ <property name="label" translatable="yes">unencrypted authentication</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">False</property>+ <property name="active">True</property>+ <property name="draw_indicator">True</property>+ <property name="group">radiobutton2</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">2</property>+ </packing>+ </child>+ <child>+ <widget class="GtkAlignment" id="alignment1">+ <property name="visible">True</property>+ <property name="left_padding">20</property>+ <child>+ <widget class="GtkTable" id="table1">+ <property name="visible">True</property>+ <property name="n_rows">2</property>+ <property name="n_columns">2</property>+ <property name="column_spacing">1</property>+ <property name="row_spacing">2</property>+ <child>+ <widget class="GtkLabel" id="label2">+ <property name="visible">True</property>+ <property name="xalign">1</property>+ <property name="label" translatable="yes">user id:</property>+ </widget>+ <packing>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkLabel" id="label3">+ <property name="visible">True</property>+ <property name="xalign">1</property>+ <property name="label" translatable="yes">password:</property>+ </widget>+ <packing>+ <property name="top_attach">1</property>+ <property name="bottom_attach">2</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkEntry" id="identry">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="invisible_char">●</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ <child>+ <widget class="GtkEntry" id="pswdentry">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="invisible_char">●</property>+ </widget>+ <packing>+ <property name="left_attach">1</property>+ <property name="right_attach">2</property>+ <property name="top_attach">1</property>+ <property name="bottom_attach">2</property>+ <property name="x_options">GTK_FILL</property>+ <property name="y_options">GTK_FILL</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+ <packing>+ <property name="position">3</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area1">+ <property name="visible">True</property>+ <property name="layout_style">end</property>+ <child>+ <widget class="GtkButton" id="button1">+ <property name="label">gtk-close</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ <property name="use_stock">True</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="pack_type">end</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+ <widget class="GtkDialog" id="postdialog">+ <property name="width_request">400</property>+ <property name="height_request">150</property>+ <property name="border_width">5</property>+ <property name="title" translatable="yes">hawitter - tweet</property>+ <property name="resizable">False</property>+ <property name="type_hint">normal</property>+ <property name="has_separator">False</property>+ <child internal-child="vbox">+ <widget class="GtkVBox" id="dialog-vbox2">+ <property name="visible">True</property>+ <property name="orientation">vertical</property>+ <property name="spacing">2</property>+ <child>+ <widget class="GtkVBox" id="vbox2">+ <property name="visible">True</property>+ <property name="orientation">vertical</property>+ <child>+ <widget class="GtkLabel" id="remaining">+ <property name="visible">True</property>+ <property name="label" translatable="yes">140 characters left</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ <child>+ <widget class="GtkTextView" id="tweetbody">+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="position">1</property>+ </packing>+ </child>+ <child internal-child="action_area">+ <widget class="GtkHButtonBox" id="dialog-action_area2">+ <property name="visible">True</property>+ <property name="layout_style">end</property>+ <child>+ <widget class="GtkButton" id="button2">+ <property name="label" translatable="yes">send</property>+ <property name="visible">True</property>+ <property name="can_focus">True</property>+ <property name="receives_default">True</property>+ <property name="xalign">0.50999999046325684</property>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="fill">False</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ <packing>+ <property name="expand">False</property>+ <property name="pack_type">end</property>+ <property name="position">0</property>+ </packing>+ </child>+ </widget>+ </child>+ </widget>+</glade-interface>
+ hawitter_128sq.svg view
@@ -0,0 +1,110 @@+<?xml version="1.0" encoding="UTF-8" standalone="no"?>+<svg+ xmlns:dc="http://purl.org/dc/elements/1.1/"+ xmlns:cc="http://creativecommons.org/ns#"+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"+ xmlns:svg="http://www.w3.org/2000/svg"+ xmlns="http://www.w3.org/2000/svg"+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"+ width="128"+ height="128"+ viewBox="0 0 102.4 102.39999"+ version="1.1"+ id="svg2900"+ inkscape:version="0.47 r22583"+ sodipodi:docname="hawitter_centered.svg"+ inkscape:export-filename="/home/xyx/devenv/hawitter/hawitter_128sq.png"+ inkscape:export-xdpi="16.002222"+ inkscape:export-ydpi="16.002222">+ <metadata+ id="metadata2920">+ <rdf:RDF>+ <cc:Work+ rdf:about="">+ <dc:format>image/svg+xml</dc:format>+ <dc:type+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />+ <dc:title></dc:title>+ </cc:Work>+ </rdf:RDF>+ </metadata>+ <sodipodi:namedview+ pagecolor="#ffffff"+ bordercolor="#666666"+ borderopacity="1"+ objecttolerance="10"+ gridtolerance="10"+ guidetolerance="10"+ inkscape:pageopacity="0"+ inkscape:pageshadow="2"+ inkscape:window-width="838"+ inkscape:window-height="1023"+ id="namedview2918"+ showgrid="false"+ inkscape:zoom="1.716227"+ inkscape:cx="-52.998198"+ inkscape:cy="78.331889"+ inkscape:window-x="0"+ inkscape:window-y="25"+ inkscape:window-maximized="0"+ inkscape:current-layer="svg2900" />+ <defs+ id="defs2902">+ <inkscape:perspective+ sodipodi:type="inkscape:persp3d"+ inkscape:vp_x="0 : 212.59839 : 1"+ inkscape:vp_y="0 : 1000 : 0"+ inkscape:vp_z="602.36212 : 212.59839 : 1"+ inkscape:persp3d-origin="301.18106 : 141.73226 : 1"+ id="perspective2922" />+ <clipPath+ id="clip1">+ <path+ d="m 0,340.15625 481.89062,0 L 481.89062,0 0,0 0,340.15625 z m 0,0"+ id="path2905" />+ </clipPath>+ <inkscape:perspective+ id="perspective3049"+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+ inkscape:vp_z="1 : 0.5 : 1"+ inkscape:vp_y="0 : 1000 : 0"+ inkscape:vp_x="0 : 0.5 : 1"+ sodipodi:type="inkscape:persp3d" />+ <inkscape:perspective+ id="perspective3072"+ inkscape:persp3d-origin="0.5 : 0.33333333 : 1"+ inkscape:vp_z="1 : 0.5 : 1"+ inkscape:vp_y="0 : 1000 : 0"+ inkscape:vp_x="0 : 0.5 : 1"+ sodipodi:type="inkscape:persp3d" />+ </defs>+ <g+ id="g2818"+ transform="translate(6.4546249,0.48166647)">+ <path+ transform="matrix(0.17313019,0,0,0.183031,-3.6257093,19.552725)"+ d="M 0,-8.5625 A 8.5580266,8.5580266 0 0 0 -7.125,4.75 L 103.09375,170.0625 -7.125,335.40625 A 8.5580266,8.5580266 0 0 0 0,348.71875 l 85.03125,0 a 8.5580266,8.5580266 0 0 0 7.125,-3.8125 L 205.5625,174.8125 a 8.5580266,8.5580266 0 0 0 0,-9.5 L 92.15625,-4.75 a 8.5580266,8.5580266 0 0 0 -7.125,-3.8125 L 0,-8.5625 z"+ id="path3098"+ style="fill:#666666;fill-opacity:1;fill-rule:nonzero;stroke:none"+ inkscape:original="M 0 0 L 113.375 170.0625 L 0 340.15625 L 85.03125 340.15625 L 198.4375 170.0625 L 85.03125 0 L 0 0 z "+ inkscape:radius="8.5571709"+ sodipodi:type="inkscape:offset" />+ <path+ transform="matrix(0.17313019,0,0,0.183031,-2.2406683,19.552725)"+ d="m 113.375,-8.3125 a 8.300195,8.300195 0 0 0 -6.90625,12.90625 l 110.34375,165.46875 -110.34375,165.5 a 8.300195,8.300195 0 0 0 6.90625,12.90625 l 85.0625,0 A 8.300195,8.300195 0 0 0 205.34375,344.75 L 269.28125,248.8125 333.25,344.75 a 8.300195,8.300195 0 0 0 6.90625,3.71875 l 85.03125,0 a 8.300195,8.300195 0 0 0 6.90625,-12.90625 L 205.34375,-4.59375 A 8.300195,8.300195 0 0 0 198.4375,-8.3125 l -85.0625,0 z"+ id="path3096"+ style="fill:#999999;fill-opacity:1;fill-rule:nonzero;stroke:none"+ inkscape:original="M 113.375 0 L 226.78125 170.0625 L 113.375 340.15625 L 198.4375 340.15625 L 269.28125 233.84375 L 340.15625 340.15625 L 425.1875 340.15625 L 198.4375 0 L 113.375 0 z "+ inkscape:radius="8.299365"+ sodipodi:type="inkscape:offset" />+ <path+ transform="matrix(0.13850415,0,0,0.1464248,-0.57861732,19.552725)"+ d="M 282.875,-11.3125 A 11.566895,11.566895 0 0 0 273.25,6.65625 l 283.46875,425.1875 a 11.566895,11.566895 0 0 0 9.625,5.15625 l 109.28125,0 a 11.566895,11.566895 0 0 0 11.5625,-11.5625 l 0,-70.875 A 11.566895,11.566895 0 0 0 675.625,343 l -44.0625,0 -123.65625,-185.4375 67.46875,0 A 11.566895,11.566895 0 0 0 586.9375,146 l 0,-70.875 A 11.566895,11.566895 0 0 0 575.375,63.5625 l -130.125,0 -46.46875,-69.71875 a 11.566895,11.566895 0 0 0 -9.625,-5.15625 l -106.28125,0 z"+ id="path3094"+ style="fill:#33ccff;fill-opacity:1;fill-rule:nonzero;stroke:none"+ inkscape:original="M 282.875 0.25 L 566.34375 425.4375 L 675.625 425.4375 L 675.625 354.5625 L 625.375 354.5625 L 486.3125 146 L 575.375 146 L 575.375 75.125 L 439.0625 75.125 L 389.15625 0.25 L 282.875 0.25 z "+ inkscape:radius="11.565739"+ sodipodi:type="inkscape:offset" />+ </g>+</svg>