[C++] 151 FLTK : Fl_Multiline_OutputとFl_Text_Displayの比較

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

Fl_MultiLine_OutputにFl_Scrollbarを内包させられるかどうか公式ドキュメントを調べたところ、そもそもの性質が合わないために難しいことが判明しました。

調べた結果を以下の図にまとめました。違和感を覚えていたFl_Groupの命名の意味がようやく理解できました。言っても仕方ないですが、Fl_Containerとでも命名してくれていたら理解は早かったと思います。

Fl_Input_群は行数を把握できない低機能なウィジェットであることが分かりました。行数が分からないのではFl_Scrollbarを内包できないです。

ただFl_ScrollをコンテナとしてFl_Multiline_Outputを中に配置した場合に適切に動作するよう調整が可能なのか、確認しておく必要があります。

FLTKのクラス名をJava的な感覚で解釈すると痛い目にあいますね。Swingがいかに小慣れた巧みな命名をしているか、Swingの方がむしろ特殊で突出して優れていると見るべきでしょうか。だからこそ一時代を築くことが出来たのだと思います。

A

[C++] 150 FLTK : Fl_Multiline_Outputにスクロールバー配置 未完成

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

Fl_Multiline_Outputにスクロールバーを配置することは可能ですが、テキスト行数とは連動せず使えません。Fl_Scrollの高さを短くすると動くことは動くものの、上端から下端までカバーできないです。

Fl_Text_Displayのソースコードを読み、Fl_Multiline_Outputに内包できるかどうか検討してみます。

水色のFl_Multiline_Outputはテキスト行が増えてもスクロールバーに変化なし
Fl_Scroll* scroll = new Fl_Scroll(10,165,340,130,"My Output");
    scroll->type(Fl_Scroll::VERTICAL_ALWAYS);
    scroll->align(Fl_Align(FL_ALIGN_TOP_RIGHT));
    scroll->labelsize(10);
    scroll->labelcolor(fl_rgb_color(255,255,255));
    
Fl_Multiline_Output* outputLine = new Fl_Multiline_Output(10,165,340,130);
    outputLine->color(fl_rgb_color(188,226,232)); // みずいろ
    outputLine->textsize(12);
    outputLine->type(FL_MULTILINE_OUTPUT_WRAP);
    outputLine->labelsize(10);
    outputLine->labelcolor(fl_rgb_color(0,0,0));
    
scroll->end();

[C++] 149 FLTK : スクロールバー内包ウィジェット Fl_Text_Display

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

Fl_Multiline_Outputがスクロールバーを内包しないことを知り少なからず落胆しましたが、気を取り直してFl_Text_DisplayでGUIを再構築しました。

字面からのイメージで適当に組んでみたら、たまたま上手くいきました。

表示文字列のstringを作成してFl_Text_Bufferに放り込み、Fl_Text_Displayで表示するという流れです。

初期状態はWindowと同じ色ですが、Fl_Text_Bufferを読み込むと設定した色に変わります。

Fl_Text_Display *textDisplay;
Fl_Text_Buffer *textBuffer;
string bufferstr;

void formatConvert(){
    string path2;
    textBuffer = new Fl_Text_Buffer(0, 12800);

    // フォーマットリストのindex取得
    int formatNum = choice->value();

    // 変換元のファイルパス取得
    const char* path = input_line->value();
    cout << "path "<< path << endl;

    // stringへ変換
    string path_str = string(path);

    // ファイルパスに半角スペースが含まれる場合はアンダースコアに置き換えたファイル名に変更
    if (path_str.find(" ") != std::string::npos)
    {
        path2 = spt.splitJoin2(path_str, " ", "_", 0, -1);

        // 元ファイルをリネーム
        rename(path, path2.c_str());
    } else {
        path2 = path_str;
    }

    cout << "path2 "<< path2 << endl;

    // 自製ライブラリSplitによりファイル名から拡張子を削除
    string prefix = spt.splitJoin(path2, ".", 0, -2);
    cout << "prefix "<< prefix << endl;

    // 変換先のファイルパス作成
    string path3 = prefix + fmt[formatNum];
    cout << "path3 "<< path3 << endl;

    // ファイル変換コマンド作成
    string cmd = "/opt/homebrew/Cellar/ffmpeg/5.1/bin/ffmpeg -y -i " + path2 + " -f " + fmt[formatNum] + " " + path3 + " && echo ffmpeg完了";
    cout << "cmd: "<< cmd << endl;

    string appendstr = "cmd: " + cmd + "\n";
    bufferstr += appendstr;
    cout << "bufferstr: " << bufferstr << endl;

    textBuffer->append(bufferstr.c_str());
    textDisplay->buffer(textBuffer);

    outputTextMake(cmd);
    browser->load(outputText);
    int line_num = browser->size();
    browser->bottomline(line_num);

    cout << "フォーマット変換完了!" << endl;
}
Fl_Text_Display* textDisplay = new Fl_Text_Display(10,165,340,130,"My Output");
    textDisplay->color(fl_rgb_color(188,226,232)); // みずいろ
    textDisplay->textsize(12);
    textDisplay->wrap_mode(Fl_Text_Display::WRAP_AT_BOUNDS, 0); // 折り返し設定
    textDisplay->labelsize(10);
    textDisplay->labelcolor(fl_rgb_color(255,255,255));
    textDisplay->align(Fl_Align(FL_ALIGN_TOP_RIGHT));

