/*
 * WinSnap Win32 Window Capture Plug-in
 * Copyright (C) 1999 Craig Setera
 * Craig Setera, setera@infonet.isl.net
 * 07/14/1999
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * Based on (at least) the following plug-ins:
 * Screenshot
 * TWAIN
 *
 * Any suggestions, bug-reports or patches are welcome.
 *
 * This plug-in provides Win32 users with screen snapshot ability.
 *
 */

/*
 * Revision history
 *
 *  (07/13/99)  v0.5   First working version (internal)
 *  (07/14/99)  v0.55  Switched to g_error, g_message, etc.
 *  (07/16/99)  v0.60  Better timing, comment cleanup
 *  (07/16/99)  v0.70  Switched name
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <windows.h>
#include "resource.h"
#include "gtk/gtk.h"
#include "libgimp/gimp.h"

/*
 * Plug-in Definitions
 */
#define PLUG_IN_NAME        "extension-winsnap"
#define PLUG_IN_PRINT_NAME  "WinSnap"
#define PLUG_IN_DESCRIPTION "Capture a Win32 window or desktop image"
#define PLUG_IN_HELP        "This plug-in will capture an image of a Win32 window or desktop"
#define PLUG_IN_AUTHOR      "Craig Setera (setera@infonet.isl.net)"
#define PLUG_IN_COPYRIGHT   "Craig Setera"
#define PLUG_IN_VERSION     "v0.70 (07/16/1999)"
#define PLUG_IN_MENU_PATH   "<Toolbox>/File/Acquire/Screen Shot..."

/*
 * Application definitions
 */
#define SELECT_FRAME		0
#define SELECT_CLIENT		1
#define SELECT_WINDOW		2

#define SHOW_WINDOW		FALSE
#define APP_NAME		PLUG_IN_NAME
#define WM_DOCAPTURE		(WM_USER + 100)

/* File variables */
static int			captureType;
static char			buffer[512];
static char		       *capBytes = NULL;
static HWND			mainHwnd = NULL;
static HINSTANCE		hInst = NULL;
static HCURSOR			selectCursor = 0;
static ICONINFO			iconInfo;

/* Forward declarations */
static void init(void);
static void quit(void);
static void query(void);
static void run(char *, int, GParam *, int *, GParam **);
static gint sendBMPToGimp(HBITMAP hBMP, HDC hDC, RECT rect);

BOOL CALLBACK dialogProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

/* Data structure holding data between runs */
typedef struct {
  gint root;
  gint decor;
} WinSnapValues;

/* Default WinSnap values */
static WinSnapValues winsnapvals =
{
  FALSE,
  TRUE
};

/* The dialog information */
typedef struct {
  GtkWidget *decor_button;
  GtkWidget *single_button;
  GtkWidget *root_button;
  gint run;
} WinSnapInterface;

/* The dialog data */
static WinSnapInterface winsnapintf =
{
  NULL,
  NULL,
  NULL,
  FALSE	    /* run */
};

/* This plug-in's functions */
GPlugInInfo PLUG_IN_INFO =
{
  NULL,    /* init_proc */
  NULL,    /* quit_proc */
  query,   /* query_proc */
  run,     /* run_proc */
};
	
/* We create a DIB section to hold the grabbed area. The scanlines in
 * DIB sections are aligned ona LONG (four byte) boundary. Its pixel
 * data is in RGB (BGR actually) format, three bytes per pixel.
 *
 * The GIMP uses no alignment for its pixel regions. The GIMP image we
 * create is of type RGB, i.e. three bytes per pixel, too. Thus in
 * order to be able to quickly transfer all of the image at a time, we
 * must use a DIB section and pixel region the scanline width of which
 * is evenly divisible with both 3 and 4. I.e. it must be a multiple
 * of 12 bytes, or in pixels, a multiple of four pixels.
 */

#define ROUND4(width) (((width-1)/4+1)*4)

/******************************************************************
 * Debug stuff
 ******************************************************************/

/*
 * formatWindowsError
 *
 * Format the latest Windows error message into
 * a readable string.  Store in the provided
 * buffer.
 */
void
formatWindowsError(char *buffer)
{
  LPVOID lpMsgBuf;

  /* Format the message */
  FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL, GetLastError(),
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
		(LPTSTR) &lpMsgBuf, 0, NULL );

  /* Copy to the buffer */
  strcpy(buffer, lpMsgBuf);

  LocalFree(lpMsgBuf);
}

