A declarative Swing GUI builder for Groovy applications that provides a concise and maintainable way to create complex Swing user interfaces
—
Observable data containers and specialized models that support property change notifications and data binding integration.
The foundation interface for all observable data containers.
/**
* Interface for observable values with change notification support
*/
interface ValueModel {
/** Get the current value */
Object getValue()
/** Set the value and notify listeners */
void setValue(Object value)
/** Get the expected type of values */
Class getType()
/** Check if the model allows value changes */
boolean isEditable()
/** Add property change listener */
void addPropertyChangeListener(PropertyChangeListener listener)
/** Remove property change listener */
void removePropertyChangeListener(PropertyChangeListener listener)
}Simple value containers for common use cases.
/**
* Basic value container with property change support
*/
class ValueHolder implements ValueModel {
/** Create with initial value */
ValueHolder(Object value = null)
/** Create with initial value and type constraint */
ValueHolder(Object value, Class type)
/** Create with initial value, type, and editability */
ValueHolder(Object value, Class type, boolean editable)
}
/**
* Property-based value model for bean properties
*/
class PropertyModel implements ValueModel {
/** Create model for bean property */
PropertyModel(Object bean, String property)
/** Get the source bean */
Object getBean()
/** Get the property name */
String getProperty()
}
/**
* Nested property model for deep property access
*/
class NestedValueModel implements ValueModel {
/** Create model for nested property path */
NestedValueModel(Object bean, String propertyPath)
/** Get the source bean */
Object getBean()
/** Get the property path (e.g., "address.street") */
String getPropertyPath()
}
/**
* Closure-based computed value model
*/
class ClosureModel implements ValueModel {
/** Create read-only computed model */
ClosureModel(Closure readClosure)
/** Create read-write computed model */
ClosureModel(Closure readClosure, Closure writeClosure)
/** Create with type constraint */
ClosureModel(Closure readClosure, Class type)
/** Create full-featured computed model */
ClosureModel(Closure readClosure, Closure writeClosure, Class type)
}Specialized models for form data management.
/**
* Model for managing form data with validation and buffering
*/
class FormModel implements ValueModel {
/** Create form model with initial data */
FormModel(Map initialData = [:])
/** Get value for specific field */
Object getFieldValue(String fieldName)
/** Set value for specific field */
void setFieldValue(String fieldName, Object value)
/** Check if form has changes from initial state */
boolean isDirty()
/** Check if specific field has changes */
boolean isFieldDirty(String fieldName)
/** Reset form to initial state */
void reset()
/** Commit current values as new initial state */
void commit()
/** Get all field names */
Set<String> getFieldNames()
/** Add field validator */
void addFieldValidator(String fieldName, Closure validator)
/** Remove field validator */
void removeFieldValidator(String fieldName, Closure validator)
/** Validate all fields */
Map<String, List<String>> validate()
/** Validate specific field */
List<String> validateField(String fieldName)
}Enhanced table models with Groovy-specific features.
/**
* Extended DefaultTableModel with enhanced Groovy support
*/
class DefaultTableModel extends javax.swing.table.DefaultTableModel {
/** Create with column names */
DefaultTableModel(List columnNames)
/** Create with data and column names */
DefaultTableModel(List<List> data, List columnNames)
/** Create with data map and column names */
DefaultTableModel(List<Map> data, List columnNames)
/** Add row using Map */
void addRow(Map rowData)
/** Insert row using Map */
void insertRow(int index, Map rowData)
/** Get row as Map */
Map getRowAsMap(int rowIndex)
/** Set row from Map */
void setRowFromMap(int rowIndex, Map rowData)
/** Find rows matching criteria */
List<Integer> findRows(Closure criteria)
/** Filter rows by criteria */
List<Map> filterRows(Closure criteria)
}
/**
* Extended table column with enhanced features
*/
class DefaultTableColumn extends TableColumn {
/** Create with identifier and display name */
DefaultTableColumn(Object identifier, String displayName)
/** Set column renderer from closure */
void setRendererFromClosure(Closure rendererClosure)
/** Set column editor from closure */
void setEditorFromClosure(Closure editorClosure)
/** Set column validator */
void setValidator(Closure validator)
/** Get column validator */
Closure getValidator()
}Utility classes for working with models.
/**
* Utility for wrapping lists as ListModel
*/
class ListWrapperListModel implements ListModel {
/** Create wrapper for existing list */
ListWrapperListModel(List wrappedList)
/** Get the wrapped list */
List getList()
/** Refresh model after list changes */
void refresh()
}
/**
* Base class for sortable table models
*/
class TableSorter extends AbstractTableModel implements TableModelListener {
/** Create sorter for existing table model */
TableSorter(TableModel model)
/** Sort by column */
void sortByColumn(int column)
/** Sort by column with custom comparator */
void sortByColumn(int column, Comparator comparator)
/** Check if column is sortable */
boolean isSortable(int column)
/** Set column sortable state */
void setSortable(int column, boolean sortable)
}import groovy.model.*
// Simple value holder
def nameModel = new ValueHolder('John Doe')
nameModel.addPropertyChangeListener { evt ->
println "Name changed from '${evt.oldValue}' to '${evt.newValue}'"
}
nameModel.value = 'Jane Smith' // Triggers listener
// Property model
class Person {
String name
int age
}
def person = new Person(name: 'Alice', age: 30)
def nameProperty = new PropertyModel(person, 'name')
def ageProperty = new PropertyModel(person, 'age')
// Changes to person will notify model listeners
person.name = 'Bob'class Address {
String street
String city
}
class Customer {
String name
Address address
}
def customer = new Customer(
name: 'John',
address: new Address(street: '123 Main St', city: 'Anytown')
)
// Access nested properties
def streetModel = new NestedValueModel(customer, 'address.street')
def cityModel = new NestedValueModel(customer, 'address.city')
println streetModel.value // "123 Main St"
streetModel.value = '456 Oak Ave'
println customer.address.street // "456 Oak Ave"def firstNameModel = new ValueHolder('John')
def lastNameModel = new ValueHolder('Doe')
// Read-only computed model
def fullNameModel = new ClosureModel {
"${firstNameModel.value} ${lastNameModel.value}"
}
println fullNameModel.value // "John Doe"
// Read-write computed model
def temperatureF = new ValueHolder(32.0)
def temperatureC = new ClosureModel(
{ (temperatureF.value - 32) * 5 / 9 }, // Read closure
{ temperatureF.value = it * 9 / 5 + 32 } // Write closure
)
temperatureC.value = 0.0
println temperatureF.value // 32.0def formModel = new FormModel([
firstName: 'John',
lastName: 'Doe',
email: 'john@example.com',
age: 30
])
// Add validators
formModel.addFieldValidator('email') { value ->
if (!value?.contains('@')) {
return 'Invalid email format'
}
return null
}
formModel.addFieldValidator('age') { value ->
if (value < 18) {
return 'Must be 18 or older'
}
return null
}
// Make changes
formModel.setFieldValue('email', 'invalid-email')
formModel.setFieldValue('age', 16)
// Validate
def errors = formModel.validate()
errors.each { field, messages ->
println "$field: ${messages.join(', ')}"
}
// Check dirty state
println "Form is dirty: ${formModel.dirty}"
println "Email field is dirty: ${formModel.isFieldDirty('email')}"def columns = ['Name', 'Age', 'Email']
def tableModel = new DefaultTableModel(columns)
// Add rows using Maps
tableModel.addRow([name: 'Alice', age: 25, email: 'alice@example.com'])
tableModel.addRow([name: 'Bob', age: 30, email: 'bob@example.com'])
tableModel.addRow([name: 'Charlie', age: 35, email: 'charlie@example.com'])
// Find rows by criteria
def youngUsers = tableModel.findRows { row ->
tableModel.getRowAsMap(row).age < 30
}
// Filter data
def filteredData = tableModel.filterRows { rowMap ->
rowMap.name.startsWith('A')
}
println "Young users: $youngUsers"
println "Names starting with A: $filteredData"Install with Tessl CLI
npx tessl i tessl/maven-org-codehaus-groovy--groovy-swing