[C++] 38 FLTK : findコマンド生成アプリ/Fl_Native_File_Chooser

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

OSネイティブなFile Chooserが使えるとされるFl_Native_File_Chooserを試してみました。

MacOSのFile Chooserは正しく表示されましたが、ディレクトリパスの取り込みはうまくいきませんでした。Windowを閉じることもできず、仕方なくDockから終了させました。この現象確認はIntel Macでは未実施です。

#include <Fl_Native_File_Chooser.H>

void filechooser_cb(Fl_Widget*, void*) {
    // Fl_File_Chooser chooser(".",                        // directory
    //                         "*",                        // filter
    //                         Fl_File_Chooser::DIRECTORY,     // chooser type
    //                         "Dir Select");        // title
    
    Fl_Native_File_Chooser* chooser = new Fl_Native_File_Chooser();
    chooser->type(Fl_Native_File_Chooser::BROWSE_DIRECTORY);
    chooser->title("Dir Select");
    chooser->filter("*");
    chooser->directory(".");

    chooser->show();

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

    dir_input->insert(chooser->filename());
}

[C++] 37 FLTK : findコマンド生成アプリ/Fl_File_Chooser

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

ディレクトリ選択のためにFl_File_Chooserを使用します。

案外すんなりと導入できました。見た目は硬派な感じです。

#include <FL/Fl_File_Chooser.H>

void filechooser_cb(Fl_Widget*, void*) {
    Fl_File_Chooser chooser(".",      // directory
                            "*",      // filter
                            Fl_File_Chooser::DIRECTORY, // chooser type
                            "Dir");   // title
    chooser.show();

    while(chooser.shown())
        { Fl::wait(); }

    dir_input->insert(chooser.value());
}

参考サイト

[C++] 36 日時の取得 localtime関数

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

開発中のアプリにデフォルトとして今日の日付を表示するため、関数を用意しました。

今回は年月日だけですが年月日時分秒も可能です。

ネット情報から拝借したものをアレンジしています。

#include <iostream>
#include <iomanip>

using std::setfill; using std::setw;
using std::stringstream;

string today;
time_t t;
stringstream s;
const tm* localTime;

string getDatetimeStr() {
    t = time(nullptr);
    localTime = localtime(&t);
    s << localTime->tm_year + 1900;
    s << setw(2) << setfill('0') << localTime->tm_mon + 1;
    s << setw(2) << setfill('0') << localTime->tm_mday;
    // s << setw(2) << setfill('0') << localTime->tm_hour;
    // s << setw(2) << setfill('0') << localTime->tm_min;
    // s << setw(2) << setfill('0') << localTime->tm_sec;
    return s.str();
}

参考サイト

[Python] 96 再掲 Nikon画像ファイルの日付別取り込み

[M1 Mac, Big Sur 11.6.5, Python 3.10.0]

前のブログサイトからの引っ越しです。画像ファイルをSDカードから外部SSDへ日付振り分けしてコピーします。

Nikon Trasnferという配布アプリで日付振り分けができることを知らずにコードを書きました。今も愛用しています。

# coding: UTF-8

import shutil ,os ,glob
from PIL import Image, ExifTags
import collections

path_list = glob.glob('/NIKON D500 /DCIM/101ND500/*.JPG')

exif_datetime_list = []
for path in path_list: 
    img = Image.open(path)

    exif = { ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in ExifTags.TAGS }

    # print(exif)
    exif_datetime = [v for k, v in exif.items() if k == "DateTimeOriginal"]
    print(exif_datetime[0])

    exif_datetime_str = (exif_datetime[0].split(':')[0])[2:4] + exif_datetime[0].split(':')[1] + (exif_datetime[0].split(':')[2])[0:2]
    print(exif_datetime_str)
    exif_datetime_list.append(exif_datetime_str)

print(exif_datetime_list)

# 作成日の内訳を集計し作成日のリストを作成する
keys = collections.Counter(exif_datetime_list).keys()
print(keys)

# 作成日フォルダを作成する
for key in keys:
    try:
        os.mkdir('/photo/D500/' + str(key))
    except FileExistsError:
        pass

