[C++] 03 FLTKのMakefile作成 サブディレクトリへの対応

[M1 Mac, Big Sur 11.6.1, FLTK 1.3.8]

自製アプリのJavaからC++への移植にあたり、複数ソースコード・複数サブディレクトリに対応したMakefileを作成しました。参考サイトの記事がなければ何日掛かったか分かりませんね。本当に感謝です。

appファイルを作成するには、カレントディレクトリをbinに変更し、”fltk-config –post [実行ファイル]”コマンドを使います。

CMakeという便利なツールがあることを途中で知りましたが、もうMakefileを作ってしまったのでこのまま進めます。趣味としてプログラミングを楽しまれるのであればMakefileの自作をお勧めします。

これでコーディングに専念できる環境がほぼ整いました。

# コンパイラ設定他
COMPILER = clang++
DEBUG = -g

# フラグ設定
CXXFLAGS = $(shell fltk-config --use-gl --use-images --cxxflags )
LDFLAGS = $(shell fltk-config --use-gl --use-images --ldflags ) -lc++ 

# includeパス設定
INCLUDE = -I../include -I/opt/homebrew/Cellar/fltk/1.3.8/include

# linkパス設定
LINK = -L/opt/homebrew/Cellar/jpeg/9e/lib -L/opt/homebrew/Cellar/libpng/1.6.37/lib

# 実行ファイル設定
TARGET = test
TARGETDIR = ../bin

# ソースコードパス
SRCROOT   = .

# oファイルの出力ディレクトリ
OBJROOT   = ../obj

# ソースディレクトリのリスト化
SRCDIRS := $(shell find $(SRCROOT) -type d)

# ソースディレクトリから全てのcppファイルをリスト化
SOURCES   = $(foreach dir, $(SRCDIRS), $(wildcard $(dir)/*.cpp))

# cppファイルのリストからオブジェクトファイルをリスト化
OBJECTS   = $(addprefix $(OBJROOT), $(patsubst ./%,/%,$(SOURCES:.cpp=.o)))

# oファイルの出力ディレクトリをリスト化
OBJDIRS   = $(addprefix $(OBJROOT), $(patsubst ./%,/%,$(SRCDIRS)))

# cppファイルからoファイル作成
$(OBJROOT)/%.o: $(SRCROOT)/%.cpp
	@if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi
	$(COMPILER) $(CXXFLAGS) $(INCLUDE) $(DEBUG) -o $@ -c $<

# oファイルから実行ファイル作成
$(TARGET): $(OBJECTS)
	$(COMPILER) -o $(TARGETDIR)/$@ $(OBJECTS) $(LDFLAGS) $(LINK)

# 依存ファイル
-include $(DEPENDS)

# 全ソース強制コンパイル
all: clean $(TARGET)

# 全ファイル削除(cpp以外)
clean:
	rm -rf $(OBJDIRS) $(DEPENDS) $(TARGETDIR)/$(TARGET)

参考サイト

[C++] 02 FLTKのMakefile修正(appファイル作成)

[M1 Mac, Big Sur 11.6.1, FLTK 1.3.8]

昨日の段階ではmakeコマンドで実行ファイルを作成できませんでしたが、少し知識を仕入れたので更にその先のappファイルを一発作成できるようMakefileを修正しました。

Mac用であるappファイルはこのように簡単に作れます。ただWindowsストア用appxファイルはどうやったら作成できるのか。かなり難航しそうな予感がします。

Makefileの記述はぱっと見、取っつきにくいですが、そのうち慣れるでしょう。

CXX = $(shell fltk-config --cxx)
DEBUG = -g
CXXFLAGS = $(shell fltk-config --use-gl --use-images --cxxflags ) -I.
LDFLAGS = $(shell fltk-config --use-gl --use-images --ldflags ) -lc++ -L/opt/homebrew/Cellar/jpeg/9e/lib -L/opt/homebrew/Cellar/libpng/1.6.37/lib
LINK = $(CXX)
TARGET = test
OBJS = test.o
SRCS = test.cpp
.SUFFIXES: .o .cxx

# appファイル作成関連
STRIP      = strip
POSTBUILD  = fltk-config --post

# コマンド構成
%.o: %.cxx
	$(CXX) $(CXXFLAGS) $(DEBUG) -c $<
$(TARGET): $(OBJS)
	$(LINK) -o $(TARGET) $(OBJS) $(LDFLAGS)
	$(STRIP) $@
	$(POSTBUILD) $@

clean: $(TARGET) $(OBJS)
	rm -f *.o 2> /dev/null
	rm -f $(TARGET) 2> /dev/null

参考サイト:Article #599: Beginners Guide to fltk-config

[C++] 01 FLTKのMakefile作成

[M1 Mac, Big Sur 11.6.1, FLTK 1.3.8]

GUI作成ツールのFLTKをいじり始めました。今のところ非IDE環境です。

まずはHello WorldのMakefileをFLTKマニュアル(全1123ページの長編)を参考に作成しましたが、実行時エラーが発生しました。後で見ると–use-glや–use-imagesなど余計なオプションがあるものの、そのまま進めています。

試行錯誤の末、makeコマンド一発での実行ファイル作成をあきらめ、生成されたコマンドのオプションを修正し2つのコマンドで実行ファイルを作成しました。オプションに”-lc++”を追加することで”clang: error: linker command failed with exit code 1″の沼から脱しました。プログラミング強者が集うStack Overflow英語版のおかげです。なおこのオプション追加はIntel Macでも必要です。

Python, Javaと学んできてついに本丸のC++に到達しました。挫折しないようのんびり進めていきます。

CXX = $(shell fltk-config --cxx)
DEBUG =-g
CXXFLAGS = $(shell fltk-config --use-gl --use-images --cxxflags ) -I.
LDFLAGS = $(shell fltk-config --use-gl --use-images --ldflags )
LDSTATIC = $(shell fltk-config --use-gl --use-images --ldstaticflags )
LINK = $(CXX)
TARGET = test
OBJS = test.o
SRCS = test.cpp
.SUFFIXES: .o .cxx
%.o: %.cxx
	$(CXX) $(CXXFLAGS) $(DEBUG) -c $<
all: $(TARGET)
	$(LINK) -o $(TARGET) $(OBJS) $(LDFLAGS)
$(TARGET): $(OBJS)
test.o: test.cpp
clean: $(TARGET) $(OBJS)
	rm -f *.o 2> /dev/null
	rm -f $(TARGET) 2> /dev/null
# makeで作成したコンパイルとビルドのコマンド
# 後者から-lpngと-ljpegを削除して-lc++を追加(Intel Macでは削除不要)

clang++ -I/opt/homebrew/Cellar/fltk/1.3.8/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT -I. -c -o test.o test.cpp && \
cc -L/opt/homebrew/Cellar/fltk/1.3.8/lib -lc++ -lfltk_images -lz -lfltk_gl -framework OpenGL -lfltk -lpthread -framework Cocoa test.o -o test

# -lpngと-ljpegを削除しない場合は以下のコマンド(-Lオプション2つと-lc++追加)
clang++ -I/opt/homebrew/Cellar/fltk/1.3.8/include -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_THREAD_SAFE -D_REENTRANT -I.   -c -o test.o test.cpp && \
cc -L/opt/homebrew/Cellar/fltk/1.3.8/lib -L/opt/homebrew/Cellar/jpeg/9e/lib -L/opt/homebrew/Cellar/libpng/1.6.37/lib -lc++ -ljpeg -lpng -lfltk_images -lz -lfltk_gl -framework OpenGL -lfltk -lpthread -framework Cocoa  test.o   -o test

ウィジェット・ツールキットの検討

JavaのSwingでGUIアプリを作成する際、既存のLook and Feelを混在させるとその制約を受けて自由度が低くなるケースが多々あるため、言語にとらわれず新たなウィジェット・ツールキットを検討しています。

C++であればFLTKあたりが比較的容易に導入できそうなので、勉強を兼ねてSwingで開発中のアプリを移植できるか試しにいじってみることにしました。そこそこの外観で処理速度アップが目標です。

Swingの使いこなしの方もまだまだですし、並行してスキルアップに努めます。SynthLookAndFeelを使えばデフォルトのLook and Feelを無効化できるので、アプリの処理速度にこだわらなければこれで好きなように描画可能です。

[Java]103 Look and Feelの外観

これまで様々なLook and Feelを扱ってきましたが、Swingで作ったアプリの外観は美的観点から問題があるように思います。

デフォルトであるクロスプラットフォームL&FのMetalは青色が幅を利かせています。WindowsのTabbedPaneはbackgroundが薄い青色で塗りつぶされて見栄えは最悪です。

これを避けるためにコードの頭でUIManager.setLookAndFeelにてL&FをシステムL&Fに変更してみました。しかしWindowsのシステムL&Fも今ひとつです。特にJSliderのつまみは何故か透明で見れたもんじゃないです。加えてJButtonのマージンは横方向が無駄に大きく、フォントサイズをかなり小さくしないと”…”と表示されてしまいます。ここまで酷いとJavaでWindowsアプリを作る気にはならないでしょう。

MacのシステムL&FであるAquaは私の使う範囲ではJButtonのデザイン以外特に問題ないのですが、Windowsの場合はMetalもシステムL&Fもダメなので、結局JComponentを継承したクラスを必要なComponentに応じて自製しそれぞれ描画させています。

Swingの機能自体は素晴らしいというのに、肝心の見た目が悪ければプログラマとユーザーは離れてしまいます。新たなGUIライブラリの出現に期待したいところです。

[Java]102 JButtonの継承

JButtonのToolTipTextの位置を調整するため、これを継承したJButtonExクラスを作成しました。

コンストラクタも自動的に継承されるものと思っていましたが、きちんと書かないとエラーになります。

public static class JButtonEx extends JButton {
		public JButtonEx(String str) {
			setText(str);
		}
		public JButtonEx() {
		}
		@Override
		public Point getToolTipLocation(MouseEvent e) {
			Point po = e.getPoint();
			po.x += 20;
			po.y -= 10;
			return po;
		}
	}

[Java]101 UIDefaultsの内容を出力

Look and Feelの自製を見据え、UIDefaultsの内容を出力してみました。

コードはGitHubにあったものに加筆してkeyだけでなくvalueも出力できるようにしました。

以下のようになります。

import java.util.Enumeration;
import java.util.TreeSet;
import javax.swing.UIDefaults;
import javax.swing.UIManager;

public class ListUIDefaultsKeys {
	public static void main(String args[]) throws Exception {
		UIManager.LookAndFeelInfo looks[] = UIManager.getInstalledLookAndFeels();

		for (UIManager.LookAndFeelInfo info : looks) {
			System.out.println("Installed L&F "+info.getName());
		}

		TreeSet<String> lafDefaultKeys = new TreeSet<>();
		UIDefaults defaults = UIManager.getDefaults();

		Enumeration<Object> newKeys = defaults.keys();
		while (newKeys.hasMoreElements()) {
				lafDefaultKeys.add(newKeys.nextElement().toString());
		}

		System.out.println("================== UIDefaults Keys & Values ==================");
		
		int i = 1;
		for (String key : lafDefaultKeys) {
				try {
						String value = defaults.get(key).toString();
						System.out.println(String.format("%03d", i) + " " + key );
						System.out.println("    " + value);
				} catch (Exception e){
						e.printStackTrace();
				}
				i++;
		}
	}
}
--------------------------------------------------

出力の一部
--------------------------------------------------
================== UIDefaults Keys & Values ==================
001 AbstractButton.click.textAndMnemonic
    クリック
002 AbstractDocument.addition.textAndMnemonic
    追加
003 AbstractDocument.deletion.textAndMnemonic
    削除
004 AbstractDocument.redo.textAndMnemonic
    やり直し
005 AbstractDocument.styleChange.textAndMnemonic
    スタイル変更
006 AbstractDocument.undo.textAndMnemonic
    元に戻す
007 AbstractUndoableEdit.redo.textAndMnemonic
    やり直し
008 AbstractUndoableEdit.undo.textAndMnemonic
    元に戻す
009 AuditoryCues.allAuditoryCues
    [Ljava.lang.Object;@4ac68d3e
010 AuditoryCues.cueList
    [Ljava.lang.Object;@4ac68d3e
011 AuditoryCues.noAuditoryCues
    [Ljava.lang.Object;@3339ad8e
<以下略>

参考サイト

[Java]100 VSCode:Extension Pack for Javaとjavacコマンドの比較

表題の件、Javaソースコードのコンパイルエラー時に得られる情報量に雲泥の差がありました。Extension Pack for Javaは設定を変えればjavacと同レベルになるのでしょうがデフォルトがあまりにもしょぼ過ぎました。

# VSCode Extension Pack for Java
--------------------------------------------------
Exception in thread "main" java.lang.Error: Unresolved compilation problems: 
        Unreachable catch block for InvalidPropertiesFormatException. This exception is never thrown from the try statement body
        Unreachable catch block for FileNotFoundException. This exception is never thrown from the try statement body
        Unreachable catch block for IOException. This exception is never thrown from the try statement body
        at XMLtoSetProperty.main(XMLtoSetProperty.java:32)
--------------------------------------------------

# javacコマンド
--------------------------------------------------
XMLtoSetProperty.java:32: エラー: 例外FileNotFoundExceptionは対応するtry文の本体ではスローされません
		} catch (FileNotFoundException e){
		  ^
XMLtoSetProperty.java:34: エラー: 例外IOExceptionは対応するtry文の本体ではスローされません
		} catch (IOException e){
		  ^
XMLtoSetProperty.java:36: エラー: 例外InvalidPropertiesFormatExceptionはすでに捕捉されています
		} catch (InvalidPropertiesFormatException e){
		  ^
XMLtoSetProperty.java:58: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
				in.close();
				        ^
XMLtoSetProperty.java:54: エラー: 例外FileNotFoundExceptionは報告されません。スローするには、捕捉または宣言する必要があります
			in = new FileInputStream(file);
			     ^
XMLtoSetProperty.java:55: エラー: 例外IOExceptionは報告されません。スローするには、捕捉または宣言する必要があります
			settings.loadFromXML(in);
			                    ^
エラー6個
--------------------------------------------------

VSCodeの出力内容でコードを修正できる方はあまりいないでしょう。javacコマンドであれば直すべき箇所が一目瞭然で一発修正できます。

開発速度への影響が大きいのでVSCodeでの実行がうまくいかない時は即コマンドで確認すべきだと痛感しました。

[Java] 99 OS名とLinuxディストリビューション名の取得

自製アプリのクロスプラットフォーム化にあたりOSやディストリビューションによってフォントサイズを変える必要が生じたため、以下のメソッドを作成して対応しました。

UbuntuやLinux Mint以外で”cat /etc/issue”コマンドにてディストリビューション名が分かるかどうかは不明です。

言語仕様とは言え、変数(フォントサイズ)を一々配列に格納するのは面倒です。

// Windows,Macはサイズ8,Ubuntuは7,Linux Mintは10に設定

public static int font_size(){
	String OS = System.getProperty("os.name").toLowerCase();
	int[] font_sizes = new int[1];
	if (OS.contains("linux")){
		try{
			Process p = Runtime.getRuntime().exec("cat /etc/issue");
			try{
				p.waitFor();
				InputStream is = p.getInputStream();
				InputStreamReader inputStreamReader = new InputStreamReader(is);
				Stream<String> streamOfString= new BufferedReader(inputStreamReader).lines();
				String str = streamOfString.collect(Collectors.joining());
								
				if (str.contains("Ubuntu")){
					int font_size = 7;
					font_sizes[0] = font_size;
				}else if (str.contains("Mint")){
					int font_size = 10;
					font_sizes[0] = font_size;
				}else{
					int font_size = 8;
					font_sizes[0] = font_size;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	} else {
		int font_size = 8;
		font_sizes[0] = font_size;
	}
	return font_sizes[0];
}

[Java] 98 JColorChooserのshowDialogメソッドを自製

前回の続きです。

前の記事のコードではJcolorChooserダイアログの取消ボタンが効かず、変更した色のRGB値が返されてしまいます。そこでActionListenerとしてokListenerとcancelListenerを追加しました。

それぞれのアクションとして戻り値のRGBA値を作成しますが、public修飾子が使えずこれらをActionListenerの外に渡す方法が分からなくて少々苦労しました。

結局、初期設定で作成しておいたint配列color_caseに移し替えるという何とも泥臭い方法で取り出しました。Javaの仕様ではこうするしかないようです。

okListenerとcancelListenerを今回のように使っているコード例はGoogle検索上位では見あたらなかったのでアップしておきます。

<メソッドのみ>

static int[] showDialog(Component component, String title, Color initialColor) throws HeadlessException{
		int[] color_case = new int[4];
		JColorChooser chooser = new JColorChooser(initialColor != null ? initialColor : Color.white);

		String swatches = UIManager.getString("ColorChooser.swatchesNameText");
		String hsv = UIManager.getString("ColorChooser.hsvNameText");
		String hsl = UIManager.getString("ColorChooser.hslNameText");
		String rgb = UIManager.getString("ColorChooser.rgbNameText");
		String cmyk = UIManager.getString("ColorChooser.cmykNameText");

		List<String> list = Arrays.asList(hsv,hsl,rgb);

		for (AbstractColorChooserPanel p : chooser.getChooserPanels()) {
			if (!list.contains(p.getDisplayName())) {
				chooser.removeChooserPanel(p);
			}
		}
		
		ColorTracker tracker = new ColorTracker(chooser,initialColor);

		ActionListener okListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				int[] colors = tracker.getColor();
				color_case[0] = colors[0];
				color_case[1] = colors[1];
				color_case[2] = colors[2];
				color_case[3] = colors[3];
			}
		};

		ActionListener cancelListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				int[] colors = tracker.getInitial();
				color_case[0] = colors[0];
				color_case[1] = colors[1];
				color_case[2] = colors[2];
				color_case[3] = colors[3];
			}
		};

		// ダイアログを作成
		JDialog dialog = JColorChooser.createDialog(component, title, true, chooser, okListener, cancelListener);
		dialog.setVisible(true);

		return color_case;
	}

	static class ColorTracker implements Serializable{
		JColorChooser chooser;
		Color color;
		int red;
		int green;
		int blue;
		int alpha;
		int[] colors;
	  
		ColorTracker(JColorChooser Chooser,Color initialColor){
			this.chooser = Chooser;
			this.color = initialColor;
			
		}
	  
		int[] getColor(){
			this.color = this.chooser.getColor();

			colors = new int[4];
			colors[0] = this.color.getRed();
			colors[1] = this.color.getGreen();
			colors[2] = this.color.getBlue();
			colors[3] = this.color.getAlpha();

			return colors;
		}
		int[] getInitial(){
			colors = new int[4];
			colors[0] = this.color.getRed();
			colors[1] = this.color.getGreen();
			colors[2] = this.color.getBlue();
			colors[3] = this.color.getAlpha();

			return colors;
		}
	}