/*
* #!
* Ontopoly Editor
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* 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 ontopoly.pages;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import ontopoly.components.AjaxParentCheckChild;
import ontopoly.components.AjaxParentFormChoiceComponentUpdatingBehavior;
import ontopoly.components.AjaxParentRadioChild;
import ontopoly.components.CheckLabelPanel;
import ontopoly.components.TopicDropDownChoice;
import ontopoly.components.TreePanel;
import ontopoly.model.FieldAssignment;
import ontopoly.model.FieldInstance;
import ontopoly.model.RoleField;
import ontopoly.model.Topic;
import ontopoly.model.TopicMap;
import ontopoly.model.TopicType;
import ontopoly.models.FieldInstanceModel;
import ontopoly.models.TopicModel;
import ontopoly.models.TopicTypeModel;
import ontopoly.pojos.TopicNode;
import ontopoly.utils.TreeModels;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Button;
import org.apache.wicket.markup.html.form.Check;
import org.apache.wicket.markup.html.form.CheckGroup;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.Radio;
import org.apache.wicket.markup.html.form.RadioGroup;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.ResourceModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ModalFindPage<T> extends Panel {
public static final int ACTIVE_TAB_SEARCH = 1;
public static final int ACTIVE_TAB_BROWSE = 2;
protected static Logger log = LoggerFactory.getLogger(ModalFindPage.class);
protected FieldInstanceModel fieldInstanceModel;
AjaxLink<Object> searchTabLink;
AjaxLink<Object> browseTabLink;
private boolean errorInSearch = false;
protected TreeModel emptyTreeModel = TreeModels.createEmptyTreeModel();
private IModel<List<TopicType>> playerTypesChoicesModel;
private TopicModel<TopicType> selectedTypeModel;
private IModel<List<Topic>> results;
public ModalFindPage(String id, FieldInstanceModel fieldInstanceModel, int activeTab) {
super(id);
this.fieldInstanceModel = fieldInstanceModel;
final WebMarkupContainer popupContent = new WebMarkupContainer("popupContent");
popupContent.setOutputMarkupId(true);
add(popupContent);
FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance();
FieldAssignment fieldAssignment = fieldInstance.getFieldAssignment();
RoleField roleField = (RoleField)fieldAssignment.getFieldDefinition();
popupContent.add(new Label("title", new Model<String>(roleField.getFieldName())));
final WebMarkupContainer searchTab = createSearchTab();
popupContent.add(searchTab);
final WebMarkupContainer browseTab = createBrowseTab();
popupContent.add(browseTab);
this.searchTabLink = new AjaxLink<Object>("searchTabLink") {
@Override
public void onClick(AjaxRequestTarget target) {
searchTab.setVisible(true);
browseTab.setVisible(false);
searchTabLink.setEnabled(false);
browseTabLink.setEnabled(true);
target.addComponent(popupContent);
}
};
popupContent.add(searchTabLink);
this.browseTabLink = new AjaxLink<Object>("browseTabLink") {
@Override
public void onClick(AjaxRequestTarget target) {
searchTab.setVisible(false);
browseTab.setVisible(true);
searchTabLink.setEnabled(true);
browseTabLink.setEnabled(false);
target.addComponent(popupContent);
}
};
popupContent.add(browseTabLink);
if (activeTab == ACTIVE_TAB_BROWSE) {
searchTab.setVisible(false);
browseTab.setVisible(true);
searchTabLink.setEnabled(true);
browseTabLink.setEnabled(false);
} else {
searchTab.setVisible(true);
browseTab.setVisible(false);
searchTabLink.setEnabled(false);
browseTabLink.setEnabled(true);
}
}
protected boolean isMaxOneCardinality() {
FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance();
return fieldInstance.getFieldAssignment().getFieldDefinition().getCardinality().isMaxOne();
}
private WebMarkupContainer createSearchTab() {
WebMarkupContainer searchTab = new WebMarkupContainer("searchTab");
searchTab.setOutputMarkupId(true);
final TextField<String> searchTermField = new TextField<String>("searchTerm", new Model<String>(null));
searchTermField.add(new AjaxFormComponentUpdatingBehavior("onchange") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// do nothing here as we only want the server to be notified
}
});
searchTab.add(searchTermField);
final WebMarkupContainer resultsContainer = new WebMarkupContainer("resultsContainer");
resultsContainer.setOutputMarkupId(true);
searchTab.add(resultsContainer);
this.results = new LoadableDetachableModel<List<Topic>>() {
@Override
protected List<Topic> load() {
String searchTerm = (String)searchTermField.getModelObject();
if (searchTerm == null) {
return Collections.emptyList();
} else {
try {
FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance();
RoleField associationField = (RoleField)fieldInstance.getFieldAssignment().getFieldDefinition();
RoleField otherField = (RoleField)associationField.getFieldsForOtherRoles().iterator().next();
return otherField.searchAllowedPlayers(searchTerm);
} catch(Exception e) {
errorInSearch = true;
return Collections.emptyList();
}
}
}
};
Button searchButton = new Button("search");
searchButton.add(new AjaxFormComponentUpdatingBehavior("onclick") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
results.detach();
target.addComponent(resultsContainer);
}
});
searchTab.add(searchButton);
final FormComponent<? extends Object> checkGroup;
final boolean maxOneCardinality = isMaxOneCardinality();
if (maxOneCardinality) {
checkGroup = new RadioGroup<String>("checkGroup", new Model<String>());
} else {
checkGroup = new CheckGroup<String>("checkGroup", new HashSet<String>());
}
checkGroup.add(new AjaxFormChoiceComponentUpdatingBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// notify server when selection is made
}
});
resultsContainer.add(checkGroup);
final WebMarkupContainer unsuccessfulSearchContainer = new WebMarkupContainer("unsuccessfulSearchContainer") {
public boolean isVisible() {
IModel<String> model = searchTermField.getModel();
if (model == null)
return false;
return !(model.getObject() == null ||
model.getObject().equals("")) &&
((List<Topic>)results.getObject()).isEmpty();
}
};
unsuccessfulSearchContainer.setOutputMarkupPlaceholderTag(true);
checkGroup.add(unsuccessfulSearchContainer);
Label message = new Label("message", new ResourceModel(errorInSearch ? "search.error" : "search.empty"));
unsuccessfulSearchContainer.add(message);
ListView<Topic> listView = new ListView<Topic>("results", results) {
public void populateItem(ListItem<Topic> item) {
Topic hit = item.getModelObject();
if (maxOneCardinality) {
Radio<String> check = new Radio<String>("check", new Model<String>(hit.getId())) {
@Override
protected void onComponentTag(final ComponentTag tag) {
tag.put("type", "radio");
super.onComponentTag(tag);
}
};
item.add(check);
} else {
Check<String> check = new Check<String>("check", new Model<String>(hit.getId())) {
@Override
protected void onComponentTag(final ComponentTag tag) {
tag.put("type", "checkbox");
super.onComponentTag(tag);
}
};
item.add(check);
}
item.add(new Label("topic", new Model<String>(hit.getName())));
item.add(new Label("type", new Model<String>(((TopicType)hit.getTopicTypes().iterator().next()).getName())));
}
};
checkGroup.add(listView);
Button closeOkButton = new Button("closeOK");
closeOkButton.add(new AjaxFormComponentUpdatingBehavior("onclick") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
Object modelObject = checkGroup.getModelObject();
Collection selected;
if (modelObject instanceof String)
selected = Collections.singleton(modelObject);
else
selected = (Collection)modelObject;
if (selected == null) selected = Collections.emptyList();
// add associations for selected topics
onSelectionConfirmed(target, selected);
onCloseOk(target);
// reset search term field
searchTermField.getDefaultModel().setObject(null);
}
});
searchTab.add(closeOkButton);
Button closeCancelButton = new Button("closeCancel");
closeCancelButton.add(new AjaxFormComponentUpdatingBehavior("onclick") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
onCloseCancel(target);
// reset search term field
searchTermField.getDefaultModel().setObject(null);
}
});
searchTab.add(closeCancelButton);
return searchTab;
}
private WebMarkupContainer createBrowseTab() {
final WebMarkupContainer browseTab = new WebMarkupContainer("browseTab");
browseTab.setOutputMarkupId(true);
this.playerTypesChoicesModel = new LoadableDetachableModel<List<TopicType>>() {
@Override
protected List<TopicType> load() {
// TODO: should merge with PlayerTypesModel.java (extend to filter my large instance types)
FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance();
RoleField associationField = (RoleField)fieldInstance.getFieldAssignment().getFieldDefinition();
// FIXME: this doesn't work for n+ary fields
RoleField otherField = (RoleField)associationField.getFieldsForOtherRoles().iterator().next();
TopicMap tm = associationField.getTopicMap();
// include all topic types except those with large instance sets
Collection<TopicType> allowedValueTypes = otherField.getDeclaredPlayerTypes();
Collection<TopicType> largeInstanceSets = tm.getTopicTypesWithLargeInstanceSets();
List<TopicType> topicTypes = new ArrayList<TopicType>(allowedValueTypes.size());
Iterator<TopicType> iter = allowedValueTypes.iterator();
while (iter.hasNext()) {
TopicType topicType = iter.next();
if (!largeInstanceSets.contains(topicType))
topicTypes.add(topicType);
}
return topicTypes;
}
};
List<TopicType> playerTypes = playerTypesChoicesModel.getObject();
TopicType selectedType = playerTypes.size() == 1 ? playerTypes.get(0) : null;
this.selectedTypeModel = new TopicModel<TopicType>(selectedType, TopicModel.TYPE_TOPIC_TYPE);
final WebMarkupContainer resultsContainer = new WebMarkupContainer("resultsContainer");
resultsContainer.setOutputMarkupId(true);
browseTab.add(resultsContainer);
final FormComponent checkGroup;
final Model<String> radioGroupModel = new Model<String>();
final Collection<String> checkGroupModel = new HashSet<String>();
final boolean maxOneCardinality = isMaxOneCardinality();
if (maxOneCardinality) {
checkGroup = new RadioGroup<String>("checkGroup", radioGroupModel);
} else {
checkGroup = new CheckGroup<String>("checkGroup", checkGroupModel);
}
final AjaxParentFormChoiceComponentUpdatingBehavior apfc = new AjaxParentFormChoiceComponentUpdatingBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
// notify server when selection is made
}
};
checkGroup.add(apfc);
resultsContainer.add(checkGroup);
// create a tree
final TreePanel treePanel = new TreePanel("results", getTreeModel(null)) {
@Override
protected Component populateNode(String id, TreeNode treeNode) {
DefaultMutableTreeNode mTreeNode = (DefaultMutableTreeNode)treeNode;
final TopicNode node = (TopicNode)mTreeNode.getUserObject();
Topic selectedType = selectedTypeModel.getTopic();
final boolean selectable = node.getTopic().isInstanceOf(selectedType);
// create link with label
return new CheckLabelPanel(id) {
@Override
protected Component newCheck(String id) {
if (maxOneCardinality) {
return new AjaxParentRadioChild<String>(id, new Model<String>(node.getTopicId()), apfc) {
@Override
public boolean isVisible() {
return selectable;
}
@Override
protected void onComponentTag(final ComponentTag tag) {
tag.put("type", "radio");
super.onComponentTag(tag);
}
};
} else {
return new AjaxParentCheckChild(id, new Model<String>(node.getTopicId()), apfc) {
@Override
public boolean isVisible() {
return selectable;
}
@Override
protected void onComponentTag(final ComponentTag tag) {
tag.put("type", "checkbox");
super.onComponentTag(tag);
}
};
}
}
@Override
protected Label newLabel(String id) {
Label label = new Label(id, new Model<String>(node.getName()));
label.setRenderBodyOnly(false);
return label;
}
};
}
};
treePanel.setOutputMarkupId(true);
checkGroup.add(treePanel);
// NOTE: need to readd model here because page, which we depend on
// in the construction of the tree model, is not available in
// TreePanel constructor
if (this.selectedTypeModel != null)
treePanel.setDefaultModel(getTreeModel(selectedType));
final TopicDropDownChoice<TopicType> playerTypesDropDown = new TopicDropDownChoice<TopicType>("playerTypes", this.selectedTypeModel, playerTypesChoicesModel);
playerTypesDropDown.add(new AjaxFormComponentUpdatingBehavior("onchange") {
protected void onUpdate(AjaxRequestTarget target) {
// replace tree model
TopicType topicType = (TopicType)playerTypesDropDown.getDefaultModelObject();
treePanel.setDefaultModel(getTreeModel(topicType));
target.addComponent(resultsContainer);
}
});
browseTab.add(playerTypesDropDown);
Button closeOkButton = new Button("closeOK");
closeOkButton.add(new AjaxFormComponentUpdatingBehavior("onclick") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
Object modelObject = checkGroup.getModelObject();
Collection selected;
if (modelObject instanceof String)
selected = Collections.singleton(modelObject);
else
selected = (Collection)modelObject;
if (selected == null) selected = Collections.EMPTY_LIST;
// add associations for selected topics
onSelectionConfirmed(target, selected);
onCloseOk(target);
// reset selected topic type, choice group and tree model
selectedTypeModel.setObject(null);
treePanel.setDefaultModel(getTreeModel(null));
radioGroupModel.setObject(null);
checkGroupModel.clear();
}
});
browseTab.add(closeOkButton);
Button closeCancelButton = new Button("closeCancel");
closeCancelButton.add(new AjaxFormComponentUpdatingBehavior("onclick") {
@Override
protected void onUpdate(AjaxRequestTarget target) {
onCloseCancel(target);
// reset selected topic type, choice group and tree model
selectedTypeModel.setObject(null);
treePanel.setDefaultModel(getTreeModel(null));
radioGroupModel.setObject(null);
checkGroupModel.clear();
}
});
browseTab.add(closeCancelButton);
return browseTab;
}
protected IModel<TreeModel> getTreeModel(TopicType _topicType) {
TopicTypeModel topicTypeModel = new TopicTypeModel(_topicType);
TopicType topicType = topicTypeModel.getTopicType();
if (topicType == null) {
return new Model((Serializable)emptyTreeModel);
} else {
// AbstractOntopolyPage page = (AbstractOntopolyPage)getPage();
boolean adminEnabled = false; // page.isAdministrationEnabled();
return new Model((Serializable)TreeModels.createInstancesTreeModel(topicType, adminEnabled));
}
}
protected abstract void onSelectionConfirmed(AjaxRequestTarget target, Collection<T> selected);
protected abstract void onCloseOk(AjaxRequestTarget target);
protected abstract void onCloseCancel(AjaxRequestTarget target);
@Override
public void onDetach() {
playerTypesChoicesModel.detach();
selectedTypeModel.detach();
results.detach();
super.onDetach();
}
}