Commits

asypost committed 656d27e

Reorganize,clean up,ImageViewer add zoom to fit,zoom 100%,copy url

Comments (0)

Files changed (16)

 *.pyc
-.idea
+.idea
+*.swp

attributes.py

-#encoding=utf-8
-import functools
-
-class AsyncAttribute(object):
-    '''
-    Just a attribute for determine if the function need to be called aysnc
-    '''
-    def __init__(self,func):
-        super(AsyncAttribute,self).__init__()
-        assert callable(func)
-        functools.update_wrapper(self,func)
-        self.__func=func
-
-    def __call__(self,*args,**kwargs):
-        return self.__func(*args,**kwargs)

core/attributes.py

+#encoding=utf-8
+import functools
+
+class AsyncAttribute(object):
+    '''
+    Just a attribute for determine if the function need to be called aysnc
+    '''
+    def __init__(self,func):
+        super(AsyncAttribute,self).__init__()
+        assert callable(func)
+        functools.update_wrapper(self,func)
+        self.__func=func
+
+    def __call__(self,*args,**kwargs):
+        return self.__func(*args,**kwargs)

core/decorators.py

                 worker=Thread()
                 worker.setDaemon(True)
                 def run():
-                    return idle_add(callback,func(*args,**kwargs))
+                    return idle_add(callback,*func(*args,**kwargs))
                 worker.run=run
                 worker.start()
             return __
 from gi.repository import WebKit
 import logging
 from bases import Request
+import webbrowser
 
 logger=logging.getLogger("WeiboView")
 
         self.connect("new-window-policy-decision-requested",self.on_new_window_requested)
         #self.connect("script-alert",self.on_script_alert)
         self.connect("load-finished",self.on_load_finished)
-        #self.connect("hovering-over-link",self.on_over_link)
         self.connect("title-changed",self.on_title_changed)
-    
+
     def __uri_protocol(self,uri):
         return (uri.split("://",1)[0]+"://").lower()
 
+    def open_in_system_browser(self,uri):
+        return webbrowser.open(uri)
+
     def on_title_changed(self,view,frame,title):
         handler=self.get_handler_for(title)
         if handler:
             return True
         else:
             if self.__uri_protocol(request.get_uri())!="file://":
-                import webbrowser
-                return webbrowser.open(request.get_uri())
+                return self.open_in_system_browser(request.get_uri())
         return False
 
     def on_new_window_requested(self,view,frame,request,decision,u_data):
-        #handler=self.get_handler_for(request.get_uri())
-        #if handler:
-         #   handler.handle_uri(request.get_uri())
-          #  return True
-        #self.load_uri(request.get_uri())
-        #return False
-        import webbrowser
-        return webbrowser.open(request.get_uri())
+        self.open_in_system_browser(request.get_uri())
+
 
     def on_script_alert(self,view,webframe,message):
         self.do_request(Request("ui://alert",message=message))
     def get_handler_for(self,uri):
         return self.__handlers.get(self.__uri_protocol(uri),None)
 
-    def on_over_link(self,view,alt,href):
-        self.do_request(Request("ui://tooltip",title=alt or "",message=href or ""))
-
     def do_request(self,request):
         handler=self.get_handler_for(request.uri)
         if handler:
         return False
 
     def on_load_finished(self,view,webframe):
-        pass
+        pass

core/view_pywebkit.py

     from webkit.webkit import WebView
 import logging
 from bases import Request
+import webbrowser
 
 logger=logging.getLogger("WeiboView")
 
         self.connect("new-window-policy-decision-requested",self.on_new_window_requested)
         #self.connect("script-alert",self.on_script_alert)
         self.connect("load-finished",self.on_load_finished)
-        #self.connect("hovering-over-link",self.on_over_link)
         self.connect("title-changed",self.on_title_changed)
-    
+
     def __uri_protocol(self,uri):
         return (uri.split("://",1)[0]+"://").lower()
 
+    def open_in_system_browser(self,uri):
+        return webbrowser.open(uri)
+
     def on_title_changed(self,view,frame,title):
         handler=self.get_handler_for(title)
         if handler:
             return True
         else:
             if self.__uri_protocol(request.get_uri())!="file://":
-                import webbrowser
-                return webbrowser.open(request.get_uri())
+                return self.open_in_system_browser(request.get_uri())
         return False
 
     def on_new_window_requested(self,view,frame,request,decision,u_data):
-        #handler=self.get_handler_for(request.get_uri())
-        #if handler:
-         #   handler.handle_uri(request.get_uri())
-          #  return True
-        #self.load_uri(request.get_uri())
-        #return False
-        import webbrowser
-        return webbrowser.open(request.get_uri())
+        return self.open_in_system_browser(request.get_uri())
 
     def on_script_alert(self,view,webframe,message):
         self.do_request(Request("ui://alert",message=message))
     def get_handler_for(self,uri):
         return self.__handlers.get(self.__uri_protocol(uri),None)
 
-    def on_over_link(self,view,alt,href):
-        self.do_request(Request("ui://tooltip",title=alt or "",message=href or ""))
-
     def do_request(self,request):
         handler=self.get_handler_for(request.uri)
         if handler:
         return False
 
     def on_load_finished(self,view,webframe):
-        pass
+        pass

gui/__init__.py

Empty file added.

gui/gi/__init__.py

