// =================================================
// Определения
// =================================================

#define INCL_WIN
#define INCL_GPI
#define INCL_WINDIALOGS
#include <os2.h>
#include <stdio.h>
#include <string.h>
#include "kbdmsg.h"

// Прототип функции окна приложения
MRESULT EXPENTRY WndProc(HWND, ULONG, MPARAM, MPARAM);

// =================================================
// Глобальные переменные
// =================================================

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Keyboard Messages";

// Размеры окна Client Window
SHORT cxClient;
SHORT cyClient;

// Размеры символов выбранного шрифта
SHORT cxChar, cyChar, cyDesc;

// Заголовок столбцов
CHAR  szTitle[] = "VKEY  SCAN  FS    REPT  CHAR";

// =================================================
// Главная функция приложения main
// =================================================

int main()
{
  HMQ   hmq;
  QMSG  qmsg;
  BOOL  fRc;

  // Флаги для создания окна Frame Window
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR      | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON;

  // Имя класса главного окна
  CHAR  szWndClass[] = "KBDMSG";

  hab = WinInitialize(0);
  if(hab == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка инициализации",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    return(-1);
  }

  // Создаем очередь сообщений
  hmq = WinCreateMsgQueue(hab, 0);
  if(hmq == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании очереди сообщений",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinTerminate(hab);
    return(-1);
  }

  // Регистрация главного окна приложения
  fRc = WinRegisterClass(hab, szWndClass,
    (PFNWP)WndProc, 0, 0);
  if(fRc == FALSE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при регистрации класса главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return(-1);
  }

  // Создаем главное окно приложения
  hWndFrame = WinCreateStdWindow (HWND_DESKTOP,
    WS_VISIBLE,
    &flFrameFlags, szWndClass, szAppTitle,
    0, 0, ID_APP_FRAMEWND, &hWndClient);
  if(hWndFrame == NULLHANDLE)
  {
    WinMessageBox(HWND_DESKTOP, HWND_DESKTOP,
      "Ошибка при создании главного окна",
      "Ошибка", 0, MB_ICONHAND | MB_OK);
    WinDestroyMsgQueue(hmq);
    WinTerminate(hab);

    return(-1);
  }

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

  WinDestroyWindow(hWndFrame);
  WinDestroyMsgQueue(hmq);
  WinTerminate(hab);
  return(0);
}

// =================================================
// Функция главного окна приложения
// =================================================

