New bug() function to quickly bring up a debugging prompt.
[rox-lib.git] / python / rox / loading.py
blobd7f3d3790c1ad5baadba21b10ae289c173c6b216
1 """ROX applications should provide good drag-and-drop support. Use this module
2 to allow drops onto widgets in your application."""
4 import rox
5 from rox import g, alert, get_local_path, _
7 gdk = g.gdk
9 TARGET_URILIST = 0
10 TARGET_RAW = 1
12 def unescape(uri):
13 if '%' not in uri: return uri
14 import re
15 return re.sub('%[0-9a-fA-F][0-9a-fA-F]',
16 lambda match: chr(int(match.group(0)[1:], 16)),
17 uri)
19 def extract_uris(data):
20 """Convert a text/uri-list to a python list of (unescaped) URIs"""
21 lines = data.split('\r\n')
22 out = []
23 for l in lines:
24 if l == chr(0):
25 continue # (gmc adds a '\0' line)
26 if l and l[0] != '#':
27 out.append(unescape(l))
28 return out
30 def provides(context, type): return type in map(str, context.targets)
32 class RemoteFiles(Exception):
33 "Internal use"
34 def __init__(self):
35 Exception.__init__(self, _('Cannot load files from a remote machine '
36 '(multiple files, or target application/octet-stream not provided)'))
38 class XDSLoader:
39 """A mix-in class for widgets that can have files/data dropped on
40 them. Object should also be a GtkWidget."""
42 def __init__(self, types):
43 """Call this after initialising the widget.
44 Types is a list of MIME-types, or None to only accept files."""
46 targets = [('text/uri-list', 0, TARGET_URILIST)]
47 if types:
48 for type in types + ['application/octet-stream']:
49 targets.append((type, 0, TARGET_RAW))
51 self.targets = targets
52 self.xds_proxy_for(self)
54 def xds_proxy_for(self, widget):
55 "Handle drops on this widget as if they were to 'self'."
56 # (Konqueror requires ACTION_MOVE)
57 widget.drag_dest_set(g.DEST_DEFAULT_MOTION | g.DEST_DEFAULT_HIGHLIGHT,
58 self.targets,
59 gdk.ACTION_COPY | gdk.ACTION_MOVE | gdk.ACTION_PRIVATE)
61 widget.connect('drag-data-received', self.xds_data_received)
62 widget.connect('drag-drop', self.xds_drag_drop)
64 def xds_drag_drop(self, widget, context, data, info, time):
65 """Called when something is dropped on us. Decide which of the
66 offered targets to request and ask for it. xds_data_received will
67 be called when it finally arrives."""
68 target = widget.drag_dest_find_target(context, self.targets)
69 context.rox_leafname = None
70 if target is None:
71 # Error?
72 context.drop_finish(False, time)
73 else:
74 if provides(context, 'XdndDirectSave0'):
75 import saving
76 context.rox_leafname = saving._read_xds_property(context, False)
77 widget.drag_get_data(context, target, time)
78 return True
80 def xds_data_received(self, widget, context, x, y, selection, info, time):
81 "Called when we get some data. Internal."
82 if selection.data is None:
83 # Timeout?
84 context.drop_finish(False, time)
85 return
87 if info == TARGET_RAW:
88 try:
89 self.xds_load_from_selection(selection, context.rox_leafname)
90 except:
91 context.drop_finish(False, time)
92 raise
93 context.drop_finish(True, time)
94 return 1
95 if info != TARGET_URILIST:
96 return 0
98 uris = extract_uris(selection.data)
99 if not uris:
100 alert("Nothing to load!")
101 context.drop_finish(False, time)
102 return 1
104 try:
105 try:
106 self.xds_load_uris(uris)
107 except RemoteFiles:
108 if len(uris) != 1 or not provides(context, 'application/octet-stream'):
109 raise
110 widget.drag_get_data(context, 'application/octet-stream', time)
111 return 1 # Don't do drag_finish
112 except:
113 context.drop_finish(False, time)
114 rox.report_exception()
115 else:
116 context.drop_finish(True, time)
118 return 1
120 def xds_load_uris(self, uris):
121 """Try to load each URI in the list. Override this if you can handle URIs
122 directly. The default method passes each local path to xds_load_from_file()
123 and displays an error for anything else."""
124 paths = []
125 for uri in uris:
126 path = get_local_path(uri)
127 if path:
128 paths.append(path)
129 if len(paths) < len(uris):
130 raise RemoteFiles
131 for path in paths:
132 self.xds_load_from_file(path)
134 def xds_load_from_file(self, path):
135 """Try to load this local file. Override this if you have a better way
136 to load files. The default method opens the file and calls xds_load_from_stream()."""
137 try:
138 self.xds_load_from_stream(path, None, open(path, 'rb'))
139 except:
140 rox.report_exception()
142 def xds_load_from_selection(self, selection, leafname = None):
143 """Try to load this selection (data from another application). The default
144 puts the data in a cStringIO and calls xds_load_from_stream()."""
145 if selection.data is None:
146 g.gdk.beep() # Load aborted
147 return
148 from cStringIO import StringIO
149 type = str(selection.type)
150 self.xds_load_from_stream(leafname, type, StringIO(selection.data))
152 def xds_load_from_stream(self, name, type, stream):
153 """Called when we get any data sent via drag-and-drop in any way (local
154 file or remote application transfer). You should override this and do
155 something with the data. 'name' may be None (if the data is unnamed),
156 a leafname, or a full path or URI. 'type' is the MIME type, or None if
157 unknown."""
158 alert('Got some data, but missing code to handle it!\n\n(name="%s";type="%s")'
159 % (name, type))