|
在写桌面软件时,通常会使用到托盘上的泡泡提示功能,让我们来看看使用python如何实现这个小功能。
一、Linux系统:
在Linux上,实现一个气泡提示非常简单,使用GTK实现的pynotify模块提供了些功能,我的环境是Ubuntu,默认安装此模块,如果没有,可从http://home.gna.org/py-notify/下载源文件编译安装一个。实现代码如下:
#!/usr/bin/python#coding:utf-8import pynotifypynotify.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 osimport gtkimport pangoimport gobject# This code is used only on Windows to get the location on the taskbar# Taken from emesene Notifications (Gpl v3)taskbarOffsety = 0taskbarOffsetx = 0if os.name == "nt":import ctypesfrom ctypes.wintypes import RECT, DWORDuser = ctypes.windll.user32MONITORINFOF_PRIMARY = 1HMONITOR = 1class MONITORINFO(ctypes.Structure):_fields_ = [('cbSize', DWORD),('rcMonitor', RECT),('rcWork', RECT),('dwFlags', DWORD)]taskbarSide = "bottom"taskbarOffset = 30info = MONITORINFO()info.cbSize = ctypes.sizeof(info)info.dwFlags = MONITORINFOF_PRIMARYuser.GetMonitorInfoW(HMONITOR, ctypes.byref(info))if info.rcMonitor.bottom != info.rcWork.bottom:taskbarOffsety = info.rcMonitor.bottom - info.rcWork.bottomif info.rcMonitor.top != info.rcWork.top:taskbarSide = "top"taskbarOffsety = info.rcWork.top - info.rcMonitor.topif info.rcMonitor.left != info.rcWork.left:taskbarSide = "left"taskbarOffsetx = info.rcWork.left - info.rcMonitor.leftif info.rcMonitor.right != info.rcWork.right:taskbarSide = "right"taskbarOffsetx = info.rcMonitor.right - info.rcWork.rightclass 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 thereis no human intervention. This can be overridden temporarily by passinga 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_xself.size_y = -1 if (size_y == None): passelse:size_yself.timeout = timeoutself.corner = cornerself.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 andthe bottom of the stack.`edge_offset_x` : distance from the right edge of the screen andthe side of the stack.`max_popups` : The maximum number of popups to be shown on the screenat 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.Pixmapor 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 = 0self.edge_offset_y = 0self.max_popups = 5self.fg_color = Noneself.bg_color = Noneself.bg_pixmap = Noneself.bg_mask = Noneself.show_timeout = Falseself.close_but = Trueself.fontdesc = ("Sans Bold 14", "Sans 12", "Sans 10")self._notify_stack = []self._offset = 0def 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].ydef destroy_popup_cb(self, popup):self._notify_stack.remove(popup)#move popups down if requiredoffset = 0for note in self._notify_stack:offset = note.reposition(offset, self)self._offset = offsetclass Popup(gtk.Window):def __init__(self, stack, title, message, image, leftCb, middleCb, rightCb):gtk.Window.__init__(self, type=gtk.WINDOW_POPUP)self.leftclickCB = leftCbself.middleclickCB = middleCbself.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_cbif type(stack.fontdesc) == tuple or type(stack.fontdesc) == list:fontH, fontM, fontC = stack.fontdescelse:fontH = fontM = fontC = stack.fontdescmain_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 eheader_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 eself.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 eself.timeout = stack.timeoutbody_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_timeoutself.hover = Falseself.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 += taskbarOffsetxelif not stack.corner[0] and taskbarSide == 'right':stack.edge_offset_x += taskbarOffsetxif stack.corner[1] and taskbarSide == "top":stack.edge_offset_x += taskbarOffsetyelif not stack.corner[1] and taskbarSide == 'bottom':stack.edge_offset_x += taskbarOffsetyif stack.corner[0]:posx = stack.edge_offset_xelse:posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_xsep_y = 0 if (stack._offset == 0):passelse:stack.sep_yself.y += sep_yif stack.corner[1]:posy = stack._offset + stack.edge_offset_y + sep_yelse:posy = gtk.gdk.screen_height()- self.y - stack._offset - stack.edge_offset_yself.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_xelse:posx = gtk.gdk.screen_width() - self.x - stack.edge_offset_xif stack.corner[1]:posy = offset + stack.edge_offset_ynew_offset = self.y + offsetelse: new_offset = self.y + offsetposy = gtk.gdk.screen_height() - new_offset - stack.edge_offset_y + stack.sep_yself.move(posx, posy)return new_offsetdef fade_in(self):opacity = self.get_opacity()opacity += 0.15if opacity >= 1:self.wait_timer = gobject.timeout_add(1000, self.wait)return Falseself.set_opacity(opacity)return Truedef wait(self):if not self.hover:self.timeout -= 1if 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 Falsereturn Truedef fade_out(self):opacity = self.get_opacity()opacity -= 0.10if opacity <= 0:self.in_progress = Falseself.hide_notification()return Falseself.set_opacity(opacity)return Truedef on_hover(self, window, event, hover):"""Starts/Stops the notification timer on a mouse in/out event"""self.hover = hoverdef hide_notification(self, *args):"""Destroys the notification and tells the stack to move theremaining 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 Truedef 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 usagedef 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 = 20notifier.edge_offset_y = 30notifier.new_popup("Windows上的泡泡提示", "NND,比Linux下复杂多了,效果还不怎么样", image=image)return Truedef 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()
效果如下: |
|