Project

General

Profile

Download (20.8 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 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

    
10
package eu.etaxonomy.taxeditor.bulkeditor.e4;
11

    
12
import java.io.Serializable;
13
import java.util.ArrayList;
14
import java.util.HashMap;
15
import java.util.List;
16
import java.util.Map;
17

    
18
import javax.annotation.PostConstruct;
19
import javax.annotation.PreDestroy;
20
import javax.inject.Inject;
21

    
22
import org.eclipse.core.runtime.IProgressMonitor;
23
import org.eclipse.core.runtime.NullProgressMonitor;
24
import org.eclipse.e4.core.di.annotations.Optional;
25
import org.eclipse.e4.core.services.events.IEventBroker;
26
import org.eclipse.e4.ui.di.Focus;
27
import org.eclipse.e4.ui.di.Persist;
28
import org.eclipse.e4.ui.di.UIEventTopic;
29
import org.eclipse.e4.ui.model.application.ui.MDirtyable;
30
import org.eclipse.e4.ui.model.application.ui.basic.MPart;
31
import org.eclipse.e4.ui.services.EMenuService;
32
import org.eclipse.e4.ui.workbench.modeling.ESelectionService;
33
import org.eclipse.jface.dialogs.MessageDialog;
34
import org.eclipse.jface.layout.GridDataFactory;
35
import org.eclipse.jface.viewers.ISelectionChangedListener;
36
import org.eclipse.jface.viewers.IStructuredSelection;
37
import org.eclipse.jface.viewers.StructuredSelection;
38
import org.eclipse.nebula.widgets.nattable.NatTable;
39
import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommand;
40
import org.eclipse.nebula.widgets.nattable.command.VisualRefreshCommandHandler;
41
import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;
42
import org.eclipse.nebula.widgets.nattable.config.AbstractUiBindingConfiguration;
43
import org.eclipse.nebula.widgets.nattable.config.ConfigRegistry;
44
import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;
45
import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;
46
import org.eclipse.nebula.widgets.nattable.config.IEditableRule;
47
import org.eclipse.nebula.widgets.nattable.data.IRowIdAccessor;
48
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
49
import org.eclipse.nebula.widgets.nattable.edit.EditConfigAttributes;
50
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsEventLayer;
51
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.GlazedListsSortModel;
52
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
53
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;
54
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;
55
import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;
56
import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;
57
import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;
58
import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;
59
import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;
60
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
61
import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;
62
import org.eclipse.nebula.widgets.nattable.selection.RowSelectionModel;
63
import org.eclipse.nebula.widgets.nattable.selection.RowSelectionProvider;
64
import org.eclipse.nebula.widgets.nattable.sort.SortHeaderLayer;
65
import org.eclipse.nebula.widgets.nattable.sort.config.SingleClickSortConfiguration;
66
import org.eclipse.nebula.widgets.nattable.style.DisplayMode;
67
import org.eclipse.nebula.widgets.nattable.style.theme.ModernNatTableThemeConfiguration;
68
import org.eclipse.nebula.widgets.nattable.ui.binding.UiBindingRegistry;
69
import org.eclipse.nebula.widgets.nattable.ui.matcher.MouseEventMatcher;
70
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuAction;
71
import org.eclipse.nebula.widgets.nattable.ui.menu.PopupMenuBuilder;
72
import org.eclipse.swt.SWT;
73
import org.eclipse.swt.dnd.Clipboard;
74
import org.eclipse.swt.dnd.TextTransfer;
75
import org.eclipse.swt.dnd.Transfer;
76
import org.eclipse.swt.layout.GridData;
77
import org.eclipse.swt.layout.GridLayout;
78
import org.eclipse.swt.widgets.Composite;
79
import org.eclipse.swt.widgets.Display;
80
import org.eclipse.swt.widgets.Menu;
81

    
82
import ca.odell.glazedlists.SortedList;
83
import eu.etaxonomy.cdm.api.conversation.ConversationHolder;
84
import eu.etaxonomy.cdm.api.conversation.IConversationEnabled;
85
import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
86
import eu.etaxonomy.cdm.model.common.CdmBase;
87
import eu.etaxonomy.cdm.model.description.SpecimenDescription;
88
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
89
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
90
import eu.etaxonomy.cdm.model.taxon.Taxon;
91
import eu.etaxonomy.cdm.persistence.hibernate.CdmDataChangeMap;
92
import eu.etaxonomy.taxeditor.bulkeditor.BulkEditorQuery;
93
import eu.etaxonomy.taxeditor.bulkeditor.input.AbstractBulkEditorInput;
94
import eu.etaxonomy.taxeditor.bulkeditor.input.sortprovider.TitleCacheComparator;
95
import eu.etaxonomy.taxeditor.editor.ITaxonEditor;
96
import eu.etaxonomy.taxeditor.event.WorkbenchEventConstants;
97
import eu.etaxonomy.taxeditor.l10n.Messages;
98
import eu.etaxonomy.taxeditor.model.IDerivedUnitFacadePart;
99
import eu.etaxonomy.taxeditor.model.IDirtyMarkable;
100
import eu.etaxonomy.taxeditor.model.IPartContentHasDetails;
101
import eu.etaxonomy.taxeditor.model.IPartContentHasFactualData;
102
import eu.etaxonomy.taxeditor.model.IPartContentHasMedia;
103
import eu.etaxonomy.taxeditor.model.IPartContentHasSupplementalData;
104
import eu.etaxonomy.taxeditor.operation.IPostOperationEnabled;
105
import eu.etaxonomy.taxeditor.workbench.part.IE4SavablePart;
106

    
107
/**
108
 *
109
 * @author pplitzner
110
 * @since Sep 8, 2017
111
 *
112
 */
113
public class BulkEditorE4 implements IPartContentHasDetails, IConversationEnabled, IPostOperationEnabled,
114
        IDirtyMarkable, IDerivedUnitFacadePart, IPartContentHasFactualData,
115
        IPartContentHasSupplementalData, IPartContentHasMedia, IE4SavablePart, ITaxonEditor {
116

    
117
    public static final String TYPE_PROPERTY = Messages.BulkEditorE4_TYPE;
118

    
119
    @Inject
120
	private MDirtyable dirty;
121

    
122
    private AbstractBulkEditorInput input;
123

    
124
    private Composite topComposite;
125

    
126
    private ConversationHolder conversation;
127

    
128
    @Inject
129
    private ESelectionService selService;
130

    
131
    @Inject
132
    private IEventBroker eventBroker;
133

    
134
    private ISelectionChangedListener selectionChangedListener;
135

    
136
    @Inject
137
    private MPart thisPart;
138

    
139
    private BulkEditorQuery lastQuery = null;
140

    
141
    private Composite bottomComposite;
142

    
143
    @Inject
144
    private EMenuService menuService;
145

    
146
    private NatTable natTable;
147

    
148
    private DefaultBodyLayerStack bodyLayer;
149

    
150
    private ListDataProvider<CdmBase> bodyDataProvider;
151

    
152
    @Inject
153
    public BulkEditorE4() {
154
	}
155

    
156
	@SuppressWarnings("unused")
157
    public void init(AbstractBulkEditorInput<?> input){
158
	    this.input = input;
159
	    this.conversation = input.getConversation();
160

    
161
	    new BulkEditorSearchE4(this, topComposite, SWT.NONE);
162
	    //layout needed because the search bar is added after @PostConstuct method
163
	    topComposite.getParent().layout();
164

    
165
	    thisPart.setLabel(input.getEditorName());
166

    
167
        if(input.getEntityUuid()!=null){
168
            performSearch(new BulkEditorQuery(input.getEntityUuid().toString()));
169
        }
170

    
171
        createTable();
172

    
173
        configureTable();
174

    
175
        styleTable();
176

    
177
        GridDataFactory.fillDefaults().grab(true, true).applyTo(natTable);
178

    
179
        //propagate selection
180
        selectionChangedListener = (event -> selService.setSelection(getSelection()));
181
        RowSelectionProvider<CdmBase> selectionProvider = new RowSelectionProvider<CdmBase>(bodyLayer.getSelectionLayer(), bodyDataProvider, true);
182
        selectionProvider.addSelectionChangedListener(selectionChangedListener);
183

    
184
        bottomComposite.layout();
185
	}
186

    
187
	private void createTable(){
188
	    ConfigRegistry configRegistry = new ConfigRegistry();
189
	    //property map
190
        Map<String, String> propertyToLabels = new HashMap<>();
191
        propertyToLabels.put(getEditorInput().getName(), getEditorInput().getName());
192
        propertyToLabels.put(TYPE_PROPERTY, TYPE_PROPERTY);
193
        String[] propertyNames = new String[] { input.getName(), TYPE_PROPERTY };
194
        //sorted list
195
        SortedList<CdmBase> sortedList = new SortedList<>(input.getModel(), new TitleCacheComparator());
196
        //data provider
197
        BulkEditorPropertyAccessor columnPropertyAccessor = new BulkEditorPropertyAccessor(input);
198
        bodyDataProvider = new ListDataProvider<CdmBase>(sortedList,
199
                columnPropertyAccessor);
200
        DefaultColumnHeaderDataProvider colHeaderDataProvider = new DefaultColumnHeaderDataProvider(
201
                propertyNames, propertyToLabels);
202
        DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(
203
                bodyDataProvider);
204
        //body
205
        DataLayer dataLayer = new DataLayer(bodyDataProvider);
206
        dataLayer.registerCommandHandler(new VisualRefreshCommandHandler());
207
        GlazedListsEventLayer<CdmBase> eventLayer = new GlazedListsEventLayer<>(dataLayer, input.getModel());
208
        bodyLayer = new DefaultBodyLayerStack(eventLayer);
209
        //column
210
        DataLayer columnHeaderDataLayer = new DataLayer(colHeaderDataProvider);
211
        ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(
212
                columnHeaderDataLayer,
213
                bodyLayer, bodyLayer.getSelectionLayer());
214
        // add the SortHeaderLayer to the column header layer stack
215
        // as we use GlazedLists, we use the GlazedListsSortModel which
216
        // delegates the sorting to the SortedList
217
        final SortHeaderLayer<SpecimenDescription> sortHeaderLayer = new SortHeaderLayer<>(
218
                columnHeaderLayer,
219
                new GlazedListsSortModel<>(
220
                        sortedList,
221
                        columnPropertyAccessor,
222
                        configRegistry,
223
                        columnHeaderDataLayer));
224
        //row
225
        DataLayer rowHeaderDataLayer = new DataLayer(rowHeaderDataProvider);
226
        RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(rowHeaderDataLayer,
227
                bodyLayer, bodyLayer.getSelectionLayer());
228
        rowHeaderDataLayer.setDefaultRowHeight(20);
229
        rowHeaderDataLayer.setColumnWidthByPosition(0, 50);
230
        rowHeaderDataLayer.setColumnWidthByPosition(1, 200);
231
        rowHeaderDataLayer.setColumnWidthByPosition(2, 100);
232

    
233
        //corner
234
        DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(
235
                colHeaderDataProvider, rowHeaderDataProvider);
236
        CornerLayer cornerLayer = new CornerLayer(new DataLayer(
237
                cornerDataProvider), rowHeaderLayer, sortHeaderLayer);
238
        //grid
239
        GridLayer gridLayer = new GridLayer(bodyLayer, sortHeaderLayer,
240
                rowHeaderLayer, cornerLayer);
241

    
242
        natTable = new NatTable(bottomComposite, gridLayer, false);
243
        natTable.setConfigRegistry(configRegistry);
244

    
245
        //full row selection
246
        bodyLayer.getSelectionLayer().setSelectionModel(new RowSelectionModel<CdmBase>(bodyLayer.getSelectionLayer(), bodyDataProvider, new IRowIdAccessor<CdmBase>() {
247
            @Override
248
            public Serializable getRowId(CdmBase rowObject) {
249
                return input.getModel().indexOf(rowObject);
250
            }
251
        }));
252
	}
253

    
254
	private void configureTable(){
255
        //+++CONTEXT MENU+++
256
        menuService.registerContextMenu(natTable, "eu.etaxonomy.taxeditor.bulkeditor.popupmenu.bulkeditor"); //$NON-NLS-1$
257
        // get the menu registered by EMenuService
258
        final Menu e4Menu = natTable.getMenu();
259
        // remove the menu reference from NatTable instance
260
        natTable.setMenu(null);
261
        natTable.addConfiguration(
262
                new AbstractUiBindingConfiguration() {
263
            @Override
264
            public void configureUiBindings(
265
                    UiBindingRegistry uiBindingRegistry) {
266
                // add e4 menu to NatTable
267
                new PopupMenuBuilder(natTable, e4Menu)
268
                    .build();
269

    
270
                // register the UI binding for header, corner and body region
271
                uiBindingRegistry.registerMouseDownBinding(
272
                        new MouseEventMatcher(
273
                                SWT.NONE,
274
                                GridRegion.BODY,
275
                                MouseEventMatcher.RIGHT_BUTTON),
276
                        new PopupMenuAction(e4Menu));
277
                uiBindingRegistry.registerMouseDownBinding(
278
                        new MouseEventMatcher(
279
                                SWT.NONE,
280
                                GridRegion.COLUMN_HEADER,
281
                                MouseEventMatcher.RIGHT_BUTTON),
282
                        new PopupMenuAction(e4Menu));
283
                uiBindingRegistry.registerMouseDownBinding(
284
                        new MouseEventMatcher(
285
                                SWT.NONE,
286
                                GridRegion.CORNER,
287
                                MouseEventMatcher.RIGHT_BUTTON),
288
                        new PopupMenuAction(e4Menu));
289
            }
290
        });
291

    
292
        //make cells editable to allow selecting the text
293
        natTable.addConfiguration(new AbstractRegistryConfiguration() {
294
            @Override
295
            public void configureRegistry(IConfigRegistry configRegistry) {
296
              //make cell editable
297
                configRegistry.registerConfigAttribute(
298
                        EditConfigAttributes.CELL_EDITABLE_RULE,
299
                        IEditableRule.ALWAYS_EDITABLE,
300
                        DisplayMode.EDIT);
301
            }
302
        });
303

    
304
        //enable sorting
305
        natTable.addConfiguration(new SingleClickSortConfiguration());
306
        //add default configuration because autoconfigure is set to false in constructor
307
        natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
308
        natTable.configure();
309
	}
310

    
311
	private void styleTable(){
312
	    natTable.setTheme(new ModernNatTableThemeConfiguration());
313
	}
314

    
315
	/** {@inheritDoc} */
316
	@PostConstruct
317
	public void createPartControl(Composite parent) {
318
		parent.setLayout(new GridLayout());
319

    
320
		topComposite = new Composite(parent, SWT.NONE);
321
		topComposite.setLayout(new GridLayout());
322

    
323
		GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
324
		topComposite.setLayoutData(gridData);
325

    
326
		bottomComposite = new Composite(parent, SWT.NONE);
327
		bottomComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
328
		bottomComposite.setLayout(new GridLayout());
329
	}
330

    
331
	@Override
332
	@Persist
333
	public void save(IProgressMonitor monitor) {
334
	    save(monitor, true);
335
	}
336

    
337
	@Focus
338
	public void setFocus() {
339
        //make sure to bind again if maybe in another view the conversation was unbound
340
        if(conversation!=null && !conversation.isBound()){
341
            conversation.bind();
342
        }
343
        if(input!=null && input.getCdmEntitySession()!= null) {
344
            input.getCdmEntitySession().bind();
345
        }
346

    
347
	    //make sure to bind again if maybe in another view the conversation was unbound
348
	    eventBroker.post(WorkbenchEventConstants.CURRENT_ACTIVE_EDITOR, this);
349
	}
350

    
351
	@PreDestroy
352
	public void dispose() {
353
	    if(conversation!=null){
354
	        conversation.unregisterForDataStoreChanges(this);
355
	        conversation.close();
356
	    }
357
	    if(input!=null){
358
	        input.dispose();
359
	    }
360
	    dirty.setDirty(false);
361
	}
362

    
363
	public void save(IProgressMonitor monitor, boolean resetMerge) {
364
	    if (!input.getCdmEntitySession().isActive()){
365
            input.getCdmEntitySession().bind();
366
        }
367
	    input.saveModel(resetMerge);
368

    
369
	    IStructuredSelection selection = getSelection();
370

    
371
        dirty.setDirty(false);
372
        input.dispose();
373
        input.bind();
374
        conversation.commit(true);
375

    
376
        if (lastQuery != null){
377
            performSearch(lastQuery, selection);
378
        }
379
	}
380

    
381
	public void performSearch(BulkEditorQuery query) {
382
	    performSearch(query, null);
383
	}
384

    
385
	/** {@inheritDoc}
386
	 * @param selection */
387
    public void performSearch(BulkEditorQuery query, IStructuredSelection selection) {
388
        if (query != null) {
389
            // TODO check if dirty, prompt save
390
            if (isDirty()) {
391
                String[] labels = {Messages.BulkEditorE4_SAVE_AND_SEARCH, Messages.BulkEditorE4_DONT_SAVE,Messages.BulkEditorE4_CANCEL};
392
                MessageDialog dialog =new MessageDialog(topComposite.getShell(), Messages.BulkEditorE4_SAVE_CHANGES_TITLE, null, Messages.BulkEditorE4_SAVE_CHANGES_MESSAGE, MessageDialog.QUESTION,labels, 0);
393
                int result = dialog.open();
394
                if (result == 0) {
395
                    save(new NullProgressMonitor());
396
                } else if (result == 2){
397
                    return;
398
                }
399
            }
400
            dirty.setDirty(false);
401
            input.performSearch(query, selection);
402
            lastQuery = query;
403
        }
404
    }
405

    
406
    @Optional
407
    @Inject
408
    private void updateAfterSearch(@UIEventTopic(WorkbenchEventConstants.BULK_EDITOR_SEARCH_FINISHED)IStructuredSelection selection){
409
        if(selection!=null){
410
            setSelection(selection);
411
        }
412
//        //auto resize columns
413
//        InitializeAutoResizeColumnsCommand command = new InitializeAutoResizeColumnsCommand(
414
//                natTable, 2, natTable.getConfigRegistry(), new GCFactory(
415
//                        natTable));
416
//        natTable.doCommand(command);
417
//        command = new InitializeAutoResizeColumnsCommand(
418
//                natTable, 1, natTable.getConfigRegistry(), new GCFactory(
419
//                        natTable));
420
//        natTable.doCommand(command);
421
    }
422

    
423
    public void refresh(){
424
        natTable.doCommand(new VisualRefreshCommand());
425
    }
426

    
427
    public IStructuredSelection getSelection(){
428
        List<CdmBase> selection = new ArrayList<>();
429
        int[] fullySelectedRowPositions = bodyLayer.getSelectionLayer().getFullySelectedRowPositions();
430
        for (int i : fullySelectedRowPositions) {
431
            Object rowObject = bodyDataProvider.getRowObject(i);
432
            if(rowObject instanceof CdmBase){
433
                selection.add((CdmBase) rowObject);
434
            }
435
        }
436
        return new StructuredSelection(selection);
437
    }
438

    
439
    public void setSelection(IStructuredSelection selection){
440
        Object[] objects = selection.toArray();
441
        for (Object object : objects) {
442
            if(object instanceof CdmBase){
443
                bodyLayer.getSelectionLayer().selectRow(0, bodyDataProvider.indexOfRowObject((CdmBase) object), false, false);
444
            }
445
        }
446
    }
447

    
448
    public void setDirty(){
449
        dirty.setDirty(true);
450
    }
451

    
452
    public boolean isDirty() {
453
        return dirty.isDirty();
454
    }
455

    
456
    public AbstractBulkEditorInput getEditorInput() {
457
        return input;
458
    }
459

    
460
    public void copyDataToClipboard() {
461
        String textData = "";
462
        IStructuredSelection selection = getSelection();
463
        Object[] objects = selection.toArray();
464
        for (Object object : objects) {
465
            if(object instanceof CdmBase){
466
                textData += getEditorInput().getText((CdmBase)object);
467
            }
468
        }
469
        final TextTransfer textTransfer = TextTransfer.getInstance();
470
        final Clipboard clipboard = new Clipboard(Display.getDefault());
471
        try {
472
            clipboard.setContents(new Object[] { textData.toString() },
473
                    new Transfer[] { textTransfer });
474
        } finally {
475
            clipboard.dispose();
476
        }
477
    }
478

    
479
    @Override
480
    public void update(CdmDataChangeMap arg0) {
481
    }
482

    
483
    @Override
484
    public boolean canAttachMedia() {
485
        return true;
486
    }
487

    
488
    @Override
489
    public void changed(Object element) {
490
        if(element instanceof DerivedUnitFacade){
491
            DerivedUnit derivedUnit = ((DerivedUnitFacade) element).innerDerivedUnit();
492
            if(derivedUnit!=null){
493
                getEditorInput().addSaveCandidate(derivedUnit);
494
            }
495
            FieldUnit fieldUnit = ((DerivedUnitFacade) element).innerFieldUnit();
496
            if(fieldUnit!=null){
497
                getEditorInput().addSaveCandidate(fieldUnit);
498
            }
499
        }
500
        else if (element instanceof CdmBase) {
501
            getEditorInput().addSaveCandidate((CdmBase)element);
502
            input.replaceInModel((CdmBase) element);
503
        }
504
        dirty.setDirty(true);
505
        setSelection(new StructuredSelection(element));
506
    }
507

    
508
    @Override
509
    public void forceDirty() {
510
        dirty.setDirty(true);
511
    }
512

    
513
    @Override
514
    public boolean postOperation(CdmBase objectAffectedByOperation) {
515
        return false;
516
    }
517

    
518
    @Override
519
    public boolean onComplete() {
520
        return false;
521
    }
522

    
523
    @Override
524
    public ConversationHolder getConversationHolder() {
525
        return conversation;
526
    }
527

    
528
    public BulkEditorQuery getLastQuery() {
529
       return lastQuery;
530
    }
531

    
532
    public void setLastQuery(BulkEditorQuery lastQuery) {
533
       this.lastQuery = lastQuery;
534

    
535
    }
536

    
537
    @Override
538
    public Taxon getTaxon() {
539
        IStructuredSelection selection = getSelection();
540
        if(selection.size()==1) {
541
            Object object = selection.iterator().next();
542
            if(object instanceof Taxon){
543
                return (Taxon) object;
544
            }
545
        }
546
        return null;
547
    }
548

    
549
}
(1-1/5)