2018年4月8日日曜日

macOS ファイルオープンイベント

Finder でアプリケーションアイコンにファイルをドロップするとアプリケーションが起動されてファイルを開くプログラムです。PNG と XPM をドロップでき、アプリケーションウィンドウにも画像ファイルをドロップできるようにしています。

application.h:

#pragma once

#include <QApplication>

class Application : public QApplication
{
    Q_OBJECT

public:
    Application(int& argc, char** argv);

signals:
    void requestedFile(const QString& fileName);

protected:
    bool event(QEvent* event) override;
};
QApplication をサブクラス化し event() を再実装して QEvent::FileOpen イベントを受け取れるようにします。
application.cpp:

#include "application.h"
#include <QFileOpenEvent>

Application::Application(int& argc, char** argv)
    : QApplication(argc, argv)
{
}

bool Application::event(QEvent* event)
{
    if (event->type() == QEvent::FileOpen) {
        emit requestedFile(static_cast<QFileOpenEvent*>(event)->file());
        return true;
    }

    return QApplication::event(event);
}
QEvent::FileOpen イベントを受け取ったならば、QFileOpenEvent::file() でファイル名が分かります。 そのファイル名を実引数にして requestedFile() シグナルを送信します。
droparea.h:

#pragma once

#include <QWidget>

class QDragEnterEvent;
class QDropEvent;

class DropArea : public QWidget
{
    Q_OBJECT

public:
    explicit DropArea(QWidget* parent = 0);

public slots:
    void appendImage(const QString& fileName);

protected:
    void dragEnterEvent(QDragEnterEvent* event) override;
    void dropEvent(QDropEvent* event) override;

public:
    QSize sizeHint() const override;

private:
    QSize totalImageSize;
    int numberOfImages;
};
アプリケーションアイコンにドロップした画像ファイルを表示するウィジェットです。このウィジェットに画像ファイルをドロップできるようにもしています。
droparea.cpp:

#include "droparea.h"
#include "application.h"
#include "pixmapview.h"
#include <QApplication>
#include <QDragEnterEvent>
#include <QMimeData>
#include <QLabel>
#include <QLayout>

DropArea::DropArea(QWidget* parent)
    : QWidget(parent)
{
    numberOfImages = 0;

    setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);

    setAcceptDrops(true);

    const auto topLayout = new QVBoxLayout(this);
    topLayout->addStretch();
    topLayout->setSpacing(8);

    QObject::connect(qobject_cast<Application*>(qApp), &Application::requestedFile, this, &DropArea::appendImage);

    setAutoFillBackground(true);
    QPalette background = palette();
    background.setColor(QPalette::Window, Qt::white);
    setPalette(background);
}

void DropArea::appendImage(const QString& fileName)
{
    const QPixmap pixmap(fileName);
    if (!pixmap.isNull()) {
        auto pixmapView = new PixmapView(pixmap, fileName);

        numberOfImages += 1;
        totalImageSize.rwidth() = qMax(totalImageSize.width(), pixmapView->width());
        totalImageSize.rheight() += pixmapView->height();

        auto topLayout = qobject_cast<QVBoxLayout*>(layout());
        Q_ASSERT(topLayout);

        int left, top, right, bottom;
        topLayout->getContentsMargins(&left, &top, &right, &bottom);
        const int newWidth = totalImageSize.width() + left + right;
        const int newHeight = qMax(totalImageSize.height() + top + bottom + topLayout->spacing()*(numberOfImages - 1), height());
        setFixedSize(newWidth, newHeight);

        topLayout->insertWidget(topLayout->count() - 1, pixmapView);
    }
}

void DropArea::dragEnterEvent(QDragEnterEvent* event)
{
    if (event->mimeData()->hasUrls()) {
        event->accept();
    }
}

void DropArea::dropEvent(QDropEvent* event)
{
    for (const auto& url : event->mimeData()->urls()) {
        appendImage(url.path());
    }
    event->acceptProposedAction();
}

QSize DropArea::sizeHint() const
{
    return QSize(300, 300);
}
Application::requestedFile() シグナルを appendImage() スロットに接続して、アプリケーションアイコンにファイルがドロップされたときに画像追加がされるようにしています。

ドロップされたファイルで QPixmap のインスタンス生成が成功したならば画像ファイルと考えます。失敗したならば何もしません。画像ファイルがドロップされたときにレイアウトの最後のスペーサーの前に画像を表示する PixmapView インスタンスを追加しています。レイアウトの最後のスペーサーは addStretch() で追加しているのでできる限り広がるため画像が上部に詰められてレイアウトされます。
pixmapview.h:

#pragma once

#include <QWidget>

