'a main resizable modal dialog with a tab control, each tab control have a sub tab-modeless-dialog with his own controls
'those sub tab-modeless-dialog have to be sibbling of the tab control, not child.
'all messages from all tab-modeless-dialog goes to TabsCommonDialogProc callback,
'one could create one callback for each dialog.
'code is ansi, some minor changes are needed for wide unicode.
'GradientFill is used to draw the background.
'See Raymond Chen https://devblogs.microsoft.com/oldnewthing/20191015-00/?p=102996

//KickResource "D:\Dev\Oxygen\o2\~code\~~~Resource.res"
//KickSwitch -64
//KickEnd

#autodim off
indexbase 0

uses "Dialogs.inc"

%MainDialogButton01      101
%MainDialogButtonIDCANCEL IDCANCEL

%Dialog0Frame        0101
%Dialog0ButtonOk    0102
%Dialog0Edit        0201

%Dialog1Frame        1101
%Dialog1ButtonOk    1102
%Dialog1Edit        1201

%Dialog2Frame        2101
%Dialog2ButtonOk    2102
%Dialog2Edit        2201

%DS_MODALFRAME      0x0080
%DS_3DLOOK          0x0004
%DS_NOFAILCREATE    0x0010
%DEFAULT_GUI_FONT    17
%GCL_HICON          -14
%WS_EX_RIGHTCROLLBAR 0
%WM_APP              0x08000
%WM_MOVING          0x0216
%RDW_INVALIDATE      0x0001
%RDW_ALLCHILDREN    0x0080
%RDW_INVALIDATE      0x0001
%RDW_INTERNALPAINT  0x0002
%RDW_ERASE          0x0004

%SWP_NOSIZE          0x0001
%SWP_NOMOVE          0x0002
%SWP_NOZORDER        0x0004
%SWP_NOREDRAW        0x0008
%SWP_NOACTIVATE      0x0010
%SWP_FRAMECHANGED    0x0020
%SWP_SHOWWINDOW      0x0040
%SWP_HIDEWINDOW      0x0080
%SWP_NOCOPYBITS      0x0100
%SWP_NOOWNERZORDER  0x0200
%SWP_NOSENDCHANGING  0x0400
%SWP_DRAWFRAME      %SWP_FRAMECHANGED
%SWP_NOREPOSITION    %SWP_NOOWNERZORDER

%TCS_MULTILINE      0x0200
%TCIF_TEXT          1
%TCIF_IMAGE        2
%TCIF_PARAM        0x0008
%TCIF_STATE        0x0010
%TCM_FIRST          0x1300
%TCN_FIRST          0-550
%TCN_SELCHANGE      %TCN_FIRST - 1
%TCN_SELCHANGING    %TCN_FIRST - 2
%TCM_GETIMAGELIST  %TCM_FIRST + 2
%TCM_SETIMAGELIST  %TCM_FIRST + 3
%TCM_GETITEMCOUNT  %TCM_FIRST + 4
%TCM_GETCURSEL      %TCM_FIRST + 11
%TCM_INSERTITEMA    %TCM_FIRST + 7
%TCM_ADJUSTRECT    %TCM_FIRST + 40
%TCM_INSERTITEMW    %TCM_FIRST + 62
%TCM_INSERTITEM    %TCM_INSERTITEMW

%RDW_VALIDATE        0x0008
%RDW_NOINTERNALPAINT 0x0010
%RDW_NOERASE        0x0020
%RDW_NOCHILDREN      0x0040
%RDW_ALLCHILDREN    0x0080
%RDW_UPDATENOW      0x0100
%RDW_ERASENOW        0x0200
%RDW_FRAME          0x0400
%RDW_NOFRAME        0x0800
%WC_TABCONTROL      "SysTabControl32"
%WC_STATIC          "Static"
%ICC_TAB_CLASSES    8

%SWP_SHOWWINDOW    64
%SIZE_MINIMIZED    1
%HWND_TOP          0
%HWND_DESKTOP      0

