転生してないけどQtでちょろっとツールを作ってみた件

[グレンジ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/grenge) 16日目の記事を担当しました、siigと申します。
グレンジでクライアントエンジニアをしております。

プロジェクトの進行中、急ぎでツールを作る必要が生まれたので、どうやって作るか考えていたところ、
最近お勧めされていたQtで作るのはどうかと思いついたので、Qtで手早く作ってみることにしました。

#  Qt（キュート）とは 

<img width="600" alt="Qt.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/55de3a9d-e4c9-6d0f-6bd2-6c8f87128a76.png">

Qt（キュート）とはクロスプラットフォーム上でアプリケーションを作成する事ができるフレームワークです。
詳しくは公式サイトやWikipediaを参照してみてください。

公式サイト
https://www1.qt.io/jp/

Wikipedia
https://ja.wikipedia.org/wiki/Qt

# Qtのダウンロードとインストール

Qtの最新版は以下のリンク先からダウンロードする事が出来ます。

https://www.qt.io/download

ライセンスによって二つのバージョンがありますが、状況に応じて選ぶといいと思います。
今回の記事についてはmac用のオープンソース版でまとめています。

# アプリケーションを作ってみる

- 作成する内容

	作成するツールは以下の処理を行うものとします。<br>
	    1. ファイル選択ダイアログを開いて、CSVファイルを読み込む。
	    2. 変換ボタンを押すと以下の処理を行う。
	    2-1. 改行コードを半角スペースに置き換える。
	    2-2. １バイト毎に特定の文字をXORして、元のデータがぱっと見解らないようにする。
	    2-3. 加工したデータをバイナリファイルとして書き出す。

	 **※今回はアプリケーションの作成手順が目的の為にアプリケーションの中身については細かく説明しません。** 
<br>
- プロジェクトを作る

	Qt Creatorを立ち上げると以下の画面になるので、新しいプロジェクト→アプリケーション→Qtウィジェットアプリケーションを選択してプロジェクト名を入力します。
	キットはデフォルトのままでいいと思います（iOS/Androidその他のプラットフォームで作る場合は必要に応じて選択してください。）
	クラス情報もデフォルトのままでいいでしょう。
	<img width="400" alt="Qt1.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/e3ae64f2-d7c5-c932-af43-a28b15707043.png"><img width="400" alt="Qt2.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/f942e6d3-9a11-7d69-2a81-d68f409e92f1.png">
<br>
- アプリケーションの実装

1. UIパーツを配置する<br>
	- Forms/mainwindow.uiをダブルクリックするとUIの編集画面になります。
<img width="1000" alt="Qt3.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/18f8f6c5-94ff-5d24-6393-7b6ee91ecc69.png">
	- 左側にあるパーツリストからLineEditをひとつとPush Buttonをふたつ、ウィンドウ上に配置します。<br><br>
<img width="600" alt="Qt4.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/688e95c8-c198-b031-9aad-f2c630095bdc.png">
<br><br>
	- ボタンにマウスカーソルを合わせて右クリック→「スロットへ移動(※1)」を選択すると以下の画面になります。<br><br>
<img width="400" alt="Qt5.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/f87075c6-c07a-245e-a9d3-852182e9fb99.png">
<br><br>
	- 今回はボタンを押したら反応、なので「clicked()」を選びます。
	- これでボタンにシグナル(※1)が設定され、かつシグナル発生時に呼び出されるスロットがmainwindow.cpp上に自動的に用意されました。２つのボタンそれぞれに対して上記の作業を行います。<br><br>
<img width="500" alt="Qt6.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/ea55019f-9d62-cf22-2598-8bc71c5af50b.png"><br>
これでuiの作成は終わりです。メインメニューから「mainwindow.uiの保存」を選んで保存します。<br>
<br>
 **(※1) スロット・シグナルとは** <br>
https://blog.qt.io/jp/2010/06/17/signals-and-slots-2/<br>
大雑把に考えて、シグナル＝イベント、スロット＝イベントで呼び出されるコールバック、だと思ってもらえればいいと思います。<br>
<br>
2. コードを実装する

	Qtでは 普通に標準C++ライブラリも使えますが、それだとQtを使う意味がなくなってしまうので、今回はQt独自のライブラリを使うことにします。
	細かい内容はネットでぐぐってみるか以下のリファレンスを参照してみてください。

	http://doc.qt.io/qt-5/reference-overview.html

	とりあえずコーディングをさっさと進めます。
	ボタンイベントにファイルの読み込みと変換と書き込みを実装します。
	こんな感じのコードになりました。**雑です。**

```cpp:mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_pushButton_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::MainWindow *ui;

    bool readCsvFile(const QString& path);
    bool selectCsvFile();
    bool exportBinaryFile();
    QString loadCsvData(const QString& filePath);
    bool saveBinaryData(const QString& filePath, const QByteArray& array);

    QString m_filePath;
    QVector<QString> m_fileList;
};

#endif // MAINWINDOW_H
```

