GoSuda

Implémentation d'une visionneuse d'images à liste multi-fichiers basée sur Go et Tcl/Tk 9.0

By Yunjin Lee views ...

Dans le précédent article, nous avons brièvement examiné la bibliothèque CGo-Free Tcl/Tk. Dans cette séance, nous allons adapter l'exemple de la fois précédente pour créer une visionneuse d'images.

Plan de la visionneuse d'images

  1. L'afficheur d'images de la dernière fois ne disposait d'aucune fonction de suppression d'images ; plus on chargeait d'images, plus la fenêtre manquait de place. Supprimons les étiquettes inutilisées.
  2. Si l'afficheur d'images doit présenter plusieurs images sous forme de liste, créons une liste.
  3. Il est préférable de charger plusieurs images en une seule fois.
  4. Implémentons également la possibilité de retirer de la liste les images que l'on ne souhaite pas consulter.
  5. Créons une fonction permettant de sélectionner et d'afficher une image particulière parmi plusieurs images.

Bibliothèque Tcl/Tk 9.0 modifiée

La bibliothèque existante ne propose qu'une implémentation insuffisante du Listbox, ce qui rend difficile l'affichage d'une liste d'images. Téléchargeons la bibliothèque modifiée. Si Git CLI n'est pas installé, vous pouvez également télécharger l'archive tarball ou zip.

1git clone https://github.com/gg582/tk9.0

Examinons d'abord quelques-unes des fonctions ajoutées.

Avant d'étudier les nouvelles fonctions, voyons la structure des fonctions existantes en analysant brièvement la fonction Destroy située à la ligne 1017 de tk.go.

1func Destroy(options ...Opt) {
2	evalErr(fmt.Sprintf("destroy %s", collect(options...)))
3}

Cette fonction est implémentée en transmettant une action, sous forme de script Tcl, à la fonction evalErr. Autrement dit, pour réaliser la fonction souhaitée, il suffit de transmettre la commande au format du script Tcl correspondant.

À titre d'exemple, implémentons une méthode qui ajoute un élément à une listbox. Tout d'abord, pour le scripting Tcl, consultons la commande insert parmi les commandes utilisables sur un listbox dans le manuel officiel.

Commande insert

D'après la page de description de la commande insert, insert insère les éléments énumérés à un index spécifié. Pour l'implémenter, on peut donc écrire un code tel que :

1func (l *ListboxWidget) AddItem(index int, items string) {
2	evalErr(fmt.Sprintf("%s insert %d %s", l.fpath, index, items))
3}

Maintenant que nous avons compris le principe général de l'implémentation, présentons les fonctions supplémentaires du Listbox.

Liste : insertion et suppression

 1package main
 2import . "modernc.org/tk9.0"
 3
 4func main() {
 5    length := 3
 6    l := Listbox()
 7    l.AddItem(0, "item1 item2 item3")
 8    b1 := TButton(Txt("Delete Multiple Items, index (0-1)"), Command( func(){
 9        if length >= 2 {
10            l.DeleteItems(0,1)
11            length-=2
12        }
13    }))
14    b2 := TButton(Txt("Delete One Item, index (0)"), Command( func () {
15        if length > 0 {
16            l.DeleteOne(0)
17            length-=1
18        }
19    }))
20    Pack(TExit(),l,b1,b2)
21    App.Wait()
22}
23

Dans le programme ci-dessus, AddItem insère, à partir de l'index 0, les différents éléments séparés par des espaces. Les éléments item1 à item3 reçoivent respectivement les index 0, 1 et 2. Exécutons l'exemple pour observer le fonctionnement de la suppression d'éléments.

Liste : obtention des éléments sélectionnés

Récupérons maintenant les éléments sélectionnés dans le Listbox et vérifions-les.

 1package main
 2
 3import . "modernc.org/tk9.0"
 4
 5func main() {
 6    l := Listbox()
 7    l.SelectMode("multiple")
 8    l.AddItem(0, "item1 item2 item3")
 9    btn := TButton(Txt("Print Selected"), Command( func() {
10        sel := l.Selected()
11        for _, i := range sel {
12            println(l.GetOne(i))
13        }
14    }))
15
16    Pack(TExit(), l, btn)
17    App.Wait()
18}

La méthode Selected récupère les index de tous les éléments actuellement sélectionnés dans le Listbox. La méthode GetOne récupère la valeur de l'élément correspondant à l'index donné. Le résultat affiché dans la console permet de le vérifier. À noter que la méthode analogue Get reçoit un index de début et un index de fin, et récupère les valeurs de tous les éléments de la plage.

Modifions maintenant les couleurs du listbox.

Examinons d'abord l'exemple suivant.

 1package main
 2
 3import . "modernc.org/tk9.0"
 4
 5func main() {
 6    l := Listbox()
 7    l.Background("blue")
 8    l.Foreground("yellow")
 9    l.SelectBackground("black")
10    l.SelectForeground("white")
11    l.Height(20)
12    l.Width(6)
13    l.AddItem(0, "item1 item2 item3")
14    l.ItemForeground(0,"red")
15    l.ItemBackground(0,"green")
16    l.ItemSelectBackground(0,"white")
17    l.ItemSelectForeground(0,"black")
18    l.Relief("ridged")
19    Pack(TExit(),l)
20    App.Wait()
21}

