File gui-windows.c of Package arachne

/*
gui-windows.c
Author: Hayden Walles
Date: 7 March 2008
Windows support for Arachne.
*/


/*
    Copyright (C) 2008 Hayden Walles

    This file is part of Arachne.

    Arachne is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Arachne is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/


#include "arachne.h"


HIMAGELIST hToolbarImageList=NULL;
HANDLE hInstance;
char *HelpPath=NULL;
int nCmdShow;
int Shown=0;

void setWorkToSize(LOOM *loom,int width,int height);


char *vasprintf(char *fmt,va_list ap){
  va_list cap;
  int len;
  char *buffer;
  va_copy(cap,ap);
  
  len=vsnprintf(NULL,0,fmt,ap);
  
  buffer=malloc(len+1);
  if(buffer==NULL)
    return NULL;
  //  va_end(ap);

  vsnprintf(buffer,len+1,fmt,cap);
  va_end(cap);

  return buffer;

}


char *asprintf(char *fmt, ...){
  va_list ap;
  char *new;
  
  va_start(ap,fmt);
  new=vasprintf(fmt,ap);
  va_end(ap);
  return new;

}

  
void setTitle(LOOM *loom){
  char *slashptr,*msg;

  if(loom->filename==NULL)
    SetWindowText(loom->window,"Arachne");
  else {
    if((slashptr=strrchr(loom->filename,'\\'))||(slashptr=strrchr(loom->filename,'/')))
      slashptr++;
    else
      slashptr=loom->filename;
    
    msg=asprintf("Arachne - %s",slashptr);
    SetWindowText(loom->window,msg);
    free(msg);
  }

}

/*This takes a prosed window rect and determines the corresponding workspace rect.*/
int winRectToWorkRect(HWND hWnd,HWND hToolbar, HWND hStatusbar, RECT *win, RECT *work){
  /*We use WM_NCCALCSIZE to get the window to tell us what the client space would be.  The main window doesn't have any redraw styles so this shouldn't cause anything to be repainted.  It's a bit sneaky though.*/
  RECT rect;

  *work=*win;
  SendMessage(hWnd,WM_NCCALCSIZE,FALSE,(LPARAM)work);
  
  /*Now we subtract the height of the toolbar and the status bar.  We assume these never change height, which is true at the moment.  If we ever allow the toolbar to change height, things will get trickier.*/

  /*The toolbar contribution*/
  GetWindowRect(hToolbar,&rect);
  work->top+=rect.bottom-rect.top;

  /*The statusbar contribution*/
  GetClientRect(hStatusbar,&rect);
  work->bottom-=rect.bottom;

  /*And work should now contain the screen rect of the work area for the specified window size.*/

  return 1;


}



/*This takes the dimensions of a workspace rect and determines the corresponding minimum window size.  It takes into account the menu, toolbar and status bar.  The workspace is the part of the client area where the image is displayed.  It'salmost the inverse of winRectToWorkRect, but it's more useful to use widths and heights than actual rects going in this direction.*/
int workPtToWinPt(HWND hWnd,HWND hToolbar,HWND hStatusbar, POINT *workpt, POINT *windowpt){
  RECT rect,sr;
  int dclientheight;
  /*I think this is foolproof - Windows doesn't make this very easy though.

    We begin by estimating the total client rect required to accommodate the desired workspace.*/

  /*The workspace*/
  rect.left=0;
  rect.right=workpt->x;
  rect.top=0;
  rect.bottom=workpt->y;

  /*The toolbar contribution (assumes toolbar is always at top.  I think this is under my control)*/
  GetWindowRect(hToolbar,&sr);
  rect.bottom+=sr.bottom-sr.top;


  /*The statusbar contribution*/
  GetClientRect(hStatusbar,&sr);
  rect.bottom+=sr.bottom;

  dclientheight=rect.bottom; /*Save this for later.*/
  /*Use this to estimate the window size required.*/
  AdjustWindowRect(&rect,WS_OVERLAPPEDWINDOW,TRUE); 

  /*It's possible for the menu to take more space than this accounts for.  We can correct for this by asking the window to compute the _actual_ client area...*/
  sr=rect;
  SendMessage(hWnd,WM_NCCALCSIZE,FALSE,(LPARAM)&sr);
  /*And the difference between this and the desired size is the compensation.*/


  windowpt->x=rect.right-rect.left;
  windowpt->y=rect.bottom-rect.top + (dclientheight-(sr.bottom-sr.top));

  /*And that's the end of the computation.*/
  return;

}

void openCommand(LOOM *loom){
  OPENFILENAME ofn;
  char buffer[1024]="";
  ofn.lStructSize=sizeof(ofn);
  ofn.hwndOwner=loom->window;
  ofn.hInstance=NULL;
  ofn.lpstrFilter="All Supported Images\0*.JPEG;*.JPG;*.TIF\0JPEG Images\0*.JPEG;*.JPG\0TIFF Images\0*.TIF\0";
  ofn.lpstrCustomFilter=NULL;
  ofn.nMaxCustFilter=0;
  ofn.nFilterIndex=1;
  ofn.lpstrFile=buffer;
  ofn.nMaxFile=1024;
  ofn.lpstrFileTitle=NULL;
  ofn.nMaxFileTitle=0;
  
  ofn.lpstrInitialDir=NULL;
  ofn.lpstrTitle=NULL;
  ofn.Flags=OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY;
  ofn.lpstrDefExt=NULL;
  ofn.pvReserved=NULL;
  ofn.dwReserved=0;
  ofn.FlagsEx=0;

  if(GetOpenFileName(&ofn)){
    replaceLoomFromFile(loom,ofn.lpstrFile);
  }

  
}

