pylint fixes
[pysize.git] / pysize / ui / gtk / pysize_window.py
bloba7dc28e2b35f874b0cf818c0f92ff23919cc21d5
1 # -*- coding: UTF-8 -*-
2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation; either version 2 of the License, or
5 # (at your option) any later version.
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU Library General Public License for more details.
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
16 # See the COPYING file for license information.
18 # Copyright (c) 2006, 2007, 2008 Guillaume Chazarain <guichaz@gmail.com>
20 import os
21 import pygtk
22 pygtk.require('2.0')
23 import gtk
24 assert gtk.pygtk_version >= (2, 8)
25 import gobject
26 import time
27 import shutil
29 from pysize.ui.gtk.gazpacho_loader.loader import ObjectBuilder
31 from pysize.ui.gtk.pysize_widget import PysizeWidget
32 from pysize.ui.gtk.help import show_help
33 from pysize.ui.utils import human_unit, sanitize_string
34 from pysize.core import compute_size
35 from pysize.core.compute_size import size_observable
36 from pysize.core import history
37 from pysize.core.deletion import get_deleted, restore
38 from pysize.version import VERSION
40 def hover_changed(main_widget, hover_node, status_bar):
41 status_bar.pop(0)
42 nodes = main_widget.get_selected_nodes()
43 if not nodes:
44 if hover_node:
45 nodes = [hover_node]
46 else:
47 nodes = [main_widget.tree.root]
48 size = sum([node.size for node in nodes])
49 text = ' + '.join([node.get_fullname() for node in nodes])
50 label = human_unit(size) + ' | ' + sanitize_string(text, max_length=1000)
51 status_bar.push(0, label)
53 def size_observer(progress_bar):
54 now = time.time()
55 if now - size_observer.last_pulse > 0.04:
56 size_observer.last_pulse = now
57 gobject.idle_add(lambda: progress_bar.pulse())
58 size_observer.last_pulse = time.time()
60 def hide_pbar(progress_bar, visible):
61 if visible:
62 progress_bar.show()
63 else:
64 progress_bar.hide()
66 def update_action(zoom_fit_action, main_widget):
67 # Enable if the zoom is not already 'auto' and there is a tree
68 enable = main_widget.min_size != 'auto' and \
69 not not main_widget.tree.root.get_fullpaths()
70 zoom_fit_action.set_sensitive(enable)
72 def update_title(window, main_widget):
73 root = main_widget.tree.root
74 window.set_title('Pysize - %s | %s' %
75 (human_unit(root.size), sanitize_string(root.get_name())))
77 def update_back_action(back_action, next_insertion_index):
78 back_action.set_sensitive(next_insertion_index > 1)
80 def update_forward_action(forward_action, next_insertion_index, history):
81 forward_action.set_sensitive(next_insertion_index < len(history))
83 def update_hist_menu(main_widget, hist_menu, next_insertion_index, hist_list):
84 class activator(object):
85 def __init__(self, index):
86 self.index = index
88 def activate(self, unused_menu_item):
89 main_widget.set_paths(history.go_to_history(self.index))
91 for nr, item in enumerate(hist_menu):
92 if nr > 2: # Keep 'Back' and 'Forward'
93 hist_menu.remove(item)
94 item = gtk.SeparatorMenuItem()
95 item.show()
96 hist_menu.add(item)
97 for nr, (paths, name) in enumerate(hist_list):
98 # We want underscores, not mnemonics
99 name = sanitize_string(name, max_length=100).replace('_', '__')
100 item = gtk.CheckMenuItem(name)
101 if nr + 1 == next_insertion_index:
102 item.set_active(True)
103 item.connect('activate', activator(nr).activate)
104 item.set_draw_as_radio(True)
105 item.show()
106 hist_menu.add(item)
108 def show_deleted_files(dialog, treeview):
109 model = treeview.get_model()
110 model.clear()
111 for path in get_deleted():
112 size = human_unit(compute_size.slow(path, account_deletion=False))
113 model.append((size, path))
114 dialog.run()
115 dialog.hide()
117 def iter_selection(treeview):
118 model = treeview.get_model()
119 selection = treeview.get_selection().get_selected_rows()[1]
120 if selection:
121 selection.reverse()
122 for treepath in selection:
123 treeiter = model.get_iter(treepath)
124 path = model.get_value(treeiter, 1)
125 yield treeiter, path
127 def deleted_files_update_selection(treeview, restore_button, delete_button,
128 deleted_files_size):
129 selected_paths = [s[1] for s in iter_selection(treeview)]
130 sensitive = len(selected_paths) > 0
131 restore_button.set_sensitive(sensitive)
132 delete_button.set_sensitive(sensitive)
133 selected_paths = selected_paths or get_deleted()
134 total = compute_size.slow_sum(selected_paths, account_deletion=False)
135 deleted_files_size.set_text(human_unit(total))
137 def restore_deleted_files(treeview, main_widget):
138 if treeview.get_selection().count_selected_rows() > 0:
139 model = treeview.get_model()
140 for treeiter, path in iter_selection(treeview):
141 restore(path)
142 model.remove(treeiter)
143 main_widget.schedule_new_tree()
145 def delete_deleted_files(treeview, main_widget):
146 if treeview.get_selection().count_selected_rows() <= 0:
147 return;
148 dialog = gtk.MessageDialog(parent=None, flags=gtk.DIALOG_MODAL,
149 type=gtk.MESSAGE_WARNING,
150 buttons=gtk.BUTTONS_YES_NO,
151 message_format='The selected will be permanently'
152 ' deleted, do you really want to delete them?')
153 response = dialog.run()
154 dialog.destroy()
155 if response == gtk.RESPONSE_YES:
156 model = treeview.get_model()
157 for treeiter, path in iter_selection(treeview):
158 restore(path)
159 try:
160 if os.path.isdir(path):
161 def rmtree_error(function, path, excinfo):
162 print excinfo[1]
163 shutil.rmtree(path, onerror=rmtree_error)
164 else:
165 os.remove(path)
166 except OSError, e:
167 print e
168 model.remove(treeiter)
169 main_widget.schedule_new_tree()
171 def show_about():
172 about = gtk.AboutDialog()
173 about.set_name('Pysize')
174 about.set_version(VERSION)
175 about.set_copyright('Copyright © 2006-2007 Guillaume Chazarain')
176 about.set_website('http://guichaz.free.fr/pysize')
177 logo_filename = os.path.join(os.path.dirname(__file__), 'logo.svg')
178 try:
179 logo = gtk.gdk.pixbuf_new_from_file(logo_filename)
180 logo = logo.scale_simple(200, 200, gtk.gdk.INTERP_HYPER)
181 about.set_logo(logo)
182 except Exception, e:
183 # Not showing the logo is harmless
184 print e
185 about.run()
186 about.destroy()
188 def quit_gui():
189 if get_deleted():
190 dialog = gtk.MessageDialog(parent=None, flags=gtk.DIALOG_MODAL,
191 type=gtk.MESSAGE_WARNING,
192 buttons=gtk.BUTTONS_YES_NO,
193 message_format='Some files have been '
194 'scheduled for deletion, but have not been '
195 'deleted. Do you still want to quit?')
196 response = dialog.run()
197 dialog.destroy()
198 if response != gtk.RESPONSE_YES:
199 return True
201 gtk.main_quit()
202 return True
204 class PysizeWindow(object):
205 def __init__(self, options, args):
206 create_widget = lambda **unused_kwargs: PysizeWidget(options, args)
207 glade_filename = os.path.join(os.path.dirname(__file__),
208 'main_window.glade')
209 builder = ObjectBuilder(glade_filename,
210 custom = {'pysize_widget': create_widget})
212 main_widget = builder.get_widget('main_widget')
213 status_bar = builder.get_widget('status_bar')
214 main_widget.connect('hover_changed', hover_changed, status_bar)
216 progress_bar = builder.get_widget('progress_bar')
217 size_observable.add_observer(lambda: size_observer(progress_bar))
218 pbar_hider = lambda w, building: hide_pbar(progress_bar, building)
219 main_widget.connect('building-tree-state-changed', pbar_hider)
221 max_depth = builder.get_widget('max_depth')
222 spin_init = lambda w: max_depth.set_value(main_widget.options.max_depth)
223 main_widget.connect('realize', spin_init)
225 ui_manager = builder.get_widget('uimanager')
226 zoom_fit_action = ui_manager.get_action('ui/toolbar/ZoomFit')
227 action_updater = lambda w, building: update_action(zoom_fit_action, w)
228 main_widget.connect('building-tree-state-changed', action_updater)
230 # Disable actions when the tree is empty: pysize launched with no arg
231 class disabler(object):
232 def __init__(self, action):
233 self.action = action
234 def disable(self, main_widget, building):
235 sensitive = not not main_widget.tree.root.get_fullpaths()
236 self.action.set_sensitive(sensitive)
237 for path in 'ui/toolbar/ParentDirectory', \
238 'ui/menubar/ActionsMenu/QuickRefresh', \
239 'ui/menubar/ActionsMenu/CompleteReload', \
240 'ui/toolbar/ZoomIn', 'ui/toolbar/ZoomOut':
241 action = ui_manager.get_action(path)
242 action_disabler = disabler(action).disable
243 main_widget.connect('building-tree-state-changed', action_disabler)
245 back_action = ui_manager.get_action('ui/toolbar/Back')
246 back_action.set_sensitive(False)
247 update_back = lambda n, h: update_back_action(back_action, n)
248 history.history_observable.add_observer(update_back)
250 forward_action = ui_manager.get_action('ui/toolbar/Forward')
251 forward_action.set_sensitive(False)
252 update_forw = lambda n, h: update_forward_action(forward_action, n, h)
253 history.history_observable.add_observer(update_forw)
255 hist_menu_action = ui_manager.get_action('ui/menubar/HistoryMenu')
256 hist_menu = hist_menu_action.get_proxies()[0].get_submenu()
257 upd_hist = lambda n, h: update_hist_menu(main_widget, hist_menu, n, h)
258 history.history_observable.add_observer(upd_hist)
260 toolbar = builder.get_widget('toolbar')
261 toolbar_remaining = builder.get_widget('toolbar_remaining')
262 toolbar_remaining.unparent()
263 tool_item = gtk.ToolItem()
264 tool_item.add(toolbar_remaining)
265 toolbar.insert(tool_item, -1)
267 self.add_refresh_buttons(toolbar, ui_manager)
269 deleted_files_dialog = builder.get_widget('deleted_files_dialog')
270 deleted_files_treeview = builder.get_widget('deleted_files_treeview')
271 deleted_files_restore = builder.get_widget('deleted_files_restore')
272 deleted_files_delete = builder.get_widget('deleted_files_delete')
273 deleted_files_size = builder.get_widget('deleted_files_size')
274 deleted_files_cb = lambda: deleted_files_update_selection(
275 deleted_files_treeview, deleted_files_restore, deleted_files_delete,
276 deleted_files_size)
277 deleted_files_event = lambda *args: gobject.idle_add(deleted_files_cb)
278 deleted_files_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
280 logo_filename = os.path.join(os.path.dirname(__file__), 'logo.svg')
281 try:
282 gtk.window_set_default_icon_from_file(logo_filename)
283 except Exception, e:
284 # Not showing the logo is harmless
285 print e
287 callbacks = {
288 'quit_action': lambda *args: quit_gui(),
289 'quick_refresh_action': lambda w: main_widget.schedule_new_tree(),
290 'complete_reload_action': lambda w: main_widget.complete_reload(),
291 'zoom_fit_action': lambda w: main_widget.zoom_fit(),
292 'zoom_in_action': lambda w: main_widget.zoom_in(),
293 'zoom_out_action': lambda w: main_widget.zoom_out(),
294 'parent_directory_action': lambda w: main_widget.parent_directory(),
295 'open_action': lambda w: main_widget.open(),
296 'back_action': lambda w: main_widget.set_paths(history.back()),
297 'forw_action': lambda w: main_widget.set_paths(history.forward()),
298 'deleted_files_action': lambda w:
299 show_deleted_files(deleted_files_dialog, deleted_files_treeview),
300 'close_deleted_files': lambda b: deleted_files_dialog.hide(),
301 'restore_deleted_files': lambda b:
302 restore_deleted_files(deleted_files_treeview, main_widget),
303 'delete_deleted_files': lambda b:
304 delete_deleted_files(deleted_files_treeview, main_widget),
305 'max_depth_changed': main_widget.max_depth_changed,
306 'about_action': lambda a: show_about(),
307 'help_action': lambda a: show_help(),
308 'deleted_files_change_selection': deleted_files_event
310 builder.signal_autoconnect(callbacks)
312 window = builder.get_widget('main_window')
313 title_updater = lambda w, tree: update_title(window, main_widget)
314 main_widget.connect('building-tree-state-changed', title_updater)
315 window.show_all()
317 def add_refresh_buttons(self, toolbar, ui_manager):
318 quick = gtk.MenuToolButton(stock_id='')
319 tooltips = gtk.Tooltips()
320 quick_act = ui_manager.get_action('ui/menubar/ActionsMenu/QuickRefresh')
321 quick_act.connect_proxy(quick)
322 quick.set_tooltip(tooltips, quick_act.get_property('tooltip'))
323 menu = gtk.Menu()
324 quick.set_menu(menu)
325 complete = gtk.MenuItem()
326 cmp_act = ui_manager.get_action('ui/menubar/ActionsMenu/CompleteReload')
327 cmp_act.connect_proxy(complete)
328 tooltips.set_tip(complete, cmp_act.get_property('tooltip'))
329 menu.add(complete)
330 toolbar.insert(quick, 1)
331 # The gtk.Tooltips object need to be kept referenced, otherwise it will
332 # be garbage collected and will destroy its associated tooltips.
333 PysizeWindow.__tooltips__ = tooltips