テキストにマーカ付けれたら便利じゃない？（全8色）

## 開発の経緯
- メッセージアプリのテキストバックアップを読み直すときに話者識別用のマーカーが付けたかった
- エラーログを読むときに分析が大変
- 長い論文を読むときに、読むべきポイントを見つけるのが大変
- 長いコンテンツからAIに投げるテキストを見つけやすくしたかった

## 使い方
- コピペでテキストを貼り付けます。
- 色を付けたいテキストをウインドウ上部の8つのマスのどれかに入れます。
- 単語やアルファベット、段落など長い文章も入ります。日本語もOK
- 色を付けたいマスをダブルクリックすると好きな色を付けれます
- ウインドウ下部にあるOKボタンの操作で選んだテキストに色が付きます
- コントロールキー＋マウスホイール操作で文字の大きさが変わります。
- シンタックスハイライトのようなこともできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3808329/51777dcf-747a-d97b-e897-4c7a77087bb2.png)

## 今後のアップデート
- 基本的なテキストエディタとしての機能を追加（現状ビューワーです）
- リアルタイムマーカー（OKボタンを押さなくても色がつくように）
- 色数をユーザーが選べるようにする？

ということで以下が本文コードです

```python:AutoMarker3.11.py
import tkinter as tk
from tkinter import colorchooser, font
from tkinter import messagebox
from tkinter import filedialog
import configparser
import os

root = tk.Tk()
root.title("AutoMarker3.1")

upper_frame = tk.Frame(root)
upper_frame.pack(side="top", fill="x")

lower_frame = tk.Frame(root)
lower_frame.pack(side="top", fill="x")

trigger_dict = {}
entry_list = []

config = configparser.ConfigParser()

def choose_color(event, entry):
    if event.state & 0x4:  # Check if Control key is pressed
        color = colorchooser.askcolor()[1]
        if color:
            entry.config(bg=color)
            
def update_trigger_dict():
    trigger_dict.clear()
    for entry in entry_list:
        if entry.get() and entry.cget('bg'):
            trigger_dict[entry.get()] = entry.cget('bg')
            
def load_settings():
    try:
        if os.path.exists('AutoMarker.ini'):
            config.read('AutoMarker.ini')
            for i, entry in enumerate(entry_list):
                entry.insert(0, config.get('Settings', f'TriggerText{i}', fallback=''))
                if config.has_option('Settings', f'TriggerColor{i}'):
                    entry.config(bg=config.get('Settings', f'TriggerColor{i}'))
            text_widget.insert('1.0', config.get('Settings', 'Text', fallback=''))

            # Load window size and position
            if config.has_option('Settings', 'WindowSize'):
                root.geometry(config.get('Settings', 'WindowSize'))
                
    except Exception as e:
        print(f"Error while loading settings: {e}")

def save_settings():
    try:
        config['Settings'] = {}
        for i, entry in enumerate(entry_list):
            config.set('Settings', f'TriggerText{i}', entry.get())
            config.set('Settings', f'TriggerColor{i}', entry.cget('bg'))
        config.set('Settings', 'Text', text_widget.get("1.0", "end"))
        config.set('Settings', 'WindowSize', root.geometry())

        with open('AutoMarker.ini', 'w') as configfile:
            config.write(configfile)

    except Exception as e:
        print(f"Error while saving settings: {e}")

    root.destroy()  # This line will close the window after saving settings.

root.protocol("WM_DELETE_WINDOW", save_settings)


for i in range(4):
    entry = tk.Entry(upper_frame)
    entry_list.append(entry)
    entry.bind("<Button-1>", lambda event, entry=entry: choose_color(event, entry))
    entry.bind("<FocusOut>", lambda event: update_trigger_dict())
    entry.pack(side="left", expand=True, fill="x")

for i in range(4):
    entry = tk.Entry(lower_frame)
    entry_list.append(entry)
    entry.bind("<Button-1>", lambda event, entry=entry: choose_color(event, entry))
    entry.bind("<FocusOut>", lambda event: update_trigger_dict())
    entry.pack(side="left", expand=True, fill="x")

def change_fontsize(event):
    size = current_font.actual()['size']
    if event.delta > 0:
        current_font.configure(size=size + 1)
    else:
        current_font.configure(size=size - 1)
    text_widget.config(font=current_font)

text_widget = tk.Text(root, undo=True)
text_widget.pack(expand=True, fill='both')
current_font = font.nametofont(text_widget.cget("font"))
text_widget.bind("<Control-MouseWheel>", change_fontsize)

def highlight_text(event=None):
    update_trigger_dict()

    for tag in text_widget.tag_names():
        text_widget.tag_delete(tag)

    for trigger_text, color in trigger_dict.items():
        index = '1.0'
        while True:
            index = text_widget.search(trigger_text, index, 'end')
            if not index: break
            text_widget.tag_add(trigger_text, index, f"{index}+{len(trigger_text)}c")
            text_widget.tag_config(trigger_text, background=color)
            index = f"{index}+{len(trigger_text)}c"

ok_button = tk.Button(root, text="OK", command=highlight_text)
ok_button.pack(side="bottom")

def save_file():
    file_path = tk.filedialog.asksaveasfilename(defaultextension=".txt")
    if file_path:
        with open(file_path, "w") as file:
            file.write(text_widget.get("1.0", "end"))

def load_file():
    file_path = tk.filedialog.askopenfilename(filetypes=[("Text Files", "*.txt")])
    if file_path:
        with open(file_path, "r") as file:
            text_widget.delete("1.0", "end")
            text_widget.insert("1.0", file.read())

def on_closing():
    save_window_position()
    save_log(text_area)  # 追加: ログを保存
    app.quit()  # 追加: アプリケーションを終了
    
"""
menubar = tk.Menu(root)

file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="Open", command=load_file, accelerator="Ctrl+O")
file_menu.add_command(label="Save", command=save_file, accelerator="Ctrl+S")
file_menu.add_command(label="Save As...", command=save_file, accelerator="Ctrl+Shift+S")
menubar.add_cascade(label="File", menu=file_menu)

edit_menu = tk.Menu(menubar, tearoff=0)
edit_menu.add_command(label="Undo", command=text_widget.edit_undo, accelerator="Ctrl+Z")
edit_menu.add_command(label="Redo", command=text_widget.edit_redo, accelerator="Ctrl+Shift+Z")
menubar.add_cascade(label="Edit", menu=edit_menu)

root.config(menu=menubar)
"""

root.bind("<Control-o>", lambda event: load_file())
root.bind("<Control-s>", lambda event: save_file())
root.bind("<Control-Shift-S>", lambda event: save_file())
root.bind("<Control-z>", lambda event: text_widget.edit_undo())
root.bind("<Control-Shift-Z>", lambda event: text_widget.edit_redo())

try:
    load_settings()
except Exception as e:
    print(f"An error occurred while loading settings: {e}")

root.protocol("WM_DELETE_WINDOW", save_settings)
root.mainloop()

puts 'code block.'
```