1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
import tkinter as tk
class SelectableText(tk.Text):
def __init__(self, master, **kwarg):
super().__init__(master, **kwarg, wrap='word')
self.focus_set() # pour donner le focus sur le texte dès le démarrage de l'appli
self.tag_configure("monTag", background="#fcc",
font ="")
# cur_ind = index courant variable au cours du glissement , max_ind = index maxi , min_ind = index mini et down_ind = index du texte lors d'un clic down
self.cur_ind = self.max_ind = self.min_ind = self.down_ind = '' # initialisation par précaution mais ce sera fait de manière plus pertinente dans la méthode mouse_down
self.bind("<Control-Button-1>", self.mouse_down)
self.bind("<Button-1>", self.mouse_down) # pour obliger les variables à être définies même si la touche Ctrl n'est pas enfoncée juste avant le down
self.bind("<Control-B1-Motion>", self.mouse_drag) # pour obliger l'appui sur la touche control pdt le glissement
self.bind("<ButtonRelease-1>", self.mouse_up)
self.bind("<KeyRelease-Control_L>", self.mouse_up)
self.bind("<Control-Double-Button-1>", self.double_click)
def double_click(self, event):
pattern = "[\s,;:!?.\"'()]" # regex permettant de détecter le caractère de transition entre deux mot (espaces, virgule, point...)
self.debut_mot = self.index(f"@{event.x},{event.y} wordstart")
self.fin_mot = self.search(pattern, self.debut_mot, regexp = True, forwards=True)
self.tag_add(tk.SEL, self.debut_mot, self.fin_mot)
def mouse_down(self, event):
self.down_ind = self.index(f"@{event.x},{event.y}") # '@x,y' est la position située juste avant le caractère le plus proche de la position (x, y)
self.cur_ind = self.max_ind = self.min_ind = self.down_ind
def update_ind(self): # permet de mettre à jour les index et la sélection en cours en réduisant éventuellement une sélection trop grande lors d'une inversion du glissement
def verif_and_compare(i1 , op , i2):
if not i1 or not i2: # permet de vérifier si les deux index sont définis avant de les comparer
return False
result = self.compare(i1, op, i2)
return self.compare(i1, op, i2) # compare les positions de deux index du widget texte, et retourne True si la relation précisée par op entre les deux index est vérifiée
if verif_and_compare(self.cur_ind, ">", self.down_ind):
self.tag_add(tk.SEL, self.down_ind, self.cur_ind)
if verif_and_compare(self.max_ind, ">" , self.cur_ind) : # se produit quand on revient à G après être allé à D
self.tag_remove(tk.SEL, self.cur_ind , self.max_ind)
self.max_ind = self.cur_ind
elif verif_and_compare(self.cur_ind , "<" , self.down_ind) :
self.tag_add(tk.SEL, self.cur_ind, self.down_ind)
if verif_and_compare(self.min_ind, "<" , self.cur_ind) : # se produit quand on revient à D après être allé à G
self.tag_remove(tk.SEL, self.min_ind, self.cur_ind)
self.min_ind = self.cur_ind
elif verif_and_compare(self.cur_ind , "==" , self.down_ind) :
if verif_and_compare(self.min_ind, "<", self.cur_ind) : # se produit quand on revient à D après être allé à G
self.tag_remove(tk.SEL, self.min_ind, self.cur_ind)
elif verif_and_compare(self.cur_ind, "<" , self.max_ind) : # se produit quand on revient à G après être allé à D
self.tag_remove(tk.SEL, self.cur_ind, self.max_ind)
self.cur_ind = self.max_ind = self.min_ind = self.down_ind # on peut imposer l'index du mouse-down à tous les autres index dans ce cas
def mouse_drag(self, event):
self.cur_ind = self.index(f"@{event.x},{event.y}")
self.update_ind()
def mouse_up(self, event):
self.cur_ind = self.index(f"@{event.x},{event.y}")
self.update_ind()
self.max_ind = self.min_ind = self.cur_ind = self.down_ind = ""
def agirSurSelection(self , action):
sel_zones = [str(z) for z in self.tag_ranges(tk.SEL)]
for i in range(0,int(len(sel_zones)-1), 2):
startSel , endSel = sel_zones[i] , sel_zones[i+1]
if action == "tag_add":
self.tag_add("monTag", startSel, endSel)
elif action == "tag_remove":
self.tag_remove("monTag", startSel, endSel)
root = tk.Tk()
root.title("Sélection multiple avec le widget Text de tkinter")
text = SelectableText(root, width=80, height=30)
me = '''Mode d'emploi de l'interface :
Pour réaliser une sélection multiple, il faut utiliser la méthode
classique, c'est à dire maintenir la touche Ctrl tout en effectuant
l'une des deux actions suivantes :
1 - cliquer avec le bouton de gauche puis faire "glisser" le curseur
de la souris sur le texte à sélectionner, relâcher le clic avant de
passer à une autre partie du texte.
2 - double-cliquer sur chacun des mots à sélectionner.
Les parties sélectionnées peuvent être mises en forme(taguées)
simultanément en cliquant sur le bouton correspondant.
Un autre bouton permet de supprimer la mise en forme de toutes
les parties sélectionnées.
Remarque : l'utilisation de la touche Ctrl avant de double-cliquer sur
un mot permet d'éviter la sélection des signes de ponctuation alors
que le double-clic provoque leur sélection lorsque la touche Ctrl n'est
pas enfoncée.
Exemple : tester chaque méthode du double clic avec le mot suivant :
,ponctuation?
'''
text.insert('end', me)
bTagSel = tk.Button(root, text = "Tag select", command = lambda arg="tag_add" : text.agirSurSelection(arg))
bDetagSel = tk.Button(root, text = "Detag select", command = lambda arg="tag_remove" : text.agirSurSelection(arg))
text.grid(row=0, column=0, rowspan =3)
bTagSel.grid(row=0, column=1)
bDetagSel.grid(row=1, column=1)
root.mainloop() |