/*
 	HEWXWIN.CPP
 	Non-portable user-interface/front-end functions for wxWindows

	for the Hugo Engine

 	by Kent Tessman (c) 2000-2006

 	(Note that this file hewxwin.c contains user interface functions;
	hewx.c contains the standard engine interface routines.

	Quick structural overview:
	
	HEFrame frame - main window
	  HECanvas canvas - child of frame; drawing surface
	    wxBitmap bitmap
	    	- bitmap is twice the height of the visible canvas, so
	    	  that instead of scrolling every line, we just have to
	    	  move the "virtual window" down the bitmap; the visible
	    	  canvas is updated by blitting the virtual window to the
	    	  visible window, then moving the virtual window back to
	    	  the top
*/

#include "hewx.h"

#include "wx/config.h"
#include "wx/fontdlg.h"
#include "wx/colordlg.h"
#ifndef NO_GRAPHICS
#include "wx/image.h"
#endif

// Special Mac includes
#ifdef __WXMAC__
#include "MacMedia.h"
#ifdef SCROLLBACK_DEFINED
#ifdef __APPLE_CC__
#include <Carbon/Carbon.h>
#else
#include <Carbon.h>
#endif
#endif
#endif

#ifdef __WXMSW__
#undef GetProp
#endif

extern "C"
{
#include "heheader.h"
}

#define TYPED_STORY_COMMANDS

// This is to account for some wxWindows client-sizing weirdness
#define FIXOFFSET 4

// Window, etc.
bool engine_running = false;
HEApp *app;
HEFrame *frame;
HECanvas *canvas = NULL;
wxBitmap *bitmap = NULL;
wxMenuBar *menubar;
wxTextCtrl *scrollback = NULL;
static int window_x = 50, window_y = 50,
	window_width = 650, window_height = 500;
int fast_scrolling = 0;
int sound_enabled = 1, sound_grayedout = 0, graphics_enabled = 1;
int graphics_smoothing = 0;
char processed_accelerator_key = false;

// Fonts
#ifndef __WXMAC__
#define DEFAULT_FIXED_FACE "Courier"
#define DEFAULT_FIXED_SIZE 12
#define DEFAULT_PROP_FACE "Swiss"
#define DEFAULT_PROP_SIZE 12
#else
#define DEFAULT_FIXED_FACE "Courier New"
#define DEFAULT_FIXED_SIZE 14
#define DEFAULT_PROP_FACE "Helvetica"
#define DEFAULT_PROP_SIZE 14
#endif
#define DEFAULT_PROP_FAMILY wxSWISS
#define DEFAULT_FIXED_FAMILY wxMODERN
wxFont *fontCurrent = NULL;
char faceProp[64] = DEFAULT_PROP_FACE, faceFixed[64] = DEFAULT_FIXED_FACE;
int sizeProp = DEFAULT_PROP_SIZE, sizeFixed = DEFAULT_FIXED_SIZE;
int familyProp = DEFAULT_PROP_FAMILY, familyFixed = DEFAULT_FIXED_FAMILY;

// Colors
wxColour *def_fcolor, *def_bgcolor, *def_slfcolor, *def_slbgcolor;
wxColour *update_bgcolor;

// From herun.c:
extern "C"
{
extern int physical_lowest_windowbottom, lowest_windowbottom;
extern wxColour *current_back_color;
}

#ifdef __WXMAC__
void SetQuartzRendering(bool on);
long quartz_rendering = false;
SInt32 system_version;
#endif


// ----------------------------------------------------------------------------
// A simple C-namespace alert for debugging
// ----------------------------------------------------------------------------

extern "C" void wxAlert(char *msg)
{
	wxMessageBox(wxString::FromAscii(msg), wxT("Debugging Message"), wxOK | wxICON_EXCLAMATION);
}

extern "C" void wxAlertVal(char *msg, int val)
{
	char buf[32];
	sprintf(buf, "%s = %d", msg, val);
	wxAlert(buf);
}

// A utility string conversion function needed by wxWindows 2.3
/*
#ifdef __WXMAC__
#include <Types.h>

extern "C" void CopyCStringToPascal(const char* src, Str255 dst)
{
	short length = 0;
	
	// handle case of overlapping strings
	if ((void*)src == (void*)dst)
	{
		unsigned char* curdst = &dst[1];
		unsigned char thisChar;
				
		thisChar = *(const unsigned char*)src++;
		while (thisChar != '\0') 
		{
			unsigned char	nextChar;
			
			// use nextChar so we don't overwrite what we are about to read
			nextChar = *(const unsigned char*)src++;
			*curdst++ = thisChar;
			thisChar = nextChar;
			
			if (++length >= 255)
				break;
		}
	}
	else if (src != NULL)
	{
		unsigned char* curdst = &dst[1];
		short overflow = 255;		// count down so test it loop is faster
		register char temp;
	
		// Can't do the K&R C thing of Òwhile (*s++ = *t++)Ó because it will copy trailing zero
		// which might overrun pascal buffer.  Instead we use a temp variable.
		while ((temp = *src++) != 0) 
		{
			*(char*)curdst++ = temp;
				
			if (--overflow <= 0)
				break;
		}
		length = 255 - overflow;
	}
	dst[0] = length;
}
#endif	// __WXMAC__
*/

// ----------------------------------------------------------------------------
// TypeCommand()
// ----------------------------------------------------------------------------

void TypeCommand(char *cmd, bool clear, bool linefeed)
{					
	int i;
	
	if (clear) PushKeypress(27);
	// Each letter of the command
	for (i=0; i<(int)strlen(cmd); i++)
	{
		char c = cmd[i];
#ifdef __WXMAC__
		if ((unsigned char)c > 127)
		{
			char *k = strchr(StringANSI, c);
			if (k!=NULL) c = StringMac[k - StringANSI];
		}
#endif
		PushKeypress(c);
	}
	if (linefeed)
	{
		PushKeypress(13);
	}
	else
	{
		PushKeypress(' ');
	}
}


// ----------------------------------------------------------------------------
// Processing wxWindows events; anytime we need to peek into the wxWindows
// event queue to let it automatically take care of pending messages, call
// ProcesswxWinMessages().
// ----------------------------------------------------------------------------

void ProcesswxWinMessages(void)
{
#ifndef __WXMAC__
	app->ProcessIdle();
	if (app->Pending())
		app->Dispatch();
	else
		wxUsleep(10);
#else
	// For Mac, we have to rewrite the event loop (swiped from wxMac's
	// app.cpp) so that we can preempt certain event handling
	EventRecord event;
	
	ServiceQTMovieTasks();

	long sleepTime = 1; // GetCaretTime() / 4;

	if (WaitNextEvent(everyEvent, &event, sleepTime, (RgnHandle)app->s_macCursorRgn))
	{
		if (!CompassEvent(&event))
			app->MacHandleOneEvent(&event);
	}
	else
	{
		// idlers
		WindowPtr window = ::FrontWindow();
		if (window)
			::IdleControls(window);

		wxTheApp->ProcessIdle();
	}
	if (event.what != kHighLevelEvent)
		SetRectRgn((RgnHandle)app->s_macCursorRgn, event.where.h, event.where.v,  event.where.h + 1, event.where.v + 1);
	// repeaters
	app->DeletePendingObjects();
	wxMacProcessNotifierAndPendingEvents();
#endif
}