%DeviceCount        7
string device[]  = {"Monitor","Printer","Speaker","Mouse","USB","BlueTooth","Battery"}
word deviceBmp[] = {3, 7, 15, 18, 47, 55, 45}

type TCITEM 'TCITEMA TCITEMW
  int  mask, dwState, dwStateMask
  char* pszText
  int  cchTextMax, iImage
  sys  lParam
end type
typedef TCITEM TC_ITEM

TYPE SP_CLASSIMAGELIST_DATA
  dword cbSize    'DWORD
  sys  ImageList 'HIMAGELIST
  sys  Reserved  'ULONG_PTR
END TYPE

! SetupDiGetClassImageList    lib "SetupApi.dll" alias "SetupDiGetClassImageList"(sys) as bool
! SetupDiDestroyClassImageList lib "SetupApi.dll" alias "SetupDiDestroyClassImageList"(sys) as bool
'______________________________________________________________________________

%GRADIENT_FILL_RECT_H  0x00000000
%GRADIENT_FILL_RECT_V  0x00000001
%GRADIENT_FILL_TRIANGLE 0x00000002
%GRADIENT_FILL_OP_FLAG  0x000000ff

TYPE GRADIENT_RECT '8 bytes
  dword UpperLeft
  dword LowerRight
END TYPE

TYPE TRIVERTEX '16 bytes
  LONG  x    'LONG
  LONG  y    'LONG
  WORD  Red  'COLOR16
  WORD  Green 'COLOR16
  WORD  Blue  'COLOR16
  WORD  Alpha 'COLOR16
END TYPE

! GradientFill lib "MSIMG32.DLL" alias "GradientFill" (sys, sys, dword, sys, dword, dword) as bool
'______________________________________________________________________________

sub DrawGradient(sys hDlg, sys hDC, short TabId)
GRADIENT_RECT GradientRect
dim TRIVERTEX Vertex(1)
Rect rc

GetClientRect(hDlg, rc)

Vertex(0).x    = 0
Vertex(0).y    = 0
Vertex(0).Red  = 0x0010
Vertex(0).Green = 0x0010
Vertex(0).Blue  = 0x0010

Vertex(1).x    = rc.Right
Vertex(1).y    = rc.Bottom
Vertex(1).Red  = 0
Vertex(1).Green = 0
Vertex(1).Blue  = 0xffff

GradientRect.UpperLeft  = 0 'Vertex(0)
GradientRect.LowerRight = 1 'Vertex(1)

GradientFill(hDC, @Vertex(0, 0), 2, @GradientRect, 1, GRADIENT_FILL_RECT_V)

end sub
'_____________________________________________________________________________

function TabsCommonDialogProc(sys hTabDialog, uint uMsg, sys wParam, lParam) as int callback
'this is a common dialog used by every TABs dialog
static sys hTabControl, hBrush
static short TabId 'will be set in WM_ERASEBKGND
static long ClientSizeX, ClientSizeY

