001package jmri.jmrit.display.palette; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.awt.Component; 006import java.awt.Dimension; 007import java.awt.GridBagConstraints; 008import java.awt.GridBagLayout; 009import java.awt.datatransfer.DataFlavor; 010import java.awt.datatransfer.UnsupportedFlavorException; 011import java.awt.event.ActionListener; 012import java.io.IOException; 013import java.util.ArrayList; 014import java.util.HashMap; 015import java.util.Iterator; 016import java.util.Map.Entry; 017 018import javax.annotation.Nonnull; 019import javax.swing.Box; 020import javax.swing.JLabel; 021import javax.swing.JPanel; 022 023import jmri.NamedBean; 024import jmri.Turnout; 025import jmri.jmrit.catalog.DragJLabel; 026import jmri.jmrit.catalog.NamedIcon; 027import jmri.jmrit.display.DisplayFrame; 028import jmri.jmrit.display.Editor; 029import jmri.jmrit.display.IndicatorTurnoutIcon; 030import jmri.jmrit.picker.PickListModel; 031import jmri.util.swing.ImagePanel; 032import jmri.util.swing.JmriJOptionPane; 033 034/** 035 * JPanel for IndicatorTurnout items. 036 * 037 * @author Pete Cressman Copyright (c) 2010, 2020 038 */ 039public class IndicatorTOItemPanel extends TableItemPanel<Turnout> { 040 041 private JPanel _tablePanel; 042 private HashMap<String, HashMap<String, NamedIcon>> _unstoredMaps; 043 private DetectionPanel _detectPanel; 044 protected HashMap<String, HashMap<String, NamedIcon>> _iconGroupsMap; 045 046 public IndicatorTOItemPanel(DisplayFrame parentFrame, String type, String family, PickListModel<Turnout> model) { 047 super(parentFrame, type, family, model); 048 } 049 050 @Override 051 public void init() { 052 if (!_initialized) { 053 super.init(); 054 _detectPanel = new DetectionPanel(this); 055 add(_detectPanel, 1); 056 } 057 hideIcons(); 058 } 059 060 /** 061 * CircuitBuilder init for conversion of plain track to indicator track. 062 */ 063 @Override 064 public void init(JPanel bottomPanel) { 065 super.init(bottomPanel); 066 add(_iconFamilyPanel, 0); 067 } 068 069 /** 070 * Init for update of existing indicator turnout. 071 * _bottom3Panel has "Update Panel" button put onto _bottom1Panel. 072 * 073 * @param doneAction doneAction 074 * @param iconMaps iconMaps 075 */ 076 public void initUpdate(ActionListener doneAction, HashMap<String, HashMap<String, NamedIcon>> iconMaps) { 077 _iconGroupsMap = iconMaps; 078 if (iconMaps != null) { 079 checkCurrentMaps(iconMaps); // is map in families?, does user want to add it? etc. 080 } 081 if (_family == null || _family.isEmpty()) { 082 _family = Bundle.getMessage("unNamed"); 083 } 084 _detectPanel = new DetectionPanel(this); 085 super.init(doneAction, null); 086 add(_detectPanel, 1); 087 add(_iconFamilyPanel, 2); 088 } 089 090 private void checkCurrentMaps(HashMap<String, HashMap<String, NamedIcon>> iconMaps) { 091 String family = getValidFamily(_family, iconMaps); 092 if (_isUnstoredMap) { 093 _unstoredMaps = iconMaps; 094 int result = JmriJOptionPane.showConfirmDialog(_frame.getEditor(), Bundle.getMessage("UnkownFamilyName", family), 095 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, 096 JmriJOptionPane.QUESTION_MESSAGE); 097 if (result == JmriJOptionPane.YES_OPTION) { 098 ItemPalette.addLevel4Family(_itemType, family, iconMaps); 099 } 100 _family = family; 101 } else { 102 if (family != null) { // icons same as a known family, maybe with another name 103 if (!family.equals(_family)) { 104 log.info("{} icon's family \"{}\" found but is called \"{}\" in the Catalog. Name changed to Catalog name.", 105 _itemType, _family, family); 106 _family = family; 107 } 108 return; 109 } 110 } 111 112 } 113 114 protected String getValidFamily(String family, HashMap<String, HashMap<String, NamedIcon>> iconMap) { 115 HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families = ItemPalette.getLevel4FamilyMaps(_itemType); 116 if (families == null || families.isEmpty()) { 117 return null; 118 } 119 String mapFamily; 120 if (iconMap != null) { 121 mapFamily = findFamilyOfMaps(null, iconMap, families); 122 if (log.isDebugEnabled()) { 123 log.debug("getValidFamily: findFamilyOfMaps {} found stored family \"{}\" for family \"{}\".", _itemType, mapFamily, family); 124 } 125 if (mapFamily == null) { 126 _isUnstoredMap = true; 127 } else { 128 _isUnstoredMap = false; 129 if (family != null) { 130 return mapFamily; 131 } 132 } 133 } 134 mapFamily = family; 135 // check that name is not duplicate. 136 boolean nameOK = false; 137 while (!nameOK) { 138 if (mapFamily == null || mapFamily.isEmpty()) { 139 Component fr; 140 if (_dialog != null) fr = _dialog; else fr = this; 141 mapFamily = JmriJOptionPane.showInputDialog(fr, Bundle.getMessage("EnterFamilyName"), 142 Bundle.getMessage("createNewFamily"), JmriJOptionPane.QUESTION_MESSAGE); 143 if (mapFamily == null || mapFamily.isEmpty()) { // user quit 144 return null; 145 } 146 } 147 Iterator<String> iter = families.keySet().iterator(); 148 while (iter.hasNext()) { 149 String fam = iter.next(); 150 log.debug("check names. fam={} family={} mapFamily={}", fam, family, mapFamily); 151 if (mapFamily.equals(fam)) { // family cannot be null 152 JmriJOptionPane.showMessageDialog(_frame, 153 Bundle.getMessage("DuplicateFamilyName", mapFamily, _itemType, Bundle.getMessage("UseAnotherName")), 154 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 155 mapFamily = null; 156 nameOK = false; 157 break; 158 } 159 nameOK = true; 160 } 161 } 162 return mapFamily; 163 } 164 165 /** 166 * Find the family name of the map in a families HashMap. 167 * 168 * @param exemptFamily exclude from matching 169 * @param newMap iconMap 170 * @param families families of itemType 171 * @return null if map is not in the family 172 */ 173 protected String findFamilyOfMaps(String exemptFamily, 174 HashMap<String, HashMap<String, NamedIcon>> newMap, 175 HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families) { 176 for (Entry<String, HashMap<String, HashMap<String, NamedIcon>>> entry : families.entrySet()) { 177 String family = entry.getKey(); 178 if (!family.equals(exemptFamily)) { 179 log.debug(" familyKey = {}", entry.getKey()); 180 HashMap<String, HashMap<String, NamedIcon>> statusMaps = entry.getValue(); 181 if (familiesAreEqual(newMap, statusMaps)) { 182 return family; 183 } 184 } 185 } 186 return null; 187 } 188 189 // Test if status families are equal 190 protected boolean familiesAreEqual( 191 HashMap<String, HashMap<String, NamedIcon>> famOne, 192 HashMap<String, HashMap<String, NamedIcon>> famTwo) { 193 if (famOne.size() != famTwo.size()) { 194 return false; 195 } 196 for (Entry<String, HashMap<String, NamedIcon>> ent : famOne.entrySet()) { 197 String statusKey = ent.getKey(); 198 log.debug(" statusKey = {}", statusKey); 199 HashMap<String, NamedIcon> map = famTwo.get(statusKey); 200 if (map == null) { 201 return false; 202 } 203 if (!mapsAreEqual(ent.getValue(), map)) { 204 return false; 205 } 206 log.debug(" status {}'s are equal.", statusKey); 207 } 208 return true; 209 } 210 211 @Override 212 protected boolean namesStoredMap(String family) { 213 HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families = 214 ItemPalette.getLevel4FamilyMaps(_itemType); 215 if (families.keySet().contains(family)) { 216 return true; 217 } 218 return false; 219 } 220 221 /* 222 * Get a handle in order to change visibility. 223 */ 224 @Override 225 protected JPanel initTablePanel(PickListModel<Turnout> model) { 226 _tablePanel = super.initTablePanel(model); 227 return _tablePanel; 228 } 229 230 @Override 231 public void dispose() { 232 if (_detectPanel != null) { 233 _detectPanel.dispose(); 234 } 235 super.dispose(); 236 } 237 238 @Override 239 protected void makeFamiliesPanel() { 240 HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> 241 families = ItemPalette.getLevel4FamilyMaps(_itemType); 242 boolean isEmpty = families.values().isEmpty(); 243 if (_bottomPanel == null) { 244 makeBottomPanel(isEmpty); 245 } else { 246 if (isEmpty ^ _wasEmpty) { 247 remove(_bottomPanel); 248 makeBottomPanel(isEmpty); 249 } 250 } 251 _wasEmpty = isEmpty; 252 if (isEmpty) { 253 _iconGroupsMap = _unstoredMaps; 254 addIcons2Panel(_iconGroupsMap, _iconPanel, false); 255 if (!_suppressDragging) { 256 makeDragIconPanel(); 257 makeDndIcon(); 258 } 259 addFamilyPanels(false); 260 } else { 261 makeFamiliesPanel(families); 262 } 263 } 264 private void makeFamiliesPanel(@Nonnull HashMap<String, HashMap<String, HashMap<String, NamedIcon>>> families) { 265 266 makeFamilyButtons(families.keySet()); // makes _familyButtonPanel 267 if (_iconGroupsMap == null) { 268 _iconGroupsMap = families.get(_family); 269 if (_iconGroupsMap == null) { 270 _isUnstoredMap = true; 271 _iconGroupsMap = _unstoredMaps; 272 } 273 } 274 addIcons2Panel(_iconGroupsMap, _iconPanel, false); // need to have family maps identified before calling 275 276 if (!_suppressDragging) { 277 makeDragIconPanel(); 278 makeDndIcon(); 279 } 280 addFamilyPanels(!families.isEmpty()); 281 } 282 283 @Override 284 protected String getDisplayKey() { 285 return "TurnoutStateClosed"; 286 } 287 288 /** 289 * Add current family icons to Show Icons pane when _showIconsButton pressed 290 * Also, dropIcon is true, call is from Icondialog and current family icons are 291 * added for editing. 292 * @see #hideIcons() 293 * 294 * @param iconMaps family maps 295 * @param iconPanel panel to fill with icons 296 * @param dropIcon true for ability to drop new image on icon to change icon source 297 * */ 298 protected void addIcons2Panel(HashMap<String, HashMap<String, NamedIcon>> iconMaps, ImagePanel iconPanel, boolean dropIcon) { 299 if (iconMaps == null) { 300 return; 301 } 302 GridBagLayout gridbag = new GridBagLayout(); 303 iconPanel.setLayout(gridbag); 304 iconPanel.removeAll(); 305 306 GridBagConstraints c = ItemPanel.itemGridBagConstraint(); 307 308 if (iconMaps.isEmpty()) { 309 iconPanel.add(Box.createRigidArea(new Dimension(70,70))); 310 } 311 312 for (Entry<String, HashMap<String, NamedIcon>> stringHashMapEntry : iconMaps.entrySet()) { 313 c.gridx = 0; 314 c.gridy++; 315 316 String statusName = stringHashMapEntry.getKey(); 317 JPanel panel = new JPanel(); 318 panel.add(new JLabel(ItemPalette.convertText(statusName))); 319 panel.setOpaque(false); 320 gridbag.setConstraints(panel, c); 321 iconPanel.add(panel); 322 c.gridx++; 323 HashMap<String, NamedIcon> iconMap = stringHashMapEntry.getValue(); 324 ItemPanel.checkIconMap("Turnout", iconMap); // NOI18N 325 for (Entry<String, NamedIcon> ent : iconMap.entrySet()) { 326 String key = ent.getKey(); 327 panel = makeIconDisplayPanel(key, iconMap, dropIcon); 328 329 gridbag.setConstraints(panel, c); 330 iconPanel.add(panel); 331 c.gridx++; 332 } 333 } 334 } 335 336 @Override 337 protected void hideIcons() { 338 if (_tablePanel != null) { 339 _tablePanel.setVisible(true); 340 _tablePanel.invalidate(); 341 } 342 if (_detectPanel != null) { 343 _detectPanel.setVisible(true); 344 _detectPanel.invalidate(); 345 } 346 super.hideIcons(); 347 } 348 349 @Override 350 protected void showIcons() { 351 if (_detectPanel != null) { 352 _detectPanel.setVisible(false); 353 _detectPanel.invalidate(); 354 } 355 if (_tablePanel != null) { 356 _tablePanel.setVisible(false); 357 _tablePanel.invalidate(); // force redraw 358 } 359 super.showIcons(); 360 } 361 362 /** 363 * Action item for delete family. 364 */ 365 @Override 366 protected void deleteFamilySet() { 367 if (JmriJOptionPane.showConfirmDialog(_frame, Bundle.getMessage("confirmDelete", _family), 368 Bundle.getMessage("QuestionTitle"), JmriJOptionPane.YES_NO_OPTION, JmriJOptionPane.QUESTION_MESSAGE) 369 == JmriJOptionPane.YES_OPTION) { 370 ItemPalette.removeLevel4IconMap(_itemType, _family, null); 371 _family = null; 372 _tablePanel.setVisible(true); 373 updateFamiliesPanel(); 374 setFamily(_family); 375 } 376 } 377 378 protected HashMap<String, HashMap<String, NamedIcon>> makeNewIconMap() { 379 HashMap<String, HashMap<String, NamedIcon>> map = new HashMap<>(); 380 for (String statusKey : INDICATOR_TRACK) { 381 map.put(statusKey, makeNewIconMap("Turnout")); // NOI18N 382 } 383 return map; 384 } 385 386 protected void makeDndIcon() { 387 if (_iconGroupsMap != null) { 388 makeDndIcon(_iconGroupsMap.get("ClearTrack")); 389 } else { 390 makeDndIcon(null); 391 } 392 } 393 394 /** 395 * Needed by setFamily() change _family display 396 */ 397 @Override 398 protected void setFamilyMaps() { 399 _iconGroupsMap = ItemPalette.getLevel4Family(_itemType, _family); 400 if (_iconGroupsMap == null) { 401 _isUnstoredMap = true; 402 _iconGroupsMap = _unstoredMaps; 403 } 404 if (!_suppressDragging) { 405 makeDragIconPanel(); 406 makeDndIcon(); 407 } 408 addIcons2Panel(_iconGroupsMap, _iconPanel, false); 409 } 410 411 @Override 412 @SuppressFBWarnings(value = "BC_UNCONFIRMED_CAST", justification = "Cast follows specific Constructor") 413 protected void openDialog(String type, String family) { 414 closeDialogs(); 415 hideIcons(); 416 _dialog = new IndicatorTOIconDialog(type, family, this); 417 IndicatorTOIconDialog d = (IndicatorTOIconDialog)_dialog; 418 if (_family == null) { 419 d.setMaps(null); 420 } else { 421 d.setMaps(_iconGroupsMap); 422 } 423 d.pack(); 424 } 425 426 protected void dialogDone(String family, HashMap<String, HashMap<String, NamedIcon>> iconMap) { 427 if (!_update && !(family.equals(_family) && familiesAreEqual(iconMap, _iconGroupsMap))) { 428 ItemPalette.removeLevel4IconMap(_itemType, _family, null); 429 ItemPalette.addLevel4Family(_itemType, family, iconMap); 430 } else { 431 _iconGroupsMap = iconMap; 432 if (!namesStoredMap(family)) { 433 _isUnstoredMap = true; 434 } 435 if (_isUnstoredMap) { 436 _unstoredMaps = _iconGroupsMap; 437 } 438 } 439 _family = family; 440 makeFamiliesPanel(); 441 setFamily(family); 442 _cntlDown = false; 443 hideIcons(); 444 if (log.isDebugEnabled()) { 445 log.debug("dialogDoneAction done for {} {}. {} unStored={}", 446 _itemType, _family, (_update?"update":""), _isUnstoredMap); 447 } 448 } 449 450 /* 451 * **************** pseudo inheritance ******************** 452 */ 453 public boolean getShowTrainName() { 454 return _detectPanel.getShowTrainName(); 455 } 456 457 public void setShowTrainName(boolean show) { 458 _detectPanel.setShowTrainName(show); 459 } 460 461 public String getOccSensor() { 462 return _detectPanel.getOccSensor(); 463 } 464 465 public String getOccBlock() { 466 return _detectPanel.getOccBlock(); 467 } 468 469 public void setOccDetector(String name) { 470 _detectPanel.setOccDetector(name); 471 } 472 473 public ArrayList<String> getPaths() { 474 return _detectPanel.getPaths(); 475 } 476 477 public void setPaths(ArrayList<String> paths) { 478 _detectPanel.setPaths(paths); 479 } 480 481 public HashMap<String, HashMap<String, NamedIcon>> getIconMaps() { 482 if (_iconGroupsMap != null) { 483 return _iconGroupsMap; 484 } 485 _iconGroupsMap = ItemPalette.getLevel4FamilyMaps(_itemType).get(_family); 486 if (_iconGroupsMap == null) { 487 _iconGroupsMap = _unstoredMaps; 488 } 489 if (_iconGroupsMap == null) { 490 log.warn("Family \"{}\" for type \"{}\" not found.", _family, _itemType); 491 _iconGroupsMap = makeNewIconMap(); 492 } 493 return _iconGroupsMap; 494 } 495 496 @Override 497 protected JLabel getDragger(DataFlavor flavor, 498 HashMap<String, NamedIcon> map, NamedIcon icon) { 499 return new IconDragJLabel(flavor, icon); 500 } 501 502 protected class IconDragJLabel extends DragJLabel { 503 504 public IconDragJLabel(DataFlavor flavor, NamedIcon icon) { 505 super(flavor, icon); 506 } 507 508 @Override 509 public boolean isDataFlavorSupported(DataFlavor flavor) { 510 return super.isDataFlavorSupported(flavor); 511 } 512 513 @Override 514 protected boolean okToDrag() { 515 NamedBean bean = getDeviceNamedBean(); 516 if (bean == null) { 517 JmriJOptionPane.showMessageDialog(this, Bundle.getMessage("noRowSelected"), 518 Bundle.getMessage("WarningTitle"), JmriJOptionPane.WARNING_MESSAGE); 519 return false; 520 } 521 return true; 522 } 523 524 @Override 525 public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { 526 if (!isDataFlavorSupported(flavor)) { 527 return null; 528 } 529 NamedBean bean = getDeviceNamedBean(); 530 if (bean == null) { 531 return null; 532 } 533 534 HashMap<String, HashMap<String, NamedIcon>> iconMap = getIconMaps(); 535 536 if (flavor.isMimeTypeEqual(Editor.POSITIONABLE_FLAVOR)) { 537 IndicatorTurnoutIcon t = new IndicatorTurnoutIcon(_frame.getEditor()); 538 539 t.setOccBlock(_detectPanel.getOccBlock()); 540 t.setOccSensor(_detectPanel.getOccSensor()); 541 t.setShowTrain(_detectPanel.getShowTrainName()); 542 t.setTurnout(bean.getSystemName()); 543 t.setFamily(_family); 544 545 for (Entry<String, HashMap<String, NamedIcon>> entry : iconMap.entrySet()) { 546 String status = entry.getKey(); 547 for (Entry<String, NamedIcon> ent : entry.getValue().entrySet()) { 548 t.setIcon(status, ent.getKey(), new NamedIcon(ent.getValue())); 549 } 550 } 551 t.setLevel(Editor.TURNOUTS); 552 return t; 553 } else if (DataFlavor.stringFlavor.equals(flavor)) { 554 StringBuilder sb = new StringBuilder(_itemType); 555 sb.append(" icons for \""); 556 sb.append(bean.getDisplayName()); 557 sb.append("\""); 558 return sb.toString(); 559 } 560 return null; 561 } 562 } 563 564 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(IndicatorTOItemPanel.class); 565 566}