[C++] 23 FLTK : Fl_Inputのカーソルずれ対策

Fl_Input内のテキストを編集しようとした際にカーソルにずれがあることが発覚しました。このずれた位置からでないと正確に編集できません。

Mac版ExcelのVisual Basic Editorでも似たような現象があり、VBAからPythonに移行するきっかけとなりました。

今回も私にとっては一発レッド案件でしたが、フォントをデフォルトのFL_HELVETICAから日本語フォントに変更することで解消しました。

// main関数で設定

Fl::set_font(FL_HELVETICA, "Osaka");
修正前
修正後

[C++] 22 split関数の自製

C++にはsplit関数というものがないのでネット情報から拝借しました。他にも色々方法があります。

下記コードではsplit関数でファイル名の拡張子を取り出し、checkImage関数によりpngであれば1、jpgであれば2、それら以外は-1を返します。

C++11から採用のregex_search関数で大文字・小文字に関係なく検索しています。

C/C++は型変換が面倒ですね。C/C++を常用していれば慣れるのでしょうが、たまに使う程度だと都度調べることになるので疲れます。一覧表を用意したいところです。

#include "process_image.h"
#include <string>
#include <vector>
#include <regex>

// split関数
std::vector<std::string> split(std::string str, char del) {
    int first = 0;
    int last = str.find_first_of(del);
 
    std::vector<std::string> result;
 
    while (first < str.size()) {
        std::string subStr(str, first, last - first);
 
        result.push_back(subStr);
 
        first = last + 1;
        last = str.find_first_of(del, first);
 
        if (last == std::string::npos) {
            last = str.size();
        }
    }
	return result;
}

int checkImage(const char* filename){
	std::string filename_str = std::string(filename);
    char del = '.';
	
	std::vector<std::string> list = split(filename_str, del);
	std::string extension = list.back();

	std::regex png("png", std::regex_constants::icase);
	std::regex jpg("jpg", std::regex_constants::icase);
    std::smatch m;

	if (std::regex_search(extension, m, png)){
		return 1;
	} else if (std::regex_search(extension, m, jpg)){
		return 2;
	} else {
		return -1;
	}
}

参考サイト

[C++] 21 Makefile作例 srcディレクトリ単層

以前紹介したMakefileはsrcディレクトリ内にサブディレクトリがあるケースのものでしたが、サブディレクトリなしのMakefileを記しておきます。

# コンパイラ設定
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = $(shell pkg-config libpng --cflags)
LDFLAGS = $(shell pkg-config libpng --libs)

# includeパス設定(-I)
INCLUDE = -I./include -I/opt/homebrew/Cellar/libpng/1.6.37/include

# ライブラリパス設定(-l)
LIBRARY0 =

# 優先ライブラリパス設定(-L)
LIBRARY = -L/opt/homebrew/Cellar/jpeg/9e/lib -L/opt/homebrew/Cellar/libpng/1.6.37/lib

# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/main.cpp $(SRCDIR)/libpng_test.cpp

# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/main.o $(OBJDIR)/libpng_test.o

# 実行ファイル
TARGETDIR = ./bin
TARGET = png_mono

# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/main.o: $(SRCDIR)/main.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

$(OBJDIR)/libpng_test.o: $(SRCDIR)/libpng_test.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET): $(OBJS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY0) $(LDFLAGS) $(LIBRARY)

# コンパイル&ビルド
all: clean $(TARGET)

# ファイル削除
clean:
	rm -rf $(OBJS) $(TARGET)

[C++] 20 Exiv2 Exifデータの取得

画像ファイルのinfoデータを取得する方法について調べていたらExifデータを取得するExiv2というライブラリを見つけたので試してみました。

Homebrewからインストールも可能ですが、今回は公式サイトからtar.gzファイルをDLしました。

最初はエラー出力から自力でフラグを探っていきましたが結局うまくいかず、CPPFLAGSやLDFLAGSをコマンドで取得しMakefileを作成しました。

公式サイトにあるexifprint.cppというソースコードでテストし、Exif全データを出力させることができました。

なおPCで作成した画像ファイルのinfoデータはこのライブラリでは読み取れません。あくまで写真のメタデータ取得用です。

# コンパイラ設定他
COMPILER = clang++
DEBUG = -g

# フラグ設定
CPPFLAGS = $(shell pkg-config exiv2 --cflags )
LDFLAGS = $(shell pkg-config exiv2 --libs )

# includeパス設定
INCLUDE =

# linkパス設定
LINK =

# ライブラリパス設定
LIBRARY =

# 実行ファイル設定
TARGET = exifprint
TARGETDIR = ../bin

# ソースコードパス
SRCROOT = .

# oファイルの出力ディレクトリ
OBJROOT = ../obj

# ソースディレクトリ
SRCDIR = ../src

# オブジェクトファイル
OBJECT = $(OBJROOT)/$(TARGET).o

# cppファイルからoファイル作成
$(OBJROOT)/%.o: $(SRCROOT)/%.cpp
	$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET): $(OBJECT)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJECT) $(LINK) $(LDFLAGS) $(LIBRARY)

# コンパイル&ビルド
all: clean $(TARGET)

# ファイル削除
clean:
	rm -rf $(OBJECT) $(TARGETDIR)/$(TARGET)

作成した実行ファイルでExif全データを出力するコマンド

