/*
* Copyright 2009 Fred Sauer
*
* 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.
*/
package com.allen_sauer.gwt.log.client;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasAllMouseHandlers;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.allen_sauer.gwt.log.client.impl.LogClientBundle;
import com.allen_sauer.gwt.log.client.util.DOMUtil;
import com.allen_sauer.gwt.log.shared.LogRecord;
/**
* Logger which outputs to a draggable floating <code>DIV</code>.
*/
public class DivLogger implements Logger {
// CHECKSTYLE_JAVADOC_OFF
private class LogDockPanel extends DockPanel {
private HandlerRegistration resizeRegistration;
private final ResizeHandler windowResizeListener = new ResizeHandler() {
@Override
public void onResize(ResizeEvent event) {
int width = event.getWidth();
int height = event.getHeight();
resize(width, height);
}
};
@Override
protected void onLoad() {
super.onLoad();
resizeRegistration = Window.addResizeHandler(windowResizeListener);
}
@Override
protected void onUnload() {
super.onUnload();
resizeRegistration.removeHandler();
}
private void resize(int width, int height) {
scrollPanel.setPixelSize(Math.max(300, (int) (Window.getClientWidth() * .8)),
Math.max(100, (int) (Window.getClientHeight() * .3)));
}
}
private class MouseDragHandler implements MouseMoveHandler, MouseUpHandler, MouseDownHandler {
private boolean dragging = false;
private final Label dragHandle;
private int dragStartX;
private int dragStartY;
public MouseDragHandler(Label dragHandle) {
this.dragHandle = dragHandle;
dragHandle.addMouseDownHandler(this);
dragHandle.addMouseUpHandler(this);
dragHandle.addMouseMoveHandler(this);
}
@Override
public void onMouseDown(MouseDownEvent event) {
dragging = true;
dragStartX = event.getRelativeX(dragHandle.getElement());
dragStartY = event.getRelativeY(dragHandle.getElement());
DOM.setCapture(dragHandle.getElement());
}
@Override
public void onMouseMove(MouseMoveEvent event) {
if (dragging) {
int absX = event.getRelativeX(dragHandle.getElement()) + logDockPanel.getAbsoluteLeft();
int absY = event.getRelativeY(dragHandle.getElement()) + logDockPanel.getAbsoluteTop();
RootPanel.get().setWidgetPosition(logDockPanel, absX - dragStartX, absY - dragStartY);
}
}
@Override
public void onMouseUp(MouseUpEvent event) {
dragging = false;
DOM.releaseCapture(dragHandle.getElement());
}
}
private class MouseResizeHandler implements MouseMoveHandler, MouseUpHandler, MouseDownHandler {
private boolean dragging = false;
private int dragStartX;
private int dragStartY;
private final Widget resizePanel;
public MouseResizeHandler(Widget resizePanel) {
this.resizePanel = resizePanel;
HasAllMouseHandlers hamh = (HasAllMouseHandlers) resizePanel;
hamh.addMouseMoveHandler(this);
hamh.addMouseDownHandler(this);
hamh.addMouseUpHandler(this);
}
@Override
public void onMouseDown(MouseDownEvent event) {
dragging = true;
DOM.setCapture(resizePanel.getElement());
dragStartX = event.getX();
dragStartY = event.getY();
DOM.eventPreventDefault(DOM.eventGetCurrentEvent());
}
@Override
public void onMouseMove(MouseMoveEvent event) {
if (dragging) {
scrollPanel.incrementPixelSize(event.getX() - dragStartX, event.getY() - dragStartY);
scrollPanel.setVerticalScrollPosition(MAX_VERTICAL_SCROLL);
}
}
@Override
public void onMouseUp(MouseUpEvent event) {
dragging = false;
DOM.releaseCapture(resizePanel.getElement());
}
}
private static class ScrollPanelImpl extends ScrollPanel {
private int minScrollPanelHeight = -1;
private int minScrollPanelWidth = -1;
private int scrollPanelHeight;
private int scrollPanelWidth;
public void checkMinSize() {
if (minScrollPanelWidth == -1) {
// need try-catch for certain initialization cases such as an early
// JavaScript alert()
try {
minScrollPanelWidth = getOffsetWidth();
minScrollPanelHeight = getOffsetHeight();
} catch (Throwable ignore) {
}
}
}
public void incrementPixelSize(int width, int height) {
setPixelSize(scrollPanelWidth + width, scrollPanelHeight + height);
}
@Override
public void setPixelSize(int width, int height) {
super.setPixelSize(scrollPanelWidth = Math.max(width, minScrollPanelWidth),
scrollPanelHeight = Math.max(height, minScrollPanelHeight));
}
}
private static final int[] levels = {
Log.LOG_LEVEL_TRACE, Log.LOG_LEVEL_DEBUG, Log.LOG_LEVEL_INFO, Log.LOG_LEVEL_WARN,
Log.LOG_LEVEL_ERROR, Log.LOG_LEVEL_FATAL, Log.LOG_LEVEL_OFF,};
private static final int MAX_VERTICAL_SCROLL = 0x6666666;
private static final String STACKTRACE_ELEMENT_PREFIX = " at ";
private static final int UPDATE_INTERVAL_MILLIS = 500;
private boolean dirty = false;
private Button[] levelButtons;
private final LogDockPanel logDockPanel = new LogDockPanel();
private String logText = "";
private final HTML logTextArea = new HTML();
private final ScrollPanelImpl scrollPanel = new ScrollPanelImpl();
private final Timer timer;
/**
* Default constructor.
*/
public DivLogger() {
logDockPanel.addStyleName(LogClientBundle.INSTANCE.css().logPanel());
logTextArea.addStyleName(LogClientBundle.INSTANCE.css().logTextArea());
scrollPanel.addStyleName(LogClientBundle.INSTANCE.css().logScrollPanel());
// scrollPanel.setAlwaysShowScrollBars(true);
final FocusPanel headerPanel = makeHeader();
Widget resizePanel = new Image(GWT.getModuleBaseURL() + "gwt-log-triangle-10x10.png");
resizePanel.addStyleName(LogClientBundle.INSTANCE.css().logResizeSe());
new MouseResizeHandler(resizePanel);
logDockPanel.add(headerPanel, DockPanel.NORTH);
logDockPanel.add(scrollPanel, DockPanel.CENTER);
logDockPanel.add(resizePanel, DockPanel.SOUTH);
DOM.setStyleAttribute(DOM.getParent(resizePanel.getElement()), "lineHeight", "1px");
logDockPanel.setCellHorizontalAlignment(resizePanel, HasHorizontalAlignment.ALIGN_RIGHT);
scrollPanel.setWidget(logTextArea);
timer = new Timer() {
@Override
public void run() {
dirty = false;
logTextArea.setHTML(logTextArea.getHTML() + logText);
logText = "";
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
scrollPanel.setVerticalScrollPosition(MAX_VERTICAL_SCROLL);
}
});
}
};
}
@Override
public final void clear() {
logTextArea.setHTML("");
}
public final Widget getWidget() {
return logDockPanel;
}
@Override
public final boolean isSupported() {
return true;
}
public final boolean isVisible() {
return logDockPanel.isAttached();
}
@Override
public void log(LogRecord record) {
String text = record.getFormattedMessage().replaceAll("<", "<").replaceAll(">", ">");
String title = makeTitle(record);
Throwable throwable = record.getThrowable();
if (throwable != null) {
while (throwable != null) {
/* Use throwable.toString() and not throwable.getClass().getName() and
* throwable.getMessage(), so that instances of UnwrappedClientThrowable, when stack trace
* deobfuscation is enabled) display properly
*/
text += "<b>" + throwable.toString() + "</b>";
StackTraceElement[] stackTraceElements = throwable.getStackTrace();
if (stackTraceElements.length > 0) {
text += "<div class='log-stacktrace'>";
for (StackTraceElement element : stackTraceElements) {
text += STACKTRACE_ELEMENT_PREFIX + element + "<br>";
}
text += "</div>";
}
throwable = throwable.getCause();
if (throwable != null) {
text += "Caused by: ";
}
}
}
text = text.replaceAll("\r\n|\r|\n", "<BR>");
addLogText("<div class='" + LogClientBundle.INSTANCE.css().logMessage()
+ "' onmouseover='className+=\" log-message-hover\"' "
+ "onmouseout='className=className.replace(/ log-message-hover/g,\"\")' style='color: "
+ getColor(record.getLevel()) + "' title='" + title + "'>" + text + "</div>");
// Intended to run the first time a message is logged
if (!logDockPanel.isAttached()) {
ensureInitialized();
// Attach dock panel to the bottom of the window
logDockPanel.getElement().getStyle().setVisibility(Visibility.HIDDEN);
moveTo(0, 0);
int x = Math.max(0, (Window.getClientWidth() - logDockPanel.getOffsetWidth()) / 2);
int y = Math.max(0, Window.getClientHeight() - logDockPanel.getOffsetHeight());
moveTo(x, y);
logDockPanel.getElement().getStyle().setVisibility(Visibility.VISIBLE);
}
}
public final void moveTo(int x, int y) {
// Need to ensure initialization, in case moveTo() is called before the first call to log()
ensureInitialized();
RootPanel.get().add(logDockPanel, x, y);
}
@Override
public void setCurrentLogLevel(int level) {
for (int i = 0; i < levels.length; i++) {
if (levels[i] < Log.getLowestLogLevel()) {
levelButtons[i].setEnabled(false);
} else {
String levelText = LogUtil.levelToString(levels[i]);
boolean current = level == levels[i];
levelButtons[i].setTitle(current ? "Current (runtime) log level is already '" + levelText
+ "'" : "Set current (runtime) log level to '" + levelText + "'");
boolean active = level <= levels[i];
DOM.setStyleAttribute(levelButtons[i].getElement(), "color", active ? getColor(levels[i])
: "#ccc");
}
}
}
public final void setPixelSize(int width, int height) {
logTextArea.setPixelSize(width, height);
}
public final void setSize(String width, String height) {
logTextArea.setSize(width, height);
}
private void addLogText(String logTest) {
logText += logTest;
if (!dirty) {
dirty = true;
timer.schedule(UPDATE_INTERVAL_MILLIS);
}
}
private void ensureInitialized() {
scrollPanel.checkMinSize();
logDockPanel.resize(Window.getClientWidth(), Window.getClientHeight());
}
private String getColor(int logLevel) {
if (logLevel == Log.LOG_LEVEL_OFF) {
return "#000"; // black
}
if (logLevel >= Log.LOG_LEVEL_FATAL) {
return "#F00"; // bright red
}
if (logLevel >= Log.LOG_LEVEL_ERROR) {
return "#C11B17"; // dark red
}
if (logLevel >= Log.LOG_LEVEL_WARN) {
return "#E56717"; // dark orange
}
if (logLevel >= Log.LOG_LEVEL_INFO) {
return "#2B60DE"; // blue
}
if (logLevel >= Log.LOG_LEVEL_DEBUG) {
return "#20b000"; // green
}
return "#F0F"; // purple
}
/**
* @deprecated
*/
@Deprecated
private FocusPanel makeHeader() {
FocusPanel header;
header = new FocusPanel();
HorizontalPanel masterPanel = new HorizontalPanel();
masterPanel.setWidth("100%");
header.add(masterPanel);
final Label titleLabel = new Label("gwt-log", false);
titleLabel.setStylePrimaryName(LogClientBundle.INSTANCE.css().logTitle());
HorizontalPanel buttonPanel = new HorizontalPanel();
levelButtons = new Button[levels.length];
for (int i = 0; i < levels.length; i++) {
final int level = levels[i];
levelButtons[i] = new Button(LogUtil.levelToString(level));
buttonPanel.add(levelButtons[i]);
levelButtons[i].addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
((Button) event.getSource()).setFocus(false);
Log.setCurrentLogLevel(level);
}
});
}
Button clearButton = new Button("Clear");
clearButton.addStyleName(LogClientBundle.INSTANCE.css().logClearButton());
DOM.setStyleAttribute(clearButton.getElement(), "color", "#00c");
clearButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
((Button) event.getSource()).setFocus(false);
Log.clear();
}
});
buttonPanel.add(clearButton);
Button aboutButton = new Button("About");
aboutButton.addStyleName(LogClientBundle.INSTANCE.css().logClearAbout());
aboutButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
((Button) event.getSource()).setFocus(false);
Log.diagnostic("\n" //
+ "gwt-log-" + Log.getVersion() //
+ " - Runtime logging for your Google Web Toolkit projects\n" + //
"Copyright 2007-2008 Fred Sauer\n" + //
"The original software is available from:\n" + //
"\u00a0\u00a0\u00a0\u00a0http://allen-sauer.com/gwt/\n", null);
}
});
Button closeButton = new Button("X");
closeButton.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
logDockPanel.removeFromParent();
}
});
masterPanel.add(titleLabel);
masterPanel.add(buttonPanel);
masterPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_RIGHT);
masterPanel.add(aboutButton);
masterPanel.add(closeButton);
masterPanel.setCellHeight(titleLabel, "100%");
masterPanel.setCellWidth(titleLabel, "50%");
masterPanel.setCellWidth(aboutButton, "50%");
new MouseDragHandler(titleLabel);
return header;
}
private String makeTitle(LogRecord record) {
String message = record.getFormattedMessage();
Throwable throwable = record.getThrowable();
if (throwable != null) {
if (throwable.getMessage() == null) {
message = throwable.getClass().getName();
} else {
message = throwable.getMessage().replaceAll(
throwable.getClass().getName().replaceAll("^(.+\\.).+$", "$1"), "");
}
}
return DOMUtil.adjustTitleLineBreaks(message).replaceAll("<", "<").replaceAll(">", ">").replaceAll(
"'", "\"");
}
}