Python:桌面气泡提示功能实现


在写桌面软件时,通常会使用到托盘上的泡泡提示功能,让我们来看看使用python如何实现这个小功能。

一、Linux系统:

在Linux上,实现一个气泡提示非常简单,使用GTK实现的pynotify模块提供了些功能,我的环境是Ubuntu,默认安装此模块,如果没有,可从http://home.gna.org/py-notify/下载源文件编译安装一个。实现代码如下:

  1. #!/usr/bin/python   
  2. #coding:utf-8   
  3.   
  4. import pynotify  
  5.   
  6. pynotify.init ("Bubble@Linux")  
  7. bubble_notify = pynotify.Notification ("Linux上的泡泡提示""看,比Windows上实现方便多了!")  
  8. bubble_notify.show ()  

效果:

Python:桌面气泡提示功能实现

二、Windows下的实现。

Windows下实现是比较复杂的,没有pynotify这样一个模块,找到了一个还算不错的模块(地址:https://github.com/woodenbrick/gtkPopupNotify,这个类有些语法上的小问题,至少在python2.6下如此,需要修改一下,如下是修改后的代码),基本可用,代码如下:

  1. #!/usr/bin/env python   
  2. # -*- coding: utf-8 -*-   
  3.   
  4. #gtkPopupNotify.py   
  5. #   
  6. # Copyright 2009 Daniel Woodhouse    
  7. # modified by NickCis 2010 http://github.com/NickCis/gtkPopupNotify   
  8. # Modifications:   
  9. #         Added: * Corner support (notifications can be displayed in all corners   
  10. #                * Use of gtk Stock items or pixbuf to render images in notifications   
  11. #                * Posibility of use fixed height   
  12. #                * Posibility of use image as background   
  13. #                * Not displaying over Windows taskbar(taken from emesene gpl v3)   
  14. #                * y separation.   
  15. #                * font description options   
  16. #                * Callbacks For left, middle and right click   
  17. #   
  18. #This program is free software: you can redistribute it and/or modify   
  19. #it under the terms of the GNU Lesser General Public License as published by   
  20. #the Free Software Foundation, either version 3 of the License, or   
  21. #(at your option) any later version.   
  22. #   
  23. #This program is distributed in the hope that it will be useful,   
  24. #but WITHOUT ANY WARRANTY; without even the implied warranty of   
  25. #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   
  26. #GNU Lesser General Public License for more details.   
  27. #   
  28. #You should have received a copy of the GNU Lesser General Public License   
  29. #along with this program.  If not, see <http://www.gnu.org/licenses/>.   
  30.   
  31.   
  32. import os  
  33. import gtk  
  34. import pango  
  35. import gobject  
  36.   
  37. # This code is used only on Windows to get the location on the taskbar   
  38. # Taken from emesene Notifications (Gpl v3)   
  39. taskbarOffsety = 0  
  40. taskbarOffsetx = 0  
  41. if os.name == "nt":  
  42.     import ctypes  
  43.     from ctypes.wintypes import RECT, DWORD  
  44.     user = ctypes.windll.user32  
  45.     MONITORINFOF_PRIMARY = 1  
  46.     HMONITOR = 1  
  47.   
  48.     class MONITORINFO(ctypes.Structure):  
  49.         _fields_ = [  
  50.             ('cbSize', DWORD),  
  51.             ('rcMonitor', RECT),  
  52.             ('rcWork', RECT),  
  53.             ('dwFlags', DWORD)  
  54.             ]  
  55.   
  56.     taskbarSide = "bottom"  
  57.     taskbarOffset = 30  
  58.     info = MONITORINFO()  
  59.     info.cbSize = ctypes.sizeof(info)  
  60.     info.dwFlags =  MONITORINFOF_PRIMARY  
  61.     user.GetMonitorInfoW(HMONITOR, ctypes.byref(info))  
  62.     if info.rcMonitor.bottom != info.rcWork.bottom:  
  63.         taskbarOffsety = info.rcMonitor.bottom - info.rcWork.bottom  
  64.     if info.rcMonitor.top != info.rcWork.top:  
  65.         taskbarSide = "top"  
  66.         taskbarOffsety = info.rcWork.top - info.rcMonitor.top  
  67.     if info.rcMonitor.left != info.rcWork.left:  
  68.         taskbarSide = "left"  
  69.         taskbarOffsetx = info.rcWork.left - info.rcMonitor.left  
  70.     if info.rcMonitor.right != info.rcWork.right:  
  71.         taskbarSide = "right"  
  72.         taskbarOffsetx = info.rcMonitor.right - info.rcWork.right  
  73.   
  74. class NotificationStack:  
  75.       
  76.     def __init__(self, size_x=300, size_y=-1, timeout=5, corner=(FalseFalse), sep_y=0):  
  77.         """ 
  78.         Create a new notification stack.  The recommended way to create Popup instances. 
  79.           Parameters: 
  80.             `size_x` : The desired width of the notifications. 
  81.             `size_y` : The desired minimum height of the notifications. If it isn't set, 
  82.             or setted to None, the size will automatically adjust 
  83.             `timeout` : Popup instance will disappear after this timeout if there 
  84.             is no human intervention. This can be overridden temporarily by passing 
  85.             a new timout to the new_popup method. 
  86.             `coner` : 2 Value tuple: (true if left, True if top) 
  87.             `sep_y` : y distance to separate notifications from each other 
  88.         """  
  89.         self.size_x = size_x  
  90.         self.size_y = -1   
  91.         if (size_y == None):   
  92.             pass  
  93.         else:  
  94.              size_y  
  95.         self.timeout = timeout  
  96.         self.corner = corner  
  97.         self.sep_y = sep_y  
  98.         """ 
  99.         Other parameters: 
  100.         These will take effect for every popup created after the change. 
  101.              
  102.             `edge_offset_y` : distance from the bottom of the screen and 
  103.             the bottom of the stack. 
  104.             `edge_offset_x` : distance from the right edge of the screen and 
  105.             the side of the stack. 
  106.             `max_popups` : The maximum number of popups to be shown on the screen 
  107.             at one time. 
  108.             `bg_color` : if None default is used (usually grey). set with a gtk.gdk.Color. 
  109.             `bg_pixmap` : Pixmap to use as background of notification. You can set a gtk.gdk.Pixmap 
  110.             or a path to a image. If none, the color background will be displayed. 
  111.             `bg_mask` : If a gtk.gdk.pixmap is specified under bg_pixmap, the mask of the pixmap has to be setted here. 
  112.             `fg_color` : if None default is used (usually black). set with a gtk.gdk.Color. 
  113.             `show_timeout` : if True, a countdown till destruction will be displayed. 
  114.             `close_but` : if True, the close button will be displayed. 
  115.             `fontdesc` : a 3 value Tuple containing the pango.FontDescriptions of the Header, message and counter 
  116.              (in that order). If a string is suplyed, it will be used for the 3 the same FontDescription. 
  117.              http://doc.stoq.com.br/devel/pygtk/class-pangofontdescription.html 
  118.         """          
  119.         self.edge_offset_x = 0  
  120.         self.edge_offset_y = 0  
  121.         self.max_popups = 5  
  122.         self.fg_color = None  
  123.         self.bg_color = None  
  124.         self.bg_pixmap = None  
  125.         self.bg_mask = None  
  126.         self.show_timeout = False  
  127.         self.close_but = True  
  128.         self.fontdesc = ("Sans Bold 14""Sans 12""Sans 10")  
  129.           
  130.         self._notify_stack = []  
  131.         self._offset = 0  
  132.   
  133.           
  134.     def new_popup(self, title, message, image=None, leftCb=None, middleCb=None, rightCb=None):  
  135.         """Create a new Popup instance."""  
  136.         if len(self._notify_stack) == self.max_popups:  
  137.             self._notify_stack[0].hide_notification()  
  138.         self._notify_stack.append(Popup(self, title, message, image, leftCb, middleCb, rightCb))  
  139.         self._offset += self._notify_stack[-1].y  
  140.           
  141.     def destroy_popup_cb(self, popup):  
  142.         self._notify_stack.remove(popup)  
  143.         #move popups down if required   
  144.         offset = 0  
  145.         for note in self._notify_stack:  
  146.             offset = note.reposition(offset, self)  
  147.         self._offset = offset  
  148.       
  149.       
  150.   
  151.       
  152. class Popup(gtk.Window):  
  153.     def __init__(self, stack, title, message, image, leftCb, middleCb, rightCb):  
  154.         gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)  
  155.           
  156.         self.leftclickCB = leftCb  
  157.         self.middleclickCB = middleCb  
  158.         self.rightclickCB = rightCb          
  159.           
  160.         self.set_size_request(stack.size_x, stack.size_y)  
  161.         self.set_decorated(False)  
  162.         self.set_deletable(False)  
  163.         self.set_property("skip-pager-hint"True)  
  164.         self.set_property("skip-taskbar-hint"True)  
  165.         self.connect("enter-notify-event"self.on_hover, True)  
  166.         self.connect("leave-notify-event"self.on_hover, False)  
  167.         self.set_opacity(0.2)  
  168.         self.destroy_cb = stack.destroy_popup_cb  
  169.           
  170.         if type(stack.fontdesc) == tuple or type(stack.fontdesc) == list:  
  171.             fontH, fontM, fontC = stack.fontdesc  
  172.         else:  
  173.             fontH = fontM = fontC = stack.fontdesc  
  174.           
  175.         main_box = gtk.VBox()  
  176.         header_box = gtk.HBox()  
  177.         self.header = gtk.Label()  
  178.         self.header.set_markup("<b>%s</b>" % title)  
  179.         self.header.set_padding(33)  
  180.         self.header.set_alignment(00)  
  181.         try:  
  182.             self.header.modify_font(pango.FontDescription(fontH))  
  183.         except Exception, e:  
  184.             print e  
  185.         header_box.pack_start(self.header, TrueTrue5)  
  186.         if stack.close_but:  
  187.             close_button = gtk.Image()  
  188.           
  189.             close_button.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)  
  190.             close_button.set_padding(33)  
  191.             close_window = gtk.EventBox()  
  192.             close_window.set_visible_window(False)  
  193.             close_window.connect("button-press-event"self.hide_notification)  
  194.             close_window.add(close_button)  
  195.             header_box.pack_end(close_window, FalseFalse)  
  196.         main_box.pack_start(header_box)  
  197.           
  198.         body_box = gtk.HBox()  
  199.         if image is not None:  
  200.             self.image = gtk.Image()  
  201.             self.image.set_size_request(7070)  
  202.             self.image.set_alignment(00)  
  203.             if image in gtk.stock_list_ids():  
  204.                 self.image.set_from_stock(image, gtk.ICON_SIZE_DIALOG)  
  205.             elif type(image) == gtk.gdk.Pixbuf:  
  206.                 self.image.set_from_pixbuf(image)  
  207.             else:  
  208.                 self.image.set_from_file(image)  
  209.             body_box.pack_start(self.image, FalseFalse5)  
  210.         self.message = gtk.Label()  
  211.         self.message.set_property("wrap"True)  
  212.         self.message.set_size_request(stack.size_x - 90, -1)  
  213.         self.message.set_alignment(00)  
  214.         self.message.set_padding(510)  
  215.         self.message.set_markup(message)  
  216.         try:  
  217.             self.message.modify_font(pango.FontDescription(fontM))  
  218.         except Exception, e:  
  219.             print e  
  220.         self.counter = gtk.Label()  
  221.         self.counter.set_alignment(11)  
  222.         self.counter.set_padding(33)  
  223.         try:  
  224.             self.counter.modify_font(pango.FontDescription(fontC))  
  225.         except Exception, e:  
  226.             print e  
  227.         self.timeout = stack.timeout  
  228.           
  229.         body_box.pack_start(self.message, TrueFalse5)  
  230.         body_box.pack_end(self.counter, FalseFalse5)  
  231.         main_box.pack_start(body_box)  
  232.         eventbox = gtk.EventBox()  
  233.         eventbox.set_property('visible-window'False)  
  234.         eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)  
  235.         eventbox.connect("button_press_event"self.onClick)    
  236.         eventbox.add(main_box)  
  237.         self.add(eventbox)  
  238.         if stack.bg_pixmap is not None:  
  239.             if not type(stack.bg_pixmap) == gtk.gdk.Pixmap:  
  240.                 stack.bg_pixmap, stack.bg_mask = gtk.gdk.pixbuf_new_from_file(stack.bg_pixmap).render_pixmap_and_mask()  
  241.             self.set_app_paintable(True)  
  242.             self.connect_after("realize"self.callbackrealize, stack.bg_pixmap, stack.bg_mask)  
  243.         elif stack.bg_color is not None:  
  244.             self.modify_bg(gtk.STATE_NORMAL, stack.bg_color)  
  245.         if stack.fg_color is not None:  
  246.             self.message.modify_fg(gtk.STATE_NORMAL, stack.fg_color)  
  247.             self.header.modify_fg(gtk.STATE_NORMAL, stack.fg_color)  
  248.             self.counter.modify_fg(gtk.STATE_NORMAL, stack.fg_color)  
  249.         self.show_timeout = stack.show_timeout  
  250.         self.hover = False  
  251.         self.show_all()  
  252.         self.x, self.y = self.size_request()  
  253.         #Not displaying over windows bar    
  254.         if os.name == 'nt':  
  255.             if stack.corner[0and taskbarSide == "left":  
  256.                 stack.edge_offset_x += taskbarOffsetx  
  257.             elif not stack.corner[0and taskbarSide == 'right':  
  258.                 stack.edge_offset_x += taskbarOffsetx  
  259.             if stack.corner[1and taskbarSide == "top":  
  260.                 stack.edge_offset_x += taskbarOffsety  
  261.             elif not stack.corner[1and taskbarSide == 'bottom':  
  262.                 stack.edge_offset_x += taskbarOffsety  
  263.                   
  264.         if stack.corner[0]:  
  265.             posx = stack.edge_offset_x  
  266.         else:  
  267.             posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x  
  268.         sep_y = 0   
  269.         if (stack._offset == 0):  
  270.             pass  
  271.         else:  
  272.              stack.sep_y  
  273.         self.y += sep_y  
  274.         if stack.corner[1]:  
  275.             posy = stack._offset + stack.edge_offset_y + sep_y  
  276.         else:  
  277.             posy = gtk.gdk.screen_height()- self.y - stack._offset - stack.edge_offset_y  
  278.         self.move(posx, posy)  
  279.         self.fade_in_timer = gobject.timeout_add(100self.fade_in)  
  280.           
  281.           
  282.   
  283.     def reposition(self, offset, stack):  
  284.         """Move the notification window down, when an older notification is removed"""  
  285.         if stack.corner[0]:  
  286.             posx = stack.edge_offset_x  
  287.         else:  
  288.             posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x  
  289.         if stack.corner[1]:  
  290.             posy = offset + stack.edge_offset_y  
  291.             new_offset = self.y + offset  
  292.         else:              
  293.             new_offset = self.y + offset  
  294.             posy = gtk.gdk.screen_height() - new_offset - stack.edge_offset_y + stack.sep_y  
  295.         self.move(posx, posy)  
  296.         return new_offset  
  297.   
  298.       
  299.     def fade_in(self):  
  300.         opacity = self.get_opacity()  
  301.         opacity += 0.15  
  302.         if opacity >= 1:  
  303.             self.wait_timer = gobject.timeout_add(1000self.wait)  
  304.             return False  
  305.         self.set_opacity(opacity)  
  306.         return True  
  307.               
  308.     def wait(self):  
  309.         if not self.hover:  
  310.             self.timeout -= 1  
  311.         if self.show_timeout:  
  312.             self.counter.set_markup(str("<b>%s</b>" % self.timeout))  
  313.         if self.timeout == 0:  
  314.             self.fade_out_timer = gobject.timeout_add(100self.fade_out)  
  315.             return False  
  316.         return True  
  317.         
  318.       
  319.     def fade_out(self):  
  320.         opacity = self.get_opacity()  
  321.         opacity -= 0.10  
  322.         if opacity <= 0:  
  323.             self.in_progress = False  
  324.             self.hide_notification()  
  325.             return False  
  326.         self.set_opacity(opacity)  
  327.         return True  
  328.       
  329.     def on_hover(self, window, event, hover):  
  330.         """Starts/Stops the notification timer on a mouse in/out event"""  
  331.         self.hover = hover  
  332.   
  333.           
  334.     def hide_notification(self, *args):  
  335.         """Destroys the notification and tells the stack to move the 
  336.         remaining notification windows"""  
  337.         for timer in ("fade_in_timer""fade_out_timer""wait_timer"):  
  338.             if hasattr(self, timer):  
  339.                 gobject.source_remove(getattr(self, timer))  
  340.         self.destroy()  
  341.         self.destroy_cb(self)  
  342.   
  343.     def callbackrealize(self, widget, pixmap, mask=False):  
  344.         #width, height = pixmap.get_size()   
  345.         #self.resize(width, height)   
  346.         if mask is not False:  
  347.             self.shape_combine_mask(mask, 00)  
  348.         self.window.set_back_pixmap(pixmap, False)  
  349.         return True  
  350.   
  351.     def onClick(self, widget, event):  
  352.         if event.button == 1 and self.leftclickCB != None:  
  353.             self.leftclickCB()  
  354.             self.hide_notification()  
  355.         if event.button == 2 and self.middleclickCB != None:  
  356.             self.middleclickCB()  
  357.             self.hide_notification()  
  358.         if event.button == 3 and self.rightclickCB != None:  
  359.             self.rightclickCB()  
  360.             self.hide_notification()  
  361.   
  362. if __name__ == "__main__":  
  363.     #example usage   
  364.       
  365.     def notify_factory():  
  366.         color = ("green""blue")  
  367.         image = "logo1_64.png"  
  368.         notifier.bg_color = gtk.gdk.Color(color[0])  
  369.         notifier.fg_color = gtk.gdk.Color(color[1])  
  370.         notifier.show_timeout = True   
  371.         notifier.edge_offset_x = 20  
  372.         notifier.edge_offset_y = 30  
  373.         notifier.new_popup("Windows上的泡泡提示""NND,比Linux下复杂多了,效果还不怎么样", image=image)  
  374.         return True  
  375.   
  376.     def gtk_main_quit():  
  377.         print "quitting"  
  378.         gtk.main_quit()  
  379.       
  380.     notifier = NotificationStack(timeout=1)   
  381.     gobject.timeout_add(4000, notify_factory)  
  382.     gobject.timeout_add(8000, gtk_main_quit)  
  383.     gtk.main()  

效果如下:

Python:桌面气泡提示功能实现

相关内容