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.io.InputStream;
018import java.math.BigInteger;
019import java.net.URL;
020import java.net.URLClassLoader;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Scanner;
024import java.util.StringTokenizer;
025
026import hdf.object.Attribute;
027import hdf.object.Datatype;
028import hdf.object.Group;
029import hdf.object.HObject;
030import hdf.object.MetaDataContainer;
031import hdf.object.h5.H5CompoundAttr;
032import hdf.object.h5.H5Datatype;
033import hdf.object.h5.H5ScalarAttr;
034import hdf.view.Tools;
035import hdf.view.ViewProperties;
036
037import org.eclipse.swt.SWT;
038import org.eclipse.swt.browser.Browser;
039import org.eclipse.swt.events.DisposeEvent;
040import org.eclipse.swt.events.DisposeListener;
041import org.eclipse.swt.events.SelectionAdapter;
042import org.eclipse.swt.events.SelectionEvent;
043import org.eclipse.swt.graphics.Point;
044import org.eclipse.swt.graphics.Rectangle;
045import org.eclipse.swt.layout.GridData;
046import org.eclipse.swt.layout.GridLayout;
047import org.eclipse.swt.widgets.Button;
048import org.eclipse.swt.widgets.Combo;
049import org.eclipse.swt.widgets.Composite;
050import org.eclipse.swt.widgets.Dialog;
051import org.eclipse.swt.widgets.Display;
052import org.eclipse.swt.widgets.Label;
053import org.eclipse.swt.widgets.Shell;
054import org.eclipse.swt.widgets.Text;
055
056/**
057 * NewScalarAttributeDialog displays components for adding a new attribute.
058 *
059 * @author Jordan T. Henderson
060 * @version 2.4 1/7/2016
061 */
062public class NewScalarAttributeDialog extends NewDataObjectDialog {
063
064    private static final org.slf4j.Logger log =
065        org.slf4j.LoggerFactory.getLogger(NewScalarAttributeDialog.class);
066
067    /** the default length of a string attribute */
068    public static final int DEFAULT_STRING_ATTRIBUTE_LENGTH = 256;
069
070    private Text currentSizeField;
071
072    private Combo rankChoice;
073
074    /** TextField for entering the name of the attribute */
075    protected Text nameField;
076
077    /**
078     * Constructs a NewScalarAttributeDialog with specified object (dataset, group, or
079     * image) for the new attribute to be attached to.
080     *
081     * @param parent
082     *            the parent shell of the dialog
083     * @param pObject
084     *            the parent object which the new attribute is attached to.
085     * @param objs
086     *            the list of all objects.
087     */
088    public NewScalarAttributeDialog(Shell parent, HObject pObject, List<HObject> objs)
089    {
090        super(parent, pObject, objs);
091    }
092
093    /**
094     * Open the NewScalarAttributeDialog for adding a new attribute.
095     */
096    public void open()
097    {
098        Shell parent = getParent();
099        shell        = new Shell(parent, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
100        shell.setFont(curFont);
101        shell.setText("New Attribute...");
102        shell.setImages(ViewProperties.getHdfIcons());
103        shell.setLayout(new GridLayout(1, true));
104
105        // Create Attribute name / Parent Object region
106        Composite fieldComposite = new Composite(shell, SWT.NONE);
107        fieldComposite.setLayout(new GridLayout(2, false));
108        fieldComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
109
110        Label attributeNameLabel = new Label(fieldComposite, SWT.LEFT);
111        attributeNameLabel.setFont(curFont);
112        attributeNameLabel.setText("Attribute name: ");
113
114        nameField = new Text(fieldComposite, SWT.SINGLE | SWT.BORDER);
115        nameField.setFont(curFont);
116        GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
117
118        data.minimumWidth = 250;
119        nameField.setLayoutData(data);
120
121        Label parentObjectLabel = new Label(fieldComposite, SWT.LEFT);
122        parentObjectLabel.setFont(curFont);
123        parentObjectLabel.setText("Parent Object: ");
124
125        // Create Datatype region
126        createDatatypeWidget();
127
128        // Create Dataspace region
129        org.eclipse.swt.widgets.Group dataspaceGroup = new org.eclipse.swt.widgets.Group(shell, SWT.NONE);
130        dataspaceGroup.setFont(curFont);
131        dataspaceGroup.setText("Dataspace");
132        dataspaceGroup.setLayout(new GridLayout(3, true));
133        dataspaceGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
134
135        Label label = new Label(dataspaceGroup, SWT.LEFT);
136        label.setFont(curFont);
137        label.setText("No. of dimensions");
138
139        label = new Label(dataspaceGroup, SWT.LEFT);
140        label.setFont(curFont);
141        label.setText("Current size");
142
143        // Dummy label
144        label = new Label(dataspaceGroup, SWT.LEFT);
145        label.setFont(curFont);
146        label.setText("");
147
148        rankChoice = new Combo(dataspaceGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
149        rankChoice.setFont(curFont);
150        rankChoice.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
151        rankChoice.addSelectionListener(new SelectionAdapter() {
152            @Override
153            public void widgetSelected(SelectionEvent e)
154            {
155                int rank                     = rankChoice.getSelectionIndex() + 1;
156                StringBuilder currentSizeStr = new StringBuilder("1");
157
158                for (int i = 1; i < rank; i++) {
159                    currentSizeStr.append(" x 1");
160                }
161
162                currentSizeField.setText(currentSizeStr.toString());
163
164                String currentStr = currentSizeField.getText();
165                int idx           = currentStr.lastIndexOf('x');
166            }
167        });
168
169        for (int i = 1; i < 33; i++) {
170            rankChoice.add(String.valueOf(i));
171        }
172        rankChoice.select(1);
173
174        currentSizeField = new Text(dataspaceGroup, SWT.SINGLE | SWT.BORDER);
175        currentSizeField.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
176        currentSizeField.setFont(curFont);
177        currentSizeField.setText("1 x 1");
178
179        // Create Ok/Cancel/Help button region
180        Composite buttonComposite = new Composite(shell, SWT.NONE);
181        buttonComposite.setLayout(new GridLayout(3, false));
182        buttonComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
183
184        Button okButton = new Button(buttonComposite, SWT.PUSH);
185        okButton.setFont(curFont);
186        okButton.setText("   &OK   ");
187        okButton.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
188        okButton.addSelectionListener(new SelectionAdapter() {
189            @Override
190            public void widgetSelected(SelectionEvent e)
191            {
192                if (createAttribute()) {
193                    shell.dispose();
194                }
195            }
196        });
197
198        Button cancelButton = new Button(buttonComposite, SWT.PUSH);
199        cancelButton.setFont(curFont);
200        cancelButton.setText(" &Cancel ");
201        cancelButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, false, false));
202        cancelButton.addSelectionListener(new SelectionAdapter() {
203            @Override
204            public void widgetSelected(SelectionEvent e)
205            {
206                newObject = null;
207                shell.dispose();
208            }
209        });
210
211        Button helpButton = new Button(buttonComposite, SWT.PUSH);
212        helpButton.setFont(curFont);
213        helpButton.setText(" &Help ");
214        helpButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false));
215        helpButton.addSelectionListener(new SelectionAdapter() {
216            @Override
217            public void widgetSelected(SelectionEvent e)
218            {
219                new HelpDialog(shell).open();
220            }
221        });
222
223        shell.pack();
224
225        shell.addDisposeListener(new DisposeListener() {
226            @Override
227            public void widgetDisposed(DisposeEvent e)
228            {
229                if (curFont != null)
230                    curFont.dispose();
231            }
232        });
233
234        shell.setMinimumSize(shell.computeSize(SWT.DEFAULT, SWT.DEFAULT));
235
236        Rectangle parentBounds = parent.getBounds();
237        Point shellSize        = shell.getSize();
238        shell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
239                          (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
240
241        shell.open();
242
243        Display display = shell.getDisplay();
244        while (!shell.isDisposed())
245            if (!display.readAndDispatch())
246                display.sleep();
247    }
248
249    /** Check if the dim size is valid */
250    private void checkDimSize()
251    {
252        String dimStr         = currentSizeField.getText();
253        StringTokenizer stDim = new StringTokenizer(dimStr, "x");
254
255        int rank = stDim.countTokens();
256        long dim = 0;
257        for (int i = 0; i < rank; i++) {
258            String token = stDim.nextToken().trim();
259
260            token = token.toLowerCase();
261            try {
262                dim = Long.parseLong(token);
263            }
264            catch (NumberFormatException ex) {
265                shell.getDisplay().beep();
266                Tools.showError(shell, "Check", "Invalid dimension size: " + dimStr);
267                return;
268            }
269        } //  (int i = 0; i < rank; i++)
270    }
271
272    @SuppressWarnings("unchecked")
273    private boolean createAttribute()
274    {
275        String attrName = null;
276        int rank        = -1;
277        long[] dims;
278
279        attrName = nameField.getText();
280        if (attrName != null) {
281            attrName = attrName.trim();
282        }
283
284        if ((attrName == null) || (attrName.length() < 1)) {
285            shell.getDisplay().beep();
286            Tools.showError(shell, "Create", "Attribute name is not specified.");
287            return false;
288        }
289
290        rank               = rankChoice.getSelectionIndex() + 1;
291        StringTokenizer st = new StringTokenizer(currentSizeField.getText(), "x");
292        if (st.countTokens() < rank) {
293            shell.getDisplay().beep();
294            Tools.showError(shell, "Create",
295                            "Number of values in the current dimension size is less than " + rank);
296            return false;
297        }
298
299        long lsize   = 1; // The total size
300        long l       = 0;
301        dims         = new long[rank];
302        String token = null;
303        for (int i = 0; i < rank; i++) {
304            token = st.nextToken().trim();
305            try {
306                l = Long.parseLong(token);
307            }
308            catch (NumberFormatException ex) {
309                shell.getDisplay().beep();
310                Tools.showError(shell, "Create", "Invalid dimension size: " + currentSizeField.getText());
311                return false;
312            }
313
314            if (l <= 0) {
315                shell.getDisplay().beep();
316                Tools.showError(shell, "Create", "Dimension size must be greater than zero.");
317                return false;
318            }
319
320            dims[i] = l;
321            lsize *= l;
322        }
323        log.trace("Create: lsize={}", lsize);
324
325        Attribute attr = null;
326        try {
327            H5Datatype datatype = (H5Datatype)createNewDatatype(null);
328
329            if (datatype.isCompound())
330                attr = (Attribute) new H5CompoundAttr(parentObj, attrName, datatype, dims);
331            else
332                attr = (Attribute) new H5ScalarAttr(parentObj, attrName, datatype, dims);
333            Object value = H5Datatype.allocateArray(datatype, (int)lsize);
334            attr.setAttributeData(value);
335
336            log.trace("writeMetadata() via write()");
337            attr.writeAttribute();
338        }
339        catch (Exception ex) {
340            Tools.showError(shell, "Create", ex.getMessage());
341            log.debug("createAttribute(): ", ex);
342            return false;
343        }
344
345        newObject = (HObject)attr;
346
347        return true;
348    }
349
350    private class HelpDialog extends Dialog {
351        private Shell helpShell;
352
353        public HelpDialog(Shell parent) { super(parent, SWT.APPLICATION_MODAL); }
354
355        public void open()
356        {
357            Shell parent = getParent();
358            helpShell =
359                new Shell(parent, SWT.TITLE | SWT.CLOSE | SWT.RESIZE | SWT.BORDER | SWT.APPLICATION_MODAL);
360            helpShell.setFont(curFont);
361            helpShell.setText("Create New Attribute");
362            helpShell.setImages(ViewProperties.getHdfIcons());
363            helpShell.setLayout(new GridLayout(1, true));
364
365            // Try to create a Browser on platforms that support it
366            try {
367                Browser browser = new Browser(helpShell, SWT.NONE);
368                browser.setFont(curFont);
369                browser.setBounds(0, 0, 500, 500);
370                browser.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
371
372                if (ClassLoader.getSystemResource("hdf/view/HDFView.class").toString().startsWith("jar")) {
373                    // Attempt to load HTML help file from jar
374                    try (InputStream in =
375                             getClass().getClassLoader().getResourceAsStream("hdf/view/NewAttrHelp.html")) {
376                        Scanner scan         = new Scanner(in);
377                        StringBuilder buffer = new StringBuilder();
378                        while (scan.hasNextLine()) {
379                            buffer.append(scan.nextLine());
380                        }
381
382                        browser.setText(buffer.toString());
383
384                        scan.close();
385                    }
386                    catch (Exception e) {
387                        StringBuilder buff = new StringBuilder();
388                        buff.append("<html>")
389                            .append("<body>")
390                            .append("ERROR: cannot load help information.")
391                            .append("</body>")
392                            .append("</html>");
393                        browser.setText(buff.toString(), true);
394                    }
395                }
396                else {
397                    try {
398                        URL url = null, url2 = null, url3 = null;
399                        String rootPath = ViewProperties.getViewRoot();
400
401                        try {
402                            url = new URL("file://" + rootPath + "/HDFView.jar");
403                        }
404                        catch (java.net.MalformedURLException mfu) {
405                            log.debug("help information:", mfu);
406                        }
407
408                        try {
409                            url2 = new URL("file://" + rootPath + "/");
410                        }
411                        catch (java.net.MalformedURLException mfu) {
412                            log.debug("help information:", mfu);
413                        }
414
415                        try {
416                            url3 = new URL("file://" + rootPath + "/src/");
417                        }
418                        catch (java.net.MalformedURLException mfu) {
419                            log.debug("help information:", mfu);
420                        }
421
422                        URL uu[] = {url, url2, url3};
423                        try (URLClassLoader cl = new URLClassLoader(uu)) {
424                            URL u = cl.findResource("hdf/view/NewAttrHelp.html");
425
426                            browser.setUrl(u.toString());
427                        }
428                        catch (Exception ex) {
429                            log.trace("URLClassLoader failed:", ex);
430                        }
431                    }
432                    catch (Exception e) {
433                        StringBuilder buff = new StringBuilder();
434                        buff.append("<html>")
435                            .append("<body>")
436                            .append("ERROR: cannot load help information.")
437                            .append("</body>")
438                            .append("</html>");
439                        browser.setText(buff.toString(), true);
440                    }
441                }
442
443                Button okButton = new Button(helpShell, SWT.PUSH);
444                okButton.setFont(curFont);
445                okButton.setText("   &OK   ");
446                okButton.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, false));
447                okButton.addSelectionListener(new SelectionAdapter() {
448                    @Override
449                    public void widgetSelected(SelectionEvent e)
450                    {
451                        helpShell.dispose();
452                    }
453                });
454
455                helpShell.pack();
456
457                helpShell.setSize(new Point(500, 500));
458
459                Rectangle parentBounds = parent.getBounds();
460                Point shellSize        = helpShell.getSize();
461                helpShell.setLocation((parentBounds.x + (parentBounds.width / 2)) - (shellSize.x / 2),
462                                      (parentBounds.y + (parentBounds.height / 2)) - (shellSize.y / 2));
463
464                helpShell.open();
465
466                Display display = parent.getDisplay();
467                while (!helpShell.isDisposed()) {
468                    if (!display.readAndDispatch())
469                        display.sleep();
470                }
471            }
472            catch (Error er) {
473                // Try opening help link in external browser if platform
474                // doesn't support SWT browser
475                Tools.showError(shell, "Browser support",
476                                "Platform doesn't support Browser. Opening external link in web browser...");
477
478                // TODO: Add support for launching in external browser
479            }
480            catch (Exception ex) {
481                log.debug("Open New Attribute Help failure: ", ex);
482            }
483        }
484    }
485
486    /**
487     * Get the new attribute created.
488     *
489     * @return the new attribute created.
490     */
491    public Attribute getAttribute() { return (Attribute)newObject; }
492}