Aus Linux-Magazin 11/2003

Java-Anwendungen für KDE

Linux wird auf dem Desktop salonfähig, von Java ist dies leider nicht zu behaupten, zu gründlich wurde der Ruf schon vor Jahren ruiniert. Mit den Java-Language-Bindings ist aber eine performante und saubere Integration in das K Desktop Environment möglich.

Java-Anwendungen auf dem Client gelten immer noch als wenig performant, auch wenn dieses Argument in Zeiten von Einsteiger-PCs jenseits von 2 GHz und 256 MByte Speicher immer weniger sticht. Aber: Noch so viel Performance ändert gar nichts an dem zweiten Kritikpunkt – dem Look & Feel der Anwendungen. Suns Widgetset AWT war bei Anwendern und Programmierern gleichermaßen unbeliebt. Unter Linux zeigte es sich im altbackenen Motif-Design. Mit Java 1.1 und Swing (seit 1.2 integriert) besserte sich die Situation zumindest für den Programmierer. Er kann jetzt aus einem umfassenden Satz an Widgets wählen. So gibt es kaum einen Wunsch, der unerfüllt bleibt.

Swing bietet darüber hinaus noch ein zur Laufzeit austauschbares Look & Feel. Letztlich bleiben aber Swing-Anwendungen auf Linux-Seite Fremdkörper für die verbreiteten Desktopsysteme. Ob dies tatsächlich die Usability einschränkt, sei dahingestellt. Ein »Datei | Öffnen«-Dialog im Dateimenü oben rechts wird erfahrene Anwender kaum überraschen, kann unbedarfte Nutzer aber durchaus verunsichern.

Neben Usability und Ästhetik spielen für integrierte Desktop-Umgebungen auch andere Gesichtspunkte eine Rolle, so zum Beispiel die einheitliche Ablage von Konfigurationen. Deshalb ist eine engere Einbindung von Java-Anwendungen in den Linux-Desktop sehr zu begrüßen. Ein Artikel im Linux-Magazin[1] zeigte bereits vor längerer Zeit die Integration von Java in Gnome[2] und diese Coffee-Shop-Ausgabe demonstriert die Verzahnung mit dem KDE.

Bindings per RPM oder CVS

Um Java-GUI-Anwendungen im KDE-Look & Feel schreiben zu können, müssen die Java-Bindings installiert sein. Für SuSE gibt es zum Beispiel fertige Binary-RPMS (»kdebindings3-java-3.1.3«). In anderen Fällen hilft der Weg über das KDE-CVS. Die Anlaufstelle für die Java-Bindings ist[2], von dort gibt es auch einen Link zum CVS.

Nur vier Dateien sind erforderlich, um die Integration zu vollbringen: zwei Jar-Dateien für Kompilation und Laufzeit (»qtjava.jar« und »koala.jar«) sowie zwei dynamische Bibliotheken (»libqtjava.so« und »libkdejava.so«). Die Jar-Dateien landen zumindest bei SuSE unter »/opt/kde3/lib/java«, die dynamischen Bibliotheken dagegen erwartungsgemäß unter »/opt/kde3/lib/«.

Konfiguration ganz easy

Die Konfiguration besteht darin, die Jar-Dateien während der Entwicklung und Ausführung in den »CLASSPATH« aufzunehmen. Die dynamischen Bibliotheken sind nur zur Ausführungszeit notwendig, sie müssen im »java.library.path« liegen. Ersteres erfolgt in der IDE der Wahl beziehungsweise im Make- oder Build-File. Zum Ausprobieren hat sich auch das Skript aus Listing 1 bewährt. Es enthält eine Shell-Funktion, die alle Jars aus allen als Argumente übergebenen Verzeichnissen dem »CLASSPATH« hinzufügt. Das Skript lädt man über den Punkt-Befehl (»source«) in die aktuelle Shell, anschließend wird die Funktion aufgerufen:

addjars.inc
addjars /opt/kde3/lib/java