// ----------------------------------------------------------------------------
// Loading and saving defaults
// ----------------------------------------------------------------------------

#define MAKE_COLOR(c, rgb) \
	c = new wxColour((rgb&0xff0000)>>16, (rgb&0xff00)>>8, rgb&0xff)

void LoadDefaults(void)
{
	wxConfig *conf = new wxConfig(wxString::FromAscii(CONFIG_NAME));
	conf->SetPath(wxT("/Engine"));

	conf->Read(wxT("Xpos"), (long *)&window_x);
	conf->Read(wxT("Ypos"), (long *)&window_y);
	conf->Read(wxT("Width"), (long *)&window_width);
	conf->Read(wxT("Height"), (long *)&window_height);
	conf->Read(wxT("FastScrolling"), (long *)&fast_scrolling);
	conf->Read(wxT("Graphics"), (long *)&graphics_enabled);
	conf->Read(wxT("GraphicsSmoothing"), (long *)&graphics_smoothing);
	conf->Read(wxT("Sound"), (long *)&sound_enabled);

	conf->Read(wxT("FontFamilyProp"), (long *)&familyProp);
	conf->Read(wxT("FontFamilyFixed"), (long *)&familyFixed);
	conf->Read(wxT("FontSizeProp"), (long *)&sizeProp);
	conf->Read(wxT("FontSizeFixed"), (long *)&sizeFixed);
	wxString fontname;
	if (conf->Read(wxT("FontProp"), &fontname)) strcpy(faceProp, fontname.mb_str());
	if (conf->Read(wxT("FontFixed"), &fontname)) strcpy(faceFixed, fontname.mb_str());
#ifdef USE_SMARTFORMATTING
	conf->Read(wxT("FontSmartFormatting"), (long *)&smartformatting);
#endif
	long color;
	if (conf->Read(wxT("Foreground"), &color)) MAKE_COLOR(def_fcolor, color);
	if (conf->Read(wxT("Background"), &color)) MAKE_COLOR(def_bgcolor, color);
	if (conf->Read(wxT("SLForeground"), &color)) MAKE_COLOR(def_slfcolor, color);
	if (conf->Read(wxT("SLBackground"), &color)) MAKE_COLOR(def_slbgcolor, color);
#ifdef COMPASS_ROSE_DEFINED
	conf->Read(wxT("CompassShowing"), (long *)&compass_showing);
	conf->Read(wxT("CompassX"), (long *)&compass_x);
	conf->Read(wxT("CompassY"), (long *)&compass_y);
#endif
#ifdef __WXMAC__
	conf->Read(wxT("QuartzRendering"), &quartz_rendering);
#endif

	conf->SetPath(wxT("/"));
	delete conf;
}

void SaveDefaults(void)
{
	wxConfig *conf = new wxConfig(wxString::FromAscii(CONFIG_NAME));
	conf->SetPath(wxT("/Engine"));

	conf->Write(wxT("Xpos"), (long)window_x);
	conf->Write(wxT("Ypos"), (long)window_y);
	conf->Write(wxT("Width"), (long)window_width);
	conf->Write(wxT("Height"), (long)window_height);
	conf->Write(wxT("FastScrolling"), (long)fast_scrolling);
	conf->Write(wxT("Graphics"), (long)graphics_enabled);
	conf->Write(wxT("GraphicsSmoothing"), (long)graphics_smoothing);
	conf->Write(wxT("Sound"), (long)sound_enabled);

	wxString fontname;
	fontname = wxString::FromAscii(faceProp);
	conf->Write(wxT("FontProp"), fontname);
	fontname = wxString::FromAscii(faceFixed);
	conf->Write(wxT("FontFixed"), fontname);
	conf->Write(wxT("FontFamilyProp"), (long)familyProp);
	conf->Write(wxT("FontFamilyFixed"), (long)familyFixed);
	conf->Write(wxT("FontSizeProp"), (long)sizeProp);
	conf->Write(wxT("FontSizeFixed"), (long)sizeFixed);
#ifdef USE_SMARTFORMATTING
	conf->Write(wxT("FontSmartFormatting"), (long)smartformatting);
#endif
	long color;
	color = (long)def_fcolor->Red()<<16 | (long)def_fcolor->Green()<<8 | def_fcolor->Blue();
	conf->Write(wxT("Foreground"), color);
	color = (long)def_bgcolor->Red()<<16 | (long)def_bgcolor->Green()<<8 | def_bgcolor->Blue();
	conf->Write(wxT("Background"), color);
	color = (long)def_slfcolor->Red()<<16 | (long)def_slfcolor->Green()<<8 | def_slfcolor->Blue();
	conf->Write(wxT("SLForeground"), color);
	color = (long)def_slbgcolor->Red()<<16 | (long)def_slbgcolor->Green()<<8 | def_slbgcolor->Blue();
	conf->Write(wxT("SLBackground"), color);
#ifdef COMPASS_ROSE_DEFINED
	conf->Write(wxT("CompassShowing"), (long)compass_showing);
	conf->Write(wxT("CompassX"), (long)compass_x);
	conf->Write(wxT("CompassY"), (long)compass_y);
#endif
#ifdef __WXMAC__
	conf->Write(wxT("QuartzRendering"), quartz_rendering);
#endif

	conf->SetPath(wxT("/"));
	delete conf;
}


// ----------------------------------------------------------------------------
// The application class
// ----------------------------------------------------------------------------

int filename_arg = 1;

// Create a new application object: this macro will allow wxWindows to create
// the application object during program execution (it's better than using a
// static object for many reasons) and also declares the accessor function
// wxGetApp() which will return the reference of the right type (i.e., HEApp and
// not wxApp)
IMPLEMENT_APP(HEApp)

