2012年2月1日水曜日

( Qt C++ )カスタムドラッグタイプを使用する①


はいそれでは表題の件やっていきます。

QMimeDataのサポートするものだけでプログラムを組んでいければ楽この上ないですが、しかしサポート外の独自データを含めたい場合があるかと思います。そんな時にこの記事は役に立つかと思います。

今回は C++ GUI Programming with Qt4 210ページの"Supporting Custom Drag Types"のところの1番目の項目をやっていこうと思います。

1.We can provide arbitrary data as a QByteArray using QMimeData::setData()
and extract it later using QMimeData::data().
(訳)
私たちはQMimeData::setData()を使用してQByteArrayとして任意のデータを提供することができます。QMimeData::Data()を使用して、後でそれを抽出できます。

の部分の項目をやっていきます。

サンプルはC++ GUI Programming with Qt4 211ページからのものを参考に少々変更して書いていきます。

そして、いつものようにQtCreaterの使用を前提とします。(QtCreaterなどの使い方は ”Qtをはじめよう" を見てください。)

ではさっそくコードを


(custumlist.h)
#include <QListWidget>
#include <QMouseEvent>
#include <QApplication>
#include <QTableWidget>
#include <QTextDocument>

class CustumList : public QListWidget
{
    Q_OBJECT//マクロ

public:
    explicit CustumList(QWidget *parent = 0);//コンストラクタ

protected:
    void mousePressEvent(QMouseEvent *e);//マウス左をクリックしたら呼ばれる
    void mouseMoveEvent(QMouseEvent *e);//マウスカーソルを移動したら呼ばれる
    void dragEnterEvent(QDragEnterEvent *e);//ドラッグされたものが来たら呼ばれる
    void dragMoveEvent(QDragMoveEvent *e);//ドラッグ&ドロップが操作されている間呼ばれる
    void dropEvent(QDropEvent *e);//ドロップの動作

private:
    void startDrag();//自分のウィジットでドラッグを開始した場合
    QString toCsv(const QString &plainText);//テキストをCSVに変換
    QString toHtml(const QString &plainText);//テキストをHTMLに変換
    QPoint startPos;//マウスの位置
};

(custumlist.cpp)
#include "custumlist.h"

CustumList::CustumList(QWidget *parent) :
    QListWidget(parent)//コンストラクタ
{
    //ドラッグ&ドロップを受け付ける
    //trueで許可。
    setAcceptDrops(true);
}

void CustumList::mousePressEvent(QMouseEvent *e)
{
    //マウス左ボタンをクリックしたら
    if(e->button() == Qt::LeftButton)
    {
        //そのマウスの位置を保存
        startPos = e->pos();
    }

    //デフォルトの動作へ
    QListWidget::mousePressEvent(e);
}

void CustumList::mouseMoveEvent(QMouseEvent *e)
{
    ////マウス左ボタンをクリック中なら
    if(e->buttons() & Qt::LeftButton)
    {
        //保存していたマウスの位置と現在のマウスの位置の差を求める
        //manhattanLengthは絶対値を求めるもの
        int distance = (e->pos() - startPos).manhattanLength();

        //上で求めた差分とアプリで設定してあるドラッグ&ドロップを開始する差分
        //を比べ、それ以上ならドラッグを開始する。
        //なおstartDragDistance()はデフォルトでは4が返る
        //変更はsetStartDragDistance(int)
        if(distance >= QApplication::startDragDistance())
        {
            startDrag();
        }

        QListWidget::mouseMoveEvent(e);
    }
}

void CustumList::startDrag()
{
    //現在のカーソル位置の文字列を取得
    QString plainText = currentItem()->data(0).toString();

    if (plainText.isEmpty())
        return;

    QMimeData *mimeData = new QMimeData;
    mimeData->setText(plainText);//テキストをセット。text()で抽出可
    mimeData->setHtml(toHtml(plainText));//htmlをセット。html()で抽出可
    
    //ここ重要!任意のデータをセットできる。
    mimeData->setData("text/csv", toCsv(plainText).toUtf8());

    QDrag *drag = new QDrag(this);
    drag->setMimeData(mimeData);

    if (drag->start(Qt::CopyAction | Qt::MoveAction) == Qt::MoveAction)
        delete currentItem();
}

void CustumList::dragEnterEvent(QDragEnterEvent *e)
{
    CustumList *source =
            qobject_cast<CustumList *>(e->source());

    //自分意外からのドラック&ドロップなら
    if (source && source != this)
    {
        e->setDropAction(Qt::MoveAction);
        e->accept();
    }
}

void CustumList::dragMoveEvent(QDragMoveEvent *e)
{
    CustumList *source =
            qobject_cast<CustumList *>(e->source());

    //自分意外からのドラック&ドロップなら
    if (source && source != this)
    {
        e->setDropAction(Qt::MoveAction);
        e->accept();
    }
}

void CustumList::dropEvent(QDropEvent *e)
{
    CustumList *source =
            qobject_cast<CustumList *>(e->source());

    //自分意外からのドラック&ドロップなら
    if (source && source != this)
    {
        //自分のアイテムへドロップされたものを追加
        if (e->mimeData()->hasFormat("text/csv"))//MIMEの書式チェック
        {
            //独自データ抽出
            QByteArray csvData = e->mimeData()->data("text/csv");
            QString csvText = QString::fromUtf8(csvData);

            addItem(QString("CSV : %1, HTML : %2").arg(csvText).arg(e->mimeData()->html()));
            e->setDropAction(Qt::MoveAction);
            e->accept();
        }

    }
}

//csvに整形する
QString CustumList::toCsv(const QString &plainText)
{
    QString result = plainText;

    result.replace("\\", "\\\\");
    result.replace("\"", "\\\"");
    result.replace("\t", "\", \"");
    result.replace("\n", "\"\n\"");

    result.prepend("\"");
    result.append("\"");

    return result;
}

//htmlに整形する
QString CustumList::toHtml(const QString &plainText)
{
    QString result = Qt::escape(plainText);

    result.replace("\t", "<td>");
    result.replace("\n", "\n<tr><td>");

    result.prepend("<table>\n<tr><td>");
    result.append("\n</table>");

    return result;
}

はいややこしいですね。しかし実は簡単です。見るべきところはstartDrag()とdropEvent(QDropEvent *e)の部分です。他は前前回と同じなので説明は省きます。

まずstartDrag()の部分を説明します。
まずlistの現在選択中の要素の文字列を抽出します。
そしてQMimeDataを作成し、setTextで抽出文字列をそのままセットします。
次にさらにsetHtmlで抽出文字列をtoHtml()で加工したものをセットします。
そして最後にsetDataでMIMEの型となる"text/csv"、それと抽出文字列をtoCsv()で加工したものをセットします。このsetDataでユーザ定義の型とデータを格納できます。(MIMEの型を"win"、データを"ubuntu"なんてセットすることも、もはやMIMEではありませんが格納可能です。)

この作成したMimeDataをQDragにセットしdrag->startを開始しています。

次にdropEvent(QDropEvent *e)の部分を説明します。
※このイベントまで来る過程がわからない場合は前々回の記事を参考にしてください。

まず自分以外の同ウィジットからのドラック&ドロップであることを確認するためeのsource()で取り出したものをキャストし自身の実体と比較します。もし違うならば処理を続行します。

次にeのMimeDataのフォーマットをhasFormatで確認しています。
hasFormatの引数と等しければ処理を続行します。

次にe->mimeData()->data(Mime型の文字列)でユーザ定義のデータをQByteArrayで取り出します。引数に対応したデータが返ってきます。この取り出したデータをQStringに変換し、e->mimeData()にセットされているhtmlデータと一緒にlistのアイテムとして表示しています。

※なおMIMEデータの取り出しですがsetText()でセットしたものはtext()で、setHtml()でセットしたものはhtml()で、setData()でセットしたものはdata()で取り出すことができます。その他にもありますので、詳しくはQMimeDataのリファレンスを参照してください。


実行すると以下のようになります。
(ドラッグ&ドロップ実行前)
 

(隣のカスタムリストにドラッグ&ドロップしたところ)
 


以上です。


※ちなみにリストには以下のような感じでアイテムをセットしました。
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    //以下の要領でセット
    ui->listWidget->addItem(new QListWidgetItem("Win\tMac\tLinux"));
}

MainWindow::~MainWindow()
{
    delete ui;
}