Die dynamischen Bibliotheken findet der Java-Interpreter, wenn man das Suchverzeichnis mittels

java -Djava.library.path=/opt/kde3/lib ...

angibt. Mehr ist zur Installation und Konfiguration nicht nötig. Allerdings: Ohne Dokumentation geht nichts – und hier ist man völlig auf den Quellcode angewiesen. Im Prinzip genügt es zwar, sich die API-Referenz über Standardmethoden (»javadoc«) selbst zu erzeugen, aber eine Metadokumentation bleibt wünschenswert.

Umdenken für Java-Programmierer

Als Beispiel dient ein etwas komplexeres “Hello World!”-Programm. Das Hauptfenster des Programms »KHello« besteht aus einem (editierbaren) Textfeld, das nach dem Start den String »Hello KDE from Java!« anzeigt. Außerdem sind dort ein Menü mit den üblichen File-Einträgen (also Open, Save, Quit) sowie ein Hilfemenü untergebracht. Im Grunde ist »KHello« aber ein ganz simpler Editor (siehe Abbildung 1). Das Speichern einer Datei ist der Einfachheit halber nicht implementiert, stattdessen zeigt das Programm eine Messagebox (siehe Abbildung 2) an.

Das in Listing 2 abgedruckte Programm zeigt für KDE-Programmierer nichts Überraschendes. Java-Entwickler hingegen, die noch nie für KDE programmiert haben, müssen sich an zwei Stellen umgewöhnen. Erstens muss das Programm explizit einen Event-Loop starten, zum anderen arbeitet die Eventverarbeitung im Vergleich zum AWT/Swing deutlich anders. Ansonsten haben natürlich die Widgets eigene Namen, die Funktionalitäten (und die Methodennamen) sind aber sehr ähnlich.

Abbildung 1: Der KHello-Editor bearbeitet »KHello.java«.

Abbildung 1: Der KHello-Editor bearbeitet »KHello.java«.

Einheitliche Services mit »KApplication«

Die Klasse »KApplication« stellt Services bereit, die von allen KDE-Anwendungen genutzt werden (sollten). Die »main«-Methode (ab Zeile 185 in Listing 2) erzeugt ein Objekt dieser Klasse, setzt das Main-Widget und führt die »exec()«-Methode aus. Damit ist der Event-Loop gestartet, der die Benutzereingaben verarbeitet. Bei einem AWT-basierten Programm wäre dieser explizite Start nicht notwendig.

Das Main-Widget ist ein Objekt der »KHello«-Klasse, das von »KMainWindow« abgeleitet ist. Der Konstruktor erzeugt dann ein »QTextEdit«-Control sowie die Menüstruktur. »KMainWindow« hat eine Reihe nützlicher Methoden, so zum Beispiel »menuBar()« (siehe Zeile 87) oder »helpMenu()« (Zeile 121). Die erste Methode stellt das Hauptmenü zur Verfügung, in die die Methode »createMenu()« dann das File- und das Help-Menü einhängt (Zeilen 88 bis 90).

Die »helpMenu()«-Methode dagegen erzeugt ein komplettes KDE-Standard-Hilfemenü, mit Menü-Einträgen für das KDE-Hilfezentrum, einem »About-KDE«-Dialog und so weiter (in Abbildung 1 zu sehen). Nur den Text für den About-Dialog (Abbildung 3) muss man spezifizieren (Zeilen 114 bis 121).

Die in Abbildung 2 gezeigte Messagebox ist ein gutes Beispiel für den Nutzen der KDE-Integration. Das »KApplication«-Objekt erhält als zweiten Parameter in Zeile 186 einen String. Damit ist der Name der Applikation dem System bekannt. Selektiert der Nutzer die Checkbox in Abbildung 2, wird die Messagebox nicht mehr angezeigt. Abgelegt ist die Information unter »~/.kde/share /config/KHellorc«. All dies geschieht transparent und ohne Aufwand für den Programmierer.

