/* * Copyright 2002-2008, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Michael Pfeiffer * Hartmut Reh * julun */ #include "Preview.h" #include "GraphicsDriver.h" #include "PrintUtils.h" #include #include #include #include #include #include #include #include #include #include #include // #pragma mark - PreviewPage PreviewPage::PreviewPage(int32 page, PrintJobPage* pjp) : fPage(page) , fStatus(B_ERROR) , fNumberOfPictures(0) , fRects(NULL) , fPoints(NULL) , fPictures(NULL) { fNumberOfPictures = pjp->NumberOfPictures(); fRects = new BRect[fNumberOfPictures]; fPoints = new BPoint[fNumberOfPictures]; fPictures = new BPicture[fNumberOfPictures]; for (int32 i = 0; i < fNumberOfPictures; ++i) { fStatus = pjp->NextPicture(fPictures[i], fPoints[i], fRects[i]); if (fStatus != B_OK) break; } } PreviewPage::~PreviewPage() { delete [] fRects; delete [] fPoints; delete [] fPictures; } status_t PreviewPage::InitCheck() const { return fStatus; } void PreviewPage::Draw(BView* view, const BRect& printRect) { ASSERT(fStatus == B_OK); for (int32 i = 0; i < fNumberOfPictures; i++) view->DrawPicture(&fPictures[i], printRect.LeftTop() + fPoints[i]); } // #pragma mark - PreviewView namespace { const float kPreviewTopMargin = 10.0; const float kPreviewBottomMargin = 30.0; const float kPreviewLeftMargin = 10.0; const float kPreviewRightMargin = 30.0; // TODO share constant with JobData const char *kJDOrientation = "orientation"; const char *kJDNup = "JJJJ_nup"; const char *kJDReverse = "JJJJ_reverse"; const char* kJDPageSelection = "JJJJ_page_selection"; const uint8 ZOOM_IN[] = { 16, 1, 6, 6, 0, 0, 15, 128, 48, 96, 32, 32, 66, 16, 66, 16, 79, 144, 66, 16, 66, 16, 32, 32, 48, 112, 15, 184, 0, 28, 0, 14, 0, 6, 0, 0, 15, 128, 63, 224, 127, 240, 127, 240, 255, 248, 255, 248, 255, 248, 255, 248, 255, 248, 127, 248, 127, 248, 63, 252, 15, 254, 0, 31, 0, 15, 0, 7 }; const uint8 ZOOM_OUT[] = { 16, 1, 6, 6, 0, 0, 15, 128, 48, 96, 32, 32, 64, 16, 64, 16, 79, 144, 64, 16, 64, 16, 32, 32, 48, 112, 15, 184, 0, 28, 0, 14, 0, 6, 0, 0, 15, 128, 63, 224, 127, 240, 127, 240, 255, 248, 255, 248, 255, 248, 255, 248, 255, 248, 127, 248, 127, 248, 63, 252, 15, 254, 0, 31, 0, 15, 0, 7 }; BRect RotateRect(const BRect& rect) { return BRect(rect.top, rect.left, rect.bottom, rect.right); } BRect ScaleDown(BRect rect, float factor) { rect.left /= factor; rect.top /= factor; rect.right /= factor; rect.bottom /= factor; return rect; } BPoint CalulateOffset(int32 numberOfPagesPerPage, int32 index, JobData::Orientation orientation, BRect printableRect) { BPoint offset(0.0, 0.0); if (numberOfPagesPerPage == 1) return offset; float width = printableRect.Width(); float height = printableRect.Height(); switch (numberOfPagesPerPage) { case 2: { if (index == 1) { if (JobData::kPortrait == orientation) offset.x = width; else offset.y = height; } } break; case 8: { if (JobData::kPortrait == orientation) { offset.x = width * (index / 2); offset.y = height * (index % 2); } else { offset.x = width * (index % 2); offset.y = height * (index / 2); } } break; case 32: { if (JobData::kPortrait == orientation) { offset.x = width * (index / 4); offset.y = height * (index % 4); } else { offset.x = width * (index % 4); offset.y = height * (index / 4); } } break; case 4: { case 9: case 16: case 25: case 36: case 49: case 64: case 81: case 100: case 121: int32 value = int32(sqrt(double(numberOfPagesPerPage))); offset.x = width * (index % value); offset.y = height * (index / value); } break; } return offset; } } PreviewView::PreviewView(BFile* jobFile, BRect rect) : BView(rect, "PreviewView", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS) , fPage(0) , fZoom(0) , fReader(jobFile) , fReverse(false) , fPaperRect(BRect()) , fPrintableRect(BRect()) , fTracking(false) , fInsideView(true) , fScrollStart(0.0, 0.0) , fNumberOfPages(1) , fNumberOfPagesPerPage(1) , fCachedPage(NULL) , fOrientation(JobData::kPortrait) , fPageSelection(JobData::kAllPages) { int32 value32; if (fReader.JobSettings()->FindInt32(kJDOrientation, &value32) == B_OK) fOrientation = (JobData::Orientation)value32; if (fReader.JobSettings()->FindInt32(kJDPageSelection, &value32) == B_OK) fPageSelection = (JobData::PageSelection)value32; bool value; if (fReader.JobSettings()->FindBool(kJDReverse, &value) == B_OK) fReverse = value; if (fReader.JobSettings()->FindInt32(kJDNup, &value32) == B_OK) fNumberOfPagesPerPage = value32; fNumberOfPages = (fReader.NumberOfPages() + fNumberOfPagesPerPage - 1) / fNumberOfPagesPerPage; if (fPageSelection == JobData::kOddNumberedPages) fNumberOfPages = (fNumberOfPages + 1) / 2; else if (fPageSelection == JobData::kEvenNumberedPages) fNumberOfPages /= 2; fPaperRect = fReader.PaperRect(); fPrintableRect = fReader.PrintableRect(); switch (fNumberOfPagesPerPage) { case 2: case 8: case 32: case 128: { fPaperRect = RotateRect(fPaperRect); fPrintableRect = RotateRect(fPrintableRect); } break; } } PreviewView::~PreviewView() { delete fCachedPage; } void PreviewView::Show() { BView::Show(); be_app->SetCursor(ZOOM_IN); } void PreviewView::Hide() { be_app->SetCursor(B_HAND_CURSOR); BView::Hide(); } void PreviewView::Draw(BRect rect) { if (fReader.InitCheck() == B_OK) { _DrawPageFrame(rect); _DrawPage(rect); _DrawMarginFrame(rect); } } void PreviewView::FrameResized(float width, float height) { Invalidate(); FixScrollbars(); } void PreviewView::MouseDown(BPoint point) { MakeFocus(true); BMessage *message = Window()->CurrentMessage(); int32 button; if (message && message->FindInt32("buttons", &button) == B_OK) { if (button == B_PRIMARY_MOUSE_BUTTON) { int32 modifier; if (message->FindInt32("modifiers", &modifier) == B_OK) { if (modifier & B_SHIFT_KEY) ZoomOut(); else ZoomIn(); } } if (button == B_SECONDARY_MOUSE_BUTTON) { fTracking = true; be_app->SetCursor(B_HAND_CURSOR); SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS | B_NO_POINTER_HISTORY); fScrollStart = Bounds().LeftTop() + ConvertToScreen(point); } } } void PreviewView::MouseMoved(BPoint point, uint32 transit, const BMessage* message) { if (fTracking) { uint32 button; GetMouse(&point, &button, false); point = fScrollStart - ConvertToScreen(point); float hMin, hMax; BScrollBar *hBar = ScrollBar(B_HORIZONTAL); hBar->GetRange(&hMin, &hMax); float vMin, vMax; BScrollBar *vBar = ScrollBar(B_VERTICAL); vBar->GetRange(&vMin, &vMax); if (point.x < 0.0) point.x = 0.0; if (point.y < 0.0) point.y = 0.0; if (point.x > hMax) point.x = hMax; if (point.y > vMax) point.y = vMax; ScrollTo(point); } else { switch (transit) { case B_ENTERED_VIEW: { fInsideView = true; be_app->SetCursor(ZOOM_IN); } break; case B_EXITED_VIEW: { fInsideView = false; be_app->SetCursor(B_HAND_CURSOR); } break; default: { if (modifiers() & B_SHIFT_KEY) be_app->SetCursor(ZOOM_OUT); else be_app->SetCursor(ZOOM_IN); } break; } } } void PreviewView::MouseUp(BPoint point) { (void)point; fTracking = false; fScrollStart.Set(0.0, 0.0); if (fInsideView && ((modifiers() & B_SHIFT_KEY) == 0)) be_app->SetCursor(ZOOM_IN); } void PreviewView::KeyDown(const char* bytes, int32 numBytes) { MakeFocus(true); switch (bytes[0]) { case '-': { if (modifiers() & B_CONTROL_KEY) ZoomOut(); } break; case '+' : { if (modifiers() & B_CONTROL_KEY) ZoomIn(); } break; default: { BView::KeyDown(bytes, numBytes); } break; } } void PreviewView::ShowFirstPage() { if (!ShowsFirstPage()) { fPage = 0; Invalidate(); } } void PreviewView::ShowPrevPage() { if (!ShowsFirstPage()) { fPage--; Invalidate(); } } void PreviewView::ShowNextPage() { if (!ShowsLastPage()) { fPage++; Invalidate(); } } void PreviewView::ShowLastPage() { if (!ShowsLastPage()) { fPage = NumberOfPages()-1; Invalidate(); } } bool PreviewView::ShowsFirstPage() const { return fPage == 0; } bool PreviewView::ShowsLastPage() const { return fPage == NumberOfPages() - 1; } void PreviewView::ShowFindPage(int32 page) { page--; if (page < 0) { page = 0; } else if (page > (NumberOfPages()-1)) { page = NumberOfPages()-1; } fPage = page; Invalidate(); } void PreviewView::ZoomIn() { if (CanZoomIn()) { fZoom++; FixScrollbars(); Invalidate(); } } bool PreviewView::CanZoomIn() const { return fZoom < 4; } void PreviewView::ZoomOut() { if (CanZoomOut()) { fZoom--; FixScrollbars(); Invalidate(); } } bool PreviewView::CanZoomOut() const { return fZoom > -2; } void PreviewView::FixScrollbars() { float width = _PaperRect().Width() + kPreviewLeftMargin + kPreviewRightMargin; float height = _PaperRect().Height() + kPreviewTopMargin + kPreviewBottomMargin; BRect frame(Bounds()); float x = width - frame.Width(); if (x < 0.0) x = 0.0; float y = height - frame.Height(); if (y < 0.0) y = 0.0; BScrollBar * scroll = ScrollBar(B_HORIZONTAL); scroll->SetRange(0.0, x); scroll->SetProportion((width - x) / width); float bigStep = frame.Width() - 2; float smallStep = bigStep / 10.; scroll->SetSteps(smallStep, bigStep); scroll = ScrollBar(B_VERTICAL); scroll->SetRange(0.0, y); scroll->SetProportion((height - y) / height); bigStep = frame.Height() - 2; smallStep = bigStep / 10.; scroll->SetSteps(smallStep, bigStep); } BRect PreviewView::ViewRect() const { BRect r(_PaperRect()); r.right += kPreviewLeftMargin + kPreviewRightMargin; r.bottom += kPreviewTopMargin + kPreviewBottomMargin; return r; } status_t PreviewView::InitCheck() const { return fReader.InitCheck(); } int32 PreviewView::NumberOfPages() const { return fNumberOfPages; } BRect PreviewView::_PaperRect() const { return ScaleRect(fPaperRect, _ZoomFactor()); } float PreviewView::_ZoomFactor() const { const int32 b = 4; int32 zoom; if (fZoom > 0) { zoom = (1 << b) << fZoom; } else { zoom = (1 << b) >> -fZoom; } float factor = zoom / (float)(1 << b); return factor * fReader.GetScale() / 100.0; } BRect PreviewView::_PrintableRect() const { return ScaleRect(fPrintableRect, _ZoomFactor()); } bool PreviewView::_IsPageValid() const { return fCachedPage && fCachedPage->InitCheck() == B_OK; } void PreviewView::_LoadPage(int32 page) { delete fCachedPage; fCachedPage = NULL; PrintJobPage pjp; if (fReader.GetPage(page, pjp) == B_OK) fCachedPage = new PreviewPage(page, &pjp); } bool PreviewView::_IsPageLoaded(int32 page) const { return fCachedPage != NULL && fCachedPage->Page() == page; } BRect PreviewView::_ContentRect() const { float offsetX = kPreviewLeftMargin; float offsetY = kPreviewTopMargin; BRect rect = Bounds(); BRect paperRect = _PaperRect(); float min, max; ScrollBar(B_HORIZONTAL)->GetRange(&min, &max); if (min == max) { if ((paperRect.right + 2 * offsetX) < rect.right) offsetX = (rect.right - (paperRect.right + 2 * offsetX)) / 2; } ScrollBar(B_VERTICAL)->GetRange(&min, &max); if (min == max) { if ((paperRect.bottom + 2 * offsetY) < rect.bottom) offsetY = (rect.bottom - (paperRect.bottom + 2 * offsetY)) / 2; } paperRect.OffsetTo(offsetX, offsetY); return paperRect; } void PreviewView::_DrawPageFrame(BRect rect) { const float kShadowIndent = 3; const float kShadowWidth = 3; const rgb_color frameColor = { 0, 0, 0, 0 }; const rgb_color shadowColor = { 90, 90, 90, 0 }; PushState(); // draw page border around page BRect r(_ContentRect().InsetByCopy(-1, -1)); SetHighColor(frameColor); StrokeRect(r); // draw page shadow SetHighColor(shadowColor); float x = r.right + 1; float right = x + kShadowWidth; float bottom = r.bottom + 1 + kShadowWidth; float y = r.top + kShadowIndent; FillRect(BRect(x, y, right, bottom)); x = r.left + kShadowIndent; y = r.bottom + 1; FillRect(BRect(x, y, r.right, bottom)); PopState(); } void PreviewView::_DrawPage(BRect rect) { BRect printRect(_PrintableRect()); switch (fNumberOfPagesPerPage) { case 2: case 8: case 32: case 128: { printRect = RotateRect(printRect); } break; } printRect.OffsetBy(_ContentRect().LeftTop()); BPoint scalingXY = GraphicsDriver::GetScale(fNumberOfPagesPerPage, printRect, 100.0); float scaling = min_c(scalingXY.x, scalingXY.y); printRect = ScaleDown(printRect, _ZoomFactor() * scaling); BRect clipRect(ScaleRect(printRect, scaling)); for (int32 index = 0; index < fNumberOfPagesPerPage; ++index) { int32 pageNumber = _GetPageNumber(index); if (pageNumber < 0) continue; if (!_IsPageLoaded(pageNumber)) _LoadPage(pageNumber); if (!_IsPageValid()) continue; BPoint offset(CalulateOffset(fNumberOfPagesPerPage, index, fOrientation, clipRect)); clipRect.OffsetTo(printRect.LeftTop()); clipRect.OffsetBy(offset); BRegion clip(clipRect); ConstrainClippingRegion(&clip); SetScale(_ZoomFactor() * scaling); fCachedPage->Draw(this, printRect.OffsetByCopy(offset)); if (fNumberOfPagesPerPage > 1) StrokeRect(clipRect.InsetByCopy(1.0, 1.0), B_MIXED_COLORS); SetScale(1.0); ConstrainClippingRegion(NULL); } } void PreviewView::_DrawMarginFrame(BRect rect) { BRect paperRect(_ContentRect()); BRect printRect(_PrintableRect()); printRect.OffsetBy(paperRect.LeftTop()); const rgb_color highColor = HighColor(); const rgb_color white = { 255, 255, 255, 255 }; SetHighColor(white); FillRect(BRect(paperRect.left, paperRect.top, printRect.left , paperRect.bottom)); FillRect(BRect(paperRect.left, paperRect.top, paperRect.right , printRect.top)); FillRect(BRect(printRect.right, paperRect.top, paperRect.right , paperRect.bottom)); FillRect(BRect(paperRect.left, printRect.bottom, paperRect.right , paperRect.bottom)); SetHighColor(highColor); BeginLineArray(4); SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); StrokeLine(BPoint(printRect.left, paperRect.top), BPoint(printRect.left, paperRect.bottom), B_MIXED_COLORS); StrokeLine(BPoint(printRect.right, paperRect.top), BPoint(printRect.right, paperRect.bottom), B_MIXED_COLORS); StrokeLine(BPoint(paperRect.left, printRect.top), BPoint(paperRect.right, printRect.top), B_MIXED_COLORS); StrokeLine(BPoint(paperRect.left, printRect.bottom), BPoint(paperRect.right, printRect.bottom), B_MIXED_COLORS); EndLineArray(); } int32 PreviewView::_GetPageNumber(int32 index) const { int32 page = fPage; if (fReverse) page = fNumberOfPages - fPage - 1; if (fPageSelection == JobData::kOddNumberedPages) page *= 2; // 0, 2, 4, ... else if (fPageSelection == JobData::kEvenNumberedPages) page = 2 * page + 1; // 1, 3, 5, ... return page * fNumberOfPagesPerPage + index; } // #pragma mark - PreviewWindow PreviewWindow::PreviewWindow(BFile* jobFile, bool showOkAndCancelButtons) : BlockingWindow(BRect(20, 24, 400, 600), "Preview", B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS) , fButtonBarHeight(0.0) { BRect bounds(Bounds()); BView* panel = new BBox(Bounds(), "top_panel", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_PLAIN_BORDER); AddChild(panel); bounds.OffsetBy(10.0, 10.0); fFirst = new BButton(bounds, "first", "First page", new BMessage(MSG_FIRST_PAGE)); panel->AddChild(fFirst); fFirst->ResizeToPreferred(); bounds.OffsetBy(fFirst->Bounds().Width() + 10.0, 0.0); fPrev = new BButton(bounds, "previous", "Previous page", new BMessage(MSG_PREV_PAGE)); panel->AddChild(fPrev); fPrev->ResizeToPreferred(); bounds.OffsetBy(fPrev->Bounds().Width() + 10.0, 0.0); fNext = new BButton(bounds, "next", "Next page", new BMessage(MSG_NEXT_PAGE)); panel->AddChild(fNext); fNext->ResizeToPreferred(); bounds.OffsetBy(fNext->Bounds().Width() + 10.0, 0.0); fLast = new BButton(bounds, "last", "Last page", new BMessage(MSG_LAST_PAGE)); panel->AddChild(fLast); fLast->ResizeToPreferred(); bounds = fLast->Frame(); bounds.OffsetBy(fLast->Bounds().Width() + 10.0, 0.0); fPageNumber = new BTextControl(bounds, "numOfPage", "99", "", new BMessage(MSG_FIND_PAGE)); panel->AddChild(fPageNumber); fPageNumber->ResizeToPreferred(); fPageNumber->SetDivider(0.0); fPageNumber->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_RIGHT); fPageNumber->MoveBy(0.0, bounds.Height() - fPageNumber->Bounds().Height()); uint32 num; for (num = 0; num <= 255; num++) fPageNumber->TextView()->DisallowChar(num); for (num = 0; num <= 9; num++) fPageNumber->TextView()->AllowChar('0' + num); fPageNumber->TextView()-> SetMaxBytes(5); bounds.OffsetBy(fPageNumber->Bounds().Width() + 5.0, 0.0); fPageText = new BStringView(bounds, "pageText", ""); panel->AddChild(fPageText); fPageText->ResizeTo(fPageText->StringWidth("of 99999 pages"), fFirst->Bounds().Height()); bounds.OffsetBy(fPageText->Bounds().Width() + 10.0, 0.0); fZoomIn = new BButton(bounds, "zoomIn", "Zoom in", new BMessage(MSG_ZOOM_IN)); panel->AddChild(fZoomIn); fZoomIn->ResizeToPreferred(); bounds.OffsetBy(fZoomIn->Bounds().Width() + 10.0, 0.0); fZoomOut = new BButton(bounds, "ZoomOut", "Zoom out", new BMessage(MSG_ZOOM_OUT)); panel->AddChild(fZoomOut); fZoomOut->ResizeToPreferred(); fButtonBarHeight = fZoomOut->Frame().bottom + 10.0; bounds = Bounds(); bounds.top = fButtonBarHeight; if (showOkAndCancelButtons) { // adjust preview height bounds.bottom -= fButtonBarHeight; // update the total height of both bars fButtonBarHeight *= 2; // cancel printing if user closes the preview window SetUserQuitResult(B_ERROR); BButton *printJob = new BButton(BRect(), "printJob", "Print", new BMessage(MSG_PRINT_JOB), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); panel->AddChild(printJob); printJob->ResizeToPreferred(); printJob->MoveTo(bounds.right - (printJob->Bounds().Width() + 10.0), bounds.bottom + 10.0); BButton *cancelJob = new BButton(BRect(), "cancelJob", "Cancel", new BMessage(MSG_CANCEL_JOB), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); panel->AddChild(cancelJob); cancelJob->ResizeToPreferred(); cancelJob->MoveTo(printJob->Frame().left - (10.0 + cancelJob->Bounds().Width()), bounds.bottom + 10.0); printJob->MakeDefault(true); } bounds.right -= B_V_SCROLL_BAR_WIDTH; bounds.bottom -= B_H_SCROLL_BAR_HEIGHT; fPreview = new PreviewView(jobFile, bounds); fPreviewScroller = new BScrollView("PreviewScroller", fPreview, B_FOLLOW_ALL, B_FRAME_EVENTS, true, true, B_FANCY_BORDER); fPreviewScroller->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); panel->AddChild(fPreviewScroller); if (fPreview->InitCheck() == B_OK) { _ResizeToPage(); fPreview->FixScrollbars(); _UpdateControls(); fPreview->MakeFocus(true); } } void PreviewWindow::MessageReceived(BMessage* m) { switch (m->what) { case MSG_FIRST_PAGE: fPreview->ShowFirstPage(); break; case MSG_NEXT_PAGE: fPreview->ShowNextPage(); break; case MSG_PREV_PAGE: fPreview->ShowPrevPage(); break; case MSG_LAST_PAGE: fPreview->ShowLastPage(); break; case MSG_FIND_PAGE: fPreview->ShowFindPage(atoi(fPageNumber->Text())) ; break; case MSG_ZOOM_IN: fPreview->ZoomIn(); break; case MSG_ZOOM_OUT: fPreview->ZoomOut(); break; case B_MODIFIERS_CHANGED: fPreview->MouseMoved(BPoint(), B_INSIDE_VIEW, m); break; case MSG_CANCEL_JOB: Quit(B_ERROR); break; case MSG_PRINT_JOB: Quit(B_OK); break; default: BlockingWindow::MessageReceived(m); return; } _UpdateControls(); } status_t PreviewWindow::Go() { status_t st = InitCheck(); if (st == B_OK) return BlockingWindow::Go(); be_app->SetCursor(B_HAND_CURSOR); Quit(); return st; } void PreviewWindow::_ResizeToPage() { BScreen screen; if (screen.Frame().right == 0.0) return; const float windowBorderWidth = 5; const float windowBorderHeight = 5; BRect rect(fPreview->ViewRect()); float width = rect.Width() + 1 + B_V_SCROLL_BAR_WIDTH; float height = rect.Height() + 1 + fButtonBarHeight + B_H_SCROLL_BAR_HEIGHT; rect = screen.Frame(); // dimensions so that window does not reach outside of screen float maxWidth = rect.Width() + 1 - windowBorderWidth - Frame().left; float maxHeight = rect.Height() + 1 - windowBorderHeight - Frame().top; // width so that all buttons are visible float minWidth = fZoomOut->Frame().right + 10; if (width < minWidth) width = minWidth; if (width > maxWidth) width = maxWidth; if (height > maxHeight) height = maxHeight; ResizeTo(width, height); } void PreviewWindow::_UpdateControls() { fFirst->SetEnabled(!fPreview->ShowsFirstPage()); fPrev->SetEnabled(!fPreview->ShowsFirstPage()); fNext->SetEnabled(!fPreview->ShowsLastPage()); fLast->SetEnabled(!fPreview->ShowsLastPage()); fZoomIn->SetEnabled(fPreview->CanZoomIn()); fZoomOut->SetEnabled(fPreview->CanZoomOut()); BString text; text << fPreview->CurrentPage(); fPageNumber->SetText(text.String()); text.SetTo("of "); text << fPreview->NumberOfPages() << " Page"; if (fPreview->NumberOfPages() > 1) text.Append("s"); fPageText->SetText(text.String()); }