2 @copyright: (C) 2008, Thomas Leonard
3 @see: U{http://roscidus.com}
5 from __future__
import with_statement
10 from logging
import warn
12 gtk_theme
= gtk
.icon_theme_get_default()
16 def get_themed_icon(name
, icon_size
):
18 return gtk_theme
.load_icon('text-x-generic', icon_size
, 0)
19 except gobject
.GError
:
22 icon_text_plain
= get_themed_icon('text-x-generic', icon_size
)
23 icon_dir
= get_themed_icon('folder', icon_size
)
25 def sort_key(name
, info
):
26 t
= 9 - int(info
.get_file_type())
27 return str(t
) + name
.lower()
29 COLOUR_EXEC
= '#008000'
30 COLOUR_NORMAL
= '#000000'
31 COLOUR_DIR
= '#0000a0'
32 COLOUR_ERROR
= '#800000'
34 QUERY
= 'standard::*,unix::mode,thumbnail::path'
37 return name
.startswith('.')
40 def __init__(self
, name
):
46 def get_file_type(self
):
47 return gio
.FILE_TYPE_UNKNOWN
49 def make_error_row(name
, ex
):
50 info
= ErrorInfo(name
)
51 return [name
, None, info
, sort_key(name
, info
), COLOUR_ERROR
]
54 name
= info
.get_name()
59 thumbnail
= info
.get_attribute_byte_string('thumbnail::path')
62 loader
= gtk
.gdk
.PixbufLoader('png')
64 with
open(thumbnail
) as stream
:
65 loader
.write(stream
.read())
68 pixbuf
= loader
.get_pixbuf()
69 assert pixbuf
, "Failed to load PNG thumbnail"
71 warn("Failed to load cached PNG thumbnail icon: %s", ex
)
73 icon
= info
.get_icon()
75 gtkicon_info
= gtk_theme
.choose_icon(icon
.get_names(), icon_size
, 0)
77 gtkicon_info
.get_filename()
78 pixbuf
= gtkicon_info
.load_icon()
79 mode
= info
.get_attribute_uint32('unix::mode')
80 if stat
.S_ISDIR(mode
):
85 colour
= COLOUR_NORMAL
86 return [name
, pixbuf
, info
, sort_key(name
, info
), colour
]
92 SORT
= 3 # Type-then-name
98 def __init__(self
, file):
99 assert isinstance(file, gio
.File
), file
104 assert not self
.users
, "DirModel garbage collected while still monitoring! Monitored by " + str(self
.users
)
106 def add_ref(self
, user
):
111 assert not self
.monitor
112 assert not self
.model
113 self
.model
= gtk
.ListStore(str, gtk
.gdk
.Pixbuf
, object, str, str)
115 # Should really be the view that sorts, but see GTK bug #523724
116 self
.model
.set_sort_column_id(DirModel
.SORT
, gtk
.SORT_ASCENDING
)
118 self
.monitor
= self
.file.monitor_directory(0)
119 weakself
= weakref
.ref(self
)
120 self
.monitor
.connect('changed', lambda *args
: weakself().contents_changed(*args
))
121 self
.build_contents()
123 self
.users
.append(user
)
125 def del_ref(self
, user
):
127 self
.users
.remove(user
)
133 self
.monitor
.cancel()
136 def contents_changed(self
, monitor
, this_file
, other_file
, event_type
):
137 name
= this_file
.get_basename()
140 #print "build", monitor, this_file.get_basename(), other_file, event_type
141 if event_type
== gio
.FILE_MONITOR_EVENT_CREATED
:
142 #print "Create", name
143 i
= self
.model
.append(None)
144 elif event_type
in (gio
.FILE_MONITOR_EVENT_DELETED
, gio
.FILE_MONITOR_EVENT_CHANGED
, gio
.FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED
):
145 i
= self
.model
.get_iter_root()
147 if self
.model
[i
][DirModel
.NAME
] == name
:
149 i
= self
.model
.iter_next(i
)
151 #print "Note: deleted unknown file", name
152 self
.build_contents()
153 elif event_type
== gio
.FILE_MONITOR_EVENT_CHANGES_DONE_HINT
:
156 print "Unknown event type", event_type
, name
157 self
.build_contents()
159 if event_type
in (gio
.FILE_MONITOR_EVENT_CREATED
, gio
.FILE_MONITOR_EVENT_CHANGED
, gio
.FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED
):
160 #print "Update", name
162 info
= this_file
.query_info(QUERY
, 0)
163 except gobject
.GError
, ex
:
164 self
.model
[i
] = make_error_row(name
, ex
)
166 self
.model
[i
] = make_row(info
)
167 elif event_type
== gio
.FILE_MONITOR_EVENT_DELETED
:
168 #print "Remove", name
171 assert False, "Unreachable! " + str(event_type
)
173 def build_contents(self
):
177 i
= m
.get_iter_root()
179 name_to_iter
[m
[i
][DirModel
.NAME
]] = i
186 e
= self
.file.enumerate_children(QUERY
, 0)
187 except gobject
.GError
, ex
:
199 # name_to_iter contains the old contents
200 # files contains the desired contents
204 name
= row
[DirModel
.NAME
]
206 i
= name_to_iter
.get(name
, None)
209 del name_to_iter
[name
]
213 # name_to_iter contains old contents that are no longer desired
214 # new contains rows that are desired but were not present
216 i
= m
.get_iter_root()
218 if m
[i
][DirModel
.NAME
] in name_to_iter
:
228 dirs
= weakref
.WeakValueDictionary()
229 def get_dir_model(file):
230 # Might want some caching here...