![JAR search and dependency download from the Maven repository](/logo.png)
com.openfin.desktop.DockingManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of openfin-snap-dock Show documentation
Show all versions of openfin-snap-dock Show documentation
OpenFin Window Docking Manager
The newest version!
package com.openfin.desktop;
import com.openfin.desktop.win32.ExternalWindowObserver;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Manages snap and docking of Openfin windows and Java windows
*
* Created by wche on 3/5/2016.
*
*/
public class DockingManager {
private final static Logger logger = LoggerFactory.getLogger(DockingManager.class.getName());
private DesktopConnection desktopConnection;
private int snappingDistance = 20; // snap if distance of 2 windows equals snappingDistance
private Map memberMap = new HashMap();
private java.util.List dockCandidates = new ArrayList(); // stores 2 windows that are close enough to dock.
private BoundsChangingListener boundsChangingListener;
private BoundsChangeListener boundsChangeListener;
private String javaWindowParentUuid;
/**
* Constructor for Docking Manager
*
* In order for a Java window to be dockable, it needs to be assigned a parent HTML5 app as a parent app. The html5 app needs to be active when
* registerJavaWindow is called.
*
* @param desktopConnection connection to OpenFin Runtime
* @param javaWindowParentUuid UUID of parent app each Java window will be assigned
*/
public DockingManager(DesktopConnection desktopConnection, String javaWindowParentUuid) throws Exception{
if (desktopConnection.isConnected()) {
this.desktopConnection = desktopConnection;
this.javaWindowParentUuid = javaWindowParentUuid;
this.boundsChangeListener = new BoundsChangeListener();
this.boundsChangingListener = new BoundsChangingListener();
initRequestHandlers();
} else {
logger.error("DesktopConnection is not connected");
throw new DesktopException("DesktopConnection is not connected");
}
}
private void initRequestHandlers() throws Exception {
this.desktopConnection.getInterApplicationBus().subscribe("*", "undock-window", (uuid, topic, data) -> {
processUndockRequest((JSONObject)data);
});
this.desktopConnection.getInterApplicationBus().subscribe("*", "register-docking-window", (uuid, topic, data) -> {
processRegisterRequest((JSONObject)data);
});
this.desktopConnection.getInterApplicationBus().subscribe("*", "unregister-docking-window", (uuid, topic, data) -> {
processUnregisterRequest((JSONObject)data);
});
}
/**
* Register a window with DockingManager
*
* @param window
*/
public synchronized void registerWindow(Window window) {
String newKey = WindowMember.getMemberKey(window);
if (this.memberMap.get(newKey) == null) {
this.addWindow(window);
} else {
logger.error(String.format("%s already registered", newKey));
}
}
/**
* Register a java.awt.Window with DockingManager
*
* @param javaWindowName a name by which the window will be referenced in OpenFin API
* @param window
* @param ackListener
*/
public void registerJavaWindow(String javaWindowName, java.awt.Window window, AckListener ackListener) {
CountDownLatch latch = new CountDownLatch(1);
try {
ExternalWindowObserver externalWindowObserver = new ExternalWindowObserver(desktopConnection.getPort(), javaWindowParentUuid, javaWindowName, window,
new AckListener() {
@Override
public void onSuccess(Ack ack) {
if (ack.isSuccessful()) {
Window ofWindow = Window.wrap(javaWindowParentUuid, javaWindowName, desktopConnection);
registerWindow(ofWindow);
}
latch.countDown();
}
@Override
public void onError(Ack ack) {
logger.error("Error registering java window ", ack.getReason());
latch.countDown();
if (ackListener != null) {
ackListener.onError(ack);
}
}
});
latch.await(10, TimeUnit.SECONDS);
if (latch.getCount() == 0) {
WindowMember member = memberMap.get(WindowMember.getMemberKey(javaWindowParentUuid, javaWindowName));
if (member != null) {
member.setExternalWindowObserver(externalWindowObserver);
ackSuccess(ackListener);
} else {
ackError(ackListener, "Error registering Java window");
}
} else {
ackError(ackListener, "Error registering Java window");
}
} catch (Exception e) {
logger.error("Error registering external window", e);
latch.countDown();
ackError(ackListener, e.getMessage());
}
}
private void ackSuccess(AckListener ackListener) {
if (ackListener != null) {
ackListener.onSuccess(new Ack(new JSONObject(), this));
}
}
private void ackError(AckListener ackListener, String reason) {
if (ackListener != null) {
JSONObject obj = new JSONObject();
obj.put("reason", reason);
ackListener.onError(new Ack(obj, this));
}
}
/**
* Add a window to managed list and set up event listeners for the window
*
* @param window
*/
private void addWindow(Window window) {
WindowMember member = new WindowMember(window, this.desktopConnection);
this.memberMap.put(member.getKey(), member);
this.addBoundsChangeListener("disabled-frame-bounds-changing", member, this.boundsChangingListener);
this.addBoundsChangeListener("disabled-frame-bounds-changed", member, this.boundsChangeListener);
// This class will manage frame move and resize
window.disableFrame(new AckListener() {
@Override
public void onSuccess(Ack ack) {
logger.debug(String.format("Frame disabled %s", member.getKey()));
}
@Override
public void onError(Ack ack) {
logger.error(String.format("onError disable Frame disabled %s %s", member.getKey(), ack.getReason()));
}
});
// get initial bounds
window.getBounds(bounds -> {
member.setBounds(bounds);
}, new AckListener() {
@Override
public void onSuccess(Ack ack) {
}
@Override
public void onError(Ack ack) {
logger.error(String.format("onError getBounds %s", ack.getReason()));
}
});
}
public synchronized void unregisterWindow(String applicationUuid, String windowName) {
WindowMember member = this.memberMap.get(WindowMember.getMemberKey(applicationUuid, windowName));
if (member != null) {
removeWindow(member);
} else {
logger.error(String.format("Window not registered %s", WindowMember.getMemberKey(applicationUuid, windowName)));
}
}
private void removeWindow(WindowMember member) {
logger.debug(String.format("Removing %s", member.getKey()));
this.memberMap.remove(member.getKey());
member.getWindow().removeEventListener("disabled-frame-bounds-changing", this.boundsChangingListener, null);
member.getWindow().removeEventListener("disabled-frame-bounds-changed", this.boundsChangeListener, null);
member.getWindow().enableFrame(null);
if (member.getExternalWindowObserver() != null) {
try {
member.getExternalWindowObserver().dispose();
} catch (Exception e) {
logger.error(String.format("Error disposing ExternalWindowObserver %s", member.getKey()), e);
}
}
}
/**
* clean up everything
*/
public void dispose() {
logger.debug("calling dispose");
HashSet members = new HashSet<>(this.memberMap.values());
members.forEach(m -> removeWindow(m));
}
private void addBoundsChangeListener(final String eventName, final WindowMember member, EventListener eventListener) {
member.getWindow().addEventListener(eventName, eventListener, new AckListener() {
@Override
public void onSuccess(Ack ack) {
if (!ack.isSuccessful()) {
logger.error(String.format("Failed to add event listener %s to window %s", eventName, member.getKey()));
}
}
@Override
public void onError(Ack ack) {
logger.error(String.format("Failed to add event listener %s to window %s", eventName, member.getKey()));
}
});
}
private WindowBounds parseBounds(JSONObject data) {
WindowBounds bounds = new WindowBounds(JsonUtils.getIntegerValue(data, "top", null),
JsonUtils.getIntegerValue(data, "left", null),
JsonUtils.getIntegerValue(data, "width", null),
JsonUtils.getIntegerValue(data, "height", null)
);
return bounds;
}
private WindowMember parseMember(JSONObject data) {
WindowMember member = null;
String appUuid = data.getString("uuid");
String windowName = data.getString("name");
if (appUuid != null && windowName != null) {
member = this.memberMap.get(WindowMember.getMemberKey(appUuid, windowName));
}
return member;
}
/**
* Process disabled-frame-bounds-changing event
*
* @param movingMember event origin
* @param bounds new bounds from the event
*/
private void onWindowMoving(WindowMember movingMember, WindowBounds bounds) {
WindowBounds movingBounds = bounds;
if (!movingMember.isDocked()) {
this.dockCandidates.clear();
Iterator memberIterator = this.memberMap.values().iterator();
while (memberIterator.hasNext()) {
WindowMember another = memberIterator.next();
if (!another.equals(movingMember)) {
WindowBounds snapBounds = shouldSnap(movingMember, movingBounds, another);
if (snapBounds != null) {
this.dockCandidates.add(movingMember); // actual docking happens in boundsChanged
this.dockCandidates.add(another);
logger.debug(String.format("Snapping %s", movingMember.getKey()));
movingBounds = snapBounds;
break;
}
}
}
} else {
logger.debug("Bounds changing already docked " + movingMember.getKey());
}
movingMember.updateBounds(movingBounds);
}
/**
* Process disabled-frame-bounds-changed
*
* @param movingMember event orign
* @param bounds new bounds in the event
*/
private void onWindowMoved(WindowMember movingMember, WindowBounds bounds) {
if (dockCandidates.size() == 2) {
WindowMember movingCandidate = dockCandidates.get(0);
WindowMember targetCandidate = dockCandidates.get(1);
if (movingCandidate.equals(movingCandidate)) {
targetCandidate.dock(movingCandidate);
} else {
logger.error(String.format("Docking candidate mismatch %s %s", movingMember.getKey(), movingCandidate.getKey()));
}
dockCandidates.clear();
} else {
movingMember.updateBounds(bounds);
}
}
/**
* Check if movingMember should be snapped to anchorMember
*
* @param movingMember moving
* @param movingBounds potential snap target
* @param anchorMember new bounds of movingMember while being moved
* @return new bounds for movingMember to move for snapping
*/
private WindowBounds shouldSnap(WindowMember movingMember, WindowBounds movingBounds, WindowMember anchorMember) {
logger.debug(String.format("Checking shouldDock %s to %s", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
WindowBounds newBounds = null;
if (!movingMember.isDocked() || !anchorMember.isDocked()) {
WindowBounds anchorBounds = anchorMember.getBounds();
if (movingBounds != null && anchorBounds != null) {
int bottom1 = movingBounds.getTop() + movingBounds.getHeight();
int bottom2 = anchorBounds.getTop() + anchorBounds.getHeight();
int right1 = movingBounds.getLeft() + movingBounds.getWidth();
int right2 = anchorBounds.getLeft() + anchorBounds.getWidth();
if (movingBounds.getLeft() < right2 && anchorBounds.getLeft() < right1) {
// vertical docking
if (Math.abs(movingBounds.getTop() - bottom2) < snappingDistance) {
newBounds = new WindowBounds(movingBounds.getTop(), movingBounds.getLeft(), movingBounds.getWidth(), movingBounds.getHeight());
newBounds.setTop(bottom2);
logger.debug(String.format("Detecting bottom-top docking %s to %s", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
}
else if (Math.abs(anchorBounds.getTop() - bottom1) < snappingDistance) {
newBounds = new WindowBounds(movingBounds.getTop(), movingBounds.getLeft(), movingBounds.getWidth(), movingBounds.getHeight());
newBounds.setTop(anchorBounds.getTop() - movingBounds.getHeight());
logger.debug(String.format("Detecting top-bottom docking %s to %s", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
} else {
logger.debug(String.format("shouldDock %s to %s too far top-bottom", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
}
}
else if (movingBounds.getTop() < bottom2 && anchorBounds.getTop() < bottom1) {
// horizontal docking
if (Math.abs(movingBounds.getLeft() - right2) < snappingDistance) {
newBounds = new WindowBounds(movingBounds.getTop(), movingBounds.getLeft(), movingBounds.getWidth(), movingBounds.getHeight());
newBounds.setLeft(right2);
logger.debug(String.format("Detecting right-left docking %s to %s", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
}
else if (Math.abs(right1 - anchorBounds.getLeft()) < snappingDistance) {
newBounds = new WindowBounds(movingBounds.getTop(), movingBounds.getLeft(), movingBounds.getWidth(), movingBounds.getHeight());
newBounds.setLeft(anchorBounds.getLeft() - movingBounds.getWidth());
logger.debug(String.format("Detecting left-right docking %s to %s", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
}
} else {
logger.debug(String.format("shouldDock %s to %s not overlapping", movingMember.getWindow().getName(), anchorMember.getWindow().getName()));
}
}
}
return newBounds;
}
private void processUndockRequest(JSONObject request) {
String appUuid = request.getString("applicationUuid");
String windowName = request.getString("windowName");
if (appUuid != null && windowName != null) {
WindowMember member = this.memberMap.get(WindowMember.getMemberKey(appUuid, windowName));
if (member != null) {
if (member.isDocked()) {
member.undock();
}
} else {
logger.error(String.format("Window not registered %s %s", appUuid, windowName));
}
} else {
logger.error(String.format("Invalid request to duck window %s", request.toString()));
}
}
private void processRegisterRequest(JSONObject request) {
String appUuid = request.getString("applicationUuid");
String windowName = request.getString("windowName");
if (appUuid != null && windowName != null) {
Window window = Window.wrap(appUuid, windowName, this.desktopConnection);
registerWindow(window);
} else {
logger.error(String.format("Invalid request to duck window %s", request.toString()));
}
}
private void processUnregisterRequest(JSONObject request) {
String appUuid = request.getString("applicationUuid");
String windowName = request.getString("windowName");
if (appUuid != null && windowName != null) {
this.unregisterWindow(appUuid, windowName);
} else {
logger.error(String.format("Invalid request to unregister window %s", request.toString()));
}
}
/**
* EventLister class for disabled-frame-bounds-changed event
*/
class BoundsChangeListener implements EventListener {
@Override
public void eventReceived(ActionEvent actionEvent) {
JSONObject data = actionEvent.getEventObject();
WindowMember member = parseMember(data);
if (member != null) {
WindowBounds bounds = parseBounds(data);
onWindowMoved(member, bounds);
} else {
logger.error(String.format("Window not registered %s", data.toString()));
}
}
}
/**
* EventLister class for disabled-frame-bounds-changing event
*/
class BoundsChangingListener implements EventListener {
@Override
public void eventReceived(ActionEvent actionEvent) {
JSONObject data = actionEvent.getEventObject();
WindowMember member = parseMember(data);
if (member != null) {
WindowBounds bounds = parseBounds(data);
onWindowMoving(member, bounds);
} else {
logger.error(String.format("Window not registered %s", data.toString()));
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy