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