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

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

// Максимальное количество строк по вертикали
#define YSIZE 50

// Максимальное количество символов по горизонтали
#define XSIZE 50

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

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

HAB  hab;
HWND hWndFrame;
HWND hWndClient;

CHAR szAppTitle[] = "Scroll Demo";

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

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

// Структура для записи метрик шрифта
FONTMETRICS fm;

// Идентификаторы полос просмотра
HWND hwndXScroll;
HWND hwndYScroll;

// Текущие координаты движков
INT nXScrollPos;
INT nYScrollPos;

// Текущие координаты для вывода текста
LONG cxCurrentPosition;
LONG cyCurrentPosition;

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

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

  // Флаги для создания окна Frame Window
  // Добавляем флаги FCF_VERTSCROLL и FCF_HORZSCROLL,
  // в результате чего в главном окне будут созданы
  // вертикальная и горизонтальная полосы просмотра
  ULONG flFrameFlags =
    FCF_SYSMENU    | FCF_TITLEBAR      | FCF_MINMAX   |
    FCF_SIZEBORDER | FCF_SHELLPOSITION | FCF_TASKLIST |
    FCF_ICON       | FCF_VERTSCROLL    | FCF_HORZSCROLL;

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

  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);
  }

  // Получаем пространство отображения
  hps = WinGetPS(hWndFrame);

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

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

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

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

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

  // Запускаем цикл обработки сообщений
  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)
{
  HPS hps;
  RECTL rec;

  switch (msg)
  {
    case WM_CREATE:
    {
      // Начальные координаты движков
      nYScrollPos = 0;
      nXScrollPos = 0;

      // Начальные координаты для вывода текста
      cyCurrentPosition = cyClient;
      cxCurrentPosition =
        nXScrollPos * cxChar + cxChar;

      // Определяем идентификатор окна для
      // вертикальной полосы просмотра
      hwndYScroll = WinWindowFromID(
        WinQueryWindow(hWnd, QW_PARENT), FID_VERTSCROLL),

      // Устанавливаем диапазон изменений координат
      // движка и начальное положение для
      // вертикальной полосы просмотра
      WinSendMsg(hwndYScroll,
        SBM_SETSCROLLBAR, (MPARAM)0,
        MPFROM2SHORT(0, YSIZE));

      // Выполняем аналогичные действия для
      // горизонтальной полосы просмотра
      hwndXScroll = WinWindowFromID(
        WinQueryWindow(hWnd, QW_PARENT), FID_HORZSCROLL),

      WinSendMsg(hwndXScroll,
        SBM_SETSCROLLBAR, (MPARAM)0,
        MPFROM2SHORT(0, XSIZE));

      return FALSE;
    }

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

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);
      return 0;
    }

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

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

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

      // Устанавливаем начальные координаты для
      // вывода текста
      cxCurrentPosition = -nXScrollPos * cxChar + cxChar;
      cyCurrentPosition = cyClient;

      // Выводим метрики шрифта
      PrintString(hps, fm.szFamilyname,     "szFamilyname");
      PrintString(hps, fm.szFacename,       "szFacename");

      PrintLong(hps, fm.idRegistry,         "idRegistry");
      PrintLong(hps, fm.usCodePage,         "usCodePage");
      PrintLong(hps, fm.lEmHeight,          "lEmHeight");
      PrintLong(hps, fm.lXHeight,           "lXHeight");
      PrintLong(hps, fm.lMaxAscender,       "lMaxAscender");
      PrintLong(hps, fm.lMaxDescender,      "lMaxDescender");
      PrintLong(hps, fm.lLowerCaseAscent,   "lLowerCaseAscent");
      PrintLong(hps, fm.lLowerCaseDescent,  "lLowerCaseDescent");
      PrintLong(hps, fm.lInternalLeading,   "lInternalLeading");
      PrintLong(hps, fm.lExternalLeading,   "lExternalLeading");
      PrintLong(hps, fm.lAveCharWidth,      "lAveCharWidth");
      PrintLong(hps, fm.lMaxCharInc,        "lMaxCharInc");
      PrintLong(hps, fm.lEmInc,             "lEmInc");
      PrintLong(hps, fm.lMaxBaselineExt,    "lMaxBaselineExt");
      PrintLong(hps, fm.sCharSlope,         "sCharSlope");
      PrintLong(hps, fm.sInlineDir,         "sInlineDir");
      PrintLong(hps, fm.sCharRot,           "sCharRot");
      PrintLong(hps, fm.usWeightClass,      "usWeightClass");
      PrintLong(hps, fm.usWidthClass,       "usWidthClass");
      PrintLong(hps, fm.sXDeviceRes,        "sXDeviceRes");
      PrintLong(hps, fm.sYDeviceRes,        "sYDeviceRes");
      PrintLong(hps, fm.sFirstChar,         "sFirstChar");
      PrintLong(hps, fm.sLastChar,          "sLastChar");
      PrintLong(hps, fm.sDefaultChar,       "sDefaultChar");
      PrintLong(hps, fm.sBreakChar,         "sBreakChar");
      PrintLong(hps, fm.sNominalPointSize,  "sNominalPointSize");
      PrintLong(hps, fm.sMinimumPointSize,  "sMinimumPointSize");
      PrintLong(hps, fm.sMaximumPointSize,  "sMaximumPointSize");
      PrintLong(hps, fm.fsType,             "fsType");
      PrintLong(hps, fm.fsDefn,             "fsDefn");
      PrintLong(hps, fm.fsSelection,        "fsSelection");
      PrintLong(hps, fm.fsCapabilities,     "fsCapabilities");
      PrintLong(hps, fm.lSubscriptXSize,    "lSubscriptXSize");
      PrintLong(hps, fm.lSubscriptYSize,    "lSubscriptYSize");
      PrintLong(hps, fm.lSubscriptXOffset,  "lSubscriptXOffset");
      PrintLong(hps, fm.lSubscriptYOffset,  "lSubscriptYOffset");
      PrintLong(hps, fm.lSuperscriptXSize,  "lSuperscriptXSize");
      PrintLong(hps, fm.lSuperscriptYSize,  "lSuperscriptYSize");
      PrintLong(hps, fm.lSuperscriptXOffset,"lSuperscriptXOffset");
      PrintLong(hps, fm.lSuperscriptYOffset,"lSuperscriptYOffset");
      PrintLong(hps, fm.lUnderscoreSize,    "lUnderscoreSize");
      PrintLong(hps, fm.lUnderscorePosition,"lUnderscorePosition");
      PrintLong(hps, fm.lStrikeoutSize,     "lStrikeoutSize");
      PrintLong(hps, fm.lStrikeoutPosition, "lStrikeoutPosition");
      PrintLong(hps, fm.sKerningPairs,      "sKerningPairs");
      PrintLong(hps, fm.sFamilyClass,       "sFamilyClass");
      PrintLong(hps, fm.lMatch,             "lMatch");
      PrintLong(hps, fm.FamilyNameAtom,     "FamilyNameAtom");
      PrintLong(hps, fm.FaceNameAtom,       "FaceNameAtom");

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

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

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

    // Это сообщение приходит от вертикальной
    // полосы просмотра
    case WM_VSCROLL:
    {
      // В зависимости от кода извещения
      // изменяем содержимое переменной, в которой
      // хранится координата движка
      switch (SHORT2FROMMP(mp2))
      {
        case SB_LINEDOWN:
        {
          nYScrollPos += 1;
          break;
        }
        case SB_LINEUP:
        {
          nYScrollPos -= 1;
          break;
        }
        case SB_PAGEDOWN:
        {
          nYScrollPos += cyClient / cyChar;
          break;
        }
        case SB_PAGEUP:
        {
          nYScrollPos -= cyClient / cyChar;
          break;
        }
        case SB_SLIDERTRACK:
        {
          nYScrollPos = SHORT1FROMMP(mp2);
          break;
        }

        default:
          break;
      }

      // Ограничиваем диапазон изменения
      // координаты движка
      if(nYScrollPos > YSIZE) nYScrollPos = YSIZE;
      if(nYScrollPos < 0)  nYScrollPos = 0;

      // Устанавливаем новую позицию движка
      WinSendMsg(hwndYScroll, SBM_SETPOS,
        (MPARAM)nYScrollPos, NULL);

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);

      return 0;
    }

    // Это сообщение приходит от горизонтальной
    // полосы просмотра
    case WM_HSCROLL:
    {
      // В зависимости от кода извещения
      // изменяем содержимое переменной, в которой
      // хранится координата движка
      switch (SHORT2FROMMP(mp2))
      {
        case SB_LINELEFT:
        {
          nXScrollPos -= 1;
          break;
        }
        case SB_LINERIGHT:
        {
          nXScrollPos += 1;
          break;
        }
        case SB_PAGERIGHT:
        {
          nXScrollPos += 10;
          break;
        }
        case SB_PAGELEFT:
        {
          nXScrollPos -= 10;
          break;
        }
        case SB_SLIDERTRACK:
        {
          nXScrollPos = SHORT1FROMMP(mp2);
          break;
        }

        default:
          break;
      }

      // Ограничиваем диапазон изменения
      // координаты движка
      if(nXScrollPos < 0)  nXScrollPos = 0;

      // Устанавливаем новую позицию движка
      WinSendMsg(hwndXScroll, SBM_SETPOS,
        (MPARAM)nXScrollPos, NULL);

      // Перерисовываем окно приложения
      WinInvalidateRect(hWnd, NULL, TRUE);

      return 0;
    }

    // Это сообщение появляется, когда пользователь
    // нажимает или отжимает клавишу
    case WM_CHAR:
    {
      // Пропускаем только виртуальные клавиши
      if(!(CHARMSG(&msg) ->fs & KC_VIRTUALKEY))
        return 0;

      // Фильтруем сообщения, поступающие при
      // отжатии клавиш
      if(!(CHARMSG(&msg) ->fs & KC_KEYUP))
        return 0;

      // В зависимости от виртуального кода клавиши
      // посылаем окну нашего прилоджения
      // сообщения WM_HSCROLL или WM_HSCROLL для
      // работы с полосами просмотра при помощи
      // клавиатуры
      switch(CHARMSG(&msg) ->vkey)
      {
        case VK_LEFT:
        {
           WinSendMsg(hWnd, WM_HSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINELEFT));
          break;
        }
        case VK_RIGHT:
        {
           WinSendMsg(hWnd, WM_HSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINERIGHT));
          break;
        }
        case VK_UP:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINEUP));
          break;
        }
        case VK_DOWN:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_LINEDOWN));
          break;
        }
        case VK_PAGEUP:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_PAGEUP));
          break;
        }
        case VK_PAGEDOWN:
        {
           WinSendMsg(hWnd, WM_VSCROLL,
             NULL, MPFROM2SHORT(0, SB_PAGEDOWN));
          break;
        }
        default:
          break;
      }

      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;
  fat.lMatch = 0L;
  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);
  GpiDeleteSetId(hps, 1L);
}

