/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.pms.ui.concept.editor; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.pentaho.pms.schema.concept.ConceptInterface; import org.pentaho.pms.util.ObjectAlreadyExistsException; /** * Dialog for editing the base sets of concepts from which all other concepts inherit. * * <p>A note about <code>lastSelection</code>:</p> * <p>lastSelection keeps track of the last concept to which we successfully transitioned. Why is this necessary? * Because state on the screen may be invalid (e.g. user entered bogus value) then we must stop the transition, alert * the user, and switch back to the offending screen. Here, "screen" is a card in a stack layout.</p> * * @author mlowery */ public class ConceptEditorDialog extends Dialog { // ~ Static fields/initializers ====================================================================================== private static final Log logger = LogFactory.getLog(ConceptEditorDialog.class); // ~ Instance fields ================================================================================================= protected String activeLocale; protected Composite detailsComposite; protected StackLayout stackLayout; protected Composite cardComposite; protected Map propertyEditorContext = new HashMap(); protected ConceptModelRegistry conceptModelRegistry = new ConceptModelRegistry(); protected Map<ConceptInterface, Composite> cards = new HashMap<ConceptInterface, Composite>(); protected Control defaultCard; private Text conceptNameField; private IConceptTreeModel conceptTreeModel; private ISelectionChangedListener conceptTreeSelectionChangedListener; private ConceptTreeWidget conceptTree; private PropertyWidgetManager2 propertyWidgetManager; private ConceptInterface lastSelection; // ~ Constructors ==================================================================================================== public ConceptEditorDialog(final Shell parent, final IConceptTreeModel conceptTreeModel) { super(parent); propertyEditorContext.put("locales", conceptTreeModel.getSchemaMeta().getLocales()); this.conceptTreeModel = conceptTreeModel; } // ~ Methods ========================================================================================================= protected void setShellStyle(int newShellStyle) { super.setShellStyle(newShellStyle | SWT.RESIZE); } protected void configureShell(final Shell shell) { super.configureShell(shell); shell.setText("Concept Editor"); } protected Point getInitialSize() { return new Point(1000, 800); } protected final Control createDialogArea(final Composite parent) { Composite c0 = (Composite) super.createDialogArea(parent); Composite container = new Composite(c0, SWT.NONE); container.setLayout(new FormLayout()); GridData gdContainer = new GridData(GridData.FILL_BOTH); container.setLayoutData(gdContainer); SashForm s0 = new SashForm(container, SWT.HORIZONTAL); FormData fdSash = new FormData(); fdSash.left = new FormAttachment(0, 0); fdSash.top = new FormAttachment(0, 0); fdSash.right = new FormAttachment(100, 0); fdSash.bottom = new FormAttachment(100, 0); s0.setLayoutData(fdSash); Composite c12 = new Composite(s0, SWT.NONE); c12.setLayout(new FormLayout()); Composite placeholderComposite = new Composite(c12, SWT.NONE); FormData fdDetailsComposite = new FormData(); fdDetailsComposite.top = new FormAttachment(0, 0); fdDetailsComposite.left = new FormAttachment(0, 0); fdDetailsComposite.right = new FormAttachment(100, -5); fdDetailsComposite.bottom = new FormAttachment(100, 0); placeholderComposite.setLayoutData(fdDetailsComposite); placeholderComposite.setLayout(new FormLayout()); conceptTree = new ConceptTreeWidget(placeholderComposite, SWT.NONE, conceptTreeModel, true); FormData fdlList = new FormData(); fdlList.left = new FormAttachment(0, 0); fdlList.top = new FormAttachment(0, 0); fdlList.right = new FormAttachment(100, 0); fdlList.bottom = new FormAttachment(100, 0); conceptTree.setLayoutData(fdlList); conceptTreeSelectionChangedListener = new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent e) { if (lastSelection != null && lastSelection.equals(((StructuredSelection) e.getSelection()).getFirstElement())) { return; } boolean hasErrors = popupValidationErrorDialogIfNecessary(); if (!hasErrors) { if (logger.isDebugEnabled()) { logger.debug("heard conceptTree selection changed event: " + e); logger.debug("attempting to swap cards"); } if (!e.getSelection().isEmpty()) { TreeSelection treeSel = (TreeSelection) e.getSelection(); if (treeSel.getFirstElement() instanceof ConceptInterface) { ConceptInterface cu = (ConceptInterface) treeSel.getFirstElement(); swapCard(cu); } else { swapCard(null); } } } else { // set selection back where it was if (!lastSelection.equals(((TreeSelection) e.getSelection()).getFirstElement())) { conceptTree.setSelection(new StructuredSelection(lastSelection)); } } } }; conceptTree.addSelectionChangedListener(conceptTreeSelectionChangedListener); Composite spacer = new Composite(s0, SWT.NONE); spacer.setLayout(new FormLayout()); cardComposite = new Composite(spacer, SWT.NONE); FormData fdCardComposite = new FormData(); fdCardComposite.top = new FormAttachment(0, 0); fdCardComposite.left = new FormAttachment(0, 5); fdCardComposite.right = new FormAttachment(100, 0); fdCardComposite.bottom = new FormAttachment(100, 0); cardComposite.setLayoutData(fdCardComposite); stackLayout = new StackLayout(); cardComposite.setLayout(stackLayout); defaultCard = new DefaultCard(cardComposite, SWT.NONE); swapCard(null); s0.setWeights(new int[] { 1, 3 }); return c0; } protected void okPressed() { boolean hasErrors = popupValidationErrorDialogIfNecessary(); if (!hasErrors) { try { conceptTreeModel.save(); } catch (ObjectAlreadyExistsException e) { if (logger.isErrorEnabled()) { logger.error("an exception occurred", e); } MessageDialog.openError(getShell(), "Error", "There was an error during save."); } cleanup(); super.okPressed(); } } protected void cleanup() { conceptTree.removeSelectionChangedListener(conceptTreeSelectionChangedListener); } private void swapCard(final ConceptInterface concept) { if (null == concept) { stackLayout.topControl = defaultCard; } else { if (null == cards.get(concept)) { IConceptModel conceptModel = conceptModelRegistry.getConceptModel(concept); Composite conceptEditor = new Composite(cardComposite, SWT.NONE); conceptEditor.setLayout(new FillLayout()); Group group = new Group(conceptEditor, SWT.SHADOW_OUT); group.setText("Properties"); group.setLayout(new FillLayout()); SashForm s0 = new SashForm(group, SWT.HORIZONTAL); s0.SASH_WIDTH = 10; PropertyNavigationWidget propertyNavigationWidget = new PropertyNavigationWidget(s0, SWT.NONE); propertyNavigationWidget.setConceptModel(conceptModel); propertyWidgetManager = new PropertyWidgetManager2(s0, SWT.NONE, propertyEditorContext, conceptTreeModel .getSchemaMeta().getSecurityReference()); propertyWidgetManager.setConceptModel(conceptModel); propertyNavigationWidget.addSelectionChangedListener(propertyWidgetManager); s0.setWeights(new int[] { 1, 2 }); cards.put(concept, conceptEditor); } stackLayout.topControl = (Control) cards.get(concept); } lastSelection = concept; cardComposite.layout(); } protected void cancelPressed() { cleanup(); super.cancelPressed(); } /** * The card that shows when there is no selection in the concept selection tree. */ private class DefaultCard extends Composite { public DefaultCard(final Composite parent, final int style) { super(parent, style); createContents(); } private void createContents() { setLayout(new GridLayout()); Label lab0 = new Label(this, SWT.CENTER); lab0.setText("Select a concept to begin editing properties."); GridData gd = new GridData(); gd.verticalAlignment = GridData.CENTER; gd.horizontalAlignment = GridData.CENTER; gd.grabExcessHorizontalSpace = true; gd.grabExcessVerticalSpace = true; lab0.setLayoutData(gd); } } /** * Unfortunate duplication of code. (Same method is in AbstractTableDialog.) */ protected boolean popupValidationErrorDialogIfNecessary() { // if propertyWidgetManager is null, then we are not currently displaying a card; we displaying the defaultCard if (propertyWidgetManager == null) { return false; } List<String> errorMessages = propertyWidgetManager.validateWidgets(); if (errorMessages.isEmpty()) { return false; } else { StringBuilder buf = new StringBuilder(); for (String errorMessage : errorMessages) { buf.append(errorMessage + "\n"); } MessageDialog.openError(getShell(), "Errors", buf.toString()); return true; } } }