Several incompatible changes to the experimental proxy API to make it simpler
[rox-lib.git] / python / rox / loading.py
blob8f8b50c198c827a1cc908b078b6f1edad819cda7
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 extract_uris(data):
13 """Convert a text/uri-list to a python list of (still escaped) URIs"""
14 lines = data.split('\r\n')
15 out = []
16 for l in lines:
17 if l == chr(0):
18 continue # (gmc adds a '\0' line)
19 if l and l[0] != '#':
20 out.append(l)
21 return out
23 def provides(context, type): return type in map(str, context.targets)
25 class RemoteFiles(Exception):
26 "Internal use"
27 def __init__(self):
28 Exception.__init__(self, _('Cannot load files from a remote machine '
29 '(multiple files, or target application/octet-stream not provided)'))
31 class XDSLoader:
32 """A mix-in class for widgets that can have files/data dropped on
33 them. If object is also a GtkWidget, xds_proxy_for(self) is called
34 automatically."""
36 def __init__(self, types):
37 """Call this after initialising the widget.
38 Types is a list of MIME-types, or None to only accept files."""
40 targets = [('text/uri-list', 0, TARGET_URILIST)]
41 if types:
42 for mimetype in types + ['application/octet-stream']:
43 targets.append((mimetype, 0, TARGET_RAW))
45 self.targets = targets
46 if isinstance(self, g.Widget):
47 self.xds_proxy_for(self)
49 def xds_proxy_for(self, widget):
50 "Handle drops on this widget as if they were to 'self'."
51 # (Konqueror requires ACTION_MOVE)
52 widget.drag_dest_set(g.DEST_DEFAULT_MOTION | g.DEST_DEFAULT_HIGHLIGHT,
53 self.targets,
54 gdk.ACTION_COPY | gdk.ACTION_MOVE | gdk.ACTION_PRIVATE)
56 widget.connect('drag-data-received', self.xds_data_received)
57 widget.connect('drag-drop', self.xds_drag_drop)
59 def xds_drag_drop(self, widget, context, data, info, time):
60 """Called when something is dropped on us. Decide which of the
61 offered targets to request and ask for it. xds_data_received will
62 be called when it finally arrives."""
63 target = widget.drag_dest_find_target(context, self.targets)
64 context.rox_leafname = None
65 if target is None:
66 # Error?
67 context.drop_finish(False, time)
68 else:
69 if provides(context, 'XdndDirectSave0'):
70 import saving
71 context.rox_leafname = saving._read_xds_property(context, False)
72 widget.drag_get_data(context, target, time)
73 return True
75 def xds_data_received(self, widget, context, x, y, selection, info, time):
76 "Called when we get some data. Internal."
77 if selection.data is None:
78 # Timeout?
79 context.drop_finish(False, time)
80 return
82 if info == TARGET_RAW:
83 try:
84 self.xds_load_from_selection(selection, context.rox_leafname)
85 except:
86 context.drop_finish(False, time)
87 raise
88 context.drop_finish(True, time)
89 return 1
90 if info != TARGET_URILIST:
91 return 0
93 uris = extract_uris(selection.data)
94 if not uris:
95 alert("Nothing to load!")
96 context.drop_finish(False, time)
97 return 1
99 try:
100 try:
101 self.xds_load_uris(uris)
102 except RemoteFiles:
103 if len(uris) != 1 or not provides(context, 'application/octet-stream'):
104 raise
105 widget.drag_get_data(context, 'application/octet-stream', time)
106 return 1 # Don't do drag_finish
107 except:
108 context.drop_finish(False, time)
109 rox.report_exception()
110 else:
111 context.drop_finish(True, time)
113 return 1
115 def xds_load_uris(self, uris):
116 """Try to load each URI in the list. Override this if you can handle URIs
117 directly. The default method passes each local path to xds_load_from_file()
118 and displays an error for anything else.
119 The uris are escaped, so a space will appear as '%20'"""
120 paths = []
121 for uri in uris:
122 path = get_local_path(uri)
123 if path:
124 paths.append(path)
125 if len(paths) < len(uris):
126 raise RemoteFiles
127 for path in paths:
128 self.xds_load_from_file(path)
130 def xds_load_from_file(self, path):
131 """Try to load this local file. Override this if you have a better way
132 to load files. The default method opens the file and calls xds_load_from_stream()."""
133 try:
134 self.xds_load_from_stream(path, None, open(path, 'rb'))
135 except:
136 rox.report_exception()
138 def xds_load_from_selection(self, selection, leafname = None):
139 """Try to load this selection (data from another application). The default
140 puts the data in a cStringIO and calls xds_load_from_stream()."""
141 if selection.data is None:
142 g.gdk.beep() # Load aborted
143 return
144 from cStringIO import StringIO
145 mimetype = str(selection.type)
146 self.xds_load_from_stream(leafname, mimetype, StringIO(selection.data))
148 def xds_load_from_stream(self, name, mimetype, stream):
149 """Called when we get any data sent via drag-and-drop in any way (local
150 file or remote application transfer). You should override this and do
151 something with the data. 'name' may be None (if the data is unnamed),
152 a leafname, or a full path or URI. 'mimetype' is the MIME type, or None if
153 unknown."""
154 alert('Got some data, but missing code to handle it!\n\n(name="%s";mimetype="%s")'
155 % (name, mimetype))