void saveAsCommand(LOOM *loom){
  OPENFILENAME ofn;
  char buffer[1024]="";
  char *dotptr,*slashptr,*name;
  int len;

  if((loom->mode==LOOM_MODE_ORIG)||(loom->map==NULL)){
    guiReportError(loom,"The current image does not differ from the initial image - there is nothing to save.");
    return;
  }

  /*Construct a default name.*/
  if((slashptr=strrchr(loom->filename,'\\'))||(slashptr=strrchr(loom->filename,'/')))
    slashptr++;
  else
    slashptr=loom->filename;

  dotptr=strrchr(slashptr,'.');
  if((dotptr==NULL)||(dotptr==slashptr))
    len=strlen(slashptr);
  else
    len=dotptr-slashptr;

  if(len>1024-32){ /*This gives the inserted stuff _plenty_ of room.*/
    /*To prevent a buffer overrun.*/
    slashptr="image";
    len=5;
  }

  if(loom->mode==LOOM_MODE_HORZ)
    sprintf(buffer,"%.*s_%dx%d.jpg",len,slashptr,loom->span,imageHeight(loom->image)); 
  else
    sprintf(buffer,"%.*s_%dx%d.jpg",len,slashptr,imageWidth(loom->image),loom->span); 

  ofn.lStructSize=sizeof(ofn);
  ofn.hwndOwner=loom->window;
  ofn.hInstance=NULL;
  ofn.lpstrFilter="JPEG Images\0*.JPEG;*.JPG\0TIFF Images\0*.TIF\0";
  ofn.lpstrCustomFilter=NULL;
  ofn.nMaxCustFilter=0;
  ofn.nFilterIndex=1;
  ofn.lpstrFile=buffer;
  ofn.nMaxFile=1024;
  ofn.lpstrFileTitle=NULL;
  ofn.nMaxFileTitle=0;
  
  ofn.lpstrInitialDir=NULL;
  ofn.lpstrTitle=NULL;
  ofn.Flags=OFN_PATHMUSTEXIST|OFN_OVERWRITEPROMPT|OFN_HIDEREADONLY;
  ofn.lpstrDefExt=NULL;
  ofn.pvReserved=NULL;
  ofn.dwReserved=0;
  ofn.FlagsEx=0;

  if(GetSaveFileName(&ofn)){
    if((ofn.nFileExtension==0)||(ofn.lpstrFile[ofn.nFileExtension]==0)){
      /*Make a reasonable guess about the desired extension.*/
      /*A curiosity here: the behaviour of ofn.nFileExtension at the boundary cases is exactly opposite to that described in the SDK docs.*/
      if(ofn.nFilterIndex==1)
	name=asprintf("%s%sjpg",ofn.lpstrFile,ofn.nFileExtension?"":".");
      else
	name=asprintf("%s%stif",ofn.lpstrFile,ofn.nFileExtension?"":".");
      if(name==NULL)
	guiReportError(loom,"Out of memory.");
      else {
	/*The Save As... won't have had a chance to check for an existing file.  Do that now.*/
	HANDLE handle;
	handle=CreateFile(name,GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
	if(handle!=NULL){
	  CloseHandle(handle);
	}
	if((handle==NULL)||(MessageBox(loom->window,"The file already exists.  Are you sure you want to replace it?","Save As",MB_YESNO|MB_ICONWARNING)==IDYES))
	  saveLoom(loom,name);
	free(name);
      }
    }
    else
      saveLoom(loom,ofn.lpstrFile);
  }
  
}


INT_PTR CALLBACK aboutDlgProc(HWND hDlg,UINT uMsg, WPARAM wParam,LPARAM lParam){

  switch(uMsg){
  case WM_INITDIALOG:


    return FALSE;

  case WM_COMMAND:
    EndDialog(hDlg,0);
    
  default:
      return FALSE;
  }

  return TRUE;

}


void processCommand(LOOM *loom, WPARAM wParam, LPARAM lParam){

  switch(LOWORD(wParam)){
  case ID_FILE_OPEN:
    openCommand(loom);
    break;

  case ID_FILE_SAVEAS:
    saveAsCommand(loom);
    break;

  case ID_FILE_EXIT:
    /*Perhaps should ask if they really want to quit if they're looking at a modified image.*/
    PostQuitMessage(0);
    break;

  case ID_MODE_ORIG:
    changeModeO(loom);
    break;

  case ID_MODE_VERT:
    changeModeV(loom);
    break;

  case ID_MODE_HORZ:
    changeModeH(loom);
    break;

  case ID_TOOL_PROTECT:
    pickupProtectTool(loom);
    break;

  case ID_TOOL_EXPOSE:
    pickupHideTool(loom);
    break;

  case ID_TOOL_CLEAR:
    pickupClearTool(loom);
    break;

  case ID_OPTION_DYNAMICENERGY:
    {
      HMENU hMenu=GetMenu(loom->window);
      int oldstate=GetMenuState(hMenu,ID_OPTION_DYNAMICENERGY,MF_BYCOMMAND);
      loom->flags&=~LOOM_DYNAMIC_ENERGY;
      if(!oldstate){
	loom->flags|=LOOM_DYNAMIC_ENERGY;
	CheckMenuItem(hMenu,ID_OPTION_DYNAMICENERGY,MF_BYCOMMAND|MF_CHECKED);
      }
      else
	CheckMenuItem(hMenu,ID_OPTION_DYNAMICENERGY,MF_BYCOMMAND|MF_UNCHECKED);
      
      switchDynamicEnergy(loom);
    }
    break;

  case ID_HELP_HELP: 
    {
      int result;
      if(HelpPath==NULL){
	guiReportError(loom,"Cannot find help files.  I expect to find them in a subdirectory 'doc' of the directory containing the Arachne program file.");
      }
      else {
	result=(int)ShellExecute(loom->window,"open",HelpPath,NULL,"doc",SW_SHOWNORMAL);
	if(result<=32){
	  guiReportError(loom,"There was a problem displaying the main help file.  Perhaps it isn't where I expect to find it, at %s.",HelpPath);
	}
      }
    }
    break;
  case ID_HELP_ABOUT:

    DialogBox(hInstance,MAKEINTRESOURCE(ID_DLG_ABOUT),loom->window,&aboutDlgProc);

    break;



  }

}


int initialiseToolbar(LOOM *loom){
  TBBUTTON btn,sep;

  loom->private.hToolbar=CreateWindowEx(0,
					TOOLBARCLASSNAME,
					"",
					WS_CHILD|WS_VISIBLE|TBSTYLE_TOOLTIPS,
					0,0,0,0,
					loom->window,
					NULL,
					hInstance,
					NULL);
  SendMessage(loom->private.hToolbar,TB_BUTTONSTRUCTSIZE,sizeof(TBBUTTON),0);
  SendMessage(loom->private.hToolbar,TB_SETIMAGELIST,0,(LPARAM)hToolbarImageList);

  
  SendMessage(loom->private.hToolbar,TB_ADDSTRING,0,(LPARAM)"Width\0Height\0Original\0Protect\0Expose\0Clear\0");
  btn.iBitmap=2;
  btn.idCommand=ID_MODE_HORZ;
  btn.fsState=TBSTATE_ENABLED;
  btn.fsStyle=BTNS_AUTOSIZE|BTNS_CHECKGROUP;
  btn.iString=0;
  loom->horzmodebtn.hToolbar=loom->private.hToolbar;
  loom->horzmodebtn.id=ID_MODE_HORZ;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,0,(LPARAM)&btn);
  btn.iBitmap=1;
  btn.iString=1;
  btn.idCommand=ID_MODE_VERT;
  loom->vertmodebtn.hToolbar=loom->private.hToolbar;
  loom->vertmodebtn.id=ID_MODE_VERT;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,1,(LPARAM)&btn);
  btn.iBitmap=0;
  btn.iString=2;
  btn.idCommand=ID_MODE_ORIG;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,2,(LPARAM)&btn);
  loom->origmodebtn.hToolbar=loom->private.hToolbar;
  loom->origmodebtn.id=ID_MODE_ORIG;

  sep.iBitmap=-1;
  sep.idCommand=0;
  sep.fsState=0;
  sep.fsStyle=BTNS_SEP;
  sep.iString=-1;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,3,(LPARAM)&sep);

  btn.iBitmap=3;
  btn.iString=3;
  btn.idCommand=ID_TOOL_PROTECT;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,4,(LPARAM)&btn); 
  loom->protectbtn.hToolbar=loom->private.hToolbar;
  loom->protectbtn.id=ID_TOOL_PROTECT;
  btn.iBitmap=4;
  btn.iString=4;
  btn.idCommand=ID_TOOL_EXPOSE;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,5,(LPARAM)&btn);
  loom->exposebtn.hToolbar=loom->private.hToolbar;
  loom->exposebtn.id=ID_TOOL_EXPOSE;
  btn.iBitmap=5;
  btn.iString=5;
  btn.idCommand=ID_TOOL_CLEAR;
  SendMessage(loom->private.hToolbar,TB_INSERTBUTTON,6,(LPARAM)&btn);
  loom->clearbtn.hToolbar=loom->private.hToolbar;
  loom->clearbtn.id=ID_TOOL_CLEAR;


  SendMessage(loom->private.hToolbar,TB_AUTOSIZE,0,0);
  
}

