Ob als Ascii-Editor oder für interaktive Anzeigen mit verschiedenen Schriften und Bildern – das Textwidget von Tk ist sehr flexibel einsetzbar. Diese Federlesen-Folge erklärt das Universalwidget anhand eines einfachen Editors und eines Readers für RSS-Dateien.
Wer das Tk-Textwidget in eigenen Programmen nur zum Anzeigen von Ascii-Text nutzt, ahnt vielleicht nicht, was in diesem unscheinbaren Multitalent alles steckt. Es fungiert als Editor inklusive Undo-Funktion, beherrscht viele Formatierungen und Schriften, bettet weitere Widgets ein und Interaktionen lassen sich für jeden einzelnen Buchstaben programmieren. Das Widget unterstützt jede wichtige Aktion zur Textdarstellung und -Bearbeitung, daher dient es seit Jahren als Basis für ausgewachsene Editoren wie den Source-Navigator[1].
Einfacher Editor
Listing 1 enthält einen einfachen Editor, wie er in Abbildung 1 zu sehen ist. Mit ihm öffnet und speichert der Benutzer normale Textdateien. Der Aufruf in Zeile 5 erzeugt das Textwidget und verbindet es mit den beiden Scrollbars, die in Zeile 8 und 9 angelegt werden. Darauf folgt ein Label, das den Namen der geöffneten Datei zeigt. Die Zeilen 12 bis 20 kümmern sich um das Layout-Management, also um das automatische Anordnen der Widgets im Fenster.
|
Listing 1: |
|---|
01 #!/usr/bin/wish
02 package require Tk
03
04 # GUI erzeugen
05 text .text -bg white -undo yes -wrap word
06 -yscrollcommand ".vscroll set"
07 -xscrollcommand ".hscroll set"
08 scrollbar .hscroll -orient horizontal -command ".text xview"
09 scrollbar .vscroll -orient vertical -command ".text yview"
10 label .status -font 8 -textvariable ::currentFile
11
12 grid .text .vscroll -sticky nesw
13 grid .hscroll -sticky ew
14 grid .status -sticky ew
15
16 grid rowconfigure . 0 -weight 1
17 grid rowconfigure . 1 -weight 0
18 grid rowconfigure . 2 -weight 0
19 grid columnconfigure . 0 -weight 1
20 grid columnconfigure . 1 -weight 0
21
22 # Menü erzeugen
23 menu .menu
24 . configure -menu .menu
25
26 menu .menu.file -tearoff 0
27 .menu add cascade -menu .menu.file -label "Datei"
28 .menu.file add command -label "Öffnen" -command openFile
29 .menu.file add command -label "Speichern unter..." -command saveFileAs
30 .menu.file add separator
31 .menu.file add command -label "Beenden" -command {exit 0}
32
33 menu .menu.edit -tearoff 0
34 .menu add cascade -menu .menu.edit -label "Edit "
35 .menu.edit add command -label "Rückgängig machen "
36 -command {.text edit undo}
37 .menu.edit add command -label "Wiederherstellen "
38 -command {.text edit redo}
39 .menu.file add separator
40 .menu.edit add command -label "Euro-Symbol einfügen"
41 -command {.text insert "insert" u20ac}
42 .menu.edit add command -label "Zeile löschen"
43 -command {.text delete "insert linestart" "insert lineend"}
44 .menu.edit add command -label "Zum Zeilenanfang"
45 -command {.text mark set insert "insert linestart"}
46
47 set dateitypen {
48 {"Textdateien" {.txt} }
49 {"Tcl-Skripte" {.tcl} }
50 {"C-Dateien" {.c .h} }
51 {"Alle Dateien" *}
52 }
53
54 proc openFile {} {
55 while {true} {
56 set fileName [tk_getOpenFile -filetypes $::dateitypen]
57 if {[string length $fileName] == 0} {
58 break
59 } elseif {[file exists $fileName] && [file readable $fileName]} {
60 set fd [open $fileName r]
61 .text delete 1.0 end
62 .text insert 1.0 [read $fd]
63 close $fd
64 set ::currentFile $fileName
65 break
66 } else {
67 tk_messageBox -icon error -title "Lesefehler"
68 -message "Datei «$fileName» kann nicht gelesen werden"
69 }
70 }
71 }
72
73 proc saveFileAs {} {
74 while {true} {
75 set fileName [tk_getSaveFile -filetypes $::dateitypen
76 -initialfile [file tail $::currentFile]]
77 if {[string length $fileName] == 0} {
78 break
79 } elseif {[catch {
80 set fd [open $fileName w]
81 puts -nonewline $fd [.text get 1.0 "end -1c"]
82 close $fd
83 } err]} {
84 set antwort [tk_messageBox -type retrycancel
85 -icon error -title "Fehler"
86 -message "Beim Abspeichern trat ein Fehler auf:n$err"]
87 if {[string match "cancel" $antwort]} {
88 break
89 }
90 set $fileName ""
91 } else {
92 set ::currentFile $fileName
93 break
94 }
95 }
96 }
|

