
/* $Id: Controller.m,v 1.2 1996/10/12 14:30:34 vkyr Exp vkyr $ */

#import <streams/streams.h>
#import <objc/typedstream.h>
#import <objc/NXStringTable.h>
#import <strings.h>
#import "Controller.h"
#import "DragImageView.h"
#import "PrefsController.h"
#import "Text_Fmt.h"

@implementation Controller

char     linelen[100];
char str[100],str2[100];
static char  *info_array[50];
int    inf_cnt;


/*==========================================================
 * initialisation methods
 *==========================================================*/

- appDidInit:sender
{
	[super init];
	[myWindow setDelegate: self];
	openReq = [OpenPanel new];
	dragImage = [[NXImage alloc] initFromSection:"man.tiff"];
	[dragImageView setScaleState:YES];
	[dragImageView setImage:dragImage];
	[myWindow getFrame: &initViewSize];
	[myUnits setTitle:[prefsController whatUnit]]; // Set to DefaultsDB value
	if ([prefsController shouldShowWin] == YES)    // Startup window?
		[[myWindow display] makeKeyAndOrderFront:self];
	[self setDummyWindow];
	[self setFontsScrollView];
	[self setDummyScrollView];
	return self;
}


/*==========================================================
 * Supporting methods for accessing objects
 *==========================================================*/

- getMyWindow
{
	return myWindow;
}

- getFonts
{
    return fontsScrollView;
}

- getPages
{
    return pagesField;
}

- getHeight
{
    return heightField;
}

- getWidth
{
    return widthField;
}

- getMyUnits
{
	return myUnits;
}

- getOpenButton
{
	return openButton;
}

/*============================================================
 * Update units to be conform to the actual user selection
 *============================================================*/
 
- setMyUnits
{
	const char *itemtitle = [[myUnits selectedCell] title];
	
	if (strcasecmp(itemtitle, "Inches") == 0)
		[self inches:self];
	if (strcasecmp(itemtitle, "Cms") == 0)
		[self cms:self];
	if (strcasecmp(itemtitle, "Points") == 0)
		[self points:self];
	if (strcasecmp(itemtitle, "Picas") == 0)
		[self picas:self];
	return self;
}

- setDummyWindow
{
	// Create an invisible dummy window
	NXSetRect(&aRect, 0.0, 0.0, 350.0, 150.0);
	dummyWindow = [ [Window alloc] initContent: &aRect
		style: NX_TITLEDSTYLE backing:NX_BUFFERED
		buttonMask:NX_MINIATURIZEBUTTONMASK defer:YES];
	return self;
}

- setFontsScrollView
{
	// Create a text scrollview that holds the list of used fonts
	NXSetRect(&aRect, 0, 0, 293, 180); // Don't change these sizes!!!
	fontsScrollView = [ [ScrollView alloc] initFrame: &aRect];
	[fontsScrollView setVertScrollerRequired:YES];
	[fontsScrollView setHorizScrollerRequired:NO];
	[fontsScrollView setBorderType: NX_BEZEL];
	//[[dummyWindow contentView] addSubview: fontsScrollView];
	[fontsScrollView getContentSize: &aSize];
	// create the text object for the fontsScrollView
	NXSetRect(&aRect, 2.0, 2.0, aSize.width-2.0, aSize.height-2.0);
	fontsText = [ [Text alloc] initFrame: &aRect text: NULL
		 alignment: NX_LEFTALIGNED];
	[fontsText setOpaque:YES];
	// notify superview when frame rectangle changes -- allows scroller updates
	[fontsText notifyAncestorWhenFrameChanged:YES];
	[fontsText setEditable: NO];
	[fontsText setVertResizable:YES]; 
	[fontsText setHorizResizable:NO];
	// create min and max size of text
	aSize.width = 0.0; [fontsText setMinSize: &aSize];
	aSize.height = 1000000; [fontsText setMaxSize: &aSize];
	// set the text as docview of scrollview
	[fontsScrollView setDocView: fontsText];
	[[dummyWindow contentView] addSubview: fontsScrollView];
	return self;
}