exifprint [画像ファイルパス]
Exif.Image.Make                              0x010f Ascii      18  NIKON CORPORATION
Exif.Image.Model                             0x0110 Ascii      11  NIKON D500
Exif.Image.Orientation                       0x0112 Short       1  1
Exif.Image.XResolution                       0x011a Rational    1  300/1
Exif.Image.YResolution                       0x011b Rational    1  300/1
Exif.Image.ResolutionUnit                    0x0128 Short       1  2
Exif.Image.Software                          0x0131 Ascii      10  Ver.1.20 

exifprint.cppソースコード

[C++] 19 FLTK:setTextとgetTextに相当する機能

FLTKでJava・SwingのsetTextとgetTextに相当する機能を探すのに少々時間がかかりました。

Swing : setText FLTK : value(const char *)
Swing : getText FLTK : value()

value関数が両方の機能を担っています。Fl_Inputの文字列を全削除する時はvalue(“”)と書きます。value()では消去ではなくコピーするので要注意です。こういった仕組みは軽量高速化のために余計な関数を設定しないという思想の表れと理解しました。

一方、Qtは速さを犠牲にしてsetTextやgetTextを導入しています。JavaやPythonからプログラミングの世界に入った方々はQtに親しみを覚え、FLTKには違和感しかないでしょう。

私もvalueの機能を知った時は脱力しましたが、一貫した設計思想に感銘を受けました。

ところで、新興言語のRustがFLTKを積極的に活用しているようでそちらに興味が向きはじめました。

C++版が完成したら次は学習を兼ねたRust版作成を課題候補にしておきます。

<関連する関数>

void inspect(void){
    output_line->insert("inspect\n");
    const char *path = input_line->value();
    output_line->insert(path);
    output_line->insert("\n");
}
void resize(void){
    output_line->insert("resize\n");
}
void icns(void){
    output_line->insert("icns\n");
}

void execute_cb(Fl_Widget*, void*) {
    onoff_inspect = inspect_rbtn->value(); 
    onoff_resize = resize_rbtn->value();
    onoff_icns = icns_rbtn->value();

    if (onoff_inspect == 1){
        inspect();
    } else if (onoff_resize == 1){
        resize();
    } else {
        icns();
    }
}
void clear_cb(Fl_Widget*, void*) {
    input_line->value("");
}

[C++] 18 FLTK:ドラッグ&ドロップ

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

一つの山場と予想されたドラッグ&ドロップですが、これについては有益なネット情報がありましたので拝借しました。

この40行のコードを書こうと思ったら今の私なら半日仕事です。とても助かりました。参考コードでauto型の存在を初めて知りました(C++11から採用)。Javaもvar型で型推論できますが、使ったことはありませんでした。

Fl_InputとセットになったFL_Box継承のBoxクラスは他のウィジェットが見えるように invisible(FL_NO_BOX)にするなど私なりに少し工夫を入れています。

ここまででappファイルのサイズはたったの942KBです。この分だと最終的には2,3MB以内に収まる感じがします。PyQt6版の100分の1です。

期待通りの爆速軽量アプリに仕上がりそうです。

<関連する関数とクラス>
class Box : Fl_Box {
  Fl_Input* input_line;
  public:
    Box(int, int, int, int, Fl_Input*);
  private:
    auto handle(int) -> int override;
};

Box::Box(int x, int y, int width, int height, Fl_Input* input) : Fl_Box(FL_NO_BOX, x, y, width, height, "") {
   this->input_line = input;
}

auto Box::handle(int event) -> int {
   switch (event) {
     case FL_DND_DRAG:
     case FL_DND_ENTER:
     case FL_DND_RELEASE:
     return 1;

     case FL_PASTE:
       input_line->value(Fl::event_text());
     return 1;

     default:
     return Fl_Box::handle(event);
  }
}

<mainクラスの一部>
input_line = new Fl_Input(50,10,220,25,"");

Box *box = new Box(0, 0, 360,220, input_line);

参考サイト

[C++] 17 FLTK:バージョン1.3.8のビルド&インストール

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8(24/2/13現在 1.3.9)]

HomebrewからApple Silicon用ビルド済みバイナリが配布されていますが、自分でビルドしたかったのでGitHubにあるバージョン1.4.0のREADME.macOS.mdを読みながら試してみました。

1.4.0ではビルドはできたもののインストールに失敗、安定版の1.3.8ではうまくいきました。

これでFLTKを自分の好きなように改変することも可能になりました。

<FLTK 1.3.8のインストール方法>
事前にCMakeなど必要なツールをインストールしておく。

1.公式サイトからバージョン1.3.8のtar.gzファイルをダウンロードする。

2.ファイルを解凍してfltkフォルダ内にディレクトリを作成し、cmakeコマンドを実行する。

mkdir build
cd build
mkdir Makefile
cd Makefile
cmake -G "Unix Makefiles" \
    -D OPTION_USE_SYSTEM_LIBJPEG=Off \
    -D OPTION_USE_SYSTEM_ZLIB=Off \
    -D OPTION_USE_SYSTEM_LIBPNG=Off \
    -D OPTION_USE_THREADS=ON \
    ../..

2024/2/13 オプション追加:
-D OPTION_USE_THREADS=ONによりマルチスレッドサポートを有効にする。Fl::lockなどが使えるようになる。

3.ビルドする。

make

4.デモアプリを起動する(省略可)。

open bin/test/demo.app

5.インストールする。[/usr/local/bin]

sudo make install

[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();
}