[C++] 148 FLTK : Fl_Multiline_Outputはスクロールバーを内包しない

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

ビデオツールアプリを作り込んでいますが、ここにきてFl_Multiline_Outputがスクロールバーを内包しないことが判明しました。まさかの事態です。

Fl_Text_Displayがスクロールバーを内包しているので取りあえずこれに置き換えましたが、リッチテキスト用ですからこれまでとは流儀が全く異なります。まず背景色を以前の水色に設定できません。insert関数の使い方も違うようです。

FLTKについては色々クセが強いところに慣れながら使ってきたものの、メインとして使うには時間的コストが過大です。

先日導入したwxWidgetsを試してみたいところです。

[C++] 147 FLTK : Fl_Multiline_Outputの折り返し設定

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

結構長い期間の懸案だったFl_Multiline_Outputの折り返し設定ができました。

公式サイトの”Fl_Input_ Class Reference” – “Detailed Description”に説明がありました。上位クラスにあるとは思っていましたが、Fl_InputではなくFl_Input_なのが盲点でした。ちなみに折り返しは英語ではwrapになります。

Fl_Multiline_Output outputLine = new Fl_Multiline_Output(10,165,340,130,"My Output");
    outputLine->color(fl_rgb_color(188,226,232)); // みずいろ
    outputLine->labelcolor(fl_rgb_color(255,255,255));
    outputLine->textsize(12);
    outputLine->align(Fl_Align(FL_ALIGN_TOP_RIGHT)); // ラベルの配置
    outputLine->type(FL_MULTILINE_OUTPUT_WRAP); // 折り返し設定
    outputLine->labelsize(10);

[C++] 146 FLTK : アプリ内コマンドは絶対パス FFmpeg

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

ffmpegコマンドをアプリ内で実行する際、コマンドは絶対パスにしないと動作しません。コンソール付き実行ファイルではコマンド名だけで動作するものの、appファイルでは動かないです。

ffmpegコマンドの標準出力はpopenで捉えられませんでしたが、movからmp4への変換自体はできたのでよしとします。

22/8/18追記:
FFmpegの公式サイトからffmpeg-107780-g6ded80af92.7zをダウンロードして実行ファイルを使ってみたところ、MacOSがカーネル機能拡張されたとかでパニック修復ブートにて再起動し、その後再起動を繰り返すようになりました。
ディスクユーティリティのFirst Aidでは直らなかったので、TimeMachineで復元し、復活させました。偶然かもしれませんが、取扱は慎重にした方が良さそうです。MacOSの場合はHomebrewからもインストールできます。こちらは問題ありませんでした。
これまでFirst Aidが修復に貢献したことは一度もなく時間の無駄なので、今後は即TimeMachineで復元します。走り出したら中断できないのも低評価です。

void outputTextMake(string cmd){
    char str[READ_SIZE];
    size_t ret;

    FILE* fp = popen(cmd.c_str(), "r");
    FILE* fo = fopen(outputText, "a+");

    while(1){
        ret = fread(str, sizeof(char), READ_SIZE, fp);
        fwrite(str, sizeof(char), ret, fo);
        
        if(ret < READ_SIZE){
            break; 
        } 
    }

    // 文字列追記
    string finish = "変換完了\n";
    fprintf(fo, "%s", finish.c_str());

    pclose(fp);
    fclose(fo);
}