Abbildung 1: Der Editor aus Listing 1 zeigt wichtige Funktionen des Textwidgets. Er öffnet und schreibt Dateien und beherrscht unbegrenztes Undo und Redo.
Undo inklusive
Neben den üblichen Optionen wie Farben und Darstellung der Ränder kennt das Textwidget ein Reihe eigener Optionen. Mit »-undo yes« ist der eingebaute Undo-Mechanismus aktivierbar, »-wrap Variante« gibt an, wie Textzeilen darzustellen sind, die nicht in die Breite des Widgets passen. Zur Auswahl stehen dafür der Umbruch nach ganzen Wörtern »word«, buchstabenweise »char« oder kein Umbruch »none«.
Das Textwidget bietet per Default die plattformüblichen Tastenkombinationen zum Bewegen des Cursors, Ausschneiden, Kopieren oder Rückgängigmachen. Der Benutzer greift also in die gewohnten Tasten oder zur Maus, um Texte zu schreiben und zu bearbeiten.
Dem Tcl-Skript bietet das Textwidget einige Kommandos, um Text auszulesen, einzufügen oder zu löschen. Diese Befehle benötigen ein oder zwei Positionsangaben, um die gewünschte Stelle zu benennen. Am einfachsten ist die absolute Angabe von Zeile und Buchstabe, durch einen Punkt getrennt. Die Zeilennummern beginnen bei 1, die Spalten bei 0, die linke obere Ecke ist daher »1.0«. Diese Zählung ist zwar nicht besonders konsequent, deckt sich aber mit den meisten Unix-Editoren.
Der Programmierer kann Positionen auch durch so genannte Marken definieren. Eigene Marken setzt er per ».text mark set Name Position« in das Widget. Beim Ändern des Textes verschieben sich die Marken wie normale Buchstaben, bleiben selbst aber unsichtbar. Zwei Marken sind in jedem Textwidget vorhanden, »insert« und »end«. Die Insert-Marke bezeichnet die aktuelle Position des Cursors, die End-Marke folgt nach dem letzten Zeichen des Textes.
Positionsbestimmung
Zusätzlich zu den absoluten Positionsangaben sind auch relative möglich: »1.0 lineend« bezeichnet das Ende der ersten Zeile, »1.0 lineend wordstart« den Anfang des letzten Wortes in der ersten Zeile. Auch das Verschieben um einige Zeilen oder Zeichen ist möglich. Beispielsweise bezeichnet »end -1 char« die Stelle direkt vor dem letzten Zeichen des Textes und »insert +1 line« geht von der Cursorposition aus eine Zeile nach unten. Char und Line lassen sich auch als »c« und »l« abkürzen.
Wie diese Möglichkeiten zu verwenden sind, zeigt Listing 1. Zeile 41 fügt an der Insert-Marke das Euro-Symbol ein: ».text insert “insert” Buchstabe«. Hier mag verwirren, dass das Einfügekommando den gleichen Namen trägt wie die Einfügemarke, beide heißen »insert«. Das Kommando in Zeile 43 löscht die aktuelle Zeile, ausgehend von der Einfügemarke benutzt es den Zeilenanfang und das Zeilenende als Löschbereich. Zeile 45 schiebt die Insert-Marke zum Zeilenanfang und bewegt damit den Cursor.
Weitere Editorkommandos sind leicht selbst zu definieren, die vordefinierten sind ebenso entstanden. Je nach benutzter Linux-Distribution sind diese Definitionen in der Datei »/usr /lib/tk8.4/text.tcl« oder in »/usr/local /lib/tk8.4/text.tcl« als Inspiration für eigene Befehle zu finden.
Auch die Funktionen zum Öffnen (Zeile 54) und Sichern (Zeile 73) der Datei sind interessant. In einer Endlosschleife versuchen sie ihre Aufgabe so lange, bis der Benutzer eine les- oder schreibbare Datei gewählt hat oder die Aktion absichtlich abbricht. Beim Speichern ist Vorsicht geboten: Da die Ende-Marke hinter dem letzten Zeichen steht, muss das »get«-Kommando in Zeile 81 den Bereich »1.0 “end -1c”« verwenden. Andernfalls würde das Textwidget ein Newline anhängen, das im Text ursprünglich nicht vorhanden ist. Auch »puts« würde ohne »-nonewline« einen Zeilenumbruch bewirken.
Bunter Text
Unterschiedliche Farben und Schriften gestalten den Text übersichtlicher und ansprechender. Das Tk-Textwidget beherrscht auch dies. Der Programmierer definiert beliebige Textbereiche, denen er eine bestimmte Darstellung zuordnet. Die Bereiche sind durch Tags markiert, wobei jedes Tag für eine eigene Darstellung sorgt. Den meisten Entwicklern dürfte das Konzept von HTML und der Cascading Style Sheets bekannt sein, die Tk-Implementation ist jedoch wesentlich älter. Die Bereiche dürfen sich in Tk zudem beliebig überlappen.
Tags kann der Programmierer schon beim Einfügen des Textes angeben, er hängt die Namen der Tags einfach an das Insert-Kommando an: ».text insert Position Text Tag-Liste«. Er kann sie auch nachträglich verändern, ergänzen oder entfernen. Wie das Textwidget den getaggten Text darstellt, ist frei konfigurierbar, beispielsweise Farbe und Schriftart: ».text tag configure Name -foreground green -font {Palatino 20}«.
Ausgeblendet
Eine Besonderheit ist die Elide-Option, sie ändert die Sichtbarkeit eines Textbereichs. Der Text ist bei gesetzter Elide zwar vorhanden, das Widget zeigt ihn nur nicht an. Diese Option verwenden Editoren für das Code-Folding, also zum Ausblenden bestimmter Abschnitte, etwa der Methodenrümpfe.
Die Federlesen-Folge im Linux-Magazin 1/05[2] stellt eine eigens entwickelte Bibliothek für das Lesen von RSS-Dateien vor[3]. Zusammen mit dem Textwidget eignet sie sich, um RSS-Feeds in einem GUI darzustellen. Der RSS-Reader erhält die Überschriften und Texte aus dem RSS-Stream. Für eine möglichst kompakte Darstellung soll er nach dem Start nur die Überschriften der einzelnen Nachrichten anzeigen, zur besseren Unterscheidung abwechselnd mit weißem und grauem Hintergrund.
Erst wenn der Benutzer die Maus über eine Überschrift bewegt, blendet das Textwidget die zugehörige Beschreibung ein. Durch Doppelklick auf den Text übergibt das Programm den Link an einen HTML-Browser, etwa Galeon oder Firefox. Das Listing 2 enthält den kompletten Reader, Abbildung 2 zeigt ihn mit den Nachrichten der Tagesschau. Die »init«-Prozedur (Zeile 9) definiert die Oberfläche mit dem Textwidget und einem Scrollbar. Die Version im Download-Bereich des Linux-Magazins[13] ergänzt noch einen Ausschaltknopf.
|
Listing 2: |
|---|
01 #!/usr/bin/wish
02 package require Tk
03 package require opt
04 source czrss.tcl
05
06 namespace eval ::rssgui {
07 variable counter 0
08
09 proc init {} {
10 # weißer Hintergrund
11 option add *background white
12 . configure -background white
13
14 # Hauptfenster
15 text .text -relief flat -wrap word
16 -selectbackground blue -highlightthickness 0
17 -yscrollcommand {.sby set}
18 grid .text -row 1 -column 1 -sticky nesw
19 scrollbar .sby -orient vert -highlightthickness 0
20 -command {.text yview}
21 grid .sby -row 1 -column 2 -sticky ns
22 grid rowconfigure . 0 -minsize 10
23 grid rowconfigure . 1 -weight 1
24 grid rowconfigure . 2 -minsize 10
25 grid columnconfigure . 0 -minsize 10
26 grid columnconfigure . 1 -weight 1
27 grid columnconfigure . 3 -minsize 10
28
29 # Text konfigurieren
30 .text tag configure title -foreground steelblue
31 -font {Helvetica 12} -spacing1 5 -spacing3 5
32 .text tag configure description -foreground black -elide true
33 -spacing1 10 -lmargin1 20 -lmargin2 10 -spacing3 5
34 .text tag configure gerade -background whitesmoke
35 .text tag configure ungerade -background white
36 .text tag configure sichtbar -elide false
37 .text conf -state disabled
38 }
39
40 proc setFeed {url} {
41 set doc [::czrss::doc create %AUTO% $url]
42 set channel [$doc channel]
43 wm title . [$channel title]
44 .text conf -state normal
45 .text delete 1.0 end
46 foreach item [$doc items] {
47 insertMessage [$item title] [$item description] [$item link]
48 }
49 .text conf -state disabled
50 }
51
52 proc insertMessage {title description url} {
53 variable counter
54
55 if {[expr [incr counter] % 2]} {
56 set zeile gerade
57 } else {
58 set zeile ungerade
59 }
60 .text insert end "$titlen" [list $zeile title "t$counter"]
61 .text insert end "$descriptionn" [list $zeile description "d$counter"]
62 .text tag bind "t$counter" <Enter> [list ::rssgui::enterTag "$counter"]
63 .text tag bind "t$counter" <Double-1> [list ::rssgui::startBrowser $url]
64 .text tag bind "d$counter" <Double-1> [list ::rssgui::startBrowser $url]
65 }
66
67 proc enterTag {id} {
68 set start [lindex [.text tag nextrange "t$id" 1.0 end] 0]
69 set end [lindex [.text tag nextrange "d$id" 1.0 end] 1]
70 .text tag add sichtbar $start $end
71 .text tag bind sichtbar <Leave> [list ::rssgui::leaveTag $id]
72 }
73
74 proc leaveTag {id} {
75 .text tag remove sichtbar 1.0 end
76 }
77
78 proc startBrowser {url} {
79 foreach browser {galeon firefox mozilla konqueror netscape} {
80 set binary [lindex [auto_execok $browser] 0]
81 if {[string length $binary]} {
82 puts "$binary $url"
83 catch {exec $binary $url &}
84 break
85 }
86 }
87 }
88 }
89
90 tcl::OptProc main {
91 {url "RSS-Feed, z.B. http://www.tagesschau.de/newsticker.rdf" }
92 } {
93 rssgui::init
94 rssgui::setFeed $url
95 }
96 if {[catch {eval main $argv} res]} {
97 puts stderr $res
98 }
|

