Merge branch 'release/5.19.0'
[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 Collection<ELEMENT> collection = getCollection(getEntity());
231
232 if(collection == null || collection.isEmpty()){
233 createEmptyContent();
234 }else{
235 List<ELEMENT> elements = new ArrayList<>(collection);
236 Collections.sort(elements, getComparator());
237 createDynamicContents(elements);
238 forceExpansion = true;
239 }
240
241 this.setExpanded(forceExpansion);
242
243 reflow();
244 }
245
246 protected void createEmptyContent(){
247 label_empty = formFactory.createLabel(getLayoutComposite(), getEmptyString());
248 }
249
250 /**
251 * Creates the widgets for the collection
252 */
253 protected void createDynamicContents(Collection<ELEMENT> elements)
254 {
255 int i = 0;
256 for(final ELEMENT element : elements){
257 SelectionAdapter removeListener = new SelectionAdapter(){
258 @Override
259 public void widgetSelected(SelectionEvent e) {
260 removeElement(element);
261 internalUpdateSection(true);
262 }
263 };
264 boolean modulo = i++%2 == 0;
265 String colorResource = modulo ? Resources.COLOR_LIST_EVEN : Resources.COLOR_LIST_ODD;
266 createElementComposite(element, removeListener, AbstractUtility.getColor(colorResource));
267 }
268 }
269
270 /**
271 * Create the specific widget for the element
272 *
273 * @param element a ELEMENT object.
274 * @param removeListener a {@link org.eclipse.swt.events.SelectionListener} object.
275 * @param backgroundColor a {@link org.eclipse.swt.graphics.Color} object.
276 */
277 protected void createElementComposite(ELEMENT element, SelectionListener removeListener, Color backgroundColor){
278 entityCollectionElement = formFactory.createEntityCollectionElement(this, element, removeListener, backgroundColor, SWT.NULL);
279 }
280
281 @Override
282 public void setBackground(Color color) {
283 if(label_empty != null && !label_empty.isDisposed()){
284 label_empty.setBackground(color);
285 }
286 super.setBackground(color);
287 }
288
289 /**
290 * getTitleString
291 */
292 public String getTitleString() {
293 return CdmUtils.Nz(title);
294 }
295
296 /**
297 * setTitleString
298 */
299 public void setTitleString(String title){
300 this.title = title;
301 setSectionTitle();
302 layout();
303 }
304
305 @Override
306 public void expansionStateChanging(ExpansionEvent e) {
307 // logger.warn("Expansion State Changing");
308 }
309
310 @Override
311 public void expansionStateChanged(ExpansionEvent e) {
312 if(isExpanded()){
313 renderContent(isExpanded());
314 }else{
315 destroyDynamicContent();
316 }
317 }
318
319 private boolean expandSectionWhenContentAvailable(){
320 return PreferencesUtil.getBooleanValue(IPreferenceKeys.SHOULD_EXPAND_SECTION_WHEN_DATA_AVAILABLE, true);
321 }
322
323 /**
324 * Remove an element from the entities collection and update the section
325 *
326 * @param element a ELEMENT object.
327 */
328 public void removeElementAndUpdate(ELEMENT element) {
329 removeElement(element);
330 internalUpdateSection(true);
331 }
332
333 @Override
334 public void update(Observable o, Object arg){
335 if(o instanceof LoginManager){
336 updateToolbar();
337 }
338 }
339
340 protected void updateToolbar() {
341 if( !(getEntity() instanceof CdmBase) || (getEntity() != null && CdmStore.currentAuthentiationHasPermission(StoreUtil.getCdmEntity(getEntity()), UPDATE)) ){
342 showToolbar();
343 } else {
344 removeToolbar();
345 }
346 }
347
348 public AbstractEntityCollectionElement<ENTITY> getEntityCollectionElement() {
349 return entityCollectionElement;
350 }
351
352 /**
353 * Returns the {@link Comparator} specific for the ELEMENTs
354 * which is used to sort the elements
355 * @return the comparator for ELEMENT
356 */
357 public abstract Comparator<ELEMENT> getComparator();
358
359 /**
360 * Get the specific collection of this entity
361 *
362 * @param entity a ENTITY object.
363 * @return a {@link java.util.Collection} object.
364 */
365 public abstract Collection<ELEMENT> getCollection(ENTITY entity);
366
367
368 /**
369 * Create a new Element for this collection
370 *
371 * @return a ELEMENT object.
372 */
373 public abstract ELEMENT createNewElement();
374
375 /**
376 * Add an element to the entities collection
377 *
378 * @param element a ELEMENT object.
379 */
380 public abstract void addElement(ELEMENT element);
381
382 /**
383 * Add an existing element to the entities collection.
384 * @return the existing element
385 */
386 public abstract ELEMENT addExisting();
387
388 /**
389 * If <code>true</code> the section will also display
390 * a browse icon to choose from existing elements.
391 * <br>
392 * <b>Note:</b> when returning true you have to make sure
393 * to implement the {@link #addExisting()} method
394 * @return true if existing entities can be added;
395 * false otherwise
396 */
397 public abstract boolean allowAddExisting();
398
399 /**
400 * Remove an element from the entities collection
401 *
402 * @param element a ELEMENT object.
403 */
404 public abstract void removeElement(ELEMENT element);
405
406 /**
407 * String to display when the collection is empty
408 *
409 * @return a {@link java.lang.String} object.
410 */
411 public abstract String getEmptyString();
412
413 /**
414 * <p>getTooltipString</p>
415 *
416 * @return String to display when hovering the add button
417 */
418 protected abstract String getTooltipString();
419 }