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
21 gi
.require_version('Gtk', '3.0')
22 from gi
.repository
import Gtk
, GdkPixbuf
, Gdk
, GMenu
29 from Alacarte
import config
30 gettext
.bindtextdomain(config
.GETTEXT_PACKAGE
, config
.localedir
)
31 gettext
.textdomain(config
.GETTEXT_PACKAGE
)
34 from Alacarte
.MenuEditor
import MenuEditor
35 from Alacarte
.ItemEditor
import LauncherEditor
, DirectoryEditor
36 from Alacarte
import util
38 class MainWindow(object):
40 Gtk
.Window
.set_default_icon_name('alacarte')
41 self
.tree
= Gtk
.Builder()
42 self
.tree
.set_translation_domain(config
.GETTEXT_PACKAGE
)
43 self
.tree
.add_from_file(os
.path
.join(config
.pkgdatadir
, 'alacarte.ui'))
44 self
.tree
.connect_signals(self
)
47 self
.tree
.get_object('edit_delete').set_sensitive(False)
48 self
.tree
.get_object('edit_properties').set_sensitive(False)
49 self
.tree
.get_object('move_up_button').set_sensitive(False)
50 self
.tree
.get_object('move_down_button').set_sensitive(False)
51 self
.tree
.get_object('new_separator_button').set_sensitive(False)
53 self
.main_window
= self
.tree
.get_object('mainwindow')
57 def setMenuBasename(self
, menu_basename
):
58 if self
.editor
is not None:
59 self
.editor
.tree
.disconnect(self
.menuChangedId
)
61 self
.editor
= MenuEditor(menu_basename
)
62 self
.menuChangedId
= self
.editor
.tree
.connect("changed", self
.menuChanged
)
67 self
.tree
.get_object('mainwindow').show_all()
70 def menuChanged(self
, *a
):
73 def loadUpdates(self
):
74 menu_tree
= self
.tree
.get_object('menu_tree')
75 item_tree
= self
.tree
.get_object('item_tree')
76 items
, iter = item_tree
.get_selection().get_selected()
82 if isinstance(items
[iter][3], GMenu
.TreeEntry
):
83 item_id
= items
[iter][3].get_desktop_file_id()
84 update_type
= GMenu
.TreeItemType
.ENTRY
85 elif isinstance(items
[iter][3], GMenu
.TreeDirectory
):
86 item_id
= os
.path
.split(items
[iter][3].get_desktop_file_path())[1]
87 update_type
= GMenu
.TreeItemType
.DIRECTORY
88 elif isinstance(items
[iter][3], GMenu
.TreeSeparator
):
89 item_id
= items
.get_path(iter)
90 update_type
= GMenu
.TreeItemType
.SEPARATOR
91 menus
, iter = menu_tree
.get_selection().get_selected()
95 if menus
[iter][2].get_desktop_file_path():
96 menu_id
= os
.path
.split(menus
[iter][2].get_desktop_file_path())[1]
98 menu_id
= menus
[iter][2].get_menu_id()
101 #find current menu in new tree
103 menu_tree
.get_model().foreach(self
.findMenu
, menu_id
)
104 menus
, iter = menu_tree
.get_selection().get_selected()
106 self
.on_menu_tree_cursor_changed(menu_tree
)
107 #find current item in new list
110 for item
in item_tree
.get_model():
112 if update_type
!= GMenu
.TreeItemType
.SEPARATOR
:
113 if isinstance (item
[3], GMenu
.TreeEntry
) and item
[3].get_desktop_file_id() == item_id
:
115 if isinstance (item
[3], GMenu
.TreeDirectory
) and item
[3].get_desktop_file_path() and update_type
== GMenu
.TreeItemType
.DIRECTORY
:
116 if os
.path
.split(item
[3].get_desktop_file_path())[1] == item_id
:
118 if isinstance(item
[3], GMenu
.TreeSeparator
):
119 if not isinstance(item_id
, tuple):
120 #we may not skip the increment via "continue"
123 #separators have no id, have to find them manually
124 #probably won't work with two separators together
125 if (item_id
[0] - 1,) == (i
,):
127 elif (item_id
[0] + 1,) == (i
,):
129 elif (item_id
[0],) == (i
,):
132 item_tree
.get_selection().select_path((i
,))
133 self
.on_item_tree_cursor_changed(item_tree
)
138 def findMenu(self
, menus
, path
, iter, menu_id
):
139 if not menus
[path
][2].get_desktop_file_path():
140 if menu_id
== menus
[path
][2].get_menu_id():
141 menu_tree
= self
.tree
.get_object('menu_tree')
142 menu_tree
.expand_to_path(path
)
143 menu_tree
.get_selection().select_path(path
)
146 if os
.path
.split(menus
[path
][2].get_desktop_file_path())[1] == menu_id
:
147 menu_tree
= self
.tree
.get_object('menu_tree')
148 menu_tree
.expand_to_path(path
)
149 menu_tree
.get_selection().select_path(path
)
152 def setupMenuTree(self
):
153 self
.menu_store
= Gtk
.TreeStore(GdkPixbuf
.Pixbuf
, str, object)
154 menus
= self
.tree
.get_object('menu_tree')
155 column
= Gtk
.TreeViewColumn(_('Name'))
156 column
.set_spacing(4)
157 cell
= Gtk
.CellRendererPixbuf()
158 column
.pack_start(cell
, False)
159 column
.add_attribute(cell
, 'pixbuf', 0)
160 cell
= Gtk
.CellRendererText()
161 column
.pack_start(cell
, True)
162 column
.add_attribute(cell
, 'markup', 1)
163 menus
.append_column(column
)
164 menus
.get_selection().set_mode(Gtk
.SelectionMode
.BROWSE
)
166 def setupItemTree(self
):
167 items
= self
.tree
.get_object('item_tree')
168 column
= Gtk
.TreeViewColumn(_('Show'))
169 cell
= Gtk
.CellRendererToggle()
170 cell
.connect('toggled', self
.on_item_tree_show_toggled
)
171 column
.pack_start(cell
, True)
172 column
.add_attribute(cell
, 'active', 0)
173 #hide toggle for separators
174 column
.set_cell_data_func(cell
, self
._cell
_data
_toggle
_func
)
175 items
.append_column(column
)
176 column
= Gtk
.TreeViewColumn(_('Item'))
177 column
.set_spacing(4)
178 cell
= Gtk
.CellRendererPixbuf()
179 column
.pack_start(cell
, False)
180 column
.add_attribute(cell
, 'pixbuf', 1)
181 cell
= Gtk
.CellRendererText()
182 column
.pack_start(cell
, True)
183 column
.add_attribute(cell
, 'markup', 2)
184 items
.append_column(column
)
185 self
.item_store
= Gtk
.ListStore(bool, GdkPixbuf
.Pixbuf
, str, object)
186 items
.set_model(self
.item_store
)
188 def _cell_data_toggle_func(self
, tree_column
, renderer
, model
, treeiter
, data
=None):
189 if isinstance(model
[treeiter
][3], GMenu
.TreeSeparator
):
190 renderer
.set_property('visible', False)
192 renderer
.set_property('visible', True)
195 self
.menu_store
.clear()
196 self
.loadMenu({ None: None })
198 menu_tree
= self
.tree
.get_object('menu_tree')
199 menu_tree
.set_model(self
.menu_store
)
200 for menu
in self
.menu_store
:
201 menu_tree
.expand_to_path(menu
.path
)
202 menu_tree
.get_selection().select_path((0,))
203 self
.on_menu_tree_cursor_changed(menu_tree
)
205 def loadMenu(self
, iters
, parent
=None):
206 for menu
, show
in self
.editor
.getMenus(parent
):
207 name
= cgi
.escape(menu
.get_name())
209 icon
= util
.getIcon(menu
)
210 iters
[menu
] = self
.menu_store
.append(iters
[parent
], (icon
, name
, menu
))
211 self
.loadMenu(iters
, menu
)
213 def loadItems(self
, menu
):
214 self
.item_store
.clear()
215 for item
, show
in self
.editor
.getItems(menu
):
216 icon
= util
.getIcon(item
)
217 if isinstance(item
, GMenu
.TreeDirectory
):
218 name
= item
.get_name()
219 elif isinstance(item
, GMenu
.TreeEntry
):
220 name
= item
.get_app_info().get_display_name()
221 elif isinstance(item
, GMenu
.TreeSeparator
):
224 assert False, 'should not be reached'
226 name
= cgi
.escape(name
)
228 self
.item_store
.append((show
, icon
, name
, item
))
230 def on_delete_event(self
, widget
, event
):
233 def on_new_menu_button_clicked(self
, button
):
234 menu_tree
= self
.tree
.get_object('menu_tree')
235 menus
, iter = menu_tree
.get_selection().get_selected()
237 parent
= menus
[(0,)][2]
238 menu_tree
.expand_to_path((0,))
239 menu_tree
.get_selection().select_path((0,))
241 parent
= menus
[iter][2]
242 file_name
= util
.getUniqueFileId('alacarte-made', '.directory')
243 file_path
= os
.path
.join(util
.getUserDirectoryPath(), file_name
)
245 editor
= DirectoryEditor(self
.main_window
, file_path
)
246 editor
.file_name
= file_name
;
247 editor
.parent
= parent
.get_menu_id()
248 editor
.connect('response', self
.on_directory_created
)
251 def on_directory_created(self
, editor
, response
):
253 self
.editor
.insertExternalMenu(editor
.file_name
, editor
.parent
)
255 def on_new_item_button_clicked(self
, button
):
256 menu_tree
= self
.tree
.get_object('menu_tree')
257 menus
, iter = menu_tree
.get_selection().get_selected()
259 parent
= menus
[(0,)][2]
260 menu_tree
.expand_to_path((0,))
261 menu_tree
.get_selection().select_path((0,))
263 parent
= menus
[iter][2]
264 file_name
= util
.getUniqueFileId('alacarte-made', '.desktop')
265 file_path
= os
.path
.join(util
.getUserItemPath(), file_name
)
267 editor
= LauncherEditor(self
.main_window
, file_path
)
268 editor
.file_name
= file_name
;
269 editor
.parent
= parent
.get_menu_id()
270 editor
.connect('response', self
.on_item_created
)
273 def on_item_created(self
, editor
, response
):
275 self
.editor
.insertExternalItem(editor
.file_name
, editor
.parent
)
277 def on_new_separator_button_clicked(self
, button
):
278 item_tree
= self
.tree
.get_object('item_tree')
279 items
, iter = item_tree
.get_selection().get_selected()
283 after
= items
[iter][3]
284 menu_tree
= self
.tree
.get_object('menu_tree')
285 menus
, iter = menu_tree
.get_selection().get_selected()
286 parent
= menus
[iter][2]
287 self
.editor
.createSeparator(parent
, after
=after
)
289 def on_edit_delete_activate(self
, menu
):
290 item_tree
= self
.tree
.get_object('item_tree')
291 items
, iter = item_tree
.get_selection().get_selected()
294 item
= items
[iter][3]
295 if isinstance(item
, GMenu
.TreeEntry
):
296 self
.editor
.deleteItem(item
)
297 elif isinstance(item
, GMenu
.TreeDirectory
):
298 self
.editor
.deleteMenu(item
)
299 elif isinstance(item
, GMenu
.TreeSeparator
):
300 self
.editor
.deleteSeparator(item
)
302 def on_edit_properties_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 not isinstance(item
, GMenu
.TreeEntry
) and not isinstance(item
, GMenu
.TreeDirectory
):
311 if isinstance(item
, GMenu
.TreeEntry
):
312 file_path
= os
.path
.join(util
.getUserItemPath(), item
.get_desktop_file_id())
314 Editor
= LauncherEditor
315 elif isinstance(item
, GMenu
.TreeDirectory
):
316 file_path
= os
.path
.join(util
.getUserDirectoryPath(), os
.path
.split(item
.get_desktop_file_path())[1])
318 Editor
= DirectoryEditor
321 if not os
.path
.isfile(file_path
):
322 shutil
.copy(item
.get_desktop_file_path(), file_path
)
325 editor
= Editor(self
.main_window
, file_path
)
326 editor
.connect('response', self
.on_editor_response
, file_path
if copied
else None)
329 def on_editor_response(self
, editor
, modified
, file_path
):
330 if not modified
and file_path
is not None:
333 def on_menu_tree_cursor_changed(self
, treeview
):
334 selection
= treeview
.get_selection()
335 if selection
is None:
337 menus
, iter = selection
.get_selected()
340 menu_path
= menus
.get_path(iter)
341 item_tree
= self
.tree
.get_object('item_tree')
342 item_tree
.get_selection().unselect_all()
343 self
.loadItems(self
.menu_store
[menu_path
][2])
344 self
.tree
.get_object('edit_delete').set_sensitive(False)
345 self
.tree
.get_object('edit_properties').set_sensitive(False)
346 self
.tree
.get_object('move_up_button').set_sensitive(False)
347 self
.tree
.get_object('move_down_button').set_sensitive(False)
348 self
.tree
.get_object('new_separator_button').set_sensitive(False)
349 self
.tree
.get_object('properties_button').set_sensitive(False)
350 self
.tree
.get_object('delete_button').set_sensitive(False)
352 def on_item_tree_show_toggled(self
, cell
, path
):
353 item
= self
.item_store
[path
][3]
354 if isinstance(item
, GMenu
.TreeSeparator
):
356 if self
.item_store
[path
][0]:
357 self
.editor
.setVisible(item
, False)
359 self
.editor
.setVisible(item
, True)
360 self
.item_store
[path
][0] = not self
.item_store
[path
][0]
362 def on_item_tree_cursor_changed(self
, treeview
):
363 selection
= treeview
.get_selection()
364 if selection
is None:
366 items
, iter = selection
.get_selected()
370 item
= items
[iter][3]
371 self
.tree
.get_object('edit_delete').set_sensitive(True)
372 self
.tree
.get_object('new_separator_button').set_sensitive(True)
373 self
.tree
.get_object('delete_button').set_sensitive(True)
375 can_edit
= not isinstance(item
, GMenu
.TreeSeparator
)
376 self
.tree
.get_object('edit_properties').set_sensitive(can_edit
)
377 self
.tree
.get_object('properties_button').set_sensitive(can_edit
)
379 index
= items
.get_path(iter).get_indices()[0]
380 can_go_up
= index
> 0
381 can_go_down
= index
< len(items
) - 1
382 self
.tree
.get_object('move_up_button').set_sensitive(can_go_up
)
383 self
.tree
.get_object('move_down_button').set_sensitive(can_go_down
)
385 def on_item_tree_row_activated(self
, treeview
, path
, column
):
386 self
.on_edit_properties_activate(None)
388 def on_item_tree_popup_menu(self
, item_tree
, event
=None):
389 model
, iter = item_tree
.get_selection().get_selected()
391 #don't show if it's not the right mouse button
392 if event
.button
!= 3:
394 button
= event
.button
395 event_time
= event
.time
396 info
= item_tree
.get_path_at_pos(int(event
.x
), int(event
.y
))
398 path
, col
, cellx
, celly
= info
399 item_tree
.grab_focus()
400 item_tree
.set_cursor(path
, col
, 0)
402 path
= model
.get_path(iter)
405 item_tree
.grab_focus()
406 item_tree
.set_cursor(path
, item_tree
.get_columns()[0], 0)
407 popup
= self
.tree
.get_object('edit_menu')
408 popup
.popup(None, None, None, None, button
, event_time
)
409 #without this shift-f10 won't work
412 def on_item_tree_key_press_event(self
, item_tree
, event
):
413 if event
.keyval
== Gdk
.KEY_Delete
:
414 self
.on_edit_delete_activate(item_tree
)
416 def on_move_up_button_clicked(self
, button
):
417 item_tree
= self
.tree
.get_object('item_tree')
418 items
, iter = item_tree
.get_selection().get_selected()
421 path
= items
.get_path(iter)
422 #at top, can't move up
423 if path
.get_indices()[0] == 0:
425 item
= items
[path
][3]
426 before
= items
[(path
.get_indices()[0] - 1,)][3]
427 self
.editor
.moveItem(item
.get_parent(), item
, before
=before
)
429 def on_move_down_button_clicked(self
, button
):
430 item_tree
= self
.tree
.get_object('item_tree')
431 items
, iter = item_tree
.get_selection().get_selected()
434 path
= items
.get_path(iter)
435 #at bottom, can't move down
436 if path
.get_indices()[0] == (len(items
) - 1):
438 item
= items
[path
][3]
439 after
= items
[path
][3]
440 self
.editor
.moveItem(item
.get_parent(), item
, after
=after
)
442 def on_restore_button_clicked(self
, button
):
443 self
.editor
.restoreToSystem()
445 def on_close_button_clicked(self
, button
):
448 def on_properties_button_clicked(self
, button
):
449 self
.on_edit_properties_activate(None)
450 def on_delete_button_clicked(self
, button
):
451 self
.on_edit_delete_activate(None)
457 if len(sys
.argv
) > 1:
458 basename
= sys
.argv
[1]
463 app
.setMenuBasename(basename
)