- setDummyScrollView
{
	// Create another text scrollview to hold formated stuff for printing
	NXSetRect(&aRect, 200.0, 300.0, 350.0, 150.0);
	dummyScrollView = [ [ScrollView alloc] initFrame: &aRect];
	[dummyScrollView setVertScrollerRequired:YES];
	[dummyScrollView setHorizScrollerRequired:NO];
	//[[dummyWindow contentView] addSubview: dummyScrollView];
	[dummyScrollView getContentSize:&aSize];
	// Create another text object for this scrollview
	NXSetRect(&aRect, 0.0, 0.0, aSize.width, aSize.height);
	dummyText = [ [Text alloc] initFrame:&aRect text: NULL 
					alignment: NX_LEFTALIGNED];
	// notify superview when frame rectangle changes -- allows scroller updates
	[dummyText notifyAncestorWhenFrameChanged: YES];
	[dummyText setVertResizable: YES]; 
	[dummyText setHorizResizable: NO];
	[dummyText setMonoFont:NO];
	// create min and max size of text
	aSize.width = 0.0; 
	[dummyText setMinSize: &aSize];
	aSize.height = 1000000; 
	[dummyText setMaxSize: &aSize];
	// set the text as docview of scrollview
	[dummyScrollView setDocView: dummyText];
	[[dummyWindow contentView] addSubview: dummyScrollView];
	return self;
}

/*==========================================================
 * load InfoPanel
 *==========================================================*/

- infoPanel:sender
{
    if (infoPanel == nil) {
		[NXApp loadNibSection:"Info.nib" owner:self];
    }
    [infoPanel orderFront:sender];
    return self;
}

/*==========================================================
 * Handle Error-Messages via stringSet
 *==========================================================*/

- showError: (const char *)errorMessage
{
	NXRunAlertPanel(NULL, errorMessage,
  		[stringSet valueForStringKey:"OK"], NULL, NULL);
  return self;
}

- selectAll:sender
{
	int currentLength = [[fontsScrollView docView] textLength];
	[[fontsScrollView docView] setSel:0 :currentLength];
	return self;
}


// appAcceptsAnotherFile is an application delegate method which 
// returns whether it is OK for the application to try to open more files
// with the appOpenFile:type: method. DviInfo needs this to open dvi-files
// by double-clicking in the WM, so we return YES.

-(BOOL) appAcceptsAnotherFile:sender
{
	return (YES);
}

- openInTexview: sender
{
	[[Application workspace] openFile: [myWindow title]];
	return self;
}

/*=======================================================================
 * appOpenFile:type: is called to open the specified file. It is normally
 * called by the Application object in response to open requests from the
 * Workspace. Here we also route the open requests from the OpenPanel
 * to this method (see openRequest:).
 *=======================================================================*/

-(int) appOpenFile:(char *)fileName type:(char *)fileType
{
    return [self openDviFile:fileName];  
}

/*=======================================================================
 * openRequest: opens a new file. It puts up a open panel, and, if the user
 * doesn't cancel, it reads the specified dvi file. If the selected file
 * is not a proper dvi file, then openRequest: will complain.
 *=======================================================================*/

- openRequest:sender
{
    const char *fileName;
    const char *const types[2] = {[stringSet valueForStringKey:"extension"],
    				  NULL};

    if ([openReq runModalForTypes:types] && (fileName = [openReq filename])) {
		[self openDviFile:fileName];
    }
    else
		[self showError:[stringSet valueForStringKey:"errorOnOpenRequest"]];
    return self;
}


/*==========================================================
 * The main openDviFile action-method
 *==========================================================*/

