395 lines
16 KiB
Python
395 lines
16 KiB
Python
"""
|
||
Compiz対応ショートカットキー画面
|
||
Mate+Compiz環境で安定動作するUI
|
||
"""
|
||
|
||
import flet as ft
|
||
import signal
|
||
import sys
|
||
import logging
|
||
from datetime import datetime
|
||
|
||
class CompizShortcutsApp:
|
||
"""Compiz対応ショートカットキーアプリケーション"""
|
||
|
||
def __init__(self, page: ft.Page):
|
||
self.page = page
|
||
|
||
# ログ設定
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||
handlers=[
|
||
logging.FileHandler('app.log'),
|
||
logging.StreamHandler()
|
||
]
|
||
)
|
||
|
||
# シグナルハンドラ設定
|
||
def signal_handler(signum, frame):
|
||
print(f"\nシグナル {signum} を受信しました")
|
||
print("✅ 正常終了処理完了")
|
||
logging.info("アプリケーション正常終了")
|
||
sys.exit(0)
|
||
|
||
self.signal_handler = signal_handler # インスタンス変数として保存
|
||
|
||
signal.signal(signal.SIGINT, signal_handler)
|
||
signal.signal(signal.SIGTERM, signal_handler)
|
||
|
||
# ウィンドウ設定 - スマホ固定サイズ
|
||
page.title = "販売アシスト・ショートカットランチャー"
|
||
page.window.width = 420 # 400px → 420pxに拡大
|
||
page.window.height = 900 # 800px → 900pxに拡大して下切れ対策
|
||
page.window.resizable = False # 固定サイズ
|
||
page.window_center = True # 中央配置
|
||
page.theme_mode = ft.ThemeMode.LIGHT
|
||
|
||
# デバッグ表示
|
||
print(f"ウィンドウサイズ設定: {page.window.width} x {page.window.height}")
|
||
print(f"リサイズ可能: {page.window.resizable}")
|
||
print(f"中央配置: {page.window_center}")
|
||
|
||
# ウィンドウクローズイベント
|
||
page.on_window_close = lambda _: signal_handler(0, None)
|
||
|
||
# メインコンテナ
|
||
self.main_container = ft.Column([], expand=True, spacing=20)
|
||
|
||
# ショートカットキー設定
|
||
self.setup_shortcuts()
|
||
|
||
# UI構築
|
||
self.build_ui()
|
||
|
||
logging.info("Compiz対応ショートカットキーアプリ起動完了")
|
||
print("🚀 Compiz対応ショートカットキーアプリ起動完了")
|
||
|
||
def setup_shortcuts(self):
|
||
"""ショートカットキー設定"""
|
||
self.key_pressed = {} # キー押下状態を管理
|
||
self.last_key_time = {} # 最後のキー押下時間を管理
|
||
|
||
def on_keyboard(e: ft.KeyboardEvent):
|
||
import time
|
||
current_time = time.time()
|
||
|
||
# キーリピート防止(0.2秒以内の同じキーを無視)
|
||
if e.key in self.last_key_time and current_time - self.last_key_time[e.key] < 0.2:
|
||
return
|
||
|
||
self.last_key_time[e.key] = current_time
|
||
|
||
# 数字キーで機能呼び出し
|
||
if e.key == "1":
|
||
self.show_function("ダッシュボード", "統計情報の表示")
|
||
elif e.key == "2":
|
||
self.show_function("売上管理", "売上データの入力・管理")
|
||
elif e.key == "3":
|
||
self.show_function("顧客管理", "顧客マスタの編集")
|
||
elif e.key == "4":
|
||
self.show_function("商品管理", "商品マスタの編集")
|
||
elif e.key == "5":
|
||
self.show_function("伝票入力", "伝票データの入力")
|
||
elif e.key == "6":
|
||
self.show_function("テキストエディタ", "下書き・メモの作成")
|
||
elif e.key == "7":
|
||
self.show_function("GPS機能", "GPS情報の取得・管理")
|
||
elif e.key == "8":
|
||
self.show_function("PDF出力", "帳票のPDF出力")
|
||
elif e.key == "9":
|
||
self.show_function("設定", "アプリケーション設定")
|
||
elif e.key == "0":
|
||
self.show_function("終了", "アプリケーションの終了")
|
||
# ESCキーで終了
|
||
elif e.key == "Escape":
|
||
self.show_function("終了", "アプリケーションの終了")
|
||
# SPACEで実行
|
||
elif e.key == " ":
|
||
self.execute_current_function()
|
||
# ENTERで実行
|
||
elif e.key == "Enter":
|
||
self.execute_current_function()
|
||
|
||
self.page.on_keyboard_event = on_keyboard
|
||
logging.info("ショートカットキー設定完了")
|
||
|
||
def build_ui(self):
|
||
"""UIを構築"""
|
||
# タイトル
|
||
self.title = ft.Text(
|
||
"Compiz対応ショートカットキー",
|
||
size=36,
|
||
weight=ft.FontWeight.BOLD,
|
||
color=ft.Colors.BLUE_900,
|
||
text_align=ft.TextAlign.CENTER
|
||
)
|
||
|
||
# 説明テキスト
|
||
self.description = ft.Text(
|
||
"Mate+Compiz環境で安定動作するショートカットキー対応画面",
|
||
size=18,
|
||
color=ft.Colors.GREY_600,
|
||
text_align=ft.TextAlign.CENTER
|
||
)
|
||
|
||
# 現在の機能表示
|
||
self.current_function = ft.Container(
|
||
content=ft.Text(
|
||
"機能: 未選択",
|
||
size=24,
|
||
weight=ft.FontWeight.BOLD,
|
||
color=ft.Colors.WHITE,
|
||
text_align=ft.TextAlign.CENTER
|
||
),
|
||
padding=20,
|
||
bgcolor=ft.Colors.ORANGE,
|
||
border_radius=15,
|
||
margin=ft.Margin.symmetric(vertical=10)
|
||
)
|
||
|
||
# ショートカットキーガイド
|
||
self.shortcuts_guide = ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("ショートカットキー一覧", size=20, weight=ft.FontWeight.BOLD, text_align=ft.TextAlign.CENTER),
|
||
ft.Divider(height=2, thickness=2),
|
||
self.create_shortcut_row("1", "ダッシュボード", "統計情報の表示", ft.Colors.BLUE),
|
||
self.create_shortcut_row("2", "売上管理", "売上データの入力・管理", ft.Colors.GREEN),
|
||
self.create_shortcut_row("3", "顧客管理", "顧客マスタの編集", ft.Colors.ORANGE),
|
||
self.create_shortcut_row("4", "商品管理", "階層構造商品マスター編集", ft.Colors.PURPLE),
|
||
self.create_shortcut_row("5", "伝票入力", "伝票データの入力", ft.Colors.RED),
|
||
self.create_shortcut_row("6", "テキストエディタ", "下書き・メモの作成", ft.Colors.TEAL),
|
||
self.create_shortcut_row("7", "GPS機能", "GPS情報の取得・管理", ft.Colors.CYAN),
|
||
self.create_shortcut_row("8", "PDF出力", "帳票のPDF出力", ft.Colors.BROWN),
|
||
self.create_shortcut_row("9", "設定", "アプリケーション設定", ft.Colors.GREY),
|
||
self.create_shortcut_row("0", "終了", "アプリケーションの終了", ft.Colors.RED),
|
||
ft.Divider(height=2, thickness=2),
|
||
ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("操作方法:", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text("• 数字キー: 機能を選択", size=14),
|
||
ft.Text("• SPACE/ENTER: 選択した機能を実行", size=14),
|
||
ft.Text("• ESC: アプリケーション終了", size=14),
|
||
ft.Text("• マウスクリックも可能", size=14),
|
||
], spacing=5),
|
||
padding=15,
|
||
bgcolor=ft.Colors.BLUE_50,
|
||
border_radius=10
|
||
)
|
||
], spacing=10),
|
||
padding=20,
|
||
bgcolor=ft.Colors.WHITE,
|
||
border_radius=15,
|
||
shadow=ft.BoxShadow(
|
||
spread_radius=1,
|
||
blur_radius=5,
|
||
color=ft.Colors.GREY_300,
|
||
offset=ft.Offset(0, 2)
|
||
),
|
||
margin=ft.Margin.only(bottom=20)
|
||
)
|
||
|
||
# 実行ボタン(マウス用)
|
||
self.execute_btn = ft.Container(
|
||
content=ft.Button(
|
||
"実行",
|
||
on_click=self.execute_current_function,
|
||
style=ft.ButtonStyle(
|
||
bgcolor=ft.Colors.GREEN,
|
||
color=ft.Colors.WHITE,
|
||
elevation=5,
|
||
shape=ft.RoundedRectangleBorder(radius=10),
|
||
padding=ft.Padding.symmetric(horizontal=30, vertical=15)
|
||
)
|
||
),
|
||
alignment=ft.alignment.Alignment(0, 0),
|
||
margin=ft.Margin.symmetric(vertical=10)
|
||
)
|
||
|
||
# 環境情報
|
||
self.env_info = ft.Container(
|
||
content=ft.Column([
|
||
ft.Text("環境情報", size=16, weight=ft.FontWeight.BOLD),
|
||
ft.Text(f"OS: Linux Mint + Compiz"),
|
||
ft.Text(f"デスクトップ環境: Mate"),
|
||
ft.Text(f"対応: Compiz特殊操作に最適化"),
|
||
ft.Text(f"作成日時: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"),
|
||
], spacing=5),
|
||
padding=15,
|
||
bgcolor=ft.Colors.BLUE_50,
|
||
border_radius=10,
|
||
shadow=ft.BoxShadow(
|
||
spread_radius=1,
|
||
blur_radius=3,
|
||
color=ft.Colors.GREY_200,
|
||
offset=ft.Offset(0, 1)
|
||
)
|
||
)
|
||
|
||
# メインコンテナに追加
|
||
self.main_container.controls = [
|
||
ft.Container(
|
||
content=self.title,
|
||
margin=ft.Margin.only(bottom=10)
|
||
),
|
||
ft.Container(
|
||
content=self.description,
|
||
margin=ft.Margin.only(bottom=20)
|
||
),
|
||
ft.Divider(height=1, thickness=1),
|
||
self.current_function,
|
||
ft.Row([
|
||
self.execute_btn,
|
||
self.env_info
|
||
], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
||
ft.Divider(height=1, thickness=1),
|
||
ft.Container(
|
||
content=self.shortcuts_guide,
|
||
expand=True
|
||
)
|
||
]
|
||
|
||
# ページに追加
|
||
self.page.add(
|
||
ft.Container(
|
||
content=self.main_container,
|
||
padding=20,
|
||
bgcolor=ft.Colors.GREY_100,
|
||
width=380 # 360px → 380pxに拡大
|
||
)
|
||
)
|
||
|
||
def create_shortcut_row(self, key: str, title: str, description: str, color: ft.Colors) -> ft.Container:
|
||
"""ショートカットキー行を作成"""
|
||
return ft.Container(
|
||
content=ft.Row([
|
||
ft.Container(
|
||
content=ft.Text(key, size=22, weight=ft.FontWeight.BOLD, color=ft.Colors.WHITE),
|
||
width=60,
|
||
height=60,
|
||
bgcolor=color,
|
||
alignment=ft.alignment.Alignment(0, 0),
|
||
border_radius=12,
|
||
shadow=ft.BoxShadow(
|
||
spread_radius=1,
|
||
blur_radius=3,
|
||
color=ft.Colors.with_opacity(0.3, color),
|
||
offset=ft.Offset(0, 2)
|
||
)
|
||
),
|
||
ft.Container(
|
||
content=ft.Column([
|
||
ft.Text(title, size=16, weight=ft.FontWeight.BOLD, color=ft.Colors.BLUE_900),
|
||
ft.Text(description, size=12, color=ft.Colors.GREY_600)
|
||
], spacing=3),
|
||
width=280, # 280px → 300pxに調整
|
||
padding=ft.Padding.symmetric(horizontal=10, vertical=8),
|
||
bgcolor=ft.Colors.GREY_50,
|
||
border_radius=10,
|
||
margin=ft.Margin.only(left=8)
|
||
)
|
||
], spacing=0, width=340), # Rowに幅制限を追加
|
||
margin=ft.Margin.only(bottom=10),
|
||
on_click=lambda _: self.show_function(title, description)
|
||
)
|
||
|
||
def show_function(self, title: str, description: str):
|
||
"""機能情報を表示"""
|
||
self.current_function.content.value = f"機能: {title}"
|
||
self.current_function.content.color = ft.Colors.WHITE
|
||
self.current_function.bgcolor = ft.Colors.BLUE_900
|
||
self.page.update()
|
||
logging.info(f"機能選択: {title}")
|
||
|
||
def execute_current_function(self, e=None):
|
||
"""現在の機能を実行"""
|
||
current_text = self.current_function.content.value
|
||
|
||
if "ダッシュボード" in current_text:
|
||
self.show_message("ダッシュボード機能を起動します", ft.Colors.GREEN)
|
||
# 実際のアプリ起動
|
||
self.launch_app("app_compiz_fixed.py")
|
||
logging.info("ダッシュボード機能実行")
|
||
|
||
elif "売上管理" in current_text:
|
||
self.show_message("売上管理機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_simple_working.py")
|
||
logging.info("売上管理機能実行")
|
||
|
||
elif "顧客管理" in current_text:
|
||
self.show_message("顧客管理機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_master_management.py")
|
||
logging.info("顧客管理機能実行")
|
||
|
||
elif "商品管理" in current_text:
|
||
self.show_message("商品管理機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_hierarchical_product_master.py")
|
||
logging.info("商品管理機能実行")
|
||
|
||
elif "伝票入力" in current_text:
|
||
self.show_message("伝票入力機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_slip_framework_demo.py")
|
||
logging.info("伝票入力機能実行")
|
||
|
||
elif "テキストエディタ" in current_text:
|
||
self.show_message("テキストエディタ機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_text_editor.py")
|
||
logging.info("テキストエディタ機能実行")
|
||
|
||
elif "GPS機能" in current_text:
|
||
self.show_message("GPS機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_theme_master.py")
|
||
logging.info("GPS機能実行")
|
||
|
||
elif "PDF出力" in current_text:
|
||
self.show_message("PDF出力機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_framework_demo.py")
|
||
logging.info("PDF出力機能実行")
|
||
|
||
elif "設定" in current_text:
|
||
self.show_message("設定機能を起動します", ft.Colors.GREEN)
|
||
self.launch_app("app_robust.py")
|
||
logging.info("設定機能実行")
|
||
|
||
elif "終了" in current_text:
|
||
self.show_message("アプリケーションを終了します", ft.Colors.RED)
|
||
self.signal_handler(0, None)
|
||
|
||
def launch_app(self, app_name: str):
|
||
"""アプリを起動"""
|
||
import subprocess
|
||
import os
|
||
|
||
try:
|
||
# 現在のディレクトリでアプリを起動
|
||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||
app_path = os.path.join(script_dir, app_name)
|
||
|
||
# バックグラウンドでアプリ起動
|
||
subprocess.Popen([
|
||
"flet", "run", app_path
|
||
], cwd=script_dir)
|
||
|
||
except Exception as e:
|
||
logging.error(f"アプリ起動エラー: {e}")
|
||
self.show_message(f"アプリ起動に失敗しました: {e}", ft.Colors.RED)
|
||
|
||
def show_message(self, message: str, color: ft.Colors):
|
||
"""メッセージを表示"""
|
||
self.page.snack_bar = ft.SnackBar(
|
||
content=ft.Text(message),
|
||
bgcolor=color
|
||
)
|
||
self.page.snack_bar.open = True
|
||
self.page.update()
|
||
|
||
def main(page: ft.Page):
|
||
"""メイン関数"""
|
||
try:
|
||
app = CompizShortcutsApp(page)
|
||
|
||
except Exception as e:
|
||
logging.error(f"アプリケーション起動エラー: {e}")
|
||
|
||
if __name__ == "__main__":
|
||
ft.run(main)
|