Project

General

Profile

Download (28.9 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2017 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.vaadin.mvp;
10

    
11
import java.util.ArrayList;
12
import java.util.Arrays;
13
import java.util.HashSet;
14
import java.util.List;
15
import java.util.Map;
16
import java.util.Set;
17
import java.util.Stack;
18

    
19
import org.apache.log4j.Level;
20
import org.apache.log4j.Logger;
21
import org.vaadin.spring.events.EventScope;
22

    
23
import com.vaadin.data.Validator.InvalidValueException;
24
import com.vaadin.data.fieldgroup.BeanFieldGroup;
25
import com.vaadin.data.fieldgroup.FieldGroup.CommitEvent;
26
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
27
import com.vaadin.data.fieldgroup.FieldGroup.CommitHandler;
28
import com.vaadin.data.fieldgroup.FieldGroup.FieldGroupInvalidValueException;
29
import com.vaadin.server.AbstractErrorMessage.ContentMode;
30
import com.vaadin.server.ErrorMessage.ErrorLevel;
31
import com.vaadin.server.FontAwesome;
32
import com.vaadin.server.UserError;
33
import com.vaadin.shared.ui.MarginInfo;
34
import com.vaadin.ui.AbstractComponentContainer;
35
import com.vaadin.ui.AbstractField;
36
import com.vaadin.ui.AbstractLayout;
37
import com.vaadin.ui.AbstractOrderedLayout;
38
import com.vaadin.ui.Alignment;
39
import com.vaadin.ui.Button;
40
import com.vaadin.ui.CheckBox;
41
import com.vaadin.ui.Component;
42
import com.vaadin.ui.CssLayout;
43
import com.vaadin.ui.Field;
44
import com.vaadin.ui.GridLayout;
45
import com.vaadin.ui.GridLayout.OutOfBoundsException;
46
import com.vaadin.ui.GridLayout.OverlapsException;
47
import com.vaadin.ui.HasComponents;
48
import com.vaadin.ui.HorizontalLayout;
49
import com.vaadin.ui.Label;
50
import com.vaadin.ui.Layout;
51
import com.vaadin.ui.Layout.MarginHandler;
52
import com.vaadin.ui.Notification;
53
import com.vaadin.ui.Notification.Type;
54
import com.vaadin.ui.PopupDateField;
55
import com.vaadin.ui.TextField;
56
import com.vaadin.ui.VerticalLayout;
57
import com.vaadin.ui.themes.ValoTheme;
58

    
59
import eu.etaxonomy.cdm.database.PermissionDeniedException;
60
import eu.etaxonomy.cdm.vaadin.component.TextFieldNFix;
61
import eu.etaxonomy.cdm.vaadin.event.AbstractEditorAction;
62
import eu.etaxonomy.cdm.vaadin.event.AbstractEditorAction.EditorActionContext;
63
import eu.etaxonomy.vaadin.component.NestedFieldGroup;
64
import eu.etaxonomy.vaadin.component.SwitchableTextField;
65
import eu.etaxonomy.vaadin.mvp.event.EditorDeleteEvent;
66
import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
67
import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
68
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
69
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
70
import eu.etaxonomy.vaadin.util.PropertyIdPath;
71

    
72
/**
73
 *
74
 * Optional with a delete button which can be enabled with {@link #withDeleteButton(boolean)}
75
 *
76
 * @author a.kohlbecker
77
 * @since Apr 5, 2017
78
 *
79
 */
80
public abstract class AbstractPopupEditor<DTO extends Object, P extends AbstractEditorPresenter<DTO, ? extends ApplicationView>>
81
    extends AbstractPopupView<P> {
82

    
83
    public static final Logger logger = Logger.getLogger(AbstractPopupEditor.class);
84

    
85
    private BeanFieldGroup<DTO> fieldGroup;
86

    
87
    private VerticalLayout mainLayout;
88

    
89
    private Layout fieldLayout;
90

    
91
    private HorizontalLayout buttonLayout;
92

    
93
    private Button save;
94

    
95
    private Button cancel;
96

    
97
    private Button delete;
98

    
99
    private CssLayout toolBar = new CssLayout();
100

    
101
    private CssLayout toolBarButtonGroup = new CssLayout();
102

    
103
    private Label statusMessageLabel = new Label();
104

    
105
    Set<String> statusMessages = new HashSet<>();
106

    
107
    private GridLayout _gridLayoutCache;
108

    
109
    private boolean isBeanLoaded;
110

    
111
    private Stack<EditorActionContext> context = new Stack<EditorActionContext>();
112

    
113
    private boolean isContextUpdated;
114

    
115
    private boolean isAdvancedMode = false;
116

    
117
    private List<Component> advancedModeComponents = new ArrayList<>();
118

    
119
    private Button advancedModeButton;
120

    
121
    public AbstractPopupEditor(Layout layout, Class<DTO> dtoType) {
122

    
123
        mainLayout = new VerticalLayout();
124
        // IMPORTANT: mainLayout must be set to full size otherwise the
125
        // popup window may have problems with automatic resizing of its
126
        // content.
127
        mainLayout.setSizeFull();
128

    
129
        setCompositionRoot(mainLayout);
130

    
131
        fieldGroup = new BeanFieldGroup<>(dtoType);
132
        fieldGroup.addCommitHandler(new SaveHandler());
133

    
134
        toolBar.addStyleName(ValoTheme.WINDOW_TOP_TOOLBAR);
135
        toolBar.setWidth(100, Unit.PERCENTAGE);
136
        toolBarButtonGroup.addStyleName(ValoTheme.LAYOUT_COMPONENT_GROUP);
137
        toolBarButtonGroup.setWidthUndefined();
138
        toolBar.addComponent(toolBarButtonGroup);
139
        toolBar.setVisible(false);
140

    
141
        fieldLayout = layout;
142
        fieldLayout.setWidthUndefined();
143
        if(fieldLayout instanceof AbstractOrderedLayout){
144
            ((AbstractOrderedLayout)fieldLayout).setSpacing(true);
145
        }
146
        if(MarginHandler.class.isAssignableFrom(fieldLayout.getClass())){
147
            ((MarginHandler)fieldLayout).setMargin(new MarginInfo(false, true, true, true));
148
        }
149

    
150
        buttonLayout = new HorizontalLayout();
151
        buttonLayout.setStyleName(ValoTheme.WINDOW_BOTTOM_TOOLBAR);
152
        buttonLayout.setWidth(100, Unit.PERCENTAGE);
153
        buttonLayout.setSpacing(true);
154

    
155
        save = new Button("Save", FontAwesome.SAVE);
156
        save.setStyleName(ValoTheme.BUTTON_PRIMARY);
157
        save.addClickListener(e -> save());
158

    
159
        cancel = new Button("Cancel", FontAwesome.REMOVE);
160
        cancel.addClickListener(e -> cancel());
161

    
162
        delete = new Button("Delete", FontAwesome.TRASH);
163
        delete.setStyleName(ValoTheme.BUTTON_DANGER);
164
        delete.addClickListener(e -> delete());
165
        delete.setVisible(false);
166

    
167
        buttonLayout.addComponents(delete, save, cancel);
168
        // delete is initially invisible, let save take all space
169
        buttonLayout.setExpandRatio(save, 1);
170
        buttonLayout.setComponentAlignment(delete, Alignment.TOP_RIGHT);
171
        buttonLayout.setComponentAlignment(save, Alignment.TOP_RIGHT);
172
        buttonLayout.setComponentAlignment(cancel, Alignment.TOP_RIGHT);
173

    
174
        statusMessageLabel.setWidthUndefined();
175

    
176
        mainLayout.addComponents(toolBar, fieldLayout, statusMessageLabel, buttonLayout);
177
        mainLayout.setComponentAlignment(statusMessageLabel, Alignment.BOTTOM_RIGHT);
178
        mainLayout.setComponentAlignment(toolBar, Alignment.TOP_RIGHT);
179

    
180
        updateToolBarVisibility();
181
    }
182

    
183
    protected VerticalLayout getMainLayout() {
184
        return mainLayout;
185
    }
186

    
187
    protected Layout getFieldLayout() {
188
        return fieldLayout;
189
    }
190

    
191
    /**
192
     * @return
193
     */
194
    private GridLayout gridLayout() {
195
        if(_gridLayoutCache == null){
196
            if(fieldLayout instanceof GridLayout){
197
                _gridLayoutCache = (GridLayout)fieldLayout;
198
            } else {
199
                throw new RuntimeException("The fieldlayout of this editor is not a GridLayout");
200
            }
201
        }
202
        return _gridLayoutCache;
203
    }
204

    
205
    @Override
206
    public void setReadOnly(boolean readOnly) {
207
        super.setReadOnly(readOnly);
208
        save.setVisible(!readOnly);
209
        delete.setVisible(!readOnly);
210
        cancel.setCaption(readOnly ? "Close" : "Cancel");
211
        recursiveReadonly(readOnly, (AbstractComponentContainer)getFieldLayout());
212
    }
213

    
214
    /**
215
     * @param readOnly
216
     * @param layout
217
     */
218
    protected void recursiveReadonly(boolean readOnly, AbstractComponentContainer layout) {
219
        for(Component c : layout){
220
            c.setReadOnly(readOnly);
221
            if(c instanceof AbstractComponentContainer){
222
                recursiveReadonly(readOnly, (AbstractComponentContainer)c);
223
            }
224
        }
225
    }
226

    
227
    /**
228
     * @return
229
     * @return
230
     */
231
    protected AbstractLayout getToolBar() {
232
        return toolBar;
233
    }
234

    
235
    /**
236
     * @return
237
     * @return
238
     */
239
    protected void toolBarAdd(Component c) {
240
        toolBar.addComponent(c, toolBar.getComponentIndex(toolBarButtonGroup) - 1);
241
        updateToolBarVisibility();
242
    }
243

    
244
    /**
245
     * @return
246
     * @return
247
     */
248
    protected void toolBarButtonGroupAdd(Component c) {
249
        toolBarButtonGroup.addComponent(c);
250
        updateToolBarVisibility();
251
    }
252

    
253
    /**
254
     * @return
255
     * @return
256
     */
257
    protected void toolBarButtonGroupRemove(Component c) {
258
        toolBarButtonGroup.removeComponent(c);
259
        updateToolBarVisibility();
260
    }
261

    
262
    /**
263
     *
264
     */
265
    private void updateToolBarVisibility() {
266
        boolean showToolbar = toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1;
267
        toolBar.setVisible(toolBarButtonGroup.getComponentCount() + toolBar.getComponentCount() > 1);
268
        if(!showToolbar){
269
            mainLayout.setMargin(new MarginInfo(true, false, false, false));
270
        } else {
271
            mainLayout.setMargin(false);
272
        }
273

    
274
    }
275

    
276
    /**
277
     * The top tool-bar is initially invisible.
278
     *
279
     * @param visible
280
     */
281
    protected void setToolBarVisible(boolean visible){
282
        toolBar.setVisible(true);
283
    }
284

    
285
    /**
286
     * @return the isAdvancedMode
287
     */
288
    public boolean isAdvancedMode() {
289
        return isAdvancedMode;
290
    }
291

    
292
    /**
293
     * @param isAdvancedMode the isAdvancedMode to set
294
     */
295
    public void setAdvancedMode(boolean isAdvancedMode) {
296
        this.isAdvancedMode = isAdvancedMode;
297
        advancedModeComponents.forEach(c -> c.setVisible(isAdvancedMode));
298
    }
299

    
300
    public void setAdvancedModeEnabled(boolean activate){
301
        if(activate && advancedModeButton == null){
302
            advancedModeButton = new Button(FontAwesome.WRENCH); // FontAwesome.FLASK
303
            advancedModeButton.setIconAlternateText("Advanced mode");
304
            advancedModeButton.addStyleName(ValoTheme.BUTTON_TINY);
305
            toolBarButtonGroupAdd(advancedModeButton);
306
            advancedModeButton.addClickListener(e -> {
307
                setAdvancedMode(!isAdvancedMode);
308
                }
309
            );
310

    
311
        } else if(advancedModeButton != null) {
312
            toolBarButtonGroupRemove(advancedModeButton);
313
            advancedModeButton = null;
314
        }
315
    }
316

    
317
    public void registerAdvancedModeComponents(Component ... c){
318
        advancedModeComponents.addAll(Arrays.asList(c));
319
    }
320

    
321

    
322
    // ------------------------ event handler ------------------------ //
323

    
324
    private class SaveHandler implements CommitHandler {
325

    
326
        private static final long serialVersionUID = 2047223089707080659L;
327

    
328
        @Override
329
        public void preCommit(CommitEvent commitEvent) throws CommitException {
330
            logger.debug("preCommit(), publishing EditorPreSaveEvent");
331
            // notify the presenter to start a transaction
332
            viewEventBus.publish(this, new EditorPreSaveEvent<DTO>(AbstractPopupEditor.this, getBean()));
333
        }
334

    
335
        @Override
336
        public void postCommit(CommitEvent commitEvent) throws CommitException {
337
            try {
338
                if(logger.isTraceEnabled()){
339
                    logger.trace("postCommit() publishing EditorSaveEvent for " + getBean().toString());
340
                }
341
                // notify the presenter to persist the bean and to commit the transaction
342
                viewEventBus.publish(this, new EditorSaveEvent<DTO>(AbstractPopupEditor.this, getBean()));
343
                if(logger.isTraceEnabled()){
344
                    logger.trace("postCommit() publishing DoneWithPopupEvent");
345
                }
346
                // notify the NavigationManagerBean to close the window and to dispose the view
347
                viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(AbstractPopupEditor.this, Reason.SAVE));
348
            } catch (Exception e) {
349
                logger.error(e);
350
                throw new CommitException("Failed to store data to backend", e);
351
            }
352
        }
353
    }
354

    
355
    protected void addCommitHandler(CommitHandler commitHandler) {
356
        fieldGroup.addCommitHandler(commitHandler);
357
    }
358

    
359

    
360
    /**
361
     * Cancel editing and discard all modifications.
362
     */
363
    @Override
364
    public void cancel() {
365
        fieldGroup.discard();
366
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.CANCEL));
367
    }
