@@ -24,6 +24,8 @@ import org.codehaus.groovy.tools.shell.util.MessageSource
2424
2525import javax.swing.JDialog
2626import javax.swing.JFileChooser
27+ import javax.swing.JOptionPane
28+ import javax.swing.filechooser.FileNameExtensionFilter
2729import java.awt.Dimension
2830
2931class ConsolePreferences {
@@ -38,6 +40,18 @@ class ConsolePreferences {
3840 @Bindable
3941 int loopModeDelay
4042
43+ @Bindable
44+ int iconSize
45+
46+ @Bindable
47+ boolean scaleIconsWithFont
48+
49+ @Bindable
50+ String customLightThemePath
51+
52+ @Bindable
53+ String customDarkThemePath
54+
4155 private final console
4256 private final MessageSource T
4357
@@ -50,6 +64,10 @@ class ConsolePreferences {
5064
5165 maxOutputChars = console. loadMaxOutputChars()
5266 loopModeDelay = console. prefs. getInt(' loopModeDelay' , DEFAULT_LOOP_MODE_DELAY_MILLIS )
67+ iconSize = console. prefs. getInt(' iconSize' , Icons . SIZE_NORMAL )
68+ scaleIconsWithFont = console. prefs. getBoolean(' scaleIconsWithFont' , false )
69+ customLightThemePath = ThemeManager . customLightPath ?: ' '
70+ customDarkThemePath = ThemeManager . customDarkPath ?: ' '
5371 console. maxOutputChars = maxOutputChars
5472 }
5573
@@ -114,6 +132,79 @@ class ConsolePreferences {
114132
115133 vstrut(12 )
116134
135+ vbox(border : compoundBorder([
136+ titledBorder(T[' prefs.appearance.settings.title' ]),
137+ emptyBorder([6 , 8 , 8 , 8 ])])) {
138+ hbox {
139+ label " ${ T['prefs.icon.size']} :"
140+ hstrut(8 )
141+ buttonGroup(id : ' iconSizeGroup' )
142+ radioButton(text : T[' prefs.icon.size.small' ], id : ' iconSizeSmall' ,
143+ buttonGroup : iconSizeGroup, selected : iconSize == Icons . SIZE_SMALL ,
144+ enabled : ! scaleIconsWithFont,
145+ actionPerformed : { iconSize = Icons . SIZE_SMALL })
146+ hstrut(4 )
147+ radioButton(text : T[' prefs.icon.size.normal' ], id : ' iconSizeNormal' ,
148+ buttonGroup : iconSizeGroup, selected : iconSize == Icons . SIZE_NORMAL ,
149+ enabled : ! scaleIconsWithFont,
150+ actionPerformed : { iconSize = Icons . SIZE_NORMAL })
151+ hstrut(4 )
152+ radioButton(text : T[' prefs.icon.size.large' ], id : ' iconSizeLarge' ,
153+ buttonGroup : iconSizeGroup, selected : iconSize == Icons . SIZE_LARGE ,
154+ enabled : ! scaleIconsWithFont,
155+ actionPerformed : { iconSize = Icons . SIZE_LARGE })
156+ hglue()
157+ }
158+
159+ vstrut(4 )
160+
161+ hbox {
162+ checkBox(text : T[' prefs.scale.icons.with.font' ], id : ' scaleIconsCheckBox' ,
163+ selected : scaleIconsWithFont,
164+ actionPerformed : this . &onScaleWithFontToggled)
165+ hglue()
166+ }
167+
168+ vstrut(10 )
169+
170+ hbox {
171+ label " ${ T['prefs.custom.light.theme']} :"
172+ hstrut(6 )
173+ label(id : ' customLightPathLabel' ,
174+ text : customLightThemePath ?: T[' prefs.no.file.selected' ])
175+ hglue()
176+ button(text : T[' prefs.select' ], actionPerformed : this . &onSelectLightTheme)
177+ hstrut(4 )
178+ button(text : T[' prefs.clear' ], id : ' clearLightButton' ,
179+ enabled : customLightThemePath as boolean ,
180+ actionPerformed : this . &onClearLightTheme)
181+ }
182+
183+ vstrut(4 )
184+
185+ hbox {
186+ label " ${ T['prefs.custom.dark.theme']} :"
187+ hstrut(6 )
188+ label(id : ' customDarkPathLabel' ,
189+ text : customDarkThemePath ?: T[' prefs.no.file.selected' ])
190+ hglue()
191+ button(text : T[' prefs.select' ], actionPerformed : this . &onSelectDarkTheme)
192+ hstrut(4 )
193+ button(text : T[' prefs.clear' ], id : ' clearDarkButton' ,
194+ enabled : customDarkThemePath as boolean ,
195+ actionPerformed : this . &onClearDarkTheme)
196+ }
197+
198+ vstrut(8 )
199+
200+ hbox {
201+ hglue()
202+ button(text : T[' prefs.reload.themes' ], actionPerformed : this . &onReloadThemes)
203+ }
204+ }
205+
206+ vstrut(12 )
207+
117208 hbox {
118209 button T[' prefs.reset.defaults' ], id : ' resetPrefsButton' , actionPerformed : this . &onReset
119210 hglue()
@@ -144,6 +235,13 @@ class ConsolePreferences {
144235 private void onReset (EventObject event ) {
145236 console. swing. txtMaxOutputChars. text = DEFAULT_MAX_OUTPUT_CHARS
146237 console. swing. txtLoopModeDelay. text = DEFAULT_LOOP_MODE_DELAY_MILLIS
238+ iconSize = Icons . SIZE_NORMAL
239+ scaleIconsWithFont = false
240+ console. swing. iconSizeNormal. selected = true
241+ console. swing. scaleIconsCheckBox. selected = false
242+ setIconSizeRadiosEnabled(true )
243+ updateLightPathLabel(null )
244+ updateDarkPathLabel(null )
147245 }
148246
149247 private void onClose (EventObject event ) {
@@ -157,6 +255,26 @@ class ConsolePreferences {
157255
158256 console. setOutputPreferences(console. swing. outputFileCheckBox. enabled, outputFile)
159257
258+ // apply appearance settings
259+ boolean previousScale = console. prefs. getBoolean(' scaleIconsWithFont' , false )
260+ int previousIcon = console. prefs. getInt(' iconSize' , Icons . SIZE_NORMAL )
261+ if (scaleIconsWithFont != previousScale) {
262+ console. setScaleIconsWithFont(scaleIconsWithFont)
263+ } else if (! scaleIconsWithFont && iconSize != previousIcon) {
264+ console. applyIconSize(iconSize)
265+ }
266+
267+ // apply custom theme paths — reapply theme only if something actually changed
268+ String previousLight = ThemeManager . customLightPath ?: ' '
269+ String previousDark = ThemeManager . customDarkPath ?: ' '
270+ String newLight = customLightThemePath ?: ' '
271+ String newDark = customDarkThemePath ?: ' '
272+ if (newLight != previousLight || newDark != previousDark) {
273+ ThemeManager . customLightPath = newLight ?: null
274+ ThemeManager . customDarkPath = newDark ?: null
275+ console. reloadThemes()
276+ }
277+
160278 dialog. dispose()
161279 }
162280
@@ -173,6 +291,59 @@ class ConsolePreferences {
173291 console. swing. outputFileName. text = outputFile. path
174292 }
175293
294+ private void onScaleWithFontToggled (EventObject event ) {
295+ scaleIconsWithFont = event. source. selected
296+ setIconSizeRadiosEnabled(! scaleIconsWithFont)
297+ }
298+
299+ private void setIconSizeRadiosEnabled (boolean enabled ) {
300+ console. swing. iconSizeSmall. enabled = enabled
301+ console. swing. iconSizeNormal. enabled = enabled
302+ console. swing. iconSizeLarge. enabled = enabled
303+ }
304+
305+ private void onSelectLightTheme (EventObject event ) { pickThemeFile { it -> updateLightPathLabel(it) } }
306+ private void onSelectDarkTheme (EventObject event ) { pickThemeFile { it -> updateDarkPathLabel(it) } }
307+ private void onClearLightTheme (EventObject event ) { updateLightPathLabel(null ) }
308+ private void onClearDarkTheme (EventObject event ) { updateDarkPathLabel(null ) }
309+
310+ private void onReloadThemes (EventObject event ) {
311+ // apply any staged path changes first so Reload uses current selections
312+ ThemeManager . customLightPath = customLightThemePath ?: null
313+ ThemeManager . customDarkPath = customDarkThemePath ?: null
314+ console. reloadThemes()
315+ }
316+
317+ private void pickThemeFile (Closure onPicked ) {
318+ JFileChooser fileChooser = new JFileChooser ()
319+ fileChooser. fileFilter = new FileNameExtensionFilter (' Groovy theme files (*.theme)' , ' theme' )
320+ def groovyDir = new File (System . getProperty(' user.home' ), ' .groovy' )
321+ fileChooser. currentDirectory = groovyDir. isDirectory() ? groovyDir : new File (System . getProperty(' user.home' ))
322+ if (fileChooser. showOpenDialog(dialog) != JFileChooser . APPROVE_OPTION ) return
323+ File picked = fileChooser. selectedFile
324+ try {
325+ picked. withReader(' UTF-8' ) { r -> ThemeManager . parseTheme(r) }
326+ } catch (Exception ex) {
327+ JOptionPane . showMessageDialog(dialog,
328+ " Could not parse theme file:\n ${ picked.absolutePath} \n\n ${ ex.message} " ,
329+ ' Invalid Theme File' , JOptionPane . WARNING_MESSAGE )
330+ return
331+ }
332+ onPicked(picked. absolutePath)
333+ }
334+
335+ private void updateLightPathLabel (String path ) {
336+ customLightThemePath = path ?: ' '
337+ console. swing. customLightPathLabel. text = path ?: T[' prefs.no.file.selected' ]
338+ console. swing. clearLightButton. enabled = path as boolean
339+ }
340+
341+ private void updateDarkPathLabel (String path ) {
342+ customDarkThemePath = path ?: ' '
343+ console. swing. customDarkPathLabel. text = path ?: T[' prefs.no.file.selected' ]
344+ console. swing. clearDarkButton. enabled = path as boolean
345+ }
346+
176347 // Useful for testing gui
177348 static void main (args ) {
178349 javax.swing.UIManager . setLookAndFeel(javax.swing.UIManager . getSystemLookAndFeelClassName())
0 commit comments