Signals und Slots

Klassische Java-GUI-Anwendungen verwenden Event Listeners, um Ereignisse wie die Auswahl eines Menü-Eintrags oder das Drücken eines Buttons zu verarbeiten. Event Listeners sind Klassen, die ein vorgegebenes Interface implementieren. Die Listeners müssen dann bei den Objekten registriert werden, die die Events erzeugen (zum Beispiel dem Button). Das ganze Design ist sehr flexibel und zieht sich durch viele Bereiche der Java-Bibliotheken hindurch. Es ist auch für die Verarbeitung nicht GUI-basierter Ereignisse geeignet.

QT, die Basisbibliothek von KDE, implementiert mit dem Signal/Slot-Mechanismus einen verwandten Ansatz, der sogar noch kompakteren Code erlaubt. Events heißen hier Signale (nicht zu verwechseln mit den klassischen Unix-Signals). Objekte können Signale aussenden. Methoden, die Signale verarbeiten, stellen dafür einen so genannten Slot zur Verfügung.

Die Zeilen 99 bis 105 zeigen, wie dieser Mechanismus unter Java zu verwenden ist. Die »createFileMenu()«-Methode verknüpft die Open- und Save-Einträge mit Methoden der »KHello«-Klasse. Der »Quit«-Menü-Eintrag dagegen kommt in einen Slot des »KApplication«-Objekts. Dessen »quit()«-Methode sorgt für den geordneten Abschluss des Programms. Unter C++ ist die ganze Sache sogar noch etwas komplizierter als in Java, denn hier kommt ein eigener Präprozessor zum Einsatz, weil die Verbindung zwischen Signalen und Slots mit QT-spezifischen (proprietären) Erweiterungen des C++-Standards erfolgt. Der Präprozessor setzt sie in regulären Code um.

Listing 1: »addjars.inc«

12: addjars() {
13:   for d in $@; do
14:     jars=`find $d -name "*.jar" -maxdepth 1`
15:     for f in $jars; do
16:       cp=$cp:$f
17:     done
18:   done
19:   export CLASSPATH=$cp:$CLASSPATH
20:   echo "CLASSPATH=$CLASSPATH"
21: }
Abbildung 1: Der KHello-Editor bearbeitet »KHello.java«.

Abbildung 1: Der KHello-Editor bearbeitet »KHello.java«.

Abbildung 2: Die Speicherung wird nur simuliert.

Abbildung 2: Die Speicherung wird nur simuliert.

Die Magie hinter den Fenstern

Hauptfenster, Menüs, Messageboxen – alles an der Applikation sieht wie KDE aus, denn es ist KDE. Letztlich werden alle Methodenaufrufe von Java an Bibliotheken durchgereicht, die das Java Native Interface (JNI) nutzen, um richtige KDE/QT-Objekte zu erzeugen und dessen Methoden zu nutzen. Viel mehr als Wrapper-Funktionalität müssen also die KDE- und QT-Klassen nicht bieten. In der Tat, die Wrapper sind über Skripte erzeugt und dann behutsam nachbearbeitet worden.

Die nativen Bibliotheken müssen im »java.library.path« liegen, wie oben bereits beschrieben wurde. Innerhalb von Java muss jedes Programm die Bibliotheken explizit laden, zum Beispiel über statischen Code wie in den Zeilen 39 bis 42 des Listings 2.

Eine Anmerkung noch am Rande. KDE-Programmierern wird es aufgefallen sein: Das »KHello«-Programm verwendet konsequent QT-Widgets statt der meist leistungsfähigeren K-Widgets. Das hat einen ganz einfachen Grund, denn leider führen alle K-Widgets zu einer Speicherverletzung (»SIGSEGV«). Hier scheint ein systematisches Problem vorzuliegen, dessen Ursachen der Autor aber nicht näher erforscht hat.

QT und das AWT – ähnlicher als gedacht

