package games.strategy.triplea.ui; import java.awt.Color; import java.awt.Component; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Properties; import java.util.Set; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.DefaultCellEditor; import javax.swing.JButton; import javax.swing.JEditorPane; import javax.swing.JLabel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import games.strategy.engine.data.Change; import games.strategy.engine.data.GameData; import games.strategy.engine.data.IAttachment; import games.strategy.engine.data.PlayerID; import games.strategy.engine.data.Territory; import games.strategy.engine.data.Unit; import games.strategy.engine.data.events.GameDataChangeListener; import games.strategy.engine.delegate.IDelegateBridge; import games.strategy.engine.display.IDisplay; import games.strategy.engine.framework.IGameModifiedChannel; import games.strategy.engine.gamePlayer.IRemotePlayer; import games.strategy.engine.history.DelegateHistoryWriter; import games.strategy.engine.history.IDelegateHistoryWriter; import games.strategy.engine.random.IRandomStats.DiceType; import games.strategy.net.GUID; import games.strategy.sound.HeadlessSoundChannel; import games.strategy.sound.ISound; import games.strategy.triplea.Constants; import games.strategy.triplea.ResourceLoader; import games.strategy.triplea.ai.AbstractAI; import games.strategy.triplea.attachments.AbstractConditionsAttachment; import games.strategy.triplea.attachments.AbstractTriggerAttachment; import games.strategy.triplea.attachments.ICondition; import games.strategy.triplea.attachments.PoliticalActionAttachment; import games.strategy.triplea.attachments.RulesAttachment; import games.strategy.triplea.attachments.TriggerAttachment; import games.strategy.triplea.delegate.DiceRoll; import games.strategy.triplea.delegate.dataObjects.CasualtyDetails; import games.strategy.triplea.delegate.dataObjects.CasualtyList; import games.strategy.triplea.delegate.remote.IAbstractPlaceDelegate; import games.strategy.triplea.delegate.remote.IMoveDelegate; import games.strategy.triplea.delegate.remote.IPurchaseDelegate; import games.strategy.triplea.delegate.remote.ITechDelegate; import games.strategy.triplea.ui.display.HeadlessDisplay; import games.strategy.triplea.ui.display.ITripleADisplay; import games.strategy.ui.SwingAction; import games.strategy.util.IllegalCharacterRemover; import games.strategy.util.Tuple; import games.strategy.util.UrlStreams; /** * A panel that will show all objectives for all players, including if the objective is filled or not. */ public class ObjectivePanel extends AbstractStatPanel { private static final long serialVersionUID = 3759819236905645520L; private Map<String, Map<ICondition, String>> m_statsObjective; private ObjectiveTableModel m_objectiveModel; private IDelegateBridge m_dummyDelegate; public ObjectivePanel(final GameData data) { super(data); m_dummyDelegate = new ObjectivePanelDummyDelegateBridge(data); initLayout(); } @Override public String getName() { return ObjectiveProperties.getInstance().getProperty(ObjectiveProperties.OBJECTIVES_PANEL_NAME, "Objectives"); } public boolean isEmpty() { return m_statsObjective.isEmpty(); } public void removeDataChangeListener() { m_objectiveModel.removeDataChangeListener(); } @Override protected void initLayout() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); m_objectiveModel = new ObjectiveTableModel(); final JTable table = new JTable(m_objectiveModel); table.getTableHeader().setReorderingAllowed(false); final TableColumn column0 = table.getColumnModel().getColumn(0); column0.setPreferredWidth(34); column0.setWidth(34); column0.setMaxWidth(34); column0.setCellRenderer(new ColorTableCellRenderer()); final TableColumn column1 = table.getColumnModel().getColumn(1); column1.setCellEditor(new EditorPaneCellEditor()); column1.setCellRenderer(new EditorPaneTableCellRenderer()); final JScrollPane scroll = new JScrollPane(table); final JButton refresh = new JButton("Refresh Objectives"); refresh.setAlignmentY(Component.CENTER_ALIGNMENT); refresh.addActionListener(SwingAction.of("Refresh Objectives", e -> { m_objectiveModel.loadData(); SwingUtilities.invokeLater(() -> table.repaint()); })); add(Box.createVerticalStrut(6)); add(refresh); add(Box.createVerticalStrut(6)); add(scroll); } class ObjectiveTableModel extends AbstractTableModel implements GameDataChangeListener { private static final long serialVersionUID = 2259315408905271333L; private static final int COLUMNS_TOTAL = 2; private boolean m_isDirty = true; private String[][] m_collectedData; final Map<String, List<String>> m_sections = new LinkedHashMap<>(); private long m_timestamp = 0; public ObjectiveTableModel() { setObjectiveStats(); m_data.addDataChangeListener(this); m_isDirty = true; } public void removeDataChangeListener() { m_data.removeDataChangeListener(this); } private void setObjectiveStats() { m_statsObjective = new LinkedHashMap<>(); final ObjectiveProperties op = ObjectiveProperties.getInstance(); final Collection<PlayerID> allPlayers = m_data.getPlayerList().getPlayers(); final String gameName = IllegalCharacterRemover.replaceIllegalCharacter(m_data.getGameName(), '_').replaceAll(" ", "_").concat("."); final Map<String, List<String>> sectionsUnsorted = new HashMap<>(); final List<String> sectionsSorters = new ArrayList<>(); final Map<String, Map<ICondition, String>> statsObjectiveUnsorted = new HashMap<>(); // do sections first for (final Entry<Object, Object> entry : op.entrySet()) { final String fileKey = (String) entry.getKey(); if (!fileKey.startsWith(gameName)) { continue; } final String[] key = fileKey.substring(gameName.length(), fileKey.length()).split(";"); final String value = (String) entry.getValue(); if (key.length != 2) { System.err.println("objective.properties keys must be 2 parts: <game_name>." + ObjectiveProperties.GROUP_PROPERTY + ".<#>;player OR <game_name>.player;attachmentName"); continue; } if (!key[0].startsWith(ObjectiveProperties.GROUP_PROPERTY)) { continue; } final String[] sorter = key[0].split("\\."); if (sorter.length != 2) { System.err.println( "objective.properties " + ObjectiveProperties.GROUP_PROPERTY + "must have .<sorter> after it: " + key[0]); continue; } sectionsSorters.add(sorter[1] + ";" + key[1]); sectionsUnsorted.put(key[1], Arrays.asList(value.split(";"))); } Collections.sort(sectionsSorters); for (final String section : sectionsSorters) { final String key = section.split(";")[1]; m_sections.put(key, sectionsUnsorted.get(key)); m_statsObjective.put(key, new LinkedHashMap<>()); statsObjectiveUnsorted.put(key, new HashMap<>()); } // now do the stuff in the sections for (final Entry<Object, Object> entry : op.entrySet()) { final String fileKey = (String) entry.getKey(); if (!fileKey.startsWith(gameName)) { continue; } final String[] key = fileKey.substring(gameName.length(), fileKey.length()).split(";"); final String value = (String) entry.getValue(); if (key.length != 2) { System.err.println("objective.properties keys must be 2 parts: <game_name>." + ObjectiveProperties.GROUP_PROPERTY + ".<#>;player OR <game_name>.player;attachmentName"); continue; } if (key[0].startsWith(ObjectiveProperties.GROUP_PROPERTY)) { continue; } final PlayerID player = m_data.getPlayerList().getPlayerID(key[0]); if (player == null) { // could be an old map, or an old save, so we don't want to stop the game from running. System.err.println("objective.properties player does not exist: " + key[0]); continue; } IAttachment attachment = null; try { if (key[1].contains(Constants.RULES_OBJECTIVE_PREFIX) || key[1].contains(Constants.RULES_CONDITION_PREFIX)) { attachment = RulesAttachment.get(player, key[1], allPlayers, true); } else if (key[1].contains(Constants.TRIGGER_ATTACHMENT_PREFIX)) { attachment = TriggerAttachment.get(player, key[1], allPlayers); } else if (key[1].contains(Constants.POLITICALACTION_ATTACHMENT_PREFIX)) { attachment = PoliticalActionAttachment.get(player, key[1], allPlayers); } else { System.err.println("objective.properties objective must begin with: " + Constants.RULES_OBJECTIVE_PREFIX + " or " + Constants.RULES_CONDITION_PREFIX + " or " + Constants.TRIGGER_ATTACHMENT_PREFIX + " or " + Constants.POLITICALACTION_ATTACHMENT_PREFIX); continue; } } catch (final Exception e) { // could be an old map, or an old save, so we don't want to stop the game from running. System.err.println(e.getMessage()); continue; } if (attachment == null) { System.err.println("objective.properties attachment does not exist: " + key[1]); continue; } if (!ICondition.class.isAssignableFrom(attachment.getClass())) { throw new IllegalStateException("(wtf??) attachment is not an ICondition: " + attachment.getName()); } // find which section boolean found = false; if (m_sections.containsKey(player.getName())) { if (m_sections.get(player.getName()).contains(key[1])) { final Map<ICondition, String> map = statsObjectiveUnsorted.get(player.getName()); if (map == null) { throw new IllegalStateException("objective.properties group has nothing: " + player.getName()); } map.put((ICondition) attachment, value); statsObjectiveUnsorted.put(player.getName(), map); found = true; } } if (!found) { for (final Entry<String, List<String>> sectionEntry : m_sections.entrySet()) { if (sectionEntry.getValue().contains(key[1])) { final Map<ICondition, String> map = statsObjectiveUnsorted.get(sectionEntry.getKey()); if (map == null) { throw new IllegalStateException("objective.properties group has nothing: " + sectionEntry.getKey()); } map.put((ICondition) attachment, value); statsObjectiveUnsorted.put(sectionEntry.getKey(), map); break; } } } } for (final Entry<String, Map<ICondition, String>> entry : m_statsObjective.entrySet()) { final Map<ICondition, String> mapUnsorted = statsObjectiveUnsorted.get(entry.getKey()); final Map<ICondition, String> mapSorted = entry.getValue(); for (final String conditionString : m_sections.get(entry.getKey())) { final Iterator<ICondition> conditionIter = mapUnsorted.keySet().iterator(); while (conditionIter.hasNext()) { final ICondition condition = conditionIter.next(); if (conditionString.equals(condition.getName())) { mapSorted.put(condition, mapUnsorted.get(condition)); conditionIter.remove(); break; } } } } } @Override public synchronized Object getValueAt(final int row, final int col) { // do not refresh too often, or else it will slow the game down seriously if (m_isDirty && Calendar.getInstance().getTimeInMillis() > m_timestamp + 10000) { loadData(); m_isDirty = false; m_timestamp = Calendar.getInstance().getTimeInMillis(); } return m_collectedData[row][col]; } private synchronized void loadData() { m_data.acquireReadLock(); try { final HashMap<ICondition, String> conditions = getConditionComment(getTestedConditions()); m_collectedData = new String[getRowTotal()][COLUMNS_TOTAL]; int row = 0; for (final Entry<String, Map<ICondition, String>> mapEntry : m_statsObjective.entrySet()) { m_collectedData[row][1] = "<html><span style=\"font-size:140%\"><b><em>" + mapEntry.getKey() + "</em></b></span></html>"; for (final Entry<ICondition, String> attachmentEntry : mapEntry.getValue().entrySet()) { row++; m_collectedData[row][0] = conditions.get(attachmentEntry.getKey()); m_collectedData[row][1] = "<html>" + attachmentEntry.getValue() + "</html>"; } row++; m_collectedData[row][1] = "--------------------"; row++; } } finally { m_data.releaseReadLock(); } } public HashMap<ICondition, String> getConditionComment(final HashMap<ICondition, Boolean> testedConditions) { final HashMap<ICondition, String> conditionsComments = new HashMap<>(testedConditions.size()); for (final Entry<ICondition, Boolean> entry : testedConditions.entrySet()) { final boolean satisfied = entry.getValue(); if (entry.getKey() instanceof TriggerAttachment) { final TriggerAttachment ta = (TriggerAttachment) entry.getKey(); final int each = AbstractTriggerAttachment.getEachMultiple(ta); final int uses = ta.getUses(); if (uses < 0) { final String comment = satisfied ? (each > 1 ? "T" + each : "T") : "F"; conditionsComments.put(entry.getKey(), comment); } else if (uses == 0) { final String comment = satisfied ? "Used" : "used"; conditionsComments.put(entry.getKey(), comment); } else { final String comment = uses + "" + (satisfied ? (each > 1 ? "T" + each : "T") : "F"); conditionsComments.put(entry.getKey(), comment); } } else if (entry.getKey() instanceof RulesAttachment) { final RulesAttachment ra = (RulesAttachment) entry.getKey(); final int each = ra.getEachMultiple(); final int uses = ra.getUses(); if (uses < 0) { final String comment = satisfied ? (each > 1 ? "T" + each : "T") : "F"; conditionsComments.put(entry.getKey(), comment); } else if (uses == 0) { final String comment = satisfied ? "Used" : "used"; conditionsComments.put(entry.getKey(), comment); } else { final String comment = uses + "" + (satisfied ? (each > 1 ? "T" + each : "T") : "F"); conditionsComments.put(entry.getKey(), comment); } } else { conditionsComments.put(entry.getKey(), entry.getValue().toString()); } } return conditionsComments; } public HashMap<ICondition, Boolean> getTestedConditions() { final HashSet<ICondition> myConditions = new HashSet<>(); for (final Map<ICondition, String> map : m_statsObjective.values()) { myConditions.addAll(map.keySet()); } final HashSet<ICondition> allConditionsNeeded = AbstractConditionsAttachment.getAllConditionsRecursive(myConditions, null); return AbstractConditionsAttachment.testAllConditionsRecursive(allConditionsNeeded, null, m_dummyDelegate); } @Override public void gameDataChanged(final Change aChange) { synchronized (this) { m_isDirty = true; } SwingUtilities.invokeLater(() -> repaint()); } @Override public String getColumnName(final int col) { if (col == 0) { return "Done"; } else { return "Objective Name"; } } @Override public int getColumnCount() { return COLUMNS_TOTAL; } @Override public synchronized int getRowCount() { if (!m_isDirty) { return m_collectedData.length; } else { m_data.acquireReadLock(); try { return getRowTotal(); } finally { m_data.releaseReadLock(); } } } private int getRowTotal() { int rowsTotal = m_sections.size() * 2; // we include a space between sections as well for (final Map<ICondition, String> map : m_statsObjective.values()) { rowsTotal += map.size(); } return rowsTotal; } public synchronized void setGameData(final GameData data) { synchronized (this) { m_data.removeDataChangeListener(this); m_data = data; setObjectiveStats(); m_data.addDataChangeListener(this); m_isDirty = true; } repaint(); } } @Override public void setGameData(final GameData data) { m_dummyDelegate = new ObjectivePanelDummyDelegateBridge(data); m_data = data; m_objectiveModel.setGameData(data); m_objectiveModel.gameDataChanged(null); } } /** TODO: copy paste overlap with NotifcationMessages.java */ class ObjectiveProperties { // Filename private static final String PROPERTY_FILE = "objectives.properties"; static final String GROUP_PROPERTY = "TABLEGROUP"; static final String OBJECTIVES_PANEL_NAME = "Objectives.Panel.Name"; private static ObjectiveProperties s_op = null; private static long s_timestamp = 0; private final Properties m_properties = new Properties(); protected ObjectiveProperties() { final ResourceLoader loader = AbstractUIContext.getResourceLoader(); final URL url = loader.getResource(PROPERTY_FILE); if (url != null) { final Optional<InputStream> inputStream = UrlStreams.openStream(url); if (inputStream.isPresent()) { try { m_properties.load(inputStream.get()); } catch (final IOException e) { System.out.println("Error reading " + PROPERTY_FILE + " : " + e); } } } } public static ObjectiveProperties getInstance() { // cache properties for 1 second if (s_op == null || Calendar.getInstance().getTimeInMillis() > s_timestamp + 1000) { s_op = new ObjectiveProperties(); s_timestamp = Calendar.getInstance().getTimeInMillis(); } return s_op; } public String getProperty(final String objectiveKey) { return getProperty(objectiveKey, "Not Found In objectives.properties"); } public String getProperty(final String objectiveKey, final String defaultValue) { return m_properties.getProperty(objectiveKey, defaultValue); } public Set<Entry<Object, Object>> entrySet() { return m_properties.entrySet(); } } class ObjectivePanelDummyDelegateBridge implements IDelegateBridge { private final ITripleADisplay m_display = new HeadlessDisplay(); private final ISound m_soundChannel = new HeadlessSoundChannel(); private final DelegateHistoryWriter m_writer = new DelegateHistoryWriter(new DummyGameModifiedChannel()); private final GameData m_data; private final ObjectivePanelDummyPlayer m_dummyAI = new ObjectivePanelDummyPlayer("objective panel dummy", "None (AI)"); public ObjectivePanelDummyDelegateBridge(final GameData data) { m_data = data; } @Override public GameData getData() { return m_data; } @Override public void leaveDelegateExecution() {} @Override public Properties getStepProperties() { throw new UnsupportedOperationException(); } @Override public String getStepName() { throw new UnsupportedOperationException(); } @Override public IRemotePlayer getRemotePlayer(final PlayerID id) { return m_dummyAI; } @Override public IRemotePlayer getRemotePlayer() { return m_dummyAI; } @Override public int[] getRandom(final int max, final int count, final PlayerID player, final DiceType diceType, final String annotation) { if (count <= 0) { throw new IllegalStateException("count must be > o, annotation:" + annotation); } final int[] numbers = new int[count]; for (int i = 0; i < count; i++) { numbers[i] = getRandom(max, player, diceType, annotation); } return numbers; } @Override public int getRandom(final int max, final PlayerID player, final DiceType diceType, final String annotation) { return 0; } @Override public PlayerID getPlayerID() { return PlayerID.NULL_PLAYERID; } @Override public IDelegateHistoryWriter getHistoryWriter() { return m_writer; } @Override public IDisplay getDisplayChannelBroadcaster() { return m_display; } @Override public ISound getSoundChannelBroadcaster() { return m_soundChannel; } @Override public void enterDelegateExecution() {} @Override public void addChange(final Change aChange) {} @Override public void stopGameSequence() {} } class DummyGameModifiedChannel implements IGameModifiedChannel { @Override public void addChildToEvent(final String text, final Object renderingData) {} @Override public void gameDataChanged(final Change aChange) {} @Override public void shutDown() {} @Override public void startHistoryEvent(final String event) {} @Override public void stepChanged(final String stepName, final String delegateName, final PlayerID player, final int round, final String displayName, final boolean loadedFromSavedGame) {} @Override public void startHistoryEvent(final String event, final Object renderingData) {} } class ObjectivePanelDummyPlayer extends AbstractAI { public ObjectivePanelDummyPlayer(final String name, final String type) { super(name, type); } @Override protected void move(final boolean nonCombat, final IMoveDelegate moveDel, final GameData data, final PlayerID player) { throw new UnsupportedOperationException(); } @Override protected void place(final boolean placeForBid, final IAbstractPlaceDelegate placeDelegate, final GameData data, final PlayerID player) { throw new UnsupportedOperationException(); } @Override protected void purchase(final boolean purcahseForBid, final int PUsToSpend, final IPurchaseDelegate purchaseDelegate, final GameData data, final PlayerID player) { throw new UnsupportedOperationException(); } @Override protected void tech(final ITechDelegate techDelegate, final GameData data, final PlayerID player) { throw new UnsupportedOperationException(); } @Override public boolean confirmMoveInFaceOfAA(final Collection<Territory> aaFiringTerritories) { throw new UnsupportedOperationException(); } @Override public Collection<Unit> getNumberOfFightersToMoveToNewCarrier(final Collection<Unit> fightersThatCanBeMoved, final Territory from) { throw new UnsupportedOperationException(); } @Override public Territory retreatQuery(final GUID battleID, final boolean submerge, final Territory battleSite, final Collection<Territory> possibleTerritories, final String message) { throw new UnsupportedOperationException(); } @Override public HashMap<Territory, Collection<Unit>> scrambleUnitsQuery(final Territory scrambleTo, final Map<Territory, Tuple<Collection<Unit>, Collection<Unit>>> possibleScramblers) { throw new UnsupportedOperationException(); } @Override public Collection<Unit> selectUnitsQuery(final Territory current, final Collection<Unit> possible, final String message) { throw new UnsupportedOperationException(); } @Override public CasualtyDetails selectCasualties(final Collection<Unit> selectFrom, final Map<Unit, Collection<Unit>> dependents, final int count, final String message, final DiceRoll dice, final PlayerID hit, final Collection<Unit> friendlyUnits, final PlayerID enemyPlayer, final Collection<Unit> enemyUnits, final boolean amphibious, final Collection<Unit> amphibiousLandAttackers, final CasualtyList defaultCasualties, final GUID battleID, final Territory battlesite, final boolean allowMultipleHitsPerUnit) { throw new UnsupportedOperationException(); } @Override public Territory selectTerritoryForAirToLand(final Collection<Territory> candidates, final Territory currentTerritory, final String unitMessage) { throw new UnsupportedOperationException(); } @Override public boolean shouldBomberBomb(final Territory territory) { throw new UnsupportedOperationException(); } @Override public Unit whatShouldBomberBomb(final Territory territory, final Collection<Unit> potentialTargets, final Collection<Unit> bombers) { throw new UnsupportedOperationException(); } } class ColorTableCellRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 4197520597103598219L; private final DefaultTableCellRenderer adaptee = new DefaultTableCellRenderer(); @Override public Component getTableCellRendererComponent(final JTable table, final Object value, final boolean isSelected, final boolean hasFocus, final int row, final int column) { adaptee.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); final JLabel renderer = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); renderer.setHorizontalAlignment(SwingConstants.CENTER); if (value == null) { renderer.setBorder(BorderFactory.createEmptyBorder()); } else if (value.toString().contains("T")) { renderer.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.green)); } else if (value.toString().contains("U")) { renderer.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.blue)); } else if (value.toString().contains("u")) { renderer.setBorder(BorderFactory.createMatteBorder(2, 2, 2, 2, Color.cyan)); } else { renderer.setBorder(BorderFactory.createEmptyBorder()); } return renderer; } } // author: Heinz M. Kabutz (modified for JEditorPane by Mark Christopher Duncan) class EditorPaneCellEditor extends DefaultCellEditor { private static final long serialVersionUID = 509377442956621991L; public EditorPaneCellEditor() { super(new JTextField()); final JEditorPane textArea = new JEditorPane(); // textArea.setWrapStyleWord(true); // textArea.setLineWrap(true); textArea.setEditable(false); textArea.setContentType("text/html"); final JScrollPane scrollPane = new JScrollPane(textArea); scrollPane.setBorder(null); editorComponent = scrollPane; delegate = new DefaultCellEditor.EditorDelegate() { private static final long serialVersionUID = 5746645959173385516L; @Override public void setValue(final Object value) { textArea.setText((value != null) ? value.toString() : ""); } @Override public Object getCellEditorValue() { return textArea.getText(); } }; } } // author: Heinz M. Kabutz (modified for JEditorPane by Mark Christopher Duncan) class EditorPaneTableCellRenderer extends JEditorPane implements TableCellRenderer { private static final long serialVersionUID = -2835145877164663862L; private final DefaultTableCellRenderer adaptee = new DefaultTableCellRenderer(); private final Map<JTable, Map<Integer, Map<Integer, Integer>>> cellSizes = new HashMap<>(); public EditorPaneTableCellRenderer() { // setLineWrap(true); // setWrapStyleWord(true); setEditable(false); setContentType("text/html"); } @Override public Component getTableCellRendererComponent(final JTable table, final Object obj, final boolean isSelected, final boolean hasFocus, final int row, final int column) { // set the colors, etc. using the standard for that platform adaptee.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column); setForeground(adaptee.getForeground()); setBackground(adaptee.getBackground()); setBorder(adaptee.getBorder()); setFont(adaptee.getFont()); setText(adaptee.getText()); // This line was very important to get it working with JDK1.4 final TableColumnModel columnModel = table.getColumnModel(); setSize(columnModel.getColumn(column).getWidth(), 100000); int height_wanted = (int) getPreferredSize().getHeight(); addSize(table, row, column, height_wanted); height_wanted = findTotalMaximumRowSize(table, row); if (height_wanted != table.getRowHeight(row)) { table.setRowHeight(row, height_wanted); } return this; } private void addSize(final JTable table, final int row, final int column, final int height) { Map<Integer, Map<Integer, Integer>> rows = cellSizes.get(table); if (rows == null) { cellSizes.put(table, rows = new HashMap<>()); } Map<Integer, Integer> rowheights = rows.get(row); if (rowheights == null) { rows.put(row, rowheights = new HashMap<>()); } rowheights.put(column, height); } /** * Look through all columns and get the renderer. If it is * also a TextAreaRenderer, we look at the maximum height in * its hash table for this row. */ private static int findTotalMaximumRowSize(final JTable table, final int row) { int maximum_height = 0; final Enumeration<?> columns = table.getColumnModel().getColumns(); while (columns.hasMoreElements()) { final TableColumn tc = (TableColumn) columns.nextElement(); final TableCellRenderer cellRenderer = tc.getCellRenderer(); if (cellRenderer instanceof EditorPaneTableCellRenderer) { final EditorPaneTableCellRenderer tar = (EditorPaneTableCellRenderer) cellRenderer; maximum_height = Math.max(maximum_height, tar.findMaximumRowSize(table, row)); } } return maximum_height; } private int findMaximumRowSize(final JTable table, final int row) { final Map<Integer, Map<Integer, Integer>> rows = cellSizes.get(table); if (rows == null) { return 0; } final Map<Integer, Integer> rowheights = rows.get(row); if (rowheights == null) { return 0; } int maximum_height = 0; for (final Entry<Integer, Integer> entry : rowheights.entrySet()) { final int cellHeight = entry.getValue(); maximum_height = Math.max(maximum_height, cellHeight); } return maximum_height; } }