368

    
369
    /**
370
     * @return
371
     */
372
    private void delete() {
373
        viewEventBus.publish(this, new EditorDeleteEvent<DTO>(this, fieldGroup.getItemDataSource().getBean()));
374
        viewEventBus.publish(EventScope.UI, this, new DoneWithPopupEvent(this, Reason.DELETE));
375
    }
376

    
377
    /**
378
     * Save the changes made in the editor.
379
     */
380
    private void save() {
381
        try {
382
            fieldGroup.commit();
383
        } catch (CommitException e) {
384
            fieldGroup.getFields().forEach(f -> ((AbstractField<?>)f).setValidationVisible(true));
385
            if(e.getCause() != null && e.getCause() instanceof FieldGroupInvalidValueException){
386
                FieldGroupInvalidValueException invalidValueException = (FieldGroupInvalidValueException)e.getCause();
387
                updateFieldNotifications(invalidValueException.getInvalidFields());
388
                Notification.show("The entered data in " + invalidValueException.getInvalidFields().size() + " fields is incomplete or invalid.");
389
            } else if(e.getCause() != null && e.getCause().getCause() != null && e.getCause().getCause() instanceof PermissionDeniedException){
390
                PermissionDeniedException permissionDeniedException = (PermissionDeniedException)e.getCause().getCause();
391
                Notification.show("Permission denied", permissionDeniedException.getMessage(), Type.ERROR_MESSAGE);
392
            } else {
393
//                Logger.getLogger(this.getClass()).error("Error saving", e);
394
//                Notification.show("Error saving", Type.ERROR_MESSAGE);
395
                throw new RuntimeException("Error saving", e);
396
            }
397
        }
398
    }
