/* * $Id: Window6Impl.java 535 2008-02-19 06:02:50Z weiju $ * * Created on 2006/02/23 * Copyright 2005-2008 by Wei-ju Wu * This file is part of The Z-machine Preservation Project (ZMPP). * * ZMPP is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZMPP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZMPP. If not, see <http://www.gnu.org/licenses/>. */ package org.zmpp.swingui; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import org.zmpp.blorb.BlorbImage; import org.zmpp.vm.StoryFileHeader; import org.zmpp.vm.TextCursor; import org.zmpp.vm.Window6; /** * Windows area the most important aspect in the V6 screen model, all output is * done in respect to them. * * @author Wei-ju Wu * @version 1.0 */ public class Window6Impl implements Window6, CursorWindow { private TextCursor cursor; private FontFactory fontFactory; private int interruptRoutine; private int interruptCount; private int linecount; private int background; private int foreground; private ScreenFont font; private Viewport6 viewport; private WindowArea area; private WindowStyle style; private int windownum; private StringBuilder streambuffer; /** * Constructor. * * @param viewport the viewport containing the window * @param fontFactory the font factory * @param num the window number */ public Window6Impl(Viewport6 viewport, FontFactory fontFactory, int num) { this.viewport = viewport; this.fontFactory = fontFactory; this.windownum = num; this.streambuffer = new StringBuilder(); cursor = new TextCursorImpl(this); area = new WindowArea(); style = new WindowStyle(); } /** * Returns this window's cursor object. * * @return the cursor */ public TextCursor getCursor() { return cursor; } /** * Displays or hides the cursor. * * @param show if true, the cursor is shown, otherwise it is hidden */ public void displayCursor(boolean show) { Color color = show ? getForegroundColor() : getBackgroundColor(); getCanvas().fillRect(color, getCurrentX(), getCurrentY() - getCanvas().getFontAscent(getFont()), getFontWidth(), getFontHeight()); } /** * {@inheritDoc} */ public void drawPicture(BlorbImage picture, int y, int x) { Dimension size = picture.getSize(viewport.getWidth(), viewport.getHeight()); getCanvas().drawImage(picture.getImage(), area.getStartX() + (x - 1), area.getStartY() + (y - 1), size.width, size.height); } /** * {@inheritDoc} */ public void erasePicture(BlorbImage picture, int y, int x) { Dimension size = picture.getSize(viewport.getWidth(), viewport.getHeight()); getCanvas().fillRect(getBackgroundColor(), area.getStartX() + (x - 1), area.getStartY() + (y - 1), size.width, size.height); } /** * {@inheritDoc} */ public void move(int y, int x) { //System.out.printf("@MOVE_WINDOW win: %d x: %d y: %d\n", windownum, x, y); area.setPosition(x, y); } /** * Sets the buffer mode of the window. * * @param flag the buffer mode flag */ public void setBufferMode(boolean flag) { style.setIsWrapped(flag); } /** * {@inheritDoc} */ public void setSize(int height, int width) { //System.out.printf("win %d: setSize(): w: %d h: %d\n", windownum, width, height); area.setSize(width, height); } /** * {@inheritDoc} */ public void setStyle(int styleflags, int operation) { //System.out.printf("win %d: setStyle(): %d %d\n", windownum, styleflags, operation); style.setFlags(styleflags, operation); } /** * {@inheritDoc} */ public void setMargins(int left, int right) { //System.out.printf("setMargins(): %d %d\n", left, right); area.setMargins(left, right); } /** * {@inheritDoc} */ public int getProperty(int propertynum) { int result = 0; switch (propertynum) { case Window6.PROPERTY_Y_COORD: case Window6.PROPERTY_X_COORD: case Window6.PROPERTY_Y_SIZE: case Window6.PROPERTY_X_SIZE: case Window6.PROPERTY_LEFT_MARGIN: case Window6.PROPERTY_RIGHT_MARGIN: result = area.getProperty(propertynum); break; case Window6.PROPERTY_X_CURSOR: result = cursor.getColumn(); break; case Window6.PROPERTY_Y_CURSOR: result = cursor.getLine(); break; case Window6.PROPERTY_INTERRUPT_COUNT: result = interruptCount; break; case Window6.PROPERTY_INTERRUPT_ROUTINE: result = interruptRoutine; break; case Window6.PROPERTY_FONT_NUMBER: result = font.getNumber(); break; case Window6.PROPERTY_TEXTSTYLE: result = font.getStyle(); break; case Window6.PROPERTY_COLOURDATA: result = getColorData(); break; case Window6.PROPERTY_FONT_SIZE: result = getFontSize(); break; case Window6.PROPERTY_ATTRIBUTES: result = style.getFlags(); break; case Window6.PROPERTY_LINE_COUNT: result = linecount; break; default: break; } System.out.printf("getProperty(), win: %d, prop: %s, value: %d\n", windownum, getPropertyName(propertynum), result); return result; } private String getPropertyName(int num) { switch (num) { case Window6.PROPERTY_Y_COORD: return "y_coord"; case Window6.PROPERTY_X_COORD: return "x_coord"; case Window6.PROPERTY_Y_SIZE: return "y_size"; case Window6.PROPERTY_X_SIZE: return "x_size"; case Window6.PROPERTY_Y_CURSOR: return "y_cursor"; case Window6.PROPERTY_X_CURSOR: return "x_cursor"; case Window6.PROPERTY_LEFT_MARGIN: return "l_margin"; case Window6.PROPERTY_RIGHT_MARGIN: return "r_margin"; case Window6.PROPERTY_INTERRUPT_COUNT: return "interrupt_count"; case Window6.PROPERTY_INTERRUPT_ROUTINE: return "interrupt_routine"; case Window6.PROPERTY_FONT_NUMBER: return "fontnum"; case Window6.PROPERTY_TEXTSTYLE: return "textstyle"; case Window6.PROPERTY_COLOURDATA: return "colordata"; case Window6.PROPERTY_FONT_SIZE: return "fontsize"; case Window6.PROPERTY_ATTRIBUTES: return "attributes"; case Window6.PROPERTY_LINE_COUNT: return "linecount"; } return ""; } /** * {@inheritDoc} */ public void putProperty(int propertynum, short value) { //System.out.printf("putProperty() win: %d prop: %s value: %d\n", // windownum, getPropertyName(propertynum), value); // this method mainly is to set the interrupt function setup, all // other properties are not supported switch (propertynum) { case Window6.PROPERTY_INTERRUPT_COUNT: interruptCount = value; break; case Window6.PROPERTY_INTERRUPT_ROUTINE: interruptRoutine = value; break; case Window6.PROPERTY_LINE_COUNT: linecount = value; break; default: System.out.println("unsupported property to set: " + propertynum); } } /** * Returns the font size, the high byte contains the height, the low byte * the width. * * @return the font size */ private int getFontSize() { return ((getFontHeight() << 8 & 0xff00) | (getFontWidth() & 0xff)); } private int getFontHeight() { StoryFileHeader fileheader = viewport.getMachine().getGameData().getStoryFileHeader(); return fileheader.getFontHeight(); } private int getFontWidth() { StoryFileHeader fileheader = viewport.getMachine().getGameData().getStoryFileHeader(); return fileheader.getFontWidth(); } /** * Packs the current color numbers into an 16-bit integer value, background * color in the hi-byte, foreground in the low-byte. * * @return the color data */ private int getColorData() { return ((background << 8 & 0xff00) | (foreground & 0xff)); } /** * Sets the new background color. * * @param colornum the color number */ public void setBackground(int colornum) { if (colornum != ColorTranslator.COLOR_CURRENT) { background = colornum; } } /** * Sets the new foreground color. * * @param colornum the color number */ public void setForeground(int colornum) { if (colornum != ColorTranslator.COLOR_CURRENT) { foreground = colornum; } } /** * Sets the font in the current window. * * @param fontnum the new font number * @return the new font number */ public int setFont(int fontnum) { ScreenFont newfont = fontFactory.getFont(fontnum); if (newfont == null) { return font.getNumber(); } else { font = newfont; return font.getNumber(); } } /** * Sets a new text style. * * @param style the text style to set */ public void setTextStyle(int style) { font = fontFactory.getTextStyle(font, style); } /** * Perform an erase operation on the current line. If the specified number * of pixels is 1, the line will be erased from the current position to the * end of the window * * @param value the number of pixels */ public void eraseLine(int value) { //System.out.printf("@erase_line, win: %d value: %d\n", windownum, value); Canvas canvas = getCanvas(); if (value == 1) { // erase from the current x position to the end of the line in the // current window // Note: This could be moved to the canvas itself int currentX = getCurrentX(); area.clip(canvas); canvas.fillRect(getBackgroundColor(), currentX, getCurrentY() - canvas.getFontAscent(font.getFont()), area.getOutputWidth() - currentX, getFontHeight()); } else { // erase an area of value pixels right from the current position area.clip(canvas); canvas.fillRect(getBackgroundColor(), getCurrentX(), getCurrentY() - canvas.getFontAscent(font.getFont()), value, getFontHeight()); } } /** * Returns the area height in pixels. * * @return the area height in pixels */ public int getHeight() { return area.getHeight(); } /** * Resizes the windows to display the given numbe of lines. * * @param lines the number of lines */ public void resize(int lines) { //System.out.printf("resize(), win: %d, lines: %d\n", windownum, lines); int height = getCanvas().getFontHeight(getFont()) * lines; area.setPosition(1, 1); area.setSize(getCanvas().getWidth(), height); cursor.setPosition(1, 1); // XXX This is arbitrarily set by me } /** * Sets the vertical bounds of a window, leaving the left position and width * where they are. * * @param top the top position * @param height the height of the window */ public void setVerticalBounds(int top, int height) { //System.out.printf("setVerticalBounds(), win: %d, top: %d height: %d\n", windownum, top, height); area.setPosition(1, top); area.setSize(getCanvas().getWidth(), height); cursor.setPosition(1, 1); // XXX This is arbitrarily set by me } // ************************************************************************ // ****** CursorWindow interface // *************************************** /** * {@inheritDoc} */ public void printChar(char c, boolean isInput) { if (isZorkZeroSpecial()) { printCharZorkZeroSpecial(c, isInput); } else { printCharStandard(c, isInput); } } /** * {@inheritDoc} */ public void updateCursorCoordinates() { // the cursor in V6 is pixel-based, so there is no need to do anything // here } /** * {@inheritDoc} */ public void flushBuffer() { // save some unnecessary flushes if (streambuffer.length() > 0) { printString(streambuffer.toString()); streambuffer = new StringBuilder(); } } /** * {@inheritDoc} */ public void backspace(char previousChar) { Canvas canvas = getCanvas(); int charWidth = canvas.getCharWidth(getFont(), previousChar); // Clears the text under the cursor canvas.fillRect(getBackgroundColor(), getCurrentX() - charWidth, getCurrentY() - canvas.getFontAscent(getFont()), charWidth, canvas.getFontHeight(getFont())); cursor.setColumn(cursor.getColumn() - charWidth); } /** * {@inheritDoc} */ public void clear() { area.fill(getCanvas(), getBackgroundColor()); resetCursorToHome(); } public void scroll(int pixels) { //System.out.println("@scroll_window: " + pixels); getCanvas().scroll(this.getBackgroundColor(), area.getStartX(), area.getStartY(), area.getWidth(), area.getHeight(), pixels); } // ************************************************************************ // ****** Private methods // *************************************** private Canvas getCanvas() { return viewport.getCanvas(); } protected Font getFont() { return font.getFont(); } private Color getBackgroundColor() { if (background == ColorTranslator.COLOR_UNDER_CURSOR) { return getCanvas().getColorAtPixel(getCurrentX(), getCurrentY()); } return ColorTranslator.getInstance().translate( background, viewport.getDefaultBackground()); } private Color getForegroundColor() { if (foreground == ColorTranslator.COLOR_UNDER_CURSOR) { return getCanvas().getColorAtPixel(getCurrentX(), getCurrentY()); } return ColorTranslator.getInstance().translate( foreground, viewport.getDefaultForeground()); } private Color getTextBackground() { return font.isReverseVideo() ? getForegroundColor() : getBackgroundColor(); } private Color getTextColor() { return font.isReverseVideo() ? getBackgroundColor() : getForegroundColor(); } // *********************************************************************** // ****** Coordinate calculation // ****** In V6, the units are in pixels, so the cursor column position is // ****** actually also the x position // **************************************************** private int getCurrentX() { //System.out.println("window: " + windownum + ", currentX: " + (area.getMarginLeft() + (cursor.getColumn() - 1))); return area.getStartX() + (cursor.getColumn() - 1); //return cursor.getColumn() - 1; } private int getCurrentY() { // Current y always returns the baseline for the current line // which means it is the cursor-y - (fontheight - fontdescent) Font font = getFont(); Canvas canvas = getCanvas(); //int y = area.getStartY() + (cursor.getLine() - 1) // + (canvas.getFontHeight(font) - canvas.getFontDescent(font)) ; //System.out.println("window: " + windownum + ", currentY: " + y); return area.getStartY() + (cursor.getLine() - 1) + (getFontHeight() - canvas.getFontDescent(font)); } private void resetCursorToHome() { getCursor().setPosition(1, 1); } private void scrollIfNeeded() { int fontDescent = getCanvas().getFontDescent(getFont()); area.clip(getCanvas()); // We calulate an available height with a correction amount // of fontDescent to reserve enough scrolling space while (getCurrentY() > (area.getStartY() + area.getHeight() - fontDescent)) { getCanvas().scroll(getBackgroundColor(), area.getStartX(), area.getStartY(), area.getWidth(), area.getHeight(), getFontHeight()); getCursor().setLine(getCursor().getLine() - getFontHeight()); } } // ********************************************************************** // ****** Printing methods, standard // ********************************************** private void printCharStandard(char c, boolean isInput) { if (isNewLineInterrupt(c)) { callNewLineInterrupt(); } else { if (isInput || !style.outputIsBuffered()) { printString(String.valueOf(c)); } else { streambuffer.append(c); } } } private void printString(String str) { int lineLength = area.getOutputWidth(); //System.out.printf("printString(): '%s' window: %d linelength: %d wrapped: %b buffered: %b x: %d\n", // createDebugString(str), windownum, lineLength, style.isWrapped(), style.outputIsBuffered(), getCurrentX()); WordWrapper wordWrapper = new WordWrapper(lineLength, getCanvas(), getFont(), style.isWrapped()); String[] lines = wordWrapper.wrap(getCurrentX(), str); printLines(lines); } private void printLines(String lines[]) { Color textColor = getTextColor(); Color textbackColor = getTextBackground(); // This is a feature that is not specified, but it is supported by // DOS Frotz if ((getFont().getStyle() & Font.BOLD) > 0 && !font.isReverseVideo()) { textColor = textColor.brighter(); } for (int i = 0; i < lines.length; i++) { String line = lines[i]; printLine(line, textbackColor, textColor); if (endsWithNewLine(line)) { newline(); } } } private void printLine(String line, Color textbackColor, Color textColor) { Canvas canvas = getCanvas(); area.clip(canvas); canvas.fillRect(textbackColor, getCurrentX(), getCurrentY() - getFontHeight() + canvas.getFontDescent(getFont()), canvas.getStringWidth(getFont(), line), getFontHeight()); scrollIfNeeded(); //System.out.printf("printLine win: %d (%d, %d) str: [%s], x: %d y: %d fg: %d bg: %d\n", // windownum, area.getStartX(), area.getStartY(), // line, getCurrentX(), getCurrentY(), foreground, background); canvas.drawString(textColor, getFont(), getCurrentX(), getCurrentY(), line); cursor.setColumn(cursor.getColumn() + canvas.getStringWidth(getFont(), line)); } private static boolean endsWithNewLine(String str) { return str.length() > 0 && str.charAt(str.length() - 1) == '\n'; } private void newline() { cursor.setLine(cursor.getLine() + getFontHeight()); cursor.setColumn(1); // Cursor position is relative to window } /** * Checks the condition for a newline interrupt. * * @param c the character to check * @return true if a newline interrupt should be called */ private boolean isNewLineInterrupt(char c) { return c == '\n' && interruptCount > 0; } /** * Calls the newline interrupt. */ private void callNewLineInterrupt() { linecount--; //System.out.println("line count is now: " + linecount); if (linecount <= 0) { linecount = 0; //System.out.println("calling interrupt"); viewport.getMachine().getCpu().callInterrupt(interruptRoutine); interruptCount--; //System.out.println("interrupt count is now: " + interruptCount); } } // ********************************************************************** // ****** Printing methods, Zork Zero special // ****** Since printing is so special, it should be // ****** outfactored in a pluggable object that is given to // ****** the window on start up // ********************************************** private boolean isZorkZeroSpecial() { StoryFileHeader fileheader = viewport.getMachine().getGameData().getStoryFileHeader(); return (fileheader.getInterpreterNumber() == 6 && fileheader.getRelease() == 393 && "890714".equals(fileheader.getSerialNumber())); } private void printCharZorkZeroSpecial(char c, boolean isInput) { if (isNewLineInterrupt(c)) { //System.out.printf("win: %d left: %d startx: %d\n", windownum, // area.getLeft(), area.getStartX()); newline(); callNewLineInterrupt(); } else { // Handle the special case of // release 393.890714 and Interpreter version 6 if (isInput || !style.outputIsBuffered()) { printString(String.valueOf(c)); } else { streambuffer.append(c); } } } }