Abbildung 2: Der einfache RSS-Reader zeigt den Newsfeed der ARD-Tagesschau. Setzt der Anwender den Mauszeiger auf einen der Titel, präsentiert ihm das Programm den Text der Nachricht.
Weiß als Standardfarbe
Das Programm färbt den Standardhintergrund der Anwendung weiß. Für alle neu erzeugten Widgets lässt sich der Default ändern, das »option«-Kommando in Zeile 11 setzt die Hintergrundfarbe. Da das Hauptfenster ».« zu diesem Zeitpunkt schon existiert, ändert die »configure«-Methode in Zeile 12 auch dessen Hintergrundfarbe. Die Zeilen 30 bis 36 konfigurieren die Darstellung der unterschiedlichen Textbereiche.

Abbildungen 3a und 3b: Dank Trampoline-Erweiterung kann ein Canvas-Widget (oben) seinen Inhalt direkt als PDF-Dokument exportieren (rechts). Ohne Trampoline beherrscht das Canvas immerhin den Export nach Postscript.
Titel und Text per Tag unterscheiden
Die Titel der RSS-Meldungen erhalten das Tag »titel«. Zeile 30 legt fest, dass Text mit diesem Tag in der Farbe Steelblue erscheint und dank der Optionen »spacing1« und »spacing3« gebührenden Abstand zum darüber und darunter liegenden Text einhält. Die Schriftart ist eine 12 Punkt große Helvetica. Die Beschreibungstexte zu den Nachrichten sind mit dem »description«-Tag gekennzeichnet, »spacing2« bestimmt ihren Durchschuss (Zeilenabstand). E
Da die ausführliche Beschreibung der Nachricht per Default unsichtbar sein soll, erhält dieses Tag die Option »-elide true«. Es sind noch drei weitere Tags nötig: Grade und ungerade Zeilen erhalten unterschiedliche Hintergrundfarben. Die vom Benutzer ausgewählte Nachricht erhält das Tag »sichtbar«, bei dem »-elide false« gesetzt ist. Sind einem Textabschnitt mehrere Tags zugeordnet, deren Einstellungen sich widersprechen, so bestimmt per Default das später zugewiesene Tag über das Aussehen des Textes. Diese Gewichtung lässt sich aber auch nachträglich ändern.
|
Das |
|---|
|
Die nächste Tcl-Version kündigt sich deutlich an, unter[5] steht die Alphaversion 8.5a2 zum Download bereit. Neben viel Feinarbeit für 64-Bit-Prozessoren sind auch neue Entwicklungen in Tcl und Tk zu finden. Eine spätere Folge des Federlesen wird genauer darauf eingehen und dabei auch die Tile-Erweiterung[6] vorstellen, die Tk ein moderneres Aussehen schenkt. Für KDE wäre es einfacher, direkt Qt-Widgets zu verwenden. Dem steht aber die Lizenz entgegen: Qt untersteht der GPL, die Tcl-Lizenz ist aber BSD-basiert. Snit und TrampolineDie letzte Federlesen-Folge[2] stellte unter anderem die objektorientierte Erweiterung Snit vor. In der neuesten Version 0.97 kommt noch bessere Unterstützung für Delegation und Subkommandos hinzu[7]. Noch kein Teil der offiziellen Tcl-Quellen ist Trampoline[8]. Diese Erweiterung exportiert PDF aus dem normalen Canvas-Widget von Tk (Abbildungen 3a und 3b). Ein ähnlicher Export wäre auch für das Textwidget sehr interessant, leider hat noch niemand Entsprechendes veröffentlicht. Tabellen-Widget in reinem Tcl/TkFür die Darstellung von Listen ist Tablelist[9] von Csaba Nemethi eine gute Lösung, in der neuen Version 3.7 können die Listen andere Widgets enthalten sowie Zeilen- und Spaltentitel vom Scrolling verschont bleiben. Wer sich einen Überblick über das Zeitverhalten von Systemen verschaffen möchte, sollte einen Blick auf Kiwi[10] werfen (Abbildung 4). Das Programm zeigt Statusdiagramme an, wie sie im Echtzeitbereich üblich sind. Chatten im Web, im IRC und mit TclDas Tcler\’s Wiki – die bekannte Schatzgrube für viele Problemlösungen – hat einen neuen Chatbereich[11] erhalten. Tcl-Neulinge und -Profis tauschen hier Fragen und Antworten aus. Die Verbindungsdaten stehen auf der Wiki-Seite. Es gibt eine Webschnittstelle und einen IRC-Channel. Richtig stilecht (Abbildung 5) läuft die Unterhaltung über Bruce Hartwegs Tkchat-Client[12]. |
Die Meldung zur Schlagzeile
Die »insertMessage«-Prozedur ab Zeile 52 füllt den Text einer Nachricht in das Widget. Sie verwendet bei jedem Textblock mehrere Tags: Für die Titel (Zeile 60) abwechselnd »gerade« und »ungerade« (in der Variablen »$zeile«), zusätzlich »title« und ein weiteres Tag bestehend aus dem Buchstaben »t« und einer fortlaufenden Nummer aus der Variablen »$counter«. Der Nachrichtentext in Zeile 61 erhält ähnliche Tags: »description« und »d$counter«.
Die Zeilen 62 bis 65 nutzen die Interaktionsmöglichkeiten des Textwidgets. Per »tag bind Name Ereignis Kommando«-Subkommando definieren sie Callbacks auf einzelne Textbereiche, die auf bestimmte Maus- oder Tastaturereignisse reagieren. Wie beim normalen »bind«-Kommando sind die Ereignisse durch Schlüsselwörter wie »<Enter>« oder »<Leave>« bezeichnet. Achtung: Die spitzen Klammern sind Pflicht. Die Manpage von »bind« gibt nähere Auskünfte.

