001: // Copyright (c) 2000, 2005 BlueJ Group, Deakin University
002: //
003: // This software is made available under the terms of the "MIT License"
004: // A copy of this license is included with this source distribution
005: // in "license.txt" and is also available at:
006: // http://www.opensource.org/licenses/mit-license.html
007: // Any queries should be directed to Michael Kolling mik@bluej.org
008:
009: package bluej.editor.moe;
010:
011: import java.awt.Color;
012: import java.awt.print.*;
013: import java.awt.Graphics;
014: import java.awt.Font;
015: import java.awt.FontMetrics;
016: import java.awt.print.PrinterJob;
017: import javax.swing.text.*;
018:
019: import java.util.*;
020: import java.text.DateFormat;
021:
022: import bluej.utility.Debug;
023: import bluej.utility.Utility;
024: import bluej.Config;
025:
026: /**
027: * Class to handle printing for the MoeEditor.
028: * This borrows ideas and some source code from Andrew Weiland's Print example
029: * at http://www.wam.umd.edu/~aweiland/Print.java. Which no longer exists..
030: * @author Bruce Quig
031: * @version $ Id: $
032: */
033: public class MoePrinter {
034:
035: static final String CONTINUED_LABEL = Config
036: .getString("editor.printer.continued");
037: private final int HEADER_SPACE = 30;
038: private final int FOOTER_SPACE = 20;
039: private final int PADDING = 5;
040: private final char TAB_CHAR = '\t';
041:
042: private Book pages = new Book(); // This holds each page
043:
044: private static int titleFontSize = Config.getPropInteger(
045: "bluej.fontsize.printTitle", 14);
046: private static int footerFontSize = Config.getPropInteger(
047: "bluej.fontsize.printInfo", 10);
048: private static Font titleFont = new Font("SansSerif", Font.BOLD,
049: titleFontSize);
050: private static Font smallTitleFont = new Font("SansSerif",
051: Font.BOLD, 10);
052: private static Font footerFont = new Font("SansSerif", Font.ITALIC,
053: 9);
054:
055: private String className;
056: private int tabSize = Config.getPropInteger("bluej.editor.tabsize",
057: 4);
058:
059: /**
060: * Default constructor
061: */
062: public MoePrinter() {
063: // nothing yet
064: }
065:
066: /**
067: * Prints the document. This method produces a copy of the document
068: * as a List of Strings and delegates the printing to a printText method.
069: *
070: * @returns true if it was not cancelled.
071: */
072: public boolean printDocument(PrinterJob printJob,
073: PlainDocument document, String className, Font font,
074: PageFormat format) {
075: List lines = new ArrayList();
076:
077: this .className = className;
078: // extract tabsize attribute from document and assign to tabSize attribute
079: Integer tabSizeAsInteger = (Integer) document
080: .getProperty(PlainDocument.tabSizeAttribute);
081: if (tabSizeAsInteger != null)
082: tabSize = tabSizeAsInteger.intValue();
083:
084: try {
085: // read lock the document while reading to avoid any subsequent edits
086: // unlikely to happen due to the speed at which the document is read
087: document.readLock();
088:
089: // Get Root element of the document
090: Element root = document.getDefaultRootElement();
091: //get the number of lines (i.e. child elements)
092: int count = root.getElementCount();
093: Segment segment = new Segment();
094: // Get each line element, get its text and put it in the string list
095: for (int i = 0; i < count; i++) {
096: Element lineElement = (Element) root.getElement(i);
097: try {
098: document.getText(lineElement.getStartOffset(),
099: lineElement.getEndOffset()
100: - lineElement.getStartOffset(),
101: segment);
102: lines.add(removeNewLines(segment.toString()));
103: } catch (BadLocationException ble) {
104: Debug
105: .reportError("Exception thrown accessing document text: "
106: + ble);
107: }
108: }
109: }
110: // make sure that read lock is removed
111: finally {
112: document.readUnlock();
113: }
114:
115: return printText(printJob, lines, font, format);
116: }
117:
118: /**
119: * Remove newline and carriage return characters from the end
120: * of this string. This is needed to fix a printing bug with
121: * the handling of newline characters on some printers
122: */
123: private String removeNewLines(String line) {
124: int length = line.length();
125: char lastChar = (length > 0 ? line.charAt(line.length() - 1)
126: : ' ');
127:
128: while ((lastChar == '\n') || (lastChar == '\r')) {
129:
130: line = line.substring(0, line.length() - 1);
131: length = line.length();
132: lastChar = (length > 0 ? line.charAt(line.length() - 1)
133: : ' ');
134: }
135: return line;
136: }
137:
138: /**
139: * Prints the text. It sets paper size (at present) and paginates text
140: * into a pageable Book for printing.
141: *
142: * @returns true if it was not cancelled.
143: */
144: private synchronized boolean printText(PrinterJob job, List text,
145: Font font, PageFormat format) {
146: try {
147: pages = paginateText(text, format, font);
148:
149: // set the book pageable so the printjob knows
150: // we are printing more than one page (maybe)
151: job.setPageable(pages);
152: job.print();
153:
154: return true;
155: } catch (Exception e) {
156: // should it be an error dialog?
157: Debug.reportError("Exception thrown during printing: " + e);
158: e.printStackTrace();
159: return false;
160: }
161: }
162:
163: /**
164: * The pagination method. Paginate the text onto Printable page objects.
165: * This includes wrapping long lines of text.
166: */
167: private Book paginateText(List text, PageFormat pageFormat,
168: Font font) {
169: Book book = new Book();
170: int currentLine = 0; // line I am currently reading
171: int pageNum = 1; // page #
172:
173: // height of text area of a page
174: int height = (int) pageFormat.getImageableHeight()
175: - (HEADER_SPACE + FOOTER_SPACE);
176:
177: // number of lines on a page
178: int linesPerPage = height / (font.getSize() + 2);
179: wrapLines(text, pageFormat, font);
180:
181: // set number of pages
182: int numberOfPages = ((int) (text.size() / linesPerPage)) + 1;
183:
184: List pageText; // one page of text
185:
186: ListIterator li = text.listIterator();
187: while (pageNum <= numberOfPages) {
188: pageText = new ArrayList();
189:
190: for (int lineCount = 0; li.hasNext()
191: && lineCount < linesPerPage; lineCount++) {
192: pageText.add(li.next());
193: currentLine++; //bq needed?
194: }
195:
196: // create a new page object with the text and add it to the book
197: book.append(new MoePage(pageText, font), pageFormat);
198: pageNum++; // increase the page number I am on
199: }
200: return book; // return the completed book
201: }
202:
203: /**
204: * Wraps lines so that long lines of text outside of print page dimensions for a
205: * given page format and font are printed as a new line. This method iterates
206: * through each line of text, calculates if there is an overlap and inserts
207: * overlapping text on the next line.
208: */
209: private void wrapLines(List text, PageFormat format, Font font) {
210: // code to wrap lines of text for printing
211: // get a line, get its length, do some font metrics,
212: StyleContext context = new StyleContext();
213: FontMetrics fontMetrics = context.getFontMetrics(font);
214: int maxWidth = (int) format.getImageableWidth() - (PADDING * 2);
215: int fontWidth = fontMetrics.charWidth('m');
216: int chars = maxWidth / fontWidth;
217:
218: for (ListIterator li = text.listIterator(); li.hasNext();) {
219: String currentLine = Utility.convertTabsToSpaces(
220: (String) li.next(), tabSize);
221: li.set(currentLine);
222: int currentLineLength = currentLine.length();
223: int width = fontMetrics.stringWidth(currentLine);
224:
225: // if line needs to be wrapped
226: if (width > maxWidth) {
227: int indexOfLine = li.previousIndex();
228: // remove original
229: li.remove();
230: double iterations = (currentLineLength / chars) + 1;
231: for (int begin = 0, end = 0; iterations > 0; iterations--, begin += chars) {
232: if (begin + chars < currentLineLength)
233: end = begin + chars;
234: else
235: end = currentLineLength;
236: String newSubString = currentLine.substring(begin,
237: end);
238: if (newSubString.length() != 0)
239: li.add(newSubString);
240: }
241: }
242: }
243: }
244:
245: /* An inner class that defines one page of text based
246: * on data about the PageFormat etc. from the book defined
247: * in the parent class
248: */
249: class MoePage implements Printable {
250: private List text; // the text for the page
251: private Font font;
252:
253: MoePage(List text, Font font) {
254: this .text = text; // set the page's text
255: this .font = font; // set the page's font
256: }
257:
258: /**
259: * Method that implements Printable interface.
260: *
261: */
262: public int print(Graphics g, PageFormat pageFormat,
263: int pageIndex) throws PrinterException {
264: // the printing part
265: int position;
266: g.setFont(this .font); // Set the font
267: g.setColor(Color.black); // set color
268:
269: // get co-ordinates for frame
270: int xPosition = (int) pageFormat.getImageableX();
271: int yPosition = (int) pageFormat.getImageableY();
272: int width = (int) pageFormat.getImageableWidth();
273: int height = (int) pageFormat.getImageableHeight();
274:
275: // print a header
276: printHeader(g, pageIndex, xPosition, yPosition, width,
277: HEADER_SPACE);
278:
279: // print main text area
280: int textYPosition = yPosition + HEADER_SPACE;
281: int textXPosition = xPosition + PADDING;
282: g.drawRect(xPosition, textYPosition, width, height
283: - (HEADER_SPACE + FOOTER_SPACE));
284: // print the text
285: for (ListIterator li = text.listIterator(); li.hasNext();) {
286: position = textYPosition + (this .font.getSize() + 2)
287: * (li.nextIndex() + 1);
288: String line = (String) li.next();
289: if (line.length() == 0) // workaround for strange problem on Mac:
290: line = " "; // trying to print empty lines throws exception
291: g.drawString(line, textXPosition, position);
292: }
293:
294: // print footer
295: int footerYPosition = yPosition + height - FOOTER_SPACE;
296: printFooter(g, xPosition, footerYPosition, width,
297: FOOTER_SPACE);
298:
299: return Printable.PAGE_EXISTS; // print the page
300: }
301:
302: /**
303: * Prints a header box on a page, including a title and page number.
304: */
305: private void printHeader(Graphics g, int pageIndex, int xPos,
306: int yPos, int width, int height) {
307: // draw title box
308: g.setColor(Color.lightGray);
309: g.fillRect(xPos, yPos, width, height);
310: g.setColor(Color.black); // set color
311: g.drawRect(xPos, yPos, width, height);
312: int titleYPos = yPos + HEADER_SPACE - this .font.getSize()
313: + 2;
314: Font currentFont = null;
315: String title = "Class " + className;
316:
317: // print class name on left
318: // if first page make title bigger
319: if (pageIndex == 0)
320: g.setFont(titleFont);
321: else {
322: // don't add (continued) if there is no definition
323: if (!"".equals(CONTINUED_LABEL)
324: && !"editor.printer.continued"
325: .equals(CONTINUED_LABEL))
326: title = title + " (" + CONTINUED_LABEL + ")";
327: g.setFont(smallTitleFont);
328: }
329: // print class name
330: g.drawString(title, xPos + PADDING, titleYPos);
331:
332: // set to smaller title font for page number
333: g.setFont(smallTitleFont);
334: FontMetrics pfm = g.getFontMetrics(smallTitleFont);
335: // print page number on right
336: String pageInfo = (pageIndex + 1) + "/"
337: + pages.getNumberOfPages();
338: int pageInfoX = xPos + width - PADDING
339: - pfm.stringWidth(pageInfo);
340: g.drawString(pageInfo, pageInfoX, titleYPos);
341: g.setFont(font);
342:
343: }
344:
345: /**
346: * Prints a footer box on a page that shows the print date.
347: */
348: private void printFooter(Graphics g, int xPos, int yPos,
349: int width, int height) {
350: // set up font and text position
351: g.setFont(footerFont);
352: FontMetrics pfm = g.getFontMetrics(footerFont);
353: int footerTextYPos = yPos + FOOTER_SPACE
354: - this .font.getSize() + 2;
355:
356: // package name not shown at present
357: //g.drawString("Package: " + className, xPos + PADDING, footerTextYPos);
358:
359: // print date on right
360: Date today = new Date();
361: DateFormat dateFormat = DateFormat.getDateTimeInstance();
362: String date = dateFormat.format(today);
363: int pageInfoX = xPos + width - PADDING
364: - pfm.stringWidth(date);
365: g.drawString(date, pageInfoX, footerTextYPos);
366: //set font back to original
367: g.setFont(font);
368:
369: }
370:
371: }
372: }
|