/******************************************************************
 * Bitmap capture and handling
 ******************************************************************/

/*
 * primDoWindowCapture
 *
 * The primitive window capture functionality.  Accepts
 * the two device contexts and the rectangle to be
 * captured.
 */
static HBITMAP
primDoWindowCapture(HDC hdcWindow, HDC hdcCompat, RECT rect)
{
  HBITMAP	hbmCopy;
  HGDIOBJ	oldObject;
  BITMAPINFO	bmi;

  int width = (rect.right - rect.left);
  int height = abs(rect.bottom - rect.top);

  /* Create the bitmap info header */
  bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
  bmi.bmiHeader.biWidth = ROUND4(width);
  bmi.bmiHeader.biHeight = -height;
  bmi.bmiHeader.biPlanes = 1;
  bmi.bmiHeader.biBitCount = 24;
  bmi.bmiHeader.biCompression = BI_RGB;
  bmi.bmiHeader.biSizeImage = 0;
  bmi.bmiHeader.biXPelsPerMeter =
  bmi.bmiHeader.biYPelsPerMeter = 0;
  bmi.bmiHeader.biClrUsed = 0;
  bmi.bmiHeader.biClrImportant = 0;

  /* Create the bitmap storage space */
  hbmCopy = CreateDIBSection(hdcCompat,
			     (BITMAPINFO *) &bmi,
			     DIB_RGB_COLORS,
			     &capBytes, NULL, 0);
  if (!hbmCopy) {
    formatWindowsError(buffer);
    g_error("Error creating DIB section: %s", buffer);
    return NULL;
  }

  /* Select the bitmap into the compatible DC. */
  oldObject = SelectObject(hdcCompat, hbmCopy);
  if (!oldObject) {
    formatWindowsError(buffer);
    g_error("Error selecting object: %s", buffer);
    return NULL;
  }

  /* Copy the data from the application to the bitmap.  Even if we did
   * round up the width, BitBlt only the actual data.
   */
  if (!BitBlt(hdcCompat, 0,0,
	      width, height,
	      hdcWindow, 0,0,
	      SRCCOPY)) {
    formatWindowsError(buffer);
    g_error("Error copying bitmap: %s", buffer);
    return NULL;
  }

  /* Restore the original object */
  SelectObject(hdcCompat, oldObject);

  return hbmCopy;
}

/*
 * doCapture
 *
 * Do the capture.  Accepts the window
 * handle to be captured or the NULL value
 * to specify the root window.
 */
static int
doCapture(HWND selectedHwnd)
{
  HDC		hdcSrc;
  HDC		hdcCompat;
  MSG		msg;
  HRGN		capRegion;
  HWND		oldForeground;
  RECT		rect;
  HBITMAP	hbm;
  HANDLE	tHandle;

  /* Try and get everything out of the way before the
   * capture.
   */
  Sleep(500);

  /* Are we capturing a window or the whole screen */
  if (selectedHwnd) {

    /* Set to foreground window */
    oldForeground = GetForegroundWindow();
    SetForegroundWindow(selectedHwnd);
    BringWindowToTop(selectedHwnd);

    Sleep(500);

    /* Build a region for the capture */
    GetWindowRect(selectedHwnd, &rect);
    capRegion = CreateRectRgn(rect.left, rect.top,
			      rect.right, rect.bottom);
    if (!capRegion) {
      formatWindowsError(buffer);
      g_error("Error creating region: %s", buffer);
      return FALSE;
    }

    /* Get the device context for the selected
     * window.  Create a memory DC to use for the
     * Bit copy.
     */
    hdcSrc = GetDCEx(selectedHwnd, capRegion,
		     DCX_WINDOW | DCX_PARENTCLIP | DCX_INTERSECTRGN);
  } else {
    /* Get the device context for the whole screen */
    hdcSrc = CreateDC("DISPLAY", NULL, NULL, NULL);

    /* Get the screen's rectangle */
    rect.bottom = 0;
    rect.top = GetDeviceCaps(hdcSrc, VERTRES);
    rect.left = 0;
    rect.right = GetDeviceCaps(hdcSrc, HORZRES);
  }

  if (!hdcSrc) {
    formatWindowsError(buffer);
    g_error("Error getting device context: %s", buffer);
    return FALSE;
  }
  hdcCompat = CreateCompatibleDC(hdcSrc);
  if (!hdcCompat) {
    formatWindowsError(buffer);
    g_error("Error getting compat device context: %s", buffer);
    return FALSE;
  }

  /* Do the window capture */
  hbm = primDoWindowCapture(hdcSrc, hdcCompat, rect);
  if (!hbm)
    return FALSE;

  /* Release the device context */
  ReleaseDC(selectedHwnd, hdcSrc);

  /* Replace the previous foreground window */
  if (selectedHwnd && oldForeground)
    SetForegroundWindow(oldForeground);

  /* Send the bitmap */
  if (hbm != NULL) {
    sendBMPToGimp(hbm, hdcCompat, rect);
  }

  return TRUE;
}

