Project

General

Profile

Download (29.8 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.event.FieldReplaceEvent;
66
import eu.etaxonomy.vaadin.mvp.event.EditorDeleteEvent;
67
import eu.etaxonomy.vaadin.mvp.event.EditorPreSaveEvent;
68
import eu.etaxonomy.vaadin.mvp.event.EditorSaveEvent;
69
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent;
70
import eu.etaxonomy.vaadin.ui.view.DoneWithPopupEvent.Reason;
71
import eu.etaxonomy.vaadin.util.PropertyIdPath;
72

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

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

    
86
    private BeanFieldGroup<DTO> fieldGroup;
87

    
88
    private VerticalLayout mainLayout;
89

    
90
    private Layout fieldLayout;
91

    
92
    private HorizontalLayout buttonLayout;
93

    
94
    private Button save;
95

    
96
    private Button cancel;
97

    
98
    private Button delete;
99

    
100
    private CssLayout toolBar = new CssLayout();
101

    
102
    private CssLayout toolBarButtonGroup = new CssLayout();
103

    
104
    private Label statusMessageLabel = new Label();
105

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

    
108
    private GridLayout _gridLayoutCache;
109

    
110
    private boolean isBeanLoaded;
111

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

    
114
    private boolean isContextUpdated;
115

    
116
    private boolean isAdvancedMode = false;
117

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

    
120
    private Button advancedModeButton;
121

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

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

    
130
        setCompositionRoot(mainLayout);
131

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

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

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

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

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

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

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

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

    
175
        statusMessageLabel.setWidthUndefined();
176

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

    
181
        updateToolBarVisibility();
182
    }
183

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

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

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

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

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

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

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

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

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

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

    
275
    }
276

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

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

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

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

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

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

    
322

    
323
    // ------------------------ event handler ------------------------ //
324

    
325
    private class SaveHandler implements CommitHandler {
326

    
327
        private static final long serialVersionUID = 2047223089707080659L;
328

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

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

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

    
360

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

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

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

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

    
412
    }
413

    
414
    // ------------------------ field adding methods ------------------------ //
415

    
416

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
588
    protected void unbindField(Field field){
589
        fieldGroup.unbind(field);
590
    }
591

    
592
    /**
593
     * @param component
594
     */
595
    public void applyDefaultComponentStyles(Component component) {
596
        component.addStyleName(getDefaultComponentStyles());
597
    }
598

    
599
    protected abstract String getDefaultComponentStyles();
600

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

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

    
668
    public void setSaveButtonEnabled(boolean enabled){
669
        save.setEnabled(enabled);
670
    }
671

    
672
    public void withDeleteButton(boolean withDelete){
673

    
674
        if(withDelete){
675
            buttonLayout.setExpandRatio(save, 0);
676
            buttonLayout.setExpandRatio(delete, 1);
677
        } else {
678
            buttonLayout.setExpandRatio(save, 1);
679
            buttonLayout.setExpandRatio(delete, 0);
680
        }
681
        delete.setVisible(withDelete);
682
    }
683

    
684
    public boolean addStatusMessage(String message){
685
        boolean returnVal = statusMessages.add(message);
686
        updateStatusLabel();
687
        return returnVal;
688
    }
689

    
690
    public boolean removeStatusMessage(String message){
691
        boolean returnVal = statusMessages.remove(message);
692
        updateStatusLabel();
693
        return returnVal;
694
    }
695

    
696
    /**
697
     *
698
     */
699
    private void updateStatusLabel() {
700
        String text = "";
701
        for(String s : statusMessages){
702
            text += s + " ";
703
        }
704
        statusMessageLabel.setValue(text);
705
        statusMessageLabel.setVisible(!text.isEmpty());
706
        statusMessageLabel.addStyleName(ValoTheme.LABEL_COLORED);
707
    }
708

    
709
    // ------------------------ data binding ------------------------ //
710

    
711
    protected void bindDesign(Component component) {
712
        fieldLayout.removeAllComponents();
713
        fieldGroup.bindMemberFields(component);
714
        fieldLayout.addComponent(component);
715
    }
716

    
717

    
718
    public final void loadInEditor(Object identifier) {
719

    
720
        DTO beanToEdit = getPresenter().loadBeanById(identifier);
721
        fieldGroup.setItemDataSource(beanToEdit);
722
        afterItemDataSourceSet();
723
        getPresenter().adaptToUserPermission(beanToEdit);
724
        isBeanLoaded = true;
725
    }
726

    
727
    /**
728
     * Passes the beanInstantiator to the presenter method {@link AbstractEditorPresenter#setBeanInstantiator(BeanInstantiator)}
729
     *
730
     * @param beanInstantiator
731
     */
732
    public final void setBeanInstantiator(BeanInstantiator<DTO> beanInstantiator) {
733
        getPresenter().setBeanInstantiator(beanInstantiator);
734
    }
735

    
736
    /**
737
     * Returns the bean contained in the itemDatasource of the fieldGroup.
738
     *
739
     * @return
740
     */
741
    public DTO getBean() {
742
        if(fieldGroup.getItemDataSource() != null){
743
            return fieldGroup.getItemDataSource().getBean();
744

    
745
        }
746
        return null;
747
    }
748

    
749
    /**
750
     * @return true once the bean has been loaded indicating that all fields have
751
     *   been setup configured so that the editor is ready for use.
752
     */
753
    public boolean isBeanLoaded() {
754
        return isBeanLoaded;
755
    }
756

    
757
    /**
758
     * This method should only be used by the presenter of this view
759
     *
760
     * @param bean
761
     */
762
    protected void updateItemDataSource(DTO bean) {
763
        fieldGroup.getItemDataSource().setBean(bean);
764
    }
765

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

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

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

    
810
    /**
811
     * Set the context of editor actions parent to this editor
812
     *
813
     * @param context the context to set
814
     */
815
    public void setParentEditorActionContext(Stack<EditorActionContext> context) {
816
        if(context != null){
817
            this.context.addAll(context);
818
        }
819
    }
820

    
821
    protected AbstractField<String> replaceComponent(String propertyId, AbstractField<String> oldField, AbstractField<String> newField, int column1, int row1, int column2,
822
            int row2) {
823
                String value = oldField.getValue();
824
                newField.setValue(value);
825
                newField.setCaption(oldField.getCaption());
826
                GridLayout grid = (GridLayout)getFieldLayout();
827
                grid.removeComponent(oldField);
828

    
829
                unbindField(oldField);
830
                addField(newField, propertyId, column1, row1, column2, row2);
831
                getViewEventBus().publish(this, new FieldReplaceEvent(this, oldField, newField));
832
                return newField;
833
            }
834

    
835
}
(6-6/13)