/* * Scriptographer * * This file is part of Scriptographer, a Scripting Plugin for Adobe Illustrator * http://scriptographer.org/ * * Copyright (c) 2002-2010, Juerg Lehni * http://scratchdisk.com/ * * All rights reserved. See LICENSE file for details. * * File created on 02.01.2005. */ package com.scriptographer.adm; import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.Insets; import java.awt.LayoutManager; import java.util.Map; import java.util.regex.Pattern; import com.scratchdisk.list.List; import com.scratchdisk.script.Callable; import com.scratchdisk.util.ConversionUtils; import com.scratchdisk.util.IntegerEnumUtils; import com.scriptographer.ScriptographerEngine; import com.scriptographer.ScriptographerException; import com.scriptographer.adm.layout.TableLayout; /** * Subclasses the NotificationHandler and adds functionality for * defining track and draw callbacks (NotificationHandler can handle * these but not set them). * It also adds the onResize handler as both Item and Dialog need it, * and these are the only subclasses of CallbackHandler * * @author lehni * * @jshide */ public abstract class Component extends NotificationHandler { protected Component parent; /* * Fonts and Text Size */ protected abstract int nativeGetFont(); protected abstract void nativeSetFont(int font); public DialogFont getFont() { // Only call native function if this is actually a native item. // This avoids isValid() exceptions when using fake UI items such // as spacers. if (handle != 0) return IntegerEnumUtils.get(DialogFont.class, nativeGetFont()); return DialogFont.DEFAULT; } public void setFont(DialogFont font) { if (font != null && handle != 0) nativeSetFont(font.value); } public Size getTextSize(String text, int maxWidth, boolean ignoreBreaks) { // Create an image to get a drawer to calculate text sizes Image image = new Image(1, 1, ImageType.RGB); Drawer drawer = image.getDrawer(); drawer.setFont(getFont()); // Split at new lines chars, and measure each line separately String[] lines = ignoreBreaks ? new String[] { text.replaceAll("\r\n|\n|\r", " ") } : text.split("\r\n|\n|\r"); Size size = new Size(0, 0); for (int i = 0; i < lines.length; i++) { String line = lines[i]; if (line.length() == 0) line = " "; // Make sure empty lines are measured too // Calculate the size of this part, using the drawer int width = drawer.getTextWidth(line); if (maxWidth > 0 && width > maxWidth) width = maxWidth; int height = drawer.getTextHeight(line, width + 1); // And add up size if (width > size.width) size.width = width; size.height += height; } drawer.dispose(); return size; } public Size getTextSize(String text) { return getTextSize(text, -1, false); } public boolean isSmall() { DialogFont font = getFont(); switch (font) { case PALETTE: case PALETTE_ITALIC: case PALETTE_BOLD: case PALETTE_BOLD_ITALIC: return true; default: return false; } } /* * Use an activation mechanism for the expensive callback routines (the ones * that get called often). These are only activated if the user actually * sets a callback functions. * * Abstract declarations for native functions that enable and disable the * native track and draw proc procedures: */ private boolean trackCallback = false; /** * @jshide */ public boolean getTrackCallback() { return trackCallback; } abstract protected void nativeSetTrackCallback(boolean enabled); public void setTrackCallback(boolean enabled) { nativeSetTrackCallback(enabled); trackCallback = enabled; } private boolean drawCallback = false; /** * @jshide */ public boolean getDrawCallback() { return drawCallback; } abstract protected void nativeSetDrawCallback(boolean enabled); public void setDrawCallback(boolean enabled) { nativeSetDrawCallback(enabled); drawCallback = enabled; } /** * @param mask Tracker.MASK_* */ abstract public void setTrackMask(int mask); abstract public int getTrackMask(); protected Callable onTrack = null; public void setOnTrack(Callable func) { setTrackCallback(func != null); onTrack = func; } public Callable getOnTrack() { return onTrack; } protected boolean onTrack(Tracker tracker) { // Retrieve through getter so it can be overridden by subclasses, // e.g. HierarchyListBox Callable onTrack = this.getOnTrack(); if (onTrack != null) { Object result = ScriptographerEngine.invoke(onTrack, this, tracker); if (result != null) return ConversionUtils.toBoolean(result); } return true; } protected Callable onDraw = null; public void setOnDraw(Callable func) { setDrawCallback(func != null); onDraw = func; } public Callable getOnDraw() { return onDraw; } protected boolean onDraw(Drawer drawer) { // Retrieve through getter so it can be overridden by subclasses, // e.g. HierarchyListBox Callable onDraw = this.getOnDraw(); if (onDraw != null) { Object result = ScriptographerEngine.invoke(onDraw, this, drawer); if (result != null) return ConversionUtils.toBoolean(result); } return true; } protected Callable onResize = null; public void setOnResize(Callable func) { onResize = func; } public Callable getOnResize() { return onResize; } protected void onResize(int dx, int dy) { // Retrieve through getter so it can be overridden by subclasses, // e.g. HierarchyListBox Callable onResize = this.getOnResize(); if (onResize != null) ScriptographerEngine.invoke(onResize, this, dx, dy); } /* * AWT / Layout Bridge */ protected abstract java.awt.Component getAWTComponent(boolean create); protected AWTContainer getAWTContainer(boolean create) { java.awt.Component component = getAWTComponent(create); if (component == null && !create) return null; if (!(component instanceof AWTContainer)) throw new ScriptographerException("Component does not support sub components."); return (AWTContainer) component; } public void setLayout(LayoutManager mgr) { getAWTContainer(true).setLayout(mgr); } public LayoutManager getLayout() { return getAWTContainer(true).getLayout(); } public boolean usesLayout() { // See if this item or its parent uses a layout. AWTContainer container = getAWTContainer(false); return container != null && container.getLayout() != null || parent != null && parent.usesLayout(); } public void doLayout() { AWTContainer container = getAWTContainer(false); if (container != null) container.doLayout(); } /** * @jshide */ public void setMargin(int top, int right, int bottom, int left) { this.getAWTContainer(true).setInsets(top, left, bottom, right); } public Border getMargin() { return new Border(this.getAWTContainer(true).getInsets()); } public void setMargin(Border margin) { setMargin(margin.top, margin.right, margin.bottom, margin.left); } public void setMargin(int margin) { setMargin(margin, margin, margin, margin); } /** * @jshide */ public void setMargin(int ver, int hor) { setMargin(ver, hor, ver, hor); } public int getMarginLeft() { return getMargin().left; } public void setMarginLeft(int left) { Border margin = getMargin(); margin.left = left; setMargin(margin); } public int getMarginTop() { return getMargin().top; } public void setMarginTop(int top) { Border margin = getMargin(); margin.top = top; setMargin(margin); } public int getMarginRight() { return getMargin().right; } public void setMarginRight(int right) { Border margin = getMargin(); margin.right = right; setMargin(margin); } public int getMarginBottom() { return getMargin().bottom; } public void setMarginBottom(int bottom) { Border margin = getMargin(); margin.bottom = bottom; setMargin(margin); } protected void addComponent(Component component) { // Do not really add component here, as only ItemGroup uses it. // Do not throw an UnsupportedOperationException though as it is also // used by Frame, which just applies its own layout to the added items // but does not actually nest them inside. component.parent = this; } protected void removeComponent(Component component) { // See above. component.parent = null; } private Content content = null; public Content getContent() { if (content == null) content = new Content(this); return content; } public void setContent(Component[] elements) { // The default for setting array elements is a flow layout if (this.getLayout() == null) this.setLayout(new FlowLayout()); Content content = getContent(); content.removeAll(); content.addAll(elements); } public void setContent(List<? extends Component> elements) { // The default for setting array elements is a flow layout if (this.getLayout() == null) this.setLayout(new FlowLayout()); Content content = getContent(); content.removeAll(); content.addAll(elements); } private static final Pattern borderLayoutPattern = Pattern.compile( "^(north|south|east|west|center|first|last|before|after)$", Pattern.CASE_INSENSITIVE); public void setContent(Map<String,? extends Component> elements) { // Find out what kind of layout we have by checking the keys // in the map: if (this.getLayout() == null) { boolean borderLayout = true; for (String key : elements.keySet()) borderLayout = borderLayoutPattern.matcher(key).matches(); if (borderLayout) { this.setLayout(new BorderLayout()); } else { // TODO: Figure out amount of rows and columns by analyzing the keys. // for now we don't do that... this.setLayout(new TableLayout("preferred", "preferred")); } } Content content = getContent(); content.removeAll(); content.addAll(elements); } /** * @jshide */ public void addToContent(Component component) { getContent().add(component); } /** * @jshide */ public void addToContent(Component component, String constraints) { getContent().put(constraints, component); } /** * @jshide */ public void addToContent(Component component, int index) { getContent().add(index, component); } /** * @jshide */ public void removeFromContent(Component component) { getContent().remove(component); } /** * @jshide */ public void removeFromContent(int index) { getContent().remove(index); } /** * @jshide */ public void removeFromContent(String constraints) { getContent().remove(constraints); } /** * @jshide */ public void removeContent() { getContent().removeAll(); } /** * An abstract class that adds some commonly used things like * setInsets to java.awt.Container. * * @author lehni * */ abstract class AWTContainer extends java.awt.Container implements ComponentWrapper { Insets insets; public void setInsets(int top, int left, int bottom, int right) { insets = new Insets(top, left, bottom, right); } public Insets getInsets() { return insets; } public void doLayout() { super.doLayout(); // Walk through all the items do their layout as well: for (java.awt.Component component : getComponents()) component.doLayout(); } } }