/******************************************************************
 * Win32 entry point and setup...
 ******************************************************************/
	
#define DINV 3

/*
 * highlightWindowFrame
 *
 * Highlight (or unhighlight) the specified
 * window handle's frame.
 */
static void
highlightWindowFrame(HWND hWnd)
{
  HDC     hdc;
  RECT    rc;

  if (!IsWindow(hWnd))
    return;

  hdc = GetWindowDC(hWnd);
  GetWindowRect(hWnd, &rc);
  OffsetRect(&rc, -rc.left, -rc.top);

  if (!IsRectEmpty(&rc)) {
    PatBlt(hdc, rc.left, rc.top, rc.right-rc.left, DINV, DSTINVERT);
    PatBlt(hdc, rc.left, rc.bottom-DINV, DINV, -(rc.bottom-rc.top-2*DINV),
	   DSTINVERT);
    PatBlt(hdc, rc.right-DINV, rc.top+DINV, DINV, rc.bottom-rc.top-2*DINV,
	   DSTINVERT);
    PatBlt(hdc, rc.right, rc.bottom-DINV, -(rc.right-rc.left), DINV,
	   DSTINVERT);
  }

  ReleaseDC(hWnd, hdc);
  UpdateWindow(hWnd);
}

/*
 * setCaptureType
 *
 * Set the capture type.  Should be one of:
 * SELECT_FRAME
 * SELECT_CLIENT
 * SELECT_WINDOW
 */
void
setCaptureType(int capType)
{
  captureType = capType;
}

/*
 * myWindowFromPoint
 *
 * Map to the appropriate window from the
 * specified point.  The chosen window is
 * based on the current capture type.
 */
static HWND
myWindowFromPoint(POINT pt)
{
  HWND myHwnd;
  HWND nextHwnd;

  switch (captureType) {
  case SELECT_FRAME:
  case SELECT_CLIENT:
    nextHwnd = WindowFromPoint(pt);

    do {
      myHwnd = nextHwnd;
      nextHwnd = GetParent(myHwnd);
    } while (nextHwnd);

    return myHwnd;
    break;

  case SELECT_WINDOW:
    return WindowFromPoint(pt);
    break;
  }

  return WindowFromPoint(pt);
}

/*
 * dialogProc
 *
 * The window procedure for the window
 * selection dialog box.
 */
BOOL CALLBACK
dialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
  static int	 mouseCaptured;
  static int	 buttonDown;
  static HCURSOR oldCursor;
  static RECT	 bitmapRect;
  static HWND	 highlightedHwnd = NULL;

  switch (msg) {
  case WM_INITDIALOG:
    {
      int nonclientHeight;
      HWND hwndGroup;
      RECT dlgRect;
      RECT clientRect;
      RECT groupRect;
      BITMAP bm;

      /* Set the mouse capture flag */
      buttonDown = 0;
      mouseCaptured = 0;

      /* Calculate the bitmap dimensions */
      GetObject(iconInfo.hbmMask, sizeof(BITMAP), (VOID *)&bm);

      /* Calculate the dialog window dimensions */
      GetWindowRect(hwndDlg, &dlgRect);

      /* Calculate the group box dimensions */
      hwndGroup = GetDlgItem(hwndDlg, IDC_GROUP);
      GetWindowRect(hwndGroup, &groupRect);
      OffsetRect(&groupRect, -dlgRect.left, -dlgRect.top);

      /* The client's rectangle */
      GetClientRect(hwndDlg, &clientRect);

      /* The non-client height */
      nonclientHeight = (dlgRect.bottom - dlgRect.top) -
	(clientRect.bottom - clientRect.top);

      /* Calculate the bitmap rectangle */
      bitmapRect.top = ((groupRect.top + groupRect.bottom) / 2) -
	(bm.bmHeight / 2);
      bitmapRect.top -= nonclientHeight;
      bitmapRect.bottom = bitmapRect.top + bm.bmHeight;
      bitmapRect.left = ((groupRect.left + groupRect.right) / 2) - (bm.bmWidth / 2);
      bitmapRect.right = bitmapRect.left + bm.bmWidth;
    }
  break;

  case WM_LBUTTONDOWN:
    /* Track the button down state */
    buttonDown = 1;
    break;

  case WM_LBUTTONUP:
    buttonDown = 0;

    /* If we have mouse captured
     * we do this stuff.
     */
    if (mouseCaptured) {
      HWND		selectedHwnd;
      POINT		cursorPos;

      /* Release the capture */
      mouseCaptured = 0;
      SetCursor(oldCursor);
      ReleaseCapture();

      /* Remove the highlight */
      if (highlightedHwnd)
	highlightWindowFrame(highlightedHwnd);
      RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE);

      /* Return the selected window */
      GetCursorPos(&cursorPos);
      selectedHwnd = myWindowFromPoint(cursorPos);
      EndDialog(hwndDlg, (int) selectedHwnd);
    }
    break;

  case WM_MOUSEMOVE:
    /* If the mouse is captured, show
     * the window which is tracking
     * under the mouse position.
     */
    if (mouseCaptured) {
      HWND	currentHwnd;
      POINT cursorPos;

      /* Get the window */
      GetCursorPos(&cursorPos);
      currentHwnd = myWindowFromPoint(cursorPos);

      /* Do the highlighting */
      if (highlightedHwnd != currentHwnd) {
	if (highlightedHwnd)
	  highlightWindowFrame(highlightedHwnd);
	if (currentHwnd)
	  highlightWindowFrame(currentHwnd);
	highlightedHwnd = currentHwnd;
      }
      /* If the mouse has not been captured,
       * try to figure out if we should capture
       * the mouse.
       */
    } else if (buttonDown) {
      POINT cursorPos;

      /* Get the current client position */
      GetCursorPos(&cursorPos);
      ScreenToClient(hwndDlg, &cursorPos);

      /* Check if within the rectangle formed
       * by the bitmap
       */
      if (PtInRect(&bitmapRect, cursorPos)) {
	mouseCaptured = 1;
	oldCursor = SetCursor(selectCursor);
	SetCapture(hwndDlg);
	RedrawWindow(hwndDlg, NULL, NULL, RDW_INVALIDATE | RDW_ERASE);
      }
    }

    break;

  case WM_PAINT:
    {
      HDC             hDC;
      PAINTSTRUCT			ps;

      /* If the mouse is not captured draw
       * the cursor image
       */
      if (!mouseCaptured) {
	hDC = BeginPaint(hwndDlg, &ps);
	DrawIconEx(hDC, bitmapRect.left, bitmapRect.top, selectCursor,
		   0, 0, 0, NULL, DI_NORMAL);
	EndPaint(hwndDlg, &ps);			
      }
    }
  break;

  case WM_COMMAND:
    /* Handle the cancel button */
    switch (LOWORD(wParam)) {
    case IDCANCEL:
      EndDialog(hwndDlg, 0);
      return TRUE;
      break;
    }

  }

  return FALSE;
}

/* Don't use the normal WinMain from gimp.h */
#define WinMain WinMain_no_thanks
MAIN()
#undef WinMain

/*
 * WinMain
 *
 * The standard gimp plug-in WinMain won't quite cut it for
 * this plug-in.
 */
int APIENTRY
WinMain(HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR     lpCmdLine,
	int       nCmdShow)
{
  /*
   * Normally, we would do all of the Windows-ish set up of
   * the window classes and stuff here in WinMain.  But,
   * the only time we really need the window and message
   * queues is during the plug-in run.  So, all of that will
   * be done during run().  This avoids all of the Windows
   * setup stuff for the query().  Stash the instance handle now
   * so it is available from the run() procedure.
   */
  hInst = hInstance;

  /*
   * Now, call win32_gimp_main... This is what the normal WinMain()
   * would do.
   */
  return win32_gimp_main(__argc, __argv);
}
	