Auf den ersten Blick scheint es auf eine etwas fragwürdige Architektur hinzudeuten, alle Widgets über das JNI zu erzeugen, wie das bei den Java-Bindings für KDE der Fall ist. Tatsächlich ist dies aber auch schon beim ehrwürdigen AWT nicht anders. Denn hinter »java .awt.Button« steht zum Beispiel eine native, also ebenfalls per JNI implementierte Klasse, die das Interface »java .awt.peer.ButtonPeer« implementiert. Es ist also ein Aufruf von »java.awt.Toolkit.createButton()«, der letztlich den Button erzeugt.

Die Klasse »java.awt.Toolkit« muss für jede Plattform neu geschrieben werden. Eine klassische Einbindung von QT und KDE in Java müsste gewissermaßen nur die Toolkit-Klasse und die Implementation für die Peer-Klassen implementieren. Dann könnte jeder Java-Programmierer ohne umzulernen Java-GUI-Klassen im QT-Stil schreiben. Jedoch müssten sich die QT/KDE-Entwickler umgewöhnen. Es gibt in der Tat ein Projekt, das diesen Ansatz verfolgt: QT AWT von Richard Moore.

Allerdings scheint dieses Projekt seit einiger Zeit in Schwierigkeiten geraten zu sein, zumindest entwickelt es sich sehr schleppend. Richard schreibt dazu, dass es für den praktischen Einsatz bisher nicht in Frage kommt. Ebenfalls nicht mehr in Entwicklung scheint ein weiteres kleineres Projekt von Richard Moore zu sein, der so genannte KDE Java Applet Server, der einfach Java-Applets in beliebige KDE-Anwendungen einbetten kann. Zu finden sind beide Projekte neben anderen ebenfalls auf der KDE-Entwickler-Site unter[3].

finally{}

Mancher Leser fragt sich jetzt vielleicht, warum er nicht gleich in C++ programmieren soll, wenn das Ergebnis doch sowieso plattformabhängig ist. Das Argument hat einiges für sich, es gibt aber auch Gründe dagegen. So ist C++ zwar mächtiger als Java, aber auch schwieriger. Außerdem ist das GUI immer nur ein Teil jedes Programms – und wenn der Rest oder wichtige andere Teile schon in Java vorliegen, ist eine Anpassung an den KDE-Standard über die Java-Bindings oft sinnvoller als eine vollständige Portierung.

Java-Programmierer ohne nähere KDE-Kenntnisse sollten aber zunächst unbedingt eine Einführung zum Thema KDE-Programmierung lesen, bevor sie sich an die KDE-Java-Integration machen. Ein guter Ausgangspunkt dafür ist die KDE-Entwickler-Site[4], dort gibt es zahlreiche Links zu Tutorials und Büchern. Besonders hervorzuheben ist in diesem Zusammenhang das Buch von David Sweet[5], das online unter der Open Content Lizenz, aber auch in gedruckter Form verfügbar ist. (uwo)

Infos

1] “Kaffee für Zwerge”: Linux-Magazin 12/01, S. 124 (auch online verfügbar)