// `Main program' equivalent: the program execution "starts" here
bool HEApp::OnInit()
{
#ifdef __WXMAC__
	Gestalt(gestaltSystemVersion, &system_version);
#endif

	// Get the global application object
	app = this;

	// Do any default initialization before reading options
	def_fcolor = hugo_color(DEF_FCOLOR);
	def_bgcolor = hugo_color(DEF_BGCOLOR);
	def_slfcolor = hugo_color(DEF_SLFCOLOR);
	def_slbgcolor = hugo_color(DEF_SLBGCOLOR);

	// Load defaults before processing options in case they change
	LoadDefaults();

	// If help option is invoked from the command-line:
	if ((argc==2) && (!wxStrcmp(argv[1], wxT("--help")) || !wxStrcmp(argv[1], wxT("-h"))))
	{
		PrintArgs();
		exit(0);
	}
	
	// Process any other arguments, if applicable
	filename_arg = ProcessArgs();

	// Create the main application window
	sprintf(line, "Hugo Engine v%d.%d%s", HEVERSION, HEREVISION, HEINTERIM);
	frame = new HEFrame(wxString::FromAscii(line), wxPoint(window_x, window_y), wxSize(window_width, window_height));
	
	// Create the canvas
#ifndef __WXMAC__
	canvas = new HECanvas(frame, CANVAS_ID, wxDefaultPosition, wxDefaultSize);
#else
	// wxMac positioning fix
	canvas = new HECanvas(frame, CANVAS_ID, wxPoint(0, 0), wxDefaultSize);
#endif

	// Size the canvas (and create a bitmap)
	wxSizeEvent se;
	frame->OnResize(se);

	// AFTER the bitmap has been created we can set the background mode (for text output)
	hugo_settextcolor(DEF_FCOLOR);
	hugo_setbackcolor(DEF_BGCOLOR);

	// Show the frame and tell the application that it's our main window
#if defined(__WXMAC__)
#if wxCHECK_VERSION(2, 4, 0)
	ConstrainWindowToScreen((WindowPtr)canvas->MacGetRootWindow(), kWindowStructureRgn, kWindowConstrainMayResize, NULL, NULL);
#else
	ConstrainWindowToScreen((WindowPtr)canvas->GetMacRootWindow(), kWindowStructureRgn, kWindowConstrainMayResize, NULL, NULL);
//	RepositionWindow((WindowPtr)canvas->GetMacRootWindow(), NULL, kWindowCenterOnMainScreen);
#endif
#else
	// Also, until we can get the positioning to behave properly:
	frame->Centre(wxBOTH);
#endif
	frame->Show(TRUE);
	SetTopWindow(frame);
	canvas->SetFocus();

	// Set up a default font
	hugo_font(0);

	// Set up functions to be called on normal termination
	atexit(hugo_stopmusic);
	atexit(hugo_stopsample);
#ifdef VIDEO_SUPPORTED
	atexit(hugo_stopvideo);
#endif
	atexit(SaveDefaults);
#ifndef NO_SOUND
	atexit(ExitPlayer);
#endif	
#ifdef __WXMAC__
	atexit(CleanUpQTMediaHandling);
#endif

	// Instantiate the JPEG handler
#ifndef NO_GRAPHICS
	wxImage::AddHandler(new wxJPEGHandler);
#endif

	// Start the sound factory
#ifndef NO_SOUND
	if ((!sound_grayedout) && !InitPlayer()) sound_grayedout = true;
	if (sound_grayedout || !sound_enabled) SuspendAudio();
#endif

	frame->UpdateMenuItems();

	// If no filename has been provided, open a file-selection dialog
	if (filename_arg >= argc)
	{
		ChooseFile();
		filename_arg = 1;
	}
	
#ifdef __WXMAC__
	InitQTMediaHandling();
#ifdef SCROLLBACK_DEFINED
	// Init MLTE for scrollback window
	TXNInitTextension(NULL, 0, 0);
#endif
	if (quartz_rendering)
		SetQuartzRendering(true);
#endif	// __WXMAC
#ifdef COMPASS_ROSE_DEFINED
	ShowCompassRose(compass_showing);
#endif

	// Return something anyway to keep the compiler happy.
	return TRUE;
}

int HEApp::MainLoop()
{
	engine_running = true;
#if wxUSE_UNICODE
	char *c_arg[2];
	c_arg[0] = strdup(wxString(argv[filename_arg-1]).mb_str());
	c_arg[1] = strdup(wxString(argv[filename_arg]).mb_str()); 
	int r = he_main(2, (char **)&c_arg[0]);
#else
	int r = he_main(2, &argv[filename_arg-1]);
#endif
	engine_running = false;
	return r;
}

int HEApp::OnExit()
{
#if defined (__WXMAC__) && defined (SCROLLBACK_DEFINED)
	TXNTerminateTextension();
#endif
	return 0;
}

void HEApp::PrintArgs(void)
{
	Banner();
	printf("\t-g  Disable graphics\n");
	printf("\t-s  Disable sound\n");
#if wxUSE_THREADS
	printf("\t-t  Use multithreaded sound\n");
#endif
}

int HEApp::ProcessArgs(void)
{
	int i = 1;
	unsigned int j;

	while (i < argc)
	{
		// Either an option...
		if (argv[i][0]=='-')
		{
			for (j=1; j<wxStrlen(argv[i]); j++)
			{
				switch (argv[i][j])
				{
					case wxT('g'):
						graphics_enabled = false;
						break;
					case wxT('s'):
						//sound_enabled = false;
						sound_grayedout = true;
						break;
#if wxUSE_THREADS
					case wxT('t'):
						use_sound_thread = true;
						break;
#endif
					default:
						printf("%s: Invalid option: -%c\n", PROGRAM_NAME, argv[i][j]);
						exit(0);
				}
			}
		}

		// ...or we've hit the filename
		else
		{
			return i;
		}
		
		i++;
	}
	
	return i;
}

void HEApp::ChooseFile(void)
{
	// If filename gets created, it will be persistent (hence the
	// use of 'new')
	wxString *filename = 
		new wxString(wxFileSelector(wxT("Select a file to open"),
			NULL, NULL, NULL,
			wxT("Hugo executable files (*.hex)|*.hex|Hugo debuggable files (*.hdx)|*.hdx|All files (*.*)|*.*"),
			0, (wxWindow *)frame));

	if (filename->IsNull()) exit(0);

	argc = 2;
	argv[1] = (wxChar *)filename->c_str();
}

/* PrintFatalError

	Necessary because wxWindows can't just dump an error message to
	stderr--there may be no standard error output
*/

extern "C" void PrintFatalError(char *a)
{
	if (a[strlen(a)-1]=='\n') a[strlen(a)-1] = '\0';
	if (a[0]=='\n') a = &a[1];
	wxMessageBox(wxString::FromAscii(a), wxT("Hugo Engine"), wxOK | wxICON_EXCLAMATION);
	exit(255);
}


// ----------------------------------------------------------------------------
// Main canvas
// ----------------------------------------------------------------------------

BEGIN_EVENT_TABLE(HECanvas, wxWindow)
	EVT_SET_FOCUS(HECanvas::OnSetFocus)
	EVT_KILL_FOCUS(HECanvas::OnKillFocus)
	EVT_PAINT(HECanvas::OnPaint)
	EVT_MOUSE_EVENTS(HECanvas::OnMouseEvent)
	EVT_CHAR(HECanvas::OnChar)
END_EVENT_TABLE()

HECanvas::HECanvas(HEFrame *frame, wxWindowID id, const wxPoint& pos, const wxSize& size)
	: wxWindow(frame, id, pos, size, wxSUNKEN_BORDER)
{
	scroll_offset = 0;
	isactive = true;
#ifdef NO_WXCARET
	caret_drawn = false;
#endif
}

void HECanvas::OnSetFocus(wxFocusEvent& WXUNUSED(event))
{
	isactive = true;
}

void HECanvas::OnKillFocus(wxFocusEvent& WXUNUSED(event))
{
#ifndef __WXMAC__
// Mac doesn't seem to do a good job of dealing with things in the background
	isactive = false;
#endif
}

void HECanvas::OnPaint(wxPaintEvent& WXUNUSED(event))
{
	wxPaintDC dc(this);

	if (bitmap)
	{
		dc.DrawBitmap(*bitmap, 0, 0, FALSE);
	}
}

void HECanvas::OnMouseEvent(wxMouseEvent& event)
{
	if (getline_active)
	{
#ifdef USE_TEXTBUFFER
		if (event.LeftDClick() && event.ShiftDown())
		{
			int x = event.GetX();
			int y = event.GetY();
#if defined (__WXMAC__)
			// Some sort of offsetting thing to get to the bottom of on Mac
			x-=FIXOFFSET;
			y-=FIXOFFSET;
#endif
			char *w = TB_FindWord(x, y);

			if (w)
			{
				if (current_text_x + hugo_textwidth(w) < physical_windowwidth)
				{
					TypeCommand(w, false, false);
				}
			}
			return;
		}
#endif  // USE_TEXTBUFFER

#ifdef __WXMAC__
		// Macintosh may not have two buttons
		if ((event.LeftDown() || event.RightDown()) && !event.ShiftDown())
#else
		if (event.RightDown() && !event.ShiftDown())
#endif
		{			
			OnContextMenu(event);
			return;
		}
	}
	else if (event.LeftDown() || event.RightDown())
	{
		int x = event.GetX() - physical_windowleft;
		int y = event.GetY() - physical_windowtop;
		PushKeypress(1);
#if defined (__WXMAC__)
		// Some sort of offsetting thing to get to the bottom of on Mac
		x-=FIXOFFSET;
		y-=FIXOFFSET;
#endif
		PushKeypress(x/FIXEDCHARWIDTH+1);
		PushKeypress(y/FIXEDLINEHEIGHT+1);
	}
}

void HECanvas::OnContextMenu(wxMouseEvent& event)
{
#ifndef COMPILE_V25
	int i;
	static wxMenu *menu = NULL;
	
	// No valid commands in context menu
	if (!context_commands) return;

	// Context menu already active
	if (menu!=NULL) return;
	
	menu = new wxMenu();
	
	for (i=0; i<context_commands; i++)
	{
		// separator
		if (context_command[i][0]=='-')
			menu->AppendSeparator();
		
		// normal menu item
		else
		{
			context_command[i][0] = toupper(context_command[i][0]);
			menu->Append(Menu_Context+i, wxString::FromAscii(context_command[i]), wxT(""));
		}
	}
	
	PopupMenu(menu, event.m_x, event.m_y);
	
	delete menu;
	menu = NULL;
#endif
}

void HECanvas::OnChar(wxKeyEvent& event)
{
	int keycode = event.GetKeyCode();

	// No input while the scrollback window is active
	if (scrollback) return;

	switch (keycode)
	{
		case WXK_BACK:
			PushKeypress(BACKSPACE_KEY);
			break;
		case WXK_UP:
			PushKeypress(11);
			break;
		case WXK_DOWN:
			PushKeypress(10);
			break;
		case WXK_LEFT:
			if (event.m_controlDown)
				PushKeypress(CTRL_LEFT_KEY);
			else
				PushKeypress(8);
			break;
		case WXK_RIGHT:
			if (event.m_controlDown)
				PushKeypress(CTRL_RIGHT_KEY);
			else
				PushKeypress(21);
			break;
#ifdef VIDEO_SUPPORTED
		case WXK_ESCAPE:
			if (video_movie.isplaying && !video_movie.isbackground)
			{
				hugo_stopvideo();
				break;
			}
			// otherwise fall through
#endif
		default:
			// event.MetaDown() seems to catch NumLock on wxGTK 2.4
			if (event.ControlDown() || event.AltDown())// || event.MetaDown())
			{
				event.Skip();
			}
			else
				PushKeypress(keycode);
	}
}

void HECanvas::Update(bool visible)
{
	if (!needs_updating) return;

	wxMemoryDC dcMem;
	if (bitmap) dcMem.SelectObject(*bitmap);

	if (visible)
		needs_updating = 0;

	if (scroll_offset)
	{
		dcMem.Blit(0, physical_lowest_windowbottom,
			width, height-physical_lowest_windowbottom,
			&dcMem, 0, physical_lowest_windowbottom+scroll_offset, wxCOPY, FALSE);

		if (update_bgcolor)
		{
			/* Erase the unseen portion of the scroll-offset area */
			wxBrush brush = wxBrush(*update_bgcolor, wxSOLID);
			wxPen pen = wxPen(*update_bgcolor, 1, wxSOLID);
			dcMem.SetBrush(brush);
			dcMem.SetPen(pen);

			dcMem.DrawRectangle(0, height, width, height);

			dcMem.SetBrush(wxNullBrush);
			dcMem.SetPen(wxNullPen);
		}

		scroll_offset = 0;
	}

	// Update the visible DC
	if (visible)
	{
		wxClientDC dc(this);
#ifndef VIDEO_SUPPORTED
		dcMem.SelectObject(wxNullBitmap);
		dc.DrawBitmap(*bitmap, 0, 0, FALSE);
#else
		if (!video_movie.isplaying)
		{
			dcMem.SelectObject(wxNullBitmap);
			dc.DrawBitmap(*bitmap, 0, 0, FALSE);
		}
		else
		{
			Rect rect;
			rect.left = video_movie.rect.left;
			rect.top = video_movie.rect.top;
			rect.right = video_movie.rect.right+1;
			rect.bottom = video_movie.rect.bottom+1;
			
			OffsetRect(&rect, -FIXOFFSET, -FIXOFFSET);
			
			// If a movie's playing, blit around it to avoid flicker
			if (rect.bottom < canvas->height)
			{
				dc.Blit(0, rect.bottom, canvas->width, canvas->height-rect.bottom,
					&dcMem, 0, rect.bottom,
					wxCOPY, FALSE);
			}
			if (rect.top-FIXOFFSET > 0)
			{
				dc.Blit(0, 0, canvas->width, rect.top,
					&dcMem, 0, 0,
					wxCOPY, FALSE);
			}
			if (rect.left > 0)
			{
				dc.Blit(0, rect.top, rect.left, rect.bottom-rect.top,
					&dcMem, 0, rect.top,
					wxCOPY, FALSE);
			}
			if (rect.right < canvas->width)
			{
				dc.Blit(rect.right, rect.top, canvas->width-rect.right, rect.bottom-rect.top,
					&dcMem, rect.right, rect.top,
					wxCOPY, FALSE);
			}
		}
#endif	// VIDEO_SUPPORTED
#ifdef __WXMAC__
	// Force an update from OS X's double-buffer:
	if (system_version >= 0x1000)
		QDFlushPortBuffer(GetWindowPort((WindowPtr)MacGetRootWindow()) , NULL);
#endif
	}

	frame->GetSize(&window_width, &window_height);
	frame->GetPosition(&window_x, &window_y);
}

#ifdef NO_WXCARET

void HECanvas::DrawCaret()
{
	// With no parameters, DrawCaret() draws at the last-drawn position
	DrawCaret(caret_x, caret_y);
}

void HECanvas::DrawCaret(int x, int y)
{
	// Draw a caret (in inverse mode) at x, y

	wxClientDC dc(this);
	dc.SetLogicalFunction(wxINVERT);

#ifdef USE_WXRECT_CARAT
	wxBrush brush = wxBrush(*current_text_color, wxSOLID);
	wxPen pen = wxPen(*current_text_color, 1, wxSOLID);
	dc.SetBrush(brush);
	dc.SetPen(pen);
	dc.DrawRectangle(x, y, 2, 10);
	dc.SetBrush(wxNullBrush);
	dc.SetPen(wxNullPen);
#else
	long w, h;
	dc.SetFont(*fontCurrent);
	dc.GetTextExtent(wxT("|"), &w, &h);
//***	dc.SetTextForeground(*current_text_color);
	dc.DrawText(wxT("|"), x-w/2, y);
#endif
	caret_drawn = !caret_drawn;
	caret_x = x, caret_y = y;
}

#endif	// NO_WXCARET


// ----------------------------------------------------------------------------
// Main frame
// ----------------------------------------------------------------------------

