package com.sun.javafx.webkit.theme;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.collections.FXCollections;
import javafx.geometry.Orientation;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Control;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.RadioButton;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Slider;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Region;
import java.lang.ref.WeakReference;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import com.sun.javafx.logging.PlatformLogger;
import com.sun.javafx.logging.PlatformLogger.Level;
import com.sun.javafx.webkit.Accessor;
import com.sun.webkit.LoadListenerClient;
import com.sun.webkit.graphics.Ref;
import com.sun.webkit.graphics.RenderTheme;
import com.sun.webkit.graphics.WCGraphicsContext;
import com.sun.webkit.graphics.WCSize;
import javafx.application.Application;
public final class RenderThemeImpl extends RenderTheme {
private final static PlatformLogger log = PlatformLogger.getLogger(RenderThemeImpl.class.getName());
enum WidgetType {
TEXTFIELD (0),
BUTTON (1),
CHECKBOX (2),
RADIOBUTTON (3),
MENULIST (4),
MENULISTBUTTON (5),
SLIDER (6),
PROGRESSBAR (7),
METER (8),
SCROLLBAR (9);
private static final HashMap<Integer, WidgetType> map = new HashMap<Integer, WidgetType>();
private final int value;
private WidgetType(int value) { this.value = value; }
static { for (WidgetType v: values()) map.put(v.value, v); }
private static WidgetType convert(int index) { return map.get(index); }
};
private Accessor accessor;
private boolean isDefault;
private Pool<FormControl> pool;
static final class Pool<T extends Widget> {
private static final int INITIAL_CAPACITY = 100;
private int capacity = INITIAL_CAPACITY;
private final LinkedHashMap<Long, Integer> ids = new LinkedHashMap<>();
private final Map<Long, WeakReference<T>> pool = new HashMap<>();
private final Notifier<T> notifier;
private final String type;
interface Notifier<T> {
public void notifyRemoved(T control);
}
Pool(Notifier<T> notifier, Class<T> type) {
this.notifier = notifier;
this.type = type.getSimpleName();
}
T get(long id) {
if (log.isLoggable(Level.FINE)) {
log.fine("type: {0}, size: {1}, id: 0x{2}",
new Object[] {type, pool.size(), Long.toHexString(id)});
}
assert ids.size() == pool.size();
WeakReference<T> controlRef = pool.get(id);
if (controlRef == null) {
return null;
}
T control = controlRef.get();
if (control == null) {
return null;
}
Integer value = ids.remove(Long.valueOf(id));
ids.put(id, value);
return control;
}
void put(long id, T control, int updateContentCycleID) {
if (log.isLoggable(Level.FINEST)) {
log.finest("size: {0}, id: 0x{1}, control: {2}",
new Object[] {pool.size(), Long.toHexString(id), control.getType()});
}
if (ids.size() >= capacity) {
Long _id = ids.keySet().iterator().next();
Integer cycleID = ids.get(_id);
if (cycleID != updateContentCycleID) {
ids.remove(_id);
T _control = pool.remove(_id).get();
if (_control != null) {
notifier.notifyRemoved(_control);
}
} else {
capacity = Math.min(capacity, (int)Math.ceil(Integer.MAX_VALUE/2)) * 2;
}
}
ids.put(id, updateContentCycleID);
pool.put(id, new WeakReference<T>(control));
}
void clear() {
if (log.isLoggable(Level.FINE)) {
log.fine("size: " + pool.size() + ", controls: " + pool.values());
}
if (pool.size() == 0) {
return;
}
ids.clear();
for (WeakReference<T> controlRef : pool.values()) {
T control = controlRef.get();
if (control != null) {
notifier.notifyRemoved(control);
}
}
pool.clear();
capacity = INITIAL_CAPACITY;
}
}
static class ViewListener implements InvalidationListener {
private final Pool pool;
private final Accessor accessor;
private LoadListenerClient loadListener;
ViewListener(Pool pool, Accessor accessor) {
this.pool = pool;
this.accessor = accessor;
}
@Override public void invalidated(Observable ov) {
pool.clear();
if (accessor.getPage() != null && loadListener == null) {
loadListener = new LoadListenerClient() {
@Override
public void dispatchLoadEvent(long frame, int state, String url,
String contentType, double progress, int errorCode)
{
if (state == LoadListenerClient.PAGE_STARTED) {
pool.clear();
}
}
@Override
public void dispatchResourceLoadEvent(long frame, int state, String url,
String contentType, double progress,
int errorCode) {}
};
accessor.getPage().addLoadListenerClient(loadListener);
}
}
}
public RenderThemeImpl(final Accessor accessor) {
this.accessor = accessor;
pool = new Pool<FormControl>(fc -> {
accessor.removeChild(fc.asControl());
}, FormControl.class);
accessor.addViewListener(new ViewListener(pool, accessor));
}
public RenderThemeImpl() {
isDefault = true;
}
private void ensureNotDefault() {
if (isDefault) {
throw new IllegalStateException("the method should not be called in this context");
}
}
@Override
protected Ref createWidget(
long id,
int widgetIndex,
int state,
int w, int h,
int bgColor,
ByteBuffer extParams)
{
ensureNotDefault();
FormControl fc = pool.get(id);
WidgetType type = WidgetType.convert(widgetIndex);
if (fc == null || fc.getType() != type) {
if (fc != null) {
accessor.removeChild(fc.asControl());
}
switch (type) {
case TEXTFIELD:
fc = new FormTextField();
break;
case BUTTON:
fc = new FormButton();
break;
case CHECKBOX:
fc = new FormCheckBox();
break;
case RADIOBUTTON:
fc = new FormRadioButton();
break;
case MENULIST:
fc = new FormMenuList();
break;
case MENULISTBUTTON:
fc = new FormMenuListButton();
break;
case SLIDER:
fc = new FormSlider();
break;
case PROGRESSBAR:
fc = new FormProgressBar(WidgetType.PROGRESSBAR);
break;
case METER:
fc = new FormProgressBar(WidgetType.METER);
break;
default:
log.severe("unknown widget index: {0}", widgetIndex);
return null;
}
fc.asControl().setFocusTraversable(false);
pool.put(id, fc, accessor.getPage().getUpdateContentCycleID());
accessor.addChild(fc.asControl());
}
fc.setState(state);
Control ctrl = fc.asControl();
if (ctrl.getWidth() != w || ctrl.getHeight() != h) {
ctrl.resize(w, h);
}
if (ctrl.isManaged()) {
ctrl.setManaged(false);
}
if (type == WidgetType.SLIDER) {
Slider slider = (Slider)ctrl;
extParams.order(ByteOrder.nativeOrder());
slider.setOrientation(extParams.getInt()==0
? Orientation.HORIZONTAL
: Orientation.VERTICAL);
slider.setMax(extParams.getFloat());
slider.setMin(extParams.getFloat());
slider.setValue(extParams.getFloat());
} else if (type == WidgetType.PROGRESSBAR) {
ProgressBar progress = (ProgressBar)ctrl;
extParams.order(ByteOrder.nativeOrder());
progress.setProgress(extParams.getInt() == 1
? extParams.getFloat()
: progress.INDETERMINATE_PROGRESS);
} else if (type == WidgetType.METER) {
ProgressBar progress = (ProgressBar) ctrl;
extParams.order(ByteOrder.nativeOrder());
progress.setProgress(extParams.getFloat());
progress.setStyle(getMeterStyle(extParams.getInt()));
}
return new FormControlRef(fc);
}
private String getMeterStyle(int region) {
switch (region) {
case 1:
return "-fx-accent: yellow";
case 2:
return "-fx-accent: red";
default:
return "-fx-accent: green";
}
}
@Override
public void drawWidget(
WCGraphicsContext g,
final Ref widget,
int x, int y)
{
ensureNotDefault();
FormControl fcontrol = ((FormControlRef) widget).asFormControl();
if (fcontrol != null) {
Control control = fcontrol.asControl();
if (control != null) {
g.saveState();
g.translate(x, y);
Renderer.getRenderer().render(control, g);
g.restoreState();
}
}
}
@Override
public WCSize getWidgetSize(Ref widget) {
ensureNotDefault();
FormControl fcontrol = ((FormControlRef)widget).asFormControl();
if (fcontrol != null) {
Control control = fcontrol.asControl();
return new WCSize((float)control.getWidth(), (float)control.getHeight());
}
return new WCSize(0, 0);
}
@Override
protected int getRadioButtonSize() {
String style = Application.getUserAgentStylesheet();
if (Application.STYLESHEET_MODENA.equalsIgnoreCase(style)) {
return 20;
} else if (Application.STYLESHEET_CASPIAN.equalsIgnoreCase(style)) {
return 19;
}
return 20;
}
@Override
protected int getSelectionColor(int index) {
switch (index) {
case BACKGROUND: return 0xff0093ff;
case FOREGROUND: return 0xffffffff;
default: return 0;
}
}
private static boolean hasState(int state, int mask) {
return (state & mask) != 0;
}
private static final class FormControlRef extends Ref {
private final WeakReference<FormControl> fcRef;
private FormControlRef(FormControl fc) {
this.fcRef = new WeakReference<FormControl>(fc);
}
private FormControl asFormControl() {
return fcRef.get();
}
}
interface Widget {
public WidgetType getType();
}
private interface FormControl extends Widget {
public Control asControl();
public void setState(int state);
}
private static final class FormButton extends Button implements FormControl {
@Override public Control asControl() { return this; }
@Override public void setState(int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
setPressed(hasState(state, RenderTheme.PRESSED));
if (isPressed()) arm(); else disarm();
}
@Override public WidgetType getType() { return WidgetType.BUTTON; };
}
private static final class FormTextField extends TextField implements FormControl {
private FormTextField() {
setStyle("-fx-display-caret: false");
}
@Override public Control asControl() { return this; }
@Override public void setState(int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setEditable(hasState(state, RenderTheme.READ_ONLY));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
}
@Override public WidgetType getType() { return WidgetType.TEXTFIELD; };
}
private static final class FormCheckBox extends CheckBox implements FormControl {
@Override public Control asControl() { return this; }
@Override public void setState(int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
setSelected(hasState(state, RenderTheme.CHECKED));
}
@Override public WidgetType getType() { return WidgetType.CHECKBOX; };
}
private static final class FormRadioButton extends RadioButton implements FormControl {
@Override public Control asControl() { return this; }
@Override public void setState(int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
setSelected(hasState(state, RenderTheme.CHECKED));
}
@Override public WidgetType getType() { return WidgetType.RADIOBUTTON; };
}
private static final class FormSlider extends Slider implements FormControl {
@Override public Control asControl() { return this; }
@Override public void setState(int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
}
@Override public WidgetType getType() { return WidgetType.SLIDER; };
}
private static final class FormProgressBar extends ProgressBar implements FormControl {
private final WidgetType type;
private FormProgressBar(WidgetType type) {
this.type = type;
}
@Override public Control asControl() { return this; }
@Override public void setState(int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
}
@Override public WidgetType getType() { return type; };
}
private static final class extends ChoiceBox implements FormControl {
private () {
List<String> l = new ArrayList<String>();
l.add("");
setItems(FXCollections.observableList(l));
}
@Override public Control () { return this; }
@Override public void (int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setFocused(hasState(state, RenderTheme.FOCUSED));
setHover(hasState(state, RenderTheme.HOVERED) && !isDisabled());
}
@Override public WidgetType () { return WidgetType.MENULIST; };
}
private static final class extends Button implements FormControl {
private static final int = 20;
private static final int = 16;
@Override public Control () { return this; }
@Override public void (int state) {
setDisabled(! hasState(state, RenderTheme.ENABLED));
setHover(hasState(state, RenderTheme.HOVERED));
setPressed(hasState(state, RenderTheme.PRESSED));
if (isPressed()) arm(); else disarm();
}
private () {
setSkin(new Skin());
setFocusTraversable(false);
getStyleClass().add("form-select-button");
}
@Override public void (double width, double height) {
width = height > MAX_WIDTH ? MAX_WIDTH : height < MIN_WIDTH ? MIN_WIDTH : height;
super.resize(width, height);
setTranslateX(-width);
}
private final class extends SkinBase {
() {
super(FormMenuListButton.this);
Region arrow = new Region();
arrow.getStyleClass().add("arrow");
arrow.setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE);
BorderPane pane = new BorderPane();
pane.setCenter(arrow);
getChildren().add(pane);
}
}
@Override public WidgetType () { return WidgetType.MENULISTBUTTON; };
}
}