[C++] 130 文字列分割ライブラリの機能追加 区切り文字の変更

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

前回記事で文字列のスペースを削除して新たなファイル名を作成しましたが、より読みやすくするため区切り文字をアンダースコアなどに変更できるようライブラリにsplitJoin2関数を追加しました。

#pragma once
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <vector>
#include <cstdio>
#include <sstream>
#include <iostream>

using std::string; using std::vector;
using std::cout; using std::endl;

class Split{
    
public:
vector<string> splits(string str, const char* del);
string splitJoin(string str, const char* del, int start, int end);
string splitJoin2(string str, const char* del1, const char* del2, int start, int end);
int chCWD(string str, const char* del, int start, int end);
};
#include "Split.h"

using std::string; using std::vector;
using std::cout; using std::endl;

// 文字列を文字列delで分割しリスト化する関数
vector<string> Split::splits(string str, const char* del) {
    int first = 0;
    int last = str.find_first_of(del);
 
    vector<string> result;
 
    while (first < str.size()) {
        string subStr(str, first, last - first);
 
        result.push_back(subStr);
 
        first = last + 1;
        last = str.find_first_of(del, first);
 
        if (last == string::npos) {
            last = str.size();
        }
    }
    return result;
}

// 分割リストのstart番目からend番目までの要素を同じ区切り文字で再結合する関数(startの最小値は0)
string Split::splitJoin(string str, const char* del, int start, int end){
    string result2;

    if (str.find(del) == std::string::npos){
        cout << "区切り文字が含まれていません" << endl;
        return str;
    }

    vector<string> list = Split::splits(str, del);

    vector<string> list2;
    for (int i = 0; i < list.size(); i++){
        if (end < 0){
            end += list.size();
        }
        if (i >= start && i <= end) {
            list2.push_back(list[i]);
        }
    }

    for (int i = 0; i< list2.size(); i++){
        result2.append(list[i] + del);
    }
    return result2;
}

// 分割リストの全要素を異なる区切り文字で再結合する関数
string Split::splitJoin2(string str, const char* del1, const char* del2){
    string result3;

    if (str.find(del1) == std::string::npos){
        cout << "区切り文字が含まれていません" << endl;
        return str;
    }

    vector<string> list = Split::splits(str, del1);

    // リストの要素を区切り文字del2で結合
    const char* delim = del2;
    std::ostringstream os;
    std::copy(list.begin(), list.end(), std::ostream_iterator<string>(os, delim));
    result3 = os.str();

    return result3;
}

// 作業ディレクトリを変更(階層を上げる)
int Split::chCWD(string str, const char* del, int start, int end){
    string new_str = Split::splitJoin(str, del, start, end);

    int rtn = chdir(new_str.c_str());
    return rtn;
}

[C++] 129 FLTK : 動画変換アプリの製作 FFmpeg

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

movファイルをmp4に変換するアプリを即席で作成しました。HomebrewからFFmpegをインストールして使用しています。

GUIは以前作成した画像変換アプリのものを流用しました。とりあえずmov→mp4変換だけの単機能です。

画面から動画をキャプチャした時のファイル名に半角スペースが含まれているためにFFmpegが動かず、前処理として半角スペースを削除したファイル名にリネームしてから変換させました。

FFmpegはコンソールツールなのでappファイルではなく実行ファイルを使うことになります。

たかだかスペースのために難易度が結構高くなりました。作っておいた文字列分割ライブラリSplitが役に立っています。

#include <cstdio> 
#include "Split.h"

Split spt;

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)
    {
        // 自製ライブラリSplitにより半角スペースを区切り文字としてリスト化
        vector<string> prefix0_vec = spt.splits(path_str, ' ');

        // リストの要素を結合(スペースなしのpath作成)
        const char* delim = "";
        std::ostringstream os;
        std::copy(prefix0_vec.begin(), prefix0_vec.end(), std::ostream_iterator<string>(os, delim));
        path2 = os.str();
        cout << "path2 "<< path2 << endl;

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

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

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

    // ファイル変換コマンド作成
    string cmd = "ffmpeg -i " + path2 + " -f " + fmt[formatNum] + " " + path3;
    cout << "cmd "<< cmd << endl;

    // コマンド実行
    system(cmd.c_str());

    cout << "フォーマット変換完了!" << endl;
    output_line->insert("フォーマット変換完了!\n");
}

[C++] 128 FLTK : FileChooserの作成 命名規則 キャメルケース

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

Cancelボタンのcallback関数を追加し、命名規則をキャメルケースに統一してリネームしました。クラス名はアッパーキャメルケース、変数・関数はローワーキャメルケースにしました。アンダースコアでつなげるスネークケースよりもこちらが好みです。

まだスネークケースが混ざっているので順次直していきます。

#include <FileChooser.h>
#include <FileChooser2.h>
#include <btnAction.h>

