/* ***************************************************************************** * JFire - it's hot - Free ERP System - http://jfire.org * * Copyright (C) 2004-2005 NightLabs - http://NightLabs.org * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * * * This library 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. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin St, Fifth Floor, * * Boston, MA 02110-1301 USA * * * * Or get it online : * * http://opensource.org/licenses/lgpl-license.php * * * * * ******************************************************************************/ package org.nightlabs.jfire.trade.ui.articlecontainer.detail; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.jdo.FetchPlan; import javax.jdo.JDOHelper; import org.apache.log4j.Logger; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Status; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import org.eclipse.ui.IEditorPart; import org.nightlabs.base.ui.composite.XComposite; import org.nightlabs.base.ui.job.Job; import org.nightlabs.base.ui.notification.NotificationAdapterJob; import org.nightlabs.eclipse.extension.EPProcessorException; import org.nightlabs.jdo.NLJDOHelper; import org.nightlabs.jfire.accounting.EditLockTypeInvoice; import org.nightlabs.jfire.accounting.Invoice; import org.nightlabs.jfire.accounting.InvoiceLocal; import org.nightlabs.jfire.accounting.dao.InvoiceDAO; import org.nightlabs.jfire.accounting.id.InvoiceID; import org.nightlabs.jfire.base.jdo.GlobalJDOManagerProvider; import org.nightlabs.jfire.base.ui.editlock.EditLockCallback; import org.nightlabs.jfire.base.ui.editlock.EditLockCarrier; import org.nightlabs.jfire.base.ui.editlock.EditLockHandle; import org.nightlabs.jfire.base.ui.editlock.EditLockMan; import org.nightlabs.jfire.base.ui.editlock.InactivityAction; import org.nightlabs.jfire.editlock.id.EditLockTypeID; import org.nightlabs.jfire.jbpm.graph.def.StatableLocal; import org.nightlabs.jfire.jdo.notification.DirtyObjectID; import org.nightlabs.jfire.store.DeliveryNote; import org.nightlabs.jfire.store.DeliveryNoteLocal; import org.nightlabs.jfire.store.EditLockTypeDeliveryNote; import org.nightlabs.jfire.store.dao.DeliveryNoteDAO; import org.nightlabs.jfire.store.id.DeliveryNoteID; import org.nightlabs.jfire.trade.Article; import org.nightlabs.jfire.trade.ArticleCarrier; import org.nightlabs.jfire.trade.ArticleContainer; import org.nightlabs.jfire.trade.ArticleSegmentGroup; import org.nightlabs.jfire.trade.EditLockTypeOffer; import org.nightlabs.jfire.trade.EditLockTypeOrder; import org.nightlabs.jfire.trade.FetchGroupsTrade; import org.nightlabs.jfire.trade.Offer; import org.nightlabs.jfire.trade.OfferLocal; import org.nightlabs.jfire.trade.Order; import org.nightlabs.jfire.trade.Segment; import org.nightlabs.jfire.trade.SegmentType; import org.nightlabs.jfire.trade.dao.OfferDAO; import org.nightlabs.jfire.trade.dao.OrderDAO; import org.nightlabs.jfire.trade.id.ArticleContainerID; import org.nightlabs.jfire.trade.id.OfferID; import org.nightlabs.jfire.trade.id.OrderID; import org.nightlabs.jfire.trade.recurring.RecurringOffer; import org.nightlabs.jfire.trade.recurring.RecurringOrder; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.action.ArticleContainerEditorActionBarContributor; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.action.IArticleContainerEditActionContributor; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.deliverynote.DeliveryNoteFooterComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.deliverynote.DeliveryNoteHeaderComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.invoice.InvoiceFooterComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.invoice.InvoiceHeaderComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.offer.OfferFooterComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.offer.OfferHeaderComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.order.OrderFooterComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.order.OrderHeaderComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.recurring.RecurringOfferFooterComposite; import org.nightlabs.jfire.trade.ui.articlecontainer.detail.recurring.RecurringOrderFooterComposite; import org.nightlabs.jfire.trade.ui.resource.Messages; import org.nightlabs.notification.NotificationEvent; import org.nightlabs.notification.NotificationListener; import org.nightlabs.progress.NullProgressMonitor; import org.nightlabs.progress.ProgressMonitor; /** * This composite might be used to implement registered {@link ArticleContainerEdit} or directly in ui-code. * One of its known uses is the implementation of {@link DefaultArticleContainerEdit}. * It might also be sub-classed in order to configure the header and footer composites. * <p> * For its main part it loads the {@link ArticleContainer} from the articleContainerID it is instantiated with * and creates a {@link ClientArticleSegmentGroupSet} to manage the articles within the container. * It also asks the {@link org.nightlabs.jfire.trade.ui.articlecontainer.detail.SegmentEditFactoryRegistry} * for the right factories for all the {@link org.nightlabs.jfire.trade.ui.Segment}s * and displays the edits which are delivered from the factory. * </p> * * @author Marco Schulze - marco at nightlabs dot de * @author Alexander Bieber <!-- alex [AT] nightlabs [DOT] de --> */ public class ArticleContainerEditComposite extends XComposite implements ArticleContainerEdit { private static final Logger logger = Logger.getLogger(ArticleContainerEditComposite.class); private ArticleContainerID articleContainerID = null; /** * This is initialized by * {@link ArticleContainerEditorActionBarContributor#setActiveEditor(IEditorPart)} as * soon as the Editor became active the first time. */ private IArticleContainerEditActionContributor articleContainerEditActionContributor = null; private ArticleContainer articleContainer = null; private HeaderComposite headerComposite; private FooterComposite footerComposite; // private List segmentCompositeScrollContainers = new ArrayList(); // private List segmentEditComposites = new ArrayList(); private Map<TabItem, SegmentEdit> segmentEditsByTabItem = new HashMap<TabItem, SegmentEdit>(); // // only used if no tabFolder, means only 1 or less segmentTypes are used // private Map<Composite, SegmentEdit> segmentEditByComposite = new HashMap<Composite, SegmentEdit>(); // private Composite singleSegmentComposite; private SegmentEdit singleSegmentSegmentEdit; private Map<String, SegmentEdit> segmentPK2segmentEditMap = new HashMap<String, SegmentEdit>(); // /** // * This composite is the scrolling carrier of the {@link // #segmentCompositeContainer}. // */ // private ScrolledComposite segmentCompositeScrollContainer; // private TabFolder segmentCompositeFolder; /** Parent Composite or TabFolder for all SegmentEditComposites */ private Composite segmentCompositeFolder; private Label loadingDataLabel; /** * @param parent * @param style */ public ArticleContainerEditComposite(Composite parent, ArticleContainerID _articleContainerID) { super(parent, SWT.NONE, LayoutMode.ORDINARY_WRAPPER); loadingDataLabel = new Label(this, SWT.NONE); loadingDataLabel.setText(Messages.getString("org.nightlabs.jfire.trade.ui.articlecontainer.detail.ArticleContainerEditComposite.label.loadingData")); //$NON-NLS-1$ loadInitialArticleContainerJob = new LoadInitialArticleContainerJob(_articleContainerID); loadInitialArticleContainerJob.schedule(); } private LoadInitialArticleContainerJob loadInitialArticleContainerJob; private class LoadInitialArticleContainerJob extends Job { private ArticleContainerID loadArticleContainerID; public LoadInitialArticleContainerJob(ArticleContainerID articleContainerID) { super(Messages.getString("org.nightlabs.jfire.trade.ui.articlecontainer.detail.ArticleContainerEditComposite.job.loadingArticleContainer")); //$NON-NLS-1$ this.loadArticleContainerID = articleContainerID; assert articleContainerID != null : "loadArticleContainerID != null"; //$NON-NLS-1$ } @Override protected org.eclipse.core.runtime.IStatus run(ProgressMonitor monitor) throws Exception { loadInitialArticleContainerJob = null; // release memory initArticleContainer(loadArticleContainerID, monitor); Display.getDefault().asyncExec(new Runnable() { public void run() { if (ArticleContainerEditComposite.this.isDisposed()) return; loadingDataLabel.dispose(); loadingDataLabel = null; headerComposite = createHeaderComposite(ArticleContainerEditComposite.this); headerComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); setShowHeader(showHeader); new Label(ArticleContainerEditComposite.this, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); if (hasDifferentSegments()) { createSegmentTabFolder(); } else { segmentCompositeFolder = new XComposite(ArticleContainerEditComposite.this, SWT.NONE, LayoutMode.TOTAL_WRAPPER); } new Label(ArticleContainerEditComposite.this, SWT.SEPARATOR | SWT.HORIZONTAL).setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); createAndConfigureFooterComposite(); updateHeaderAndFooter(); EditLockTypeID editLockTypeID = getEditLockTypeID(); // try { // createSegmentEditComposites(); // } catch (EPProcessorException e) { // throw new RuntimeException(e); // } final EditLockHandle editLockHandle = EditLockMan.sharedInstance().acquireEditLock(editLockTypeID, getArticleContainerID(), "TODO", // TODO description //$NON-NLS-1$ // null, new EditLockCallback() { @Override public InactivityAction getEditLockAction(EditLockCarrier editLockCarrier) { return InactivityAction.REFRESH_LOCK; } }, getShell(), new NullProgressMonitor()); // TODO async! // TODO whenever we change sth., we should refresh the lock by calling editLockHandle.refresh()! final Class<? extends ArticleContainer> articleContainerClass = articleContainer.getClass(); GlobalJDOManagerProvider.sharedInstance().getLifecycleManager().addNotificationListener(articleContainerClass, articleContainerChangedListener); addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { GlobalJDOManagerProvider.sharedInstance().getLifecycleManager().removeNotificationListener(articleContainerClass, articleContainerChangedListener); // ArticleContainerID articleContainerID = getArticleContainerID(); // // TODO WORKAROUND JPOX bug begin // if (articleContainerID instanceof OrderID) { // OrderID orderID = (OrderID) articleContainerID; // if (orderID.organisationID == null) { // logger.warn("orderID.organisationID == null", new NullPointerException("orderID.organisationID == null")); //$NON-NLS-1$ //$NON-NLS-2$ // orderID.organisationID = order.getOrganisationID(); // } // if (orderID.orderIDPrefix == null) { // logger.warn("orderID.orderIDPrefix == null", new NullPointerException("orderID.orderIDPrefix == null")); //$NON-NLS-1$ //$NON-NLS-2$ // orderID.orderIDPrefix = order.getOrderIDPrefix(); // } // } // // TODO WORKAROUND JPOX bug end editLockHandle.release(); if (articleSegmentGroupSet != null) articleSegmentGroupSet.onDispose(); // removeDisposeListener(this); // nötig? Marco. } }); // it is likely that the action-bar-contributor has been set too early for creating the UI, hence, we call it now. if (articleContainerEditActionContributor != null) setArticleContainerEditActionContributor(articleContainerEditActionContributor); try { if (!segmentEditCompositesCreated) createSegmentEditComposites(); } catch (EPProcessorException e) { throw new RuntimeException(e); } } // void run() }); return Status.OK_STATUS; } }; private NotificationListener articleContainerChangedListener = new NotificationAdapterJob() { @Override public void notify(NotificationEvent notificationEvent) { if (isDisposed()) return; DirtyObjectID notifiedDirtyObjectID = (DirtyObjectID) notificationEvent.getFirstSubject(); if (!getArticleContainerID().equals(notifiedDirtyObjectID.getObjectID())) { // if (logger.isDebugEnabled()) { // logger.debug(""); // } return; } // reload the new ArticleContainer from the server initArticleContainer(articleContainerID, getProgressMonitor()); // update header+footer on the UI thread Display.getDefault().asyncExec(new Runnable() { public void run() { if (isDisposed()) return; updateHeaderAndFooter(); } }); } }; public ArticleContainerID getArticleContainerID() { return articleContainerID; } /** * This method is called to create the {@link HeaderComposite} of this composite. * It might be overridden to create custom headers. * <p> * This implementations will check if one of the supported implementations of * {@link ArticleContainer} was loaded and then create one of the following: * {@link OrderHeaderComposite}, {@link OfferHeaderComposite}, {@link InvoiceHeaderComposite} * or {@link DeliveryNoteHeaderComposite}. * </p> * @param parent The parent to use for the new composite. * @return A newly created {@link HeaderComposite}. */ protected HeaderComposite createHeaderComposite(Composite parent) { if (articleContainer instanceof Order) return new OrderHeaderComposite(this, (Order) articleContainer); if(articleContainer instanceof Offer) return new OfferHeaderComposite(this, (Offer) articleContainer); if (articleContainer instanceof Invoice) return new InvoiceHeaderComposite(this, (Invoice) articleContainer); if (articleContainer instanceof DeliveryNote) return new DeliveryNoteHeaderComposite(this, (DeliveryNote) articleContainer); throw new IllegalStateException("The current ArticleContainer is of an unsupported type: " + //$NON-NLS-1$ (getArticleContainer() != null ? getArticleContainer().getClass().getName() : "null") + "."); //$NON-NLS-1$ //$NON-NLS-2$ } /** * Creaetes the Footer Composite and configures it appropirately to this Composites layout. */ private void createAndConfigureFooterComposite() { footerComposite = createFooterComposite(ArticleContainerEditComposite.this); footerComposite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); } /** * This method is called to create the {@link FooterComposite} of this composite. * It might be overridden to create custom footers. * <p> * This implementations will check if one of the supported implementations of * {@link ArticleContainer} was loaded and then create one of the following: * {@link OrderFooterComposite}, {@link OfferFooterComposite}, {@link InvoiceFooterComposite} * or {@link DeliveryNoteFooterComposite}. * </p> * @param parent The parent to use for the new composite. * @return A newly created {@link FooterComposite}. */ protected FooterComposite createFooterComposite(Composite parent) { if (articleContainer instanceof Order) return new OrderFooterComposite(parent, this); if (articleContainer instanceof RecurringOrder) return new RecurringOrderFooterComposite(parent, this); if (articleContainer instanceof Offer) return new OfferFooterComposite(parent, this); if (articleContainer instanceof RecurringOffer) return new RecurringOfferFooterComposite(parent, this); if (articleContainer instanceof Invoice) return new InvoiceFooterComposite(parent, this); if (articleContainer instanceof DeliveryNote) return new DeliveryNoteFooterComposite(parent, this); throw new IllegalStateException("The current ArticleContainer is of an unsupported type: " + //$NON-NLS-1$ (getArticleContainer() != null ? getArticleContainer().getClass().getName() : "null") + "."); //$NON-NLS-1$ //$NON-NLS-2$ } /** * This method is called to determine the {@link EditLockTypeID} * that should be used to acquire an edit lock for the edited ArticleContainer. * This implementation supports Orders, Offers, Invoices and DeliveryNotes * for all other implementations of {@link ArticleContainer} this method * should be overridden. * * @return The {@link EditLockTypeID} that should be used to acquire an edit lock. */ protected EditLockTypeID getEditLockTypeID() { if (articleContainer instanceof RecurringOrder) return EditLockTypeOrder.EDIT_LOCK_TYPE_ID; if (articleContainer instanceof RecurringOffer) return EditLockTypeOffer.EDIT_LOCK_TYPE_ID; if (articleContainer instanceof Order) return EditLockTypeOrder.EDIT_LOCK_TYPE_ID; if (articleContainer instanceof Offer) return EditLockTypeOffer.EDIT_LOCK_TYPE_ID; if (articleContainer instanceof Invoice) return EditLockTypeInvoice.EDIT_LOCK_TYPE_ID; if (articleContainer instanceof DeliveryNote) return EditLockTypeDeliveryNote.EDIT_LOCK_TYPE_ID; throw new IllegalStateException("The current ArticleContainer is of an unsupported type: " + //$NON-NLS-1$ (getArticleContainer() != null ? getArticleContainer().getClass().getName() : "null") + "."); //$NON-NLS-1$ //$NON-NLS-2$ } protected boolean hasDifferentSegments() { return articleSegmentGroupSet.getArticleSegmentGroups().size() > 1; } protected void updateHeaderAndFooter() { if (headerComposite != null) headerComposite.refresh(); footerComposite.refresh(); if (logger.isDebugEnabled()) logger.debug("updateHeaderAndFooter"); //$NON-NLS-1$ } /** * @return Returns <code>null</code> before the editor has been active the * first time. Afterwards, it returns the contributor responsible for * the editor. */ public IArticleContainerEditActionContributor getArticleContainerEditActionContributor() { return articleContainerEditActionContributor; } /** * This method is called by * {@link ArticleContainerEditorActionBarContributor#setActiveEditor(IEditorPart)}. */ @Override public void setArticleContainerEditActionContributor(IArticleContainerEditActionContributor articleContainerEditActionContributor) { this.articleContainerEditActionContributor = articleContainerEditActionContributor; } // protected void recreateSegmentEditComposites() throws EPProcessorException // { // removeSegmentEditComposites(); // createSegmentEditComposites(); // } private boolean segmentEditCompositesCreated = false; protected void removeSegmentEditComposites() { segmentEditCompositesCreated = false; if (hasDifferentSegments()) { TabItem[] tabItems = ((TabFolder)segmentCompositeFolder).getItems(); for (int i = 0; i < tabItems.length; ++i) { tabItems[i].dispose(); } segmentEditsByTabItem.clear(); } else { segmentCompositeFolder.dispose(); } } private CompositeContentChangeListener segmentCompositeContentChangeListener = new CompositeContentChangeListener() { /** * @see org.nightlabs.jfire.trade.ui.articlecontainer.detail.CompositeContentChangeListener#changed(org.nightlabs.jfire.trade.ui.articlecontainer.CompositeContentChangeEvent) */ public void changed(CompositeContentChangeEvent event) { calculateScrollContentSize(); // updateHeaderAndFooter(); // I think that's not necessary here. There are already other listeners. } }; protected void createSegmentTabFolder() { segmentCompositeFolder = new TabFolder(ArticleContainerEditComposite.this, SWT.NONE); segmentCompositeFolder.setLayoutData(new GridData(GridData.FILL_BOTH)); ((TabFolder)segmentCompositeFolder).addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { updateActiveSegmentEdit(); } }); } /** * Clears maps and members that provide index and shortcuts to SegmentEdits * created. Should only be used when the Composite is re-initialized. */ protected void clearSegmentEditIndices() { segmentEditsByTabItem.clear(); singleSegmentSegmentEdit = null; segmentPK2segmentEditMap.clear(); } protected void createSegmentEditComposite(ArticleSegmentGroup asg, SegmentEdit segmentEdit) { Segment segment = segmentEdit.getArticleSegmentGroup().getSegment(); TabItem tabItem = null; if (hasDifferentSegments()) { if (!(segmentCompositeFolder instanceof TabFolder)) { // There are different segments now, but the Composite was // initialized with only one so there are not tabs // We need to completely re-initialize the Cmposite initializeCompositeWithTabFolder(); // Nothing to do any more the Composite was re-initialized return; } else { tabItem = new TabItem((TabFolder)segmentCompositeFolder, SWT.NONE); tabItem.setText( String.format( Messages.getString("org.nightlabs.jfire.trade.ui.articlecontainer.detail.ArticleContainerEditComposite.segmentTabItem.text"), //$NON-NLS-1$ segment.getSegmentType().getName().getText(), segment.getSegmentIDAsString())); } } ScrolledComposite segmentCompositeScrollContainer; // TODO do we really not want horizontal scrolling? segmentCompositeScrollContainer = new ScrolledComposite( segmentCompositeFolder, SWT.V_SCROLL); segmentCompositeScrollContainer.setExpandHorizontal(true); segmentCompositeScrollContainer.setExpandVertical(true); segmentCompositeScrollContainer.setLayoutData(new GridData( GridData.FILL_BOTH)); // segmentCompositeScrollContainer.setAlwaysShowScrollBars(true); // TODO do we really want to ALWAYS display scroll bars? if (tabItem != null) tabItem.setControl(segmentCompositeScrollContainer); // segmentCompositeScrollContainers.add(segmentCompositeScrollContainer); // segmentCompositeContainer = new // XComposite(segmentCompositeScrollContainer, SWT.NONE, // XComposite.LAYOUT_MODE_TIGHT_WRAPPER); // segmentCompositeScrollContainer.setContent(segmentCompositeContainer); Composite composite = segmentEdit .createComposite(segmentCompositeScrollContainer); // segmentCompositeContainer); segmentCompositeScrollContainer.setContent(composite); // segmentEditComposites.add(composite); // TODO Segments can be added while this ArticleContainerEditComposite is visible. See comment at the beginning of this method! Marco. if (hasDifferentSegments()) segmentEditsByTabItem.put(tabItem, segmentEdit); else { // singleSegmentComposite = segmentCompositeFolder; singleSegmentSegmentEdit = segmentEdit; // segmentEditByComposite.put(segmentCompositeFolder, segmentEdit); } segmentPK2segmentEditMap.put(segment.getPrimaryKey(), segmentEdit); layout(true, true); } /** * Disposes the segmentCompositeFolder and re-initializes the Composite with Tabs. */ protected void initializeCompositeWithTabFolder() { // We dispose off the current parent element segmentCompositeFolder.dispose(); // If there is a footer we need to dispose that, too, the ArticleEdits // will be below the footer otherwise if (footerComposite != null) { footerComposite.dispose(); } // and now re-create as TabFolder createSegmentTabFolder(); // Additionally re-initialize the Composite. This will create all SegmentEdits try { clearSegmentEditIndices(); createSegmentEditComposites(); } catch (EPProcessorException e) { throw new RuntimeException(e); } // Recreate the footer if there was one if (footerComposite != null) { createAndConfigureFooterComposite(); updateHeaderAndFooter(); } layout(true, true); } private ClientArticleSegmentGroupSet articleSegmentGroupSet = null; protected void createArticleSegmentGroups() throws EPProcessorException { ArticleCreateListener[] articleCreateListenerArray = null; ArticleChangeListener[] articleChangeListenerArray = null; if (!earlyArticleCreateListeners.isEmpty()) { Object[] listeners = earlyArticleCreateListeners.getListeners(); articleCreateListenerArray = new ArticleCreateListener[listeners.length + 1]; System.arraycopy(listeners, 0, articleCreateListenerArray, 0, listeners.length); // for (int i=0; i<earlyArticleCreateListeners.size(); i++) { // articleCreateListenerArray[i] = earlyArticleCreateListeners.get(i); // } articleCreateListenerArray[articleCreateListenerArray.length-1] = articleCreateListener; } else { articleCreateListenerArray = new ArticleCreateListener[] { articleCreateListener }; } if (!earlyArticleChangeListeners.isEmpty()) { Object[] listeners = earlyArticleChangeListeners.getListeners(); articleChangeListenerArray = new ArticleChangeListener[listeners.length + 1]; System.arraycopy(listeners, 0, articleChangeListenerArray, 0, listeners.length); // for (int i=0; i<earlyArticleChangeListeners.size(); i++) { // articleChangeListenerArray[i] = earlyArticleChangeListeners.get(i); // } articleChangeListenerArray[articleChangeListenerArray.length-1] = articleChangeListener; } else { articleChangeListenerArray = new ArticleChangeListener[] { articleChangeListener }; } articleSegmentGroupSet = new ClientArticleSegmentGroupSet(articleContainer, articleCreateListenerArray, articleChangeListenerArray); } protected void createSegmentEditComposites() throws EPProcessorException { segmentEditCompositesCreated = true; // ArticleSegmentGroupSet asgs = new ArticleSegmentGroupSet(articleContainer); for (ArticleSegmentGroup articleSegmentGroup : articleSegmentGroupSet.getArticleSegmentGroups()) createSegmentEditAndComposite(articleSegmentGroup); updateActiveSegmentEdit(); // The following line is important! Otherwise, it wouldn't scroll correctly! calculateScrollContentSize(); } protected void createSegmentEditAndComposite(ArticleSegmentGroup asg) { Segment segment = asg.getSegment(); SegmentType segmentType = segment.getSegmentType(); Class<?> articleContainerClass = GlobalJDOManagerProvider.sharedInstance().getObjectID2PCClassMap().getPersistenceCapableClass(articleContainerID); SegmentEditFactory sef = SegmentEditFactoryRegistry.sharedInstance().getSegmentEditFactory(articleContainerClass, segmentType.getClass(), true); SegmentEdit segmentEdit = sef.createSegmentEdit(this, articleContainerClass, asg); segmentEdit.addCompositeContentChangeListener(segmentCompositeContentChangeListener); createSegmentEditComposite(asg, segmentEdit); if (activeSegmentEdit == null) { activeSegmentEdit = segmentEdit; segmentEdit.getComposite().setFocus(); updateActiveSegmentEdit(); } } private ArticleCreateListener articleCreateListener = new ArticleCreateListener() { public void articlesCreated(final ArticleCreateEvent articleCreateEvent) { Runnable runnable = new Runnable() { public void run() { Map<SegmentEdit, List<ArticleCarrier>> segmentEdit2ArticleCarriers = new HashMap<SegmentEdit, List<ArticleCarrier>>(); Set<ArticleSegmentGroup> articleSegmentGroupsWithoutSegmentEdit = new HashSet<ArticleSegmentGroup>(); for (ArticleCarrier articleCarrier : articleCreateEvent.getArticleCarriers()) { Article article = articleCarrier.getArticle(); SegmentEdit segmentEdit = segmentPK2segmentEditMap.get(article.getSegment().getPrimaryKey()); if (segmentEdit == null) { // we do not yet have a SegmentEdit for article articleSegmentGroupsWithoutSegmentEdit.add(articleCarrier.getArticleSegmentGroup()); } else { // we do already have a SegmentEdit for article List<ArticleCarrier> articleCarriers = segmentEdit2ArticleCarriers.get(segmentEdit); if (articleCarriers == null) { articleCarriers = new ArrayList<ArticleCarrier>(); segmentEdit2ArticleCarriers.put(segmentEdit, articleCarriers); } articleCarriers.add(articleCarrier); } } for (Map.Entry<SegmentEdit, List<ArticleCarrier>> me : segmentEdit2ArticleCarriers.entrySet()) me.getKey().addArticles(me.getValue()); for (ArticleSegmentGroup articleSegmentGroup : articleSegmentGroupsWithoutSegmentEdit) createSegmentEditAndComposite(articleSegmentGroup); updateHeaderAndFooter(); } }; if (segmentCompositeFolder == null) // too early - try again later Display.getDefault().asyncExec(runnable); else { if (!segmentCompositeFolder.isDisposed()) // if we're not too late, do it now runnable.run(); } } }; private ArticleChangeListener articleChangeListener = new ArticleChangeListener() { public void articlesChanged(final ArticleChangeEvent articleChangeEvent) { Runnable runnable = new Runnable() { public void run() { if (!articleChangeEvent.getDeletedArticles().isEmpty()) { Map<SegmentEdit, Collection<ArticleCarrier>> segmentEdit2DeletedArticleCarriers = new HashMap<SegmentEdit, Collection<ArticleCarrier>>(); for (ArticleCarrier articleCarrier : articleChangeEvent.getDeletedArticleCarriers()) { String segmentPK = articleCarrier.getArticle().getSegment().getPrimaryKey(); SegmentEdit segmentEdit = segmentPK2segmentEditMap.get(segmentPK); Collection<ArticleCarrier> deletedArticleCarriers = segmentEdit2DeletedArticleCarriers.get(segmentEdit); if (deletedArticleCarriers == null) { deletedArticleCarriers = new ArrayList<ArticleCarrier>(); segmentEdit2DeletedArticleCarriers.put(segmentEdit, deletedArticleCarriers); } deletedArticleCarriers.add(articleCarrier); } for (Map.Entry<SegmentEdit, Collection<ArticleCarrier>> me : segmentEdit2DeletedArticleCarriers.entrySet()) me.getKey().removeArticles(me.getValue()); } updateHeaderAndFooter(); // the header + footer might iterate articles instead of using summary information => need to update whenever an article changes. } }; if (segmentCompositeFolder == null) // too early - try again later Display.getDefault().asyncExec(runnable); else { if (!segmentCompositeFolder.isDisposed()) // if we're not too late, do it now runnable.run(); } } }; protected void updateActiveSegmentEdit() { if (hasDifferentSegments()) { TabFolder tabFolder = ((TabFolder)segmentCompositeFolder); TabItem item = tabFolder.getItemCount() < 1 ? null : tabFolder.getItem(tabFolder.getSelectionIndex()); activeSegmentEdit = segmentEditsByTabItem.get(item); if (activeSegmentEdit == null && item != null && item.getControl() != null) throw new IllegalStateException( "TabItem is not registered in Map segmentEditsByTabItem!!!"); //$NON-NLS-1$ } else { // activeSegmentEdit = (SegmentEdit) segmentEditByComposite.get(segmentCompositeFolder); activeSegmentEdit = singleSegmentSegmentEdit; } ActiveSegmentEditSelectionEvent event = null; Object[] listeners = activeSegmentEditSelectionListeners.getListeners(); for (int i = 0; i < listeners.length; ++i) { ActiveSegmentEditSelectionListener listener = (ActiveSegmentEditSelectionListener) listeners[i]; if (event == null) event = new ActiveSegmentEditSelectionEvent(this); listener.selected(event); } } private ListenerList activeSegmentEditSelectionListeners = new ListenerList(); @Override public void addActiveSegmentEditSelectionListener( ActiveSegmentEditSelectionListener listener) { activeSegmentEditSelectionListeners.add(listener); } @Override public void removeActiveSegmentEditSelectionListener( ActiveSegmentEditSelectionListener listener) { activeSegmentEditSelectionListeners.remove(listener); } // private List segmentEdits = new ArrayList(); /** * This method must be called, whenever the segments add or remove controls to * their composites and hence the size changes. Otherwise, the scrolling * doesn't work correctly. */ public void calculateScrollContentSize() { for (SegmentEdit segmentEdit : segmentEditsByTabItem.values()) { calculateScrollContentSize(segmentEdit); // Composite segmentEditComposite = segmentEdit.getComposite(); // ScrolledComposite segmentCompositeScrollContainer = (ScrolledComposite) segmentEditComposite // .getParent(); // // ScrolledComposite segmentCompositeScrollContainer = (ScrolledComposite) // // it.next(); // // Control[] children = segmentCompositeScrollContainer.getChildren(); // // if (children.length != 1) // // throw new IllegalStateException("segmentCompositeScrollContainer has " // // + children.length + " child controls instead of 1!"); // // // // Composite segmentEditComposite = (Composite) children[0]; // Rectangle bounds = segmentEditComposite.getBounds(); // bounds.width = segmentCompositeScrollContainer.getClientArea().width // - segmentCompositeScrollContainer.getVerticalBar().getSize().x; // // segmentCompositeContainer.setBounds(bounds); // segmentCompositeScrollContainer.setMinSize(segmentEditComposite // .computeSize(bounds.width, SWT.DEFAULT)); // segmentEditComposite.layout(); } if (singleSegmentSegmentEdit != null) calculateScrollContentSize(singleSegmentSegmentEdit); } protected void calculateScrollContentSize(SegmentEdit segmentEdit) { Composite segmentEditComposite = segmentEdit.getComposite(); ScrolledComposite segmentCompositeScrollContainer = (ScrolledComposite) segmentEditComposite .getParent(); // ScrolledComposite segmentCompositeScrollContainer = (ScrolledComposite) // it.next(); // Control[] children = segmentCompositeScrollContainer.getChildren(); // if (children.length != 1) // throw new IllegalStateException("segmentCompositeScrollContainer has " // + children.length + " child controls instead of 1!"); // // Composite segmentEditComposite = (Composite) children[0]; Rectangle bounds = segmentEditComposite.getBounds(); bounds.width = segmentCompositeScrollContainer.getClientArea().width - segmentCompositeScrollContainer.getVerticalBar().getSize().x; // segmentCompositeContainer.setBounds(bounds); segmentCompositeScrollContainer.setMinSize(segmentEditComposite .computeSize(bounds.width, SWT.DEFAULT)); segmentEditComposite.layout(); } // protected TradeManager getTradeManager() // throws RemoteException, LoginException, CreateException, NamingException // { // return // JFireEjb3Factory.getBean(TradeManager.class, Login.getLogin().getInitialContextProperties()); // } // // protected StoreManager getStoreManager() // throws RemoteException, LoginException, CreateException, NamingException // { // return // JFireEjb3Factory.getBean(StoreManager.class, Login.getLogin().getInitialContextProperties()); // } private SegmentEdit activeSegmentEdit = null; /** * @return Returns either <code>null</code> if there is no SegmentEdit * active or the one that is. */ @Override public SegmentEdit getActiveSegmentEdit() { return activeSegmentEdit; } @Override public Collection<SegmentEdit> getSegmentEdits() { return segmentEditsByTabItem.values(); } // public List getSegmentEdits() // { // return segmentEdits; // } public static final String[] FETCH_GROUPS_ARTICLE_CONTAINER_WITHOUT_ARTICLES = { FetchPlan.DEFAULT, FetchGroupsTrade.FETCH_GROUP_ARTICLE_CONTAINER_IN_EDITOR, Segment.FETCH_GROUP_THIS_SEGMENT, SegmentType.FETCH_GROUP_THIS_SEGMENT_TYPE, StatableLocal.FETCH_GROUP_STATE }; public static final String[] FETCH_GROUPS_ORDER_WITH_ARTICLES = { FetchGroupsTrade.FETCH_GROUP_ARTICLE_CONTAINER_IN_EDITOR, Order.FETCH_GROUP_THIS_ORDER, Segment.FETCH_GROUP_THIS_SEGMENT, SegmentType.FETCH_GROUP_THIS_SEGMENT_TYPE, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_ORDER_EDITOR, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_ARTICLE_CONTAINER_EDITOR, FetchPlan.DEFAULT }; public static final String[] FETCH_GROUPS_OFFER_WITH_ARTICLES = { FetchGroupsTrade.FETCH_GROUP_ARTICLE_CONTAINER_IN_EDITOR, Offer.FETCH_GROUP_ARTICLES, OfferLocal.FETCH_GROUP_THIS_OFFER_LOCAL, StatableLocal.FETCH_GROUP_STATE, Order.FETCH_GROUP_CUSTOMER_GROUP, Segment.FETCH_GROUP_THIS_SEGMENT, SegmentType.FETCH_GROUP_THIS_SEGMENT_TYPE, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_OFFER_EDITOR, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_ARTICLE_CONTAINER_EDITOR, FetchPlan.DEFAULT }; public static final String[] FETCH_GROUPS_INVOICE_WITH_ARTICLES = { FetchGroupsTrade.FETCH_GROUP_ARTICLE_CONTAINER_IN_EDITOR, Invoice.FETCH_GROUP_THIS_INVOICE, InvoiceLocal.FETCH_GROUP_THIS_INVOICE_LOCAL, StatableLocal.FETCH_GROUP_STATE, Segment.FETCH_GROUP_THIS_SEGMENT, SegmentType.FETCH_GROUP_THIS_SEGMENT_TYPE, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_INVOICE_EDITOR, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_ARTICLE_CONTAINER_EDITOR, FetchPlan.DEFAULT }; public static final String[] FETCH_GROUPS_DELIVERY_NOTE_WITH_ARTICLES = { FetchGroupsTrade.FETCH_GROUP_ARTICLE_CONTAINER_IN_EDITOR, DeliveryNote.FETCH_GROUP_THIS_DELIVERY_NOTE, DeliveryNoteLocal.FETCH_GROUP_THIS_DELIVERY_NOTE_LOCAL, StatableLocal.FETCH_GROUP_STATE, Segment.FETCH_GROUP_THIS_SEGMENT, SegmentType.FETCH_GROUP_THIS_SEGMENT_TYPE, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_DELIVERY_NOTE_EDITOR, FetchGroupsTrade.FETCH_GROUP_ARTICLE_IN_ARTICLE_CONTAINER_EDITOR, FetchPlan.DEFAULT }; /** * Initialise this instance of <code>ArticleContainerEditComposite</code> or reload the {@link ArticleContainer} referenced * by the <code>loadArticleContainerID</code> parameter. In case of reloading, the articles are not fetched again * (they're managed separately by the {@link ClientArticleSegmentGroupSet} in {@link #articleSegmentGroupSet}) * * @param _articleContainerID the articleContainerID to be set. * @param monitor the monitor to provide feedback. */ protected synchronized void initArticleContainer(ArticleContainerID _articleContainerID, ProgressMonitor monitor) { // When reloading an ArticleContainer (because it changed), we do *NOT* load the articles again, because // they are independently updated. boolean reloadArticleContainerWithoutArticles = this.articleContainerID != null; if (this.articleContainerID == null && _articleContainerID == null) throw new IllegalStateException("articleContainerID not yet initialized and no articleContainerID parameter given!"); //$NON-NLS-1$ if (_articleContainerID == null) _articleContainerID = articleContainerID; else if (articleContainerID != null && !_articleContainerID.equals(articleContainerID)) throw new IllegalStateException("this.aritcleContainerID != ArticleContainerID articleContainerID !!!"); //$NON-NLS-1$ // monitor.beginTask("Loading article container", 100); // the monitor is directly passed to the DAOs - no need to use a sub-monitor try { this.articleContainerID = _articleContainerID; this.articleContainer = retrieveArticleContainer(articleContainerID, !reloadArticleContainerWithoutArticles, monitor); if (logger.isDebugEnabled()) logger.debug("initArticleContainerEditorInput: loaded version " + JDOHelper.getVersion(articleContainer) + " of " + JDOHelper.getObjectId(articleContainer)); //$NON-NLS-1$ //$NON-NLS-2$ if (!reloadArticleContainerWithoutArticles) createArticleSegmentGroups(); } catch (Exception e) { throw new RuntimeException(e); // } finally { // monitor.done(); } } /** * Get an {@link ArticleContainer} from the server (or the cache, if present there) using a DAO. * Override this method, if you need to retrieve a custom <code>ArticleContainer</code> implementation * with specialized fetch-groups. * * @param articleContainerID the object-id of the {@link ArticleContainer} to be loaded. * @param withArticles <code>true</code>, if the {@link ArticleContainer} should be loaded with its {@link Article}s; * <code>false</code>, if the <code>Article</code>s should <b>not</b> be detached. You must take this into account * when choosing your fetch-groups. This is necessary, because it makes no sense to detach a huge <code>ArticleContainer</code> * with all its articles again (when it changed), even though most of the articles didn't change. That's why, * the articles are independently updated ({@link ClientArticleSegmentGroupSet} takes care about that). * @param monitor the monitor for progress feedback. * @return the {@link ArticleContainer} for the specified {@link ArticleContainerID}. */ protected ArticleContainer retrieveArticleContainer(ArticleContainerID articleContainerID, boolean withArticles, ProgressMonitor monitor) { if (articleContainerID instanceof OrderID) return OrderDAO.sharedInstance().getOrder( (OrderID) articleContainerID, withArticles ? FETCH_GROUPS_ORDER_WITH_ARTICLES : FETCH_GROUPS_ARTICLE_CONTAINER_WITHOUT_ARTICLES, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, monitor ); if (articleContainerID instanceof OfferID) return OfferDAO.sharedInstance().getOffer( (OfferID) articleContainerID, withArticles ? FETCH_GROUPS_OFFER_WITH_ARTICLES : FETCH_GROUPS_ARTICLE_CONTAINER_WITHOUT_ARTICLES, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, monitor ); if (articleContainerID instanceof InvoiceID) return InvoiceDAO.sharedInstance().getInvoice( (InvoiceID) articleContainerID, withArticles ? FETCH_GROUPS_INVOICE_WITH_ARTICLES : FETCH_GROUPS_ARTICLE_CONTAINER_WITHOUT_ARTICLES, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, monitor ); if (articleContainerID instanceof DeliveryNoteID) return DeliveryNoteDAO.sharedInstance().getDeliveryNote( (DeliveryNoteID) articleContainerID, withArticles ? FETCH_GROUPS_DELIVERY_NOTE_WITH_ARTICLES : FETCH_GROUPS_ARTICLE_CONTAINER_WITHOUT_ARTICLES, NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, monitor ); // if (articleContainerID instanceof ReceptionNoteID) // return ReceptionNoteDAO.sharedInstance().getReceptionNote( // (ReceptionNoteID) articleContainerID, // fetchGroups, // NLJDOHelper.MAX_FETCH_DEPTH_NO_LIMIT, // monitor // ); throw new IllegalArgumentException("articleContainerID type \"" + articleContainerID.getClass().getName() + "\" unknown"); //$NON-NLS-1$ //$NON-NLS-2$ } public Menu createArticleContainerContextMenu(Control parent) { if (articleContainerEditActionContributor != null) return articleContainerEditActionContributor.createArticleContainerContextMenu(parent); return null; } public Menu createArticleEditContextMenu(Control parent) { return articleContainerEditActionContributor.createArticleEditContextMenu(parent); } /** * @return Returns the ArticleContainer (either * {@link org.nightlabs.jfire.trade.ui.Order}, * {@link org.nightlabs.jfire.trade.ui.Offer}, * {@link org.nightlabs.jfire.accounting.Invoice}, * {@link org.nightlabs.jfire.store.DeliveryNote} or * {@link org.nightlabs.jfire.store.ReceptionNote}) <b>WITHOUT</b> * articles. Hence, you <b>must not</b> call * {@link ArticleContainer#getArticles()}! Use {@link #getArticles()} * instead! */ @Override public ArticleContainer getArticleContainer() { return articleContainer; } @Override public Collection<Article> getArticles() { if (articleSegmentGroupSet == null) return Collections.emptyList(); return articleSegmentGroupSet.getArticles(); } // private List<ArticleChangeListener> earlyArticleChangeListeners = new ArrayList<ArticleChangeListener>(); private ListenerList earlyArticleChangeListeners = new ListenerList(); @Override public void addArticleChangeListener(ArticleChangeListener articleChangeListener) { if (articleSegmentGroupSet != null) { articleSegmentGroupSet.addArticleChangeListener(articleChangeListener); } else { earlyArticleChangeListeners.add(articleChangeListener);; } } @Override public void removeArticleChangeListener(ArticleChangeListener articleChangeListener) { if (articleSegmentGroupSet != null) { articleSegmentGroupSet.removeArticleChangeListener(articleChangeListener); } else { logger.warn("ArticleChangeListener not removed because articleSegmentGroupSet == null!"); //$NON-NLS-1$ } } // private List<ArticleCreateListener> earlyArticleCreateListeners = new ArrayList<ArticleCreateListener>(); private ListenerList earlyArticleCreateListeners = new ListenerList(); @Override public void addArticleCreateListener(ArticleCreateListener articleCreateListener) { if (articleSegmentGroupSet != null) { articleSegmentGroupSet.addArticleCreateListener(articleCreateListener); } else { earlyArticleCreateListeners.add(articleCreateListener); } } @Override public void removeArticleCreateListener(ArticleCreateListener articleCreateListener) { if (articleSegmentGroupSet != null) { articleSegmentGroupSet.removeArticleCreateListener(articleCreateListener); } else { logger.warn("ArticleCreateListener not removed because articleSegmentGroupSet == null!"); //$NON-NLS-1$ } } @Override public ClientArticleSegmentGroupSet getArticleSegmentGroupSet() { return articleSegmentGroupSet; } @Override public Composite createComposite(Composite parent) { return this; } @Override public Composite getComposite() { return this; } @Override public void init(ArticleContainerID articleContainerID) { // Noop, initialized in constructor. } private boolean showHeader = true; @Override public void setShowHeader(boolean showHeader) { this.showHeader = showHeader; if (headerComposite != null && !headerComposite.isDisposed()) { Object layoutData = headerComposite.getLayoutData(); if (layoutData instanceof GridData) { GridData gd = (GridData) layoutData; gd.exclude = !showHeader; headerComposite.getParent().layout(true, true); } } } }