void initialiseMenu(LOOM *loom){
  
  /*Initialise the menu accessors.  These are a bit crazy, but it's a hangover of the way things work in the original version, and it is nice to have these available in the main program.*/
  loom->horzmodemenu.hMenu=loom->vertmodemenu.hMenu=loom->origmodemenu.hMenu=loom->exposemenu.hMenu=loom->protectmenu.hMenu=loom->clearmenu.hMenu=GetMenu(loom->window);
  loom->horzmodemenu.first=loom->vertmodemenu.first=loom->origmodemenu.first=ID_MODE_HORZ;
  loom->horzmodemenu.last=loom->vertmodemenu.last=loom->origmodemenu.last=ID_MODE_ORIG;
  loom->horzmodemenu.id=ID_MODE_HORZ;
  loom->vertmodemenu.id=ID_MODE_VERT;
  loom->origmodemenu.id=ID_MODE_ORIG;

  loom->exposemenu.first=loom->protectmenu.first=loom->clearmenu.first=ID_TOOL_PROTECT;
  loom->exposemenu.last=loom->protectmenu.last=loom->clearmenu.last=ID_TOOL_CLEAR;
  loom->exposemenu.id=ID_TOOL_EXPOSE;
  loom->protectmenu.id=ID_TOOL_PROTECT;
  loom->clearmenu.id=ID_TOOL_CLEAR;

  
  //  menu_check_item(loom->origmodemenu);

}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg,WPARAM wParam, LPARAM lParam){
  LOOM *loom;

  /*This will be invalid until WM_CREATE has been sent, but that shouldn't be a problem.*/
  loom=GetWindowLongPtr(hWnd,0);

  switch(uMsg){
  case WM_CREATE:
    {
      CREATESTRUCT *cs=(CREATESTRUCT *)lParam;
      POINT min;
      TBBUTTON btn;
      HDC hMemDC,hDC;
      /*Create the toolbar.*/
      /*Retrieve and initialise the loom variable that is attached to each window.*/
      loom=(LOOM *)cs->lpCreateParams;
      SetWindowLongPtr(hWnd,0,loom);
      loom->window=hWnd;

      initialiseMenu(loom);


      initialiseToolbar(loom);

    /*Create the status bar.*/
    loom->private.hStatusbar=CreateWindowEx(0,
			   STATUSCLASSNAME,
			   "",
			   WS_CHILD|WS_VISIBLE,
			   0,0,0,0,
			   hWnd,
			   NULL,
			   cs->hInstance,
			   NULL);

    /*Initialise the workspace size to something.  This will be initialised properly as soon as the window is sized.  It's possible I might want this initialised to correspond to the current window size, but it doesn't much matter right now.*/

    loom->private.work.left=0;
    loom->private.work.right=0;
    loom->private.work.top=0;
    loom->private.work.bottom=0;

    /*Set the minimum workspace size to allow arbitrary.  This will soon be set to something more useful.*/
    loom->private.minwork.x=1;
    loom->private.minwork.y=1;

    //    determineMinWindowRect(hWnd,loom->private.hToolbar,loom->private.hStatusbar,&min,&loom->private.minwinpt);
    return 0;
    }

  case WM_SIZE:
    {
      RECT sr;
      if(wParam==SIZE_RESTORED){
	SendMessage(loom->private.hStatusbar,WM_SIZE,wParam,lParam); //it doesn't actually matter what the parameters are, this just causes the control to resize itself.
	SendMessage(loom->private.hToolbar,WM_SIZE,0,0); //ditto
	GetWindowRect(loom->private.hToolbar,&sr);
	
	loom->private.work.top=sr.bottom-sr.top;
	GetClientRect(loom->private.hStatusbar,&sr);
	loom->private.work.bottom=HIWORD(lParam)-sr.bottom;
	loom->private.work.left=0;
	loom->private.work.right=LOWORD(lParam);
	
	arachneResizeView(loom,loom->private.work.right-loom->private.work.left,loom->private.work.bottom-loom->private.work.top);
	InvalidateRect(hWnd,&loom->private.work,FALSE);
      }


      return 0;
    }

  case WM_PAINT:
    {
      PAINTSTRUCT ps;
      HDC hDC;
      int wwidth,wheight;
      int bwidth,bheight;
      RECT frect;
      hDC=BeginPaint(hWnd,&ps);
#if 0
      if(loom->private.dibbits==NULL){
	char scratch[256];
	/*work is a rectangle describing the work area*/
	
	/*These functions are mad.  Fortunately they're just placeholders.*/
	FillRect(hDC,&loom->private.work,GetStockObject(WHITE_BRUSH));
	FrameRect(hDC,&loom->private.work,GetStockObject(BLACK_BRUSH));
	
	wsprintf(scratch,"workspace %dx%d",loom->private.work.right-loom->private.work.left,loom->private.work.bottom-loom->private.work.top);
	DrawText(hDC,scratch,strlen(scratch),&loom->private.work,DT_CENTER|DT_VCENTER|DT_SINGLELINE);
	
      }
#endif
	wwidth=loom->private.work.right-loom->private.work.left;
	wheight=loom->private.work.bottom-loom->private.work.top;
      if(loom->private.dibbits!=NULL){

	bwidth=loom->private.dib.bmiHeader.biWidth;
	bheight=-loom->private.dib.bmiHeader.biHeight;

	/*First blit the backing bitmap on to the screen.*/
	SetDIBitsToDevice(hDC,
			  loom->private.work.left,
			  loom->private.work.top,
			  bwidth,
			  bheight,
			  0,
			  0,
			  0,
			  bheight,
			  loom->private.dibbits,
			  &loom->private.dib,
			  DIB_RGB_COLORS);
	
      }
      else {
	bwidth=0;
	bheight=0;
      }
      /*If there is extra space to the right or bottom, fill that with something.*/
      if(wwidth>bwidth){
	frect.top=loom->private.work.top;
	frect.bottom=loom->private.work.bottom;
	frect.left=loom->private.work.left+bwidth;
	frect.right=loom->private.work.right;
	FillRect(hDC,&frect,GetStockObject(LTGRAY_BRUSH));
      }
      if(wheight>bheight){
	frect.top=loom->private.work.top+bheight;
	frect.bottom=loom->private.work.bottom;
	frect.left=loom->private.work.left;
	frect.right=loom->private.work.left+bwidth;
	FillRect(hDC,&frect,GetStockObject(LTGRAY_BRUSH));
      }
      
      if((loom->mode==LOOM_MODE_ORIG)&&(loom->tool!=0)&&(loom->private.tracking||loom->private.drawing)){
	RECT tool;
	tool.left=loom->private.oldx-loom->toolwidth/2;
	tool.right=tool.left+loom->toolwidth;
	tool.top=loom->private.oldy-loom->toolwidth/2;
	tool.bottom=tool.top+loom->toolwidth;
	FrameRect(hDC,&tool,GetStockObject(BLACK_BRUSH));
      }
	
	/*That should be all that's necessary.*/
 
      EndPaint(hWnd,&ps);
      return 0;
    }

  case WM_GETMINMAXINFO:
    {
      MINMAXINFO *mmi = (MINMAXINFO *)lParam;
      /*I'm not really happy about this.  This prevents the window doing very strange things if it's loaded with an image that makes it larger than the screen size and the user tries to resize it.  Without a response here the system bizzarely makes it kangaroo down the screen and finally off the screen.  This will prevent that for windows up to about twice the size of the screen (in any dimension). Ideally, of course, my program should be using zooming or something to prevent this even arising.*/
      mmi->ptMaxTrackSize.y*=2;
      mmi->ptMaxTrackSize.x*=2;
      return 0;
    }

    

  case WM_SIZING:
    {
      RECT *proposed=(RECT *)lParam;
      RECT thiswork;
      POINT new,windim;
      /*Determine what the workspace size would be if the window were this big.*/
      winRectToWorkRect(hWnd,loom->private.hToolbar,loom->private.hStatusbar,proposed,&thiswork);


      /*Now, if either dimension is smaller than the minimum workspace size we have to set it to the minimum size - taking into account the rest of the window.*/
      if((thiswork.bottom-thiswork.top<loom->private.minwork.y)||(thiswork.right-thiswork.left<loom->private.minwork.x)){
	new.x=MAX(thiswork.right-thiswork.left,loom->private.minwork.x);
	new.y=MAX(thiswork.bottom-thiswork.top,loom->private.minwork.y);
	//	thiswork.right=thiswork.left+new.x;
	//	thiswork.bottom=thiswork.top+new.y;
	workPtToWinPt(hWnd,loom->private.hToolbar,loom->private.hStatusbar,&new,&windim);
	proposed->right=proposed->left+windim.x;
	proposed->bottom=proposed->top+windim.y;
      }

      return TRUE;
    }

  case WM_COMMAND:
    processCommand(loom,wParam,lParam);
    return 0;

  case WM_NOTIFY:
    {
      NMHDR *hdr;
      NMTTDISPINFO *tt;
      hdr = (NMHDR *)lParam;
      if(hdr->code==TTN_GETDISPINFO){
	tt=(NMTTDISPINFO *)hdr;
	switch(tt->hdr.idFrom){
	case ID_MODE_HORZ:
	  tt->lpszText="Adjust the width of the image.";
	  break;
	case ID_MODE_VERT:
	  tt->lpszText="Adjust the height of the image.";
	  break;
	case ID_MODE_ORIG:
	  tt->lpszText="Examine and annotate the original image.";
	  break;
	case ID_TOOL_PROTECT:
	  tt->lpszText="Mark parts of the image you don't want removed.";
	  break;
	case ID_TOOL_EXPOSE:
	  tt->lpszText="Mark parts of the image you want removed.";
	  break;
	case ID_TOOL_CLEAR:
	  tt->lpszText="Selectively remove earlier marks.";
	  break;
	}
	tt->hinst=NULL;
	tt->uFlags=TTF_DI_SETITEM;
      }
      return 0;
    }

  case WM_LBUTTONDOWN:
    if((loom->mode==LOOM_MODE_ORIG)&&(loom->tool!=0)){
      RECT tool;
      loom->private.tracking=FALSE;
      loom->private.drawing=TRUE;
      loom->private.oldx=LOWORD(lParam);
      loom->private.oldy=HIWORD(lParam);
      arachneAnnotate(loom,loom->private.oldx-loom->private.work.left,loom->private.oldy-loom->private.work.top);
      tool.left=loom->private.oldx-loom->toolwidth/2;
      tool.right=tool.left+loom->toolwidth;
      tool.top=loom->private.oldy-loom->toolwidth/2;
      tool.bottom=tool.top+loom->toolwidth;
      InvalidateRect(hWnd,&tool,FALSE);
      SetCapture(hWnd);
      loom->private.drawing=TRUE;
      if(loom->map!=NULL){ /*This ensures recomputation.*/
	mapDelete(loom->map);
	loom->map=NULL;
      }
      
    }
    return 0;
    
  case WM_MOUSELEAVE:
    {
      RECT tool;
      tool.left=loom->private.oldx-loom->toolwidth/2;
      tool.right=tool.left+loom->toolwidth;
      tool.top=loom->private.oldy-loom->toolwidth/2;
      tool.bottom=tool.top+loom->toolwidth;
      loom->private.tracking=FALSE;
      InvalidateRect(hWnd,&tool,FALSE);
      return 0;
    }

  case WM_MOUSEMOVE:
    if((loom->mode==LOOM_MODE_ORIG)&&(loom->tool!=0)){
      RECT tool;
      TRACKMOUSEEVENT tme;
      if(!loom->private.tracking&&!loom->private.drawing){
	tme.cbSize=sizeof(tme);
	tme.dwFlags=TME_LEAVE;
	tme.hwndTrack=hWnd;
	TrackMouseEvent(&tme);
	loom->private.tracking=TRUE;
      }

      tool.left=loom->private.oldx-loom->toolwidth/2;
      tool.right=tool.left+loom->toolwidth;
      tool.top=loom->private.oldy-loom->toolwidth/2;
      tool.bottom=tool.top+loom->toolwidth;
      InvalidateRect(hWnd,&tool,FALSE);
      loom->private.oldx=LOWORD(lParam);
      loom->private.oldy=HIWORD(lParam);
      if(loom->private.drawing)
	arachneAnnotate(loom,loom->private.oldx-loom->private.work.left,loom->private.oldy-loom->private.work.top);
      tool.left=loom->private.oldx-loom->toolwidth/2;
      tool.right=tool.left+loom->toolwidth;
      tool.top=loom->private.oldy-loom->toolwidth/2;
      tool.bottom=tool.top+loom->toolwidth;
      InvalidateRect(hWnd,&tool,FALSE);
    }
    return 0;

  case WM_LBUTTONUP:
    ReleaseCapture();
    loom->private.drawing=FALSE;
    return 0;

  case WM_DESTROY:
    /*Perhaps should ask if they really want to quit if we get WM_CLOSE and they're looking at a modified image.*/
    PostQuitMessage(0);
    return 0;
  default:
    return DefWindowProc(hWnd,uMsg,wParam,lParam);
  }

}

