/*

gbmv2.c - Display a bitmap

*/

/*...sincludes:0:*/
#define	INCL_DOS
#define	INCL_WIN
#define	INCL_GPI
#define	INCL_DEV
#define	INCL_SPL
#define	INCL_SPLDOSPRINT
#define	INCL_BITMAPFILEFORMAT
#include <os2.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <malloc.h>
#include <memory.h>
#include <process.h>
#include "scroll.h"
#include "gbm.h"
#include "gbmdlg.h"
#include "gbmdlgrc.h"
#include "gbmv2.h"
#include "gbmv2hlp.h"
#include "model.h"
#include "bmputils.h"
#include "help.h"

/*...vscroll\46\h:0:*/
/*...vgbm\46\h:0:*/
/*...vgbmdlg\46\h:0:*/
/*...vgbmdlgrc\46\h:0:*/
/*...vgbmv2\46\h:0:*/
/*...vgbmv2hlp\46\h:0:*/
/*...vmodel\46\h:0:*/
/*...vbmputils\46\h:0:*/
/*...vhelp\46\h:0:*/
/*...e*/
/*...ssuppress warnings:0:*/
/* When using VisualAge C++, it complains about the definition of
   MPFROM2SHORT (well actually MAKELONG), so I've coded a replacement. */

#undef MPFROM2SHORT
#define	MY_MAKELONG(l,h) ((ULONG)(((USHORT)(l)) | ( ((ULONG)((USHORT)(h))) << 16 ) ))
#define	MPFROM2SHORT(l,h) ((MPARAM) MY_MAKELONG(l,h))
/*...e*/
/*...suseful:0:*/
/* Windows has these 2 */

/*...sEnableMenuItem:0:*/
static VOID EnableMenuItem(HWND hWndMenu, SHORT idMenuItem, BOOL fEnabled)
	{
	WinSendMsg(hWndMenu, MM_SETITEMATTR,
		   MPFROM2SHORT(idMenuItem, TRUE),
		   MPFROM2SHORT(MIA_DISABLED, ( fEnabled ) ? 0 : MIA_DISABLED));
	}
/*...e*/
/*...sCheckMenuItem:0:*/
static VOID CheckMenuItem(HWND hWndMenu, SHORT idMenuItem, BOOL fChecked)
	{
	WinSendMsg(hWndMenu, MM_SETITEMATTR,
		   MPFROM2SHORT(idMenuItem, TRUE),
		   MPFROM2SHORT(MIA_CHECKED, ( fChecked ) ? MIA_CHECKED : 0));
	}
/*...e*/

/* These are generally useful */

/*...sWarning:0:*/
static VOID Warning(HWND hwnd, const CHAR *szFmt, ...)
	{
	va_list vars;
	CHAR sz[256+1];

	va_start(vars, szFmt);
	vsprintf(sz, szFmt, vars);
	va_end(vars);
	WinMessageBox(HWND_DESKTOP, hwnd, sz, NULL, 0, MB_OK | MB_WARNING | MB_MOVEABLE);
	}
/*...e*/

/*...sTidySysMenu:0:*/
static VOID TidySysMenu(HWND hwndFrame)
	{
	HWND hwndSysMenu = WinWindowFromID(hwndFrame, FID_SYSMENU);
	USHORT id        = SHORT1FROMMR(WinSendMsg(hwndSysMenu, MM_ITEMIDFROMPOSITION, MPFROMSHORT(0), NULL));
	MENUITEM mi;
	HWND hwndSysSubMenu;
	SHORT sInx, cItems;
	
	WinSendMsg(hwndSysMenu, MM_QUERYITEM, MPFROM2SHORT(id, FALSE), MPFROMP(&mi));
	hwndSysSubMenu = mi.hwndSubMenu;
	
	cItems = SHORT1FROMMR(WinSendMsg(hwndSysSubMenu, MM_QUERYITEMCOUNT, NULL, NULL));
	
	for ( sInx = cItems - 1; sInx >= 0; sInx-- )
		{
		id = SHORT1FROMMR(WinSendMsg(hwndSysSubMenu, MM_ITEMIDFROMPOSITION, MPFROMSHORT(sInx), NULL));

		WinSendMsg(hwndSysSubMenu, MM_QUERYITEM,
			   MPFROM2SHORT(id, FALSE), MPFROMP(&mi));
		if ( (mi.afStyle     & MIS_SEPARATOR) != 0 ||
		     (mi.afAttribute & MIA_DISABLED ) != 0 ||
		     id == (USHORT) SC_TASKMANAGER         )
			WinSendMsg(hwndSysSubMenu, MM_DELETEITEM,
				   MPFROM2SHORT(id, FALSE), NULL);
		}
	}	
/*...e*/
/*...sRestrictEntryfield:0:*/
/*
This is a function you call on an WC_ENTRYFIELD window that causes the window
to only allow strings in one set to be used and also not allow strings in
another set.
*/

typedef struct /* restr */
	{
	const CHAR *szIfIn;
	const CHAR *szMapTo;
	const CHAR *szAllowed;
	const CHAR *szNotAllowed;
	PFNWP pfnwpOld;
	} RESTR;

/*...sRestrictSubProc:0:*/
#define L_ENTRYMAX      500

/*...sMapChar:0:*/
static CHAR MapChar(RESTR *prestr, CHAR ch)
	{
	CHAR *sz;

	if ( prestr -> szIfIn == NULL || prestr -> szMapTo == NULL )
	        return ch;

	if ( (sz = strchr(prestr -> szIfIn, ch)) == NULL )
	        return ch;

	return prestr -> szMapTo[sz - prestr -> szIfIn];
	}
/*...e*/
/*...sProblemAllowed:0:*/
static BOOL ProblemAllowed(RESTR *prestr, CHAR ch)
	{
	return prestr -> szAllowed != NULL && strchr(prestr -> szAllowed, ch) == NULL;
	}                                                        /* More >>> */
/*...e*/
/*...sProblemNotAllowed:0:*/
static BOOL ProblemNotAllowed(RESTR *prestr, CHAR ch)
	{
	return prestr -> szNotAllowed != NULL && strchr(prestr -> szNotAllowed, ch) != NULL;
	}                                                        /* More >>> */
/*...e*/

MRESULT _System RestrictSubProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	RESTR *prestr = (RESTR *) WinQueryWindowULong(hwnd, QWL_USER);
	PFNWP pfnwpOld = prestr -> pfnwpOld;

	switch ( (int) msg )
	        {
/*...sWM_DESTROY \45\ clean up:16:*/
case WM_DESTROY:
	free(prestr);
	break;
/*...e*/
/*...sWM_CHAR    \45\ map incoming chars:16:*/
case WM_CHAR:
	{
	USHORT fs     = SHORT1FROMMP(mp1);
	CHAR   ch     = (CHAR) SHORT1FROMMP(mp2);
	USHORT vkey   = SHORT2FROMMP(mp2);

	if ( (fs & KC_VIRTUALKEY) != 0 && (fs & KC_ALT) != 0 &&
	     (vkey == VK_LEFT || vkey == VK_RIGHT || vkey == VK_UP || vkey == VK_DOWN) )
		/* If we let the entryfield get these, it won't process them */
		/* It will pass them to its owner, the dialog. */
		/* The dialog will use them to change the focus! */
		/* This is not desired, as Alt+NNN is being used */
		return (MRESULT) 0;

	if ( (fs & KC_CHAR) != 0 && ch != '\b' && ch != '\t' )
	        {
	        ch = MapChar(prestr, ch);

	        if ( ProblemAllowed(prestr, ch) ||
	             ProblemNotAllowed(prestr, ch) )
	                {
	                WinAlarm(HWND_DESKTOP, WA_WARNING);
	                return (MRESULT) 0;
	                }

	        mp2 = MPFROM2SHORT(ch, vkey);
	        }
	}
	break;
/*...e*/
/*...sEM_PASTE   \45\ paste then alter chars:16:*/
case EM_PASTE:
	{
	MRESULT mr = (*pfnwpOld)(hwnd, msg, mp1, mp2);
	MRESULT mrSel = WinSendMsg(hwnd, EM_QUERYSEL, NULL, NULL);
	CHAR sz[L_ENTRYMAX+1], *szTmp;

	WinQueryWindowText(hwnd, L_ENTRYMAX, sz);
	for ( szTmp = sz; *szTmp; szTmp++ )
	        {
	        *szTmp = MapChar(prestr, *szTmp);
	        if ( ProblemAllowed(prestr, *szTmp) ||
	             ProblemNotAllowed(prestr, *szTmp) )
	                {
	                strcpy(szTmp, szTmp + 1);
	                szTmp--;
	                }
	        }
	WinSetWindowText(hwnd, sz);
	WinSendMsg(hwnd, EM_SETSEL, mrSel, NULL);

	return mr;
	}
/*...e*/
	        }
	return (*pfnwpOld)(hwnd, msg, mp1, mp2);
	}
/*...e*/

static BOOL RestrictEntryfield(
	HWND hwnd,
	const CHAR *szIfIn,
	const CHAR *szMapTo,
	const CHAR *szAllowed,
	const CHAR *szNotAllowed
	)
	{
	RESTR *prestr;

	if ( (prestr = malloc(sizeof(RESTR))) == NULL )
	        return FALSE;

	prestr -> szIfIn       = szIfIn      ;
	prestr -> szMapTo      = szMapTo     ;
	prestr -> szAllowed    = szAllowed   ;
	prestr -> szNotAllowed = szNotAllowed;
	prestr -> pfnwpOld     = WinSubclassWindow(hwnd, RestrictSubProc);

	WinSetWindowULong(hwnd, QWL_USER, (LONG) prestr);

	return TRUE;
	}
/*...e*/
/*...e*/
/*...spriority:0:*/
static VOID LowPri(VOID)
	{
#ifdef NEVER
	DosSetPriority(PRTYS_THREAD, PRTYC_IDLETIME, 0, 0);
#endif
	DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, PRTYD_MINIMUM, 0);
	}

static VOID RegPri(VOID)
	{
	DosSetPriority(PRTYS_THREAD, PRTYC_REGULAR, 0, 0);
	}
/*...e*/
/*...svars:0:*/
/* Global single help instance */
static HWND hwndHelp;

/* Application name */
static CHAR szAppName[] = "GbmV2";

/* Whats the bitmap data called */
#define	MAX_FILE_NAME 256
static CHAR szFileName[MAX_FILE_NAME+1] = "";
static CHAR *FileName(VOID) { return szFileName[0] != '\0' ? szFileName : "(untitled)"; }

/* The bitmap data itself */
static BOOL fGotBitmap = FALSE;
static BOOL fUnsavedChanges = FALSE;
static MOD mod;

/* How we like to view the bitmaps */
#define	VIEW_NULL	0
#define	VIEW_HALFTONE	1
#define	VIEW_ERRDIFF	2
static BYTE bView = VIEW_NULL;
static LONG lBitCountScreen;

/* General application vars */
static BOOL fBusy = FALSE;
static HWND hwndObject;
static HWND hwndBitmap;
static HWND hwndScroller;

/* Selections in the current bitmap */
static BOOL fSelectionDefined = FALSE;
static RECTL rclSelection;
/*...e*/
/*...sundo:0:*/
/* Most replaced data gets stored here, so we can bring it back later */

static BOOL fCanUndo = FALSE;
static MOD modUndo;
static const CHAR *szWhatUndo;

/*...sDiscardUndo:0:*/
static VOID DiscardUndo(VOID)
	{
	if ( fCanUndo )
		{
		ModDelete(&modUndo);
		fCanUndo = FALSE;
		}
	}
/*...e*/
/*...sKeepForUndo:0:*/
static VOID KeepForUndo(const MOD *mod, const CHAR *szWhat)
	{
	DiscardUndo();
	if ( ModCopy(mod, &modUndo) != MOD_ERR_OK )
		return; /* Silently fail to keep undo information */
	szWhatUndo = szWhat;
	fCanUndo = TRUE;
	}
/*...e*/
/*...sUseUndoBuffer:0:*/
static VOID UseUndoBuffer(
	MOD *modToUndoTo,
	const CHAR **pszWhat
	)
	{
	ModMove(&modUndo, modToUndoTo);
	*pszWhat = szWhatUndo;
	fCanUndo = FALSE;
	}
/*...e*/
/*...e*/
/*...sCaption:0:*/
static VOID Caption(HWND hwndClient, const CHAR *szFmt, ...)
	{
	va_list vars;
	HWND hwndFrame = WinQueryWindow(hwndClient, QW_PARENT);
	CHAR sz[50+MAX_FILE_NAME+50+1], *szAppend;

	strcpy(sz, szAppName);

	szAppend = sz + strlen(sz);

	va_start(vars, szFmt);
	vsprintf(szAppend, szFmt, vars);
	va_end(vars);

	WinSetWindowText(hwndFrame, sz);
	}
