Einen einfachen Baum mit Ext GWT (Tree, TreeItem)

Im Mittelpunkt steht com.extjs.gxt.ui.client.widget.tree.Tree, dem entweder com.extjs.gxt.ui.client.widget.tree.TreeItem-Objekte zugeordnet werden, oder ein komplexeres Modell zugewiesen wird. Mit TreeItem ist schnell ein statischer Baum aufgebaut:

Tree tree = new Tree();

TreeItem rootTreeItem = new TreeItem( „Hauptobjekte“ );
tree.getRootItem().add( rootTreeItem );

rootTreeItem.add( new TreeItem( „Kunden“ ) );
rootTreeItem.add( new TreeItem( „Seminare“ ) );
rootTreeItem.add( new TreeItem( „Referenten“ ) );
rootTreeItem.setExpanded( true );

tree.addListener( Events.OnClick, new Listener<TreeEvent>()
{
  @Override public void handleEvent( TreeEvent be )
  {
    if ( be.getItem().isLeaf() )
      Info.display( „Element: „, be.getItem().getText() );
  }
} );
contentPanel.add( tree );

Wie bei Swing gibt es ein Root-Icon und darunter liegen die Kinder.

Mit einem Listener lässt sich erfragen, ob der Baum ausgefaltet wird oder ob Blätter ausgewählt wurden. Das Beispiel hört nur auf die Auswahl der Kinder und gibt eine Meldung aus.

Quaqua Look And Feel. Das bessere Mac OS X LaF

Quaqua ist ein Swing-LaF, was besonders gut an die Apple Human Interface Guidelines ranreicht.

Quaqua Look And Feel - Metalworks

Das Demo  läuft auch unter Windows.

Java Webstart

Features (von der Webseite):

  • (Nearly) Native User Experience
    Quaqua user interface delegates closely look and behave like their native counterparts. Complex user interface components, such as JFileChooser and JColorChooser, are close enough to make end users feel comfortable with them.
  • Blends into OS X Designs
    Quaqua supports three OS X designs: Tiger, Panther, Jaguar. Quaqua automatically chooses the right design for the current operating system.
  • Alternative Styles
    Quaqua offers alternative styles for many user interface elements. Such as small styles for most of the components, as well as a striped style for tables, lists and trees. Jaguar-design like tabbed panes with stacking tabs are also supported.
  • Additional Components
    Quaqua provides Swing implementations of NSBrowser and NSSheet (named JBrowser and JSheet).
  • Clean Layout
    Laying out Aqua components with Java is quite challenging. Quaqua provides an API for component alignment based on visual criteria (baseline, visual bounds, preferred gaps). JScrollPane’s automatically avoid overlaps with the grow-boxes of windows.
  • Localized in four languages
    Quaqua is localized in the following languages: German, French, Italian and English.

Neben dem LaF für Standardkomponenten gibt es mit JBrowser und JSheet noch zwei “Spezialkomponenten”.

Die Lizenz ist License (LGPL or BSD), und die Doku mit vielen Screenshots. Tolle Arbeit!

Wie viel GWT steckt in Google Square?

In Google Wave steckt eine Zeile, die einen Einsatz
vom GWT nahe legt:

<link rel="stylesheet" href="/squared/styles/squared.css">
<script language="javascript" src="/squared/js/templates.js"></script>
<script language="javascript" src="/squared/api/com.google.quality.views.squared.client.Squared.nocache.js"></script>

Verfolgt man den Link ist das typische obfuscated JavaScript zu sehen. Wer viiiiel Zeit hat, kann das ja mal debuggen und hier berichten. Ein kleiner Test mit FireBug zeigt, dass Squared sich über Service per RPC die Objekte geben lässt

