diff --git a/platform/lang-impl/src/com/intellij/codeEditor/printing/PrintManager.java b/platform/lang-impl/src/com/intellij/codeEditor/printing/PrintManager.java index 72e30785d0c2..2585d91728c3 100644 --- a/platform/lang-impl/src/com/intellij/codeEditor/printing/PrintManager.java +++ b/platform/lang-impl/src/com/intellij/codeEditor/printing/PrintManager.java @@ -114,26 +114,8 @@ class PrintManager { painter = new MultiFilePainter(filesList); } - Pageable document = new Pageable() { - @Override - public int getNumberOfPages() { - return Pageable.UNKNOWN_NUMBER_OF_PAGES; - } - - @Override - public PageFormat getPageFormat(int pageIndex) throws IndexOutOfBoundsException { - return pageFormat; - } - - @Override - public Printable getPrintable(int pageIndex) throws IndexOutOfBoundsException { - return painter; - } - }; - final PrinterJob printerJob = PrinterJob.getPrinterJob(); try { - printerJob.setPageable(document); printerJob.setPrintable(painter, pageFormat); if (!printerJob.printDialog()) { return; @@ -230,7 +212,8 @@ class PrintManager { if (doc == null) return null; EditorHighlighter highlighter = HighlighterFactory.createHighlighter(psiFile.getProject(), virtualFile); highlighter.setText(doc.getCharsSequence()); - return new TextPainter(doc, highlighter, virtualFile.getPresentableUrl(), psiFile, psiFile.getFileType(), editor); + return new TextPainter(doc, highlighter, virtualFile.getPresentableUrl(), virtualFile.getPresentableName(), + psiFile, psiFile.getFileType(), editor); } private static TextPainter initTextPainter(@NotNull final DocumentEx doc, final Project project) { @@ -249,6 +232,6 @@ class PrintManager { private static TextPainter doInitTextPainter(@NotNull final DocumentEx doc, Project project) { EditorHighlighter highlighter = HighlighterFactory.createHighlighter(project, "unknown"); highlighter.setText(doc.getCharsSequence()); - return new TextPainter(doc, highlighter, "unknown", project, FileTypes.PLAIN_TEXT, null); + return new TextPainter(doc, highlighter, "unknown", "unknown", project, FileTypes.PLAIN_TEXT, null); } } diff --git a/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java b/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java index 22a420b725a8..7c84f5e637aa 100644 --- a/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java +++ b/platform/lang-impl/src/com/intellij/codeEditor/printing/TextPainter.java @@ -28,6 +28,7 @@ import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; @@ -45,6 +46,8 @@ import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.print.PageFormat; import java.awt.print.PrinterException; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; class TextPainter extends BasePainter { @@ -62,32 +65,48 @@ class TextPainter extends BasePainter { private final Font myHeaderFont; private final EditorHighlighter myHighlighter; private final PrintSettings myPrintSettings; - private final String myFileName; + private final String myFullFileName; + private final String myShortFileName; private int myPageIndex; + private int myNumberOfPages = -1; private int mySegmentEnd; private final LineMarkerInfo[] myMethodSeparators; private int myCurrentMethodSeparator; private final CodeStyleSettings myCodeStyleSettings; private final FileType myFileType; + private boolean myPerformActualDrawing; + + private final String myPrintDate; + private final String myPrintTime; @NonNls private static final String DEFAULT_MEASURE_HEIGHT_TEXT = "A"; @NonNls private static final String DEFAULT_MEASURE_WIDTH_TEXT = "w"; + @NonNls private static final String HEADER_TOKEN_PAGE = "PAGE"; + @NonNls private static final String HEADER_TOKEN_TOTALPAGES = "TOTALPAGES"; @NonNls private static final String HEADER_TOKEN_FILE = "FILE"; + @NonNls private static final String HEADER_TOKEN_FILENAME = "FILENAME"; + @NonNls private static final String HEADER_TOKEN_DATE = "DATE"; + @NonNls private static final String HEADER_TOKEN_TIME = "TIME"; + + @NonNls private static final String DATE_FORMAT = "yyyy-MM-dd"; + @NonNls private static final String TIME_FORMAT = "HH:mm:ss"; public TextPainter(@NotNull DocumentEx editorDocument, EditorHighlighter highlighter, - String fileName, + String fullFileName, + String shortFileName, @NotNull PsiFile psiFile, FileType fileType, Editor editor) { - this(editorDocument, highlighter, fileName, psiFile.getProject(), fileType, + this(editorDocument, highlighter, fullFileName, shortFileName, psiFile.getProject(), fileType, FileSeparatorProvider.getInstance().getFileSeparators(psiFile, editorDocument, editor)); } public TextPainter(@NotNull DocumentEx editorDocument, EditorHighlighter highlighter, - String fileName, + String fullFileName, + String shortFileName, Project project, FileType fileType, List separators) { @@ -102,18 +121,26 @@ class TextPainter extends BasePainter { myBoldItalicFont = new Font(fontName, Font.BOLD | Font.ITALIC, fontSize); myHighlighter = highlighter; myHeaderFont = new Font(myPrintSettings.FOOTER_HEADER_FONT_NAME, Font.PLAIN, myPrintSettings.FOOTER_HEADER_FONT_SIZE); - myFileName = fileName; + myFullFileName = fullFileName; + myShortFileName = shortFileName; myRangeToPrint = editorDocument.createRangeMarker(0, myDocument.getTextLength()); myFileType = fileType; myMethodSeparators = separators != null ? separators.toArray(new LineMarkerInfo[separators.size()]) : new LineMarkerInfo[0]; myCurrentMethodSeparator = 0; + Date date = new Date(); + myPrintDate = new SimpleDateFormat(DATE_FORMAT).format(date); + myPrintTime = new SimpleDateFormat(TIME_FORMAT).format(date); } public void setSegment(int segmentStart, int segmentEnd) { + setSegment(myDocument.createRangeMarker(segmentStart, segmentEnd)); + } + + private void setSegment(RangeMarker marker) { if (myRangeToPrint != null) { myRangeToPrint.dispose(); } - myRangeToPrint = myDocument.createRangeMarker(segmentStart, segmentEnd); + myRangeToPrint = marker; } private float getLineHeight(Graphics g) { @@ -151,42 +178,104 @@ class TextPainter extends BasePainter { @Override public int print(final Graphics g, final PageFormat pageFormat, final int pageIndex) throws PrinterException { + if (myProgress.isCanceled()) { + return NO_SUCH_PAGE; + } + + final Graphics2D g2d = (Graphics2D)g; + + if (myNumberOfPages < 0) { + myProgress.setText(CodeEditorBundle.message("print.file.calculating.number.of.pages.progress")); + + myPerformActualDrawing = false; + + if (!calculateNumberOfPages(g2d, pageFormat)) { + return NO_SUCH_PAGE; + } + } + + myPerformActualDrawing = true; + return ApplicationManager.getApplication().runReadAction(new Computable() { @Override public Integer compute() { - if (myProgress.isCanceled() || myRangeToPrint == null || !myRangeToPrint.isValid()) { + if (!isValidRange(myRangeToPrint)) { return NO_SUCH_PAGE; } - int startOffset = myRangeToPrint.getStartOffset(); - myOffset = startOffset; - mySegmentEnd = myRangeToPrint.getEndOffset(); - myLineNumber = myDocument.getLineNumber(myOffset) + 1; - if (myOffset >= mySegmentEnd) { - return NO_SUCH_PAGE; - } isPrintingPass = !isPrintingPass; if (!isPrintingPass) { return PAGE_EXISTS; } - myProgress.setText(CodeEditorBundle.message("print.file.page.progress", myFileName, (pageIndex + 1))); + myProgress.setText(CodeEditorBundle.message("print.file.page.progress", myShortFileName, (pageIndex + 1), myNumberOfPages)); myPageIndex = pageIndex; - Graphics2D g2D = (Graphics2D) g; - Rectangle2D.Double clip = new Rectangle2D.Double(pageFormat.getImageableX(), pageFormat.getImageableY(), - pageFormat.getImageableWidth(), - pageFormat.getImageableHeight()); - draw(g2D, clip); + RangeMarker newRange = printPage(g2d, pageFormat, myRangeToPrint); + setSegment(newRange); - myRangeToPrint.dispose(); - // stop printing if there was no progress (to avoid an infinite loop) or if the whole range was processed - myRangeToPrint = myOffset > startOffset && myOffset < mySegmentEnd ? myDocument.createRangeMarker(myOffset, mySegmentEnd) : null; return PAGE_EXISTS; } }); } + private boolean calculateNumberOfPages(final Graphics2D g2d, final PageFormat pageFormat) { + myNumberOfPages = 0; + final Ref firstPage = new Ref(Boolean.TRUE); + final Ref tmpMarker = new Ref(); + while (ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public Boolean compute() { + if (firstPage.get()) { + if (!isValidRange(myRangeToPrint)) { + return false; + } + tmpMarker.set(myDocument.createRangeMarker(myRangeToPrint.getStartOffset(), myRangeToPrint.getEndOffset())); + firstPage.set(Boolean.FALSE); + } + RangeMarker range = tmpMarker.get(); + if (!isValidRange(range)) { + return false; + } + tmpMarker.set(printPage(g2d, pageFormat, range)); + range.dispose(); + return true; + } + })) { + if (myProgress.isCanceled()) { + return false; + } + myNumberOfPages++; + } + if (!tmpMarker.isNull()) { + tmpMarker.get().dispose(); + } + return true; + } + + private static boolean isValidRange(RangeMarker range) { + return range != null && range.isValid() && range.getStartOffset() < range.getEndOffset(); + } + + /** + * Prints a pageful of text from a given range. Return a remaining range to print, or null if there's nothing left. + */ + private RangeMarker printPage(Graphics2D g2d, PageFormat pageFormat, RangeMarker range) { + assert isValidRange(range); + int startOffset = range.getStartOffset(); + int endOffset = range.getEndOffset(); + + myOffset = startOffset; + mySegmentEnd = endOffset; + myLineNumber = myDocument.getLineNumber(myOffset) + 1; + Rectangle2D.Double clip = new Rectangle2D.Double(pageFormat.getImageableX(), pageFormat.getImageableY(), + pageFormat.getImageableWidth(), pageFormat.getImageableHeight()); + + draw(g2d, clip); + + return myOffset > startOffset && myOffset < endOffset ? myDocument.createRangeMarker(myOffset, endOffset) : null; + } + private void draw(Graphics2D g2D, Rectangle2D.Double clip) { double headerHeight = drawHeader(g2D, clip); clip.y += headerHeight; @@ -209,7 +298,7 @@ class TextPainter extends BasePainter { } private void drawBorder(Graphics2D g, Rectangle2D clip) { - if (myPrintSettings.DRAW_BORDER) { + if (myPrintSettings.DRAW_BORDER && myPerformActualDrawing) { Color save = g.getColor(); g.setColor(Color.black); g.draw(clip); @@ -270,10 +359,8 @@ class TextPainter extends BasePainter { Point2D position = new Point2D.Double(0, clip.getY()); double lineY = position.getY(); - while (myCurrentMethodSeparator < myMethodSeparators.length) { - LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator]; - if (marker != null && marker.startOffset >= lIterator.getEnd()) break; - myCurrentMethodSeparator++; + if (myPerformActualDrawing) { + setInitialMethodSeparatorIndex(lIterator.getEnd()); } while (!hIterator.atEnd() && !lIterator.atEnd()) { @@ -290,14 +377,13 @@ class TextPainter extends BasePainter { lIterator.advance(); myLineNumber++; - if (myCurrentMethodSeparator < myMethodSeparators.length) { - LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator]; - if (marker != null && marker.startOffset < lEnd) { + if (myPerformActualDrawing) { + LineMarkerInfo marker = getMethodSeparator(lEnd); + if (marker != null) { Color save = g.getColor(); setForegroundColor(g, marker.separatorColor); UIUtil.drawLine(g, 0, (int)lineY, (int)clip.getWidth(), (int)lineY); setForegroundColor(g, save); - myCurrentMethodSeparator++; } } @@ -343,6 +429,25 @@ class TextPainter extends BasePainter { g.translate(-clip.getX(), 0); } + + private void setInitialMethodSeparatorIndex(int initialOffset) { + while (myCurrentMethodSeparator < myMethodSeparators.length) { + LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator]; + if (marker != null && marker.startOffset >= initialOffset) break; + myCurrentMethodSeparator++; + } + } + + private LineMarkerInfo getMethodSeparator(int currentOffset) { + if (myCurrentMethodSeparator < myMethodSeparators.length) { + LineMarkerInfo marker = myMethodSeparators[myCurrentMethodSeparator]; + if (marker != null && marker.startOffset < currentOffset) { + myCurrentMethodSeparator++; + return marker; + } + } + return null; + } private double drawHeader(Graphics2D g, Rectangle2D clip) { LineMetrics lineMetrics = getHeaderFooterLineMetrics(g); @@ -407,21 +512,23 @@ class TextPainter extends BasePainter { private double drawHeaderOrFooterLine(Graphics2D g, double x, double y, double w, String headerText, String alignment) { - headerText = convertHeaderText(headerText); - g.setFont(myHeaderFont); - g.setColor(Color.black); FontRenderContext fontRenderContext = g.getFontRenderContext(); LineMetrics lineMetrics = getHeaderFooterLineMetrics(g); float lineHeight = lineMetrics.getHeight(); - float descent = lineMetrics.getDescent(); - double width = myHeaderFont.getStringBounds(headerText, fontRenderContext).getWidth() + getCharWidth(g); - float yPos = (float) (lineHeight - descent + y); - if (PrintSettings.LEFT.equals(alignment)) { - drawStringToGraphics(g, headerText, x, yPos); - } else if (PrintSettings.CENTER.equals(alignment)) { - drawStringToGraphics(g, headerText, (float) (x + (w - width) / 2), yPos); - } else if (PrintSettings.RIGHT.equals(alignment)) { - drawStringToGraphics(g, headerText, (float) (x + w - width), yPos); + if (myPerformActualDrawing) { + headerText = convertHeaderText(headerText); + g.setFont(myHeaderFont); + g.setColor(Color.black); + float descent = lineMetrics.getDescent(); + double width = myHeaderFont.getStringBounds(headerText, fontRenderContext).getWidth() + getCharWidth(g); + float yPos = (float) (lineHeight - descent + y); + if (PrintSettings.LEFT.equals(alignment)) { + drawStringToGraphics(g, headerText, x, yPos); + } else if (PrintSettings.CENTER.equals(alignment)) { + drawStringToGraphics(g, headerText, (float) (x + (w - width) / 2), yPos); + } else if (PrintSettings.RIGHT.equals(alignment)) { + drawStringToGraphics(g, headerText, (float) (x + w - width), yPos); + } } return lineHeight; } @@ -437,8 +544,16 @@ class TextPainter extends BasePainter { if (isExpression) { if (HEADER_TOKEN_PAGE.equals(token)) { result.append(myPageIndex + 1); + } else if (HEADER_TOKEN_TOTALPAGES.equals(token)) { + result.append(myNumberOfPages); } else if (HEADER_TOKEN_FILE.equals(token)) { - result.append(myFileName); + result.append(myFullFileName); + } else if (HEADER_TOKEN_FILENAME.equals(token)) { + result.append(myShortFileName); + } else if (HEADER_TOKEN_DATE.equals(token)) { + result.append(myPrintDate); + } else if (HEADER_TOKEN_TIME.equals(token)) { + result.append(myPrintTime); } } else { result.append(token); @@ -475,7 +590,7 @@ class TextPainter extends BasePainter { } private void drawLineNumber(Graphics2D g, double x, double y) { - if (!myPrintSettings.PRINT_LINE_NUMBERS) { + if (!myPrintSettings.PRINT_LINE_NUMBERS || !myPerformActualDrawing) { return; } FontRenderContext fontRenderContext = (g).getFontRenderContext(); @@ -538,7 +653,7 @@ class TextPainter extends BasePainter { double xStart = position.getX(); double x = position.getX(); double y = getLineHeight(g) - getDescent(g) + position.getY(); - if (backColor != null) { + if (backColor != null && myPerformActualDrawing) { Color savedColor = g.getColor(); setBackgroundColor(g, backColor); double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g); @@ -567,7 +682,7 @@ class TextPainter extends BasePainter { x += drawStringToGraphics(g, s, x, y); } - if (underscoredColor != null) { + if (underscoredColor != null && myPerformActualDrawing) { Color savedColor = g.getColor(); setForegroundColor(g, underscoredColor); double w = getTextSegmentWidth(text, myOffset, length, position.getX(), g); @@ -581,13 +696,17 @@ class TextPainter extends BasePainter { private double drawStringToGraphics(Graphics2D g, String s, double x, double y) { if (!myPrintSettings.PRINT_AS_GRAPHICS) { - g.drawString(s, (float) x, (float) y); + if (myPerformActualDrawing) { + g.drawString(s, (float)x, (float)y); + } return g.getFontMetrics().stringWidth(s); } else { GlyphVector v = g.getFont().createGlyphVector(g.getFontRenderContext(), s); - g.translate(x, y); - g.fill(v.getOutline()); - g.translate(-x, -y); + if (myPerformActualDrawing) { + g.translate(x, y); + g.fill(v.getOutline()); + g.translate(-x, -y); + } return v.getLogicalBounds().getWidth(); } @@ -635,9 +754,6 @@ class TextPainter extends BasePainter { @Override void dispose() { - if (myRangeToPrint != null) { - myRangeToPrint.dispose(); - myRangeToPrint = null; - } + setSegment(null); } } diff --git a/platform/platform-resources-en/src/messages/CodeEditorBundle.properties b/platform/platform-resources-en/src/messages/CodeEditorBundle.properties index 82a29b7c5435..a1c47c6de185 100644 --- a/platform/platform-resources-en/src/messages/CodeEditorBundle.properties +++ b/platform/platform-resources-en/src/messages/CodeEditorBundle.properties @@ -52,11 +52,12 @@ print.header.alignment.combobox=Alignment print.apply.button=A&pply print.progress=Printing... print.header.default.line.1=File - $FILE$ -print.header.default.line.2=Page $PAGE$ +print.header.default.line.2=Page $PAGE$ of $TOTALPAGES$ print.header.placement.header=Header print.header.placement.footer=Footer print.header.alignment.left=Left print.header.alignment.center=Center print.header.alignment.right=Right -print.file.page.progress=Printing {0}. Page {1}... +print.file.calculating.number.of.pages.progress=Calculating number of pages... +print.file.page.progress=Printing {0}. Page {1} of {2} file.not.found=File not found: {0}