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?

クリップボード履歴管理ツール

Posted at

挨拶

今回も前回に引き続きPythonの学習としてクリップボードの履歴管理ツールを作成していきます。
前回記事:Pythonによるタイマー作成

実行環境

  • OS : Pop!_OS
  • Anaconda

教本

下準備

まずは今回のツール作成に必要なライブラリ、pyperclipをインストールします。

pip install pyperclip

そもそもクリップボードとは?

クリップボードについて、教本にはこのように書かれていました。

クリップボードは一時的にテキストや画像などのデータを保存するための仕組みです。

つまり、よく使われるPCのショートカットキーctrl + cを使うことでテキストや画像を一時保存する仕組みのことです。一時保存したデータはctrl +vで貼り付け(ペースト)をすることで任意の場所に写すことができます。(いわゆるコピペと言うやつです)
今回は、コピペをした履歴を保存するツールを作成していきます。

製作開始

まずは、前回同様インストールしたライブラリの動作確認用の簡単なコードを書きます。
コード全文

import pyperclip as pp
import TkEasyGUI as tk

# 文字列をコピーする
pp.copy("test") # 実際に使う際はユーザーが入力した値が入る

# コピーした内容を取得
text = pp.paste()
# 取得した内容(test)をポップアップ表示
tk.popup(text)

このコードで"test"の文字列をコピーしクリップボードに一時保存、その内容を取得し画面にポップアップ表示させる。

補足

補足として(私含め)Linux環境では上記のコードそのままでは使えません。
Linux(今回は私の環境に近い"Ubuntu"を指しています)で行う場合はまず以下のパッケージをインストールする必要があります。

#クリップボードを操作するコマンドラインツール(xclip)をインストール
sudo apt install xclip
#クリップボードを操作するコマンドラインツール(xsel)をインストール
sudo apt install xsel

次にコード部分ですが、以下のような修正を加えます。

import pyperclip as pp
import TkEasyGUI as tk
import shutil # ファイル操作を行う標準ライブラリ。今回はコピーするクリップボードを指定する際に使用する


# pyperclipが使用するクリップボードツールを明示的に指定
if shutil.which("xclip"): # 最初はxclipを指定
    pp.set_clipboard("xclip") #コピー先をxclipに指定
elif shutil.which("xsel"): # xclipが使えない場合xselを使うよう指定
    pp.set_clipboard("xsel") #コピー先をxselに指定
# ===ここまでが追加した部分=== 

# 文字列をコピーする
pp.copy("test") # 実際に使う際はユーザーが入力した値が入る

# コピーした内容を取得
text = pp.paste()
# 取得した内容(test)をポップアップ表示
tk.popup(text)

Linux(UbuntuやPop!_OSなどのDebian系ディストリビューション)ではこれで動作させることができます。私の環境はLinux環境なので、今後クリップボードを使う際は、上記のif文を追加して作業を行います。

本番

動作確認も取れたので、ここからクリップボード履歴管理ツールの作成をしていきます。

コード

以下、コードの全文です。各処理についてはコード内のコメントに記載しています。

import os #OSに関連する機能を使うモジュール
import json #Pythonオブジェクトとのデータとjsonファイルのデータを変換するモジュール
import pyperclip as pp
import TkEasyGUI as tk
import shutil
# xclipかxselが使えるならpyperclipに指定
if shutil.which("xclip"):
    pp.set_clipboard("xclip")
elif shutil.which("xsel"):
    pp.set_clipboard("xsel")

# クリップボードの履歴を保存するファイルパスと保存するファイル名を指定
ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) # 保存先となるファイルパスを指定
SAVE_FILE = os.path.join(ROOT_DIR,'clipboard-history.json') # 保存するファイル名の指定と保存先ファイルパスの指定
# 保存できる履歴の最大数を指定
MAX_HISTORY = 20

# 既存のクリップボード履歴を読み取る
history = [] # クリップボードの内容を保持するリスト
if os.path.exists(SAVE_FILE): # 保存先のファイルがある場合処理を行う
    with open(SAVE_FILE,"r",encoding="utf-8") as f: # 履歴の保存ファイルを開き文字コードの指定をして読み込む (rは読み込む(read)の意味)
        history = json.load(f)
# 履歴を保存する関数の作成
def save_history(): #関数の宣言
    with open(SAVE_FILE,"w",encoding="utf-8") as f: # 履歴保存ファイルを開く。今回は"w(write、書き込み)を指定し変更できるようにする
        json.dump(history,f,ensure_ascii=False,indent=2) #Pythonオブジェクト(historyリスト)をjsonに変換し履歴保存ファイルとして保存