[2] Homepage von Java-Gnome: [http://java-gnome.sourceforge.net]

[3] Language-Bindings-Homepage auf Developer.kde.org: [http://developer.kde.org/language-bindings/java/index.html]

[4] Programmieren für KDE: [http://developer.kde.org/documentation/index.html]

[5] David Sweet, “KDE 2.0 Development”: SAMS 2001: [http://www.andamooka.org/index.pl?section=kde20devel]

Der Autor

Bernhard Bablok arbeitet bei der Allianz Versicherungs AG im Bereich Data-Warehouse-Systeme. Wenn er nicht Musik hört, mit dem Radl oder zu Fuß unterwegs ist, beschäftigt er sich mit Themen rund um Objektorientierung. Er ist unter [mailto:coffee-shop@bablokb.de] zu erreichen.

Listing 2: »KHello.java«

022: import java.io.*;
023: import org.kde.qt.*;
024: import org.kde.koala.*;
025:
033: public class KHello extends KMainWindow {
034:
039:   static {
040:     qtjava.initialize();
041:     kdejava.initialize();
042:   }
043:
050:   private KApplication iApp;
056:   private QTextEdit iMainWidget;
062:   private String iCurrentFilename = null;
063:
064:   /////////////////////////////////////////////
065:
070:   public KHello(KApplication app) {
071:     super(null,"KHello",1);
072:     iApp = app;
073:     setCaption("Hello KDE from Java!");
074:     createMenu();
075:     iMainWidget = new QTextEdit(this,"");
076:     iMainWidget.setText("Hello KDE from Java!");
077:     setCentralWidget(iMainWidget);
078:   }
079:
080:   //////////////////////////////////////////////
081:
086:   private void createMenu() {
087:     KMenuBar mainMenu = (KMenuBar) menuBar();
088:     mainMenu.insertItem("&File",createFileMenu());
089:     mainMenu.insertSeparator();
090:     mainMenu.insertItem("&Help",createHelpMenu());
091:   }
092:
093:   //////////////////////////////////////////////
094:
099:   private QPopupMenu createFileMenu() {
100:     QPopupMenu fileMenu = new QPopupMenu(this);
101:     fileMenu.insertItem("&Open",this,SLOT("openFile()"));
102:     fileMenu.insertItem("&Save",this,SLOT("saveFile()"));
103:     fileMenu.insertItem("&Quit",iApp,iApp.SLOT("quit()"));
104:     return fileMenu;
105:   }
106:
107:   //////////////////////////////////////////////
108:
113:   private QPopupMenu createHelpMenu() {
114:     StringBuffer aboutText = new StringBuffer();
115:
116:     aboutText.append("Java-bindings Example: nn");
117:     aboutText.append("Written by Bernhard Babloknn");
118:     aboutText.append("This example comes with ABSOLUTELY NO WARRANTY.n");
119:     aboutText.append("This is free software, and you are welcome ton");
120:     aboutText.append("redistribute it under the terms of the GPLn");
121:     QPopupMenu menu = helpMenu(aboutText.toString(),true);
122:     return menu;
123:   }
124:
125:   //////////////////////////////////////////////
126:
131:   public void openFile() {
132:     iCurrentFilename = QFileDialog.getOpenFileName("","*",this);
133:     FileReader fr = null;
134:     BufferedReader br = null;
135:     try {
136:       fr = new FileReader(iCurrentFilename);
137:       br = new BufferedReader(fr);
138:       StringBuffer content = new
139:    StringBuffer((int) (new File(iCurrentFilename)).length());
140:       String line;
141:       while (true) {
142:    line = br.readLine();
143:    if (line == null)
144:      break;
145:    else
146:      content.append(line).append("n");
147:       }
148:       iMainWidget.clear();
149:       iMainWidget.setText(content.toString());
150:       setCaption("File: " + iCurrentFilename);
151:     } catch (Exception e) {
152:     } finally {
153:       try {
154:    if (br != null)
155:      br.close();
156:    if (fr != null)
157:      fr.close();
158:       } catch (Exception e2) {
159:       }
160:     }
161:   }
162:
163:   //////////////////////////////////////////////
164:
169:   public void saveFile() {
170:     String msg;
171:     if (iCurrentFilename != null)
172:       msg = "Simulating save of file " + iCurrentFilename;
173:     else
174:       msg = "Simulating save of file <unknown>";
175:     KMessageBox.information(this,msg,"Information" ,
176:       "saveFileInformationDialog" );
177:   }
178:
179:   //////////////////////////////////////////////
180:
185:   public static void main(String[] args) {
186:     KApplication app = new KApplication(args,"KHello");
187:     KHello mainWindow = new KHello(app);
188:
189:     mainWindow.resize(300,100);
190:     app.setMainWidget(mainWindow);
191:
192:     mainWindow.show();
193:     app.exec();
194:   }
195: }
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben