Refactor DND code into Carbon and Cocoa parts, and provide a basic OS X Cocoa impleme...
[wxWidgets.git] / src / osx / carbon / dnd.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/osx/carbon/dnd.cpp
3 // Purpose: wxDropTarget, wxDropSource implementations
4 // Author: Stefan Csomor
5 // Modified by:
6 // Created: 1998-01-01
7 // RCS-ID: $Id$
8 // Copyright: (c) 1998 Stefan Csomor
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
11
12 #include "wx/wxprec.h"
13
14 #if wxUSE_DRAG_AND_DROP
15
16 #include "wx/dnd.h"
17
18 #ifndef WX_PRECOMP
19 #include "wx/app.h"
20 #include "wx/toplevel.h"
21 #include "wx/gdicmn.h"
22 #endif // WX_PRECOMP
23
24 #include "wx/osx/private.h"
25
26 // ----------------------------------------------------------------------------
27 // globals
28 // ----------------------------------------------------------------------------
29
30 typedef struct
31 {
32 wxWindow *m_currentTargetWindow;
33 wxDropTarget *m_currentTarget;
34 wxDropSource *m_currentSource;
35 wxDragResult m_result;
36 int m_flags;
37 } MacTrackingGlobals;
38
39 MacTrackingGlobals gTrackingGlobals;
40
41 void wxMacEnsureTrackingHandlersInstalled();
42
43 OSStatus wxMacPromiseKeeper(PasteboardRef WXUNUSED(inPasteboard),
44 PasteboardItemID WXUNUSED(inItem),
45 CFStringRef WXUNUSED(inFlavorType),
46 void * WXUNUSED(inContext))
47 {
48 OSStatus err = noErr;
49
50 // we might add promises here later, inContext is the wxDropSource*
51
52 return err;
53 }
54
55 wxDropTarget::wxDropTarget( wxDataObject *data )
56 : wxDropTargetBase( data )
57 {
58 wxMacEnsureTrackingHandlersInstalled();
59 }
60
61 bool wxDropTarget::CurrentDragHasSupportedFormat()
62 {
63 bool supported = false;
64 if (m_dataObject == NULL)
65 return false;
66
67 if ( gTrackingGlobals.m_currentSource != NULL )
68 {
69 wxDataObject* data = gTrackingGlobals.m_currentSource->GetDataObject();
70
71 if ( data )
72 {
73 size_t formatcount = data->GetFormatCount();
74 wxDataFormat *array = new wxDataFormat[formatcount];
75 data->GetAllFormats( array );
76 for (size_t i = 0; !supported && i < formatcount; i++)
77 {
78 wxDataFormat format = array[i];
79 if ( m_dataObject->IsSupported( format ) )
80 {
81 supported = true;
82 break;
83 }
84 }
85
86 delete [] array;
87 }
88 }
89
90 if ( !supported )
91 {
92 supported = m_dataObject->HasDataInPasteboard( m_currentDragPasteboard );
93 }
94
95 return supported;
96 }
97
98 bool wxDropTarget::GetData()
99 {
100 if (m_dataObject == NULL)
101 return false;
102
103 if ( !CurrentDragHasSupportedFormat() )
104 return false;
105
106 bool transferred = false;
107 if ( gTrackingGlobals.m_currentSource != NULL )
108 {
109 wxDataObject* data = gTrackingGlobals.m_currentSource->GetDataObject();
110
111 if (data != NULL)
112 {
113 size_t formatcount = data->GetFormatCount();
114 wxDataFormat *array = new wxDataFormat[formatcount];
115 data->GetAllFormats( array );
116 for (size_t i = 0; !transferred && i < formatcount; i++)
117 {
118 wxDataFormat format = array[i];
119 if ( m_dataObject->IsSupported( format ) )
120 {
121 int size = data->GetDataSize( format );
122 transferred = true;
123
124 if (size == 0)
125 {
126 m_dataObject->SetData( format, 0, 0 );
127 }
128 else
129 {
130 char *d = new char[size];
131 data->GetDataHere( format, (void*)d );
132 m_dataObject->SetData( format, size, d );
133 delete [] d;
134 }
135 }
136 }
137
138 delete [] array;
139 }
140 }
141
142 if ( !transferred )
143 {
144 transferred = m_dataObject->GetFromPasteboard( m_currentDragPasteboard );
145 }
146
147 return transferred;
148 }
149
150 //-------------------------------------------------------------------------
151 // wxDropSource
152 //-------------------------------------------------------------------------
153
154 wxDropSource::wxDropSource(wxWindow *win,
155 const wxCursor &cursorCopy,
156 const wxCursor &cursorMove,
157 const wxCursor &cursorStop)
158 : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
159 {
160 wxMacEnsureTrackingHandlersInstalled();
161
162 m_window = win;
163 }
164
165 wxDropSource::wxDropSource(wxDataObject& data,
166 wxWindow *win,
167 const wxCursor &cursorCopy,
168 const wxCursor &cursorMove,
169 const wxCursor &cursorStop)
170 : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
171 {
172 wxMacEnsureTrackingHandlersInstalled();
173
174 SetData( data );
175 m_window = win;
176 }
177
178 wxDragResult wxDropSource::DoDragDrop(int flags)
179 {
180 wxASSERT_MSG( m_data, wxT("Drop source: no data") );
181
182 if ((m_data == NULL) || (m_data->GetFormatCount() == 0))
183 return (wxDragResult)wxDragNone;
184
185 DragReference theDrag;
186 RgnHandle dragRegion;
187 OSStatus err = noErr;
188 PasteboardRef pasteboard;
189
190 // add data to drag
191
192 err = PasteboardCreate( kPasteboardUniqueName, &pasteboard );
193 if ( err != noErr )
194 return wxDragNone;
195
196 // we add a dummy promise keeper because of strange messages when linking against carbon debug
197 err = PasteboardSetPromiseKeeper( pasteboard, wxMacPromiseKeeper, this );
198 if ( err != noErr )
199 {
200 CFRelease( pasteboard );
201 return wxDragNone;
202 }
203
204 err = PasteboardClear( pasteboard );
205 if ( err != noErr )
206 {
207 CFRelease( pasteboard );
208 return wxDragNone;
209 }
210 PasteboardSynchronize( pasteboard );
211
212 m_data->AddToPasteboard( pasteboard, 1 );
213
214 if (NewDragWithPasteboard( pasteboard , &theDrag) != noErr)
215 {
216 CFRelease( pasteboard );
217 return wxDragNone;
218 }
219
220 dragRegion = NewRgn();
221 RgnHandle tempRgn = NewRgn();
222
223 EventRecord rec;
224 ConvertEventRefToEventRecord( (EventRef) wxTheApp->MacGetCurrentEvent(), &rec );
225
226 const short dragRegionOuterBoundary = 10;
227 const short dragRegionInnerBoundary = 9;
228
229 SetRectRgn(
230 dragRegion,
231 rec.where.h - dragRegionOuterBoundary,
232 rec.where.v - dragRegionOuterBoundary,
233 rec.where.h + dragRegionOuterBoundary,
234 rec.where.v + dragRegionOuterBoundary );
235
236 SetRectRgn(
237 tempRgn,
238 rec.where.h - dragRegionInnerBoundary,
239 rec.where.v - dragRegionInnerBoundary,
240 rec.where.h + dragRegionInnerBoundary,
241 rec.where.v + dragRegionInnerBoundary );
242
243 DiffRgn( dragRegion, tempRgn, dragRegion );
244 DisposeRgn( tempRgn );
245
246 // TODO: work with promises in order to return data
247 // only when drag was successfully completed
248
249 gTrackingGlobals.m_currentSource = this;
250 gTrackingGlobals.m_result = wxDragNone;
251 gTrackingGlobals.m_flags = flags;
252
253 err = TrackDrag( theDrag, &rec, dragRegion );
254
255 DisposeRgn( dragRegion );
256 DisposeDrag( theDrag );
257 CFRelease( pasteboard );
258 gTrackingGlobals.m_currentSource = NULL;
259
260 return gTrackingGlobals.m_result;
261 }
262
263 bool gTrackingGlobalsInstalled = false;
264
265 // passing the globals via refcon is not needed by the CFM and later architectures anymore
266 // but I'll leave it in there, just in case...
267
268 pascal OSErr wxMacWindowDragTrackingHandler(
269 DragTrackingMessage theMessage, WindowPtr theWindow,
270 void *handlerRefCon, DragReference theDrag );
271 pascal OSErr wxMacWindowDragReceiveHandler(
272 WindowPtr theWindow, void *handlerRefCon,
273 DragReference theDrag );
274
275 void wxMacEnsureTrackingHandlersInstalled()
276 {
277 if ( !gTrackingGlobalsInstalled )
278 {
279 OSStatus err;
280
281 err = InstallTrackingHandler( NewDragTrackingHandlerUPP(wxMacWindowDragTrackingHandler), 0L, &gTrackingGlobals );
282 verify_noerr( err );
283
284 err = InstallReceiveHandler( NewDragReceiveHandlerUPP(wxMacWindowDragReceiveHandler), 0L, &gTrackingGlobals );
285 verify_noerr( err );
286
287 gTrackingGlobalsInstalled = true;
288 }
289 }
290
291 pascal OSErr wxMacWindowDragTrackingHandler(
292 DragTrackingMessage theMessage, WindowPtr theWindow,
293 void *handlerRefCon, DragReference theDrag )
294 {
295 MacTrackingGlobals* trackingGlobals = (MacTrackingGlobals*) handlerRefCon;
296
297 Point mouse, localMouse;
298 DragAttributes attributes;
299
300 GetDragAttributes( theDrag, &attributes );
301 PasteboardRef pasteboard = 0;
302 GetDragPasteboard( theDrag, &pasteboard );
303 wxNonOwnedWindow* toplevel = wxNonOwnedWindow::GetFromWXWindow( (WXWindow) theWindow );
304
305 bool optionDown = GetCurrentKeyModifiers() & optionKey;
306 wxDragResult result = optionDown ? wxDragCopy : wxDragMove;
307
308 switch (theMessage)
309 {
310 case kDragTrackingEnterHandler:
311 case kDragTrackingLeaveHandler:
312 break;
313
314 case kDragTrackingEnterWindow:
315 if (trackingGlobals != NULL)
316 {
317 trackingGlobals->m_currentTargetWindow = NULL;
318 trackingGlobals->m_currentTarget = NULL;
319 }
320 break;
321
322 case kDragTrackingInWindow:
323 if (trackingGlobals == NULL)
324 break;
325 if (toplevel == NULL)
326 break;
327
328 GetDragMouse( theDrag, &mouse, 0L );
329 {
330 int x = mouse.h ;
331 int y = mouse.v ;
332 toplevel->GetNonOwnedPeer()->ScreenToWindow( &x, &y );
333 localMouse.h = x;
334 localMouse.v = y;
335
336 {
337 wxWindow *win = NULL;
338 ControlPartCode controlPart;
339 ControlRef control = FindControlUnderMouse( localMouse, theWindow, &controlPart );
340 if ( control )
341 win = wxFindWindowFromWXWidget( (WXWidget) control );
342 else
343 win = toplevel;
344
345 int localx, localy;
346 localx = localMouse.h;
347 localy = localMouse.v;
348
349 if ( win )
350 win->MacRootWindowToWindow( &localx, &localy );
351 if ( win != trackingGlobals->m_currentTargetWindow )
352 {
353 if ( trackingGlobals->m_currentTargetWindow )
354 {
355 // this window is left
356 if ( trackingGlobals->m_currentTarget )
357 {
358 HideDragHilite( theDrag );
359 trackingGlobals->m_currentTarget->SetCurrentDragPasteboard( pasteboard );
360 trackingGlobals->m_currentTarget->OnLeave();
361 trackingGlobals->m_currentTarget = NULL;
362 trackingGlobals->m_currentTargetWindow = NULL;
363 }
364 }
365
366 if ( win )
367 {
368 // this window is entered
369 trackingGlobals->m_currentTargetWindow = win;
370 trackingGlobals->m_currentTarget = win->GetDropTarget();
371 {
372 if ( trackingGlobals->m_currentTarget )
373 {
374 trackingGlobals->m_currentTarget->SetCurrentDragPasteboard( pasteboard );
375 result = trackingGlobals->m_currentTarget->OnEnter( localx, localy, result );
376 }
377
378 if ( result != wxDragNone )
379 {
380 int x, y;
381
382 x = y = 0;
383 win->MacWindowToRootWindow( &x, &y );
384 RgnHandle hiliteRgn = NewRgn();
385 Rect r = { y, x, y + win->GetSize().y, x + win->GetSize().x };
386 RectRgn( hiliteRgn, &r );
387 ShowDragHilite( theDrag, hiliteRgn, true );
388 DisposeRgn( hiliteRgn );
389 }
390 }
391 }
392 }
393 else
394 {
395 if ( trackingGlobals->m_currentTarget )
396 {
397 trackingGlobals->m_currentTarget->SetCurrentDragPasteboard( pasteboard );
398 result = trackingGlobals->m_currentTarget->OnDragOver( localx, localy, result );
399 }
400 }
401
402 // set cursor for OnEnter and OnDragOver
403 if ( trackingGlobals->m_currentSource && !trackingGlobals->m_currentSource->GiveFeedback( result ) )
404 {
405 if ( !trackingGlobals->m_currentSource->MacInstallDefaultCursor( result ) )
406 {
407 wxStockCursor cursorID = wxCURSOR_NONE;
408
409 switch (result)
410 {
411 case wxDragCopy:
412 cursorID = wxCURSOR_COPY_ARROW;
413 break;
414
415 case wxDragMove:
416 cursorID = wxCURSOR_ARROW;
417 break;
418
419 case wxDragNone:
420 cursorID = wxCURSOR_NO_ENTRY;
421 break;
422
423 case wxDragError:
424 case wxDragLink:
425 case wxDragCancel:
426 default:
427 // put these here to make gcc happy
428 ;
429 }
430
431 if (cursorID != wxCURSOR_NONE)
432 {
433 wxCursor cursor( cursorID );
434 cursor.MacInstall();
435 }
436 }
437 }
438 }
439 }
440 break;
441
442 case kDragTrackingLeaveWindow:
443 if (trackingGlobals == NULL)
444 break;
445
446 if (trackingGlobals->m_currentTarget)
447 {
448 trackingGlobals->m_currentTarget->SetCurrentDragPasteboard( pasteboard );
449 trackingGlobals->m_currentTarget->OnLeave();
450 HideDragHilite( theDrag );
451 trackingGlobals->m_currentTarget = NULL;
452 }
453 trackingGlobals->m_currentTargetWindow = NULL;
454 break;
455
456 default:
457 break;
458 }
459
460 return noErr;
461 }
462
463 pascal OSErr wxMacWindowDragReceiveHandler(
464 WindowPtr theWindow,
465 void *handlerRefCon,
466 DragReference theDrag)
467 {
468 MacTrackingGlobals* trackingGlobals = (MacTrackingGlobals*)handlerRefCon;
469 if ( trackingGlobals->m_currentTarget )
470 {
471 Point mouse, localMouse;
472 int localx, localy;
473
474 PasteboardRef pasteboard = 0;
475 GetDragPasteboard( theDrag, &pasteboard );
476 trackingGlobals->m_currentTarget->SetCurrentDragPasteboard( pasteboard );
477 GetDragMouse( theDrag, &mouse, 0L );
478 localMouse = mouse;
479 localx = localMouse.h;
480 localy = localMouse.v;
481 wxNonOwnedWindow* tlw = wxNonOwnedWindow::GetFromWXWindow((WXWindow) theWindow);
482 if ( tlw )
483 tlw->GetNonOwnedPeer()->ScreenToWindow( &localx, &localy );
484
485 // TODO : should we use client coordinates?
486 if ( trackingGlobals->m_currentTargetWindow )
487 trackingGlobals->m_currentTargetWindow->MacRootWindowToWindow( &localx, &localy );
488 if ( trackingGlobals->m_currentTarget->OnDrop( localx, localy ) )
489 {
490 // the option key indicates copy in Mac UI, if it's not pressed do
491 // move by default if it's allowed at all
492 wxDragResult
493 result = !(trackingGlobals->m_flags & wxDrag_AllowMove) ||
494 (GetCurrentKeyModifiers() & optionKey)
495 ? wxDragCopy
496 : wxDragMove;
497 trackingGlobals->m_result =
498 trackingGlobals->m_currentTarget->OnData( localx, localy, result );
499 }
500 }
501
502 return noErr;
503 }
504
505 #endif // wxUSE_DRAG_AND_DROP
506