/*
* ModalDialogBase.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.core.client.widget;
import com.google.gwt.animation.client.Animation;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Style;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import org.rstudio.core.client.Point;
import org.rstudio.core.client.command.ShortcutManager;
import org.rstudio.core.client.command.ShortcutManager.Handle;
import org.rstudio.core.client.dom.DomUtils;
import org.rstudio.core.client.dom.NativeWindow;
import org.rstudio.core.client.theme.res.ThemeStyles;
import org.rstudio.studio.client.RStudioGinjector;
import java.util.ArrayList;
public abstract class ModalDialogBase extends DialogBox
{
protected ModalDialogBase()
{
this(null);
}
protected ModalDialogBase(SimplePanel containerPanel)
{
// core initialization. passing false for modal works around
// modal PopupPanel supressing global keyboard accelerators (like
// Ctrl-N or Ctrl-T). modality is achieved via setGlassEnabled(true)
super(false, false);
setGlassEnabled(true);
addStyleDependentName("ModalDialog");
// main panel used to host UI
mainPanel_ = new VerticalPanel();
bottomPanel_ = new HorizontalPanel();
bottomPanel_.setStyleName(ThemeStyles.INSTANCE.dialogBottomPanel());
bottomPanel_.setWidth("100%");
buttonPanel_ = new HorizontalPanel();
leftButtonPanel_ = new HorizontalPanel();
bottomPanel_.add(leftButtonPanel_);
bottomPanel_.add(buttonPanel_);
setButtonAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
mainPanel_.add(bottomPanel_);
// embed main panel in a custom container if specified
containerPanel_ = containerPanel;
if (containerPanel_ != null)
{
containerPanel_.setWidget(mainPanel_);
setWidget(containerPanel_);
}
else
{
setWidget(mainPanel_);
}
addDomHandler(new KeyDownHandler()
{
public void onKeyDown(KeyDownEvent event)
{
// Is this too aggressive? Alternatively we could only filter out
// keycodes that are known to be problematic (pgup/pgdown)
event.stopPropagation();
}
}, KeyDownEvent.getType());
}
@Override
protected void beginDragging(MouseDownEvent event)
{
// Prevent text selection from occurring when moving the dialog box
event.preventDefault();
super.beginDragging(event);
}
@Override
protected void onLoad()
{
super.onLoad();
ModalDialogTracker.onShow(this);
if (shortcutDisableHandle_ != null)
shortcutDisableHandle_.close();
shortcutDisableHandle_ = ShortcutManager.INSTANCE.disable();
try
{
// 728: Focus remains in Source view when message dialog pops up over it
NativeWindow.get().focus();
}
catch(Throwable e)
{
}
}
@Override
protected void onUnload()
{
if (shortcutDisableHandle_ != null)
shortcutDisableHandle_.close();
shortcutDisableHandle_ = null;
ModalDialogTracker.onHide(this);
super.onUnload();
}
public boolean isEscapeDisabled()
{
return escapeDisabled_;
}
public void setEscapeDisabled(boolean escapeDisabled)
{
escapeDisabled_ = escapeDisabled;
}
public void showModal()
{
if (mainWidget_ == null)
{
mainWidget_ = createMainWidget();
// get the main widget to line up with the right edge of the buttons.
mainWidget_.getElement().getStyle().setMarginRight(2, Unit.PX);
mainPanel_.insert(mainWidget_, 0);
}
originallyActiveElement_ = DomUtils.getActiveElement();
if (originallyActiveElement_ != null)
originallyActiveElement_.blur();
// position the dialog
positionAndShowDialog(new Command() {
@Override
public void execute()
{
// defer shown notification to allow all elements to render
// before attempting to interact w/ them programatically (e.g. setFocus)
Timer timer = new Timer() {
public void run() {
onDialogShown();
}
};
timer.schedule(100);
}
});
}
protected abstract Widget createMainWidget() ;
protected void positionAndShowDialog(Command onCompleted)
{
super.center();
onCompleted.execute();
}
protected void onDialogShown()
{
}
protected void addOkButton(ThemedButton okButton)
{
okButton_ = okButton;
okButton_.addStyleDependentName("DialogAction");
okButton_.setDefault(defaultOverrideButton_ == null);
addButton(okButton_);
}
protected void setOkButtonCaption(String caption)
{
okButton_.setText(caption);
}
protected void enableOkButton(boolean enabled)
{
okButton_.setEnabled(enabled);
}
protected void clickOkButton()
{
okButton_.click();
}
protected void setOkButtonVisible(boolean visible)
{
okButton_.setVisible(visible);
}
protected void focusOkButton()
{
if (okButton_ != null)
FocusHelper.setFocusDeferred(okButton_);
}
protected void enableCancelButton(boolean enabled)
{
cancelButton_.setEnabled(enabled);
}
protected void setDefaultOverrideButton(ThemedButton button)
{
if (button != defaultOverrideButton_)
{
if (defaultOverrideButton_ != null)
defaultOverrideButton_.setDefault(false);
defaultOverrideButton_ = button;
if (okButton_ != null)
okButton_.setDefault(defaultOverrideButton_ == null);
if (defaultOverrideButton_ != null)
defaultOverrideButton_.setDefault(true);
}
}
protected ThemedButton addCancelButton()
{
ThemedButton cancelButton = createCancelButton(null);
addCancelButton(cancelButton);
return cancelButton;
}
protected ThemedButton createCancelButton(final Operation cancelOperation)
{
return new ThemedButton("Cancel", new ClickHandler() {
public void onClick(ClickEvent event) {
if (cancelOperation != null)
cancelOperation.execute();
closeDialog();
}
});
}
protected void addCancelButton(ThemedButton cancelButton)
{
cancelButton_ = cancelButton;
cancelButton_.addStyleDependentName("DialogAction");
addButton(cancelButton_);
}
protected void addLeftButton(ThemedButton button)
{
button.addStyleDependentName("DialogAction");
button.addStyleDependentName("DialogActionLeft");
leftButtonPanel_.add(button);
allButtons_.add(button);
}
protected void addLeftWidget(Widget widget)
{
leftButtonPanel_.add(widget);
}
protected void addButton(ThemedButton button)
{
button.addStyleDependentName("DialogAction");
buttonPanel_.add(button);
allButtons_.add(button);
}
protected void setButtonAlignment(HorizontalAlignmentConstant alignment)
{
bottomPanel_.setCellHorizontalAlignment(buttonPanel_, alignment);
}
protected ProgressIndicator addProgressIndicator()
{
return addProgressIndicator(true);
}
protected ProgressIndicator addProgressIndicator(
final boolean closeOnCompleted)
{
final SlideLabel label = new SlideLabel(true);
Element labelEl = label.getElement();
Style labelStyle = labelEl.getStyle();
labelStyle.setPosition(Style.Position.ABSOLUTE);
labelStyle.setLeft(0, Style.Unit.PX);
labelStyle.setRight(0, Style.Unit.PX);
labelStyle.setTop(-12, Style.Unit.PX);
getWidget().getElement().getParentElement().appendChild(labelEl);
return new ProgressIndicator()
{
public void onProgress(String message)
{
if (message == null)
{
label.setText("", true);
if (showing_)
clearProgress();
}
else
{
label.setText(message, false);
if (!showing_)
{
enableControls(false);
label.show();
showing_ = true;
}
}
}
public void onCompleted()
{
clearProgress();
if (closeOnCompleted)
closeDialog();
}
public void onError(String message)
{
clearProgress();
RStudioGinjector.INSTANCE.getGlobalDisplay().showErrorMessage(
"Error", message);
}
@Override
public void clearProgress()
{
if (showing_)
{
enableControls(true);
label.hide();
showing_ = false;
}
}
private boolean showing_;
};
}
public void closeDialog()
{
hide();
removeFromParent();
try
{
if (originallyActiveElement_ != null
&& !originallyActiveElement_.getTagName().equalsIgnoreCase("body"))
{
Document doc = originallyActiveElement_.getOwnerDocument();
if (doc != null)
{
originallyActiveElement_.focus();
}
}
}
catch (Exception e)
{
// focus() fail if the element is no longer visible. It's
// easier to just catch this than try to detect it.
// Also originallyActiveElement_.getTagName() can fail with:
// "Permission denied to access property 'tagName' from a non-chrome context"
// possibly due to Firefox "anonymous div" issue.
}
originallyActiveElement_ = null;
}
protected SimplePanel getContainerPanel()
{
return containerPanel_;
}
protected void enableControls(boolean enabled)
{
enableButtons(enabled);
onEnableControls(enabled);
}
protected void onEnableControls(boolean enabled)
{
}
@Override
public void onPreviewNativeEvent(Event.NativePreviewEvent event)
{
if (!ModalDialogTracker.isTopMost(this))
return;
if (event.getTypeInt() == Event.ONKEYDOWN)
{
NativeEvent nativeEvent = event.getNativeEvent();
switch (nativeEvent.getKeyCode())
{
case KeyCodes.KEY_ENTER:
// allow Enter on textareas
Element e = DomUtils.getActiveElement();
if (e.hasTagName("TEXTAREA"))
return;
ThemedButton defaultButton = defaultOverrideButton_ == null
? okButton_
: defaultOverrideButton_;
if ((defaultButton != null) && defaultButton.isEnabled())
{
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
event.cancel();
defaultButton.click();
}
break;
case KeyCodes.KEY_ESCAPE:
if (escapeDisabled_)
break;
onEscapeKeyDown(event);
break;
}
}
}
protected void onEscapeKeyDown(Event.NativePreviewEvent event)
{
NativeEvent nativeEvent = event.getNativeEvent();
if (cancelButton_ == null)
{
if ((okButton_ != null) && okButton_.isEnabled())
{
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
event.cancel();
okButton_.click();
}
}
else if (cancelButton_.isEnabled())
{
nativeEvent.preventDefault();
nativeEvent.stopPropagation();
event.cancel();
cancelButton_.click();
}
}
private void enableButtons(boolean enabled)
{
for (int i=0; i<allButtons_.size(); i++)
allButtons_.get(i).setEnabled(enabled);
}
public void move(Point p, boolean allowAnimation)
{
if (!isShowing() || !allowAnimation)
{
// Don't animate if not showing
setPopupPosition(p.getX(), p.getY());
return;
}
if (currentAnimation_ != null)
{
currentAnimation_.cancel();
currentAnimation_ = null;
}
final int origLeft = getPopupLeft();
final int origTop = getPopupTop();
final int deltaX = p.getX() - origLeft;
final int deltaY = p.getY() - origTop;
currentAnimation_ = new Animation()
{
@Override
protected void onUpdate(double progress)
{
if (!isShowing())
cancel();
else
{
setPopupPosition((int)(origLeft + deltaX * progress),
(int)(origTop + deltaY * progress));
}
}
};
currentAnimation_.run(200);
}
private Handle shortcutDisableHandle_;
private boolean escapeDisabled_;
private SimplePanel containerPanel_;
private VerticalPanel mainPanel_ ;
private HorizontalPanel bottomPanel_;
private HorizontalPanel buttonPanel_;
private HorizontalPanel leftButtonPanel_;
private ThemedButton okButton_;
private ThemedButton cancelButton_;
private ThemedButton defaultOverrideButton_;
private ArrayList<ThemedButton> allButtons_ = new ArrayList<ThemedButton>();
private Widget mainWidget_ ;
private com.google.gwt.dom.client.Element originallyActiveElement_;
private Animation currentAnimation_ = null;
}