/*This is only used once, when a file is loaded, to set the window to precisely fit the workspace.*/
void setWorkToSize(LOOM *loom,int width,int height){
  POINT min,minwin;
  RECT winr;

  min.x=MAX(loom->private.minwork.x,width);
  min.y=MAX(loom->private.minwork.y,height);
  
  if((min.x!=loom->private.work.right-loom->private.work.left)||(min.y!=loom->private.work.bottom-loom->private.work.top)){
    workPtToWinPt(loom->window,loom->private.hToolbar,loom->private.hStatusbar,&min,&minwin);
    
    GetWindowRect(loom->window,&winr);
    /*Resize the window*/
    MoveWindow(loom->window,winr.left,winr.top,minwin.x,minwin.y,TRUE);
  }

}


void guiSetWindowMinimum(LOOM *loom, int width, int height){
  POINT minwin,min;
  RECT winr;
  //  int newwidth,newheight;


  loom->private.minwork.x=width;
  loom->private.minwork.y=height;
  

  if((loom->private.work.right-loom->private.work.left<width)||(loom->private.work.bottom-loom->private.work.top<height)){
    min.x=MAX(loom->private.minwork.x,loom->private.work.right-loom->private.work.left);
    min.y=MAX(loom->private.minwork.y,loom->private.work.bottom-loom->private.work.top);
    workPtToWinPt(loom->window,loom->private.hToolbar,loom->private.hStatusbar,&min,&minwin);

    GetWindowRect(loom->window,&winr);
    /*Resize the window*/
    MoveWindow(loom->window,winr.left,winr.top,minwin.x,minwin.y,TRUE);
  
  }


}

