ikkVisualKit 0.0.0__tar.gz → 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/PKG-INFO +1 -1
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/CTkUiBase.py +110 -73
- ikkvisualkit-0.0.1/ikkVisualKit/Dialog/DialogGridSelecter.py +222 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/__init__.py +3 -1
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/__init__.py +13 -1
- ikkvisualkit-0.0.1/ikkVisualKit/image/alert.dio.png +0 -0
- ikkvisualkit-0.0.1/ikkVisualKit/image/disconnect.dio.png +0 -0
- ikkvisualkit-0.0.1/ikkVisualKit/image/folder.dio.png +0 -0
- ikkvisualkit-0.0.1/ikkVisualKit/image/start.dio.png +0 -0
- ikkvisualkit-0.0.1/ikkVisualKit/image/swap.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit.egg-info/PKG-INFO +1 -1
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit.egg-info/SOURCES.txt +5 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/pyproject.toml +1 -1
- ikkvisualkit-0.0.0/ikkVisualKit/image/start.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/LICENSE +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/README.md +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/CdialogBase.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/CdialogOk.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/CdialogYesNo.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/DialogBase.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/DialogOk.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/DialogSelectReaderPort.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Dialog/DialogYesNo.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/TkUiBase.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Tray/ResidentApp.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Tray/TrayMenu.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Tray/__init__.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Window/WindowBase.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Window/WindowBaseModan.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/Window/__init__.py +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/clear.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/goto.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/help.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/link.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/lock.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/log.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/open.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/recipe.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/save.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/search.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/setting.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit/image/unlock.dio.png +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit.egg-info/dependency_links.txt +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit.egg-info/requires.txt +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/ikkVisualKit.egg-info/top_level.txt +0 -0
- {ikkvisualkit-0.0.0 → ikkvisualkit-0.0.1}/setup.cfg +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import customtkinter as ctk
|
|
2
1
|
import os
|
|
3
2
|
import tkinter as tk
|
|
4
3
|
from pathlib import Path
|
|
@@ -7,22 +6,20 @@ from importlib.resources import files
|
|
|
7
6
|
from io import BytesIO
|
|
8
7
|
|
|
9
8
|
from PIL import Image
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import customtkinter as ctk
|
|
10
|
+
#==============================================================================
|
|
12
11
|
class CTkButtonWithTooltip(ctk.CTkButton):
|
|
13
12
|
"""ツールチップ対応のカスタムCTkButtonクラス"""
|
|
14
13
|
def __init__(self, *args, **kwargs):
|
|
15
14
|
super().__init__(*args, **kwargs)
|
|
16
15
|
self._ui_base: Optional['CTkUiBase'] = None
|
|
17
16
|
self._command: Optional[Callable[[], Any]] = None
|
|
18
|
-
|
|
19
17
|
@property
|
|
20
18
|
def tooltip_text(self) -> Optional[str]:
|
|
21
19
|
"""ツールチップテキストを取得"""
|
|
22
20
|
if self._ui_base:
|
|
23
21
|
return self._ui_base._button_tooltips.get(self)
|
|
24
22
|
return None
|
|
25
|
-
|
|
26
23
|
@tooltip_text.setter
|
|
27
24
|
def tooltip_text(self, value: Optional[str]):
|
|
28
25
|
"""ツールチップテキストを設定"""
|
|
@@ -31,12 +28,10 @@ class CTkButtonWithTooltip(ctk.CTkButton):
|
|
|
31
28
|
self._ui_base._button_tooltips[self] = value
|
|
32
29
|
else:
|
|
33
30
|
self._ui_base._button_tooltips.pop(self, None)
|
|
34
|
-
|
|
35
31
|
@property
|
|
36
32
|
def command(self) -> Optional[Callable[[], Any]]:
|
|
37
33
|
"""クリック時のコマンドを取得"""
|
|
38
34
|
return self._command
|
|
39
|
-
|
|
40
35
|
@command.setter
|
|
41
36
|
def command(self, func: Optional[Callable[[], Any]]):
|
|
42
37
|
"""クリック時のコマンドを設定"""
|
|
@@ -45,22 +40,24 @@ class CTkButtonWithTooltip(ctk.CTkButton):
|
|
|
45
40
|
self.configure(command=func, state=tk.NORMAL)
|
|
46
41
|
else:
|
|
47
42
|
self.configure(state=tk.DISABLED)
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
#==============================================================================
|
|
50
44
|
class TextInputPair:
|
|
51
45
|
"""ラベルとテキストボックスのペア"""
|
|
52
46
|
def __init__(self, label: ctk.CTkLabel, entry: ctk.CTkEntry):
|
|
53
|
-
self.label = label
|
|
54
|
-
self.entry = entry
|
|
47
|
+
self.label = label #ラベル
|
|
48
|
+
self.entry = entry #読み取り専用テキストボックス
|
|
55
49
|
self._ui_base: Optional['CTkUiBase'] = None
|
|
56
|
-
|
|
50
|
+
self._command: Optional[Callable[[], Any]] = None
|
|
51
|
+
self._attention: bool = False # attention フラグ
|
|
52
|
+
self._blink_task_id: Optional[str] = None # 点滅タスクのID
|
|
53
|
+
self._original_border_width: int = self.entry.cget("border_width") # 元の枠線幅
|
|
54
|
+
self._original_border_color: str = self.entry.cget("border_color") # 元の枠線色
|
|
57
55
|
@property
|
|
58
56
|
def tooltip_text(self) -> Optional[str]:
|
|
59
57
|
"""ツールチップテキストを取得"""
|
|
60
58
|
if self._ui_base:
|
|
61
59
|
return self._ui_base._entry_tooltips.get(self.entry)
|
|
62
60
|
return None
|
|
63
|
-
|
|
64
61
|
@tooltip_text.setter
|
|
65
62
|
def tooltip_text(self, value: Optional[str]):
|
|
66
63
|
"""ツールチップテキストを設定"""
|
|
@@ -69,56 +66,94 @@ class TextInputPair:
|
|
|
69
66
|
self._ui_base._entry_tooltips[self.entry] = value
|
|
70
67
|
else:
|
|
71
68
|
self._ui_base._entry_tooltips.pop(self.entry, None)
|
|
72
|
-
|
|
73
69
|
@property
|
|
74
|
-
def
|
|
70
|
+
def command(self) -> Optional[Callable[[], Any]]:
|
|
71
|
+
"""クリック時のコマンドを取得"""
|
|
72
|
+
return self._command
|
|
73
|
+
@command.setter
|
|
74
|
+
def command(self, func: Optional[Callable[[], Any]]):
|
|
75
|
+
"""クリック時のコマンドを設定(テキストボックス・ラベル両方のクリックに対応)"""
|
|
76
|
+
self._command = func
|
|
77
|
+
if func is not None:
|
|
78
|
+
# テキストボックスのクリックイベントをバインド
|
|
79
|
+
self.entry.bind("<Button-1>", lambda e: func())
|
|
80
|
+
# ラベルのクリックイベントをバインド
|
|
81
|
+
self.label.bind("<Button-1>", lambda e: func())
|
|
82
|
+
else:
|
|
83
|
+
# クリックイベントの削除
|
|
84
|
+
self.entry.unbind("<Button-1>")
|
|
85
|
+
self.label.unbind("<Button-1>")
|
|
86
|
+
@property
|
|
87
|
+
def attention(self) -> bool:
|
|
88
|
+
"""attention プロパティを取得"""
|
|
89
|
+
return self._attention
|
|
90
|
+
@attention.setter
|
|
91
|
+
def attention(self, value: bool):
|
|
92
|
+
"""attention プロパティを設定(True=枠線点滅、False=通常状態)"""
|
|
93
|
+
self._attention = value
|
|
94
|
+
if value:
|
|
95
|
+
self._start_border_blink()
|
|
96
|
+
else:
|
|
97
|
+
self._stop_border_blink()
|
|
98
|
+
@property
|
|
99
|
+
def value(self) -> str:
|
|
75
100
|
"""表示テキストを取得"""
|
|
76
101
|
return self.entry.get()
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def display_text(self, text: str):
|
|
102
|
+
@value.setter
|
|
103
|
+
def value(self, text: str):
|
|
80
104
|
"""表示テキストを設定(読み取り専用テキストボックスに値を設定)"""
|
|
81
105
|
# 読み取り専用を一時的に解除して値を設定
|
|
82
106
|
self.entry.configure(state="normal")
|
|
83
107
|
self.entry.delete(0, "end")
|
|
84
108
|
self.entry.insert(0, text)
|
|
85
109
|
self.entry.configure(state="disabled")
|
|
86
|
-
|
|
87
|
-
# 背景色を黄色く2回点滅
|
|
88
|
-
self._blink_entry()
|
|
89
|
-
|
|
110
|
+
self._blink_entry()# 背景色を黄色く2回点滅
|
|
90
111
|
def _blink_entry(self):
|
|
91
112
|
"""エントリーの背景色を黄色く2回点滅"""
|
|
92
113
|
if not self._ui_base or not self._ui_base._tkRoot:
|
|
93
114
|
return
|
|
94
|
-
|
|
95
115
|
blink_state = [0] # 点滅状態を保持(0=黄→1=元→2=黄→3=元→終了)
|
|
96
116
|
original_bg = self.entry.cget("fg_color") # 元の背景色を保存
|
|
97
|
-
|
|
98
117
|
def toggle_color():
|
|
99
118
|
if blink_state[0] % 2 == 0:
|
|
100
119
|
self.entry.configure(fg_color="#FFFF00") # 黄色
|
|
101
120
|
else:
|
|
102
121
|
self.entry.configure(fg_color=original_bg) # 元の色
|
|
103
|
-
|
|
104
122
|
blink_state[0] += 1
|
|
105
123
|
if blink_state[0] < 4: # 4回の遷移 = 2回点滅
|
|
106
124
|
self._ui_base._tkRoot.after(300, toggle_color)
|
|
107
|
-
|
|
108
125
|
toggle_color()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
def _start_border_blink(self):
|
|
127
|
+
"""エントリーの枠線を赤く太くして点滅"""
|
|
128
|
+
if not self._ui_base or not self._ui_base._tkRoot:
|
|
129
|
+
return
|
|
130
|
+
# 前回の点滅をキャンセル
|
|
131
|
+
if self._blink_task_id:
|
|
132
|
+
self._ui_base._tkRoot.after_cancel(self._blink_task_id)
|
|
133
|
+
blink_state = [True] # 点滅状態(True=赤、False=元)
|
|
134
|
+
def toggle_border():
|
|
135
|
+
if blink_state[0]:
|
|
136
|
+
self.entry.configure(border_width=4, border_color="#FF0000") # 赤、太い
|
|
137
|
+
else:
|
|
138
|
+
self.entry.configure(border_width=self._original_border_width,
|
|
139
|
+
border_color=self._original_border_color) # 元に戻す
|
|
140
|
+
blink_state[0] = not blink_state[0]
|
|
141
|
+
# 点続けるために next task をスケジュール
|
|
142
|
+
if self._attention:
|
|
143
|
+
self._blink_task_id = self._ui_base._tkRoot.after(500, toggle_border)
|
|
144
|
+
toggle_border()
|
|
145
|
+
def _stop_border_blink(self):
|
|
146
|
+
"""エントリーの枠線の点滅を停止して元に戻す"""
|
|
147
|
+
if self._blink_task_id:
|
|
148
|
+
if self._ui_base and self._ui_base._tkRoot:
|
|
149
|
+
self._ui_base._tkRoot.after_cancel(self._blink_task_id)
|
|
150
|
+
self._blink_task_id = None
|
|
151
|
+
# 枠線を元の状態に戻す
|
|
152
|
+
if self._ui_base and self._ui_base._tkRoot:
|
|
153
|
+
self.entry.configure(border_width=self._original_border_width,
|
|
154
|
+
border_color=self._original_border_color)
|
|
121
155
|
|
|
156
|
+
#==============================================================================
|
|
122
157
|
class LogViewBox:
|
|
123
158
|
"""ログ表示用テキストボックス"""
|
|
124
159
|
def __init__(self, text_widget: ctk.CTkTextbox, x: int, y: int, width: int, initial_lines: int = 2):
|
|
@@ -128,73 +163,66 @@ class LogViewBox:
|
|
|
128
163
|
self.width = width
|
|
129
164
|
self.lines = initial_lines
|
|
130
165
|
self.parent = None
|
|
131
|
-
|
|
132
166
|
# ログレベルごとの色定義
|
|
133
167
|
self._log_level_colors = {
|
|
134
168
|
"ERROR": "#ff6b6b", # 赤
|
|
135
169
|
"WARN": "#ffa500", # オレンジ
|
|
136
|
-
"INFO": "#
|
|
170
|
+
"INFO": "#0000ff", # 青
|
|
137
171
|
"DEBUG": "#909090" # グレー
|
|
138
172
|
}
|
|
139
|
-
|
|
140
173
|
# タグ設定
|
|
141
174
|
for level, color in self._log_level_colors.items():
|
|
142
175
|
text_widget.tag_config(level, foreground=color)
|
|
143
|
-
|
|
144
176
|
def add_log(self, message: str, level: str = "INFO"):
|
|
145
177
|
"""ログを追加(最下行に追加、自動スクロール)"""
|
|
146
178
|
if level not in self._log_level_colors:
|
|
147
179
|
level = "INFO"
|
|
148
|
-
|
|
149
180
|
# テキストボックスを編集可能に
|
|
150
181
|
self.text_widget.configure(state="normal")
|
|
151
182
|
|
|
152
|
-
#
|
|
153
|
-
|
|
183
|
+
# テキストボックスが空でなければ改行を追加
|
|
184
|
+
current_text = self.text_widget.get("1.0", "end-1c")
|
|
185
|
+
if current_text:
|
|
186
|
+
self.text_widget.insert("end", "\n")
|
|
154
187
|
|
|
188
|
+
# ログエントリを作成(改行なし)
|
|
189
|
+
log_entry = f"[{level}] {message}"
|
|
155
190
|
# 末尾に追加
|
|
156
|
-
start_index = self.text_widget.index("end
|
|
191
|
+
start_index = self.text_widget.index("end")
|
|
157
192
|
self.text_widget.insert("end", log_entry)
|
|
158
|
-
|
|
159
193
|
# ログレベルに応じて色付け
|
|
160
|
-
end_index = self.text_widget.index("end
|
|
194
|
+
end_index = self.text_widget.index("end")
|
|
161
195
|
self.text_widget.tag_add(level, start_index, end_index)
|
|
162
|
-
|
|
163
196
|
# 最下行にスクロール
|
|
164
197
|
self.text_widget.see("end")
|
|
165
|
-
|
|
166
198
|
# 読み取り専用に戻す
|
|
167
199
|
self.text_widget.configure(state="disabled")
|
|
168
|
-
|
|
169
200
|
def expand(self, lines: int):
|
|
170
201
|
"""表示行数を拡張"""
|
|
171
202
|
self.lines = lines
|
|
172
203
|
line_height = 20 # 1行あたりのピクセル高さ(概算)
|
|
173
204
|
new_height = lines * line_height + 10
|
|
174
|
-
self.text_widget.configure(height=
|
|
175
|
-
|
|
205
|
+
self.text_widget.configure(height=80)
|
|
176
206
|
def collapse(self):
|
|
177
207
|
"""デフォルト表示に戻す"""
|
|
178
208
|
self.expand(self.lines)
|
|
179
|
-
|
|
180
209
|
def clear(self):
|
|
181
210
|
"""ログをクリア"""
|
|
182
211
|
self.text_widget.configure(state="normal")
|
|
183
212
|
self.text_widget.delete("1.0", "end")
|
|
184
213
|
self.text_widget.configure(state="disabled")
|
|
185
|
-
|
|
186
214
|
@property
|
|
187
215
|
def tooltip_text(self) -> Optional[str]:
|
|
188
216
|
"""ツールチップ(互換性のため)"""
|
|
189
217
|
return None
|
|
190
|
-
|
|
191
218
|
@tooltip_text.setter
|
|
192
219
|
def tooltip_text(self, value: Optional[str]):
|
|
193
220
|
"""ツールチップ設定"""
|
|
194
221
|
if self.parent and value:
|
|
195
222
|
self.parent._entry_tooltips[self.text_widget] = value
|
|
196
|
-
|
|
197
|
-
|
|
223
|
+
#==============================================================================
|
|
224
|
+
#
|
|
225
|
+
#==============================================================================
|
|
198
226
|
class CTkUiBase:
|
|
199
227
|
"""
|
|
200
228
|
Tkは必ずメインスレッドで動かす。
|
|
@@ -306,17 +334,17 @@ class CTkUiBase:
|
|
|
306
334
|
if size_rank == 1:
|
|
307
335
|
btn_width = 96
|
|
308
336
|
btn_height = 64
|
|
309
|
-
btn_font_size =
|
|
337
|
+
btn_font_size = 16
|
|
310
338
|
btn_compound = "bottom" # アイコンの下に文字
|
|
311
339
|
fg_color = "#ADD8E6" # ライトブルー
|
|
312
|
-
hover_color = "#
|
|
340
|
+
hover_color = "#FF911B"
|
|
313
341
|
elif size_rank == 2:
|
|
314
342
|
btn_width = 128
|
|
315
343
|
btn_height = 64
|
|
316
|
-
btn_font_size =
|
|
344
|
+
btn_font_size = 16
|
|
317
345
|
btn_compound = "bottom" # アイコンの下に文字
|
|
318
346
|
fg_color = "#00FF00" # Lime
|
|
319
|
-
hover_color = "#
|
|
347
|
+
hover_color = "#FF911B"
|
|
320
348
|
else: # size_rank == 0
|
|
321
349
|
btn_width = 48
|
|
322
350
|
btn_height = 32
|
|
@@ -333,8 +361,9 @@ class CTkUiBase:
|
|
|
333
361
|
"state": tk.NORMAL if command is not None else tk.DISABLED,
|
|
334
362
|
"fg_color": fg_color,
|
|
335
363
|
"hover_color": hover_color,
|
|
336
|
-
"text_color": "
|
|
337
|
-
"
|
|
364
|
+
"text_color": "#1a1a1a", # より濃い黒
|
|
365
|
+
"text_color_disabled": "#1a1a1a", # DISABLED時も黒
|
|
366
|
+
"font": ctk.CTkFont("Meiryo",size=btn_font_size, weight="normal"),
|
|
338
367
|
"border_width": 2,
|
|
339
368
|
"border_color": "#004382",
|
|
340
369
|
"corner_radius": 4,
|
|
@@ -353,17 +382,18 @@ class CTkUiBase:
|
|
|
353
382
|
|
|
354
383
|
# デフォルトの色を保存
|
|
355
384
|
default_fg_color = button_kwargs["fg_color"]
|
|
385
|
+
default_text_color = button_kwargs["text_color"]
|
|
356
386
|
hover_fg_color = button_kwargs["hover_color"]
|
|
357
387
|
|
|
358
388
|
#マウスホバー処理
|
|
359
389
|
def on_enter(event):
|
|
360
|
-
btn.configure(fg_color=hover_fg_color, text_color=
|
|
390
|
+
btn.configure(fg_color=hover_fg_color, text_color=default_text_color)
|
|
361
391
|
tooltip_text = self._button_tooltips.get(btn)
|
|
362
392
|
if tooltip_text:
|
|
363
393
|
self._show_tooltip(btn, tooltip_text)
|
|
364
394
|
#マウスホバー解除処理
|
|
365
395
|
def on_leave(event):
|
|
366
|
-
btn.configure(fg_color=default_fg_color, text_color=
|
|
396
|
+
btn.configure(fg_color=default_fg_color, text_color=default_text_color)
|
|
367
397
|
self._hide_tooltip()
|
|
368
398
|
#ホバーイベントのバインド
|
|
369
399
|
btn.bind("<Enter>", on_enter)
|
|
@@ -438,28 +468,35 @@ class CTkUiBase:
|
|
|
438
468
|
def make_LogTerminal(self,
|
|
439
469
|
x: int, y: int, # 左上座標
|
|
440
470
|
width: int = 300, # 幅
|
|
441
|
-
initial_lines: int =
|
|
471
|
+
initial_lines: int = 4, # 初期表示行数
|
|
442
472
|
) -> Optional[LogViewBox]:
|
|
443
473
|
"""ログ表示用テキストボックスを生成"""
|
|
444
474
|
if not self._tkRoot:
|
|
445
475
|
return None
|
|
446
|
-
#
|
|
476
|
+
# フォント定義
|
|
477
|
+
font = ctk.CTkFont("Meiryo", size=12)
|
|
478
|
+
|
|
479
|
+
# 実際の行高を測定
|
|
480
|
+
# Meiryo size=16 の場合、約 18-20px が一般的
|
|
481
|
+
line_height = 20 # Meiryo 16pt の実測値
|
|
482
|
+
padding = 2 # 上下マージン
|
|
483
|
+
|
|
447
484
|
textbox = ctk.CTkTextbox(
|
|
448
485
|
self._tkRoot,
|
|
449
486
|
width=width,
|
|
450
|
-
height=initial_lines *
|
|
451
|
-
font=
|
|
487
|
+
height=initial_lines * line_height + padding, # 例: 3行 = 66px
|
|
488
|
+
font=font,
|
|
452
489
|
border_width=1,
|
|
453
490
|
border_color="#000000",
|
|
454
491
|
corner_radius=4,
|
|
492
|
+
padx=2,
|
|
493
|
+
pady=2,
|
|
455
494
|
)
|
|
456
495
|
textbox.place(x=x, y=y)
|
|
457
|
-
textbox.configure(state="disabled") # 読み取り専用
|
|
458
496
|
|
|
459
497
|
# ログビューボックス生成
|
|
460
|
-
log_view = LogViewBox(textbox, x, y, width,
|
|
498
|
+
log_view = LogViewBox(textbox, x, y, width, 80)
|
|
461
499
|
log_view.parent = self
|
|
462
|
-
|
|
463
500
|
return log_view
|
|
464
501
|
#========================================================
|
|
465
502
|
#UIに使う画像の登録
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# DialogGridSelecter.py
|
|
2
|
+
import customtkinter as tk
|
|
3
|
+
from .CdialogBase import CdialogBase
|
|
4
|
+
|
|
5
|
+
class DialogGridSelecter(CdialogBase):
|
|
6
|
+
"""
|
|
7
|
+
2次元配列に応じたボタングリッドを表示するダイアログ
|
|
8
|
+
ボタンサイズ: 64x48
|
|
9
|
+
ボタン間隙: 16
|
|
10
|
+
ウィンドウ端余白: 48
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, parent,
|
|
14
|
+
title="Grid Selecter",
|
|
15
|
+
grid_data=None,
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Parameters:
|
|
19
|
+
-----------
|
|
20
|
+
parent : tk.CTk | tk.CTkToplevel | object with _tkRoot attribute
|
|
21
|
+
親ウィンドウ(tk.CTk, tk.CTkToplevel, または _tkRoot を持つオブジェクト)
|
|
22
|
+
title : str
|
|
23
|
+
ダイアログのタイトル
|
|
24
|
+
grid_data : list[list[str|None]]
|
|
25
|
+
2次元配列(ボタンのテキスト、Noneの部分はボタンなし)
|
|
26
|
+
"""
|
|
27
|
+
# parentが_tkRoot属性を持つ場合(WindowBaseModan等)はそれを取得
|
|
28
|
+
if hasattr(parent, '_tkRoot'):
|
|
29
|
+
parent = parent._tkRoot
|
|
30
|
+
|
|
31
|
+
self._grid_data = grid_data if grid_data is not None else []
|
|
32
|
+
self._btn_min_width = 64
|
|
33
|
+
self._btn_height = 48
|
|
34
|
+
self._btn_spacing = 16
|
|
35
|
+
self._window_padding = 48
|
|
36
|
+
self._selected_text = None
|
|
37
|
+
self._btn_width = 64 # 全ボタン共通の幅
|
|
38
|
+
|
|
39
|
+
# グリッドのサイズを計算
|
|
40
|
+
size = self._calculate_window_size()
|
|
41
|
+
|
|
42
|
+
super().__init__(parent, title=title, size=size)
|
|
43
|
+
|
|
44
|
+
def _calculate_window_size(self):
|
|
45
|
+
"""ウィンドウサイズを計算(全テキスト幅の最大値を基準)"""
|
|
46
|
+
if not self._grid_data:
|
|
47
|
+
return (400, 200)
|
|
48
|
+
|
|
49
|
+
# 行数と列数を取得
|
|
50
|
+
num_rows = len(self._grid_data)
|
|
51
|
+
num_cols = max(len(row) for row in self._grid_data) if self._grid_data else 1
|
|
52
|
+
|
|
53
|
+
# フォント情報を取得してテキスト幅を計算
|
|
54
|
+
font = tk.CTkFont("Meiryo", 24, weight="bold") # フォントサイズは適宜調整
|
|
55
|
+
|
|
56
|
+
# 全テキストの最大幅を計算
|
|
57
|
+
max_text_width = self._btn_min_width
|
|
58
|
+
for row in self._grid_data:
|
|
59
|
+
for text in row:
|
|
60
|
+
if text is not None:
|
|
61
|
+
# テキストの実際の幅を計算
|
|
62
|
+
text_width = font.measure(text)
|
|
63
|
+
# ボタンの内部パディング(左右各5px)を含める
|
|
64
|
+
actual_width = text_width + 10
|
|
65
|
+
max_text_width = max(max_text_width, actual_width)
|
|
66
|
+
|
|
67
|
+
# 全ボタンを同じ幅で使用
|
|
68
|
+
self._btn_width = max_text_width
|
|
69
|
+
|
|
70
|
+
# グリッド全体の幅と高さ
|
|
71
|
+
grid_width = (self._btn_width * num_cols) + (self._btn_spacing * (num_cols - 1))
|
|
72
|
+
grid_height = (self._btn_height + self._btn_spacing) * num_rows - self._btn_spacing
|
|
73
|
+
|
|
74
|
+
# ウィンドウのサイズ(余白を含む)
|
|
75
|
+
window_width = grid_width + (self._window_padding * 2)
|
|
76
|
+
window_height = grid_height + (self._window_padding * 2)
|
|
77
|
+
|
|
78
|
+
return (window_width, window_height)
|
|
79
|
+
|
|
80
|
+
def _build(self, root):
|
|
81
|
+
"""グリッドレイアウトを構築"""
|
|
82
|
+
# ライトテーマの設定
|
|
83
|
+
root.configure(fg_color="#f0f0f0")
|
|
84
|
+
|
|
85
|
+
# 背景を作成(クリック可能にするため)
|
|
86
|
+
bg_frame = tk.CTkFrame(root, fg_color="#f0f0f0")
|
|
87
|
+
bg_frame.pack(fill="both", expand=True)
|
|
88
|
+
bg_frame.bind("<Button-1>", self._on_background_click)
|
|
89
|
+
|
|
90
|
+
# グリッドの全体サイズを計算
|
|
91
|
+
num_rows = len(self._grid_data)
|
|
92
|
+
num_cols = max(len(row) for row in self._grid_data) if self._grid_data else 1
|
|
93
|
+
grid_width = (self._btn_width * num_cols) + (self._btn_spacing * (num_cols - 1))
|
|
94
|
+
grid_height = (self._btn_height + self._btn_spacing) * num_rows - self._btn_spacing
|
|
95
|
+
|
|
96
|
+
# ボタンフレーム(サイズを明示的に設定)
|
|
97
|
+
btn_frame = tk.CTkFrame(bg_frame, fg_color="#f0f0f0", width=grid_width, height=grid_height)
|
|
98
|
+
btn_frame.place(x=self._window_padding, y=self._window_padding)
|
|
99
|
+
|
|
100
|
+
# グリッドにボタンを配置
|
|
101
|
+
for row_idx, row in enumerate(self._grid_data):
|
|
102
|
+
for col_idx, text in enumerate(row):
|
|
103
|
+
# X座標を計算(全ボタン共通の幅とボタン間隙を考慮)
|
|
104
|
+
x = (self._btn_width + self._btn_spacing) * col_idx
|
|
105
|
+
y = row_idx * (self._btn_height + self._btn_spacing)
|
|
106
|
+
|
|
107
|
+
if text is not None:
|
|
108
|
+
# ボタンを作成(全ボタン共通の幅を使用)
|
|
109
|
+
btn = tk.CTkButton(
|
|
110
|
+
btn_frame,
|
|
111
|
+
text=text,
|
|
112
|
+
width=self._btn_width,
|
|
113
|
+
height=self._btn_height,
|
|
114
|
+
font=("Meiryo", 16),
|
|
115
|
+
border_width=2,
|
|
116
|
+
corner_radius=4,
|
|
117
|
+
fg_color="#ffffff",
|
|
118
|
+
text_color="#000000",
|
|
119
|
+
border_color="#cccccc",
|
|
120
|
+
hover_color="#e000e0",
|
|
121
|
+
command=lambda t=text: self._on_button_click(t)
|
|
122
|
+
)
|
|
123
|
+
btn.place(x=x, y=y)
|
|
124
|
+
|
|
125
|
+
# Escapeキーで閉じる
|
|
126
|
+
root.bind("<Escape>", lambda e: self._on_window_close())
|
|
127
|
+
|
|
128
|
+
def _on_button_click(self, text):
|
|
129
|
+
"""ボタンをクリックした時の処理"""
|
|
130
|
+
self._selected_text = text
|
|
131
|
+
self._tkRoot.destroy()
|
|
132
|
+
|
|
133
|
+
def _on_background_click(self, event):
|
|
134
|
+
"""背景をクリックした時の処理"""
|
|
135
|
+
self._selected_text = None
|
|
136
|
+
self._tkRoot.destroy()
|
|
137
|
+
|
|
138
|
+
def _on_window_close(self):
|
|
139
|
+
"""ウィンドウを閉じた時の処理"""
|
|
140
|
+
self._selected_text = None
|
|
141
|
+
self._tkRoot.destroy()
|
|
142
|
+
|
|
143
|
+
def show(self):
|
|
144
|
+
"""ダイアログを表示して結果を返す"""
|
|
145
|
+
self._selected_text = None
|
|
146
|
+
self._position_near_mouse()
|
|
147
|
+
|
|
148
|
+
self._tkRoot.deiconify()
|
|
149
|
+
self._tkRoot.lift()
|
|
150
|
+
self._tkRoot.grab_set()
|
|
151
|
+
self._tkRoot.focus_set()
|
|
152
|
+
self._parent.wait_window(self._tkRoot)
|
|
153
|
+
|
|
154
|
+
return self._selected_text
|
|
155
|
+
|
|
156
|
+
def _position_near_mouse(self):
|
|
157
|
+
"""マウス位置をダイアログの中心に配置"""
|
|
158
|
+
self._tkRoot.update_idletasks()
|
|
159
|
+
|
|
160
|
+
# マウスの現在位置を取得
|
|
161
|
+
mouse_x = self._tkRoot.winfo_pointerx()
|
|
162
|
+
mouse_y = self._tkRoot.winfo_pointery()
|
|
163
|
+
|
|
164
|
+
# ダイアログのサイズを取得
|
|
165
|
+
dlg_width = self._size[0]
|
|
166
|
+
dlg_height = self._size[1]
|
|
167
|
+
|
|
168
|
+
# 画面のサイズを取得
|
|
169
|
+
screen_width = self._tkRoot.winfo_screenwidth()
|
|
170
|
+
screen_height = self._tkRoot.winfo_screenheight()
|
|
171
|
+
|
|
172
|
+
# マウス位置がダイアログの中心になるように配置
|
|
173
|
+
x = mouse_x - (dlg_width // 2)
|
|
174
|
+
y = mouse_y - (dlg_height // 2)
|
|
175
|
+
|
|
176
|
+
# ダイアログが画面をはみ出さないようにクリップ
|
|
177
|
+
# 左側がはみ出す場合
|
|
178
|
+
if x < 0:
|
|
179
|
+
x = 10
|
|
180
|
+
|
|
181
|
+
# 右側がはみ出す場合
|
|
182
|
+
if x + dlg_width > screen_width:
|
|
183
|
+
x = screen_width - dlg_width - 10
|
|
184
|
+
|
|
185
|
+
# 上側がはみ出す場合
|
|
186
|
+
if y < 0:
|
|
187
|
+
y = 10
|
|
188
|
+
|
|
189
|
+
# 下側がはみ出す場合
|
|
190
|
+
if y + dlg_height > screen_height:
|
|
191
|
+
y = screen_height - dlg_height - 10
|
|
192
|
+
|
|
193
|
+
# ダイアログのサイズと位置を設定
|
|
194
|
+
self._tkRoot.geometry(f"{dlg_width}x{dlg_height}+{x}+{y}")
|
|
195
|
+
|
|
196
|
+
#####################################################################
|
|
197
|
+
# 使用例(単体テスト)
|
|
198
|
+
#####################################################################
|
|
199
|
+
if __name__ == "__main__":
|
|
200
|
+
#TKのルート
|
|
201
|
+
root = tk.CTk()
|
|
202
|
+
|
|
203
|
+
def test_grid_selecter():
|
|
204
|
+
# 2次元配列でボタンを定義
|
|
205
|
+
grid_data = [
|
|
206
|
+
["入力", "確認", "完了"],
|
|
207
|
+
["キャンセル", "実行", "リセット"],
|
|
208
|
+
[None, "削除", None],
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# rootまたは_tkRootを持つオブジェクトの両方に対応
|
|
212
|
+
dlg = DialogGridSelecter(root, # root直接渡しでもOK
|
|
213
|
+
title="グリッド選択ダイアログ",
|
|
214
|
+
grid_data=grid_data
|
|
215
|
+
)
|
|
216
|
+
result = dlg.show()
|
|
217
|
+
print(f"選択された: {result}")
|
|
218
|
+
|
|
219
|
+
root.geometry("600x400")
|
|
220
|
+
root.title("Grid Selector Dialog テスト")
|
|
221
|
+
tk.CTkButton(root, text="グリッド選択ダイアログを表示", command=test_grid_selecter).pack(pady=20)
|
|
222
|
+
root.mainloop()
|
|
@@ -5,6 +5,7 @@ from .DialogSelectReaderPort import DialogSelectReaderPort
|
|
|
5
5
|
from .CdialogBase import CdialogBase, CdialogResult, CdialogIcon
|
|
6
6
|
from .CdialogOk import CdialogOk
|
|
7
7
|
from .CdialogYesNo import CdialogYesNo
|
|
8
|
+
from .DialogGridSelecter import DialogGridSelecter
|
|
8
9
|
|
|
9
10
|
__all__ = [
|
|
10
11
|
"DialogBase",
|
|
@@ -17,5 +18,6 @@ __all__ = [
|
|
|
17
18
|
"CdialogResult",
|
|
18
19
|
"CdialogIcon",
|
|
19
20
|
"CdialogOk",
|
|
20
|
-
"CdialogYesNo"
|
|
21
|
+
"CdialogYesNo",
|
|
22
|
+
"DialogGridSelecter",
|
|
21
23
|
]
|
|
@@ -2,7 +2,10 @@ from .TkUiBase import TkUiBase
|
|
|
2
2
|
from .CTkUiBase import CTkUiBase
|
|
3
3
|
from .Window import WindowBase
|
|
4
4
|
from .Window import WindowBaseModan
|
|
5
|
-
from .Dialog import
|
|
5
|
+
from .Dialog import (
|
|
6
|
+
DialogBase, DialogResult, DialogIcon, DialogOk, DialogYesNo, DialogSelectReaderPort,
|
|
7
|
+
CdialogBase, CdialogResult, CdialogIcon, CdialogOk, CdialogYesNo, DialogGridSelecter
|
|
8
|
+
)
|
|
6
9
|
from .Tray import TrayMenu, acquire_single_instance_guard, SingleInstanceGuard
|
|
7
10
|
|
|
8
11
|
__all__ = [
|
|
@@ -10,12 +13,21 @@ __all__ = [
|
|
|
10
13
|
"CTkUiBase",
|
|
11
14
|
"WindowBase",
|
|
12
15
|
"WindowBaseModan",
|
|
16
|
+
# tkinter版ダイアログ
|
|
13
17
|
"DialogBase",
|
|
14
18
|
"DialogResult",
|
|
15
19
|
"DialogIcon",
|
|
16
20
|
"DialogOk",
|
|
17
21
|
"DialogYesNo",
|
|
18
22
|
"DialogSelectReaderPort",
|
|
23
|
+
# customtkinter版ダイアログ
|
|
24
|
+
"CdialogBase",
|
|
25
|
+
"CdialogResult",
|
|
26
|
+
"CdialogIcon",
|
|
27
|
+
"CdialogOk",
|
|
28
|
+
"CdialogYesNo",
|
|
29
|
+
"DialogGridSelecter",
|
|
30
|
+
# トレイ機能
|
|
19
31
|
"TrayMenu",
|
|
20
32
|
"acquire_single_instance_guard",
|
|
21
33
|
"SingleInstanceGuard",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -13,6 +13,7 @@ ikkVisualKit/Dialog/CdialogBase.py
|
|
|
13
13
|
ikkVisualKit/Dialog/CdialogOk.py
|
|
14
14
|
ikkVisualKit/Dialog/CdialogYesNo.py
|
|
15
15
|
ikkVisualKit/Dialog/DialogBase.py
|
|
16
|
+
ikkVisualKit/Dialog/DialogGridSelecter.py
|
|
16
17
|
ikkVisualKit/Dialog/DialogOk.py
|
|
17
18
|
ikkVisualKit/Dialog/DialogSelectReaderPort.py
|
|
18
19
|
ikkVisualKit/Dialog/DialogYesNo.py
|
|
@@ -23,7 +24,10 @@ ikkVisualKit/Tray/__init__.py
|
|
|
23
24
|
ikkVisualKit/Window/WindowBase.py
|
|
24
25
|
ikkVisualKit/Window/WindowBaseModan.py
|
|
25
26
|
ikkVisualKit/Window/__init__.py
|
|
27
|
+
ikkVisualKit/image/alert.dio.png
|
|
26
28
|
ikkVisualKit/image/clear.dio.png
|
|
29
|
+
ikkVisualKit/image/disconnect.dio.png
|
|
30
|
+
ikkVisualKit/image/folder.dio.png
|
|
27
31
|
ikkVisualKit/image/goto.dio.png
|
|
28
32
|
ikkVisualKit/image/help.dio.png
|
|
29
33
|
ikkVisualKit/image/link.dio.png
|
|
@@ -35,4 +39,5 @@ ikkVisualKit/image/save.dio.png
|
|
|
35
39
|
ikkVisualKit/image/search.dio.png
|
|
36
40
|
ikkVisualKit/image/setting.dio.png
|
|
37
41
|
ikkVisualKit/image/start.dio.png
|
|
42
|
+
ikkVisualKit/image/swap.dio.png
|
|
38
43
|
ikkVisualKit/image/unlock.dio.png
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ikkVisualKit"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.1"
|
|
8
8
|
description = "Insight.k.k. VisualKit for easy application development"
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.10"
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|