/*...e*/
/*...sMakeVisual:0:*/
static BOOL MakeVisual(
	HWND hwnd,
	MOD *mod,
	BYTE bView,
	HBITMAP *phbm, LONG *plColorBg, LONG *plColorFg
	)
	{
	HAB hab = WinQueryAnchorBlock(hwnd);
	MOD_ERR mrc = MOD_ERR_OK; MOD modTmp, *modVisual = mod;
	BOOL fOk;

	switch ( bView )
		{
/*...sVIEW_HALFTONE:16:*/
case VIEW_HALFTONE:
	switch ( lBitCountScreen )
		{
		case 1:
			/* Are there any 1bpp screens still in existence? */
			mrc = ModBppMap(mod, CVT_BW, CVT_NEAREST, 8,8,8, 2, &modTmp);
			modVisual = &modTmp;
			break;
		case 4:
			mrc = ModBppMap(mod, CVT_VGA, CVT_HALFTONE, 8,8,8, 16, &modTmp);
			modVisual = &modTmp;
			break;
		case 8:
			mrc = ModBppMap(mod, CVT_784, CVT_HALFTONE, 8,8,8, 256, &modTmp);
			modVisual = &modTmp;
			break;
		case 16:
			mrc = ModBppMap(mod, CVT_RGB, CVT_HALFTONE, 5,6,5, 65536, &modTmp);
			modVisual = &modTmp;
			break;
		}
	break;
/*...e*/
/*...sVIEW_ERRDIFF:16:*/
case VIEW_ERRDIFF:
	switch ( lBitCountScreen )
		{
		case 1:
			/* Are there any 1bpp screens still in existence? */
			mrc = ModBppMap(mod, CVT_BW, CVT_ERRDIFF, 8,8,8, 2, &modTmp);
			modVisual = &modTmp;
			break;
		case 4:
			mrc = ModBppMap(mod, CVT_VGA, CVT_ERRDIFF, 8,8,8, 16, &modTmp);
			modVisual = &modTmp;
			break;
		case 8:
			mrc = ModBppMap(mod, CVT_784, CVT_ERRDIFF, 8,8,8, 256, &modTmp);
			modVisual = &modTmp;
			break;
		case 16:
			mrc = ModBppMap(mod, CVT_RGB, CVT_ERRDIFF, 5,6,5, 65536, &modTmp);
			modVisual = &modTmp;
			break;
		}
	break;
/*...e*/
		}

	if ( mrc != MOD_ERR_OK )
		{
		ModDelete(modVisual);
		return FALSE; /* Unable to make improved quality bitmap */
		}

	if ( modVisual->gbm.bpp == 1 )
/*...sremember Bg and Fg colours:16:*/
{
*plColorBg = (modVisual->gbmrgb[0].r << 16) + (modVisual->gbmrgb[0].g << 8) + modVisual->gbmrgb[0].b;
*plColorFg = (modVisual->gbmrgb[1].r << 16) + (modVisual->gbmrgb[1].g << 8) + modVisual->gbmrgb[1].b;
}
/*...e*/

	fOk = ( ModMakeHBITMAP(modVisual, hab, phbm) == MOD_ERR_OK );
	if ( modVisual != mod )
		ModDelete(modVisual);
	return fOk;
	}
/*...e*/
/*...sMakeVisualBg:0:*/
static BOOL MakeVisualBg(
	HWND hwnd,
	MOD *mod,
	BYTE bView,
	HBITMAP *phbm, LONG *plColorBg, LONG *plColorFg,
	CHAR *szFileName
	)
	{
	LowPri();
	/* Tentatively use new supplied filename, rather than current */
	Caption(hwnd, " - rendering %s", szFileName);
	if ( !MakeVisual(hwnd, mod, bView, phbm, plColorBg, plColorFg) )
		{
		/* Revert to previous filename */
		Caption(hwnd, " - %s", FileName());
		RegPri();
		Warning(hwnd, "Error creating view bitmap");
		return FALSE;
		}
	/* Leave with new supplied filename on display */
	Caption(hwnd, " - %s", szFileName);
	RegPri();
	return TRUE;
	}
/*...e*/
/*...sSaveChanges:0:*/
/*
Returns TRUE if changes saved ok.
Returns FALSE if cancel selected.
*/

static BOOL SaveChanges(HWND hwnd)
	{
	CHAR sz[255];

	if ( !fGotBitmap )
		return TRUE;

	if ( !fUnsavedChanges )
		return TRUE;

	sprintf(sz, "Save current changes: %s", FileName());
	switch ( WinMessageBox(HWND_DESKTOP, hwnd, sz, szAppName, 0, MB_YESNOCANCEL | MB_WARNING | MB_MOVEABLE) )
		{
		case MBID_YES:
			{
			GBMFILEDLG gbmfild;
			MOD_ERR mrc;

			memset(&gbmfild.fild, 0, sizeof(FILEDLG));
			gbmfild.fild.cbSize = sizeof(FILEDLG);
			gbmfild.fild.fl = (FDS_CENTER|FDS_SAVEAS_DIALOG|FDS_HELPBUTTON);
			strcpy(gbmfild.fild.szFullFile, szFileName);
			strcpy(gbmfild.szOptions, "");	
			while ( gbmfild.fild.szFullFile[0] == '\0' )
				/* Try to correct filename */
				{
				GbmFileDlg(HWND_DESKTOP, hwnd, &gbmfild);
				if ( gbmfild.fild.lReturn != DID_OK )
					return FALSE;
				}
			if ( (mrc = ModWriteToFile(&mod, gbmfild.fild.szFullFile, gbmfild.szOptions)) != MOD_ERR_OK )
				{
				Warning(hwnd, "Error saving %s: %s", gbmfild.fild.szFullFile, ModErrorString(mrc));
				return FALSE;
				}

			strcpy(szFileName, gbmfild.fild.szFullFile);
			fUnsavedChanges = FALSE;
			return TRUE;
			}
		case MBID_NO:
			return TRUE;
		case MBID_CANCEL:
			return FALSE;
		}
	/* NOT REACHED */
	return FALSE; /* Keep fussy compiler happy */
	}
/*...e*/
/*...sBitmapWndProc:0:*/
#define	WC_BITMAP "GbmV2BitmapViewerClass"

static BOOL fTracking = FALSE;

/*
This is a pretty simple window class. Its aim in life is to display the bitmap
held by the global variable 'hbm'.
*/

static HBITMAP hbm = NULLHANDLE;
static LONG lColorBg, lColorFg;
static HMTX hmtxHbm;

static VOID RequestHbm(VOID)
	{
	DosRequestMutexSem(hmtxHbm, SEM_INDEFINITE_WAIT);
	}

static VOID ReleaseHbm(VOID)
	{
	DosReleaseMutexSem(hmtxHbm);
	}

MRESULT _System BitmapWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( (int) msg )
		{
/*...sWM_PAINT       \45\ repaint client area:16:*/
case WM_PAINT:
	{
	RECTL rclUpdate;
	HPS hps = WinBeginPaint(hwnd, (HPS) NULL, &rclUpdate);

	RequestHbm();

	if ( hbm != (HBITMAP) NULL )
		{
		static SIZEL sizl = { 0, 0 };
		HAB hab = WinQueryAnchorBlock(hwnd);
		HDC hdcBmp = DevOpenDC(hab, OD_MEMORY, "*", 0L, (PDEVOPENDATA) NULL, (HDC) NULL);
		HPS hpsBmp = GpiCreatePS(hab, hdcBmp, &sizl, PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC);
		POINTL aptl[3];
		BITMAPINFOHEADER bmp;

		GpiQueryBitmapParameters(hbm, &bmp);

		GpiSetBitmap(hpsBmp, hbm);

		GpiSetBackColor(hps, GpiQueryColorIndex(hps, 0, lColorBg));
		GpiSetColor    (hps, GpiQueryColorIndex(hps, 0, lColorFg));

		aptl[0].x = 0;
		aptl[0].y = 0;
		aptl[1].x = bmp.cx;
		aptl[1].y = bmp.cy;
		aptl[2].x = 0;
		aptl[2].y = 0;
		GpiBitBlt(hps, hpsBmp, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE);

		GpiSetBitmap(hpsBmp, (HBITMAP) NULL);
		GpiDestroyPS(hpsBmp);
		DevCloseDC(hdcBmp);

		if ( fSelectionDefined )
			{
			POINTL ptl;

			GpiSetMix(hps, FM_INVERT);
			ptl.x = rclSelection.xLeft;
			ptl.y = rclSelection.yBottom;
			GpiMove(hps, &ptl);
			ptl.x = rclSelection.xRight - 1L;
			ptl.y = rclSelection.yTop   - 1L;
			GpiBox(hps, DRO_OUTLINE, &ptl, 0L, 0L);
			}
		}

	ReleaseHbm();

	WinEndPaint(hps);
	}
	return (MRESULT) 0;
/*...e*/
/*...sWM_CHAR        \45\ got a character from the user:16:*/
#define	KS(vkey,kc) ((vkey)+((kc&(KC_ALT|KC_SHIFT|KC_CTRL))<<16))

case WM_CHAR:
	{
	USHORT fs        = SHORT1FROMMP(mp1);
	CHAR   ch        = (CHAR) SHORT1FROMMP(mp2);
	USHORT vkey      = SHORT2FROMMP(mp2);
	HWND hwndHscroll = WinWindowFromID(hwndScroller, SCID_HSCROLL);
	HWND hwndVscroll = WinWindowFromID(hwndScroller, SCID_VSCROLL);

	if ( fTracking )
		/* Swallow the key */
		return (MRESULT) 0;

	if ( fs & KC_VIRTUALKEY )
		{
		switch ( KS(vkey,fs) )
			{
			case KS(VK_LEFT,0):
			case KS(VK_RIGHT,0):
				return WinSendMsg(hwndHscroll, msg, mp1, mp2);
			case KS(VK_LEFT,KC_SHIFT):
				fs &= ~KC_SHIFT;
				vkey = VK_PAGEUP;
				mp1 = MPFROMSHORT(fs);
				mp2 = MPFROM2SHORT(ch, vkey);
				return WinSendMsg(hwndHscroll, msg, mp1, mp2);
			case KS(VK_RIGHT,KC_SHIFT):
				fs &= ~KC_SHIFT;
				vkey = VK_PAGEDOWN;
				mp1 = MPFROMSHORT(fs);
				mp2 = MPFROM2SHORT(ch, vkey);
				return WinSendMsg(hwndHscroll, msg, mp1, mp2);
			case KS(VK_UP,0):
			case KS(VK_DOWN,0):
			case KS(VK_PAGEUP,0):
			case KS(VK_PAGEDOWN,0):
				return WinSendMsg(hwndVscroll, msg, mp1, mp2);
			case KS(VK_UP,KC_SHIFT):
				fs &= ~KC_SHIFT;
				vkey = VK_PAGEUP;
				mp1 = MPFROMSHORT(fs);
				mp2 = MPFROM2SHORT(ch, vkey);
				return WinSendMsg(hwndVscroll, msg, mp1, mp2);
			case KS(VK_DOWN,KC_SHIFT):
				fs &= ~KC_SHIFT;
				vkey = VK_PAGEDOWN;
				mp1 = MPFROMSHORT(fs);
				mp2 = MPFROM2SHORT(ch, vkey);
				return WinSendMsg(hwndVscroll, msg, mp1, mp2);
			}
		}
	}
	break;
/*...e*/
/*...sWM_BUTTON2DOWN \45\ cancel current selection:16:*/
case WM_BUTTON2DOWN:
	fSelectionDefined = FALSE;
	WinInvalidateRect(hwnd, NULL, TRUE);
	WinUpdateWindow(hwnd);
	break;
/*...e*/
/*...sWM_MOUSEMOVE   \45\ hourglass:16:*/
case WM_MOUSEMOVE:
	if ( fTracking )
		return (MRESULT) 0;

	if ( fBusy )
		{
		WinSetPointer(HWND_DESKTOP,
			WinQuerySysPointer(HWND_DESKTOP, SPTR_WAIT, FALSE));
		return (MRESULT) 0;
		}
	break;
/*...e*/
		}
	return WinDefWindowProc(hwnd, msg, mp1, mp2);
	}

/*...sSetBitmap:0:*/
/* Assured hbmNew != (HBITMAP) NULL on entry */

static VOID SetBitmap(HBITMAP hbmNew, LONG lColorBgNew, LONG lColorFgNew)
	{
	HBITMAP hbmOld = hbm;
	BOOL fOld = ( hbmOld != (HBITMAP) NULL );
	BITMAPINFOHEADER2 bmp;

	RequestHbm();

	if ( hbmNew == hbmOld )
		{
		ReleaseHbm();
		return;
		}

	hbm      = hbmNew;
	lColorBg = lColorBgNew;
	lColorFg = lColorFgNew;

	if ( fOld )
		GpiDeleteBitmap(hbmOld);

	bmp.cbFix = sizeof(BITMAPINFOHEADER2);
	GpiQueryBitmapInfoHeader(hbm, &bmp);
	ReleaseHbm();
	WinShowWindow(hwndBitmap, FALSE);		
	WinSetWindowPos(hwndBitmap, (HWND) NULL, 0,0,bmp.cx,bmp.cy, SWP_SIZE);

	if ( fOld )
		WinSendMsg(hwndScroller, SCM_SIZE, MPFROMHWND(hwndBitmap), NULL);
	else
		WinSendMsg(hwndScroller, SCM_CHILD, MPFROMHWND(hwndBitmap), NULL);

	WinShowWindow(hwndBitmap, TRUE);
	WinInvalidateRect(hwndBitmap, NULL, TRUE);
	WinUpdateWindow(hwndBitmap);
	}
