Chapter 7. Further KDE Compliance

by David Sweet

In This Chapter

Complying with KDE standards requires more than using the right widgets. Applications should offer drag-and-drop support, keep track of program options, cleanly handle session management, and make use of application resources such as translated text strings. Another important aspect of KDE compliance is called network transparency. This refers to allowing users to load and save remotely stored files as easily as local ones. Fortunately, the KDE libraries contain classes which simplify these tasks.

7.1. Drag and Drop

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.)

7.1.1. Responding to Drop Events

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.


Figure 7.1. KDropDemo accepts text drop events and displays the data contained in them. Here it has just accepted a URL drop from Konqueror.


7.1.2. Starting a Drag

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.47.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.


Figure 7.2. kdragdemotest shows some draggable text. You can drag the text starting from anywhere inside the widget to a suitable target, such as kdropdemotest.



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