[Obj-C++] 01 Objective-C++への移行

[M1 Mac, Big Sur 11.6.5, clang 13.0.0, no Xcode]

C++の機能も使えるようにするため、Objective-CからObjective-C++へ移行します。

mファイルの拡張子をmmに変えるだけでOKです。coutで動作確認しました。

#include <Cocoa/Cocoa.h>
#include <iostream>

using std::cout; using std::endl;

int num = 1;

// Objective-C++
NSLog(@"Objective-C++ num %d",num);

// C++
cout << "C++ num " << num << endl;

--------------------------------------------------
出力
--------------------------------------------------
Objective-C++ num 1
C++ num 1

[C++] 65 FLTK : xlsx変換アプリ / MakefileからCMakeLists.txtへの移植

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

自製Makefileの生成するコマンドを見ながらCMakeLists.txtへ移植しました。

CMakeが作成したMakefileが生成するコマンドについては以下の行を追記して内容を確認できます。

set(CMAKE_VERBOSE_MAKEFILE ON)

Objective-Cのappファイルと同様、こちらもボタンを押下してもクラッシュしませんでした。自製Makefileではできない補完をしているのでしょう。

C++でappファイルに問題がないとなるとObjective-Cに戻って開発を進める必要がありません。

Cocoaアプリの統一感のある洗練された外観も捨てがたいので何か別のアプリで再チャレンジしたいところです。

cmake_minimum_required(VERSION 3.1)

# Project
Project(XlsxConvertor VERSION 1.0.0)
FIND_PACKAGE(FLTK REQUIRED)
FIND_PACKAGE(OpenGL REQUIRED)
find_library(COCOA Cocoa)

set(CMAKE_CXX_STANDARD 11)

# make時の生成コマンドをターミナルに表示する(デフォルトでは非表示)
set(CMAKE_VERBOSE_MAKEFILE ON)

# Info.plist
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright (C) 2022")
set(MACOSX_BUNDLE_INFO_STRING "Convert tool for XLSX")

# XlsxConvertor
add_executable(${PROJECT_NAME}
    MACOSX_BUNDLE
    ./src/XlsxConvertor.cpp
    ./src/process.cpp
    ./src/modalDialog.cpp
    ./include/process.h
    ./include/modalDialog.h
    )

set(INCDIR /include)
set(INCDIR ${INCDIR} /opt/homebrew/Cellar/libpng/1.6.37/include)
set(INCDIR ${INCDIR} /opt/homebrew/Cellar/fltk/1.3.8/include)
set(INCDIR ${INCDIR} /opt/homebrew/Cellar/jpeg/9e/include)
set(INCDIR ${INCDIR} /Library/Frameworks/Python.framework/Versions/3.10/include/python3.10)
include_directories(${INCDIR})

set(LIBS dl)
set(LIBS ${LIBS} z pthread c++)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk_forms.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk_gl.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk_images.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/jpeg/9e/lib/libjpeg.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/libpng/1.6.37/lib/libpng.dylib)
set(LIBS ${LIBS} /opt/homebrew/Cellar/fltk/1.3.8/lib/libfltk.dylib)
set(LIBS ${LIBS} /Library/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib)

set(LIBDIR /opt/homebrew/Cellar/jpeg/9e/lib)
set(LIBDIR ${LIBDIR} /opt/homebrew/Cellar/libpng/1.6.37/lib)
set(LIBDIR ${LIBDIR} /opt/homebrew/Cellar/fltk/1.3.8/lib)
set(LIBDIR ${LIBDIR} /usr/local/lib /Library/Frameworks/Python.framework/Versions/3.10/lib)
link_directories(${LIBDIR})

target_link_libraries(${PROJECT_NAME}
    ${FLTK}
    ${OpenGL}
    ${LIBS}
    ${COCOA})

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

[M1 Mac, Big Sur 11.6.5, FLTK 1.3.8]

