[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
xlsx変換アプリの実装機能とGUIデザインを検討しました。
ExcelファイルからリストやCSVへの変換、および逆方向の変換を実装する予定です。

[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
xlsx変換アプリの実装機能とGUIデザインを検討しました。
ExcelファイルからリストやCSVへの変換、および逆方向の変換を実装する予定です。
[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
Excelファイルを扱うFLTKアプリの作成に着手しました。GUIはよく使うガワの使い回しです。
まずは前々回から取り組んでいるExcelファイルから列データを取り出してリストにする機能を実装しました。A列の値とセル色をリストにします。色データはRGB16進数の頭にFFが付きます。このFFはいらないのでそのうち除去するようにします。
列数や取り出すデータの種類はいずれ選択できるようにしたいです。
#define PY_SSIZE_T_CLEAN
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>
using std::string;
int XlsxToList(const char* path) {
Py_Initialize();
PyRun_SimpleString("import openpyxl");
string path_str = string(path);
string wb_str = "wb = openpyxl.load_workbook('" + path_str +"')";
PyRun_SimpleString(wb_str.c_str());
PyRun_SimpleString("ws = wb.worksheets[0]");
PyRun_SimpleString("color_name = []");
PyRun_SimpleString("color_code = []");
string for_str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
PyRun_SimpleString(for_str.c_str());
PyRun_SimpleString("print(color_name)");
PyRun_SimpleString("print(color_code)");
Py_Finalize();
return 0;
}
[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
前回の続きです。
for文はワンライナーで書けば、PyRun_SimpleString()に使えます。バックスラッシュを入れてもOKです。
リスト内包表記のワンライナーは多用していましたが、if文やfor文の中身をカンマでつなぐとワンライナーになるというのは知りませんでした。
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
#include <iostream>
#include <string.h>
using std::string;
int main() {
Py_Initialize();
PyRun_SimpleString("import openpyxl");
PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
PyRun_SimpleString("ws = wb.worksheets[0]");
PyRun_SimpleString("color_name = []");
PyRun_SimpleString("color_code = []");
string str = "for cell in ws['A']: color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
// バックスラッシュを使う場合
// string str = "for cell in ws['A']: \
// color_name.append(cell.value), color_code.append(cell.fill.fgColor.rgb)";
PyRun_SimpleString(str.c_str());
PyRun_SimpleString("print(color_name)");
PyRun_SimpleString("print(color_code)");
Py_Finalize();
return 0;
}
[M1 Mac, Big Sur 11.6.5, Python 3.10.4]
C++コードからPythonスクリプトを動かしてExcelを操作してみました。Pythonは公式サイトからダウンロード&インストールしたVer. 3.10.4を使っています。
以下のコードはExcelファイルのA列に入力された文字列とセル色をリストにして出力します。
モジュールではないのでデータの双方向なやりとりは直接できませんが、ファイルを介してなら可能でしょう。
C++でExcelを扱うにはLibXLのような3万円もする有償ライブラリが必要になるので、間接的とはいえopenpyxlで操作できるのであればこれで十分です。
FLTKのような軽量なGUIでopenpyxlやpandasが使えないかと思い、試してみた次第です。TkinterやPyQtといったウィジェットツールキットで重量級GUIにはしたくなかったものですから。
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>
int main() {
Py_Initialize();
PyRun_SimpleString("import openpyxl");
PyRun_SimpleString("wb = openpyxl.load_workbook('test.xlsx')");
PyRun_SimpleString("ws = wb.worksheets[0]");
PyRun_SimpleString("color_name = [cell.value for cell in ws['A']]");
PyRun_SimpleString("color_code = [cell.fill.fgColor.rgb for cell in ws['A']]");
PyRun_SimpleString("print(color_name)");
PyRun_SimpleString("print(color_code)");
Py_Finalize();
return 0;
}
# コンパイラ
COMPILER = clang++
DEBUG = -g
# オプション
CPPFLAGS =
# includeパス(-I)
INCLUDE = -I/Library/Frameworks/Python.framework/Versions/3.10/include/python3.10
# ライブラリパス(-l)
LIBRARY0 = -lpython3.10
# 優先ライブラリパス(-L)
LIBRARY = -L/Library/Frameworks/Python.framework/Versions/3.10/lib
# ソースファイル
SRCDIR = ./src
SRCS = $(SRCDIR)/Openpyxl.cpp
# オブジェクトファイル
OBJDIR = ./obj
OBJS = $(OBJDIR)/Openpyxl.o
# 実行ファイル
TARGETDIR = ./bin
TARGET = Openpyxl
# cppファイルからoファイル作成 $<:依存ファイル
$(OBJDIR)/Openpyxl.o: $(SRCDIR)/Openpyxl.cpp
$(COMPILER) $(CPPFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<
# oファイルから実行ファイル作成
$(TARGET):$(OBJS)
$(COMPILER) -o $(TARGETDIR)/$@ $(OBJS) $(LIBRARY0) $(LDFLAGS) $(LIBRARY)
# ファイル削除&実行ファイル作成
.PHONY:all
all: clean $(TARGET)
# ファイル削除
.PHONY:clean
clean:
rm -rf $(OBJS) $(TARGETDIR)/$(TARGET)
iOSアイコンのAppicon.appiconset内にあるContents.jsonはアイコン作成サイトで作ったものを一部書き換えて使っています。
内容は以下の通りです。
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"40x40","expected-size":"80","filename":"80.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"40x40","expected-size":"120","filename":"120.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"60x60","expected-size":"120","filename":"120.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"57x57","expected-size":"57","filename":"57.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"1x"},
{"size":"29x29","expected-size":"58","filename":"58.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"29x29","expected-size":"29","filename":"29.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"1x"},
{"size":"29x29","expected-size":"87","filename":"87.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"57x57","expected-size":"114","filename":"114.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"20x20","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"2x"},
{"size":"20x20","expected-size":"60","filename":"60.png","folder":"AppIcon.appiconset/","idiom":"iphone","scale":"3x"},
{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"AppIcon.appiconset/","scale":"1x"},
{"size":"40x40","expected-size":"80","filename":"80.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"72x72","expected-size":"72","filename":"72.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"76x76","expected-size":"152","filename":"152.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"50x50","expected-size":"100","filename":"100.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"29x29","expected-size":"58","filename":"58.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"76x76","expected-size":"76","filename":"76.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"29x29","expected-size":"29","filename":"29.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"50x50","expected-size":"50","filename":"50.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"72x72","expected-size":"144","filename":"144.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"40x40","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"},
{"size":"20x20","expected-size":"20","filename":"20.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"1x"},
{"size":"20x20","expected-size":"40","filename":"40.png","folder":"AppIcon.appiconset/","idiom":"ipad","scale":"2x"}]}
とりあえずmacOSアプリとiOSアプリに必要なアイコン類を作成できるようにしました。画像処理ライブラリとしてOpenCV(開発はインテル)を使っています。用意するのは2048*2048のpngファイルだけです。
iOSに必要なContents.jsonは前もってホームディレクトリに配置したものをコピーしました。
#include <opencv2/opencv.hpp>
void makeIcnsIOS(const char* filename){
// AppIcon.appiconsetパス作成
string path_str = string(filename);
char del = '/';
vector<string> list = split(path_str, del);
string imagename = list.back();
list.pop_back();
const char* del2 = "/";
string prefix = join(list,del2);
string dir = prefix + "/" + "AppIcon.appiconset";
cout<<"dir "<<dir<<endl;
// iconsetディレクトリ作成
mkdir(dir.c_str(),
S_IRUSR | S_IWUSR | S_IXUSR | // USR RWX
S_IRGRP | S_IWGRP | S_IXGRP | // GRP RWX
S_IROTH | S_IWOTH | S_IXOTH); // OTH RWX
// 各種pngファイル作成
int pixels[19] = {20, 29, 40, 50, 57, 58, 60, 72, 76, 80, 87, 100, 114, 120, 144, 152, 167, 180, 1024};
string filenames[19] = {"20.png","29.png","40.png","50.png","57.png","58.png","60.png","72.png","76.png","80.png","87.png","100.png","114.png","120.png","144.png","152.png","167.png","180.png","1024.png"};
int num = 0;
for (int pixel:pixels){
cv::Mat img,img_resize;
img = cv::imread(filename,cv::IMREAD_UNCHANGED);
cv::resize(img,img_resize, cv::Size(pixel,pixel),0,0,cv::INTER_LINEAR);
// リサイズファイルパス作成
string filename2 = filenames[num];
string filepath = dir + "/" + filename2;
cv::imwrite(filepath, img_resize);
num += 1;
}
// JSONファイルコピー
std::string jsonname1 = "/Users/[ユーザID]/ImageInspector/Contents.json";
std::string jsonname2 = prefix + "/AppIcon.appiconset/Contents.json";
system(("cp " + jsonname1 + " " + jsonname2).c_str());
}
[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]
macOSアプリ, iOSアプリではアイコン登録に必要な画像ファイル数、サイズおよびファイルタイプが異なるため、都度選択し作成できるよう自製アプリの機能追加に着手しました。XCodeへのiOSアプリ登録に必要なのは正確にはicnsファイルではなくappiconsetフォルダです。
まずはガワだけ、プルダウンメニューを配置しました。中身はこれから書きます。
<該当箇所のみ>
Fl_Choice *choice;
// icnsタイプ選択項目
Fl_Menu_Item icns_type[3] = {
{"macOS"},
{"iOS"},
};
// icnsタイプ選択メニュー
choice = new Fl_Choice(135, 90, 80, 20, nullptr);
choice->menu(icns_type);
[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]
前回の続きです。
FL_Menu_Itemの配列の書き方でイテレータの存在を忘れていて修正を施しましたが、コードの見た目がいまひとつなので以下のように書き換えました。
Fl_Menu_Item pulldown[4] = {
{"and"},
{"or"},
{"not"}
};
実際のアイテム数3に最後のヌルポインタを加えた4を記述する必要があります。こちらの方がすっきりしている感じがします。公式サイトにあるような複雑なメニューであれば最後に0を書くでしょう。
Fl_Menu_Item popup[] = {
{"&alpha", FL_ALT+'a', the_cb, (void*)1},
{"&beta", FL_ALT+'b', the_cb, (void*)2},
{"gamma", FL_ALT+'c', the_cb, (void*)3, FL_MENU_DIVIDER},
{"&strange", 0, strange_cb},
{"&charm", 0, charm_cb},
{"&truth", 0, truth_cb},
{"b&eauty", 0, beauty_cb},
{"sub&menu", 0, 0, 0, FL_SUBMENU},
{"one"},
{"two"},
{"three"},
{0},
{"inactive", FL_ALT+'i', 0, 0, FL_MENU_INACTIVE|FL_MENU_DIVIDER},
{"invisible",FL_ALT+'i', 0, 0, FL_MENU_INVISIBLE},
{"check", FL_ALT+'i', 0, 0, FL_MENU_TOGGLE|FL_MENU_VALUE},
{"box", FL_ALT+'i', 0, 0, FL_MENU_TOGGLE},
{0}};
お遊びで私のアプリにこの複雑なメニューを実装してみました。余談ですが、今になって何故altキーの記号が⎇なのかが理解できました。確かに左から右にまっすぐ進まずもう一方に変わっている様子で意味が伝わります。
それにしてもMontereyでは何故間違ったコードでも動いたのか、謎です。
[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]
macOS MontereyからBig Surにダウングレードしたところ、コマンドキーを押すとアプリが落ちるようになりました。エラー発生時に表示される解析情報を読むとどうやらFL_Menu_Itemまわりがおかしいようでした。
参考サイトのコードと全く同じようにプルダウンメニュー項目の最後に0を追加すると直りました。Montereyやアップグレード前のBig Surでは0がなくても正常に動いていたのに不思議です。
2022/5/18追記:
0(ヌルポインタ)が必要なのはイテレータに次の要素がないことを知らせるためでした。こんなことがすぐに分からないようではまだまだです。
Process: FileFinder [8791]
Path: /Volumes/VOLUME/*/FileFinder
Identifier: FileFinder
Version: 0
Code Type: ARM-64 (Native)
Parent Process: bash [8781]
Responsible: Terminal [414]
User ID: 501
Date/Time: 2022-05-17 00:09:08.114 +0900
OS Version: macOS 11.6.5 (20G527)
Report Version: 12
Anonymous UUID: 8099E198-41F7-976F-694A-6B1FB4EBE622
Time Awake Since Boot: 45000 seconds
System Integrity Protection: enabled
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x1600000000000000 -> 0x0000000000000000 (possible pointer authentication failure)
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [8791]
VM Regions Near 0:
-->
__TEXT 102480000-10248c000 [ 48K] r-x/r-x SM=COW /Volumes/*
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libfltk.1.3.dylib 0x00000001026d90e0 Fl_Menu_Item::test_shortcut() const + 20
1 libfltk.1.3.dylib 0x00000001026d912c Fl_Menu_Item::test_shortcut() const + 96
2 libfltk.1.3.dylib 0x00000001026d912c Fl_Menu_Item::test_shortcut() const + 96
3 libfltk.1.3.dylib 0x00000001026bcdbc Fl_Choice::handle(int) + 384
4 libfltk.1.3.dylib 0x00000001026c69b8 Fl_Group::handle(int) + 960
5 libfltk.1.3.dylib 0x00000001026b4650 send_event(int, Fl_Widget*, Fl_Window*) + 148
6 libfltk.1.3.dylib 0x00000001026b45ac Fl::handle_(int, Fl_Window*) + 1656
7 libfltk.1.3.dylib 0x00000001026b4388 Fl::handle_(int, Fl_Window*) + 1108
8 libfltk.1.3.dylib 0x00000001026aa2c0 -[FLView flagsChanged:] + 308
9 com.apple.AppKit 0x00000001a1594c10 -[NSWindow(NSEventRouting) _reallySendEvent:isDelayedEvent:] + 5644
10 com.apple.AppKit 0x00000001a1593398 -[NSWindow(NSEventRouting) sendEvent:] + 352
11 com.apple.AppKit 0x00000001a1592270 -[NSApplication(NSEvent) sendEvent:] + 2568
12 libfltk.1.3.dylib 0x00000001026a636c fl_mac_flush_and_wait(double) + 536
13 libfltk.1.3.dylib 0x00000001026b373c Fl::run() + 44
14 FileFinder 0x0000000102485e88 main + 3836 (FileFinder.cpp:491)
15 libdyld.dylib 0x000000019eb41430 start + 4
# 最後の要素の0は無意味ではなかったようです
Fl_Menu_Item pulldown[] = {
{"and"},
{"or"},
{"not"},
0
};
[M1 Mac, Monterey 12.3, FLTK 1.3.8]
macOSではfindコマンドの検索語に濁音・半濁音のかなを使えないため、これらを判定する関数を実装しました。イテレータを使えるケースですが、今回は不使用です。
正規表現が使えないようなので地道に配列を作成しました。検出するとダイアログが表示されます。
std::vector<string> dakuon = {"が","ぎ","ぐ","げ","ご","ざ","じ","ず","ぜ","ぞ","だ","ぢ","づ","で","ど","ば","び","ぶ","べ","ぼ" \
"ガ","ギ","グ","ゲ","ゴ","ザ","ジ","ズ","ゼ","ゾ","ダ","ヂ","ヅ","デ","ド","バ","ビ","ブ","ベ","ボ" \
"ぱ","ぴ","ぷ","ぺ","ぽ","パ","ピ","プ","ペ","ポ"};
bool dakuon_detection(const char* name_char) {
string name_str = string(name_char);
for (size_t i = 0; i < dakuon.size(); ++i) {
if (name_str.find(dakuon[i]) != std::string::npos) {
const char* msg = "検索不可\n濁音・半濁音の平仮名・片仮名が\n含まれています";
dlg = new modalDialog(400, 200, "Attention", msg);
dlg->hotspot(window);
int x = dlg->x_root();
int y = dlg->y_root();
dlg->resize(x+20,y+120,250,150);
dlg->set_modal();
dlg->show();
cout << msg << endl;
return true;
}
}
return false;
}
bool judge = dakuon_detection(name_char);
if (judge){
<濁音かな検出時の処理>
}else{
<濁音かな非検出時の処理>
}