/*...e*/
/*...sSetNoBitmap:0:*/
static VOID SetNoBitmap(VOID)
	{
	HBITMAP hbmOld = hbm;
	BOOL fOld = ( hbmOld != (HBITMAP) NULL );

	RequestHbm();

	if ( hbmOld == (HBITMAP) NULL )
		{
		ReleaseHbm();
		return;
		}

	hbm = (HBITMAP) NULL;

	if ( fOld )
		GpiDeleteBitmap(hbmOld);

	ReleaseHbm();
	WinSetWindowPos(hwndBitmap, (HWND) NULL, 0,0,0,0, SWP_SIZE);

	if ( fOld )
		WinSendMsg(hwndScroller, SCM_CHILD, MPFROMHWND((HWND) NULL), NULL);

	WinInvalidateRect(hwndScroller, NULL, TRUE);
	WinUpdateWindow(hwndScroller);
	}
/*...e*/
/*...e*/
/*...sObjectWndProc:0:*/
#define	WC_GBMV2_OBJECT	"GbmV2ObjectWindowClass"

/*...sUM_ user window messages:0:*/
#define	UM_NEW			 WM_USER
#define	UM_OPEN			(WM_USER+ 1)
#define	UM_SAVE			(WM_USER+ 2)
#define	UM_SAVE_AS		(WM_USER+ 3)
#define	UM_EXPORT_MET		(WM_USER+ 4)
#define	UM_PRINT		(WM_USER+ 5)
#define	UM_SNAPSHOT		(WM_USER+ 6)
#define	UM_UNDO			(WM_USER+ 7)
#define	UM_SELECT		(WM_USER+ 8)
#define	UM_DESELECT		(WM_USER+ 9)
#define	UM_COPY			(WM_USER+10)
#define	UM_PASTE		(WM_USER+11)
#define	UM_REF_HORZ		(WM_USER+12)
#define	UM_REF_VERT		(WM_USER+13)
#define	UM_ROT_90		(WM_USER+14)
#define	UM_ROT_180		(WM_USER+15)
#define	UM_ROT_270		(WM_USER+16)
#define	UM_TRANSPOSE		(WM_USER+17)
#define	UM_CROP			(WM_USER+18)
#define	UM_COLOUR		(WM_USER+19)
#define	UM_MAP			(WM_USER+20)
#define	UM_RESIZE		(WM_USER+21)
#define	UM_VIEW_NULL		(WM_USER+22)
#define	UM_VIEW_HALFTONE	(WM_USER+23)
#define	UM_VIEW_ERRDIFF		(WM_USER+24)
#define	UM_ABOUT		(WM_USER+25)

#define	UM_DONE			(WM_USER+26)
/*...e*/

/*...sTracking:0:*/
static HWND hwndClientTracking;
static TRACKINFO ti;

/*
The documentation says that ti.rclTrack is updated to the current values before
the hook functions are called. Under OS/2 2.1 GA (and perhaps other versions),
this is not the case. So I will code this differently.
*/

/*...sTrackPosHook:0:*/
static BOOL APIENTRY TrackPosHook(HAB hab, QMSG *pqmsg, ULONG msgf)
	{
	hab=hab; /* Suppress warning */
	if ( msgf == MSGF_TRACK )
		{
		POINTL ptl = pqmsg->ptl;
		WinMapWindowPoints(pqmsg->hwnd, hwndBitmap, &ptl, 1L);
		if ( ptl.x < 0L )
			ptl.x = 0L;
		else if ( ptl.x > mod.gbm.w )
			ptl.x = mod.gbm.w;
		if ( ptl.y < 0L )
			ptl.y = 0L;
		else if ( ptl.y > mod.gbm.h )
			ptl.y = mod.gbm.h;
		Caption(hwndClientTracking, " - at %ldx%ld", ptl.x, ptl.y);
		}
	return FALSE; /* Pass on to next hook etc. / ie: don't swallow msg */
	}
/*...e*/
/*...sTrackSizeHook:0:*/
static BOOL APIENTRY TrackSizeHook(HAB hab, QMSG *pqmsg, ULONG msgf)
	{
	hab=hab; /* Suppress warning */
	if ( msgf == MSGF_TRACK )
		{
		POINTL ptl = pqmsg->ptl;
		SIZEL sizl;
		WinMapWindowPoints(pqmsg->hwnd, hwndBitmap, &ptl, 1L);
		sizl.cx = (ptl.x - ti.rclTrack.xLeft  );
		sizl.cy = (ptl.y - ti.rclTrack.yBottom);
		if ( sizl.cx < 0L )
			sizl.cx = 0L;
		else if ( sizl.cx > mod.gbm.w )
			sizl.cx = mod.gbm.w;
		if ( sizl.cy < 0L )
			sizl.cy = 0L;
		else if ( sizl.cy > mod.gbm.h )
			sizl.cy = mod.gbm.h;
		Caption(hwndClientTracking, " - at %ldx%ld, size %ldx%ld",
			(long) ti.rclTrack.xLeft  ,
			(long) ti.rclTrack.yBottom,
			(long) sizl.cx,
			(long) sizl.cy);
		}
	return FALSE; /* Pass on to next hook etc. / ie: don't swallow msg */
	}
/*...e*/

static BOOL Tracking(
	HWND hwndClient,
	HWND hwnd,
	RECTL *prclTrack,
	int x, int y, int cx, int cy,
	HPOINTER hptr1, HPOINTER hptr2
	)
	{
	HAB hab = WinQueryAnchorBlock(hwnd);
	HMQ hmq = HMQ_CURRENT;
	BOOL f;

	hwndClientTracking = hwndClient;

	ti.cxBorder   = 1;
	ti.cyBorder   = 1;
	ti.cxGrid     = 0;
	ti.cyGrid     = 0;
	ti.cxKeyboard = 4;
	ti.cyKeyboard = 4;

	ti.rclBoundary.xLeft   = x;
	ti.rclBoundary.xRight  = x + cx;
	ti.rclBoundary.yBottom = y;
	ti.rclBoundary.yTop    = y + cy;

	ti.ptlMinTrackSize.x = 1;
	ti.ptlMinTrackSize.y = 1;

	ti.ptlMaxTrackSize.x = cx;
	ti.ptlMaxTrackSize.y = cy;

	ti.rclTrack.xLeft   = x + cx/4;
	ti.rclTrack.xRight  = x + cx/4;
	ti.rclTrack.yBottom = y + cy/4;
	ti.rclTrack.yTop    = y + cy/4;

/*...smove pointer:8:*/
{
POINTL ptl;

ptl.x = ti.rclTrack.xLeft;
ptl.y = ti.rclTrack.yBottom;
WinMapWindowPoints(hwnd, HWND_DESKTOP, &ptl, 1);
WinSetPointerPos(HWND_DESKTOP, ptl.x, ptl.y);
}
/*...e*/

	ti.fs = TF_MOVE | TF_STANDARD | TF_SETPOINTERPOS | TF_ALLINBOUNDARY;
	WinSetPointer(HWND_DESKTOP, hptr1);
	WinSetHook(hab, hmq, HK_MSGFILTER, (PFN) TrackPosHook, (HMODULE) NULL);
	f = WinTrackRect(hwnd, (HPS) NULL, &ti);
	WinReleaseHook(hab, hmq, HK_MSGFILTER, (PFN) TrackPosHook, (HMODULE) NULL);
	Caption(hwndClient, " - %s", FileName());
	if ( !f )
		return FALSE;

	ti.fs = TF_RIGHT | TF_TOP | TF_STANDARD | TF_SETPOINTERPOS | TF_ALLINBOUNDARY;
	WinSetPointer(HWND_DESKTOP, hptr2);
	WinSetHook(hab, hmq, HK_MSGFILTER, (PFN) TrackSizeHook, (HMODULE) NULL);
	f = WinTrackRect(hwnd, (HPS) NULL, &ti);
	WinReleaseHook(hab, hmq, HK_MSGFILTER, (PFN) TrackSizeHook, (HMODULE) NULL);
	Caption(hwndClient, " - %s", FileName());
	if ( !f )
		return FALSE;

	*prclTrack = ti.rclTrack;

	return TRUE;
	}
/*...e*/
/*...sSelect:0:*/
static BOOL Select(
	HWND hwndClient,
	HWND hwnd,
	RECTL *prclTrack,
	int x, int y, int cx, int cy
	)
	{
	HPOINTER hptr1 = WinLoadPointer(HWND_DESKTOP, (HMODULE) NULL, RID_SELECT1);
	HPOINTER hptr2 = WinLoadPointer(HWND_DESKTOP, (HMODULE) NULL, RID_SELECT2);
	BOOL f;

	fTracking = TRUE;
	f = Tracking(hwndClient, hwnd, prclTrack, x, y, cx, cy, hptr1, hptr2);
	fTracking = FALSE;

	WinDestroyPointer(hptr1);
	WinDestroyPointer(hptr2);

	return f;
	}
/*...e*/
/*...sCopyToClipbrd:0:*/
static VOID CopyToClipbrd(HWND hwndClient, RECTL rcl)
	{
	HAB hab = WinQueryAnchorBlock(hwndClient);
	HPS hps = WinGetPS(hwndClient);
	HBITMAP hbmClipbrd;
	BITMAPINFOHEADER2 bmpClipbrd;
	RECTL rclClipbrd;

	bmpClipbrd.cbFix = sizeof(BITMAPINFOHEADER2);
	GpiQueryBitmapInfoHeader(hbm, &bmpClipbrd);
	bmpClipbrd.cx = (ULONG) (rcl.xRight - rcl.xLeft);
	bmpClipbrd.cy = (ULONG) (rcl.yTop - rcl.yBottom);
	if ( (hbmClipbrd = GpiCreateBitmap(hps, &bmpClipbrd, 0L, NULL, NULL)) == (HBITMAP) NULL )
		Warning(hwndClient, "Error copying to clipboard: can't create bitmap");
	else
		{
		HMF hmf;

		rclClipbrd.xLeft   = 0; rclClipbrd.xRight = bmpClipbrd.cx;
		rclClipbrd.yBottom = 0; rclClipbrd.yTop   = bmpClipbrd.cy;
		BmpBlitter(hab, hbm, rcl, hbmClipbrd, rclClipbrd);
		WinOpenClipbrd(hab);
		WinEmptyClipbrd(hab);

		/* Try to also put CF_METAFILE data on clipboard too */
		if ( ModMakeHMF(hbmClipbrd, hab, &hmf) == MOD_ERR_OK )
			WinSetClipbrdData(hab, (ULONG) hmf, CF_METAFILE, CFI_HANDLE);

		WinSetClipbrdData(hab, (ULONG) hbmClipbrd, CF_BITMAP, CFI_HANDLE);
		WinCloseClipbrd(hab);
		}
	WinReleasePS(hps);
	}
/*...e*/
/*...sReflect:0:*/
/*...sReflectAll:0:*/
/* This routine is used to perform primitives affecting the whole bitmap */

static VOID ReflectAll(
	HWND hwndClient,
	MOD_ERR (*reflector)(const MOD *mod, MOD *modNew),
	const CHAR *szWhat, const CHAR *szUndo
	)
	{
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	Caption(hwndClient, " - %s %s", szWhat, FileName());
	mrc = (*reflector)(&mod, &modNew);
	Caption(hwndClient, " - %s", FileName());

	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error %s %s: %s", szWhat, FileName(), ModErrorString(mrc));
		return;
		}

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		return;
		}

	KeepForUndo(&mod, szUndo);

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fUnsavedChanges = TRUE;
	fSelectionDefined = FALSE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);
	}
/*...e*/
/*...sReflectSelection:0:*/
/* This routine is called for primitives that just alter the selection */

static VOID ReflectSelection(
	HWND hwndClient,
	MOD_ERR (*reflector)(const MOD *mod, MOD *modNew),
	const CHAR *szWhat, const CHAR *szUndo
	)
	{
	MOD_ERR mrc; MOD modSelBefore, modSelAfter, modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	Caption(hwndClient, " - %s %s", szWhat, FileName());

	/* Step1: Extract the selection */
	mrc = ModExtractSubrectangle(&mod,
		rclSelection.xLeft,
		rclSelection.yBottom,
		rclSelection.xRight - rclSelection.xLeft,
		rclSelection.yTop - rclSelection.yBottom,
		&modSelBefore);
	if ( mrc != MOD_ERR_OK )
		{
		Caption(hwndClient, " - %s", FileName());
		Warning(hwndClient, "Error %s %s: %s", szWhat, FileName(), ModErrorString(mrc));
		return;
		}

	/* Step2: Try to perform the operation */
	mrc = (*reflector)(&modSelBefore, &modSelAfter);
	ModDelete(&modSelBefore);
	if ( mrc != MOD_ERR_OK )
		{
		Caption(hwndClient, " - %s", FileName());
		Warning(hwndClient, "Error %s %s: %s", szWhat, FileName(), ModErrorString(mrc));
		return;
		}

	/* Step3: Make a copy of the current bitmap to build composite result */
	mrc = ModCopy(&mod, &modNew);
	if ( mrc != MOD_ERR_OK )
		{
		ModDelete(&modSelAfter);
		Caption(hwndClient, " - %s", FileName());
		Warning(hwndClient, "Error %s %s: %s", szWhat, FileName(), ModErrorString(mrc));
		return;
		}

	/* Step4: Blit the new data over the old data */
	ModBlit(&modNew, rclSelection.xLeft, rclSelection.yBottom, /* Dst */
		&modSelAfter, 0, 0,				   /* Src */
	        modSelAfter.gbm.w, modSelAfter.gbm.h);

	/* Step5: Discard after bitmap */
	ModDelete(&modSelAfter);

	/* From here onwards: Try to build new view bitmap */

	Caption(hwndClient, " - %s", FileName());

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		return;
		}

	KeepForUndo(&mod, szUndo);

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fUnsavedChanges = TRUE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);
	}
