Supporting Custom Drag Types

In the examples so far, we have relied on predefined Qt classes to hold the drag data. For example, we used QUriDrag for a file drag and QTextDrag for a text drag. Both of these classes inherit QDragObject, the base class for all drag objects. QDragObject itself inherits QMimeSource, an abstraction for providing MIME-typed data.

If we want to drag text, images, URIs, or colors, we can use Qt's QTextDrag, QImageDrag, QUriDrag, and QColorDrag classes. But if we want to drag custom data, none of these predefined classes is suitable, and so we must choose one of two alternatives:

  • We can store the drag as binary data in a QStoredDrag object.
  • We can create our own drag class by subclassing QDragObject and reimplementing a couple of virtual functions.

QStoredDrag allows us to store arbitrary binary data, so it can be used for any MIME type. For example, if we want to initiate a drag with the contents of a binary file that stores data in the (fictitious) ASDF format, we could use the following code:

void MyWidget::startDrag()
{
 QByteArray data = toAsdf();
 if (!data.isEmpty()) {
 QStoredDrag *drag = new QStoredDrag("octet-stream/x-asdf",
 this);
 drag->setEncodedData(data);
 drag->setPixmap(QPixmap::fromMimeSource("asdf.png"));
 drag->drag();
 }
}

One inconvenience of QStoredDrag is that it can only store a single MIME type. If we perform drag and drop within the same application or between multiple instances of the same application, this is seldom a problem. But if we want to interact nicely with other applications, one MIME type is rarely sufficient.

Another inconvenience is that we need to convert our data structure to a QByteArray even if the drag is not accepted in the end. If the data is large, this can slow down the application needlessly. It would be better to perform the data conversion only when the user actually drops the drag object.

A solution to both of these problems is to subclass QDragObject and reimplement format() and encodedData(), the two virtual functions used by Qt to obtain information about a drag. To show how this works, we will develop a Cell-Drag class that stores the contents of one or more cells in a rectangular QTable selection.

class CellDrag : public QDragObject
{
public:
 CellDrag(const QString &text, QWidget *parent = 0,
 const char *name = 0);

 const char *format(int index) const;
 QByteArray encodedData(const char *format) const;

 static bool canDecode(const QMimeSource *source);
 static bool decode(const QMimeSource *source, QString &str);

private:
 QString toCsv() const;
 QString toHtml() const;

 QString plainText;
};

The CellDrag class inherits QDragObject. The two functions that really matter for dragging are format() and encodedData(). It is convenient, although not strictly necessary, to provide canDecode() and decode() static functions to extract the data on a drop.

CellDrag::CellDrag(const QString &text, QWidget *parent,
 const char *name)
 : QDragObject(parent, name)
{
 plainText = text;
}

The CellDrag constructor accepts a string that represents the contents of the cells that are being dragged. The string is in the "tabs and newlines" plain text format that we used in Chapter 4 when we added clipboard support to the Spreadsheet application (p. 80).

const char *CellDrag::format(int index) const
{
 switch (index) {
 case 0:
 return "text/csv";
 case 1:
 return "text/html";
 case 2:
 return "text/plain";
 default:
 return 0;
 }
}

The format() function is reimplemented from QMimeSource to return the different MIME types supported by the drag. We support three types: comma-separated values (CSV), HTML, and plain text.

When Qt needs to determine which MIME types are provided by the drag, it calls format() with an index parameter of 0, 1, 2, ..., up until format() returns a null pointer. The MIME types for CSV and HTML were obtained from the official list, available at http://www.iana.org/assignments/media-types/

The precise order of the formats is usually irrelevant, but it's good practice to put the "best" formats first. Applications that support many formats will sometimes use the first one that matches.

QByteArray CellDrag::encodedData(const char *format) const
{
 QByteArray data;
 QTextOStream out(data);

 if (qstrcmp(format, "text/CSV") == 0) {
 out << toCsv();
 } else if (qstrcmp(format, "text/html") == 0) {
 out << toHTML();
 } else if (qstrcmp(format, "text/plain") == 0) {
 out << plainText;
 }
 return data;
}

The encodedData() function returns the data for a given MIME type. The value of the format parameter is normally one of the strings returned by format(), but we can't assume that, since not all applications check the MIME type against format() beforehand. In Qt applications, this check is usually done by calling provides() on a QDragEnterEvent or QDragMoveEvent, as we did earlier (p. 219).

To convert a QString into a QByteArray, the best approach is to use a QText-Stream. If the string contains non-ASCII characters, QTextStream will assume that the encoding is the local 8-bit encoding. (For most European countries, this means ISO 8859-1 or ISO 8859-15; see Chapter 15 for details.) It can be instructed to use other encodings by calling setEncoding() or setCodec() on the stream, as explained in Chapter 15.

QString CellDrag::toCsv() const
{
 QString out = plainText;
 out.replace("\", "\\");
 out.replace(""", "\"");
 out.replace("	", "", "");
 out.replace("
", ""
"");
 out.prepend(""");
 out.append(""");
 return out;
}

QString CellDrag::toHtml() const
{
 QString out = QStyleSheet::escape(plainText);
 out.replace("	", "");
 out.replace("
", "
");
 out.prepend("
"); out.append("

"); return out; }

The toCsv() and toHtml() functions convert a "tabs and newlines" string into a CSV or an HTML string. For example, the data

Red Green Blue
Cyan Yellow Magenta

is converted to

"Red", "Green", "Blue"
"Cyan", "Yellow", "Magenta"

or to


 
Red Green Blue
Cyan Yellow Magenta

The conversion is performed in the simplest way possible, using QString::replace(). To escape HTML special characters, we use the QStyleSheet::escape() static convenience function.

bool CellDrag::canDecode(const QMimeSource *source)
{
 return source->provides("text/plain");
}

The canDecode() function returns true if we can decode the given drag, false otherwise. For maximum flexibility, its argument is a QMimeSource. The QMimeSource class is a base class of QDragObject, QDragEnterEvent, QDragMoveEvent, and QDropEvent.

Although we provide the data in three different formats, we only accept plain text. The reason for this is that plain text is normally sufficient. If the user drags cells from a QTable to an HTML editor, we want the cells to be converted into an HTML table. But if the user drags arbitrary HTML into a QTable, we don't want to accept it.

bool CellDrag::decode(const QMimeSource *source, QString &str)
{
 QByteArray data = source->encodedData("text/plain");
 str = QString::fromLocal8Bit((const char *)data, data.size());
 return !str.isEmpty();
}

Finally, the decode() function converts the text/plain data into a QString. Again, we assume the text is encoded using the local 8-bit encoding.

If we want to be certain of using the right encoding, we could use the charset parameter of the text/plain MIME type to specify an explicit encoding. Here are a few examples:

text/plain;charset=US-ASCII
text/plain;charset=ISO-8859-1
text/plain;charset=Shift_JIS

When we use QTextDrag, it always exports UTF-8, UCS-2 (UTF-16), US-ASCII, and the local 8-bit encoding, and accepts drops from other encodings as well. Considering this, it might be smarter to implement CellDrag::decode() simply by calling QTextDrag::decode(). But even with this approach, it's still a good idea to provide a CellDrag::decode() separate from QTextDrag::decode(), in case we want to extend it later to decode another type of drag (for example, CSV drags) in addition to plain text.

Now we have our CellDrag class. To make it useful, we must integrate it with QTable. It turns out that QTable already does almost all of the work for us. All we need to do is to subclass it, call setDragEnabled(true) in our subclass's constructor, and reimplement QTable::dragObject() to return a CellDrag. Here's an example:

QDragObject *MyTable::dragObject()
{
 return new CellDrag(selectionAsString(), this);
}

We have not shown the code for the selectionAsString(), because it is the same as the core of the Spreadsheet::copy() function (p. 80).

Adding drop support to a QTable would require us to reimplement contentsDragEnterEvent() and contentsDropEvent() in the same way as we did for the Project Chooser application.

Part I: Basic Qt

Getting Started

Creating Dialogs

Creating Main Windows

Implementing Application Functionality

Creating Custom Widgets

Part II: Intermediate Qt

Layout Management

Event Processing

2D and 3D Graphics

Drag and Drop

Input/Output

Container Classes

Databases

Networking

XML

Internationalization

Providing Online Help

Multithreading

Platform-Specific Features



C++ GUI Programming with Qt 3
C++ GUI Programming with Qt 3
ISBN: 0131240722
EAN: 2147483647
Year: 2006
Pages: 140

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net