python实现桌面气泡提示功能

yipeiwu_com5年前Python基础

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

一、Linux系统

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

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

效果:

二、Windows下的实现

Windows下实现是比较复杂的,没有pynotify这样一个模块,找到了一个还算不错的模块(地址),这个类有些语法上的小问题,至少在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()

效果如下:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持【听图阁-专注于Python设计】。

相关文章

Python绘制七段数码管实例代码

Python绘制七段数码管实例代码

七段数码管(seven-segmentindicator)由7段数码管拼接而成,每段有亮或不亮两种情况,改进型的七段数码管还包括一个小数点位置 绘制模式: input:输入当前日期的数字...

解决python3中解压zip文件是文件名乱码的问题

在zip标准中,对文件名的 encoding 用的不是 unicode,而可能是各种软件根据系统的默认字符集来采用(此为猜测),因此zipfile中根据文件 flag 检测的时候,只支持...

Python从单元素字典中获取key和value的实例

之前写代码很多时候会遇到这么一种情况:在python的字典中只有一个key/value键值对,想要获取其中的这一个元素还要写个for循环获取。 网上搜了一下,发现还有很多简单的方法: 方...

在Python中操作字符串之startswith()方法的使用

 startswith()方法检查字符串是否以str开始,任选限制匹配与给定索引的开始和结束。 语法 以下是startswith()方法的语法: str.startswit...

Matplotlib中文乱码的3种解决方案

前言 Matplotlib是一个Python 2D绘图库,它可以在各种平台上以各种硬拷贝格式和交互式环境生成出具有出版品质的图形。 Matplotlib可用于Python脚本,Pytho...