+from ui import WeiboUI
+#!/usr/bin/env python2
+#encoding=utf-8
+import os
+from gi.repository import Gdk
+from core.bases import URIHandler
+from core.bases import Request
+from core.view import WebUIView
+from gi.repository import Gtk
+import logging
+from core.configuration import config
+from gettext import gettext as _
+from gi.repository import GLib
+from core.decorators import debug
+from notify import Notification
+import json
+from core.attributes import AsyncAttribute
+from core.decorators import async
+from widgets import ImageViewer,AuthorizeDialog
+
+GLib.threads_init()
+Gdk.threads_init()
+
+FORMAT = '[%(levelname)s] %(name)s: %(message)s'
+
+logging.basicConfig(format=FORMAT,level=logging.DEBUG)
+logger=logging.getLogger("UIHandler")
+
+class WeiboUI(URIHandler):
+    protocol="ui://"
+
+    def __init__(self):
+        super(WeiboUI,self).__init__()
+
+        self.main_window=Gtk.Window()
+        self.main_window.set_icon_name("gwibber")
+        self.main_window.set_title(_("Sina micorblog"))
+        self.main_window.set_default_size(970,600)
+
+        self.weibo_view=WebUIView()
+        self.weibo_view.register_uri_handler(self)
+
+        self.scrolled_window=Gtk.ScrolledWindow.new(None,None)
+
+        self.main_window.add(self.scrolled_window)
+        self.scrolled_window.add(self.weibo_view)
+
+        template=open(os.path.join(config.resources_dir,"index.html")).read()
+        self.weibo_view.load_string(template,"text/html","UTF-8","file://"+\
+            os.path.join(config.resources_dir,"index.html"))
+
+        self.main_window.show_all()
+
+        self.__connect_signals()
+
+    def pending_response(self,request,response):
+        response_json=json.dumps({"request_id":request.id,"data":response})
+        self.weibo_view.execute_script("Native.Contact.pendingResponse({0})".format(response_json))
+        return False
+
+    def handle_request_async(self,request,func):
+        print(request.params)
+        data=func(self,**request.params)
+        return request,data
+
+    def handle_uri(self,uri,*args,**kwargs):
+        if self.is_support(uri):
+            request=Request.from_uri(uri)
+            action_array=request.path[len(self.protocol):].split("/")
+            action=action_array[0] if action_array else None
+            try:
+                if action:
+                    action=getattr(self,action)
+                    if isinstance(action,AsyncAttribute):
+                        async(self.pending_response)(self.handle_request_async)(request,action)
+                    else:
+                        data= action(**request.params)
+                        self.pending_response(request,data)
+            except Exception as e:
+                logger.error("Call {0}({1}) error:{2}".format(action,request.params,e))
+
+    def is_support(self,uri):
+        ret=False
+        if uri.lower().startswith(self.protocol):
+            tmp=uri[len(self.protocol):].split("?")
+            cmd=tmp[0]
+            action=cmd.split("/")[0]
+            ret=hasattr(self,action) and callable(getattr(self,action))
+        ret or logger.warn("Unsupport URI:{0}".format(uri))
+        return ret
+
+    def log(self,log_level,log):
+        log_levels={"info":logger.info,\
+                    "warn":logger.warn,\
+                    "error":logger.error}
+        _log=log_levels.get(log_level,None)
+        if _log:
+            _log(log)
+
+    @debug
+    def show_image(self,url):
+        image_view=ImageViewer(url)
+        image_view.show_all()
+
+    @debug
+    def show_authorize_dialog(self,auhorize_url,code_url):
+        logger.debug("start Authorize dialog")
+        dialog=AuthorizeDialog(self.main_window)
+        code=None
+        if dialog.open(auhorize_url,code_url)==Gtk.ResponseType.ACCEPT:
+            code=dialog.code if dialog.code else ""
+        dialog.destroy()
+        return code
+
+    #@AsyncAttribute
+    @debug
+    def alert(self,message=None):
+        if not message:return True
+     #   Gdk.threads_enter()
+        dialog=Gtk.MessageDialog(self.main_window,Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                          Gtk.MessageType.INFO,Gtk.ButtonsType.OK,message)
+        dialog.set_title(_("Sina micorblog"))
+        dialog.run()
+        dialog.destroy()
+      #  Gdk.threads_leave()
+        return True
+
+    def get_notification(self):
+        return Notification(_("Sina Micorblog"))
+
+    def tooltip(self,title=None,message=None):
+        pass
+
+    def notify(self,summary,body,icon,image=None):
+        notification=self.get_notification()
+        notification.update(summary,body,icon)
+        if image:
+            notification.set_image(image)
+        notification.show()
+
+    def __connect_signals(self):
+        self.main_window.connect("destroy",self.app_quit)
+
+    def app_quit(self,data=None):
+        return Gtk.main_quit()

gui/gi/widgets.py

