1
# -*- coding: utf-8 -*-
2
# vim: set noexpandtab:
3
# Alacarte Menu Editor - Simple fd.o Compliant Menu Editor
4
# Copyright (C) 2006 Travis Watkins
6
# This library is free software; you can redistribute it and/or
7
# modify it under the terms of the GNU Library General Public
8
# License as published by the Free Software Foundation; either
9
# version 2 of the License, or (at your option) any later version.
11
# This library is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
# Library General Public License for more details.
16
# You should have received a copy of the GNU Library General Public
17
# License along with this library; if not, write to the Free Software
18
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
from gi.repository import Gtk, GObject, Gio, GdkPixbuf, Gdk, GMenu, GLib
22
import gettext, locale
26
from Alacarte import config
27
gettext.bindtextdomain(config.GETTEXT_PACKAGE, config.localedir)
28
gettext.textdomain(config.GETTEXT_PACKAGE)
29
locale.bind_textdomain_codeset(config.GETTEXT_PACKAGE,'UTF-8')
32
from Alacarte.MenuEditor import MenuEditor
33
from Alacarte import util
35
class MainWindow(object):
37
#hack to make editing menu properties work
41
def __init__(self, datadir, version):
42
self.file_path = datadir
43
self.version = version
44
self.editor = MenuEditor()
45
self.editor.applications.tree.connect("changed", self.menuChanged)
46
Gtk.Window.set_default_icon_name('alacarte')
47
self.tree = Gtk.Builder()
48
self.tree.set_translation_domain(config.GETTEXT_PACKAGE)
49
self.tree.add_from_file(os.path.join(self.file_path, 'alacarte.ui'))
50
self.tree.connect_signals(self)
53
self.tree.get_object('edit_delete').set_sensitive(False)
54
self.tree.get_object('edit_revert_to_original').set_sensitive(False)
55
self.tree.get_object('edit_properties').set_sensitive(False)
56
self.tree.get_object('move_up_button').set_sensitive(False)
57
self.tree.get_object('move_down_button').set_sensitive(False)
58
self.tree.get_object('new_separator_button').set_sensitive(False)
59
accelgroup = Gtk.AccelGroup()
60
keyval, modifier = Gtk.accelerator_parse('F1')
61
accelgroup.connect(keyval, modifier, Gtk.AccelFlags.VISIBLE, self.on_help_button_clicked)
62
self.tree.get_object('mainwindow').add_accel_group(accelgroup)
66
self.tree.get_object('mainwindow').show_all()
69
def menuChanged(self, *a):
72
def loadUpdates(self):
73
if not self.allow_update:
75
menu_tree = self.tree.get_object('menu_tree')
76
item_tree = self.tree.get_object('item_tree')
77
items, iter = item_tree.get_selection().get_selected()
83
if isinstance(items[iter][3], GMenu.TreeEntry):
84
item_id = items[iter][3].get_desktop_file_id()
85
update_type = GMenu.TreeItemType.ENTRY
86
elif isinstance(items[iter][3], GMenu.TreeDirectory):
87
item_id = os.path.split(items[iter][3].get_desktop_file_path())[1]
88
update_type = GMenu.TreeItemType.DIRECTORY
89
elif isinstance(items[iter][3], GMenu.Tree.Separator):
90
item_id = items.get_path(iter)
91
update_type = GMenu.TreeItemType.SEPARATOR
92
menus, iter = menu_tree.get_selection().get_selected()
96
if menus[iter][2].get_desktop_file_path():
97
menu_id = os.path.split(menus[iter][2].get_desktop_file_path())[1]
99
menu_id = menus[iter][2].get_menu_id()
102
#find current menu in new tree
104
menu_tree.get_model().foreach(self.findMenu, menu_id)
105
menus, iter = menu_tree.get_selection().get_selected()
107
self.on_menu_tree_cursor_changed(menu_tree)
108
#find current item in new list
111
for item in item_tree.get_model():
113
if update_type != GMenu.TreeItemType.SEPARATOR:
114
if isinstance (item[3], GMenu.TreeEntry) and item[3].get_desktop_file_id() == item_id:
116
if isinstance (item[3], GMenu.TreeDirectory) and item[3].get_desktop_file_path() and update_type == GMenu.TreeItemType.DIRECTORY:
117
if os.path.split(item[3].get_desktop_file_path())[1] == item_id:
119
if isinstance(item[3], GMenu.TreeSeparator):
120
if not isinstance(item_id, tuple):
121
#we may not skip the increment via "continue"
124
#separators have no id, have to find them manually
125
#probably won't work with two separators together
126
if (item_id[0] - 1,) == (i,):
128
elif (item_id[0] + 1,) == (i,):
130
elif (item_id[0],) == (i,):
133
item_tree.get_selection().select_path((i,))
134
self.on_item_tree_cursor_changed(item_tree)
139
def findMenu(self, menus, path, iter, menu_id):
140
if not menus[path][2].get_desktop_file_path():
141
if menu_id == menus[path][2].get_menu_id():
142
menu_tree = self.tree.get_object('menu_tree')
143
menu_tree.expand_to_path(path)
144
menu_tree.get_selection().select_path(path)
147
if os.path.split(menus[path][2].get_desktop_file_path())[1] == menu_id:
148
menu_tree = self.tree.get_object('menu_tree')
149
menu_tree.expand_to_path(path)
150
menu_tree.get_selection().select_path(path)
153
def setupMenuTree(self):
154
self.menu_store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str, object)
155
menus = self.tree.get_object('menu_tree')
156
column = Gtk.TreeViewColumn(_('Name'))
157
column.set_spacing(4)
158
cell = Gtk.CellRendererPixbuf()
159
column.pack_start(cell, False)
160
column.add_attribute(cell, 'pixbuf', 0)
161
cell = Gtk.CellRendererText()
162
column.pack_start(cell, True)
163
column.add_attribute(cell, 'markup', 1)
164
menus.append_column(column)
165
menus.get_selection().set_mode(Gtk.SelectionMode.BROWSE)
167
def setupItemTree(self):
168
items = self.tree.get_object('item_tree')
169
column = Gtk.TreeViewColumn(_('Show'))
170
cell = Gtk.CellRendererToggle()
171
cell.connect('toggled', self.on_item_tree_show_toggled)
172
column.pack_start(cell, True)
173
column.add_attribute(cell, 'active', 0)
174
#hide toggle for separators
175
column.set_cell_data_func(cell, self._cell_data_toggle_func)
176
items.append_column(column)
177
column = Gtk.TreeViewColumn(_('Item'))
178
column.set_spacing(4)
179
cell = Gtk.CellRendererPixbuf()
180
column.pack_start(cell, False)
181
column.add_attribute(cell, 'pixbuf', 1)
182
cell = Gtk.CellRendererText()
183
column.pack_start(cell, True)
184
column.add_attribute(cell, 'markup', 2)
185
items.append_column(column)
186
self.item_store = Gtk.ListStore(bool, GdkPixbuf.Pixbuf, str, object)
187
items.set_model(self.item_store)
189
def _cell_data_toggle_func(self, tree_column, renderer, model, treeiter, data=None):
190
if isinstance(model[treeiter][3], GMenu.TreeSeparator):
191
renderer.set_property('visible', False)
193
renderer.set_property('visible', True)
196
self.menu_store.clear()
197
self.loadMenu({ None: None })
199
menu_tree = self.tree.get_object('menu_tree')
200
menu_tree.set_model(self.menu_store)
201
for menu in self.menu_store:
202
menu_tree.expand_to_path(menu.path)
203
menu_tree.get_selection().select_path((0,))
204
self.on_menu_tree_cursor_changed(menu_tree)
206
def loadMenu(self, iters, parent=None):
207
for menu, show in self.editor.getMenus(parent):
208
name = cgi.escape(menu.get_name())
210
name = "<small><i>%s</i></small>" % (name,)
212
icon = util.getIcon(menu)
213
iters[menu] = self.menu_store.append(iters[parent], (icon, name, menu))
214
self.loadMenu(iters, menu)
216
def loadItems(self, menu, menu_path):
217
self.item_store.clear()
218
for item, show in self.editor.getItems(menu):
219
icon = util.getIcon(item)
220
if isinstance(item, GMenu.TreeDirectory):
221
name = item.get_name()
222
elif isinstance(item, GMenu.TreeEntry):
223
name = item.get_app_info().get_display_name()
224
elif isinstance(item, GMenu.Separator):
227
assert False, 'should not be reached'
229
name = cgi.escape(name)
231
name = "<small><i>%s</i></small>" % (name,)
233
self.item_store.append((show, icon, name, item))
235
#this is a little timeout callback to insert new items after
236
#gnome-desktop-item-edit has finished running
237
def waitForNewItemProcess(self, process, parent_id, file_path):
238
if process.poll() is not None:
239
if os.path.isfile(file_path):
240
self.editor.insertExternalItem(os.path.split(file_path)[1], parent_id)
244
def waitForNewMenuProcess(self, process, parent_id, file_path):
245
if process.poll() is not None:
246
if os.path.isfile(file_path):
247
self.editor.insertExternalMenu(os.path.split(file_path)[1], parent_id)
251
#this callback keeps you from editing the same item twice
252
def waitForEditProcess(self, process, file_path):
253
if process.poll() is not None:
254
self.edit_pool.remove(file_path)
258
def on_new_menu_button_clicked(self, button):
259
menu_tree = self.tree.get_object('menu_tree')
260
menus, iter = menu_tree.get_selection().get_selected()
262
parent = menus[(0,)][2]
263
menu_tree.expand_to_path((0,))
264
menu_tree.get_selection().select_path((0,))
266
parent = menus[iter][2]
267
file_path = os.path.join(util.getUserDirectoryPath(), util.getUniqueFileId('alacarte-made', '.directory'))
268
if os.getenv("XDG_CURRENT_DESKTOP") == "XFCE":
269
process = subprocess.Popen(['exo-desktop-item-edit', file_path], env=os.environ)
271
process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ)
272
GObject.timeout_add(100, self.waitForNewMenuProcess, process, parent.get_menu_id(), file_path)
274
def on_new_item_button_clicked(self, button):
275
menu_tree = self.tree.get_object('menu_tree')
276
menus, iter = menu_tree.get_selection().get_selected()
278
parent = menus[(0,)][2]
279
menu_tree.expand_to_path((0,))
280
menu_tree.get_selection().select_path((0,))
282
parent = menus[iter][2]
283
file_path = os.path.join(util.getUserItemPath(), util.getUniqueFileId('alacarte-made', '.desktop'))
284
if os.getenv("XDG_CURRENT_DESKTOP") == "XFCE":
285
process = subprocess.Popen(['exo-desktop-item-edit', file_path], env=os.environ)
287
process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ)
288
GObject.timeout_add(100, self.waitForNewItemProcess, process, parent.get_menu_id(), file_path)
290
def on_new_separator_button_clicked(self, button):
291
item_tree = self.tree.get_object('item_tree')
292
items, iter = item_tree.get_selection().get_selected()
296
after = items[iter][3]
297
menu_tree = self.tree.get_object('menu_tree')
298
menus, iter = menu_tree.get_selection().get_selected()
299
parent = menus[iter][2]
300
self.editor.createSeparator(parent, after=after)
302
def on_edit_delete_activate(self, menu):
303
item_tree = self.tree.get_object('item_tree')
304
items, iter = item_tree.get_selection().get_selected()
307
item = items[iter][3]
308
if isinstance(item, GMenu.TreeEntry):
309
self.editor.deleteItem(item)
310
elif isinstance(item, GMenu.TreeDirectory):
311
self.editor.deleteMenu(item)
312
elif isinstance(item, GMenu.TreeSeparator):
313
self.editor.deleteSeparator(item)
315
def on_edit_revert_to_original_activate(self, menu):
316
item_tree = self.tree.get_object('item_tree')
317
items, iter = item_tree.get_selection().get_selected()
320
item = items[iter][3]
321
if isinstance(item, GMenu.TreeEntry):
322
self.editor.revertItem(item)
323
elif isinstance(item, GMenu.TreeDirectory):
324
self.editor.revertMenu(item)
326
def on_edit_properties_activate(self, menu):
327
item_tree = self.tree.get_object('item_tree')
328
items, iter = item_tree.get_selection().get_selected()
331
item = items[iter][3]
332
if not isinstance(item, GMenu.TreeEntry) and not isinstance(item, GMenu.TreeDirectory):
335
if isinstance(item, GMenu.TreeEntry):
336
file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id())
338
elif isinstance(item, GMenu.TreeDirectory):
339
file_path = os.path.join(util.getUserDirectoryPath(), os.path.split(item.get_desktop_file_path())[1])
342
if not os.path.isfile(file_path):
343
data = open(item.get_desktop_file_path()).read()
344
open(file_path, 'w').write(data)
346
if file_path not in self.edit_pool:
347
self.edit_pool.append(file_path)
348
if os.getenv("XDG_CURRENT_DESKTOP") == "XFCE":
349
process = subprocess.Popen(['exo-desktop-item-edit', file_path], env=os.environ)
351
process = subprocess.Popen(['gnome-desktop-item-edit', file_path], env=os.environ)
352
GObject.timeout_add(100, self.waitForEditProcess, process, file_path)
354
def on_menu_tree_cursor_changed(self, treeview):
355
selection = treeview.get_selection()
356
if selection is None:
358
menus, iter = selection.get_selected()
361
menu_path = menus.get_path(iter)
362
item_tree = self.tree.get_object('item_tree')
363
item_tree.get_selection().unselect_all()
364
self.loadItems(self.menu_store[menu_path][2], menu_path)
365
self.tree.get_object('edit_delete').set_sensitive(False)
366
self.tree.get_object('edit_revert_to_original').set_sensitive(False)
367
self.tree.get_object('edit_properties').set_sensitive(False)
368
self.tree.get_object('move_up_button').set_sensitive(False)
369
self.tree.get_object('move_down_button').set_sensitive(False)
370
self.tree.get_object('new_separator_button').set_sensitive(False)
371
self.tree.get_object('properties_button').set_sensitive(False)
372
self.tree.get_object('delete_button').set_sensitive(False)
374
def on_item_tree_show_toggled(self, cell, path):
375
item = self.item_store[path][3]
376
if isinstance(item, GMenu.TreeSeparator):
378
if self.item_store[path][0]:
379
self.editor.setVisible(item, False)
381
self.editor.setVisible(item, True)
382
self.item_store[path][0] = not self.item_store[path][0]
384
def on_item_tree_cursor_changed(self, treeview):
385
selection = treeview.get_selection()
386
if selection is None:
388
items, iter = selection.get_selected()
392
item = items[iter][3]
393
self.tree.get_object('edit_delete').set_sensitive(True)
394
self.tree.get_object('new_separator_button').set_sensitive(True)
395
self.tree.get_object('delete_button').set_sensitive(True)
397
can_revert = self.editor.canRevert(item)
398
self.tree.get_object('edit_revert_to_original').set_sensitive(can_revert)
400
can_edit = not isinstance(item, GMenu.TreeSeparator)
401
self.tree.get_object('edit_properties').set_sensitive(can_edit)
402
self.tree.get_object('properties_button').set_sensitive(can_edit)
404
index = items.get_path(iter).get_indices()[0]
405
can_go_up = index > 0
406
can_go_down = index < len(items) - 1
407
self.tree.get_object('move_up_button').set_sensitive(can_go_up)
408
self.tree.get_object('move_down_button').set_sensitive(can_go_down)
410
def on_item_tree_row_activated(self, treeview, path, column):
411
self.on_edit_properties_activate(None)
413
def on_item_tree_popup_menu(self, item_tree, event=None):
414
model, iter = item_tree.get_selection().get_selected()
416
#don't show if it's not the right mouse button
417
if event.button != 3:
419
button = event.button
420
event_time = event.time
421
info = item_tree.get_path_at_pos(int(event.x), int(event.y))
423
path, col, cellx, celly = info
424
item_tree.grab_focus()
425
item_tree.set_cursor(path, col, 0)
427
path = model.get_path(iter)
430
item_tree.grab_focus()
431
item_tree.set_cursor(path, item_tree.get_columns()[0], 0)
432
popup = self.tree.get_object('edit_menu')
433
popup.popup(None, None, None, None, button, event_time)
434
#without this shift-f10 won't work
437
def on_item_tree_key_press_event(self, item_tree, event):
438
if event.keyval == Gdk.KEY_Delete:
439
self.on_edit_delete_activate(item_tree)
441
def on_move_up_button_clicked(self, button):
442
item_tree = self.tree.get_object('item_tree')
443
items, iter = item_tree.get_selection().get_selected()
446
path = items.get_path(iter)
447
#at top, can't move up
448
if path.get_indices()[0] == 0:
450
item = items[path][3]
451
before = items[(path.get_indices()[0] - 1,)][3]
452
if isinstance(item, GMenu.TreeEntry):
453
self.editor.moveItem(item, item.get_parent(), before=before)
454
elif isinstance(item, GMenu.TreeDirectory):
455
self.editor.moveMenu(item, item.get_parent(), before=before)
456
elif isinstance(item, GMenu.TreeSeparator):
457
self.editor.moveSeparator(item, item.get_parent(), before=before)
459
def on_move_down_button_clicked(self, button):
460
item_tree = self.tree.get_object('item_tree')
461
items, iter = item_tree.get_selection().get_selected()
464
path = items.get_path(iter)
465
#at bottom, can't move down
466
if path.get_indices()[0] == (len(items) - 1):
468
item = items[path][3]
469
after = items[path][3]
470
if isinstance(item, GMenu.TreeEntry):
471
self.editor.moveItem(item, item.get_parent(), after=after)
472
elif isinstance(item, GMenu.TreeDirectory):
473
self.editor.moveMenu(item, item.get_parent(), after=after)
474
elif isinstance(item, GMenu.TreeSeparator):
475
self.editor.moveSeparator(item, item.get_parent(), after=after)
477
def on_help_button_clicked(self, *args):
478
Gtk.show_uri(Gdk.Screen.get_default(), "ghelp:user-guide#menu-editor", Gtk.get_current_event_time())
480
def on_revert_button_clicked(self, button):
481
dialog = self.tree.get_object('revertdialog')
482
dialog.set_transient_for(self.tree.get_object('mainwindow'))
484
if dialog.run() == Gtk.ResponseType.YES:
488
def on_close_button_clicked(self, button):
491
def on_properties_button_clicked(self, button):
492
self.on_edit_properties_activate(None)
493
def on_delete_button_clicked(self, button):
494
self.on_edit_delete_activate(None)
496
def on_style_set(self, *args):