class PixmapView : public QWidget
{
    Q_OBJECT

public:
    PixmapView(const QPixmap& pixmap, const QString& path, QWidget* parent = 0);
};
画像ファイル一つを表示するウィジェットです。
pixmapview.cpp:

#include "pixmapview.h"
#include <QLabel>
#include <QLayout>
#include <QDebug>

PixmapView::PixmapView(const QPixmap& pixmap, const QString& path, QWidget* parent)
    : QWidget(parent)
{
    const auto topLayout = new QVBoxLayout(this);

    const auto pixmapLabel = new QLabel;
    pixmapLabel->setPixmap(pixmap);
    topLayout->addWidget(pixmapLabel);

    topLayout->addWidget(new QLabel(path));
    setFixedSize(sizeHint());

    setAutoFillBackground(true);
    QPalette background = palette();
    background.setColor(QPalette::Window, "whitesmoke");
    setPalette(background);
}
画像の下にドロップされた画像のファイル名も表示しています。
harness.h:

#pragma once

#include <QWidget>

class Harness : public QWidget
{
    Q_OBJECT

public:
    explicit Harness(QWidget* parent = 0);
};
画像を表示する DropArea をスクロールさせて表示するウィジェットです。
harness.cpp:

#include "harness.h"
#include "droparea.h"
#include <QScrollArea>
#include <QLayout>

Harness::Harness(QWidget* parent)
    : QWidget(parent)
{
    const auto topLayout = new QVBoxLayout(this);

    const auto dropArea = new DropArea(this);

    const auto scrollArea = new QScrollArea(this);
    scrollArea->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    scrollArea->resize(300, 300);
    scrollArea->setWidget(dropArea);

    topLayout->addWidget(scrollArea);
}
スクロール領域がウィンドウ全体を占めるようにしています。
main.cpp:

#include "application.h"
#include "harness.h"

int main(int argc, char** argv)
{
    Application app(argc, argv);

    Harness harness;
    harness.show();

    return app.exec();
}
main() では Harness のインスタンスを作って表示しているだけです。
Info.plist

<!DOCTYPE plist PUBLIC
    "-//Apple Computer//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>xpm</string>
            </array>
            <key>CFBundleTypeMIMETypes</key>
            <array>
                <string>image/x-xpm</string>
            </array>
            <key>CFBundleTypeName</key>
            <string>X PixMap File</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSIsAppleDefaultForType</key>
            <true/>
        </dict>
        <dict>
            <key>CFBundleTypeExtensions</key>
            <array>
                <string>png</string>
            </array>
            <key>CFBundleTypeMIMETypes</key>
            <array>
                <string>image/png</string>
            </array>
            <key>CFBundleTypeName</key>
            <string>Portable Network Graphics File</string>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>LSIsAppleDefaultForType</key>
            <true/>
        </dict>
    </array>
    <key>CFBundleIconFile</key>
    <string>appfileopen.icns</string>
    <key>CFBundleExecutable</key>
    <string>appleevents</string>
    <key>CFBundleIdentifier</key>
    <string>jp.co.reversedomainname.fileopenevent</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleExecutable</key>
    <string>AppFileOpen</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0</string>
    <key>CSResourcesFileMapped</key>
    <true/>
    <key>NSPrincipalClass</key>
    <string>NSApplication</string>
    <key>NSHighResolutionCapable</key>
    <string>True</string>
</dict>
</plist>
PNG と XPM をドロップするとアプリケーションが起動されるように設定しています。アプリケーションアイコン appfileopen.icns も設定しています。
appfileopen.pro:

!macos {
    error(Only for macOS)
}

QT += widgets

TARGET = AppFileOpen

TEMPLATE = app
SOURCES += main.cpp \
    pixmapview.cpp \
    application.cpp \
    droparea.cpp \
    harness.cpp

HEADERS += \
    pixmapview.h \
    application.h \
    droparea.h \
    harness.h

QMAKE_INFO_PLIST=Info.plist
ICON=icon/appfileopen.icns
QMAKE_INFO_PLIST に Info.plist を指定し、ICON に appfileopen.icns を指定して、ビルドするとアプリケーションバンドルの適切な場所にコピーされるようにしています。



左上のアプリケーションアイコンに PNG か XPM をドロップするとアプリケーションが起動し画像ファイルが表示されます。起動後にドロップすると画像ファイルが追加されます。


アプリケーションウィンドウに画像ファイルをドロップすると画像ファイルが追加して表示されます。Qt が扱える画像はすべて受け付けるようにしています。画像ファイル以外もドロップできますが無視して何もしません。

参考情報

QFileOpenEvent Class

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。