Drag and drop is by now a familiar aspect of operating systems. The user clicks an object and, without releasing the mouse button, moves the mouse pointer (drags the object) to another position, and then releases the button (drops the object). Usually and icon is displayed under or near the mouse pointer that indicates what kind of data is being dragged and whether the current widget lying under the pointer is a valid drop target. Drag and drop is used, for example, to move a file or folder from one folder to another. The user can also drag a file onto an open application and, if the file type is appropriate, expect the application to open the file for viewing or editing. In KDE, drag and drop is also used to place applications, (in the form of .desktop files, discussed in "Application Resources") on the panel. The Qt drag-and-drop system is based on XDND protocol. This is a publicly available drag-and-drop protocol and is used by GNOME/GTK+, Mozilla, Star Office, XEmacs, and other projects and applications. The drag types are described using public, standard MIME types, which means that drag data types should be identifiable even when the drag is coming from a non-Qt system. You can find more information about the XDND protocol at http://www.cco.caltech.edu/~jafl/xdnd/. KDE/Qt applications can also accept drops which have been dragged from Motif-based applications, such as Netscape Navigator, further integrating the user's desktop. (Currently, however, you cannot drag from a KDE/Qt program to a Motif program.) When a user drags some data over a widget, a drag-enter event is generated. A drop generates a drop event. You can reimplement the QWidget handlers for these events to process drops. Listings 7.1 and 7.2 show code for a widget called KDropDemo, which demonstrates how to process drop events.
Example 7.1. kdropdemo.h: Contains the Class Definition for the Widget KDropDemo 1
2 1: #ifndef __KDROPDEMO_H__
3 2: #define __KDROPDEMO_H__
4 3:
5 4:
6 5: #include <qlabel.h>
7 6:
8 7: /**
9 8: * KDropDemo
10 9: * Accepts dropped URLs.
11 10: **/
12 11: class KDropDemo : public QLabel
13 12: {
14 13: public:
15 14: KDropDemo (QWidget *parent, const char *name=0);
16 15:
17 16: protected:
18 17: void dropEvent (QDropEvent *qdropevent);
19 18: void dragEnterEvent (QDragEnterEvent *qdragenterevent);
20 19: };
21 20:
22 21: #endif |
KDropDemo inherits QLabel, but any subclass of QWidget can accept drops in the same way as presented here. You just need to reimplement dragEnterEvent() and dropEvent(), as shown in Listing 7.2, to process the corresponding events.
Example 7.2. kdropdemo.cpp: Contains the Class Declaration for the Widget KDropDemo 1
2 1: #include <qdragobject.h>
3 2:
4 3: #include "kdropdemo.h"
5 4:
6 5: KDropDemo::KDropDemo (QWidget *parent, const char *name) :
7 6: QLabel (parent, name)
8 7: {
9 8: setAcceptDrops(true);
10 9: setText ("---------------No drops yet.---------------");
11 10: }
12 11:
13 12: void
14 13: KDropDemo::dragEnterEvent (QDragEnterEvent *qdragenterevent)
15 14: {
16 15: qdragenterevent->accept (QTextDrag::canDecode (qdragenterevent));
17 16: }
18 17:
19 18: void
20 19: KDropDemo::dropEvent (QDropEvent *qdropevent)
21 20: {
22 21: QString text;
23 22:
24 23: if (QTextDrag::decode (qdropevent, text))
25 24: {
26 25: setText (text);
27 26: }
28 27: } |
First, you need to tell Qt that you want to accept drops by calling setAcceptDrops(true) in line 8. This instructs Qt to generate drag-enter and drop events for your application. Before you get a drop event for any given particular data type, you also need to announce that you are interested in it. You do this in the method dragEnterEvent(). The method QDragEnterEvent::accept() is called to tell Qt that you are interested in some data type. Line 15 says you are interested in receiving text drops. You can determine whether the data is actually text by calling the static method QTextDrag::canDecode() with the pointer to the instance of QDragEnterEvent as an argument in the method dropEvent() (see lines 18–27). This makes the process somewhat transparent. Qt contains classes that support text (QTextDrag) and images (QImageDrag). To accept drags of other types, you need to examine the data's MIME type (returned by the methods QDragObject::provides() or QDragObject::format()). If you plan to drag and drop custom data types within an application, you will need to create subclasses of QDragObject, which will hold data of your custom types. In dropEvent(), you do the interesting work. You decode the data into a QString using QTextDrag (line 23) and change the text of the QLabel to the new QString. Listing 7.3 presents a simple main() function that you can use to try out KDropDemo.
Example 7.3. main.cpp: Contains a main() Function Suitable for Testing the KDropDemo Widget 1
2 1: #include <kapp.h>
3 2:
4 3: #include "kdropdemo.h"
5 4:
6 5: int
7 6: main (int argc, char *argv[])
8 7: {
9 8: KApplication kapplication (argc, argv, "kdropdemotest");
10 9: KDropDemo kdropdemo (0);
11 10:
12 11: kdropdemo.show();
13 12: kapplication.setMainWidget (&kdropdemo);
14 13: return kapplication.exec();
15 14: }
16 |
You can try this widget out by dragging a file from konqueror to the window. The URL of the file will be displayed in the widget. (You may have to enlarge the window to see the entire URL.) Figure 7.1 shows the results of this drag-and-drop action. | |
The problem with starting a drag lies not in informing Qt that you would like it to be done, but in informing the user that it is possible. How can you do this? The draggable objects in Konqueror are the icons representing the files or folders. This is a common enough situation (in terms of file managers) that many users are familiar with. Netscape Navigator 4.51 places a small icon on the toolbar that is draggable and represents the page being viewed. To inform the user that they can drag this icon, a message saying so is placed in the statusbar whenever the user passes the mouse pointer over the icon. Also, a ToolTip pops up to again inform the user that the icon is draggable. (These types of help messages are discussed in the next section.) The next example shows how to start the drag process. (Because this widget is not presented in the context of an application, this example will focus on dragging and not on the problem of informing the user that the widget is a place to start dragging.) Listings 7.4–7.6 show code that demonstrates how to start a drag event.
Example 7.4. kdragdemo.h: Contains a Class Declaration for the Widget KDragDemo 1
2 1: #ifndef __KDRAGDEMO_H__
3 2: #define __KDRAGDEMO_H__
4 3:
5 4:
6 5: #include <qlabel.h>
7 6:
8 7: /**
9 8: * KDragDemo
10 9: *
11 10: **/
12 11: class KDragDemo : public QLabel
13 12: {
14 13: public:
15 14: KDragDemo (QWidget *parent, const char *name=0);
16 15:
17 16: protected:
18 17: bool dragging;
19 18:
20 19: void mouseMoveEvent (QMouseEvent *qmouseevent);
21 20: void mouseReleaseEvent (QMouseEvent *qmouseevent);
22 21: };
23 22:
24 23: #endif |
In the nomenclature of XDND, the widget you are creating is a drag source. You can drag the text to any target that accepts text drops, such as kdropdemotest. Again, we derive from QLabel but note that the technique presented here is valid for any subclass of QWidget.
Example 7.5. kdragdemo.cpp: Contains a Class Definition for the Widget KDragDemo 1
2 1: #include <qdragobject.h>
3 2:
4 3: #include <kglobalsettings.h>
5 4:
6 5: #include "kdragdemo.h"
7 6:
8 7:
9 8: KDragDemo::KDragDemo (QWidget *parent, const char *name) :
10 9: QLabel (parent, name)
11 10: {
12 11: setText ("This is draggable text.");
13 12: }
14 13:
15 14:
16 15: void
17 16: KDragDemo::mousePressEvent (QMouseEvent *qmouseevent)
18 17: {
19 18: startposition = qmouseevent->pos();
20 19: }
21 20:
22 21: void
23 22: KDragDemo::mouseMoveEvent (QMouseEvent *qmouseevent)
24 23: {
25 24: int mindragdist = KGlobalSettings::dndEventDelay();
26 25:
27 26: if (qmouseevent->state()&Qt::LeftButton
28 27: &&( qmouseevent->pos().x() > startposition.x() + mindragdist
29 28: || qmouseevent->pos().x() < startposition.x() - mindragdist
30 29: || qmouseevent->pos().y() > startposition.y() + mindragdist
31 30: || qmouseevent->pos().y() < startposition.y() - mindragdist) )
32 31: {
33 32: QTextDrag *qtextdrag = new QTextDrag( text(), this);
34 33: qtextdrag->dragCopy();
35 34: }
36 35: }
37 |
In the constructor, you set the text to be displayed in the window. The start of a drag is defined as when the user holds down the left mouse button and moves the mouse more than a certain number of pixels. That certain number is used in all KDE applications (to give them a consistent feel) and is returned by the static function KGlobalSettings::dndEventDelay(). You can implement this behavior by first saving the position at which the user first clicks the mouse, on line 18, in the method mousePressEvent(). Then, in the method mouseMoveEvent() check QMouseEvent::state() (line 26) to see whether the mouse button is being held down—in which case the bit given by Qt::LeftButton will be set in QMouseEvent()::stat()—and see whether the user has moved more than KGlobalSettings::dndEventDelay() pixels in any direction from startposition. The first time this happens, you create a QTextDrag (derived from QDragObject) object (line 20). This object handles the communication with potential XDND targets and, since it is owned by Qt, don't delete it at any point. On line 33, you indicate to the QTextDrag object that you want to allow drag-and-drop operations that result in the data being copied to the target. The types of operations are DragCopy Copy the data from the source to the target. DragMove Copy the data from the source to the target, and remove it from the source. DragDefault The mode is determined by Qt. DragCopyOrMove The default mode is used unless the user holds down the control key while dragging.
These are constants of type DragMode and are defined in qdragobject.h. To use them, call QDragObject::drag() (with a DragMode as an argument instead of dragCopy()). To avoid creating more instances of QTextDrag for each of the subsequent mouse-move events you should expect to receive, set the flag dragging to true and avoid starting new drags while this flag is still true. Reset it to false after the user releases the mouse button. This indicates the end of the drag operation regardless of whether the drop was successful. Listing 7.6 is a main() function, which you can use to create a simple program, kdragdemotest, which you can use to test the KDragDemo widget. You can try kdragdemotest by dragging to kdropdemotest. You can also drag to KEdit or Konsole. Figure 7.2 shows kdragdemotest.
Example 7.6. main.cpp: Contains a main() Function Suitable for Testing KDragDemo 1
2 1: #include <kapp.h>
3 2:
4 3: #include "kdragdemo.h"
5 4:
6 5: int
7 6: main (int argc, char *argv[])
8 7: {
9 8: KApplication kapplication (argc, argv, "kdragdemotest");
10 9: KDragDemo kdragdemo (0);
11 10:
12 11: kdragdemo.show();
13 12: kapplication.setMainWidget (&kdragdemo);
14 13: return kapplication.exec();
15 14: }
16 |
| |
| |