/*
 * InitApplication
 *
 * Initialize window data and register the window class
 */
BOOL
InitApplication(HINSTANCE hInstance)
{
  WNDCLASS wc;
  BOOL retValue;
		
  /* Get some resources */
#ifdef _MSC_VER
  /* For some reason this works only with MSVC */
  selectCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_SELECT));
#else
  selectCursor = LoadCursor(NULL, IDC_CROSS);
#endif
  GetIconInfo(selectCursor, &iconInfo);

  /*
   * Fill in window class structure with parameters to describe
   * the main window.
   */
  wc.style = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc = (WNDPROC) WndProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
  wc.lpszClassName = APP_NAME;
  wc.lpszMenuName = NULL;
		
  /* Register the window class and stash success/failure code. */
  retValue = RegisterClass(&wc);
		
  /* Log error */
  if (!retValue) {
    formatWindowsError(buffer);
    g_error("Error Registering class: %s", buffer);
    return retValue;
  }
		
  return retValue;
}
	
/*
 * InitInstance
 *
 * Create the main window for the application.
 */
BOOL
InitInstance(HINSTANCE hInstance, int nCmdShow)
{
  /* Create our window */
  mainHwnd = CreateWindow(APP_NAME, APP_NAME, WS_OVERLAPPEDWINDOW,
			  CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
			  NULL, NULL, hInstance, NULL);
		
  if (!mainHwnd) {
    return (FALSE);
  }
		
  ShowWindow(mainHwnd, nCmdShow);
  UpdateWindow(mainHwnd);
		
  return TRUE;
}
	
/*
 * winsnapWinMain
 *
 * This is the function that represents the code that
 * would normally reside in WinMain (see above).  This
 * function will get called during run() in order to set
 * up the windowing environment necessary for WinSnap to
 * operate.
 */
int
winsnapWinMain(void)
{
  MSG msg;

  /* Perform instance initialization */
  if (!InitApplication(hInst))
    return (FALSE);
		
  /* Perform application initialization */
  if (!InitInstance(hInst, SHOW_WINDOW))
    return (FALSE);
		
  /* Main message loop */
  while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  return (msg.wParam);
}
	
/*
 * WndProc
 *
 * Process window message for the main window.
 */
LRESULT CALLBACK
WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  HWND selectedHwnd;

  switch (message) {
			
  case WM_CREATE:
    /* The window is created... Send the capture message */
    PostMessage(hwnd, WM_DOCAPTURE, 0, 0);
    break;
	
  case WM_DOCAPTURE:
    /* Get the selected window handle */
    selectedHwnd = (HWND) DialogBox(hInst, MAKEINTRESOURCE(IDD_SELECT),
				    hwnd, (DLGPROC) dialogProc);
    if (selectedHwnd)
      doCapture(selectedHwnd);

    PostQuitMessage(0);

    break;

  case WM_DESTROY:
    PostQuitMessage(0);
    break;

  default:
    return (DefWindowProc(hwnd, message, wParam, lParam));
  }

  return 0;
}

/*
 * doRootWindowCapture
 *
 * Capture the root window
 */
static void
doRootWindowCapture(void)
{
  /* Do the window capture */
  doCapture(0);
}

/*
 * doNonRootWindowCapture
 *
 * Capture a selected window.
 */
static void
doWindowCapture(void)
{
  /* Start up a standard Win32
   * message handling loop for
   * selection of the window
   * to be captured
   */
  winsnapWinMain();
}
	
/******************************************************************
 * Snapshot configuration dialog
 ******************************************************************/

/*
 * snap_close_callback
 *
 * The close button has been pressed... Close
 * down the widgetry.
 */
static void
snap_close_callback(GtkWidget *widget,
		    gpointer data)
{
  gtk_main_quit();
}

/*
 * snap_grab_callback
 *
 * The "grab" button has been selected.
 */
static void
snap_grab_callback(GtkWidget *widget,
										gpointer data)
{
  winsnapintf.run = TRUE;
  gtk_widget_destroy(GTK_WIDGET (data));
}

/*
 * snap_toggle_update
 *
 * The radio buttons have been updated.
 */