["com.google.quality.views.squared.shared.model.Workspace/4054808271","US presidents",
"com.google.quality.views.squared.shared.model.Suggestions/3280816700",
"java.util.ArrayList/3821976829",
"com.google.quality.views.squared.shared.model.Entry/3459296909" ...

OpenOffice Draw + XForms Export + iText = PDF

Für unseren Seminarbetrieb erstellen wir nach dem Seminarabschluss aus einer XML-Datei die Rechnung und Zertifikate für den Kunden. Die Vorlagen sind in OpenOffice verfasst und können ohne Probleme verändert werden. Ein großer Vorteil ist weiterhin, dass OO einen PDF Exporter mitbringt.

Der Nachteil der Lösung, OpenOffice für die PDF-Erzeugung fernzusteuern ist, dass es sehr schwergewichtig. OO muss installiert und gestartet werden. Das ist auf einem Server, etwa einem einfachen Servlet-Container oder sogar der Google App Engine for Java natürlich nicht denkbar.

Ein anderer Weg ist daher, zwar weiterhin OpenOffice für die Erstellung einer Vorlage zu verwenden, aber beim PDF-Export anders vorzugehen. Die Lösungen können Acrobat Forms (Acroforms) bieten, also eigentlich interaktive Felder, die man später über ein Programm füllen kann. Im ersten Schritt erzeugt man daher mit OpenOffice Draw eine Formular. (Das wird etwa beschrieben unter http://www.devx.com/opensource/Article/38178/.) Dann exportiert man das Dokument in PDF unter Erhaltung der Formulareigenschaften. Dieses PDF lässt sich nun mit iText auslesen, die Formularzellen füllen und wieder als PDF schreiben. Dabei kann die “Formulareigenschaft” wegfallen, dass man später bei dem PDF-Dokument nichts mehr von interaktiven Elementen sieht.

import java.io.FileOutputStream;
import com.lowagie.text.pdf.*;

public class FillInPdfForm
{
  public static void main(String[] args) throws Exception
  {
    PdfReader reader = new PdfReader(„c:/rein.pdf“);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(„c:/raus.pdf“));
    BaseFont font = BaseFont.createFont(„c:\\windows\\fonts\\Calibri.ttf“, BaseFont.CP1252, BaseFont.NOT_EMBEDDED);
    AcroFields form = stamper.getAcroFields();
    for( Object o : reader.getAcroForm().getFields() )
      form.setFieldProperty( ((PRAcroForm.FieldInformation) o).getName(), „textfont“, font, null);
    form.setField(„name“, „Christian Ullenboom“);
    form.setField(„content“, „Brav sein\nLieb sein“);
    stamper.setFormFlattening(true);
    stamper.close();
  }
}

Etwas lästig ist, dass beim Ausfüllen der in OO zugewiesen Font flöten geht. Daher muss man diesen neu zuweisen. Sonst ist es Arial/Helvetica.

Wie Google Wave das GWT antreibt

Über Googe Wave wurde in den Medien schon ausreichend gesprochen. (Auf jeden Fall das Video ansehen! Der korrigierende Editor ist super.)

Wirklich interessant ist ist die Tatsache, dass die Clientseite von Google Wave in GWT implementiert ist. Das ist in meinen Augen ein unschlagbares Argument für die Ausgereiftheit von GWT, wenn Google das in einem Projekt einsetzt, was von unglaublichen vielen Menschen verwendert werden wird und definitiv performant laufen muss. Dabei sollte laut einem Blog-Eintrag http://mtwong.ning.com/profiles/blogs/google-io-conference-google-1 Wave auch überhaupt nicht in GWT entwickelt werden, da der Projektleiter Zweifel hatte, GWT würde gescheit laufen. Aber Wave hat wohl auch die Entwicklung von GWT positiv beeinflusst, etwa bei den Tools. Der Blog-Eintrag erwähnt interessante weitere Projekte und GWT-Features:

Update JDK 1.6.0_14 (6u14)

Änderungen listet http://java.sun.com/javase/6/webnotes/6u14.html auf. Interessant sind meines Erachtens: Compressed Object Pointers und Garbage First (G1) Garbage Collector, weil diese Dinge sind, die für Java 7 vorgesehen waren. Aber nun kann man die schon mal “in the wild” testen, was eine gute Sache ist. Dann noch JAX WS 2.1.6 and JAXB 2.1.10 und ein Update von JavaDB (wobei Derby schon deutlich weiter ist, komisch).

Vorschläge für Projekt Coin (Java 7 Sprachänderungen) geht in die nächste Runde

Eine Abstimmung hat ergeben, welche Spracherweiterungen es in Java 7 geben könnte:

In der näheren Auswahl sind desweiteren noch:

Alle andere Vorschläge, die bisher in der Mailingliste http://mail.openjdk.java.net/pipermail/coin-dev/ vorstellt wurden, sind damit raus. (Eine Begründung für die Ablehnung gibt es nicht zwangsläufig.)

Hast du nix zu tun, schreibst du eine neue IDE …

Das ist zumindest das Ziel von https://gravityide.dev.java.net/. Eine “Easy-to-use Java IDE for Java and other JVM languages”. Problem nur: Was einfach mit einem Editor beginnt wird (zwangsläufig) komplexer und komplexer und komplexer. Man kann darüber streiten, was – nach einer gewissen Einarbeitungszeit – nun intuitiver ist, IntelliJ, NetBeans, Eclipse, JDevelper (oder vi?), aber dass es bei einem Editor mit farblicher Hervorhebung nicht getan ist, ist klar. Und wer mit lauten Sprüchen wie “Gravity will be the only IDE you’ll need.” wirbt, und mit dem Satz “A GUI editor so you can make forms in seconds!” schreit, macht sich keine Freunde, wenn gravityide Vaporware bleibt; gravityide bleibt also vorerst das Duke Nukem Forever der Java-IDEs. Toll!

SmartGWT 1.1

Von SmartGWT, der bekannten OpenSource-GWT-Toollibrary gibt es eine neue Version. Der Showcase zeigt neue Komponenten: http://www.smartclient.com/smartgwt/showcase/. Die Release-Notes führen auf:

Unified DataBoundComponent interface which is implemented by DynamicForm, ListGrid, TreeGrid, Menu, ColumnTree, TileGrid, DetailViewer and Calendar.

– Consistent representation of a Record which can be used by any DataBoundComponent

– Support for GWT-RPC DataSource with DataBoundComponents

– Various component and skin performance improvements

Formula and Summary fields:
built-in wizards for end users to define formula fields that can
compute values using other fields, or summary fields that can combine
other fields with intervening / surrounding text. Available in all
DataBoundComponents, easy to persist as preferences

I18n support with resource bundles from 14 locales

– Significant improvements to Javadocs with tons of documentation on various concepts. (over 30MB on disk)

– API’s to save and restore the display and selection state of various components to/from the server

Automatic databound dragging
behaviors: grids and trees will now inspect data relations declared in
DataSources and automatically “do the right thing” for a databound drag

– Numerous enhancements. See the detailed API Changes document

Over 50 additional enhancements and bug fixes that were logged in tracker

– Several new events and extension points on various components to allow advanced usage

– A helloworld-1.6 starter project for SmartGWT with GWT 1.6

– Several new examples in the SmartGWT Showcase (see „New Samples“ side nav item in the Showcase)

Key API’s added

DateUtil

– Ability to globally set the Date input and rendered formats across the application

DataBoundComponent

– ability to select / deselect records, fetch / filter data and carry out various other operations on any DataBoundComponent

DataSource and DataSourceField

– ability to specify custom field value extractor logic for a given DataSourceField

– API’s to control http caching and result batch size

– DataSourceField.setCanSortClientOnly

Window

– listeners for minimize, maximize, restore and close

Calendar

– support for customizing event edit dialog fields

– event remove listener

Forms

– convenience constructors for various FormItems

– support for customizing form field and title tooltips

– TextItem : support for character casing and entry restrictions (regexp based)

– TextItem : support for hints displayed in-field

– ComboBoxItem : fetchDelay setting

– AutoFitTextAreaItem: autofits to contained text as the user types

Column Tree (Miller Columns)

– added node selected listener

ListGrid

– Grouping modes: built-in and custom grouping modes, such as the ability to group a date column by week, day, month or year

– added header click listener

– added record drop listener

– ability to customize grid groupings by record values and also group title

– support for adding custom formula and summary fields on the fly

– ability to register custom handler to control visibility of a ListGridField

– added ListGridField.setCanFreeze, ListGridField.setCanSort

SectionStack (Accordion)

– added section header click event

TreeGrid

– added folder drop listener

– ability to override getIcon() / getNodeTitle() to allow users to have custom node icon / title logic

JOpenDocument API

Zum Zugriff auf Dokumente und Spreadsheets von OpenOffice ist http://www.jopendocument.org/ eine GPL-Bibliothek, die das mit einer einfachen API unterstützt. Etwa das laden und verändern von Spreadsheets:

File file = new File( „c:/in.ods“ );
SpreadSheet createFromFile = SpreadSheet.createFromFile( file );
Sheet sheet = createFromFile.getSheet( 0 );
sheet.setValueAt( „Filling test“, 1, 1 );
sheet.getCellAt( „A1“ ).setValue( „On site support“ );
sheet.getCellAt( „I10“ ).setValue( new Date() );
sheet.getCellAt( „F24“ ).setValue( 3 );

File outputFile = new File( „c:/out.ods“ );
sheet.getSpreadSheet().saveAs( outputFile );

Weitere Beispiele gibt http://www.jopendocument.org/documentation.html.

Interessant ein ein purer Java-Viewer, und damit die Möglichkeit in PDF zu exportieren, ohne dass man OO dazu fernsteuern muss.

Beim Testen der SpreadSheet-API sind mir leider einige Nachteile aufgefallen:

  • Es gibt keine Named References
  • Die API ist sehr Datei-orientiert. Nur im Speicher Dokumente anzulesen und zu verarbeiten ist nicht möglich. Ich sehe erst einmal keine Methode, wie ein Servlet z.B. sich den InputStream auf ein OO-Dokuments holen und als OutputStream an den Client verschicken kann, ohne dass man vorher das OO-Dokument in eine Datei schreibt.
  • Soll der eingebauter Viewer verwendet werden, können TIFF-Bilder nicht angezeigt werden.
  • GPL könnte für einige Bereiche ein Problem sein. Es werden aber kommerzielle Lizenzen verkauft.

Thema der Woche: Speicherverwaltung, Memory-Leaks

Lies das online-Kapitle http://java.sun.com/developer/Books/javaprogramming/bitterjava/bitterjavach06.pdf über Speicherprobleme.

Lies http://java.sun.com/javase/6/docs/api/java/lang/ref/package-summary.html und insbesondere http://java.sun.com/javase/6/docs/api/java/lang/ref/PhantomReference.html.

Aufgaben: Suche aus http://www.koders.com/default.aspx?s=phantomreference&btn=&la=Java&li=* ein Beispiel für PhantomReference. Dokumentiere, warum hier PhantomReference eingesetzt wird und keine andere Lösung exisitiert.

Was für Ergänzungen bieten die Google Collections mit den folgenden Klassen?

Implementiere ein Beispiel mit den Klassen, wobei eine OutOfMemoryError provoziert werden soll und dann gezeigt werden soll, dass die schwache Referenz gelöst wurde.

Texte umbrechen (Word-Wrap)

Dafür gibt es unterschiedliche Varianten. Die beste ist noch, einen BreakIterator zu nutzen. NetBeans (OpenIDE) bringt hier Funktionen mit. Extrahiert und ohne Abhängigkeiten lässt sich das auf folgendes reduzieren:

import java.text.BreakIterator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.StringTokenizer;
public class StringUtils
{
/**
* Indent and wrap multi-line strings.
*
* @param original the original string to wrap
* @param width the maximum width of lines
* @param breakIterator algorithm for breaking lines
* @param removeNewLines if <code>true</code>, any newlines in the original string are ignored
* @return the whole string with embedded newlines
*/
public static String wrapAndIndentString( String original, String indent, int width )
{
BreakIterator breakIterator = BreakIterator.getWordInstance();
    List<String> lines = wrapStringToArray( original, width, breakIterator, true );
StringBuffer retBuf = new StringBuffer();
    for ( String line : lines )
{
retBuf.append( indent );
retBuf.append( line );
retBuf.append( '\n' );
}
    return retBuf.toString();
}
  /**
* Wrap multi-line strings (and get the individual lines).
*
* @param original the original string to wrap
* @param width the maximum width of lines
* @param breakIterator breaks original to chars, words, sentences, depending on what instance you provide.
* @param removeNewLines if <code>true</code>, any newlines in the original string are ignored
* @return the lines after wrapping
*/
public static List<String> wrapStringToArray( String original, int width,
BreakIterator breakIterator, boolean removeNewLines )
{
if ( original.length() == 0 )
return Arrays.asList( original );
    String[] workingSet; 
    // substitute original newlines with spaces,
// remove newlines from head and tail
if ( removeNewLines )
{
original = original.trim();
original = original.replace( '\n', ' ' );
workingSet = new String[] { original };
}
else
{
StringTokenizer tokens = new StringTokenizer( original, "\n" ); // NOI18N
int len = tokens.countTokens();
workingSet = new String[len];
      for ( int i = 0; i < len; i++ )
workingSet[ i ] = tokens.nextToken();
}
    if ( width < 1 )
width = 1;
    if ( original.length() <= width )
return Arrays.asList( workingSet );
    widthcheck :
{
boolean ok = true;
      for ( int i = 0; i < workingSet.length; i++ )
{
ok = ok && (workingSet[ i ].length() < width);
        if ( !ok )
break widthcheck;
}
      return Arrays.asList( workingSet );
}
    ArrayList<String> lines = new ArrayList<String>(); 
    int lineStart = 0; // the position of start of currently processed line in
// the original string
    for ( int i = 0; i < workingSet.length; i++ )
{
if ( workingSet[ i ].length() < width )
lines.add( workingSet[ i ] );
else
{
breakIterator.setText( workingSet[ i ] );
        int nextStart = breakIterator.next();
int prevStart = 0;
        do
{
while ( ((nextStart - lineStart) < width) && (nextStart != BreakIterator.DONE) )
{
prevStart = nextStart;
nextStart = breakIterator.next();
}
          if ( nextStart == BreakIterator.DONE )
nextStart = prevStart = workingSet[ i ].length();
          if ( prevStart == 0 )
prevStart = nextStart;
          lines.add( workingSet[ i ].substring( lineStart, prevStart ) ); 
          lineStart = prevStart;
prevStart = 0;
} while ( lineStart < workingSet[ i ].length() );
        lineStart = 0;
}
}
    return lines;
}
}

Schade nur, dass GWT keinen BreakIterator für die Clientseite emuliert und selbst das Paket http://code.google.com/p/gwtx/ nicht. Schade …

Ext GWT Beispiel einer editierbaren Tabelle

Die Daten einer Tabelle liegen zeilenweise in einem ListStore. Die ListStore wird mit dem generischen Typ parametrisiert, den das Modell speichern soll. Es können entweder eigene Beans sein, oder wir nutzen BaseModel, ein generischer Speicher von Ext GWT, der wie eine Map (oder Dyna-Bean) arbeitet.

ListStore<BaseModel> store = new ListStore<BaseModel>();

Geben wir der Tabelle zwei Zeilen, also zwei BaseModel-Objekte.

BaseModel c = new BaseModel();
c.set( „url“, „http://www.tutego.com/index.html „);
c.set( „days“, „34“);
c.set( „price“, „233“);
store.add( c );

c = new BaseModel();
c.set( „url“, „http://www.heise.de/newsticker/ „);
c.set( „days“, „3“);
c.set( „price“, „2553“);
store.add( c );

Die BaseModel-Objekte beschreiben also drei Spalten.

Nun sind die Daten im Speicher und die Informationen für die Spalten müssen aufgebaut werden. Jede Spalte wird durch ein ColumnConfig-Objekt beschrieben. Die ColumnConfig-Objekte werden dann in einer Liste gespeichert und zur Initialisierung eines ColumnModels genutzt. Ein ColumnConfig-Objekt bekommt Informationen wie die Breite, aber auch Editoren zugewiesen.

List<ColumnConfig> configs = new ArrayList<ColumnConfig>();

TextField<String> column1TextEditor = new TextField<String>();
column1TextEditor.setAllowBlank( false );
ColumnConfig column1 = new ColumnConfig( „url“, „URL“, 400 );
column1.setEditor( new CellEditor( column1TextEditor ) );
configs.add( column1 ); 

ColumnConfig column2 = new ColumnConfig( „days“, „Tag(e)“, 80 );
column2.setEditor( new CellEditor( new TextField<String>() ) );
configs.add( column2 );

ColumnConfig column3 = new ColumnConfig( „price“, „Gesamtpreis“, 80 );
column3.setEditor( new CellEditor( new TextField<String>() ) );
configs.add( column3 );

ColumnModel cm = new ColumnModel( configs );

Der parametrisierte Konstruktor von ColumnConfig erwartet einen Schlüsselnamen, den Titel für den Spaltenkopf und die Breite.

Jetzt kommt es zur Hautkomponente, dem EditorGrid. Er benötigt drei Informationen: Die Tabellendaten (der ListStore store), die Spaltendaten (ColumnModel cm) und als generische Angabe der Datentyp der Modellelemente.

EditorGrid<BaseModel> grid = new EditorGrid<BaseModel>( store, cm );

Die Komponenten wird nun wie jede andere auf den Container gesetzt.

panel.add( grid );

Zugriff auf die Daten liefert store.getModels(). Möchte man durch alle Zeilen laufen, erledigt dies einfach das erweiterte for:

for ( BaseModel model :store.getModels() )
  …

Ext GWT BeanBinding Beispiel

Ext GWT bietet eine einfache Möglichkeit, JavaBeans an Formulare zu binden. Im ersten Schritt muss die Bean dazu vorbereitet werden.

  1. Die JavaBean implementiert das Marker-Interface com.extjs.gxt.ui.client.data.BeanModelTag.
  2. Will man’s nicht-invasiv, so deklariert man ein Zusatz-Interface, was BeanModelMarker erweitert und hängt dort eine spezielle Zusatz-Annotation an, mit dem Verweise auf die JavaBean.

Gebunden werden soll der Titel eines Seminars an das Textfeld titleTextField auf dem FormPanel:

FormPanel courseDescriptionPanel = new FormPanel();

TextField<String> titleTextField = new TextField<String>();
titleTextField.setFieldLabel( „Kurstitel“ );
titleTextField.setName( „title“ );
titleTextField.setAllowBlank( false ); 
courseDescriptionPanel.add( titleTextField );

Im nächsten Schritt kann man die Bean aufbauen und etwa mit Werten aus einem Service füllen:

Course c = new Course();
c.setTitle( „ejb super kurs“ );

Dann wird um diese Standard-JavaBean eine Ext GWT-Klasse gelegt, quasi ein Wrapper:

BeanModel userModel = BeanModelLookup.get().getFactory(Course.class).createModel( c );

Dieses BeanModel wird als nächstes mit dem FormPanel verbunden, auf dem das Texteingabefeld liegt.

FormBinding formBindingCourse = new FormBinding( courseDescriptionPanel );

Und das muss mit dem BeanModel (dem Wrapper um das Exemplar unserer JavaBean) verbunden werden:

formBindingCourse.bind( userModel );

Nun kennt der formBindingCourse die Bean, aber noch nicht, welches Gui-Element eigentlich mit welcher Property verbunden ist. Das kann man mit autoBind() automatisieren, oder aber mit addFieldBinding() manuell für jedes Element setzen. Bei unserem Textfeld heißt das:

formBindingCourse.addFieldBinding( new FieldBinding( titleTextField, „title“ ) );

Jede Änderung an der Gui wird nun an die Bean weitergeben.

Weiteres Infos unter http://extjs.com/blog/2008/07/14/preview-java-bean-support-with-ext-gwt/ oder in Foren-Beispielen.

Wenn Captchas zu langweilig sind, wie wär’s mit Kätzchen?

Dass Captchas wie

pwntcha/testsuite/passport/passport_000.jpeg

pwntcha/testsuite/yahoo/yahoo_053.jpeg

für uns Menschen nicht leicht zu lesen ist eine Sache, eine andere ist, dass viele schon gehackt wurden und Spammer so die Form-Formulare vollmüllen.

Ein anderes Verfahren zeigt Bilder, aus denen man ein bestimmtes auswählen muss. Diese Idee verfolgt zum Beispiel http://www.thepcspy.com/kittenauth. Hier muss man aus einer Reihe von Tieren auswählen, um das Formular abzuschicken.

Unbenannt

Mein einziges Problem: Bei einigen Fragen fällt die Auswahl nicht leicht. Etwa bei der Frage, was alles Fohlen (engl. foals) sind. Einige Bilder sind so undeutlich, dass ich drei Versuche brauche um ein Formular zu bestätigen. Das ergibt wenig Sinn.

Siehe dazu auch http://research.microsoft.com/en-us/um/redmond/projects/asirra/.