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?

Python CustomTkinterでTeXの報告書を管理するアプリを作る

Last updated at Posted at 2025-11-10

TeX で作っている報告書類が 20 個ほどあるのだが、修正を行うたびにガチャガチャやるのが面倒なこともあり、このアプリを作ろうと思い立った。
TeX はコンパイルする手間が必要だが、慣れてしまえば Word より入力は楽だし、このような自動化にも馴染むので、手放せない。
なお、使用マシンは、M1 Pro MacBook Pro 14" である。

アプリ実行中のイメージは以下の画像の通り。
Screenshot 2025-11-10 at 7.29.54.png

ボタンの機能は以下の通り。

  • TeX pdf;選択したファイルの pdf を作成
  • Clear all:チェックボックスの選択を全解除
  • Select all:チェックボックスを全選択
  • View:選択した pdf を preview で表示
  • Exit:アプリ終了
  • Clear:(Treated Files) の下に表示された pdf 作成完了のファイル名をクリア

機能としては、チェックボックスで選択したTeX ファイルから pdf を作成する、また選択したファイルを preview で表示するというもの。管理するファイルと報告書タイトルは別途テキストファイルに書き込んであり、これを読み込む。また TeX のプレアンブルもテキストファイルにしてあり、各報告書共通で使うようにしている。

ファイル管理用テキストファイルの中身は以下の画像の通り。新しい報告書を追加する場合は、ここにファイル名=ディレクトリ名とタイトルを追加すれば GUI は自動的に更新される。
Screenshot 2025-11-10 at 7.33.46.png

TeXのプレアンブルは以下の画像の通り。数式はamsmathであり、\renewcommand{\familydefault}{\sfdefault}により、本文フォントは sans-serif にしている。(本文は英語です)
amsmathと sans-serif の組み合わせが組版的に正しいかはわからないが、少なくとも本文のsansーserif は、自分的には読みやすい。

Screenshot 2025-11-10 at 7.34.30.png

大した処理をしているわけではないが、ここで、少しプログラミング上のTipsを述べておく。

チェックボックスの作成

    var =[tk.BooleanVar(
          value=False) for i in range(n)]
    cbox=[ctk.CTkCheckBox(
          frame1,
          width=200,
          height=30,
          text=sfile[i], 
          variable=var[i]
          ) for i in range(n)]
    for i in range(n):
        ir,ic=divmod(i,3)
        cbox[i].grid(row=ir,column=ic)

チェックボックスは、別途テキストファイルに記載された報告書の数だけ自動的に生成される。チェックボックスのオン・オフは、varという変数を定義しこれの値(True or False)により把握する。
チェックボックスの配置はgridで行っているが、列数を3に固定し、ir,ic=divmod(i,3)で表示順を列数で割った商と余りを行番号と列番号に当てはめている。
アプリを立ち上げた状態では、チェックボックスは、全解除(False)の状態である。

pdf 作成制御

    def get_value():
        tbox.delete('0.0','end') 
        app.update()
        for i in range(n):
            if var[i].get()==True:
                dlist=pre_org.copy()
                dirn=sfile[i]  
                if i==0:
                    cmd='python py_toc.py'
                    subprocess.run(cmd,shell=True)
                    tex_pre_toc(dlist)
                else:    
                    tstr=stitle[i]
                    tex_pre(dirn,tstr,dlist)
                tex_pdf(dirn)
                tbox.insert('end',dirn+'\n')
                tbox.see('end')
                app.update()
        messagebox.showinfo('showinfo','Work has been completed')

TeX pdfのボタンを押すと、チェックボックスで選択されているファイルの pdf を作成する。pdf 作成処理が完了すると、ボタン群の下に配置したテキストボックスに逐次処理完了ファイル名を出力していく。TeX による pdf 作成そのものは、関数tex_pdf内で、platexおよびdvipdfmxにより行っている。
上記リスト中、python の外部プログラム py_toc.pyを実行しているが、これは、ファイルリストを読み込み、これを表形式にした画像ファイルを作成するもの。TeX の中でこれを読み込み、報告書全体のカバーページを作成している。
pdf 作成済みファイルの逐次出力は、テキストボックスにinsertで追加するのだが、スクロールバーを自動でスクロールさせる、すなわち最下段のテキストを表示するように.see('end')を入れる。またファイル名を追加したらGUI表示を更新するためapp.update()を入れる。この処理を取り出したコードは以下の通り。

tbox.insert('end',dirn+'\n')
tbox.see('end')
app.update()

pdf 表示

    def view():
        for i in range(n):
            if var[i].get()==True:
                dd=sfile[i]  
                cmd=f'open ./0_pdf_doc/{dd}.pdf'
                subprocess.run(cmd,shell=True)
        messagebox.showinfo('showinfo','Work has been completed')

チェックボックスで選択されている pdf ファイルを preview で表示する。私の環境では、初めの数個は、独立したウインドウで表示され、その後はタブ内に表示される。これは preview の設定によるようである。

プログラム全文

import customtkinter as ctk
import tkinter as tk
import numpy as np
import subprocess
import os
import shutil
from tkinter import messagebox


def inp():
    fnameR='_flist.txt'
    f=open(fnameR,'r')
    datalist=f.readlines()
    f.close()
    sfile=[]
    stitle=[]
    for text in datalist:
        text=text.strip()
        text=text.split(',')
        sfile.append(text[0])
        stitle.append(text[1])
    f = open('_tex_pre.txt', 'r')
    data = f.read()
    f.close()
    pre_org=data.split('\n')
    return sfile,stitle,pre_org


def tex_pdf(dd):
    dir1=f'./{dd}/tex'
    os.chdir(dir1)
    cmd1='platex tex_test'
    cmd2='dvipdfmx tex_test'
    subprocess.run(cmd1,shell=True)
    subprocess.run(cmd1,shell=True)
    subprocess.run(cmd2,shell=True)
    os.chdir('../../')
    file1=f'{dir1}/tex_test.pdf'
    file2=f'./0_pdf_doc/{dd}.pdf'
    shutil.copy(file1,file2)        


def tex_pre_toc(dlist):
    tstr=r'Sugoi Project\\Design Calculation Report of Power Station Civil Structure\\during Construction Stage'
    author='Yamada Tarou (x-company)'
    date=r'\today'
    ss=[]
    for text in dlist:
        if 0<=text.find('xxxxx'):
            text='\\title{'+tstr+'}'
        if 0<=text.find('author{}'):
            text='\\author{'+author+'}'
        if 0<=text.find('date{}'):
            text='\\date{'+date+'}'
        if 0<=text.find('thispagestyle{empty}'): continue
        if 0<=text.find('addtocounter{page}{-1}'): continue
        if 0<=text.find('tableofcontents'): continue
        if 0<=text.find('pagebreak'): continue
        ss.append(text)
    s='\n'.join(ss)
    dirn='./000_cover_toc'
    fnameW=f'./{dirn}/tex/tex_test.tex'
    f=open(fnameW,'w')
    print(s,file=f)
    f.close()


def tex_pre(dirn,tstr,dlist):
    ss=[]
    for text in dlist:
        if 0<=text.find('xxxxx'):
            text='\\title{'+tstr+'}'  
        ss.append(text)
    s='\n'.join(ss)
    fnameW=f'./{dirn}/tex/tex_test.tex'
    f=open(fnameW,'w')
    print(s,file=f)
    f.close()


def main():
    sfile,stitle,pre_org=inp()
    n=len(sfile)

    # Set appearance mode and default color theme
    ctk.set_appearance_mode('dark')
    ctk.set_default_color_theme('blue')

    # Create the main window
    app = ctk.CTk()
    app.title('TeX pdf')

    fon,fsz='Calibri',20
    col='transparent'

    frame1=ctk.CTkFrame(app,fg_color=col)
    var =[tk.BooleanVar(
          value=False) for i in range(n)]
    cbox=[ctk.CTkCheckBox(
          frame1,
          width=200,
          height=30,
          text=sfile[i], 
          variable=var[i]
          ) for i in range(n)]
    for i in range(n):
        ir,ic=divmod(i,3)
        cbox[i].grid(row=ir,column=ic)
    frame1.pack(padx=10,pady=10)


    def get_value():
        tbox.delete('0.0','end') 
        app.update()
        for i in range(n):
            if var[i].get()==True:
                dlist=pre_org.copy()
                dirn=sfile[i]  
                if i==0:
                    cmd='python py_toc.py'
                    subprocess.run(cmd,shell=True)
                    tex_pre_toc(dlist)
                else:    
                    tstr=stitle[i]
                    tex_pre(dirn,tstr,dlist)
                tex_pdf(dirn)
                tbox.insert('end',dirn+'\n')
                tbox.see('end')
                app.update()
        messagebox.showinfo('showinfo','Work has been completed')


    def can_all():
        for i in range(n):
            var[i].set(False)


    def set_all():
        for i in range(n):
            var[i].set(True)


    def view():
        for i in range(n):
            if var[i].get()==True:
                dd=sfile[i]  
                cmd=f'open ./0_pdf_doc/{dd}.pdf'
                subprocess.run(cmd,shell=True)
        messagebox.showinfo('showinfo','Work has been completed')


    def close_app():
        exit(0)


    frame2=ctk.CTkFrame(app,fg_color=col)
    button1 = ctk.CTkButton(frame2,text='TeX pdf', width=100,font=(fon,fsz),command=get_value)
    button2 = ctk.CTkButton(frame2,text='Clear all',width=100,font=(fon,fsz),command=can_all)
    button3 = ctk.CTkButton(frame2,text='Select all',width=100,font=(fon,fsz),command=set_all)
    button4 = ctk.CTkButton(frame2,text='View', width=100,font=(fon,fsz),command=view)
    button5 = ctk.CTkButton(frame2,text='Exit',width=100,font=(fon,fsz),command=close_app)
    button1.grid(row=0,column=0,padx=5, pady=10)
    button2.grid(row=0,column=1,padx=5, pady=10)
    button3.grid(row=0,column=2,padx=5, pady=10)
    button4.grid(row=0,column=3,padx=5, pady=10)
    button5.grid(row=0,column=4,padx=5, pady=10)
    frame2.pack(fill=ctk.X,padx=10)


    def cls():
        tbox.delete('0.0','end') 
        app.update()

    frame3=ctk.CTkFrame(app,fg_color=col)
    tcol='#aaaaaa'
    lb=ctk.CTkLabel(
        frame3,
        text='(Treated Files)',
        font=(fon,fsz),
        text_color=tcol,
        anchor=ctk.W
        )
    bt=ctk.CTkButton(
        frame3,
        text='Clear',
        width=100,
        font=(fon,fsz),
        text_color=tcol,
        command=cls
        )
    ww=300
    tbox=ctk.CTkTextbox(
        frame3,
        width=ww,
        height=0.3*ww,
        font=('Courier',fsz-2)
        )
    lb.grid(row=0,column=0)
    bt.grid(row=0,column=1)
    tbox.grid(row=1,column=0,columnspan=2)
    frame3.pack(fill=ctk.X,padx=10,pady=10)


    # Run the application
    app.mainloop()


#---------------
# Execute
#---------------
if __name__ == '__main__': main()

以 上

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?