From: <ste...@us...> - 2010-08-10 23:08:15
|
Revision: 1378 http://rails.svn.sourceforge.net/rails/?rev=1378&view=rev Author: stefanfrey Date: 2010-08-10 23:08:08 +0000 (Tue, 10 Aug 2010) Log Message: ----------- Added dynamic (linked to game situations) report window Modified Paths: -------------- trunk/18xx/LocalisedText.properties trunk/18xx/data/Properties.xml trunk/18xx/data/profiles/default.profile trunk/18xx/rails/game/ReportBuffer.java trunk/18xx/rails/game/move/MoveStack.java trunk/18xx/rails/ui/swing/GameUIManager.java trunk/18xx/rails/ui/swing/ORUIManager.java trunk/18xx/rails/ui/swing/ORWindow.java trunk/18xx/rails/ui/swing/ReportWindow.java trunk/18xx/rails/ui/swing/StatusWindow.java Added Paths: ----------- trunk/18xx/rails/ui/swing/AbstractReportWindow.java trunk/18xx/rails/ui/swing/ReportWindowDynamic.java Modified: trunk/18xx/LocalisedText.properties =================================================================== --- trunk/18xx/LocalisedText.properties 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/LocalisedText.properties 2010-08-10 23:08:08 UTC (rev 1378) @@ -168,8 +168,9 @@ Config.label.report.directory=Report directory Config.label.report.filename.date_time_pattern=Report filename date pattern Config.label.report.filename.extension=Report filename extension +Config.label.report.window.editable=Report window editable Config.label.report.window.open=Report window open -Config.label.report.window.editable=Report window editable +Config.label.report.window.type=Report window type Config.label.route.colour.1=Route color for first train Config.label.route.colour.2=Route color for second train Config.label.route.colour.3=Route color for third train @@ -185,9 +186,9 @@ Config.section.Format=Format/Colors Config.section.General=General Config.section.Log=Log -Config.section.Map=Map +Config.section.Font=Fonts Config.section.Save=Save -Config.section.UI=Windows/Fonts +Config.section.UI=Map/Report ConfirmToken=Press Lay Token to confirm token, click another city hex, or press the No Token button. connected=connected CorrectCashAddMoney=CORRECTION: {0} receives {1} from the bank @@ -485,6 +486,8 @@ RepayLoans=Repay loan(s) RepayLoan=Repay {0} loan(s) of {1} for {2} REPORT=Report Window +REPORT_MOVE_BACKWARD=<< +REPORT_MOVE_FORWARD=>> REVENUE=Revenue RevenueCalculation=support for revenue calculation RevenueStations=, Cities = {0}, Towns = {1} Modified: trunk/18xx/data/Properties.xml =================================================================== --- trunk/18xx/data/Properties.xml 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/data/Properties.xml 2010-08-10 23:08:08 UTC (rev 1378) @@ -19,10 +19,7 @@ <Property name="save.recovery.active" type="BOOLEAN" /> <Property name="save.recovery.filepath" type="FILE" /> </Section> - <Section name="UI"> - <Property name="report.window.open" type="BOOLEAN" /> - <Property name="report.window.editable" type="BOOLEAN" /> - <Property name="stockchart.window.open" type="BOOLEAN" /> + <Section name="Font"> <Property name="font.ui.scale" type="PERCENT" initClass="rails.ui.swing.GameUIManager" initMethod="updateUILookAndFeel" initParameter="no" /> <Property name="font.ui.name" type="FONT" @@ -30,9 +27,13 @@ <Property name="font.ui.style" type="LIST" values="plain,bold" initClass="rails.ui.swing.GameUIManager" initMethod="updateUILookAndFeel" initParameter="no" /> </Section> - <Section name="Map"> + <Section name="UI"> <Property name="map.autoscroll" type="BOOLEAN" /> <Property name="map.zoomstep" type="INTEGER" /> + <Property name="report.window.type" type="LIST" values="static,dynamic" /> + <Property name="report.window.open" type="BOOLEAN" /> + <Property name="report.window.editable" type="BOOLEAN" /> + <Property name="stockchart.window.open" type="BOOLEAN" /> </Section> <Section name="Format"> <Property name="money_format" type="STRING" /> Modified: trunk/18xx/data/profiles/default.profile =================================================================== --- trunk/18xx/data/profiles/default.profile 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/data/profiles/default.profile 2010-08-10 23:08:08 UTC (rev 1378) @@ -14,6 +14,7 @@ save.recovery.filepath=18xx_autosave.rails ### Panel UI +report.window.type=dynamic report.window.open=yes report.window.editable=no stockchart.window.open=yes Modified: trunk/18xx/rails/game/ReportBuffer.java =================================================================== --- trunk/18xx/rails/game/ReportBuffer.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/game/ReportBuffer.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -9,6 +9,7 @@ import org.apache.log4j.Logger; import rails.util.Config; +import rails.util.LocalText; import rails.util.Util; /** @@ -22,16 +23,69 @@ * */ public final class ReportBuffer { + + /** defines the collection of data that is stored in the report buffer */ + private class ReportItem { + private List<String> messages = new ArrayList<String>(); + private int index = 0; + private Player player = null; + private RoundI round = null; + + private void addMessage(String message) { + // ignore undos and redos + messages.add(message); + } + + private String getMessages() { + StringBuffer s = new StringBuffer(); + for (String message:messages) { + s.append(message); + } + return s.toString(); + } + + private String toHtml() { + StringBuffer s = new StringBuffer(); + boolean init = true; + for (String message:messages) { + if (init) { + s.append("<a href=http://rails:" + index + ">"); + s.append(message); + s.append("</a><br>"); + init = false; + } else { + s.append(message + "<br>"); + } + } + return s.toString(); + } + + public String toString() { + StringBuffer s = new StringBuffer(); + s.append("ReportItem for MoveStackIndex = " + index); + s.append(", player = " + player); + s.append(", round = " + round); + s.append(", messages = "); s.append(getMessages()); + return s.toString(); + } + } + + /** * A stack for displaying messages in the Log Window. Such messages are * intended to record the progress of the rails.game and can be used as a * rails.game report. */ - private List<String> reportQueue = new ArrayList<String>(); + private List<String> reportQueue = new ArrayList<String> (); /** Another stack for messages that must "wait" for other messages */ private List<String> waitQueue = new ArrayList<String> (); + /** Archive stack, the integer index corresponds with the moveset items */ + private SortedMap<Integer, ReportItem> reportItems = new TreeMap<Integer, ReportItem>(); + /** Indicator string to find the active message position in the parsed html document */ + public static final String ACTIVE_MESSAGE_INDICATOR = "(**)"; + private String reportPathname = null; private PrintWriter report = null; @@ -42,6 +96,7 @@ private static String reportDirectory = null; private static final String DEFAULT_DTS_PATTERN = "yyyyMMdd_HHmm"; private static final String DEFAULT_REPORT_EXTENSION = "txt"; + static { reportDirectory = Config.get("report.directory").trim(); @@ -53,15 +108,16 @@ public ReportBuffer() { + reportItems.put(0, new ReportItem()); if (!initialQueue.isEmpty()) { for (String s : initialQueue) { - addMessage (s); + addMessage(s, -1); // start of the game } initialQueue.clear(); } } - + private List<String> getReportQueue() { return reportQueue; } @@ -70,11 +126,15 @@ reportQueue.clear(); } - private void addMessage (String message) { + private void addMessage(String message, int moveStackIndex) { if (message != null) { - if (message.equals("")) + if (message.equals("")) { message = "---"; // workaround for testing + } + // legacy report queue reportQueue.add(message); + // new queue + reportItems.get(moveStackIndex).addMessage(message); /* Also log the message */ if (message.length() > 0) log.info(message); /* Also write it to the report file, if requested */ @@ -130,8 +190,58 @@ wantReport = false; } } + + private void addReportItem(int index, Player player, RoundI round) { + ReportItem newItem = new ReportItem(); + newItem.index = index; + newItem.player = player; + newItem.round = round; + reportItems.put(index, newItem); + Set<Integer> deleteIndices = new HashSet<Integer> + (reportItems.tailMap(index + 1).keySet()); + for (Integer i:deleteIndices) { + reportItems.remove(i); + } + } + /** Movestack calls the report item to update */ + public static void createNewReportItem(int index) { + // check availablity + GameManagerI gm = GameManager.getInstance(); + ReportBuffer instance = null; + if (gm != null) { + instance = gm.getReportBuffer(); + } + if (gm == null || instance == null) { + return; + } + // all there, add new report item + Player player = gm.getCurrentPlayer(); + RoundI round = gm.getCurrentRound(); + instance.addReportItem(index, player, round); + } + + public static String getReportItems() { + int index = GameManager.getInstance().getMoveStack().getIndex(); + ReportBuffer instance = getInstance(); + + StringBuffer s = new StringBuffer(); + s.append("<html>"); + for (ReportItem item:instance.reportItems.values()) { + if (item.index == index-1) { + s.append("<p bgcolor=Yellow>" + ACTIVE_MESSAGE_INDICATOR) ; + } + s.append(item.toHtml()); + if (item.index == (index-1)) { + s.append("</p><"); + } + } + s.append("</html>"); + + return s.toString(); + } + /** Get the current log buffer, and clear it */ public static String get() { ReportBuffer instance = getInstance(); @@ -146,17 +256,25 @@ return result.toString(); } - + /** Add a message to the log buffer (and display it on the console) */ public static void add(String message) { GameManagerI gm = GameManager.getInstance(); ReportBuffer instance = null; - if (gm != null) instance = gm.getReportBuffer(); - if (gm == null || instance == null) { + if (gm != null) { + instance = gm.getReportBuffer(); + } + if (instance == null) { // Queue in a static buffer until the instance is created initialQueue.add(message); } else { - instance.addMessage(message); + // ignore undo and redo for the new reportItems + if (message.equals(LocalText.getText("UNDO")) || message.equals(LocalText.getText("REDO"))) { + instance.reportQueue.add(message); + return; + } + int moveStackIndex = gm.getMoveStack().getIndex(); + instance.addMessage(message, moveStackIndex); } } @@ -169,7 +287,7 @@ else return instance.getReportQueue(); } - + /** clear the current buffer */ public static void clear() { ReportBuffer instance = getInstance(); @@ -184,18 +302,17 @@ return GameManager.getInstance().getReportBuffer(); } - - - public static void addWaiting (String string) { - getInstance().waitQueue.add (string); + public static void addWaiting (String message) { + getInstance().waitQueue.add(message); } public static void getAllWaiting () { ReportBuffer instance = getInstance(); for (String message : instance.waitQueue) { - instance.addMessage (message); + add(message); } instance.waitQueue.clear(); } + } Modified: trunk/18xx/rails/game/move/MoveStack.java =================================================================== --- trunk/18xx/rails/game/move/MoveStack.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/game/move/MoveStack.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -68,6 +68,7 @@ moveStack.add(currentMoveSet); lastIndex++; currentMoveSet = null; + ReportBuffer.createNewReportItem(this.getIndex()); return true; } } @@ -112,7 +113,7 @@ MoveSet undoAction; do { ReportBuffer.add(LocalText.getText("UNDO")); - // log.debug ("MoveStack undo index is "+lastIndex); + log.debug ("MoveStack undo index is "+lastIndex); undoAction = moveStack.get(lastIndex--); undoAction.unexecute(); } while (undoAction.isLinkedToPreviousMove()); @@ -129,14 +130,13 @@ public boolean redoMoveSet () { if (currentMoveSet == null && lastIndex < moveStack.size() - 1) { MoveSet redoAction; - redoAction= moveStack.get(++lastIndex); do { + redoAction = moveStack.get(++lastIndex); ReportBuffer.add(LocalText.getText("REDO")); + log.debug ("MoveStack redo index is "+lastIndex); redoAction.reexecute(); if (lastIndex == moveStack.size() - 1) break; - redoAction= moveStack.get(++lastIndex); - } while (redoAction.isLinkedToPreviousMove()); - // log.debug ("MoveStack redo index is "+lastIndex); + } while (moveStack.get(lastIndex + 1).isLinkedToPreviousMove()); return true; } else { log.error("Invalid redo: index=" + lastIndex + " size=" @@ -164,5 +164,21 @@ public int getIndex() { return lastIndex + 1; } - + + /** + * undo/redo to a given moveStack index + */ + public boolean gotoIndex(int index) { + if (getIndex() == index) return true; + else if (getIndex() < index) { + while (getIndex() < index) { + if (!redoMoveSet()) return false; + } + } else { + while (getIndex() > index) { + if (!undoMoveSet(true)) return false; + } + }; + return true; + } } Added: trunk/18xx/rails/ui/swing/AbstractReportWindow.java =================================================================== --- trunk/18xx/rails/ui/swing/AbstractReportWindow.java (rev 0) +++ trunk/18xx/rails/ui/swing/AbstractReportWindow.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -0,0 +1,34 @@ +package rails.ui.swing; + +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JFrame; + +import rails.util.Config; +import rails.util.LocalText; + +public abstract class AbstractReportWindow extends JFrame { + private static final long serialVersionUID = 1L; + + public void init() { + setSize(400, 400); + setLocation(600, 400); + setTitle(LocalText.getText("GameReportTitle")); + + final JFrame frame = this; + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + StatusWindow.uncheckMenuItemBox(StatusWindow.REPORT_CMD); + frame.dispose(); + } + }); + setVisible("yes".equalsIgnoreCase(Config.get("report.window.open"))); + } + + public abstract void updateLog(); + + public abstract void scrollDown(); + +} \ No newline at end of file Property changes on: trunk/18xx/rails/ui/swing/AbstractReportWindow.java ___________________________________________________________________ Added: svn:mime-type + text/plain Modified: trunk/18xx/rails/ui/swing/GameUIManager.java =================================================================== --- trunk/18xx/rails/ui/swing/GameUIManager.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/ui/swing/GameUIManager.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -29,7 +29,7 @@ public StockChart stockChart; public StatusWindow statusWindow; - public ReportWindow reportWindow; + public AbstractReportWindow reportWindow; public ConfigWindow configWindow; public ORUIManager orUIManager; public ORWindow orWindow; // TEMPORARY @@ -157,7 +157,11 @@ imageLoader = new ImageLoader(); stockChart = new StockChart(this); - reportWindow = new ReportWindow(gameManager); + if (Config.get("report.window.type").equalsIgnoreCase("static")) { + reportWindow = new ReportWindow(gameManager); + } else { + reportWindow = new ReportWindowDynamic(this); + } orWindow = new ORWindow(this); orUIManager = orWindow.getORUIManager(); @@ -223,7 +227,7 @@ // Follow-up the result log.debug("==Result from server: " + result); - reportWindow.addLog(); + reportWindow.updateLog(); /* if (DisplayBuffer.getAutoDisplay()) { if (displayServerMessage()) { @@ -449,6 +453,7 @@ } updateStatus(activeWindow); + } /** Stub, to be overridden in subclasses for special round types */ Modified: trunk/18xx/rails/ui/swing/ORUIManager.java =================================================================== --- trunk/18xx/rails/ui/swing/ORUIManager.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/ui/swing/ORUIManager.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -472,7 +472,7 @@ } - gameUIManager.reportWindow.addLog(); + gameUIManager.reportWindow.updateLog(); } /** Stub, can be overridden in subclasses */ Modified: trunk/18xx/rails/ui/swing/ORWindow.java =================================================================== --- trunk/18xx/rails/ui/swing/ORWindow.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/ui/swing/ORWindow.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -101,7 +101,7 @@ } }); - gameUIManager.reportWindow.addLog(); + gameUIManager.reportWindow.updateLog(); } public ORUIManager getORUIManager() { Modified: trunk/18xx/rails/ui/swing/ReportWindow.java =================================================================== --- trunk/18xx/rails/ui/swing/ReportWindow.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/ui/swing/ReportWindow.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -25,7 +25,7 @@ * This is the UI for the LogWindow. It displays logged messages to the user * during the rails.game. */ -public class ReportWindow extends JFrame implements ActionListener, KeyListener { +public class ReportWindow extends AbstractReportWindow implements ActionListener, KeyListener { private static final long serialVersionUID = 1L; private JTextArea reportText; @@ -156,23 +156,16 @@ setContentPane(messagePanel); - setSize(400, 400); - setLocation(600, 400); - setTitle(LocalText.getText("GameReportTitle")); - - final JFrame frame = this; - addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent e) { - StatusWindow.uncheckMenuItemBox(StatusWindow.REPORT_CMD); - frame.dispose(); - } - }); addKeyListener(this); - setVisible("yes".equalsIgnoreCase(Config.get("report.window.open"))); + + // default report window settings + super.init(); } - public void addLog() { + /* (non-Javadoc) + * @see rails.ui.swing.ReportWindowI#updateLog() + */ + public void updateLog() { String newText = ReportBuffer.get(); if (newText.length() > 0) { reportText.append(newText); Added: trunk/18xx/rails/ui/swing/ReportWindowDynamic.java =================================================================== --- trunk/18xx/rails/ui/swing/ReportWindowDynamic.java (rev 0) +++ trunk/18xx/rails/ui/swing/ReportWindowDynamic.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -0,0 +1,163 @@ +package rails.ui.swing; + +import java.awt.EventQueue; +import java.awt.Font; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.net.URL; +import java.util.List; + +import javax.swing.JEditorPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.UIManager; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.html.HTMLDocument; + +import org.apache.log4j.Logger; + +import rails.game.ReportBuffer; +import rails.game.action.GameAction; +import rails.game.action.PossibleActions; +import rails.game.move.MoveStack; +import rails.ui.swing.elements.ActionButton; +import rails.util.LocalText; + +/** + * Dynamic Report window that acts as linked game history + */ + +public class ReportWindowDynamic extends AbstractReportWindow implements ActionListener, HyperlinkListener { + private static final long serialVersionUID = 1L; + + private GameUIManager gameUIManager; + + private JScrollPane reportPane; + private JEditorPane editorPane; + + private JPanel buttonPanel; + private ActionButton forwardButton; + private ActionButton backwardButton; + + protected static Logger log = + Logger.getLogger(ReportWindowDynamic.class.getPackage().getName()); + + public ReportWindowDynamic(GameUIManager gameUIManager) { + super(); + this.gameUIManager = gameUIManager; + init(); + } + + public void init() { + editorPane = new JEditorPane(); + editorPane.setEditable(false); + editorPane.setContentType("text/html"); + editorPane.addHyperlinkListener(this); + editorPane.setOpaque(false); + editorPane.setBorder(null); + + // add a CSS rule to force body tags to use the default label font + // instead of the value in javax.swing.text.html.default.csss + Font font = UIManager.getFont("Label.font"); + String bodyRule = "body { font-family: " + font.getFamily() + "; " + + "font-size: " + font.getSize() + "pt; }"; + ((HTMLDocument)editorPane.getDocument()).getStyleSheet().addRule(bodyRule); + + reportPane = new JScrollPane(editorPane); + add(reportPane, "Center"); + + buttonPanel = new JPanel(); + add(buttonPanel, "South"); + + + backwardButton = new ActionButton(LocalText.getText("REPORT_MOVE_BACKWARD")); + backwardButton.addActionListener(this); + buttonPanel.add(backwardButton); + + forwardButton = new ActionButton(LocalText.getText("REPORT_MOVE_FORWARD")); + forwardButton.addActionListener(this); + buttonPanel.add(forwardButton); + + super.init(); + } + + @Override + public void updateLog() { + // set the content of the pane to the current + editorPane.setText(ReportBuffer.getReportItems()); + scrollDown(); + + forwardButton.setEnabled(false); + backwardButton.setEnabled(true); + List<GameAction> gameActions = PossibleActions.getInstance().getType(GameAction.class); + for (GameAction action:gameActions) { + switch (action.getMode()) { + case GameAction.UNDO: + case GameAction.FORCED_UNDO: + backwardButton.setPossibleAction(action); + backwardButton.setEnabled(true); + break; + case GameAction.REDO: + forwardButton.setPossibleAction(action); + forwardButton.setEnabled(true); + break; + } + } + } + + @Override + public void scrollDown() { + // only set caret if visible + if (!this.isVisible()) return; + + // find the active message in the parsed html code (not identical to the position in the html string) + // thus the message indicator is used + int caretPosition; + try{ + String docText = editorPane.getDocument().getText(0, editorPane.getDocument().getLength()); + caretPosition = docText.indexOf(ReportBuffer.ACTIVE_MESSAGE_INDICATOR); + } catch (BadLocationException e){ + caretPosition = -1; + }; + final int caretPositionStore = caretPosition; + if (caretPosition != -1) { + editorPane.setCaretPosition(caretPositionStore); + } + } + + public void actionPerformed(ActionEvent e) { + ActionButton button = (ActionButton)e.getSource(); + GameAction action = (GameAction)button.getPossibleActions().get(0); + gameUIManager.processOnServer(action); + } + + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { + URL url = e.getURL(); +// String protocol = e.getURL().getProtocol(); + int index = url.getPort(); + gotoIndex(index + 1); + } + } + + private void gotoIndex(int index) { + MoveStack stack = gameUIManager.getGameManager().getMoveStack(); + int currentIndex = stack.getIndex(); + if (index > currentIndex) { // move forward + if (index != currentIndex +1) { + stack.gotoIndex(index - 1); + } + GameAction action = new GameAction(GameAction.REDO); + gameUIManager.processOnServer(action); + } else if (index < currentIndex) { // move backward + if (index != currentIndex - 1) { + stack.gotoIndex(index + 1); + } + GameAction action = new GameAction(GameAction.FORCED_UNDO); + gameUIManager.processOnServer(action); + } + } + +} Property changes on: trunk/18xx/rails/ui/swing/ReportWindowDynamic.java ___________________________________________________________________ Added: svn:mime-type + text/plain Modified: trunk/18xx/rails/ui/swing/StatusWindow.java =================================================================== --- trunk/18xx/rails/ui/swing/StatusWindow.java 2010-08-10 21:52:19 UTC (rev 1377) +++ trunk/18xx/rails/ui/swing/StatusWindow.java 2010-08-10 23:08:08 UTC (rev 1378) @@ -602,6 +602,7 @@ System.exit(0); } else if (command.equals(REPORT_CMD)) { gameUIManager.reportWindow.setVisible(((JMenuItem) actor.getSource()).isSelected()); + gameUIManager.reportWindow.scrollDown(); return; } else if (command.equals(MARKET_CMD)) { gameUIManager.stockChart.setVisible(((JMenuItem) actor.getSource()).isSelected()); This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |