/* Copyright (C) 2006 Christian Schneider * * This file is part of Nomad. * * Nomad is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Nomad is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Nomad; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Created on Jan 21, 2007 */ package net.sf.nmedit.jtheme.component.plaf; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; import java.util.Comparator; import java.util.EventObject; import javax.swing.AbstractAction; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.JTextField; import javax.swing.UIDefaults; import javax.swing.border.Border; import net.sf.nmedit.jpatch.ImageSource; import net.sf.nmedit.jpatch.ModuleDescriptions; import net.sf.nmedit.jpatch.PModule; import net.sf.nmedit.jpatch.PModuleDescriptor; import net.sf.nmedit.jpatch.history.PUndoableEditSupport; import net.sf.nmedit.jpatch.transform.PTModuleMapping; import net.sf.nmedit.jpatch.transform.PTTransformations; import net.sf.nmedit.jtheme.JTContext; import net.sf.nmedit.jtheme.component.JTComponent; import net.sf.nmedit.jtheme.component.JTModule; import net.sf.nmedit.jtheme.util.JThemeUtils; import net.sf.nmedit.nmutils.Platform; import net.sf.nmedit.nmutils.swing.EscapeKeyListener; import net.sf.nmedit.nmutils.swing.LimitedText; public class JTModuleUI extends JTComponentUI { public static final String moduleBorder = "ModuleUI.Border"; public static final String moduleFont = "ModuleUI.Font"; public static final String moduleSelectionBorder = "ModuleUI.Selected.Border"; private static final String moduleTransIcon = "ModuleUI.transformationIcon"; private static final String moduleTransIconHovered = "ModuleUI.transformationIcon.hovered"; private static final Color DEFAULT_MTICON_COLOR = new Color(0x626262); private static final Color DEFAULT_MTICON_COLOR_HOVERED = new Color(0x929292); public static final String moduleActionMapKey = "module.actionMap"; public static JTModuleUI createUI(JComponent c) { return new JTModuleUI((JTModule)c); } private JTModule module; private static JPopupMenu transformPopupMenu; protected JTModuleUI(JTModule module) { this.module = module; } private TitleEditor titleEditor; private void setEditModuleTitle(boolean edit) { if (edit) { titleEditor = new TitleEditor(); module.add(titleEditor.textfield, 0); titleEditor.init(); } else { TitleEditor te = titleEditor; if (te != null) { if (te.textfield != null) module.remove(te.textfield); titleEditor = null; } } } // private static transient Map<ModuleDescriptor, Color> backgroundColors; private String currentTitle; private String currentShortTitle = ""; // never null private String getShortTitle(JTModule module) { String title = module.getTitle(); String shortTitle = currentShortTitle; if (currentTitle == title) return shortTitle; shortTitle = JThemeUtils.getTitleNoColorKey(title); if (shortTitle == null) shortTitle = ""; this.currentTitle = title; this.currentShortTitle = shortTitle; return shortTitle; } protected void updateTitle(JTModule module) { String title = module.getTitle(); if (title != null) { int colorkey = JThemeUtils.getColorKey(title); setModuleColor(colorkey); } else { setModuleColor(0); } } private ImageIcon getModuleTransformationIcon(UIDefaults def) { Icon icon = def.getIcon(moduleTransIcon); if (icon == null || (!(icon instanceof ImageIcon))) { icon = new ImageIcon(renderModuleTransformationIcon(DEFAULT_MTICON_COLOR)); def.put(moduleTransIcon, icon); } return (ImageIcon) icon; } protected Icon getModuleTransformationIconHovered(UIDefaults def) { Icon icon = def.getIcon(moduleTransIconHovered); if (icon == null) { icon = new ImageIcon(renderModuleTransformationIcon(DEFAULT_MTICON_COLOR_HOVERED)); def.put(moduleTransIconHovered, icon); } return icon; } protected Image renderModuleTransformationIcon(Color c) { BufferedImage bi = new BufferedImage(12, 12, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = bi.createGraphics(); try { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); paintModuleTransformationIcon(g2, c, 11, 11); } finally { g2.dispose(); } return bi; } private void paintModuleTransformationIcon(Graphics2D g2, Color c, int w, int h) { g2.setColor(c); // outline g2.drawRect(0, 0, w-1, h-1); g2.drawLine(0, 1, 1, 1); // TL g2.drawLine(w-2, 1, w-1, 1); // TR g2.drawLine(0, h-2, 1, h-2); // BL g2.drawLine(w-2, h-2, w-1, h-2); // BR // arrow Polygon arrow = new Polygon(); arrow.addPoint(2, 3); arrow.addPoint(w-2, 3); arrow.addPoint(w/2, h-2); g2.fill(arrow); } private Border border; private Border selectionBorder; private ImageIcon transformationIcon; private static Insets cachedInsets; private static Font currentFont; private static FontMetrics currentFontMetrics; private FontMetrics getFontMetrics(JComponent c, Font font) { FontMetrics fm = currentFontMetrics; if (currentFont == font && fm != null) return fm; fm = c.getFontMetrics(font); currentFontMetrics = fm; currentFont = font; return fm; } private Insets getInsets(JComponent c) { return cachedInsets = c.getInsets(cachedInsets); } public void installUI(JComponent c) { JTModule module = (JTModule) c; BasicEventHandler beh = createEventHandler(module); beh.install(module); JTContext jtcontext = module.getContext(); UIDefaults uidefaults = jtcontext.getUIDefaults(); if (border == null) border = uidefaults.getBorder(moduleBorder); if (selectionBorder == null) selectionBorder = uidefaults.getBorder(moduleSelectionBorder); Font font = uidefaults.getFont(moduleFont); if (font != null) module.setFont(font); c.setBorder(module.isSelected() ? selectionBorder : border); transformationIcon = getModuleTransformationIcon(uidefaults); } public void uninstallUI(JComponent c) { JTModule module = (JTModule) c; BasicEventHandler eventHandler = getEventHandler(module); if (eventHandler != null) eventHandler.uninstall(module); } private static BasicEventHandler beh; protected BasicEventHandler createEventHandler(JTModule module) { if (beh == null) beh = new BasicEventHandler(); return beh; } protected BasicEventHandler getEventHandler(JTModule module) { Object[] o = module.getListeners(MouseListener.class); for (int i=o.length-1;i>=0;i--) if (o[i] instanceof BasicEventHandler) return (BasicEventHandler) o[i]; return null; } public static class BasicEventHandler extends MouseAdapter implements PropertyChangeListener, FocusListener { private JTModule getModule(EventObject e) { Object src = e.getSource(); if (src != null && src instanceof JTModule) return (JTModule) src; return null; } public void install(JTModule module) { module.addMouseListener(this); module.addFocusListener(this); module.addPropertyChangeListener(this); } public void uninstall(JTModule module) { module.removeMouseListener(this); module.removeFocusListener(this); module.removePropertyChangeListener(this); } public void mousePressed(MouseEvent e) { Component c = e.getComponent(); if (c instanceof JTModule) { if (!c.hasFocus()) { c.requestFocus(); } } JTModule module = getModule(e); if (module == null || module.getUI() == null) return; if (module.getUI().handleTransformPopup(e)) return; if (module.getUI().handleLabelClick(e)) return; } public void propertyChange(PropertyChangeEvent evt) { JTModule module = getModule(evt); if (module == null || module.getUI() == null) return; if (JTModule.PROPERTY_TITLE.equals(evt.getPropertyName())) { module.getUI().updateTitle(module); module.repaint(); } else if (JTModule.PROPERTY_SELECTED.equals(evt.getPropertyName())) { JTModuleUI ui = module.getUI(); module.setBorder(module.isSelected() ? ui.selectionBorder : ui.border); } } public void focusGained(FocusEvent e) { e.getComponent().repaint(); } public void focusLost(FocusEvent e) { e.getComponent().repaint(); } } private boolean handleLabelClick(MouseEvent e) { if (e.getClickCount() == 2 && Platform.isLeftMouseButtonOnly(e) && hitTitle(e.getX(), e.getY())) { setEditModuleTitle(true); return true; } return false; } private boolean handleTransformPopup(MouseEvent me) { if (Platform.isLeftMouseButtonOnly(me) && hitTransformationIcon(me.getX(), me.getY()) && me.getClickCount()==1) { if (transformPopupMenu != null && transformPopupMenu.getInvoker()==me.getComponent()) { // close popup transformPopupMenu.setVisible(false); transformPopupMenu = null; } else { // show popup PModule m = module.getModule(); if (m != null) { createPopup(me, m); return true; } } } return false; } private void createPopup(MouseEvent e, final PModule source) { ModuleDescriptions md = source.getDescriptor().getModules(); ClassLoader loader = md.getModuleDescriptionsClassLoader(); PTTransformations t = md.getTransformations(); if (t == null) return ; PTModuleMapping[] mappings = t.getMappings(source.getDescriptor()); // PTBasicTransformations.sort(mappings); Arrays.sort(mappings, new Comparator<PTModuleMapping>() { public int compare(PTModuleMapping o1, PTModuleMapping o2) { PModuleDescriptor md1 = o1.getTarget(source.getDescriptor()); PModuleDescriptor md2 = o2.getTarget(source.getDescriptor()); return md1.getName().compareTo(md2.getName()); } }); transformPopupMenu = null; if (mappings.length>0) { for (int i=0;i<mappings.length;i++) { if (transformPopupMenu == null) { transformPopupMenu = new JPopupMenu(); } transformPopupMenu.add(new TransformAction(loader, source, mappings[i])); } transformPopupMenu.show(e.getComponent(), e.getX(), e.getY()); } } private static class TransformAction extends AbstractAction { /** * */ private static final long serialVersionUID = -682154271222280870L; private PModule source; private PTModuleMapping mapping; public TransformAction(ClassLoader loader, PModule source, PTModuleMapping mapping) { this.source = source; this.mapping = mapping; PModuleDescriptor md = mapping.getTarget(source.getDescriptor()); if (md == null) { putValue(NAME, "#error"); setEnabled(false); return; } //float covering = ((int)(mapping.getCovering()*10000))/100f; putValue(NAME, md.getName()); String hint = "<html><body><strong>transformed</strong><br/>connector(s): "+mapping.getConnectorCount() +"<br/>parameter(s): "+mapping.getParameterCount() +"</body></html>"; putValue(AbstractAction.SHORT_DESCRIPTION, hint); ImageSource is = md.get16x16IconSource(); if (is != null) { Image img = md.getModules().getImage(is); if (img != null) putValue(SMALL_ICON, new ImageIcon(img)); } } public void actionPerformed(ActionEvent e) { if (isEnabled()) { // XXX color hack PUndoableEditSupport ues = source.getParentComponent().getEditSupport(); String t1 = source.getTitle(); ues.beginUpdate("Transform " + source.getTitle()); try { PModule result = mapping.transform(source); if (result != null) { int colorKey = JThemeUtils.getColorKey(t1); result.setTitle(JThemeUtils.setColorKey(result.getTitle(),colorKey)); } } finally { ues.endUpdate(); } } } } private boolean hitRect(int x, int y, int rwidth, int rheight) { return (0<=x) && (x<rwidth) && (0<=y) && (y<rheight); } public boolean hitTransformationIcon(int x, int y) { if (transformationIcon == null) return false; Insets insets = getInsets(module); x-=insets.left; y-=insets.top; int w = transformationIcon.getIconWidth(); int h = transformationIcon.getIconHeight(); return hitRect(x, y, w, h); } public void paintStaticLayer(Graphics2D g, JTComponent c) { if (c.isOpaque()) { Insets insets = getInsets(c); g.setColor(c.getBackground()); int left = insets.left; Border border = c.getBorder(); if (border == null || border.isBorderOpaque()) { g.fillRect(0, 0, c.getWidth(), c.getHeight()); } else { g.fillRect(insets.left, insets.top, c.getWidth()-(insets.left+insets.right-1), c.getHeight()-(insets.top+insets.bottom-1)); } if (transformationIcon != null) { transformationIcon.paintIcon(c, g, insets.left, insets.top); left+=transformationIcon.getIconWidth(); } left+=2; //padding - even if no icon is painted Font font = module.getFont(); FontMetrics fm = getFontMetrics(module, font); if (module.hasFocus()) { g.setColor(Color.WHITE); g.fillRect(left, insets.left, TITLE_WIDTH, fm.getHeight()); } String title = getShortTitle(module); if (title != null && title.length()>0) { g.setFont(font); g.setColor(Color.BLACK); g.drawString(title, left, insets.top+fm.getHeight()-fm.getDescent()); } } } final int TITLE_WIDTH = 56; // TODO hard coded values are bad public boolean hitTitle(int x, int y) { Insets insets = getInsets(module); int l = insets.left; int t = insets.top; int h = 0; int w = TITLE_WIDTH; // constant width if (transformationIcon != null) { l+=transformationIcon.getIconWidth(); } l+=2; //padding - even if no icon is painted String title = getShortTitle(module); if (title != null && title.length()>0) { FontMetrics fm = getFontMetrics(module, module.getFont()); h = fm.getHeight(); } x-=l; y-=t; return hitRect(x, y, w, h); } private Point getTitleTopLeft() { Insets insets = getInsets(module); int l = insets.left; int t = insets.top; if (transformationIcon != null) { l+=transformationIcon.getIconWidth(); } l+=2; //padding - even if no icon is painted return new Point(l, t); } public void paintDynamicLayer(Graphics2D g, JTComponent c) { // no op } private class TitleEditor implements FocusListener, ActionListener { /** * */ private static final long serialVersionUID = 7086106076035449738L; private JTextField textfield; public TitleEditor() { textfield = new JTextField(new LimitedText(16), getShortTitle(module), 16); textfield.setFont(module.getFont()); textfield.addKeyListener(new EscapeKeyListener(this, 0, "escape")); textfield.addActionListener(this); textfield.addFocusListener(this); textfield.setBorder(null); } public void init() { textfield.setLocation(getTitleTopLeft()); textfield.setSize(textfield.getPreferredSize()); if (!textfield.requestFocusInWindow()) setEditModuleTitle(false); } public void focusGained(FocusEvent e) { // no op } public void focusLost(FocusEvent e) { setEditModuleTitle(false); } public void actionPerformed(ActionEvent e) { Object s = e.getSource(); if (!(s instanceof JTextField)) return; JTextField tf = (JTextField) s; if ("escape".equals(e.getActionCommand())) { setEditModuleTitle(false); } else if (e.getID() == ActionEvent.ACTION_PERFORMED) { String title = module.getTitle(); int colorkey = JThemeUtils.getColorKey(title); title = tf.getText(); if (colorkey>0) title += "$"+colorkey; module.setTitle(title); setEditModuleTitle(false); module.repaint(); } } } private Color fill = null; private Color getFill() { if (fill == null) { Object f = module.getClientProperty("fill"); if (f instanceof Color) fill = (Color) f; } return fill; } protected void setModuleColor(int colorCode){ Color bg = null; if (colorCode>0) { UIDefaults defaults = module.getContext().getUIDefaults(); Color c = defaults.getColor("module.background$"+colorCode); if (c != null) bg = c; } if (bg == null) bg = getFill(); module.setBackground(bg); } }