ユーザ用ツール

差分

このページの2つのバージョン間の差分を表示します。

この比較画面へのリンク

両方とも前のリビジョン前のリビジョン
次のリビジョン
前のリビジョン
ハイハイスクールアドベンチャー_qt版 [2024/02/06 11:23] – [ダイアログについて] arakiハイハイスクールアドベンチャー_qt版 [2025/10/01 05:02] (現在) – [環境非依存化] araki
行 34: 行 34:
 入出力部以外はM5版と同じである。 入出力部以外はM5版と同じである。
  
 +===== ダウンロードとインストール =====
 +
 +いくつかの環境用にビルドしたパッケージを用意してあるので、それを使ってインストールすればいろいろ面倒なことなく遊べます。
 +下にある説明に従ってビルドしてもいいかと思います。
 +
 +  * [[https://www.wildtree.jp/~araki/QHHSAdv110.zip|Windows版]]
 +  * [[https://www.wildtree.jp/~araki/qhhsadv_1.0.0_ubuntu24.04.02_amd64.deb|Ubuntu 24.04(x64)版]]
 +  * [[https://www.wildtree.jp/~araki/qhhsadv_1.0.0_ubuntu25.04_amd64.deb|Ubuntu 25.04(x64)版]]
 +  * [[https://www.wildtree.jp/~araki/qhhsadv_1.0.0_debian12_arm64.deb|Debian12(ARM64)版]]
 +
 +Windows版はインストーラを走らせるだけです。
 +
 +Linux版はdeb形式のパッケージになっています。
 +今どきのデスクトップ環境だと、メニューにも登録されるかと思います。
 +
 +ご自分の環境にあったパッケージを取得したら、
 +
 +<code bash>
 +$ sudo apt install -y ./qhhsadv_1.0.0_ubuntu24.04.02_amd64.deb
 +</code>
 +
 +などとしてインストールしてください。
 ===== ビルド ===== ===== ビルド =====
  
 QtCreatorを使用して作成したので、QtCreatorを使ってビルドするのが簡単だが、cmakeで直接ビルドすることも可能である。 QtCreatorを使用して作成したので、QtCreatorを使ってビルドするのが簡単だが、cmakeで直接ビルドすることも可能である。
 ソースはM5版同様[[https://github.com/wildtree/qhhsadv.git|GitHub]]から取得可能である。 ソースはM5版同様[[https://github.com/wildtree/qhhsadv.git|GitHub]]から取得可能である。
-[[https://www.wildtree.jp/~araki/HHSAdv_data.zip|データファイル]]は ~/.HHSAdv の下に展開する。+[[https://www.wildtree.jp/~araki/HHSAdv_data.zip|データファイル]]は実行時に自動的に ~/.local/share/WildTreeJP/QHHSAdv の下に展開される。
  
 GitHubからは git clone で取得する。 GitHubからは git clone で取得する。
-<code>+<code bash>
 $ git clone https://github.com/wildtree/qhhsadv.git $ git clone https://github.com/wildtree/qhhsadv.git
 </code> </code>
行 48: 行 70:
 cmakeでビルドするなら、 cmakeでビルドするなら、
  
-<code>+<code bash>
 $ mkdir build $ mkdir build
 $ cd build $ cd build
-$ cmake ..+$ cmake -DCMAKE_INSTALL_PREFIX=/ ..
 $ make $ make
 +$ sudo make install
 </code> </code>
  
 と、通常の cmakeの作法に従えばいい。 と、通常の cmakeの作法に従えばいい。
  
-実行は qhhsadv を実行すればいい。+実行は qhhsadv を実行する。 
 + 
 +インストールは /opt/qhhsadv に実行ファイル、リソースなどをインストールし、/usr/bin/qhhsadv に起動用のラッパーを、また、デスクトップ環境用のメニューへの追加用の設定ファイルもインストールされる、
  
 {{:hhsadv:qhhsadv.png?400|ハイハイスクールアドベンチャー}} {{:hhsadv:qhhsadv.png?400|ハイハイスクールアドベンチャー}}
行 98: 行 123:
  
 ところが、これやったら、ダイアログの左上に出てくるだけで、狙ったところにスクロールバーはつかない。 ところが、これやったら、ダイアログの左上に出てくるだけで、狙ったところにスクロールバーはつかない。
-((後で考えたらもしかすると QGridLayout *l = qobject_cast<QGridLayout*>(this->widget()->layout()); とするべきだったのかもしれない。))+((後で考えたらもしかすると QGridLayout *l = qobject_cast<QGridLayout*>(this->widget()->layout() ); とするべきだったのかもしれない。))
  
 もうなんか、アイコンのあるなしで場所代わるし、レイアウト触るんじゃなくて、表示してる QLabel の中身をスクロールバーつきの QLabelに変えちゃえばいいんじゃね? もうなんか、アイコンのあるなしで場所代わるし、レイアウト触るんじゃなくて、表示してる QLabel の中身をスクロールバーつきの QLabelに変えちゃえばいいんじゃね?
行 141: 行 166:
 Qt::WA_DeleteOnClose をセットしておくことで、ダイアログを閉じたら勝手に削除してくれるので staticな関数なのに、オブジェクトを返さずにすむ。 Qt::WA_DeleteOnClose をセットしておくことで、ダイアログを閉じたら勝手に削除してくれるので staticな関数なのに、オブジェクトを返さずにすむ。
 お手軽だが、こんなの、Qtのソースみないとわからない。 お手軽だが、こんなの、Qtのソースみないとわからない。
 +
 +==== Qt5/Qt6の互換性 ====
 +
 +主に[[uConsole]]でコード書いていた関係で、Qt5を使って開発したため、Qt6で動くかどうかとか全然ちゃんと調べてなかった。
 +[[DevTerm]]の方は逆にQt6をインストールしてあったので、そっちでビルドできるかトライしたらあっさりこけた。
 +
 +ただし、こけたのは QMediaPlayer 周り。
 +
 +音鳴らす機能はおまけなのでなくてもいい。
 +
 +とりあえず、動作チェックのために、Qt6 なら飛ばすようにした。
 +
 +<code>
 +#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
 +        _mp.setMedia(QUrl::fromLocalFile(files()->mp3_file(sound_names[n]).c_str()));
 +        _mp.setVolume(100);
 +        _mp.play();
 +#endif
 +</code>
 +
 +ざっくり、Qt6.0.0より前のバージョンの時だけコンパイルされるようにした。
 +
 +でも待って。Qt6のリファレンスにサンプルコードあるじゃん。
 +ってことで、速攻で Qt6用のコードも追加した。
 +音もちゃんと鳴った。
 +
 +<code>
 +#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
 +        _mp.setMedia(QUrl::fromLocalFile(files()->mp3_file(sound_names[n]).c_str()));
 +        _mp.setVolume(100);
 +        _mp.play();
 +#else
 +        _mp.setAudioOutput(&_audio);
 +        _mp.setSource(QUrl::fromLocalFile(files()->mp3_file(sound_names[n]).c_str()));
 +        _audio.setVolume(100);
 +        _mp.play();
 +#endif
 +</code> 
 +
 +ボリュームの制御が QAudioOutput に移されてて、あとは setMedia()が setSource()になっただけ。
 +とはいえ、違いは違い。
 +
 +==== テーマ対応 ====
 +
 +[[ハイハイスクールアドベンチャー Android版]]、[[ハイハイスクールアドベンチャー Windows版]]と、テーマ対応を成し遂げてきたのであれば、Qt版でも対応するのが筋というものでしょう。
 +
 +Qtのテーマ対応は、ちょっと見ただけだとそれほど難しくも手間でもなさそうです。
 +但し、完璧を期さないのであれば、ですが。
 +
 +
 +=== テーマ対応の概要 ===
 +
 +いくつかやり方はあるようですが、テーマファイル((QSS: Qt Style Sheet))で作って、それを適用するのが柔軟性がありそうでよさそうです。
 +
 +しかも、Windows版と違って、書くこと少なくていいです。
 +
 +<file css light.qss>
 +/* light qss */
 +QWidget {
 +        background-color: #ffffff;
 +        color: #000000;
 +}
 +
 +QPushButton {
 +        background-color: #e0e0e0;
 +        border: 1px solid #888;
 +        padding: 4px 8px;
 +}
 +</file>
 +
 +<file css dark.qss>
 +/* dark qss */
 +QWidget {
 +        background-color: #2b2b2b;
 +        color: #dddddd;
 +}
 +
 +QPushButton {
 +        background-color: #444444;
 +        border: 1px solid #666;
 +        padding: 4px 8px;
 +}
 +</file>
 +
 +<code cpp>
 +void
 +applyTheme(bool dark)
 +{
 +    QString fileName = QString(theme_file(dark).c_str());
 +    QFile f(fileName);
 +    if (f.open(QFile::ReadOnly))
 +    {
 +        QString style = QLatin1String(f.readAll());
 +        qApp->setStyleSheet(style);
 +    }
 +}
 +</code>
 +
 +=== やっぱりタイトルバー ===
 +
 +Qtもテーマ対応をしたところで、タイトルバーは「対象外」なんだとのことです。
 +もちろん、Windows版同様、カスタムタイトルバーを使うことで対応できるようです。
 +そして、Windows版同様、ボタンの実装や処理もやらないといけないようです。
 +
 +ここで問題となるのが、マルチプラットフォーム対応の場合。
 +要するに、どこまでやれるのかがまだよくわかってない((調査中))ので、一応この部分はまだ保留にしてあります。
 +
 +=== システムテーマの検出 ===
 +
 +Qt6.5.0以降は機種非依存でシステムテーマを取り出すことができる。
 +なので、この機能を使えば、機種ごとに頑張ってテーマの検出コードを書く必要がない。
 +
 +頑張って機種ごとのテーマ検出コード書くくらいなら、世の中のQtが全部6.5以降になるのを期待して、Qt6.5以降のみ、「システム追随」が機能するってことで良しとしています。
 +
 +<code cpp>
 +        bool dark = false;
 +        if (theme == ThemeType::System)
 +        {
 +#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
 +            dark = qApp->styleHints()->colorScheme() == Qt::ColorScheme::Dark;
 +#endif
 +        }
 +        else
 +        {
 +            dark = (theme == ThemeType::Dark);
 +        }
 +        this->applyTheme(dark);
 +</code>
 +
 +
 +=== QSSの限界 ===
 +
 +WindowsのXAMLでもHTMLにおけるCSSでも、コーディングなしでスライドスイッチ風ウィジェットを実現できます。
 +QtでもQSSでできるのかなあと思ってAIに聞いたら「できます」というので、やってみたら、全然できませんでした。
 +
 +最終的に、AIも「それはQSSの限界ですね」といって、あっさり、カスタムウィジェット+カスタムスタイルで逃げちゃいました。
 +
 +QSSはCSSのサブセットみたいなものですが、本当に装飾的な部分しか扱えないようです。
 +
 +=== チェックボックスをスライドスイッチに ===
 +
 +{{::qhhsadv:prefs.png?400|}}
 +
 +チェックボックスでも機能的には十分なのですが、今どき、スライドスイッチ風があるべき姿でしょう。
 +QSSでできると期待したのにできなかった奴です。
 +
 +なお、現状のバージョンはスライドスイッチをアニメーションで切り替えたりはできません。
 +ザクっとON/OFFが切り替わるやつです。
 +
 +スイッチ部品をテーマに対応させるために、プロパティだけを提供するためのラッパークラスを用意します。
 +
 +<file cpp switchbox.h>
 +#ifndef SWITCHBOX_H
 +#define SWITCHBOX_H
 +
 +#include <QObject>
 +#include <QCheckBox>
 +
 +class SwitchBox : public QCheckBox {
 +    Q_OBJECT
 +    Q_PROPERTY(QColor switchColorOn READ switchColorOn WRITE setSwitchColorOn)
 +    Q_PROPERTY(QColor switchColorOff READ switchColorOff WRITE setSwitchColorOff)
 +    Q_PROPERTY(QColor switchButtonColor READ switchButtonColor WRITE setSwitchButtonColor)
 +
 +public:
 +    explicit SwitchBox(QWidget *parent = nullptr)
 +        : QCheckBox(parent),
 +        m_on(Qt::blue),
 +        m_off(Qt::gray),
 +        m_button(Qt::white) {}
 +
 +    QColor switchColorOn() const { return m_on; }
 +    void setSwitchColorOn(const QColor &c) { m_on = c; }
 +
 +    QColor switchColorOff() const { return m_off; }
 +    void setSwitchColorOff(const QColor &c) { m_off = c; }
 +
 +    QColor switchButtonColor() const { return m_button; }
 +    void setSwitchButtonColor(const QColor &c) { m_button = c; }
 +
 +private:
 +    QColor m_on;
 +    QColor m_off;
 +    QColor m_button;
 +};
 +
 +#endif // SWITCHBOX_H
 +</file>
 +
 +これだけでは足りないので、スイッチボックス風のスタイルを提供するクラスを作ります。
 +
 +<file cpp switchstyle.h>
 +#ifndef SWITCHSTYLE_H
 +#define SWITCHSTYLE_H
 +
 +#include <QProxyStyle>
 +#include <QStyleOption>
 +#include <QStyleOptionButton>
 +#include <QPainter>
 +#include <QRect>
 +#include <QColor>
 +
 +class SwitchStyle : public QProxyStyle {
 +public:
 +    using QProxyStyle::QProxyStyle;
 +
 +    // QSS から色を取得(プロパティベース)
 +    QColor getSwitchColor(const QWidget *widget, bool checked) const {
 +        const char *propName = checked ? "switchColorOn" : "switchColorOff";
 +        QVariant colorProp = widget ? widget->property(propName) : QVariant();
 +        if (colorProp.canConvert<QColor>())
 +            return colorProp.value<QColor>();
 +        return checked ? QColor("#1E90FF") : QColor("#AAAAAA"); // デフォルト
 +    }
 +
 +    QColor getButtonColor(const QWidget *widget) const {
 +        QVariant colorProp = widget ? widget->property("switchButtonColor") : QVariant();
 +        if (colorProp.canConvert<QColor>())
 +            return colorProp.value<QColor>();
 +        return Qt::white; // デフォルト
 +    }
 +
 +    QRect subElementRect(SubElement element,
 +                         const QStyleOption *option,
 +                         const QWidget *widget) const override {
 +        if (element == SE_CheckBoxIndicator) {
 +            // チェックボックス全体の矩形
 +            QRect fullRect = option->rect;
 +
 +            // インジケータのサイズ(例:40x20)
 +            int w = 40;
 +            int h = 20;
 +
 +            // fullRect の右端にインジケータを配置
 +            int x = fullRect.right() - w;
 +            int y = fullRect.top() + (fullRect.height() - h) / 2;
 +
 +            return QRect(x, y, w, h);
 +        }
 +        return QProxyStyle::subElementRect(element, option, widget);
 +    }
 +
 +    QSize sizeFromContents(ContentsType type,
 +                           const QStyleOption *option,
 +                           const QSize &contentsSize,
 +                           const QWidget *widget) const override {
 +        QSize sz = QProxyStyle::sizeFromContents(type, option, contentsSize, widget);
 +        if (type == CT_CheckBox)
 +        {
 +            sz.setHeight(std::max(sz.height(), 24));
 +            sz.setWidth(std::max(sz.width(), 80)); // ← 幅を確保
 +        }
 +        return sz;
 +    }
 +
 +    int pixelMetric(PixelMetric metric,
 +                    const QStyleOption *option,
 +                    const QWidget *widget) const override {
 +        if (metric == PM_IndicatorWidth) return 40;
 +        if (metric == PM_IndicatorHeight) return 20;
 +        return QProxyStyle::pixelMetric(metric, option, widget);
 +    }
 +
 +    void drawPrimitive(PrimitiveElement element,
 +                       const QStyleOption *option,
 +                       QPainter *painter,
 +                       const QWidget *widget = nullptr) const override {
 +        if (element == PE_IndicatorCheckBox) {
 +            if (!option) return;
 +            QRect r = option->rect;
 +            painter->setRenderHint(QPainter::Antialiasing);
 +
 +            bool checked = option->state & State_On;
 +            QColor trackColor = getSwitchColor(widget, checked);
 +
 +            // 背景(トラック)
 +            painter->setBrush(trackColor);
 +            painter->setPen(Qt::NoPen);
 +            painter->drawRoundedRect(r, r.height()/2, r.height()/2);
 +
 +            // つまみ(白い円)
 +            int knobDiameter = r.height() - 4;
 +            int x = checked ? r.right() - knobDiameter - 2 : r.left() + 2;
 +            QRect knobRect(x, r.top() + 2, knobDiameter, knobDiameter);
 +
 +            painter->setBrush(getButtonColor(widget));
 +            painter->drawEllipse(knobRect);
 +            return;
 +        }
 +        QProxyStyle::drawPrimitive(element, option, painter, widget);
 +    }
 +};
 +
 +#endif // SWITCHSTYLE_H
 +</file>
 +
 +最初は、描画するdrawPrimitive()だけだったのですが、QSSから色情報をとりたいとか、スイッチをダイアログの右端に配置しようとしたら、右にはみ出したり、ラベルの高さより高くなってしたが削られたりしたのでsizeFromContents()を投入したり、右にはみ出さなくなったけれど、今度は、右端の部分しか表示されないのでsubElementRect()で十分な幅が得られるようにしたりと、この分量になりました。
 +
 +このスイッチは、色について三つのプロパティを持っています。
 +
 +  * qproperty-switchColorOn Onの時の背景色
 +  * qproperty-switchColorOff Offの時の背景色
 +  * qproperty-switchButtonColor ボタン色
 +
 +
 +QSSにはこんな感じで設定します。
 +
 +<code CSS>
 +SwitchBox {
 +    qproperty-switchColorOn: #4CC2FF;
 +    qproperty-switchColorOff: #CCCCCC;
 +    qproperty-switchButtonColor: #FFFFFF;
 +}
 +</code>
 +
 +使い方はこんな感じで。
 +
 +<code cpp>
 +    SwitchBox *chkSound = new SwitchBox();
 +    chkSound->setStyle(new SwitchStyle(chkSound->style()));
 +    chkSound->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
 +    chkSound->setChecked(_sound);
 +    QObject::connect(chkSound, SIGNAL(stateChanged(int)), this, SLOT(setSound(int)));
 +    QGridLayout *grid0 = new QGridLayout;
 +    grid0->addWidget(new QLabel("音を鳴らす"), 0, 0, Qt::AlignLeft);
 +    grid0->addWidget(chkSound, 0, 1, Qt::AlignRight);
 +</code>
 +
 +SwitchBox のインスタンスを作ったら、setStyleで SwitchStyleのインスタンスを割り当てます。
 +ここまでしてやるとスライドスイッチ風のウィジェットが出来上がります。
 +中身は見ての通り CheckBoxそのものなんですけれどね。
 +==== 環境非依存化 ====
 +
 +Qtはマルチプラットフォームのツールキットなので、適切にコーディングすれば、Linuxだけでなく、WindowsやMacOSでも動作させることができます。
 +
 +当初、Qt版はLinux上で開発していたため、GUIまわり以外の部分は、UNIX由来のサービスを使いまくっていたので、Windows版Qtでビルドしようとしたら、山ほどエラーが出まして、これを機に環境依存コードを排除して、全面的に書き直しました。
 +
 +ファイル入出力も、Qtのコールに書き換えました。
 +これは、WindowsとUNIX(とMacOS)で改行コードの扱いが違うことで、ファイル操作の際に余計な処理が入る関係です。
 +
 +とりあえず、WindowsとLinux ((Ubuntu 24.04LTS, Ubuntu 25.04、Debian 12(ARM64)で確認))でのビルドが可能なことは確認しました。
 +((なお、Apple系のデバイスは持ってないし買う気もないのでできるかどうかはどなたか試してください。))
 +
 +==== Windows版 ====
 +
 +Windows版を実行したら、コンソールウィンドウが開く、コンソールアプリでした。
 +調べたところ、エントリーポイントが main()だとコンソールアプリ、WinMain()だと非コンソールアプリになるとか。
 +
 +え、てことは #if とかでエントリポイントを変える感じ?
 +
 +と、思ったら他にもやり方がありました。
 +CMakeLists.txt でコントロールできるそうで。
 +qt_add_executable()に WIN32 ってつければいいんだとか。
 +結局 if(WIN32)とかで切り替えるわけですが((WIN32は関係ない環境では無視されるらしいので、別にifいらんのですが、アイコンの処理とかの関連もあるので。))、プログラム側でやるよりはすっきりして異様に思います。
 +
 +<code cmake>
 +if(WIN32)
 +    set(app_icon_resource_windows "${CMAKE_CURRENT_SOURCE_DIR}/qhhsadv.rc")
 +    list(APPEND PROJECT_SOURCES "${app_icon_resources_windows}")
 +endif()
 +
 +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
 +    if(WIN32)
 +        qt_add_executable(qhhsadv WIN32
 +            ${PROJECT_SOURCES}
 +            qhhsadv.rc
 +        )
 +    else()
 +        qt_add_executable(qhhsadv
 +            ${PROJECT_SOURCES}
 +        )
 +    endif()
 +
 +    qt_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
 +else()
 +    if(ANDROID)
 +        add_library(qhhsadv SHARED
 +            ${PROJECT_SOURCES}
 +        )
 +    else()
 +        add_executable(qhhsadv
 +            ${PROJECT_SOURCES}
 +        )
 +    endif()
 +
 +    qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${TS_FILES})
 +endif()
 +</code>
 +
 +==== リソースフォルダーのコピー ====
 +
 +リソースデータは AppLocalDataLocation/WildTreeJP/QHHSAdv の下にあることを想定しています。
 +なければ、プログラムのあるディレクトリにある data ディレクトリの中身をコピーしてから動作を開始します。
 +
 +なので、ソースツリーの中で管理されているリソースディレクトリを、ビルド時に、ビルドディレクトリにコピーしてほしいのです。
 +
 +CMakeLists.txt を利用する場合は次のようになります。
 +
 +<code cmake>
 +add_custom_command(TARGET qhhsadv POST_BUILD
 +    COMMAND ${CMAKE_COMMAND} -E copy_directory
 +            ${CMAKE_SOURCE_DIR}/data
 +            $<TARGET_FILE_DIR:qhhsadv>/data
 +)
 +</code>
 +
 +==== インストーラとパッケージ ====
 +
 +Qtはマルチプラットフォームなので、ビルド後の扱いをまじめに考えるとちょっとめんどくさい。
 +
 +基本的に、[[https://github.com/wildtree/qhhsadv.git|GitHub]]から持って行って、自分でビルドして遊んで、でもいいんですが、Linuxはともかく、WindowsにいちいちQtをインストールしてっていうのはなかなかにハードルが高く、遊んでもらえません。
 +
 +Linuxにしても、今どきのユーザはパッケージをインストールして使うスタイルの人も多いのでしょう。
 +何しろ、環境をセットアップして最初にやるべきことが build-essentialsをインストールすることなのですから。
 +
 +なので、インストールとパッケージについてあれこれ書いておきます。
 +
 +=== Linux ===
 +
 +Linuxの場合は、ビルドしてインストールすることから始めます。
 +パッケージも、インストールされたイメージをもとに作るので、まずはインストールなのです。
 +
 +make を走らせるのだから、make install すればいいだろうということですが、そのためには installターゲットが生成されなければなりません。
 +
 +Makefileは cmakeが作るのだから、cmakeに作るように指示しなければなりません。
 +
 +<code cmake>
 +if(UNIX AND NOT APPLE)
 +    # 実行ファイルを /opt/qhhsadv に配置
 +    install(TARGETS qhhsadv DESTINATION /opt/qhhsadv)
 +
 +    # data ディレクトリを /opt/qhhsadv にコピー
 +    install(DIRECTORY ${CMAKE_BINARY_DIR}/data DESTINATION /opt/qhhsadv)
 +
 +    # ラッパースクリプトを /usr/bin にインストール
 +    install(PROGRAMS ${CMAKE_SOURCE_DIR}/scripts/qhhsadv.sh
 +            DESTINATION /usr/bin
 +            RENAME qhhsadv)
 +    # .desktop ファイル
 +    install(FILES ${CMAKE_SOURCE_DIR}/resources/linux/qhhsadv.desktop
 +            DESTINATION /usr/share/applications)
 +
 +    # アイコン(48x48 の例)
 +    install(FILES ${CMAKE_SOURCE_DIR}/resources/icons/qhhsadv.png
 +            DESTINATION /usr/share/icons/hicolor/48x48/apps)
 +endif()
 +</code>
 +
 +ビルドはマルチターゲットなので、Linuxは **UNIXかつ非APPLE**という扱いになります。
 +ここに、インストールするものとその行先をずらずらと書いていきます。
 +
 +DESTINATIONに行き先を指定するわけですが、PREFIXとかつけないでいいのか?
 +といわれれば、それはcmakeがよしなにやってくれるから、気にせず、/から書いておけばいいのです。
 +
 +データリソースの扱いは、本来、UNIX系の慣習に倣うなら、/usr/share/qhhsadv の下に置くべきなのでしょうが、Windowsっぽく、プログラムバイナリのある場所にある想定なので、今更、ソースから書き換えるのもめんどくさいです。
 +
 +なので、こういう感じでインストールする先として、最近では /opt が使われるので、/opt/qhhsadv の下に、プログラムもリソースも一緒くたに置くことにしています。
 +
 +/usr/bin には /opt/qhhsadv/qhhsadv を呼び出すだけのスクリプトを置いておきます。
 +
 +あとは、
 +
 +<code bash>
 +$ cmake -DCMAKE_INSTALL_PREFIX=/ ..
 +$ make
 +$ sudo make install
 +</code>
 +
 +とすれば、インストールしてくれます。
 +
 +== パッケージング ==
 +
 +以前なら、Slackware系、RedHat系、Debian系がしのぎを削っていましたが、今どきはもうDebian系一択でいいでしょう。
 +deb形式のパッケージにまとめることを想定しています。
 +
 +<del>パッケージングするためには、仮想ルートフォルダの下にイメージをインストールします。
 +
 +<code bash>
 +$ make install DESTDIR=$PWD/package-root
 +</code>
 +
 +これで、./package-root ができて、その下にインストールされた形になります。
 +
 +次に、パッケージ用のファイルを作ります。
 +
 +<code bash>
 +$ mkdir ./package-root/DEBIAN
 +</code>
 +
 +./package-root/DEBAIN というフォルダを作ったら、その下に control という名前のファイルを作ります。
 +これは Ubuntu24.04.02 用のファイルになります。
 +
 +<file ini control>
 +Package: qhhsadv
 +Version: 1.0.0
 +Section: games
 +Priority: optional
 +Architecture: amd64
 +Maintainer: Hiroyuki Araki <wildtree@gmail.com>
 +Depends: libc6 (>= 2.31), libqt6core6t64 (>= 6.4), libqt6multimedia6 (>=6.4), libqt6widgets6t64 (>= 6.4)
 +Description: High High School Adventure for Qt
 + A Qt-based High High School Adventure implementation.
 +</file>
 +
 +あとは、パッケージの作成を行います。
 +
 +<code bash>
 +$ dpkg-deb --root-owner-group --build package-root qhhsadv_1.0.0_ubutu24.04.2_amd64.deb
 +</code></del>
 +
 +パッケージングでcontrolファイルを毎回作るの面倒くさいとAIにもちかけたら、CPackにすればいいじゃん、といわれました。
 +なにそれ?
 +
 +<code cmake>
 +if(UNIX AND NOT APPLE)
 +    # パッケージの基本情報
 +    set(CPACK_PACKAGE_NAME ${APP_NAME})
 +    set(CPACK_PACKAGE_VERSION ${APP_VERSION})
 +    set(CPACK_PACKAGE_CONTACT "wildtree@gmail.com")
 +
 +    # Debian パッケージ用のメタ情報
 +    set(CPACK_GENERATOR "DEB")
 +    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Hiroyuki Araki") # 必須
 +    set(CPACK_DEBIAN_PACKAGE_SECTION "games")
 +    set(CPACK_DEBIAN_PACKAGE_DEPENDS "libqt6core6 (>= 6.4.0), libc6 (>= 2.34), libqt6widget6 (>= 6.4.0)")
 +
 +    # アーキテクチャ指定(自動判定も可能)
 +    string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" DETECTED_ARCH)
 +
 +    if(DETECTED_ARCH STREQUAL "x86_64")
 +        set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
 +    elseif(DETECTED_ARCH STREQUAL "aarch64")
 +        set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "arm64")
 +    elseif(DETECTED_ARCH MATCHES "armv7")
 +        set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "armhf")
 +    else()
 +        set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${DETECTED_ARCH}") # fallback
 +    endif()
 +
 +    include(CPack)
 +endif()
 +</code>
 +
 +インストールターゲットとか特には必要としません。
 +パッケージの作成までは以下の手順になります。
 +
 +<code bash>
 +$ git clone https://github.com/wildtree/qhhsadv.git
 +$ cd qhhsadv
 +$ cmake -B build -DCMAKE_BUILD_TYPE=Release
 +$ cmake --build build --target all
 +$ cpack -G DEB -C Release --config build/CPackConfig.cmake
 +</code>
 +
 +インストールは dpkgでも aptでも好きなのでインストーラを走らせるだけです。
 +カレントフォルダに qhhsadv_1.1.1_Linux.deb のようなファイルができます。
 +バージョン番号は、CMakeLists.txtの頭の方に書いてあります。
 +
 +<code cmake>
 +# Application basic information
 +set(APP_NAME "qhhsadv")
 +set(APP_VERSION_MAJOR "1")
 +set(APP_VERSION_MINOR "1")
 +set(APP_VERSION_PATCH "1")
 +set(APP_VERSION "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}")
 +</code>
 +
 +<code bash>
 +$ sudo apt install -y ./qhhsadv_1.0.0_ubuntu24.04.02_amd64.deb
 +</code>
 +
 +=== Windows ===
 +
 +Windowsの場合は、Linuxのように、直接 make でインストールするのは現実的ではありません。
 +なので、インストーラを作ってそれでインストールする形をとります。
 +
 +WiXかInno Setup 6のどっちかが定番らしいですが、WiXは基本的に Visal Studio 2022のプラグインとして使うのが普通なので、Qt Creator や cmakeでやるのはちょっとめんどくさそうです。
 +
 +なので、Inno Setup 6を使うことにします。
 +Inno Setup 6は winget でインストールできます。
 +
 +<code powershell>
 +PS C:\> winget install JRSoftware.InnoSetup
 +</code>
 +
 +DEBIANのcontrol同様、Inno Setup には Setup.issという設定ファイルを作る必要があります。
 +
 +<file ini setup.iss.in>
 +; ハイハイスクールアドベンチャー インストーラ (Inno Setup)
 +
 +[Setup]
 +AppName=High High School Adventure Qt
 +AppVersion=@APP_VERSION@
 +AppPublisher=WildTreeJP
 +DefaultDirName={commonpf}\QHHSAdv
 +DefaultGroupName=ハイハイスクールアドベンチャー
 +UninstallDisplayIcon={app}\qhhsadv.exe
 +OutputBaseFilename=qhhsadv_setup
 +Compression=lzma
 +SolidCompression=yes
 +
 +[Languages]
 +Name: "japanese"; MessagesFile: "compiler:Languages\Japanese.isl"
 +Name: "english"; MessagesFile: "compiler:Default.isl"
 +
 +[Tasks]
 +Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"
 +
 +[Files]
 +; Qtアプリ本体と依存DLLをまとめたフォルダを指定
 +Source: "deploy\qhhsadv.exe"; DestDir: "{app}"; Flags: ignoreversion
 +Source: "deploy\*"; DestDir: "{app}"; Flags: recursesubdirs createallsubdirs ignoreversion
 +
 +; リソースディレクトリ
 +Source: "deploy\data\*"; DestDir: "{app}\data"; Flags: recursesubdirs createallsubdirs ignoreversion
 +
 +[Icons]
 +Name: "{group}\ハイハイスクールアドベンチャー"; Filename: "{app}\qhhsadv.exe"
 +Name: "{commondesktop}\ハイハイスクールアドベンチャー"; Filename: "{app}\qhhsadv.exe"; Tasks: desktopicon
 +
 +[Run]
 +; インストール完了後に起動するオプション
 +Filename: "{app}\qhhsadv.exe"; Description: "{cm:LaunchProgram,ハイハイスクールアドベンチャー}"; Flags: nowait postinstall skipifsilent
 +</file>
 +
 +デプロイするのに必要なファイルやフォルダを、インストーラの作業ディレクトリの下の deploy にまとめることにします。
 +この方針に合うように CMakeLists.txt に WIN32用の記述を追加していきます。
 +
 +<code cmake>
 +if(WIN32)
 +    # デプロイ先ディレクトリ
 +    set(DEPLOY_DIR ${CMAKE_BINARY_DIR}/installer/deploy)
 +
 +    # コピー対象
 +    set(APP_EXE $<TARGET_FILE:qhhsadv>)
 +    set(DATA_DIR ${CMAKE_BINARY_DIR}/data)
 +
 +    # デプロイ処理
 +    add_custom_command(
 +        TARGET qhhsadv POST_BUILD
 +        COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPLOY_DIR}
 +        COMMAND ${CMAKE_COMMAND} -E copy $<TARGET_FILE:qhhsadv> ${DEPLOY_DIR}
 +        COMMAND ${CMAKE_COMMAND} -E copy_directory ${DATA_DIR} ${DEPLOY_DIR}/data
 +        COMMENT "Copying files to installer/deploy..."
 +    )
 +
 +    # Qt DLL やプラグインを集める場合(windeployqt)
 +    # Qt bin ディレクトリを探す
 +    get_target_property(QT_QMAKE_EXECUTABLE Qt6::qmake IMPORTED_LOCATION)
 +    get_filename_component(QT_BIN_DIR "${QT_QMAKE_EXECUTABLE}" DIRECTORY)
 +
 +    set(WINDEPLOYQT_EXECUTABLE "${QT_BIN_DIR}/windeployqt.exe")
 +
 +    add_custom_command(
 +        TARGET qhhsadv POST_BUILD
 +        COMMAND "${WINDEPLOYQT_EXECUTABLE}" --dir ${DEPLOY_DIR} $<TARGET_FILE:qhhsadv>
 +        COMMENT "Running windeployqt..."
 +    )
 +
 +    add_custom_command(
 +        TARGET qhhsadv POST_BUILD
 +        COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/installer
 +        COMMENT "Copying setup.iss..."
 +    )
 +    configure_file(${CMAKE_SOURCE_DIR}/installer/setup.iss.in ${CMAKE_BINARY_DIR}/installer/setup.iss @ONLY)
 +
 +    # Inno Setup コンパイラを探す
 +
 +    find_program(INNO_SETUP_COMPILER
 +        NAMES ISCC iscc
 +        HINTS
 +            "$ENV{LOCALAPPDATA}/Programs/Inno Setup 6"
 +    )
 +    # PATH に入っている場合も探す
 +    if(NOT INNO_SETUP_COMPILER)
 +        find_program(INNO_SETUP_COMPILER NAMES ISCC iscc)
 +    endif()
 +
 +    if(INNO_SETUP_COMPILER)
 +        message(STATUS "Inno Setup found: ${INNO_SETUP_COMPILER}")
 +
 +        # .iss スクリプトの場所
 +        set(INNO_SCRIPT ${CMAKE_BINARY_DIR}/installer/setup.iss)
 +
 +        # 出力先(例: build ディレクトリ内)
 +        set(INSTALLER_OUTPUT ${CMAKE_BINARY_DIR}/installer/qhhsadv_setup.exe)
 +
 +        add_custom_command(
 +            OUTPUT ${INSTALLER_OUTPUT}
 +            COMMAND "${INNO_SETUP_COMPILER}" "${INNO_SCRIPT}"
 +            DEPENDS qhhsadv  # アプリ本体がビルドされてから実行
 +            WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
 +            COMMENT "Building Inno Setup installer..."
 +        )
 +
 +        add_custom_target(installer ALL
 +            DEPENDS ${INSTALLER_OUTPUT}
 +        )
 +    else()
 +        message(WARNING "Inno Setup (ISCC.exe) not found. Skipping installer target.")
 +    endif()
 +endif()
 +</code>
 +
 +Windows版Qtには、必要なDLLなどをまとめてくれる windeployqt.exe というツールが入っていて、これがDLLをかき集めてくれます。
 +ランタイムパッケージなどの提供がないQtなので、これはWindows版をデプロイするのには必須です。
 +
 +生成されたプログラム、リソースなどをコピーして、DLLなどをかき集めたら、ISCC.exe を呼び出して、インストーラに仕上げます。
 +
 +ここまでを、Windows環境でビルドを走らせると一気にやってくれます。
  

This website uses cookies. By using the website, you agree with storing cookies on your computer. Also, you acknowledge that you have read and understand our Privacy Policy. If you do not agree, please leave the website.

More information