399

    
400
    /**
401
     * @param invalidFields
402
     */
403
    private void updateFieldNotifications(Map<Field<?>, InvalidValueException> invalidFields) {
404
        for(Field<?> f : invalidFields.keySet()){
405
            if(f instanceof AbstractField){
406
                String message = invalidFields.get(f).getHtmlMessage();
407
                ((AbstractField)f).setComponentError(new UserError(message, ContentMode.HTML, ErrorLevel.ERROR));
408
            }
409
        }
410

    
411
    }
412

    
413
    // ------------------------ field adding methods ------------------------ //
414

    
415

    
416
    protected TextField addTextField(String caption, String propertyId) {
417
        return addField(new TextFieldNFix(caption), propertyId);
418
    }
419

    
420
    protected TextField addTextField(String caption, String propertyId, int column1, int row1,
421
            int column2, int row2)
422
            throws OverlapsException, OutOfBoundsException {
423
        return addField(new TextFieldNFix(caption), propertyId, column1, row1, column2, row2);
424
    }
425

    
426
    protected TextField addTextField(String caption, String propertyId, int column, int row)
427
            throws OverlapsException, OutOfBoundsException {
428
        return addField(new TextFieldNFix(caption), propertyId, column, row);
429
    }
430

    
431
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column1, int row1,
432
            int column2, int row2)
433
            throws OverlapsException, OutOfBoundsException {
434

    
435
        SwitchableTextField field = new SwitchableTextField(caption);
436
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
437
        addComponent(field, column1, row1, column2, row2);
438
        return field;
439
    }
440

    
441
    protected SwitchableTextField addSwitchableTextField(String caption, String textPropertyId, String switchPropertyId, int column, int row)
442
            throws OverlapsException, OutOfBoundsException {
443

    
444
        SwitchableTextField field = new SwitchableTextField(caption);
445
        field.bindTo(fieldGroup, textPropertyId, switchPropertyId);
446
        addComponent(field, column, row);
447
        return field;
448
    }
449

    
450
    protected PopupDateField addDateField(String caption, String propertyId) {
451
        return addField(new PopupDateField(caption), propertyId);
452
    }
453

    
454
    protected CheckBox addCheckBox(String caption, String propertyId) {
455
        return addField(new CheckBox(caption), propertyId);
456
    }
457

    
458
    protected CheckBox addCheckBox(String caption, String propertyId, int column, int row){
459
        return addField(new CheckBox(caption), propertyId, column, row);
460
    }
461

    
462
    protected <T extends Field> T addField(T field, String propertyId) {
463
        fieldGroup.bind(field, propertyId);
464
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
465
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
466
        }
