/*  
	BeOS Front-end du PDF file reader xpdf.
    Copyright (C) 1998 Hubert Figuiere
	Copyright (C) 2000 Michael Pfeiffer
	
    This program 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 2 of the License, or
    (at your option) any later version.

    This program 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 this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
//
// PDFView.cpp
//
// (c) 1998-99 Hubert Figuiere
//
// $Id: PDFView.cpp,v 1.7 1999/03/27 00:50:01 hub Exp $
//


#include <stdio.h>
#include <storage/Path.h>
#include <interface/ScrollBar.h>
#include <interface/PrintJob.h>
#include <Entry.h>
#include <String.h>
#include <Alert.h>
#include <Roster.h>
#include <StringView.h>
#include <Application.h>
#include <Looper.h>
#include <MessageQueue.h>

#include <TextOutputDev.h>
#include "FindTextWindow.h"
#include "PDFWindow.h"
#include "PDFView.h"
#include "PDFExceptions.h"
#include "BepdfApplication.h"
#include "PrintingProgressWindow.h"

// zoom factor is 1.2 (similar to DVI magsteps)
static const int kZoomDPI[MAX_ZOOM - MIN_ZOOM + 1] = {
	29, 35, 42, 50, 60,
	72,
	86, 104, 124, 149, 179
};

#define DEF_ZOOM	0
#define BITMAP_COLOR_SPACE B_RGB_16_BIT

inline static float RealSize (float x, float zoomDPI)
{
	return zoomDPI / 72 * x;
}


///////////////////////////////////////////////////////////////////////////
PDFView::PDFView (entry_ref * ref, BRect frame,
								const char *name,
								uint32 resizeMask,
								uint32 flags)
	: BView (frame, name, resizeMask, flags)
{
	SetViewColor(B_TRANSPARENT_COLOR);
	mCurrentPage = 0;
	mDoc = NULL;
	mOk = false;
	mRotation = 0.0f;
	mZoom = DEF_ZOOM;
	mBitmap = NULL;
	mPrintSettings = NULL;
	mMouseDown = 0;
	mLinkAction = NULL;
	mSelected = NOT_SELECTED;

	rgb_color fill_color = {0, 0, 255, 64};
	SetHighColor(fill_color); // fill color for selection
//	rgb_color background_color = {255, 0, 0, 255};
//	SetLowColor(background_color); 
	
	if (LoadFile(ref, true)) {	
		SetViewCursor(my_app->handCursor, true);
		RecreateBitmap ();
	
		mOffscreenView = new BView(mBitmap->Bounds(), "", B_FOLLOW_NONE, 0);
		mBitmap->AddChild (mOffscreenView);
		mOutputDev = new BeOutputDev (mOffscreenView, this);
		mOutputDev->startDoc();
		mOk = true;
	}
}


///////////////////////////////////////////////////////////////////////////
bool
PDFView::LoadFile(entry_ref *ref, bool init) {
	GString * fileName = NULL;
	BEntry entry (ref, true);
	BPath path;
	
	mSelected = NOT_SELECTED;
	
	entry.GetPath (&path);
	
	fileName = new GString ((char*)path.Path ());
	PDFDoc *newDoc = new PDFDoc (fileName);
	if (! newDoc->isOk ()) {
		delete newDoc;
		return false;
	}
	
	PDFDoc *old = mDoc;
	mDoc = NULL;
	if (old != NULL) delete old;

	mTitle = new GString("PDFViewer: ");
	mTitle->append ((char*)path.Leaf ());
	
	mWidth = RealSize (newDoc->getPageWidth(1), kZoomDPI[mZoom - MIN_ZOOM]);
	mHeight = RealSize (newDoc->getPageHeight(1), kZoomDPI[mZoom - MIN_ZOOM]);
	
	if (init) {
		mDoc = newDoc;
	} else if (Window()->Lock()) {
		mCurrentPage = 1; mHistory.MakeEmpty(); Redraw(newDoc);
		mDoc = newDoc; FixScrollbars();
		Window()->SetTitle (mTitle->getCString());
		mOutputDev->startDoc();
		Window()->Unlock();
	}

	return true;
}

///////////////////////////////////////////////////////////////////////////
PDFView::~PDFView()
{
	delete mBitmap;
	delete mDoc;
	delete mOutputDev;
	delete mTitle;
}

///////////////////////////////////////////////////////////////////////////
void PDFView::MessageReceived(BMessage *msg) {
	if (msg->what == B_SIMPLE_DATA) {
		entry_ref ref;
		if (B_OK == msg->FindRef("refs", 0, &ref)) {
			be_app->RefsReceived(msg);
			return;
		}
	}
	BView::MessageReceived(msg);
}
///////////////////////////////////////////////////////////////////////////
void
PDFView::Draw(BRect updateRect)
{
	if (IsPrinting()) {
		mDoc->displayPage (mOutputDev, mCurrentPage, kZoomDPI[mZoom - MIN_ZOOM], mRotation, gTrue);	
	} else {
		if (mBitmap == NULL) {
	#ifdef DEBUG
			fprintf (stderr, "WARNING: PDFView::Draw() NULL bitmap\n");
	#endif
			return;
		}
		DrawBitmap (mBitmap, updateRect, updateRect);
/*
		BRect rect(Bounds());
		if (mWidth < rect.right) {
			FillRect(BRect(mWidth+1, 0, rect.right, rect.bottom), B_SOLID_LOW);
		}
		if (mHeight < rect.bottom) {
			FillRect(BRect(0, mHeight, mWidth, rect.bottom), B_SOLID_LOW); 
		}
*/
		switch (mSelected) {
			case DO_SELECTION:
				StrokeRect(mSelection);
				break;
			case SELECTED:
				SetDrawingMode(B_OP_ALPHA);
				FillRect(mSelection);
				SetDrawingMode(B_OP_COPY);
				break;
		}
	}
}

