[C++] 16 FLTK:Callback

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

C++のCallback(JavaでいうところのActionListener)がうまくいかずに難航しています。

とりあえずヘッダファイルは書かずにソースファイルだけで簡単なCallbackができるようにしました。下記コードでは実行ボタンを押すとラジオボタン3個のbool値がFl_Multiline_Outputに出力されます。

あとFl_Multiline_Outputへの追加出力がaddやappendでは出来ません。該当するメンバ関数がなかなか見つかりませんでした。Fl_Input_という目立たない上位クラスにinsertを見つけた時は若干イラッとしました。

ユーザーが発信しているネット情報は今回の内容についてはほぼ皆無でした。ドキュメントやソースコード、公式サイト、開発当事者のサンプルコード公開サイトなどを参考に自力での解決が求められます。どうしても困ったら公式サイトの掲示板に投稿するのもありでしょう。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl_Tooltip.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Round_Button.H>
#include <FL/Fl_Radio_Round_Button.H>
#include <FL/Fl_Hor_Slider.H>
#include <FL/Fl_Multiline_Output.H>
#include "FL/Enumerations.H"

Fl_Window *window;
Fl_Box *file;
Fl_Input *input;
Fl_Group *radio_btns;
Fl_Multiline_Output *output;
Fl_Radio_Round_Button *inspect;
Fl_Radio_Round_Button *resize;
Fl_Radio_Round_Button *icns;
Fl_Box *width_label;
Fl_Input *width;
Fl_Box *height_label;
Fl_Input *height;
Fl_Button *execution;
Fl_Button *clear;

int onoff_inspect;
int onoff_resize;
int onoff_icns;

void execute(void){
    onoff_inspect = inspect->value(); 
    onoff_resize = resize->value();
    onoff_icns = icns->value();

    std::string tmp = std::to_string(onoff_inspect);
    char const *num_char = tmp.c_str();
    std::string tmp2 = std::to_string(onoff_resize);
    char const *num_char2 = tmp2.c_str();
    std::string tmp3 = std::to_string(onoff_icns);
    char const *num_char3 = tmp3.c_str();
    output->insert(num_char);
    output->insert(num_char2);
    output->insert(num_char3);
    output->insert("\n");
}

void test(Fl_Widget*,void*) {
    execute(); 
}

int main(int argc, char **argv) {
    window = new Fl_Window(100,100,360,220,"IMAGE INSPECTOR");
    window->color(fl_rgb_color(112,128,144));
    
    // file
    file = new Fl_Box(15,15,35,16,"File");
    file->labelsize(14);
    file->labelcolor(fl_rgb_color(255,239,213));
    file->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));

    input = new Fl_Input(50,10,220,25,"");

    radio_btns = new Fl_Group(50,40,90,70,"");{
        radio_btns->labelsize(12);
        // Inspect
        inspect = new Fl_Radio_Round_Button(50,40,90,20,"Inspect");
        inspect->labelcolor(fl_rgb_color(255,239,213));
        inspect->setonly();
        
        // Resize
        resize = new Fl_Radio_Round_Button(50,65,90,20,"Resize");
        resize->labelcolor(fl_rgb_color(255,239,213));
        
        // icns作成
        icns = new Fl_Radio_Round_Button(50,90,90,20,"icns作成");
        icns->labelcolor(fl_rgb_color(255,239,213));
        
    }
    radio_btns->end();

    width_label = new Fl_Box(135,70,15,10,"W");
    width_label->labelsize(12);
    width_label->labelcolor(fl_rgb_color(255,239,213));
    width_label->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));
    
    width = new Fl_Input(155,65,45,20,"");

    height_label = new Fl_Box(205,70,15,10,"H");
    height_label->labelcolor(fl_rgb_color(255,239,213));
    height_label->labelsize(12);
    height_label->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));
    
    height = new Fl_Input(220,65,45,20,"");

    execution = new Fl_Button(290,10,50,30,"実行");
    execution->color(fl_rgb_color(112,128,144));
    execution->labelcolor(fl_rgb_color(255,239,213));
    execution->labelsize(14);
    execution->callback(test);
    execution->when(FL_WHEN_RELEASE); // 省略可

    clear = new Fl_Button(290,50,50,30,"クリア");
    clear->color(fl_rgb_color(112,128,144));
    clear->labelcolor(fl_rgb_color(255,239,213));
    clear->labelsize(14);

    output = new Fl_Multiline_Output(50,115,240,100,"");

    window->end();
    window->show(argc, argv);
    return Fl::run();
}

[C++] 15 FLTK:Fl_Radio_Round_Button

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

PyQt6アプリのGUI画面を移植しました。

Fl_Round_Buttonはラジオボタン機能を持っていないただの丸いボタンだったので、Fl_Radio_Round_Buttonに変更しました。

ガワだけというのもありますが、爆速な起動に改めて感動しました。こうじゃないとC++を扱う意味がありません。

FLTKのあまりの癖の強さにGTKへの移行を考えたのですが、メンバ関数名の余計な変更など向こうの方がやりたい放題でした。やっぱりこちらのお世話になります。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl_Tooltip.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Radio_Round_Button.H>
#include <FL/Fl_Hor_Slider.H>
#include <FL/Fl_Multiline_Output.H>

int main(int argc, char **argv) {
    Fl_Window *window = new Fl_Window(100,100,360,220,"IMAGE INSPECTOR");
    window->color(fl_rgb_color(112,128,144));
    
    // File
    Fl_Box *file = new Fl_Box(15,15,35,16,"File");
    file->labelsize(14);
    file->labelcolor(fl_rgb_color(255,239,213));
    file->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));

    Fl_Input *file_input = new Fl_Input(50,10,220,25,"");

    Fl_Group *radio_btns = new Fl_Group(50,40,90,70,"");{
        radio_btns->labelsize(12);
        // Inspect
        Fl_Radio_Round_Button *inspect = new Fl_Radio_Round_Button(50,40,90,20,"Inspect");
        inspect->labelcolor(fl_rgb_color(255,239,213));
        inspect->setonly();
        // Resize
        Fl_Radio_Round_Button *resize = new Fl_Radio_Round_Button(50,65,90,20,"Resize");
        resize->labelcolor(fl_rgb_color(255,239,213));
        // icns作成
        Fl_Radio_Round_Button *icns = new Fl_Radio_Round_Button(50,90,90,20,"icns作成");
        icns->labelcolor(fl_rgb_color(255,239,213));
    }
    radio_btns->end();

    Fl_Box *width_label = new Fl_Box(135,70,15,10,"W");
    width_label->labelsize(12);
    width_label->labelcolor(fl_rgb_color(255,239,213));
    width_label->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));
    
    Fl_Input *width = new Fl_Input(155,65,45,20,"");

    Fl_Box *height_label = new Fl_Box(205,70,15,10,"H");
    height_label->labelcolor(fl_rgb_color(255,239,213));
    height_label->labelsize(12);
    height_label->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));
    
    Fl_Input *height = new Fl_Input(220,65,45,20,"");

    Fl_Button *execution = new Fl_Button(290,10,50,30,"実行");
    execution->color(fl_rgb_color(112,128,144));
    execution->labelcolor(fl_rgb_color(255,239,213));
    execution->labelsize(14);

    Fl_Button *clear = new Fl_Button(290,50,50,30,"クリア");
    clear->color(fl_rgb_color(112,128,144));
    clear->labelcolor(fl_rgb_color(255,239,213));
    clear->labelsize(14);

    Fl_Multiline_Output *output = new Fl_Multiline_Output(50,115,240,100,"");

    window->end();
    window->show(argc, argv);
    return Fl::run();
}
Fl_Radio_Round_Button
Fl_Round_Buttonではグループ化できず

[C++] 14 FLTK:Fl_Round_Button

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

PyQt6アプリのQt5への移植はシグナル/スロットの接続がうまくいかず断念しました。

Electron(JavaScript)といいQt5といいアプリ内通信に苦手意識があります。シグナルがどう伝わっていくのか、printコマンドを随所に挟んで解明していくというような手段を会得しないとどうしようもないです。

