0
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【python】よく使うリンクやファイルをボタンで登録するlauncher

Last updated at Posted at 2024-01-08

よく使うリンクやファイル、フォルダを登録しボタンで飛べるlauncherを作成したので備忘録として残しておきます。

2023/1/10 追記
webサイトはドラッグ&ドロップでurlが取得できないため、
ラベルスペースで右クリック⇒サブウィンドウを表示し、リンクを登録できるよう修正しました。。

launcher.py
import tkinter as tk
import tkinter.ttk as ttk
from tkinterdnd2 import *
import subprocess
import webbrowser
import os
import urllib.request
import ssl
import tkinter.simpledialog as simpledialog
import tkinter.messagebox as messagebox

class Launcher:

    # 拡張子.txtのファイル名を取得
    FILE_NAME_LIST=os.listdir("./")
    TEXT_FILE_NAME_LIST=[]
    for i in range(len(FILE_NAME_LIST)):
        if ".txt"==os.path.splitext(FILE_NAME_LIST[i])[1]:
            TEXT_FILE_NAME_LIST.append(FILE_NAME_LIST[i])

    ssl._create_default_https_context = ssl._create_unverified_context

    def main(self):
        global nb
        global root
        # ウィンドウ設定
        root = TkinterDnD.Tk()
        root.title("Launcher")
        root.geometry("325x400")
        root.resizable(width=False, height=False)

        # タブ作成
        nb = ttk.Notebook(root)
        self.create_tab(Launcher.TEXT_FILE_NAME_LIST)

        root.mainloop()

    @classmethod
    def create_tab(cls, file_name_list):
        # タブ作成
        global nb
        global tab_list

        # タブのインスタンスを作成
        tab_dict={}
        tab_list = []
        for file_name in file_name_list:
            tab = tk.Frame(nb)
            tab_dict[tab]=file_name

            # タブに表示する文字列の作成
            nb.add(tab, text=file_name.replace(".txt",""), padding=2)

            tab_list.append(tab)

        nb.pack(expand=True, fill=tk.BOTH)

        # タブの中身を作成
        for tab, file_path in tab_dict.items():
            # ラベルを作成
            label = cls.create_label(tab)
            label.pack(expand=True, fill=tk.BOTH)

            # ファイル読み込み
            link_dict, not_link_dict = cls.read_file(file_path)

            # ラベルの中にボタン作成
            cls.create_button(label, link_dict, not_link_dict)

    @classmethod
    def create_label(cls, tab):
        # ラベル作成
        global nb

        # labelにスクロールが付けれないため、スクロールを紐づけられるcavasを作成
        canvas = tk.Canvas(tab, width=200, relief=tk.RIDGE, cursor="hand2")
        # スクロールバーの作成
        scrollbar = tk.Scrollbar(canvas, orient=tk.VERTICAL, command=canvas.yview)
        # Canvasのスクロール範囲を設定
        canvas.configure(scrollregion=(0, 0, 300, 1000))
        canvas.configure(yscrollcommand=scrollbar.set)
        # 表示
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        canvas.pack(expand=True, fill=tk.BOTH)

        frame=tk.Frame(canvas)
        # Canvas上の座標(0, 0)に対してFrameの左上(nw=north-west)をあてがうように、Frameを埋め込む
        canvas.create_window((0, 0), window=frame, anchor="nw", width=300, height=1000)

        label = ttk.Label(frame, text=nb.tab(tab, "text"), width=200, relief=tk.RIDGE, cursor="hand2")
        label.drop_target_register(DND_FILES)
        # drop_pathメソッドとラベルを紐づけ
        label.dnd_bind("<<Drop>>", cls.drop_path)
        # ラベルとメニューを紐づけ
        cls.link_label_and_menu(label)

        return label

    @classmethod
    def create_button(cls, label, link_dict, not_link_dict):
        # link
        row = 0
        col = 0
        for name, url in link_dict.items():
            # ボタン作成
            cls.create_link_button(label, name, url, row, col)
            if col == 1:
                row += 1
                col = 0
                continue
            col += 1

        # link以外
        for name, path in not_link_dict.items():
            # ボタン作成
            cls.create_not_link_button(label, name, path, row, col)
            if col == 1:
                row += 1
                col = 0
                continue
            col += 1

    @classmethod
    def create_link_button(cls, label, button_name, url, row, col):
        # ボタン作成
        button = tk.Button(label, text=button_name,
                        command=lambda: cls.jump_to_link(url), width=20, relief="raised")
        button.grid(column=col, row=row)

        # ボタンとメニューの紐づけ
        cls.link_button_and_menu(button)

    @classmethod
    def create_not_link_button(cls, label, button_name, path, row, col):
        # ボタン作成
        button = tk.Button(label, text=button_name,
                        command=lambda: cls.open_path(path), width=20, relief="raised")
        button.grid(column=col, row=row)

        # ボタンとメニューの紐づけ
        cls.link_button_and_menu(button)

    @classmethod
    def drop_path(cls, event):
        global nb
        global tab_list
        # 入力ダイアログを表示し、ボタン名を取得
        input_data = simpledialog.askstring("Input Box", "ボタン名を入力してください")

        # ドラッグ&ドロップされたパスから先頭と末尾の""を取り除く
        path = event.data[1:]
        path = path[:-1]

        # タブ名取得
        tab_name = nb.tab(nb.select(), "text")

        # ファイル読み込み
        file_path = f"{tab_name}.txt"
        link_dict, not_link_dict=cls.read_file(file_path)

        not_link_dict[input_data] = path

        # ファイル書き込み
        cls.write_file(file_path, link_dict, not_link_dict)

        # 現在のタブ削除
        for tab in tab_list:
            nb.forget(tab)
        # タブから再作成
        cls.create_tab(cls.TEXT_FILE_NAME_LIST)

    @classmethod
    def link_button_and_menu(cls, button):
        global pmenu
        pmenu=tk.Menu(button, tearoff=0)
        pmenu.add_command(label="ボタン名変更", command=cls.change_button_name)
        pmenu.add_command(label="ボタン削除", command=cls.delete_button)
        # 右クリックでメニュー表示
        button.bind("<Button-3>", cls.show_button_menu)

    @classmethod
    def show_button_menu(cls, e):
        global select_button
        global pmenu
        select_button=e.widget.cget("text")
        pmenu.post(e.x_root, e.y_root)

    @classmethod
    def change_button_name(cls):
        global nb
        global tab_list
        global select_button
        # 入力ダイアログを表示し、新しいボタン名を取得
        input_data = simpledialog.askstring("Input Box", "ボタン名を入力してください")

        # タブ名取得
        tab_name = nb.tab(nb.select(), "text")

        # ファイル読み込み
        file_path = f"{tab_name}.txt"
        link_dict, not_link_dict = cls.read_file(file_path)

        # リンクを格納した辞書に変更するボタン名があるか判定
        link_path = link_dict.get(select_button, None)
        if link_path is not None:
            link_dict = cls.change_key(link_dict, select_button, input_data)
        else:
            # リンク以外を格納した辞書に変更するボタン名があるか判定
            not_link_path = not_link_dict.get(select_button, None)
            if not_link_path is not None:
                not_link_dict = cls.change_key(not_link_dict, select_button, input_data)

        # ファイル書き込み
        cls.write_file(file_path, link_dict, not_link_dict)

        # 現在のタブ削除
        for tab in tab_list:
            nb.forget(tab)
        # タブから再作成
        cls.create_tab(cls.TEXT_FILE_NAME_LIST)

    @classmethod
    def change_key(cls, target_dict, old_key, new_key):
        new_dict = {}
        key_list = []
        value_list = []

        for key, value in target_dict.items():
            key_list.append(key)
            value_list.append(value)

        for i, key in enumerate(key_list):
            if key_list[i] == old_key:
                key_list[i] = new_key
                break

        for i, key in enumerate(key_list):
            new_dict[key] = value_list[i]

        return new_dict

    @classmethod
    def delete_button(cls):
        global nb
        global select_button
        global tab_list

        # タブ名取得
        tab_name = nb.tab(nb.select(), "text")

        # ファイル書き込み
        file_path = f"{tab_name}.txt"
        link_dict, not_link_dict = cls.read_file(file_path)
        # ボタン名と一致するものを削除
        for k in link_dict:
            if k == select_button:
                link_dict.pop(k, None)
                break

        for k in not_link_dict:
            if k == select_button:
                not_link_dict.pop(k, None)
                break

        # ファイル書き込み
        cls.write_file(file_path, link_dict, not_link_dict)

        # 現在のタブ削除
        for tab in tab_list:
            nb.forget(tab)
        # タブから再作成
        cls.create_tab(cls.TEXT_FILE_NAME_LIST)

    @classmethod
    def link_label_and_menu(cls, label):
        global label_menu
        label_menu=tk.Menu(label, tearoff=0)
        label_menu.add_command(label="リンク登録", command=cls.show_link_dialog)
        # 右クリックでメニュー表示
        label.bind("<Button-3>", cls.show_label_menu)

    @classmethod
    def show_label_menu(cls, e):
        global label_menu
        label_menu.post(e.x_root, e.y_root)

    @classmethod
    def show_link_dialog(cls):
        # サブウインドウの作成
        global root
        global sub_window
        sub_window = tk.Toplevel(root)
        sub_window.geometry("300x100")

        input_link_name_label = tk.Label(sub_window, text='リンク名:')
        input_link_name_label.place(x=20, y=20)

        input_link_name = tk.Entry(sub_window, width=30)
        input_link_name.place(x=80, y=20)

        input_url_label = tk.Label(sub_window, text='url        :')
        input_url_label.place(x=20, y=50)

        input_url = tk.Entry(sub_window, width=30)
        input_url.place(x=80, y=50)

        button = tk.Button(sub_window, text='登録', command=lambda: cls.register_link(input_link_name, input_url))
        button.place(x=140, y=70)

    @classmethod
    def register_link(cls, input_link_name, input_url):
        global nb
        global tab_list
        global sub_window

        link_name = input_link_name.get()
        url = input_url.get()

        # タブ名取得
        tab_name = nb.tab(nb.select(), "text")

        # ファイル読み込み
        file_path = f"{tab_name}.txt"
        link_dict, not_link_dict=cls.read_file(file_path)

        # リンク判定
        is_link = cls.check_url(url)

        if is_link:
            link_dict[link_name] = url
        else:
            messagebox.showerror("エラー", "リンクが有効ではありません")

        # ファイル書き込み
        cls.write_file(file_path, link_dict, not_link_dict)

        # 現在のタブ削除
        for tab in tab_list:
            nb.forget(tab)
        # タブから再作成
        cls.create_tab(cls.TEXT_FILE_NAME_LIST)

        # サブウィンドウを閉じる
        sub_window.destroy()

    @classmethod
    def read_file(cls, file_path):
        # ファイル読み込み

        link_dict = {}
        not_link_dict = {}

        f = open(file_path, 'r', encoding='utf-8')
        while True:
            data = f.readline().rstrip('\n')
            if data == '':
                break

            data_list = data.split(',')
            key = data_list[0]
            value = data_list[1]

            # パスがリンクかを判定
            is_link = cls.check_url(value)

            if is_link:
                link_dict[key] = value
            else:
                not_link_dict[key] = value

        return link_dict, not_link_dict

    @classmethod
    def write_file(cls, file_path, link_dict, not_link_dict):
        # ファイル書き込み
        write_list=[]

        for name, url in link_dict.items():
            row = name + ',' + url
            write_list.append(row)

        for name, path in not_link_dict.items():
            row = name + ',' + path
            write_list.append(row)

        file_path = './' + file_path

        with open(file_path, mode='w',encoding='utf-8')as f:
            f.write('\n'.join(write_list))

    @classmethod
    def check_url(cls, url):
        # urlのリンクが有効か判定
        is_link = True
        try:
            f = urllib.request.urlopen(url)
            f.close()
        except Exception as e:
            is_link = False

        return is_link

    @classmethod
    def jump_to_link(cls, url):
        # リンクを開く
        webbrowser.open(url)

    @classmethod
    def open_path(cls, path):
        # 指定されたパスを開く
        subprocess.Popen(["start", path], shell=True)

if __name__ == "__main__":
    launcher = Launcher()
    launcher.main()

0
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?