3. Come Creare un Editor Testuale con Python Pt.2

Questa è la seconda parte del tutorial su come creare un editor di testo utilizzando il modulo Tkinter della Standard Library di Python. Potete trovare il codice completo di questa lezione nel repository di GitHub.

Nella lezione precedente abbiamo creato due classi: PyText e Menubar. In Menubar abbiamo definito parte delle caratteristiche che avrà la barra superiore del nostro editor testuale e abbiamo instanziato il menù all’interno dell’inizializzatore init della classe PyText: quest’ultima rappresenta il “controller” del nostro programma, è collegata a master (la nostra finestra principale) e contiene i widget Text e Scrollbar.

Attualmente il nostro codice è già in grado di farci scrivere del testo nell’editor: ora dobbiamo finire di implementare la possibilità di aprire, modificare e salvare documenti di estensioni diverse.


Come aprire i file di testo dal filesystem del nostro computer

Definiamo una nuova variabile filename all’interno del dunder init in PyText per rappresentare il file su cui stiamo lavorando:

self.filename = None

Ora possiamo iniziare ad implementare i metodi della classe PyText che abbiamo creato in precedenza iniziando dal metodo open_file, che ci permetterà di aprire qualsiasi documento testuale all’interno del nostro editor. Se viene chiamato open_file ed è presente un file, il metodo deve inanzitutto cancellare il conteuto corrente della schermata: utilizziamo il metodo delete del widget di tipo Text passando come parametri da quale area della finestra deve iniziare per cancellare il testo e dove deve finire. I parametri 1.0 indicano l'inizio e END la fine come abbiamo visto nella lezione precedente.

class PyText:

    def __init__(self, master):
		# ...
		self.filename = None
        # ...

    def open_file(self):
        if self.filename:
            self.textarea.delete(1.0, tk.END)

Ora che l’area testuale è libera possiamo aprire il file e inserirlo nell’area di testo aprendolo con la funzione open in modalità lettura e inserendo il testo con il metodo insert:

with open(self.filename, "r") as f:
    self.textarea.insert(1.0, f.read())

Per fare in modo che la classe sia in grado di reperire il file che dobbiamo aprire dobbiamo importare un nuovo modulo di tkinter:

from tkinter import filedialog

Questo modulo ci permette di creare una finestra per esplorare le cartelle del nostro computer e selezionare dei file specifici. Modifichiamo quindi open_file per implementare il metodo filename di filedialog a cui passiamo come parametri defaultextension (che indica quali file testuali cercare per primi) e filetypes (che accetta una lista di tuple con estensioni di file riconosciuti abbinata a una loro descrizione verbosa).

def open_file(self):
    self.name = filedialog.askopenfilename(
        defaultextension=".txt",
        filetypes=[("Tutti i file", "*.*"),
                    "File di Testo", "*.txt"),
                    "Script Python", "*.py"),
                    "Markdown text", "*.md"),
                    "File JavaScript", "*.js"),
                    "HTML", "*.html"),
                    "CSS", "*.css")])
    if self.filename:
        self.textarea.delete(1.0, tk.END)

Ora alla pressione del tasto Apri FIle verrà chiamato il metodo open_file di PyText per mostrarci la finestra di dialogo. Questa ci permette di scegliere un file di testo che, se viene riconosciuto come corretto, verrà importato nell’editor.

Facciamo in modo che la finestra prenda come titolo quello del file che abbiamo importato. Modifichiamo il metodo set_window_title di PyText passandogli come parametro un nome, che inizialmente sarà None. Controlliamo se il nome è stato passato e chiamiamo master per impostarlo.

class PyText:
    # ...

    def set_window_title(self, name=None):
        if name:
            self.master.title(name +  " - PyText")
        else:
            self.master.title("Untitled - PyText")

Torniamo al metodo open_file e facciamo in modo che set_window_title venga chiamato all’apertura di un nuovo file.

class PyText:
    # ...

    def open_file(self):
        # ...

        if self.filename:
            # ...

            self.set_window_title(self.filename)


Aggiungiamo un metodo per creare un nuovo file

Adesso occupiamoci del metodo che predispone l’editor ad ospitare un nuovo file vuoto, ovvero new_file di PyText. Puliamo la schermata come abbiamo fatto con open_file, rimuoviamo il titolo e chiamiamo set_window_title senza passargli alcun parametro:

class PyText:
    # ...

    def new_file(self):
        self.textarea.delete(1.0, tk.END)
        self.filename = None
        self.set_window_title()

Adesso se eseguiamo il nostro programma vediamo che è possibile creare un nuovo file.


Come implementare le funzionalità salva e salva con nome per il nostro editor

Occupiamoci ora del metodo save_as (salva con nome) che ci permetterà di associare un nome+estensione al contenuto che sarà presente nella schermata testuale e salvarlo nel filesystem. Dato che stiamo implementando funzionalità di scrittuta utilizziamo le clausole try ed except per fare in modo che venga creato un nuovo file con un’estensione specifica similmente a come abbiamo fatto con il metodo open_file verificando che non ci siano errori:

