Go ve Tcl/Tk 9.0 ile Çoklu Dosya Liste Kutusu Tabanlı Görüntü Görüntüleyici Uygulaması

Önceki yazıda CGo-Free Tcl/Tk kütüphanesini kısaca inceledik. Bu sefer önceki örneği geliştirerek bir görüntü görüntüleyici oluşturmaya çalışacağız.
Görüntü Görüntüleyici Planı
- Önceki görüntü gösterici, görüntü silme özelliğine sahip olmadığı için birden fazla görüntü yüklendikçe pencere boyutu yetersiz kalıyordu. Kullanılmayan etiketleri silelim.
- Görüntü göstericide birden fazla görüntüyü listeleyeceksek bir liste oluşturalım.
- Birden fazla görüntüyü tek seferde yüklemek iyi olacaktır.
- Görmek istemediğimiz görüntüleri listeden çıkarma işlevini de uygulayalım.
- Çoklu görüntüler arasından belirli bir görüntüyü seçip görüntüleme özelliği oluşturalım.
Değiştirilmiş Tcl/Tk 9.0 Kütüphanesi
Mevcut kütüphanede Listbox uygulaması yetersiz olduğu için görüntü listesini göstermek zordur. Değiştirilmiş kütüphaneyi indirelim. Git CLI yüklü değilse tarball veya zip dosyası olarak da indirebilirsiniz.
1git clone https://github.com/gg582/tk9.0
Öncelikle eklenen özelliklerden bazılarına bakalım.
Yeni fonksiyonları incelemeden önce, mevcut fonksiyonların nasıl yapılandırıldığını tk.go dosyasının 1017. satırındaki Destroy fonksiyonu üzerinden kısaca inceleyelim.
1func Destroy(options ...Opt) {
2 evalErr(fmt.Sprintf("destroy %s", collect(options...)))
3}
Bu fonksiyon, işlemi Tcl betik biçiminde evalErr fonksiyonunaileterek uygulanmıştır. Bu, istenen işlevi gerçekleştirmek için yalnızca ilgili Tcl betik biçiminde komutu iletmenin yeterli olduğu anlamına gelir.
Örnek olarak, bir liste kutusuna öğe ekleyen bir metot uygulayalım. Öncelikle Tcl betiği için resmi kılavuzda listbox'ta kullanılabilen komutlardan insert'e bakalım.
insert komutunun açıklama sayfasına bakıldığında, insert belirtilen dizine sıralanmış öğeleri ekler. Bunu uygulamak için
1func (l *ListboxWidget) AddItem(index int, items string) {
2 evalErr(fmt.Sprintf("%s insert %d %s", l.fpath, index, items))
3}
gibi bir kod yazabiliriz.
Artık genel uygulama mantığını anladığımıza göre, Listbox için eklenen ek işlevleri açıklayalım.
Liste: Ekleme/Silme
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
Yukarıdaki programda AddItem, boşlukla ayrılmış farklı öğeleri 0 indeksinden başlayarak sırayla ekler. item1-item3 öğeleri sırasıyla 0, 1, 2 indekslerini alır. Öğe silmenin nasıl çalıştığını örneği çalıştırarak görelim.
Liste: Seçili Öğeleri Alma
Şimdi Listbox'ta seçilen öğeleri alıp kontrol edelim.
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}
Selected metodu, mevcut Listbox'ta seçili tüm öğelerin indekslerini alır. GetOne metodu ise ilgili indeksteki öğenin değerini alır. Bu, konsola yazdırılan sonuçtan anlaşılabilir. Not olarak, benzer bir metot olan Get, başlangıç ve bitiş indekslerini alarak aralıktaki tüm öğelerin değerlerini alır.
Sırada liste kutusunun renklerini değiştirmek var.
Öncelikle aşağıdaki örneğe bakalım.
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}

Kodda yazıldığı gibi yükseklik arttı. Ayrıca renklerin doğru uygulandığını görebilirsiniz. Burada yalnızca belirli bir öğeye farklı renk uygulayan bir seçenek belirtildiği için yalnızca ilk satırın renginin farklı uygulandığı görülmektedir.
Ayrıca büyük bir fark yaratmasa da, Relief metodu ile flat, groove, raise, ridge, solid ve sunken seçenekleri arasından widget kenarlığının stilini değiştirebilirsiniz.
Görüntü Görüntüleyici Örneği
Şimdi öğrendiğimiz widget'ları kullanarak bir görüntü görüntüleyici oluşturalım. Örnek program aşağıdaki gibidir.
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

Bu örnekte uygulamayı basitleştirmek için tüm görüntü widget'ları yüklenirken önceden oluşturulmakta ve yinelenen dosyalar kontrol edilmemektedir. Yukarıda bahsedilen sorunlar iyileştirilebilir; ayrıca yorum satırına alınmış DefaultTheme metodunu kullanarak temayı değiştirmeyi de deneyebilirsiniz. Bu noktaları geliştirdiğiniz yeni bir program yazarak pratik yapmanızı öneririz.
Sonuç
Bu yazıda, Go dilindeki Tcl/Tk kütüphanesinin komut çağrılarının nasıl çalıştığını inceledik ve liste kutusu eklenmiş bir görüntü görüntüleyici oluşturduk.
- Tcl/Tk kütüphanesinde komut çağırma yöntemi
- Liste kutusu kullanım yöntemi
- Liste kutusu widget'ının özelliklerinin değiştirilmesi
- Görüntü görüntüleyici geliştirme
Bu yöntemle diğer kütüphanelerin değiştirilmesine de girişebilir ve eklediğiniz özelliklerle tamamlanmış bir program yazmayı deneyebilirsiniz.