int guiInitialiseLoom(LOOM *new){
  HWND hWnd;

  fflush(stdout);

  /*Initialise the drawing flag*/
  new->private.drawing=FALSE;
  new->private.tracking=FALSE;

 /*Initialise the dib header.  The memory is allocated elsewhere - it's important that the width and height are set to zero here, as these fields are used to determine if memory needs to be allocated.*/
  new->private.dib.bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
  new->private.dib.bmiHeader.biWidth=0;
  new->private.dib.bmiHeader.biHeight=0;
  new->private.dib.bmiHeader.biPlanes=1;
  new->private.dib.bmiHeader.biBitCount=24;
  new->private.dib.bmiHeader.biCompression=BI_RGB;
  new->private.dib.bmiHeader.biSizeImage=0;
  new->private.dib.bmiHeader.biXPelsPerMeter=96*100/2.54; //I don't know that these need to be set.
  new->private.dib.bmiHeader.biYPelsPerMeter=96*100/2.54;
  new->private.dib.bmiHeader.biClrUsed=0;
  new->private.dib.bmiHeader.biClrImportant=0;
  /*Initialise the bitmap data pointer, too.*/
  new->private.dibbits=NULL;


  /*Now create the main window.  During it's creation it will create its own status bar and toolbar.*/
  hWnd=CreateWindow("ArachneLoom",
		    "Arachne",
		    WS_OVERLAPPEDWINDOW&~WS_MAXIMIZEBOX,
		    CW_USEDEFAULT,
		    CW_USEDEFAULT,
		    CW_USEDEFAULT,
		    0, /*Default height*/
		    NULL,
		    NULL,
		    hInstance,
		    new);
  if(hWnd==NULL){
    guiReportError(NULL,"Couldn't create window.");
    exit(1);
  }


 
    if(new->filename==NULL){
      guiSetWindowMinimum(new,512,256);
      setWorkToSize(new,512,256);
    }
    else{
      guiSetWindowMinimum(new,imageWidth(new->image),imageHeight(new->image));
      setWorkToSize(new,imageWidth(new->image),imageHeight(new->image));
    }
   

  setTitle(new);
  ShowWindow(hWnd,Shown ? SW_SHOWNORMAL: nCmdShow);
  Shown=1;
  UpdateWindow(hWnd);
  return 1;
}