static void
snap_toggle_update (GtkWidget *widget,
		     gpointer   radio_button)
{
  gint *toggle_val;

  toggle_val = (gint *) radio_button;

  if (GTK_TOGGLE_BUTTON (widget)->active)
    *toggle_val = TRUE;
  else
    *toggle_val = FALSE;

#ifdef CAN_SET_DECOR
  if (widget == winsnapintf.single_button)
    gtk_widget_set_sensitive (winsnapintf.decor_button, *toggle_val);
#endif /* CAN_SET_DECOR */
}

/*
 * snap_dialog
 *
 * Bring up the GTK dialog for setting snapshot
 * parameters.
 */
static gint
snap_dialog(void)
{
  GtkWidget		*dialog;
  GtkWidget		*frame;
  GtkWidget		*vbox;
  GtkWidget		*button;
  GtkWidget		*hbox;
  GtkWidget		*radio_label;
  GSList		*radio_group = NULL;
  gint			 radio_pressed[2];
  gint			 decorations;
  gint			 argc = 1;
  gchar		       **argv = g_new (gchar *, 1);

  /* Set the name */
  argv[0] = g_strdup("winsnap");

  /* Set defaults */
  radio_pressed[0] = (winsnapvals.root == FALSE);
  radio_pressed[1] = (winsnapvals.root == TRUE);
  decorations = winsnapvals.decor;

  /* Init GTK  */
  gtk_init(&argc, &argv);
  gtk_rc_parse(gimp_gtkrc());

  gdk_set_use_xshm(gimp_use_xshm());

  /* Main Dialog */
  dialog = gtk_dialog_new ();
  gtk_window_set_title(GTK_WINDOW(dialog), PLUG_IN_PRINT_NAME);
  gtk_window_position(GTK_WINDOW(dialog), GTK_WIN_POS_MOUSE);
  gtk_signal_connect(GTK_OBJECT(dialog), "destroy",
		     (GtkSignalFunc) snap_close_callback,
		     NULL);

  /*  Action area  */
  button = gtk_button_new_with_label("Grab");
  GTK_WIDGET_SET_FLAGS(button, GTK_CAN_DEFAULT);
  gtk_signal_connect(GTK_OBJECT (button), "clicked",
                     (GtkSignalFunc) snap_grab_callback,
                     dialog);
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area),
		button, TRUE, TRUE, 0);
  gtk_widget_grab_default(button);
  gtk_widget_show(button);

  button = gtk_button_new_with_label("Cancel");
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
			     (GtkSignalFunc) gtk_widget_destroy,
			     GTK_OBJECT(dialog));
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG (dialog)->action_area),
		      button, TRUE, TRUE, 0);
  gtk_widget_show(button);

  /*  Single Window */
  frame = gtk_frame_new(NULL);
  gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width(GTK_CONTAINER(frame), 4);
  gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),
		     frame, TRUE, TRUE, 0);

  vbox = gtk_vbox_new(FALSE, 4);
  gtk_container_border_width(GTK_CONTAINER(vbox), 4);
  gtk_container_add(GTK_CONTAINER(frame), vbox);

  hbox = gtk_hbox_new (FALSE, 4);
  gtk_box_pack_start (GTK_BOX (vbox),
		      hbox, TRUE, TRUE, 0);
  winsnapintf.single_button = gtk_radio_button_new ( radio_group );
  radio_group = gtk_radio_button_group ( GTK_RADIO_BUTTON (winsnapintf.single_button) );
  gtk_box_pack_start (GTK_BOX (hbox),
		      winsnapintf.single_button, TRUE, TRUE, 0);
  gtk_signal_connect ( GTK_OBJECT (winsnapintf.single_button), "toggled",
		       (GtkSignalFunc) snap_toggle_update,
		       &radio_pressed[0]);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (winsnapintf.single_button),
				   radio_pressed[0]);
  gtk_widget_show (winsnapintf.single_button);
  radio_label = gtk_label_new ( "Grab a single window" );
  gtk_box_pack_start (GTK_BOX (hbox),
		      radio_label, TRUE, TRUE, 0);
  gtk_widget_show (radio_label);
  gtk_widget_show (hbox);

  /* With decorations */