# 画像ファイルを各フォルダにコピーする
for path in path_list:
    
    # 画像ファイルを読み込む
    img = Image.open(path)

    exif = { ExifTags.TAGS[k]: v for k, v in img._getexif().items() if k in ExifTags.TAGS }

    exif_datetime = [v for k, v in exif.items() if k == "DateTimeOriginal"]
    
    exif_datetime_str = (exif_datetime[0].split(':')[0])[2:4] + exif_datetime[0].split(':')[1] + (exif_datetime[0].split(':')[2])[0:2]

    # ファイルパスを文字列に変換する
    path_str = str(path)

    # ファイル名を抽出する(スラッシュで分割した最後の文字列)
    filename = path_str.split('/')[-1]

    # RAWファイル名を生成する
    filename_raw = filename.split('.')[0] + '.NEF'

    # RAWファイルのコピー元を生成する
    filename_raw_src = '/NIKON D500 /DCIM/101ND500/' + str(filename_raw)
    # RAWファイルのコピー先を生成する
    filename_raw_dest = '/photo/D500/'+ str(exif_datetime_str) + '/' + str(filename_raw)

    print(f'RAWコピー元 {filename_raw_src}')
    print(f'RAWコピー先 {filename_raw_dest}\n')

    filename_jpeg_dest = '/photo/D500/'+ str(exif_datetime_str) + '/' + str(filename)

    print(f'JPEGコピー元 {path}')
    print(f'JPEGコピー先 {filename_jpeg_dest}\n')

    # JPGファイルをコピーする
    shutil.copy2(path,filename_jpeg_dest)

    # RAWファイルをコピーする
    try:
        shutil.copy2(filename_raw_src,filename_raw_dest)
    except FileNotFoundError :
        pass

# Macのデスクトップ通知
def main():
    os.system("osascript -e 'display notification \"Nikonファイルコピー\nコード実行完了\"'")

if __name__ == '__main__':
    main()

[C++] 34 FLTK : findコマンド生成アプリ/絶対パス

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

アプリ作成の仕込みとしてfindコマンドへの理解を深めています。

前回の記事で検索結果を絶対パスで出力する場合はディレクトリ指定の所をpwdに置き換えます。

cd /test && \
find `pwd` -type file -newerat '20220414 23:59' ! -newerat '20220415 23:59' ! -name "*DS_Store*" -and -name "*PkgInfo*" && \
find `pwd` -type file -newerat '20220414 23:59' ! -newerat '20220415 23:59' ! -name "*DS_Store*" -and -name "*PkgInfo*" | wc -l
--------------------------------------------------
出力例
--------------------------------------------------
/Python/test/PkgInfo
       1

[C++] 33 FLTK : findコマンド生成アプリ/日付指定

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

FLTKアプリ第2弾の作成に着手しました。

今一つ信用できないFinderによる検索やSpotlight検索に代わる実用アプリです。

例えば、4/15にtestディレクトリで作成、アクセス、更新したファイルの名前と件数を出力するコマンドは以下の通りです。作成中のアプリはこのコマンドを生成して実行してくれます。

このコマンドでの日時の指定範囲です。
20220414 23:59 < range <= 20220415 23:59

cd /test && \
find . -type file -newerct '20220414 23:59' ! -newerct '20220415 23:59' ! -name "*DS_Store*" && \
find . -type file -newerct '20220414 23:59' ! -newerct '20220415 23:59' ! -name "*DS_Store*" | wc -l && \
find . -type file -newerat '20220414 23:59' ! -newerat '20220415 23:59' ! -name "*DS_Store*" && \
find . -type file -newerat '20220414 23:59' ! -newerat '20220415 23:59' ! -name "*DS_Store*" | wc -l && \
find . -type file -newermt '20220414 23:59' ! -newermt '20220415 23:59' ! -name "*DS_Store*" && \
find . -type file -newermt '20220414 23:59' ! -newermt '20220415 23:59' ! -name "*DS_Store*" | wc -l
--------------------------------------------------
出力例
--------------------------------------------------
       0
./Python/python/test.py
./ShellScript/test.sh
       3
./app/FileFinder/File Finder.png
       1

[C++] 32 FLTK: 親子ウィジェットを消去するコールバック [移植完了]

前回の続きです。

表示させたモーダルダイアログを消すコールバックを作成しました。最初は汎用ポインタvoid*の扱い方がわかりませんでした。

モーダルダイアログとOKボタンの親子関係を構築させ、ボタンを押すと親子ウィジェット共に消去するといった内容です。解決するまではOKボタンを押すとボタンだけが消えるという怪現象に取りつかれていました。

FLTKの仕様にひたすら振り回されました。当たり前の話ですが、つじつまが合うように書いていけばいずれ解決するという感じです。時間的コスパはとてつもなく悪いですね。

