GP-4676 adding option to globally control blinking cursors

This commit is contained in:
ghidragon 2024-06-12 16:10:24 -04:00
parent 907a834fdc
commit 2f823d23eb
8 changed files with 107 additions and 33 deletions

View File

@ -392,9 +392,15 @@
<TBODY>
<TR>
<TH valign="top" align="left"><B>Option</B></TH>
<TH valign="top" align="left"><B>Description</B></TH>
</TR>
<TR>
<TD valign="top" width="200" align="left">Allow Blinking Cursors</TD>
<TD valign="top" align="left">This controls whether text components and other
components that have a text cursor will blink when they have focus.</TD>
</TR>
<TR>
<TD valign="top" width="200" align="left">Automatically Save Tools</TD>

View File

@ -63,7 +63,6 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
private static final String CURSOR_COLOR_OPTIONS_NAME = "Cursor.Cursor Color - Focused";
private static final String UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME =
"Cursor.Cursor Color - Unfocused";
private static final String BLINK_CURSOR_OPTIONS_NAME = "Cursor.Blink Cursor";
private static final String MOUSE_WHEEL_HORIZONTAL_SCROLLING_OPTIONS_NAME =
"Mouse.Horizontal Scrolling";
@ -417,10 +416,6 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
Color color = ((Color) newValue);
fieldPanel.setNonFocusCursorColor(color);
}
else if (optionName.equals(BLINK_CURSOR_OPTIONS_NAME)) {
Boolean isBlinkCursor = ((Boolean) newValue);
fieldPanel.setBlinkCursor(isBlinkCursor);
}
else if (optionName.equals(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR)) {
cursorHighlightColor = (Color) newValue;
if (currentCursorMarkers != null) {
@ -562,8 +557,6 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
fieldOptions.registerThemeColorBinding(UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME,
UNFOCUSED_CURSOR_COLOR.getId(), helpLocation,
"The color of the cursor in the browser when the browser does not have focus.");
fieldOptions.registerOption(BLINK_CURSOR_OPTIONS_NAME, true, helpLocation,
"When selected, the cursor will blink when the containing window is focused.");
fieldOptions.registerThemeColorBinding(GhidraOptions.HIGHLIGHT_CURSOR_LINE_COLOR,
CURRENT_LINE_HIGHLIGHT_COLOR.getId(), helpLocation,
"The background color of the line where the cursor is located");
@ -599,9 +592,6 @@ public abstract class AbstractCodeBrowserPlugin<P extends CodeViewerProvider> ex
color = fieldOptions.getColor(UNFOCUSED_CURSOR_COLOR_OPTIONS_NAME, UNFOCUSED_CURSOR_COLOR);
fieldPanel.setNonFocusCursorColor(color);
Boolean isBlinkCursor = fieldOptions.getBoolean(BLINK_CURSOR_OPTIONS_NAME, true);
fieldPanel.setBlinkCursor(isBlinkCursor);
boolean horizontalScrollingEnabled =
fieldOptions.getBoolean(MOUSE_WHEEL_HORIZONTAL_SCROLLING_OPTIONS_NAME, true);
fieldPanel.setHorizontalScrollingEnabled(horizontalScrollingEnabled);

View File

@ -42,6 +42,7 @@ import docking.widgets.indexedscrollpane.IndexScrollListener;
import docking.widgets.indexedscrollpane.IndexedScrollable;
import generic.theme.GColor;
import generic.theme.GThemeDefaults.Colors.Messages;
import generic.theme.ThemeManager;
import ghidra.util.*;
public class FieldPanel extends JPanel
@ -127,6 +128,7 @@ public class FieldPanel extends JPanel
setFocusable(true);
hoverHandler = new HoverHandler(this);
initializeCursorBlinking();
}
@Override
@ -392,6 +394,17 @@ public class FieldPanel extends JPanel
super.repaint();
}
@Override
public void updateUI() {
super.updateUI();
initializeCursorBlinking();
}
private void initializeCursorBlinking() {
boolean blinkingCursors = ThemeManager.getInstance().isBlinkingCursors();
setBlinkCursor(blinkingCursors);
}
@Override
public Dimension getPreferredSize() {
if (viewport == null) {
@ -423,7 +436,9 @@ public class FieldPanel extends JPanel
}
public void setBlinkCursor(Boolean blinkCursor) {
cursorHandler.setBlinkCursor(blinkCursor);
if (cursorHandler != null) {
cursorHandler.setBlinkCursor(blinkCursor);
}
}
public void enableSelection(boolean b) {
@ -1397,6 +1412,7 @@ public class FieldPanel extends JPanel
return accessibleFieldPanel;
}
@Override
public void mouseWheelMoved(double preciseWheelRotation, boolean horizontal) {
Layout firstLayout = model.getLayout(BigInteger.ZERO);

View File

@ -45,6 +45,7 @@ public class ApplicationThemeManager extends ThemeManager {
// stores the original value for ids whose value has changed from the current theme
private GThemeValueMap changedValuesMap = new GThemeValueMap();
protected LookAndFeelManager lookAndFeelManager;
private boolean blinkingCursors = true;
/**
* Initialized the Theme and its values for the application.
@ -126,26 +127,31 @@ public class ApplicationThemeManager extends ThemeManager {
}
update(() -> {
activeTheme = theme;
activeLafType = theme.getLookAndFeelType();
useDarkDefaults = theme.useDarkDefaults();
cleanUiDefaults(); // clear out any values previous themes may have installed
lookAndFeelManager = activeLafType.getLookAndFeelManager(this);
try {
lookAndFeelManager.installLookAndFeel();
if (updateLookAndFeel()) {
themePreferences.save(theme);
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
}
catch (Exception e) {
Msg.error(this, "Error setting Look and Feel: " + activeLafType.getName(), e);
}
});
currentValues.checkForUnresolvedReferences();
}
private boolean updateLookAndFeel() {
try {
cleanUiDefaults(); // clear out any values previous themes may have installed
lookAndFeelManager.installLookAndFeel();
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
return true;
}
catch (Exception e) {
Msg.error(this, "Error setting Look and Feel: " + activeLafType.getName(), e);
}
return false;
}
@Override
public void setLookAndFeel(LafType lafType, boolean useDarkDefaults) {
@ -162,19 +168,31 @@ public class ApplicationThemeManager extends ThemeManager {
this.useDarkDefaults = useDarkDefaults;
update(() -> {
cleanUiDefaults(); // clear out any values previous themes may have installed
lookAndFeelManager = lafType.getLookAndFeelManager(this);
try {
lookAndFeelManager.installLookAndFeel();
notifyThemeChanged(new AllValuesChangedThemeEvent(true));
}
catch (Exception e) {
Msg.error(this, "Error setting Look and Feel: " + lafType.getName(), e);
}
updateLookAndFeel();
});
}
@Override
public void setBlinkingCursors(boolean b) {
if (blinkingCursors == b) {
return;
}
blinkingCursors = b;
// Need to reinstall the look and feel so that UIDefaults for cursor blinking are set.
// For most look and feels, we could have just updated the UIs, but because Nimbus
// doesn't respect UIDefaults changes after loading, it is easier to just reinstall.
update(() -> {
updateLookAndFeel();
});
}
@Override
public boolean isBlinkingCursors() {
return blinkingCursors;
}
@Override
public void addTheme(GTheme newTheme) {
loadThemes();

View File

@ -19,8 +19,7 @@ import java.awt.*;
import java.util.List;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.LookAndFeel;
import javax.swing.*;
import javax.swing.plaf.ComponentUI;
import generic.theme.builtin.*;
@ -667,6 +666,29 @@ public abstract class ThemeManager {
int newSize = Math.max(MIN_FONT_SIZE, currentSize += amount);
setFont(fontValue.getId(), directFont.deriveFont((float) newSize));
}
}
/**
* Sets application's blinking cursor state. This will affect all JTextFields, JTextAreas,
* JTextPanes via {@link UIDefaults}. Custom components can also respect this setting by
* either adding a {@link ThemeListener} or overriding {@link JComponent#updateUI()}
* <P> NOTE: This method is a bit odd here as it doesn't really apply to a theme. But it
* requires manipulation of the look and feel which is managed by the theme. If other
* application level properties come along and also require changing the UIDefaults,
* perhaps a more general solution might be to add a way for clients to register a callback
* so that they get a chance to change the UIDefaults map as the look and feel is loaded.
* @param b true for blinking text cursors, false for non-blinking text cursors
*/
public void setBlinkingCursors(boolean b) {
throw new UnsupportedOperationException();
}
/**
* Returns true if the application should allow blinking cursors, false otherwise. Custom
* components can use this method to determine if they should have a blinking cursor or not.
* @return true if the application should allow blinking cursors, false otherwise.
*/
public boolean isBlinkingCursors() {
return true;
}
}

View File

@ -37,7 +37,7 @@ public class FlatLookAndFeelManager extends LookAndFeelManager {
@Override
protected void fixupLookAndFeelIssues() {
super.fixupLookAndFeelIssues();
//
// The FlatTreeUI class will remove default renderers inside the call to updateRenderer()
// if "Tree.showDefaultIcons" is false. We want the tree to display folder icons.

View File

@ -37,6 +37,7 @@ import utilities.util.reflection.ReflectionUtilities;
*/
public abstract class LookAndFeelManager {
private static final int DEFAULT_CURSOR_BLINK_RATE = 500;
private LafType laf;
private Map<String, ComponentFontRegistry> fontRegistryMap = new HashMap<>();
private Map<Component, String> componentToIdMap = new WeakHashMap<>();
@ -290,6 +291,7 @@ public abstract class LookAndFeelManager {
*/
protected void fixupLookAndFeelIssues() {
installGlobalFontSizeOverride();
installCursorBlinkingProperties();
}
/**
@ -355,6 +357,15 @@ public abstract class LookAndFeelManager {
setGlobalFontSizeOverride(overrideFontInteger);
}
public void installCursorBlinkingProperties() {
UIDefaults defaults = UIManager.getDefaults();
int blinkRate = themeManager.isBlinkingCursors() ? DEFAULT_CURSOR_BLINK_RATE : 0;
defaults.put("TextPane.caretBlinkRate", blinkRate);
defaults.put("TextField.caretBlinkRate", blinkRate);
defaults.put("TextArea.caretBlinkRate", blinkRate);
}
private void installCustomLookAndFeelActions() {
// these prefixes are for text components
String[] UIPrefixValues =

View File

@ -42,6 +42,7 @@ import docking.util.AnimationUtils;
import docking.util.image.ToolIconURL;
import docking.widgets.OptionDialog;
import generic.jar.ResourceFile;
import generic.theme.ThemeManager;
import generic.util.WindowUtilities;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.util.GenericHelpTopics;
@ -91,6 +92,7 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools";
private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation";
private static final String SHOW_TOOLTIPS_OPTION_NAME = "Show Tooltips";
private static final String BLINKING_CURSORS_OPTION_NAME = "Allow Blinking Cursors";
// TODO: Experimental Option !!
private static final String ENABLE_COMPRESSED_DATABUFFER_OUTPUT =
@ -353,6 +355,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
"When enabled data buffers sent to Ghidra Server are compressed (see server " +
"configuration for other direction)");
options.registerOption(BLINKING_CURSORS_OPTION_NAME, true, help, "This controls whether" +
" text cursors blink when focused");
options.registerOption(RESTORE_PREVIOUS_PROJECT_NAME, true, help,
"Restore the previous project when Ghidra starts.");
@ -373,6 +378,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
shouldRestorePreviousProject = options.getBoolean(RESTORE_PREVIOUS_PROJECT_NAME, true);
boolean blink = options.getBoolean(BLINKING_CURSORS_OPTION_NAME, true);
ThemeManager.getInstance().setBlinkingCursors(blink);
options.addOptionsChangeListener(this);
}
@ -397,6 +405,9 @@ public class FrontEndTool extends PluginTool implements OptionsChangeListener {
else if (RESTORE_PREVIOUS_PROJECT_NAME.equals(optionName)) {
shouldRestorePreviousProject = (Boolean) newValue;
}
else if (BLINKING_CURSORS_OPTION_NAME.equals(optionName)) {
ThemeManager.getInstance().setBlinkingCursors((Boolean) newValue);
}
}
@Override