/*...e*/

static VOID Reflect(
	HWND hwndClient,
	MOD_ERR (*reflector)(const MOD *mod, MOD *modNew),
	const CHAR *szWhat, const CHAR *szUndo
	)
	{
	if ( fSelectionDefined )
		ReflectSelection(hwndClient, reflector, szWhat, szUndo);
	else
		ReflectAll(hwndClient, reflector, szWhat, szUndo);
	}
/*...e*/
/*...sNewView:0:*/
static VOID NewView(HWND hwndClient, BYTE bViewNew)
	{
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	if ( !MakeVisualBg(hwndClient, &mod, bViewNew, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		return;

	bView = bViewNew;
	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);
	}
/*...e*/

/*...sAboutDlgProc:0:*/
MRESULT _System AboutDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( msg )
		{
/*...sWM_INITDLG:16:*/
case WM_INITDLG:
	{
	CHAR sz[80+1];
	sprintf(sz, "Version %4.2f, using GBM.DLL version %4.2f",
		(double) VERSION/100.0, (double) gbm_version()/100.0);
	WinSetDlgItemText(hwnd, DID_VERSION_TEXT, sz);
	TidySysMenu(hwnd);
	return (MRESULT) FALSE; /* We have not set the focus */
	}
/*...e*/
/*...sWM_COMMAND:16:*/
case WM_COMMAND:
	switch ( COMMANDMSG(&msg) -> cmd )
		{
/*...sDID_OK:32:*/
case DID_OK:
	WinDismissDlg(hwnd, TRUE);
	return (MRESULT) 0;
/*...e*/
/*...sDID_CANCEL:32:*/
case DID_CANCEL:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
		}
	break;
/*...e*/
/*...sWM_CLOSE:16:*/
case WM_CLOSE:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
/*...sWM_HELP:16:*/
case WM_HELP:
	/* Parent is HWND_DESKTOP */
	/* WinDefDlgProc() will pass this up to the parent */
	/* So redirect to the owner */
	return WinSendMsg(WinQueryWindow(hwnd, QW_OWNER), msg, mp1, mp2);
/*...e*/
		}
	return WinDefDlgProc(hwnd, msg, mp1, mp2);
	}
/*...e*/
/*...sColourDlgProc:0:*/
static int map = CVT_I_TO_L;
static double gama = 2.1, shelf = 0.0;

MRESULT _System ColourDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( msg )
		{
/*...sWM_INITDLG:16:*/
case WM_INITDLG:
	{
	CHAR sz[50+1];
	SHORT id;

	switch ( map )
		{
		case CVT_I_TO_L:	id = DID_I_TO_L;	break;
		case CVT_I_TO_P:	id = DID_I_TO_P;	break;
		case CVT_L_TO_I:	id = DID_L_TO_I;	break;
		case CVT_L_TO_P:	id = DID_L_TO_P;	break;
		case CVT_P_TO_I:	id = DID_P_TO_I;	break;
		case CVT_P_TO_L:	id = DID_P_TO_L;	break;
		}

	WinSendDlgItemMsg(hwnd, id, BM_CLICK, MPFROMSHORT(TRUE), NULL);
	WinSetFocus(HWND_DESKTOP, WinWindowFromID(hwnd, id));

	sprintf(sz, "%1.1f", gama);
	WinSetDlgItemText(hwnd, DID_GAMMA, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_GAMMA), NULL, NULL, "0123456789.", NULL);

	sprintf(sz, "%1.1f", shelf);
	WinSetDlgItemText(hwnd, DID_SHELF, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_SHELF), NULL, NULL, "0123456789.", NULL);

	TidySysMenu(hwnd);
	return (MRESULT) TRUE; /* We have set the focus */
	}
/*...e*/
/*...sWM_COMMAND:16:*/
case WM_COMMAND:
	switch ( COMMANDMSG(&msg) -> cmd )
		{
/*...sDID_OK:32:*/
case DID_OK:
	{
	SHORT sInx = SHORT1FROMMR(WinSendDlgItemMsg(hwnd, DID_I_TO_L, BM_QUERYCHECKINDEX, NULL, NULL)) - 1;
	CHAR sz[50+1];
	int mapNew;
	double gamaNew, shelfNew;

	switch ( sInx )
		{
		case DID_I_TO_L - DID_I_TO_L:	mapNew = CVT_I_TO_L;	break;
		case DID_I_TO_P - DID_I_TO_L:	mapNew = CVT_I_TO_P;	break;
		case DID_L_TO_I - DID_I_TO_L:	mapNew = CVT_L_TO_I;	break;
		case DID_L_TO_P - DID_I_TO_L:	mapNew = CVT_L_TO_P;	break;
		case DID_P_TO_I - DID_I_TO_L:	mapNew = CVT_P_TO_I;	break;
		case DID_P_TO_L - DID_I_TO_L:	mapNew = CVT_P_TO_L;	break;
		}
	WinQueryDlgItemText(hwnd, DID_GAMMA, sizeof(sz), sz);
	sscanf(sz, "%lf", &gamaNew);
	WinQueryDlgItemText(hwnd, DID_SHELF, sizeof(sz), sz);
	sscanf(sz, "%lf", &shelfNew);

	if ( gamaNew < 0.1 || gamaNew > 10.0 )
		Warning(hwnd, "Gamma must be between 0.1 and 10.0");
	else if ( shelfNew < 0.0 || shelfNew > 1.0 )
		Warning(hwnd, "Shelf must be between 0.0 and 1.0");
	else
		{
		map   = mapNew;
		gama  = gamaNew;
		shelf = shelfNew;
		WinDismissDlg(hwnd, TRUE);
		return (MRESULT) 0;
		}
	}
	break;
/*...e*/
/*...sDID_CANCEL:32:*/
case DID_CANCEL:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
		}
	break;
/*...e*/
/*...sWM_CONTROL:16:*/
case WM_CONTROL:
	{
	SHORT id = SHORT1FROMMP(mp1);
	SHORT note = SHORT2FROMMP(mp1);
	HWND hwndG = WinWindowFromID(hwnd, DID_GAMMA_TEXT);
	HWND hwndS = WinWindowFromID(hwnd, DID_SHELF_TEXT);

	switch ( id )
		{
		case DID_I_TO_L:
		case DID_L_TO_I:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndG, FALSE);
				WinEnableWindow(hwndS, FALSE);
				}
			break;
		case DID_I_TO_P:
		case DID_P_TO_I:
		case DID_P_TO_L:
		case DID_L_TO_P:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndG, TRUE);
				WinEnableWindow(hwndS, TRUE);
				}
			break;
		}
	}
	break;
/*...e*/
/*...sWM_CLOSE:16:*/
case WM_CLOSE:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
/*...sWM_HELP:16:*/
case WM_HELP:
	/* Parent is HWND_DESKTOP */
	/* WinDefDlgProc() will pass this up to the parent */
	/* So redirect to the owner */
	return WinSendMsg(WinQueryWindow(hwnd, QW_OWNER), msg, mp1, mp2);
/*...e*/
		}
	return WinDefDlgProc(hwnd, msg, mp1, mp2);
	}
/*...e*/
/*...sMapDlgProc:0:*/
static int iKeepRed = 8, iKeepGreen = 8, iKeepBlue = 8, nCols = 256;
static int iPal = CVT_784, iAlg = CVT_ERRDIFF;

MRESULT _System MapDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( msg )
		{
/*...sWM_INITDLG:16:*/
case WM_INITDLG:
	{
	CHAR sz[50+1];
	SHORT id;

	switch ( iPal )
		{
		case CVT_BW:		id = DID_MAP_BW;	break;
		case CVT_VGA:		id = DID_MAP_VGA;	break;
		case CVT_8:		id = DID_MAP_8;		break;
		case CVT_4G:		id = DID_MAP_4G;	break;
		case CVT_784:		id = DID_MAP_784;	break;
		case CVT_666:		id = DID_MAP_666;	break;
		case CVT_8G:		id = DID_MAP_8G;	break;
		case CVT_TRIPEL:	id = DID_MAP_TRIPEL;	break;
		case CVT_RGB:		id = DID_MAP_RGB;	break;
		case CVT_FREQ:		id = DID_MAP_FREQ;	break;
		case CVT_MCUT:		id = DID_MAP_MCUT;	break;
		}

	WinSendDlgItemMsg(hwnd, id, BM_CLICK, MPFROMSHORT(TRUE), NULL);
	WinSetFocus(HWND_DESKTOP, WinWindowFromID(hwnd, id));

	switch ( iAlg )
		{
		case CVT_NEAREST:	id = DID_NEAREST;	break;
		case CVT_HALFTONE:	id = DID_HALFTONE;	break;
		case CVT_ERRDIFF:	id = DID_ERRDIFF;	break;
		}

	WinSendDlgItemMsg(hwnd, id, BM_CLICK, MPFROMSHORT(TRUE), NULL);

	sprintf(sz, "%d", iKeepRed);
	WinSetDlgItemText(hwnd, DID_R, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_R), NULL, NULL, "0123456789", NULL);

	sprintf(sz, "%d", iKeepGreen);
	WinSetDlgItemText(hwnd, DID_G, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_G), NULL, NULL, "0123456789", NULL);

	sprintf(sz, "%d", iKeepBlue);
	WinSetDlgItemText(hwnd, DID_B, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_B), NULL, NULL, "0123456789", NULL);

	sprintf(sz, "%d", nCols);
	WinSetDlgItemText(hwnd, DID_N, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_N), NULL, NULL, "0123456789", NULL);

	TidySysMenu(hwnd);
	return (MRESULT) TRUE; /* We have set the focus */
	}
/*...e*/
/*...sWM_CONTROL:16:*/
case WM_CONTROL:
	{
	SHORT id = SHORT1FROMMP(mp1);
	SHORT note = SHORT2FROMMP(mp1);
	HWND hwndR = WinWindowFromID(hwnd, DID_R_TEXT);
	HWND hwndG = WinWindowFromID(hwnd, DID_G_TEXT);
	HWND hwndB = WinWindowFromID(hwnd, DID_B_TEXT);
	HWND hwndN = WinWindowFromID(hwnd, DID_N_TEXT);
	HWND hwndH = WinWindowFromID(hwnd, DID_HALFTONE);
	HWND hwndE = WinWindowFromID(hwnd, DID_ERRDIFF);

	switch ( id )
		{
		case DID_MAP_BW:
		case DID_MAP_8:
		case DID_MAP_VGA:
		case DID_MAP_4G:
		case DID_MAP_784:
		case DID_MAP_666:
		case DID_MAP_8G:
		case DID_MAP_TRIPEL:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndR, FALSE);
				WinEnableWindow(hwndG, FALSE);
				WinEnableWindow(hwndB, FALSE);
				WinEnableWindow(hwndN, FALSE);
				}
			break;
		case DID_MAP_RGB:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndR, TRUE);
				WinEnableWindow(hwndG, TRUE);
				WinEnableWindow(hwndB, TRUE);
				WinEnableWindow(hwndN, FALSE);
				}
			break;
		case DID_MAP_FREQ:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndR, TRUE);
				WinEnableWindow(hwndG, TRUE);
				WinEnableWindow(hwndB, TRUE);
				WinEnableWindow(hwndN, TRUE);
				}
			break;
		case DID_MAP_MCUT:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndR, FALSE);
				WinEnableWindow(hwndG, FALSE);
				WinEnableWindow(hwndB, FALSE);
				WinEnableWindow(hwndN, TRUE);
				}
			break;
		}

	switch ( id )
		{
		case DID_MAP_8G:
		case DID_MAP_TRIPEL:
		case DID_MAP_FREQ:
		case DID_MAP_MCUT:
			if ( note == BN_CLICKED )
				{
				WinSendDlgItemMsg(hwnd, DID_NEAREST, BM_CLICK, MPFROMSHORT(TRUE), NULL);
				WinEnableWindow(hwndH, FALSE);
				WinEnableWindow(hwndE, FALSE);
				}
			break;
		case DID_MAP_BW:
		case DID_MAP_4G:
			if ( note == BN_CLICKED )
				{
				WinSendDlgItemMsg(hwnd, DID_NEAREST, BM_CLICK, MPFROMSHORT(TRUE), NULL);
				WinEnableWindow(hwndH, FALSE);
				WinEnableWindow(hwndE, TRUE);
				}
			break;
		case DID_MAP_8:
		case DID_MAP_VGA:
		case DID_MAP_784:
		case DID_MAP_666:
		case DID_MAP_RGB:
			if ( note == BN_CLICKED )
				{
				WinEnableWindow(hwndH, TRUE);
				WinEnableWindow(hwndE, TRUE);
				}
			break;
		}
	}
	break;
