Qtでタスクトレイ常駐アプリをつくろう

# はじめに

[Qt Advent Calendar 2018](https://qiita.com/advent-calendar/2018/qt)の記事になります。
もともとAdvent Calendarは、クリスマスまでの日数をカウントダウンするために使われていたカレンダーで、12/1から毎日1つずつ、日付の窓を開けて中に入っている小さなお菓子やプレゼントを楽しむものらしいですねぇ。
日本では、プリキュアや戦隊モノのシールを貼った靴型のケースにお菓子が入ったものが売っていますが、小さい頃一切買ってもらえなかった事を思い出してしまい、イケメンの欧米人に生まれていたらっと思ったり思わなかったり....


(閉口休話)


さてさて6日目の本記事では、デスクトップSlackアプリ等でも使われているタスクトレイ上に表示される

- アプリのアイコン表示
- デスクトップ通知

が簡単にできちゃう

- Widgets(C++)版のQSystemTrayIcon class
- QML版のSystemTrayIcon QML Type

の機能ついてご紹介します。


# Qt Widgets を使用したタスクトレイのアプリアイコン表示とデスクトップ通知

Qt Widgetsを使用する場合には、QSystemTrayIcon classを利用して実装します。  
実装したCodeを見ながら、機能を説明していきます。

```cpp:traywidget.h
#include <QSystemTrayIcon>

class QWidget;
class QMenu;
class QAction;

class TrayWidget : public QSystemTrayIcon {
 Q_OBJECT
 public:
  explicit TrayWidget(QWidget *parent = Q_NULLPTR);

 private:
  QMenu *menu_;
  QAction *action_quit_;
  QAction *action_qt_;

 public Q_SLOTS:
  void Message();
};
```

```cpp:traywidget.cpp
#include <QMenu>
#include <QAction>
#include <QIcon>
#include <QApplication>

/// TrayWidgetクラスは、QSystemTrayIconを継承して作成しています
TrayWidget::TrayWidget(QWidget *parent)
  : QSystemTrayIcon(parent)
  , menu_(new QMenu(parent))
  , action_quit_(new QAction(tr("&Quit"), parent))
  , action_qt_(new QAction(tr("Qtちゃん"), parent))
{
  /// [1-1] タスクトレイアイコン クリックメニュー登録
  menu_->addAction(action_qt_);
  menu_->addSeparator();
  menu_->addAction(action_quit_);

  /// [1-2] タスクトレイアイコン クリックメニュー設定
  setContextMenu(menu_);
  
  /// [1-3] メニュー各項目の動作シグナル/スロット接続
  // "Qtちゃん"メニューでメッセージ表示
  connect(action_qt_, &QAction::triggered, this, &TrayWidget::Message);
  // "Quit"にて、タスクトレイアイコンアプリを閉じる
  connect(action_quit_, &QAction::triggered, qApp, &QApplication::quit);

  /// [2] 初期アイコン設定
  setIcon(QIcon(":/QtChanBack.png"));

  /// [3] タスクトレイに表示
  show();
　
  /// アイコン ダブルクリック時のメッセージ表示動作シグナル/スロット接続
  connect(this, &QSystemTrayIcon::activated,
          [this](QSystemTrayIcon::ActivationReason reason){
            if (reason == QSystemTrayIcon::DoubleClick) {
              Message();
            }
          }
  );
}


void TrayWidget::Message() {
  /// [3] デスクトップ通知
  showMessage(tr("ゆるキャラ"), tr("Qtちゃんだよ"),
                    QIcon(":/QtChan.png"), 5000 /*msec*/);
}

```

**実行結果**
![SystemTrayxcf.png](https://qiita-image-store.s3.amazonaws.com/0/7975/b07a68c0-9f97-a9da-05c6-a56dc75ccbbd.png)

それでは、ざっくりやれちゃうことを見ていきましょう。ここでのTrayWidgetクラスは、QSystemTrayIconを継承させています。

#### 1. タスクトレイアイコンの右クリックメニュー表示
QSystemTrayIcon::setContextMenu(QMenu *menu)にて、QMenuクラスを設定する事によりメニューを追加します。[1-2]
QMenuクラスは、QMenu::addAction(QAction *action)にてメニューの項目を追加していきます。各メニューの項目については、QActionクラスを生成して設定します。[1-1]
各メニュー項目についてのイベントは、Qt固有のシグナル/スロットにて接続していく感じです。[1-3]

#### 2. アプリのアイコン表示  
アイコンの設定は、QSystemTrayIcon::setIcon(const QIcon &icon)にてアイコンを登録します。上記のコードでは、リソースファイルからQtChanBack.pngを追加しています。

#### 3. デスクトップ通知
QSystemTrayIcon::(const QString &title, const QString &msg, const QIcon &icon, int msecs)にてメッセージを表示します。
各変数は、

	title : メッセージタイトル
	msg : メッセージ
	icon : メッセージ部のアイコン
	　　　　（メッセージ表示中のタスクトレイアイコンも同時に変更されます）
	msecs : メッセージ表示時間(単位：msec)
といった感じです。

# QML を使用したタスクトレイのアプリアイコン表示とデスクトップ通知

QMLでは、Qt.labs.platformを使った、SystemTrayIcon QML Typeを利用して実装します。
こちらも実装したCodeを見ながら、機能を説明していきます。

```qml:main.qml(メインQML)
import QtQuick 2.11

Item {
  SysTrayIcon {}
}
```

```qml:SysTrayIconForm.ui.qml(UI関連QML)
import QtQuick 2.11
import Qt.labs.platform 1.0

SystemTrayIcon {
  id: root_

  property alias menu_item_qtchan: menu_item_qtchan_
  property alias menu_item_quit: menu_item_quit_

  /// [1-3] タスクトレイアイコンを表示させる
  visible: true

  /// [2-1] タスクトレイに表示するアイコン設定
  iconSource: "qrc:/QtChanBack.png"

  /// [1-1] メニューの設定
  menu: Menu {
    /// [1-2] "Qtちゃん"メニュー項目設定 
    MenuItem {
      id: menu_item_qtchan_
      text: qsTr("Qtちゃん")
    }

    MenuItem { separator: true }

    /// [1-2] "Quit"メニュー項目設定 
    MenuItem {
      id: menu_item_quit_
      text: qsTr("&Quit")
    }
  }
}
```

```qml:SysTrayIcon.qml(ロジック関連)
import QtQuick 2.11
import Qt.labs.platform 1.0

SysTrayIconForm {
  id: root_

  /// アイコンクリック時のアイコン表示トグル動作用フラグ
  property bool isQtChanBack: false

  /// [A-1] "Qtちゃん"メニュートリガーイベント処理
  menu_item_qtchan.onTriggered: {
    /// [C-1] デスクトップ通知
    root_.showMessage(qsTr("ゆるキャラ"), qsTr("Qtちゃんだよ"),
                      SystemTrayIcon.Information, 5000 /*msec*/)
  }

  /// [A-1] "Quit"メニュートリガーイベント処理
  menu_item_quit.onTriggered: {
    Qt.quit()
  }

  /// [D-1] タスクトレイアイコンダブルクリック処理
  onActivated: {
    /// [D-2] ダブルクリップ時のみ動作させる
    if (reason === SystemTrayIcon.DoubleClick) {
      /// アイコン画像のトグル動作
      if (root_.isQtChanBack) {
        root_.iconSource = "qrc:/QtChanBack.png"
        root_.isQtChanBack = false
      } else {
        root_.iconSource = "qrc:/QtChan.png"
        root_.isQtChanBack = true
      }
    }
  }
}
```

**実行結果(タスクトレイ表示)**
![SystemTrayQML2.png](https://qiita-image-store.s3.amazonaws.com/0/7975/d43ce779-a864-d7b6-d6d3-756830c068ea.png)

**実行結果(デスクトップ通知 表示)**
![SystemTrayQML.png](https://qiita-image-store.s3.amazonaws.com/0/7975/4f45808a-0000-5771-dc07-2ee28602e9a3.png)
(デスクトップ通知メッセージのアイコン~~が....~~は、Qt側が用意している、情報・警告・エラーアイコンのみ対応)


それでは説明の前に、最近のQMLファイルの扱いについて説明したいと思います。
最近のQMLファイルは、

* UIフォーム用のQMLサブセットとして、${QMLタイプ名}Form.ui.qmlファイル。
* 実際の動作としてJavaScript等を使用した命令型のコード記述する、${QMLタイプ名}.qmlファイル。

と分けて記述していきます。[^1]
[^1]: [Qt Quick デザイナのワークフロー](https://blog.qt.io/jp/2011/08/09/proposal-qt-quick-designer-workflow/)
今回使用したSystemTrayIcon QMLタイプは、Qt.labs.platformモジュールを使用したものになっており、残念ながらQt CreatorのQt QuickデザイナのGUI編集に対応していませんが、QMLのサブセットファイル(拡張子：.ui.qml)にする事によって直感的にQt QuickデザイナにてGUIを作成することができるようになっていますのでオススメです。

前置きが長くなっちゃいましたが、SystemTrayIcon QMLタイプを使用したCodeを見ていきましょう

#### 1. タスクトレイアイコンの右クリックメニュー表示
SystemTrayIcon QMLタイプのmenuプロパティに、Menu QMLタイプを設定することによりメニューを追加します。[1-1] 
またMenu QMLタイプの中に、MenuItem QMLタイプにてメニューの項目を追加していきます。[1-2]
SystemTrayIcon QMLタイプのvisibleプロパティによってアイコン表示/非表示の設定が可能です。[1-3]
各メニュー項目の動作については、onTriggeredにて受ける事ができ、命令型のコード側(.qml)に記述していきます。[A-1]

#### 2. アプリのアイコン表示
SystemTrayIcon QMLタイプのアイコンの設定は、iconSourceプロパティにてアイコンの画像を登録します。[2-1]
上記のコードでは、リソースファイルからQtChanBack.pngを追加しています。

#### 3. デスクトップ通知
SystemTrayIcon QMLタイプのメソッドである
showMessage(string title, string message, MessageIcon icon, int msecs)
にてメッセージを表示させることができます。[C-1]
各変数は、

	title : メッセージタイトル
	message : メッセージ
	icon : メッセージ部のアイコン(enum MessageIcon)
	　　　　（メッセージ表示中のタスクトレイアイコンも同時に変更されます）
	msecs : メッセージ表示時間(msec)
といった感じです。

~~今回のCodeでは、iconがうまく設定できなかった為、タイトルとメッセージの表示だけにとどめています。この為、メッセージ表示時間はデフォルト値の10,000msec(10秒)となっています。誰か、iconをどうやって設定すればいいか教えて欲しかったりします.....。~~
QML側では、Qt側が提供している次のアイコンのみ提供されています。

| 定数 | 内容 |
|---|---|
| SystemTrayIcon.Information | 情報アイコン(デフォルト) |
| SystemTrayIcon.Warning | 警告アイコン |
| SystemTrayIcon.Critical | エラーアイコン |
| SystemTrayIcon.NoIcon | Codeを見た限り未対応っぽい |

#### 4. タスクトレイアイコンのダブルクリック動作
SystemTrayIcon QMLタイプのactivated(ActivationReason reason)シグナルのスロット処理を命令型のコード側(.qml)に記述していきます。[D-1]
引数である、reasonにアイコンをクリックした時のイベント定数が設定されるので、それを確認してダブルクリック(SystemTrayIcon.DoubleClick)のみに反応させます。[D-2]
イベントとしては、以下の内容が受け取れます。

| 定数 | 内容 |
|---|---|
| SystemTrayIcon.Unknown | 不明なイベント(どんな時に来るか、ちょっとわからない) |
| SystemTrayIcon.Context | コンテキストメニュー表示（アイコン 右クリック時） |
| SystemTrayIcon.DoubleClick | アイコン ダブルクリック時 |
| SystemTrayIcon.Trigger | アイコン　クリック時 |
| SystemTrayIcon.MiddleClick | アイコン マウスの中ボタンクリック時 |

# まとめ
ざっと、WidgetsとQML両方でのタスクトレイ常駐アプリについて説明していきました。
個人的には、ScriptベースのQMLの方が割りと分かりやすいCodeが書けるかなぁっと思ったりしています。

本記事で書いたCodeをGiHubにおいておきますので興味がある方はcloneして見てみてください。
https://github.com/sazus/QtSystemTray

# おまけ
今回使用した画像は、Qtのマスコットキャラクターをねらった、私一押しのゆるキャラだったりしています。まったく認知度が乏しいので広まって欲しいですね！
![Qtchan_small.png](https://qiita-image-store.s3.amazonaws.com/0/7975/11d4c0ad-ce2c-2f73-9152-34528cdd35db.png)