+#encoding=utf-8
+
+from gi.repository import Gdk,Gtk,GdkPixbuf,GLib
+from threading import Thread
+from gettext import gettext as _
+import os
+import urllib2
+import logging
+from gi.repository import WebKit
+from core.bases import Request
+
+logger=logging.getLogger("Widgets")
+
+class ImageViewer(Gtk.Window):
+    """docstring for ImageViewer"""
+    def __init__(self, url,parent=None):
+        super(ImageViewer, self).__init__()
+        self.pixbuf_loader=None
+        self.url=url
+        self.origin_animation=None
+        self.running=True
+        if parent:
+            self.set_transient_for(parent)
+        self.main_box=Gtk.Box.new(Gtk.Orientation.VERTICAL,0)
+        self.set_default_size(600,400)
+        self.set_title(_("Image"))
+        self.set_position(Gtk.WindowPosition.CENTER)
+        self.image_view=Gtk.Image.new()
+        view_port=Gtk.Viewport()
+        self.scrolled_window=Gtk.ScrolledWindow()
+        view_port.add(self.image_view)
+        self.scrolled_window.add(view_port)
+
+        #toolbar
+        self.toolbar=Gtk.Toolbar()
+        self.main_box.pack_start(self.toolbar,False,True,0)
+        self.tool_save_button=Gtk.ToolButton.new_from_stock(Gtk.STOCK_SAVE)
+        self.toolbar.insert(self.tool_save_button,0)
+        self.tool_zoom_100_button=Gtk.ToolButton.new_from_stock(Gtk.STOCK_ZOOM_100)
+        self.toolbar.insert(self.tool_zoom_100_button,0)
+        self.tool_zoom_100_button.set_sensitive(False)
+        self.tool_zoom_fit_button=Gtk.ToolButton.new_from_stock(Gtk.STOCK_ZOOM_FIT)
+        self.toolbar.insert(self.tool_zoom_fit_button,0)
+        self.tool_zoom_fit_button.set_sensitive(False)
+        self.tool_copy_url_button=Gtk.ToolButton.new_from_stock(Gtk.STOCK_COPY)
+        self.toolbar.insert(self.tool_copy_url_button,0)
+        #signals
+        self.tool_save_button.connect("clicked",self.on_save_button_clicked)
+        self.tool_copy_url_button.connect("clicked",self.on_copy_url_button_clicked)
+        self.tool_zoom_100_button.connect("clicked",self.on_zoom_100_button_clicked)
+        self.tool_zoom_fit_button.connect("clicked",self.on_zoom_fit_button_clicked)
+        self.main_box.pack_start(self.scrolled_window,True,True,0)
+        self.add(self.main_box)
+        self.start_download(self.url)
+        self.connect("destroy",self.on_destroy)
+
+    def on_copy_url_button_clicked(self,widget,data=None):
+        clipboard=Gtk.Clipboard.get_for_display(Gdk.Display.get_default(),Gdk.SELECTION_CLIPBOARD)
+        clipboard.set_text(self.url,-1)
+        clipboard.store()
+
+    def on_zoom_100_button_clicked(self,widget,data=None):
+        if self.origin_animation:
+            self.image_view.set_from_animation(self.origin_animation)
+
+    def on_zoom_fit_button_clicked(self,widget,data=None):
+        if self.origin_animation:
+            static_image=self.origin_animation.get_static_image()
+            image_rate=float(static_image.get_width())/static_image.get_height()
+            window_rate=float(self.scrolled_window.get_allocated_width())/self.scrolled_window.get_allocated_height()
+            if image_rate>window_rate:
+                scale_rate=float(static_image.get_width())/self.scrolled_window.get_allocated_width()
+            else:
+                scale_rate=float(static_image.get_height())/self.scrolled_window.get_allocated_height()
+            static_image=static_image.scale_simple(static_image.get_width()/scale_rate,\
+                                                   static_image.get_height()/scale_rate,\
+                                                   GdkPixbuf.InterpType.BILINEAR)
+            self.image_view.set_from_pixbuf(static_image)
+
+    def on_save_button_clicked(self,widget,data=None):
+        if self.url:
+            dialog=Gtk.FileChooserDialog(_("Save Image"),
+                                             self,
+                                             Gtk.FileChooserAction.SAVE,
+                                             (Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,
+                                             Gtk.STOCK_SAVE,Gtk.ResponseType.ACCEPT))
+            dialog.set_do_overwrite_confirmation(True)
+            if dialog.run()==Gtk.ResponseType.ACCEPT:
+                filename=dialog.get_filename()
+                ext=os.path.split(self.url)[1]
+                filename+=ext;
+                try:
+                    def run():
+                        data=urllib2.urlopen(self.url).read()
+                        f=open(filename,"wb")
+                        f.write(data)
+                        f.close()
+                    downloader=Thread()
+                    downloader.run=run
+                    downloader.start()
+                except Exception,e:
+                    message_dialog=Gtk.MessageDialog(self.main_window,Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                          Gtk.MessageType.ERROR,Gtk.ButtonsType.OK,_("Save Image Failed"))
+                    message_dialog.set_title(_("Sina micorblog"))
+                    message_dialog.run()
+                    message_dialog.destroy()
+                    logger.log(e)
+            dialog.destroy()
+
+
+    def on_destroy(self,data=None):
+        self.running=False
+        if self.pixbuf_loader:
+            try:
+                self.pixbuf_loader.close()
+            except Exception,e:
+                logger.debug(e)
+
+    def update_toolbar(self):
+        """
+        Update the toolbar button states;
+        for zoom buttons:they are enabled only
+        if the GtkImage contains a satic image
+        """
+        button_zoom_sensitive=True if self.origin_animation and \
+                self.origin_animation.is_static_image() else False
+        self.tool_zoom_100_button.set_sensitive(button_zoom_sensitive)
+        self.tool_zoom_fit_button.set_sensitive(button_zoom_sensitive)
+
+    def update_image(self,loader,data=None):
+            try:
+                ani=loader.get_animation()
+                self.image_view.set_from_animation(ani)
+            except Exception,e:
+                logger.debug(e)
+
+    def on_image_downloading(self,data):
+        self.pixbuf_loader.write(data)
+
+    def on_image_downloaded(self):
+        print("download finished")
+        self.update_image(self.pixbuf_loader)
+        self.pixbuf_loader.close()
+        self.origin_animation=self.image_view.get_animation()
+        self.pixbuf_loader=None
+        self.ajust_size(self.origin_animation)
+        self.update_toolbar()
+
+    def on_image_download_failed(self,message):
+        dialog=Gtk.MessageDialog(self,Gtk.DialogFlags.DESTROY_WITH_PARENT,
+                          Gtk.MessageType.ERROR,None,message)
+        dialog.set_title(_("Sina micorblog"))
+        dialog.set_modal(True)
+        close_button=dialog.add_button(Gtk.STOCK_CLOSE,Gtk.ResponseType.CLOSE)
+        def close(button,data=None):
+            dialog.destroy()
+            self.destroy()
+        close_button.connect("clicked",close)
+        dialog.show_all()
+
+    def ajust_size(self,image):
+        if not image:return False
+
+        pixbuf_width=image.get_width()
+        pixbuf_height=image.get_height()
+        screen_width=self.get_screen().get_width()
+        screen_height=self.get_screen().get_height()
+        width,height=self.get_size()
+        toolbar_height=self.toolbar.get_allocated_height()
+
+        if pixbuf_width<=screen_width:
+            width=pixbuf_width
+        if  pixbuf_height<=screen_height:
+            height=pixbuf_height
+
+        height+=toolbar_height+self.get_border_width()
+        width+=self.get_border_width()
+        self.resize(width,height)
+
+    def start_download(self,url):
+        def run():
+            try:
+                buffer_size=1024
+                stream=urllib2.urlopen(url)
+                data=stream.read(buffer_size)
+                while data and self.running:
+                    GLib.idle_add(self.on_image_downloading,data)
+                    data=stream.read(buffer_size)
+                GLib.idle_add(self.on_image_downloaded)
+            except Exception,e:
+                logger.debug(e)
+                GLib.idle_add(self.on_image_download_failed,_("Loading Image Failed"))
+        if not self.pixbuf_loader:
+            self.pixbuf_loader=GdkPixbuf.PixbufLoader()
+            self.pixbuf_loader.connect("area-prepared",self.update_image)
+        thread=Thread()
+        thread.setDaemon(True)
+        thread.run=run
+        thread.start()
+
+
+class AuthorizeDialog(Gtk.Dialog):
+    def __init__(self,parent=None):
+        super(AuthorizeDialog,self).__init__()
+        if parent:
+            self.set_transient_for(parent)
+        self.set_destroy_with_parent(True)
+        self.set_title(_("Authorize"))
+        self.set_modal(True)
+        self.set_default_size(300,400)
+        self.__webview=WebKit.WebView()
+        self.scrolled_window=Gtk.ScrolledWindow.new(None,None)
+        self.__webview.connect("navigation-requested",self.on_navigation_request)
+        self.get_content_area().add(self.__webview)
+        self.access_token_uri=None
+        self.code=None
+        self.show_all()
+
+    def open(self,author_uri,access_token_uri):
+        self.access_token_uri=access_token_uri
+        self.__webview.open(author_uri)
+        return self.run()
+
+    def on_navigation_request(self,view,frame,request):
+        url=request.get_uri()
+        if url.startswith(self.access_token_uri):
+            r=Request.from_uri(url)
+            code=r.params["code"]
+            self.code=code
+            self.response(Gtk.ResponseType.ACCEPT)
+            return True
+        return False