select case uMsg

  case WM_INITDIALOG
    dword RGB_BLUE = 0x00FF0000
    hBrush = CreateSolidBrush(RGB_BLUE)
    hTabControl = FindWindowEx(GetParent(hTabDialog), 0, "SysTabControl32", "wcTabControl-01")
    return true

  case WM_COMMAND
    select case TabId
      case 0 'Tab-Dialog-0
        select case loword(wParam)
          case Dialog0ButtonOk
            if hiword(wParam) = BN_CLICKED OR hiword(wParam) = 1
              Beep(5500, 50)
            end if
          case Dialog0Edit
            if hiword(wParam) = EN_CHANGE THEN
            end if
            if hiword(wParam) = EN_SETFOCUS THEN
              Beep(2500, 50)
            end if
            if hiword(wParam) = EN_KILLFOCUS THEN
            end if
        end select
      case 1 'Tab-Dialog-1
        select case loword(wParam)
          case Dialog1ButtonOk
            if hiword(wParam) = BN_CLICKED OR hiword(wParam) = 1
              Beep(1500, 50)
            end if
        end select
      case 2 'Tab-Dialog-2
        select case loword(wParam)
          case Dialog2ButtonOk
            if hiword(wParam) = BN_CLICKED OR hiword(wParam) = 1
              Beep(1500, 50) : Beep(3500, 50)
            end if
        end select
    end select

    CASE WM_ERASEBKGND
      'display each TAB dialog with a gradient background color
      TabId = SendMessage(hTabControl, TCM_GETCURSEL, 0, 0)
      DrawGradient(hTabDialog, wParam, TabId)
      gosub MoveControl
      Return(1)

  case WM_CTLCOLOREDIT 'WM_CTLCOLORBTN | WM_CTLCOLORMSGBOX | WM_CTLCOLORSTATIC | WM_CTLCOLORSCROLLBAR | WM_CTLCOLORLISTBOX
    select GetDlgCtrlID(lParam)
      'edit control colorisation
      case Dialog0Edit, Dialog1Edit, Dialog2Edit
        SetTextColor(wParam, 0x00FFFF) 'yellow
        SetBkColor(wParam, 0xFF0000) 'blue
        return(hBrush) 'blue
      case else
        return(0)
    end select

  case WM_SIZE 'dialog size have changed
    if wParam <> SIZE_MINIMIZED
      ClientSizeX = loword(lParam)
      ClientSizeY = hiword(lParam)
      gosub MoveControl
    endif

  case WM_CLOSE
    'DestroyWindow(hTabDialog)

  case WM_DESTROY
    if hBrush then DeleteObject(hBrush)
    PostQuitMessage(0)

end select

return 0

MoveControl:
  select case TabId
    case 0
      MoveWindow(GetDlgItem(hTabDialog, Dialog0Frame), 10, 10, ClientSizeX - 20, ClientSizeY - 20, %TRUE)
      MoveWindow(GetDlgItem(hTabDialog, Dialog0ButtonOk), 30, 40, 110, 25, %TRUE)
      MoveWindow(GetDlgItem(hTabDialog, Dialog0Edit), 150, 45, 50, 16, %TRUE)
    case 1
      MoveWindow(GetDlgItem(hTabDialog, Dialog1Frame), 10, 10, ClientSizeX - 20, ClientSizeY - 20, %TRUE)
      MoveWindow(GetDlgItem(hTabDialog, Dialog1ButtonOk), 30, 40, 110, 25, %TRUE)
      MoveWindow(GetDlgItem(hTabDialog, Dialog1Edit), 150, 45, 50, 16, %TRUE)
    case 2
      MoveWindow(GetDlgItem(hTabDialog, Dialog2Frame), 10, 10, ClientSizeX - 20, ClientSizeY - 20, %TRUE)
      MoveWindow(GetDlgItem(hTabDialog, Dialog2ButtonOk), 30, 40, 110, 25, %TRUE)
      MoveWindow(GetDlgItem(hTabDialog, Dialog2Edit), 150, 45, 50, 16, %TRUE)
  end select
ret

end function
'______________________________________________________________________________

function MainDialogProc(sys hDlg, uint uMsg, sys wParam, lParam) as int callback
'creation of tab control and all needed tab-dialogs with their control
static SP_CLASSIMAGELIST_DATA ClassImageListData
static sys hIcon, hFont, hTabControl, ImageList
static short TabId, TabIdPrv
dim static sys hTabDlg[6]

