在写桌面软件时,通常会使用到托盘上的泡泡提示功能,让我们来看看使用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
. 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("
%s" % 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("
%s" % 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()
效果如下: