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