/* * #! * 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.components; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import net.ontopia.utils.ObjectUtils; import ontopoly.LockManager; import ontopoly.OntopolySession; import ontopoly.jquery.DraggableBehavior; import ontopoly.jquery.DroppableBehavior; import ontopoly.model.CreateAction; import ontopoly.model.EditMode; import ontopoly.model.FieldAssignment; import ontopoly.model.FieldInstance; import ontopoly.model.InterfaceControl; import ontopoly.model.RoleField; import ontopoly.model.Topic; import ontopoly.model.TopicMap; import ontopoly.models.FieldDefinitionModel; import ontopoly.models.FieldInstanceModel; import ontopoly.models.FieldValueModel; import ontopoly.models.FieldValuesModel; import ontopoly.models.FieldsViewModel; import ontopoly.models.RoleFieldModel; import ontopoly.models.TopicModel; import ontopoly.pages.AbstractOntopolyPage; import ontopoly.pages.ModalFindPage; import ontopoly.utils.RoleFieldValueComparator; import org.apache.wicket.Component; import org.apache.wicket.MarkupContainer; import org.apache.wicket.PageParameters; import org.apache.wicket.Session; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.list.ListItem; import org.apache.wicket.markup.html.list.ListView; import org.apache.wicket.markup.html.panel.FeedbackPanel; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; public class FieldInstanceAssociationBinaryPanel extends AbstractFieldInstancePanel { protected final ConfirmDeletePanel confirmDeletePanel; protected final RoleFieldModel ofieldModel; protected final TopicModel<Topic> topicModel; protected final RoleFieldModel roleFieldModel; public FieldInstanceAssociationBinaryPanel(String id, final FieldInstanceModel fieldInstanceModel, final FieldsViewModel fieldsViewModel, final boolean readonlyField, final boolean traversable) { this(id, fieldInstanceModel, fieldsViewModel, readonlyField, false, traversable); } protected FieldInstanceAssociationBinaryPanel(String id, final FieldInstanceModel fieldInstanceModel, final FieldsViewModel fieldsViewModel, final boolean readonlyField, final boolean embedded, final boolean traversable) { super(id, fieldInstanceModel); FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance(); FieldAssignment fieldAssignment = fieldInstance.getFieldAssignment(); RoleField roleField = (RoleField)fieldAssignment.getFieldDefinition(); this.roleFieldModel = new RoleFieldModel(roleField); add(new FieldDefinitionLabel("fieldLabel", new FieldDefinitionModel(roleField))); // set up container this.fieldValuesContainer = new WebMarkupContainer("fieldValuesContainer"); fieldValuesContainer.setOutputMarkupId(true); add(fieldValuesContainer); // add feedback panel this.feedbackPanel = new FeedbackPanel("feedback", new AbstractFieldInstancePanelFeedbackMessageFilter()); feedbackPanel.setOutputMarkupId(true); fieldValuesContainer.add(feedbackPanel); this.confirmDeletePanel = new ConfirmDeletePanel("confirm", fieldValuesContainer) { @Override protected void onDeleteTopic(AjaxRequestTarget target) { super.onDeleteTopic(target); FieldInstanceAssociationBinaryPanel.this.onUpdate(target); } }; confirmDeletePanel.setOutputMarkupId(true); fieldValuesContainer.add(confirmDeletePanel); RoleField ofield = (RoleField)roleField.getFieldsForOtherRoles().iterator().next(); this.ofieldModel = new RoleFieldModel(ofield); this.topicModel = new TopicModel<Topic>(fieldInstance.getInstance()); InterfaceControl interfaceControl = ofield.getInterfaceControl(); WebMarkupContainer fieldValuesList = new WebMarkupContainer("fieldValuesList"); fieldValuesContainer.add(fieldValuesList); final String fieldDefinitionId = roleField.getId(); EditMode editMode = roleField.getEditMode(); final boolean ownedvalues = editMode.isOwnedValues(); final boolean allowAdd = !(ownedvalues || editMode.isNewValuesOnly() || editMode.isNoEdit()); final boolean allowCreate = !(editMode.isExistingValuesOnly() || editMode.isNoEdit()); final boolean allowRemove = !editMode.isNoEdit(); final boolean sortable = roleField.isSortable(); // add field values component(s) // TODO: consider moving ordering logic into object model if (sortable) { // HACK: retrieving values ourselves so that we can get them ordered this.fieldValuesModel = new FieldValuesModel(fieldInstanceModel) { @Override protected List<RoleField.ValueIF> getValues(FieldInstance fieldInstance) { Topic instance = fieldInstance.getInstance(); RoleField roleField = (RoleField)fieldInstance.getFieldAssignment().getFieldDefinition(); return roleField.getOrderedValues(instance, ofieldModel.getRoleField()); } }; } else { Comparator<Object> comparator = new RoleFieldValueComparator(topicModel, ofieldModel); this.fieldValuesModel = new FieldValuesModel(fieldInstanceModel, comparator); } this.listView = new ListView<FieldValueModel>("fieldValues", fieldValuesModel) { @Override protected void onBeforeRender() { validateCardinality(); super.onBeforeRender(); } public void populateItem(final ListItem<FieldValueModel> item) { FieldValueModel fieldValueModel = item.getModelObject(); // get topic Topic oplayer = null; if (fieldValueModel.isExistingValue()) { RoleField.ValueIF valueIf = (RoleField.ValueIF)fieldValueModel.getObject(); RoleField ofield = ofieldModel.getRoleField(); oplayer = valueIf.getPlayer(ofield, fieldInstanceModel.getFieldInstance().getInstance()); } final String topicMapId = (oplayer == null ? null : oplayer.getTopicMap().getId()); final String topicId = (oplayer == null ? null : oplayer.getId()); // acquire lock for embedded topic final boolean isLockedByOther; if (embedded && fieldValueModel.isExistingValue()) { OntopolySession session = (OntopolySession)Session.get(); String lockerId = session.getLockerId(getRequest()); LockManager.Lock lock = session.lock(oplayer, lockerId); isLockedByOther = !lock.ownedBy(lockerId); } else { isLockedByOther = false; } final boolean readonly = readonlyField || isLockedByOther; boolean itemSortable = !readonly && sortable && fieldValueModel.isExistingValue(); if (itemSortable) { item.add(new DroppableBehavior(fieldDefinitionId) { @Override protected MarkupContainer getDropContainer() { return listView; } @Override protected void onDrop(Component component, AjaxRequestTarget target) { FieldValueModel fvm_dg = (FieldValueModel)component.getDefaultModelObject(); FieldValueModel fvm_do = (FieldValueModel)getComponent().getDefaultModelObject(); RoleField.ValueIF rfv_dg = (RoleField.ValueIF)fvm_dg.getFieldValue(); RoleField.ValueIF rfv_do = (RoleField.ValueIF)fvm_do.getFieldValue(); Topic topic = topicModel.getTopic(); RoleField rfield = roleFieldModel.getRoleField(); RoleField ofield = ofieldModel.getRoleField(); rfield.moveAfter(topic, ofield, rfv_dg, rfv_do); getModel().detach(); // FIXME: better if we could just tweak model directly without detaching listView.removeAll(); target.addComponent(fieldValuesContainer); } }); item.add(new DraggableBehavior(fieldDefinitionId)); } item.setOutputMarkupId(true); WebMarkupContainer fieldIconContainer = new WebMarkupContainer("fieldIconContainer"); fieldIconContainer.add(new OntopolyImage("fieldIcon", "dnd.gif", new ResourceModel("icon.dnd.reorder"))); fieldIconContainer.setVisible(itemSortable); item.add(fieldIconContainer); final WebMarkupContainer fieldValueButtons = new WebMarkupContainer("fieldValueButtons"); fieldValueButtons.setOutputMarkupId(true); item.add(fieldValueButtons); FieldInstanceRemoveButton removeButton = new FieldInstanceRemoveButton("remove", "remove-value.gif", fieldValueModel) { @Override public boolean isVisible() { boolean visible = !readonly && fieldValueModel.isExistingValue() && allowRemove; // && !isValueProtected; if (visible) { // filter by player AbstractOntopolyPage page = (AbstractOntopolyPage)getPage(); RoleField.ValueIF value = (RoleField.ValueIF)fieldValueModel.getObject(); Topic[] players = value.getPlayers(); for (int i=0; i < players.length; i++) { if (!page.filterTopic(players[i])) return false; } } return visible; } @Override public void onClick(AjaxRequestTarget target) { // FIXME: could reuse some of these variable from above FieldInstance fieldInstance = fieldValueModel.getFieldInstanceModel().getFieldInstance(); Object value = fieldValueModel.getObject(); Topic currentTopic = fieldInstance.getInstance(); RoleField currentField = (RoleField)fieldInstance.getFieldAssignment().getFieldDefinition(); RoleField selectedField = ofieldModel.getRoleField(); RoleField.ValueIF valueIf = (RoleField.ValueIF)value; Topic selectedTopic = valueIf.getPlayer(selectedField, fieldInstance.getInstance()); // check with page to see if add is allowed boolean changesMade = false; AbstractOntopolyPage page = (AbstractOntopolyPage)getPage(); if (page.isRemoveAllowed(currentTopic, currentField, selectedTopic, selectedField)) { if (ownedvalues) { // don't remove system topics if (!selectedTopic.isSystemTopic()) { FieldInstanceAssociationBinaryPanel.this.confirmDeletePanel.setTopic(selectedTopic); changesMade = true; } } else { fieldInstance.removeValue(value, page.getListener()); changesMade = true; } } // notify association panel so that it can update itself if (changesMade) FieldInstanceAssociationBinaryPanel.this.onUpdate(target); } }; fieldValueButtons.add(removeButton); // embedded goto button OntopolyImageLink gotoButton = new OntopolyImageLink("goto", "goto.gif", new ResourceModel("icon.goto.topic")) { @Override public boolean isVisible() { FieldValueModel fieldValueModel = item.getModelObject(); return embedded && fieldValueModel.isExistingValue(); } @Override public void onClick(AjaxRequestTarget target) { // navigate to topic PageParameters pageParameters = new PageParameters(); pageParameters.put("topicMapId", topicMapId); pageParameters.put("topicId", topicId); setResponsePage(getPage().getClass(), pageParameters); setRedirect(true); } }; fieldValueButtons.add(gotoButton); // embedded lock button OntopolyImageLink lockButton = new OntopolyImageLink("lock", "lock.gif", new ResourceModel("icon.topic.locked")) { @Override public boolean isVisible() { return embedded && isLockedByOther; } @Override public void onClick(AjaxRequestTarget target) { } }; fieldValueButtons.add(lockButton); // binary // ISSUE: should not really pass in readonly-parameter here as it is only relevant if page is readonly FieldInstanceAssociationBinaryField binaryField = new FieldInstanceAssociationBinaryField("fieldValue", ofieldModel, fieldValueModel, fieldsViewModel, readonly, embedded, traversable, allowAdd) { @Override protected void performNewSelection(FieldValueModel fieldValueModel, RoleField selectedField, Topic selectedTopic) { RoleField.ValueIF value = FieldInstanceAssociationBinaryPanel.this.performNewSelection(selectedField, selectedTopic); fieldValueModel.setExistingValue(value); } }; if (binaryField.getUpdateableComponent() != null) binaryField.getUpdateableComponent().add(new FieldUpdatingBehaviour(true)); item.add(binaryField); addNewFieldValueCssClass(item, fieldValuesModel, fieldValueModel); } }; listView.setReuseItems(true); fieldValuesList.add(listView); // figure out which buttons to show this.fieldInstanceButtons = new WebMarkupContainer("fieldInstanceButtons"); fieldInstanceButtons.setOutputMarkupId(true); add(fieldInstanceButtons); if (readonlyField || !allowAdd) { // unused components fieldInstanceButtons.add(new Label("add", new Model<String>("unused")).setVisible(false)); fieldInstanceButtons.add(new Label("find", new Model<String>("unused")).setVisible(false)); fieldInstanceButtons.add(new Label("findModal", new Model<String>("unused")).setVisible(false)); // add/find button } else if (interfaceControl.isDropDownList() || interfaceControl.isAutoComplete()) { // "add" button OntopolyImageLink addButton = new OntopolyImageLink("add", "add.gif") { @Override public void onClick(AjaxRequestTarget target) { boolean showExtraField = !fieldValuesModel.getShowExtraField(); fieldValuesModel.setShowExtraField(showExtraField, true); listView.removeAll(); updateDependentComponents(target); } @Override public boolean isVisible() { if (readonlyField) return false; else return fieldValuesModel.containsExisting(); } @Override public String getImage() { return fieldValuesModel.getShowExtraField() ? "remove.gif" : "add.gif"; } @Override public IModel<String> getTitleModel() { return new ResourceModel(fieldValuesModel.getShowExtraField() ? "icon.remove.hide-field" : "icon.add.add-value"); } }; fieldInstanceButtons.add(addButton); // unused components fieldInstanceButtons.add(new Label("find", new Model<String>("unused")).setVisible(false)); fieldInstanceButtons.add(new Label("findModal", new Model<String>("unused")).setVisible(false)); } else if (interfaceControl.isSearchDialog() || interfaceControl.isBrowseDialog()) { // "search"/"browse" button final ModalWindow findModal = new ModalWindow("findModal"); fieldInstanceButtons.add(findModal); int activeTab = (interfaceControl.isSearchDialog() ? ModalFindPage.ACTIVE_TAB_SEARCH : ModalFindPage.ACTIVE_TAB_BROWSE); findModal.setContent(new ModalFindPage<String>(findModal.getContentId(), fieldInstanceModel, activeTab) { @Override protected void onSelectionConfirmed(AjaxRequestTarget target, Collection<String> selected) { FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance(); RoleField currentField = (RoleField)fieldInstance.getFieldAssignment().getFieldDefinition(); RoleField selectedField = (RoleField)currentField.getFieldsForOtherRoles().iterator().next(); // check with page to see if add is allowed if (ObjectUtils.different(currentField, selectedField) || // if assoc type is symmetric currentField == selectedField, // but we're still OK to go ahead, so checking for that // (this is issue 457) currentField.getAssociationType().isSymmetric()) { Topic currentTopic = fieldInstance.getInstance(); TopicMap topicMap = currentTopic.getTopicMap(); boolean changesMade = false; Iterator<String> iter = selected.iterator(); while (iter.hasNext()) { String objectId = (String)iter.next(); AbstractOntopolyPage page = (AbstractOntopolyPage)getPage(); Topic selectedTopic = topicMap.getTopicById(objectId); if (page.isAddAllowed(currentTopic, currentField, selectedTopic, selectedField)) { performNewSelection(selectedField, selectedTopic); changesMade = true; } } // notify association panel so that it can update itself if (changesMade) FieldInstanceAssociationBinaryPanel.this.onUpdate(target); } } @Override protected void onCloseCancel(AjaxRequestTarget target) { findModal.close(target); } @Override protected void onCloseOk(AjaxRequestTarget target) { findModal.close(target); } }); findModal.setTitle(new ResourceModel("ModalWindow.title.find.topic").getObject().toString()); findModal.setCookieName("findModal"); OntopolyImageLink findButton = new OntopolyImageLink("find", "search.gif", new ResourceModel("find.topic")) { @Override public void onClick(AjaxRequestTarget target) { findModal.show(target); } }; fieldInstanceButtons.add(findButton); // unused components fieldInstanceButtons.add(new Label("add", new Model<String>("unused")).setVisible(false)); } else { throw new RuntimeException("Unsupported interface control: " + interfaceControl); } // create button if (readonlyField || !allowCreate) { fieldInstanceButtons.add(new Label("create").setVisible(false)); } else { CreateAction ca = roleField.getCreateAction(); int createAction; if (embedded || ca.isNone()) createAction = FieldInstanceCreatePlayerPanel.CREATE_ACTION_NONE; else if (ca.isNavigate()) createAction = FieldInstanceCreatePlayerPanel.CREATE_ACTION_NAVIGATE; else createAction = FieldInstanceCreatePlayerPanel.CREATE_ACTION_POPUP; FieldInstanceCreatePlayerPanel createPanel = new FieldInstanceCreatePlayerPanel("create", fieldInstanceModel, fieldsViewModel, new RoleFieldModel(ofield), this, createAction) { @Override protected void performNewSelection(RoleFieldModel ofieldModel, Topic selectedTopic) { FieldInstanceAssociationBinaryPanel.this.performNewSelection(ofieldModel.getRoleField(), selectedTopic); } }; createPanel.setOutputMarkupId(true); fieldInstanceButtons.add(createPanel); } } protected RoleField.ValueIF performNewSelection(RoleField selectedField, Topic selectedTopic) { FieldInstance fieldInstance = fieldInstanceModel.getFieldInstance(); RoleField currentField = (RoleField)fieldInstance.getFieldAssignment().getFieldDefinition(); Topic currentTopic = fieldInstance.getInstance(); RoleField.ValueIF value = RoleField.createValue(2); value.addPlayer(currentField, currentTopic); value.addPlayer(selectedField, selectedTopic); AbstractOntopolyPage page = (AbstractOntopolyPage)getPage(); fieldInstance.addValue(value, page.getListener()); // currentField.addValue(fieldInstance, value, page.getListener()); return value; } @Override public void onDetach() { ofieldModel.detach(); topicModel.detach(); roleFieldModel.detach(); super.onDetach(); } }