int browserType;
char appDir[FL_PATH_MAX]; // アプリ用ディレクトリ
char selectPath[FL_PATH_MAX]; // 選択ファイルパス
vector<const char*> selectPaths; // 複数選択ファイルパス

void fileListShow(const char* dirname)
{	
	// アプリ用ディレクトリの読込&全ファイル表示
	FileBrowser->load(dirname);

	// アプリ用ディレクトリをコピー
	fl_filename_absolute(appDir, sizeof(appDir), dirname);
	cout << "appDir " << appDir << endl;

	inputFileName->value(dirname);
}

void browserTypeSet(int t) {
	cout << "int t " << t << endl;

	browserType = t;
	if (t == FileChooser::MULTI){
		FileBrowser->type(FL_MULTI_BROWSER);
	}else{
		FileBrowser->type(FL_HOLD_BROWSER);
	}

	if (t == FileChooser::CREATE){
		btnNew->activate();
	}else{
		btnNew->deactivate();
	}

	if (t == FileChooser::DIRECTORY){
		FileBrowser->filetype(Fl_File_Browser::DIRECTORIES);
	}else{
		FileBrowser->filetype(Fl_File_Browser::FILES);
	}
}

void FileBrowserCB()
{
	char *fileName;

	// クリックしたファイルのファイル名を取得
	fileName = (char *)FileBrowser->Fl_Browser::text(FileBrowser->Fl_Browser::value());
	cout << "fileName " << fileName << endl; 
	
	if (!fileName) return;

	selectPath[0] = '\0';

	// ディレクトリ名にファイル名を結合
	strcat(selectPath ,appDir);
	strcat(selectPath ,"/");
	strcat(selectPath ,fileName);

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

	if (browserType == 1) { // 削除を選択してFL_MULTI_BROWSERになっている場合
		char* name = new char[strlen(selectPath) + 1];
    	strcpy(name, selectPath);
		selectPaths.push_back(name);
	}

	//ファイルパスを表示
	inputFileName->value("");
	inputFileName->value(selectPath);

}

void btnOKCB(Fl_Return_Button*, void*)
{
	// FileChooserを閉じる
	chooser->Fl_Window::hide();

}

void btnCancelCB(Fl_Button*, void*)
{
	if (browserType != 1) { 
		selectPath[0] = '\0';
	} else {
		selectPaths.clear();
	}

	// FileChooserを閉じる
	chooser->Fl_Window::hide();

}

[C++] 127 FLTK : FileChooserの作成 複数ファイル削除

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

前回記事のコードでは複数ファイル削除に対応していなかったので修正しました。

C/C++ではメモリアドレスの使い回しがデフォルトなのでvectorを作成しても全ての要素が最後にpush_backしたものに上書きされます。これを回避するには都度new演算子でヒープ領域を確保しコピーする必要があります。

今回の方法ではクリックしたファイルしか削除できないため、shiftキーで範囲選択しても最初と最後以外のファイルは削除されずに残ります。

おそらくFl_File_Browserのメンバ関数を使えばウィジェットのデータあるいはユーザデータとして全選択ファイルパスのリストを作成できるかと思います。これは手間取りそうなので気が向いたら考えます。

#include <FileChooser.h>
#include <FileChooser2.h>
#include <btnAction.h>

int BrowserType;
char pathname[FL_PATH_MAX]; // ホームディレクトリ格納
char pathname2[FL_PATH_MAX]; // 選択ファイルパス格納
vector<const char*> filepaths; // 複数選択ファイルパス格納

void fileListShow(const char* dirname)
{	
	// 設定ディレクトリの読込&全ファイル表示
	FileBrowser->load(dirname);

	// 設定ディレクトリの絶対パス表示
	fl_filename_absolute(pathname, sizeof(pathname), dirname);
	cout << "pathname " << pathname << endl;

	inputFileName->value(dirname);
}

void BrowserTypeSet(int t) {
	cout << "int t " << t << endl;

	BrowserType = t;
	if (t == FileChooser::MULTI){
		filepaths.clear();
		FileBrowser->type(FL_MULTI_BROWSER);
	}else{
		FileBrowser->type(FL_HOLD_BROWSER);
	}

	if (t == FileChooser::CREATE){
		btnNew->activate();
	}else{
		btnNew->deactivate();
	}

	if (t == FileChooser::DIRECTORY){
		FileBrowser->filetype(Fl_File_Browser::DIRECTORIES);
	}else{
		FileBrowser->filetype(Fl_File_Browser::FILES);
	}
}

