/*
* Copyright 2011 Facebook, Inc.
*
* 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.facebook.tsdb.tsdash.client;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.facebook.tsdb.tsdash.client.event.AutoReloadEvent;
import com.facebook.tsdb.tsdash.client.event.AutoReloadEventHandler;
import com.facebook.tsdb.tsdash.client.event.KeyboardShortcutEvent;
import com.facebook.tsdb.tsdash.client.event.KeyboardShortcutHandler;
import com.facebook.tsdb.tsdash.client.event.MetricEvent;
import com.facebook.tsdb.tsdash.client.event.MetricEventHandler;
import com.facebook.tsdb.tsdash.client.event.PlotOptionsEvent;
import com.facebook.tsdb.tsdash.client.event.PlotOptionsEventHandler;
import com.facebook.tsdb.tsdash.client.event.StateChangeEvent;
import com.facebook.tsdb.tsdash.client.event.TagEvent;
import com.facebook.tsdb.tsdash.client.event.TagEventHandler;
import com.facebook.tsdb.tsdash.client.event.TimeRangeChangeEvent;
import com.facebook.tsdb.tsdash.client.event.TimeRangeChangeEventHandler;
import com.facebook.tsdb.tsdash.client.event.ViewChangeEvent;
import com.facebook.tsdb.tsdash.client.event.ViewChangeEventHandler;
import com.facebook.tsdb.tsdash.client.event.StateChangeEvent.StateChange;
import com.facebook.tsdb.tsdash.client.model.ApplicationState;
import com.facebook.tsdb.tsdash.client.model.Metric;
import com.facebook.tsdb.tsdash.client.presenter.AutoreloadPresenter;
import com.facebook.tsdb.tsdash.client.presenter.ErrorPresenter;
import com.facebook.tsdb.tsdash.client.presenter.GraphPresenter;
import com.facebook.tsdb.tsdash.client.presenter.LogPresenter;
import com.facebook.tsdb.tsdash.client.presenter.MetricPresenter;
import com.facebook.tsdb.tsdash.client.presenter.PlotOptionsPresenter;
import com.facebook.tsdb.tsdash.client.presenter.Presenter;
import com.facebook.tsdb.tsdash.client.presenter.TimePresenter;
import com.facebook.tsdb.tsdash.client.presenter.TopMenuPresenter;
import com.facebook.tsdb.tsdash.client.presenter.WrapPresenter;
import com.facebook.tsdb.tsdash.client.service.HTTPService;
import com.facebook.tsdb.tsdash.client.ui.AutoreloadWidget;
import com.facebook.tsdb.tsdash.client.ui.ErrorWidget;
import com.facebook.tsdb.tsdash.client.ui.GraphWidget;
import com.facebook.tsdb.tsdash.client.ui.LogWidget;
import com.facebook.tsdb.tsdash.client.ui.MetricsFormWidget;
import com.facebook.tsdb.tsdash.client.ui.PlotOptionsWidget;
import com.facebook.tsdb.tsdash.client.ui.SelectTimeWidget;
import com.facebook.tsdb.tsdash.client.ui.TopMenuWidget;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.RootPanel;
public class ApplicationController implements ValueChangeHandler<String>,
Presenter {
private final HandlerManager eventBus;
private final HTTPService service;
private HasWidgets topContainer = null;
private HasWidgets viewContainer = null;
private HasWidgets errorContainer = null;
private final TopMenuPresenter topPresenter;
private final ErrorPresenter errorPresenter;
private final MetricPresenter metricPresenter;
private final Map<String, ArrayList<Presenter>> viewPresenters =
new HashMap<String, ArrayList<Presenter>>();
private ApplicationState appState = new ApplicationState();
private long keyShortcutTs = 0;
public ApplicationController(HandlerManager eventBus, HTTPService service) {
this.eventBus = eventBus;
this.service = service;
History.addValueChangeHandler(this);
listenViewChange();
listenTimeRangeChange();
listenMetricEvents();
listenPlotOptionsEvents();
listenCtrlF();
bindKeyShortcuts();
bindAutoReload();
topPresenter = new TopMenuPresenter(eventBus, new TopMenuWidget());
errorPresenter = new ErrorPresenter(eventBus, new ErrorWidget());
// create view presenters
// graph
ArrayList<Presenter> graphPresenters = new ArrayList<Presenter>();
WrapPresenter graphMenu = new WrapPresenter("graphMenu");
WrapPresenter graphContent = new WrapPresenter("graphContent");
graphMenu.add(new TimePresenter(eventBus, new SelectTimeWidget()));
graphMenu.add(new PlotOptionsPresenter(eventBus,
new PlotOptionsWidget()));
metricPresenter = new MetricPresenter(eventBus, service,
new MetricsFormWidget());
graphMenu.add(metricPresenter);
graphContent.add(new AutoreloadPresenter(eventBus,
new AutoreloadWidget()));
graphContent.add(new GraphPresenter(eventBus, service,
new GraphWidget()));
graphPresenters.add(graphMenu);
graphPresenters.add(graphContent);
// log
ArrayList<Presenter> logPresenters = new ArrayList<Presenter>();
logPresenters.add(new LogPresenter(eventBus, new LogWidget()));
// set the mapping
viewPresenters.put(ViewChangeEvent.View.GRAPH.toString(),
graphPresenters);
viewPresenters.put(ViewChangeEvent.View.LOG.toString(), logPresenters);
}
private void fireStateChange(StateChange stateChange) {
History.newItem(appState.toJSON(), false);
eventBus.fireEvent(new StateChangeEvent(stateChange, appState));
}
public void go() {
this.topContainer = RootPanel.get("topmenu");
this.viewContainer = RootPanel.get("content");
this.errorContainer = RootPanel.get("error");
fireInit();
}
private void listenViewChange() {
eventBus.addHandler(ViewChangeEvent.TYPE, new ViewChangeEventHandler() {
@Override
public void onChange(ViewChangeEvent event) {
appState.view = event.getView();
// update the selected item from the top menu
topPresenter.setSelected(event.getView());
displayView();
// fire the view state change
fireStateChange(StateChange.VIEW);
}
});
}
private void listenTimeRangeChange() {
eventBus.addHandler(TimeRangeChangeEvent.TYPE,
new TimeRangeChangeEventHandler() {
@Override
public void onChange(TimeRangeChangeEvent event) {
appState.timeMode = event.getMode();
appState.timeRange = event.getTimeRange();
fireStateChange(StateChange.TIME);
}
});
}
private void listenMetricEvents() {
eventBus.addHandler(MetricEvent.TYPE, new MetricEventHandler() {
@Override
public void onAdd(final MetricEvent event) {
Metric newMetric = event.getMetric();
if (!newMetric.isPlottable()) {
// only plot-able metrics in the state
return;
}
appState.metrics.add(newMetric);
fireStateChange(StateChange.METRIC);
}
@Override
public void onDelete(MetricEvent event) {
if (!event.getMetric().isPlottable()) {
return;
}
appState.metrics.remove(event.getMetric());
fireStateChange(StateChange.METRIC);
}
@Override
public void onToggle(MetricEvent event) {
if (!event.getMetric().isPlottable()) {
return;
}
fireStateChange(StateChange.METRIC);
}
@Override
public void onAggregatorChange(MetricEvent event) {
if (!event.getMetric().isPlottable()) {
return;
}
fireStateChange(StateChange.METRIC);
}
});
// metric tag events
eventBus.addHandler(TagEvent.TYPE, new TagEventHandler() {
@Override
public void onSet(TagEvent event) {
if (!event.getMetric().isPlottable()) {
return;
}
fireStateChange(StateChange.METRIC);
}
@Override
public void onRemove(TagEvent event) {
if (!event.getMetric().isPlottable()) {
return;
}
fireStateChange(StateChange.METRIC);
}
});
}
private void listenPlotOptionsEvents() {
eventBus.addHandler(PlotOptionsEvent.TYPE,
new PlotOptionsEventHandler() {
@Override
public void onPlotOptionsChange(PlotOptionsEvent event) {
appState.interactive = event.isInteractive();
appState.surface = event.surfaceEnabled();
appState.palette = event.colorPaletteEnabled();
fireStateChange(StateChange.PLOT);
}
});
}
private void bindAutoReload() {
eventBus.addHandler(AutoReloadEvent.TYPE, new AutoReloadEventHandler() {
@Override
public void onPeriodChange(AutoReloadEvent event) {
appState.reloadPeriod = event.getPeriod();
History.newItem(appState.toJSON(), false);
}
@Override
public void onLaunch(AutoReloadEvent event) {
appState.timeRange.move(event.getPeriod());
fireStateChange(StateChange.TIME);
}
@Override
public void onEnable(AutoReloadEvent event) {
appState.autoReload = event.isAutoReloading();
History.newItem(appState.toJSON(), false);
}
});
}
private void bindKeyShortcuts() {
Event.addNativePreviewHandler(new NativePreviewHandler() {
@Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
NativeEvent nativeEvent = event.getNativeEvent();
if (nativeEvent.getCtrlKey() && nativeEvent.getKeyCode() == ' ') {
nativeEvent.preventDefault();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
long now = (new Date()).getTime();
if (now - keyShortcutTs < 500) {
return;
}
keyShortcutTs = now;
if (appState.needsAutoreload()) {
eventBus.fireEvent(new KeyboardShortcutEvent(
KeyboardShortcutEvent.Shortcut.CTRL_SPACE));
}
}
});
} else if (nativeEvent.getCtrlKey()
&& nativeEvent.getKeyCode() == 'F') {
nativeEvent.preventDefault();
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
long now = (new Date()).getTime();
if (now - keyShortcutTs < 500) {
return;
}
keyShortcutTs = now;
eventBus.fireEvent(new KeyboardShortcutEvent(
KeyboardShortcutEvent.Shortcut.CTRL_F));
}
});
}
}
});
}
private void listenCtrlF() {
eventBus.addHandler(KeyboardShortcutEvent.TYPE,
new KeyboardShortcutHandler() {
@Override
public void onCtrlSpace(KeyboardShortcutEvent event) {
// ignore
}
@Override
public void onCtrlF(KeyboardShortcutEvent event) {
if (appState.view != ViewChangeEvent.View.GRAPH) {
Window.alert("You can change fullscreen mode only "
+ "in graph view");
return;
}
if (!appState.fullscreen && !appState.hasPlot()) {
Window.alert("Nothing to plot");
return;
}
// toggle fullscreen
appState.fullscreen = !appState.fullscreen;
if (appState.fullscreen) {
RootPanel.getBodyElement().addClassName(
"fullscreen");
} else {
RootPanel.getBodyElement().removeClassName(
"fullscreen");
}
fireStateChange(StateChange.SCREEN);
}
});
}
private void displayView() {
viewContainer.clear();
if (appState.fullscreen) {
RootPanel.getBodyElement().addClassName("fullscreen");
}
for (Presenter view : viewPresenters.get(appState.view.toString())) {
view.go(viewContainer, appState);
}
}
private void fireInit() {
History.fireCurrentHistoryState();
}
@Override
public void onValueChange(ValueChangeEvent<String> event) {
String historyToken = event.getValue();
if (!historyToken.isEmpty()) {
try {
appState = new ApplicationState(historyToken);
} catch (Exception e) {
Window.alert("Error: " + e);
// reset the application state to default
appState = new ApplicationState();
History.newItem(appState.toJSON(), false);
}
}
// display
topPresenter.go(topContainer, appState);
errorPresenter.go(errorContainer, appState);
displayView();
}
@Override
public void go(HasWidgets container, ApplicationState appState) {
go();
}
}