#ifdef CAN_SET_DECOR
  hbox = gtk_hbox_new (FALSE, 0);
  gtk_box_pack_end (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);
  winsnapintf.decor_button = gtk_check_button_new_with_label ("Include decorations");
  gtk_signal_connect (GTK_OBJECT (winsnapintf.decor_button), "toggled",
                      (GtkSignalFunc) snap_toggle_update,
                      &decorations);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (winsnapintf.decor_button),
			       decorations);
  gtk_box_pack_end (GTK_BOX (hbox), winsnapintf.decor_button, FALSE, FALSE, 0);
  gtk_widget_set_sensitive (winsnapintf.decor_button, radio_pressed[0]);
  gtk_widget_show (winsnapintf.decor_button);
#endif /* CAN_SET_DECOR */

  gtk_widget_show (hbox);
  gtk_widget_show (vbox);
  gtk_widget_show (frame);

  /* Root Window */
  frame = gtk_frame_new (NULL);
  gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
  gtk_container_border_width (GTK_CONTAINER (frame), 4);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
		      frame, TRUE, TRUE, 0);

  hbox = gtk_hbox_new (FALSE, 4);
  gtk_container_border_width (GTK_CONTAINER (hbox), 4);
  gtk_container_add (GTK_CONTAINER (frame), hbox);

  winsnapintf.root_button = gtk_radio_button_new ( radio_group );
  radio_group = gtk_radio_button_group ( GTK_RADIO_BUTTON (winsnapintf.root_button) );
  gtk_box_pack_start (GTK_BOX (hbox),  winsnapintf.root_button, TRUE, TRUE, 0);
  gtk_signal_connect ( GTK_OBJECT (winsnapintf.root_button), "toggled",
		       (GtkSignalFunc) snap_toggle_update,
		       &radio_pressed[1]);
  gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (winsnapintf.root_button),
				   radio_pressed[1]);
  gtk_widget_show (winsnapintf.root_button);

  radio_label = gtk_label_new ( "Grab the whole screen" );
  gtk_box_pack_start (GTK_BOX (hbox), radio_label, TRUE, TRUE, 0);
  gtk_widget_show (radio_label);

  gtk_widget_show (hbox);
  gtk_widget_show (frame);

  gtk_widget_show (dialog);

  gtk_main ();
  gdk_flush ();

  winsnapvals.root = radio_pressed[1];
  winsnapvals.decor = decorations;

  return winsnapintf.run;
}

/******************************************************************
 * GIMP Plug-in entry points
 ******************************************************************/
	
/*
 * Plug-in Parameter definitions
 */
#define NUMBER_IN_ARGS 3
#define IN_ARGS { PARAM_INT32,    "run_mode",  "Interactive, non-interactive" },\
                { PARAM_INT32,    "root",      "Root window { TRUE, FALSE }" },\
                { PARAM_INT32,    "decorations", \
									"Include Window Decorations { TRUE, FALSE }" }

#define NUMBER_OUT_ARGS 1
#define OUT_ARGS { PARAM_IMAGE,   "image",     "Output image" }

	
/*
 * query
 *
 * The plug-in is being queried.  Install our procedure for
 * capturing windows.
 */
static void
query(void)
{
  static GParamDef args[] = { IN_ARGS };
  static GParamDef return_vals[] = { OUT_ARGS };

  /* the installation of the plugin */
  gimp_install_procedure(PLUG_IN_NAME,
			 PLUG_IN_DESCRIPTION,
			 PLUG_IN_HELP,
			 PLUG_IN_AUTHOR,
			 PLUG_IN_COPYRIGHT,
			 PLUG_IN_VERSION,
			 PLUG_IN_MENU_PATH,
			 NULL,
			 PROC_EXTENSION,		
			 NUMBER_IN_ARGS,
			 NUMBER_OUT_ARGS,
			 args,
			 return_vals);
}
	
/* Return values storage */
static GParam values[2];

/*
 * run
 *
 * The plug-in is being requested to run.
 * Capture an window image.
 */