ところでQt Creatorは便利ですが想像以上の重さでした。特にクリーンの遅さには閉口しましたね。また他のIDE以上に書かされている感が強く、結局馴染めなかったです。

仕方なく9日ぶりにFLTKに戻ってきて、こちらに移植しはじめました。しかし思うのですが、FLTKのラジオボタンが四角いというのは色々尋常ではないです。FLTKが芸術作品であれば何も言いませんが、ユーザーが活用するためのものではないんですかね。

面倒でもC言語で一から開発する方が精神衛生的に良いのではないか、と思い始めています。メインはJava・Python、余興でC言語というのが私には合っているのかもしれません。

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Widget.H>
#include <FL/Fl_Tooltip.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Input.H>
#include <FL/Fl_Round_Button.H>
#include <FL/Fl_Hor_Slider.H>

int main(int argc, char **argv) {
    Fl_Window *window = new Fl_Window(100,100,360,220,"IMAGE INSPECTOR");
    window->color(fl_rgb_color(112,128,144));

    Fl_Group *area = new Fl_Group(0,0,360,220,"");{
        // file
        Fl_Box *file = new Fl_Box(15,15,35,16,"File");
        file->labelsize(12);
        file->align(Fl_Align(FL_ALIGN_INSIDE|FL_ALIGN_LEFT));
        Fl_Input *file_input = new Fl_Input(50,10,220,25,"");

        // inspect
        Fl_Round_Button *inspect = new Fl_Round_Button(50,40,90,20,"Inspect");
        inspect->labelsize(12);

    }
    area->end();
    
    window->end();
    window->show(argc, argv);
    return Fl::run();
}

[C++] 13 Qt5 GUIアプリ作成 QtWidgets

[M1 Mac, Big Sur 11.6.5]

GUI画面が出来上がりました。基本的にはPyQt6版のコードをコピペしてメソッドのピリオドを->に書き換えただけです。オブジェクト作成の際にnewを付けたり、行の最後にセミコロンを入れるのを忘れがちでした。

次はボタン動作ですが、Qtではシグナル/スロットという仕組みになっています。ざっと説明を読んだものの今ひとつピンとこないです。

#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QButtonGroup>
#include <QtWidgets/QRadioButton>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QWidget>
#include <QMainWindow>

int main(int argc, char *argv[]){
    QApplication app(argc, argv);

    QMainWindow* mainWin = new QMainWindow();
    mainWin->setGeometry(100,100,360,220);
    mainWin->setWindowTitle("IMAGE INSPECTOR");
    mainWin->setStyleSheet("background: '#708090';");

    QLabel* file = new QLabel(mainWin);
    file->setText("File");
    file->setGeometry(15,15,35,16);
    file->setStyleSheet("foreground: '#FFFAFA';font-size:12px;");

    QLineEdit* input= new QLineEdit(mainWin);
    input->setGeometry(50,10,220,25);
    
    QPushButton* execution = new QPushButton(mainWin);
    execution->setText("実行");
    execution->setGeometry(290,10,50,30);
    execution->setStyleSheet("foreground: '#FFFAFA';font-size:12px;");
    QPushButton::connect(execution, SIGNAL( clicked() ),&app, SLOT(quit()) );

    QPushButton* clear = new QPushButton(mainWin);
    clear->setText("クリア");
    clear->setGeometry(290,50,50,30);
    clear->setStyleSheet("foreground: '#FFFAFA';font-size:12px;");

    QButtonGroup* rbtns = new QButtonGroup(mainWin);
    QRadioButton* inspect = new QRadioButton(mainWin);
    inspect->setText("Inspect");
    inspect->setGeometry(50,40,90,20);
    inspect->setChecked(true);
    rbtns->addButton(inspect);
    
    QRadioButton* resize_img = new QRadioButton(mainWin);
    resize_img->setText("Resize");
    resize_img->setGeometry(50,65,90,20);
    rbtns->addButton(resize_img);

    QLabel* width_label = new QLabel(mainWin);
    width_label->setText("W");
    width_label->setGeometry(135,70,15,10);
    width_label->setStyleSheet("font-size:10px;");
    
    QLineEdit* width= new QLineEdit(mainWin);
    width->setGeometry(155,65,45,20);
    
    QLabel* height_label = new QLabel(mainWin);
    height_label->setText("H");
    height_label->setGeometry(205,70,15,10);
    height_label->setStyleSheet("font-size:10px;");
        
    QLineEdit* height= new QLineEdit(mainWin);
    height->setGeometry(220,65,45,20);
        
    QRadioButton* icns = new QRadioButton(mainWin);
    icns->setText("icns作成");
    icns->setGeometry(50,90,90,20);
    icns->setToolTip("PNG file[2048*2048,72px] required");
    rbtns->addButton(icns);
    
    QTextEdit* output = new QTextEdit(mainWin);
    output->setGeometry(50,115,240,100);
    
    mainWin->show();
    return app.exec();
}