467
        addComponent(field);
468
        return field;
469
    }
470

    
471
    /**
472
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
473
     *
474
     * @param field
475
     *            the field to be added, not <code>null</code>.
476
     * @param propertyId
477
     * @param column
478
     *            the column index, starting from 0.
479
     * @param row
480
     *            the row index, starting from 0.
481
     * @throws OverlapsException
482
     *             if the new component overlaps with any of the components
483
     *             already in the grid.
484
     * @throws OutOfBoundsException
485
     *             if the cell is outside the grid area.
486
     */
487
    protected <T extends Field> T addField(T field, String propertyId, int column, int row)
488
            throws OverlapsException, OutOfBoundsException {
489
        fieldGroup.bind(field, propertyId);
490
        if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
491
            ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
492
        }
493
        addComponent(field, column, row);
494
        return field;
495
    }
496

    
497
    /**
498
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
499
     *
500
     * @param field
501
     * @param propertyId
502
     * @param column1
503
     * @param row1
504
     * @param column2
505
     * @param row2
506
     * @return
507
     * @throws OverlapsException
508
     * @throws OutOfBoundsException
509
     */
510
    protected <T extends Field> T addField(T field, String propertyId, int column1, int row1,
511
            int column2, int row2)
512
            throws OverlapsException, OutOfBoundsException {
513
        if(propertyId != null){
514
            fieldGroup.bind(field, propertyId);
515
            if(NestedFieldGroup.class.isAssignableFrom(field.getClass())){
516
                ((NestedFieldGroup)field).registerParentFieldGroup(fieldGroup);
517
            }
518
        }
519
        addComponent(field, column1, row1, column2, row2);
520
        return field;
521
    }