Objective-Cのドラッグ&ドロップが今の私にはあまりにも難解なので、FLTKではどうやって実装していたか振り返ります。かなり簡単に実装できます。

FL_Boxでファイルをドロップする領域を指定し、ドロップされたファイルの情報をFL_Boxの引数で指定したFL_Inputに渡します。単純明快です。

Objective-Cのドラッグ&ドロップについては必須ではなく努力目標にして、FLTKアプリのCMakeLists.txt作成に優先して取り組むことにします。

同じ内容の実装でもC++(FLTK)とObjective-Cでこれだけ難易度が違うと難しい方を学ぼうとする気力がなかなかわきません。

Xcodeで書けばいいのですが、xibファイルやStoryboardを扱いたくないのでモチベが上がらないです。

<該当箇所のみ>

class Box : Fl_Box {
    Fl_Input* input_line2;

    public:
        Box(int, int, int, int, Fl_Input*);
    private:
        auto handle(int) -> int override;
};

Box::Box(int x, int y, int width_input, int height_input, Fl_Input* input) : 
    Fl_Box(FL_NO_BOX, x, y, width_input, height_input, "") {
    this->input_line2 = 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_line2->value(Fl::event_text());
            input_line2->textsize(12);
            input_line2->textfont(FL_HELVETICA);

            return 1;

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

int main(int argc, char **argv) {
    Box *box = new Box(0, 0, 360,220, input_line);
}

[Obj-C] 16 GUIアプリ : MakefileをCMakeLists.txtへ移植

[M1 Mac, Big Sur 11.6.5, no Xcode]

MakefileをCMakeLists.txtへ移植しました。

CMakeが作成したMakefileがどういったコマンドを実行しているのか分からないので気が進まなかったのですが、非XCode環境で開発を進めるにはどうも避けられないためやむなく着手しました。

自製Makefileでは認識していた -lpython3.10がリンク時に見つからないというエラーへの対策でだいぶ時間を取られました。出力をよく読むとビルドの時にはちゃんと認識していたので、そのライブラリパスをtarget_link_librariesのところに書いたらうまくいきました。CMakeは案外融通の効かないところがあるようです。

いちいちbuildディレクトリに潜るのは面倒なのでプロジェクトフォルダのルートからMakefileでCMakeを実行するというちょっとややこしいけど楽なやり方で進めています。make allコマンド一発でCMakeを経てappファイルまで作ってくれます。

自製Makefileで作成したappファイルはボタン押下時に落ちてしまいましたが、今回のappファイルはクラッシュしません。これは全く期待していなかったのでうれしい限りです。

C++からわざわざObjective-Cへ移植したかいがありました。

ビルド時に-lpython3.10を認識しているが、リンク形成ではエラーになる。
cmake_minimum_required(VERSION 3.1)

# Project
Project(XlsxConvertor VERSION 1.0.0)
find_library(COREFOUNDATION CoreFoundation)
find_library(COCOA Cocoa)

# Info.plist
set(MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
set(MACOSX_BUNDLE_COPYRIGHT "Copyright (C) 2022")
set(MACOSX_BUNDLE_INFO_STRING "Convert tool for XLSX")

# XlsxConvertor
add_executable(${PROJECT_NAME}
    MACOSX_BUNDLE
    ./src/XlsxConvertor.m
    ./src/process.m
    ./include/process.h)

set(INCDIR ./include)
set(INCDIR ${INCDIR} /Library/Frameworks/Python.framework/Versions/3.10/include/python3.10)
include_directories(${INCDIR})

set(LIBDIR /Library/Frameworks/Python.framework/Versions/3.10/lib)
link_directories(${LIBDIR})

set(LIBS dl)
set(LIBS ${LIBS} /Library/Frameworks/Python.framework/Versions/3.10/lib/libpython3.10.dylib)

target_link_libraries(${PROJECT_NAME}
    ${COREFOUNDATION}
    ${COCOA}
    ${LIBS})
# CMakeを経てappファイル作成(アイコン登録含む)

TARGET = XlsxConvertor

.PHONY : all
all:
	rm -rf build/*
	cd /projects/XlsxConvertor09/build && cmake .. && make
	mkdir ./build/$(TARGET).app/Contents/Resources
	cp ./images/XlsxConvertor.icns ./build/$(TARGET).app/Contents/Resources
	plutil -replace 'CFBundleIconFile' -string "XlsxConvertor.icns" ./build/$(TARGET).app/Contents/Info.plist

[Obj-C] 15 GUIアプリ : CSVファイルをxlsxファイルやリストに変換

[M1 Mac, Big Sur 11.6.5, no Xcode]

Pythonのライブラリであるpandasやcsvを使って、CSVファイルをxlsxファイルやリストに変換しました。リストへの変換はちょっとした小技が必要です。

これで基本的な機能の実装は完了しました。あとはファイルのドラッグ&ドロップや例外処理について書いていきます。

<該当箇所のみ>

import openpyxl, csv

def csv_to_xlsx(path):
    # xlsxファイル名作成
    xlsx_path = path.split('.')[0] + '.xlsx'
    
    df = pd.read_csv(path)
    df.to_excel(xlsx_path, encoding='utf-8', index = False, header = False)

    return "csv_to_xlsx sccess"

def csv_to_list(path):
    # csvファイルを読み込みリスト化
    with open(path, encoding='utf-8-sig') as f:
        reader = csv.reader(f)
        l = [row for row in reader]

    # ネストになっているため平滑化
    csv_list = [e for ele in l for e in ele]
        
    return str(csv_list)

[Obj-C] 14 GUIアプリ : NumPyでリストをcsvファイル化

[M1 Mac, Big Sur 11.6.5, no Xcode]

PythonのライブラリNumPyでリストをcsvファイル化できるようにしました。

様々な方法がありますが、入れ子になったリストに対応できるNumPyを選択しました。

コードは以下の通りです。

<該当箇所のみ>
#include <Cocoa/Cocoa.h>
#include "process.h"

NSString* result;
NSString* path;
NSString* list;
Convert* convert;

- (IBAction) OnButton1Click:(id)sender {
	onoff_XlsxToList = radioButton_a1.state; 
	onoff_XlsxToCsv = radioButton_a2.state; 
	onoff_ListToXlsx = radioButton_b1.state; 
	onoff_ListToCsv = radioButton_b2.state; 
	onoff_CsvToXlsx = radioButton_c1.state; 
	onoff_CsvToList = radioButton_c2.state;

	if (radioButton_a1.state == 1){
		rbtn_num = 1;
	} else if (radioButton_a2.state == 1){
		rbtn_num = 2;
	} else if (radioButton_b1.state == 1){
		rbtn_num = 3;
	} else if (radioButton_b2.state == 1){
		rbtn_num = 4;
	} else if (radioButton_c1.state == 1){
		rbtn_num = 5;
	} else {
		rbtn_num = 6;
	}

	NSLog(@"%d",rbtn_num);

	switch (rbtn_num){
	case 1:
	case 2:
	case 5:
	case 6:
		path = [textBox1 stringValue];
		convert = [[Convert alloc] init];
		result = [convert ConvertFunc:path number:rbtn_num];
		[textview setString:result];
		break;
	case 3:
	case 4:
		list = [textBox2 stringValue];
		convert = [[Convert alloc] init];
		result = [convert ConvertFunc:list number:rbtn_num];
		[textview setString:result];
		break;
	}
		
}
<該当箇所のみ>

import numpy as np

def list_to_csv(list):
    home = os.path.expanduser('~')
    file = home + '/Desktop/ltoc.csv'
    
    new_list = eval(list)
    np.savetxt(file, new_list, delimiter =",",fmt ='% s')
    
    return "list_to_csv success"

[Obj-C] 13 GUIアプリ : リストをxlsxファイル化

[M1 Mac, Big Sur 11.6.5, no Xcode]

Pythonのライブラリopenpyxlを使って、リストをxlsxファイルに変換できるようにしました。デスクトップにtest.xlsxとして作成されます。

コードは以下の通りです。

#include <Cocoa/Cocoa.h>
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>

@implementation Convert

- (NSString *)ConvertFunc:(NSString *)path number:(int)num {
    switch (num) {
    case 1:{
        Py_Initialize();
        // pyファイルの指定
        PyObject* myModuleString = PyUnicode_FromString((char*)"xlsx_convert");

        // pyファイルのモジュール化
        PyObject* myModule = PyImport_Import(myModuleString);

        // pyファイル内の関数を指定
        PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_list");

        // 関数の引数を設定
        PyObject* args = PyTuple_Pack(1,PyUnicode_FromString((char *) [path UTF8String]));

        // 関数を実行し戻り値をPyObjectとして取得
        PyObject* myResult = PyObject_CallObject(myFunction,args);

        // PyObjectをconst char*に変換
        const char* result = PyUnicode_AsUTF8(myResult);
        NSString* result_ns = [NSString stringWithCString: result encoding:NSUTF8StringEncoding];

        NSLog(@"%@",result_ns);

        return result_ns;
        Py_FinalizeEx();
        break;
    }
    case 2:{
        Py_Initialize();

        PyObject* myModuleString = PyUnicode_FromString((char*)"xlsx_convert");
        PyObject* myModule = PyImport_Import(myModuleString);
        PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_csv");
        PyObject* args = PyTuple_Pack(1,PyUnicode_FromString((char *) [path UTF8String]));
        PyObject* myResult = PyObject_CallObject(myFunction,args);
        const char* result = PyUnicode_AsUTF8(myResult);
        NSString* result_ns = [NSString stringWithCString: result encoding:NSUTF8StringEncoding];

        return result_ns;
        Py_FinalizeEx();
        break;
    }           
    case 3:{
        Py_Initialize();

        PyObject* myModuleString = PyUnicode_FromString((char*)"xlsx_convert");
        PyObject* myModule = PyImport_Import(myModuleString);
        PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"list_to_xlsx");
        PyObject* args = PyTuple_Pack(1,PyUnicode_FromString((char *) [path UTF8String]));
        PyObject* myResult = PyObject_CallObject(myFunction,args);
        const char* result = PyUnicode_AsUTF8(myResult);
        NSString* result_ns = [NSString stringWithCString: result encoding:NSUTF8StringEncoding];

        return result_ns;
        Py_FinalizeEx();
        break;
    }
    case 4:
        return @"case 4";
        break;
    case 5:
        return @"case 5";
        break;
    case 6:
        return @"case 6";
        break;
    default:
        return @"default";
        break;
    }

}
@end
import openpyxl, os
import pandas as pd

def xlsx_to_list(path):
    wb = openpyxl.load_workbook(path)
    ws = wb.worksheets[0]

    color_name = []
    color_code = []
    for cell in ws['A']:
        color_name.append(cell.value)
        color_code.append(cell.fill.fgColor.rgb)
        
    return str(color_name)

def xlsx_to_csv(path):
    # csvファイル名作成
    csv_path = path.split('.')[0] + '.csv'
    
    wb = openpyxl.load_workbook(path)
    ws = wb.worksheets[0]
    df = pd.DataFrame(ws.values)
    
    df.to_csv(csv_path, index = False, header = None)

    return "xlsx_to_csv success"
    
def list_to_xlsx(list):
    home = os.path.expanduser('~')
    file = home + '/Desktop/test.xlsx'
    
    wb = openpyxl.Workbook()
    ws = wb.worksheets[0]

    # 引数の文字列をリスト化
    new_list = eval(list)
    
    num = 0
    for i in range(1,1 +1):
        for j in range(1,len(new_list) +1):
            ws.cell(row = j, column = i).value = new_list[num]
            num = num + 1

    wb.save(file)
    
    return "list_to_xlsx success"

[Obj-C] 12 GUIアプリ : pandasでxlsx to csv

[M1 Mac, Big Sur 11.6.5, no Xcode]

Pythonのライブラリpandasとopenpyxlを使ってExcelファイルをCSVファイルに変換しました。

コードは以下の通りです。

#include <Cocoa/Cocoa.h>
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>

@implementation Convert

- (NSString *)ConvertFunc:(NSString *)path number:(int)num {
    switch (num) {
    case 1:{
        Py_Initialize();
        // pyファイルの指定
        PyObject* myModuleString = PyUnicode_FromString((char*)"xlsx_convert");

        // pyファイルのモジュール化
        PyObject* myModule = PyImport_Import(myModuleString);

        // pyファイル内の関数を指定
        PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_list");

        // 関数の引数を設定
        PyObject* args = PyTuple_Pack(1,PyUnicode_FromString((char *) [path UTF8String]));

        // 関数を実行し戻り値をPyObjectとして取得
        PyObject* myResult = PyObject_CallObject(myFunction,args);

        // PyObjectをconst char*に変換
        const char* result = PyUnicode_AsUTF8(myResult);
        NSString* result_ns = [NSString stringWithCString: result encoding:NSUTF8StringEncoding];

        NSLog(@"%@",result_ns);

        return result_ns;
        Py_FinalizeEx();
        break;
    }
    case 2:{
        Py_Initialize();

        PyObject* myModuleString = PyUnicode_FromString((char*)"xlsx_convert");
        PyObject* myModule = PyImport_Import(myModuleString);
        PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_csv");
        PyObject* args = PyTuple_Pack(1,PyUnicode_FromString((char *) [path UTF8String]));
        PyObject* myResult = PyObject_CallObject(myFunction,args);
        const char* result = PyUnicode_AsUTF8(myResult);
        NSString* result_ns = [NSString stringWithCString: result encoding:NSUTF8StringEncoding];

        return result_ns;
        Py_FinalizeEx();
        break;
    }      
    case 3:
        return @"case 3";
        break;
    case 4:
        return @"case 4";
        break;
    case 5:
        return @"case 5";
        break;
    case 6:
        return @"case 6";
        break;
    default:
        return @"default";
        break;
    }

}
@end
import openpyxl
import pandas as pd

def xlsx_to_list(path):
    wb = openpyxl.load_workbook(path)
    ws = wb.worksheets[0]

    color_name = []
    color_code = []
    for cell in ws['A']:
        color_name.append(cell.value)
        color_code.append(cell.fill.fgColor.rgb)
        
    return str(color_name)

def xlsx_to_csv(path):
    # csvファイル名作成
    csv_path = path.split('.')[0] + '.csv'
    
    wb = openpyxl.load_workbook(path)
    ws = wb.worksheets[0]
    df = pd.DataFrame(ws.values)
    
    df.to_csv(csv_path, index = False, header = None)

    return "xlsx_to_csv success"

[Obj-C] 11 GUIアプリ : switch文

[M1 Mac, Big Sur 11.6.5, no Xcode]

ONになっているラジオボタンによって処理を変えるためswitch文を書きました。if文と同様、C言語の書き方になります。

Objective-Cを学び始めてからずっと引っかかっているのが関数の引数です。第2引数以降に引数ラベルを付けるというのが何とも独特です。

さすがにSwiftでは廃止だろうと思うところですが、逆に第1引数にも引数ラベルを付けるようになっています。

まさかと思いつつさらに調べていたら、どうも名前つき引数導入はトレンドだということが分かりました。確かに引数を順番に並べなくてもよくて便利ではありますが、引数ラベルをわざわざ書くのが面倒なので小規模開発の私には不要です。

<該当箇所のみ>

- (IBAction) OnButton1Click:(id)sender {
	onoff_XlsxToList = radioButton_a1.state; 
	onoff_XlsxToCsv = radioButton_a2.state; 
	onoff_ListToXlsx = radioButton_b1.state; 
	onoff_ListToCsv = radioButton_b2.state; 
	onoff_CsvToXlsx = radioButton_c1.state; 
	onoff_CsvToList = radioButton_c2.state;

	if (radioButton_a1.state == 1){
		rbtn_num = 1;
	} else if (radioButton_a2.state == 1){
		rbtn_num = 2;
	} else if (radioButton_b1.state == 1){
		rbtn_num = 3;
	} else if (radioButton_b2.state == 1){
		rbtn_num = 4;
	} else if (radioButton_c1.state == 1){
		rbtn_num = 5;
	} else {
		rbtn_num = 6;
	}

	NSLog(@"%d",rbtn_num);

	NSString *path = [textBox1 stringValue];

	Convert *convert = [[Convert alloc] init];
	NSString *result = [convert ConvertFunc:path number:rbtn_num];
	[textview setString:result];
}
#include <Cocoa/Cocoa.h>
#include "process.h"
#include </Library/Frameworks/Python.framework/Versions/3.10/include/python3.10/Python.h>

@implementation Convert

- (NSString *)ConvertFunc:(NSString *)path number:(int)num {
    switch (num) {
    case 1:{
        Py_Initialize();
        // pyファイルの指定
        PyObject* myModuleString = PyUnicode_FromString((char*)"test");

        // pyファイルのモジュール化
        PyObject* myModule = PyImport_Import(myModuleString);

        // pyファイル内の関数を指定
        PyObject* myFunction = PyObject_GetAttrString(myModule,(char*)"xlsx_to_list");

        // 関数の引数を設定
        PyObject* args = PyTuple_Pack(1,PyUnicode_FromString((char *) [path UTF8String]));

        // 関数を実行し戻り値をPyObjectとして取得
        PyObject* myResult = PyObject_CallObject(myFunction,args);

        // PyObjectをconst char*に変換
        const char* result = PyUnicode_AsUTF8(myResult);
        NSString* result_ns = [NSString stringWithCString: result encoding:NSUTF8StringEncoding];

        NSLog(@"%@",result_ns);

        return result_ns;
        Py_FinalizeEx();
        break;
    }
    case 2:
        return @"case 2";
        break;
    case 3:
        return @"case 3";
        break;
    case 4:
        return @"case 4";
        break;
    case 5:
        return @"case 5";
        break;
    case 6:
        return @"case 6";
        break;
    default:
        return @"default";
        break;
    }
}
@end

[Obj-C] 10 GUIアプリ : NSScrollView, NSTextView

[M1 Mac, Big Sur 11.6.5, no Xcode]

垂直スクロールバー付きNSTextViewを配置しました。

NSScrollViewを配置してからNSTextViewをはめ込むという手法です。

Apple公式ドキュメントの内容を丸々拝借しました。直接リンクでアクセス可能です。

<該当箇所のみ>

NSScrollView* scrollview;
NSTextView* theTextView;

// NSScrollView
scrollview = [[[NSScrollView alloc] initWithFrame:NSMakeRect(20, 265-100-155, 320, 100)] autorelease];
NSSize contentSize = [scrollview contentSize];
[scrollview setBorderType:NSNoBorder];
[scrollview setHasVerticalScroller:YES];
[scrollview setHasHorizontalScroller:NO];
[scrollview setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];

// NSTextView
theTextView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)];
[theTextView setMinSize:NSMakeSize(0.0, contentSize.height)];
[theTextView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
[theTextView setVerticallyResizable:YES];
[theTextView setHorizontallyResizable:NO];
[theTextView setAutoresizingMask:NSViewWidthSizable];
[[theTextView textContainer] setContainerSize:NSMakeSize(contentSize.width, FLT_MAX)];
[[theTextView textContainer] setWidthTracksTextView:YES];

[scrollview setDocumentView:theTextView];

Putting an NSTextView Object in an NSScrollView