1 package swingjs.plaf;
\r
3 import jsjava.awt.AWTEvent;
\r
4 import jsjava.awt.Color;
\r
5 import jsjava.awt.Component;
\r
6 import jsjava.awt.Dimension;
\r
7 import jsjava.awt.Font;
\r
8 import jsjava.awt.FontMetrics;
\r
9 import jsjava.awt.Graphics;
\r
10 import jsjava.awt.GraphicsConfiguration;
\r
11 import jsjava.awt.Image;
\r
12 import jsjava.awt.Insets;
\r
13 import jsjava.awt.Point;
\r
14 import jsjava.awt.Rectangle;
\r
15 import jsjava.awt.Toolkit;
\r
16 import jsjava.awt.event.FocusEvent;
\r
17 import jsjava.awt.event.PaintEvent;
\r
18 import jsjava.awt.image.ColorModel;
\r
19 import jsjava.awt.image.ImageObserver;
\r
20 import jsjava.awt.image.ImageProducer;
\r
21 import jsjava.awt.image.VolatileImage;
\r
22 import jsjava.awt.peer.ContainerPeer;
\r
23 import jsjava.awt.peer.LightweightPeer;
\r
24 import jsjavax.swing.AbstractButton;
\r
25 import jsjavax.swing.JComponent;
\r
26 import jsjavax.swing.JRootPane;
\r
27 import jsjavax.swing.plaf.ComponentUI;
\r
28 import jssun.awt.CausedFocusEvent.Cause;
\r
29 import swingjs.JSToolkit;
\r
30 import swingjs.api.DOMNode;
\r
31 import swingjs.api.JQueryObject;
\r
34 * The JSComponentUI subclasses are where all the detailed HTML5 implementation is
\r
35 * carried out. These subclasses mirror the subclasses found in the actual javax.swing.plaf
\r
36 * but have an important difference in that that effectively act as both the UI (a single
\r
37 * implementation for a given AppContext in Swing) and a peer (one implementation per component).
\r
39 * So here we store both the constants for the HTML5 "LookAndFeel", but also
\r
40 * HTML5 objects that really are on the page.
\r
42 * Essentially, at least for now, we are not implementing the HTML5LookAndFeel as such. We'll see how that goes.
\r
46 * @author Bob Hanson
\r
49 public abstract class JSComponentUI extends ComponentUI implements JSEventHandler {
\r
52 * provides a unique id for any component; set on instantiation
\r
54 protected static int incr;
\r
60 protected String id;
\r
63 * the associated JComponent; for which this is c.ui
\r
66 protected JComponent c;
\r
70 * the outermost div holding a component -- left, top, and for a container width and height
\r
72 protected DOMNode outerNode;
\r
75 * the main object for the component, possibly containing others, such as radio button with its label
\r
77 protected DOMNode domNode;
\r
80 * a component or subcomponent that can be enabled/disabled
\r
82 protected DOMNode enableNode;
\r
85 * the part of a component that can hold text
\r
87 protected DOMNode textNode;
\r
90 * the subcomponent with the value field
\r
92 protected DOMNode valueNode;
\r
95 * a component that is being scrolled by a JScrollPane
\r
97 protected DOMNode scrollNode;
\r
101 * a component that is focusable
\r
103 protected DOMNode focusNode;
\r
107 * DOM components pre-defined (JScrollPane)
\r
110 protected Component[] components;
\r
113 * a numerical reference for an ID
\r
118 * not implemented/needed currently. Java handles this nicely
\r
121 protected boolean isTainted = true;
\r
124 * left and top coordinates
\r
126 protected int x, y;
\r
129 * preferred dimension set by user
\r
132 protected Dimension preferredSize;
\r
139 protected boolean isContainer;
\r
142 * linked nodes of this class
\r
145 protected JSComponentUI parent;
\r
148 String currentText;
\r
152 * the scroller for a text area
\r
154 protected JSScrollPaneUI scrollerNode;
\r
158 * uiClassID for this component
\r
160 protected String classID;
\r
163 private DOMNode document, body;
\r
166 protected boolean needPreferred;
\r
170 public JSComponentUI() {
\r
174 protected void setDoc() {
\r
178 * this.document = document;
\r
179 * this.body = document.body;
\r
184 protected abstract void installJSUI();
\r
185 protected abstract void uninstallJSUI();
\r
187 public void installUI(JComponent c) {
\r
188 // already done installJSUI();
\r
191 public void uninstallUI(JComponent c) {
\r
195 protected JQueryObject $(DOMNode node) {
\r
196 return JSToolkit.getJQuery().$(node);
\r
200 * mark this component as in need of update;
\r
201 * maybe not necessary, though. It comes after the value callback
\r
203 public void setTainted() {
\r
207 public abstract DOMNode getDOMObject();
\r
209 public JSComponentUI set(JComponent target) {
\r
213 getPreferredSize(c);
\r
214 installJSUI(); // need to do this immediately, not later
\r
218 protected void newID() {
\r
219 classID = c.getUIClassID();
\r
222 id = c.getHTMLName(classID) + "_" + num;
\r
226 protected DOMNode setCssFont(DOMNode obj, Font font) {
\r
227 if (font != null) {
\r
228 int istyle = font.getStyle();
\r
229 String name = font.getFamily();
\r
230 if (name == "Dialog")
\r
232 DOMNode.setStyles(obj, "font-family", name, "font-size",
\r
233 font.getSize() + "px", "font-style",
\r
234 ((istyle & Font.ITALIC) == 0 ? "normal" : "italic"), "font-weight",
\r
235 ((istyle & Font.BOLD) == 0 ? "normal" : "bold"));
\r
237 if (c.isBackgroundSet())
\r
238 setBackground(c.getBackground());
\r
239 setForeground(c.getForeground());
\r
243 protected DOMNode createDOMObject(String key, String id, String... attr) {
\r
244 DOMNode obj = DOMNode.createElement(key, id);
\r
245 for (int i = 0; i < attr.length;)
\r
246 DOMNode.setAttr(obj, attr[i++], attr[i++]);
\r
247 if (!c.isEnabled())
\r
253 * JSmolCore.js will look for data-UI attribute and, if found, reroute directly here
\r
256 protected void bindMouse(DOMNode node) {
\r
257 DOMNode.setAttr(node, "data-UI", this);
\r
261 * called by JmolCore.js
\r
262 * @return true if handled
\r
264 public boolean handleJSEvent(Object target, int eventType, Object jQueryEvent) {
\r
265 //System.out.println(id + " handling event " + eventType + jQueryEvent);
\r
269 protected DOMNode wrap(String type, String id, DOMNode... elements) {
\r
270 return append(createDOMObject(type, id + type), elements);
\r
273 protected DOMNode append(DOMNode obj, DOMNode[] elements) {
\r
274 for (int i = 0; i < elements.length; i++) {
\r
275 obj.appendChild(elements[i]);
\r
280 protected void debugDump(DOMNode d) {
\r
281 System.out.println(DOMNode.getAttr(d, "outerHTML"));
\r
284 protected static void vCenter(DOMNode obj, int offset) {
\r
285 DOMNode.setStyles(obj,
\r
287 "transform","translateY(" + offset + "%)");
\r
291 * overloaded to allow panel and radiobutton to handle slightly differently
\r
297 protected Dimension setHTMLSize(DOMNode obj, boolean addCSS) {
\r
298 return setHTMLSize1(obj, addCSS, true);
\r
302 * also called by JSRadioButtonUI so that it can calculate
\r
303 * subset dimensions
\r
307 * @param usePreferred
\r
310 @SuppressWarnings("unused")
\r
311 protected Dimension setHTMLSize1(DOMNode node, boolean addCSS, boolean usePreferred) {
\r
315 String w0 = null, h0 = null;
\r
316 DOMNode parentNode = null;
\r
318 if (scrollerNode != null) {
\r
319 w = scrollerNode.c.getWidth();
\r
320 h = scrollerNode.c.getHeight();
\r
321 } else if (usePreferred && preferredSize != null) {
\r
322 // user has set preferred size
\r
323 w = preferredSize.width;
\r
324 h = preferredSize.height;
\r
326 // determine the natural size of this object
\r
327 // save the parent node -- we will need to reset that.
\r
328 parentNode = DOMNode.remove(node);
\r
330 // remove position, width, and height, because those are what we are
\r
335 * w0 = node.style.width;
\r
336 * h0 = node.style.height;
\r
339 DOMNode.setStyles(node, "position", null, "width", null, "height", null);
\r
341 if (DOMNode.getAttr(node, "tagName") == "DIV")
\r
344 div = wrap("div", id + "_temp", node);
\r
345 DOMNode.setStyles(div, "position", "absolute");
\r
347 // process of discovering width and height is facilitated using jQuery
\r
348 // and appending to document.body.
\r
350 body.appendChild(div);
\r
352 //System.out.println(DOMNode.getAttr(node, "outerHTML"));
\r
353 w = (int) Math.ceil($(div).width() + 0.5);
\r
354 h = (int) Math.ceil($(div).height() + 0.5);
\r
355 body.removeChild(div);
\r
358 Dimension size = getCSSDimension(w, h);
\r
360 DOMNode.setStyles(node, "position", "absolute");
\r
361 DOMNode.setSize(node, size.width, size.height);
\r
363 DOMNode.setStyles(node, "position", null);
\r
364 // check to reset width/height after getPreferredSize
\r
366 DOMNode.setStyles(node, "width", w0, "height", h0);
\r
368 if (parentNode != null)
\r
369 parentNode.appendChild(node);
\r
370 //System.out.println("JSComponentUI " + id + " resized to " + w + "x" + h + " parent=" + DOMNode.getAttr(parentNode,"id"));
\r
375 * can be overloaded to allow some special adjustments
\r
381 protected Dimension getCSSDimension(int w, int h) {
\r
382 return new Dimension(w, h);
\r
386 * creates the DOM node and inserts it into the tree at the correct place,
\r
387 * iterating through all children if this is a container
\r
392 protected DOMNode setHTMLElement() {
\r
396 // check for root pane -- not included in DOM
\r
397 JRootPane root = (isContainer ? c.getRootPane() : null);
\r
403 domNode = getDOMObject();
\r
405 // divObj will need recreating if a propertyChange event has occurred
\r
406 // check for content pane -- needs to be added to the HTML5 content layer
\r
409 // needs some work for changes after applet creation
\r
411 if (outerNode == null) {
\r
412 outerNode = wrap("div", id, domNode);
\r
413 if (root != null && root.getContentPane() == c)
\r
414 swingjs.JSToolkit.getHTML5Applet(c)._getContentLayer()
\r
415 .appendChild(outerNode);
\r
420 DOMNode.setStyles(outerNode, "position", "absolute", "left", (x = c.getX())
\r
421 + "px", "top", (y = c.getY()) + "px");
\r
425 // set width from component
\r
427 System.out.println("JSComponentUI container " + id + " " + c.getBounds());
\r
428 DOMNode.setSize(outerNode, c.getWidth(), c.getHeight());
\r
430 // add all children
\r
431 Component[] children = (components == null ? c.getComponents()
\r
433 for (int i = children.length; --i >= 0;) {
\r
434 JSComponentUI ui = JSToolkit.getUI(children[i], false);
\r
436 // Box.Filler has no ui.
\r
439 if (ui.outerNode == null)
\r
440 ui.setHTMLElement();
\r
441 if (ui.outerNode == null) {
\r
442 System.out.println("JSCUI could not add " + ui.c.getName() + " to "
\r
445 outerNode.appendChild(ui.outerNode);
\r
451 // mark as not tainted
\r
452 // debugDump(divObj);
\r
458 * c ignored because JSComponentUI is one per component
\r
460 public Dimension getPreferredSize(JComponent c) {
\r
461 //System.out.println("getPreferredSize for " + id + " " + c.getName());
\r
462 Dimension d = setHTMLSize(getDOMObject(), false);
\r
463 //System.out.println("JSComponentUI " + id + " getting preferred size as " + d);
\r
467 public void paint(Graphics g, JComponent c) {
\r
468 // Note that for now, button graphics
\r
469 // are BEHIND the button. We will need to paint onto the
\r
470 // glass pane for this to work, and then also manage
\r
471 // mouse clicks and key clicks with that in mind.
\r
472 if (c.isOpaque()) {
\r
473 g.setColor(c.getBackground());
\r
474 g.fillRect(0, 0, c.getWidth(), c.getHeight());
\r
478 public void update(Graphics g, JComponent c) {
\r
479 // called from JComponent.paintComponent
\r
480 boolean testing = false;//true;
\r
482 g.setColor(Color.red);
\r
483 g.drawRect(0, 0, c.getWidth(), c.getHeight());
\r
484 System.out.println("drawing " + c.getWidth() + " " + c.getHeight());
\r
490 public Dimension getMinimumSize(JComponent c) {
\r
491 return getPreferredSize(c);
\r
494 public Dimension getMaximumSize(JComponent c) {
\r
495 return null;// getPreferredSize(c);
\r
499 * Returns <code>true</code> if the specified <i>x,y</i> location is
\r
500 * contained within the look and feel's defined shape of the specified
\r
501 * component. <code>x</code> and <code>y</code> are defined to be relative
\r
502 * to the coordinate system of the specified component. Although
\r
503 * a component's <code>bounds</code> is constrained to a rectangle,
\r
504 * this method provides the means for defining a non-rectangular
\r
505 * shape within those bounds for the purpose of hit detection.
\r
507 * @param c the component where the <i>x,y</i> location is being queried;
\r
508 * this argument is often ignored,
\r
509 * but might be used if the UI object is stateless
\r
510 * and shared by multiple components
\r
511 * @param x the <i>x</i> coordinate of the point
\r
512 * @param y the <i>y</i> coordinate of the point
\r
514 * @see jsjavax.swing.JComponent#contains
\r
515 * @see jsjava.awt.Component#contains
\r
517 public boolean contains(JComponent c, int x, int y) {
\r
518 return c.inside(x, y);
\r
522 * Returns an instance of the UI delegate for the specified component.
\r
523 * Each subclass must provide its own static <code>createUI</code>
\r
524 * method that returns an instance of that UI delegate subclass.
\r
525 * If the UI delegate subclass is stateless, it may return an instance
\r
526 * that is shared by multiple components. If the UI delegate is
\r
527 * stateful, then it should return a new instance per component.
\r
528 * The default implementation of this method throws an error, as it
\r
529 * should never be invoked.
\r
531 public static ComponentUI createUI(JComponent c) {
\r
532 // SwingJS so, actually, we don't do this. This class is NOT stateless.
\r
533 // Instead, what we do is to create a unique instance
\r
534 // right in UIManager. The sequence is:
\r
535 // JRadioButton.updateUI()
\r
536 // --> jsjavax.swing.UIManager.getUI(this)
\r
537 // --> jsjavax.swing.UIManager.getDefaults().getUI(target)
\r
538 // --> JSToolkit.getComponentUI(target)
\r
539 // --> creates an instance of JRadioButtonUI and returns
\r
540 // that instance as JRadioButton.ui, which is NOT static.
\r
542 // throw new Error("ComponentUI.createUI not implemented.");
\r
547 * Returns the baseline. The baseline is measured from the top of
\r
548 * the component. This method is primarily meant for
\r
549 * <code>LayoutManager</code>s to align components along their
\r
550 * baseline. A return value less than 0 indicates this component
\r
551 * does not have a reasonable baseline and that
\r
552 * <code>LayoutManager</code>s should not align this component on
\r
555 * This method returns -1. Subclasses that have a meaningful baseline
\r
556 * should override appropriately.
\r
558 * @param c <code>JComponent</code> baseline is being requested for
\r
559 * @param width the width to get the baseline for
\r
560 * @param height the height to get the baseline for
\r
561 * @throws NullPointerException if <code>c</code> is <code>null</code>
\r
562 * @throws IllegalArgumentException if width or height is < 0
\r
563 * @return baseline or a value < 0 indicating there is no reasonable
\r
565 * @see jsjavax.swing.JComponent#getBaseline(int,int)
\r
568 public int getBaseline(JComponent c, int width, int height) {
\r
570 throw new NullPointerException("Component must be non-null");
\r
572 if (width < 0 || height < 0) {
\r
573 throw new IllegalArgumentException(
\r
574 "Width and height must be >= 0");
\r
580 * Returns an enum indicating how the baseline of he component
\r
581 * changes as the size changes. This method is primarily meant for
\r
582 * layout managers and GUI builders.
\r
584 * This method returns <code>BaselineResizeBehavior.OTHER</code>.
\r
585 * Subclasses that support a baseline should override appropriately.
\r
587 * @param c <code>JComponent</code> to return baseline resize behavior for
\r
588 * @return an enum indicating how the baseline changes as the component
\r
590 * @throws NullPointerException if <code>c</code> is <code>null</code>
\r
591 * @see jsjavax.swing.JComponent#getBaseline(int, int)
\r
594 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
\r
597 throw new NullPointerException("Component must be non-null");
\r
599 return Component.BaselineResizeBehavior.OTHER;
\r
603 * overridden in JSPasswordFieldUI
\r
606 public String getJSTextValue() {
\r
607 return (String) DOMNode.getAttr(domNode, valueNode == null ? "innerHTML" : "value");
\r
610 public void notifyPropertyChanged(String prop) {
\r
611 DOMNode obj = null;
\r
613 if (prop == "text") {
\r
614 val = ((AbstractButton) c).getText();
\r
615 if (val.equals(currentText)) // we set it here, then fired the property change
\r
618 if (textNode != null) {
\r
619 prop = "innerHTML";
\r
621 } else if (valueNode != null) {
\r
625 } else if (prop == "preferredSize") {
\r
626 preferredSize = c.getPreferredSize(); // may be null
\r
627 getPreferredSize();
\r
631 System.out.println("JSComponentUI: unrecognized prop: " + prop);
\r
633 System.out.println("JSComponentUI: setting " + id + " " + prop);// + " " + val);
\r
634 setProp(obj, prop, val);
\r
638 protected DOMNode setProp(DOMNode obj, String prop, String val) {
\r
639 return DOMNode.setAttr(obj, prop, val);
\r
643 public boolean isObscured() {
\r
644 JSToolkit.notImplemented("");
\r
649 public boolean canDetermineObscurity() {
\r
650 JSToolkit.notImplemented("");
\r
655 public void setVisible(boolean b) {
\r
656 DOMNode.setStyles(outerNode, "display", b ? "block" : "none");
\r
660 public void setEnabled(boolean b) {
\r
661 if (enableNode != null)
\r
662 DOMNode.setAttr(enableNode, "disabled", (b ? null : "TRUE"));
\r
666 public void paint(Graphics g) {
\r
667 // nothing to do here
\r
671 public void repaint(long tm, int x, int y, int width, int height) {
\r
672 // nothing to do here
\r
676 public void print(Graphics g) {
\r
677 JSToolkit.notImplemented("");
\r
681 public void setBounds(int x, int y, int width, int height, int op) {
\r
685 case SET_CLIENT_SIZE:
\r
686 if (scrollerNode != null) {
\r
687 width = Math.min(width, scrollerNode.c.getWidth());
\r
688 height = Math.min(height, scrollerNode.c.getHeight());
\r
690 System.out.println(id + " setBounds " + x + " " + y + " " + width + " " + height + " op=" + op);
\r
691 if (domNode != null)
\r
692 DOMNode.setSize(domNode, width, height);
\r
698 public void handleEvent(AWTEvent e) {
\r
699 JSToolkit.notImplemented("");
\r
704 public void coalescePaintEvent(PaintEvent e) {
\r
705 JSToolkit.notImplemented("");
\r
710 * Coordinates relative to the document
\r
714 public Point getLocationOnScreen() {
\r
715 Insets offset = (Insets) $(outerNode).offset();
\r
716 return new Point(offset.left, offset.top);
\r
720 public Dimension getPreferredSize() {
\r
721 return getPreferredSize(c);
\r
725 public Dimension getMinimumSize() {
\r
726 JSToolkit.notImplemented("");
\r
727 return getPreferredSize(c);
\r
731 public ColorModel getColorModel() {
\r
732 return Toolkit.getDefaultToolkit().getColorModel();
\r
736 public Toolkit getToolkit() {
\r
737 return Toolkit.getDefaultToolkit();
\r
741 public Graphics getGraphics() {
\r
742 // n/a -- called from java.awt.Component when NOT a LightweightPeer.
\r
747 public FontMetrics getFontMetrics(Font font) {
\r
748 return c.getFontMetrics(font);
\r
752 public void dispose() {
\r
753 JSToolkit.notImplemented("");
\r
757 public void setForeground(Color color) {
\r
758 if (domNode != null)
\r
759 DOMNode.setStyles(domNode, "color", JSToolkit.getCSSColor(color == null ? Color.black : color));
\r
763 public void setBackground(Color color) {
\r
764 if (domNode != null)
\r
765 DOMNode.setStyles(domNode, "background-color", JSToolkit.getCSSColor(color == null ? Color.white : color));
\r
769 public void setFont(Font f) {
\r
770 if (domNode != null)
\r
771 setCssFont(domNode, f);
\r
775 public void updateCursorImmediately() {
\r
776 JSToolkit.notImplemented("");
\r
780 public boolean requestFocus(Component lightweightChild, boolean temporary,
\r
781 boolean focusedWindowChangeAllowed, long time, Cause cause) {
\r
782 if (focusNode == null)
\r
784 $(focusNode).focus();
\r
785 if (textNode != null)
\r
786 $(textNode).select();
\r
791 public boolean isFocusable() {
\r
792 return (focusNode != null);
\r
796 public Image createImage(ImageProducer producer) {
\r
797 JSToolkit.notImplemented("");
\r
802 public Image createImage(int width, int height) {
\r
803 JSToolkit.notImplemented("");
\r
808 public VolatileImage createVolatileImage(int width, int height) {
\r
809 JSToolkit.notImplemented("");
\r
814 public boolean prepareImage(Image img, int w, int h, ImageObserver o) {
\r
815 JSToolkit.notImplemented("");
\r
820 public int checkImage(Image img, int w, int h, ImageObserver o) {
\r
821 JSToolkit.notImplemented("");
\r
826 public GraphicsConfiguration getGraphicsConfiguration() {
\r
827 JSToolkit.notImplemented("");
\r
832 public boolean handlesWheelScrolling() {
\r
833 JSToolkit.notImplemented("");
\r
838 public Image getBackBuffer() {
\r
839 JSToolkit.notImplemented("");
\r
844 public void destroyBuffers() {
\r
845 JSToolkit.notImplemented("");
\r
850 public void reparent(ContainerPeer newContainer) {
\r
851 JSToolkit.notImplemented("");
\r
856 public boolean isReparentSupported() {
\r
857 JSToolkit.notImplemented("");
\r
862 public void layout() {
\r
863 JSToolkit.notImplemented("");
\r
868 public Rectangle getBounds() {
\r
869 JSToolkit.notImplemented("");
\r
873 public boolean hasFocus() {
\r
874 return focusNode != null && focusNode == DOMNode.getAttr(document, "activeElement");
\r
877 public void notifyFocus(boolean focusGained) {
\r
878 Toolkit.getEventQueue().postEvent(new FocusEvent(c, focusGained ? FocusEvent.FOCUS_GAINED : FocusEvent.FOCUS_LOST));
\r