void formatConvert(){
    string path2;

    // フォーマットリストのindex取得
    int formatNum = choice->value();

    // 変換元のファイルパス取得
    const char* path = input_line->value();
    cout << "path "<< path << endl;

    // stringへ変換
    string path_str = string(path);

    // ファイルパスに半角スペースが含まれる場合はこれをアンダースコアに置換したファイル名に変更
    if (path_str.find(" ") != std::string::npos)
    {
        path2 = spt.splitJoin2(path_str, " ", "_", 0, -1);

        // 元ファイルをリネーム
        rename(path, path2.c_str());
    } else {
        path2 = path_str;
    }

    cout << "path2 "<< path2 << endl;

    // 自製ライブラリSplitによりファイル名から拡張子を削除
    string prefix = spt.splitJoin(path2, ".", 0, -2);
    cout << "prefix "<< prefix << endl;

    // 変換先のファイルパス作成
    string path3 = prefix + fmt[formatNum];
    cout << "path3 "<< path3 << endl;

    // ファイル変換コマンド作成(ffmpegは絶対パス)
    string cmd = "/opt/homebrew/Cellar/ffmpeg/5.1/bin/ffmpeg -i " + path2 + " -f " + fmt[formatNum] + " " + path3 + " && echo ffmpeg完了";
    cout << "cmd: "<< cmd << endl;
    output_line->insert("cmd: ");
    output_line->insert(cmd.c_str());
    output_line->insert("\n");

    outputTextMake(cmd);
    browser->load(outputText);

    cout << "フォーマット変換完了!" << endl;
}

[C++] 145 FLTK : Fl_Browserの最終行を常に表示 

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

すぐに分かるだろうと思っていたものの、そうでもなかったです。結構重要な機能のはずですが、かなりそっけない扱いです。まあSwingと比べてしまうのは酷でしょうか。

browser->load(outputText);

int line_num = browser->size(); // 全行数取得

browser->bottomline(line_num);

[C++] 144 FLTK : 標準出力の表示 Fl_Browser

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

コンソールコマンドによるファイル検索結果をFl_Multiline_Outputに表示しようとしてうまくいかず苦慮していましたが、検索結果を一旦テキストファイルとして出力し、Fl_Browserに読み込ませるとすんなりできました。テキストファイルはホームディレクトリ直下に置きました。

popen関数を使った場合はpclose関数で、fopen関数はfclose関数で閉じます。popenをfcloseで閉じようとしたために2回目以降の検索結果が表示されず、少し手間取ってしまいました。

これまではFl_Multiline_Outputがまともに使えない状態だったため、やむなくコンソール付き実行ファイルを使っていましたが、これでappファイルにて完結できるようになりました。

#define READ_SIZE 12800 // 検索結果は最大12800バイトまで

const char* outputText;

void outputTextMake(string cmd){
    char str[READ_SIZE];
    size_t ret;

    FILE* fp = popen(cmd.c_str(), "r"); // コマンドに対する出力をFILE構造体として受け取る
    FILE* fo = fopen(outputText, "a+"); // 検索結果を追記していく,起動時に削除初期化

    while(1){
        ret = fread(str, sizeof(char), READ_SIZE, fp);
        fwrite(str, sizeof(char), ret, fo);
        
        if(ret < READ_SIZE){
            break; 
        } 
    }

    // 文字列追記
    string finish = "検索完了\n";
    fprintf(fo, "%s", finish.c_str());

    pclose(fp);
    fclose(fo);
}
// 出力ファイル名作成
const char* homedir = getenv("HOME");
cout << "homedir " << homedir << endl;

string outputText_str = string(homedir) + "/_filefinder.txt"; // 検索結果記録ファイルパス
outputText = outputText_str.c_str();

remove(outputText); // 削除初期化
Fl_Browser *browser;
stringstream cmd;

cmd << "cd " << [選択したディレクトリパス] << " && " << "find `pwd` -type file ";
outputTextMake(cmd.str());

browser->load(outputText);

[C++] 143 FLTK : ダブルクリック時の動作 その2 Fl::event_clicks()

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

[C++] 138の続きです。

Fl_File_Browserをダブルクリックして上下層のディレクトリをブラウズできるようにしました。

Fl_File_Chooser::directory関数がポインタ演算を使っていてとても読みにくくメンテナンス性も低くなるため、この関数は使わないようにしました。ポインタ演算はかなりハード寄りの表現なので、オープンソースでは使わない方がいいように思います。

extern Fl_Input *dirInput;
extern FileChooser* chooser;
extern const char*  dir_; // FileChooser内カレントディレクトリ

char selectPath[FL_PATH_MAX]; // 選択ファイルパス

vector<string> split(const string& str, char delim){
    std::istringstream iss(str);
    string tmp;
    vector<string> res;
    while (getline(iss, tmp, delim)) res.push_back(tmp);
    return res;
}

