MainWindow: Fix whitespace
[alacarte.git] / Alacarte / MainWindow.py
blob4239d249811ee92e46776ff5be2c3c315ba4459a
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 import gi
21 gi.require_version('Gtk', '3.0')
22 from gi.repository import Gtk, GdkPixbuf, Gdk, GMenu
23 import sys
24 import cgi
25 import os
26 import gettext
27 import shutil
29 from Alacarte import config
30 gettext.bindtextdomain(config.GETTEXT_PACKAGE, config.localedir)
31 gettext.textdomain(config.GETTEXT_PACKAGE)
33 _ = gettext.gettext
34 from Alacarte.MenuEditor import MenuEditor
35 from Alacarte.ItemEditor import LauncherEditor, DirectoryEditor
36 from Alacarte import util
38 class MainWindow(object):
39 def __init__(self):
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)
45 self.setupMenuTree()
46 self.setupItemTree()
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')
55 self.editor = None
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)
63 self.menuChanged()
65 def run(self):
66 self.loadMenus()
67 self.tree.get_object('mainwindow').show_all()
68 Gtk.main()
70 def menuChanged(self, *a):
71 self.loadUpdates()
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()
77 update_items = False
78 update_type = None
79 item_id = None
80 if iter:
81 update_items = True
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()
92 update_menus = False
93 menu_id = None
94 if iter:
95 if menus[iter][2].get_desktop_file_path():
96 menu_id = os.path.split(menus[iter][2].get_desktop_file_path())[1]
97 else:
98 menu_id = menus[iter][2].get_menu_id()
99 update_menus = True
100 self.loadMenus()
101 #find current menu in new tree
102 if update_menus:
103 menu_tree.get_model().foreach(self.findMenu, menu_id)
104 menus, iter = menu_tree.get_selection().get_selected()
105 if iter:
106 self.on_menu_tree_cursor_changed(menu_tree)
107 #find current item in new list
108 if update_items:
109 i = 0
110 for item in item_tree.get_model():
111 found = False
112 if update_type != GMenu.TreeItemType.SEPARATOR:
113 if isinstance (item[3], GMenu.TreeEntry) and item[3].get_desktop_file_id() == item_id:
114 found = True
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:
117 found = True
118 if isinstance(item[3], GMenu.TreeSeparator):
119 if not isinstance(item_id, tuple):
120 #we may not skip the increment via "continue"
121 i += 1
122 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,):
126 found = True
127 elif (item_id[0] + 1,) == (i,):
128 found = True
129 elif (item_id[0],) == (i,):
130 found = True
131 if found:
132 item_tree.get_selection().select_path((i,))
133 self.on_item_tree_cursor_changed(item_tree)
134 break
135 i += 1
136 return False
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)
144 return True
145 return False
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)
150 return True
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)
191 else:
192 renderer.set_property('visible', True)
194 def loadMenus(self):
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):
222 name = '---'
223 else:
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):
231 self.quit()
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()
236 if not iter:
237 parent = menus[(0,)][2]
238 menu_tree.expand_to_path((0,))
239 menu_tree.get_selection().select_path((0,))
240 else:
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)
249 editor.run()
251 def on_directory_created(self, editor, response):
252 if 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()
258 if not iter:
259 parent = menus[(0,)][2]
260 menu_tree.expand_to_path((0,))
261 menu_tree.get_selection().select_path((0,))
262 else:
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)
271 editor.run()
273 def on_item_created(self, editor, response):
274 if 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()
280 if not iter:
281 return
282 else:
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()
292 if not iter:
293 return
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()
305 if not iter:
306 return
307 item = items[iter][3]
308 if not isinstance(item, GMenu.TreeEntry) and not isinstance(item, GMenu.TreeDirectory):
309 return
311 if isinstance(item, GMenu.TreeEntry):
312 file_path = os.path.join(util.getUserItemPath(), item.get_desktop_file_id())
313 file_type = 'Item'
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])
317 file_type = 'Menu'
318 Editor = DirectoryEditor
320 copied = False
321 if not os.path.isfile(file_path):
322 shutil.copy(item.get_desktop_file_path(), file_path)
323 copied = True
325 editor = Editor(self.main_window, file_path)
326 editor.connect('response', self.on_editor_response, file_path if copied else None)
327 editor.run()
329 def on_editor_response(self, editor, modified, file_path):
330 if not modified and file_path is not None:
331 os.remove(file_path)
333 def on_menu_tree_cursor_changed(self, treeview):
334 selection = treeview.get_selection()
335 if selection is None:
336 return
337 menus, iter = selection.get_selected()
338 if iter is None:
339 return
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):
355 return
356 if self.item_store[path][0]:
357 self.editor.setVisible(item, False)
358 else:
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:
365 return
366 items, iter = selection.get_selected()
367 if iter is None:
368 return
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()
390 if event:
391 #don't show if it's not the right mouse button
392 if event.button != 3:
393 return
394 button = event.button
395 event_time = event.time
396 info = item_tree.get_path_at_pos(int(event.x), int(event.y))
397 if info is not None:
398 path, col, cellx, celly = info
399 item_tree.grab_focus()
400 item_tree.set_cursor(path, col, 0)
401 else:
402 path = model.get_path(iter)
403 button = 0
404 event_time = 0
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
410 return True
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()
419 if not iter:
420 return
421 path = items.get_path(iter)
422 #at top, can't move up
423 if path.get_indices()[0] == 0:
424 return
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()
432 if not iter:
433 return
434 path = items.get_path(iter)
435 #at bottom, can't move down
436 if path.get_indices()[0] == (len(items) - 1):
437 return
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):
446 self.quit()
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)
453 def quit(self):
454 Gtk.main_quit()
456 def main():
457 if len(sys.argv) > 1:
458 basename = sys.argv[1]
459 else:
460 basename = None
462 app = MainWindow()
463 app.setMenuBasename(basename)
464 app.run()