# 画面に表示する履歴の整形をする関数
def list_format(history): #関数の宣言と引数にhistoryリストを指定
# lambda関数(ラムダ関数、無名関数と呼ばれる簡単な処理を一時的に行うための関数)を使い各処理を定義
    lf = lambda v: v.strip().replace("\n","")  # strip関数で改行を含む前後の空白文字を削除、replace関数で改行コード(\n)を除去
    short = lambda v: v[:20] + "..." if len(v) > 20 else v  # 表示する履歴が20文字を超える場合、21字目以降の文字を"..."に置き換えて表示(保存した内容が変わるわけではない)
    return [f"{i+1:02}: {lf(short(h))}" for i, h in enumerate(history)] # 上記で定義したlambda関数を使い整形、履歴にインデックス(行番号)を付与した新しいリストを返す
# 表示するウィンドウのレイアウトを決める
LAYOUT = [
       [tk.Text("履歴を選んで'コピーボタン'を押してください。")], # 簡単な使い方説明を表示
       [tk.Listbox( # Listboxはリストの項目から選択を行えるようにする関数
                   # 選択肢の設定
                   values = list_format(history), #表示させる選択肢に 整形された履歴を指定
                   size = (40,15), # 画面に表示するウィンドウの大きさを指定
                   key="-history-" # 選択した履歴をリストから取得するためのキーを設定
           )],
       [# 各種ボタンの作成
        tk.Button("コピー"),tk.Button("削除"),tk.Button("終了")
        ]
       ]
# ウィンドウの作成
window = tk.Window("クリップボード履歴", LAYOUT)
# イベントループを使用しウィンドウ内の処理を実行
while True:
    # イベントを取得する
    event,values = window.read(timeout=100) # ウィンドウを読み込むイベントループの開始時間を100msに指定
    # 終了ボタンが押された場合処理を終了しウィンドウを閉じる
    if event == "終了":
        break
    # コピーボタンを押した場合の処理
    if event == "コピー":
        # 選択された履歴をクリップボードにコピー
        if values["-history-"]: # リストが空の状態かチェック
            set_text = values["-history-"][0] # LAYOUTで設定したキーを使い、リストの最初の文字列(index[0]、行番号)を取得
            index = int(set_text[0:2]) # リストから取得した値の行番号を取り出す
            text = history[index - 1] # 行番号から1を引くことでリストに対応した正しいindexを算出、取得する値として指定(行番号01から1を引くと0になる)
            pp.copy(text) # 上記で指定したindexの値をクリップボードにコピー
            tk.popup("コピーしました")
    # 削除ボタンを押した場合
    if event == "削除":
        if values["-history-"]:
            sel_text = values["-history-"][0] # 行番号を取得
            # 履歴のデータを取り出す
            index = int(sel_text[0:2])
            del history[index - 1]
            window["-history-"].update(list_format(history)) # キーを使い取得した要素を更新後の状態で上書きする
            save_history() # 上記で上書きした内容を保存
            tk.popup("履歴を削除しました")
            pp.copy("") # 重複登録防止(削除してもクリップボードには値があるため、空文字列で上書きして履歴から完全に削除できるようにするため)
    # クリップボードの内容を確認し処理を行う
    text = pp.paste() # クリップボードの値を取得
    if text and text not in history:#クリップボードに値があり履歴に値がない場合
        history.insert(0, text) # リストの先頭に追加
        if len(history) > MAX_HISTORY:
            history.pop()
        window["-history-"].update(list_format(history))
        save_history()
window.close()

補足

ensure_ascii=False,indent=2

この部分をjson.dump関数の引数に指定することで、日本語をjsonファイルへ変換する際にエスケープされるのを防ぎ読みやすい状態に直すことできる。

  • ensure_asciiはデフォルトではTrueだが、日本語は非ASCII文(アルファベット以外の文字があること)なので、Trueのままではアルファベットに変換されてしまう。Falseにすることで元の文字をそのまま出力できる
  • indentはjsonファイルに記録した履歴を読みやすいようにインデント(字下げ)を整えるために使用

今回の重要ポイント

  • OSの違いによるクリップボードツールの指定
    • Linux環境では"xclip"や"xsel"などのクリップボードツールを指定する必要がある
  • 普段何気なく使うコピペも履歴を取る場合では、ファイルパスの指定、内容の整形(改行コードの排除や表示数制限、行番号の付与)、Pythonオブジェクト(今回の場合はhistoryリスト)をjsonファイルに変換して保存など、様々な処理をする必要がある
  • 簡単な処理はラムダ関数を使って1文で書くことができる

感想

上記の重要ポイント(特にラムダ関数の使い方)の細かい部分への理解が追いついておらず、人に説明ができるようになるにはもう少しかかりそうです。

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?