2 @copyright: (C) 2008, Thomas Leonard
3 @see: U{http://roscidus.com}
5 import os
, sys
, fnmatch
6 from zeroinstall
.support
import tasks
# tmp
11 from gtk
import keysyms
16 gtk_theme
= gtk
.icon_theme_get_default()
17 icon_text_plain
= gtk_theme
.load_icon('text-x-generic', icon_size
, 0)
18 icon_dir
= gtk_theme
.load_icon('folder', icon_size
, 0)
20 RETURN_KEYS
= (keysyms
.Return
, keysyms
.KP_Enter
, keysyms
.ISO_Enter
)
22 class DirUpdated(tasks
.Blocker
):
25 class ShellController
:
26 def __init__(self
, file):
31 def build_contents(self
):
33 e
= self
.file.enumerate_children('standard::*', 0)
39 contents
[info
.get_name()] = info
40 self
.contents
= contents
43 self
.updated
.trigger()
44 self
.updated
= DirUpdated("Updates for " + self
.file.get_uri())
46 def item_activated(self
, name
):
47 item_info
= self
.contents
[name
]
48 child
= self
.file.get_child(name
)
50 if item_info
.get_file_type() == gio
.FILE_TYPE_DIRECTORY
:
54 self
.view
.run_in_terminal(['cat', child
.get_path()], self
.file.get_path())
57 parent
= self
.file.get_parent()
62 def gtk_tree_model(contents
):
63 tm
= gtk
.ListStore(str, gtk
.gdk
.Pixbuf
)
64 tm
.set_sort_column_id(0, gtk
.SORT_ASCENDING
)
65 for name
, info
in contents
.iteritems():
66 if not name
.startswith('.'):
68 if info
.get_file_type() == gio
.FILE_TYPE_DIRECTORY
:
71 icon
= icon_text_plain
72 tm
[new
] = [name
, icon
]
79 def __init__(self
, view
):
83 def entry_changed(self
, entry
):
84 self
.pattern
= entry
.get_text()
88 model
= iv
.get_model()
90 pattern_star
= self
.pattern
+ '*'
92 # If the user only entered lower-case letters do a case insensitive match
93 case_insensitive
= (self
.pattern
== self
.pattern
.lower())
99 def match(m
, path
, iter):
103 if fnmatch
.fnmatch(name
, self
.pattern
):
104 to_select
.append(path
)
105 elif fnmatch
.fnmatch(name
, pattern_star
):
106 prefix_matches
.append(path
)
107 if iv
.path_is_selected(path
):
108 already_selected
.append(path
)
113 cursor_path
= (iv
.get_cursor() or (None, None))[0]
115 if to_select
== [] and prefix_matches
:
116 # No matches, but put the cursor on the closest
117 if cursor_path
in prefix_matches
:
118 to_select
= [cursor_path
]
120 to_select
= [prefix_matches
[0]]
123 for path
in to_select
:
125 if cursor_path
not in to_select
:
126 iv
.set_cursor(to_select
[0])
129 def get_entry_text(self
):
132 def get_button_label(self
):
135 class CommandArgument(Argument
):
136 def get_button_label(self
):
140 def __init__(self
, hbox
):
145 def set_args(self
, args
):
147 self
.edit_arg
= self
.args
[-1]
151 for w
in self
.widgets
:
154 self
.active_entry
= None
157 if x
is self
.edit_arg
:
159 arg
.set_text(x
.get_entry_text())
160 arg
.connect('changed', x
.entry_changed
)
161 self
.active_entry
= arg
163 arg
= gtk
.Button(x
.get_button_label())
164 arg
.set_relief(gtk
.RELIEF_NONE
)
165 arg
.connect('clicked', lambda b
: self
.activate(x
))
167 self
.hbox
.pack_start(arg
, False, True, 0)
168 self
.widgets
.append(arg
)
170 def activate(self
, x
):
173 i
= self
.args
.index(x
)
174 self
.widgets
[i
].grab_focus()
176 def key_press_event(self
, kev
):
177 if not self
.active_entry
:
179 old_text
= self
.active_entry
.get_text()
180 self
.active_entry
.grab_focus() # Otherwise it selects the added text
181 self
.active_entry
.event(kev
)
182 return self
.active_entry
.get_text() != old_text
186 user_seen_terminal_contents
= False
188 def __init__(self
, shell
):
192 builder
= gtk
.Builder()
193 builder
.add_from_file(os
.path
.join(os
.path
.dirname(__file__
), "ui.xml"))
194 self
.window
= builder
.get_object('directory')
196 cd_parent
= builder
.get_object('cd-parent')
197 cd_parent
.connect('activate', lambda a
: self
.shell
.cd_parent())
199 # Must show window before adding icons, or we randomly get:
200 # The error was 'BadAlloc (insufficient resources for operation)'
203 self
.window_destroyed
= tasks
.Blocker('Window destroyed')
204 self
.window
.connect('destroy', lambda w
: self
.window_destroyed
.trigger())
206 self
.window
.connect('key-press-event', self
.key_press_event
)
208 self
.iv
= builder
.get_object('iconview')
209 self
.iv
.set_text_column(0)
210 self
.iv
.set_pixbuf_column(1)
211 self
.iv
.set_selection_mode(gtk
.SELECTION_MULTIPLE
)
213 self
.iv
.connect('item-activated', self
.item_activated
)
215 command_area
= builder
.get_object('command')
216 self
.command_argv
= ArgvView(command_area
)
219 self
.notebook
= builder
.get_object('notebook')
221 self
.window
.show_all()
223 def show_terminal(self
):
224 if not self
.terminal
:
225 def terminal_contents_changed(vte
):
226 self
.user_seen_terminal_contents
= False
228 def terminal_child_exited():
229 if self
.user_seen_terminal_contents
:
230 self
.notebook
.set_current_page(FILER_PAGE
)
232 self
.terminal
.feed('\r\nProcess complete. Press Return to return to filer view.\r\n')
233 self
.waiting_for_return
= True
236 self
.terminal
= vte
.Terminal()
237 self
.terminal
.connect('contents-changed', terminal_contents_changed
)
238 self
.terminal
.connect('child-exited', lambda vte
: gobject
.timeout_add(100, terminal_child_exited
))
241 self
.notebook
.add(self
.terminal
)
242 self
.notebook
.set_current_page(TERMINAL_PAGE
)
243 self
.waiting_for_return
= False
246 self
.command_argv
.set_args([CommandArgument(self
), Argument(self
)])
248 self
.iv
.unselect_all()
250 def key_press_event(self
, window
, kev
):
251 if self
.terminal
and self
.terminal
.flags() & gtk
.HAS_FOCUS
:
252 if kev
.keyval
in RETURN_KEYS
and self
.waiting_for_return
:
253 self
.notebook
.set_current_page(FILER_PAGE
)
255 self
.user_seen_terminal_contents
= True
258 if kev
.keyval
== keysyms
.Escape
:
261 elif kev
.keyval
in RETURN_KEYS
:
262 selected
= self
.iv
.get_selected_items()
263 if len(selected
) == 1:
264 self
.iv
.item_activated(selected
[0])
267 # Are we ready for special characters?
268 if self
.command_argv
.active_entry
and self
.command_argv
.active_entry
.flags() & gtk
.HAS_FOCUS
:
269 accept_special
= True # TODO: check cursor is at end
271 accept_special
= True
274 if kev
.keyval
== keysyms
.comma
:
275 self
.command_argv
.activate(self
.command_argv
.args
[0])
278 if self
.iv
.flags() & gtk
.HAS_FOCUS
:
279 if self
.iv
.event(kev
):
280 # Handled by IconView (e.g. cursor motion)
282 elif kev
.keyval
== keysyms
.BackSpace
:
283 self
.shell
.cd_parent()
285 if not self
.command_argv
.key_press_event(kev
):
286 self
.iv
.grab_focus() # Restore focus to IconView
289 def run_in_terminal(self
, argv
, cwd
):
291 self
.user_seen_terminal_contents
= True
292 self
.terminal
.fork_command(argv
[0], argv
, None, cwd
, False, False, False)
298 tree_model
= gtk_tree_model(self
.shell
.contents
)
299 self
.iv
.set_model(tree_model
)
300 if self
.shell
.contents
:
301 self
.iv
.set_cursor((0,))
302 self
.window
.set_title(self
.shell
.file.get_uri())
303 blockers
= [self
.shell
.updated
, self
.window_destroyed
]
305 tasks
.check(blockers
)
306 if self
.window_destroyed
.happened
:
309 def item_activated(self
, iv
, path
):
313 name
= tm
[tm
.get_iter(path
)][0]
314 self
.shell
.item_activated(name
)