Merge new deletion support
[pysize.git] / pysize / ui / gtk / pysize_window.py
blobb6e89593521eafcb862128127f2a79a685160298
1 # This program is free software; you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation; either version 2 of the License, or
4 # (at your option) any later version.
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU Library General Public License for more details.
11 # You should have received a copy of the GNU General Public License
12 # along with this program; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # See the COPYING file for license information.
17 # Copyright (c) 2006 Guillaume Chazarain <guichaz@yahoo.fr>
19 import os
20 import pygtk
21 pygtk.require('2.0')
22 import gtk
23 assert gtk.pygtk_version >= (2, 8)
24 import gobject
25 import pango
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.utils import human_unit, short_string, sanitize_string
33 from pysize.core import compute_size
34 from pysize.core.compute_size import size_observable
35 from pysize.core import history
36 from pysize.core.deletion import get_deleted, restore
38 def hover_changed(main_widget, hover_node, status_bar):
39 status_bar.pop(0)
40 node = hover_node or main_widget.tree.root
41 label = human_unit(node.size) + ' | ' + node.get_fullname()
42 status_bar.push(0, label)
44 def size_observer(progress_bar):
45 now = time.time()
46 if now - size_observer.last_pulse > 0.04:
47 size_observer.last_pulse = now
48 gobject.idle_add(lambda: progress_bar.pulse())
49 size_observer.last_pulse = time.time()
51 def hide_pbar(progress_bar, visible):
52 if visible:
53 progress_bar.show()
54 else:
55 progress_bar.hide()
57 def update_action(zoom_fit_action, main_widget):
58 # Enable if the zoom is not already 'auto' and there is a tree
59 enable = main_widget.options.min_size != 'auto' and \
60 not not main_widget.tree.root.get_fullpaths()
61 zoom_fit_action.set_sensitive(enable)
63 def update_title(window, main_widget):
64 root = main_widget.tree.root
65 window.set_title('Pysize - %s | %s' %
66 (human_unit(root.size), sanitize_string(root.get_name())))
68 def update_back_action(back_action, next_insertion_index, history):
69 back_action.set_sensitive(next_insertion_index > 1)
71 def update_forward_action(forward_action, next_insertion_index, history):
72 forward_action.set_sensitive(next_insertion_index < len(history))
74 def update_hist_menu(main_widget, hist_menu, next_insertion_index, hist_list):
75 class activator(object):
76 def __init__(self, index):
77 self.index = index
79 def activate(self, unused_menu_item):
80 main_widget.set_paths(history.go_to_history(self.index))
82 for nr, item in enumerate(hist_menu):
83 if nr > 2: # Keep 'Back' and 'Forward'
84 hist_menu.remove(item)
85 item = gtk.SeparatorMenuItem()
86 item.show()
87 hist_menu.add(item)
88 for nr, (paths, name) in enumerate(hist_list):
89 # We want underscores, not mnemonics
90 name = short_string(sanitize_string(name), 100).replace('_', '__')
91 item = gtk.CheckMenuItem(name)
92 if nr + 1 == next_insertion_index:
93 item.set_active(True)
94 item.connect('activate', activator(nr).activate)
95 item.set_draw_as_radio(True)
96 item.show()
97 hist_menu.add(item)
99 def show_deleted_files(dialog, treeview):
100 model = treeview.get_model()
101 model.clear()
102 for path in get_deleted():
103 size = human_unit(compute_size.slow(path, account_deletion=False))
104 model.append((size, path))
105 dialog.run()
106 dialog.hide()
108 def iter_selection(treeview):
109 model = treeview.get_model()
110 selection = treeview.get_selection().get_selected_rows()[1]
111 if selection:
112 selection.reverse()
113 for treepath in selection:
114 treeiter = model.get_iter(treepath)
115 path = model.get_value(treeiter, 1)
116 yield treeiter, path
118 def restore_deleted_files(treeview, main_widget):
119 if treeview.get_selection().count_selected_rows() > 0:
120 model = treeview.get_model()
121 for treeiter, path in iter_selection(treeview):
122 restore(path)
123 model.remove(treeiter)
124 main_widget.schedule_new_tree()
126 def delete_deleted_files(treeview):
127 if treeview.get_selection().count_selected_rows() <= 0:
128 return;
129 dialog = gtk.MessageDialog(parent=None, flags=gtk.DIALOG_MODAL,
130 type=gtk.MESSAGE_WARNING,
131 buttons=gtk.BUTTONS_YES_NO,
132 message_format='The selected will be permanently'
133 'deleted, do you really want to delete them?')
134 response = dialog.run()
135 dialog.destroy()
136 if response == gtk.RESPONSE_YES:
137 model = treeview.get_model()
138 for treeiter, path in iter_selection(treeview):
139 restore(path)
140 if os.path.isdir(path):
141 shutil.rmtree(path)
142 else:
143 os.remove(path)
144 model.remove(treeiter)
146 class PysizeWindow(object):
147 def __init__(self, options, args):
148 create_widget = lambda **unused_kwargs: PysizeWidget(options, args)
149 glade_filename = os.path.join(os.path.dirname(__file__),
150 'main_window.glade')
151 builder = ObjectBuilder(glade_filename,
152 custom = {'pysize_widget': create_widget})
154 main_widget = builder.get_widget('main_widget')
155 status_bar = builder.get_widget('status_bar')
156 main_widget.connect('hover_changed', hover_changed, status_bar)
158 progress_bar = builder.get_widget('progress_bar')
159 size_observable.add_observer(lambda: size_observer(progress_bar))
160 pbar_hider = lambda w, building: hide_pbar(progress_bar, building)
161 main_widget.connect('building-tree-state-changed', pbar_hider)
163 max_depth = builder.get_widget('max_depth')
164 spin_init = lambda w: max_depth.set_value(main_widget.options.max_depth)
165 main_widget.connect('realize', spin_init)
167 ui_manager = builder.get_widget('uimanager')
168 zoom_fit_action = ui_manager.get_action('ui/toolbar/ZoomFit')
169 action_updater = lambda w, building: update_action(zoom_fit_action, w)
170 main_widget.connect('building-tree-state-changed', action_updater)
172 # Disable actions when the tree is empty: pysize launched with no arg
173 class disabler(object):
174 def __init__(self, action):
175 self.action = action
176 def disable(self, main_widget, building):
177 sensitive = not not main_widget.tree.root.get_fullpaths()
178 self.action.set_sensitive(sensitive)
179 for name in 'ParentDirectory', 'Refresh', 'ZoomIn', 'ZoomOut':
180 action = ui_manager.get_action('ui/toolbar/' + name)
181 action_disabler = disabler(action).disable
182 main_widget.connect('building-tree-state-changed', action_disabler)
184 back_action = ui_manager.get_action('ui/toolbar/Back')
185 back_action.set_sensitive(False)
186 update_back = lambda n, h: update_back_action(back_action, n, h)
187 history.history_observable.add_observer(update_back)
189 forward_action = ui_manager.get_action('ui/toolbar/Forward')
190 forward_action.set_sensitive(False)
191 update_forw = lambda n, h: update_forward_action(forward_action, n, h)
192 history.history_observable.add_observer(update_forw)
194 hist_menu_action = ui_manager.get_action('ui/menubar/HistoryMenu')
195 hist_menu = hist_menu_action.get_proxies()[0].get_submenu()
196 upd_hist = lambda n, h: update_hist_menu(main_widget, hist_menu, n, h)
197 history.history_observable.add_observer(upd_hist)
199 toolbar = builder.get_widget('toolbar')
200 toolbar_remaining = builder.get_widget('toolbar_remaining')
201 toolbar_remaining.unparent()
202 tool_item = gtk.ToolItem()
203 tool_item.add(toolbar_remaining)
204 toolbar.insert(tool_item, -1)
206 deleted_files_dialog = builder.get_widget('deleted_files_dialog')
207 deleted_files_treeview = builder.get_widget('deleted_files_treeview')
208 deleted_files_treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
210 logo_filename = os.path.join(os.path.dirname(__file__), 'logo.svg')
211 gtk.window_set_default_icon_from_file(logo_filename)
213 callbacks = {
214 'quit_action': lambda w: gtk.main_quit(),
215 'refresh_tree_action': lambda w: main_widget.refresh_tree(),
216 'zoom_fit_action': lambda w: main_widget.zoom_fit(),
217 'zoom_in_action': lambda w: main_widget.zoom_in(),
218 'zoom_out_action': lambda w: main_widget.zoom_out(),
219 'parent_directory_action': lambda w: main_widget.parent_directory(),
220 'open_action': lambda w: main_widget.open(),
221 'back_action': lambda w: main_widget.set_paths(history.back()),
222 'forw_action': lambda w: main_widget.set_paths(history.forward()),
223 'deleted_files_action': lambda w:
224 show_deleted_files(deleted_files_dialog, deleted_files_treeview),
225 'close_deleted_files': lambda b: deleted_files_dialog.hide(),
226 'restore_deleted_files': lambda b:
227 restore_deleted_files(deleted_files_treeview, main_widget),
228 'delete_deleted_files': lambda b:
229 delete_deleted_files(deleted_files_treeview),
230 'max_depth_changed': main_widget.max_depth_changed
232 builder.signal_autoconnect(callbacks)
234 window = builder.get_widget('main_window')
235 title_updater = lambda w, tree: update_title(window, main_widget)
236 main_widget.connect('building-tree-state-changed', title_updater)
237 window.show_all()