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

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

一、Linux系统:

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

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

效果:

二、Windows下的实现。

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

#!/usr/bin/env python  
# -*- coding: utf-8 -*-  
  
#gtkPopupNotify.py  
#  
# Copyright 2009 Daniel Woodhouse   
# modified by NickCis 2010 http://github.com/NickCis/gtkPopupNotify  
# Modifications:  
#         Added: * Corner support (notifications can be displayed in all corners  
#                * Use of gtk Stock items or pixbuf to render images in notifications  
#                * Posibility of use fixed height  
#                * Posibility of use image as background  
#                * Not displaying over Windows taskbar(taken from emesene gpl v3)  
#                * y separation.  
#                * font description options  
#                * Callbacks For left, middle and right click  
#  
#This program is free software: you can redistribute it and/or modify  
#it under the terms of the GNU Lesser General Public License as published by  
#the Free Software Foundation, either version 3 of the License, or  
#(at your option) any later version.  
#  
#This program is distributed in the hope that it will be useful,  
#but WITHOUT ANY WARRANTY; without even the implied warranty of  
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
#GNU Lesser General Public License for more details.  
#  
#You should have received a copy of the GNU Lesser General Public License  
#along with this program.  If not, see <http://www.gnu.org/licenses/>.  
  
  
import os  
import gtk  
import pango  
import gobject  
  
# This code is used only on Windows to get the location on the taskbar  
# Taken from emesene Notifications (Gpl v3)  
taskbarOffsety = 0  
taskbarOffsetx = 0  
if os.name == "nt":  
    import ctypes  
    from ctypes.wintypes import RECT, DWORD  
    user = ctypes.windll.user32  
    MONITORINFOF_PRIMARY = 1  
    HMONITOR = 1  
  
    class MONITORINFO(ctypes.Structure):  
        _fields_ = [  
            ('cbSize', DWORD),  
            ('rcMonitor', RECT),  
            ('rcWork', RECT),  
            ('dwFlags', DWORD)  
            ]  
  
    taskbarSide = "bottom"  
    taskbarOffset = 30  
    info = MONITORINFO()  
    info.cbSize = ctypes.sizeof(info)  
    info.dwFlags =  MONITORINFOF_PRIMARY  
    user.GetMonitorInfoW(HMONITOR, ctypes.byref(info))  
    if info.rcMonitor.bottom != info.rcWork.bottom:  
        taskbarOffsety = info.rcMonitor.bottom - info.rcWork.bottom  
    if info.rcMonitor.top != info.rcWork.top:  
        taskbarSide = "top"  
        taskbarOffsety = info.rcWork.top - info.rcMonitor.top  
    if info.rcMonitor.left != info.rcWork.left:  
        taskbarSide = "left"  
        taskbarOffsetx = info.rcWork.left - info.rcMonitor.left  
    if info.rcMonitor.right != info.rcWork.right:  
        taskbarSide = "right"  
        taskbarOffsetx = info.rcMonitor.right - info.rcWork.right  
  
class NotificationStack:  
      
    def __init__(self, size_x=300, size_y=-1, timeout=5, corner=(False, False), sep_y=0):  
        """ 
        Create a new notification stack.  The recommended way to create Popup instances. 
          Parameters: 
            `size_x` : The desired width of the notifications. 
            `size_y` : The desired minimum height of the notifications. If it isn't set, 
            or setted to None, the size will automatically adjust 
            `timeout` : Popup instance will disappear after this timeout if there 
            is no human intervention. This can be overridden temporarily by passing 
            a new timout to the new_popup method. 
            `coner` : 2 Value tuple: (true if left, True if top) 
            `sep_y` : y distance to separate notifications from each other 
        """  
        self.size_x = size_x  
        self.size_y = -1   
        if (size_y == None):   
            pass  
        else:  
             size_y  
        self.timeout = timeout  
        self.corner = corner  
        self.sep_y = sep_y  
        """ 
        Other parameters: 
        These will take effect for every popup created after the change. 
             
            `edge_offset_y` : distance from the bottom of the screen and 
            the bottom of the stack. 
            `edge_offset_x` : distance from the right edge of the screen and 
            the side of the stack. 
            `max_popups` : The maximum number of popups to be shown on the screen 
            at one time. 
            `bg_color` : if None default is used (usually grey). set with a gtk.gdk.Color. 
            `bg_pixmap` : Pixmap to use as background of notification. You can set a gtk.gdk.Pixmap 
            or a path to a image. If none, the color background will be displayed. 
            `bg_mask` : If a gtk.gdk.pixmap is specified under bg_pixmap, the mask of the pixmap has to be setted here. 
            `fg_color` : if None default is used (usually black). set with a gtk.gdk.Color. 
            `show_timeout` : if True, a countdown till destruction will be displayed. 
            `close_but` : if True, the close button will be displayed. 
            `fontdesc` : a 3 value Tuple containing the pango.FontDescriptions of the Header, message and counter 
             (in that order). If a string is suplyed, it will be used for the 3 the same FontDescription. 
             http://doc.stoq.com.br/devel/pygtk/class-pangofontdescription.html 
        """          
        self.edge_offset_x = 0  
        self.edge_offset_y = 0  
        self.max_popups = 5  
        self.fg_color = None  
        self.bg_color = None  
        self.bg_pixmap = None  
        self.bg_mask = None  
        self.show_timeout = False  
        self.close_but = True  
        self.fontdesc = ("Sans Bold 14", "Sans 12", "Sans 10")  
          
        self._notify_stack = []  
        self._offset = 0  
  
          
    def new_popup(self, title, message, image=None, leftCb=None, middleCb=None, rightCb=None):  
        """Create a new Popup instance."""  
        if len(self._notify_stack) == self.max_popups:  
            self._notify_stack[0].hide_notification()  
        self._notify_stack.append(Popup(self, title, message, image, leftCb, middleCb, rightCb))  
        self._offset += self._notify_stack[-1].y  
          
    def destroy_popup_cb(self, popup):  
        self._notify_stack.remove(popup)  
        #move popups down if required  
        offset = 0  
        for note in self._notify_stack:  
            offset = note.reposition(offset, self)  
        self._offset = offset  
      
      
  
      
