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