001/*****************************************************************************
002 * Copyright by The HDF Group.                                               *
003 * Copyright by the Board of Trustees of the University of Illinois.         *
004 * All rights reserved.                                                      *
005 *                                                                           *
006 * This file is part of the HDF Java Products distribution.                  *
007 * The full copyright notice, including terms governing use, modification,   *
008 * and redistribution, is contained in the COPYING file, which can be found  *
009 * at the root of the source code distribution tree,                         *
010 * or in https://www.hdfgroup.org/licenses.                                  *
011 * If you do not have access to either file, you may request a copy from     *
012 * help@hdfgroup.org.                                                        *
013 ****************************************************************************/
014
015package hdf.view.dialog;
016
017import java.util.Iterator;
018import java.util.List;
019import java.util.StringTokenizer;
020import java.util.Vector;
021
022import org.slf4j.Logger;
023import org.slf4j.LoggerFactory;
024
025import org.eclipse.swt.SWT;
026import org.eclipse.swt.custom.CCombo;
027import org.eclipse.swt.custom.TableEditor;
028import org.eclipse.swt.events.DisposeEvent;
029import org.eclipse.swt.events.DisposeListener;
030import org.eclipse.swt.events.ModifyEvent;
031import org.eclipse.swt.events.ModifyListener;
032import org.eclipse.swt.events.SelectionAdapter;
033import org.eclipse.swt.events.SelectionEvent;
034import org.eclipse.swt.events.TraverseEvent;
035import org.eclipse.swt.events.TraverseListener;
036import org.eclipse.swt.graphics.Point;
037import org.eclipse.swt.graphics.Rectangle;
038import org.eclipse.swt.layout.GridData;
039import org.eclipse.swt.layout.GridLayout;
040import org.eclipse.swt.widgets.Button;
041import org.eclipse.swt.widgets.Combo;
042import org.eclipse.swt.widgets.Composite;
043import org.eclipse.swt.widgets.Display;
044import org.eclipse.swt.widgets.Event;
045import org.eclipse.swt.widgets.Label;
046import org.eclipse.swt.widgets.Listener;
047import org.eclipse.swt.widgets.Shell;
048import org.eclipse.swt.widgets.Table;
049import org.eclipse.swt.widgets.TableColumn;
050import org.eclipse.swt.widgets.TableItem;
051import org.eclipse.swt.widgets.Text;
052
053import hdf.object.Attribute;
054import hdf.object.Datatype;
055import hdf.object.Group;
056import hdf.object.HObject;
057import hdf.object.h5.H5CompoundAttr;
058import hdf.object.h5.H5Datatype;
059
060import hdf.view.Tools;
061import hdf.view.ViewProperties;
062
063/**
064 * NewCompoundAttributeDialog shows a message dialog requesting user input for creating
065 * a new HDF5 compound attribute.
066 *
067 * @author Allen Byrne
068 * @version 1.0 7/20/2021
069 */
070public class NewCompoundAttributeDialog extends NewDataObjectDialog {
071
072    private static final Logger log = LoggerFactory.getLogger(NewCompoundAttributeDialog.class);
073
074    private static final String[] DATATYPE_NAMES   = {
075        "byte (8-bit)", // 0
076        "short (16-bit)", // 1
077        "int (32-bit)", // 2
078        "unsigned byte (8-bit)", // 3
079        "unsigned short (16-bit)", // 4
080        "unsigned int (32-bit)", // 5
081        "long (64-bit)", // 6
082        "float", // 7
083        "double", // 8
084        "string", // 9
085        "enum", // 10
086        "unsigned long (64-bit)" // 11
087    };
088
089    private Combo                 nFieldBox, templateChoice;
090
091    private Vector<H5CompoundAttr>  compoundAttrList;
092
093    private int                   numberOfMembers;
094
095    private Table                 table;
096
097    private TableEditor[][]       editors;
098
099    private Text                  nameField, currentSizeField;
100
101    private Combo                 rankChoice;
102
103    /**
104     * Constructs a NewCompoundAttributeDialog with specified list of possible parent
105     * objects.
106     *
107     * @param parent
108     *            the parent shell of the dialog
109     * @param pObject
110     *            the parent object which the new attribute is attached to.
111     * @param objs
112     *            the list of all objects.
113     */
114    public NewCompoundAttributeDialog(Shell parent, HObject pObject, List<HObject> objs) {
115        super(parent, pObject, objs);
116
117        numberOfMembers = 2;
118
119        compoundAttrList = new Vector<>(objs.size());
120    }
121
122    /**
123     * Open the NewCompoundAttributeDialog for adding a new compound attribute.
124     */
125    public void open() {
126        Shell parent = getParent();
127        shell = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
128        shell.setFont(curFont);
129        shell.setText("New Compound Attribute...");
130        shell.setImages(ViewProperties.getHdfIcons());
131        shell.setLayout(new GridLayout(1, false));
132
133
134        // Create Name/Parent Object/Import field region
135        Composite fieldComposite = new Composite(shell, SWT.NONE);
136        GridLayout layout = new GridLayout(2, false);
137        layout.verticalSpacing = 0;
138        fieldComposite.setLayout(layout);
139        fieldComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
140
141        Label attributeNameLabel = new Label(fieldComposite, SWT.LEFT);
142        attributeNameLabel.setFont(curFont);
143        attributeNameLabel.setText("Attribute name: ");
144
145        nameField = new Text(fieldComposite, SWT.SINGLE | SWT.BORDER);
146        nameField.setFont(curFont);
147        nameField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
148
149        // Create Dataspace region
150        org.eclipse.swt.widgets.Group dataspaceGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
151        dataspaceGroup.setFont(curFont);
152        dataspaceGroup.setText("Dataspace");
153        dataspaceGroup.setLayout(new GridLayout(3, true));
154        dataspaceGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
155
156        Label label = new Label(dataspaceGroup, SWT.LEFT);
157        label.setFont(curFont);
158        label.setText("No. of dimensions");
159
160        label = new Label(dataspaceGroup, SWT.LEFT);
161        label.setFont(curFont);
162        label.setText("Current size");
163
164        // Dummy label
165        label = new Label(dataspaceGroup, SWT.LEFT);
166        label.setFont(curFont);
167        label.setText("");
168
169        rankChoice = new Combo(dataspaceGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
170        rankChoice.setFont(curFont);
171        rankChoice.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
172        rankChoice.addSelectionListener(new SelectionAdapter() {
173            @Override
174            public void widgetSelected(SelectionEvent e) {
175                int rank = rankChoice.getSelectionIndex() + 1;
176                StringBuilder currentSizeStr = new StringBuilder("1");
177
178                for (int i = 1; i < rank; i++) {
179                    currentSizeStr.append(" x 1");
180                }
181
182                currentSizeField.setText(currentSizeStr.toString());
183
184                String currentStr = currentSizeField.getText();
185                int idx = currentStr.lastIndexOf('x');
186            }
187        });
188
189        for (int i = 1; i < 33; i++) {
190            rankChoice.add(String.valueOf(i));
191        }
192        rankChoice.select(1);
193
194        currentSizeField = new Text(dataspaceGroup, SWT.SINGLE | SWT.BORDER);
195        currentSizeField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
196        currentSizeField.setFont(curFont);
197        currentSizeField.setText("1 x 1");
198
199        // Create Properties region
200        org.eclipse.swt.widgets.Group propertiesGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
201        propertiesGroup.setLayout(new GridLayout(2, false));
202        propertiesGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
203        propertiesGroup.setFont(curFont);
204        propertiesGroup.setText("Compound Datatype Properties");
205
206        label = new Label(propertiesGroup, SWT.LEFT);
207        label.setFont(curFont);
208        label.setText("Number of Members:");
209
210        nFieldBox = new Combo(propertiesGroup, SWT.DROP_DOWN);
211        nFieldBox.setFont(curFont);
212        nFieldBox.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
213        nFieldBox.addSelectionListener(new SelectionAdapter() {
214            @Override
215            public void widgetSelected(SelectionEvent e) {
216                updateMemberTableItems();
217            }
218        });
219        nFieldBox.addTraverseListener(new TraverseListener() {
220            @Override
221            public void keyTraversed(TraverseEvent e) {
222                if (e.detail == SWT.TRAVERSE_RETURN) updateMemberTableItems();
223            }
224        });
225
226        for (int i = 1; i <= 100; i++) {
227            nFieldBox.add(String.valueOf(i));
228        }
229
230        table = new Table(propertiesGroup, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
231        table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
232        table.setLinesVisible(false);
233        table.setHeaderVisible(true);
234        table.setFont(curFont);
235
236        editors = new TableEditor[nFieldBox.getItemCount()][3];
237
238        String[] colNames = { "Name", "Datatype", "Array size / String length / Enum names" };
239
240        TableColumn column = new TableColumn(table, SWT.NONE);
241        column.setText(colNames[0]);
242
243        column = new TableColumn(table, SWT.NONE);
244        column.setText(colNames[1]);
245
246        column = new TableColumn(table, SWT.NONE);
247        column.setText(colNames[2]);
248
249        for (int i = 0; i < 2; i++) {
250            TableEditor[] editor = addMemberTableItem(table);
251            editors[i][0] = editor[0];
252            editors[i][1] = editor[1];
253            editors[i][2] = editor[2];
254        }
255
256        for(int i = 0; i < table.getColumnCount(); i++) {
257            table.getColumn(i).pack();
258        }
259
260        // Last table column always expands to fill remaining table size
261        table.addListener(SWT.Resize, new Listener() {
262            @Override
263            public void handleEvent(Event e) {
264                Table table = (Table) e.widget;
265                Rectangle area = table.getClientArea();
266                int columnCount = table.getColumnCount();
267                int totalGridLineWidth = (columnCount - 1) * table.getGridLineWidth();
268
269                int totalColumnWidth = 0;
270                for (TableColumn column : table.getColumns()) {
271                    totalColumnWidth += column.getWidth();
272                }
273
274                int diff = area.width - (totalColumnWidth + totalGridLineWidth);
275
276                TableColumn col = table.getColumns()[columnCount - 1];
277                col.setWidth(diff + col.getWidth());
278            }
279        });
280
281        // Disable table selection highlighting
282        table.addListener(SWT.EraseItem, new Listener() {
283            @Override
284            public void handleEvent(Event e) {
285                if ((e.detail & SWT.SELECTED) != 0) {
286                    e.detail &= ~SWT.SELECTED;
287                }
288            }
289        });
290
291        // Create Ok/Cancel button region
292        Composite buttonComposite = new Composite(shell, SWT.NONE);
293        buttonComposite.setLayout(new GridLayout(2, true));
294        buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1));
295
296        Button okButton = new Button(buttonComposite, SWT.PUSH);
297        okButton.setFont(curFont);
298        okButton.setText("   &OK   ");
299        okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
300        okButton.addSelectionListener(new SelectionAdapter() {
301            @Override
302            public void widgetSelected(SelectionEvent e) {
303                if (createAttribute()) {
304                    shell.dispose();
305                }
306            }
307        });
308
309        Button cancelButton = new Button(buttonComposite, SWT.PUSH);
310        cancelButton.setFont(curFont);
311        cancelButton.setText(" &Cancel ");
312        cancelButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
313        cancelButton.addSelectionListener(new SelectionAdapter() {
314            @Override
315            public void widgetSelected(SelectionEvent e) {
316                newObject = null;
317                shell.dispose();
318            }
319        });
320
321        rankChoice.select(0);
322        nFieldBox.select(nFieldBox.indexOf(String.valueOf(numberOfMembers)));
323
324        shell.pack();
325
326        table.getColumn(0).setWidth(table.getClientArea().width / 3);
327        table.getColumn(1).setWidth(table.getClientArea().width / 3);
328
329        shell.addDisposeListener(new DisposeListener() {
330            @Override
331            public void widgetDisposed(DisposeEvent e) {
332                if (curFont != null) curFont.dispose();
333            }
334        });
335
336        shell.setMinimumSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
337
338        Rectangle parentBounds = parent.getBounds();
339        Point shellSize = shell.getSize();
340        shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
341                          (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
342
343        shell.open();
344
345        Display display = shell.getDisplay();
346        while (!shell.isDisposed())
347            if (!display.readAndDispatch())
348                display.sleep();
349    }
350
351    @SuppressWarnings("unchecked")
352    private boolean createAttribute() {
353        String attrName = null;
354        int rank = -1;
355        long[] dims;
356
357        attrName = nameField.getText();
358        if (attrName != null) {
359            attrName = attrName.trim();
360        }
361
362        if ((attrName == null) || (attrName.length() < 1)) {
363            shell.getDisplay().beep();
364            Tools.showError(shell, "Create", "Attribute name is not specified.");
365            return false;
366        }
367
368        int n = table.getItemCount();
369        if (n <= 0) {
370            return false;
371        }
372
373        String[] mNames = new String[n];
374        Datatype[] mDatatypes = new Datatype[n];
375        int[] mOrders = new int[n];
376
377        for (int i = 0; i < n; i++) {
378            String name = (String) table.getItem(i).getData("MemberName");
379            if ((name == null) || (name.length() <= 0)) {
380                throw new IllegalArgumentException("Member name is empty");
381            }
382            mNames[i] = name;
383            log.trace("createCompoundAttribute member[{}] name = {}", i, mNames[i]);
384
385            int order = 1;
386            String orderStr = (String) table.getItem(i).getData("MemberSize");
387            if (orderStr != null) {
388                try {
389                    order = Integer.parseInt(orderStr);
390                }
391                catch (Exception ex) {
392                    log.debug("compound order:", ex);
393                }
394            }
395            mOrders[i] = order;
396
397            String typeName = (String) table.getItem(i).getData("MemberType");
398            log.trace("createCompoundAttribute type[{}] name = {}", i, typeName);
399            Datatype type = null;
400            try {
401                if (DATATYPE_NAMES[0].equals(typeName)) {
402                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 1, Datatype.NATIVE, Datatype.NATIVE);
403                }
404                else if (DATATYPE_NAMES[1].equals(typeName)) {
405                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 2, Datatype.NATIVE, Datatype.NATIVE);
406                }
407                else if (DATATYPE_NAMES[2].equals(typeName)) {
408                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 4, Datatype.NATIVE, Datatype.NATIVE);
409                }
410                else if (DATATYPE_NAMES[3].equals(typeName)) {
411                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 1, Datatype.NATIVE, Datatype.SIGN_NONE);
412                }
413                else if (DATATYPE_NAMES[4].equals(typeName)) {
414                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 2, Datatype.NATIVE, Datatype.SIGN_NONE);
415                }
416                else if (DATATYPE_NAMES[5].equals(typeName)) {
417                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 4, Datatype.NATIVE, Datatype.SIGN_NONE);
418                }
419                else if (DATATYPE_NAMES[6].equals(typeName)) {
420                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 8, Datatype.NATIVE, Datatype.NATIVE);
421                }
422                else if (DATATYPE_NAMES[7].equals(typeName)) {
423                    type = fileFormat.createDatatype(Datatype.CLASS_FLOAT, 4, Datatype.NATIVE, Datatype.NATIVE);
424                }
425                else if (DATATYPE_NAMES[8].equals(typeName)) {
426                    type = fileFormat.createDatatype(Datatype.CLASS_FLOAT, 8, Datatype.NATIVE, Datatype.NATIVE);
427                }
428                else if (DATATYPE_NAMES[9].equals(typeName)) {
429                    type = fileFormat.createDatatype(Datatype.CLASS_STRING, order, Datatype.NATIVE, Datatype.NATIVE);
430                }
431                else if (DATATYPE_NAMES[10].equals(typeName)) { // enum
432                    type = fileFormat.createDatatype(Datatype.CLASS_ENUM, 4, Datatype.NATIVE, Datatype.NATIVE);
433                    if ((orderStr == null) || (orderStr.length() < 1) || orderStr.endsWith("...")) {
434                        shell.getDisplay().beep();
435                        Tools.showError(shell, "Create", "Invalid member values: " + orderStr);
436                        return false;
437                    }
438                    else {
439                        type.setEnumMembers(orderStr);
440                    }
441                }
442                else if (DATATYPE_NAMES[11].equals(typeName)) {
443                    type = fileFormat.createDatatype(Datatype.CLASS_INTEGER, 8, Datatype.NATIVE, Datatype.SIGN_NONE);
444                }
445                else {
446                    throw new IllegalArgumentException("Invalid data type.");
447                }
448                mDatatypes[i] = type;
449            }
450            catch (Exception ex) {
451                Tools.showError(shell, "Create", ex.getMessage());
452                log.debug("createAttribute(): ", ex);
453                return false;
454            }
455        } //  (int i=0; i<n; i++)
456
457        rank = rankChoice.getSelectionIndex() + 1;
458        log.trace("createCompoundAttribute rank={}", rank);
459        StringTokenizer st = new StringTokenizer(currentSizeField.getText(), "x");
460        if (st.countTokens() < rank) {
461            shell.getDisplay().beep();
462            Tools.showError(shell, "Create", "Number of values in the current dimension size is less than " + rank);
463            return false;
464        }
465
466        long lsize = 1; // The total size
467        long l = 0;
468        dims = new long[rank];
469        String token = null;
470        for (int i = 0; i < rank; i++) {
471            token = st.nextToken().trim();
472            try {
473                l = Long.parseLong(token);
474            }
475            catch (NumberFormatException ex) {
476                shell.getDisplay().beep();
477                Tools.showError(shell, "Create", "Invalid dimension size: " + currentSizeField.getText());
478                return false;
479            }
480
481            if (l <= 0) {
482                shell.getDisplay().beep();
483                Tools.showError(shell, "Create", "Dimension size must be greater than zero.");
484                return false;
485            }
486
487            dims[i] = l;
488            lsize *= l;
489        }
490        log.trace("Create: lsize={}", lsize);
491
492        Attribute attr = null;
493        try {
494            H5Datatype datatype = (H5Datatype)createNewDatatype(null);
495
496            attr = (Attribute)new H5CompoundAttr(parentObj, attrName, datatype, dims);
497            Object value = H5Datatype.allocateArray(datatype, (int) lsize);
498            attr.setAttributeData(value);
499
500            log.trace("writeMetadata() via write()");
501            attr.writeAttribute();
502        }
503        catch (Exception ex) {
504            Tools.showError(shell, "Create", ex.getMessage());
505            log.debug("createAttribute(): ", ex);
506            return false;
507        }
508
509        newObject = (HObject)attr;
510
511        return true;
512    }
513
514    private void updateMemberTableItems() {
515        int n = 0;
516
517        try {
518            n = Integer.valueOf(nFieldBox.getItem(nFieldBox.getSelectionIndex())).intValue();
519        }
520        catch (Exception ex) {
521            log.debug("Change number of members:", ex);
522            return;
523        }
524
525        if (n == numberOfMembers) {
526            return;
527        }
528
529        if(n > numberOfMembers) {
530            try {
531                for (int i = numberOfMembers; i < n; i++) {
532                    TableEditor[] editor = addMemberTableItem(table);
533                    editors[i][0] = editor[0];
534                    editors[i][1] = editor[1];
535                    editors[i][2] = editor[2];
536                }
537            }
538            catch (Exception ex) {
539                log.debug("Error adding member table items: ", ex);
540                return;
541            }
542        }
543        else {
544            try {
545                for(int i = numberOfMembers - 1; i >= n; i--) {
546                    table.remove(i);
547                }
548            }
549            catch (Exception ex) {
550                log.debug("Error removing member table items: ", ex);
551                return;
552            }
553        }
554
555        table.setItemCount(n);
556        numberOfMembers = n;
557    }
558
559    private TableEditor[] addMemberTableItem(Table table) {
560        final TableItem item = new TableItem(table, SWT.NONE);
561        final TableEditor[] editor = new TableEditor[3];
562
563        for (int i = 0; i < editor.length; i++) editor[i] = new TableEditor(table);
564
565        final Text nameText = new Text(table, SWT.SINGLE | SWT.BORDER);
566        nameText.setFont(curFont);
567
568        editor[0].grabHorizontal = true;
569        editor[0].grabVertical = true;
570        editor[0].horizontalAlignment = SWT.LEFT;
571        editor[0].verticalAlignment = SWT.TOP;
572        editor[0].setEditor(nameText, item, 0);
573
574        nameText.addModifyListener(new ModifyListener() {
575            @Override
576            public void modifyText(ModifyEvent e) {
577                Text text = (Text) e.widget;
578                item.setData("MemberName", text.getText());
579            }
580        });
581
582        final CCombo typeCombo = new CCombo(table, SWT.DROP_DOWN | SWT.READ_ONLY);
583        typeCombo.setFont(curFont);
584        typeCombo.setItems(DATATYPE_NAMES);
585
586        editor[1].grabHorizontal = true;
587        editor[1].grabVertical = true;
588        editor[1].horizontalAlignment = SWT.LEFT;
589        editor[1].verticalAlignment = SWT.TOP;
590        editor[1].setEditor(typeCombo, item, 1);
591
592        typeCombo.addSelectionListener(new SelectionAdapter() {
593            @Override
594            public void widgetSelected(SelectionEvent e) {
595                CCombo combo = (CCombo) e.widget;
596                item.setData("MemberType", combo.getItem(combo.getSelectionIndex()));
597            }
598        });
599
600        final Text sizeText = new Text(table, SWT.SINGLE | SWT.BORDER);
601        sizeText.setFont(curFont);
602
603        editor[2].grabHorizontal = true;
604        editor[2].grabVertical = true;
605        editor[2].horizontalAlignment = SWT.LEFT;
606        editor[2].verticalAlignment = SWT.TOP;
607        editor[2].setEditor(sizeText, item, 2);
608
609        sizeText.addModifyListener(new ModifyListener() {
610            @Override
611            public void modifyText(ModifyEvent e) {
612                Text text = (Text) e.widget;
613                item.setData("MemberSize", text.getText());
614            }
615        });
616
617        item.setData("MemberName", "");
618        item.setData("MemberType", "");
619        item.setData("MemberSize", "");
620
621        item.addDisposeListener(new DisposeListener() {
622            @Override
623            public void widgetDisposed(DisposeEvent e) {
624                editor[0].dispose();
625                editor[1].dispose();
626                editor[2].dispose();
627                nameText.dispose();
628                typeCombo.dispose();
629                sizeText.dispose();
630            }
631        });
632
633        return editor;
634    }
635}