gui/pygtk/__init__.py

+from ui import WeiboUI
+#!/usr/bin/env python2
+#encoding=utf-8
+
+import gtk
+import logging
+from core.bases import URIHandler
+from core.bases import Request
+from core.view_pywebkit import WebUIView
+from core.configuration import config
+from gettext import gettext as _
+from core.decorators import debug
+import os
+import json
+from notify import Notification
+from core.attributes import AsyncAttribute
+from core.decorators import async
+from widgets import ImageViewer,AuthorizeDialog
+
+gtk.gdk.threads_init()
+gtk.threads_init()
+
+FORMAT = '[%(levelname)s] %(name)s: %(message)s'
+
+logging.basicConfig(format=FORMAT,level=logging.DEBUG)
+logger=logging.getLogger("UIHandler")
+
+class WeiboUI(URIHandler):
+    protocol="ui://"
+
+    def __init__(self):
+        super(WeiboUI,self).__init__()
+
+        self.main_window=gtk.Window()
+        self.main_window.set_icon_name("gwibber")
+        self.main_window.set_title(_("Sina micorblog"))
+        self.main_window.set_default_size(970,600)
+
+        self.weibo_view=WebUIView()
+        self.weibo_view.register_uri_handler(self)
+
+        self.scrolled_window=gtk.ScrolledWindow(None,None)
+
+        self.main_window.add(self.scrolled_window)
+        self.scrolled_window.add(self.weibo_view)
+
+        template=open(os.path.join(config.resources_dir,"index.html")).read()
+        self.weibo_view.load_string(template,"text/html","UTF-8","file://"+os.path.join(config.resources_dir,"index.html"))
+
+        self.main_window.show_all()
+
+        self.__connect_signals()
+
+    def pending_response(self,request,response):
+        response_json=json.dumps({"request_id":request.id,"data":response})
+        self.weibo_view.execute_script("Native.Contact.pendingResponse({0})".format(response_json))
+        return False
+
+    def handle_request_async(self,request,func):
+        data=func(self,**request.params)
+        return request,data
+
+    def handle_uri(self,uri,*args,**kwargs):
+        if self.is_support(uri):
+            request=Request.from_uri(uri)
+            action_array=request.path[len(self.protocol):].split("/")
+            action=action_array[0] if action_array else None
+            try:
+                if action:
+                    action=getattr(self,action)
+                    if isinstance(action,AsyncAttribute):
+                        async(self.pending_response)(self.handle_request_async)(request,action)
+                    else:
+                        data= action(**request.params)
+                        self.pending_response(request,data)
+            except Exception as e:
+                logger.error("Call {0}({1}) error:{2}".format(action,request.params,e))
+
+    def is_support(self,uri):
+        ret=False
+        if uri.lower().startswith(self.protocol):
+            tmp=uri[len(self.protocol):].split("?")
+            cmd=tmp[0]
+            action=cmd.split("/")[0]
+            ret=hasattr(self,action) and callable(getattr(self,action))
+        ret or logger.warn("Unsupport URI:{0}".format(uri))
+        return ret
+
+    def log(self,log_level,log):
+        log_levels={"info":logger.info,\
+                    "warn":logger.warn,\
+                    "error":logger.error}
+        _log=log_levels.get(log_level,None)
+        if _log:
+            _log(log)
+
+    @debug
+    def show_image(self,url):
+        image_view=ImageViewer(url)
+        image_view.show_all()
+
+    @AsyncAttribute
+    @debug
+    def alert(self,message=None):
+        if not message:return True
+        gtk.threads_enter()
+        dialog=gtk.MessageDialog(self.main_window,gtk.DIALOG_DESTROY_WITH_PARENT,
+                          gtk.MESSAGE_INFO,gtk.BUTTONS_OK,message)
+        dialog.set_title(_("Sina micorblog"))
+        dialog.run()
+        dialog.destroy()
+        gtk.threads_leave()
+        return True
+
+    @debug
+    def show_authorize_dialog(self,auhorize_url,code_url):
+        logger.debug("start Authorize dialog")
+        dialog=AuthorizeDialog(self.main_window)
+        code=None
+        if dialog.open(auhorize_url,code_url)==gtk.RESPONSE_ACCEPT:
+            code=dialog.code if dialog.code else ""
+        dialog.destroy()
+        return code
+
+    def get_notification(self):
+        return Notification(_("Sina Micorblog"))
+
+    def tooltip(self,title=None,message=None):
+        pass
+
+    def notify(self,summary,body,icon,image=None):
+        notification=self.get_notification()
+        notification.update(summary,body,icon)
+        if image:
+            notification.set_image(image)
+        notification.show()
+
+    def __connect_signals(self):
+        self.main_window.connect("destroy",self.app_quit)
+
+    def app_quit(self,data=None):
+        return gtk.main_quit()

gui/pygtk/widgets.py