Résultat de l'application des couleurs

Comme indiqué dans le code ci-dessus, la hauteur a augmenté. On constate également que les couleurs ont bien été appliquées. Ici, une option permettant de définir une couleur différente pour un élément particulier a été spécifiée, c'est pourquoi seule la première ligne présente une couleur différente.

Par ailleurs, bien que la différence ne soit pas importante, la méthode Relief permet de modifier le style de la bordure du widget parmi flat, groove, raise, ridge, solid et sunken.

Exemple de visionneuse d'images

Créons maintenant une visionneuse d'images à l'aide des widgets que nous venons d'étudier. Voici le programme d'exemple :

  1package main
  2
  3import (
  4    "fmt"
  5    "log"
  6    "os"
  7    "runtime"
  8    "strings"
  9    "path"
 10
 11    . "modernc.org/tk9.0"
 12)
 13
 14var pbuttons []*TButtonWidget
 15var extensions []FileType
 16var pbutton *TButtonWidget = nil
 17var listbox, listbox2 *ListboxWidget
 18var cur *LabelWidget = nil
 19var imagesLoaded []*LabelWidget
 20func PhotoName(fileName string) string {
 21        fileName = path.Base(fileName)
 22        return fileName[:len(fileName)-len(path.Ext(fileName))]
 23}
 24
 25func handleFileOpen() {
 26    res := GetOpenFile(Multiple(true),Filetypes(extensions)) //다중 선택을 활성화하고 필터를 켭니다.
 27    s := make([]string,0,1000)
 28    for _, itm := range res {
 29        if itm != "" {
 30            tmp := strings.Split(itm," ")
 31            s = append(s,tmp...)
 32        }
 33    }
 34
 35    for _, photo := range s {
 36        formatCheck := strings.Split(photo, ".")
 37        format := formatCheck[len(formatCheck)-1]
 38
 39        if (strings.Compare(format, "png") == 0) || (strings.Compare(format, "ico") == 0) {
 40            picFile, err := os.Open(photo)
 41            if err != nil {
 42                log.Println("Error while opening photo, error is: ", err)
 43            }
 44
 45            pictureRawData := make([]byte, 10000*10000)
 46            picFile.Read(pictureRawData)
 47
 48            imageLabel := Label(Image(NewPhoto(Data(pictureRawData))))
 49                        imagesLoaded = append(imagesLoaded,imageLabel)
 50            var deleteTestButton *TButtonWidget
 51            deleteTestButton = TButton(
 52                Txt("Unshow Image"),
 53            Command(func() {
 54                GridForget(imageLabel.Window)
 55                GridForget(deleteTestButton.Window)
 56            }))
 57
 58            pbuttons = append(pbuttons,deleteTestButton)
 59
 60                        listbox.AddItem(len(imagesLoaded)-1,PhotoName(photo))
 61                        listbox2.AddItem(len(imagesLoaded)-1,PhotoName(photo))
 62            picFile.Close()
 63        }
 64    }
 65}
 66
 67func DeleteSelected () {
 68    s:=listbox.Selected()
 69    if len(s) == 0 {
 70        return
 71        }
 72    for _, i := range s {
 73        listbox.DeleteOne(i)
 74        listbox2.DeleteOne(i)
 75        if len(imagesLoaded)-1>i {
 76            continue
 77        }
 78        if cur == imagesLoaded[i] {
 79            pbutton = nil
 80            cur = nil
 81        }
 82        Destroy(imagesLoaded[i])
 83        Destroy(pbuttons[i])
 84                imagesLoaded = append(imagesLoaded[:i],imagesLoaded[i+1:]...)
 85        pbuttons = append(pbuttons[:i], pbuttons[i+1:]...)
 86    }
 87}
 88
 89func SelectImage() {
 90        s:=listbox2.Selected()
 91        if len(s) == 0 {
 92                return
 93        }
 94
 95    if len(imagesLoaded) -1 < s[0] {
 96        return
 97    }
 98    if imagesLoaded[s[0]] == nil {
 99        return
100    }
101    if cur != nil {
102            GridForget(cur.Window)
103    }
104    if pbutton != nil {
105        GridForget(pbutton.Window)
106    }
107
108        Grid(imagesLoaded[s[0]], Row(0), Column(2))
109    Grid(pbuttons[s[0]], Row(0), Column(3))
110    cur = imagesLoaded[s[0]]
111    pbutton = pbuttons[s[0]]
112}
113
114func SelectIndex(index int) {
115
116    if len(imagesLoaded) -1 <index {
117        return
118    }
119    if imagesLoaded[index] == nil {
120        return
121    }
122    if cur != nil {
123            GridForget(cur.Window)
124    }
125    if pbutton != nil {
126        GridForget(pbutton.Window)
127    }
128
129        Grid(imagesLoaded[index], Row(0), Column(2))
130    Grid(pbuttons[index], Row(0), Column(3))
131    cur = imagesLoaded[index]
132    pbutton = pbuttons[index]
133}
134
135func main() {
136    menubar := Menu()
137    //DefaultTheme("awdark","themes/awthemes-10.4.0")
138    //테마를 사용하고 싶을 때에는 테마 명과 경로를 지정해 줍니다.
139    fileMenu := menubar.Menu()
140    extensions = make([]FileType,0,1)
141    extensions = append(extensions, FileType{ "Supported Images", []string {".png",".ico"}, "" } )
142    //필터에 png와 ico를 넣어 줍니다.
143    fileMenu.AddCommand(Lbl("Open..."), Underline(0), Accelerator("Ctrl+O"), Command(func () {
144        handleFileOpen()
145        SelectIndex(len(imagesLoaded)-1)
146    } ))
147    Bind(App, "<Control-o>", Command(func() { fileMenu.Invoke(0) }))
148    fileMenu.AddCommand(Lbl("Exit"), Underline(1), Accelerator("Ctrl+Q"), ExitHandler())
149    Bind(App, "<Control-q>", Command(func() { fileMenu.Invoke(1) }))
150    menubar.AddCascade(Lbl("File"), Underline(0), Mnu(fileMenu))
151        imagesLoaded = make([]*LabelWidget, 0, 10000)
152    pbuttons = make([]*TButtonWidget,0,10000)
153    var scrollx, scroll, scroll2, scrollx2 *TScrollbarWidget
154        listbox = Listbox(Yscrollcommand(func(e *Event) { e.ScrollSet(scroll)}) , Xscrollcommand( func(e *Event) { e.ScrollSet(scrollx)}))
155        listbox2 = Listbox(Yscrollcommand(func(e *Event) { e.ScrollSet(scroll2)}), Xscrollcommand(func(e *Event) { e.ScrollSet(scrollx2)}))
156        listbox.SelectMode("multiple")
157        listbox2 = Listbox()
158        listbox.Background("white")
159        listbox.SelectBackground("blue")
160        listbox.SelectForeground("yellow")
161        listbox2.Background("grey")
162        listbox2.SelectBackground("green")
163    listbox2.SelectForeground("blue")
164    listbox2.SelectBackground("brown")
165        listbox.Height(5)
166        listbox.Width(4)
167        listbox2.Height(5)
168        listbox2.Width(4)
169        delBtn := Button(Txt("Delete"), Command(func () { DeleteSelected() }))
170        selBtn := Button(Txt("Select"), Command(func () { SelectImage() }))
171        scroll = TScrollbar(Command(func(e *Event) { e.Yview(listbox) }))
172        scrollx = TScrollbar(Orient("horizontal"),Command(func(e *Event) { e.Xview(listbox) }))
173    scroll2 = TScrollbar(Command(func(e *Event) { e.Yview(listbox2) }))
174        scrollx2 = TScrollbar(Orient("horizontal"),Command(func(e *Event) { e.Xview(listbox2) }))
175        Grid(listbox,Row(1),Column(0), Sticky("nes"))
176        Grid(scroll,Row(1),Column(1), Sticky("nes"))
177    Grid(scrollx,Row(2),Column(0),  Sticky("nes"))
178        Grid(delBtn,Row(3),Column(0), Sticky("nes"))
179        Grid(listbox2,Row(1),Column(2), Sticky("nes"))
180        Grid(scroll2,Row(1),Column(3), Sticky("nes"))
181    Grid(scrollx2,Row(2),Column(2), Sticky("nes"))
182        Grid(selBtn,Row(3),Column(2), Sticky("nes"))
183    App.WmTitle(fmt.Sprintf("%s on %s", App.WmTitle(""), runtime.GOOS))
184    App.Configure(Mnu(menubar), Width("80c"), Height("60c")).Wait()
185}
186

Résultat d'exécution de la visionneuse d'images

Dans cet exemple, afin de simplifier l'implémentation, tous les widgets d'image sont créés à l'avance lors du chargement, et les fichiers en double ne sont pas vérifiés. Vous pouvez améliorer les problèmes évoqués précédemment, et essayer de modifier le thème en utilisant la méthode DefaultTheme, dont la partie est commentée. Nous vous encourageons à vous exercer en créant un programme amélioré intégrant ces points.

Synthèse

Dans cet article, nous avons vu comment fonctionne l'appel de commandes de la bibliothèque Tcl/Tk en Go, et nous avons créé une visionneuse d'images dotée d'un listbox.

  1. Méthode d'appel de commandes de la bibliothèque Tcl/Tk
  2. Utilisation du listbox
  3. Modification des propriétés du widget listbox
  4. Création d'une visionneuse d'images

Nous vous invitons à appliquer la même démarche pour modifier d'autres bibliothèques, et à réaliser un programme complet à partir des fonctions que vous aurez ajoutées.