1///////////////////////////////////////////////////////////////////////////////
2// Name:        src/mac/classic/dnd.cpp
3// Purpose:     wxDropTarget, wxDropSource, wxDataObject implementation
4// Author:      Stefan Csomor
5// Modified by:
6// Created:     1998-01-01
7// RCS-ID:      $Id: dnd.cpp 39797 2006-06-19 20:18:46Z ABX $
8// Copyright:   (c) 1998 Stefan Csomor
9// Licence:     wxWindows licence
10///////////////////////////////////////////////////////////////////////////////
11
12#include "wx/wxprec.h"
13
14#ifdef __BORLANDC__
15    #pragma hdrstop
16#endif
17
18#if wxUSE_DRAG_AND_DROP
19
20#include "wx/dnd.h"
21
22#ifndef WX_PRECOMP
23    #include "wx/app.h"
24    #include "wx/window.h"
25    #include "wx/toplevel.h"
26    #include "wx/gdicmn.h"
27#endif // WX_PRECOMP
28
29#include "wx/mac/private.h"
30
31// ----------------------------------------------------------------------------
32// global
33// ----------------------------------------------------------------------------
34
35void wxMacEnsureTrackingHandlersInstalled() ;
36
37typedef struct
38{
39    wxWindow* m_currentTargetWindow ;
40    wxDropTarget* m_currentTarget ;
41    wxDropSource* m_currentSource ;
42} MacTrackingGlobals ;
43
44MacTrackingGlobals gTrackingGlobals ;
45
46//----------------------------------------------------------------------------
47// wxDropTarget
48//----------------------------------------------------------------------------
49
50wxDropTarget::wxDropTarget( wxDataObject *data )
51            : wxDropTargetBase( data )
52{
53    wxMacEnsureTrackingHandlersInstalled() ;
54}
55
56wxDragResult wxDropTarget::OnDragOver( wxCoord WXUNUSED(x),
57                                       wxCoord WXUNUSED(y),
58                                       wxDragResult def )
59{
60
61    return CurrentDragHasSupportedFormat() ? def : wxDragNone;
62}
63
64bool wxDropTarget::OnDrop( wxCoord WXUNUSED(x), wxCoord WXUNUSED(y) )
65{
66    if (!m_dataObject)
67        return false;
68
69    return CurrentDragHasSupportedFormat() ;
70}
71
72wxDragResult wxDropTarget::OnData( wxCoord WXUNUSED(x), wxCoord WXUNUSED(y),
73                                   wxDragResult def )
74{
75    if (!m_dataObject)
76        return wxDragNone;
77
78    if (!CurrentDragHasSupportedFormat())
79        return wxDragNone;
80
81    return GetData() ? def : wxDragNone;
82}
83
84bool wxDropTarget::CurrentDragHasSupportedFormat()
85{
86    bool supported = false ;
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            delete[] array ;
106        }
107    }
108    if ( !supported )
109    {
110        UInt16 items ;
111        OSErr result;
112        CountDragItems((DragReference)m_currentDrag, &items);
113        for (UInt16 index = 1; index <= items && supported == false ; ++index)
114        {
115            ItemReference theItem;
116            FlavorType theType ;
117            UInt16 flavors = 0 ;
118            GetDragItemReferenceNumber((DragReference)m_currentDrag, index, &theItem);
119            CountDragItemFlavors( (DragReference)m_currentDrag, theItem , &flavors ) ;
120            for ( UInt16 flavor = 1 ; flavor <= flavors ; ++flavor )
121            {
122                result = GetFlavorType((DragReference)m_currentDrag, theItem, flavor , &theType);
123                if ( m_dataObject->IsSupportedFormat( wxDataFormat( theType ) ) )
124                {
125                    supported = true ;
126                    break ;
127                }
128            }
129        }
130    }
131    return supported ;
132}
133
134bool wxDropTarget::GetData()
135{
136    if (!m_dataObject)
137        return false;
138
139    if ( !CurrentDragHasSupportedFormat() )
140        return false ;
141
142    bool transferred = false ;
143    if ( gTrackingGlobals.m_currentSource != NULL )
144    {
145        wxDataObject* data = gTrackingGlobals.m_currentSource->GetDataObject() ;
146
147        if ( data )
148        {
149            size_t formatcount = data->GetFormatCount() ;
150            wxDataFormat *array = new wxDataFormat[ formatcount  ];
151            data->GetAllFormats( array );
152            for (size_t i = 0; !transferred && i < formatcount ; i++)
153            {
154                wxDataFormat format = array[i] ;
155                if ( m_dataObject->IsSupported( format ) )
156                {
157                    int size = data->GetDataSize( format );
158                    transferred = true ;
159
160                    if (size == 0)
161                    {
162                        m_dataObject->SetData(format , 0 , 0 ) ;
163                    }
164                    else
165                    {
166                        char *d = new char[size];
167                        data->GetDataHere( format , (void*) d );
168                        m_dataObject->SetData( format , size , d ) ;
169                        delete[] d ;
170                    }
171                }
172            }
173            delete[] array ;
174        }
175    }
176    if ( !transferred )
177    {
178        UInt16 items ;
179        OSErr result;
180        bool firstFileAdded = false ;
181        CountDragItems((DragReference)m_currentDrag, &items);
182        for (UInt16 index = 1; index <= items; ++index)
183        {
184            ItemReference theItem;
185            FlavorType theType ;
186            UInt16 flavors = 0 ;
187            GetDragItemReferenceNumber((DragReference)m_currentDrag, index, &theItem);
188            CountDragItemFlavors( (DragReference)m_currentDrag, theItem , &flavors ) ;
189            for ( UInt16 flavor = 1 ; flavor <= flavors ; ++flavor )
190            {
191                result = GetFlavorType((DragReference)m_currentDrag, theItem, flavor , &theType);
192                wxDataFormat format(theType) ;
193                if ( m_dataObject->IsSupportedFormat( format ) )
194                {
195                    FlavorFlags theFlags;
196                    result = GetFlavorFlags((DragReference)m_currentDrag, theItem, theType, &theFlags);
197                    if (result == noErr)
198                    {
199                        Size dataSize ;
200                        Ptr theData ;
201                        GetFlavorDataSize((DragReference)m_currentDrag, theItem, theType, &dataSize);
202                        if ( theType == 'TEXT' )
203                        {
204                            // this increment is only valid for allocating, on the next GetFlavorData
205                            // call it is reset again to the original value
206                            dataSize++ ;
207                        }
208                        theData = new char[dataSize];
209                        GetFlavorData((DragReference)m_currentDrag, theItem, theType, (void*) theData, &dataSize, 0L);
210                        if( theType == 'TEXT' )
211                        {
212                            theData[dataSize]=0 ;
213                            wxString convert( theData , wxConvLocal ) ;
214                            m_dataObject->SetData( format, convert.length() * sizeof(wxChar), (const wxChar*) convert );
215                        }
216                        else if ( theType == kDragFlavorTypeHFS )
217                        {
218                            HFSFlavor* theFile = (HFSFlavor*) theData ;
219                            wxString name = wxMacFSSpec2MacFilename( &theFile->fileSpec ) ;
220                            if (  firstFileAdded )
221                                ((wxFileDataObject*)m_dataObject)->AddFile( name ) ;
222                            else
223                            {
224                                ((wxFileDataObject*)m_dataObject)->SetData( 0 , name.c_str() ) ;
225                                firstFileAdded = true ;
226                            }
227                        }
228                        else
229                        {
230                            m_dataObject->SetData( format, dataSize, theData );
231                        }
232                        delete[] theData;
233                    }
234                    break ;
235                }
236            }
237        }
238    }
239    return true ;
240}
241
242//-------------------------------------------------------------------------
243// wxDropSource
244//-------------------------------------------------------------------------
245
246//-----------------------------------------------------------------------------
247// drag request
248
249wxDropSource::wxDropSource(wxWindow *win,
250                           const wxCursor &cursorCopy,
251                           const wxCursor &cursorMove,
252                           const wxCursor &cursorStop)
253            : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
254{
255    wxMacEnsureTrackingHandlersInstalled() ;
256    m_window = win;
257}
258
259wxDropSource::wxDropSource(wxDataObject& data,
260                           wxWindow *win,
261                           const wxCursor &cursorCopy,
262                           const wxCursor &cursorMove,
263                           const wxCursor &cursorStop)
264            : wxDropSourceBase(cursorCopy, cursorMove, cursorStop)
265{
266    wxMacEnsureTrackingHandlersInstalled() ;
267    SetData( data );
268    m_window = win;
269}
270
271wxDropSource::~wxDropSource()
272{
273}
274
275
276wxDragResult wxDropSource::DoDragDrop(int WXUNUSED(flags))
277{
278    wxASSERT_MSG( m_data, wxT("Drop source: no data") );
279
280    if (!m_data)
281        return (wxDragResult) wxDragNone;
282
283    if (m_data->GetFormatCount() == 0)
284        return (wxDragResult) wxDragNone;
285
286    OSErr result;
287    DragReference theDrag;
288    RgnHandle dragRegion;
289    if ((result = NewDrag(&theDrag)))
290    {
291        return wxDragNone ;
292    }
293    // add data to drag
294    size_t formatCount = m_data->GetFormatCount() ;
295    wxDataFormat *formats = new wxDataFormat[formatCount] ;
296    m_data->GetAllFormats( formats ) ;
297    ItemReference theItem = 1 ;
298    for ( size_t i = 0 ; i < formatCount ; ++i )
299    {
300        size_t dataSize = m_data->GetDataSize( formats[i] ) ;
301        Ptr dataPtr = new char[dataSize] ;
302        m_data->GetDataHere( formats[i] , dataPtr ) ;
303        OSType type = formats[i].GetFormatId() ;
304        if ( type == 'TEXT' )
305        {
306            dataSize-- ;
307            dataPtr[ dataSize ] = 0 ;
308            wxString st( (wxChar*) dataPtr ) ;
309            wxCharBuffer buf = st.mb_str( wxConvLocal) ;
310            AddDragItemFlavor(theDrag, theItem, type , buf.data(), strlen(buf), 0);
311        }
312        else if (type == kDragFlavorTypeHFS )
313        {
314            HFSFlavor  theFlavor ;
315            OSErr err = noErr;
316            CInfoPBRec cat;
317
318            wxMacFilename2FSSpec( dataPtr , &theFlavor.fileSpec ) ;
319
320            cat.hFileInfo.ioNamePtr = theFlavor.fileSpec.name;
321            cat.hFileInfo.ioVRefNum = theFlavor.fileSpec.vRefNum;
322            cat.hFileInfo.ioDirID = theFlavor.fileSpec.parID;
323            cat.hFileInfo.ioFDirIndex = 0;
324            err = PBGetCatInfoSync(&cat);
325            if (err == noErr )
326            {
327                theFlavor.fdFlags = cat.hFileInfo.ioFlFndrInfo.fdFlags;
328                if (theFlavor.fileSpec.parID == fsRtParID) {
329                    theFlavor.fileCreator = 'MACS';
330                    theFlavor.fileType = 'disk';
331                } else if ((cat.hFileInfo.ioFlAttrib & ioDirMask) != 0) {
332                    theFlavor.fileCreator = 'MACS';
333                    theFlavor.fileType = 'fold';
334                } else {
335                    theFlavor.fileCreator = cat.hFileInfo.ioFlFndrInfo.fdCreator;
336                    theFlavor.fileType = cat.hFileInfo.ioFlFndrInfo.fdType;
337                }
338                AddDragItemFlavor(theDrag, theItem, type , &theFlavor, sizeof(theFlavor), 0);
339            }
340        }
341        else
342        {
343            AddDragItemFlavor(theDrag, theItem, type , dataPtr, dataSize, 0);
344        }
345        delete[] dataPtr ;
346    }
347    delete[] formats ;
348
349    dragRegion = NewRgn();
350    RgnHandle tempRgn = NewRgn() ;
351
352    EventRecord* ev = NULL ;
353#if !TARGET_CARBON // TODO
354    ev = (EventRecord*) wxTheApp->MacGetCurrentEvent() ;
355#else
356    EventRecord rec ;
357    ev = &rec ;
358    wxMacConvertEventToRecord( (EventRef) wxTheApp->MacGetCurrentEvent() , &rec ) ;
359#endif
360    const short dragRegionOuterBoundary = 10 ;
361    const short dragRegionInnerBoundary = 9 ;
362
363    SetRectRgn( dragRegion , ev->where.h - dragRegionOuterBoundary ,
364        ev->where.v  - dragRegionOuterBoundary ,
365        ev->where.h + dragRegionOuterBoundary ,
366        ev->where.v + dragRegionOuterBoundary ) ;
367
368    SetRectRgn( tempRgn , ev->where.h - dragRegionInnerBoundary ,
369        ev->where.v  - dragRegionInnerBoundary ,
370        ev->where.h + dragRegionInnerBoundary ,
371        ev->where.v + dragRegionInnerBoundary ) ;
372
373    DiffRgn( dragRegion , tempRgn , dragRegion ) ;
374    DisposeRgn( tempRgn ) ;
375
376    // TODO:work with promises in order to return data only when drag
377    // was successfully completed
378
379    gTrackingGlobals.m_currentSource = this ;
380    result = TrackDrag(theDrag, ev , dragRegion);
381    DisposeRgn(dragRegion);
382    DisposeDrag(theDrag);
383    gTrackingGlobals.m_currentSource = NULL ;
384
385    KeyMap keymap;
386    GetKeys(keymap);
387    bool optionDown = keymap[1] & 4;
388    wxDragResult dndresult = optionDown ? wxDragCopy : wxDragMove;
389    return dndresult;
390}
391
392bool wxDropSource::MacInstallDefaultCursor(wxDragResult effect)
393{
394    const wxCursor& cursor = GetCursor(effect);
395    if ( cursor.Ok() )
396    {
397        cursor.MacInstall() ;
398
399        return true;
400    }
401    else
402    {
403        return false;
404    }
405}
406
407bool gTrackingGlobalsInstalled = false ;
408
409// passing the globals via refcon is not needed by the CFM and later architectures anymore
410// but I'll leave it in there, just in case...
411
412pascal OSErr wxMacWindowDragTrackingHandler(DragTrackingMessage theMessage, WindowPtr theWindow,
413  void *handlerRefCon, DragReference theDrag) ;
414pascal OSErr wxMacWindowDragReceiveHandler(WindowPtr theWindow, void *handlerRefCon,
415DragReference theDrag) ;
416
417void wxMacEnsureTrackingHandlersInstalled()
418{
419    if( !gTrackingGlobalsInstalled )
420    {
421        OSErr result;
422
423        result = InstallTrackingHandler(NewDragTrackingHandlerUPP(wxMacWindowDragTrackingHandler), 0L,&gTrackingGlobals);
424        wxASSERT( result == noErr ) ;
425        result = InstallReceiveHandler(NewDragReceiveHandlerUPP(wxMacWindowDragReceiveHandler), 0L, &gTrackingGlobals);
426        wxASSERT( result == noErr ) ;
427
428        gTrackingGlobalsInstalled = true ;
429    }
430}
431
432pascal OSErr wxMacWindowDragTrackingHandler(DragTrackingMessage theMessage, WindowPtr theWindow,
433  void *handlerRefCon, DragReference theDrag)
434{
435    MacTrackingGlobals* trackingGlobals = (MacTrackingGlobals*) handlerRefCon;
436    Point mouse, localMouse;
437    DragAttributes attributes;
438    GetDragAttributes(theDrag, &attributes);
439    wxTopLevelWindowMac* toplevel = wxFindWinFromMacWindow( (WXWindow) theWindow ) ;
440
441    KeyMap keymap;
442    GetKeys(keymap);
443    bool optionDown = keymap[1] & 4;
444    wxDragResult result = optionDown ? wxDragCopy : wxDragMove;
445
446    switch(theMessage)
447    {
448        case kDragTrackingEnterHandler:
449            break;
450        case kDragTrackingLeaveHandler:
451            break;
452        case kDragTrackingEnterWindow:
453            trackingGlobals->m_currentTargetWindow = NULL ;
454            trackingGlobals->m_currentTarget = NULL ;
455            break;
456        case kDragTrackingInWindow:
457            if (toplevel == NULL)
458                break;
459
460            GetDragMouse(theDrag, &mouse, 0L);
461            localMouse = mouse;
462            GlobalToLocal(&localMouse);
463
464
465
466//            if (attributes & kDragHasLeftSenderWindow)
467            {
468                wxPoint point(localMouse.h , localMouse.v) ;
469                wxWindow *win = NULL ;
470                toplevel->MacGetWindowFromPointSub( point , &win ) ;
471                int localx , localy ;
472                localx = localMouse.h ;
473                localy = localMouse.v ;
474                //TODO : should we use client coordinates
475                if ( win )
476                    win->MacRootWindowToWindow( &localx , &localy ) ;
477                if ( win != trackingGlobals->m_currentTargetWindow )
478                {
479                    if ( trackingGlobals->m_currentTargetWindow )
480                    {
481                        // this window is left
482                        if ( trackingGlobals->m_currentTarget )
483                        {
484                            HideDragHilite(theDrag);
485                            trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag ) ;
486                            trackingGlobals->m_currentTarget->OnLeave() ;
487                            trackingGlobals->m_currentTarget = NULL;
488                            trackingGlobals->m_currentTargetWindow = NULL ;
489                        }
490                    }
491                    if ( win )
492                    {
493                        // this window is entered
494                        trackingGlobals->m_currentTargetWindow = win ;
495                        trackingGlobals->m_currentTarget = win->GetDropTarget() ;
496                        {
497
498                            if ( trackingGlobals->m_currentTarget )
499                            {
500                                trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag ) ;
501                                result = trackingGlobals->m_currentTarget->OnEnter(
502                                    localx , localy , result ) ;
503                            }
504
505
506                            if ( result != wxDragNone )
507                            {
508                                int x , y ;
509                                x = y = 0 ;
510                                win->MacWindowToRootWindow( &x , &y ) ;
511                                RgnHandle hiliteRgn = NewRgn() ;
512                                SetRectRgn( hiliteRgn , x , y , x+win->GetSize().x ,y+win->GetSize().y) ;
513                                ShowDragHilite(theDrag, hiliteRgn, true);
514                                DisposeRgn( hiliteRgn ) ;
515                            }
516                        }
517                    }
518                }
519                else
520                {
521                    if( trackingGlobals->m_currentTarget )
522                    {
523                        trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag ) ;
524                        trackingGlobals->m_currentTarget->OnDragOver(
525                            localx , localy , result ) ;
526                    }
527                }
528
529                // set cursor for OnEnter and OnDragOver
530                if ( trackingGlobals->m_currentSource && trackingGlobals->m_currentSource->GiveFeedback( result ) == FALSE )
531                {
532                  if ( trackingGlobals->m_currentSource->MacInstallDefaultCursor( result ) == FALSE )
533                  {
534                      switch( result )
535                      {
536                          case wxDragCopy :
537                              {
538                                  wxCursor cursor(wxCURSOR_COPY_ARROW) ;
539                                  cursor.MacInstall() ;
540                              }
541                              break ;
542                          case wxDragMove :
543                              {
544                                  wxCursor cursor(wxCURSOR_ARROW) ;
545                                  cursor.MacInstall() ;
546                              }
547                              break ;
548                          case wxDragNone :
549                              {
550                                  wxCursor cursor(wxCURSOR_NO_ENTRY) ;
551                                  cursor.MacInstall() ;
552                              }
553                              break ;
554
555                          case wxDragError:
556                          case wxDragLink:
557                          case wxDragCancel:
558                              // put these here to make gcc happy
559                              ;
560                      }
561                  }
562                }
563
564          }
565            // MyTrackItemUnderMouse(localMouse, theWindow);
566            break;
567        case kDragTrackingLeaveWindow:
568            if (trackingGlobals->m_currentTarget)
569            {
570                trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag ) ;
571                trackingGlobals->m_currentTarget->OnLeave() ;
572                HideDragHilite(theDrag);
573                trackingGlobals->m_currentTarget = NULL ;
574            }
575            trackingGlobals->m_currentTargetWindow = NULL ;
576            break;
577    }
578    return(noErr);
579}
580
581pascal OSErr wxMacWindowDragReceiveHandler(WindowPtr theWindow,
582                                           void *handlerRefCon,
583                                           DragReference theDrag)
584{
585    MacTrackingGlobals* trackingGlobals = (MacTrackingGlobals*) handlerRefCon;
586    if ( trackingGlobals->m_currentTarget )
587    {
588        Point mouse,localMouse ;
589        int localx,localy ;
590
591        trackingGlobals->m_currentTarget->SetCurrentDrag( theDrag ) ;
592        GetDragMouse(theDrag, &mouse, 0L);
593        localMouse = mouse;
594        GlobalToLocal(&localMouse);
595        localx = localMouse.h ;
596        localy = localMouse.v ;
597        //TODO : should we use client coordinates
598        if ( trackingGlobals->m_currentTargetWindow )
599            trackingGlobals->m_currentTargetWindow->MacRootWindowToWindow( &localx , &localy ) ;
600        if ( trackingGlobals->m_currentTarget->OnDrop( localx , localy ) )
601        {
602            KeyMap keymap;
603            GetKeys(keymap);
604            bool optionDown = keymap[1] & 4;
605            wxDragResult result = optionDown ? wxDragCopy : wxDragMove;
606            trackingGlobals->m_currentTarget->OnData( localx , localy , result ) ;
607        }
608    }
609    return(noErr);
610}
611#endif
612