522

    
523
    protected Field<?> getField(Object propertyId){
524
        return fieldGroup.getField(propertyId);
525
    }
526

    
527
    public PropertyIdPath boundPropertyIdPath(Field<?> field){
528

    
529
        PropertyIdPath propertyIdPath = null;
530
        Object propertyId = fieldGroup.getPropertyId(field);
531

    
532
        if(propertyId == null){
533
            // not found in the editor field group. Maybe the field is bound to a nested fieldgroup?
534
            // 1. find the NestedFieldGroup implementations from the field up to the editor
535
            logger.setLevel(Level.DEBUG);
536
            PropertyIdPath nestedPropertyIds = new PropertyIdPath();
537
            Field parentField = field;
538
            HasComponents parentComponent = parentField.getParent();
539
            logger.debug("field: " + parentField.getClass().getSimpleName());
540
            while(parentComponent != null){
541
                logger.debug("parentComponent: " + parentComponent.getClass().getSimpleName());
542
                if(NestedFieldGroup.class.isAssignableFrom(parentComponent.getClass()) && AbstractField.class.isAssignableFrom(parentComponent.getClass())){
543
                    Object propId = ((NestedFieldGroup)parentComponent).getFieldGroup().getPropertyId(parentField);
544
                    if(propId != null){
545
                        logger.debug("propId: " + propId.toString());
546
                        nestedPropertyIds.addParent(propId);
547
                    }
548
                    logger.debug("parentField: " + parentField.getClass().getSimpleName());
549
                    parentField = (Field)parentComponent;
550
                } else if(parentComponent == this) {
551
                    // we reached the editor itself
552
                    Object propId = fieldGroup.getPropertyId(parentField);
553
                    if(propId != null){
554
                        logger.debug("propId: " + propId.toString());
555
                        nestedPropertyIds.addParent(propId);
556
                    }
557
                    propertyIdPath = nestedPropertyIds;
558
                    break;
559
                }
560
                parentComponent = parentComponent.getParent();
561
            }
562
            // 2. check the NestedFieldGroup binding the field is direct or indirect child component of the editor
563
//            NO lONGER NEEDED
564
//            parentComponent = parentField.getParent(); // get component containing the last parent field found
565
//            while(true){
566
//                if(parentComponent == getFieldLayout()){
567
//                    propertyIdPath = nestedPropertyIds;
568
//                    break;
569
//                }
570
//                parentComponent = parentComponent.getParent();
571
//            }
572
        } else {
573
            propertyIdPath = new PropertyIdPath(propertyId);
574
        }
575
        return propertyIdPath;
576
    }
577

    
578
    protected void addComponent(Component component) {
579
        fieldLayout.addComponent(component);
580
        applyDefaultComponentStyles(component);
581
    }
582

    
583
    protected void bindField(Field field, String propertyId){
584
        fieldGroup.bind(field, propertyId);
585
    }
586

    
587
    /**
588
     * @param component
589
     */
590
    public void applyDefaultComponentStyles(Component component) {
591
        component.addStyleName(getDefaultComponentStyles());
592
    }