class Popup(gtk.Window):  
    def __init__(self, stack, title, message, image, leftCb, middleCb, rightCb):  
        gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)  
          
        self.leftclickCB = leftCb  
        self.middleclickCB = middleCb  
        self.rightclickCB = rightCb          
          
        self.set_size_request(stack.size_x, stack.size_y)  
        self.set_decorated(False)  
        self.set_deletable(False)  
        self.set_property("skip-pager-hint", True)  
        self.set_property("skip-taskbar-hint", True)  
        self.connect("enter-notify-event", self.on_hover, True)  
        self.connect("leave-notify-event", self.on_hover, False)  
        self.set_opacity(0.2)  
        self.destroy_cb = stack.destroy_popup_cb  
          
        if type(stack.fontdesc) == tuple or type(stack.fontdesc) == list:  
            fontH, fontM, fontC = stack.fontdesc  
        else:  
            fontH = fontM = fontC = stack.fontdesc  
          
        main_box = gtk.VBox()  
        header_box = gtk.HBox()  
        self.header = gtk.Label()  
        self.header.set_markup("<b>%s</b>" % title)  
        self.header.set_padding(3, 3)  
        self.header.set_alignment(0, 0)  
        try:  
            self.header.modify_font(pango.FontDescription(fontH))  
        except Exception, e:  
            print e  
        header_box.pack_start(self.header, True, True, 5)  
        if stack.close_but:  
            close_button = gtk.Image()  
          
            close_button.set_from_stock(gtk.STOCK_CANCEL, gtk.ICON_SIZE_BUTTON)  
            close_button.set_padding(3, 3)  
            close_window = gtk.EventBox()  
            close_window.set_visible_window(False)  
            close_window.connect("button-press-event", self.hide_notification)  
            close_window.add(close_button)  
            header_box.pack_end(close_window, False, False)  
        main_box.pack_start(header_box)  
          
        body_box = gtk.HBox()  
        if image is not None:  
            self.image = gtk.Image()  
            self.image.set_size_request(70, 70)  
            self.image.set_alignment(0, 0)  
            if image in gtk.stock_list_ids():  
                self.image.set_from_stock(image, gtk.ICON_SIZE_DIALOG)  
            elif type(image) == gtk.gdk.Pixbuf:  
                self.image.set_from_pixbuf(image)  
            else:  
                self.image.set_from_file(image)  
            body_box.pack_start(self.image, False, False, 5)  
        self.message = gtk.Label()  
        self.message.set_property("wrap", True)  
        self.message.set_size_request(stack.size_x - 90, -1)  
        self.message.set_alignment(0, 0)  
        self.message.set_padding(5, 10)  
        self.message.set_markup(message)  
        try:  
            self.message.modify_font(pango.FontDescription(fontM))  
        except Exception, e:  
            print e  
        self.counter = gtk.Label()  
        self.counter.set_alignment(1, 1)  
        self.counter.set_padding(3, 3)  
        try:  
            self.counter.modify_font(pango.FontDescription(fontC))  
        except Exception, e:  
            print e  
        self.timeout = stack.timeout  
          
        body_box.pack_start(self.message, True, False, 5)  
        body_box.pack_end(self.counter, False, False, 5)  
        main_box.pack_start(body_box)  
        eventbox = gtk.EventBox()  
        eventbox.set_property('visible-window', False)  
        eventbox.set_events(gtk.gdk.BUTTON_PRESS_MASK)  
        eventbox.connect("button_press_event", self.onClick)    
        eventbox.add(main_box)  
        self.add(eventbox)  
        if stack.bg_pixmap is not None:  
            if not type(stack.bg_pixmap) == gtk.gdk.Pixmap:  
                stack.bg_pixmap, stack.bg_mask = gtk.gdk.pixbuf_new_from_file(stack.bg_pixmap).render_pixmap_and_mask()  
            self.set_app_paintable(True)  
            self.connect_after("realize", self.callbackrealize, stack.bg_pixmap, stack.bg_mask)  
        elif stack.bg_color is not None:  
            self.modify_bg(gtk.STATE_NORMAL, stack.bg_color)  
        if stack.fg_color is not None:  
            self.message.modify_fg(gtk.STATE_NORMAL, stack.fg_color)  
            self.header.modify_fg(gtk.STATE_NORMAL, stack.fg_color)  
            self.counter.modify_fg(gtk.STATE_NORMAL, stack.fg_color)  
        self.show_timeout = stack.show_timeout  
        self.hover = False  
        self.show_all()  
        self.x, self.y = self.size_request()  
        #Not displaying over windows bar   
        if os.name == 'nt':  
            if stack.corner[0] and taskbarSide == "left":  
                stack.edge_offset_x += taskbarOffsetx  
            elif not stack.corner[0] and taskbarSide == 'right':  
                stack.edge_offset_x += taskbarOffsetx  
            if stack.corner[1] and taskbarSide == "top":  
                stack.edge_offset_x += taskbarOffsety  
            elif not stack.corner[1] and taskbarSide == 'bottom':  
                stack.edge_offset_x += taskbarOffsety  
                  
        if stack.corner[0]:  
            posx = stack.edge_offset_x  
        else:  
            posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x  
        sep_y = 0   
        if (stack._offset == 0):  
            pass  
        else:  
             stack.sep_y  
        self.y += sep_y  
        if stack.corner[1]:  
            posy = stack._offset + stack.edge_offset_y + sep_y  
        else:  
            posy = gtk.gdk.screen_height()- self.y - stack._offset - stack.edge_offset_y  
        self.move(posx, posy)  
        self.fade_in_timer = gobject.timeout_add(100, self.fade_in)  
          
          
  
    def reposition(self, offset, stack):  
        """Move the notification window down, when an older notification is removed"""  
        if stack.corner[0]:  
            posx = stack.edge_offset_x  
        else:  
            posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_x  
        if stack.corner[1]:  
            posy = offset + stack.edge_offset_y  
            new_offset = self.y + offset  
        else:              
            new_offset = self.y + offset  
            posy = gtk.gdk.screen_height() - new_offset - stack.edge_offset_y + stack.sep_y  
        self.move(posx, posy)  
        return new_offset  
  
      
    def fade_in(self):  
        opacity = self.get_opacity()  
        opacity += 0.15  
        if opacity >= 1:  
            self.wait_timer = gobject.timeout_add(1000, self.wait)  
            return False  
        self.set_opacity(opacity)  
        return True  
              
    def wait(self):  
        if not self.hover:  
            self.timeout -= 1  
        if self.show_timeout:  
            self.counter.set_markup(str("<b>%s</b>" % self.timeout))  
        if self.timeout == 0:  
            self.fade_out_timer = gobject.timeout_add(100, self.fade_out)  
            return False  
        return True  
        
      
    def fade_out(self):  
        opacity = self.get_opacity()  
        opacity -= 0.10  
        if opacity <= 0:  
            self.in_progress = False  
            self.hide_notification()  
            return False  
        self.set_opacity(opacity)  
        return True  
      
    def on_hover(self, window, event, hover):  
        """Starts/Stops the notification timer on a mouse in/out event"""  
        self.hover = hover  
  
          
    def hide_notification(self, *args):  
        """Destroys the notification and tells the stack to move the 
        remaining notification windows"""  
        for timer in ("fade_in_timer", "fade_out_timer", "wait_timer"):  
            if hasattr(self, timer):  
                gobject.source_remove(getattr(self, timer))  
        self.destroy()  
        self.destroy_cb(self)  
  
    def callbackrealize(self, widget, pixmap, mask=False):  
        #width, height = pixmap.get_size()  
        #self.resize(width, height)  
        if mask is not False:  
            self.shape_combine_mask(mask, 0, 0)  
        self.window.set_back_pixmap(pixmap, False)  
        return True  
  
    def onClick(self, widget, event):  
        if event.button == 1 and self.leftclickCB != None:  
            self.leftclickCB()  
            self.hide_notification()  
        if event.button == 2 and self.middleclickCB != None:  
            self.middleclickCB()  
            self.hide_notification()  
        if event.button == 3 and self.rightclickCB != None:  
            self.rightclickCB()  
            self.hide_notification()  
  
if __name__ == "__main__":  
    #example usage  
      
    def notify_factory():  
        color = ("green", "blue")  
        image = "logo1_64.png"  
        notifier.bg_color = gtk.gdk.Color(color[0])  
        notifier.fg_color = gtk.gdk.Color(color[1])  
        notifier.show_timeout = True   
        notifier.edge_offset_x = 20  
        notifier.edge_offset_y = 30  
        notifier.new_popup("Windows上的泡泡提示", "NND,比Linux下复杂多了,效果还不怎么样", image=image)  
        return True  
  
    def gtk_main_quit():  
        print "quitting"  
        gtk.main_quit()  
      
    notifier = NotificationStack(timeout=1)   
    gobject.timeout_add(4000, notify_factory)  
    gobject.timeout_add(8000, gtk_main_quit)  
    gtk.main()  

效果如下:

 

 

 

转载于:https://my.oschina.net/210920/blog/756574

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值