-(int) openDviFile:(const char *)fileName
{
	FILE    *dvi_file;
	int i;

   inf_cnt = 0;
   dvi_file = fopen(fileName, "rb"); 
   if (dvi_file == NULL) 
   {
		[self showError:[stringSet valueForStringKey:"errorOnOpen"]];
		return NO;
   }
   else 
   {
		[[fontsScrollView docView] setText:""];
		[self process_dvi_file: dvi_file];
		fclose(dvi_file);
		[myWindow setTitle:fileName];
		[self setMyUnits];  // Setup correct values for the selected Unit
		[myUnits setEnabled: YES];     // Enable Units popupbutton
		[openButton setEnabled: YES];  // Enable TeXview pushbutton
		for (i = 0; i < inf_cnt; i++)
			[[fontsScrollView docView] printf: info_array[i]];
		[[myWindow display] makeKeyAndOrderFront:self];
		for (i = 0; i < inf_cnt; i++)
			free(info_array[i]);

		return YES;
    } 
}


/*=================================================
 *** PLAIN DVI-FILE-FORMAT STUFF BEGINS HERE ...***
 *================================================*/
 
#define post		248
#define post_post	249
#define dvi_id		2
#define nop			138
#define fnt_def1	243
#define fnt_def2	244
#define fnt_def3	245
#define fnt_def4	246

typedef unsigned long four_bytes;

int cur_command;
double unity;
double val, val2;

void print_dimen(four_bytes x)
{
	val = unity*x / 10000.0;
	if (val > 10)
	{
		val /= 10;
	}
}

void print_dimen2(four_bytes x)
{
	val2 = unity*x / 10000.0;
	if (val2 > 10)
	{
		val2 /= 10;
	}
}


four_bytes read_unsigned(df, bytes)
FILE *df;
int bytes;
{
	four_bytes x;
	
	x = getc(df);
	while (bytes-- >1)
	{
		x *= 256;
		x += getc(df);
	}
	return(x);
}


int reverse_getc(df)
FILE *df;
{
	fseek(df, -2, SEEK_CUR);
	return(getc(df));
}


- process_dvi_file:(FILE *)df
{
	four_bytes design_size, at_size;
	four_bytes num, den, magnification;
	four_bytes page_width, page_height;
	int		   a, l, pages;
	double	   font_size;
	fpos_t	   post_loc;	
	
	fseek(df,0,SEEK_END);					/* File-end */
											/* Ignore fillbytes i*/
	while ((cur_command = reverse_getc(df)) == 223)
		;
	if (cur_command != dvi_id) {
		[self showError:[stringSet valueForStringKey:"wrongIdByte"]];
		return self;
	}
										   /* Read backwards till post_post */
	reverse_getc(df); reverse_getc(df); reverse_getc(df);
	reverse_getc(df);	cur_command = reverse_getc(df);
	if (cur_command != post_post) {
		[self showError:[stringSet valueForStringKey:"noPostamblefound"]];
		return self;
	}
	post_loc = (fpos_t)read_unsigned(df,4);
											/* Jump to postamble */
	if (fseek(df,post_loc,SEEK_SET) != 0) {
		[self showError:[stringSet valueForStringKey:"seekError"]];
		return self;
	}
	if ((cur_command = (int) read_unsigned(df,1)) != post) {
		[self showError:[stringSet valueForStringKey:"noPostamblefound"]];
		return self;
	}
	read_unsigned(df,4);
									/* Pointer to the begin of the last page */
	num = read_unsigned(df,4);  den = read_unsigned(df,4);
	magnification = read_unsigned(df,4);
	unity = (num*((double) magnification/1000))/den;
	page_height = read_unsigned(df,4);
	page_width = read_unsigned(df,4);
	read_unsigned(df,2);							/* Ignore stacksize */
	pages = (int) read_unsigned(df,2);
	[pagesField setIntValue: pages];
	print_dimen(page_width);
	[widthField setDoubleValue: val];
	print_dimen2(page_height);
	[heightField setDoubleValue: val2];
	info_array[inf_cnt] = (char *)malloc(sizeof(linelen));
	
	while (cur_command != post_post)
	{
		int   lc = 0;
	    char  ftemp[100];

		cur_command = (int) read_unsigned(df,1);
		switch (cur_command)
		{
			case nop:
			case post_post : break;
			case fnt_def1 :
			case fnt_def2 :
			case fnt_def3 :
			case fnt_def4 :
				read_unsigned(df,cur_command-fnt_def1+1);
				read_unsigned(df,4);				/* throw away checksum */
				at_size = read_unsigned(df,4);
				design_size = read_unsigned(df,4);
				a = (int) read_unsigned(df,1);
				l = (int) read_unsigned(df,1);
				while (a-- > 0)
					info_array[inf_cnt][lc++] = getc(df);
				while (l-- > 0)
					info_array[inf_cnt][lc++] = getc(df);
				font_size = (at_size*((double)magnification/1000))/design_size;
				info_array[inf_cnt][lc] = '\0';
				sprintf(ftemp, "\t\t at magnification \t %5.3f \n", font_size);
				strcat(info_array[inf_cnt++], ftemp);
				info_array[inf_cnt] = (char *)malloc(sizeof(linelen));
				break;
		}
	}
	return self;
}
/*================================================
 *** PLAIN DVI-FILE-FORMAT STUFF ENDS HERE ... ***
 *===============================================*/