[C++] 12 Qt5 GUIアプリ作成 QMainWindow

[M1 Mac, Big Sur 11.6.5]

Qt5でデプロイの目処が立ったので、ようやくPyQt6からの移植に着手しました。

さすが古株のウィジェットツールキットだけあってかなり前からの記事が検索でヒットしました。ただ記事の日付やバージョンを明記していないため、陳腐化した情報に振り回されることもありました。

自分もそうですがせめて記事の日付やバージョン情報をきちんと書いておかないと後進の役に立たないです。

まずはQMainWindowの座標とサイズ、背景色を設定しました。

#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QWidget>
#include <QMainWindow>

int main(int argc, char *argv[]){
    QApplication a(argc, argv);

    QMainWindow *mainWin = new QMainWindow();
    mainWin->setGeometry(100,100,360,220);
    mainWin->setWindowTitle("IMAGE INSPECTOR");
    mainWin->setStyleSheet("QMainWindow {background: '#708090';}");
    
    mainWin->show();
    return a.exec();
}

[C++] 11 Qt5 appファイル作成&デプロイ qmake

[M1 Mac, Big Sur 11.6.5]

Qt6のCMakeがまだよく分からないので、Qt5にダウングレードしてデプロイしました。Hello WorldレベルであればApple Siliconでも問題は発生しませんでした。

Java(Oracle)はノンIDE派にも優しくてユーザーへの負担が少なくなるように配慮されている印象ですが、最新のQt6については高度化が著しい上に情報が少なく、専用IDEであるQt Creatorを使わないと厳しい感じがします。いずれノンIDEで挑戦したいです。

手順は以下の通りです。

1.Qtの公式サイトに登録し、Qt5.15.2(27.2GB)をダウンロード&インストールする。

2.Qtのバイナリがあるところへパスを通す。今回は優先順位を最上位にしました。

export PATH=[HOME]/Qt/5.15.2/clang_64/bin:$PATH

3.プロジェクトディレクトリをカレントディレクトリにして、以下コマンドでMakefileとproファイルを作成する。

qmake -project && qmake

4.アイコンを設定するため、proファイルに以下内容を追記する。

# Qt5以降用
QT+=widgets

# アイコン設定
ICON = images/ImageInspector.icns
RESOURCE_FILES.files = $$ICON
RESOURCE_FILES.path = Contents/Resources
QMAKE_BUNDLE_DATA += RESOURCE_FILES

5.makeコマンドでappファイルを作成する。

6.以下コマンドでアプリをデプロイする。
macdeployqtコマンドへのパスは通っているはずなのにエラーになり、仕方なく絶対パスを使いました。サイズはHello Worldでも40MBになりました。PyQt6よりもかなり小さくなったものの、まだ大きいです。

[HOME]/Qt/5.15.2/clang_64/bin/macdeployqt ImageInspector.app/

[C++] 10 Qt6 CMakeによるビルド

[M1 Mac, Big Sur 11.6.5]

リリース用appファイルの作成方法を調べていくうちに、Qt6ではqmakeではなくCmakeというビルド自動化ソフトの使用が推奨されていることが判明しました。

せっかくMakefileを使えるようになったのにまた難物が登場しました。

とりあえず実行ファイルを作成するところまでできたので記録しておきます。

