6fb66665da24ec028bacbc3a4479a2f814c2b489
[taxeditor.git] / eu.etaxonomy.taxeditor.store / src / main / java / eu / etaxonomy / taxeditor / ui / section / AbstractEntityCollectionSection.java
1 /**
2 * Copyright (C) 2018 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
8 */
9 package eu.etaxonomy.taxeditor.ui.section;
10
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.Collections;
14 import java.util.Comparator;
15 import java.util.EnumSet;
16 import java.util.List;
17 import java.util.Observable;
18 import java.util.Observer;
19
20 import org.eclipse.jface.action.Action;
21 import org.eclipse.jface.action.IAction;
22 import org.eclipse.jface.action.ToolBarManager;
23 import org.eclipse.jface.resource.ImageDescriptor;
24 import org.eclipse.swt.SWT;
25 import org.eclipse.swt.events.DisposeEvent;
26 import org.eclipse.swt.events.DisposeListener;
27 import org.eclipse.swt.events.SelectionAdapter;
28 import org.eclipse.swt.events.SelectionEvent;
29 import org.eclipse.swt.events.SelectionListener;
30 import org.eclipse.swt.graphics.Color;
31 import org.eclipse.swt.graphics.ImageData;
32 import org.eclipse.swt.widgets.Composite;
33 import org.eclipse.swt.widgets.Control;
34 import org.eclipse.swt.widgets.Label;
35 import org.eclipse.ui.forms.events.ExpansionEvent;
36 import org.eclipse.ui.forms.events.IExpansionListener;
37 import org.eclipse.ui.forms.widgets.ExpandableComposite;
38
39 import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
40 import eu.etaxonomy.cdm.common.CdmUtils;
41 import eu.etaxonomy.cdm.model.common.CdmBase;
42 import eu.etaxonomy.cdm.model.permission.CRUD;
43 import eu.etaxonomy.taxeditor.model.AbstractUtility;
44 import eu.etaxonomy.taxeditor.model.ImageResources;
45 import eu.etaxonomy.taxeditor.preference.IPreferenceKeys;
46 import eu.etaxonomy.taxeditor.preference.PreferencesUtil;
47 import eu.etaxonomy.taxeditor.preference.Resources;
48 import eu.etaxonomy.taxeditor.store.CdmStore;
49 import eu.etaxonomy.taxeditor.store.LoginManager;
50 import eu.etaxonomy.taxeditor.store.StoreUtil;
51 import eu.etaxonomy.taxeditor.ui.element.AbstractFormSection;
52 import eu.etaxonomy.taxeditor.ui.element.CdmFormFactory;
53 import eu.etaxonomy.taxeditor.ui.element.ICdmFormElement;
54
55 /**
56 * This class visualizes an CDM entity of type ENTITY and additionally provides
57 * the functionality to add other elements of type ELEMENT to them.
58 *
59 * @param <ENTITY> A CDM entity which should be visualized by this section.
60 * @param <ELEMENT> An element that can be added (multiple times) to this entity.
61 *
62 * @author n.hoffmann
63 */
64 public abstract class AbstractEntityCollectionSection<ENTITY, ELEMENT>
65 extends AbstractFormSection<ENTITY>
66 implements IExpansionListener, Observer {
67
68 private static final EnumSet<CRUD> UPDATE = EnumSet.of(CRUD.UPDATE);
69
70 protected Composite container;
71
72 private Label label_empty;
73
74 private String title;
75
76 private AbstractEntityCollectionElement<ENTITY> entityCollectionElement;
77
78 public AbstractEntityCollectionSection(CdmFormFactory formFactory, ConversationHolder conversation, ICdmFormElement parentElement, String title, int style) {
79 super(formFactory, parentElement, ExpandableComposite.CLIENT_INDENT | style);
80 this.title = title;
81 this.setText(getTitleString());
82 updateToolbar();
83
84 addExpansionListener(this);
85
86 CdmStore.getLoginManager().addObserver(this);
87 addDisposeListener(new DisposeListener() {
88 @Override
89 public void widgetDisposed(DisposeEvent e) {
90 CdmStore.getLoginManager().deleteObserver(AbstractEntityCollectionSection.this);
91 }
92 });
93 }
94
95 protected Control createToolbar() {
96 ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
97
98 Action addAction = new Action("Add", IAction.AS_PUSH_BUTTON){
99 @Override
100 public void run() {
101 ELEMENT element = createNewElement();
102 if(element != null){
103 addElement(element);
104 if(! getSection().isExpanded()) {
105 getSection().setExpanded(true);
106 }
107 internalUpdateSection(true);
108 }
109 }
110 };
111 addAction.setImageDescriptor(new ImageDescriptor() {
112
113 @Override
114 public ImageData getImageData() {
115 return ImageResources.getImage(ImageResources.ADD_ICON).getImageData();
116 }
117 });
118 addAction.setToolTipText(getTooltipString());
119
120 Action browseAction = null;
121 if(allowAddExisting()){
122 browseAction = new Action("Browse", IAction.AS_PUSH_BUTTON){
123 @Override
124 public void run() {
125 ELEMENT element = addExisting();
126 if(element != null){
127 addElement(element);
128 if(! getSection().isExpanded()) {
129 getSection().setExpanded(true);
130 }
131 internalUpdateSection(true);
132 }
133 }
134 };
135 browseAction.setImageDescriptor(new ImageDescriptor() {
136
137 @Override
138 public ImageData getImageData() {
139 return ImageResources.getImage(ImageResources.BROWSE_ICON).getImageData();
140 }
141 });
142 browseAction.setToolTipText("Browse");
143 }
144
145 toolBarManager.add(addAction);
146 if(browseAction!=null){
147 toolBarManager.add(browseAction);
148 }
149
150 addAction(toolBarManager);
151
152 return toolBarManager.createControl(this);
153 }
154
155 protected void addAction(ToolBarManager toolBarManager) {
156 // default implementation empty
157 }
158
159 /**
160 * using this method is discouraged, use updateToolBar() instead
161 */
162 public void showToolbar(){
163 setTextClient(createToolbar());
164 }
165
166 /**
167 * using this method is discouraged, use updateToolBar() instead
168 */
169 public void removeToolbar(){
170 setTextClient(null);
171 }
172
173 @Override
174 public void setEntity(ENTITY entity) {
175 if(entity != null){
176 super.setEntity(entity);
177 internalUpdateSection(false);
178 }
179 setSectionTitle();
180 updateToolbar();
181 layout();
182 }
183
184 /**
185 * Sets the title for the section. Adds a "+" sign if the collection is not empty for this section.
186 * Override in subclasses if you want to have a different behaviour.
187 */
188 protected void setSectionTitle() {
189 Collection<ELEMENT> collection = getCollection(getEntity());
190 if(collection != null && collection.size() > 0){
191 this.setText(getTitleString() + " +");
192 }else{
193 this.setText(getTitleString());
194 }
195 }
196
197 /**
198 * Removes all content from the container
199 */
200 private void destroyDynamicContent(){
201 if(label_empty != null){
202 label_empty.dispose();
203 label_empty = null;
204 }
205 removeElements();
206 }
207
208 /**
209 * Call this method after dynamically changing the client area.
210 * If the options changed is set to <code>true</code>, will also fire a state changed
211 * event to inform the user of unsaved changes.
212 *
213 * @param changed a boolean.
214 */
215 protected void internalUpdateSection(boolean changed){
216 setSectionTitle();
217 destroyDynamicContent();
218 if(isExpanded() || expandSectionWhenContentAvailable()) {
219 renderContent(isExpanded());
220 }
221 if(changed) {
222 firePropertyChangeEvent(this);
223 }
224 }
225
226 /**
227 * Create the elements to be shown in this section client area
228 */
229 private void renderContent(boolean forceExpansion)
230 {
231 Collection<ELEMENT> collection = getCollection(getEntity());
232
233 if(collection == null || collection.isEmpty()){
234 createEmptyContent();
235 }else{
236 List<ELEMENT> elements = new ArrayList<>(collection);
237 Collections.sort(elements, getComparator());
238 createDynamicContents(elements);
239 forceExpansion = true;
240 }
241
242 this.setExpanded(forceExpansion);
243
244 reflow();
245 }
246
247 protected void createEmptyContent(){
248 label_empty = formFactory.createLabel(getLayoutComposite(), getEmptyString());
249 }
250
251 /**
252 * Creates the widgets for the collection
253 *
254 * @param elements a {@link java.util.Collection} object.
255 */
256 protected void createDynamicContents(Collection<ELEMENT> elements)
257 {
258 int i = 0;
259 for(final ELEMENT element : elements){
260 SelectionAdapter removeListener = new SelectionAdapter(){
261 @Override
262 public void widgetSelected(SelectionEvent e) {
263 removeElement(element);
264 internalUpdateSection(true);
265 }
266 };
267 boolean modulo = i++%2 == 0;
268 String colorResource = modulo ? Resources.COLOR_LIST_EVEN : Resources.COLOR_LIST_ODD;
269 createElementComposite(element, removeListener, AbstractUtility.getColor(colorResource));
270 }
271 }
272
273 /**
274 * Create the specific widget for the element
275 *
276 * @param element a ELEMENT object.
277 * @param removeListener a {@link org.eclipse.swt.events.SelectionListener} object.
278 * @param backgroundColor a {@link org.eclipse.swt.graphics.Color} object.
279 */
280 protected void createElementComposite(ELEMENT element, SelectionListener removeListener, Color backgroundColor){
281 entityCollectionElement = formFactory.createEntityCollectionElement(this, element, removeListener, backgroundColor, SWT.NULL);
282 }
283
284 /** {@inheritDoc} */
285 @Override
286 public void setBackground(Color color) {
287 if(label_empty != null && !label_empty.isDisposed()){
288 label_empty.setBackground(color);
289 }
290 super.setBackground(color);
291 }
292
293 /**
294 * <p>getTitleString</p>
295 *
296 * @return a {@link java.lang.String} object.
297 */
298 public String getTitleString() {
299 return CdmUtils.Nz(title);
300 }
301
302 /**
303 * <p>setTitleString</p>
304 *
305 * @param title a {@link java.lang.String} object.
306 */
307 public void setTitleString(String title){
308 this.title = title;
309 setSectionTitle();
310 layout();
311 }
312
313 /** {@inheritDoc} */
314 @Override
315 public void expansionStateChanging(ExpansionEvent e) {
316 // logger.warn("Expansion State Changing");
317 }
318
319 /** {@inheritDoc} */
320 @Override
321 public void expansionStateChanged(ExpansionEvent e) {
322 if(isExpanded()){
323 renderContent(isExpanded());
324 }else{
325 destroyDynamicContent();
326 }
327 }
328
329 private boolean expandSectionWhenContentAvailable(){
330 return PreferencesUtil.getBooleanValue(IPreferenceKeys.SHOULD_EXPAND_SECTION_WHEN_DATA_AVAILABLE, true);
331 }
332
333 /**
334 * Remove an element from the entities collection and update the section
335 *
336 * @param element a ELEMENT object.
337 */
338 public void removeElementAndUpdate(ELEMENT element) {
339 removeElement(element);
340 internalUpdateSection(true);
341 }
342
343 @Override
344 public void update(Observable o, Object arg){
345 if(o instanceof LoginManager){
346 updateToolbar();
347 }
348 }
349
350 protected void updateToolbar() {
351 if( !(getEntity() instanceof CdmBase) || (getEntity() != null && CdmStore.currentAuthentiationHasPermission(StoreUtil.getCdmEntity(getEntity()), UPDATE)) ){
352 showToolbar();
353 } else {
354 removeToolbar();
355 }
356 }
357
358 public AbstractEntityCollectionElement<ENTITY> getEntityCollectionElement() {
359 return entityCollectionElement;
360 }
361
362 /**
363 * Returns the {@link Comparator} specific for the ELEMENTs
364 * which is used to sort the elements
365 * @return the comparator for ELEMENT
366 */
367 public abstract Comparator<ELEMENT> getComparator();
368
369 /**
370 * Get the specific collection of this entity
371 *
372 * @param entity a ENTITY object.
373 * @return a {@link java.util.Collection} object.
374 */
375 public abstract Collection<ELEMENT> getCollection(ENTITY entity);
376
377
378 /**
379 * Create a new Element for this collection
380 *
381 * @return a ELEMENT object.
382 */
383 public abstract ELEMENT createNewElement();
384
385 /**
386 * Add an element to the entities collection
387 *
388 * @param element a ELEMENT object.
389 */
390 public abstract void addElement(ELEMENT element);
391
392 /**
393 * Add an existing element to the entities collection.
394 * @return the existing element
395 */
396 public abstract ELEMENT addExisting();
397
398 /**
399 * If <code>true</code> the section will also display
400 * a browse icon to choose from existing elements.
401 * <br>
402 * <b>Note:</b> when returning true you have to make sure
403 * to implement the {@link #addExisting()} method
404 * @return true if existing entities can be added;
405 * false otherwise
406 */
407 public abstract boolean allowAddExisting();
408
409 /**
410 * Remove an element from the entities collection
411 *
412 * @param element a ELEMENT object.
413 */
414 public abstract void removeElement(ELEMENT element);
415
416 /**
417 * String to display when the collection is empty
418 *
419 * @return a {@link java.lang.String} object.
420 */
421 public abstract String getEmptyString();
422
423 /**
424 * <p>getTooltipString</p>
425 *
426 * @return String to display when hovering the add button
427 */
428 protected abstract String getTooltipString();
429 }