BEGIN_EVENT_TABLE(HEFrame, wxFrame)
	// Window events
	EVT_SIZE(HEFrame::OnResize)
	EVT_CHAR(HECanvas::OnChar)
	EVT_LEFT_DOWN(HECanvas::OnMouseEvent)
	EVT_RIGHT_DOWN(HECanvas::OnContextMenu)
	EVT_CLOSE(HEFrame::OnClose)
	// File menu
	EVT_MENU(Menu_Open, HEFrame::OnOpen)
	EVT_MENU(Menu_Quit, HEFrame::OnQuit)
	// Story
	EVT_MENU(Menu_Restart, HEFrame::OnStory)
	EVT_MENU(Menu_Restore, HEFrame::OnStory)
	EVT_MENU(Menu_Save, HEFrame::OnStory)
	EVT_MENU(Menu_Undo, HEFrame::OnStory)
	// Options
	EVT_MENU(Menu_FixedFont, HEFrame::OnFont)
	EVT_MENU(Menu_PropFont, HEFrame::OnFont)
	EVT_MENU(Menu_DefaultFonts, HEFrame::OnFont)
	EVT_MENU(Menu_SmartFormatting, HEFrame::OnFont)
#ifdef __WXMAC__
	EVT_MENU(Menu_QuartzRendering, HEFrame::OnQuartzRendering)
#endif
	EVT_MENU(Menu_ForeColor, HEFrame::OnColor)
	EVT_MENU(Menu_BackColor, HEFrame::OnColor)
	EVT_MENU(Menu_SLForeColor, HEFrame::OnColor)
	EVT_MENU(Menu_SLBackColor, HEFrame::OnColor)
	EVT_MENU(Menu_RestoreColors, HEFrame::OnColor)
	EVT_MENU(Menu_FastScrolling, HEFrame::OnFastScrolling)
	EVT_MENU(Menu_Graphics, HEFrame::OnGraphics)
	EVT_MENU(Menu_GraphicsSmoothing, HEFrame::OnGraphicsSmoothing)
	EVT_MENU(Menu_Sound, HEFrame::OnSound)
	EVT_MENU(Menu_Unfreeze, HEFrame::OnUnfreeze)
	EVT_MENU(Menu_ResetDisplay, HEFrame::OnResetDisplay)
#ifdef COMPASS_ROSE_DEFINED
	EVT_MENU(Menu_CompassRose, HEFrame::OnCompassRose)
#endif
	EVT_MENU(Menu_Scrollback, HEFrame::OnScrollback)
	// Help
	EVT_MENU(Menu_About, HEFrame::OnAbout)
#ifndef COMPILE_V25
	// Context menu commands
	EVT_MENU_RANGE(Menu_Context, (Menu_Context+MAX_CONTEXT_COMMANDS), HEFrame::HandleContextCommand)
#endif
END_EVENT_TABLE()

// frame constructor
HEFrame::HEFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
       : wxFrame((wxFrame *)NULL, -1, title, pos, size)
{
	// create a menu bar
	wxMenu *menuFile = new wxMenu();
	menuFile->Append(Menu_Open, wxT("&Open New Story...\tCtrl+O"), wxT("Open a new story"));
	menuFile->AppendSeparator();
	menuFile->Append(Menu_Quit, wxT("&Quit\tCtrl+Q"), wxT("Quit this program"));

	wxMenu *menuStory = new wxMenu();
	menuStory->Append(Menu_Restart, wxT("R&estart Current Story"), wxT("Restart current story"));
	menuStory->AppendSeparator();
	menuStory->Append(Menu_Restore, wxT("&Restore Story...\tCtrl+R"), wxT("Restore a previously saved story"));
	menuStory->Append(Menu_Save, wxT("&Save Story...\tCtrl+S"), wxT("Save current story position"));
	menuStory->AppendSeparator();
	menuStory->Append(Menu_Undo, wxT("&Undo Last Turn\tCtrl+Z"), wxT("Undo last turn"));

	wxMenu *menuOptions = new wxMenu();
	wxMenu *menuFont = new wxMenu();
	menuFont->Append(Menu_FixedFont, wxT("&Fixed width..."), wxT("Fixed-width font"));
	menuFont->Append(Menu_PropFont, wxT("&Proportional..."), wxT("Proportional font"));
	menuFont->AppendSeparator();
	menuFont->Append(Menu_DefaultFonts, wxT("&Restore default fonts"), wxT("Restore default fonts"));
#ifdef USE_SMARTFORMATTING
	menuFont->AppendSeparator();
	menuFont->Append(Menu_SmartFormatting, wxT("&Smart formatting"), wxT("Smart formatting"), TRUE);
#endif
#ifdef __WXMAC__
	SInt32 version;
	Gestalt(gestaltSystemVersion, &version);
	// Quartz text rendering from Carbon is only available with 10.1.5 and later
	if (version >= 0x1015)
		menuFont->Append(Menu_QuartzRendering, wxT("&Quartz rendering"), wxT("Quartz rendering"), TRUE);
#endif
	menuOptions->Append(Menu_Fonts, wxT("&Fonts"), menuFont, wxT("Select fonts"));
	wxMenu *menuColor = new wxMenu();
	menuColor->Append(Menu_ForeColor, wxT("&Foreground..."), wxT("Default text color"));
	menuColor->Append(Menu_BackColor, wxT("&Background..."), wxT("Default background color"));
	menuColor->Append(Menu_SLForeColor, wxT("&Statusline foreground..."), wxT("Default statusline text color"));
	menuColor->Append(Menu_SLBackColor, wxT("S&tatusline background..."), wxT("Default statusline background color"));
	menuColor->AppendSeparator();
	menuColor->Append(Menu_RestoreColors, wxT("&Restore default colors"), wxT("Restore default colors"));
	menuOptions->Append(Menu_Colors, wxT("C&olors"), menuColor, wxT("Select (default only) colors"));
	menuOptions->AppendSeparator();
	menuOptions->Append(Menu_FastScrolling, wxT("F&ast Scrolling"), wxT("Fast text updating on or off"), TRUE);
	menuOptions->Append(Menu_Graphics, wxT("Display &Graphics"), wxT("Graphics display on or off"), TRUE);
	menuOptions->Append(Menu_GraphicsSmoothing, wxT("Graphics S&moothing"), wxT("Graphics smoothing on or off"), true);
	menuOptions->Append(Menu_Sound, wxT("&Play Sounds and Music\tCtrl+P"), wxT("Sound/music playback on or off"), TRUE);
	menuOptions->AppendSeparator();
	menuOptions->Append(Menu_Unfreeze, wxT("&Unfreeze Windows\tCtrl+U"), wxT("Unfreeze existing windows"));
	menuOptions->Append(Menu_ResetDisplay, wxT("&Reset Display\tCtrl+D"), wxT("Erase existing display"));
	menuOptions->AppendSeparator();
#ifdef COMPASS_ROSE_DEFINED
	menuOptions->Append(Menu_CompassRose, wxT("Show &Compass Rose\tCtrl+C"), wxT("Show compass rose"), TRUE);
#endif
	menuOptions->Append(Menu_Scrollback, wxT("Show Scro&llback Window\tCtrl+L"), wxT("Show scrollback window"), TRUE);
#ifndef SCROLLBACK_DEFINED
	menuOptions->Enable(Menu_Scrollback, FALSE);
#endif

	wxMenu *menuHelp = new wxMenu();
#ifndef __WXMAC__
	menuHelp->Append(Menu_About, wxT("&About..."), wxT("Show \"About\" box"));
#else
	// Have a Mac-consistent Apple menu style
	menuHelp->Append(Menu_About, wxT("&About the Hugo Engine..."), wxT("Show \"About\" box"));
#endif

	// now append the freshly created menus to the menu bar...
	menubar = new wxMenuBar;
	menubar->Append(menuFile, wxT("&File"));
	menubar->Append(menuStory, wxT("&Story"));
	menubar->Append(menuOptions, wxT("&Options"));
	menubar->Append(menuHelp, wxT("&Help"));
#ifdef __WXMAC__
	// wxMac will automatically move Menu_About to the Apple menu
	app->s_macAboutMenuItemId = Menu_About;
#endif
		
	// ... and attach this menu bar to the frame
	SetMenuBar(menubar);
	UpdateMenuItems();
	
	// create a status bar (with 1 pane only)
//  	CreateStatusBar(1);
//  	SetStatusText("Hugo Engine");
}

