blob: ba484bf35fa32daf657e04167e0c87aa16fa7ab3 [file] [log] [blame]
package org.jetbrains.plugins.javaFX.sceneBuilder;
import com.oracle.javafx.scenebuilder.kit.editor.EditorController;
import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController;
import com.oracle.javafx.scenebuilder.kit.editor.panel.hierarchy.treeview.HierarchyTreeViewController;
import com.oracle.javafx.scenebuilder.kit.editor.panel.inspector.InspectorPanelController;
import com.oracle.javafx.scenebuilder.kit.editor.panel.library.LibraryPanelController;
import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup;
import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup;
import com.oracle.javafx.scenebuilder.kit.fxom.*;
import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.embed.swing.JFXPanel;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.control.SplitPane;
import javax.swing.*;
import java.net.URL;
import java.util.*;
/**
* @author Alexander Lobas
*/
public class SceneBuilderImpl implements SceneBuilder {
private final URL myFileURL;
private final EditorCallback myEditorCallback;
private final JFXPanel myPanel = new JFXPanel();
private EditorController myEditorController;
private volatile boolean mySkipChanges;
private ChangeListener<Number> myListener;
private ChangeListener<Number> mySelectionListener;
private final Map<String, int[][]> mySelectionState = new FixedHashMap<String, int[][]>(16);
public SceneBuilderImpl(URL url, EditorCallback editorCallback) {
myFileURL = url;
myEditorCallback = editorCallback;
Platform.setImplicitExit(false);
Platform.runLater(new Runnable() {
@Override
public void run() {
create();
}
});
}
private void create() {
myEditorController = new EditorController();
HierarchyTreeViewController componentTree = new HierarchyTreeViewController(myEditorController);
ContentPanelController canvas = new ContentPanelController(myEditorController);
InspectorPanelController propertyTable = new InspectorPanelController(myEditorController);
LibraryPanelController palette = new LibraryPanelController(myEditorController);
loadFile();
startChangeListener();
SplitPane leftPane = new SplitPane();
leftPane.setOrientation(Orientation.VERTICAL);
leftPane.getItems().addAll(palette.getPanelRoot(), componentTree.getPanelRoot());
leftPane.setDividerPositions(0.5, 0.5);
SplitPane.setResizableWithParent(leftPane, Boolean.FALSE);
SplitPane.setResizableWithParent(propertyTable.getPanelRoot(), Boolean.FALSE);
SplitPane mainPane = new SplitPane();
mainPane.getItems().addAll(leftPane, canvas.getPanelRoot(), propertyTable.getPanelRoot());
mainPane.setDividerPositions(0.11036789297658862, 0.8963210702341137);
myPanel.setScene(new Scene(mainPane, -1, -1, true, SceneAntialiasing.BALANCED));
}
@Override
public JComponent getPanel() {
return myPanel;
}
@Override
public void reloadFile() {
if (myEditorController != null) {
Platform.runLater(new Runnable() {
@Override
public void run() {
loadFile();
}
});
}
}
private void startChangeListener() {
myListener = new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (!mySkipChanges) {
myEditorCallback.saveChanges(myEditorController.getFxmlText());
}
}
};
mySelectionListener = new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if (!mySkipChanges) {
int[][] state = getSelectionState();
if (state != null) {
mySelectionState.put(myEditorController.getFxmlText(), state);
}
}
}
};
myEditorController.getJobManager().revisionProperty().addListener(myListener);
myEditorController.getSelection().revisionProperty().addListener(mySelectionListener);
}
@Override
public void close() {
if (myEditorController != null) {
if (mySelectionListener != null) {
myEditorController.getSelection().revisionProperty().removeListener(mySelectionListener);
}
if (myListener != null) {
myEditorController.getJobManager().revisionProperty().removeListener(myListener);
}
}
}
private void loadFile() {
mySkipChanges = true;
try {
String fxmlText = FXOMDocument.readContentFromURL(myFileURL);
myEditorController.setFxmlTextAndLocation(fxmlText, myFileURL);
int[][] selectionState = mySelectionState.get(fxmlText);
if (selectionState != null) {
restoreSelection(selectionState);
}
}
catch (Throwable e) {
myEditorCallback.handleError(e);
}
finally {
mySkipChanges = false;
}
}
private int[][] getSelectionState() {
AbstractSelectionGroup group = myEditorController.getSelection().getGroup();
if (group instanceof ObjectSelectionGroup) {
Set<FXOMObject> items = ((ObjectSelectionGroup)group).getItems();
int[][] state = new int[items.size()][];
int index = 0;
for (FXOMObject item : items) {
IntArrayList path = new IntArrayList();
componentToPath(item, path);
state[index++] = path.toArray();
}
return state;
}
return null;
}
private static void componentToPath(FXOMObject component, IntArrayList path) {
FXOMObject parent = component.getParentObject();
if (parent != null) {
path.add(0, component.getParentProperty().getValues().indexOf(component));
componentToPath(parent, path);
}
}
private void restoreSelection(int[][] state) {
Collection<FXOMObject> newSelection = new ArrayList<FXOMObject>();
FXOMObject rootComponent = myEditorController.getFxomDocument().getFxomRoot();
for (int[] path : state) {
pathToComponent(newSelection, rootComponent, path, 0);
}
myEditorController.getSelection().select(newSelection);
}
private static void pathToComponent(Collection<FXOMObject> components, FXOMObject component, int[] path, int index) {
if (index == path.length) {
components.add(component);
}
else {
List<FXOMObject> children = Collections.emptyList();
Map<PropertyName, FXOMProperty> properties = ((FXOMInstance)component).getProperties();
for (Map.Entry<PropertyName, FXOMProperty> entry : properties.entrySet()) {
FXOMProperty value = entry.getValue();
if (value instanceof FXOMPropertyC) {
children = ((FXOMPropertyC)value).getValues();
break;
}
}
int componentIndex = path[index];
if (0 <= componentIndex && componentIndex < children.size()) {
pathToComponent(components, children.get(componentIndex), path, index + 1);
}
}
}
private static class FixedHashMap<K, V> extends HashMap<K, V> {
private final int mySize;
private final List<K> myKeys = new LinkedList<K>();
public FixedHashMap(int size) {
mySize = size;
}
@Override
public V put(K key, V value) {
if (!myKeys.contains(key)) {
if (myKeys.size() >= mySize) {
remove(myKeys.remove(0));
}
myKeys.add(key);
}
return super.put(key, value);
}
@Override
public V get(Object key) {
if (myKeys.contains(key)) {
int index = myKeys.indexOf(key);
int last = myKeys.size() - 1;
myKeys.set(index, myKeys.get(last));
myKeys.set(last, (K)key);
}
return super.get(key);
}
}
private static final int[] EMPTY_INTS = new int[0];
private static class IntArrayList {
private int[] myData = EMPTY_INTS;
private int mySize;
public void add(int index, int element) {
if (index > mySize || index < 0) {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + mySize);
}
ensureCapacity(mySize + 1);
System.arraycopy(myData, index, myData, index + 1, mySize - index);
myData[index] = element;
mySize++;
}
public void ensureCapacity(int minCapacity) {
int oldCapacity = myData.length;
if (minCapacity > oldCapacity) {
int[] oldData = myData;
int newCapacity = oldCapacity * 3 / 2 + 1;
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
myData = new int[newCapacity];
System.arraycopy(oldData, 0, myData, 0, mySize);
}
}
public int[] toArray() {
int[] result = new int[mySize];
System.arraycopy(myData, 0, result, 0, mySize);
return result;
}
}
}