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}