package com.applang.components; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.event.InputEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.border.Border; import javax.swing.event.AncestorEvent; import javax.swing.event.AncestorListener; import org.gjt.sp.jedit.Buffer; import org.gjt.sp.jedit.EditBus; import org.gjt.sp.jedit.EditPane; import org.gjt.sp.jedit.EditPlugin; import org.gjt.sp.jedit.View; import org.gjt.sp.jedit.jEdit; import org.gjt.sp.jedit.EditBus.EBHandler; import org.gjt.sp.jedit.bufferset.BufferSet; import org.gjt.sp.jedit.bufferset.BufferSetManager; import org.gjt.sp.jedit.msg.BufferChanging; import org.gjt.sp.jedit.msg.BufferUpdate; import org.gjt.sp.jedit.msg.EditPaneUpdate; import org.gjt.sp.jedit.msg.ViewUpdate; import org.gjt.sp.jedit.textarea.Gutter; import org.gjt.sp.jedit.visitors.JEditVisitorAdapter; import org.gjt.sp.util.Log; import static com.applang.SwingUtil.*; import static com.applang.Util.*; import static com.applang.Util2.*; import static com.applang.PluginUtils.*; /** * The DoubleFeature plugin * */ public class DoubleFeaturePlugin extends EditPlugin { protected static DoubleFeaturePlugin self = null; @Override public void start() { self = this; EditBus.addToBus(self); } @Override public void stop() { EditBus.removeFromBus(self); } protected View view = null; @EBHandler public void handleViewUpdate(ViewUpdate msg) { view = msg.getView(); if (msg.getWhat() == ViewUpdate.EDIT_PANE_CHANGED) { updateGutters(view, view.getEditPane()); } } protected HashMap<EditPane, DoubleFeature> doubleFeatures = new HashMap<EditPane, DoubleFeature>(); @EBHandler public void handleEditPaneUpdate(EditPaneUpdate msg) { EditPane editPane = msg.getEditPane(); if (msg.getWhat() == EditPaneUpdate.CREATED) { registerEditPane(editPane); } else if (msg.getWhat() == EditPaneUpdate.DESTROYED) { DoubleFeature doubleFeature = doubleFeatures.get(editPane); doubleFeatures.remove(editPane); no_println("unregistered", doubleFeature); editPane.removeAncestorListener(ancestorListener); } } public DoubleFeature registerEditPane(EditPane editPane) { DoubleFeature doubleFeature = doubleFeatures.get(editPane); if (doubleFeature == null) { doubleFeature = new DoubleFeature(editPane.getTextArea()); doubleFeatures.put(editPane, doubleFeature); no_println("registered", doubleFeature); editPane.addAncestorListener(ancestorListener); } return doubleFeature; } public static DoubleFeature registerPane(EditPane editPane) { return self.registerEditPane(editPane); } AncestorListener ancestorListener = new AncestorListener() { public void ancestorRemoved(AncestorEvent event) { } public void ancestorMoved(AncestorEvent event) { EditPane editPane = (EditPane) event.getSource(); for (Object pane : doubleFeatures.keySet()) { DoubleFeature doubleFeature = doubleFeatures.get(pane); if (editPane.equals(pane) && doubleFeature.focused) focusRequest((EditPane) editPane); } } public void ancestorAdded(AncestorEvent event) { } }; @EBHandler public void handleBufferChanging(final BufferChanging msg) { EditPane pane = msg.getEditPane(); if (msg.getWhat() == EditPaneUpdate.BUFFER_CHANGING) { final DoubleFeature doubleFeature = registerEditPane(pane); final Buffer buffer = msg.getBuffer(); if (pendingBuffers.contains(buffer)) { pendingFeatures.put(doubleFeature, buffer); no_println("pending", doubleFeature); if (fileExists(buffer.getPath())) return; } if (featuredBuffers.containsKey(buffer)) { doubleFeature.toggle(true, new Job<Container>() { public void perform(Container c, Object[] parms) throws Exception { doFeature(doubleFeature, buffer, msg); } }); diag_println(DIAG_OFF, "featured", identity(pane), doubleFeature); focusRequest(pane); } else { doubleFeature.toggle(false, null); diag_println(DIAG_OFF, "reduced", identity(pane), doubleFeature); } Container parent = pane.getParent(); if (parent != null) printContainer(identity(parent), parent, _null()); } }; protected static class FeatureBufferChanging extends BufferChanging { public InputEvent inputEvent; public FeatureBufferChanging(Component component, Buffer newBuffer, InputEvent inputEvent) { super((EditPane) SwingUtilities.getAncestorOfClass(EditPane.class, component), newBuffer); this.inputEvent = inputEvent; } } @EBHandler public void handleFeatureBufferChanging(FeatureBufferChanging msg) { if (msg.getWhat() == EditPaneUpdate.BUFFER_CHANGING) { handleBufferChanging(msg); } } private void focusRequest(final EditPane focusPane) { SwingUtilities.invokeLater(new Runnable() { public void run() { setEditPane(focusPane); DoubleFeature doubleFeature = doubleFeatures.get(focusPane); if (doubleFeature != null) doubleFeature.requestFocus(); updateGutters(view, focusPane); String s = ""; for (Object pane : doubleFeatures.keySet()) { boolean focused = focusPane.equals(pane); doubleFeature = doubleFeatures.get(pane); doubleFeature.focused = focused; s += enclose(focused ? "(" : "", identity(pane), focused ? ")" : "", " "); } diag_println(DIAG_OFF, "focused", s); } }); } public static void diag_visit() { JEditVisitorAdapter visitor = new JEditVisitorAdapter() { @Override public void visit(final EditPane editPane) { findComponents(editPane, new Predicate<Component>() { public boolean apply(Component c) { if (c.hasFocus()) { diag_println(DIAG_OFF, "hasFocus", identity(c), identity(editPane)); return true; } return false; } }); } }; jEdit.visit(visitor); } protected Border getGutterBorder(EditPane editPane, String fieldName) { Gutter gutter = editPane.getTextArea().getGutter(); Border border = getPrivateField(Gutter.class, gutter, fieldName); return border == null ? BorderFactory.createEmptyBorder() : border; } protected boolean setGutterBorder(Component component, Border border) { if (component instanceof FeatureContainer) { FeatureContainer fc = (FeatureContainer) component; fc.getGutter().setBorder(border); return true; } else return false; } protected void updateGutters(View view, EditPane focusPane) { EditPane[] editPanes = view.getEditPanes(); for(int i = 0; i < editPanes.length; i++) { EditPane editPane = editPanes[i]; final Border border = getGutterBorder(editPane, editPane.equals(focusPane) ? "focusBorder" : "noFocusBorder"); if (featuredBuffers.containsKey(editPane.getBuffer())) { findFirstComponent(editPane, new Predicate<Component>() { public boolean apply(Component c) { return setGutterBorder(c, border); } }); } else { editPane.getTextArea().getGutter().setBorder(border); } } } private Hashtable<DoubleFeature,Buffer> pendingFeatures = new Hashtable<DoubleFeature,Buffer>(); private HashSet<Buffer> pendingBuffers = new HashSet<Buffer>(); private boolean noFeature = false; @EBHandler public void handleBufferUpdate(BufferUpdate msg) { final Buffer buffer = msg.getBuffer(); if (msg.getWhat() == BufferUpdate.CREATED) { pendingBuffers.add(buffer); } else if (msg.getWhat() == BufferUpdate.LOADED) { pendingBuffers.remove(buffer); if (!noFeature) { String feature = buffer.getStringProperty(FEATURE); if (notNullOrEmpty(feature) && !featuredBuffers.containsKey(buffer)) { addFeature(buffer, feature); } EditPane editPane = null; if (pendingFeatures.containsValue(buffer)) { for (DoubleFeature doubleFeature : pendingFeatures.keySet()) if (buffer.equals(pendingFeatures.get(doubleFeature))) { pendingFeatures.remove(doubleFeature); for (Map.Entry<EditPane,DoubleFeature> entry : doubleFeatures.entrySet()) if (entry.getValue().equals(doubleFeature)) editPane = entry.getKey(); } } else if (featuredBuffers.containsKey(buffer)) { EditPane[] editPanes = getEditPanesFor(buffer); if (editPanes.length > 0) editPane = editPanes[0]; } if (editPane != null) { EditBus.send(new BufferChanging(editPane, buffer)); } } } else if (msg.getWhat() == BufferUpdate.CLOSED) { if (featuredBuffers.containsKey(buffer)) removeFeature(buffer); } } protected void bufferChange(Buffer buffer) { EditPane[] editPanes = getEditPanesFor(buffer); if (editPanes.length > 0) EditBus.send(new BufferChanging(editPanes[0], buffer)); } public static class FeatureContainer extends JComponent implements MouseListener { public FeatureContainer(Buffer buffer) { setLayout(new BorderLayout()); this.buffer = buffer; addMouseListener(this); String format = getProperty("doublefeature.dummy-tooltip.message", "'%s'"); setToolTipText(String.format(format, buffer)); addGutter(); } Buffer buffer; public void mouseClicked(MouseEvent ev) { EditBus.send(new FeatureBufferChanging((Component)ev.getSource(), buffer, ev)); } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } void addGutter() { JPanel gutter = new JPanel(); gutter.setName("gutter"); add(gutter, BorderLayout.WEST); } public JComponent getGutter() { return findFirstComponent(this, "gutter"); } @Override public String toString() { Writer writer = write(new StringWriter(), identity(this)); return writer.toString(); } } private EditPane getEditPaneByDescendant(MouseEvent e) { return (EditPane)SwingUtilities.getAncestorOfClass( EditPane.class, (Component)e.getSource()); } private MouseListener focusRequestListener = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { EditPane editPane = getEditPaneByDescendant(e); if (editPane != null) focusRequest(editPane); } }; protected void installFocusClickListener(Container container, boolean...install) { Component component = findFirstComponent(container, DoubleFeature.FOCUS, Constraint.AMONG); if (component != null) { if (param(true, 0, install)) component.addMouseListener(focusRequestListener); else component.removeMouseListener(focusRequestListener); } } private Hashtable<Buffer,JComponent> featuredBuffers = new Hashtable<Buffer,JComponent>(); protected JComponent constructFeature(final Buffer buffer, String feature, JComponent container, Object...params) throws IOException { installFocusClickListener(container); return container; } protected void deconstructFeature(String feature, JComponent container) { installFocusClickListener(container, false); } protected boolean addFeature(Buffer buffer, String feature, Object...params) { JComponent container = new FeatureContainer(buffer); try { container = constructFeature(buffer, feature, container, arrayappend(objects(focusRequestListener), params)); } catch (Exception e) { Log.log(Log.ERROR, getClass().getName() + ".addFeature", e); } if (container == null) container = new FeatureContainer(buffer); buffer.setStringProperty(FEATURE, feature); featuredBuffers.put(buffer, container); diag_println(DIAG_OFF, "addFeature", buffer); return true; } protected void removeFeature(Buffer buffer) { JComponent container = featuredBuffers.get(buffer); String feature = buffer.getStringProperty(FEATURE); deconstructFeature(feature, container); buffer.setStringProperty(FEATURE, ""); featuredBuffers.remove(buffer); diag_println(DIAG_OFF, "removeFeature", buffer); } protected JComponent featuredWidget(JComponent widget, BufferChanging msg) { return widget; } private void doFeature(DoubleFeature doubleFeature, Buffer buffer, BufferChanging msg) { JComponent widget = featuredWidget(featuredBuffers.get(buffer), msg); Container container = widget.getParent(); if (container instanceof EditPane) { container.remove(widget); DoubleFeature df = doubleFeatures.get(container); if (df != null) { df.setWidget(new FeatureContainer(buffer)); df.addFeatureTo(container); } } doubleFeature.setWidget(widget); } public Buffer newFeatureBuffer(String feature, Object...params) { BufferSetManager bufferSetManager = jEdit.getBufferSetManager(); EditPane editPane = jEdit.getActiveView().getEditPane(); Buffer buffer = editPane.getBuffer(); Buffer newBuffer = createFeatureBuffer(); addFeature(newBuffer, feature, params); for (BufferSet bufferSet : bufferSetManager.getOwners(buffer)) { View[] views = jEdit.getViews(); for (View view : views) { EditPane[] editPanes = view.getEditPanes(); for (EditPane pane : editPanes) { if (pane.getBufferSet() == bufferSet) { bufferSetManager.addBuffer(pane, newBuffer); if (pane.equals(editPane)) pane.setBuffer(newBuffer, false); } } } } return newBuffer; } public static void spellcheckBuffer(Buffer buffer) { if (!self.featuredBuffers.containsKey(buffer)) { if (self.addFeature(buffer, "spellcheck")) self.bufferChange(buffer); } } }