```c:mainwindow.cpp
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QFile>

#include "mainwindow.h"
#include "ui_mainwindow.h"

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

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

void MainWindow::on_pushButton_clicked()
{
    selectCsvFile();
}

void MainWindow::on_pushButton_2_clicked()
{
    exportBinaryFile();
}

bool MainWindow::readCsvFile(const QString& path) {
    bool result = false;
    QFileInfo fileInfo(path);

    if (fileInfo.exists() == true) {
        QString readData = loadCsvData(path);
        if (readData.size() == 0){
            return false;
        }
        QList<QString> data = readData.split("\r\n");
        m_fileList.clear();
        foreach (const QString& lineString, data) {
            QList<QString> param = lineString.split(",");
            if (param[0] == "0" || param.size() != 5) {
                continue;
            }
            m_fileList.append(lineString);
        }

        if (m_fileList.size() != 0) {
            ui->lineEdit->setPlaceholderText(path);
            result = true;
        }
    }
    return result;
}


bool MainWindow::selectCsvFile() {

    bool result = false;
    QString applicationPath = QCoreApplication::applicationDirPath();

    QString csvPath = QFileDialog::getOpenFileName(this,
                                                   tr("csvファイルを選択してください"),
                                                   applicationPath,
                                                   tr("csv file(*.csv)"));
    if (csvPath.size() == 0) {
        return false;
    }
    m_filePath = csvPath;
    QString resultText = tr("csvファイルを読み込めませんでした");

    if (readCsvFile(csvPath) == true) {
        resultText = tr("csvファイルを読み込みました");
        result = true;
    }

    QMessageBox msgBox(this);
    msgBox.setText(resultText);
    msgBox.setWindowTitle(tr("タイトル"));
    msgBox.exec();

    return result;
}

bool MainWindow::exportBinaryFile() {
    bool result = false;
    QByteArray array;

    foreach (const QString& lineString, m_fileList) {
        QStringList splitList = lineString.split(",");
        if (splitList.count() != 5){
            continue;
        }
        QString temp = QString(splitList[1] + "," + splitList[2] + "," + splitList[3] + " ").toUtf8();
        array += temp;
    }

    QByteArray maskBase = "(c)Grenge AllRights Reserved.";

    for( int i = 0 ; i < array.size() ; ++i) {
        char mask = maskBase.at(i % maskBase.size());
        array[i] = array[i] ^ mask;
    }

    int index = m_filePath.indexOf(".");
    QString newPath = m_filePath.mid(0, index) + ".pld";
    QString resultText = tr("csvファイルを書き出せませんでした");

    if (saveBinaryData(newPath, array) == true) {
        resultText = tr("pldファイルを書き出しました");
        result = true;
    }

    QMessageBox msgBox(this);
    msgBox.setText(resultText);
    msgBox.setWindowTitle(tr("タイトル"));
    msgBox.exec();

    return result;
}


QString MainWindow::loadCsvData(const QString& filePath) {
    QString data;
    QFile fp(filePath);

    if (!fp.open(QIODevice::ReadOnly)) {
        qDebug() << "csv file can't read.";
    } else {
        QTextStream st(&fp);
        data = st.readAll();
        fp.close();
    }
    return data;
}

bool MainWindow::saveBinaryData(const QString& filePath, const QByteArray& array) {
    QFile fp(filePath);
    if (!fp.open(QFile::ReadWrite)) {
        qDebug() << "pld file can't write.";
        return false;
    }
    fp.write(array);
    fp.close();
    return true;
}
```

これでコードの実装も終わりました。いよいよ動かしてみます。

# 作ったアプリケーションを動かしてみる

実装が終わったので、とりあえず動かしてみます。
QT Creator左下の「虫の絵がついた再生ボタン」アイコンをクリックするとアプリをデバッグ実行できます。

<img width="141" alt="Qt7.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/5b817c60-b435-f26e-9351-8ad10302b68e.png">

右上の小さなボタンを押すとファイル選択のダイアログが出るのでExcelで適当に作ったcsvファイルを読ませます。

<img width="467" alt="app.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/1885d9e7-f286-344e-c711-ebedd701c2d0.png"><img width="413" alt="sample.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/60f522a1-3912-2c76-a08d-bf6695efd496.png">
下の大きなボタンを押すと読み込んだCSVファイルと同じ場所に「ファイル名.pld」というファイルができている筈ですので、これをバイナリエディタで覗いてみます。

<img width="389" alt="dump.png" src="https://qiita-image-store.s3.amazonaws.com/0/322790/2a906501-d1bd-6778-2ab2-57c244729291.png">

簡単には読めなさそうなものができました。


# 配布用アプリケーションを作成してみる

作ったアプリケーションをQt Creatorを通さずにアプリケーション単体として動かしたい場合があります。
あるいは他の環境でアプリケーションを動かそうとするときに必要なライブラリが足りなくて起動できない場合があります。
コマンドラインを使って以下の手順で配布用アプリケーションを作成できます。

1. コンソールでプロジェクトのディレクトリに移動する。
2. 以下のコマンドを実行する。<br>qmake（Makefileが無い初回のみ）<br>make clean<br>qmake -config release<br>make
3. これでフォルダ内に「アプリ名.app」が出来上がっていると思います。
4. 次にこれを他に持っていけるようにします。<br>macdeployqt アプリ名.app

これで出来上がったアプリケーションを他の環境でも動かす事が可能になりました。

# おわりに

ざっとですが、Qtで簡単なアプリケーションを作ってみました。イチから全てを作る手間を考えたら本当に簡単だと思います。
私自身も、ツールで苦労した過去の自分に教えてあげたいぐらいです。
もちろん簡単なものしか作れないのではなく、より規模の大きなアプリケーションも作れると思います。

この記事を見て、Qtで何か作ってみようか、という興味を持たれたら嬉しいです。