Newer
Older
multi_map_manager / apps / mylib / application.py
@koki koki on 19 Oct 2022 8 KB update
import tkinter as tk
import tkinter.filedialog
import re
from pathlib import Path
from .tools import Tools



class Application(tk.Frame):

    def __init__(self, master):
        super().__init__(master)
        self.theme = {"main":  "#444",
                      "bg1":   "#222"}

        #### 画面上部のメニューバーを作成 ####
        self.menu_bar = tk.Menu(self)
        ## Fileメニューの作成
        self.file_menu = tk.Menu(self.menu_bar, tearoff=tk.OFF,
            bg=self.theme["main"], fg="white", activebackground="gray", activeborderwidth=5
        )
        ## Fileメニュー内のOpenメニューを作成
        self.open_menu = tk.Menu(self.file_menu, tearoff=tk.OFF,
            bg=self.theme["main"], fg="white", activebackground="gray", activeborderwidth=5,
            disabledforeground="black"
        )
        self.open_menu.add_command(label="Base map",       command=self.menu_open_base)
        self.open_menu.add_command(label="Additional map", command=self.menu_open_addtion, state="disabled")
        self.file_menu.add_cascade(label="Open", menu=self.open_menu)
        ## Fileメニューその他
        self.file_menu.add_command(label="Export", command=self.menu_export, accelerator="Ctrl+E")
        self.file_menu.add_separator()
        self.file_menu.add_command(label="Exit",    command=self.menu_exit,   accelerator="Ctrl+Q")
        ## キーボードショートカットを設定
        self.bind_all("<Control-e>",       self.menu_export)
        self.bind_all("<Control-q>",       self.menu_exit)
        ## 大元に作成したメニューバーを設定
        self.menu_bar.add_cascade(label=" File ", menu=self.file_menu) # Fileメニューとしてバーに追加
        self.master.config(menu=self.menu_bar)

        #### 仕切り線で大きさを変更できるウィンドウ ####
        paned_window = tk.PanedWindow(self.master, sashwidth=3, bg="gray", relief=tk.RAISED)
        paned_window.pack(expand=True, fill=tk.BOTH)
        
        #### ツールボタン群とレイヤーリストを表示するフレーム ####
        self.tools = Tools(paned_window, self.theme, width=300, bg=self.theme["main"])
        paned_window.add(self.tools, minsize=self.tools.min_width, padx=2, pady=2)
        ## マップ画像を表示するフレームを追加
        paned_window.add(self.tools.map_disp, minsize=self.tools.map_disp.min_width, padx=2, pady=2)
        return


    """
    ++++++++++ File menu functions ++++++++++
    """
    def menu_open_base(self, event=None):
        map_path = self.menu_open(title="Select base map yaml file")
        if not map_path: return
        waypoints_path = self.menu_open(title="Select waypoints file for the map")
        if not waypoints_path: return

        self.tools.set_base_map(Path(map_path).resolve(), Path(waypoints_path).resolve())
        self.open_menu.entryconfigure("Base map", state="disabled")
        self.open_menu.entryconfigure("Additional map", state="normal")
        return
    

    def menu_open_addtion(self, event=None):
        map_path = self.menu_open(title="Select additional map yaml file")
        if not map_path: return
        waypoints_path = self.menu_open(title="Select waypoints file for the map")
        if not waypoints_path: return
        self.tools.add_map(Path(map_path).resolve(), Path(waypoints_path).resolve())
        return


    def menu_open(self, title):
        filepath = tkinter.filedialog.askopenfilename(
            parent=self.master,
            title=title,
            initialdir=str(Path(".")),
            filetypes=[("YAML", ".yaml")]
        )
        return filepath
    

    def menu_export(self, event=None):
        if (len(self.tools.label_list) < 2): return
        win = tk.Toplevel()
        win.geometry("800x300+50+50")
        win.minsize(width=500, height=300)
        win.attributes('-topmost', True)
        win.title("Export")
        font = ("Consolas", 12)

        ### ファイルパスを参照するダイアログを開く関数(ボタンコールバック) ###
        def ref_btn_callback_f(entry: tk.Entry, init_dir):
            filepath = tkinter.filedialog.asksaveasfilename(
                parent=win,
                title="File path to export",
                initialdir=init_dir,
                filetypes=[("YAML", ".yaml")]
            )
            if not filepath: return
            entry.delete(0, tk.END)
            entry.insert(tk.END, str(filepath))
        
        ### ファルダパスを参照するダイアログを開く関数(ボタンコールバック) ###
        def ref_btn_callback_d(entry: tk.Entry, init_dir):
            dirpath = tkinter.filedialog.askdirectory(
                parent=win,
                title="Directory path to export maps",
                initialdir=init_dir
            )
            if not dirpath: return
            entry.delete(0, tk.END)
            entry.insert(tk.END, str(dirpath))

        ### ファイルの保存場所の表示、変更をするフィールドを作成する関数 ###
        def create_entry(label_txt, init_path, callback):
            frame = tk.Frame(win)
            frame.pack(expand=False, fill=tk.X, padx=5, pady=10)
            label = tk.Label(frame, text=label_txt, anchor=tk.W, font=font)
            label.grid(column=0, row=0, padx=3, pady=2, sticky=tk.EW)
            ref_btn = tk.Button(frame, image=self.tools.folder_icon)
            ref_btn.grid(column=1, row=1, sticky=tk.E, padx=5)
            tbox = tk.Entry(frame, font=font)
            tbox.grid(column=0, row=1, padx=20, pady=2, sticky=tk.EW)
            tbox.insert(tk.END, init_path)
            init_dir = str(Path(init_path).parent)
            ref_btn["command"] = lambda entry=tbox, init_dir=init_dir: callback(entry, init_dir)
            frame.grid_columnconfigure(0, weight=1)
            return tbox
        
        ## マルチマップyaml、結合したウェイポイント、合成した地図画像と情報yamlを取得
        # multimap_yaml, path = self.tools.get_multimap_yaml()
        map_img_list, map_yaml_list, path = self.tools.get_map_lists()
        tbox1 = create_entry("- Multi mps directory:", str(path), ref_btn_callback_d)
        wp_yaml, path = self.tools.get_waypoints_yaml()
        tbox2 = create_entry("- Merged waypoints yaml file:", str(path), ref_btn_callback_f)
        merged_img_pil, merged_yaml, path = self.tools.get_merged_map()
        tbox3 = create_entry("- Merged map (yaml, pgm) file:", str(path), ref_btn_callback_f)

        ### それぞれをファイルに書き込む関数(ボタンコールバック) ###
        def export_btn_callback():
            # multi maps
            path = tbox1.get()
            for i, img in enumerate(map_img_list):
                img_path = Path(path) / Path("map{}.pgm".format(i))
                img.save(str(img_path.resolve()))
                map_yaml_list[i]["image"] = str(img_path.resolve())
                str_yaml = ""
                line = "\n"
                for key, val in map_yaml_list[i].items():
                    str_yaml += "{}: {}".format(key, val) + line
                with open(str(img_path.with_suffix(".yaml")), 'w') as f:
                    f.write(str_yaml)

            # merged waypoints
            path = tbox2.get()
            with open(path, 'w') as f:
                f.write(wp_yaml)
            
            # merged map 
            path = Path(tbox3.get()).resolve()
            new_merged_yaml = re.sub("image: .*\n", "image: {}\n".format(str(path.with_suffix(".pgm"))), merged_yaml)
            with open(str(path.with_suffix(".yaml")), 'w') as f:
                f.write(new_merged_yaml)
            merged_img_pil.save(str(path.with_suffix(".pgm")))
            win.destroy()
            return

        ## エクスポートボタン、キャンセルボタン
        export_btn = tk.Button(win, text="Export", font=font)
        export_btn["command"] = export_btn_callback
        export_btn.pack(side=tk.RIGHT, padx=50, pady=20)
        cancel_btn = tk.Button(win, text="Cancel", font=font)
        cancel_btn["command"] = win.destroy
        cancel_btn.pack(side=tk.LEFT, padx=50, pady=20)
        return
    

    def menu_exit(self, event=None):
        self.master.destroy()