+#encoding=utf-8
+
+import gtk
+from gettext import gettext as _
+from webkit.webkit import WebView
+import logging
+import urllib2
+from core.bases import Request
+from threading import Thread
+
+logger=logging.getLogger("Widgets")
+
+class ImageViewer(gtk.Window):
+    """docstring for ImageViewer"""
+    def __init__(self, url,parent=None):
+        super(ImageViewer, self).__init__()
+        self.pixbuf_loader=None
+        self.running=True
+        if parent:
+            self.set_transient_for(parent)
+        self.set_default_size(600,400)
+        self.set_title(_("Image"))
+        self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
+        self.image_view=gtk.Image()
+        view_port=gtk.Viewport()
+        scrolled_window=gtk.ScrolledWindow()
+        scrolled_window.add(view_port)
+        view_port.add(self.image_view)
+        self.add(scrolled_window)
+        self.image_view.show()
+        self.start_download(url)
+        self.connect("destroy",self.on_destroy)
+
+    def on_destroy(self,data=None):
+        self.running=False
+        if self.pixbuf_loader:
+            try:
+                self.pixbuf_loader.close()
+            except Exception,e:
+                logger.debug(e)
+
+    def update_image(self,loader,data=None):
+            try:
+                ani=loader.get_animation()
+                self.image_view.set_from_animation(ani)
+            except Exception,e:
+                logger.debug(e)
+
+    def on_image_downloading(self,data):
+        self.pixbuf_loader.write(data)
+
+    def on_image_downloaded(self):
+        self.update_image(self.pixbuf_loader)
+        self.pixbuf_loader.close()
+        self.pixbuf_loader=None
+        self.ajust_size()
+
+    def on_image_download_failed(self,message):
+        dialog=gtk.MessageDialog(self,gtk.DIALOG_DESTROY_WITH_PARENT,
+                          gtk.MESSAGE_ERROR,None,message)
+        dialog.set_title(_("Sina micorblog"))
+        dialog.set_modal(True)
+        close_button=dialog.add_button(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE)
+        def close(button,data=None):
+            dialog.destroy()
+            self.destroy()
+        close_button.connect("clicked",close)
+        dialog.show_all()
+
+    def ajust_size(self):
+        if not self.image_view.get_animation():
+            return False
+        pixbuf_width=self.image_view.get_animation().get_width()
+        pixbuf_height=self.image_view.get_animation().get_height()
+        screen_width=self.get_screen().get_width()
+        screen_height=self.get_screen().get_height()
+        width,height=self.get_size()
+        if pixbuf_width<=screen_width:
+            width=pixbuf_width
+        if  pixbuf_height<=screen_height:
+            height=pixbuf_height
+        self.resize(width,height)
+
+    def start_download(self,url):
+        def run():
+            try:
+                buffer_size=1024
+                stream=urllib2.urlopen(url)
+                data=stream.read(buffer_size)
+                while data and self.running:
+                    gtk.idle_add(self.on_image_downloading,data)
+                    data=stream.read(buffer_size)
+                gtk.idle_add(self.on_image_downloaded)
+            except Exception,e:
+                print(e)
+                logger.debug(e)
+                gtk.idle_add(self.on_image_download_failed,_("Loading Image Failed"))
+        if not self.pixbuf_loader:
+            self.pixbuf_loader=gtk.gdk.PixbufLoader()
+            self.pixbuf_loader.connect("area-prepared",self.update_image)
+        thread=Thread()
+        thread.setDaemon(True)
+        thread.run=run
+        thread.start()
+
+
+class AuthorizeDialog(gtk.Dialog):
+    def __init__(self,parent=None):
+        super(AuthorizeDialog,self).__init__()
+        if parent:
+            self.set_transient_for(parent)
+        self.set_destroy_with_parent(True)
+        self.set_title(_("Authorize"))
+        self.set_modal(True)
+        self.set_default_size(300,400)
+        self.__webview=WebView()
+        self.scrolled_window=gtk.ScrolledWindow(None,None)
+        self.__webview.connect("navigation-requested",self.on_navigation_request)
+        self.get_content_area().add(self.__webview)
+        self.access_token_uri=None
+        self.code=None
+        self.show_all()
+
+    def open(self,author_uri,access_token_uri):
+        self.access_token_uri=access_token_uri
+        self.__webview.open(author_uri)
+        return self.run()
+
+    def on_navigation_request(self,view,frame,request):
+        url=request.get_uri()
+        if url.startswith(self.access_token_uri):
+            r=Request.from_uri(url)
+            code=r.params["code"]
+            self.code=code
+            self.response(gtk.RESPONSE_ACCEPT)
+            return True
+        return False
 #!/usr/bin/env python2
 
 def pygtk_backend():
-    import pygtk
-    from ui_pygtk import WeiboUI
+    from gui.pygtk import WeiboUI
     import gtk
+    gtk.threads_enter()
     WeiboUI()
     gtk.main()
+    gtk.threads_leave()
 
 def pygobject_backend():
     import gi
     gi.require_version("Gtk","3.0")
     gi.require_version("WebKit","3.0")
-    from ui import WeiboUI
-    from gi.repository import Gtk
+    from gui.gi import WeiboUI
+    from gi.repository import Gtk,Gdk
+    Gdk.threads_enter()
     Gtk.init(sys.argv)
     WeiboUI()
     Gtk.main()
+    Gdk.threads_leave()
 
 def auto_detect():
     try:
     import os
     import gettext
     from argparser import ArgParser
-    from gettext import gettext as _
-    from core.configuration import config
 
     gettext.bindtextdomain("SinaWeibo",os.path.join(os.path.dirname(__file__),"resources/i18n"))
     gettext.textdomain('SinaWeibo')

ui.py

