SwingBuilder for creating Swing GUIs in Groovy - provides DSL for declarative UI construction
—
Groovy Swing provides comprehensive menu system support with factories for creating menu bars, menus, menu items, and popup menus with full action integration.
Core menu components for building application menu systems.
// Menu containers
def menuBar(Map attributes = [:], Closure closure = null)
def menu(Map attributes = [:], Closure closure = null)
def popupMenu(Map attributes = [:], Closure closure = null)
// Menu items
def menuItem(Map attributes = [:], Closure closure = null)
def checkBoxMenuItem(Map attributes = [:], Closure closure = null)
def radioButtonMenuItem(Map attributes = [:], Closure closure = null)
// Separators
def separator(Map attributes = [:])Creating standard application menus with menu bars and menu items.
swing.frame(title: 'Menu Example') {
// Create menu bar
menuBar {
// File menu
menu(text: 'File', mnemonic: 'F') {
menuItem(text: 'New', mnemonic: 'N', accelerator: shortcut('N')) {
actionPerformed { println 'New file' }
}
menuItem(text: 'Open', mnemonic: 'O', accelerator: shortcut('O')) {
actionPerformed { println 'Open file' }
}
menuItem(text: 'Save', mnemonic: 'S', accelerator: shortcut('S')) {
actionPerformed { println 'Save file' }
}
separator()
menuItem(text: 'Exit', mnemonic: 'x') {
actionPerformed { System.exit(0) }
}
}
// Edit menu
menu(text: 'Edit', mnemonic: 'E') {
menuItem(text: 'Cut', accelerator: shortcut('X')) {
actionPerformed { println 'Cut' }
}
menuItem(text: 'Copy', accelerator: shortcut('C')) {
actionPerformed { println 'Copy' }
}
menuItem(text: 'Paste', accelerator: shortcut('V')) {
actionPerformed { println 'Paste' }
}
}
// View menu with checkboxes
menu(text: 'View', mnemonic: 'V') {
checkBoxMenuItem(text: 'Show Toolbar', selected: true) {
actionPerformed { e ->
println "Toolbar ${e.source.selected ? 'shown' : 'hidden'}"
}
}
checkBoxMenuItem(text: 'Show Status Bar', selected: false) {
actionPerformed { e ->
println "Status bar ${e.source.selected ? 'shown' : 'hidden'}"
}
}
}
}
// Main content
panel {
label(text: 'Application content')
}
}Using Action objects for consistent menu and toolbar integration.
// Action factory for reusable actions
def action(Map attributes = [:])
// Action attributes:
// name: String - action name/text
// closure: Closure - action code
// mnemonic: String/int - keyboard mnemonic
// accelerator: KeyStroke - keyboard shortcut
// smallIcon: Icon - small icon for toolbar
// largeIcon: Icon - large icon
// shortDescription: String - tooltip text
// longDescription: String - detailed description
// enabled: boolean - whether action is enableddef swing = new SwingBuilder()
// Define reusable actions
def newAction = swing.action(
name: 'New',
closure: { println 'Creating new document' },
mnemonic: 'N',
accelerator: swing.shortcut('N'),
smallIcon: swing.imageIcon('/icons/new.png'),
shortDescription: 'Create a new document'
)
def saveAction = swing.action(
name: 'Save',
closure: { println 'Saving document' },
mnemonic: 'S',
accelerator: swing.shortcut('S'),
smallIcon: swing.imageIcon('/icons/save.png'),
shortDescription: 'Save the current document'
)
def exitAction = swing.action(
name: 'Exit',
closure: { System.exit(0) },
mnemonic: 'x',
shortDescription: 'Exit the application'
)
// Use actions in both menu and toolbar
swing.frame(title: 'Action Integration') {
borderLayout()
// Menu bar using actions
menuBar {
menu(text: 'File') {
menuItem(action: newAction)
separator()
menuItem(action: saveAction)
separator()
menuItem(action: exitAction)
}
}
// Toolbar using same actions
toolBar(constraints: BorderLayout.NORTH) {
button(action: newAction)
button(action: saveAction)
separator()
button(action: exitAction)
}
// Content area
panel(constraints: BorderLayout.CENTER) {
label(text: 'Document content')
}
}Context menus that appear on right-click or other triggers.
def popupMenu(Map attributes = [:], Closure closure = null)
// PopupMenu is typically shown via:
// component.componentPopupMenu = popupMenu
// or programmatically with show(component, x, y)// Create popup menu
def contextMenu = swing.popupMenu {
menuItem(text: 'Cut') {
actionPerformed { println 'Cut from context menu' }
}
menuItem(text: 'Copy') {
actionPerformed { println 'Copy from context menu' }
}
menuItem(text: 'Paste') {
actionPerformed { println 'Paste from context menu' }
}
separator()
menuItem(text: 'Properties') {
actionPerformed { println 'Show properties' }
}
}
// Attach popup to component
swing.textArea(
rows: 10,
columns: 40,
componentPopupMenu: contextMenu
)
// Manual popup triggering
swing.panel {
mouseClicked { e ->
if (e.button == MouseEvent.BUTTON3) { // Right click
contextMenu.show(e.component, e.x, e.y)
}
}
}Different types of menu items for various interaction patterns.
def viewSettings = [
showToolbar: true,
showStatusBar: false,
showSidebar: true
]
swing.menu(text: 'View') {
checkBoxMenuItem(
text: 'Show Toolbar',
selected: bind(source: viewSettings, sourceProperty: 'showToolbar')
) {
actionPerformed { e ->
viewSettings.showToolbar = e.source.selected
updateToolbarVisibility(viewSettings.showToolbar)
}
}
checkBoxMenuItem(
text: 'Show Status Bar',
selected: bind(source: viewSettings, sourceProperty: 'showStatusBar')
) {
actionPerformed { e ->
viewSettings.showStatusBar = e.source.selected
updateStatusBarVisibility(viewSettings.showStatusBar)
}
}
}def documentMode = new ValueHolder('edit')
swing.menu(text: 'Mode') {
def modeGroup = buttonGroup()
radioButtonMenuItem(
text: 'Edit Mode',
buttonGroup: modeGroup,
selected: bind(source: documentMode, sourceProperty: 'value',
converter: { it == 'edit' })
) {
actionPerformed {
documentMode.value = 'edit'
switchToEditMode()
}
}
radioButtonMenuItem(
text: 'Preview Mode',
buttonGroup: modeGroup,
selected: bind(source: documentMode, sourceProperty: 'value',
converter: { it == 'preview' })
) {
actionPerformed {
documentMode.value = 'preview'
switchToPreviewMode()
}
}
radioButtonMenuItem(
text: 'Presentation Mode',
buttonGroup: modeGroup,
selected: bind(source: documentMode, sourceProperty: 'value',
converter: { it == 'presentation' })
) {
actionPerformed {
documentMode.value = 'presentation'
switchToPresentationMode()
}
}
}Creating menus that change based on application state.
class RecentFilesManager {
List<String> recentFiles = []
int maxFiles = 5
void addFile(String filename) {
recentFiles.remove(filename) // Remove if already exists
recentFiles.add(0, filename) // Add to beginning
if (recentFiles.size() > maxFiles) {
recentFiles = recentFiles[0..<maxFiles]
}
}
}
def recentFilesManager = new RecentFilesManager()
def updateRecentFilesMenu = { menu ->
// Clear existing recent file items
menu.removeAll()
if (recentFilesManager.recentFiles.empty) {
def item = swing.menuItem(text: 'No recent files', enabled: false)
menu.add(item)
} else {
recentFilesManager.recentFiles.eachWithIndex { filename, index ->
def item = swing.menuItem(
text: "&${index + 1} ${new File(filename).name}",
toolTipText: filename
) {
actionPerformed {
openFile(filename)
}
}
menu.add(item)
}
}
}
swing.frame(title: 'Dynamic Menu Example') {
menuBar {
menu(text: 'File') {
menuItem(text: 'New') { actionPerformed { /* new file logic */ } }
menuItem(text: 'Open') {
actionPerformed {
def filename = showOpenDialog()
if (filename) {
openFile(filename)
recentFilesManager.addFile(filename)
updateRecentFilesMenu(recentFilesMenu)
}
}
}
separator()
// Recent files submenu
def recentFilesMenu = menu(text: 'Recent Files')
updateRecentFilesMenu(recentFilesMenu)
}
}
}Customizing menu appearance with icons, fonts, and colors.
swing.menuBar {
menu(text: 'File', font: new Font('Arial', Font.BOLD, 12)) {
menuItem(
text: 'New Document',
icon: swing.imageIcon('/icons/document-new.png'),
accelerator: swing.shortcut('N')
) {
actionPerformed { println 'New document' }
}
menuItem(
text: 'Open Document',
icon: swing.imageIcon('/icons/document-open.png'),
accelerator: swing.shortcut('O')
) {
actionPerformed { println 'Open document' }
}
separator()
menuItem(
text: 'Recent Documents',
icon: swing.imageIcon('/icons/document-recent.png')
) {
// Submenu with recent files
}
}
menu(text: 'Tools') {
menuItem(
text: 'Preferences',
icon: swing.imageIcon('/icons/preferences.png'),
foreground: Color.BLUE
) {
actionPerformed { showPreferences() }
}
}
}Setting up proper keyboard navigation and mnemonics.
swing.menuBar {
menu(text: 'File', mnemonic: 'F') { // Alt+F to open
menuItem(
text: 'New',
mnemonic: 'N', // Alt+F, N to activate
accelerator: swing.shortcut('N') // Ctrl+N direct shortcut
)
menuItem(
text: 'Open Recent',
mnemonic: 'R'
) {
// Submenu
menuItem(text: 'Document 1.txt', mnemonic: '1')
menuItem(text: 'Document 2.txt', mnemonic: '2')
}
}
menu(text: 'Edit', mnemonic: 'E') { // Alt+E to open
menuItem(
text: 'Undo',
mnemonic: 'U',
accelerator: swing.shortcut('Z')
)
menuItem(
text: 'Redo',
mnemonic: 'R',
accelerator: swing.shortcut('Y')
)
}
}Managing menu item states based on application context.
class MenuStateManager {
boolean hasSelection = false
boolean hasContent = false
boolean canUndo = false
boolean canRedo = false
def cutAction, copyAction, pasteAction, undoAction, redoAction
void updateMenuStates() {
cutAction?.enabled = hasSelection
copyAction?.enabled = hasSelection
undoAction?.enabled = canUndo
redoAction?.enabled = canRedo
}
}
def stateManager = new MenuStateManager()
// Create actions with initial states
stateManager.cutAction = swing.action(
name: 'Cut',
enabled: false,
closure: { performCut() }
)
stateManager.copyAction = swing.action(
name: 'Copy',
enabled: false,
closure: { performCopy() }
)
// Update states based on events
textComponent.selectionListener = { e ->
stateManager.hasSelection = (e.source.selectedText != null)
stateManager.updateMenuStates()
}
// Use in menu
swing.menuBar {
menu(text: 'Edit') {
menuItem(action: stateManager.cutAction)
menuItem(action: stateManager.copyAction)
// ... other items
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-apache-groovy--groovy-swing