0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

生成AIにTodoアプリを作らせてみた

Posted at

生成AIって本当にすごい・・・。
プログラミングほぼできないけど、生成AIにコード書かせるだけで、簡単にアプリ作れちゃう!
python入れて、以下のコードを.pyで保存するだけ。

image.png

import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
import json
import os
from datetime import datetime

class TodoApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Todo アプリ ")
        self.root.geometry("900x750")  
        
        # データファイル
        self.data_file = "todos.json"
        self.todos = self.load_todos()
        
        # 変数の初期化
        self.filter_var = tk.StringVar(value="すべて")
        self.priority_var = tk.StringVar(value="普通")
        
        self.create_widgets()
        self.setup_keyboard_shortcuts()
        self.refresh_list()
    
    def create_widgets(self):
        # メインフレーム
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 入力エリア
        input_frame = ttk.Frame(main_frame)
        input_frame.grid(row=0, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        
        ttk.Label(input_frame, text="新しいタスク:", font=('', 12)).grid(row=0, column=0, sticky=tk.W)
        
        self.task_entry = ttk.Entry(input_frame, width=60, font=('', 12))
        self.task_entry.grid(row=1, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
        self.task_entry.bind('<Return>', lambda e: self.add_task())
        
        ttk.Button(input_frame, text="追加", command=self.add_task).grid(row=1, column=1)
        
        # 優先度選択
        ttk.Label(input_frame, text="優先度:", font=('', 12)).grid(row=2, column=0, sticky=tk.W, pady=(10, 0))
        priority_combo = ttk.Combobox(input_frame, textvariable=self.priority_var, 
                                     values=["高", "普通", "低"], state="readonly", width=10)
        priority_combo.grid(row=3, column=0, sticky=tk.W, pady=(0, 10))
        
        input_frame.columnconfigure(0, weight=1)
        
        # フィルターエリア
        self.create_filter_widgets(main_frame)
        
        # リストエリア
        list_frame = ttk.Frame(main_frame)
        list_frame.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        
        # Treeview(リスト表示)
        columns = ('status', 'priority', 'task', 'created')
        self.tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=18)
        
        self.tree.heading('status', text='状態')
        self.tree.heading('priority', text='優先度')
        self.tree.heading('task', text='タスク')
        self.tree.heading('created', text='作成日時')
        
        self.tree.column('status', width=100)
        self.tree.column('priority', width=80)
        self.tree.column('task', width=400)
        self.tree.column('created', width=180)
        
        # 優先度別の色分け設定
        self.tree.tag_configure('high_priority', background='#ffebee')
        self.tree.tag_configure('normal_priority', background='white')
        self.tree.tag_configure('low_priority', background='#f3e5f5')
        self.tree.tag_configure('completed', background='#e8f5e8')
        
        # ダブルクリックで編集
        self.tree.bind('<Double-1>', lambda e: self.edit_task())
        
        # 右クリックメニュー
        self.create_context_menu()
        self.tree.bind('<Button-3>', self.show_context_menu)  
        
        # スクロールバー
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.tree.yview)
        self.tree.configure(yscrollcommand=scrollbar.set)
        
        self.tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
        
        list_frame.columnconfigure(0, weight=1)
        list_frame.rowconfigure(0, weight=1)
        
        # ボタンエリア
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=3, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 10))
        
        # 左側のボタン
        left_buttons = ttk.Frame(button_frame)
        left_buttons.pack(side=tk.LEFT)
        
        ttk.Button(left_buttons, text="完了にする (Ctrl+D)", command=self.mark_completed).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(left_buttons, text="未完了に戻す", command=self.mark_pending).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(left_buttons, text="編集 (ダブルクリック)", command=self.edit_task).pack(side=tk.LEFT, padx=(0, 5))
        
        # 中央のボタン(優先度変更)
        center_buttons = ttk.Frame(button_frame)
        center_buttons.pack(side=tk.LEFT, padx=(20, 0))
        
        ttk.Label(center_buttons, text="優先度変更:", font=('', 10)).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(center_buttons, text="🔴高", command=lambda: self.change_priority("高"), width=6).pack(side=tk.LEFT, padx=(0, 2))
        ttk.Button(center_buttons, text="🟡普通", command=lambda: self.change_priority("普通"), width=6).pack(side=tk.LEFT, padx=(0, 2))
        ttk.Button(center_buttons, text="🔵低", command=lambda: self.change_priority("低"), width=6).pack(side=tk.LEFT, padx=(0, 2))
        
        # 右側のボタン
        right_buttons = ttk.Frame(button_frame)
        right_buttons.pack(side=tk.RIGHT)
        
        ttk.Button(right_buttons, text="削除 (Delete)", command=self.delete_task).pack(side=tk.LEFT, padx=(0, 5))
        ttk.Button(right_buttons, text="完了済みを全削除", command=self.clear_completed).pack(side=tk.LEFT)
        
        # ステータスバー
        self.create_status_bar(main_frame)
        
        # ウィンドウのリサイズ設定
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)
        main_frame.rowconfigure(2, weight=1)
    
    def create_context_menu(self):
        """右クリックメニューを作成"""
        self.context_menu = tk.Menu(self.root, tearoff=0)
        self.context_menu.add_command(label="編集", command=self.edit_task)
        self.context_menu.add_separator()
        self.context_menu.add_command(label="完了にする", command=self.mark_completed)
        self.context_menu.add_command(label="未完了に戻す", command=self.mark_pending)
        self.context_menu.add_separator()
        
        # 優先度変更サブメニュー
        priority_menu = tk.Menu(self.context_menu, tearoff=0)
        priority_menu.add_command(label="🔴 高優先度", command=lambda: self.change_priority("高"))
        priority_menu.add_command(label="🟡 普通優先度", command=lambda: self.change_priority("普通"))
        priority_menu.add_command(label="🔵 低優先度", command=lambda: self.change_priority("低"))
        self.context_menu.add_cascade(label="優先度変更", menu=priority_menu)
        
        self.context_menu.add_separator()
        self.context_menu.add_command(label="削除", command=self.delete_task)
    
    def show_context_menu(self, event):
        """右クリックメニューを表示"""
        # クリックした位置のアイテムを選択
        item = self.tree.identify_row(event.y)
        if item:
            self.tree.selection_set(item)
            self.context_menu.post(event.x_root, event.y_root)
    
    def create_filter_widgets(self, parent):
        # フィルターフレーム
        filter_frame = ttk.Frame(parent)
        filter_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 5))
        
        ttk.Label(filter_frame, text="表示:", font=('', 11)).pack(side=tk.LEFT, padx=(0, 5))
        
        filter_combo = ttk.Combobox(filter_frame, textvariable=self.filter_var,
                                   values=["すべて", "未完了のみ", "完了済みのみ", "高優先度", "普通優先度", "低優先度"],
                                   state="readonly", width=15)
        filter_combo.pack(side=tk.LEFT, padx=(0, 20))
        filter_combo.bind('<<ComboboxSelected>>', lambda e: self.refresh_list())
        
        # 検索機能
        ttk.Label(filter_frame, text="検索:", font=('', 11)).pack(side=tk.LEFT, padx=(0, 5))
        self.search_var = tk.StringVar()
        search_entry = ttk.Entry(filter_frame, textvariable=self.search_var, width=20)
        search_entry.pack(side=tk.LEFT)
        search_entry.bind('<KeyRelease>', lambda e: self.refresh_list())
        
        # ソート機能
        ttk.Label(filter_frame, text="並び順:", font=('', 11)).pack(side=tk.LEFT, padx=(20, 5))
        self.sort_var = tk.StringVar(value="作成日時")
        sort_combo = ttk.Combobox(filter_frame, textvariable=self.sort_var,
                                 values=["作成日時", "優先度", "タスク名", "状態"],
                                 state="readonly", width=12)
        sort_combo.pack(side=tk.LEFT)
        sort_combo.bind('<<ComboboxSelected>>', lambda e: self.refresh_list())
    
    def create_status_bar(self, parent):
        self.status_frame = ttk.Frame(parent)
        self.status_frame.grid(row=4, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(5, 0))
        
        self.status_label = ttk.Label(self.status_frame, text="", font=('', 10))
        self.status_label.pack(side=tk.LEFT)
        
        # ショートカットヒント
        hint_label = ttk.Label(self.status_frame, 
                              text="ヒント: Ctrl+N=新規入力, Ctrl+D=完了, Delete=削除, ダブルクリック=編集, 右クリック=メニュー", 
                              font=('', 9), foreground='gray')
        hint_label.pack(side=tk.RIGHT)
    
    def setup_keyboard_shortcuts(self):
        # キーボードショートカット
        self.root.bind('<Control-n>', lambda e: self.task_entry.focus())
        self.root.bind('<Control-N>', lambda e: self.task_entry.focus())
        self.root.bind('<Delete>', lambda e: self.delete_task())
        self.root.bind('<Control-d>', lambda e: self.mark_completed())
        self.root.bind('<Control-D>', lambda e: self.mark_completed())
        self.root.bind('<F2>', lambda e: self.edit_task())
        
        # 優先度変更のショートカット
        self.root.bind('<Control-1>', lambda e: self.change_priority("高"))
        self.root.bind('<Control-2>', lambda e: self.change_priority("普通"))
        self.root.bind('<Control-3>', lambda e: self.change_priority("低"))
    
    def change_priority(self, new_priority):
        """選択されたタスクの優先度を変更"""
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "優先度を変更するタスクを選択してください")
            return
        
        item = self.tree.item(selected[0])
        task_text = item['values'][2]  # タスク列
        
        # タスクを見つけて優先度を変更
        for todo in self.todos:
            if todo['task'] == task_text:
                old_priority = todo.get('priority', '普通')
                todo['priority'] = new_priority
                
                # 変更ログを追加(オプション)
                if 'priority_history' not in todo:
                    todo['priority_history'] = []
                todo['priority_history'].append({
                    'from': old_priority,
                    'to': new_priority,
                    'changed_at': datetime.now().strftime("%Y-%m-%d %H:%M")
                })
                
                break
        
        self.save_todos()
        self.refresh_list()
        
        # 成功メッセージ
        priority_symbols = {'高': '🔴', '普通': '🟡', '低': '🔵'}
        messagebox.showinfo("成功", f"優先度を {priority_symbols.get(new_priority, '')} {new_priority} に変更しました")
    
    def add_task(self):
        task_text = self.task_entry.get().strip()
        if not task_text:
            messagebox.showwarning("警告", "タスクを入力してください")
            return
        
        # 重複チェック
        if any(todo['task'] == task_text for todo in self.todos):
            messagebox.showwarning("警告", "同じタスクが既に存在します")
            return
        
        priority = self.priority_var.get()
        
        new_todo = {
            'id': max([todo.get('id', 0) for todo in self.todos], default=0) + 1,
            'task': task_text,
            'completed': False,
            'priority': priority,
            'created': datetime.now().strftime("%Y-%m-%d %H:%M"),
            'priority_history': [{
                'from': None,
                'to': priority,
                'changed_at': datetime.now().strftime("%Y-%m-%d %H:%M")
            }]
        }
        
        self.todos.append(new_todo)
        self.task_entry.delete(0, tk.END)
        self.priority_var.set("普通")  # 優先度をリセット
        self.save_todos()
        self.refresh_list()
        messagebox.showinfo("成功", "タスクを追加しました")
    
    def edit_task(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "編集するタスクを選択してください")
            return
        
        item = self.tree.item(selected[0])
        current_task = item['values'][2]  # タスク列のインデックスを修正
        
        # 編集ダイアログ
        new_task = simpledialog.askstring("タスク編集", "新しいタスク名:", initialvalue=current_task)
        if new_task and new_task.strip() and new_task.strip() != current_task:
            # 重複チェック
            if any(todo['task'] == new_task.strip() for todo in self.todos):
                messagebox.showwarning("警告", "同じタスクが既に存在します")
                return
            
            for todo in self.todos:
                if todo['task'] == current_task:
                    todo['task'] = new_task.strip()
                    break
            self.save_todos()
            self.refresh_list()
            messagebox.showinfo("成功", "タスクを編集しました")
    
    def mark_completed(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "タスクを選択してください")
            return
        
        item = self.tree.item(selected[0])
        task_text = item['values'][2]  # タスク列のインデックスを修正
        
        for todo in self.todos:
            if todo['task'] == task_text:
                todo['completed'] = True
                todo['completed_date'] = datetime.now().strftime("%Y-%m-%d %H:%M")
                break
        
        self.save_todos()
        self.refresh_list()
    
    def mark_pending(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "タスクを選択してください")
            return
        
        item = self.tree.item(selected[0])
        task_text = item['values'][2]  # タスク列のインデックスを修正
        
        for todo in self.todos:
            if todo['task'] == task_text:
                todo['completed'] = False
                if 'completed_date' in todo:
                    del todo['completed_date']
                break
        
        self.save_todos()
        self.refresh_list()
    
    def delete_task(self):
        selected = self.tree.selection()
        if not selected:
            messagebox.showwarning("警告", "タスクを選択してください")
            return
        
        if messagebox.askyesno("確認", "選択したタスクを削除しますか?"):
            item = self.tree.item(selected[0])
            task_text = item['values'][2]  # タスク列のインデックスを修正
            
            self.todos = [todo for todo in self.todos if todo['task'] != task_text]
            self.save_todos()
            self.refresh_list()
            messagebox.showinfo("成功", "タスクを削除しました")
    
    def clear_completed(self):
        completed_count = sum(1 for todo in self.todos if todo['completed'])
        if completed_count == 0:
            messagebox.showinfo("情報", "完了済みのタスクはありません")
            return
        
        if messagebox.askyesno("確認", f"完了済みのタスク {completed_count} 件を削除しますか?"):
            self.todos = [todo for todo in self.todos if not todo['completed']]
            self.save_todos()
            self.refresh_list()
            messagebox.showinfo("成功", f"{completed_count} 件のタスクを削除しました")
    
    def get_filtered_todos(self):
        filtered_todos = self.todos.copy()
        
        # フィルタリング
        filter_value = self.filter_var.get()
        if filter_value == "未完了のみ":
            filtered_todos = [todo for todo in filtered_todos if not todo['completed']]
        elif filter_value == "完了済みのみ":
            filtered_todos = [todo for todo in filtered_todos if todo['completed']]
        elif filter_value == "高優先度":
            filtered_todos = [todo for todo in filtered_todos if todo.get('priority', '普通') == '高']
        elif filter_value == "普通優先度":
            filtered_todos = [todo for todo in filtered_todos if todo.get('priority', '普通') == '普通']
        elif filter_value == "低優先度":
            filtered_todos = [todo for todo in filtered_todos if todo.get('priority', '普通') == '低']
        
        # 検索
        search_text = self.search_var.get().lower() if hasattr(self, 'search_var') else ""
        if search_text:
            filtered_todos = [todo for todo in filtered_todos if search_text in todo['task'].lower()]
        
        # ソート
        sort_value = self.sort_var.get() if hasattr(self, 'sort_var') else "作成日時"
        if sort_value == "優先度":
            priority_order = {'高': 0, '普通': 1, '低': 2}
            filtered_todos.sort(key=lambda x: priority_order.get(x.get('priority', '普通'), 1))
        elif sort_value == "タスク名":
            filtered_todos.sort(key=lambda x: x['task'].lower())
        elif sort_value == "状態":
            filtered_todos.sort(key=lambda x: x['completed'])
        else:  # 作成日時
            filtered_todos.sort(key=lambda x: x['created'], reverse=True)
        
        return filtered_todos
    
    def refresh_list(self):
        # リストをクリア
        for item in self.tree.get_children():
            self.tree.delete(item)
        
        # フィルタリング・ソートされたタスクを取得
        filtered_todos = self.get_filtered_todos()
        
        # タスクを追加
        for todo in filtered_todos:
            status = "✓ 完了" if todo['completed'] else "○ 未完了"
            priority = todo.get('priority', '普通')
            
            # 優先度記号を追加
            priority_symbol = {'高': '🔴', '普通': '🟡', '低': '🔵'}
            priority_display = f"{priority_symbol.get(priority, '🟡')} {priority}"
            
            # タグを設定(色分け用)
            tags = []
            if todo['completed']:
                tags.append('completed')
            else:
                if priority == '高':
                    tags.append('high_priority')
                elif priority == '低':
                    tags.append('low_priority')
                else:
                    tags.append('normal_priority')
            
            self.tree.insert('', tk.END, 
                           values=(status, priority_display, todo['task'], todo['created']),
                           tags=tags)
        
        self.update_status()
    
    def update_status(self):
        total = len(self.todos)
        completed = sum(1 for todo in self.todos if todo['completed'])
        pending = total - completed
        
        high_priority = sum(1 for todo in self.todos if todo.get('priority', '普通') == '高' and not todo['completed'])
        
        status_text = f"総タスク数: {total} | 完了: {completed} | 未完了: {pending}"
        if high_priority > 0:
            status_text += f" | 高優先度(未完了): {high_priority}"
        
        self.status_label.config(text=status_text)
    
    def load_todos(self):
        if os.path.exists(self.data_file):
            try:
                with open(self.data_file, 'r', encoding='utf-8') as f:
                    todos = json.load(f)
                    # 既存のデータに優先度がない場合は追加
                    for todo in todos:
                        if 'priority' not in todo:
                            todo['priority'] = '普通'
                    return todos
            except Exception as e:
                messagebox.showerror("エラー", f"データの読み込みに失敗しました: {e}")
                return []
        return []
    
    def save_todos(self):
        try:
            with open(self.data_file, 'w', encoding='utf-8') as f:
                json.dump(self.todos, f, ensure_ascii=False, indent=2)
        except Exception as e:
            messagebox.showerror("エラー", f"保存に失敗しました: {e}")

if __name__ == "__main__":
    root = tk.Tk()
    app = TodoApp(root)
    root.mainloop()
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?