Skip to content

Commit 47c0e91

Browse files
committed
Improve support for Look and Feels
This commit introduces a new SwingLookAndFeelService to encapsulate Look-and-Feel-related logic, and restore the last used L+F on startup. Closes #3.
1 parent 5c22757 commit 47c0e91

File tree

4 files changed

+224
-180
lines changed

4 files changed

+224
-180
lines changed

src/main/java/org/scijava/ui/swing/AbstractSwingUI.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
import org.scijava.ui.awt.AWTWindowEventDispatcher;
6363
import org.scijava.ui.console.ConsolePane;
6464
import org.scijava.ui.swing.console.SwingConsolePane;
65+
import org.scijava.ui.swing.laf.SwingLookAndFeelService;
6566
import org.scijava.ui.swing.menu.SwingJMenuBarCreator;
6667
import org.scijava.ui.swing.menu.SwingJPopupMenuCreator;
6768
import org.scijava.ui.viewer.DisplayViewer;
@@ -92,6 +93,9 @@ public abstract class AbstractSwingUI extends AbstractUserInterface implements
9293
@Parameter
9394
private UIService uiService;
9495

96+
@Parameter(required = false)
97+
private SwingLookAndFeelService lafService;
98+
9599
@Parameter
96100
private ThreadService threadService;
97101