void HEFrame::UpdateMenuItems(void)
{
#ifdef __WXMAC__
	// Mac won't allow us to launch another instance
	menubar->Enable(Menu_Open, FALSE);
	menubar->Check(Menu_QuartzRendering, quartz_rendering);
#endif
#ifdef USE_SMARTFORMATTING
	menubar->Check(Menu_SmartFormatting, smartformatting?TRUE:FALSE);
#endif
	menubar->Check(Menu_FastScrolling, fast_scrolling?TRUE:FALSE);
#ifdef NO_GRAPHICS
	menubar->Enable(Menu_Graphics, FALSE);
	menubar->Check(Menu_Graphics, FALSE);
	menubar->Enable(Menu_GraphicsSmoothing, FALSE);
	menubar->Check(Menu_GraphicsSmoothing, FALSE);
#else
	menubar->Check(Menu_Graphics, graphics_enabled?TRUE:FALSE);
	menubar->Enable(Menu_GraphicsSmoothing, graphics_enabled?TRUE:FALSE);
	menubar->Check(Menu_GraphicsSmoothing, graphics_smoothing?TRUE:FALSE);
#endif
#ifdef NO_SOUND
	menubar->Enable(Menu_Sound, FALSE);
	menubar->Check(Menu_Sound, FALSE);
#else
	menubar->Check(Menu_Sound, sound_enabled && !sound_grayedout);
	menubar->Enable(Menu_Sound, sound_grayedout?FALSE:TRUE);
#endif
#ifdef COMPASS_ROSE_DEFINED
	menubar->Check(Menu_CompassRose, compass_showing?TRUE:FALSE);
#endif
	menubar->Check(Menu_Scrollback, scrollback?TRUE:FALSE);
}

void VerifyBitmapResize(wxBitmap *bmp)
{
	if (!bmp->Ok())
	{
		// Should probably only ever see this on a Mac
		wxMessageBox(
wxT("There is not enough memory to properly resize the Hugo display.  If necessary, try freeing \
memory by closing other programs, or by increasing the memory available to the Hugo Engine."),
			wxT("Hugo Engine"), wxOK | wxICON_EXCLAMATION);
	}
}

void HEFrame::OnResize(wxSizeEvent& WXUNUSED(event))
{
	wxMemoryDC dcMem;
	wxMemoryDC dcCopy;
	wxBitmap *bitmapCopy = NULL;

	if (!canvas) return;

	// Copy the existing bitmap before we zotz it
	if (bitmap)
	{
		bitmapCopy = new wxBitmap(bitmap->GetWidth()-FIXOFFSET, bitmap->GetHeight()-FIXOFFSET);
//		VerifyBitmapResize(bitmapCopy);
		dcCopy.SelectObject(*bitmapCopy);
//		dcCopy.Blit(0, 0, bitmap->GetWidth(), bitmap->GetHeight(), dcMem, 0, 0);
		dcCopy.DrawBitmap(*bitmap, 0, 0, FALSE);
	}

	// Get rid of the old bitmap and create a new resized one
	if (bitmap) delete bitmap;
	GetClientSize(&canvas->width, &canvas->height);
	bitmap = new wxBitmap(canvas->width, canvas->height*2);
	VerifyBitmapResize(bitmap);
	dcMem.SelectObject(*bitmap);
	dcMem.Clear();

	// Copy the contents of the old bitmap back to the (new) memory bitmap
	if (bitmapCopy)
	{
		dcMem.DrawBitmap(*bitmapCopy, 0, 0, FALSE);
		delete bitmapCopy;
	}

	// Resize the canvas if it's been created
	if (canvas) canvas->SetSize(canvas->width, canvas->height);

	canvas->height-=FIXOFFSET;
	canvas->width-=FIXOFFSET;
#ifdef __WXMAC__
	canvas->height--;
	canvas->width--;
#endif

	// See if we need to shift the bitmap up if the window
	// has become smaller
	if (current_text_y+lineheight > canvas->height)
	{
		dcMem.Blit(0, 0, canvas->width, canvas->height*2,
			&dcMem, 0, current_text_y+lineheight-canvas->height);
		dcMem.SelectObject(wxNullBitmap);

/* There's probably a good reason why this isn't working, but the height*2
   above makes it unnecessary
*/
 		wxBrush brush = wxBrush(*current_back_color, wxSOLID);
		wxPen pen = wxPen(*current_back_color, 1, wxSOLID);
		dcMem.SetBrush(brush);
		dcMem.SetPen(pen);
		dcMem.DrawRectangle(0, canvas->height, canvas->width, canvas->height);
	}

	// If the text display has already been initialized, then recalculate
	// for the new canvas dimensions
	if (FIXEDCHARWIDTH)
	{
		hugo_settextmode();

		// Do engine-internal post-resize metric tweaking here, too
		if (!inwindow)
		{
			physical_windowright = canvas->width;
			physical_windowwidth = canvas->width - physical_windowleft + 1;
			physical_windowbottom = canvas->height;
			physical_windowheight = canvas->height - physical_windowtop + 1;

			if (currentline > physical_windowheight/lineheight)
				currentline = physical_windowheight / lineheight;
		}
	}

	display_needs_repaint = true;

	canvas->needs_updating = true;
	canvas->Update(TRUE);

	if (scrollback) scrollback->SetSize(canvas->width, canvas->height);
}

// If autoclose is 0, OnQuit will be called before closing
#ifdef __WXMAC__
char autoclose = 0;
#else
char autoclose = 1;
#endif

void HEFrame::OnClose(wxCloseEvent& event)
{
	if (autoclose)
		exit(0);
	else
	{
		wxCommandEvent wce;
		OnQuit(wce);
	}
}

void HEFrame::HandleContextCommand(wxCommandEvent& event)
{
#ifndef COMPILE_V25
	char *cc;
	int i, n, noreturn = 0;
	
	n = event.m_id - Menu_Context;
	
	cc = context_command[n];
	
	// If command ends with "...", don't push 
	// Enter at the end
	if ((strlen(cc)>=4) && !strcmp(cc+strlen(cc)-3, "..."))
	{
		noreturn = 1;
	}

	// Esc to clear input
	PushKeypress(27);
	// Each letter of the command
	for (i=0; i<(int)strlen(cc)-(noreturn?4:0); i++)
		PushKeypress(cc[i]);

	if (!noreturn)
		// Enter
		PushKeypress(13);
	else
	{
		PushKeypress(cc[i]);
		PushKeypress(' ');
	}
#endif  // !COMPILE_V25
}


