2 @copyright: (C) 2008, Thomas Leonard
3 @see: U{http://roscidus.com}
5 import os
, sys
, fnmatch
6 from zeroinstall
.support
import tasks
# tmp
12 from gtk
import keysyms
20 DirModel
= directory
.DirModel
22 RETURN_KEYS
= (keysyms
.Return
, keysyms
.KP_Enter
, keysyms
.ISO_Enter
)
24 def get_default_command(args
):
26 raise Warning("No defaults for multiple arguments!")
29 if arg
.type == 'newfile':
37 WRAP_WIDTH
= 20 # Wrap width in chars (approx)
41 user_seen_terminal_contents
= False
42 warning_timeout
= None
46 def __init__(self
, cwd_file
):
47 builder
= gtk
.Builder()
48 builder
.add_from_file(os
.path
.join(os
.path
.dirname(__file__
), "ui.xml"))
49 self
.window
= builder
.get_object('directory')
50 self
.notebook
= builder
.get_object('notebook')
52 cd_parent
= builder
.get_object('cd-parent')
53 cd_parent
.connect('activate', lambda a
: self
.cd_parent())
55 cd_home
= builder
.get_object('cd-home')
56 cd_home
.connect('activate', lambda a
: self
.cd_home())
58 trash
= builder
.get_object('trash')
59 trash
.connect('activate', lambda a
: self
.trash())
61 # Must show window before adding icons, or we randomly get:
62 # The error was 'BadAlloc (insufficient resources for operation)'
65 self
.window_destroyed
= tasks
.Blocker('Window destroyed')
66 self
.window
.connect('destroy', lambda w
: self
.window_destroyed
.trigger())
68 self
.window
.connect('key-press-event', self
.key_press_event
)
70 self
.iv
= builder
.get_object('iconview')
71 self
.iv
.set_text_column(0)
72 self
.iv
.set_pixbuf_column(1)
73 self
.iv
.set_selection_mode(gtk
.SELECTION_MULTIPLE
)
75 text
= self
.iv
.get_cells()[0]
76 self
.iv
.set_attributes(text
, text
= DirModel
.NAME
, foreground
= DirModel
.COLOUR
)
77 pango_context
= self
.iv
.get_pango_context()
78 font_metrics
= pango_context
.get_metrics(self
.iv
.style
.font_desc
, pango_context
.get_language())
79 text
.set_property('wrap-width', WRAP_WIDTH
* font_metrics
.get_approximate_char_width() / pango
.SCALE
)
81 ui
= builder
.get_object('uimanager')
83 accelgroup
= ui
.get_accel_group()
84 self
.window
.add_accel_group(accelgroup
)
86 popup
= ui
.get_widget('ui/main-popup')
88 self
.iv
.connect('item-activated', self
.item_activated
)
89 def iv_button_press(widget
, bev
):
90 if bev
.type == gtk
.gdk
.BUTTON_PRESS
and bev
.button
== 3:
91 selected
= self
.iv
.get_selected_items()
92 pointer_path
= self
.iv
.get_path_at_pos(int(bev
.x
), int(bev
.y
))
93 if pointer_path
and pointer_path
not in selected
:
95 self
.iv
.unselect_all()
96 self
.iv
.select_path(pointer_path
)
97 self
.iv
.set_cursor(pointer_path
)
99 popup
.popup(None, None, None, bev
.button
, bev
.time
)
100 self
.iv
.connect('button-press-event', iv_button_press
)
102 command_area
= builder
.get_object('command')
103 self
.command_argv
= line
.ArgvView(command_area
)
105 self
.status_msg
= builder
.get_object('status_msg')
107 self
.set_cwd(cwd_file
)
110 self
.window
.show_all()
112 def iter_contents(self
):
113 m
= self
.iv
.get_model()
114 i
= m
.get_iter_root()
119 def warning(self
, msg
):
121 self
.status_msg
.set_text('')
123 if self
.warning_timeout
is not None:
124 gobject
.source_remove(self
.warning_timeout
)
125 self
.status_msg
.set_text(msg
)
126 self
.warning_timeout
= gobject
.timeout_add(2000, hide_warning
)
128 def show_terminal(self
):
129 # Actually, don't show it until we get some output...
130 if not self
.terminal
:
131 def terminal_contents_changed(vte
):
132 if self
.notebook
.get_current_page() == FILER_PAGE
:
133 self
.notebook
.set_current_page(TERMINAL_PAGE
)
134 self
.user_seen_terminal_contents
= False
136 def terminal_child_exited():
137 if self
.user_seen_terminal_contents
:
138 self
.notebook
.set_current_page(FILER_PAGE
)
140 self
.terminal
.feed('\r\nProcess complete. Press Return to return to filer view.\r\n')
141 self
.waiting_for_return
= True
144 self
.terminal
= vte
.Terminal()
145 self
.terminal
.connect('contents-changed', terminal_contents_changed
)
146 self
.terminal
.connect('child-exited', lambda vte
: gobject
.timeout_add(100, terminal_child_exited
))
148 # Should be configurable.
150 # cp /usr/share/fonts/X11/misc/9x15B-ISO8859-1.pcf.gz ~/.fonts/
151 self
.terminal
.set_font(pango
.FontDescription("Fixed 12"))
155 self
.notebook
.add(self
.terminal
)
156 self
.waiting_for_return
= False
160 self
.command_argv
.set_args([line
.CommandArgument(self
), line
.Argument(self
)])
161 if self
.notebook
.get_current_page() == FILER_PAGE
:
163 self
.iv
.unselect_all()
165 def key_press_event(self
, window
, kev
):
166 #for x in dir(keysyms):
167 # if getattr(keysyms, x) == kev.keyval:
170 if self
.terminal
and self
.terminal
.flags() & gtk
.HAS_FOCUS
:
171 if kev
.keyval
in RETURN_KEYS
and self
.waiting_for_return
:
172 self
.notebook
.set_current_page(FILER_PAGE
)
174 self
.user_seen_terminal_contents
= True
177 if kev
.keyval
== keysyms
.space
:
178 if self
.command_argv
.space():
181 if kev
.keyval
== keysyms
.Tab
:
182 if self
.command_argv
.tab():
185 if kev
.keyval
== keysyms
.Up
:
186 if self
.command_argv
.updown(-1):
189 if kev
.keyval
== keysyms
.Down
:
190 if self
.command_argv
.updown(1):
193 if kev
.keyval
== keysyms
.Escape
:
196 elif kev
.keyval
in RETURN_KEYS
:
197 self
.execute_command()
200 # Are we ready for special characters?
201 if self
.command_argv
.active_entry
and self
.command_argv
.active_entry
.flags() & gtk
.HAS_FOCUS
:
202 accept_special
= True # TODO: check cursor is at end
204 accept_special
= True
207 if kev
.keyval
== keysyms
.comma
:
208 self
.command_argv
.activate(self
.command_argv
.args
[0])
210 elif kev
.keyval
== keysyms
.semicolon
and len(self
.command_argv
.args
) == 2:
211 self
.command_argv
.set_args([line
.CommandArgument(self
)])
212 self
.command_argv
.widgets
[0].grab_focus()
215 if self
.iv
.flags() & gtk
.HAS_FOCUS
:
216 if self
.iv
.event(kev
):
217 # Handled by IconView (e.g. cursor motion)
219 elif kev
.keyval
== keysyms
.BackSpace
:
222 if not self
.command_argv
.key_press_event(kev
):
223 self
.iv
.grab_focus() # Restore focus to IconView
227 def run_in_terminal(self
, argv
):
228 cmd
= support
.find_in_path(argv
[0])
230 raise Warning("Command '%s' not found in $PATH" % argv
[0])
231 if not os
.path
.exists(cmd
):
232 raise Warning("Command '%s' does not exist!" % argv
[0])
235 self
.user_seen_terminal_contents
= True
236 self
.terminal
.fork_command(cmd
, argv
, None, self
.cwd
.file.get_path(), False, False, False)
238 def execute_command(self
, override_command
= None):
240 self
.command_argv
.finish_edit()
241 args
= self
.command_argv
.args
243 override
= line
.CommandArgument(self
)
244 override
.command
= override_command
245 args
= [override
] + args
[1:]
246 self
.run_command(args
)
248 self
.warning(str(ex
))
253 """Make the IconView show the cwd."""
254 if self
.view_dir
!= self
.cwd
:
255 self
.set_view_dir(self
.cwd
.file)
257 def set_view_dir(self
, dir_file
):
259 self
.view_dir
.del_ref(self
)
261 self
.view_dir
= directory
.get_dir_model(dir_file
)
262 self
.view_dir
.add_ref(self
)
264 # This segfaults. See GTK bug #523724.
265 #tree_model = gtk.TreeModelSort(self.view_dir.model)
266 #tree_model.set_sort_column_id(DirModel.SORT, gtk.SORT_ASCENDING)
267 tree_model
= self
.view_dir
.model
269 self
.iv
.set_model(tree_model
)
270 if tree_model
.get_iter_root():
271 self
.iv
.set_cursor((0,))
273 if self
.view_dir
.error
:
274 self
.warning(str(self
.view_dir
.error
))
276 def set_cwd(self
, cwd_file
):
278 self
.cwd
.del_ref(self
)
280 self
.cwd
= directory
.get_dir_model(cwd_file
)
281 self
.cwd
.add_ref(self
)
283 self
.window
.set_title(self
.cwd
.file.get_uri())
290 blockers
= [self
.window_destroyed
]
292 tasks
.check(blockers
)
293 if self
.window_destroyed
.happened
:
296 def get_iter(self
, name
):
297 for i
, row
in self
.iter_contents():
298 if row
[DirModel
.NAME
] == name
:
300 raise Exception("File '%s' not found!" % name
)
302 def get_cursor_path(self
):
303 return (self
.iv
.get_cursor() or (None, None))[0]
305 def item_activated(self
, iv
, path
):
308 row
= tm
[tm
.get_iter(path
)]
309 name
= row
[DirModel
.NAME
]
310 item_info
= row
[DirModel
.INFO
]
312 child
= self
.view_dir
.file.get_child(name
)
315 self
.open_item(child
)
317 def open_item(self
, item_file
):
318 item_info
= item_file
.query_info('standard::*', 0)
319 if item_info
.get_file_type() == gio
.FILE_TYPE_DIRECTORY
:
320 self
.set_cwd(item_file
)
322 self
.run_in_terminal(['gvim', item_file
.get_path()])
325 self
.execute_command('rox:trash')
328 self
.set_cwd(gio
.file_new_for_path(os
.path
.expanduser('~')))
331 leaf
= self
.cwd
.file.get_basename()
332 parent
= self
.cwd
.file.get_parent()
335 for i
, row
in self
.iter_contents():
336 if row
[DirModel
.NAME
] == leaf
:
337 path
= self
.iv
.get_model().get_path(i
)
338 self
.iv
.select_path(path
)
339 self
.iv
.set_cursor(path
)
342 def run_command(self
, args
):
345 argv
+= a
.expand_to_argv()
347 argv
[0] = get_default_command(args
[1:])
349 builtin
= commands
.builtin_commands
.get(argv
[0], None)
351 msg
= builtin(self
, argv
[1:])
353 self
.warning(msg
) # Not really a warning, just info
355 self
.run_in_terminal(argv
)