// ----------------------------------------
// Просмотр Clipboard
// ----------------------------------------
#define STRICT
#include <windows.h>
#include <mem.h>
#include "clipshow.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawBitmap(HDC, int, int, HBITMAP);
void PrepareMetaFile(HDC, LPMETAFILEPICT, int, int);
void SizeToHiMetric(int*, int*);
void HiMetricToSize(int*, int*);
void HiMetricToSizeScaled(int*, int*, int, int);

char const szClassName[]   = "ClipShowClass";
char const szWindowTitle[] = "Clipboard Show";
// =====================================
// Функция WinMain
// =====================================
#pragma argsused
int PASCAL
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpszCmdLine, int nCmdShow)
{
  MSG  msg;   // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

// Не разрешаем запуск нескольких копий приложения
  if(hPrevInstance)
    return FALSE;

  if(!InitApp(hInstance))
    return FALSE;

  hwnd = CreateWindow(
    szClassName, szWindowTitle,
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT,
    0, 0, hInstance, NULL);
  if(!hwnd)
    return FALSE;

  ShowWindow(hwnd, nCmdShow);
  UpdateWindow(hwnd);

  while(GetMessage(&msg, 0, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }
  return msg.wParam;
}
// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================
BOOL
InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации
                  // класса окна
  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC) WndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APP_ICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szClassName;

  aWndClass = RegisterClass(&wc);
  return (aWndClass != 0);
}
// =====================================
// Функция WndProc
// =====================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  RECT rc;
  static HWND hwndNextViewer;
  HGLOBAL hglbClipData;
  LPSTR   lpszClipBuf;
  HBITMAP hBitmap;
  static HPALETTE hPal, hOldPal;
  static int cxClient, cyClient;
  HMETAFILE hmf;
  LPMETAFILEPICT lpmfp;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Наше окно становится окном просмотра Clipboard
      hwndNextViewer = SetClipboardViewer(hwnd);
      return 0;
    }
    case WM_SIZE:
    {
      cxClient = LOWORD(lParam);
      cyClient = HIWORD(lParam);
      return 0;
    }
    case WM_PAINT:
    {
      hdc = BeginPaint(hwnd, &ps);
      GetClientRect(hwnd, &rc);

      // Открываем Clipboard
      OpenClipboard(hwnd);

      // Читаем данные в формате метафайла
      hmf = (HMETAFILE)GetClipboardData(CF_METAFILEPICT);
      if(hmf != NULL)
      {
        // Фиксируем память заголовка метафайла
        lpmfp = (LPMETAFILEPICT)GlobalLock(hmf);
        if(lpmfp != NULL)
        {
          // Сохраняем контекст отображения
          SaveDC(hdc);

          // Устанавливаем параметры контекста отображения
          // в соответствии с содержимым заголовка метафайла
          PrepareMetaFile(hdc, lpmfp, cxClient, cyClient);

          // Проигрываем метафайл 
          PlayMetaFile(hdc, lpmfp->hMF);

          // Восстанавливаем контекст отображения
          RestoreDC(hdc, -1);

          // Освобождаем память заголовка метафайла
          GlobalUnlock(hmf);
        }
      }
      else
      {
        // Читаем данные в формате CF_BITMAP
        hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
        if(hBitmap != NULL)
        {
          // Читаем палитру
          hPal = (HPALETTE)GetClipboardData(CF_PALETTE);
          if(hPal)
          {
            // Если Clipboard содержит палитру,
            // выбираем и реализуем ее
            hOldPal = SelectPalette(hdc, hPal, FALSE);
            RealizePalette(hdc);
          }
          // Рисуем изображение в окне
          DrawBitmap(hdc, 0, 0, hBitmap);
        }
        // Читаем текстовые данные
        else
        {
          hglbClipData = GetClipboardData(CF_TEXT);
          if(hglbClipData)
          {
            // Фиксируем блок памяти и рисуем текст
            lpszClipBuf = (LPSTR)GlobalLock(hglbClipData);
            DrawText(hdc, lpszClipBuf, -1, &rc, DT_EXPANDTABS);

            // Расфиксируем блок памяти
            GlobalUnlock(hglbClipData);
          }
        }
      }
      // Закрываем Clipboard
      CloseClipboard();
      EndPaint(hwnd, &ps);
    }
    // Произошли изменения в цепочке окон просмотра
    // Clipboard
    case WM_CHANGECBCHAIN:
    {
      // Если идентификатор удаляемого окна просмотра
      // равен идентификатору следующего окна в цепочке,
      // запоминаем идентификатор нового следующего
      // окна просмотра
      if(wParam == (WPARAM)hwndNextViewer)
        hwndNextViewer = (HWND)LOWORD(lParam);

      // Передаем сообщение следующему окну просмотра
      // по цепочке
      else if(hwndNextViewer)
        SendMessage(hwndNextViewer, msg, wParam, lParam);
      return 0;
        
    }
    // Содержимое Clipboard изменилось, наше приложение
    // должно перерисовать свое окно
    case WM_DRAWCLIPBOARD:
    {
      // Если наше окно не последнее в цепочке,
      // передаем сообщение дальше по цепочке
      if(hwndNextViewer)
        SendMessage(hwndNextViewer, msg, wParam, lParam);
      InvalidateRect(hwnd, NULL, TRUE);
      return 0;
    }
    case WM_COMMAND:
    {
      switch (wParam)
      {
        case CM_HELPABOUT:
        {
	       MessageBox(hwnd,
            "Приложение CLIPSHOW\n(C) Фролов А.В., 1995",
	         (LPSTR)szWindowTitle, MB_OK | MB_ICONINFORMATION);
          return 0;
        }
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          return 0;
        }
        default:
          return 0;
      }
    }
    // Отслеживаем изменения палитры
    case WM_PALETTECHANGED:
    {
      if(hwnd == (HWND) wParam)
        break;
    }
    case WM_QUERYNEWPALETTE:
    {
      int nChanged;

      hdc = GetDC(hwnd);
      hOldPal = SelectPalette(hdc, hPal,
        (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
      nChanged = RealizePalette(hdc);
      SelectPalette(hdc, hOldPal, TRUE);
      ReleaseDC(hwnd, hdc);
      if(nChanged)
        InvalidateRect(hwnd, NULL, TRUE);
      return nChanged;
    }
    // При завершении работы приложения изменяем
    // цепочку окон просмотра Clipboard,
    // удаляя из нее окно нашего приложения
    case WM_DESTROY:
    {
      ChangeClipboardChain(hwnd, hwndNextViewer);
      PostQuitMessage(0);
      return 0;
    }
    default:
      break;
  }
  return DefWindowProc(hwnd, msg, wParam, lParam);
}
// =====================================
// Функция DrawBitmap
// =====================================
void
DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap)
{
  HBITMAP hbm, hOldbm;
  HDC hMemDC;
  BITMAP bm;
  POINT  ptSize, ptOrg;

  hMemDC = CreateCompatibleDC(hDC);
  hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
  if (hOldbm)
  {
    SetMapMode(hMemDC, GetMapMode(hDC));
    GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

    ptSize.x = bm.bmWidth;   // ширина
    ptSize.y = bm.bmHeight;  // высота
    DPtoLP(hDC, &ptSize, 1);

    ptOrg.x = 0;
    ptOrg.y = 0;
    DPtoLP(hMemDC, &ptOrg, 1);

    BitBlt(hDC, x, y, ptSize.x, ptSize.y,
	   hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
    SelectObject(hMemDC, hOldbm);
  }
  DeleteDC(hMemDC);
}
// =====================================
// Функция PrepareMetaFile
// =====================================
void
PrepareMetaFile(HDC hdc, LPMETAFILEPICT lpmfp,
  int cxClient, int cyClient)
{
  int x, y;

  // Устанавливаем такой режим отображения,
  // какой указан в заголовке метафайла
  SetMapMode(hdc, lpmfp->mm);

  // Для изотропного и анизотропного режима
  // анализируем поля xExt и yExt заголовка
  if(lpmfp->mm == MM_ISOTROPIC ||
     lpmfp->mm == MM_ANISOTROPIC)
  {
    // Если xExt равен нулю, устанавливаем
    // размеры изображения равными размерам
    // внутренней области окна
    if(lpmfp->xExt == 0)
      SetViewportExtEx(hdc, cxClient, cyClient, NULL);

    // Если xExt больше нуля, устанавливаем
    // размеры изображения в соответствии со значениями,
    // указанными в заголовке метафайла
    else if(lpmfp->xExt > 0)
    {
      x = lpmfp->xExt;
      y = lpmfp->yExt;

      // Выполняем преобразование из
      // сотых долей мм в пикселы
      HiMetricToSize(&x, &y);
      SetViewportExtEx(hdc, x, y, NULL);
    }

    // Если xExt меньше нуля, сохраняем масштаб
    // изображения, разрешая изменения размеров
    else if(lpmfp->xExt < 0)
    {
      x = -lpmfp->xExt;
      y = -lpmfp->yExt;

      // Выполняем преобразование из
      // сотых долей мм в пикселы с учетом
      // размеров окна
      HiMetricToSizeScaled(&x, &y, cxClient, cyClient);
      SetViewportExtEx(hdc, x, y, NULL);
    }
  }
  // Для остальных режимов отображения устанавливаем
  // размеры изображения равными размерам,
  // указанным в заголовке метафайла
  else
    SetViewportExtEx(hdc, lpmfp->xExt, lpmfp->yExt, NULL);
}
// =====================================
// Функция SizeToHiMetric
// =====================================
void SizeToHiMetric(int *width, int *height)
{
  HDC hDC = GetDC(0);

  // Определяем количество пикселов в логическом
  // дюйме по горизонтали и по вертикали
  int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
  int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);

  // Константа для преобразования
  const long HiMetricPerInch = 2540;

  // Выполняем преобразование
  if(width)  *width  = int(*width * HiMetricPerInch / dpiX);
  if(height) *height = int(*height * HiMetricPerInch / dpiY);

  ReleaseDC(0, hDC);
}
// =====================================
// Функция HiMetricToSize
// =====================================
void HiMetricToSize(int *width, int *height)
{
  HDC hDC = GetDC(0);
  long int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
  long int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);
  const long HiMetricPerInch = 2540;

  if(width  != NULL) *width  =
    int(*width * dpiX / HiMetricPerInch);
  if(height != NULL) *height =
    int(*height * dpiY / HiMetricPerInch);

  ReleaseDC (0, hDC);
}
// =====================================
// Функция HiMetricToSizeScaled
// =====================================
void HiMetricToSizeScaled (int *width, int *height,
  int cxClient, int cyClient)
{
  HDC hDC = GetDC(0);
  long int dpiX = GetDeviceCaps(hDC, LOGPIXELSX); 
  long int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);
  const long HiMetricPerInch = 2540;
  int xPic, yPic;

  // Определяем размеры в пикселах
  if (width !=  NULL) xPic =
    int(*width * dpiX / HiMetricPerInch);
  if (height != NULL) yPic =
    int(*height * dpiY / HiMetricPerInch);

  // Сравниваем коэффициенты масштабирования
  // по горизонтали и вертикали
  if((cxClient*100L) / xPic > (cyClient*100L) / yPic)
  {
    // Если коэффициент масштабирования по
    // горизонтали больше, масштабируем
    // по горизонтали
    xPic = ((long)xPic * cyClient) / (long)yPic;
    yPic = cyClient;
  }
  else
  {
    // Иначе масштабируем по вертикали
    yPic = ((long)cxClient * yPic) / (long)xPic;
    xPic = cxClient;
  }
  ReleaseDC (0, hDC);

  // Новые размеры
  *width = xPic; *height = yPic;
}

