4
# a very hackish, XML-based menu-system (c) RYX (Rico Pfaus) 2007
6
# NOTE: This thing is to be considered a quick hack and it lacks on all ends.
7
# It should be either improved (and become a OOP-system ) or removed
8
# once there is a suitable alternative ...
12
import xml.dom.minidom
13
from xml.dom.minidom import Node
16
# creates a nw gtk.ImageMenuItem from a given icon-/filename.
17
# If no absolute path is given, the function checks for the name
18
# of the icon within the current gtk-theme.
19
def imageitem_from_name (filename, label, icon_size=32):
20
"""Creates a nw gtk.ImageMenuItem from a given icon-/filename."""
21
item = gtk.ImageMenuItem(label)
23
if filename and filename[0]=='/':
26
image.set_from_file(filename)
27
pb = image.get_pixbuf()
29
if pb.get_width() > icon_size :
30
pb2 = pb.scale_simple(
33
image.set_from_pixbuf(pb2)
35
image.set_from_pixbuf(pb)
37
print "Error while creating image from file: "+filename
40
image.set_from_icon_name(filename, 3) # TODO: use better size
45
def read_desktop_file (filename):
46
"""Read ".desktop"-file into a dict
47
NOTE: Should use utils.IniReader ..."""
51
f = open (filename, "r")
53
print "Error: file " + filename + " not found."
57
if line[0] != "#" and line !="\n" and line[0] != "[":
58
ll = line.split('=', 1)
60
list[ll[0]] = ll[1].replace("\n", "")
63
def fill_menu_from_directory (dirname, menu, callback, filter='*',
64
id_prefix='', id_suffix='', search=[], replace=[], skip=[]):
65
"""Create MenuItems from a directory.
66
TODO: use regular expressions"""
67
# create theme-list from theme-directory
68
lst = glob.glob(dirname + "/" + filter)
69
#print "Scanning: "+dirname + "/" + filter
71
dlen = len(dirname) + 1
72
# check each entry in dir
74
#print "FILE: " + filename
75
fname = filename[dlen:]
77
if skip.count(fname)<1:
79
# create label (replace unwanted strings)
81
if l>0 and l == len(replace):
83
fname = fname.replace(search[i], replace[i])
84
# create label (add prefix/suffix/replace)
85
id = id_prefix + fname + id_suffix
88
item = gtk.MenuItem(fname)
89
item.connect("activate", callback, id)
93
def create_menu_from_xml (node, callback, icon_size=22):
94
"""Create a gtk.Menu by an XML-Node"""
96
for node in node.childNodes:
99
if type == Node.ELEMENT_NODE:
100
label = node.getAttribute("label")
101
id = node.getAttribute("id")
104
# <item> gtk.MenuItem
105
if node.nodeName == "item":
106
item = gtk.MenuItem(label)
107
# <checkitem> gtk.CheckMenuItem
108
elif node.nodeName == "checkitem":
109
item = gtk.CheckMenuItem(label)
111
if node.hasAttribute("checked"):
112
item.set_active(True)
113
# <imageitem> gtk.ImageMenuItem
114
elif node.nodeName == "imageitem":
115
icon = node.getAttribute("icon")
116
item = imageitem_from_name(icon, label, icon_size)
117
# <separator> gtk.SeparatorMenuItem
118
elif node.nodeName == "separator":
119
item = gtk.SeparatorMenuItem()
121
elif node.nodeName == "appdir":
122
# create menu from dir with desktop-files
123
path = node.getAttribute("path")
124
appmenu = ApplicationMenu(path)
125
cats = node.getAttribute("cats").split(",")
127
item = gtk.MenuItem(cat)
128
#item = imageitem_from_name('games', cat)
129
submenu = appmenu.get_menu_for_category(cat, callback)
130
item.set_submenu(submenu)
133
item = None # to overjump further append-item calls
134
# <scandir> create directory list
135
elif node.nodeName == "scandir":
136
# get dirname, prefix, suffix, replace-list, skip-list
137
dir = node.getAttribute("directory")
138
# replace $HOME with environment var
139
dir = dir.replace('$HOME', os.environ['HOME'])
140
#expr = node.getAttribute("expr")
141
idprfx = node.getAttribute("id_prefix")
142
idsufx = node.getAttribute("id_suffix")
143
srch = node.getAttribute("search").split(',')
144
repl = node.getAttribute("replace").split(',')
145
skp = node.getAttribute("skip").split(',')
146
# get filter attribute
147
flt = node.getAttribute("filter")
150
# scan directory and append items to current menu
151
#fill_menu_from_directory(dir, menu, callback, regexp=expr, filter=flt)
152
fill_menu_from_directory(dir, menu, callback, filter=flt,
153
id_prefix=idprfx, id_suffix=idsufx, search=srch,
154
replace=repl, skip=skp)
157
if node.hasChildNodes():
158
# ... call function recursive and set returned menu as submenu
159
submenu = create_menu_from_xml(node,
161
item.set_submenu(submenu)
164
item.connect("activate", callback, id)
168
def create_menu_from_file (filename, callback):
169
"""Creates a menu from an XML-file and returns None if something went wrong"""
172
doc = xml.dom.minidom.parse(filename)
174
print "XML-Error: "+str(e)
176
return create_menu_from_xml(doc.firstChild, callback)
180
class ApplicationMenu:
181
"""A utility-class to simplify the creation of gtk.Menus from directories with
182
desktop-files. Reads all files in one or multiple directories into its internal list
183
and offers an easy way to create entire categories as complete gtk.Menu
184
with gtk.ImageMenuItems. """
186
# the path to read files from
188
# list with apps (could be called "cache")
192
def __init__ (self, path):
194
self.__categories = {}
195
self.read_directory(path)
197
# read all desktop-files in a directory into the internal list
198
# and sort them into the available categories
199
def read_directory (self, path):
200
dirlst = glob.glob(path + '/*')
204
if file[-8:]=='.desktop':
205
fname = file[namelen:]
206
#print "file: "+fname
207
df = read_desktop_file(file)
215
cats = df['Categories'].split(';')
217
#if typ == "Application":
218
self.__applications.append(df)
219
except Exception, ex:
220
print "Exception: "+str(ex)
221
print "An error occured with desktop-file: "+file
223
# return a gtk.Menu with all app in the given category
224
def get_menu_for_category (self, cat_name, callback):
225
# get apps in the given category
227
for app in self.__applications:
229
if (';'+app['Categories']).count(';'+cat_name+';') > 0:
235
# create menu from list
238
item = imageitem_from_name(app['Icon'], app['Name'], 24)
240
item.connect("activate", callback, "exec:" + app['Exec'])
251
def menu_handler(item, id):
254
print "EXECUTE: " + id[5:]
256
def button_press(widget, event):
257
widget.menu.popup(None, None, None, event.button,
261
def destroy(widget, event):
266
# ApplicationMenu test
268
appmenu = ApplicationMenu('/usr/share/applications')
272
win.connect("delete_event", destroy)
273
but = gtk.Button("Press!")
274
but.menu = gtk.Menu()
275
lst = ["Development", "Office", "Game", "Utility"]
276
for i in xrange(len(lst)):
277
item = gtk.MenuItem(lst[i])
278
submenu = appmenu.get_menu_for_category(lst[i], menu_handler)
280
item.set_submenu(submenu)
282
but.menu.append(item)
284
but.connect("button_press_event", button_press)
292
if __name__ == "__main__":
294
import screenlets.utils
297
def xml_menu_handler (item, id):
301
print "EXECUTE: " + id[5:]
303
def button_press (widget, event):
304
widget.menu.popup(None, None, None, event.button,
308
def destroy (widget, event):
311
# create menu from XML-file
312
p = screenlets.utils.find_first_screenlet_path('Control')
314
menu = create_menu_from_file(p + "/menu.xml", xml_menu_handler)
318
win.connect("delete_event", destroy)
319
but = gtk.Button("Press!")
321
but.connect("button_press_event", button_press)
327
print "Error while creating menu."