MRESULT EXPENTRY
WndProc(HWND hWnd, ULONG msg, MPARAM mp1, MPARAM mp2)
{
  CHAR szMsg[100];
  int nMsgSize;
  HPS hps;
  POINTL ptl;
  RECTL rec;

  CHRMSG cm;
  FONTMETRICS fm;

  switch (msg)
  {
    // При создании главного окна приложения
    // определяем и сохраняем метрики шрифта
    // с фиксированной шириной символов
    case WM_CREATE:
    {
      // Получаем пространство отображения
      hps = WinGetPS(hWnd);

      // Выбираем в пространство отображения шрифт
      // с фиксированной шириной символов
      SetCourierFont(hps);

      // Определяем метрики шрифта
      GpiQueryFontMetrics(hps, (LONG)sizeof(fm), &fm);

      cxChar = fm.lAveCharWidth;
      cyChar = fm.lMaxBaselineExt;
      cyDesc = fm.lMaxDescender;

      // Устанавливаем шрифт, выбранный в пространство
      // отображения по умолчанию
      ResetFont(hps);

      // Возвращаем пространство отображения
      WinReleasePS(hps);
      return FALSE;
    }

    // Во время перерисовки стираем содержимое
    // окна и выводим строку заголовка таблицы
    case WM_PAINT:
    {
      // Получаем пространство отображения
      hps = WinBeginPaint(hWnd, NULLHANDLE, &rec);

      // Закрашиваем область, требующую обновление
      WinFillRect(hps, &rec, CLR_WHITE);

      // Выбираем в пространство отображения шрифт
      // с фиксированной шириной символов
      SetCourierFont(hps);

      // Рисуем заголовок таблицы в нижней части окна
      ptl.x = cxChar;
      ptl.y = cyDesc + cyChar;
      GpiCharStringAt(hps, &ptl, strlen(szTitle), szTitle);

      // Устанавливаем шрифт, выбранный в пространство
      // отображения по умолчанию
      ResetFont(hps);

      // Возвращаем пространство отображения
      WinEndPaint(hps);
      return 0;
    }

    case WM_ERASEBACKGROUND:
      return(MRFROMLONG(1L));

    // При изменении размеров окна сохраняем новые
    // размеры и перерисовываем окно
    case WM_SIZE:
    {
      cxClient = SHORT1FROMMP(mp2);
      cyClient = SHORT2FROMMP(mp2);

      // Все окно требует перерисовки
      WinInvalidateRect(hWnd, NULL, TRUE);
      return 0;
    }

    // Это сообщение появляется, когда пользователь
    // нажимает или отжимает клавишу
    case WM_CHAR:
    {
      // Получаем пространство отображения
      hps = WinGetPS(hWnd);

      // Выбираем в пространство отображения шрифт
      // с фиксированной шириной символов
      SetCourierFont(hps);

      // Выделяем параметры клавиатурного сообщения
      // и сохраняем их в структуре cm
      cm.chr      = CHARMSG(&msg) ->chr;
      cm.vkey     = CHARMSG(&msg) ->vkey;
      cm.scancode = CHARMSG(&msg) ->scancode;
      cm.cRepeat  = CHARMSG(&msg) ->cRepeat;
      cm.fs       = CHARMSG(&msg) ->fs;

      // Готовим строку для отображения параметров
      // клавиатурного сообщения
      sprintf(szMsg, "%04x  %04x  %04x  %d     %c",
        cm.vkey, cm.scancode, cm.fs, cm.cRepeat,
        cm.fs & KC_CHAR ? cm.chr : ' ');

      // Отображаем строку над заголовком таблицы
      // в нижней части окна
      ptl.x = cxChar;
      ptl.y = 2 * cyChar + cyDesc;

      nMsgSize = strlen(szMsg);
      GpiCharStringAt(hps, &ptl, nMsgSize, szMsg);

      // Устанавливаем шрифт, выбранный в пространство
      // отображения по умолчанию
      ResetFont(hps);

      // Возвращаем пространство отображения
      WinReleasePS(hps);

      // Устанавливаем границы сдвигаемой области окна
      WinSetRect(hab, &rec,
        0, 2 * cyChar, cxClient, cyClient);

      // Выполняем сдвиг верхней части окна
      WinScrollWindow(hWnd, 0, cyChar + cyDesc,
        &rec, NULL, NULLHANDLE, NULL, SW_INVALIDATERGN);

      return 0;
    }

    default:
      return(WinDefWindowProc(hWnd, msg, mp1, mp2));
  }
}

// =================================================
// Выбор шрифта с фиксированной шириной символов
// =================================================

void SetCourierFont(HPS hps)
{
  FATTRS fat;

  // Заполняем структуру описанием нужного
  // нам шрифта

  // Размер структуры
  fat.usRecordLength = sizeof(FATTRS);

  // Название шрифта
  strcpy(fat.szFacename ,"Courier");

  // Используем нормальный шрифт без выделений
  // наклоном, подчеркиванием и т. п.
  fat.fsSelection = 0;

  // Указываем, что система Presentation Manager должна
  // подобрать шрифт, подходящий к нашему описанию
  fat.lMatch = 0L;

  // Регистрационный номер, должен быть равен 0
  fat.idRegistry = 0;

  // Кодовая страница
  fat.usCodePage = 850;

  // Высота шрифта
  fat.lMaxBaselineExt = 12L;

  // Ширина шрифта
  fat.lAveCharWidth = 12L;

  // Тип шрифта - обычный без использования кернинга,
  // двухбайтовых символов и т. д.
  fat.fsType = 0;

  // Использование шрифта - шрифт, который отображается
  // без смешивания с графикой
  fat.fsFontUse = FATTR_FONTUSE_NOMIX;

  // Создаем логический шрифт, имеющий идентификатор 1L
  GpiCreateLogFont(hps, NULL, 1L, &fat);

  // Выбираем созданный шрифт в пространство отображения
  GpiSetCharSet(hps, 1L);
}

// =================================================
// Установка шрифта, выбранного в пространство
// отображения по умолчанию
// =================================================
void ResetFont(HPS hps)
{
  // Выбираем шрифт по умолчанию
  GpiSetCharSet(hps, LCID_DEFAULT);

  // Удаляем созданный ранее шрифт
  // с идентификатором 1L
  GpiDeleteSetId(hps, 1L);
}