select case uMsg

  case WM_INITDIALOG
    hIcon = ExtractIcon(GetModuleHandle(0), "Shell32.dll", 294) 'o icon
    SetClassLongPtr(hDlg, GCL_HICON, hIcon)
    hFont = GetStockObject(DEFAULT_GUI_FONT) 'DeleteObject not really needed becose StockObject
    SendMessage(GetDlgItem(hDlg, MainDialogButton01), WM_SETFONT, hFont, 0)
    SendMessage(GetDlgItem(hDlg, MainDialogButtonIDCANCEL), WM_SETFONT, hFont, 0)

    'create main tab control container
    hTabControl = CreateWindowEx(0, WC_TABCONTROL, "wcTabControl-01",
                                  WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE | TCS_MULTILINE | WS_TABSTOP,
                                  0, 0, 100, 100, 'remove TCS_MULTILINE for one row only tabs
                                  hDlg, null, GetModuleHandle(0), null)
    if hTabControl = null : mbox "Create Tab Control failed" : return null : endif
    SendMessage(hTabControl, WM_SETFONT, hFont, 0)

    'steal bmp image list from SetupDi api
    ClassImageListData.cbSize = SIZEOF(SP_CLASSIMAGELIST_DATA)
    SetupDiGetClassImageList(@ClassImageListData)
    SendMessage(hTabControl, TCM_SETIMAGELIST, 0, ClassImageListData.ImageList)

    'create seven (DeviceCount) TAB item
    TCITEM TabItem
    TabItem.mask = TCIF_TEXT | TCIF_IMAGE | TCIF_PARAM
    for TabId = 0 to < %DeviceCount
        TabItem.pszText    = device[TabId]
        TabItem.cchTextMax = len(device[TabId])
        TabItem.iImage    = deviceBmp[TabId] 'select an icon in the image list
        TabItem.lParam    = TabId 'if more or less than 4 bytes of application-defined data exist per tab,
                                  'an application must define a structure and use it instead of the TCITEM structure.
                                  'The first member of the application-defined
        if SendMessage(hTabControl, TCM_INSERTITEMA, TabId, @TabItem) = -1 then
          mbox "InsertItem Tab failed" : DestroyWindow(hTabControl)
          return null
        endif
    next

    'create seven (DeviceCount) hidden tab-dialog, only view one
    TabId = 0
    Dialog(10, 30, 120, 40, "TabDialog()", WS_CHILD | DS_SETFONT | DS_3DLOOK | WS_TABSTOP, 9, "Segoe UI", WS_EX_CONTROLPARENT)
    GroupBox("Frame - " + device(TabId) + " " + str(TabId), Dialog0Frame, 1, 1, 118, 38)
    PushButton("IDOK 0", Dialog0ButtonOk, 20, 25, 40, 12)
    Control("[edit 0]", Dialog0Edit, "Edit", ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, 76, 28, 40, 8, WS_EX_LEFT | WS_EX_STATICEDGE)
    hTabDlg(TabId) = CreateModelessDialog(hDlg, @TabsCommonDialogProc, TabId)

    TabId = 1
    Dialog(10, 30, 120, 40, "TabDialog()", WS_CHILD | DS_SETFONT | DS_3DLOOK | WS_TABSTOP, 9, "Segoe UI", WS_EX_CONTROLPARENT)
    GroupBox("Frame - " + device(TabId) + " " + str(TabId), Dialog1Frame, 1, 1, 118, 38)
    PushButton("IDOK 1", Dialog1ButtonOk, 20, 25, 40, 12)
    Control("[edit 1]", Dialog1Edit, "Edit", ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, 76, 28, 40, 8, WS_EX_LEFT | WS_EX_STATICEDGE)
    hTabDlg(TabId) = CreateModelessDialog(hDlg, @TabsCommonDialogProc, TabId)

    TabId = 2
    Dialog(10, 30, 120, 40, "TabDialog()", WS_CHILD | DS_SETFONT | DS_3DLOOK | WS_TABSTOP, 9, "Segoe UI", WS_EX_CONTROLPARENT)
    GroupBox("Frame - " + device(TabId) + " " + str(TabId), Dialog2Frame, 1, 1, 118, 38)
    PushButton("IDOK 2", Dialog2ButtonOk, 20, 25, 40, 12)
    Control("[edit 2]", Dialog2Edit, "Edit", ES_LEFT | ES_NOHIDESEL | WS_TABSTOP, 76, 28, 40, 8, WS_EX_LEFT | WS_EX_STATICEDGE)
    hTabDlg(TabId) = CreateModelessDialog(hDlg, @TabsCommonDialogProc, TabId)

    for TabId = 3 to < DeviceCount
      Dialog(10, 30, 120, 40, "TabDialog()", WS_CHILD | DS_SETFONT | DS_3DLOOK | WS_TABSTOP, 9, "Segoe UI", WS_EX_CONTROLPARENT)
      hTabDlg(TabId) = CreateModelessDialog(hDlg, @TabsCommonDialogProc, TabId)
    next
    TabId = 0 : ShowWindow(hTabDlg(TabId), SW_SHOW)
    return true

  case WM_COMMAND
    select case loword(wParam)
      case MainDialogButton01
        if hiword(wParam) = BN_CLICKED | hiword(wParam) = 1
          Beep(3500, 50)
        endif
      case MainDialogButtonIDCANCEL
        if hiword(wParam) = BN_CLICKED OR hiword(wParam) = 1
          EndDialog(hDlg, null)
        end if
    end select

  case WM_SIZE 'main dialog size have changed
    if wParam <> SIZE_MINIMIZED
      long ClientSizeX = loword(lParam) 'client area width  in pixels.
      long ClientSizeY = hiword(lParam) 'client area height in pixels.
      MoveWindow(hTabControl, 10, 10, ClientSizeX - 20, ClientSizeY - 50, %TRUE)
      gosub TabClientRectAjust 'TabId must be set right before gosub  'ADJUSTRECT
      MoveWindow(GetDlgItem(hDlg, MainDialogButton01), 20, ClientSizeY - 32, 90, 25, %TRUE)
      MoveWindow(GetDlgItem(hDlg, MainDialogButtonIDCANCEL), ClientSizeX - 110, ClientSizeY - 32, 90, 25, %TRUE)
    endif

  case WM_NOTIFY 'tab selection
    NMHDR pNotifyMessageHeader at lParam
    if pNotifyMessageHeader.hwndFrom = hTabControl then
      'tab notification: NM_CLICK, NM_DBLCLK, NM_RCLICK, NM_RDBLCLK, NM_RELEASEDCAPTURE,
      '                  TCN_FOCUSCHANGE, TCN_GETOBJECT, TCN_KEYDOWN, TCN_SELCHANGE, TCN_SELCHANGING.
      if pNotifyMessageHeader.code = TCN_SELCHANGING then 'sent before the selection changes
        TabIdPrv = SendMessage(hTabControl, TCM_GETCURSEL, 0, 0)
        ShowWindow(hTabDlg(TabIdPrv), SW_HIDE)
      elseif pNotifyMessageHeader.code = TCN_SELCHANGE then 'sent after the selection changes
        TabId = SendMessage(hTabControl, TCM_GETCURSEL, 0, 0)
        gosub TabClientRectAjust
        ShowWindow(hTabDlg(TabId), SW_SHOWNORMAL)
      endif
    endif

  case WM_CLOSE
    EndDialog(hDlg, null)

  case WM_DESTROY
    if ClassImageListData.ImageList then ImageList_Destroy(@ClassImageListData) 'in this case, don't use SetupDiDestroyClassImageList()
    if hIcon then DestroyIcon(hIcon)

end select

return 0

TabClientRectAjust:
  rect rcTabControl
  GetWindowRect(hTabControl, rcTabControl)
  MapWindowPoints(HWND_DESKTOP, hDlg, rcTabControl, 2)
  SendMessage(hTabControl, TCM_ADJUSTRECT, false, rcTabControl) 'window rectangle
  MoveWindow(hTabDlg(TabId), rcTabControl.left,    rcTabControl.top,
                              rcTabControl.right  - rcTabControl.left,
                              rcTabControl.bottom - rcTabControl.top, %TRUE)
ret

end function
'______________________________________________________________________________

sub winmain()
sys hDlg
MSG wMsg

init_common_controls()
Dialog(0, 0, 300, 200, "OxygenBASIC" + sizeof(sys) * 8 + " - TAB control - Sizeable", WS_CAPTION |
        WS_SIZEBOX | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU | DS_CENTER, 9, "Segoe UI", 0)

PushButton("Button", MainDialogButton01, 40, 85, 40, 12)
PushButton("Close", MainDialogButtonIDCANCEL, 130, 85, 40, 12)

CreateModalDialog(null, @MainDialogProc, 0)

end sub
'______________________________________________________________________________

winmain()
'______________________________________________________________________________