Abbildung 4: Kiwi stellt Ausführungs-Traces als übersichtliche Diagramme dar. Das Programm ist für Echtzeitsoftware gedacht, eignet sich aber für beliebige nebenläufige Applikationen, etwa Threads und Prozesse.
Interaktive Textbereiche
Fährt der Benutzer mit der Maus über eine Überschrift, sorgt Zeile 62 dafür, dass Tcl die »enterTag«-Prozedur (Zeile 67) aufruft. Sie setzt das »sichtbar«-Tag auf Titel und Beschreibung. Deren Anfangs- und Endpunkt ermittelt die Prozedur mit Hilfe der Tags, die die fortlaufende Nummer im Namen tragen. An das Sichtbar-Tag bindet die »enterTag«-Prozedur einen Callback-Aufruf, der aus dem Namen einer Prozedur und einem Parameter besteht.
Wie zu sehen ist, lassen sich Tags jederzeit dynamisch definieren, löschen und ändern. Dies geschieht ein weiteres Mal in der »leaveTag«-Prozedur (Zeile 74). Sie entfernt das Sichtbar-Tag aus dem ganzen Text und sorgt so dafür, dass der Reader nur noch die Überschriften anzeigt. Fährt der Benutzer mit der Maus über die Überschriften, klappt der Reader nacheinander die Beschreibungen auf. Das ergibt eine angenehm und einfach zu bedienende Oberfläche bei kompakter Darstellung. Mit einer Scrollmaus erhält man auf diese Weise sehr schnell ein Überblick über angefallene Nachrichten.
Mit den Möglichkeiten des Textwidgets ist es leicht, selbst komplexe Bedienkonzepte schnell zu implementieren. Egal ob Editor, HTML-Browser oder Chatclient, die Anzahl der Textwidget-basierten Anwendungen ist groß. Wer Geschmack an einem eigenen Editor bekommen hat, dem sei Ctext[4] empfohlen. Es implementiert das wichtige Syntax-Highlighting und spart so einiges an Programmierarbeit. (fjl)