/*=======================================================================
 * Print the invisibleView (textview) via printPSCode: 
 *=======================================================================*/

- printRequest:sender
{	
    if ([[fontsScrollView docView] textLength] == 0) 
		[self showError:[stringSet valueForStringKey:"errorOnPrintRequest"]];
    else {
		[self copytext:self];
		[[[NXApp printInfo] setHorizCentered:NO] setVertCentered:NO];
		[[dummyScrollView docView] printPSCode:self];
    }
    return self;
}


/*====================================================================
 * copy formated text to invisibleView  (activated from printRequest)
 *====================================================================*/

- copytext:sender
{
	NXStream *stream;
	int pagecount, txtlen;
	double pagewidth, pageheight;
	const char *unitstr;
	
	pagecount  = [pagesField intValue];
	pagewidth  = [widthField doubleValue];
	pageheight = [heightField doubleValue];
	unitstr    = [[myUnits selectedCell] title];
	
	// clear dummyScrollView
	[[dummyScrollView docView] setText:""];
    [[dummyScrollView docView] printf:"Generated by DviInfo\n\n"];
	[[dummyScrollView docView] printf: "File: %s\n\n", [myWindow title]];
	[[dummyScrollView docView] printf:"Pages:\t\t%d\n", pagecount];
	[[dummyScrollView docView] printf: "Page width:\t%5.3f %s\n", pagewidth,  unitstr];
	[[dummyScrollView docView] printf: "Page height:\t%5.3f %s\n\n", pageheight, unitstr];
		
	txtlen = [[fontsScrollView docView] textLength];
	if (txtlen != 0)
		[[dummyScrollView docView] printf: "Used fonts are:\n\n"];
	
	/* Get the entire text, using the rich RTF format */
	stream = NXOpenMemory(NULL, 0, NX_READWRITE);
	
	if (stream)   
		[[fontsScrollView docView] writeRichText: stream];  // write to stream
		
	/* Stick the text from the fontsText into the dummyScrollView */
	if (stream)
	{
		int currentLength = [[dummyScrollView docView] textLength];
		[[dummyScrollView docView] setSel:currentLength :currentLength];
		NXSeek(stream, 0L, NX_FROMSTART);
		[[dummyScrollView docView] replaceSelWithRichText: stream];
		[[dummyScrollView docView] scrollSelToVisible];
		NXCloseMemory(stream, NX_FREEBUFFER);
	}
	
	return self;
}
	
/*======================================================
 * Methods which calculate the dvi-page units
 *======================================================*/

