/*
* Copyright (C) 2010 Jan Pokorsky
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cz.incad.kramerius.editor.client.presenter;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.http.client.UrlBuilder;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
//import com.google.inject.Inject;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.SuggestOracle.Callback;
import com.google.gwt.user.client.ui.SuggestOracle.Request;
import com.google.gwt.user.client.ui.SuggestOracle.Response;
import cz.incad.kramerius.editor.client.EditorConfiguration;
import cz.incad.kramerius.editor.client.EditorMessages;
import cz.incad.kramerius.editor.client.view.ContainerViewImpl;
import cz.incad.kramerius.editor.client.view.EditorView;
import cz.incad.kramerius.editor.client.view.EditorViewsFactory;
import cz.incad.kramerius.editor.client.view.LoadView;
import cz.incad.kramerius.editor.client.view.RelationsView;
import cz.incad.kramerius.editor.client.view.Renderer;
import cz.incad.kramerius.editor.client.view.SaveView;
import cz.incad.kramerius.editor.share.GWTKrameriusObject;
import cz.incad.kramerius.editor.share.GWTRelationKindModel;
import cz.incad.kramerius.editor.share.GWTRelationModel;
import cz.incad.kramerius.editor.share.InputValidator;
import cz.incad.kramerius.editor.share.InputValidator.Validator;
import cz.incad.kramerius.editor.share.rpc.GetKrameriusObjectQuery;
import cz.incad.kramerius.editor.share.rpc.GetKrameriusObjectResult;
import cz.incad.kramerius.editor.share.rpc.GetSuggestionQuery;
import cz.incad.kramerius.editor.share.rpc.GetSuggestionResult;
import cz.incad.kramerius.editor.share.rpc.GetSuggestionResult.Suggestion;
import cz.incad.kramerius.editor.share.rpc.GetTabstitleQuery;
import cz.incad.kramerius.editor.share.rpc.GetTabstitleResult;
import cz.incad.kramerius.editor.share.rpc.SaveRelationsQuery;
import cz.incad.kramerius.editor.share.rpc.SaveRelationsResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.BatchAction;
import net.customware.gwt.dispatch.shared.BatchResult;
/**
*
* @author Jan Pokorsky
*/
public class EditorPresenter implements Presenter, LoadView.Callback, EditorView.Callback {
private static final EditorMessages I18N_MSG = GWT.create(EditorMessages.class);
private static final Response EMPTY_RESPONSE = new Response(Collections.<Suggestion>emptyList());
private final EditorView display;
private LoadView loadView;
private final SaveView<GWTRelationModel> saveView;
private ContainerPresenter clipboardPresenter;
private final RelationModelChangeHandler relationModelChangeHandler;
private final Map<Display, RelationsPresenter> view2PresenterMap = new LinkedHashMap<Display, RelationsPresenter>();
private final Map<GWTRelationModel, Presenter.Display> relModel2viewMap = new LinkedHashMap<GWTRelationModel, Display>();
private final DispatchAsync dispatcher;
private boolean isBound = false;
// @Inject
// public EditorPresenter(EditorDisplay display, Provider<ContainerDisplay> contDisplayProvider) {
public EditorPresenter(EditorView display, DispatchAsync dispatcher) {
this.display = display;
this.clipboardPresenter = new ContainerPresenter(EditorViewsFactory.getInstance().createContainerView(), this);
this.display.setClipboard(this.clipboardPresenter.getDisplay());
((ContainerViewImpl) clipboardPresenter.getDisplay()).debug = true;
this.dispatcher = dispatcher;
this.relationModelChangeHandler = new RelationModelChangeHandler();
this.saveView = EditorViewsFactory.getInstance().createSaveView();
this.saveView.setRenderer(new Renderer<String, GWTRelationModel>() {
@Override
public String render(GWTRelationModel model) {
GWTKrameriusObject item = model.getKrameriusObject();
String title = item.getProperties().get("title");
return title != null ? title :"notitle";
}
@Override
public String renderTitle(GWTRelationModel model) {
GWTKrameriusObject item = model.getKrameriusObject();
return item.getKind() + ", " + item.getPID();
}
});
display.setLanguages(Languages.getLocaleDisplayNames(), Languages.getCurrentLocaleIndex());
}
public void bind() {
if (isBound) {
return;
}
isBound = true;
display.setCallback(this);
clipboardPresenter.setModel(new GWTRelationKindModel());
clipboardPresenter.bind();
}
@Override
public Display getDisplay() {
return display;
}
public void load(String pid) {
load(pid, null);
}
private void load(String pid, final Runnable callback) {
// first check if pid is already loaded
for (GWTRelationModel rm : this.relModel2viewMap.keySet()) {
if (rm.getKrameriusObject().getPID().equals(pid)) {
Display relView = this.relModel2viewMap.get(rm);
this.display.select(relView);
if (callback != null) {
callback.run();
}
return;
}
}
String localeParam = Window.Location.getParameter("locale");
if (localeParam == null && "".equals(localeParam)) {
localeParam = "en";
}
// run query
dispatcher.execute(new GetKrameriusObjectQuery(pid,localeParam), new AsyncCallback<GetKrameriusObjectResult>() {
@Override
public void onFailure(Throwable caught) {
loadView.hide();
Window.alert(I18N_MSG.remoteConnectionFailure(caught.getMessage()));
GWT.log(caught.getMessage(), caught);
}
@Override
public void onSuccess(GetKrameriusObjectResult result) {
editRelations(new GWTRelationModel(result.getResult()));
if (callback != null) {
callback.run();
}
}
});
}
private void editRelations(final GWTRelationModel relationModel) {
//construct title
GWTKrameriusObject container = relationModel.getKrameriusObject();
//this.dispatcher.
String pid = container.getProperties().get("pid");
String tabsTitle = container.getProperties().get("constructedTitle");
//GWTKrameriusObject container = relationModel.getKrameriusObject();
RelationsPresenter relationsPresenter = new RelationsPresenter(
EditorViewsFactory.getInstance().createRelationsView(), EditorPresenter.this);
RelationsView relView = relationsPresenter.getDisplay();
EditorPresenter.this.display.add(relView, tabsTitle);
relationsPresenter.setModel(relationModel);
EditorPresenter.this.relModel2viewMap.put(relationModel, relView);
EditorPresenter.this.view2PresenterMap.put(relView, relationsPresenter);
relationsPresenter.bind();
relationModel.addValueChangeHandler(EditorPresenter.this.relationModelChangeHandler);
}
private void save(final List<GWTRelationModel> relModels) {
save(relModels, null);
}
/**
* Saves relation models and runs callback iff the save is successful.
* The callback may be {@code null}.
*/
private void save(final List<GWTRelationModel> relModels, final Runnable callback) {
SaveRelationsQuery[] queries = new SaveRelationsQuery[relModels.size()];
for (int i = 0; i < queries.length; i++) {
GWTRelationModel relModel = relModels.get(i);
queries[i] = new SaveRelationsQuery(relModel);
}
this.dispatcher.execute(
new BatchAction(BatchAction.OnException.CONTINUE, queries),
new AsyncCallback<BatchResult>() {
@Override
public void onFailure(Throwable caught) {
Window.alert(I18N_MSG.remoteConnectionFailure(caught.getMessage()));
}
@Override
public void onSuccess(BatchResult result) {
StringBuilder err = new StringBuilder();
int errCount = 0;
for (int i = 0, size = result.size(); i < size; i++) {
SaveRelationsResult saveResult = result.getResult(i, SaveRelationsResult.class);
GWTRelationModel relModel = relModels.get(i);
if (saveResult != null) {
relModel.save();
buildUrl(relModel.getKrameriusObject().getPID());
} else {
++errCount;
GWTKrameriusObject kobj = relModel.getKrameriusObject();
if (errCount > 1) {
err.append(",\n ");
}
String title = kobj.getProperties().get("title");
err.append(title != null ? title : "notitle").append('(').append(kobj.getPID()).append(')');
GWT.log(kobj.getPID(), result.getException(i));
}
}
if (errCount > 0) {
Window.alert(I18N_MSG.cannotSaveObject(errCount, err.toString()));
} else if (callback != null) {
callback.run();
}
//reindexAjaxCall();
}
});
}
/**
* Helper that asks user to save changes first and in all cases other than
* the cancel case it runs the callback.
*/
private void saveAttempt(final Runnable callback) {
List<GWTRelationModel> saveables = getModifiedRelations();
if (saveables.isEmpty()) {
callback.run();
} else {
this.saveView.setCallback(new SaveView.Callback() {
@Override
public void onSaveViewCommit(boolean discard) {
saveView.hide();
if (discard) {
callback.run();
} else {
save(saveView.getSelected(), callback);
}
}
});
this.saveView.setSaveables(saveables);
this.saveView.setDiscardable(true);
this.saveView.show();
}
}
@Override
public void onLanguagesClick(int index) {
final String selection = Languages.getLocaleName(index);
Runnable action = new Runnable() {
@Override
public void run() {
// XXX it would be nice to keep open editors
UrlBuilder urlBuilder = Window.Location.createUrlBuilder();
urlBuilder.setParameter("locale", selection);
String url = urlBuilder.buildString();
Window.Location.replace(url);
}
};
saveAttempt(action);
}
@Override
public void onLoadClick() {
if (loadView == null) {
loadView = EditorViewsFactory.getInstance().createLoadView();
}
loadView.setCallback(this);
loadView.show();
}
@Override
public void onSaveClick() {
this.saveView.setCallback(new SaveView.Callback() {
@Override
public void onSaveViewCommit(boolean discard) {
EditorPresenter.this.saveView.hide();
EditorPresenter.this.save(EditorPresenter.this.saveView.getSelected());
}
});
List<GWTRelationModel> saveables = getModifiedRelations();
this.saveView.setSaveables(saveables);
this.saveView.setDiscardable(false);
this.saveView.show();
}
public static native void buildUrl(String pid) /*-{
var link = "http://"+$wnd.location.host+"/search/lr?action=start&def=reindex&out=text¶ms=reindexDoc,"+pid+",editor+reindex";
if (typeof $wnd.reindex == 'undefined') {
$wnd.reindex = [link];
} else {
$wnd.reindex.push(link);
}
console.log("Reindex build "+$wnd.reindex);
}-*/;
public static native void reindexAjaxCall() /*-{
var links = $wnd.reindex ;
for (var i=0;i<links.length;i++) {
var link = links[i];
console.log(link);
var xhReq = new XMLHttpRequest();
xhReq.open("GET", link, false);
xhReq.send(null);
var serverResponse = xhReq.responseText;
}
}-*/;
@Override
public void onLoadViewCommit(String input) {
Validator<String> validator = InputValidator.validatePID(input);
if (validator.isValid()) {
load(validator.getNormalized(), new Runnable() {
@Override
public void run() {
loadView.hide();
}
});
} else {
loadView.showError(validator.getErrorMessage());
}
}
@Override
public void onLoadViewSuggestionCommit(SuggestOracle.Suggestion suggestion) {
Suggestion kSuggestion = (Suggestion) suggestion;
loadView.pid().setValue(kSuggestion.getPid(), false);
onLoadViewCommit(kSuggestion.getPid());
}
@Override
public void onLoadViewSuggestionRequest(final Request request, final Callback callback) {
final String filter = request.getQuery();
if (filter == null || filter.trim().isEmpty()) {
callback.onSuggestionsReady(request, EMPTY_RESPONSE);
}
GetSuggestionQuery query = new GetSuggestionQuery(filter, request.getLimit());
dispatcher.execute(query, new AsyncCallback<GetSuggestionResult>() {
@Override
public void onFailure(Throwable caught) {
callback.onSuggestionsReady(request, EMPTY_RESPONSE);
}
@Override
public void onSuccess(GetSuggestionResult result) {
if (result.isServerFailure()) {
loadView.showError(I18N_MSG.serverQueryFailure());
return;
}
// check whether user types faster then we can fetch suggestions
if (filter.equals(loadView.title().getValue())) {
callback.onSuggestionsReady(request, result);
}
}
});
}
@Override
public void onEditorTabClose(final Display selected) {
final RelationsPresenter relPresenter = this.view2PresenterMap.get(selected);
final GWTRelationModel relModel = relPresenter.getModel();
final Runnable doEditorTabClose = new Runnable() {
@Override
public void run() {
display.remove(selected);
view2PresenterMap.remove(selected);
relModel2viewMap.remove(relModel);
relPresenter.unbind();
}
};
if (relModel.isModified()) {
this.saveView.setCallback(new SaveView.Callback() {
@Override
public void onSaveViewCommit(boolean discard) {
saveView.hide();
if (discard) {
doEditorTabClose.run();
} else {
save(saveView.getSelected(), doEditorTabClose);
}
}
});
this.saveView.setSaveables(Collections.singletonList(relModel));
this.saveView.setDiscardable(true);
this.saveView.show();
} else {
doEditorTabClose.run();
}
}
@Override
public void onKrameriusClick() {
Runnable doKrameriusClick = new Runnable() {
@Override
public void run() {
String krameriusUrl = EditorConfiguration.getInstance().getKrameriusURL();
krameriusUrl += "?language=" + Languages.getLocaleName(Languages.getCurrentLocaleIndex());
Window.Location.assign(krameriusUrl);
}
};
saveAttempt(doKrameriusClick);
}
private List<GWTRelationModel> getModifiedRelations() {
List<GWTRelationModel> saveables = new ArrayList<GWTRelationModel>();
for (GWTRelationModel relModel : this.relModel2viewMap.keySet()) {
if (relModel.isModified()) {
saveables.add(relModel);
}
}
return saveables;
}
private void setModified(GWTRelationModel relModel) {
Display relView = this.relModel2viewMap.get(relModel);
this.display.setModified(relView, relModel.isModified());
}
private final class RelationModelChangeHandler implements ValueChangeHandler<GWTRelationModel> {
@Override
public void onValueChange(ValueChangeEvent<GWTRelationModel> event) {
GWTRelationModel relModel = event.getValue();
EditorPresenter.this.setModified(relModel);
}
}
/**
* Provides languages compiled with the application.
* See {@code Editor.gwt.xml} to edit new languages.
*/
private static final class Languages {
private static final Languages INSTANCE = new Languages();
private String[] locales;
private String[] localeDisplayNames;
private int currLocaleIndex;
public static int getCurrentLocaleIndex() {
return INSTANCE.currLocaleIndex;
}
public static String[] getLocaleDisplayNames() {
return INSTANCE.localeDisplayNames;
}
public static String getLocaleName(int index) {
return INSTANCE.locales[index];
}
public static String[] getLocaleNames() {
return INSTANCE.locales;
}
private Languages() {
locales = LocaleInfo.getAvailableLocaleNames();
List<String> localesAsList = Arrays.asList(locales);
int defaultLocaleIndex = localesAsList.indexOf("default");
// remove "default" locale since default just duplicates "en" locale; see Editor.gwt.xml
if (defaultLocaleIndex >= 0) {
// make writable array
localesAsList = new ArrayList<String>(localesAsList);
localesAsList.remove(defaultLocaleIndex);
locales = localesAsList.toArray(new String[localesAsList.size()]);
}
localeDisplayNames = new String[locales.length];
String currLocale = LocaleInfo.getCurrentLocale().getLocaleName();
currLocaleIndex = -1;
for (int i = 0; i < locales.length; i++) {
String locale = locales[i];
localeDisplayNames[i] = LocaleInfo.getLocaleNativeDisplayName(locale);
if (currLocale.equals(locale)) {
currLocaleIndex = i;
}
}
}
}
}