// ----------------------------------------------------------------------------
// Menu functions from here down
// ----------------------------------------------------------------------------

void HEFrame::OnOpen(wxCommandEvent& WXUNUSED(event))
{
	// To make sure any changes are available to the new instance
	SaveDefaults();

	// argv[0] should contain the path and name of this executable
	// as launched
	wxString arg(app->argv[0]);
	wxExecute(arg);
}

void HEFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
{
	if ((!engine_running) || wxMessageBox(
		wxT("Are you sure you want to abandon the current story?"),
		wxT("Quit Current Story"),
		wxYES_NO | wxICON_EXCLAMATION)==wxYES)
	{
		autoclose = 1;
		SaveDefaults();
		Close(TRUE);
	}
}

void HEFrame::OnStory(wxCommandEvent &event)
{
	int r = 0;
	
	if (!during_player_input)
	{
		wxMessageBox(wxT("Only valid at command input"),
			wxT("Story Operation"),
			wxOK | wxICON_EXCLAMATION);
		return;
	}

	if (scrollback)
	{
		wxMessageBox(wxT("You must close the scrollback window first"),
			wxT("Story Operation"),
			wxOK | wxICON_EXCLAMATION);
		return;
	}
	
	getline_active = false;
			
	/* The printing and new-prompt simulation are because the normal 
	 * flow of a Hugo program expects "save", "restore", etc. to be 
	 * entered as typed commands.
	 */
#ifdef TYPED_STORY_COMMANDS
	// Other commands are entered as if they were typed
	if (event.GetId()==Menu_Restart)
#endif
		Printout((char *)"");

	switch(event.GetId())
	{
		case Menu_Restart:
			if (wxMessageBox(
				wxT("Are you sure you wish to abandon the current story?"),
				wxT("Restart Current Story"),
				wxICON_EXCLAMATION | wxYES_NO,
				frame)==wxYES)
			{
#ifdef __WXMAC__
				HiliteMenu(0);
#endif
				// Font may change
				int tf = currentfont &= ~ITALIC_FONT;
				// So restarts aren't nested
				during_player_input = false;
				r = RunRestart();
				during_player_input = true;
				hugo_font(tf);
			}
			else
			{
				Printout((char *)"[Restart cancelled]");
				goto NewPrompt;
			}
			break;

#ifndef TYPED_STORY_COMMANDS
		case Menu_Restore:
			Printout((char *)"[RESTORE]");
			r = RunRestore();
			break;
		case Menu_Save:
			Printout((char *)"[SAVE]");
			r = RunSave();
			break;
		case Menu_Undo:
			Printout((char *)"[UNDO]");
			r = Undo();
			break;
#else
		case Menu_Restore:
			getline_active = true;
			TypeCommand((char *)"Restore", true, true);
			return;
		case Menu_Save:
			getline_active = true;
			TypeCommand((char *)"Save", true, true);
			return;
		case Menu_Undo:
			getline_active = true;
			TypeCommand((char *)"Undo", true, true);
			return;
#endif
	}


	if (!r && event.GetId()!=Menu_Undo)
	{
		wxMessageBox(wxT("Unable to perform file operation"),
			wxT("File Error"), wxOK | wxICON_EXCLAMATION, frame);
	}
#ifndef TYPED_STORY_COMMANDS
	else if (!r)
	{
		wxMessageBox(wxT("Unable to undo"), 
			wxT("Undo Error"), wxOK | wxICON_EXCLAMATION, frame);
	}
#endif

NewPrompt:
	getline_active = true;
	
	// Simulate a new prompt a la GetCommand()
	full = 0;
	hugo_settextpos(1, physical_windowheight/lineheight+1);
	hugo_print((char *)"\n");
	hugo_print(GetWord(var[prompt]));
	FlushBuffer();
	ConstrainCursor();
	processed_accelerator_key = true;
	canvas->needs_updating = true;
	canvas->Update(TRUE);

	full_buffer = false;
}

void HEFrame::OnFont(wxCommandEvent &event)
{
	if (event.GetId()==Menu_DefaultFonts)
	{
		strcpy(faceFixed, DEFAULT_FIXED_FACE);
		familyFixed = DEFAULT_FIXED_FAMILY;
		sizeFixed = DEFAULT_FIXED_SIZE;
		strcpy(faceProp, DEFAULT_PROP_FACE);
		familyProp = DEFAULT_PROP_FAMILY;
		sizeProp = DEFAULT_PROP_SIZE;

		display_needs_repaint = true;
		
		return;
	}
#ifdef USE_SMARTFORMATTING
	else if (event.GetId()==Menu_SmartFormatting)
	{
		smartformatting = !smartformatting;
		UpdateMenuItems();
		return;
	}
#endif

	wxFont initial;
	wxFontData data;
	char facename[64];
	int family, size;

	if (event.GetId()==Menu_FixedFont)
	{
		strcpy(facename, faceFixed);
		family = familyFixed;
		size = sizeFixed;
	}
	else
	{
		strcpy(facename, faceProp);
		family = familyProp;
		size = sizeProp;
	}
	initial.SetFaceName(wxString::FromAscii(facename));
	initial.SetFamily(family);
	initial.SetPointSize(size);
	data.SetInitialFont(initial);

	wxFontDialog dialog((wxWindow *)this, &data);
	if (dialog.ShowModal()==wxID_OK)
	{
#ifdef NO_WXCARET
		if (canvas->caret_drawn) canvas->DrawCaret();	
#endif
		wxFontData selected = dialog.GetFontData();
		wxFont font = wxFont(selected.GetChosenFont());
		const char *name = font.GetFaceName().mb_str();
		
		if (event.GetId()==Menu_FixedFont)
		{
			strcpy(faceFixed, name);
			familyFixed = font.GetFamily();
			sizeFixed = font.GetPointSize();
		}
		else
		{
			strcpy(faceProp, name);
			familyProp = font.GetFamily();
			sizeProp = font.GetPointSize();
		}

		display_needs_repaint = true;
	}
}


#ifdef __WXMAC__
static CFBundleRef getBundle(CFStringRef frameworkPath)
{
	CFBundleRef bundle = NULL;
    
	//	Make a CFURLRef from the CFString representation of the bundle's path.
	//	See the Core Foundation URL Services chapter for details.
	CFURLRef bundleURL = CFURLCreateWithFileSystemPath(NULL, frameworkPath, kCFURLPOSIXPathStyle, true);
	if (bundleURL != NULL) {
        bundle = CFBundleCreate(NULL, bundleURL);
        if (bundle != NULL)
        	CFBundleLoadExecutable(bundle);
        CFRelease(bundleURL);
	}
	
	return bundle;
}

static void* getQDFunction(CFStringRef functionName)
{
  static CFBundleRef systemBundle = getBundle(CFSTR("/System/Library/Frameworks/ApplicationServices.framework"));
  if (systemBundle)
    return CFBundleGetFunctionPointerForName(systemBundle, functionName);
  return NULL;
}