-#!/usr/bin/env python2
-#encoding=utf-8
-import os
-from gi.repository import GdkPixbuf,Gdk
-from core.bases import URIHandler
-from core.bases import Request
-from core.view import WebUIView
-from gi.repository import Gtk
-from gi.repository import WebKit
-import logging
-import urllib2
-from core.configuration import config
-from gettext import gettext as _
-from threading import Thread
-from gi.repository import GLib
-from core.decorators import debug
-from notify import Notification
-import json
-from attributes import AsyncAttribute
-from core.decorators import async
-
-GLib.threads_init()
-Gdk.threads_init()
-
-FORMAT = '[%(levelname)s] %(name)s: %(message)s'
-
-logging.basicConfig(format=FORMAT,level=logging.DEBUG)
-logger=logging.getLogger("UIHandler")
-
-class WeiboUI(URIHandler):
-    protocol="ui://"
-
-    def __init__(self):
-        super(WeiboUI,self).__init__()
-
-        self.main_window=Gtk.Window()
-        self.main_window.set_icon_name("gwibber")
-        self.main_window.set_title(_("Sina micorblog"))
-        self.main_window.set_default_size(970,600)
-
-        self.weibo_view=WebUIView()
-        self.weibo_view.register_uri_handler(self)
-
-        self.scrolled_window=Gtk.ScrolledWindow.new(None,None)
-
-        self.main_window.add(self.scrolled_window)
-        self.scrolled_window.add(self.weibo_view)
-
-        template=open(os.path.join(config.resources_dir,"index.html")).read()
-        self.weibo_view.load_string(template,"text/html","UTF-8","file://"+\
-            os.path.join(config.resources_dir,"index.html"))
-
-        self.main_window.show_all()
-
-        self.__connect_signals()
-
-    def pending_response(self,request,response):
-        response_json=json.dumps({"request_id":request.id,"data":response})
-        self.weibo_view.execute_script("Native.Contact.pendingResponse({0})".format(response_json))
-
-    def handle_request_async(self,request,func):
-        data=func(**request.params)
-        return request,data
-
-    def handle_uri(self,uri,*args,**kwargs):
-        if self.is_support(uri):
-            request=Request.from_uri(uri)
-            args=request.params
-            action_array=request.path[len(self.protocol):].split("/")
-            action=action_array[0] if action_array else None
-            try:
-                if action:
-                    if isinstance(action,AsyncAttribute):
-                        async(self.pending_response)(self.handle_request_async)(request,action)
-                    else:
-                        data= getattr(self,action)(**request.params)
-                        self.pending_response(request,data)
-            except Exception as e:
-                logger.error("Call {0}({1}) error:{2}".format(action,request.params,e))
-
-    def is_support(self,uri):
-        ret=False
-        if uri.lower().startswith(self.protocol):
-            tmp=uri[len(self.protocol):].split("?")
-            cmd=tmp[0]
-            action=cmd.split("/")[0]
-            ret=hasattr(self,action) and callable(getattr(self,action))
-        ret or logger.warn("Unsupport URI:{0}".format(uri))
-        return ret
-
-    @debug
-    def show_image(self,url):
-        image_view=ImageViewer(url)
-        image_view.show_all()
-
-    @debug
-    def show_authorize_dialog(self,auhorize_url,code_url):
-        logger.debug("start Authorize dialog")
-        dialog=AuthorizeDialog(self.main_window)
-        code=None
-        if dialog.open(auhorize_url,code_url)==Gtk.ResponseType.ACCEPT:
-            code=dialog.code if dialog.code else ""
-        dialog.destroy()
-        return code
-
-    def get_notification(self):
-        return Notification(_("Sina Micorblog"))
-
-    def tooltip(self,title=None,message=None):
-        pass
-
-    def notify(self,summary,body,icon,image=None):
-        notification=self.get_notification()
-        notification.update(summary,body,icon)
-        if image:
-            notification.set_image(image)
-        notification.show()
-
-    def __connect_signals(self):
-        self.main_window.connect("destroy",self.app_quit)
-
-    def app_quit(self,data=None):
-        return Gtk.main_quit()
-
-class ImageViewer(Gtk.Window):
-    """docstring for ImageViewer"""
-    def __init__(self, url,parent=None):
-        super(ImageViewer, self).__init__()
-        self.pixbuf_loader=None
-        self.url=url
-        self.running=True
-        if parent:
-            self.set_transient_for(parent)
-        self.main_box=Gtk.Box.new(Gtk.Orientation.VERTICAL,0)
-        self.set_default_size(600,400)
-        self.set_title(_("Image"))
-        self.set_position(Gtk.WindowPosition.CENTER)
-        self.image_view=Gtk.Image.new()
-        view_port=Gtk.Viewport()
-        scrolled_window=Gtk.ScrolledWindow()
-        view_port.add(self.image_view)
-        scrolled_window.add(view_port)
-
-        #toolbar
-        self.toolbar=Gtk.Toolbar()
-        self.main_box.pack_start(self.toolbar,False,True,0)
-        self.tool_save_button=Gtk.ToolButton.new_from_stock(Gtk.STOCK_SAVE)
-        self.toolbar.insert(self.tool_save_button,0)
-        #save signal
-        self.tool_save_button.connect("clicked",self.on_save_button_clicked)
-        self.main_box.pack_start(scrolled_window,True,True,0)
-        self.add(self.main_box)
-        self.start_download(self.url)
-        self.connect("destroy",self.on_destroy)
-
-    def on_save_button_clicked(self,widget,data=None):
-        if self.url:
-            dialog=Gtk.FileChooserDialog(_("Save Image"),
-                                             self,
-                                             Gtk.FileChooserAction.SAVE,
-                                             (Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL,
-                                             Gtk.STOCK_SAVE,Gtk.ResponseType.ACCEPT))
-            dialog.set_do_overwrite_confirmation(True)
-            if dialog.run()==Gtk.ResponseType.ACCEPT:
-                filename=dialog.get_filename()
-                ext=os.path.split(self.url)[1]
-                filename+=ext;
-                try:
-                    def run():
-                        data=urllib2.urlopen(self.url).read()
-                        f=open(filename,"wb")
-                        f.write(data)
-                        f.close()
-                    downloader=Thread()
-                    downloader.run=run
-                    downloader.start()
-                except Exception,e:
-                    message_dialog=Gtk.MessageDialog(self.main_window,Gtk.DialogFlags.DESTROY_WITH_PARENT,
-                          Gtk.MessageType.ERROR,Gtk.ButtonsType.OK,_("Save Image Failed"))
-                    message_dialog.set_title(_("Sina micorblog"))
-                    message_dialog.run()
-                    message_dialog.destroy()
-                    logger.log(e)
-            dialog.destroy()
-
-
-    def on_destroy(self,data=None):
-        self.running=False
-        if self.pixbuf_loader:
-            try:
-                self.pixbuf_loader.close()
-            except Exception,e:
-                logger.debug(e)
-
-    def update_image(self,loader,data=None):
-            try:
-                ani=loader.get_animation()
-                self.image_view.set_from_animation(ani)
-            except Exception,e:
-                logger.debug(e)
-
-    def on_image_downloading(self,data):
-        self.pixbuf_loader.write(data)
-
-    def on_image_downloaded(self):
-        print("download finished")
-        self.update_image(self.pixbuf_loader)
-        self.pixbuf_loader.close()
-        self.pixbuf_loader=None
-        self.ajust_size()
-
-    def on_image_download_failed(self,message):
-        dialog=Gtk.MessageDialog(self,Gtk.DialogFlags.DESTROY_WITH_PARENT,
-                          Gtk.MessageType.ERROR,None,message)
-        dialog.set_title(_("Sina micorblog"))
-        dialog.set_modal(True)
-        close_button=dialog.add_button(Gtk.STOCK_CLOSE,Gtk.ResponseType.CLOSE)
-        def close(button,data=None):
-            dialog.destroy()
-            self.destroy()
-        close_button.connect("clicked",close)
-        dialog.show_all()
-
-    def ajust_size(self):
-        if not self.image_view.get_animation():
-            return False
-        pixbuf_width=self.image_view.get_animation().get_width()
-        pixbuf_height=self.image_view.get_animation().get_height()
-        screen_width=self.get_screen().get_width()
-        screen_height=self.get_screen().get_height()
-        width,height=self.get_size()
-        if pixbuf_width<=screen_width:
-            width=pixbuf_width
-        if  pixbuf_height<=screen_height:
-            height=pixbuf_height
-        self.resize(width,height)
-
-    def start_download(self,url):
-        def run():
-            try:
-                buffer_size=1024
-                stream=urllib2.urlopen(url)
-                data=stream.read(buffer_size)
-                while data and self.running:
-                    GLib.idle_add(self.on_image_downloading,data)
-                    data=stream.read(buffer_size)
-                GLib.idle_add(self.on_image_downloaded)
-            except Exception,e:
-                print(e)
-                logger.debug(e)
-                GLib.idle_add(self.on_image_download_failed,_("Loading Image Failed"))
-        if not self.pixbuf_loader:
-            self.pixbuf_loader=GdkPixbuf.PixbufLoader()
-            self.pixbuf_loader.connect("area-prepared",self.update_image)
-        thread=Thread()
-        thread.setDaemon(True)
-        thread.run=run
-        thread.start()
-
-
-class AuthorizeDialog(Gtk.Dialog):
-    def __init__(self,parent=None):
-        super(AuthorizeDialog,self).__init__()
-        if parent:
-            self.set_transient_for(parent)
-        self.set_destroy_with_parent(True)
-        self.set_title(_("Authorize"))
-        self.set_modal(True)
-        self.set_default_size(300,400)
-        self.__webview=WebKit.WebView()
-        self.scrolled_window=Gtk.ScrolledWindow.new(None,None)
-        self.__webview.connect("navigation-requested",self.on_navigation_request)
-        self.get_content_area().add(self.__webview)
-        self.access_token_uri=None
-        self.code=None
-        self.show_all()
-
-    def open(self,author_uri,access_token_uri):
-        self.access_token_uri=access_token_uri
-        self.__webview.open(author_uri)
-        return self.run()
-
-    def on_navigation_request(self,view,frame,request):
-        url=request.get_uri()
-        if url.startswith(self.access_token_uri):
-            r=Request.from_uri(url)
-            code=r.params["code"]
-            self.code=code
-            self.response(Gtk.ResponseType.ACCEPT)
-            return True
-        return False