593

    
594
    protected abstract String getDefaultComponentStyles();
595

    
596
    /**
597
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
598
     * <p>
599
     * Adds the component to the grid in cells column1,row1 (NortWest corner of
600
     * the area.) End coordinates (SouthEast corner of the area) are the same as
601
     * column1,row1. The coordinates are zero-based. Component width and height
602
     * is 1.
603
     *
604
     * @param component
605
     *            the component to be added, not <code>null</code>.
606
     * @param column
607
     *            the column index, starting from 0.
608
     * @param row
609
     *            the row index, starting from 0.
610
     * @throws OverlapsException
611
     *             if the new component overlaps with any of the components
612
     *             already in the grid.
613
     * @throws OutOfBoundsException
614
     *             if the cell is outside the grid area.
615
     */
616
    public void addComponent(Component component, int column, int row)
617
            throws OverlapsException, OutOfBoundsException {
618
        applyDefaultComponentStyles(component);
619
        gridLayout().addComponent(component, column, row, column, row);
620
    }
621

    
622
    /**
623
     * Can only be used if the <code>fieldlayout</code> is a GridLayout.
624
     * <p>
625
     * Adds a component to the grid in the specified area. The area is defined
626
     * by specifying the upper left corner (column1, row1) and the lower right
627
     * corner (column2, row2) of the area. The coordinates are zero-based.
628
     * </p>
629
     *
630
     * <p>
631
     * If the area overlaps with any of the existing components already present
632
     * in the grid, the operation will fail and an {@link OverlapsException} is
633
     * thrown.
634
     * </p>
635
     *
636
     * @param component
637
     *            the component to be added, not <code>null</code>.
638
     * @param column1
639
     *            the column of the upper left corner of the area <code>c</code>
640
     *            is supposed to occupy. The leftmost column has index 0.
641
     * @param row1
642
     *            the row of the upper left corner of the area <code>c</code> is
643
     *            supposed to occupy. The topmost row has index 0.
644
     * @param column2
645
     *            the column of the lower right corner of the area
646
     *            <code>c</code> is supposed to occupy.
647
     * @param row2
648
     *            the row of the lower right corner of the area <code>c</code>
649
     *            is supposed to occupy.
650
     * @throws OverlapsException
651
     *             if the new component overlaps with any of the components
652
     *             already in the grid.
653
     * @throws OutOfBoundsException
654
     *             if the cells are outside the grid area.
655
     */
656
    public void addComponent(Component component, int column1, int row1,
657
            int column2, int row2)
658
            throws OverlapsException, OutOfBoundsException {
659
        applyDefaultComponentStyles(component);
660
        gridLayout().addComponent(component, column1, row1, column2, row2);
661
    }
662

    
663
    public void setSaveButtonEnabled(boolean enabled){
664
        save.setEnabled(enabled);
665
    }
666

    
667
    public void withDeleteButton(boolean withDelete){
668

    
669
        if(withDelete){
670
            buttonLayout.setExpandRatio(save, 0);
671
            buttonLayout.setExpandRatio(delete, 1);
672
        } else {
673
            buttonLayout.setExpandRatio(save, 1);
674
            buttonLayout.setExpandRatio(delete, 0);
675
        }
676
        delete.setVisible(withDelete);
677
    }
678

    
679
    public boolean addStatusMessage(String message){
680
        boolean returnVal = statusMessages.add(message);
681
        updateStatusLabel();
682
        return returnVal;
683
    }
684

    
685
    public boolean removeStatusMessage(String message){
686
        boolean returnVal = statusMessages.remove(message);
687
        updateStatusLabel();
688
        return returnVal;
689
    }
690

    
691
    /**
692
     *
693
     */
694
    private void updateStatusLabel() {
695
        String text = "";
696
        for(String s : statusMessages){
697
            text += s + " ";
698
        }
699
        statusMessageLabel.setValue(text);
700
        statusMessageLabel.setVisible(!text.isEmpty());
701
        statusMessageLabel.addStyleName(ValoTheme.LABEL_COLORED);
702
    }
