view src/luan/modules/swing/SwingLuan.java @ 1980:2662ff07a6c8

drop files
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 27 Jun 2025 17:51:16 -0600
parents 7278eb509524
children
line wrap: on
line source

package luan.modules.swing;

import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;
import javax.swing.KeyStroke;
import javax.swing.Action;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JViewport;
import javax.swing.Timer;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import javax.swing.text.JTextComponent;
import javax.swing.text.Document;
import javax.swing.undo.UndoManager;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.Point;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.WindowListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.ComponentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import goodjava.logging.Logger;
import goodjava.logging.LoggerFactory;
import luan.Luan;
import luan.LuanFunction;
import luan.LuanTable;
import luan.LuanException;
import luan.LuanRuntimeException;


public class SwingLuan {
	private static final Logger logger = LoggerFactory.getLogger(SwingLuan.class);

	private static Luan luan;
	static {
		SwingUtilities.invokeLater(new Runnable() { public void run() {
			luan = new Luan();
		} } );
	}

	public static Luan luan() throws LuanException {
		if( !SwingUtilities.isEventDispatchThread() )
			throw new LuanException("Not in swing thread");
		if( luan==null )
			throw new LuanException("Not initialized");
		return luan;
	}

	public static void exception(LuanException e) {
		System.err.println(e.getLuanStackTraceString());
		System.exit(1);
	}

	private static void exception(String msg) {
		exception( new LuanException(msg) );
	}

	private static Runnable runnable(final LuanFunction fn) {
		return new Runnable() {
			public void run() {
				try {
					fn.call(luan());
				} catch(LuanException e) {
					exception(e);
				} catch(LuanRuntimeException e) {
					exception(e.luanException);
				}
			}
		};
	}

	public static void run(LuanFunction fn) throws InterruptedException, InvocationTargetException {
		SwingUtilities.invokeAndWait(runnable(fn));
	}

	public static void runLater(LuanFunction fn) throws InterruptedException, InvocationTargetException {
		SwingUtilities.invokeLater(runnable(fn));
	}

	public static ActionListener newActionListener(final LuanFunction fn) throws LuanException {
		if( fn == null )
			exception("function is null");
		if( !fn.canTake(1) )
			exception("action listener function must take event parameter");
		return new ActionListener() {
			@Override public void actionPerformed(ActionEvent event) {
				try {
					LuanTable t = new LuanTable();
					Object source = event.getSource();
					if( source instanceof JComponent ) {
						JComponent jcomponent = (JComponent)source;
						Object component = jcomponent.getClientProperty("luan");
						if( component != null )
							t.rawPut("source",component);
					}
					String action = event.getActionCommand();
					if( action != null )
						t.rawPut("action",action);
					fn.call(luan(),t);
				} catch(LuanException e) {
					//throw new LuanRuntimeException(e);
					exception(e);
				}
			}
		};
	}

	public static WindowListener newCloseListener(final LuanFunction fn) {
		return new WindowAdapter() {
			@Override public void windowClosed(WindowEvent event) {
				try {
					fn.call(luan());
				} catch(LuanException e) {
					exception(e);
				}
			}
		};
	}

	public static WindowListener newWindowFocusListener(final LuanFunction fn) {
		return new WindowAdapter() {
			@Override public void windowGainedFocus(WindowEvent event) {
				try {
					fn.call(luan());
				} catch(LuanException e) {
					exception(e);
				}
			}
		};
	}

	public static ComponentListener newResizeListener(final LuanFunction fn) {
		return new ComponentAdapter() {
			@Override public void componentResized(ComponentEvent event) {
				try {
					fn.call(luan());
				} catch(LuanException e) {
					exception(e);
				}
			}
		};
	}

	public static void notifyAfterResizeStops(Component comp,final ComponentListener cl,int timeAfterStopped) {
		comp.addComponentListener( new ComponentAdapter() {
			ComponentEvent event;
			final Timer timer = new Timer( timeAfterStopped, new ActionListener() {
				@Override public void actionPerformed(ActionEvent _e) {
					cl.componentResized(event);
				}
			} );

			{
				timer.setRepeats(false);
			}

			@Override public void componentResized(ComponentEvent event) {
				this.event = event;
				timer.restart();
			}
		} );
	}

