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

Source Code for Module screenlets.menu

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