703

    
704
    // ------------------------ data binding ------------------------ //
705

    
706
    protected void bindDesign(Component component) {
707
        fieldLayout.removeAllComponents();
708
        fieldGroup.bindMemberFields(component);
709
        fieldLayout.addComponent(component);
710
    }
711

    
712

    
713
    public final void loadInEditor(Object identifier) {
714

    
715
        DTO beanToEdit = getPresenter().loadBeanById(identifier);
716
        fieldGroup.setItemDataSource(beanToEdit);
717
        afterItemDataSourceSet();
718
        getPresenter().adaptToUserPermission(beanToEdit);
719
        isBeanLoaded = true;
720
    }
721

    
722
    /**
723
     * Passes the beanInstantiator to the presenter method {@link AbstractEditorPresenter#setBeanInstantiator(BeanInstantiator)}
724
     *
725
     * @param beanInstantiator
726
     */
727
    public final void setBeanInstantiator(BeanInstantiator<DTO> beanInstantiator) {
728
        getPresenter().setBeanInstantiator(beanInstantiator);
729
    }
730

    
731
    /**
732
     * Returns the bean contained in the itemDatasource of the fieldGroup.
733
     *
734
     * @return
735
     */
736
    public DTO getBean() {
737
        if(fieldGroup.getItemDataSource() != null){
738
            return fieldGroup.getItemDataSource().getBean();
739

    
740
        }
741
        return null;
742
    }
743

    
744
    /**
745
     * @return true once the bean has been loaded indicating that all fields have
746
     *   been setup configured so that the editor is ready for use.
747
     */
748
    public boolean isBeanLoaded() {
749
        return isBeanLoaded;
750
    }
751

    
752
    /**
753
     * This method should only be used by the presenter of this view
754
     *
755
     * @param bean
756
     */
757
    protected void updateItemDataSource(DTO bean) {
758
        fieldGroup.getItemDataSource().setBean(bean);
759
    }
760

    
761
    /**
762
     * This method is called after setting the item data source whereby the
763
     * {@link FieldGroup#configureField(Field<?> field)} method will be called.
764
     * In this method all fields are set to default states defined for the fieldGroup.
765
     * <p>
766
     * You can now implement this method if you need to modify the state or value of individual fields.
767
     */
768
    protected void afterItemDataSourceSet() {
769
    }
770

    
771
    // ------------------------ issue related temporary solutions --------------------- //
772
    /**
773
     * Publicly accessible equivalent to getPreseneter(), needed for
774
     * managing the presenter listeners.
775
     * <p>
776
     * TODO: refactor the presenter listeners management to get rid of this method
777
     *
778
     * @return
779
     * @deprecated marked deprecated to emphasize on the special character of this method
780
     *    which should only be used internally see #6673
781
     */
782
    @Deprecated
783
    public P presenter() {
784
        return getPresenter();
785
    }
786

    
787
    /**
788
     * Returns the context of editor actions for this editor.
789
     * The context submitted with {@link #setParentContext(Stack)} will be updated
790
     * to represent the current context.
791
     *
792
     * @return the context
793
     */
794
    public Stack<EditorActionContext> getEditorActionContext() {
795
        if(!isContextUpdated){
796
            if(getBean() == null){
797
                throw new RuntimeException("getContext() is only possible after the bean is loaded");
798
            }
799
            context.push(new AbstractEditorAction.EditorActionContext(getBean(), this));
800
            isContextUpdated = true;
801
        }
802
        return context;
803
    }
804

    
805
    /**
806
     * Set the context of editor actions parent to this editor
807
     *
808
     * @param context the context to set
809
     */
810
    public void setParentEditorActionContext(Stack<EditorActionContext> context) {
811
        if(context != null){
812
            this.context.addAll(context);
813
        }
814
    }
815

    
816
}
(6-6/13)