// =================================================
// Вывод в окне значения строчной переменной
// =================================================

void PrintString(HPS hps, PSZ pszValue, PSZ pszName)
{
  int i;
  CHAR szBuf[80];
  POINTL ptl;

  // Выводим название поля структуры
  sprintf(szBuf, "%s", pszName);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition;
  ptl.y = cyCurrentPosition -
    cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  // Выводим значение, записанное в этом поле
  sprintf(szBuf, "= %s", pszValue);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition + 20 * cxChar;
  ptl.y = cyCurrentPosition -
    cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  // Изменяем текущую позицию для вывода
  cyCurrentPosition -= cyChar;
}

// =================================================
// Вывод в окне значения переменной типа LONG
// =================================================

void PrintLong(HPS hps, LONG lValue, PSZ pszName)
{
  int i;
  CHAR szBuf[80];
  POINTL ptl;

  sprintf(szBuf, "%s", pszName);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition;
  ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  sprintf(szBuf, "= %ld", lValue);
  i = strlen(szBuf);

  ptl.x = cxCurrentPosition + 20 * cxChar;
  ptl.y = cyCurrentPosition - cyChar * (1 - nYScrollPos);
  GpiCharStringAt(hps, &ptl, i, szBuf);

  cyCurrentPosition -= cyChar;
}

