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}