int tokenizeCmdLine(LPTSTR string,LPTSTR **argv, int *argc){
#ifdef UNICODE
  *argv=CommandLineToArgvW(string,argc);
#else
  LPTSTR *array=NULL;
  LPTSTR new;
  int n=0;
  int i=0,o=0,j;
  int inquote=0,initem=0;
  /*Do it ourselves.  Very quick and dirty, I probably will convert everything to unicode in the end anyway.*/
  new=strdup(string);
  if(new==NULL)
    return 0;
  
  i=0;
  o=0;
  while(string[i]!=0){
    if(initem){
      if(string[i]=='"'){
	new[o++]=0;
	if(inquote){
	  initem=0;
	  inquote=0;
	}
	else{
	  inquote=1; //new item
	  n++;
	}
      }
      else if(!inquote&&isspace(string[i])){
	new[o++]=0;
	initem=0;
      }
      else {
	new[o++]=string[i];
      }
    }
    else if(!isspace(string[i])){
      initem=1;
      n++;
      if(string[i]=='"')
	inquote=1;
      else
	new[o++]=string[i];
    }
    i++;
  }

  if(initem){
    new[o++]=0;
  }

  /*Now prepare the return result.*/
  array=malloc(sizeof(LPSTR)*(n+1));

  i=0;
  j=0;
  while(i<o){
    array[j++]=new+i;
    while(new[i]!=0)
      i++;
    i++;
  }
  array[n]=NULL;

  *argv=array;
  *argc=n;


#endif

  return 1;
}