///////////////////////////////////////////////////////////////////////////
void
PDFView::ScrollTo (BPoint point) {
	BView::ScrollTo(point);
	
	BPoint mouse; uint32 buttons;
	GetMouse(&mouse, &buttons);
	DisplayLink(mouse);
}

///////////////////////////////////////////////////////////////////////////
void
PDFView::FrameResized (float width, float height)
{
	FixScrollbars();
}


///////////////////////////////////////////////////////////////////////////
void
PDFView::AttachedToWindow ()
{
	Window()->SetTitle (mTitle->getCString());
	SetViewCursor(my_app->handCursor);
#if 0
	// print installed font family and style names
	int32 numFamilies = count_font_families(); 
   for ( int32 i = 0; i < numFamilies; i++ ) { 
       font_family family; 
       uint32 flags; 
       if ( get_font_family(i, &family, &flags) == B_OK ) { 
           int32 numStyles = count_font_styles(family); 
           for ( int32 j = 0; j < numStyles; j++ ) { 
               font_style style; 
               if ( get_font_style(family, j, &style, &flags) 
                                                 == B_OK ) { 
               	fprintf(stderr, "%s %s\n", family, style);
               } 
           } 
       } 
   }
#endif
}

///////////////////////////////////////////////////////////////////////////
void 
PDFView::ScrollVertical (bool down, float by) {
	BRect rect(Bounds ());
	float scrollBy = (by > 0) ? rect.Height() * by : -by;
	if (down) {
		if (rect.bottom < mHeight-1) {
			ScrollBy (0, scrollBy);
		} else {
			if (mCurrentPage != mDoc->getNumPages()) { // bottom of last page not reached
				SetPage (mCurrentPage + 1);
				ScrollTo (rect.left, 0);
			}
		}
	} else { // up
		if (rect.top != 0) {
			ScrollBy (0, -scrollBy);
		} else {
			if (mCurrentPage != 1) { // top of first page not reached
				SetPage (mCurrentPage - 1);
				ScrollTo (rect.left, mHeight);
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////
void 
PDFView::ScrollHorizontal (bool right, float by) {
	BRect rect(Bounds());
	float scrollBy = (by > 0) ? rect.Width() * by : -by;
	if (right) {
		if (rect.right < mWidth - 1) {
			ScrollBy (scrollBy, 0);
		}
	} else {
		if (rect.left != 0) {
			ScrollBy (-scrollBy, 0);
		}
	}
}
///////////////////////////////////////////////////////////////////////////
void
PDFView::KeyDown (const char * bytes, int32 numBytes)
{
	switch (*bytes) {
	case B_PAGE_UP:
		SetPage (mCurrentPage - 1);
		break;
	case B_SPACE:
	case B_BACKSPACE:
		ScrollVertical (*bytes == B_SPACE, 0.95);
		break;
	case B_DOWN_ARROW:
	case B_UP_ARROW:
		ScrollVertical (*bytes == B_DOWN_ARROW, -20);
		break;
	case B_LEFT_ARROW:
	case B_RIGHT_ARROW:
		ScrollHorizontal(*bytes == B_RIGHT_ARROW, -20);
		break;
	case B_PAGE_DOWN:
		SetPage (mCurrentPage + 1);
		break;
	case B_HOME:
		SetPage (1);
		break;
	case B_END:
		SetPage (GetNumPages ());
		break;
	default:
		BView::KeyDown (bytes, numBytes);
		break;
	}
}

///////////////////////////////////////////////////////////////////////////
void PDFView::SetViewCursor(BCursor *cursor, bool sync) {
	if (Window()->Lock()) {
		mViewCursor = cursor;
		BView::SetViewCursor(cursor, sync);
		Window()->Unlock();
	}
}	
///////////////////////////////////////////////////////////////////////////
void
PDFView::MouseDown (BPoint point) {
	MakeFocus();
//	fprintf(stderr, "MouseDown\n");
	uint32 buttons;
	GetMouse(&point, &buttons, false);
	if ((buttons == B_PRIMARY_MOUSE_BUTTON) && (modifiers() & B_CONTROL_KEY)) {
		buttons = B_SECONDARY_MOUSE_BUTTON; // simulate secondary button 
	}
	switch (buttons) {
		case B_PRIMARY_MOUSE_BUTTON: // follow link or move view 
			mMouseDown = B_PRIMARY_MOUSE_BUTTON;
			if (!HandleLink(point)) {
				SetMouseEventMask(B_POINTER_EVENTS);
		  		SetViewCursor(my_app->grabCursor);
				mMouseDown = true;
				mMousePosition = ConvertToScreen(point);
			}
			break;
		case B_SECONDARY_MOUSE_BUTTON: // text selection
			SetMouseEventMask(B_POINTER_EVENTS);
		  	SetViewCursor(my_app->pointerCursor);
			if (mSelected != NOT_SELECTED) {
				Invalidate(mSelection);
			}
			mMouseDown = B_SECONDARY_MOUSE_BUTTON;
			mSelected = DO_SELECTION;
			mMousePosition = ConvertToScreen(point);
			mSelectionStart = point;
			mSelection.SetLeftTop(point);
			mSelection.SetRightBottom(point);
			break;
	}
}

///////////////////////////////////////////////////////////////////////////
// is msg1 older than msg2?
#if 0
static bool IsOlder(BMessage *msg1, BMessage *msg2) {
int64 when1, when2;
	msg1->FindInt64("when", &when1);
	msg2->FindInt64("when", &when2);
	return when1 < when2; 
}
#endif

void PDFView::SkipMouseMoveMsgs() {
	BMessage *mouseMovedMsg;
	while ((mouseMovedMsg = Looper()->MessageQueue()->FindMessage(B_MOUSE_MOVED, 0)))
	{
#if 1
		Looper()->MessageQueue()->RemoveMessage(mouseMovedMsg);
		delete mouseMovedMsg;
#else
		BMessage *mouseUpMsg = Looper()->MessageQueue()->FindMessage(B_MOUSE_UP, 0);
//			if (mouseUpMsg != NULL) fprintf(stderr, "-- MouseUp --\n");
		if ((mouseUpMsg == NULL) || IsOlder(mouseMovedMsg, mouseUpMsg)) {
			Looper()->MessageQueue()->RemoveMessage(mouseMovedMsg);
			delete mouseMovedMsg;
		} else {
			break;
		}
#endif
	}
//		fprintf(stderr, "MouseMoved exited\n");
}
void
PDFView::MouseMoved (BPoint point, uint32 transit, const BMessage *msg) {
//	fprintf(stderr, "MouseMoved\n");
	// FIXME: Where is the best place to set the initial Cursor of a view?
	if ((transit == B_ENTERED_VIEW) && (mViewCursor != NULL)) {
		if (Window()->Lock()) {
			BView::SetViewCursor(mViewCursor);
			Window()->Unlock();
			mViewCursor = NULL;
		}
	}
	switch (mMouseDown) {
		case 0:
			DisplayLink(point);
			break;
		case B_PRIMARY_MOUSE_BUTTON: {
			SkipMouseMoveMsgs();

			BPoint mousePosition;
			uint32 buttons;
			BPoint offset;
			float x, y, r_min, r_max;
			BScrollBar *scroll;
	
			GetMouse(&mousePosition, &buttons, false);
			point = ConvertToScreen(mousePosition);
			offset = point - mMousePosition;
			mMousePosition = point;
	
			scroll = ScrollBar(B_HORIZONTAL);
			scroll->GetRange(&r_min, &r_max);
			x = min_c(r_max, max_c(scroll->Value() - offset.x, r_min));
	
			scroll = ScrollBar(B_VERTICAL);
			scroll->GetRange(&r_min, &r_max);
			y = min_c(r_max, max_c(scroll->Value() - offset.y, r_min));
	
			ScrollTo(x, y);
			if ((buttons & B_PRIMARY_MOUSE_BUTTON) == 0) {
				MouseUp(mousePosition);
			}
			break; }
		case B_SECONDARY_MOUSE_BUTTON: { // text selection
			int updateCounter = 5;
			// while(true) {
				SkipMouseMoveMsgs();

				uint32 buttons;
				BScrollBar *scroll;			
				float x, y, r_min, r_max;
				BRect bounds = Bounds();
				
				scroll = ScrollBar(B_VERTICAL);
				scroll->GetRange(&r_min, &r_max);
				if (point.x < bounds.left) { // scroll left
					x = point.x;
				} else if (point.x > bounds.right) { // scroll right
					x = point.x - bounds.Width();
				} else {
					x = bounds.left;
				}
				x = min_c(r_max, max_c(x, r_min));
				
				scroll = ScrollBar(B_VERTICAL);
				scroll->GetRange(&r_min, &r_max);
				if (point.y < bounds.top) { // scroll up
					y = point.y;
				} else if (point.y > bounds.bottom) { // scroll down
					y = point.y - bounds.Height();
				} else {
					y = bounds.top;
				}
				y = min_c(r_max, max_c(y, r_min));
				if ((x != bounds.left) || (y != bounds.top)) {
					ScrollTo(x, y);
				}
				
				BRect rect(mSelection);
				if (point.x < mSelectionStart.x) {
					mSelection.left = point.x; mSelection.right = mSelectionStart.x;
				} else {
					mSelection.left = mSelectionStart.x; mSelection.right = point.x;
				}
				
				if (point.y < mSelectionStart.y) {
					mSelection.top = point.y; mSelection.bottom = mSelectionStart.y;
				} else {
					mSelection.top = mSelectionStart.y; mSelection.bottom = point.y;
				}
				
				if (rect != mSelection) {
					Invalidate(rect | mSelection);
				}
/*							
				GetMouse(&point, &buttons, false);
				if (buttons == 0) {
					MouseUp(point);
					return;
				}
				if (updateCounter == 5) {
					Window()->UpdateIfNeeded();
					updateCounter = 0;
				} else {
					updateCounter ++;
				}
				snooze(10000);
			};
*/
			break; }
	}
}

///////////////////////////////////////////////////////////////////////////
void
PDFView::MouseUp (BPoint point) {
//	fprintf(stderr, "MouseUp\n");
	if (mMouseDown == B_SECONDARY_MOUSE_BUTTON) {
		mSelected = SELECTED;
		Invalidate(mSelection);
		CopySelection();
	}
	
	if (mMouseDown != 0) {
		SetViewCursor(my_app->handCursor);
		DisplayLink(point);
		mMouseDown = 0;
	}
}

///////////////////////////////////////////////////////////////////////////
bool
PDFView::HandleLink(BPoint point) {
	double x, y;
	int dx, dy;
	LinkAction *action = NULL;
	LinkActionKind kind;
	LinkDest *dest = NULL;
	GString *namedDest = NULL;
	GString *fileName;
	char *s;
	int pg;
	Ref pageRef;
	
	if ((mDoc == NULL) || (mDoc->getNumPages() == 0)) return false;

	mOutputDev->cvtDevToUser(point.x, point.y, &x, &y);
	if ((action = mDoc->findLink(x, y)) != NULL) {
		switch (kind = action->getKind()) {

		// GoTo / GoToR action
		case actionGoTo:
		case actionGoToR:
			if (kind == actionGoTo) {
				dest = NULL;
				namedDest = NULL;
				if ((dest = ((LinkGoTo *)action)->getDest()))
					dest = dest->copy();
				else if ((namedDest = ((LinkGoTo *)action)->getNamedDest()))
					namedDest = namedDest->copy();
				} else {
				/*
					dest = NULL;
					namedDest = NULL;
					if ((dest = ((LinkGoToR *)action)->getDest()))
						dest = dest->copy();
					else if ((namedDest = ((LinkGoToR *)action)->getNamedDest()))
						namedDest = namedDest->copy();
					s = ((LinkGoToR *)action)->getFileName()->getCString();
					//~ translate path name for VMS (deal with '/')
					if (isAbsolutePath(s))
						fileName = new GString(s);
					else
						fileName = appendToPath(
					grabPath(mDoc->getFileName()->getCString()), s);
					if (!loadFile(fileName)) {
						if (dest)
							delete dest;
						if (namedDest)
							delete namedDest;
						return;
					}
				*/
				}
				if (namedDest) {
					dest = mDoc->findDest(namedDest);
					delete namedDest;
				}
				if (!dest) {
					if (kind == actionGoToR)
						SetPage(1);
						/*displayPage(1, mZoom, 0, gTrue);*/
				} else {
					if (dest->isPageRef()) {
						pageRef = dest->getPageRef();
						pg = mDoc->findPage(pageRef.num, pageRef.gen);
					} else {
						pg = dest->getPageNum();
					}
					if (pg > 0 && pg != mCurrentPage)
						SetPage(pg);
						/*displayPage(pg, mZoom, mRotate, gTrue); */
					else if (pg <= 0)
						SetPage(1);
						/*displayPage(1, mZoom, mRotate, gTrue);*/
					switch (dest->getKind()) {
					case destXYZ:
						mOutputDev->cvtUserToDev(dest->getLeft(), dest->getTop(), &dx, &dy);
						if (dest->getChangeLeft() || dest->getChangeTop()) {
							if (dest->getChangeLeft())
								/*hScrollbar->setPos(dx, canvas->getWidth())*/;
							if (dest->getChangeTop())
								/*vScrollbar->setPos(dy, canvas->getHeight())*/;
							/*canvas->scroll(hScrollbar->getPos(), vScrollbar->getPos());*/
						}
						//~ what is the zoom parameter?
						break;
					case destFit:
					case destFitB:
						//~ do fit
						/*
						hScrollbar->setPos(0, canvas->getWidth());
						vScrollbar->setPos(0, canvas->getHeight());
						canvas->scroll(hScrollbar->getPos(), vScrollbar->getPos());
						*/
						break;
					case destFitH:
					case destFitBH:
						//~ do fit
						/*
						out->cvtUserToDev(0, dest->getTop(), &dx, &dy);
						hScrollbar->setPos(0, canvas->getWidth());
						vScrollbar->setPos(dy, canvas->getHeight());
						canvas->scroll(hScrollbar->getPos(), vScrollbar->getPos());
						*/
						break;
					case destFitV:
					case destFitBV:
						//~ do fit
						/*
						out->cvtUserToDev(dest->getLeft(), 0, &dx, &dy);
						hScrollbar->setPos(dx, canvas->getWidth());
						vScrollbar->setPos(0, canvas->getHeight());
						canvas->scroll(hScrollbar->getPos(), vScrollbar->getPos());
						*/
						break;
					case destFitR:
						//~ do fit
						/*
						out->cvtUserToDev(dest->getLeft(), dest->getTop(), &dx, &dy);
						hScrollbar->setPos(dx, canvas->getWidth());
						vScrollbar->setPos(dy, canvas->getHeight());
						canvas->scroll(hScrollbar->getPos(), vScrollbar->getPos());
						*/
						break;
					}
				delete dest;
				return true;
			}
			break;
		
			// Launch action
		case actionLaunch:
			fileName = ((LinkLaunch *)action)->getFileName();
			s = fileName->getCString();
			if (!strcmp(s + fileName->getLength() - 4, ".pdf") ||
				!strcmp(s + fileName->getLength() - 4, ".PDF")) {
				//~ translate path name for VMS (deal with '/')
				/*
				if (isAbsolutePath(s))
					fileName = fileName->copy();
				else
					fileName = appendToPath(
				grabPath(mDoc->getFileName()->getCString()), s);
				if (!loadFile(fileName))
					return;
				*/
				// displayPage(1, zoom, rotate, gTrue);
			} else {
				fileName = fileName->copy();
				if (((LinkLaunch *)action)->getParams()) {
					fileName->append(' ');
					fileName->append(((LinkLaunch *)action)->getParams());
				}
#ifdef VMS
				fileName->insert(0, "spawn/nowait ");
#elif defined(__EMX__)
				fileName->insert(0, "start /min /n ");
#else
				fileName->append(" &");
#endif
				BString string("Execute the command:");
				string += fileName->getCString();
				string += "?";
				BAlert *dialog = new BAlert("xpdf: Launch",
						 string.String(),
						 "Ok", "Cancel");
				if (dialog->Go() == 0)
					system(fileName->getCString());
				delete dialog;
				delete fileName;
				return true;
			}
			break;

		// URI action
		case actionURI:
			/*
			if (urlCommand) {
				for (s = urlCommand->getCString(); *s; ++s) {
					if (s[0] == '%' && s[1] == 's')
						break;
				}
				if (s) {
					fileName = new GString(urlCommand->getCString(),
						s - urlCommand->getCString());
					fileName->append(((LinkURI *)action)->getURI());
					fileName->append(s+2);
				} else {
					fileName = urlCommand->copy();
				}
#ifdef VMS
				fileName->insert(0, "spawn/nowait ");
#elif defined(__EMX__)
				fileName->insert(0, "start /min /n ");
#else
				fileName->append(" &");
#endif
				system(fileName->getCString());
				delete fileName;
			} else {
				fprintf(stderr, "URI: %s\n",
					((LinkURI *)action)->getURI()->getCString());
			}
			*/
			// open link with Netpositive
			{
			char *argv[2] = {((LinkURI *)action)->getURI()->getCString(),
				NULL};
			be_roster->Launch("application/x-vnd.Be-NPOS", 1, argv);
			}
			return true;

		// unknown action type
		case actionUnknown:
			fprintf(stderr, "Unknown link action type: '%s'",
				((LinkUnknown *)action)->getAction()->getCString());
			break;
		}
	}
	return false;
}
///////////////////////////////////////////////////////////////////////////
void 
PDFView::DisplayLink(BPoint point) {
double x, y;
char *s;
LinkAction *action;
	if ((mDoc == NULL) || (mDoc->getNumPages() == 0)) return;

	mOutputDev->cvtDevToUser(point.x, point.y, &x, &y);
	if ((action = mDoc->findLink(x, y)) != NULL) {
		if (action != mLinkAction) {
			if (!mLinkAction)
				SetViewCursor(my_app->linkCursor);
			mLinkAction = action;
			s = NULL;
			switch (mLinkAction->getKind()) {
			case actionGoTo:
				s = "[internal link]";
				break;
			case actionGoToR:
				s = ((LinkGoToR *)action)->getFileName()->getCString();
				break;
			case actionLaunch:
				s = ((LinkLaunch *)action)->getFileName()->getCString();
				break;
			case actionURI:
				s = ((LinkURI *)action)->getURI()->getCString();
				break;
			case actionUnknown:
				s = "[unknown link]";
				break;
			}
			BString string("Link: ");
			string += s;
			((PDFWindow*)Window())->SetStatus(string.String());
		}
	} else {
		if (mLinkAction) {
			mLinkAction = NULL;
			SetViewCursor(my_app->handCursor); 
			((PDFWindow*)Window())->SetStatus("");
		}
	}
}
///////////////////////////////////////////////////////////////////////////
void
PDFView::FixScrollbars ()
{
	BRect frame = Bounds();
	BScrollBar * scroll;
	float x, y;
	float bigStep, smallStep;

	x = mWidth - frame.Width();
	if (x < 0.0) {
		x = 0.0;
	}
	y = mHeight - frame.Height();
	if (y < 0.0) {
		y = 0.0;
	}
	
	scroll = ScrollBar (B_HORIZONTAL);
	scroll->SetRange (0.0, x);
	scroll->SetProportion ((mWidth - x) / mWidth);
	bigStep = frame.Width() - 2;
	smallStep = bigStep / 10.;
	scroll->SetSteps (smallStep, bigStep);

	scroll = ScrollBar (B_VERTICAL);
	scroll->SetRange (0.0, y);
	scroll->SetProportion ((mHeight - y) / mHeight);
	bigStep = frame.Height() - 2;
	smallStep = bigStep / 10.;
	scroll->SetSteps (smallStep, bigStep);
}


///////////////////////////////////////////////////////////////////////////
void 
PDFView::Redraw(PDFDoc *mDoc)
{
	PDFWindow * parentWin;
	if (!mOutputDev) {
		return;
	}

	if (mDoc == NULL) mDoc = this->mDoc;
	
	mWidth = RealSize (mDoc->getPageWidth (mCurrentPage), kZoomDPI[mZoom - MIN_ZOOM]);
	mHeight = RealSize (mDoc->getPageHeight (mCurrentPage), kZoomDPI[mZoom - MIN_ZOOM]);

	if ((mDoc->getPageRotate(mCurrentPage) == 90) ||
		(mDoc->getPageRotate(mCurrentPage) == 270)) {
		float h = mWidth; mWidth = mHeight; mHeight = h;
	}

	if ((mRotation == 90) || (mRotation == 270)) {
		float h = mWidth; mWidth = mHeight; mHeight = h;
	}

	parentWin = dynamic_cast<PDFWindow *> (Window());
	if (parentWin != NULL) {
		BMessage * notify = new BMessage (PDFWindow::PAGE_CHANGE_NOTIFY_MSG);
		
		notify->AddInt32 (PDFWindow::PAGE_MSG_LABEL, mCurrentPage);
		parentWin->PostMessage (notify);
		delete notify;
		
		parentWin->SetZoomSize (mWidth, mHeight);
		parentWin->PostMessage (notify);
	}

	mBitmap->Lock ();
	mOffscreenView->RemoveSelf ();
	mBitmap->Unlock ();
	
	RecreateBitmap ();
	mOffscreenView->ResizeTo (mWidth, mHeight);
	mBitmap->AddChild (mOffscreenView);

	mBitmap->Lock ();
	rgb_color hColor = mOffscreenView->HighColor ();
	mOffscreenView->SetHighColor (255, 255, 255);
	mOffscreenView->FillRect (Frame ());
	mOffscreenView->SetHighColor (hColor);
	FillRect (Bounds(), B_SOLID_LOW);
	mDoc->displayPage (mOutputDev, mCurrentPage, kZoomDPI[mZoom - MIN_ZOOM], mRotation, gTrue);
	mOffscreenView->Sync ();
	mBitmap->Unlock ();
	
	DrawBitmap (mBitmap, BPoint (0, 0));
	
	BPoint mouse; uint32 buttons;
	GetMouse(&mouse, &buttons);
	DisplayLink(mouse);
}

///////////////////////////////////////////////////////////////////////////
void
PDFView::Dump() {
	mBitmap->Unlock();
	DrawBitmap(mBitmap, BPoint(0, 0));
	mBitmap->Lock();
}

///////////////////////////////////////////////////////////////////////////
void 
PDFView::SetPage (int page, bool record)
{
	// printf ("PDFView::SetPage (%d)\n", page);
	mSelected = NOT_SELECTED;
	
	int currentPage = mCurrentPage;
	if (mCurrentPage != page) {
		if (page < 1) {
			mCurrentPage = 1;
		}
		else if (page > GetNumPages()) {
			mCurrentPage = GetNumPages();
		}
		else {
			mCurrentPage = page;
		}
		
		if (currentPage != mCurrentPage) {
			if (record) mHistory.Add(mCurrentPage);
		
			Redraw ();
			FixScrollbars ();
		}
	}
}

//////////////////////////////////////////////////////////////////
void
PDFView::Back() {
	if (mHistory.Back()) {
		SetPage(mHistory.GetTop(), false);
	}
}
//////////////////////////////////////////////////////////////////
void
PDFView::SetZoom (int zoom)
{
	if (mZoom != zoom) {
		mZoom = zoom;
		Redraw ();
		FixScrollbars ();
	}
}

//////////////////////////////////////////////////////////////////
void
PDFView::SetRotation (float rotation)
{
	if (mRotation != rotation) {
		mRotation = rotation;
		Redraw ();
		FixScrollbars ();
	}
}

//////////////////////////////////////////////////////////////////
void
PDFView::RecreateBitmap ()
{
	delete mBitmap;
	mBitmap = new BBitmap(BRect (0, 0, mWidth, mHeight), 
							BITMAP_COLOR_SPACE, true, false);
}
//////////////////////////////////////////////////////////////////
void PDFView::SetSelection(int xMin, int yMin, int xMax, int yMax) {
	BRect rect(mSelection);
	mSelection.Set(xMin, yMin, xMax, yMax);
	Window()->Lock();
	if (mSelected != SELECTED) {
		Invalidate(mSelection | rect);
	} else {
		mSelected = SELECTED;
		Invalidate(mSelection);
	}
	Window()->Unlock();
}

//////////////////////////////////////////////////////////////////
void PDFView::GetSelection(int &xMin, int &yMin, int &xMax, int &yMax) {
	if (mSelected == SELECTED) {
		xMin = (int)mSelection.left;
		yMin = (int)mSelection.top;
		xMax = (int)mSelection.right;
		yMax = (int)mSelection.bottom;
	} else {
		xMin = yMin = xMax = yMax = 0;
	}
}
//////////////////////////////////////////////////////////////////
void PDFView::CopySelection() {
	if ((mSelected == SELECTED) && (mSelection.left < mSelection.right) && (mSelection.top < mSelection.bottom)) {
		GString *s = mOutputDev->getText(mSelection.left, mSelection.top, mSelection.right, mSelection.bottom);
		if (be_clipboard->Lock()) {
			be_clipboard->Clear();

			BMessage *clip = NULL;
			if ((clip = be_clipboard->Data()) != NULL) {
				// copy bitmap to clipboard
				BMessage data;
				BRect rect(0, 0, mSelection.Width(), mSelection.Height());
				BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
				BBitmap bitmap(rect, mBitmap->ColorSpace(), true);
				if (bitmap.Lock()) {
					bitmap.AddChild(&view);
					view.DrawBitmap(mBitmap, mSelection, rect);
					view.Sync();
					bitmap.RemoveChild(&view);
					bitmap.Unlock();
				}
				bitmap.Archive(&data);
				clip->AddMessage("image/x-vnd.Be-bitmap", &data);
				clip->AddRect("rect", rect);		

				// copy text to clipboard
				clip->AddData("text/plain", B_MIME_TYPE, s->getCString(), s->getLength());
				be_clipboard->Commit();
			}
			be_clipboard->Unlock();
		}
	}
}

#ifdef __MWERKS__
#pragma mark ------searching-------
#endif
///////////////////////////////////////////////////////////
volatile static bool stopFindThread = false;

typedef struct {
	BString text;
	PDFDoc *doc;
	BeOutputDev *out;
	PDFView *view;
	PDFWindow *window;
	FindTextWindow *find;
} FindThreadData;

static void SendPageMsg(FindThreadData *env, BMessage *msg, int32 page) {
	msg->AddInt32("page", page);
	env->find->PostMessage(msg);
	msg->MakeEmpty();
}

int32 find_thread(void *data) {
FindThreadData *env = (FindThreadData*)data;
	BMessage msg(FindTextWindow::FIND_SET_PAGE_MSG);
	TextOutputDev *textOut;
	int xMin, yMin, xMax, yMax;
	int selectXMin, selectYMin, selectXMax, selectYMax;
	double xMin1, yMin1, xMax1, yMax1;
	int pg;
	GBool top;
	char *s = (char*)env->text.String();
	
	bool found = false;
#if 0
	// set cursors to watch
	win->setBusyCursor(gTrue);
	findWin->setBusyCursor(gTrue);
#endif

	env->view->GetSelection(selectXMin, selectYMin, selectXMax, selectYMax);
	// search current page starting at current selection or top of page
	xMin = yMin = xMax = yMax = 0;
	if (selectXMin < selectXMax && selectYMin < selectYMax) {
		xMin = selectXMax;
		yMin = (selectYMin + selectYMax) / 2;
		top = gFalse;
	} else {
		top = gTrue;
	}
	if (env->out->findText(s, top, gTrue, &xMin, &yMin, &xMax, &yMax))
		goto found;

	// search following pages
	textOut = new TextOutputDev(NULL,
						useEUCJP ? textOutASCII7 : textOutLatin1,
						gFalse);
	if (!textOut->isOk()) {
		delete textOut;
		goto done;
	}
	for (pg = env->view->Page() + 1; pg <= env->doc->getNumPages(); ++pg) {		
		if (stopFindThread) goto done;
		SendPageMsg(env, &msg, pg);
		env->doc->displayPage(textOut, pg, 72, 0, gFalse);
		if (textOut->findText((char*)s, gTrue, gTrue, &xMin1, &yMin1, &xMax1, &yMax1))
			goto foundPage;
	}

	// search previous pages
	for (pg = 1; pg < env->view->Page(); ++pg) {
		if (stopFindThread) goto done;
		SendPageMsg(env, &msg, pg);
		env->doc->displayPage(textOut, pg, 72, 0, gFalse);
		if (textOut->findText((char*)s, gTrue, gTrue, &xMin1, &yMin1, &xMax1, &yMax1))
			goto foundPage;
	}
	delete textOut;

	// search current page ending at current selection
	if (selectXMin < selectXMax && selectYMin < selectYMax) {
		xMax = selectXMin;
		yMax = (selectYMin + selectYMax) / 2;
		if (env->out->findText(s, gTrue, gFalse, &xMin, &yMin, &xMax, &yMax))
			goto found;
	}

	// not found
	// XBell(display, 0);
	goto done;

	// found on a different page
 foundPage:
	delete textOut;
	env->window->Lock();
	env->view->SetPage(pg);
	env->window->Unlock();
	if (!env->out->findText((char*)s, gTrue, gTrue, &xMin, &yMin, &xMax, &yMax))
		goto done; // this can happen if coalescing is bad

	// found: change the selection
 found:
 	found = true;
	env->view->SetSelection(xMin, yMin, xMax, yMax);
#ifndef NO_TEXT_SELECT
	if (env->doc->okToCopy()) {
		env->view->CopySelection();
	}
#endif

 done:
 	env->window->PostMessage((uint32)(found ? FindTextWindow::TEXT_FOUND_NOTIFY_MSG : FindTextWindow::TEXT_NOT_FOUND_NOTIFY_MSG));
 	return found;
#if 0
	// reset cursors to normal
	win->setBusyCursor(gFalse);
	findWin->setBusyCursor(gFalse);
#endif
}

///////////////////////////////////////////////////////////
void PDFView::Find(const char *s, PDFWindow *window, FindTextWindow *find) {
thread_id tid;
FindThreadData *data = new FindThreadData();
	data->text = BString(s);
	data->doc = mDoc;
	data->out = mOutputDev;
	data->view = this;
	data->window = window;
	data->find = find;
	stopFindThread = false;
	
	tid = spawn_thread(find_thread, "find_thread", B_NORMAL_PRIORITY, data);
	resume_thread(tid);
}

void PDFView::StopFind() {
	stopFindThread = true;
}

#ifdef __MWERKS__
#pragma mark ------printing-------
#endif

///////////////////////////////////////////////////////////////////////////
/*
	based up Be sample code in "Be Newsletter Volume 2, Issue 18 -- May 6, 1998"
*/

status_t
PDFView::PageSetup()
{
	status_t result = B_ERROR;

	BPrintJob  printJob(this->mTitle->getCString());


	if (mPrintSettings != NULL) {
		/* page setup has already been run */
		printJob.SetSettings(new BMessage(*mPrintSettings));
	}

	result = printJob.ConfigPage();

	if (result == B_NO_ERROR) {
	
		delete (mPrintSettings);
		mPrintSettings = printJob.Settings();
	}

	return result;
}

#define ENABLE_PRINT_TRHEAD 1

#if ENABLE_PRINT_TRHEAD
///////////////////////////////////////////////////////////////////////////
class PrintView : public BView {
public:
	PrintView(PDFView *view, PDFDoc *mDoc, BMessage *printSettings, const char *title, int zoom, int rotation, BRect rect);
	~PrintView();
	void SetPage(int32 page);
	void Draw(BRect updateRect);
	friend int32 printing_thread(void *data);
private:
	PDFView *mView;
	PDFDoc *mDoc;
	BeOutputDev *mOutputDev;
	int32 mCurrentPage;
	char *mTitle;
	int mZoom;
	int mRotation;
	BMessage *mPrintSettings;
	BRect mRect;
};

///////////////////////////////////////////////////////////////////////////
PrintView::PrintView(PDFView *view, PDFDoc *doc, BMessage *printSettings, const char *title, int zoom, int rotation, BRect rect) :
	BView (BRect(1000, 1000, 1000+rect.Width(), 1000+rect.Height()), "print_view", B_FOLLOW_NONE, B_WILL_DRAW) {
	mView = view; // PDFView
	mDoc = doc; 
	mPrintSettings = printSettings;
	mTitle = (char*)title;
	mZoom = zoom; 
	mRotation = rotation;
	mRect = rect;
	mOutputDev = new BeOutputDev(this, NULL);
}

///////////////////////////////////////////////////////////////////////////
PrintView::~PrintView() {
	delete mOutputDev;
}

///////////////////////////////////////////////////////////////////////////
void PrintView::SetPage(int32 page) {
	mCurrentPage = page;
}

///////////////////////////////////////////////////////////////////////////
void
PrintView::Draw(BRect updateRect)
{
	if (Window()->Lock()) {
#if 1
		mDoc->displayPage (mOutputDev, mCurrentPage, kZoomDPI[mZoom - MIN_ZOOM], mRotation, gTrue);	
#else
		fprintf(stderr, "ok 0\n");
		mView->SetLineMode(mView->LineCapMode(), mView->LineJoinMode(), mView->LineMiterLimit());
		fprintf(stderr, "ok 1\n");
		mView->LineJoinMode();
		fprintf(stderr, "ok 2\n");
		mView->LineMiterLimit();
		fprintf(stderr, "ok 3\n");
		mView->LineCapMode();
		fprintf(stderr, "ok 6\n");
		mView->SetLineMode(mView->LineCapMode(), mView->LineJoinMode(), mView->LineMiterLimit());
		fprintf(stderr, "ok 7\n");
		StrokeRect(BRect(10, 10, 100, 200));
		// snooze(100000);
#endif
		Flush();
		Window()->Unlock();
	}
	else {
		fprintf(stderr, "fehler\n");
	}
}


// printing thread
int32 printing_thread(void *data) {
// PDFView *view = (PDFView*)data;
PrintView *view = (PrintView*)data;
	BPrintJob printJob(view->mTitle);

	printJob.SetSettings(new BMessage(*view->mPrintSettings));

	// BeOutputDev *screenDev = view->mOutputDev;
	// view->mOutputDev = new BeOutputDev(view, NULL);
	PrintingProgressWindow *progress = NULL;
	
	if (printJob.ConfigJob() == B_NO_ERROR) {
		int32  curPage = 1;
		int32  firstPage;
		int32  lastPage;
		int32  pagesInDocument;
		BRect  pageRect = printJob.PrintableRect();

		pagesInDocument = view->mDoc->getNumPages ();
		firstPage = printJob.FirstPage();
		lastPage = printJob.LastPage();

		if (lastPage > pagesInDocument) {
			lastPage = pagesInDocument;
		}
		
		progress = new PrintingProgressWindow(view->mTitle, view->mRect, lastPage - firstPage + 1);
		progress->Lock();
		progress->AddChild(view);
		progress->Unlock();
		printJob.BeginJob();

		for (curPage = firstPage; (curPage <= lastPage) && !progress->Stopped(); curPage++) {
			view->SetPage(curPage);
			progress->SetPage(curPage);
			float width = RealSize (view->mDoc->getPageWidth (curPage), kZoomDPI[view->mZoom - MIN_ZOOM]);
			float height = RealSize (view->mDoc->getPageHeight (curPage), kZoomDPI[view->mZoom - MIN_ZOOM]);

			if ((view->mDoc->getPageRotate(curPage) == 90) ||
				(view->mDoc->getPageRotate(curPage) == 270)) {
				float h = width; width = height; height = h;
			}

			if ((view->mRotation == 90) || (view->mRotation == 270)) {
				float h = width; width = height; height = h;
			}
			
			BRect curPageRect(0, 0, width, height);
			// center page
			BPoint origin((pageRect.Width() - width) / 2,
							(pageRect.Height() - height) / 2);
			printJob.DrawView(view, curPageRect, origin);
			
			printJob.SpoolPage();

			if (!printJob.CanContinue() || progress->Aborted()) {
				progress->Lock();
				progress->RemoveChild(view);
				progress->Unlock();
				goto catastrophic_exit;
			}
		}
		progress->Lock();
		progress->RemoveChild(view);
		progress->Unlock();
		printJob.CommitJob();
	}

catastrophic_exit:;
	if (progress != NULL) progress->PostMessage(B_QUIT_REQUESTED);

	// restore the page 
	int32 page = view->mView->mCurrentPage;
	view->mView->mCurrentPage = -1; // force redraw
	view->mView->Window()->Lock();
	view->mView->SetPage (page, false);
	view->mView->Window()->Unlock();

	delete view;
	return 0;
}
#endif
///////////////////////////////////////////////////////////////////////////
void 
PDFView::Print()
{
	if (mPrintSettings == NULL) {
		if (PageSetup() != B_NO_ERROR) {
			return;
		}
	}
#if ENABLE_PRINT_TRHEAD
	PrintView *pView = new PrintView(this, mDoc, mPrintSettings, mTitle->getCString(), mZoom, mRotation, Bounds());
	thread_id tid = spawn_thread(printing_thread, "printing_thread", B_NORMAL_PRIORITY, pView);
	resume_thread(tid);
#else
	BPrintJob printJob(mTitle->getCString());

	printJob.SetSettings(new BMessage(*mPrintSettings));

	BeOutputDev *currentOutputDev = mOutputDev;
	mOutputDev = new BeOutputDev(this, NULL);
	PrintingProgressWindow *progress = NULL;
	int currentPage = mCurrentPage;
	
	if (printJob.ConfigJob() == B_NO_ERROR) {
		int32  curPage = 1;
		int32  firstPage;
		int32  lastPage;
		int32  pagesInDocument;
		BRect  pageRect = printJob.PrintableRect();

		pagesInDocument = mDoc->getNumPages ();
		firstPage = printJob.FirstPage();
		lastPage = printJob.LastPage();

		if (lastPage > pagesInDocument) {
			lastPage = pagesInDocument;
		}
		
		Window()->Lock();
		progress = new PrintingProgressWindow(mTitle->getCString(), Bounds(), lastPage - firstPage + 1);
		Window()->Unlock();
		printJob.BeginJob();

		for (curPage = firstPage; (curPage <= lastPage) && !progress->Stopped(); curPage++) {
			mCurrentPage = curPage;
			progress->SetPage(curPage);
			float width = RealSize (mDoc->getPageWidth (curPage), kZoomDPI[mZoom - MIN_ZOOM]);
			float height = RealSize (mDoc->getPageHeight (curPage), kZoomDPI[mZoom - MIN_ZOOM]);

			if ((mDoc->getPageRotate(curPage) == 90) ||
				(mDoc->getPageRotate(curPage) == 270)) {
				float h = width; width = height; height = h;
			}

			if ((mRotation == 90) || (mRotation == 270)) {
				float h = width; width = height; height = h;
			}
			
			BRect curPageRect(0, 0, width, height);
			// center page
			BPoint origin((pageRect.Width() - width) / 2,
							(pageRect.Height() - height) / 2);
			printJob.DrawView(this, curPageRect, origin);
			
			printJob.SpoolPage();

			if (!printJob.CanContinue() || progress->Aborted()) {
				goto catastrophic_exit;
			}
		}
		printJob.CommitJob();
	}

catastrophic_exit:;
	if (progress != NULL) progress->PostMessage(B_QUIT_REQUESTED);
	mOutputDev = currentOutputDev;
	// restore the page 
	mCurrentPage = -1; // force redraw
	Window()->Lock();
	SetPage (currentPage, false);
	Window()->Unlock();
#endif
}

