Package screenlets :: Module menu
[hide private]
[frames] | no frames]

Source Code for Module screenlets.menu

  1  # This application is released under the GNU General Public License  
  2  # v3 (or, at your option, any later version). You can find the full  
  3  # text of the license under http://www.gnu.org/licenses/gpl.txt.  
  4  # By using, editing and/or distributing this software you agree to  
  5  # the terms and conditions of this license.  
  6  # Thank you for using free software! 
  7   
  8  # a very hackish, XML-based menu-system (c) RYX (Rico Pfaus) 2007 
  9  # 
 10  # NOTE: This thing is to be considered a quick hack and it lacks on all ends. 
 11  #       It should be either improved (and become a OOP-system ) or removed 
 12  #       once there is a suitable alternative ... 
 13  # 
 14   
 15  import glob, gtk 
 16  import xml.dom.minidom 
 17  from xml.dom.minidom import Node 
 18  import os 
 19  import gettext 
 20  import screenlets 
 21   
 22  gettext.textdomain('screenlets') 
 23  gettext.bindtextdomain('screenlets', screenlets.INSTALL_PREFIX +  '/share/locale') 
 24   
25 -def _(s):
26 return gettext.gettext(s)
27
28 -def add_menuitem (menu, label, callback=None, cb_data=None):
29 """Convenience function to create a menuitem, connect 30 a callback, and add the menuitem to menu.""" 31 if label == "-": 32 item = gtk.SeparatorMenuItem() 33 else: 34 item = gtk.MenuItem(label) 35 return add_menuitem_with_item(menu, item, callback, cb_data)
36
37 -def add_image_menuitem (menu, stock, label=None, callback=None, cb_data=None):
38 """Convenience function to create an ImageMenuItem, connect 39 a callback, and add the menuitem to menu.""" 40 item = ImageMenuItem(stock, label) 41 return add_menuitem_with_item(menu, item, callback, cb_data)
42
43 -def add_submenuitem (root_menu, label, lst, images=None, image_size=(22,22), callback=None, prefix=None):
44 """Convenience function to add submenuitems to a right-click menu through a list. 45 46 images is an optional list of filenames to be used as an image in each menuitem. 47 Each item in the list should either be a string or None. (If an item is None, gtk's 48 no-image icon will be used.) 49 50 If callback is not None, each menuitem will be connected to callback with it's 51 label as callback data. If prefix exists, prefix will be prefixed to the label's 52 name in the callback data. 53 54 Returns the new submenu.""" 55 root_item = gtk.MenuItem(label) 56 root_menu.append(root_item) 57 root_item.show() 58 59 menu = gtk.Menu() 60 root_item.set_submenu(menu) 61 62 i = 0 63 for name in lst: 64 # if this menu contains _some_ images 65 if images is not None: 66 item = ImageMenuItem(label=name) 67 # if there's an image for this specific item then use it 68 if images[i] is not None: 69 pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(images[i], *image_size) 70 item.set_image_from_pixbuf(pixbuf) 71 # if there isn't an image then cause gtk to use the generic broken-img icon 72 else: 73 item.set_image_from_file('') 74 # if this menu doesn't contain _any_ images 75 else: 76 item = gtk.MenuItem(name) 77 if callback is not None: 78 if prefix is not None: 79 item.connect("activate", callback, prefix+name) 80 else: 81 item.connect("activate", callback, name) 82 item.show() 83 menu.append(item) 84 i += 1 85 86 return menu
87
88 -def add_menuitem_with_item (menu, item, callback=None, cb_data=None):
89 """Convenience function to add a menuitem to a menu 90 and connect a callback.""" 91 if callback: 92 if cb_data: 93 item.connect("activate", callback, cb_data) 94 else: 95 item.connect("activate", callback) 96 menu.append(item) 97 item.show() 98 return item
99
100 -def create_menu_from_file (filename, callback):
101 """Creates a menu from an XML-file and returns None if something went wrong""" 102 doc = None 103 try: 104 doc = xml.dom.minidom.parse(filename) 105 except Exception, e: 106 print "XML-Error: %s" % str(e) 107 return None 108 return create_menu_from_xml(doc.firstChild, callback)
109
110 -def create_menu_from_xml (node, callback, icon_size=22):
111 """Create a gtk.Menu by an XML-Node""" 112 menu = gtk.Menu() 113 for node in node.childNodes: 114 #print node 115 type = node.nodeType 116 if type == Node.ELEMENT_NODE: 117 label = node.getAttribute("label") 118 id = node.getAttribute("id") 119 item = None 120 is_check = False 121 # <item> gtk.MenuItem 122 if node.nodeName == "item": 123 item = gtk.MenuItem(label) 124 # <checkitem> gtk.CheckMenuItem 125 elif node.nodeName == "checkitem": 126 item = gtk.CheckMenuItem(label) 127 is_check = True 128 if node.hasAttribute("checked"): 129 item.set_active(True) 130 # <imageitem> gtk.ImageMenuItem 131 elif node.nodeName == "imageitem": 132 icon = node.getAttribute("icon") 133 item = imageitem_from_name(icon, label, icon_size) 134 # <separator> gtk.SeparatorMenuItem 135 elif node.nodeName == "separator": 136 item = gtk.SeparatorMenuItem() 137 # <appdir> 138 elif node.nodeName == "appdir": 139 # create menu from dir with desktop-files 140 path = node.getAttribute("path") 141 appmenu = ApplicationMenu(path) 142 cats = node.getAttribute("cats").split(",") 143 for cat in cats: 144 item = gtk.MenuItem(cat) 145 #item = imageitem_from_name('games', cat) 146 submenu = appmenu.get_menu_for_category(cat, callback) 147 item.set_submenu(submenu) 148 item.show() 149 menu.append(item) 150 item = None # to overjump further append-item calls 151 # <scandir> create directory list 152 elif node.nodeName == "scandir": 153 # get dirname, prefix, suffix, replace-list, skip-list 154 dir = node.getAttribute("directory") 155 # replace $HOME with environment var 156 dir = dir.replace('$HOME', os.environ['HOME']) 157 #expr = node.getAttribute("expr") 158 idprfx = node.getAttribute("id_prefix") 159 idsufx = node.getAttribute("id_suffix") 160 srch = node.getAttribute("search").split(',') 161 repl = node.getAttribute("replace").split(',') 162 skp = node.getAttribute("skip").split(',') 163 # get filter attribute 164 flt = node.getAttribute("filter") 165 if flt=='': 166 flt='*' 167 # scan directory and append items to current menu 168 #fill_menu_from_directory(dir, menu, callback, regexp=expr, filter=flt) 169 fill_menu_from_directory(dir, menu, callback, filter=flt, 170 id_prefix=idprfx, id_suffix=idsufx, search=srch, 171 replace=repl, skip=skp) 172 # item created? 173 if item: 174 if node.hasChildNodes(): 175 # ... call function recursive and set returned menu as submenu 176 submenu = create_menu_from_xml(node, 177 callback, icon_size) 178 item.set_submenu(submenu) 179 item.show() 180 if id: 181 item.connect("activate", callback, id) 182 menu.append(item) 183 return menu
184
185 -def fill_menu_from_directory (dirname, menu, callback, filter='*', 186 id_prefix='', id_suffix='', search=[], replace=[], skip=[]):
187 """Create MenuItems from a directory. 188 TODO: use regular expressions""" 189 # create theme-list from theme-directory 190 lst = glob.glob(dirname + "/" + filter) 191 #print "Scanning: "+dirname + "/" + filter 192 lst.sort() 193 dlen = len(dirname) + 1 194 # check each entry in dir 195 for filename in lst: 196 #print "FILE: " + filename 197 fname = filename[dlen:] 198 # file allowed? 199 if skip.count(fname)<1: 200 #print "OK" 201 # create label (replace unwanted strings) 202 l = len(search) 203 if l>0 and l == len(replace): 204 for i in xrange(l): 205 fname = fname.replace(search[i], replace[i]) 206 # create label (add prefix/suffix/replace) 207 id = id_prefix + fname + id_suffix 208 #print "NAME: "+fname 209 # create menuitem 210 item = gtk.MenuItem(fname) 211 item.connect("activate", callback, id) 212 item.show() 213 menu.append(item)
214
215 -def imageitem_from_name (filename, label, icon_size=32):
216 """Creates a new gtk.ImageMenuItem from a given icon/filename. 217 If an absolute path is not given, the function checks for the name 218 of the icon within the current gtk-theme.""" 219 item = gtk.ImageMenuItem(label) 220 image = gtk.Image() 221 if filename and filename[0]=='/': 222 # load from file 223 try: 224 image.set_from_file(filename) 225 pb = image.get_pixbuf() 226 # rescale, if too big 227 if pb.get_width() > icon_size : 228 pb2 = pb.scale_simple( 229 icon_size, icon_size, 230 gtk.gdk.INTERP_HYPER) 231 image.set_from_pixbuf(pb2) 232 else: 233 image.set_from_pixbuf(pb) 234 except: 235 print "Error while creating image from file: %s" % filename 236 return None 237 else: 238 image.set_from_icon_name(filename, 3) # TODO: use better size 239 if image: 240 item.set_image(image) 241 return item
242
243 -def read_desktop_file (filename):
244 """Read ".desktop"-file into a dict 245 NOTE: Should use utils.IniReader ...""" 246 list = {} 247 f=None 248 try: 249 f = open (filename, "r") 250 except: 251 print "Error: file %s not found." % filename 252 if f: 253 lines = f.readlines() 254 for line in lines: 255 if line[0] != "#" and line !="\n" and line[0] != "[": 256 ll = line.split('=', 1) 257 if len(ll) > 1: 258 list[ll[0]] = ll[1].replace("\n", "") 259 return list
260 261 #----------------------------------------------- 262 # Classes 263 #----------------------------------------------- 264
265 -class ApplicationMenu(object):
266 """A utility-class to simplify the creation of gtk.Menus from directories with 267 desktop-files. Reads all files in one or multiple directories into its internal list 268 and offers an easy way to create entire categories as complete gtk.Menu 269 with gtk.ImageMenuItems. """ 270 271 # the path to read files from 272 __path = "" 273 # list with apps (could be called "cache") 274 __applications = [] 275
276 - def __init__ (self, path):
277 """constructor""" 278 self.__path = path 279 self.__categories = {} 280 self.read_directory(path)
281
282 - def read_directory (self, path):
283 """read all desktop-files in a directory into the internal list 284 and sort them into the available categories""" 285 dirlst = glob.glob(path + '/*') 286 #print "Path: "+path 287 namelen = len(path) 288 for file in dirlst: 289 if file[-8:]=='.desktop': 290 fname = file[namelen:] 291 #print "file: "+fname 292 df = read_desktop_file(file) 293 name = "" 294 icon = "" 295 cmd = "" 296 try: 297 name = df['Name'] 298 icon = df['Icon'] 299 cmd = df['Exec'] 300 cats = df['Categories'].split(';') 301 #typ = df['Type'] 302 #if typ == "Application": 303 self.__applications.append(df) 304 except Exception, ex: 305 print "Exception: %s" % str(ex) 306 print "An error occured with desktop-file: %s" % file
307
308 - def get_menu_for_category (self, cat_name, callback):
309 """returns a gtk.Menu with all apps in the given category""" 310 # get apps in the category 311 applist = [] 312 for app in self.__applications: 313 try: 314 if (';'+app['Categories']).count(';'+cat_name+';') > 0: 315 applist.append(app) 316 except: 317 pass 318 319 # remove duplicates 320 for app in applist: 321 if applist.count(app) > 1: 322 applist.remove(app) 323 # sort list 324 applist.sort() 325 # create menu from list 326 menu = gtk.Menu() 327 for app in applist: 328 item = imageitem_from_name(app['Icon'], app['Name'], 24) 329 if item: 330 item.connect("activate", callback, "exec:" + app['Exec']) 331 item.show() 332 menu.append(item) 333 # return menu 334 return menu
335
336 -class DefaultMenuItem(object):
337 """A container with constants for the default menuitems""" 338 339 # default menuitem constants (is it right to increase like this?) 340 NONE = 0 341 DELETE = 1 342 THEMES = 2 343 INFO = 4 344 SIZE = 8 345 WINDOW_MENU = 16 346 PROPERTIES = 32 347 DELETE = 64 348 QUIT = 128 349 QUIT_ALL = 256 350 # EXPERIMENTAL!! If you use this, the file menu.xml in the 351 # Screenlet's data-dir is used for generating the menu ... 352 XML = 512 353 ADD = 1024 354 # the default items 355 STANDARD = 1|2|8|16|32|64|128|256|1024
356 357
358 -class ImageMenuItem(gtk.ImageMenuItem):
359 """A menuitem with a custom image and label. 360 To set the image to a non-stock image, just 361 create the menuitem without an image and then 362 set the image with the appropriate method.""" 363
364 - def __init__ (self, stock=gtk.STOCK_MISSING_IMAGE, label=None):
365 """stock: a stock image or 'none'. 366 label: text to set as the label or None.""" 367 # call the superclass 368 super(ImageMenuItem, self).__init__(stock) 369 370 # get the label and image for later 371 self.label = self.get_child() 372 self.image = self.get_image() 373 374 # set the label to custom text 375 if label is not None: 376 self.set_label(label)
377
378 - def set_image_from_file (self, filename):
379 """Set the image from file.""" 380 self.image.set_from_file(filename)
381
382 - def set_image_from_pixbuf (self, pixbuf):
383 """Set the image from a pixbuf.""" 384 self.image.set_from_pixbuf(pixbuf)
385
386 - def set_image_from_stock(self, name):
387 """Set the image from a stock image.""" 388 self.image.set_from_stock(name, gtk.ICON_SIZE_MENU)
389
390 - def set_label(self, text):
391 """Set the label's text.""" 392 self.label.set_text(text)
393
394 - def set_image_size (self, width, height):
395 """Resize the menuitem's image.""" 396 self.image.set_size_request(width, height)
397