/*...e*/
/*...sWM_COMMAND:16:*/
case WM_COMMAND:
	switch ( COMMANDMSG(&msg) -> cmd )
		{
/*...sDID_OK:32:*/
case DID_OK:
	{
	SHORT sInx;
	CHAR sz[50+1];
	int iPalNew, iAlgNew;
	int iKeepRedNew, iKeepGreenNew, iKeepBlueNew, nColsNew;

	sInx = SHORT1FROMMR(WinSendDlgItemMsg(hwnd, DID_MAP_BW, BM_QUERYCHECKINDEX, NULL, NULL)) - 1;
	switch ( sInx )
		{
		case DID_MAP_BW     - DID_MAP_BW:	iPalNew = CVT_BW;	break;
		case DID_MAP_VGA    - DID_MAP_BW:	iPalNew = CVT_VGA;	break;
		case DID_MAP_8      - DID_MAP_BW:	iPalNew = CVT_8;	break;
		case DID_MAP_4G     - DID_MAP_BW:	iPalNew = CVT_4G;	break;
		case DID_MAP_784    - DID_MAP_BW:	iPalNew = CVT_784;	break;
		case DID_MAP_666    - DID_MAP_BW:	iPalNew = CVT_666;	break;
		case DID_MAP_8G     - DID_MAP_BW:	iPalNew = CVT_8G;	break;
		case DID_MAP_TRIPEL - DID_MAP_BW:	iPalNew = CVT_TRIPEL;	break;
		case DID_MAP_RGB    - DID_MAP_BW:	iPalNew = CVT_RGB;	break;
		case DID_MAP_FREQ   - DID_MAP_BW:	iPalNew = CVT_FREQ;	break;
		case DID_MAP_MCUT   - DID_MAP_BW:	iPalNew = CVT_MCUT;	break;
		}

	WinEnableWindow(WinWindowFromID(hwnd, DID_NEAREST ), TRUE);
	WinEnableWindow(WinWindowFromID(hwnd, DID_HALFTONE), TRUE);
	WinEnableWindow(WinWindowFromID(hwnd, DID_ERRDIFF ), TRUE);

	sInx = SHORT1FROMMR(WinSendDlgItemMsg(hwnd, DID_NEAREST, BM_QUERYCHECKINDEX, NULL, NULL)) - 1;
	switch ( sInx )
		{
		case DID_NEAREST  - DID_NEAREST:	iAlgNew = CVT_NEAREST;	break;
		case DID_HALFTONE - DID_NEAREST:	iAlgNew = CVT_HALFTONE;	break;
		case DID_ERRDIFF  - DID_NEAREST:	iAlgNew = CVT_ERRDIFF;	break;
		}

	WinQueryDlgItemText(hwnd, DID_R, sizeof(sz), sz);
	sscanf(sz, "%d", &iKeepRedNew);
	WinQueryDlgItemText(hwnd, DID_G, sizeof(sz), sz);
	sscanf(sz, "%d", &iKeepGreenNew);
	WinQueryDlgItemText(hwnd, DID_B, sizeof(sz), sz);
	sscanf(sz, "%d", &iKeepBlueNew);
	WinQueryDlgItemText(hwnd, DID_N, sizeof(sz), sz);
	sscanf(sz, "%d", &nColsNew);

	if ( iKeepRedNew   < 0 || iKeepRedNew   > 8 )
		Warning(hwnd, "No. bits R must be between 0 and 8");
	else if ( iKeepGreenNew < 0 || iKeepGreenNew > 8 )
		Warning(hwnd, "No. bits G must be between 0 and 8");
	else if ( iKeepBlueNew  < 0 || iKeepBlueNew  > 8 )
		Warning(hwnd, "No. bits B must be between 0 and 8");
	else if ( nColsNew      < 1 || nColsNew      > 256 )
		Warning(hwnd, "No. bits B must be between 1 and 256");
	else
		{
		iPal       = iPalNew;
		iAlg       = iAlgNew;
		iKeepRed   = iKeepRedNew;
		iKeepGreen = iKeepGreenNew;
		iKeepBlue  = iKeepBlueNew;
		nCols      = nColsNew;
		WinDismissDlg(hwnd, TRUE);
		return (MRESULT) 0;
		}
	}
	return (MRESULT) 0;
/*...e*/
/*...sDID_CANCEL:32:*/
case DID_CANCEL:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
		}
	break;
/*...e*/
/*...sWM_CLOSE:16:*/
case WM_CLOSE:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
/*...sWM_HELP:16:*/
case WM_HELP:
	/* Parent is HWND_DESKTOP */
	/* WinDefDlgProc() will pass this up to the parent */
	/* So redirect to the owner */
	return WinSendMsg(WinQueryWindow(hwnd, QW_OWNER), msg, mp1, mp2);
/*...e*/
		}
	return WinDefDlgProc(hwnd, msg, mp1, mp2);
	}
/*...e*/
/*...sResizeDlgProc:0:*/
static int cxNew = -1, cyNew = -1;

MRESULT _System ResizeDlgProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( msg )
		{
/*...sWM_INITDLG:16:*/
case WM_INITDLG:
	{
	CHAR sz[100+1];

	sprintf(sz, "Bitmap is currently %dx%d at %dbpp",
		mod.gbm.w, mod.gbm.h, mod.gbm.bpp);
	WinSetDlgItemText(hwnd, DID_CURRENTLY, sz);

	WinSetFocus(HWND_DESKTOP, WinWindowFromID(hwnd, DID_WIDTH));

	if ( cxNew == -1 ) cxNew = mod.gbm.w;
	sprintf(sz, "%d", cxNew);
	WinSetDlgItemText(hwnd, DID_WIDTH, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_WIDTH ), NULL, NULL, "0123456789", NULL);

	if ( cyNew == -1 ) cyNew = mod.gbm.h;
	sprintf(sz, "%d", cyNew);
	WinSetDlgItemText(hwnd, DID_HEIGHT, sz);
	RestrictEntryfield(WinWindowFromID(hwnd, DID_HEIGHT), NULL, NULL, "0123456789", NULL);

	WinSetDlgItemText(hwnd, DID_MUL, "2");
	RestrictEntryfield(WinWindowFromID(hwnd, DID_MUL), NULL, NULL, "0123456789.", NULL);

	TidySysMenu(hwnd);
	return (MRESULT) TRUE; /* We have set the focus */
	}