double unitval, newval, newval2;

- cms:sender
{
	unitval = 1;
	[widthField setDoubleValue: val];
	[heightField setDoubleValue: val2];
	return self;
}

- inches:sender
{
	unitval = 0.3937;
	[widthField setDoubleValue: val*unitval];
	[heightField setDoubleValue: val2*unitval];
	return self;
}

- points:sender
{
	unitval = 28.346;
	if ([prefsController shouldRoundUnit] == YES)
	{
		newval  = rint(val*unitval);
		newval2 = rint(val2*unitval);
	}
	else
	{
		newval  = val*unitval;
		newval2 = val2*unitval;
	}
	[widthField setDoubleValue: newval];
	[heightField setDoubleValue: newval2];
	return self;
}

- picas:sender
{
	unitval = 2.3623;
	if ([prefsController shouldRoundUnit] == YES)
	{
		newval  = rint(val*unitval);
		newval2 = rint(val2*unitval);
	}
	else
	{
		newval  = val*unitval;
		newval2 = val2*unitval;
	}
	[widthField setDoubleValue: newval];
	[heightField setDoubleValue: newval2];
	return self;
}

/*=============================================================
 * Window resizing method which adds/hides the fonts scrollview
 *=============================================================*/

- resizeWindow:sender
{
	NXRect fontsTextFrame, newWinFrame;
		
	[fontsScrollView getFrame: &fontsTextFrame];
	NX_HEIGHT(&newWinFrame) = NX_HEIGHT(&initViewSize) + 
								NX_HEIGHT(&fontsTextFrame);
    if ([resizeButton state] == 1)
	{
		[[myWindow contentView] addSubview: fontsScrollView];
		[myWindow sizeWindow:NX_WIDTH(&initViewSize) 
							:NX_HEIGHT(&newWinFrame) byCorner:3];
	}
	else
	{
		[fontsScrollView removeFromSuperview];
		[myWindow sizeWindow:NX_WIDTH(&initViewSize) 
							:NX_HEIGHT(&initViewSize) byCorner:3];
	}
    return self;
}

@end

@implementation Window(Sizing)

#define CORNER_UPPER_LEFT	0
#define CORNER_LOWER_LEFT	1
#define CORNER_UPPER_RIGHT	2
#define CORNER_LOWER_RIGHT	3

// Keep 'a' between x and y
#define CLAMP(a,x,y) (MAX((x), MIN((y), (a))))

/*=====================================================================
 * This Method resizes the receiving window as if it was dragged with 
 * the given corner. This method is useful when you want the window to 
 * resize by a corner other that the default upper right.
 *=====================================================================*/
- sizeWindow:(NXCoord)width :(NXCoord)height byCorner:(int)corner
{
	NXRect newFrame;
	NXSize minSize, maxSize;

	// Clamp width and height to their respective minimum and maximum values
	[self getMinSize:&minSize]; [self getMaxSize:&maxSize];
	width = CLAMP(width, minSize.width, maxSize.width);
	height = CLAMP(height, minSize.height, maxSize.height);

	// Set newFrame from the old frame and the new sizes
	NXSetRect(&newFrame, NX_X(&frame), NX_Y(&frame), width, height);

	// Move the respective corner by the amount of growth and set newFrame
	switch(corner) {
		case CORNER_UPPER_LEFT:
			NX_X(&newFrame) -= width - NX_WIDTH(&frame);
			break;
		case CORNER_LOWER_LEFT:
			NX_X(&newFrame) -= width - NX_WIDTH(&frame);
			NX_Y(&newFrame) -= height - NX_HEIGHT(&frame);
			break;
		case CORNER_UPPER_RIGHT:
			break;
		case CORNER_LOWER_RIGHT:
			NX_Y(&newFrame) -= height - NX_HEIGHT(&frame);
			break;
	}
	[self placeWindowAndDisplay:&newFrame];
	return self;
}
@end
