package ch.unifr.pai.twice.layout.client.eclipseLayout;
/*
* Copyright 2013 Oliver Schmid
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import ch.unifr.pai.twice.layout.client.commons.MiceDialogCaption;
import ch.unifr.pai.twice.layout.client.commons.ResizableDecoratorPanel;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.ComplexPanel;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.ResizeLayoutPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.SplitLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
/**
*
* @author Oliver Schmid
*
*/
public class MiceSplitLayoutPanel extends SplitLayoutPanel {
private final Map<ResizableDecoratorPanel, Widget> slots = new HashMap<ResizableDecoratorPanel, Widget>();
private ResizableDecoratorPanel centralDecoratorPanel;
private boolean isInFullscreenMode = false;
public MiceSplitLayoutPanel() {
super(5);
}
/**
* Removes a widget from the split layout panel. If the widget is not the center tab panel, it can be removed directly. Otherwise, it is checked if there
* are other widgets in the split panel which can be used as the new center widget (because a layout panel always has to contain a center widget),
* afterwards the tab panel can be removed. If no other tab panels are around which can be made the new center widget, the whole split layout panel is
* removed from its parent unless it is the root split layout panel (what results in a warning)
*
* @see com.google.gwt.user.client.ui.SplitLayoutPanel#remove(com.google.gwt.user.client.ui.Widget)
*/
@Override
public boolean remove(Widget child) {
if (getWidgetDirection(child.getParent()) != Direction.CENTER) {
setWidgetMinSize(child.getParent(), 0);
setWidgetSize(child.getParent(), 0);
return super.remove(child.getParent());
}
else {
Widget lastNonCenterWidget = getLastNonCenterWidget();
if (lastNonCenterWidget instanceof MiceLayoutTabPanel || lastNonCenterWidget instanceof MiceSplitLayoutPanel) {
Widget originParent = lastNonCenterWidget.getParent();
((ResizeLayoutPanel) getCenter()).setWidget(lastNonCenterWidget);
setWidgetSize(originParent, 0);
setWidgetMinSize(originParent, 0);
return super.remove(originParent);
}
else if (getParentMiceSplitLayoutPanel() != null) {
return getParentMiceSplitLayoutPanel().remove(this);
}
else {
Window.alert("You've tried to remove the last element - this is not allowed");
return false;
}
}
}
/**
* @return the last added widget which is not the center widget or null if there is none.
*/
public Widget getLastNonCenterWidget() {
if (getWidgetCount() < 2)
return null;
for (int i = getWidgetCount() - 2; i >= 0; i--) {
if (getWidget(i) instanceof ResizeLayoutPanel)
return getChildTabPanelFromDirectChild((ResizeLayoutPanel) getWidget(i));
}
return null;
}
/**
* Adds a widget to the panel. If the widget is a {@link ResizeLayoutPanel}, this means that it is a container and therefore should be added directly.
* Otherwise it has to be wrapped with such a {@link ResizeLayoutPanel} to ensure, that it is resizing accordingly to changing dimensions.
*
* If the widget that shall be added is {@link MiceSplitLayoutPanel} or a {@link MiceLayoutTabPanel}, it can be added to the newly created
* {@link ResizeLayoutPanel} directly. For all other widgets, a new {@link MiceLayoutTabPanel} has to be introduced and the widget (or if it is a
* {@link ComplexPanel} the children of the widget) has/have to be added to this tab.
*
* @see com.google.gwt.user.client.ui.SplitLayoutPanel#insert(com.google.gwt.user.client.ui.Widget, com.google.gwt.user.client.ui.DockLayoutPanel.Direction,
* double, com.google.gwt.user.client.ui.Widget)
*/
@Override
public void insert(Widget child, Direction direction, double size, Widget before) {
if (child instanceof ResizeLayoutPanel) {
super.insert(child, direction, size, before);
}
else {
ResizeLayoutPanel panel = new ResizeLayoutPanel();
if (child instanceof MiceSplitLayoutPanel || child instanceof MiceLayoutTabPanel) {
panel.setWidget(child);
super.insert(panel, direction, size, before);
}
else {
MiceLayoutTabPanel slot = new MiceLayoutTabPanel(20);
if (child instanceof ComplexPanel) {
ComplexPanel p = (ComplexPanel) child;
for (int i = p.getWidgetCount(); i > 0; i--) {
slot.add(p.getWidget(i - 1));
}
if (slot.getWidgetCount() > 0)
slot.selectTab(0);
}
else {
slot.add(child);
}
panel.setWidget(slot);
super.insert(panel, direction, size, before);
}
}
}
private void setWidgetToSlot(ResizableDecoratorPanel slot, Widget w) {
DockLayoutPanel layout = new DockLayoutPanel(Unit.PX);
layout.addNorth(createHeader(slot, w), 30);
layout.add(w);
slot.setWidget(layout);
slots.put(slot, w);
}
/**
* Creates the header widget for a slot (including the icons for dialog, fullscreen or no-fullscreen mode.
*
* @param slot
* @param w
* @return
*/
private HorizontalPanel createHeader(final ResizableDecoratorPanel slot, Widget w) {
HorizontalPanel header = new HorizontalPanel();
header.setHeight("100%");
header.setWidth("100%");
header.setStyleName("miceWidgetCaption");
if (!isInFullscreenMode) {
Image dialogButton = new Image(GWT.getModuleBaseURL() + "images/dialog.png");
dialogButton.addStyleName("miceWidgetButton");
dialogButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
toDialog(slot);
}
});
header.add(dialogButton);
Image fullscreenButton = new Image(GWT.getModuleBaseURL() + "images/fullscreen.png");
fullscreenButton.addStyleName("miceWidgetButton");
fullscreenButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
makeFullScreen(slot);
}
});
header.add(fullscreenButton);
}
else {
Image nofullscreenButton = new Image(GWT.getModuleBaseURL() + "images/nofullscreen.png");
nofullscreenButton.addStyleName("miceWidgetButton");
nofullscreenButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
releaseFullScreen();
}
});
header.add(nofullscreenButton);
}
//
// Image closeButton = new Image(GWT.getModuleBaseURL()
// + "images/close_hover.png");
// closeButton.addStyleName("miceWidgetButton");
// header.add(closeButton);
return header;
}
private Widget currentCenterWidget;
/**
* The original slot of the current full screen widget. This is used to reattach the current fullscreen widget back to the root panel
*/
private ResizableDecoratorPanel originSlotOfCurrentFullscreenWidget;
/**
* Stores the size of the widgets before switching to fullscreen mode for being able to reset these when releaseing fullscreen mode again.
*/
private final Map<Widget, Integer> sizeBeforeFullscreen = new HashMap<Widget, Integer>();
/**
* Minimizes the current component which is displayed in fullscreen mode. If the layout is not in fullscreen mode, this method does nothing.
*/
private void releaseFullScreen() {
if (isInFullscreenMode) {
isInFullscreenMode = false;
Widget minimizeWidget = slots.get(getCenter());
setWidgetToSlot(originSlotOfCurrentFullscreenWidget, minimizeWidget);
setWidgetToSlot((ResizableDecoratorPanel) getCenter(), currentCenterWidget);
for (Widget widget : getChildren()) {
if (getWidgetDirection(widget) != Direction.CENTER)
setWidgetSize(widget, sizeBeforeFullscreen.get(widget));
}
onResize();
currentCenterWidget = null;
originSlotOfCurrentFullscreenWidget = null;
sizeBeforeFullscreen.clear();
}
}
/**
* Sets the current decorator panel (the wrapper around the tabs) to fullscreen if the application is not yet in fullscreen mode - otherwise it does
* nothing.
*
* @param w
*/
private void makeFullScreen(ResizableDecoratorPanel w) {
if (!isInFullscreenMode) {
isInFullscreenMode = true;
currentCenterWidget = slots.get(getCenter());
for (Widget widget : getChildren()) {
sizeBeforeFullscreen.put(widget, getSizeForWidget(widget));
}
setWidgetToSlot(centralDecoratorPanel, slots.get(w));
originSlotOfCurrentFullscreenWidget = w;
for (Widget widget : getChildren()) {
if (this.getWidgetDirection(widget) != Direction.CENTER)
this.setWidgetSize(widget, 0);
}
centralDecoratorPanel.onResize();
}
}
/**
* Stores the width of the widget that is sent to the dialog to recover the size settings if the dialog is closed again
*/
private final Map<ResizableDecoratorPanel, Integer> widthOfDialogsOriginalSlots = new HashMap<ResizableDecoratorPanel, Integer>();
/**
* Currently opened dialogs
*/
private final Map<DialogBox, ResizableDecoratorPanel> dialogs = new HashMap<DialogBox, ResizableDecoratorPanel>();
/**
* Stores the size of the slots before the widget is sent to the dialog to recover the size settings if the dialog is closed again
*/
private final Map<ResizableDecoratorPanel, Integer> originsOfCenterReplacements = new LinkedHashMap<ResizableDecoratorPanel, Integer>();
/**
* Close the dialog and adds the widget to the previous panel
*
* @param dialog
*/
private void closeDialog(DialogBox dialog) {
ResizableDecoratorPanel panel = dialogs.remove(dialog);
if (getWidgetDirection(panel) == Direction.CENTER) {
if (originsOfCenterReplacements.size() > 0) {
ResizableDecoratorPanel lastReplacement = null;
Iterator<ResizableDecoratorPanel> i = originsOfCenterReplacements.keySet().iterator();
while (i.hasNext())
lastReplacement = i.next();
if (lastReplacement != null) {
setWidgetToSlot(lastReplacement, slots.get(getCenter()));
setWidgetSize(lastReplacement, originsOfCenterReplacements.get(lastReplacement));
}
}
}
setWidgetToSlot(panel, dialog.getWidget());
if (getWidgetDirection(panel) != Direction.CENTER) {
setWidgetSize(panel, widthOfDialogsOriginalSlots.remove(panel));
}
dialog.hide();
onResize();
}
/**
* @return the lastly added slot which is not a dialog. This is needed to reattach the dialog to the root layout
*/
private ResizableDecoratorPanel getLastAddedNonDialogSlot() {
for (int i = getChildren().size(); i > 0; i--) {
Widget w = getChildren().get(i - 1);
if (w instanceof ResizableDecoratorPanel) {
if (!dialogs.containsValue(w)) {
return ((ResizableDecoratorPanel) w);
}
}
}
return null;
}
/**
* Removes the widget from the root layout and presents it in a dialog box.
*
* @param w
*/
private void toDialog(ResizableDecoratorPanel w) {
if (isInFullscreenMode) {
w = originSlotOfCurrentFullscreenWidget;
releaseFullScreen();
}
final ResizableDecoratorPanel finalSlot = w;
MiceDialogCaption caption;
final DialogBox dbox = new DialogBox(false, false, (caption = new MiceDialogCaption()));
caption.setHandlers(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
closeDialog(dbox);
makeFullScreen(finalSlot);
}
}, new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
closeDialog(dbox);
}
});
dialogs.put(dbox, w);
dbox.setPopupPosition(w.getAbsoluteLeft(), w.getAbsoluteTop());
Widget element = slots.get(w);
dbox.setWidget(element);
widthOfDialogsOriginalSlots.put(w, getSizeForWidget(w));
dbox.show();
if (getWidgetDirection(w) == Direction.CENTER) {
ResizableDecoratorPanel lastAddedDecoratorPanel = getLastAddedNonDialogSlot();
if (lastAddedDecoratorPanel != null) {
setWidgetToSlot((ResizableDecoratorPanel) getCenter(), slots.get(lastAddedDecoratorPanel));
setWidgetSize(lastAddedDecoratorPanel, 0);
originsOfCenterReplacements.put(lastAddedDecoratorPanel, getSizeForWidget(lastAddedDecoratorPanel));
}
}
else {
setWidgetSize(w, 0);
}
onResize();
}
/**
* @param w
* @return the size of the widget in pixels. Depending on the layouting position, this is either the widget's width (for horizontal orientation) or height
* (for vertical orientation).
*/
private Integer getSizeForWidget(Widget w) {
switch (this.getWidgetDirection(w)) {
case EAST:
case WEST:
return w.getOffsetWidth();
}
return w.getOffsetHeight();
}
/**
* @return the parent split layout panel or null if this is the root split layout panel
*/
public MiceSplitLayoutPanel getParentMiceSplitLayoutPanel() {
if (getParent().getParent() instanceof MiceSplitLayoutPanel)
return (MiceSplitLayoutPanel) getParent().getParent();
else
return null;
}
/**
* @param w
* @return the tab panel of the given {@link ResizeLayoutPanel}
*/
private Widget getChildTabPanelFromDirectChild(ResizeLayoutPanel w) {
return ((SimplePanel) w).getWidget();
}
}