class PyText:
    # ...

    def save_as(self):
        try:
            new_file = filedialog.asksaveasfilename(
                initialfile="Untitled.txt",
                defaultextension=".txt",
                filetypes=[("Tutti i file", "*.*"),
                           ("File di Testo", "*.txt"),
                           ("Script Python", "*.py"),
                           ("Markdown Text", "*.md"),
                           ("File JavaScript", "*.js"),
                           ("Documenti HMTL", "*.html"),
                           ("Documenti CSS", "*.css")])
            textarea_content = self.textarea.get(1.0, tk.END)
            with open(new_file, "w") as f:
                f.write(textarea_content)
            self.filename = new_file
            self.set_window_title(self.filename)
        except Exception as e:
            print(e)

A questo punto se eseguiamo il codice e selezioniamo dal Menu il tasto “Salva con Nome”, ci verrà mostrato un menu a tendina con le varie estensioni che ci permettono di filtrare i file.

Implementiamo infine il metodo save in modo simile a save_as, con la differenza che esso dovrà controllare se il file è già stato salvato valutando la presenza di un valore per filename: se il nome è presente, il metodo save aggiornerà il file con le modifiche correnti, se il nome non è presente chiamerà save_as.

class PyText:
    # ...

    def save(self):
        if self.filename:
            try:
                textarea_content = self.textarea.get(1.0, tk.END)
                with open(self.filename, "w") as f:
                    f.write(textarea_content)
            except Exception as e:
                print(e)
        else:
            self.save_as()

Se eseguiamo il codice a questo punto dovremmo poter avere la possibilità di aprire, salvare i file e selezionare le estensioni che abbiamo aggiunto come filtro:

screenshot_editor_tk02

Il nostro codice completo è come segue:

import tkinter as tk
from tkinter import filedialog


class Menubar:

    def __init__(self, parent):
        font_specs = ("ubuntu", 14)

        menubar = tk.Menu(parent.master, font=font_specs)
        parent.master.config(menu=menubar)

        file_dropdown = tk.Menu(menubar, font=font_specs, tearoff=0)
        file_dropdown.add_command(label="Nuovo File",
                                  command=parent.new_file)
        file_dropdown.add_command(label="Apri File",
                                  command=parent.open_file)
        file_dropdown.add_command(label="Salva",
                                  command=parent.save)
        file_dropdown.add_command(label="Salva con Nome",
                                  command=parent.save_as)
        file_dropdown.add_separator()
        file_dropdown.add_command(label="Esci",
                                  command=parent.master.destroy)

        menubar.add_cascade(label="File", menu=file_dropdown)


class PyText:

    def __init__(self, master):
        master.title("Untitled - PyText")
        master.geometry("1200x700")

        font_specs = ("ubuntu", 18)

        self.master = master
        self.filename = None

        self.textarea = tk.Text(master, font=font_specs)
        self.scroll = tk.Scrollbar(master, command=self.textarea.yview)
        self.textarea.configure(yscrollcommand=self.scroll.set)
        self.textarea.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        self.scroll.pack(side=tk.RIGHT, fill=tk.Y)

        self.menubar = Menubar(self)

    def set_window_title(self, name=None):
        if name:
            self.master.title(name +  " - PyText")
        else:
            self.master.title("Untitled - PyText")

    def new_file(self):
        self.textarea.delete(1.0, tk.END)
        self.filename = None
        self.set_window_title()

    def open_file(self):
        self.filename = filedialog.askopenfilename(
            defaultextension=".txt",
            filetypes=[("Tutti i file", "*.*"),
                       ("File di Testo", "*.txt"),
                       ("Script Python", "*.py"),
                       ("Markdown Text", "*.md"),
                       ("File JavaScript", "*.js"),
                       ("Documenti HMTL", "*.html"),
                       ("Documenti CSS", "*.css")])
        if self.filename:
            self.textarea.delete(1.0, tk.END)
            with open(self.filename, "r") as f:
                self.textarea.insert(1.0, f.read())
            self.set_window_title(self.filename)

    def save(self):
        if self.filename:
            try:
                textarea_content = self.textarea.get(1.0, tk.END)
                with open(self.filename, "w") as f:
                    f.write(textarea_content)               
            except Exception as e:
                print(e)
        else:
            self.save_as()

    def save_as(self):
        try:
            new_file = filedialog.asksaveasfilename(
                initialfile="Untitled.txt",
                defaultextension=".txt",
                filetypes=[("Tutti i file", "*.*"),
                           ("File di Testo", "*.txt"),
                           ("Script Python", "*.py"),
                           ("Markdown Text", "*.md"),
                           ("File JavaScript", "*.js"),
                           ("Documenti HMTL", "*.html"),
                           ("Documenti CSS", "*.css")])
            textarea_content = self.textarea.get(1.0, tk.END)
            with open(new_file, "w") as f:
                f.write(textarea_content)
            self.filename = new_file
            self.set_window_title(self.filename)
        except Exception as e:
            print(e)
  
 
if __name__ == "__main__":
    master = tk.Tk()
    pt = PyText(master)
    master.mainloop()

Nella terza ed ultima parte di questo tutorial concluderemo il nostro editor di testo personalizzato mostrando un messaggio nella barra inferiore della finestra quando un file viene salvato, aggiungeremo la possibilità di manipolare i file tramite scorciatoie da tastiera e aggiungeremo un ulteriore menù che contiene le informazioni sull’editor.