Abbildung 5: Wer sich mit Tcl-Experten unterhalten will, findet sie meist im Tcler’s Chat. Als Client-Software bietet sich Tkchat an, das Programm ist selbst in Tcl programmiert.
|
Infos |
|---|
|
[1] Source-Navigator: [http://sourcenav.sourceforge.net] [2] Carsten Zerbst, “Appetithäppchen – RSS-Newsfeeds lesen mit Tcl, Snit und TDOM”: Linux-Magazin 01/05, S. 102 [3] RSS-Reader: [http://wiki.tcl.tk/12801] [4] Tklib: [http://tcllib.sourceforge.net] [5] Tcl 8.5 (derzeit im Alphastadium): [http://www.tcl.tk/software/tcltk/8.5.html] [6] Tile: [http://tktable.sourceforge.net/tile/] [7] Snit: [http://www.wjduquette.com/snit/] [8] Trampoline: [http://trampoline.sourceforge.net] [9] Tablelist: [http://www.nemethi.de] [10] Kiwi: [http://rtportal.upv.es/apps/kiwi/] [11] Wiki-Chat: [http://wiki.tcl.tk/1178] [12] Tkchat: [http://wiki.tcl.tk/tkchat] [13] Files zum Artikel: [ftp://ftp.linux-magazin.de/pub/magazin/2005/03/Federlesen/] |