@@ -242,6 +246,9 @@ public void dispose() {
242246

243247
@Override
244248
protected void createUI() {
249+
// Before we create any UI components, initialize the Look and Feel.
250+
if (lafService != null) lafService.initLookAndFeel();
251+
245252
final JMenuBar menuBar = createMenus();
246253

247254
appFrame = new SwingApplicationFrame(appService.getApp().getTitle());

src/main/java/org/scijava/ui/swing/SwingApplicationFrame.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,11 @@ public int getLocationY() {
7272

7373
@Override
7474
public void activate() {
75-
EventQueue.invokeLater(new Runnable() {
76-
77-
@Override
78-
public void run() {
79-
// NB: You might think calling requestFocus() would work, but no.
80-
// The following solution is from: http://bit.ly/zAXzd5
81-
toFront();
82-
repaint();
83-
}
75+
EventQueue.invokeLater(() -> {
76+
// NB: You might think calling requestFocus() would work, but no.
77+
// The following solution is from: http://bit.ly/zAXzd5
78+
toFront();
79+
repaint();
8480
});
8581
}
8682

src/main/java/org/scijava/ui/swing/laf/OptionsLookAndFeel.java

Lines changed: 20 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -29,37 +29,21 @@
2929

3030
package org.scijava.ui.swing.laf;
3131

32-
import java.awt.Component;
33-
import java.awt.EventQueue;
34-
import java.awt.Window;
35-
import java.util.ArrayList;
36-
import java.util.HashSet;
37-
import java.util.Set;
32+
import java.util.Arrays;
33+
import java.util.Collections;
34+
import java.util.List;
35+
import java.util.stream.Collectors;
3836

39-
import javax.swing.SwingUtilities;
37+
import javax.swing.LookAndFeel;
4038
import javax.swing.UIManager;
4139
import javax.swing.UIManager.LookAndFeelInfo;
42-
import javax.swing.UnsupportedLookAndFeelException;
4340

44-
import com.formdev.flatlaf.FlatLightLaf;
45-
import com.formdev.flatlaf.FlatDarkLaf;
46-
import com.formdev.flatlaf.FlatIntelliJLaf;
47-
import com.formdev.flatlaf.FlatLaf;
48-
import com.formdev.flatlaf.FlatDarculaLaf;
49-
50-
import org.scijava.display.Display;
51-
import org.scijava.display.DisplayService;
52-
import org.scijava.log.LogService;
5341
import org.scijava.menu.MenuConstants;
5442
import org.scijava.module.MutableModuleItem;
5543
import org.scijava.options.OptionsPlugin;
5644
import org.scijava.plugin.Menu;
5745
import org.scijava.plugin.Parameter;
5846
import org.scijava.plugin.Plugin;
59-
import org.scijava.ui.UIService;
60-
import org.scijava.ui.UserInterface;
61-
import org.scijava.ui.viewer.DisplayViewer;
62-
import org.scijava.widget.UIComponent;
6347

6448
/**
6549
* Runs the Edit::Options::Look and Feel dialog.
@@ -79,9 +63,10 @@ public class OptionsLookAndFeel extends OptionsPlugin {
7963

8064
// -- Parameters --
8165

82-
@Parameter
83-
private LogService log;
66+
@Parameter(required = false)
67+
private SwingLookAndFeelService lafService;
8468

69+
// NB: This setting is persisted by the SwingLookAndFeelService.
8570
@Parameter(label = "Look & Feel", persist = false,
8671
initializer = "initLookAndFeel")
8772
private String lookAndFeel;
@@ -100,164 +85,28 @@ public void setLookAndFeel(final String lookAndFeel) {
10085

10186
@Override
10287
public void run() {
103-
// set look and feel
104-
final LookAndFeelInfo[] lookAndFeels = UIManager.getInstalledLookAndFeels();
105-
for (final LookAndFeelInfo lookAndFeelInfo : lookAndFeels) {
106-
if (lookAndFeelInfo.getName().equals(lookAndFeel)) {
107-
try {
108-
UIManager.setLookAndFeel(lookAndFeelInfo.getClassName());
109-
EventQueue.invokeLater(new Runnable() {
110-
111-
@Override
112-
public void run() {
113-
refreshSwingComponents();
114-
}
115-
});
116-
}
117-
catch (final ClassNotFoundException e) {
118-
log.error(e);
119-
}
120-
catch (final InstantiationException e) {
121-
log.error(e);
122-
}
123-
catch (final IllegalAccessException e) {
124-
log.error(e);
125-
}
126-
catch (final UnsupportedLookAndFeelException e) {
127-
log.error(e);
128-
}
129-
break;
130-
}
131-
}
132-
88+
if (lafService != null) lafService.setLookAndFeel(lookAndFeel);
13389
super.run();
13490
}
13591

13692
// -- Initializers --
13793

13894
protected void initLookAndFeel() {
139-
final String lafClass = UIManager.getLookAndFeel().getClass().getName();
140-
LookAndFeelInfo[] lookAndFeels = UIManager.getInstalledLookAndFeels();
141-
142-
// Make UIManager aware of FlatLaf look and feels, as needed
143-
if (!isRegistered(lookAndFeels, FlatLightLaf.NAME)) FlatLightLaf.installLafInfo();
144-
if (!isRegistered(lookAndFeels, FlatDarkLaf.NAME)) FlatDarkLaf.installLafInfo();
145-
if (!isRegistered(lookAndFeels, FlatDarculaLaf.NAME)) FlatDarculaLaf.installLafInfo();
146-
if (!isRegistered(lookAndFeels, FlatIntelliJLaf.NAME)) FlatIntelliJLaf.installLafInfo();
147-
lookAndFeels = UIManager.getInstalledLookAndFeels(); // retrieve updated list
148-
149-
final ArrayList<String> lookAndFeelChoices = new ArrayList<>();
150-
for (final LookAndFeelInfo lafInfo : lookAndFeels) {
151-
final String lafName = lafInfo.getName();
152-
lookAndFeelChoices.add(lafInfo.getName());
153-
if (lafClass.equals(lafInfo.getClassName())) {
154-
lookAndFeel = lafName;
155-
}
156-
}
157-
158-
final MutableModuleItem<String> lookAndFeelItem =
95+
final MutableModuleItem<String> lafItem = //
15996
getInfo().getMutableInput(LOOK_AND_FEEL, String.class);
160-
lookAndFeelItem.setChoices(lookAndFeelChoices);
161-
}
162-
163-
// -- Helper methods --
164-
165-
/** Assesses whether lookAndFeels contains the laf associated with lafName*/
166-
private boolean isRegistered(final LookAndFeelInfo[] lookAndFeels, final String lafName) {
167-
for (final LookAndFeelInfo lafInfo : lookAndFeels) {
168-
if (lafInfo.getName().equals(lafName))
169-
return true;
170-
}
171-
return false;
172-
}
173-
174-
/** Tells all known Swing components to change to the new Look &amp; Feel. */
175-
private void refreshSwingComponents() {
176-
// TODO: Change this hacky logic to call a clean UIService API
177-
// for window retrieval. But does not exist as of this writing.
17897

179-
final Set<Component> components = new HashSet<>();
98+
final LookAndFeel laf = UIManager.getLookAndFeel();
99+
lookAndFeel = laf == null ? "<None>" : laf.getName();
180100

181-
// add Swing UI components from visible UIs
182-
for (final UserInterface ui : uiService().getVisibleUIs()) {
183-
findComponents(components, ui.getApplicationFrame());
184-
findComponents(components, ui.getConsolePane());
101+
if (lafService == null) {
102+
lafItem.setChoices(Collections.singletonList(lookAndFeel));
185103
}
186-
187-
// add Swing UI components from visible displays
188-
for (final Display<?> d : displayService().getDisplays()) {
189-
final DisplayViewer<?> viewer = uiService().getDisplayViewer(d);
190-
if (viewer == null) continue;
191-
findComponents(components, viewer.getWindow());
192-
}
193-
194-
// refresh all discovered components
195-
for (final Component c : components) {
196-
SwingUtilities.updateComponentTreeUI(c);
197-
if (c instanceof Window) ((Window) c).pack();
198-
}
199-
}
200-
201-
/**
202-
* Extracts Swing components from the given object, adding them to
203-
* the specified set.
204-
*/
205-
private void findComponents(final Set<Component> set, final Object o) {
206-
if (o == null) return;
207-
if (o instanceof UIComponent) {
208-
final UIComponent<?> c = (UIComponent<?>) o;
209-
findComponents(set, c.getComponent());
210-
}
211-
if (o instanceof Window) set.add((Window) o);
212-
else if (o instanceof Component) {
213-
final Component c = (Component) o;
214-
final Window w = SwingUtilities.getWindowAncestor(c);
215-
set.add(w == null ? c : w);
216-
}
217-
}
218-
219-
private UIService uiService() {
220-
return getContext().service(UIService.class);
221-
}
222-
223-
private DisplayService displayService() {
224-
return getContext().service(DisplayService.class);
225-
}
226-
227-
/**
228-
* Sets the application look and feel.
229-
* <p>
230-
* Useful for setting up the look and feel early on in application startup
231-
* routine (e.g., through a macro or script)
232-
* </p>
233-
*
234-
* @param lookAndFeel the look and feel. Supported values include "FlatLaf
235-
* Light", "FlatLaf Dark", "FlatLaf Darcula", "FlatLaf
236-
* IntelliJ", and JVM defaults
237-
* ("javax.swing.plaf.metal.MetalLookAndFeel", etc.)
238-
*/
239-
public static void setupLookAndFeel(final String lookAndFeel) {
240-
switch (lookAndFeel) { // FIXME: should FlatLaf.updateUI() calls be replaced with updateUILater()?
241-
case FlatLightLaf.NAME:
242-
if (FlatLightLaf.setup()) FlatLaf.updateUI();
243-
return;
244-
case FlatDarkLaf.NAME:
245-
if (FlatDarkLaf.setup()) FlatLaf.updateUI();
246-
return;
247-
case FlatDarculaLaf.NAME:
248-
if (FlatDarculaLaf.setup()) FlatLaf.updateUI();
249-
return;
250-
case FlatIntelliJLaf.NAME:
251-
if (FlatIntelliJLaf.setup()) FlatLaf.updateUI();
252-
return;
253-
default:
254-
try {
255-
UIManager.setLookAndFeel(lookAndFeel);
256-
FlatLaf.updateUI();
257-
} catch (final Exception ex) {
258-
ex.printStackTrace();
259-
FlatLaf.revalidateAndRepaintAllFramesAndDialogs(); // Recover from possible ill-states
260-
}
104+
else {
105+
final LookAndFeelInfo[] infos = lafService.getLookAndFeels();
106+
final List<String> choices = Arrays.stream(infos)//
107+
.map(info -> info.getName())//
108+
.collect(Collectors.toList());
109+
lafItem.setChoices(choices);
261110
}
262111
}
263112
}

0 commit comments

Comments
 (0)