/*
* Copyright (C) 2012 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.cas.lib.proarc.webapp.client.widget;
import com.google.gwt.core.client.Callback;
import com.google.gwt.event.shared.HandlerRegistration;
import com.smartgwt.client.data.Record;
import com.smartgwt.client.data.ResultSet;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.types.SelectionStyle;
import com.smartgwt.client.util.BooleanCallback;
import com.smartgwt.client.util.SC;
import com.smartgwt.client.widgets.Canvas;
import com.smartgwt.client.widgets.IButton;
import com.smartgwt.client.widgets.events.ClickEvent;
import com.smartgwt.client.widgets.events.ClickHandler;
import com.smartgwt.client.widgets.grid.ListGridRecord;
import com.smartgwt.client.widgets.grid.events.SelectionUpdatedEvent;
import com.smartgwt.client.widgets.grid.events.SelectionUpdatedHandler;
import com.smartgwt.client.widgets.layout.VLayout;
import com.smartgwt.client.widgets.menu.Menu;
import com.smartgwt.client.widgets.toolbar.ToolStrip;
import cz.cas.lib.proarc.webapp.client.ClientMessages;
import cz.cas.lib.proarc.webapp.client.ClientUtils;
import cz.cas.lib.proarc.webapp.client.Editor;
import cz.cas.lib.proarc.webapp.client.action.AbstractAction;
import cz.cas.lib.proarc.webapp.client.action.ActionEvent;
import cz.cas.lib.proarc.webapp.client.action.Actions;
import cz.cas.lib.proarc.webapp.client.action.RefreshAction;
import cz.cas.lib.proarc.webapp.client.action.RefreshAction.Refreshable;
import cz.cas.lib.proarc.webapp.client.action.Selectable;
import cz.cas.lib.proarc.webapp.client.ds.MetaModelDataSource;
import cz.cas.lib.proarc.webapp.client.ds.RelationDataSource;
import cz.cas.lib.proarc.webapp.client.ds.SearchDataSource;
import cz.cas.lib.proarc.webapp.client.ds.UserDataSource;
import java.util.LinkedHashMap;
/**
* UI to show and change parent for given digital object or import batch.
*
* @author Jan Pokorsky
*/
public final class ImportParentChooser {
private final ClientMessages i18n;
private ImportParentHandler handler;
private final DigitalObjectSearchView foundView;
private final DigitalObjectTreeView treeView;
private final SelectionView selectionView;
private AbstractAction selectParentAction;
private final VLayout widget;
private Record newParent;
private Record oldParent;
private boolean loadFailed;
private boolean parentOwnerCheck = false;
/** Whether to fetch the parents filter at first view. */
private boolean firstShowParentFetch = true;
public ImportParentChooser(ClientMessages i18n) {
this.i18n = i18n;
this.widget = new VLayout(4);
widget.setWidth100();
widget.setHeight100();
widget.setOverflow(Overflow.AUTO);
selectionView = createSelectionView(i18n);
foundView = new DigitalObjectSearchView(i18n);
treeView = new DigitalObjectTreeView(i18n);
foundView.getGrid().setSelectionType(SelectionStyle.SINGLE);
foundView.getGrid().addSelectionUpdatedHandler(new SelectionUpdatedHandler() {
@Override
public void onSelectionUpdated(SelectionUpdatedEvent event) {
final ListGridRecord selectedRecord = foundView.getGrid().getSelectedRecord();
if (selectedRecord != null) {
String pid = selectedRecord.getAttribute(RelationDataSource.FIELD_PID);
treeView.setRoot(pid);
}
}
});
widget.addMember(selectionView);
Canvas foundViewWidget = foundView.asWidget();
foundViewWidget.setShowResizeBar(true);
widget.addMember(foundViewWidget);
widget.addMember(treeView.asWidget());
createActions();
initToolbar(foundView.getToolbar(), foundView);
initToolbar(treeView.getToolbar(), treeView);
initContextMenu(foundView.getGrid().getContextMenu(), foundView);
initContextMenu(treeView.getTree().getContextMenu(), treeView);
}
/**
* Registers listener for parent changes.
*
* @param handler change handler
*/
public void setHandler(ImportParentHandler handler) {
this.handler = handler;
}
/**
* Sets digital object to show its parent.
*
* @param pid digital object ID
*/
public void setDigitalObject(String pid) {
init(pid, null);
}
/**
* Sets import batch to show its parent.
*
* @param batchId import batch ID
*/
public void setImport(String batchId) {
init(null, batchId);
}
/**
* Sets whether check owner of parent object and logged user are same.
* @param check
*/
public void setParentOwnerCheck(boolean check) {
this.parentOwnerCheck = check;
}
public void focus() {
foundView.getGrid().focus();
}
private void init(String pid, String batchId) {
oldParent = null;
newParent = null;
loadFailed = true;
fetchModels(false);
loadParentSelection(pid, batchId);
foundView.onShow();
}
private void fetchModels(final boolean reload) {
MetaModelDataSource.getModels(reload, new Callback<ResultSet, Void>() {
@Override
public void onFailure(Void reason) {
}
@Override
public void onSuccess(ResultSet modelResultSet) {
LinkedHashMap<?, ?> valueMap = ClientUtils.getValueMap(modelResultSet,
MetaModelDataSource.FIELD_PID, MetaModelDataSource.FIELD_DISPLAY_NAME);
treeView.setModels(valueMap);
foundView.setModels(valueMap);
selectionView.setModels(valueMap);
if (firstShowParentFetch) {
// init the view filter with the first modelId on first show
if (!valueMap.isEmpty()) {
Object firstModel = valueMap.keySet().iterator().next();
foundView.setFilterModel(firstModel);
}
// issue 209: do not refresh the parent search on each show
firstShowParentFetch = false;
foundView.refresh();
}
}
});
}
/**
* Gets origin parent selection.
*/
public Record getOldParent() {
return oldParent;
}
/**
* @see #getOldParent
*/
public String getOldParentPid() {
return asPid(oldParent);
}
/**
* @see #getSelectedParent
*/
public String getSelectedParentPid() {
Record selectedParent = getSelectedParent();
return selectedParent == null
? null
: selectedParent.getAttribute(SearchDataSource.FIELD_PID);
}
/**
* Gets current selection of parent object. It tries to use record from TreeGrid
* to propagate add/remove of record to view properly.
*/
public Record getSelectedParent() {
return newParent;
}
/**
* Resets old/new parent values. It sould be called after storing of current selection.
* @param newParent
*/
public void onSave(Record newParent) {
this.newParent = newParent;
this.oldParent = newParent;
}
private static String asPid(Record r) {
return r == null ? null : r.getAttribute(RelationDataSource.FIELD_PID);
}
/**
* Any change of parent object selection?
*/
public boolean isChanged() {
if (loadFailed) {
return false;
} else {
String newPid = asPid(newParent);
String oldPid = asPid(oldParent);
return (oldPid == null ? newPid != null : !oldPid.equals(newPid));
}
}
public Canvas getUI() {
return widget;
}
private void loadParentSelection(final String pid, String batchId) {
if (pid == null && batchId == null) {
selectionView.setSelection(null);
return ;
}
SearchDataSource.getInstance().findParent(pid, batchId, new Callback<ResultSet, Void>() {
@Override
public void onFailure(Void reason) {
}
@Override
public void onSuccess(ResultSet result) {
if (result.isEmpty()) {
selectionView.setSelection(null);
} else {
newParent = oldParent = result.first();
selectionView.setSelection(newParent);
}
loadFailed = false;
}
});
}
private SelectionView createSelectionView(ClientMessages i18n) {
final SelectionView view = new SelectionView(i18n);
view.addClearClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
setParentSelection(null);
}
});
return view;
}
private void createActions() {
selectParentAction = new AbstractAction(
i18n.ImportParentChooser_SelectAction_Title(),
"[SKIN]/actions/approve.png",
i18n.ImportParentChooser_SelectAction_Hint()) {
@Override
public void performAction(ActionEvent event) {
Record[] selection = Actions.getSelection(event);
if (selection != null && selection.length == 1) {
onParentSelection(selection[0]);
}
}
};
}
private void initToolbar(ToolStrip toolbar, Selectable<Record> source) {
toolbar.addMember(Actions.asIconButton(new RefreshAction(i18n),
new RefreshableView((Refreshable) source)));
toolbar.addMember(Actions.asIconButton(selectParentAction, source));
}
private void initContextMenu(Menu menu, Selectable<Record> source) {
menu.addItem(Actions.asMenuItem(selectParentAction, source));
}
private void onParentSelection(final Record selection) {
String parentOwner = selection.getAttribute(SearchDataSource.FIELD_OWNER);
String username = Editor.getInstance().getUser().getAttribute(UserDataSource.FIELD_USERNAME);
if (parentOwnerCheck && !username.equals(parentOwner)) {
SC.ask(i18n.ImportParentChooser_SelectAction_ParentOwnerCheck_Msg(),
new BooleanCallback() {
@Override
public void execute(Boolean value) {
if (value != null && value) {
setParentSelection(selection);
}
}
});
} else {
setParentSelection(selection);
}
}
private void setParentSelection(Record selection) {
if (selection != null) {
newParent = treeView.getTree().getTree().find(
RelationDataSource.FIELD_PID, selection.getAttribute(RelationDataSource.FIELD_PID));
} else {
newParent = null;
}
selectionView.setSelection(selection);
handler.onParentSelectionUpdated();
}
public interface ImportParentHandler {
void onParentSelectionUpdated();
}
private final class RefreshableView implements Refreshable {
private final Refreshable delegate;
RefreshableView(Refreshable delegate) {
this.delegate = delegate;
}
@Override
public void refresh() {
fetchModels(true);
delegate.refresh();
}
}
/**
* Shows selected parent object.
*/
private final static class SelectionView extends VLayout {
private final Canvas selection;
private LinkedHashMap<?, ?> models;
private Record parentRecord;
private final IButton clear;
private final ClientMessages i18n;
SelectionView(ClientMessages i18n) {
this.i18n = i18n;
setAutoHeight();
setIsGroup(true);
setGroupTitle(i18n.ImportParentChooser_SelectionForm_Title());
setLayoutMargin(4);
selection = new Canvas();
selection.setWidth100();
selection.setAutoHeight();
selection.setMargin(4);
selection.setCanSelectText(Boolean.TRUE);
clear = new IButton(i18n.ImportParentChooser_SelectionForm_Clear_Title());
clear.setTooltip(i18n.ImportParentChooser_SelectionForm_Clear_Hint());
clear.setAutoFit(Boolean.TRUE);
setMembers(selection, clear);
}
public HandlerRegistration addClearClickHandler(ClickHandler handler) {
return clear.addClickHandler(handler);
}
private void setSelection(Record parentRecord) {
this.parentRecord = parentRecord;
if (parentRecord == null) {
selection.setContents(i18n.ImportParentChooser_EmptySelection_Msg());
return ;
}
String model = parentRecord.getAttribute(SearchDataSource.FIELD_MODEL);
if (models != null) {
Object obj = models.get(model);
if (obj != null) {
model = String.valueOf(obj);
}
}
selection.setContents(ClientUtils.format("%s: <b>%s</b>, %s",
model,
parentRecord.getAttribute(SearchDataSource.FIELD_LABEL),
parentRecord.getAttribute(SearchDataSource.FIELD_PID)
));
}
public void setModels(LinkedHashMap<?, ?> models) {
this.models = models;
setSelection(parentRecord);
}
}
}