1.CMakeを公式サイトからダウンロードしてインストールする。CMakeはGUIソフトですが、Contents内にあるバイナリを直接使います。

2.CMakeバイナリのあるところにパスを通す。

export PATH=$PATH:/Applications/CMake.app/Contents/bin

3.CMakeLists.txtを作成しプロジェクト直下に置く。

cmake_minimum_required(VERSION 3.16)

project(ImageInspector VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)

find_package(Qt6 REQUIRED COMPONENTS Widgets)

add_executable(ImageInspector
    ImageInspector.cpp
)

target_link_libraries(ImageInspector PRIVATE Qt6::Widgets)

4.プロジェクト内にbuildディレクトリを作成し、カレントディレクトリをそこに移す。

5.以下コマンドでMakefileを作成する。

cmake ..

6.makeコマンドで実行ファイルを作成する。

[C++] 09 Qt6のappファイル作成 qmake

[M1 Mac, Big Sur 11.6.5]

Qt6アプリのappファイルをテスト作成しました。複雑なアプリは作らないので専用IDEのQt Creatorは使わないつもりです。

基本的にネット情報を参考に進めました。方法は以下の通りです。デフォルトでアイコンの設定がないのは意外でした。

1. HomebrewでQt6をインストールし、バイナリのあるところにパスを通す。

export PATH=$PATH:/opt/homebrew/Cellar/qt/6.2.3_1/bin

2.プロジェクトを作成する。icnsファイルはimagesディレクトリに置く。

3.プロジェクトディレクトリをカレントディレクトリにして、以下コマンドでMakefileとproファイルを作成する。

qmake -project && qmake

4.アイコンを設定するため、proファイルに以下内容を追記する。

# Qt6用
QT+=widgets

# アイコン設定
ICON = images/ImageInspector.icns
RESOURCE_FILES.files = $$ICON
RESOURCE_FILES.path = Contents/Resources
QMAKE_BUNDLE_DATA += RESOURCE_FILES

5.makeコマンドでappファイルを作成する。

6.appファイル内Contents直下のInfo.plistに以下内容を追記する。

<key>CFBundleIconFile</key>
<string>ImageInspector.icns</string>

[C++] 08 FLTKアプリのApple公証

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

FLTKアプリのApple公証はJavaアプリとは異なりappファイルへの署名だけでパスしました。署名時の–options runtimeオプションは必須でした。

Javaアプリではappファイルの中にある実行ファイルや動的ライブラリにも署名が必要だったのでこれはありがたいです。

# 署名コマンド
codesign --force --verify --verbose \
    --sign [mac-signing-key-user-name] \
    "test.app" \
    --deep \
    --options runtime \
    --entitlements entitlements.plist \
    --timestamp

# 提出コマンド
xcrun altool --notarize-app -t osx -f "test.zip" \
    --primary-bundle-id [ID] \
    -u [登録メールアドレス] \
    -p [パスワード]

[C++] 07 FLTKのMakefile作成 appファイル作成箇所変更

[M1 Mac, Big Sur 11.6.4, FLTK 1.3.8]

Info.plistにできるだけ手を入れないよう、binディレクトリの実行ファイルをMakefileがあるsrcディレクトリにコピーしてからappファイルを作成しました。

実行ファイルをコピーして最後に消去するという工程が増えるのでコードの行数自体はさほど変わらないですが、plistファイルの中はあまりいじらない方を選択しました。今回はiconファイルの設定だけに留めています。ゆくゆくはバージョン番号やコピーライトを追記します。

Makefileの隣にappファイルが作成されるため、動作確認もすぐにでき効率的になりました。

<Makefileの一部>

# oファイルから実行ファイルとappファイル作成
$(TARGET): $(OBJECTS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJECTS) $(LINK) $(LDFLAGS)
	cp $(TARGETDIR)/$(TARGET) $(TARGET)
	$(POSTBUILD) $(TARGET)
	mkdir $(TARGET).app/Contents/Resources
	cp ../images/ColorSampleJP.icns $(TARGET).app/Contents/Resources
	plutil -insert 'CFBundleIconFile' -string "test.icns" $(TARGET).app/Contents/Info.plist
	rm -f $(TARGET)