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