/*...e*/
/*...sWM_COMMAND:16:*/
case WM_COMMAND:
	switch ( COMMANDMSG(&msg) -> cmd )
		{
/*...sDID_WIDTH_MUL:32:*/
case DID_WIDTH_MUL:
	{
	CHAR sz[40+1]; double w, n;
	WinQueryDlgItemText(hwnd, DID_WIDTH, sizeof(sz), sz);
	sscanf(sz, "%lf", &w);
	WinQueryDlgItemText(hwnd, DID_MUL, sizeof(sz), sz);
	sscanf(sz, "%lf", &n);
	w *= n;
	if ( w > 10000.0 )
		{
		Warning(hwnd, "Width of %6.2lf is too big", w);
		return (MRESULT) 0;
		}
	sprintf(sz, "%d", (int) w);
	WinSetDlgItemText(hwnd, DID_WIDTH, sz);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_WIDTH_DIV:32:*/
case DID_WIDTH_DIV:
	{
	CHAR sz[40+1]; double w, n;
	WinQueryDlgItemText(hwnd, DID_WIDTH, sizeof(sz), sz);
	sscanf(sz, "%lf", &w);
	WinQueryDlgItemText(hwnd, DID_MUL, sizeof(sz), sz);
	sscanf(sz, "%lf", &n);
	if ( n < 1e-10 )
		{
		Warning(hwnd, "N is too small");
		return (MRESULT) 0;
		}
	w /= n;
	if ( w > 10000.0 )
		{
		Warning(hwnd, "Width of %6.2lf is too big", w);
		return (MRESULT) 0;
		}
	sprintf(sz, "%d", (int) w);
	WinSetDlgItemText(hwnd, DID_WIDTH, sz);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_WIDTH_MATCH:32:*/
case DID_WIDTH_MATCH:
	{
	CHAR sz[40+1]; double w, h;
	WinQueryDlgItemText(hwnd, DID_HEIGHT, sizeof(sz), sz);
	sscanf(sz, "%lf", &h);
	w = ( h/mod.gbm.h ) * mod.gbm.w;
	if ( w > 10000.0 )
		{
		Warning(hwnd, "Width of %6.2lf is too big", w);
		return (MRESULT) 0;
		}
	sprintf(sz, "%d", (int) w);
	WinSetDlgItemText(hwnd, DID_WIDTH, sz);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_HEIGHT_MUL:32:*/
case DID_HEIGHT_MUL:
	{
	CHAR sz[40+1]; double h, n;
	WinQueryDlgItemText(hwnd, DID_HEIGHT, sizeof(sz), sz);
	sscanf(sz, "%lf", &h);
	WinQueryDlgItemText(hwnd, DID_MUL, sizeof(sz), sz);
	sscanf(sz, "%lf", &n);
	h *= n;
	if ( h > 10000.0 )
		{
		Warning(hwnd, "Height of %6.2lf is too big", h);
		return (MRESULT) 0;
		}
	sprintf(sz, "%d", (int) h);
	WinSetDlgItemText(hwnd, DID_HEIGHT, sz);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_HEIGHT_DIV:32:*/
case DID_HEIGHT_DIV:
	{
	CHAR sz[40+1]; double h, n;
	WinQueryDlgItemText(hwnd, DID_HEIGHT, sizeof(sz), sz);
	sscanf(sz, "%lf", &h);
	WinQueryDlgItemText(hwnd, DID_MUL, sizeof(sz), sz);
	sscanf(sz, "%lf", &n);
	if ( n < 1e-10 )
		{
		Warning(hwnd, "N is too small");
		return (MRESULT) 0;
		}
	h /= n;
	if ( h > 10000.0 )
		{
		Warning(hwnd, "Height of %6.2lf is too big", h);
		return (MRESULT) 0;
		}
	sprintf(sz, "%d", (int) h);
	WinSetDlgItemText(hwnd, DID_HEIGHT, sz);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_HEIGHT_MATCH:32:*/
case DID_HEIGHT_MATCH:
	{
	CHAR sz[40+1]; double w, h;
	WinQueryDlgItemText(hwnd, DID_WIDTH, sizeof(sz), sz);
	sscanf(sz, "%lf", &w);
	h = ( w/mod.gbm.w ) * mod.gbm.h;
	if ( h > 10000.0 )
		{
		Warning(hwnd, "Height of %6.2lf is too big", h);
		return (MRESULT) 0;
		}
	sprintf(sz, "%d", (int) h);
	WinSetDlgItemText(hwnd, DID_HEIGHT, sz);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_OK:32:*/
case DID_OK:
	{
	CHAR sz[50+1];

	WinQueryDlgItemText(hwnd, DID_WIDTH, sizeof(sz), sz);
	sscanf(sz, "%d", &cxNew);
	WinQueryDlgItemText(hwnd, DID_HEIGHT, sizeof(sz), sz);
	sscanf(sz, "%d", &cyNew);

	if ( cxNew < 1 || cyNew < 1 )
		Warning(hwnd, "Desired size is too small");
	else if ( cxNew > 10000 || cyNew > 10000 )
		Warning(hwnd, "Desired size is too big");
	else
		WinDismissDlg(hwnd, TRUE);
	return (MRESULT) 0;
	}
/*...e*/
/*...sDID_CANCEL:32:*/
case DID_CANCEL:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
		}
	break;
/*...e*/
/*...sWM_CLOSE:16:*/
case WM_CLOSE:
	WinDismissDlg(hwnd, FALSE);
	return (MRESULT) 0;
/*...e*/
/*...sWM_HELP:16:*/
case WM_HELP:
	/* Parent is HWND_DESKTOP */
	/* WinDefDlgProc() will pass this up to the parent */
	/* So redirect to the owner */
	return WinSendMsg(WinQueryWindow(hwnd, QW_OWNER), msg, mp1, mp2);
/*...e*/
		}
	return WinDefDlgProc(hwnd, msg, mp1, mp2);
	}
/*...e*/

/*...sRemoveExtension:0:*/
static VOID RemoveExtension(CHAR *sz)
	{
	CHAR *szDot;

	if ( (szDot = strrchr(sz, '.')) == NULL )
		return;

	if ( strchr(szDot+1, '\\') != NULL )
		return;

	*szDot = '\0';
	}
/*...e*/

MRESULT _System ObjectWndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( (int) msg )
		{
/*...sUM_NEW           \45\ clear out bitmap:16:*/
case UM_NEW:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
 
	DiscardUndo();

	if ( fGotBitmap )
		if ( SaveChanges(hwndClient) )
			{
			SetNoBitmap();

			ModDelete(&mod);
			fGotBitmap        = FALSE;
			fUnsavedChanges   = FALSE;
			fSelectionDefined = FALSE;
			strcpy(szFileName, "");

			Caption(hwndClient, "");
			}

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_OPEN          \45\ read in a new bitmap:16:*/
case UM_OPEN:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	GBMFILEDLG gbmfild;
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	if ( !SaveChanges(hwndClient) )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	memset(&(gbmfild.fild), 0, sizeof(FILEDLG));
	gbmfild.fild.cbSize = sizeof(FILEDLG);
	gbmfild.fild.fl = (FDS_CENTER|FDS_OPEN_DIALOG|FDS_HELPBUTTON);
	strcpy(gbmfild.fild.szFullFile, szFileName);
	strcpy(gbmfild.szOptions, "");	
	GbmFileDlg(HWND_DESKTOP, hwndClient, &gbmfild);
	if ( gbmfild.fild.lReturn != DID_OK )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	SetNoBitmap();

	LowPri();
	Caption(hwndClient, " - reading %s", gbmfild.fild.szFullFile);
	mrc = ModCreateFromFile(gbmfild.fild.szFullFile, gbmfild.szOptions, &modNew);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error loading %s: %s", gbmfild.fild.szFullFile, ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}
		
	if ( !MakeVisualBg(hwndClient, &modNew, 
	     bView, &hbmNew, &lColorBgNew, &lColorFgNew,
	     gbmfild.fild.szFullFile) )
		{
		ModDelete(&modNew);
		Caption(hwndClient, "");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	DiscardUndo();
	if ( fGotBitmap )
		ModDelete(&mod);

	ModMove(&modNew, &mod);
	fGotBitmap = TRUE;
	fUnsavedChanges = FALSE;
	fSelectionDefined = FALSE;
	strcpy(szFileName, gbmfild.fild.szFullFile);
	Caption(hwndClient, " - %s", FileName());

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_SAVE          \45\ save with old name:16:*/
case UM_SAVE:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD_ERR mrc;

	LowPri();
	Caption(hwndClient, " - saving %s", FileName());
	mrc = ModWriteToFile(&mod, szFileName, "");
	Caption(hwndClient, "- %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error saving %s: %s", FileName(), ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	fUnsavedChanges = FALSE;

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_SAVE_AS       \45\ save with new name:16:*/
case UM_SAVE_AS:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	GBMFILEDLG gbmfild;
	MOD_ERR mrc;

	memset(&gbmfild.fild, 0, sizeof(FILEDLG));
	gbmfild.fild.cbSize = sizeof(FILEDLG);
	gbmfild.fild.fl = (FDS_CENTER|FDS_SAVEAS_DIALOG|FDS_HELPBUTTON);
	strcpy(gbmfild.fild.szFullFile, szFileName);
	strcpy(gbmfild.szOptions, "");	
	GbmFileDlg(HWND_DESKTOP, hwndClient, &gbmfild);
	if ( gbmfild.fild.lReturn != DID_OK )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	LowPri();
	Caption(hwndClient, " - saving as %s", gbmfild.fild.szFullFile);
	mrc = ModWriteToFile(&mod, gbmfild.fild.szFullFile, gbmfild.szOptions);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error saving as %s: %s", gbmfild.fild.szFullFile, ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	strcpy(szFileName, gbmfild.fild.szFullFile);
	Caption(hwndClient, " - %s", FileName());
	fUnsavedChanges = FALSE;

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_EXPORT_MET    \45\ export to Metafile:16:*/
case UM_EXPORT_MET:
	{
	HAB hab = WinQueryAnchorBlock(hwnd);
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD_ERR mrc;
	BOOL fOk;
	HMF hmf;
	FILEDLG fild;

	memset(&fild, 0, sizeof(FILEDLG));
	fild.cbSize = sizeof(FILEDLG);
	fild.fl = (FDS_CENTER|FDS_SAVEAS_DIALOG);
	fild.pszTitle = "Export to MetaFile";
	if ( szFileName[0] != '\0' )
		{
		strcpy(fild.szFullFile, szFileName);
		RemoveExtension(fild.szFullFile);
		strcat(fild.szFullFile, ".MET");
		}
	else
		strcpy(fild.szFullFile, "EXPORT.MET");
	WinFileDlg(HWND_DESKTOP, hwndClient, &fild);
	if ( fild.lReturn != DID_OK )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	LowPri();
	Caption(hwndClient, " - making metafile");
	mrc = ModMakeHMF(hbm, hab, &hmf);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error making metafile: %s", ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	LowPri();
	Caption(hwndClient, " - exporting to %s", fild.szFullFile);
	remove(fild.szFullFile);
	fOk = GpiSaveMetaFile(hmf, fild.szFullFile);
	Caption(hwndClient, " - %s", FileName());
	RegPri();

	GpiDeleteMetaFile(hmf);

	if ( !fOk )
		{
		Warning(hwndClient, "Error exporting to %s", fild.szFullFile);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_PRINT         \45\ print image:16:*/
case UM_PRINT:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	HAB hab = WinQueryAnchorBlock(hwndClient);
	HBITMAP hbmCopy;
	BMP_ERR brc;
	CHAR sz[255+1];

	Caption(hwndClient, " - printing %s", FileName());
	LowPri();
	if ( (brc = BmpCopy(hab, hbm, &hbmCopy)) != BMP_ERR_OK )
		{
		RegPri();
		Caption(hwndClient, " - %s", FileName());
		Warning(hwndClient, "Error preparing to print %s: %s", FileName(), BmpErrorMessage(brc, sz));
		}
	else if ( (brc = BmpPrint(hab, hbmCopy, lColorFg, lColorBg, FileName(), szAppName, NULL)) != BMP_ERR_OK )
		{
		RegPri();
		Caption(hwndClient, " - %s", FileName());
		GpiDeleteBitmap(hbmCopy);
		Warning(hwndClient, "Error printing: %s", BmpErrorMessage(brc, sz));
		}
	else
		{
		RegPri();
		Caption(hwndClient, " - %s", FileName());
		GpiDeleteBitmap(hbmCopy);
		}

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}

/*...e*/
/*...sUM_SNAPSHOT      \45\ take snapshot of screen:16:*/
case UM_SNAPSHOT:
	{
	HAB hab = WinQueryAnchorBlock(hwnd);
	HWND hwndClient = HWNDFROMMP(mp1);
	int cxScreen = (int) WinQuerySysValue(HWND_DESKTOP, SV_CXSCREEN);
	int cyScreen = (int) WinQuerySysValue(HWND_DESKTOP, SV_CYSCREEN);
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;
	HDC hdcMem; SIZEL sizl; HPS hpsMem; HBITMAP hbmMem;
	struct
		{
		BITMAPINFOHEADER2 bmp2;
		RGB2 argb2Color[0x100];
		} bm;

	if ( (hdcMem = DevOpenDC(hab, OD_MEMORY, "*", 0L, (PDEVOPENDATA) NULL, (HDC) NULL)) == (HDC) NULL )
		{
		Warning(hwndClient, "Error snapshotting screen: can't create PM HDC resource");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	sizl.cx = cxScreen;
	sizl.cy = cyScreen;
	if ( (hpsMem = GpiCreatePS(hab, hdcMem, &sizl, PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC)) == (HPS) NULL )
		{
		DevCloseDC(hdcMem);
		Warning(hwndClient, "Error snapshotting screen: can't create PM HPS resource");
		return (MRESULT) 0;
		}

	memset(&(bm.bmp2), 0, sizeof(BITMAPINFOHEADER2));
	bm.bmp2.cbFix     = 16;
	bm.bmp2.cx        = cxScreen;
	bm.bmp2.cy        = cyScreen;
	bm.bmp2.cBitCount = lBitCountScreen;
	bm.bmp2.cPlanes   = 1;
	if ( (hbmMem = GpiCreateBitmap(hpsMem, &(bm.bmp2), 0L, NULL, (BITMAPINFO2 *) &bm)) == (HBITMAP) NULL )
		{
		GpiDestroyPS(hpsMem);
		DevCloseDC(hdcMem);
		Warning(hwndClient, "Error snapshotting screen: can't create PM HBITMAP resource");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}
	GpiSetBitmap(hpsMem, hbmMem);

	LowPri();
	Caption(hwndClient, " - snapshotting (untitled)");
/*...sdo the actual copy from the screen to hpsMem:24:*/
{
HWND hwndFrame = WinQueryWindow(hwndClient, QW_PARENT);
POINTL aptl[3];
HPS hps;

WinShowWindow(hwndFrame, FALSE);
DosSleep(2000L); /* Give other windows a chance to repaint */
hps = WinGetScreenPS(HWND_DESKTOP);
aptl[0].x = 0;
aptl[0].y = 0;
aptl[1].x = cxScreen;
aptl[1].y = cyScreen;
aptl[2].x = 0;
aptl[2].y = 0;
GpiBitBlt(hpsMem, hps, 3L, aptl, ROP_SRCCOPY, BBO_IGNORE);
WinReleasePS(hps);
WinShowWindow(hwndFrame, TRUE);
}
/*...e*/
	mrc = ModCreateFromHPS(hpsMem ,cxScreen, cyScreen,
		( lBitCountScreen > 8 ) ? 24 : lBitCountScreen,
		&modNew);
	Caption(hwndClient, " - %s", FileName());
	RegPri();

	GpiSetBitmap(hpsMem, (HBITMAP) NULL);
	GpiDeleteBitmap(hbmMem);
	GpiDestroyPS(hpsMem);
	DevCloseDC(hdcMem);

	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error snapshotting screen: %s", ModErrorString(mrc));
		WinSendMsg(hwndClient, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}		

	if ( !MakeVisualBg(hwndClient, &modNew, VIEW_NULL, &hbmNew, &lColorBgNew, &lColorFgNew, "(untitled)") )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	bView = VIEW_NULL;

	DiscardUndo();
	if ( fGotBitmap )
		{
		SetNoBitmap();
		ModDelete(&mod);
		fGotBitmap = FALSE;
		fUnsavedChanges = FALSE;
		fSelectionDefined = FALSE;
		}

	ModMove(&modNew, &mod);
	fGotBitmap = TRUE;
	fUnsavedChanges = TRUE;
	fSelectionDefined = FALSE;
	strcpy(szFileName, ""); /* Become untitled */
	Caption(hwndClient, " - %s", FileName());

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_UNDO          \45\ undo previous operation:16:*/
case UM_UNDO:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;
	const CHAR *szWhat;

	UseUndoBuffer(&modNew, &szWhat);

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( fGotBitmap )
		ModDelete(&mod);

	ModMove(&modNew, &mod);
	fGotBitmap        = TRUE;
	fUnsavedChanges   = TRUE;
	fSelectionDefined = FALSE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_SELECT        \45\ select region of bitmap:16:*/
case UM_SELECT:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	BITMAPINFOHEADER2 bmp;
	SWP swpBitmap, swpScroller;
	int cxScrollbar, cyScrollbar, x, y, cx, cy;

	bmp.cbFix = sizeof(BITMAPINFOHEADER2);
	GpiQueryBitmapInfoHeader(hbm, &bmp);
	fSelectionDefined = FALSE;
	WinInvalidateRect(hwndBitmap, NULL, TRUE);
	WinUpdateWindow(hwndBitmap);
	WinQueryWindowPos(hwndBitmap  , &swpBitmap  );
	WinQueryWindowPos(hwndScroller, &swpScroller);
	cxScrollbar = (int) WinQuerySysValue(HWND_DESKTOP, SV_CXVSCROLL);
	cyScrollbar = (int) WinQuerySysValue(HWND_DESKTOP, SV_CYHSCROLL);
	x = - (int) swpBitmap.x              ; if ( x < 0 ) x = 0;
	y = - (int) swpBitmap.y + cyScrollbar; if ( y < 0 ) y = 0;
	cx = (int) swpScroller.cx - cxScrollbar; if ( cx > (int) bmp.cx ) cx = (int) bmp.cx;
	cy = (int) swpScroller.cy - cyScrollbar; if ( cy > (int) bmp.cy ) cy = (int) bmp.cy;
	fSelectionDefined = Select(hwndClient, hwndBitmap, &rclSelection, x, y, cx, cy);
	if ( fSelectionDefined )
		{
		if ( rclSelection.xLeft   != rclSelection.xRight &&
		     rclSelection.yBottom != rclSelection.yTop   )
			{
			WinInvalidateRect(hwndBitmap, NULL, TRUE);
			WinUpdateWindow(hwndBitmap);
			}
		}

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_DESELECT      \45\ de\45\select any selcted area:16:*/
case UM_DESELECT:
	if ( fSelectionDefined )
		{
		fSelectionDefined = FALSE;
		WinInvalidateRect(hwndBitmap, NULL, TRUE);
		WinUpdateWindow(hwndBitmap);
		}

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
/*...e*/
/*...sUM_COPY          \45\ copy bitmap to clipboard:16:*/
case UM_COPY:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	if ( fSelectionDefined )
		CopyToClipbrd(hwndClient, rclSelection);
	else
		{
		RECTL rclAll;
		rclAll.xLeft = 0; rclAll.xRight = mod.gbm.w;
		rclAll.yBottom = 0; rclAll.yTop = mod.gbm.h;
		CopyToClipbrd(hwndClient, rclAll);
		}
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_PASTE         \45\ paste from clipboard:16:*/
case UM_PASTE:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	HAB hab = WinQueryAnchorBlock(hwndClient);
	HDC hdc; SIZEL sizl; HPS hps; HBITMAP hbmClipbrd;
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;
	struct
		{
		BITMAPINFOHEADER2 bmp2;
		RGB2 argb2Color[0x100];
		} bm;

	WinOpenClipbrd(hab);
	if ( (hbmClipbrd = (HBITMAP) WinQueryClipbrdData(hab, CF_BITMAP)) == (HBITMAP) NULL )
		/* Not able to get bitmap from the clipboard */
		{
		WinCloseClipbrd(hab);
		Warning(hwndClient, "Error pasting: can't access clipboard data");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	memset(&(bm.bmp2), 0, sizeof(bm.bmp2));
	bm.bmp2.cbFix = 16;
	GpiQueryBitmapInfoHeader(hbmClipbrd, &bm.bmp2);

	if ( lBitCountScreen < bm.bmp2.cBitCount )
		/* Data only actually stored in clipboard quality */
		bm.bmp2.cBitCount = lBitCountScreen;

	if ( bm.bmp2.cBitCount == 16 )
		bm.bmp2.cBitCount = 24;

	if ( bm.bmp2.cPlanes != 1 )
		{
		WinCloseClipbrd(hab);
		Warning(hwndClient, "Error pasting: don't know how to handle cPlanes != 1");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( (hdc = DevOpenDC(hab, OD_MEMORY, "*", 0L, (PDEVOPENDATA) NULL, (HDC) NULL)) == (HDC) NULL )
		{
		WinCloseClipbrd(hab);
		Warning(hwnd, "Error pasting: can't create PM HDC resource");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	sizl.cx = bm.bmp2.cx;
	sizl.cy = bm.bmp2.cy;
	if ( (hps = GpiCreatePS(hab, hdc, &sizl, PU_PELS | GPIF_DEFAULT | GPIT_MICRO | GPIA_ASSOC)) == (HPS) NULL )
		{
		DevCloseDC(hdc);
		WinCloseClipbrd(hab);
		Warning(hwnd, "Error pasting: can't create PM HPS resource");
		return (MRESULT) 0;
		}

	LowPri();
	Caption(hwndClient, " - pasting %s", FileName());
	GpiSetBitmap(hps, hbmClipbrd);
	mrc = ModCreateFromHPS(hps, bm.bmp2.cx, bm.bmp2.cy,
		( bm.bmp2.cBitCount > 8 ) ? 24 : bm.bmp2.cBitCount,
		&modNew);
	GpiSetBitmap(hps, (HBITMAP) NULL);
	Caption(hwndClient, " - %s", FileName());
	RegPri();

	GpiDestroyPS(hps);
	DevCloseDC(hdc);
	WinCloseClipbrd(hab);

	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error pasting: can't query bitmap bits from clipboard");
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	/* Now modNew contains a bitmap from the clipboard */

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, "(untitled)") )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( fGotBitmap )
		{
		fGotBitmap        = FALSE;
		fUnsavedChanges   = FALSE;
		fSelectionDefined = FALSE;
		KeepForUndo(&mod, "paste");
		SetNoBitmap();
		}
	else		
		DiscardUndo();

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fGotBitmap        = TRUE;
	fUnsavedChanges   = TRUE;
	fSelectionDefined = FALSE;
	Caption(hwndClient, " - %s", FileName());

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_REF_HORZ      \45\ reflect horizontally:16:*/
case UM_REF_HORZ:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	Reflect(hwndClient, ModReflectHorz, "reflecting horizontally", "horizontal reflection");
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_REF_VERT      \45\ reflect vertically:16:*/
case UM_REF_VERT:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	Reflect(hwndClient, ModReflectVert, "reflecting vertically", "vertical reflection");
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_ROT_90        \45\ rotate 90 degrees:16:*/
case UM_ROT_90:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	Reflect(hwndClient, ModRotate90, "rotating by 90 degrees", "90 degree rotation");
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_ROT_180       \45\ rotate 180 degrees:16:*/
case UM_ROT_180:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	Reflect(hwndClient, ModRotate180, "rotating by 180 degrees", "180 degree rotation");
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_ROT_270       \45\ rotate 270 degrees:16:*/
case UM_ROT_270:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	Reflect(hwndClient, ModRotate270, "rotating by 270 degrees", "270 degree rotation");
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_TRANSPOSE     \45\ transpose x for y:16:*/
case UM_TRANSPOSE:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	Reflect(hwndClient, ModTranspose, "transposing", "transposition");
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_CROP          \45\ crop to selection:16:*/
case UM_CROP:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	LowPri();
	Caption(hwndClient, " - cropping %s", FileName());
	mrc = ModExtractSubrectangle(&mod,
		rclSelection.xLeft,
		rclSelection.yBottom,
		rclSelection.xRight - rclSelection.xLeft,
		rclSelection.yTop - rclSelection.yBottom,
		&modNew);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error cropping: %s", ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	KeepForUndo(&mod, "cropping");

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fUnsavedChanges = TRUE;
	fSelectionDefined = FALSE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_COLOUR        \45\ colour space:16:*/
case UM_COLOUR:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	if ( !WinDlgBox(HWND_DESKTOP, hwndClient, ColourDlgProc, (HMODULE) NULL, RID_DLG_COLOUR, NULL) )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}
	
	LowPri();
	Caption(hwndClient, " - colour space mapping %s", FileName());
	mrc = ModColourAdjust(&mod, map, gama, shelf, &modNew);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error colour space mapping %s: %s", FileName(), ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	KeepForUndo(&mod, "mapping");

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fUnsavedChanges = TRUE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_MAP           \45\ map:16:*/
case UM_MAP:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD_ERR mrc; MOD modNew;
	BYTE bViewFastSuggested, bViewFast;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	if ( !WinDlgBox(HWND_DESKTOP, hwndClient, MapDlgProc, (HMODULE) NULL, RID_DLG_MAP, NULL) )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

/*...sspeedup rendering later:24:*/
{
CHAR *szFast =	"You have just selected a mapping to a palette which has all "
		"of its colours in the screen palette. Therefore halftoning "
		"or error diffusion will cause no improvement - change View "
		"setting to Raw PM mapping?";
bViewFastSuggested = bViewFast = bView;

switch ( iPal )
	{
	case CVT_BW:
		bViewFastSuggested = VIEW_NULL;
		break;
	case CVT_8:
	case CVT_VGA:
		if ( lBitCountScreen == 4 )
			bViewFastSuggested = VIEW_NULL;
		break;
	case CVT_784:
		if ( lBitCountScreen == 8 )
			bViewFastSuggested = VIEW_NULL;
		break;
	case CVT_FREQ:
	case CVT_RGB:
		if ( lBitCountScreen == 16 &&
		     iKeepRed   <= 5 &&
		     iKeepGreen <= 6 &&
		     iKeepBlue  <= 5 )
			bViewFastSuggested = VIEW_NULL;
		break;
	}

if ( bViewFastSuggested != bView )
	switch ( WinMessageBox(HWND_DESKTOP, hwndClient, szFast, szAppName, 0, MB_YESNOCANCEL | MB_WARNING | MB_MOVEABLE) )
		{
		case MBID_YES:
			bViewFast = bViewFastSuggested;
			break;
		case MBID_NO:
			break;
		case MBID_CANCEL:
			WinSendMsg(hwnd, UM_DONE, NULL, NULL);
			return (MRESULT) 0;
		}
}
/*...e*/
	
	LowPri();
	Caption(hwndClient, " - mapping %s", FileName());
	mrc = ModBppMap(&mod, iPal, iAlg, iKeepRed, iKeepGreen, iKeepBlue, nCols, &modNew);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error mapping %s: %s", FileName(), ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( !MakeVisualBg(hwndClient, &modNew, bViewFast, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	bView = bViewFast;

	KeepForUndo(&mod, "mapping");

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fUnsavedChanges = TRUE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_RESIZE        \45\ resize:16:*/
case UM_RESIZE:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	MOD_ERR mrc; MOD modNew;
	HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

	if ( !WinDlgBox(HWND_DESKTOP, hwndClient, ResizeDlgProc, (HMODULE) NULL, RID_DLG_RESIZE, NULL) )
		{
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}
	
	LowPri();
	Caption(hwndClient, " - resizing %s", FileName());
	mrc = ModResize(&mod, cxNew, cyNew, &modNew);
	Caption(hwndClient, " - %s", FileName());
	RegPri();
	if ( mrc != MOD_ERR_OK )
		{
		Warning(hwndClient, "Error resizing %s: %s", FileName(), ModErrorString(mrc));
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	if ( !MakeVisualBg(hwndClient, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew, FileName()) )
		{
		ModDelete(&modNew);
		WinSendMsg(hwnd, UM_DONE, NULL, NULL);
		return (MRESULT) 0;
		}

	KeepForUndo(&mod, "resizing");

    ModDelete(&mod);
	ModMove(&modNew, &mod);
	fSelectionDefined = FALSE;
	fUnsavedChanges   = TRUE;

	SetBitmap(hbmNew, lColorBgNew, lColorFgNew);

	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_VIEW_NULL     \45\ view with no transform:16:*/
case UM_VIEW_NULL:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	NewView(hwndClient, VIEW_NULL);
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_VIEW_HALFTONE \45\ view with error diffusion:16:*/
case UM_VIEW_HALFTONE:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	NewView(hwndClient, VIEW_HALFTONE);
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_VIEW_ERRDIFF  \45\ view with halftoning:16:*/
case UM_VIEW_ERRDIFF:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	NewView(hwndClient, VIEW_ERRDIFF);
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_ABOUT         \45\ about box:16:*/
case UM_ABOUT:
	{
	HWND hwndClient = HWNDFROMMP(mp1);
	WinDlgBox(HWND_DESKTOP, hwndClient, AboutDlgProc, (HMODULE) NULL, RID_DLG_ABOUT, NULL);
	WinSendMsg(hwnd, UM_DONE, NULL, NULL);
	return (MRESULT) 0;
	}
/*...e*/
/*...sUM_DONE          \45\ done\44\ and set pointer back:16:*/
case UM_DONE:
	{
	POINTL ptl;
	fBusy = FALSE;
	WinQueryPointerPos(HWND_DESKTOP, &ptl);
	WinSetPointerPos(HWND_DESKTOP, ptl.x, ptl.y);
	return (MRESULT) 0;
	}
/*...e*/
		}
	return WinDefWindowProc(hwnd, msg, mp1, mp2);
	}
/*...e*/
/*...sObjectThread:0:*/
#define	CB_STACK_OBJECT	0xf000

static VOID _Optlink ObjectThread(VOID *pvParams)
	{
	HAB hab = WinInitialize(0);
	HMQ hmq = WinCreateMsgQueue(hab, 0);
	QMSG qmsg;

	pvParams=pvParams; /* Suppress 'unref' compiler warning */

	WinRegisterClass(hab, WC_GBMV2_OBJECT, ObjectWndProc, 0L, 0);

	hwndObject = WinCreateWindow(HWND_OBJECT, WC_GBMV2_OBJECT, NULL, 0L, 0,0,0,0,
			HWND_DESKTOP, HWND_BOTTOM, 0, NULL, NULL);

	while ( WinGetMsg(hab, &qmsg, (HWND) NULL, 0, 0) )
		WinDispatchMsg(hab, &qmsg);

	WinDestroyWindow(hwndObject);

	WinDestroyMsgQueue(hmq);
	WinTerminate(hab);
	}
/*...e*/
/*...sGbmV2WndProc:0:*/
#define	WC_GBMV2 "GbmV2Class"

/*...smap MID_ to UM_:0:*/
typedef struct { int mid, um; } MIDUM;

static MIDUM mid_um[] =
	{
	MID_NEW,		UM_NEW,
	MID_OPEN,		UM_OPEN,
	MID_SAVE,		UM_SAVE,
	MID_SAVE_AS,		UM_SAVE_AS,
	MID_EXPORT_MET,		UM_EXPORT_MET,
	MID_PRINT,		UM_PRINT,
	MID_SNAPSHOT,		UM_SNAPSHOT,
	MID_UNDO,		UM_UNDO,
	MID_SELECT,		UM_SELECT,
	MID_DESELECT,		UM_DESELECT,
	MID_COPY,		UM_COPY,
	MID_PASTE,		UM_PASTE,
	MID_REF_HORZ,		UM_REF_HORZ,
	MID_REF_VERT,		UM_REF_VERT,
	MID_ROT_90,		UM_ROT_90,
	MID_ROT_180,		UM_ROT_180,
	MID_ROT_270,		UM_ROT_270,
	MID_TRANSPOSE,		UM_TRANSPOSE,
	MID_CROP,		UM_CROP,
	MID_COLOUR,		UM_COLOUR,
	MID_MAP,		UM_MAP,
	MID_RESIZE,		UM_RESIZE,
	MID_VIEW_NULL,		UM_VIEW_NULL,
	MID_VIEW_HALFTONE,	UM_VIEW_HALFTONE,
	MID_VIEW_ERRDIFF,	UM_VIEW_ERRDIFF,
	MID_ABOUT,		UM_ABOUT,
	};

#define	N_MID_UM (sizeof(mid_um)/sizeof(mid_um[0]))
/*...e*/

MRESULT _System GbmV2WndProc(HWND hwnd, ULONG msg, MPARAM mp1, MPARAM mp2)
	{
	switch ( (int) msg )
		{
/*...sWM_PAINT     \45\ repaint client area:16:*/
/*
Dead simple, as this window is usually completely covered by the scroller
window.
*/

case WM_PAINT:
	{
	HPS hps = WinBeginPaint(hwnd, (HPS) NULL, (RECTL *) NULL);

	GpiSetBackColor(hps, CLR_RED);

	GpiErase(hps);

	WinEndPaint(hps);
	}
	return (MRESULT) 0;
/*...e*/
/*...sWM_SIZE      \45\ user has resized window:16:*/
case WM_SIZE:
	{
	SWP swp;

	WinQueryWindowPos(hwnd, &swp);
	WinSetWindowPos(hwndScroller, (HWND) NULL, 0, 0, swp.cx, swp.cy, SWP_SIZE);
	}
	break;
/*...e*/
/*...sWM_INITMENU  \45\ enable right menuitems:16:*/
case WM_INITMENU:
	{
	HWND hwndFrame = WinQueryWindow(hwnd, QW_PARENT);
	HWND hwndMenu = WinWindowFromID(hwndFrame, FID_MENU);

	switch ( SHORT1FROMMP(mp1) )
		{
/*...sMID_FILE:32:*/
case MID_FILE:
	EnableMenuItem(hwndMenu, MID_NEW       , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_OPEN      , !fBusy              );
	EnableMenuItem(hwndMenu, MID_SAVE      , !fBusy && fGotBitmap && (szFileName[0] != '\0') );
	EnableMenuItem(hwndMenu, MID_SAVE_AS   , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_EXPORT_MET, !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_PRINT     , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_SNAPSHOT  , !fBusy              );
	return (MRESULT) 0;
/*...e*/
/*...sMID_EDIT:32:*/
case MID_EDIT:
	{
	CHAR szUndo[100+1];
	ULONG ulInfo;
	HAB hab = WinQueryAnchorBlock(hwnd);
	BOOL fClipbrd = WinQueryClipbrdFmtInfo(hab, CF_BITMAP, &ulInfo);

	EnableMenuItem(hwndMenu, MID_UNDO      , !fBusy && fCanUndo);
	if ( fCanUndo )
		sprintf(szUndo, "~Undo %s", szWhatUndo);
	else
		sprintf(szUndo, "~Undo");
	WinSendMsg(hwndMenu, MM_SETITEMTEXT, MPFROMSHORT(MID_UNDO), MPFROMP(szUndo));
	EnableMenuItem(hwndMenu, MID_SELECT    , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_DESELECT  , !fBusy && fSelectionDefined);
	EnableMenuItem(hwndMenu, MID_COPY      , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_PASTE     , !fBusy && fClipbrd);
	return (MRESULT) 0;
	}
/*...e*/
/*...sMID_BITMAP:32:*/
case MID_BITMAP:
	{
	BOOL fSquare = !fSelectionDefined ||
		( rclSelection.xRight - rclSelection.xLeft ==
		  rclSelection.yTop - rclSelection.yBottom );
	EnableMenuItem(hwndMenu, MID_REF_HORZ , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_REF_VERT , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_ROT_90   , !fBusy && fGotBitmap && fSquare);
	EnableMenuItem(hwndMenu, MID_ROT_180  , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_ROT_270  , !fBusy && fGotBitmap && fSquare);
	EnableMenuItem(hwndMenu, MID_TRANSPOSE, !fBusy && fGotBitmap && fSquare);
	EnableMenuItem(hwndMenu, MID_CROP     , !fBusy && fGotBitmap && fSelectionDefined);
	EnableMenuItem(hwndMenu, MID_COLOUR   , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_MAP      , !fBusy && fGotBitmap);
	EnableMenuItem(hwndMenu, MID_RESIZE   , !fBusy && fGotBitmap);
	return (MRESULT) 0;
	}
/*...e*/
/*...sMID_VIEW:32:*/
case MID_VIEW:
	{
	BOOL fHalftone = ( lBitCountScreen == 4 || lBitCountScreen == 8 || lBitCountScreen == 16 );
	BOOL fErrDiff  = ( lBitCountScreen == 4 || lBitCountScreen == 8 || lBitCountScreen == 16 );

/* Don't disable menu item when it is checked. This is against the PM menu rules and confuses the user
	EnableMenuItem(hwndMenu, MID_VIEW_NULL    , !fBusy && fGotBitmap && bView != VIEW_NULL                 );
	EnableMenuItem(hwndMenu, MID_VIEW_HALFTONE, !fBusy && fGotBitmap && bView != VIEW_HALFTONE && fHalftone);
	EnableMenuItem(hwndMenu, MID_VIEW_ERRDIFF , !fBusy && fGotBitmap && bView != VIEW_ERRDIFF  && fErrDiff );
*/
	EnableMenuItem(hwndMenu, MID_VIEW_NULL    , !fBusy && fGotBitmap             );
	EnableMenuItem(hwndMenu, MID_VIEW_HALFTONE, !fBusy && fGotBitmap && fHalftone);
	EnableMenuItem(hwndMenu, MID_VIEW_ERRDIFF , !fBusy && fGotBitmap && fErrDiff );
	CheckMenuItem (hwndMenu, MID_VIEW_NULL    , bView == VIEW_NULL    );
	CheckMenuItem (hwndMenu, MID_VIEW_HALFTONE, bView == VIEW_HALFTONE);
	CheckMenuItem (hwndMenu, MID_VIEW_ERRDIFF , bView == VIEW_ERRDIFF );
	return (MRESULT) 0;
	}
/*...e*/
		}
	}
	break;
/*...e*/
/*...sWM_COMMAND   \45\ menu command:16:*/
case WM_COMMAND:
	{
	USHORT cmd = COMMANDMSG(&msg)->cmd;
	int i;

	/* Try to handle majority case */
	for ( i = 0; i < N_MID_UM; i++ )
		if ( mid_um[i].mid == cmd )
			{
			fBusy = TRUE;
			WinPostMsg(hwndObject, mid_um[i].um, MPFROMHWND(hwnd), NULL);
			return (MRESULT) 0;
			}

	/* Now try infrequent cases */
	switch ( cmd )
		{
/*...sMID_HELP_FOR_HELP \45\ bring up help for help:32:*/
case MID_HELP_FOR_HELP:
	HlpHelpForHelp(hwndHelp);
	break;
/*...e*/
/*...sMID_EXIT          \45\ initiate shutdown of this app:32:*/
case MID_EXIT:
	WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
	break;
/*...e*/
		}

	return (MRESULT) 0;
	}
/*...e*/
/*...sWM_CLOSE     \45\ close window:16:*/
case WM_CLOSE:
	WinPostMsg(hwnd, WM_QUIT, 0L, 0L);
	return (MRESULT) 0;
/*...e*/
/*...sWM_CONTROL   \45\ override scrolling amount:16:*/
case WM_CONTROL:
	if ( SHORT1FROMMP(mp1) == WID_SCROLL )
		switch ( SHORT2FROMMP(mp1) )
			{
			case SCN_HPAGE:
			case SCN_VPAGE:
				return (MRESULT) SHORT2FROMMP(mp2);
			}
	break;
/*...e*/
/*...sWM_ACTIVATE  \45\ ensure help instance is ours:16:*/
case WM_ACTIVATE:
	{
	HWND hwndFrame = WinQueryWindow(hwnd, QW_PARENT);

	if ( mp1 && hwndHelp != (HWND) NULL )
		HlpActivate(hwndHelp, hwndFrame);
	}
	break;
/*...e*/
/*...sHM_\42\         \45\ redirect help support:16:*/
case HM_ERROR:
case HM_INFORM:
case HM_QUERY_KEYS_HELP:
case HM_EXT_HELP_UNDEFINED:
case HM_HELPSUBITEM_NOT_FOUND:
	return HlpWndProc(hwnd, msg, mp1, mp2);
/*...e*/
		}
	return WinDefWindowProc(hwnd, msg, mp1, mp2);
	}
/*...e*/
/*...smain:0:*/
int main(int argc, CHAR *argv[])
	{
	HAB hab = WinInitialize(0);
	HMQ hmq = WinCreateMsgQueue(hab, 0);
	QMSG qmsg;
	HWND hwndFrame, hwndClient;
	SWP swp;
	static ULONG flFrameFlags = FCF_TITLEBAR      | FCF_SYSMENU    |
				    FCF_SIZEBORDER    | FCF_MINMAX     |
				    FCF_MENU          | FCF_ICON       |
				    FCF_SHELLPOSITION | FCF_TASKLIST   |
				    FCF_ACCELTABLE    ;

	gbm_init();

/*...sdetermine lBitCountScreen:8:*/
{
HPS hps = WinGetPS(HWND_DESKTOP);
HDC hdc = GpiQueryDevice(hps);
DevQueryCaps(hdc, CAPS_COLOR_BITCOUNT, 1L, &lBitCountScreen);
WinReleasePS(hps);
}
/*...e*/

	DosCreateMutexSem(NULL, &hmtxHbm, 0 /*=Private*/, FALSE /*=unowned*/ );

	RegisterScrollClass(hab);
	WinRegisterClass(hab, WC_GBMV2, GbmV2WndProc, CS_CLIPCHILDREN|CS_SIZEREDRAW, 0);
	WinRegisterClass(hab, WC_BITMAP, BitmapWndProc, 0L, 0);

	hwndFrame = WinCreateStdWindow(
		HWND_DESKTOP,		/* Parent window handle              */
		WS_VISIBLE,		/* Style of frame window             */
		&flFrameFlags,		/* Pointer to control data           */
		WC_GBMV2,		/* Client window class name          */
		NULL,			/* Title bar text                    */
		0L,			/* Style of client window            */
		(HMODULE) NULL,		/* Module handle for resources       */
		RID_GBMV2,		/* ID of resources                   */
		&hwndClient);		/* Pointer to client window handle   */

	WinSetWindowText(hwndFrame, szAppName);

	WinQueryWindowPos(hwndClient, &swp);

	hwndScroller = WinCreateWindow(hwndClient, WC_SCROLL, "", WS_VISIBLE|SCS_HSCROLL|SCS_VSCROLL|SCS_HCENTRE|SCS_VCENTRE|SCS_HPAGE|SCS_VPAGE,
	     0,0,swp.cx,swp.cy, hwndClient, HWND_BOTTOM, WID_SCROLL, NULL, NULL);
	hwndBitmap = WinCreateWindow(hwndScroller, WC_BITMAP, "", WS_VISIBLE|WS_CLIPSIBLINGS,
	     0,0,0,0, hwndScroller, HWND_BOTTOM, WID_BITMAP, NULL, NULL);
	WinSetFocus(HWND_DESKTOP, hwndBitmap);

	_beginthread(ObjectThread, NULL, CB_STACK_OBJECT, NULL);

	if ( argc >= 2 )
		{
		CHAR *szOpt;
		MOD_ERR mrc; MOD modNew;
		HBITMAP hbmNew; LONG lColorBgNew, lColorFgNew;

		if ( (szOpt = strchr(argv[1], ',')) != NULL )
			*szOpt++ = '\0';
		else
			szOpt = "";

		if ( (mrc = ModCreateFromFile(argv[1], szOpt, &modNew)) != MOD_ERR_OK )
			Warning(HWND_DESKTOP, "Error loading %s: %s", argv[1], ModErrorString(mrc));
		else if ( !MakeVisual(HWND_DESKTOP, &modNew, bView, &hbmNew, &lColorBgNew, &lColorFgNew) )
			{
			ModDelete(&modNew);
			Warning(HWND_DESKTOP, "Error loading %s: can't create view bitmap", argv[1]);
			}
		else
			{
			ModMove(&modNew, &mod);
			fGotBitmap = TRUE;
			strcpy(szFileName, argv[1]);
			Caption(hwndClient, " - %s", szFileName);
			SetBitmap(hbmNew, lColorBgNew, lColorFgNew);
			}
		}

	if ( (hwndHelp = HlpInit(hwndClient,
			   (HMODULE) NULL, RID_HELP_TABLE,
			   "GBMV2.HLP GBMDLG.HLP", szAppName)) != (HWND) NULL )
		HlpActivate(hwndHelp, hwndFrame);

	do
		for ( ;; )
			{
			while ( WinGetMsg(hab, &qmsg, (HWND) NULL, 0, 0) )
				WinDispatchMsg(hab, &qmsg);
			if ( !fBusy )
				break;
			Warning(hwndFrame, "Cannot Close yet, %s is busy, please wait and retry", szAppName);
			}
	while ( !SaveChanges(hwndFrame) );

	if ( hwndHelp != (HWND) NULL )
		HlpDeinit(hwndHelp);

	WinPostMsg(hwndObject, WM_QUIT, NULL, NULL);

	WinDestroyWindow(hwndBitmap);
	WinDestroyWindow(hwndScroller);
	WinDestroyWindow(hwndFrame);

	DosCloseMutexSem(hmtxHbm);

	gbm_deinit();

	WinDestroyMsgQueue(hmq);
	WinTerminate(hab);

	return 0;
	}
/*...e*/