void SetQuartzRendering(bool on)
{
	// from Apple's technote
	enum
	{
		kQDDontChangeFlags = 0xFFFFFFFF,         // don't change anything
		kQDUseDefaultTextRendering = 0,          // bit 0
		kQDUseTrueTypeScalerGlyphs = (1 << 0),   // bit 1
		kQDUseCGTextRendering = (1 << 1),        // bit 2
		kQDUseCGTextMetrics = (1 << 2)
	};

	// turn on quartz rendering if we find the symbol in the app framework. Just turn
	// on the bits that we need, don't turn off what someone else might have wanted
	typedef UInt32 (*qd_procptr)(UInt32);  
	qd_procptr SwapQDTextFlags = (qd_procptr) getQDFunction(CFSTR("SwapQDTextFlags"));
	if (SwapQDTextFlags)
	{
		UInt32 oldFlags = SwapQDTextFlags(kQDDontChangeFlags);
		if (on)
			SwapQDTextFlags(oldFlags | kQDUseTrueTypeScalerGlyphs | kQDUseCGTextRendering);
		else
			SwapQDTextFlags(oldFlags & (~kQDUseTrueTypeScalerGlyphs) & (~kQDUseCGTextRendering));
	}
}

void HEFrame::OnQuartzRendering(wxCommandEvent& WXUNUSED(event))
{
	quartz_rendering = !quartz_rendering;
	SetQuartzRendering(quartz_rendering);
	UpdateMenuItems();
}
#endif	// __WXMAC__


void HEFrame::OnColor(wxCommandEvent &event)
{
	static wxColourData colordata;
	wxColour *color;
	
	switch (event.GetId())
	{
		case Menu_RestoreColors:
			def_fcolor = hugo_color(DEF_FCOLOR);
			def_bgcolor = hugo_color(DEF_BGCOLOR);
			def_slfcolor = hugo_color(DEF_SLFCOLOR);
			def_slbgcolor = hugo_color(DEF_SLBGCOLOR);
			return;
		case Menu_ForeColor:
			color = def_fcolor;
			break;
		case Menu_BackColor:
			color = def_bgcolor;
			break;
		case Menu_SLForeColor:
			color = def_slfcolor;
			break;
		case Menu_SLBackColor:
			color = def_slbgcolor;
			break;
		default:
			wxLogWarning(wxT("Invalid OnColor ID"));
			return;
	}

	colordata.SetColour(*color);
	wxColourDialog dialog(this, &colordata);
	if (dialog.ShowModal()==wxID_OK)
	{
		// Get the selected color and store it
		wxColourData newcolordata = wxColourData(dialog.GetColourData());
		*color = newcolordata.GetColour();

		// Also save the current custom colors
		int i;
		for (i=0; i<16; i++)
		{
			wxColour cc = wxColour(newcolordata.GetCustomColour(i));
			colordata.SetCustomColour(i, cc);
		}

		display_needs_repaint = true;
	}
}

void HEFrame::OnFastScrolling(wxCommandEvent& WXUNUSED(event))
{
	fast_scrolling = !fast_scrolling;
	UpdateMenuItems();
}

void HEFrame::OnGraphics(wxCommandEvent& WXUNUSED(event))
{
	graphics_enabled = !graphics_enabled;
	UpdateMenuItems();
	display_needs_repaint = true;
}

void HEFrame::OnGraphicsSmoothing(wxCommandEvent& WXUNUSED(event))
{
	graphics_smoothing = !graphics_smoothing;
	UpdateMenuItems();
}

void HEFrame::OnSound(wxCommandEvent& WXUNUSED(event))
{
	sound_enabled = !sound_enabled;
#ifndef NO_SOUND
	if (sound_enabled)
		ResumeAudio();
	else
		SuspendAudio();
#endif
	UpdateMenuItems();
}

void HEFrame::OnResetDisplay(wxCommandEvent& WXUNUSED(event))
{
	if (wxMessageBox(wxT("Erase existing display?"), wxT("Reset Display"),
		wxICON_EXCLAMATION | wxYES_NO)==wxYES)
	{
		hugo_clearfullscreen();
		canvas->Update(TRUE);
	}
}

void HEFrame::OnUnfreeze(wxCommandEvent& WXUNUSED(event))
{
	hugo_settextwindow(1, 1,
		SCREENWIDTH/FIXEDCHARWIDTH,
		SCREENHEIGHT/FIXEDLINEHEIGHT);
	physical_lowest_windowbottom = lowest_windowbottom = 0;
	ConstrainCursor();
	canvas->Update(TRUE);
}

void HEFrame::OnCompassRose(wxCommandEvent& WXUNUSED(event))
{
#ifdef COMPASS_ROSE_DEFINED
	ShowCompassRose(compass_showing?false:true);
	UpdateMenuItems();
#endif
}

void HEFrame::OnScrollback(wxCommandEvent& WXUNUSED(event))
{
	// OnScrollback basically toggles between creating and destroying
	// a text control that covers the existing canvas

#ifdef SCROLLBACK_DEFINED
#ifdef __WXMAC__
	ShowScrollback();
#else
	if (scrollback)
	{
		scrollback->Close();
		delete scrollback;
		scrollback = NULL;
		frame->SetFocus();
	}
	else
	{
		scrollback = new wxTextCtrl(this, SCROLLBACK_ID,
			scrollback_buffer,
#ifndef __WXMAC__
			wxPoint(0, 0), wxSize(canvas->width, canvas->height),
#else
			wxPoint(0, 0), wxSize(canvas->width+FIXOFFSET, canvas->height+FIXOFFSET),
#endif
			wxTE_READONLY | wxTE_MULTILINE | wxHSCROLL | wxVSCROLL);

		// Ensure that we'll be narrower than the main window
/* wxGTK seems to end up with a random font this way:
		wxFont font = scrollback->GetFont();
		font.SetPointSize(font.GetPointSize()-1);
		scrollback->SetFont(font);
*/
		wxFont font = wxFont(sizeProp-1, wxSWISS, wxNORMAL, wxNORMAL);
		scrollback->SetFont(font);
		
		scrollback->MakeModal(TRUE);
		scrollback->SetInsertionPointEnd();
		scrollback->Show(TRUE);
	}
#endif	// __WXMAC__
	UpdateMenuItems();
#endif	// SCROLLBACK_DEFINED
}

void HEFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
	wxString msg;
	
#ifndef __WXMAC__
	msg.Printf(
wxT("Hugo %s v%d.%d%s - wxWidgets build (%s)\n")
wxT("Copyright (c) 1995-2006 by Kent Tessman\n")
wxT("The General Coffee Company Film Productions\n\n")
//"JPEG graphics display based in part on the work of the Independent JPEG Group.  "
//"MikMod sound system used under license.\n\n"
wxT("All rights reserved.  Please see the Hugo License for details.")
,
#else
	msg.Printf(
wxT("Hugo %s v%d.%d%s for Macintosh (%s)\n")
wxT("(c) 1995-2006 by Kent Tessman\n")
wxT("The General Coffee Company Film Productions\n\n")
// wxMac links these statically:
wxT("JPEG graphics display based in part on the work of the Independent JPEG Group.  ")
wxT("MikMod sound system used under license.")
// wxMac's dialog box truncates this if it's included:
//"All rights reserved.  Please see the Hugo License for details."),
,
#endif
#if defined (DEBUGGER)
		   wxT("Debugger"),
#else
		   wxT("Engine"),
#endif
		   HEVERSION, HEREVISION, wxT(HEINTERIM), __TDATE__);	       
	
	wxMessageBox(msg, wxT("About the Hugo Engine"), wxOK | wxICON_INFORMATION, this);
}