FLTKはあらゆる機能をカバーしていますが、実装するのが本当に大変です。これでQtのような遅さだったらとてもやってられないです。

続けてFl_Multiline_Outputにスクロールバーを付けようとしたところ、ドラッグ&ドロップができなくなったので止めました。スクロールバーがなくてもカーソルを動かせば見えなくなった行を確認できます。

リサイズ時の背景黒化は解決し堅牢性もそれなりの水準に達したので、PNG用アプリとしてPyQt6からの移植を一旦完了とします。

#include "modalDialog.h"
#include <string>
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_Widget.H>
 
Fl_Button *button;

modalDialog::modalDialog(int w, int h, const char* title, const char* msg)
    : Fl_Window(w, h, title){

    int ws, hs, xs, ys;
    int margin_x = 70;
    int margin_y = 15;

    ws = 60;
    hs = 25;
    xs = w - ws - margin_x;
    ys = h - hs - margin_y;

    // メッセージ
    Fl_Box *g = new Fl_Box(0, 0, w, h-40, msg);
        
    // OKボタン
    button = new Fl_Button(xs, ys, ws, hs, "OK");
    button->parent(this);
    button->callback(PushButtonOK,this);
    button->down_box(FL_UP_BOX);

    resizable(this);
    end();
}

modalDialog::~modalDialog()
{
}

void modalDialog::PushButtonOK(Fl_Widget* widget,void* x)
{
    Fl_Group* window = widget->parent();
    window->hide();
}

背景色黒化対策

img = cv::imread(path,cv::IMREAD_UNCHANGED);

[C++] 31 FLTK:子ウィジェットの座標

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8, OpenCV 4.5.5]

自製したモーダルダイアログの位置設定に苦労しました。

親ウィンドウを指定したものの相対位置を設定する方法が分からず、仕方ないのでルートを把握しこれに加減して座標を定めました。

ただこの方法では親ウィンドウを動かしても子ウィンドウの位置は変わらずです。あくまでも応急措置になります。

とある大学の研究室がモーダルダイアログ作成のコード例を公開しており、最初はこれを参考にしましたが結局大半は自分で書く形になりました。

難解なC++を使ってコンピュータシミュレーションを研究するというのはさぞかし大変なことでしょう。

2022/4/6追記:
デモアプリの動作を確認したところ、子ウィンドウは親ウィンドウの相対位置を取れていませんでした。もしかしたら出来ない仕様なのかもしれません。
2022/7/28追記:
親ウインドウのルートから子ウィンドウの相対位置を設定できました。以下の記事に記しました。

<該当部分のみ>

dlg = new modalDialog(300, 150, "", "Attention");
        dlg->hotspot(window);
        int x = dlg->x_root();
        int y = dlg->y_root();
        cout<<"x_root "<< x <<" y_root "<< y <<endl;
        dlg->resize(x-10,y+110,300,150);
        dlg->set_modal();
        dlg->show();

[C++] 30 segmentation fault発生時のデバッグ macOS

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8, OpenCV 4.5.5]

ある条件でFLTKアプリのボタンを押した時にsegmentation faultが発生しました。ターミナルにはトラブルが発生したとの情報しかないので困っていましたが、macOSではlldbというデバッガで調査できることを知りました。Linuxはgdbコマンドです。

内容はARM64のアセンブラです。今回は読解する前に解決できましたが、ざっと見た感じ分岐前のメモリからレジスタへの読み込み時にトラブルが発生したようです。

今更ですがこのトラブルでnullと空文字列が別物と知りました。この程度の知識でも簡単なアプリなら作れます。

lldb -f 実行ファイル名
(lldb) r
(lldb) exit
<実施例>

$ lldb -f ImageInspector 
(lldb) target create ImageInspector"
Current executable set to 'ImageInspector' (arm64).
(lldb) r
Process 84834 launched: 'ImageInspector' (arm64)
Process 84834 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xffffffffffffffff)
    frame #0: 0x000000018fdea9d8 libc++.1.dylib`std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) + 20
libc++.1.dylib`std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::basic_string:
->  0x18fdea9d8 <+20>: ldrsb  w8, [x1, #0x17]
    0x18fdea9dc <+24>: tbnz   w8, #0x1f, 0x18fdea9f4    ; <+48>
    0x18fdea9e0 <+28>: ldr    q0, [x1]
    0x18fdea9e4 <+32>: ldr    x8, [x1, #0x10]
Target 0: (ImageInspector) stopped.