void FileBrowserCB()
{
	char *filename;
	char* name;

	// クリックしたファイルのファイル名を取得
	filename = (char *)FileBrowser->Fl_Browser::text(FileBrowser->Fl_Browser::value());
	cout << "filename " << filename << endl; 
	
	if (!filename) return;

	pathname2[0] = '\0';

	// ディレクトリ名にファイル名を結合
	strcat(pathname2 ,pathname);
	strcat(pathname2 ,"/");
	strcat(pathname2 ,filename);

	// 削除を選択してFL_MULTI_BROWSERになっている場合はヒープ領域を確保してコピーする
	if (BrowserType == 1) {
		char* name = new char[strlen(pathname2) + 1];
    	strcpy(name, pathname2);
		filepaths.push_back(name);
	}

	cout << "pathname " << pathname2 << endl; 

	//ファイルパスを表示
	inputFileName->value("");
	inputFileName->value(pathname2);

}

void btnOKCB(Fl_Return_Button*, void*)
{
	// FileChooserを閉じる
	chooser->Fl_Window::hide();
}

[C++] 126 FLTK : FileChooserの作成 最低限の機能実装

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

自製FileChooserに以下の機能を実装しました。元ソースコードを移植しようした時とは打って変わってシンプルな内容になりました。一から自分の好きなように書くとあっさりでした。

1.アプリ用ディレクトリの全ファイルをFl_File_Browserに表示。
2.ファイルをクリックしてファイルパスをFl_File_Inputに表示。
3.FileChooserを閉じて、カラーリストファイルを読み込む。

後はFl_File_BrowserのFilter機能、Closeボタン、プレビュー機能を実装して一旦完了とする予定です。

#include <FileChooser.h>
#include <FileChooser2.h>
#include <btnAction.h>

int BrowserType;
char pathname[FL_PATH_MAX]; // アプリ用ディレクトリ格納
char pathname2[FL_PATH_MAX]; // 選択ファイルパス格納

void fileListShow(const char* dirname)
{	
	// 設定ディレクトリの読込&全ファイル表示
	FileBrowser->load(dirname);

	// 設定ディレクトリをpathnameへコピー
	fl_filename_absolute(pathname, sizeof(pathname), dirname);
	cout << "pathname " << pathname << endl;

	inputFileName->value(dirname);
}

void BrowserTypeSet(int t) {
	cout << "int t " << t << endl;

	BrowserType = t;
	if (t == FileChooser::MULTI){
		FileBrowser->type(FL_MULTI_BROWSER);
	}else{
		FileBrowser->type(FL_HOLD_BROWSER);
	}

	if (t == FileChooser::CREATE){
		btnNew->activate();
	}else{
		btnNew->deactivate();
	}

	if (t == FileChooser::DIRECTORY){
		FileBrowser->filetype(Fl_File_Browser::DIRECTORIES);
	}else{
		FileBrowser->filetype(Fl_File_Browser::FILES);
	}
}

void FileBrowserCB()
{
	char *filename;

	// クリックしたファイルのファイル名を取得
	filename = (char *)FileBrowser->Fl_Browser::text(FileBrowser->Fl_Browser::value());
	cout << "filename " << filename << endl; 
	
	if (!filename) return;

	// pathname2を空にする
	pathname2[0] = '\0';

	// ディレクトリ名にファイル名を結合
	strcat(pathname2 ,pathname);
	strcat(pathname2 ,"/");
	strcat(pathname2 ,filename);

	cout << "pathname2 " << pathname2 << endl; 

	//ファイルパスを表示
	inputFileName->value("");
	inputFileName->value(pathname2);

}

void btnOKCB(Fl_Return_Button*, void*)
{
	// FileChooserを閉じる
	chooser->Fl_Window::hide();
}

[C++] 125 FLTK : FileChooserの作成 Fl_File_Browser

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

元ソースコードを活用してFileChooserを作成しようとしましたが、どうやらC++03以前のレガシーC++で書かれているようで、加えて変数や関数の命名が抽象的かつ主体・客体が混在していてとても読みにくいため、Fl_File_Chooserのコンストラクタ以外はほぼ一から作成することにしました。

とりあえずFl_File_Browserに指定ディレクトリ内のファイルを全て表示させました。

ソースコードを読んでみて、FLTKの開発が遅々として進まない理由が少し分かったような気がします。

[C++] 124 FLTK : FileChooserの作成 Fl_File_Chooser

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

Fl_File_Chooserの最大の欠点は相対位置を取れず常に座標(0, 0)に表示される所です。

そこでFl_File_ChooserとFl_Windowを多重継承したFileChooserクラスを作成しました。コンストラクタにあるFl_Double_Windowは削除して書き換えます。とりあえずガワだけFl_File_Chooserに似せて作ってみました。

これから操作系を実装していきます。

int x_win = window->x_root();
int y_win = window->y_root();
cout<<"x_win "<< x_win <<" y_win "<< y_win <<endl;

FileChooser* chooser = new FileChooser(appdir.c_str(), 
                        "*.csv",
                        FileChooser::SINGLE,
                        "File_Chooser Load",490,380
                        );
    