static void
run(gchar *name,		/* name of plugin */
    gint nparams,		/* number of in-paramters */
    GParam *param,		/* in-parameters */
    gint *nreturn_vals,	        /* number of out-parameters */
    GParam **return_vals)	/* out-parameters */
{
  GRunModeType run_mode;
  int wait = 1;

  /* Initialize the return values
   * Always return at least the status to the caller.
   */
  values[0].type = PARAM_STATUS;
  values[0].data.d_status = STATUS_SUCCESS;
  *nreturn_vals = 1;
  *return_vals = values;

  /* Get the runmode from the in-parameters */
  run_mode = param[0].data.d_int32;
		
  /* Set up the rest of the return parameters */
  values[1].type = PARAM_INT32;
  values[1].data.d_int32 = 0;
		
  /* Get the data from last run */
  gimp_get_data(PLUG_IN_NAME, &winsnapvals);

  /* How are we running today? */
  switch (run_mode) {
  case RUN_INTERACTIVE:
    /* Get information from the dialog */
    if (!snap_dialog())
      return;
    break;
			
  case RUN_NONINTERACTIVE:
  case RUN_WITH_LAST_VALS:
    if (!winsnapvals.root)
      values[0].data.d_status = STATUS_CALLING_ERROR;
    break;
			
  default:
    break;
  } /* switch */
	
  /* Do the actual window capture */
  if (winsnapvals.root)
    doRootWindowCapture();
  else
    doWindowCapture();

  /* Check to make sure we got at least one valid
   * image.
   */
  if (values[1].data.d_int32 > 0) {
    /* A window was captured.
     * Do final Interactive steps.
     */
    if (run_mode == RUN_INTERACTIVE) {
      /* Store variable states for next run */
      gimp_set_data(PLUG_IN_NAME, &winsnapvals, sizeof(WinSnapValues));
    }
			
    /* Set return values */
    *nreturn_vals = 2;
  } else {
    values[0].data.d_status = STATUS_EXECUTION_ERROR;
  }
}

/******************************************************************
 * GIMP format and transfer functions
 ******************************************************************/

/*
 * flipRedAndBlueBytes
 *
 * Microsoft has chosen to provide us a very nice (not!)
 * interface for retrieving bitmap bits.  DIBSections have
 * RGB information as BGR instead.  So, we have to swap
 * the silly red and blue bytes before sending to the
 * GIMP.
 */
static void
flipRedAndBlueBytes(char *buffer, int width, int height)
{
  int		npixels;
  guchar	temp;
  width = ROUND4(width);
  npixels = width*height;

  while (npixels--) {
    temp = buffer[2];
    buffer[2] = buffer[0];
    buffer[0] = temp;
    buffer += 3;
  }
}

/*
 * sendBMPToGIMP
 *
 * Take the captured data and send it across
 * to the GIMP.
 */
static gint
sendBMPToGimp(HBITMAP hBMP, HDC hDC, RECT rect)
{
  int		row;
  int		width, height;
  int		imageType, layerType;
  gint		retval;
  gint32	image_id;
  gint32	layer_id;
  GParam       *params;
  GPixelRgn	pixel_rgn;
  GDrawable    *drawable;

  /* Our width and height */
  width = (rect.right - rect.left);
  height = abs(rect.bottom - rect.top);

  /* Check that we got the memory */
  if (!capBytes) {
    g_warning("No data captured");
    return 0;
  }

  /* Flip the red and blue bytes */
  flipRedAndBlueBytes(capBytes, width, height);

  /* Set up the image and layer types */
  imageType = RGB;
  layerType = RGB_IMAGE;

  /* Create the GIMP image and layers */
  image_id = gimp_image_new(width, height, imageType);
  layer_id = gimp_layer_new(image_id, "Background",
			    ROUND4(width), height,
			    layerType, 100, NORMAL_MODE);
  gimp_image_add_layer(image_id, layer_id, 0);

  /* Get our drawable */
  drawable = gimp_drawable_get(layer_id);
		
  gimp_tile_cache_size(ROUND4(width) * gimp_tile_height() * 3);

  /* Initialize a pixel region for writing to the image */
  gimp_pixel_rgn_init(&pixel_rgn, drawable, 0, 0,
		      ROUND4(width), height, TRUE, FALSE);

  gimp_pixel_rgn_set_rect(&pixel_rgn, (guchar *) capBytes,
			  0, 0, ROUND4(width), height);

  /* Now resize the image down to the correct size if necessary. */
  if (width != ROUND4(width)) {
    gimp_layer_resize (layer_id, width, height, 0, 0);
    gimp_image_resize (image_id, width, height, 0, 0);
  }

  /* Finish up */
  gimp_drawable_flush(drawable);
  gimp_drawable_detach(drawable);
  params = gimp_run_procedure ("gimp_display_new", &retval,
			       PARAM_IMAGE, image_id, PARAM_END);

  return retval;
}