int WINAPI WinMain (HINSTANCE hInst, 
		    HINSTANCE hPrevInstance, 
		    PSTR szCmdLine, 
		    int iCmdShow) 
{
  WNDCLASSEX wc;
  HWND hWnd;
  MSG msg;
  INITCOMMONCONTROLSEX icc;
  int argc;
  LPTSTR *argv;
  
  hInstance=hInst;
  nCmdShow=iCmdShow;

  icc.dwSize=sizeof(icc);
  icc.dwICC=ICC_STANDARD_CLASSES|ICC_WIN95_CLASSES;
  
  InitCommonControlsEx(&icc);

  /*Determine the path to use for accessing the help files.  I'm assuming at the moment that the program won't be formally installed, so we have to work out where the executable is.*/
  {
    char buffer[1024],*slashptr;
    if(GetModuleFileName(GetModuleHandle(NULL),buffer,1024)>0){
      slashptr=strrchr(buffer,'\\');
      if(slashptr==NULL)
	slashptr=buffer;
      HelpPath=asprintf("%.*s\\doc\\t1.html",slashptr-buffer,buffer);
    }
    else 
      HelpPath=NULL;
  }

  
  /*Build the image list for the toolbar.*/
  hToolbarImageList=ImageList_Create(24,24,ILC_COLOR32|ILC_MASK,0/*making this up*/,1/*This too*/);
  ImageList_AddIcon(hToolbarImageList,LoadIcon(hInstance,MAKEINTRESOURCE(ICON_ORIGMODE)));
  ImageList_AddIcon(hToolbarImageList,LoadIcon(hInstance,MAKEINTRESOURCE(ICON_VERTMODE)));
  ImageList_AddIcon(hToolbarImageList,LoadIcon(hInstance,MAKEINTRESOURCE(ICON_HORZMODE)));
  ImageList_AddIcon(hToolbarImageList,LoadIcon(hInstance,MAKEINTRESOURCE(ICON_PROTECTTOOL)));
  ImageList_AddIcon(hToolbarImageList,LoadIcon(hInstance,MAKEINTRESOURCE(ICON_EXPOSETOOL)));
  ImageList_AddIcon(hToolbarImageList,LoadIcon(hInstance,MAKEINTRESOURCE(ICON_CLEARTOOL)));
  wc.cbSize=sizeof(wc);
  wc.style=0;//CS_HREDRAW|CS_VREDRAW;
  wc.lpfnWndProc=&WndProc;
  wc.cbClsExtra=0;
  wc.cbWndExtra=sizeof(LOOM *);
  wc.hInstance=hInstance;
  wc.hIcon=LoadIcon(hInstance,MAKEINTRESOURCE(ICON_ARACHNE));
  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
  wc.hbrBackground=NULL;//GetStockObject(WHITE_BRUSH);
  wc.lpszMenuName=MAKEINTRESOURCE(ID_MENU);
  wc.lpszClassName="ArachneLoom";
  wc.hIconSm=NULL;
  
  RegisterClassEx(&wc);
  
  if(!tokenizeCmdLine(GetCommandLine(),&argv,&argc)){
    guiReportError(NULL,"Error processing command line arguments.");
    exit(1);
  }
#if 0
  printf("Command line is %s\n",GetCommandLine());
  printf("%d arguments\n",argc);
  {
    int i;
    for(i=0;i<argc;i++){
      printf("[%s]\n",argv[i]);
    }
  }
 
#endif

  arachneStart(argc,argv);

  while(GetMessage(&msg,NULL,0,0)!=0){
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  
  return msg.wParam;

  
}
  






/*These are yet to be defined.*/
void initIcons(void){}
void guiReportError(LOOM *loom,char *fmt, ...){
  va_list ap;
  LPTSTR msg;
  char scratch[1024];
  

  va_start(ap,fmt);
  msg=vasprintf(fmt,ap);

  va_end(ap);
  MessageBox(NULL, msg, "Arachne", MB_OK|MB_ICONERROR);
  free(msg);
  return ;
}



void guiClearWaiting(void){
  /*This feels a bit Heath Robinson.  The better solution in windows would be to use a separate thread to do the computation, which I will eventually rewrite the program to do.*/
  MSG msg;
  int result;
  /*Clear and process any waiting messages.*/
  while(PeekMessage(&msg,NULL,0,0,FALSE)){

    if((result=GetMessage(&msg,NULL,0,0))>0){
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    else if(result==0)
      exit(msg.wParam);
    else
      return; //error getting the message, best just to leave it.  Eventually the computation will end and the main loop will kick in, if it's still a problem it's something to do with the system.
  }
  

}

void guiSetStatus(LOOM *loom,char *fmt, ...){
  va_list ap;
  LPTSTR msg;
  
  va_start(ap,fmt);
  msg=vasprintf(fmt,ap);
  va_end(ap);
  SendMessage(loom->private.hStatusbar,SB_SETTEXT,0,(LPARAM)msg);
  free(msg);

}



INT_PTR CALLBACK progressDlgProc(HWND hDlg,UINT uMsg, WPARAM wParam,LPARAM lParam){

  switch(uMsg){
  case WM_INITDIALOG:


    return FALSE;

  default:
      return FALSE;
  }

  return TRUE;

}


void guiBeginComputation(LOOM *loom){
  /*Create the dialog box that shows the progress*/
  loom->private.hProgressDlg=CreateDialog(hInstance,MAKEINTRESOURCE(ID_DLG_PROGRESS),loom->window,&progressDlgProc);

  /*Disable the main window so that the user can't do something dangerous.*/
  EnableWindow(loom->window,FALSE);


 


}

void guiEndComputation(LOOM *loom){

  /*Enable the main window again.  This needs to be done before we destroy the dialog, otherwise it won't be a candidate for activation and some other window will get selected.*/
  EnableWindow(loom->window,TRUE);

  /*Destroy the progress dialog*/
  DestroyWindow(loom->private.hProgressDlg);
  loom->private.hProgressDlg=NULL;
  


}

int guiReloadLoom(LOOM *new){
   if(new->private.dibbits!=NULL)
    free(new->private.dibbits);
  new->private.dibbits=NULL;
 

  setTitle(new);
  guiSetWindowMinimum(new,imageWidth(new->image),imageHeight(new->image));
  setWorkToSize(new,imageWidth(new->image),imageHeight(new->image));
  return 1;
}


/*NB.  What happens if this is called before the window exists?  Either make sure it never is.  It currently just baulks, but is this useful?  I imagine in fact it never _is_ called before the window exists.*/
int guiPrepareView(LOOM *loom, int width, int height){
  int rowwords;
  HDC hDC;

  if((loom->private.dibbits==NULL)||(width!=loom->private.dib.bmiHeader.biWidth)||(height!=-loom->private.dib.bmiHeader.biHeight)){
    
    /*We have to reallocate the memory for the DIB.*/
    if(loom->private.dibbits!=NULL)
      free(loom->private.dibbits);
    rowwords=(width*3+sizeof(DWORD)-1)/sizeof(DWORD);
    loom->private.dibbits=malloc(rowwords*sizeof(DWORD)*height);
    if(loom->private.dibbits==NULL){
      guiReportError(loom,"Out of memory.");
      return 0;
    }
    loom->private.dib.bmiHeader.biWidth=width;
    loom->private.dib.bmiHeader.biHeight=-height; //-ve indicates top-down bitmap
    loom->private.bytesperrow=rowwords*sizeof(DWORD); //This is a helpful value to have during accesses.

  }
  return 1;
}

void guiUpdateView(LOOM *loom){
  if(loom->window!=NULL)
    InvalidateRect(loom->window,&loom->private.work,FALSE);
}


void menu_check_item(MENUITEM item){
  if(item.first<0){
    /*A check-menu*/
    CheckMenuItem(item.hMenu,item.id,MF_BYCOMMAND|MF_CHECKED);
  }
  else {
    /*A radio menu*/
    CheckMenuRadioItem(item.hMenu,item.first,item.last,item.id,MF_BYCOMMAND|MF_CHECKED);
  }
}

void menu_uncheck_item(MENUITEM item){
    CheckMenuItem(item.hMenu,item.id,MF_BYCOMMAND|MF_UNCHECKED);

}

void guiGetWorkspace(LOOM *loom, int *width, int *height){
  if(width!=NULL)
    *width=loom->private.work.right-loom->private.work.left;
  if(height!=NULL)
    *height=loom->private.work.bottom-loom->private.work.top;

}
openSUSE Build Service is sponsored by