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