chooser->resize(x_win+85,y_win+50,490,380);
chooser->set_modal();
chooser->show();

while(chooser->shown()){
    Fl::wait();
}

[C++] 123 FLTK : Fl_File_Chooserから複数ファイルを削除 

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

Fl_File_Chooserから複数のファイルを選択して削除できるようにしました。

第3引数で単数選択(SINGLE)、複数選択(MULTI)、新規ファイル作成(CREATE)、ディレクトリ選択(DIRECTORY)を設定できます。

#include <filesystem>
#include <FileChooser.h> // 自製
namespace fs = std::filesystem;

void deleteFavList(Fl_Widget*, void*){
    cout << "deleteFavList" << endl;

    homedir = getenv("HOME");
    cout << "homedir " << homedir << endl;

    string cs = "/ColorSample";
    string appdir = string(homedir) + cs;

    if (!fs::exists(appdir)){
        fs::create_directory(appdir);
    }

    FileChooser* chooser = new FileChooser(appdir.c_str(), // 呼び出すディレクトリ
                        "*.csv",      // フィルタ
                        Fl_File_Chooser::MULTI, // 複数選択タイプ
                        "File_Chooser Delete");   // タイトル
    
    chooser->show();

    while(chooser->shown()){
        Fl::wait();
    }

    // 選択ファイル数を取得
    int num_select = chooser->count();

    if (num_select == 0){
        return;
    }

    // 選択ファイルのパスを出力し削除
    for (int n = 1; n < num_select +1; ++n){
        const char* filePath = chooser->value(n);
        cout << "filePath" << n << " " << filePath << endl;
        remove(filePath);
    }

    delete chooser;
}

[C++] 122 文字列末尾の判定および削除

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

備忘として残しておきます。

// 文字列末尾が","であれば削除する
string str = "xxxxx";

int length = str.size();
char last = str.at(length -1);
string last_str{last}; // 初期化リストを用いる手法でchar単体→string変換
   
if (last_str.find(",") != std::string::npos){
    str.pop_back();
}

[C++] 121 FLTK : CSVファイルの読込 Fl_File_Chooser

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

よく使う色をまとめたCSVファイルを作成しておいてアプリに読み込ませました。

RGBで登録するとカンマがvectorに入り込むため処理が少しややこしくなります。

#include <csvProcessChar.h>
#include <cppstd.h> // 自製C++標準ライブラリ群

vector<vector<const char*>> csvProcessChar::load(string path) {
    string eles;
    string ele;
    vector<vector<const char*>> vec2D;
    
    // csvファイル指定
    std::ifstream file(path);

    while (getline(file, eles)) {
        vector<const char*> vec;  
        std::istringstream line(eles);
        vector<string> rgb;

        cout << "eles " << eles << endl;

        int num = 1;
        if (eles.find("RGB") == std::string::npos){
            while (getline(line, ele, ',')) {
                if (num == 1){
                    const char* name_c = ele.c_str();
                    char* name_c2 = new char[strlen(name_c) + 1];
                    strcpy(name_c2, name_c);
                    vec.push_back(name_c2);
                } else if (num == 2){
                    const char* roma_c = ele.c_str();
                    char* roma_c2 = new char[strlen(roma_c) + 1];
                    strcpy(roma_c2, roma_c);
                    vec.push_back(roma_c2);
                } else {
                    const char* code_c = ele.c_str();
                    char* code_c2 = new char[strlen(code_c) + 1];
                    strcpy(code_c2, code_c);
                    vec.push_back(code_c2);
                    
                }
                num += 1;
            }
        } else {
            // RGBの場合
            while (getline(line, ele, ',')) {
                if (num == 1){
                    const char* name_c = ele.c_str();
                    char* name_c2 = new char[strlen(name_c) + 1];
                    strcpy(name_c2, name_c);
                    vec.push_back(name_c2);
                } else if (num == 2){
                    const char* roma_c = ele.c_str();
                    char* roma_c2 = new char[strlen(roma_c) + 1];
                    strcpy(roma_c2, roma_c);
                    vec.push_back(roma_c2);
                } else {
                    rgb.push_back(ele); // 3番目以降は一旦vectorへ入れてから後で結合する
                }
                num += 1;
            }
        }
        if (!(rgb.size() == 0)){
            cout << "RGBあり" << endl;
            const char* delim = ","; // 区切り文字

            // vector要素結合
            std::ostringstream os;
            std::copy(rgb.begin(), rgb.end(), std::ostream_iterator<string>(os, delim));
            string str = os.str();

            const char* code_c = str.c_str();
            char* code_c2 = new char[strlen(code_c) + 1];
            strcpy(code_c2, code_c);
            vec.push_back(code_c2);

        }
        vec2D.push_back(vec);
    }

    return vec2D;
}