Skip to content

Commit

Permalink
Merge pull request apache#7384 from matthiasblaesing/toggle_bookmarks
Browse files Browse the repository at this point in the history
Fix usage of ToggleBookmarkAction in split windows scenario
  • Loading branch information
matthiasblaesing authored Jun 5, 2024
2 parents f86ce03 + 110fc65 commit d8e2006
Showing 1 changed file with 15 additions and 256 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,130 +19,49 @@

package org.netbeans.lib.editor.bookmarks.actions;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.Action;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JToggleButton;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.editor.EditorRegistry;
import org.netbeans.editor.BaseDocument;
import org.netbeans.lib.editor.bookmarks.api.Bookmark;
import org.netbeans.editor.BaseAction;
import org.netbeans.lib.editor.bookmarks.api.BookmarkList;
import org.openide.awt.Actions;
import org.openide.cookies.EditorCookie;
import org.openide.text.NbDocument;
import org.openide.util.ContextAwareAction;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;
import org.openide.util.actions.Presenter;


/**
* Toggles a bookmark in a line in an opened document.
*
* @author Vita Stejskal
*/
public final class ToggleBookmarkAction extends AbstractAction implements ContextAwareAction, Presenter.Toolbar {
public final class ToggleBookmarkAction extends BaseAction {

private static final String ACTION_NAME = "bookmark-toggle"; // NOI18N
private static final String ACTION_ICON = "org/netbeans/modules/editor/bookmarks/resources/toggle_bookmark.png"; // NOI18N

private final JTextComponent component;

public ToggleBookmarkAction() {
this(null);
}

public ToggleBookmarkAction(JTextComponent component) {
super(
NbBundle.getMessage(ToggleBookmarkAction.class, ACTION_NAME),ImageUtilities.loadImageIcon(ACTION_ICON, false));
public ToggleBookmarkAction() {
super(NbBundle.getMessage(ToggleBookmarkAction.class, ACTION_NAME));
putValue(ICON_RESOURCE_PROPERTY, ACTION_ICON);
putValue(SHORT_DESCRIPTION, Actions.cutAmpersand((String) getValue(NAME)));
putValue("noIconInMenu", Boolean.TRUE); // NOI18N

this.component = component;
updateEnabled();
PropertyChangeListener editorRegistryListener = new EditorRegistryListener(this);
EditorRegistry.addPropertyChangeListener(editorRegistryListener);
}

private void updateEnabled() {
setEnabled(isEnabled());
}

@Override
public Action createContextAwareInstance(Lookup actionContext) {
JTextComponent jtc = findComponent(actionContext);
ToggleBookmarkAction toggleBookmarkAction = new ToggleBookmarkAction(jtc);
toggleBookmarkAction.putValue(ACCELERATOR_KEY, this.getValue(ACCELERATOR_KEY));
return toggleBookmarkAction;
}
updateEnabled();
EditorRegistry.addPropertyChangeListener((PropertyChangeEvent evt) -> {
updateEnabled();
});

@Override
public void actionPerformed(ActionEvent arg0) {
if (component != null) {
// cloned action with context
actionPerformed(component);
} else {
// global action, will have to find the current component
JTextComponent jtc = findComponent(Utilities.actionsGlobalContext());
if (jtc != null) {
actionPerformed(jtc);
}
}
}

@Override
public boolean isEnabled() {
if (component != null) {
return true;
} else {
if (EditorRegistry.lastFocusedComponent() == null) {
return false;
} else {
return true;
}
}
private void updateEnabled() {
setEnabled(EditorRegistry.lastFocusedComponent() != null);
}

@Override
public Component getToolbarPresenter() {
AbstractButton b;

if (component != null) {
b = new MyGaGaButton();
b.setModel(new BookmarkButtonModel(component));
} else {
b = new JButton();
}

b.putClientProperty("hideActionText", Boolean.TRUE); //NOI18N
b.setAction(this);

return b;
}

public static JTextComponent findComponent(Lookup lookup) {
EditorCookie ec = lookup.lookup(EditorCookie.class);
return ec == null ? null : NbDocument.findRecentEditorPane(ec);
}

private static void actionPerformed(JTextComponent target) {
public void actionPerformed(ActionEvent arg0, JTextComponent target) {
if (target != null) {
if (org.netbeans.editor.Utilities.getEditorUI(target).isGlyphGutterVisible()) {
Caret caret = target.getCaret();
Expand All @@ -154,170 +73,10 @@ private static void actionPerformed(JTextComponent target) {
}
}
}

private static final class BookmarkButtonModel extends JToggleButton.ToggleButtonModel implements PropertyChangeListener, ChangeListener {

private final JTextComponent component;
private Caret caret;
private BookmarkList bookmarks;
private int lastCurrentLineStartOffset = -1;

private PropertyChangeListener bookmarksListener = null;
private ChangeListener caretListener = null;

@SuppressWarnings("LeakingThisInConstructor")
public BookmarkButtonModel(JTextComponent component) {
this.component = component;
this.component.addPropertyChangeListener(WeakListeners.propertyChange(this, this.component));
rebuild();
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName() == null ||
"document".equals(evt.getPropertyName()) || //NOI18N
"caret".equals(evt.getPropertyName()) //NOI18N
) {
rebuild();
} else if ("bookmarks".equals(evt.getPropertyName())) { //NOI18N
lastCurrentLineStartOffset = -1;
updateState();
}
}

@Override
public void stateChanged(ChangeEvent evt) {
updateState();
}

private static boolean isBookmarkOnTheLine(BookmarkList bookmarks, int lineStartOffset) {
Bookmark bm = bookmarks.getNextBookmark(lineStartOffset - 1, false);
// System.out.println("offset: " + lineStartOffset + " -> " + bm + (bm == null ? "" : "; bm.getOffset() = " + bm.getOffset()));
return bm == null ? false : lineStartOffset == bm.getOffset();
}

private void rebuild() {
// Hookup the bookmark list
BookmarkList newBookmarks = BookmarkList.get(component.getDocument());
if (newBookmarks != bookmarks) {
if (bookmarksListener != null) {
bookmarks.removePropertyChangeListener (bookmarksListener);
bookmarksListener = null;
}

bookmarks = newBookmarks;

if (bookmarks != null) {
bookmarksListener = WeakListeners.propertyChange(this, bookmarks);
bookmarks.addPropertyChangeListener (bookmarksListener);
}
}

// Hookup the caret
Caret newCaret = component.getCaret();
if (newCaret != caret) {
if (caretListener != null) {
caret.removeChangeListener(caretListener);
caretListener = null;
}

caret = newCaret;

if (caret != null) {
caretListener = WeakListeners.change(this, caret);
caret.addChangeListener(caretListener);
}
}

lastCurrentLineStartOffset = -1;
updateState();
}

private void updateState() {
Document doc = component.getDocument();
if (caret != null && bookmarks != null && doc instanceof BaseDocument) {
try {
int currentLineStartOffset = org.netbeans.editor.Utilities.getRowStart((BaseDocument) doc, caret.getDot());
if (currentLineStartOffset != lastCurrentLineStartOffset) {
lastCurrentLineStartOffset = currentLineStartOffset;
boolean selected = isBookmarkOnTheLine(bookmarks, currentLineStartOffset);

// System.out.println("updateState: offset=" + currentLineStartOffset + ", hasBookmark=" + selected);

setSelected(selected);
}
} catch (BadLocationException e) {
// ignore
lastCurrentLineStartOffset = -1;
}
}
}
} // End of BookmarkButtonModel class

private static final class MyGaGaButton extends JToggleButton implements ChangeListener {

public MyGaGaButton() {

}

@Override
public void setModel(ButtonModel model) {
ButtonModel oldModel = getModel();
if (oldModel != null) {
oldModel.removeChangeListener(this);
}

super.setModel(model);

ButtonModel newModel = getModel();
if (newModel != null) {
newModel.addChangeListener(this);
}

stateChanged(null);
}

@Override
public void stateChanged(ChangeEvent evt) {
boolean selected = isSelected();
super.setContentAreaFilled(selected);
super.setBorderPainted(selected);
}

@Override
public void setBorderPainted(boolean arg0) {
if (!isSelected()) {
super.setBorderPainted(arg0);
}
}

@Override
public void setContentAreaFilled(boolean arg0) {
if (!isSelected()) {
super.setContentAreaFilled(arg0);
}
}
} // End of MyGaGaButton class

private static final class EditorRegistryListener implements PropertyChangeListener {

private final Reference<ToggleBookmarkAction> actionRef;

EditorRegistryListener(ToggleBookmarkAction action) {
actionRef = new WeakReference<ToggleBookmarkAction>(action);
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
ToggleBookmarkAction action = actionRef.get();
if (action != null) {
action.updateEnabled();
} else {
EditorRegistry.removePropertyChangeListener(this); // EditorRegistry fires frequently so remove this way
}
}

public static JTextComponent findComponent(Lookup lookup) {
EditorCookie ec = lookup.lookup(EditorCookie.class);
return ec == null ? null : NbDocument.findRecentEditorPane(ec);
}

}

0 comments on commit d8e2006

Please sign in to comment.