Newer
Older
multi_map_manager / apps / mylib / application.py
@koki0624 koki0624 on 8 Nov 2022 8 KB update
import tkinter as tk
import tkinter.filedialog
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.open_menu.add_command(label="Multimaps dir",  command=self.menu_open_multi)
        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.configure(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.open_yaml(title="Select base map yaml file")
        if not map_path: return
        waypoints_path = self.open_yaml(title="Select waypoints file for the map")
        if not waypoints_path: return

        suc = self.tools.set_base_map(Path(map_path).resolve(), Path(waypoints_path).resolve())
        if not suc: return
        self.open_menu.entryconfigure("Base map", state="disabled")
        self.open_menu.entryconfigure("Multimaps dir", state="disabled")
        self.open_menu.entryconfigure("Additional map", state="normal")
        return
    

    def menu_open_addtion(self, event=None):
        map_path = self.open_yaml(title="Select additional map yaml file")
        if not map_path: return
        waypoints_path = self.open_yaml(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_multi(self, event=None):
        dirpath = tkinter.filedialog.askdirectory(
            parent=self.master,
            title="Select multi maps directory",
            initialdir=str(Path("."))
        )
        if not dirpath: return
        dirpath = Path(dirpath)
        if not (dirpath / Path("map0.yaml")).exists(): return
        waypoints_path = self.open_yaml(title="Select waypoints file for the map")
        if not waypoints_path: return
        suc = self.tools.set_multimaps(dirpath, Path(waypoints_path).resolve())
        if not suc: return
        self.open_menu.entryconfigure("Base map", state="disabled")
        self.open_menu.entryconfigure("Multimaps dir", state="disabled")
        self.open_menu.entryconfigure("Additional map", state="normal")
        return
    

    def open_yaml(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("700x200+50+50")
        win.minsize(width=500, height=200)
        win.attributes('-topmost', True)
        win.title("Export")
        font = ("Arial", 12)

        ### ファルダパスを参照するダイアログを開く関数(ボタンコールバック) ###
        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))
        
        ## サブウィンドウにフォルダ入力エントリーを作成
        frame = tk.Frame(win)
        frame.pack(expand=False, fill=tk.X, padx=5, pady=10)
        label = tk.Label(frame, text="Directory :", 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)
        init_dir = str(Path(".").resolve())
        tbox.insert(tk.END, init_dir)
        ref_btn["command"] = lambda entry=tbox, init_dir=init_dir: ref_btn_callback_d(entry, init_dir)
        frame.grid_columnconfigure(0, weight=1)

        ## マルチマップyaml、結合したウェイポイント、合成した地図画像と情報yamlを取得
        map_img_list, map_yaml_list = self.tools.get_map_lists()
        wp_yaml, _ = self.tools.get_waypoints_yaml()
        merged_img_pil, merged_yaml = self.tools.get_merged_map()

        ### それぞれをファイルに書き込む関数(ボタンコールバック) ###
        def export_btn_callback():
            # multi maps
            dir_path = Path(tbox.get()).resolve()
            if not dir_path.exists(): dir_path.mkdir()
            for i, img in enumerate(map_img_list):
                img_path = dir_path / Path("map{}.pgm".format(i))
                img.save(str(img_path.resolve()))
                map_yaml_list[i]["image"] = "./"+str(img_path.name)
                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
            wp_filepath = dir_path / Path("waypoints.yaml")
            with open(wp_filepath, 'w') as f:
                f.write(wp_yaml)
            
            # merged map (yaml, pgm)
            yaml_path = dir_path / Path("merged_map.yaml")
            img_path = yaml_path.with_suffix(".pgm")
            merged_yaml["image"] = "./" + str(img_path.name)
            str_yaml = ""
            line = "\n"
            for key, val in merged_yaml.items():
                str_yaml += "{}: {}".format(key, val) + line
            with open(str(yaml_path), 'w') as f:
                f.write(str_yaml)
            merged_img_pil.save(str(img_path))
            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()