void FileBrowserCB()
{
	char *dirName;
	const char* delim2 = "/";

	// クリックしたディレクトリ名を取得
	dirName = (char *)FileBrowser->Fl_Browser::text(FileBrowser->Fl_Browser::value());
	cout << "dirName " << dirName << endl;

	if (!dirName) return;

	string dirNameStr = string(dirName);

	// ディレクトリ名を絶対パスに変換
	if (dirNameStr.compare("../") == 0){ // "../"をクリックした場合は上層へ移行
		string dirStr = string(dir_);
		cout << "dirStr " << dirStr << endl;

		vector<string> dirStrList = split(dirStr);
		dirStrList.pop_back();

		std::ostringstream os;
		std::copy(dirStrList.begin(), dirStrList.end(), std::ostream_iterator<std::string>(os, delim2));
		std::string dirUpper = os.str();

		cout << "dirUpper " << dirUpper << endl;

		strcat(selectPath, dirUpper.c_str());
	} else {
		strcat(selectPath, dir_);
		strcat(selectPath, dirName);
	}

	cout << "selectPath " << selectPath << endl; 

	if (Fl::event_clicks()) {
		cout << "ダブルクリックしました" << endl;

		// 選択したディレクトリの内容を表示し、カレントディレクトリdir_を更新
		if (std::filesystem::is_directory(selectPath)){
			FileBrowser->load(selectPath);

			char* dir_0 = new char[strlen(selectPath) + 1];
    		strcpy(dir_0, selectPath);
			dir_ = dir_0;
		}

		if (string(dir_).compare("/") == 0){
			cout << "この階層が上限です" << endl;
			return;
		}

		cout << "ダブルクリック後 dir_ " << dir_ << endl;

		selectPath[0] = '\0';
		return;
	} 

	//選択ディレクトリ名を表示
	inputFileName->value("");
	inputFileName->value(selectPath);

	// カレントディレクトリdir_確認
	cout << "dir_ " << dir_ << endl;

	selectPath[0] = '\0';
}

void btnOKCB(Fl_Return_Button*, void*)
{
	const char* dir = inputFileName->value();
	dirInput->value(dir);
	chooser->Fl_Window::hide();
}

[C++] 142 FLTK : 線の描画 Lineクラスの改良

[M1 Mac, Big Sur 11.6.8, clang 13.0.0, FLTK 1.3.8, NO IDE]

前回の続きです。

他のウィジェットと同様に後から色や線種などを変えられるようにしました。

Lineクラス作成を通してFl_Widgetの仕組みが理解できたのは収穫でした。

右の縦線は角のある実線
#pragma once
#include <FLstd.h>
#include <cppstd.h>

class Line: public Fl_Widget
{
	int styletype_, stylewidth_;
	Fl_Color color_;
public:
	enum {
	FL_SOLID = 0 , FL_DASH = 1 , FL_DOT = 2 , FL_DASHDOT = 3 ,
	FL_DASHDOTDOT = 4 , FL_CAP_FLAT = 0x100 , FL_CAP_ROUND = 0x200 , FL_CAP_SQUARE = 0x300 ,
	FL_JOIN_MITER = 0x1000 , FL_JOIN_ROUND = 0x2000 , FL_JOIN_BEVEL = 0x3000
	};
	
	Line(int x,int y, int w,int h);
	~Line();
	void draw();

	Fl_Color color() const {return color_;}
	void color(Fl_Color bg) {color_ = bg;}

	vector<int> style() const {return {styletype_, stylewidth_};}
	void style(int type, int width) {styletype_= type; stylewidth_= width;}
};
#include <FLstd.h>
#include <cppstd.h>
#include <Line.h>

Line::Line(int x,int y, int w,int h):Fl_Widget(x,y,w,h){
	styletype_ = FL_SOLID;
	stylewidth_ = 1;
	color_	 = FL_GRAY;
}
Line::~Line(){}
void Line::draw(){
	Fl_Color color1 = color();
	fl_color(color1);

	vector<int> style1 = style();
	fl_line_style(style1[0],style1[1]);

	int x1 = x(), y1 = y();
	int x2 = x()+ w(), y2 = y()+ h();
	fl_line(x1,y1,x2,y2);
}
Line *line1 = new Line(40,171,309,0);

Line *line2 = new Line(349,10,0,195);
	line2->color(fl_rgb_color(0,0,0));
	line2->style(Line::FL_CAP_SQUARE,5);

Line *line3 = new Line(278,131,71,0);

Line *line4 = new Line(278,131,0,41);