ui_pygtk.py

-#!/usr/bin/env python2
-#encoding=utf-8
-
-import gtk
-from webkit.webkit import WebView
-import logging
-from core.bases import URIHandler
-from core.bases import Request
-from core.view_pywebkit import WebUIView
-from core.configuration import config
-from gettext import gettext as _
-from threading import Thread
-from core.decorators import debug
-import os
-import urllib2
-import json
-from notify import Notification
-from attributes import AsyncAttribute
-from core.decorators import async
-
-gtk.gdk.threads_init()
-gtk.threads_init()
-
-FORMAT = '[%(levelname)s] %(name)s: %(message)s'
-
-logging.basicConfig(format=FORMAT,level=logging.DEBUG)
-logger=logging.getLogger("UIHandler")
-
-class WeiboUI(URIHandler):
-    protocol="ui://"
-
-    def __init__(self):
-        super(WeiboUI,self).__init__()
-
-        self.main_window=gtk.Window()
-        self.main_window.set_icon_name("gwibber")
-        self.main_window.set_title(_("Sina micorblog"))
-        self.main_window.set_default_size(970,600)
-
-        self.weibo_view=WebUIView()
-        self.weibo_view.register_uri_handler(self)
-
-        self.scrolled_window=gtk.ScrolledWindow(None,None)
-
-        self.main_window.add(self.scrolled_window)
-        self.scrolled_window.add(self.weibo_view)
-
-        template=open(os.path.join(config.resources_dir,"index.html")).read()
-        self.weibo_view.load_string(template,"text/html","UTF-8","file://"+os.path.join(config.resources_dir,"index.html"))
-
-        self.main_window.show_all()
-
-        self.__connect_signals()
-
-    def pending_response(self,request,response):
-        response_json=json.dumps({"request_id":request.id,"data":response})
-        self.weibo_view.execute_script("Native.Contact.pendingResponse({0})".format(response_json))
-
-    def handle_request_async(self,request,func):
-        data=func(**request.params)
-        return request,data
-
-    def handle_uri(self,uri,*args,**kwargs):
-        if self.is_support(uri):
-            request=Request.from_uri(uri)
-            action_array=request.path[len(self.protocol):].split("/")
-            action=action_array[0] if action_array else None
-            try:
-                if action:
-                    if isinstance(action,AsyncAttribute):
-                        async(self.pending_response)(self.handle_request_async)(request,action)
-                    else:
-                        data= getattr(self,action)(**request.params)
-                        self.pending_response(request,data)
-            except Exception as e:
-                logger.error("Call {0}({1}) error:{2}".format(action,request.params,e))
-
-    def is_support(self,uri):
-        ret=False
-        if uri.lower().startswith(self.protocol):
-            tmp=uri[len(self.protocol):].split("?")
-            cmd=tmp[0]
-            action=cmd.split("/")[0]
-            ret=hasattr(self,action) and callable(getattr(self,action))
-        ret or logger.warn("Unsupport URI:{0}".format(uri))
-        return ret
-
-    @debug
-    def show_image(self,url):
-        image_view=ImageViewer(url)
-        image_view.show_all()
-
-    @debug
-    def alert(self,message=None):
-        if not message:return True
-        dialog=gtk.MessageDialog(self.main_window,gtk.DIALOG_DESTROY_WITH_PARENT,
-                          gtk.MESSAGE_INFO,gtk.BUTTONS_OK,message)
-        dialog.set_title(_("Sina micorblog"))
-        dialog.run()
-        dialog.destroy()
-        return True
-
-    @debug
-    def show_authorize_dialog(self,auhorize_url,code_url):
-        logger.debug("start Authorize dialog")
-        dialog=AuthorizeDialog(self.main_window)
-        code=None
-        if dialog.open(auhorize_url,code_url)==gtk.RESPONSE_ACCEPT:
-            code=dialog.code if dialog.code else ""
-        dialog.destroy()
-        return code
-
-    def get_notification(self):
-        return Notification(_("Sina Micorblog"))
-
-    def tooltip(self,title=None,message=None):
-        pass
-
-    def notify(self,summary,body,icon,image=None):
-        notification=self.get_notification()
-        notification.update(summary,body,icon)
-        if image:
-            notification.set_image(image)
-        notification.show()
-
-    def __connect_signals(self):
-        self.main_window.connect("destroy",self.app_quit)
-
-    def app_quit(self,data=None):
-        return gtk.main_quit()
-
-class ImageViewer(gtk.Window):
-    """docstring for ImageViewer"""
-    def __init__(self, url,parent=None):
-        super(ImageViewer, self).__init__()
-        self.pixbuf_loader=None
-        self.running=True
-        if parent:
-            self.set_transient_for(parent)
-        self.set_default_size(600,400)
-        self.set_title(_("Image"))
-        self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
-        self.image_view=gtk.Image()
-        view_port=gtk.Viewport()
-        scrolled_window=gtk.ScrolledWindow()
-        scrolled_window.add(view_port)
-        view_port.add(self.image_view)
-        self.add(scrolled_window)
-        self.image_view.show()
-        self.start_download(url)
-        self.connect("destroy",self.on_destroy)
-
-    def on_destroy(self,data=None):
-        self.running=False
-        if self.pixbuf_loader:
-            try:
-                self.pixbuf_loader.close()
-            except Exception,e:
-                logger.debug(e)
-
-    def update_image(self,loader,data=None):
-            try:
-                ani=loader.get_animation()
-                self.image_view.set_from_animation(ani)
-            except Exception,e:
-                logger.debug(e)
-
-    def on_image_downloading(self,data):
-        self.pixbuf_loader.write(data)
-
-    def on_image_downloaded(self):
-        self.update_image(self.pixbuf_loader)
-        self.pixbuf_loader.close()
-        self.pixbuf_loader=None
-        self.ajust_size()
-
-    def on_image_download_failed(self,message):
-        dialog=gtk.MessageDialog(self,gtk.DIALOG_DESTROY_WITH_PARENT,
-                          gtk.MESSAGE_ERROR,None,message)
-        dialog.set_title(_("Sina micorblog"))
-        dialog.set_modal(True)
-        close_button=dialog.add_button(gtk.STOCK_CLOSE,gtk.RESPONSE_CLOSE)
-        def close(button,data=None):
-            dialog.destroy()
-            self.destroy()
-        close_button.connect("clicked",close)
-        dialog.show_all()
-
-    def ajust_size(self):
-        if not self.image_view.get_animation():
-            return False
-        pixbuf_width=self.image_view.get_animation().get_width()
-        pixbuf_height=self.image_view.get_animation().get_height()
-        screen_width=self.get_screen().get_width()
-        screen_height=self.get_screen().get_height()
-        width,height=self.get_size()
-        if pixbuf_width<=screen_width:
-            width=pixbuf_width
-        if  pixbuf_height<=screen_height:
-            height=pixbuf_height
-        self.resize(width,height)
-
-    def start_download(self,url):
-        def run():
-            try:
-                buffer_size=1024
-                stream=urllib2.urlopen(url)
-                data=stream.read(buffer_size)
-                while data and self.running:
-                    gtk.idle_add(self.on_image_downloading,data)
-                    data=stream.read(buffer_size)
-                gtk.idle_add(self.on_image_downloaded)
-            except Exception,e:
-                print(e)
-                logger.debug(e)
-                gtk.idle_add(self.on_image_download_failed,_("Loading Image Failed"))
-        if not self.pixbuf_loader:
-            self.pixbuf_loader=gtk.gdk.PixbufLoader()
-            self.pixbuf_loader.connect("area-prepared",self.update_image)
-        thread=Thread()
-        thread.setDaemon(True)
-        thread.run=run
-        thread.start()
-
-
-class AuthorizeDialog(gtk.Dialog):
-    def __init__(self,parent=None):
-        super(AuthorizeDialog,self).__init__()
-        if parent:
-            self.set_transient_for(parent)
-        self.set_destroy_with_parent(True)
-        self.set_title(_("Authorize"))
-        self.set_modal(True)
-        self.set_default_size(300,400)
-        self.__webview=WebView()
-        self.scrolled_window=gtk.ScrolledWindow(None,None)
-        self.__webview.connect("navigation-requested",self.on_navigation_request)
-        self.get_content_area().add(self.__webview)
-        self.access_token_uri=None
-        self.code=None
-        self.show_all()
-
-    def open(self,author_uri,access_token_uri):
-        self.access_token_uri=access_token_uri
-        self.__webview.open(author_uri)
-        return self.run()
-
-    def on_navigation_request(self,view,frame,request):
-        url=request.get_uri()
-        if url.startswith(self.access_token_uri):
-            r=Request.from_uri(url)
-            code=r.params["code"]
-            self.code=code
-            self.response(gtk.RESPONSE_ACCEPT)
-            return True
-        return False