	public static ComponentListener newMoveListener(final LuanFunction fn) {
		return new ComponentAdapter() {
			@Override public void componentMoved(ComponentEvent event) {
				try {
					fn.call(luan());
				} catch(LuanException e) {
					exception(e);
				}
			}
		};
	}

	public static ChangeListener newChangeListener(final LuanFunction fn) {
		return new ChangeListener() {
			@Override public void stateChanged(ChangeEvent event) {
				try {
					fn.call(luan());
				} catch(LuanException e) {
					exception(e);
				}
			}
		};
	}

	public static void notifyAfterMoveStops(Component comp,final ComponentListener cl,int timeAfterStopped) {
		comp.addComponentListener( new ComponentAdapter() {
			ComponentEvent event;
			final Timer timer = new Timer( timeAfterStopped, new ActionListener() {
				@Override public void actionPerformed(ActionEvent _e) {
					cl.componentMoved(event);
				}
			} );

			{
				timer.setRepeats(false);
			}

			@Override public void componentMoved(ComponentEvent event) {
				this.event = event;
				timer.restart();
			}
		} );
	}

	public static void fixTextComponent(final JTextComponent tc) {
		tc.getInputMap().put(KeyStroke.getKeyStroke("meta Z"),"undo");
		Action undoAction = new AbstractAction() {
			@Override public void actionPerformed(ActionEvent e) {
				UndoManager undoManager = (UndoManager)tc.getDocument().getProperty("undo");
				if( undoManager.canUndo() )
					undoManager.undo();
			}
		};
		tc.getActionMap().put("undo",undoAction);

		tc.getInputMap().put(KeyStroke.getKeyStroke("shift meta Z"),"redo");
		Action redoAction = new AbstractAction() {
			@Override public void actionPerformed(ActionEvent e) {
				UndoManager undoManager = (UndoManager)tc.getDocument().getProperty("undo");
				if( undoManager.canRedo() )
					undoManager.redo();
			}
		};
		tc.getActionMap().put("redo",redoAction);
	}

	public static void scrollIntoViewVertically(JComponent component) throws LuanException {
		Rectangle bounds = component.getBounds();
		int y1 = bounds.y;
		int dy = bounds.height;
		Component c; 
		for( c = component.getParent(); !(c instanceof JViewport); c = c.getParent() ) {
			if( c == null )
				throw new LuanException("Not inside a viewport");
			bounds = c.getBounds();
			y1 += bounds.y;
		}
		JViewport viewport = (JViewport)c;
		y1 -= viewport.getView().getBounds().y;
		int y2 = y1 + dy;
		Point p = viewport.getViewPosition();
		int y1View = p.y;
		int y2View = y1View + viewport.getBounds().height;
		if( y1View > y1 ) {
			p.y = y1;
			viewport.setViewPosition(p);
		} else if ( y2View < y2 ) {
			p.y += y2 - y2View;
			viewport.setViewPosition(p);
		}
	}




	public static boolean isWordChar(char c) {
		return Character.isLetterOrDigit(c) || c=='_';
	}

	public static abstract class SelectAction extends AbstractAction {
		@Override public void actionPerformed(ActionEvent e) {
			JTextComponent comp = (JTextComponent) e.getSource();
			int offset = comp.getCaretPosition();
			String text = comp.getText();
			if (offset < 0 || offset >= text.length()) {
				return;
			}
			if (!ok(text.charAt(offset))) {
				//select(offset, offset);
				return;
			}
			int start = offset;
			while (start > 0 && ok(text.charAt(start - 1)))
				start--;
			int end = offset;
			while (end < text.length() && ok(text.charAt(end)))
				end++;
			comp.select(start, end);
		}

		abstract boolean ok(char c);
	}

	public static final Action selectWordAction = new SelectAction() {
		@Override boolean ok(char c) {
			return isWordChar(c);
		}
	};

	public static final Action selectWordAndDotsAction = new SelectAction() {
		@Override boolean ok(char c) {
			return isWordChar(c) || c=='.';
		}
	};
}