JustPaste.it

; WinApp.pbi
; Window Application Framework
; Created: 2019-04-08

; Provides basic window application functionality

; Conventions:
; Variable - lowerCamelCase
; Procedure, structure - UpperCamelCase

; Bugs and workarounds:
; 1. In Windows when restoring a maximised window (on startup) it doesn't register properly if there is a
;    status bar. https://www.purebasic.fr/english/viewtopic.php?f=4&t=70094
;    *** A workaround has been applied
;    *** Fix has been applied to Purebasic and the workaround has been removed
;
; 2. In Linux (only some versions?) the #PB_Event_SizeWindow is triggered after the #PB_Event_MaximizeWindow.
;    This causes problems when relying on #PB_Event_MaximizeWindow to update the window contents.
;    It is possible to avoid this bug completely by not using the MaximiseAppWindow() procedure for anything.
;    *** No workaround necessary, do not rely on MaximiseAppWindow() to update window contents
;
; 3. Linux always returns normal for the window type when using GetWindowState(0). This can cause problems for 
;    procedures that update the window size only when the window is normal. Separate code needs to be used for
;    Linux and Windows.
;    *** A workaround has been applied
;    *** 20/05/2024 - problem is no longer there, removing workaround
;

EnableExplicit

;- 00 - Includes

;CompilerIf #PB_Compiler_OS = #PB_OS_Windows
;  XIncludeFile "Includes/ObjectTheme/ObjectTheme.pbi"
;  UseModule ObjectTheme
;CompilerEndIf

;XIncludeFile "Includes/MousePointer/MousePointer.pbi"

CompilerIf #PB_Compiler_OS = #PB_OS_Windows
  Macro MOUSE_CURSOR_ARROW : #IDC_ARROW : EndMacro
  Macro MOUSE_CURSOR_SIZEWE : #IDC_SIZEWE : EndMacro
  Macro MOUSE_CURSOR_SIZENS : #IDC_SIZENS : EndMacro
  Macro MOUSE_CURSOR_BUSY : #IDC_WAIT : EndMacro
CompilerElseIf #PB_Compiler_OS = #PB_OS_MacOS
  Macro MOUSE_CURSOR_ARROW : 0 : EndMacro
  Macro MOUSE_CURSOR_SIZEWE : 17 : EndMacro
  Macro MOUSE_CURSOR_SIZENS : 32 : EndMacro
  Macro MOUSE_CURSOR_BUSY : 7 : EndMacro
  ImportC ""
    SetThemeCursor(CursorType.L)
  EndImport
CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux
  Global *Cursor.GdkCursor
  Macro MOUSE_CURSOR_ARROW : #GDK_ARROW : EndMacro
  Macro MOUSE_CURSOR_SIZEWE : #GDK_SB_H_DOUBLE_ARROW : EndMacro
  Macro MOUSE_CURSOR_SIZENS : #GDK_SB_V_DOUBLE_ARROW : EndMacro
  Macro MOUSE_CURSOR_BUSY : #GDK_WATCH : EndMacro
  ImportC ""
    gtk_widget_get_window(*widget.GtkWidget)
  EndImport
CompilerEndIf  

Procedure SetCursor(hWnd, CursorId)
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    SetClassLongPtr_(hWnd, #GCL_HCURSOR, LoadCursor_(0, CursorId))
  CompilerElseIf #PB_Compiler_OS = #PB_OS_MacOS
    SetThemeCursor(CursorId)
  CompilerElseIf #PB_Compiler_OS = #PB_OS_Linux
      *Cursor= gdk_cursor_new_(CursorID)
      If *Cursor
          gdk_window_set_cursor_(gtk_widget_get_window(hWnd), *Cursor)
      EndIf
  CompilerEndIf
EndProcedure

CompilerIf #PB_Compiler_OS = #PB_OS_Linux
  #GTK_STYLE_PROVIDER_PRIORITY_FALLBACK = 1
  #GTK_STYLE_PROVIDER_PRIORITY_THEME = 200
  #GTK_STYLE_PROVIDER_PRIORITY_SETTINGS = 400
  #GTK_STYLE_PROVIDER_PRIORITY_APPLICATION = 600
  #GTK_STYLE_PROVIDER_PRIORITY_USER  = 800
  ImportC ""
    gtk_css_provider_load_from_data(*CSSProvider, CSSData.P-UTF8, Length, *Error.GError)
    gtk_css_provider_new()
    gtk_style_context_add_provider_for_screen(*Screen.GdkScreen, *StyleProvider, Priority)
  EndImport
  Procedure SetLinuxStyle()
    Protected CSSProvider, CSSDefault.s, Screen
    CSSDefault = "button, entry {min-height: 20px;} "
    CSSDefault + "tab {min-height: 20px;} "
    CSSDefault + "button, scale {padding-bottom: 2px; padding-left: 2px; padding-right: 2px; padding-top: 2px} "
    CSSDefault + "entry {padding-bottom: 3px; padding-left: 6px; padding-right: 6px; padding-top: 3px} "
    CSSDefault + "scrolledwindow undershoot.top, scrolledwindow undershoot.right, scrolledwindow undershoot.bottom, scrolledwindow undershoot.left { background-image: none; } "
    CSSProvider= gtk_css_provider_new()
    gtk_css_provider_load_from_data(CSSProvider, CSSDefault, -1, 0)
    Screen = gdk_display_get_default_screen_(gdk_display_get_default_())
    gtk_style_context_add_provider_for_screen(Screen, CSSProvider, #GTK_STYLE_PROVIDER_PRIORITY_APPLICATION)
    g_object_unref_(CSSProvider)
  EndProcedure
CompilerEndIf

;- 10 - Enumerations

Enumeration GadgetType
  #GadgetTypeText
  #GadgetTypeString
  #GadgetTypeButton
  #GadgetTypeButtonImage
  #GadgetTypeCanvas
  #GadgetTypeScrollArea
EndEnumeration

Enumeration DWMWINDOWATTRIBUTE ; used for darkmode
  #DWMWA_USE_IMMERSIVE_DARK_MODE = 20
EndEnumeration

Enumeration AlignHorizontal
  #AlignHorizontalLeft
  #AlignHorizontalCentre
  #AlignHorizontalRight
EndEnumeration

Enumeration AlignVertical
  #AlignVerticalTop
  #AlignVerticalCentre
  #AlignVerticalBottom
EndEnumeration

Enumeration ColourPalette
  #ColourPalette0
  #ColourPalette1
  #ColourPalette2
  #ColourPalette3
  #ColourPalette4
  #ColourPalette5
  #ColourPalette6
EndEnumeration

Enumeration GadgetEdge
  #GadgetEdgeNone
  #GadgetEdgeTop
  #GadgetEdgeRight
  #GadgetEdgeBottom
  #GadgetEdgeLeft
EndEnumeration

Enumeration DimensionType
  #DimensionNormal
  #DimensionPercentage
  #DimensionCalculated
EndEnumeration

;- 20 - Constants

#MAX_WINDOWS = 4
#MAX_GADGETS = 50
#MAX_MENU_TITLES = 20
#MAX_MENU_ITEMS = 50
#MAX_EVENTS = 64
#MAX_COLOUR_SCHEMES = 6

#MS_TO_SAVE_MOVED_WINDOW = 1500
#ENABLE_MENU = 0
#DISABLE_MENU = 1

#DARK_MODE_BACKGROUND = $383838
#DARK_MODE_COLOUR1 = $191919
#DARK_MODE_COLOUR2 = $202020
#DARK_MODE_FOREGROUND = $FFFFFF

#Bug_1_Workaround = #True
#Bug_3_Workaround = #True

#SCROLLBAR_WIDTH_WIN = 18
#SCROLLBAR_WIDTH_MAC = 18
#SCROLLBAR_WIDTH_LIN = 14

;- 30 - Structures

Structure DesktopStructure
  ; structure to store parametres for each available display
  name.s
  width.i
  height.i
  depth.i
  frequency.i
  resolutionX.d
  resolutionY.d
EndStructure

Structure WindowStructure
  allowResize.i    ; to allow the window to be resized or maximised
  backColour.i      ; background colour of window
  centerNew.i             ; center the window if Position_Set is not set
  centerParent.i          ; always centers window to the parent window
  disableParent.i         ; when set causes the parent window to be disabled
  height.i                ; Height
  invisible.i             ; set to make the window invisible
  maximised.i             ; set when window is maximised   
  maximiseEnable.i        ; enables the maximise button
  menuEnable.i
  menuInit.i ; set to 1 when the menu is initialised
  menuHeight.i ; height of the menu in pixels, Windows = 20
  minWidth.i              ; minimum size of window
  minHeight.i
  minimiseEnable.i ; enables the minimise button
  moved.i          ; set when the window is moved, reset manually
  num.i            ; index of the window, starts at 0
  parent.i         ; -1 means no parent window, 0 is the first window etc
  positionSet.i    ; set to 1 once the user sets it's position
  ratio.f          ; ratio of W and H
  ratioEnable.i    ; set to enforce keeping the ratio when resizing the window
  restoreFix.i     ; used as a workaround for bug #1
  saveState.i      ; set to save the window state  
  startMaximise.i  ; not used anymore, solution for Bug #2 is to not use MaximiseAppWindow for anything
  systemMenu.i     ; shows the system menu if set
  title.s 
  titleHeight.i
  toolbarOverlay.i ; holds the ID of the overlay used when chaning the toolbar colour
  width.i               ; width
  x.i                   ; X position of window
  y.i                   ; Y position of window
EndStructure

Structure MenuTitleStructure
  text.s
  window.i
EndStructure

Structure MenuItemStructure
  bar.i ; set to 1 for a bar
  checked.i
  enabled.i
  imageID.i
  menuID.i
  text.s
EndStructure

Structure GadgetStructure
  active.i ; when set tp 1 this will be active when showing the window
  backColour.i
  disabled.i ; set this to 1 to disable
  height.i
  flags.i ; gadget ID used to calculate the height of the gadget
  group.i ; groups allow multiple gadgets to be aligned in one direction
  heightCalc.i 
  heightType.i ; 0 = pixels, 1 = percent of window
  id.i ; gadget id used for debugging (make sure a gadget is initialised)
  imageID.i
  maxHeight.i
  maxWidth.i
  minHeight.i
  minWidth.i
  minWidthType.i ; 0 = pixels, 1 = percent of window
  minHeightType.i ; 0 = pixels, 1 = percent of window
  maxWidthType.i ; 0 = pixels, 1 = percent of window
  maxHeightType.i; 0 = pixels, 1 = percent of window
  parent.i ; gadgets can belong to a parent (container, panel or scroll area)
  resizeBottom.i
  resizeLeft.i ; the left side is resizable
  resizeRight.i; the right side is resizable
  resizeTop.i
  resizeHandleSize.i ; the size in pixels of the handle to grab with the mouse to resize the gadget
  saveConfigWidth.i ; save the width to config
  saveConfigHeight.i ; save the height to config
  scrollAreaHeight.i  ; scroll area when used with a scroll area
  scrollAreaWidth.i   ; scroll area when used with a scroll area
  scrollStep.i
  text.s  
  toolTip.s
  type.i
  width.i
  widthCalc.i ; gadget ID used to calculate the width of the gadget
  widthType.i ; 0 = pixels, 1 = percent of window
  window.i
  windowHeight.i ; the height is equal to the window height and will automatically be resized
  windowWidth.i ; the width is equal to the window width and will automatically be resized
  x.i
  xAlign.i ; 0=left, 1=right, 2=center
  xAlt.i
  xCalc.i ; calculated x from another gadget x+width
  xType.i ; ; 0 = normal, 1 = calculated from a gadget x+width
  y.i
  yAlign.i ; 0=left, 1=right, 2=center
  yAlt.i
  yCalc.i ; calculated y from another gadget y+height
  yType.i ; ; 0 = normal, 1 = calculated from a gadget y+height
EndStructure

Structure GadgetActionStructure
  hoverID.i ; the ID of the gadget being hovered over (-1 means none)
  hoverEdge.i ; the edge of the gadget being hovered over (see GadgetEdge enumeration)
  dragID.i    ; the ID of the gadget that is being dragged (-1 means none)
  dragEdge.i  ; the edge of the gadget that is being dragged
  MX.i ; mouse position
  MY.i
EndStructure
  
Structure WindowDataStructure
  ; Structure of pointers used for event binding
  *desktops.DesktopStructure
  *windows.WindowStructure ; this points to the first array record, and can move through the array as needed
  *gadgets.GadgetStructure
  *settings.SettingsStructure
  *events.EventsStructure
  *numWindows.Integer
  *numGadgets.Integer
  *numEvents.Integer
  *quit.Integer
EndStructure

Structure ColoursStructure ; holds the colours used by the app
  colour0.i
  colour1.i
  colour2.i
  colour3.i
  colour4.i
  colour5.i
  colour6.i
EndStructure

Structure SettingsStructure
  colourScheme.i ; selected colour scheme 0=default
  colours.ColoursStructure[#MAX_COLOUR_SCHEMES]
  debugOutline.i ; turns on outline of gadgets for debugging
EndStructure

Structure EventStructure
  event.i
  eventGadget.i
  eventType.i
EndStructure

;- 40 - Prototypes

PrototypeC.i DwmSetWindowAttribute(hwnd.i, dwAttribute.l, *pvAttribute, cbAttribute.l) ; used for dark mode

;- 50 - Globals

;- 60 - Variables

Define activeDesktop.i = 0 ; currently active desktop
Define activeWindow.i = 0 ; currently active window
Define addGadget.i = 0    ; next gadget to add
Define colours.ColoursStructure
Define darkMode.i                    ; turns on darkmode (Windows only)
Dim desktops.DesktopStructure(0)              ; Array to hold monitor information
Dim events.EventStructure(#MAX_EVENTS) ; array to hold events
Define numEvents.i ; number of events currently stored
Dim gadgets.GadgetStructure(#MAX_GADGETS) ; array to hold gadgets, always store gadgets for one window sequentially in one block
Define gadgetActions.GadgetActionStructure ; structure to hold variables for acting on gadgets
Define lastWindowMove.q                   ; last time a window was moved, 0 if not moved
Dim menuItems.MenuItemStructure(#MAX_MENU_ITEMS) ; array to hold menu items, always store menus for one window sequentially in one block
Dim menuTitles.MenuTitleStructure(#MAX_MENU_TITLES) ; array to hold menu titles
Dim menuWindowIndex.i(#MAX_MENU_ITEMS)              ; array to hold menu start id, useful for optimising
Define numMonitors.i = 0              ; number of monitors detected
Define quit.i = 0
Define saveSettings.i                     ; used to trigger a save of settings
Define settings.SettingsStructure
Define settingsFilename.s = "settings.cfg"
Define settingsLoaded.i = 0 ; set to 1 if settings have been loaded
Define toolbarColour.i
Define windowData.WindowDataStructure ; used to pass pointers to bound procedures
Dim windows.WindowStructure(#MAX_WINDOWS) ; array to hold windows

;- 70 - Procedure declarations

Declare ResizeGadget2(gadget.i, window.i, *gadgets.GadgetStructure, numGadgets.i, *desktop.DesktopStructure, *window.WindowStructure, *settings.SettingsStructure)

;- 80 - Procedures

Procedure.i x2(window.i, gadget.i, *gadgets.GadgetStructure, numGadgets.i, *desktop.DesktopStructure)
  ; *gadgets is a pointer to an array of gadgets
  Protected wWidth.i
  Protected x.i = 0
  Protected group.i
  Protected totalWidth.i ; width of the individual gadget or group of gadgets
  Protected firstGroupGadget.i ; the first gadget using that group
  Protected c.i
  Protected *currentGadget.GadgetStructure
  Protected *calcGadget.GadgetStructure
  Protected *nextGadget.GadgetStructure
  *currentGadget = *gadgets + (SizeOf(GadgetStructure) * gadget)
  If *currentGadget\window <> window
    ; gadget is not on the window
    ProcedureReturn -1
  EndIf
  wWidth = WindowWidth(window, #PB_Window_InnerCoordinate)
  group = *currentGadget\group
  For c = 0 To numGadgets-1
    *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
    If *nextGadget\group = group
      Break
    EndIf
  Next c  
  firstGroupGadget = c
  If group >= 0 ; only calculate group if it is an actual group
    For c = 0 To numGadgets-1
      *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      If *nextGadget\group = group
        totalWidth = totalWidth + *nextGadget\width
      EndIf
    Next c
  Else
    totalWidth = *currentGadget\width
  EndIf
  ; left aligned
  x = *currentGadget\x
  ; check if it's calculated from another gadget
  If *currentGadget\xType = 1
    ; calculated from another gadget
    *calcGadget = *gadgets + (SizeOf(GadgetStructure) * *currentGadget\xCalc)
    x = *calcGadget\x + *calcGadget\width
  EndIf
  ; centre aligned
  If *currentGadget\xAlign = #AlignHorizontalCentre
    x = (wWidth/2) - (totalWidth/2) ; set x to the left most position for the group
    If group <> -1 ; only process the group if the gadget is in one
      c = firstGroupGadget
      *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      While *nextGadget\group = group And c < numGadgets
        If c < gadget
          x = x + *nextGadget\width
        EndIf
        c = c + 1
        *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      Wend 
    EndIf
  EndIf
  ; right aligned
  If *currentGadget\xAlign = #AlignHorizontalRight
    x = wWidth - *currentGadget\width
    If group <> -1 ; only process the group if the gadget is in one
      c = firstGroupGadget
      *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      While *nextGadget\group <> group
        x = x + *nextGadget\width
        c = c + 1
        *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      Wend 
    EndIf
  EndIf 
  ProcedureReturn x
EndProcedure

Procedure.i y2(window.i, gadget.i, *gadgets.GadgetStructure, numGadgets.i, *desktop.DesktopStructure)
  ; *gadgets is a pointer to an array of gadgets
  Protected wHeight.i
  Protected y.i = 0
  Protected group.i
  Protected totalHeight.i ; width of the individual gadget or group of gadgets
  Protected firstGroupGadget.i ; the first gadget using that group
  Protected c.i
  Protected *currentGadget.GadgetStructure
  Protected *calcGadget.GadgetStructure
  Protected *nextGadget.GadgetStructure
  *currentGadget = *gadgets + (SizeOf(GadgetStructure) * gadget)
  If *currentGadget\window <> window
    ; gadget is not on the window
    ProcedureReturn -1
  EndIf
  wHeight = WindowWidth(window, #PB_Window_InnerCoordinate)
  group = *currentGadget\group
  For c = 0 To numGadgets-1
    *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
    If *nextGadget\group = group
      Break
    EndIf
  Next c  
  firstGroupGadget = c
  If group >= 0 ; only calculate group if it is an actual group
    For c = 0 To numGadgets-1
      *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      If *nextGadget\group = group
        totalHeight = totalHeight + *nextGadget\height
      EndIf
    Next c
  Else
    totalHeight = *currentGadget\height
  EndIf
  ; top aligned
  y = *currentGadget\y
  ; check if it's calculated from another gadget
  If *currentGadget\yType = 1
    ; calculated from another gadget
    *calcGadget = *gadgets + (SizeOf(GadgetStructure) * *currentGadget\yCalc)
    y = *calcGadget\y + *calcGadget\height
  EndIf
  ; centre aligned
  If *currentGadget\yAlign = #AlignVerticalCentre
    y = (wHeight/2) - (totalHeight/2) ; set x to the left most position for the group
    If group <> -1 ; only process the group if the gadget is in one
      c = firstGroupGadget
      *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      While *nextGadget\group = group And c < numGadgets
        If c < gadget
          y = y + *nextGadget\height
        EndIf
        c = c + 1
        *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      Wend 
    EndIf
  EndIf
  ; bottom aligned
  If *currentGadget\yAlign = #AlignVerticalBottom
    y = wHeight - *currentGadget\Height
    If group <> -1 ; only process the group if the gadget is in one
      c = firstGroupGadget
      *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      While *nextGadget\group <> group
        y = y + *nextGadget\height
        c = c + 1
        *nextGadget = *gadgets + (SizeOf(GadgetStructure) * c)
      Wend 
    EndIf
  EndIf 
  ProcedureReturn y
EndProcedure

Procedure.i Width2(window.i, gadget.i, *gadgets.GadgetStructure, numGadgets.i, *desktop.DesktopStructure)
  Protected wWidth = WindowWidth(window, #PB_Window_InnerCoordinate)
  Protected scaleAdjust.i = 0
  Protected x1.i, x2.i
  Protected *currentGadget.GadgetStructure = *gadgets + (SizeOf(GadgetStructure) * gadget)
  If wWidth % 2 = 1 ;And *currentGadget\xAlign = #AlignHorizontalRight
    scaleAdjust = Int(Round(*desktop\resolutionX, #PB_Round_Down)) ; this is used because sometimes it doesn't go all the way to the edge of the window due to the scale
  EndIf
  If *currentGadget\widthType = #DimensionNormal
    ProcedureReturn *currentGadget\width + scaleAdjust
  ElseIf *currentGadget\widthType = #DimensionPercentage
    ProcedureReturn (wWidth * *currentGadget\width / 100) - *currentGadget\x + scaleAdjust
  ElseIf *currentGadget\widthType = #DimensionCalculated
    x1 = x2(window, gadget, *gadgets, numGadgets, *desktop)
    x2 = x2(window, *currentGadget\widthCalc, *gadgets, numGadgets, *desktop)
    ProcedureReturn x2-x1 + scaleAdjust
  EndIf
EndProcedure

Procedure.i Height2(window.i, gadget.i, *gadgets.GadgetStructure, numGadgets.i, *desktop.DesktopStructure, *window.WindowStructure)
  Protected wHeight = WindowHeight(window, #PB_Window_InnerCoordinate)
  Protected menuHeight.i = 0
  Protected scaleAdjust.i = 0
  Protected y1.i, y2.i
  Protected *currentGadget.GadgetStructure = *gadgets + (SizeOf(GadgetStructure) * gadget)
  If *window\menuHeight > 0
    menuHeight = *window\menuHeight
  EndIf  
  If wHeight % 2 = 1 And *currentGadget\yAlign = #AlignVerticalBottom
    scaleAdjust = Int(Round(*desktop\resolutionY, #PB_Round_Down)) ; this is used because sometimes it doesn't go all the way to the edge of the window due to the scale
  EndIf
  If *currentGadget\heightType = #DimensionNormal
    ProcedureReturn *currentGadget\height + scaleAdjust
  ElseIf *currentGadget\heightType = #DimensionPercentage
    ProcedureReturn (wHeight * *currentGadget\Height / 100) - *currentGadget\y + menuHeight + scaleAdjust
  ElseIf *currentGadget\heightType = #DimensionCalculated
    y1 = y2(window, gadget, *gadgets, numGadgets, *desktop)
    y2 = y2(window, *currentGadget\heightCalc, *gadgets, numGadgets, *desktop)
    ProcedureReturn y2-y1 + scaleAdjust
  EndIf
EndProcedure

Procedure.i MaxWidth(window.i, gadget.i, *gadgets.GadgetStructure)
  Protected wWidth = WindowWidth(window, #PB_Window_InnerCoordinate)
  Protected *currentGadget.GadgetStructure = *gadgets + (SizeOf(GadgetStructure) * gadget)
  If *currentGadget\maxWidthType = #DimensionPercentage
    ProcedureReturn wWidth * *currentGadget\maxWidth / 100
  Else
    ProcedureReturn *currentGadget\maxWidth
  EndIf
EndProcedure

Procedure.i MaxHeight(window.i, gadget.i, *gadgets.GadgetStructure)
  Protected wHeight = WindowHeight(window, #PB_Window_InnerCoordinate)
  Protected *currentGadget.GadgetStructure = *gadgets + (SizeOf(GadgetStructure) * gadget)
  If *currentGadget\maxWidthType = #DimensionPercentage
    ProcedureReturn wHeight * *currentGadget\maxWidth / 100
  Else
    ProcedureReturn *currentGadget\maxHeight
  EndIf
EndProcedure

Procedure.i DragWidth(MX.i, window.i, Array gadgets.GadgetStructure(1), gadget.i, dragEdge.i)
  Protected wWidth.i
  Protected newWidth.i
  Protected minWidth.i
  Protected maxWidth.i
  wWidth = WindowWidth(window, #PB_Window_InnerCoordinate)
  minWidth = gadgets(gadget)\minWidth
  maxWidth = gadgets(gadget)\maxWidth
  If gadgets(gadget)\maxWidthType = 1
    ; convert to percentage
    maxWidth = wWidth * gadgets(gadget)\maxWidth / 100
  EndIf
  If gadgets(gadget)\minWidthType = 1
    ; convert to percentage
    minWidth = wWidth * gadgets(gadget)\minWidth / 100
  EndIf    
  Select dragEdge
    Case #GadgetEdgeLeft
      newWidth = wWidth - MX
    Case #GadgetEdgeRight
      newWidth = MX
  EndSelect
  If newWidth > maxWidth
    newWidth = maxWidth
  EndIf
  If newWidth < minWidth
    newWidth = minWidth
  EndIf      
  ProcedureReturn newWidth
EndProcedure

Procedure.i DragHeight(MY.i, window.i, Array gadgets.GadgetStructure(1), gadget.i, dragEdge.i)
  Protected wHeight.i
  Protected newHeight.i
  Protected minHeight.i
  Protected maxHeight.i
  wHeight = WindowHeight(window, #PB_Window_InnerCoordinate)
  minHeight = gadgets(gadget)\minHeight
  maxHeight = gadgets(gadget)\maxHeight
  If gadgets(gadget)\maxHeightType = 1
    ; convert to percentage
    maxHeight = wHeight * gadgets(gadget)\maxHeight / 100
  EndIf
  If gadgets(gadget)\minHeightType = 1
    ; convert to percentage
    minHeight = wHeight * gadgets(gadget)\minHeight / 100
  EndIf    
  Select dragEdge
    Case #GadgetEdgeTop
      newHeight = wHeight - MY
    Case #GadgetEdgeBottom
      newHeight = MY
  EndSelect
  ;Debug "minHeight: " + minHeight + " maxHeight: " + maxHeight
  If newHeight > maxHeight
    newHeight = maxHeight
  EndIf
  If newHeight < minHeight
    newHeight = minHeight
  EndIf      
  ProcedureReturn newHeight
EndProcedure

Procedure AutoResizeMax(window.i, *gadgets.GadgetStructure, numGadgets.i, *windows.WindowStructure, *desktops.DesktopStructure, *settings.SettingsStructure)
  Protected c.i
  Protected maxWidth.i
  Protected maxHeight.i
  Protected *currentGadget.GadgetStructure
  Protected *currentWindow.WindowStructure = *windows + SizeOf(WindowStructure) * window
  ; Resize gadgets that are too big for the window
  ; For example this happens with gadgets where the max size is a percentage of the window
  For c = 0 To numGadgets-1
    *currentGadget = *gadgets + SizeOf(GadgetStructure) * c
    If *currentGadget\maxWidthType = #DimensionPercentage And *currentGadget\window = window
      maxWidth = MaxWidth(window, c, *gadgets)
      If *currentGadget\width > maxWidth
        *currentGadget\width = maxWidth
        ResizeGadget2(c, window, *gadgets, numGadgets, *desktops, *currentWindow, *settings)
      EndIf
      maxHeight = MaxHeight(window, c, *gadgets)
      If *currentGadget\height > maxHeight
        *currentGadget\height = maxHeight
        ResizeGadget2(c, window, *gadgets, numGadgets, *desktops, *currentWindow, *settings)
      EndIf
    EndIf
  Next c    
EndProcedure
  
Procedure.i GetColour(colour.i, *settings.SettingsStructure)
  Protected returnColour.i
  Select colour
    Case 0
      returnColour = *settings\colours[*settings\colourScheme]\colour0
    Case 1
      returnColour = *settings\colours[*settings\colourScheme]\colour1
    Case 2
      returnColour = *settings\colours[*settings\colourScheme]\colour2
    Case 3
      returnColour = *settings\colours[*settings\colourScheme]\colour3
    Case 4
      returnColour = *settings\colours[*settings\colourScheme]\colour4
    Case 5
      returnColour = *settings\colours[*settings\colourScheme]\colour5
    Case 6
      returnColour = *settings\colours[*settings\colourScheme]\colour6
    Default
      returnColour = 0
  EndSelect
  ProcedureReturn returnColour
EndProcedure

Procedure CallbackQuit()
  Protected window.i = EventWindow()
  Protected *windowData.WindowDataStructure
  *windowData = GetWindowData(window)
  *WindowData\quit = 1
EndProcedure

Procedure WindowResize()
  ; Procedure to run when the window size is being changed, IE while dragging the corner
  ; While the mouse button is down no other events are triggered
  Debug "WindowResize: window resized"
  Protected c.i
  Protected *windowData.WindowDataStructure
  Protected *desktops.DesktopStructure
  Protected *desktop.DesktopStructure
  Protected *windows.WindowStructure
  Protected *gadgets.GadgetStructure
  Protected *gadget.GadgetStructure
  Protected *settings.SettingsStructure
  ;Protected *windowsStart ; variable to store original address of *windowData\windows
  Protected window.i = EventWindow()
  Protected wWidth.i
  Protected wHeight.i
  Protected newWidth.i
  Protected newHeight.i  
  Protected maxWidth.i
  Protected numGadgets.i
  *windowData = GetWindowData(window)
  ;*windowsStart = *windowData\windows
  *desktops = *windowData\desktops
  *windows = *windowData\windows
  *gadgets = *windowData\gadgets
  *settings = *windowData\settings
  *windows = *windows + (SizeOf(WindowStructure) * window)
  numGadgets = *windowData\numGadgets
  If GetWindowState(window) = #PB_Window_Normal
    ; only change the width and height while the window is in normal stage otherwise it will change it when maximised
    *windows\width = WindowWidth(window)
    *windows\height = WindowHeight(window)
  EndIf
  wWidth = WindowWidth(window)
  wHeight = WindowHeight(window)
  ; resize gadgets
  AutoResizeMax(window, *gadgets, numGadgets, *windows, *desktops, *settings)
  For c = 0 To numGadgets-1
    *gadget = *gadgets + (SizeOf(GadgetStructure) * c)
    If *gadget\window = -1
      Break ; there are no more initialised gadgets
    EndIf
    If *gadget\window = window
      ResizeGadget2(c, window, *gadgets, numGadgets, *desktops, *windows, *settings)
    EndIf
  Next c
  
  ; Set Windows pointer back to beginning
  ;*windowData\windows = *windowsStart
EndProcedure

Procedure Initialise(Array windows.WindowStructure(1), numWindows.i, Array gadgets.GadgetStructure(1), numGadgets.i, Array events.EventStructure(1), *windowData.WindowDataStructure, darkMode.i, *colours.ColoursStructure, *settings.SettingsStructure)
  ;CompilerIf #PB_Compiler_OS = #PB_OS_Windows
  ;  If darkMode
  ;    SetObjectTheme(#ObjectTheme_Dark)
  ;  EndIf
  ;CompilerEndIf
  CompilerIf #PB_Compiler_OS = #PB_OS_Linux
    SetLinuxStyle()
  CompilerEndIf  
  ; Set the windowData so it points to various variables
  *windowData\windows = @windows()
  *windowData\gadgets = @gadgets()
  *windowData\events = @events()
  *windowData\settings = *settings
  *windowData\numWindows = numWindows
  *windowData\numGadgets = numGadgets
  *windowData\quit = 0
  *settings\colourScheme = 0
  *settings\colours[0]\colour0 = $FFFFFF
  *settings\colours[0]\colour1 = $F0F0F0
  *settings\colours[0]\colour2 = $E0E0E0
  *settings\colours[0]\colour3 = $D0D0D0
  *settings\colours[0]\colour4 = $C0C0C0
  *settings\colours[0]\colour5 = $B0B0B0
  *settings\colours[0]\colour6 = $A0A0A0
  *settings\debugOutline = 0
EndProcedure

Procedure InitResizeBinding()
  BindEvent(#PB_Event_SizeWindow, @WindowResize())
EndProcedure

Procedure InitDesktop(Array desktops.DesktopStructure(1), *numMonitors.Integer, *windowData.WindowDataStructure)
  ; used to check which monitors are connected
  Protected c.i
  *numMonitors\i = ExamineDesktops()
  Debug "InitDesktop: " + *numMonitors\i + " monitor(s) detected"
  If *numMonitors\i > 0
    ReDim desktops(*numMonitors\i)
    For c = 0 To *numMonitors\i - 1
      desktops(c)\name = DesktopName(c)
      desktops(c)\width = DesktopWidth(c)
      Debug "InitDesktop: Desktop(" + c + ")\Width: " + desktops(c)\Width
      desktops(c)\height = DesktopHeight(c)
      desktops(c)\depth = DesktopDepth(c)
      desktops(c)\frequency = DesktopFrequency(c)
      desktops(c)\resolutionX = DesktopResolutionX()
      desktops(c)\resolutionY = DesktopResolutionY()
    Next
  Else
    ; ExamineDesktops() returned 0
    Debug "InitDesktop: could not initialise desktop"
    ProcedureReturn 0
  EndIf
  *windowData\desktops = @desktops()
  ProcedureReturn 1
EndProcedure

Procedure.i InitWindows(Array windows.WindowStructure(1), numWindows.i, Array gadgets.GadgetStructure(1), numGadgets.i, *windowData.WindowDataStructure)
  Protected result.i, c.i
  result = 0
  If numWindows > 0
    ;ReDim windows(numWindows)
    For c = 0 To numWindows - 1
      ; set defaults
      windows(c)\title = "Window #" + c
      windows(c)\x = 30
      windows(c)\y = 30
      windows(c)\width = 640
      windows(c)\height = 480
      windows(c)\systemMenu = 1
      windows(c)\minimiseEnable = 1
      windows(c)\maximiseEnable = 1
      windows(c)\minWidth = 0
      windows(c)\minHeight = 0
      windows(c)\allowResize = 1
      windows(c)\saveState = 1
      windows(c)\centerNew = 1
      windows(c)\parent = -1
      windows(c)\disableParent = 0
      windows(c)\centerParent = 0
      windows(c)\invisible = 0
      ; other variables
      windows(c)\positionSet = 0
      windows(c)\restoreFix = 0 ; See bug #1
      windows(c)\maximised = 0
      windows(c)\ratio = windows(0)\height / windows(0)\width
      windows(c)\moved = 0
      windows(c)\menuInit = 0
      windows(c)\menuEnable = 0
      windows(c)\backColour = -1 ; -1 means no back colour specified
    Next
    result = 1
    Debug "Initialised " + c + " windows"
  Else
    Debug "InitWindows: numWindows must be set to 1 or greater"
  EndIf
  ProcedureReturn result
EndProcedure  

Procedure InitGadgets(Array gadgets.GadgetStructure(1), *gadgetActions.GadgetActionStructure)
  Protected c.i
  For c = 0 To #MAX_GADGETS-1
    gadgets(c)\window = -1 ; set windows to -1 so the end of the gadget set can be found
    gadgets(c)\group = -1  ; -1 means no group
    gadgets(c)\parent = -1 ; -1 means no group
  Next c
  *gadgetActions\dragID = -1
  *gadgetActions\dragEdge = 0
  *gadgetActions\hoverID = -1
  *gadgetActions\hoverEdge = 0  
EndProcedure

Procedure InitMenus(Array menuTitles.MenuTitleStructure(1), Array menuItems.MenuItemStructure(1))
  Protected c.i
  For c = 0 To #MAX_MENU_TITLES-1
    menuTitles(c)\window = -1 ; set menus to -1 so the end of the menu set can be found
  Next c
  For c = 0 To #MAX_MENU_ITEMS-1
    menuItems(c)\menuID = -1 ; set menus to -1 so the end of the menu item set can be found
  Next c
EndProcedure

Procedure.i CheckGadgets(Array gadgets.GadgetStructure(1), numGadgets.i)
  Protected c.i
  Protected result.i = 1
  For c = 0 To numGadgets-1
    If gadgets(c)\window < 0
      Debug "CheckGadgets: ERROR - gadget " + c + " is not set to a window"
      result = 0
    EndIf    
    If gadgets(c)\resizeLeft Or gadgets(c)\resizeRight Or gadgets(c)\resizeTop Or gadgets(c)\resizeBottom And gadgets(c)\resizeHandleSize <=0
      Debug "CheckGadgets: ERROR - gadget " + c + " is resizeable but has no resize handle"
      result = 0
    EndIf
    If (gadgets(c)\minHeightType = 1 And gadgets(c)\minHeight > 100) Or (gadgets(c)\maxHeightType = 1 And gadgets(c)\maxHeight > 100) Or 
       (gadgets(c)\minWidthType = 1 And gadgets(c)\minWidth > 100) Or (gadgets(c)\maxWidthType = 1 And gadgets(c)\maxWidth > 100)
      Debug "CheckGadgets: ERROR - gadget " + c + " min/max out of range (0-100%)"
      result = 0
    EndIf
    If gadgets(c)\window = -1
      Debug "CheckGadgets: ERROR - gadget " + c + " window is set To -1. Please check configuration"
      result = 0
    EndIf
  Next c
  ProcedureReturn result
EndProcedure

Procedure MenuShow(window.i, Array windows.WindowStructure(1), Array menuTitles.MenuTitleStructure(1), Array menuItems.MenuItemStructure(1), *settings.SettingsStructure)
  ; Creates menu
  Protected c.i = 0
  Protected d.i = 0
  Protected menuActive.i = 0 ; is set when a menu is active on the current window
  Debug "MenuShow: showing menu for window: " + window
  ; find the first title for the window
  While menuTitles(c)\window <> window And c < #MAX_MENU_TITLES
    Debug "MenuTitle rejected: " + menuTitles(c)\window
    c = c + 1
  Wend
  If c < #MAX_MENU_TITLES
    ;Debug "MenuShow: creating menu: " + c
    CreateMenu(window, WindowID(window))
    windows(window)\menuInit = 1
    While menuTitles(c)\window = window
      menuActive = 1
      ;Debug "MenuShow: creating menu title: " + c
      MenuTitle(menuTitles(c)\text)
      While menuItems(d)\menuID = c
        ;Debug "MenuShow: creating menu item: " + d
        If menuItems(d)\bar
          MenuBar()
        Else
          MenuItem(d, menuItems(d)\text)
          If Not menuItems(d)\enabled
            DisableMenuItem(window, d, 1)
          EndIf
        EndIf
        d = d + 1
      Wend
      c = c + 1
    Wend
  Else
    Debug "Window has no menu"
  EndIf
  ; set up applicatiuon menu for macOS
  CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
    MenuItem(#PB_Menu_About, "About")
    MenuItem(#PB_Menu_Preferences, "Preferences")
    MenuItem(#PB_Menu_Quit, "Quit")
    BindMenuEvent(window, #PB_Menu_Quit, @CallbackQuit())
  CompilerEndIf 
  If menuActive
    CompilerSelect #PB_Compiler_OS
      CompilerCase #PB_OS_Linux
        windows(window)\menuHeight = 28
      CompilerCase #PB_OS_Windows
        windows(window)\menuHeight = 20
      CompilerCase #PB_OS_MacOS
        windows(window)\menuHeight = 0
    CompilerEndSelect
  EndIf
  Debug "MenuShow: complete"
EndProcedure

Procedure MenuDisable(Array menusTitles.MenuTitleStructure(1), Array menuItems.MenuItemStructure(1), window.i, state.i)
  ; Disables all menu items in a menu
  ; The menu number coresponds to the window number
  Protected c.i
  Protected d.i
  For c = 0 To #MAX_MENU_TITLES-1
    If menusTitles(c)\window = window
      Break
    EndIf
  Next c
  If state
    Debug "MenuDisable: disabling menu: " + c
  Else
    Debug "MenuDisable: enabling menu: " + c
  EndIf
  While menusTitles(c)\window = window
    While menuItems(d)\menuID = c
      ; enable them only if the flag is set
      If state = 1
        ; disable all
        DisableMenuItem(window, d, 1)
      Else
        If menuItems(d)\enabled
          DisableMenuItem(window, d, 0)
        EndIf      
      EndIf
      d = d + 1
    Wend
    c = c + 1
  Wend  
EndProcedure

Procedure.i WindowShow(window.i, *desktop.DesktopStructure, *window.WindowStructure, *activeWindow.Integer, *windowData.WindowDataStructure, darkMode.i, *settings.SettingsStructure)
  Protected flags.i, result.i, c.i
  Protected DwmSetWindowAttribute.DwmSetWindowAttribute  ; used for dark mode
  If Not *windowData
    Debug "WindowShow: *windowData not set"
    ProcedureReturn 0
  EndIf 
   If *window\systemMenu
    flags = flags | #PB_Window_SystemMenu
  EndIf
  If *window\minimiseEnable
    flags = flags | #PB_Window_MinimizeGadget
  EndIf  
  If *window\maximiseEnable
    flags = flags | #PB_Window_MaximizeGadget
  EndIf
  If (Not *window\centerParent And *window\centerNew And Not *window\positionSet)
    ; don't center to screen if the centerParent flag is set
    flags = flags | #PB_Window_ScreenCentered
  EndIf
  If *window\centerParent
    flags = flags | #PB_Window_WindowCentered
  EndIf
  If *window\maximised
    flags = flags | #PB_Window_Maximize
  EndIf
  If *window\allowResize
    flags = flags | #PB_Window_SizeGadget
  EndIf
  If *window\invisible
    flags = flags | #PB_Window_Invisible
  EndIf
  Debug "WindowShow: opening window " + window
  ;Debug "WindowShow: title: " + *window\title
  Debug "WindowShow: width: " + *window\width + ", height: " + *window\height
  Debug "WindowShow: X: " + *window\x + ", Y: " + *window\y
  If *window\parent = -1
    ; Is the top parent window
    result = OpenWindow(window, *window\x, *window\y, *window\width, *window\height, *window\title, flags)
  Else
    ; Has a parent window
    result = OpenWindow(window, *window\x, *window\y, *window\width, *window\height, *window\title, flags, WindowID(*window\parent))
  EndIf
  If *window\backColour >= 0
    SetWindowColor(window, GetColour(*window\backColour, *settings))
  EndIf
  If Not result
    Debug "WindowShow: could not initialise window"
    ProcedureReturn 0
  EndIf
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    If darkMode
      If OpenLibrary(0, "dwmapi")  
        DwmSetWindowAttribute = GetFunction(0, "DwmSetWindowAttribute")  
        DwmSetWindowAttribute(WindowID(window), #DWMWA_USE_IMMERSIVE_DARK_MODE, @darkMode, SizeOf(darkMode))
        CloseLibrary(0)
      EndIf
      SetWindowColor(window, #DARK_MODE_BACKGROUND)
    EndIf
  CompilerEndIf
  If *window\centerNew
    ; Update the x/y coordinates
    *window\x = WindowX(window)
    *window\y = WindowY(window)
  EndIf
  PokeI(*activeWindow, window) ; Set the ActiveWindow
  If *window\parent >= 0 And *window\disableParent
    ; Disable the parent window
    DisableWindow(*window\parent, #True)
  EndIf
  If *window\minWidth > 0 Or *window\minHeight > 0
    WindowBounds(window, *window\minWidth, *window\minHeight, #PB_Ignore, #PB_Ignore)
  EndIf
  SetWindowData(window, *windowData)
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    SmartWindowRefresh(window, #True)  
  CompilerEndIf  
  ProcedureReturn 1
EndProcedure

Procedure WindowClose(*activeWindow.Integer, parent.i, *quit.Integer)
  If *activeWindow\i = 0
    ; Don't close Window 0, just set Quit variable    
    Debug "WindowClose: window 0 closed, quitting"
    *quit\i = 1
    *activeWindow\i = -1
  Else
    If parent >= 0
      DisableWindow(parent, #False) ; enable the parent window
    EndIf
    CloseWindow(*activeWindow\i)
    *activeWindow\i = parent
  EndIf
EndProcedure

Procedure.i WindowMove(window.i, *window.WindowStructure)
  If GetWindowState(window) = #PB_Window_Normal  
    *Window\moved = 1
    *Window\x = WindowX(window)
    *Window\y = WindowY(window)
    *Window\positionSet = 1
    Debug "WindowMove: window moved to X: " + *window\x + " Y: " + *window\y
  EndIf    
EndProcedure

Procedure WindowMaximise(*window.WindowStructure)
  ; Due to Bug #2 this procedure cannot be used for anything except setting maximised
  Debug "WindowMaximise: window maximised"
  *window\maximised = 1
EndProcedure

Procedure WindowRestore(window.i, *windows.WindowStructure, *gadgets.GadgetStructure, numGadgets.i, *desktops.DesktopStructure, *settings.SettingsStructure)
  Protected *currentWindow.WindowStructure = *windows + SizeOf(WindowStructure) * window
  Debug "WindowRestore: window restored"
  *currentWindow\maximised = 0
  AutoResizeMax(window, *gadgets, numGadgets, *windows, *desktops, *settings)
EndProcedure

Procedure WindowLoadSettings(filename.s, numWindows.i, *settingsLoaded.Integer, Array windows.WindowStructure(1), numGadgets.i, Array gadgets.GadgetStructure(1))
  ; Loads setting if available and sets defaults when no settings are available
  Protected c.i, d.i
  Debug "WindowLoadSettings: opening " + filename
  OpenPreferences(filename)
  Debug "WindowLoadSettings: loading window settings"
  For c = 0 To  numWindows - 1
    If windows(c)\saveState ; only load windows with saveState flag
      PreferenceGroup("Window " + c)
      windows(c)\x = ReadPreferenceInteger("X", windows(c)\x)
      windows(c)\y = ReadPreferenceInteger("Y", windows(c)\y)  
      If windows(c)\allowResize
        windows(c)\width = ReadPreferenceInteger("Width", windows(c)\width)
        windows(c)\height = ReadPreferenceInteger("Height", windows(c)\height)
      EndIf
      windows(c)\maximised = ReadPreferenceInteger("Maximised", windows(c)\maximised)
      windows(c)\positionSet = ReadPreferenceInteger("Position_Set", windows(c)\positionSet)
    EndIf
    For d = 0 To numGadgets - 1
      If gadgets(d)\window = c
        If gadgets(d)\saveConfigWidth Or gadgets(d)\saveConfigHeight
          PreferenceGroup("Gadget " + d)
        EndIf
        If gadgets(d)\saveConfigWidth
          gadgets(d)\width = ReadPreferenceInteger("Width", gadgets(d)\width)
          Debug "Read gadget " + d + " width: " + gadgets(d)\width
        EndIf
        If gadgets(d)\saveConfigHeight
          gadgets(d)\height = ReadPreferenceInteger("Height", gadgets(d)\height)
        EndIf          
      EndIf
    Next d
  Next c
  *settingsLoaded\i = 1 ; set this so that SaveConfig can run
  ClosePreferences()
  ProcedureReturn 1
EndProcedure

Procedure WindowSaveSettings(filename.s, settingsLoaded.i, numWindows.i, Array windows.WindowStructure(1), numGadgets.i, Array gadgets.GadgetStructure(1))
  ; Always call LoadConfig before SaveConfig
  Protected c.i, d.i
  If settingsLoaded
    ; only save if the config has been loaded
    ; Create or open the config file
    If FileSize(filename) = -1 Or #PB_Compiler_OS = #PB_OS_Linux
      ; file doesn't exist, need to create a new file
      ; linux requires creating the file every time
      Debug "WindowSaveSettings: creating new settings file"
      If Not CreatePreferences(filename)
        Debug "WindowSaveSettings: ERROR - unable to create settings file"
        ProcedureReturn 0
      EndIf
    Else
      ; Open the existing config file
      If Not OpenPreferences(filename)
        Debug "WindowSaveSettings: ERROR - unable to open existing settings file"
        ProcedureReturn 0
      EndIf
    EndIf
    CreatePreferences(filename)
    ; Window settings are automatically saved when closing the application
    ; or changing the window (while in window mode)
    Debug "WindowSaveSettings: writing window preferences"
    For c = 0 To numWindows - 1
      If windows(c)\saveState ; only save windows with saveState flag
        PreferenceGroup("Window " + c)
        ;Debug "Saving window settings " + c
        WritePreferenceInteger("X", windows(c)\x)
        WritePreferenceInteger("Y", windows(c)\y)    
        If windows(c)\allowResize
          WritePreferenceInteger("Width", windows(c)\width)
          WritePreferenceInteger("Height", windows(c)\height)
        EndIf
        WritePreferenceInteger("Maximised", windows(c)\maximised)
        WritePreferenceInteger("Position_Set", windows(c)\positionSet)
        For d = 0 To numGadgets - 1
          If gadgets(d)\window = c
            If gadgets(d)\saveConfigWidth Or gadgets(d)\saveConfigHeight
              PreferenceGroup("Gadget " + d)
            EndIf
            If gadgets(d)\saveConfigWidth
              WritePreferenceInteger("Width",gadgets(d)\width)
            EndIf
            If gadgets(d)\saveConfigHeight
              WritePreferenceInteger("Height",gadgets(d)\height)
            EndIf          
          EndIf
        Next d
      EndIf
    Next c
    ClosePreferences()
    ProcedureReturn 1
  Else
    Debug "WindowSaveSettings: ERROR - cannot save settings because it hasn't been loaded yet"
    ProcedureReturn 0
  EndIf
EndProcedure

Procedure WindowActivate(*activeWindow.Integer)
  Debug "WindowActivate: window activated"
  *activeWindow\i = EventWindow()
EndProcedure

Procedure.i GetMouseX(window.i, *window.WindowStructure, *desktop.DesktopStructure)
  Protected wMX.i, dMX.i, MX.i
  wMX = WindowMouseX(window) / *desktop\resolutionX
  dMX = DesktopMouseX() / *desktop\resolutionX
  If wMX >= 0
    MX = wMX ; when inside the window just use the window coordinates
  Else
    MX = dMX
    If Not *window\maximised
      MX = MX - *window\x
    EndIf
  EndIf
  ProcedureReturn MX
EndProcedure

Procedure.i GetMouseY(window.i, *window.WindowStructure, *desktop.DesktopStructure)
  Protected wMY.i, dMY.i, MY.i
  wMY = WindowMouseY(window) / *desktop\resolutionY
  dMY = DesktopMouseY() / *desktop\resolutionY
  If wMY >= 0
    MY = wMY ; when inside the window just use the window coordinates
  Else
    MY = dMY
    If Not *window\maximised
      MY = MY - *window\y - *window\titleHeight
    EndIf
  EndIf
  ProcedureReturn MY
EndProcedure

Procedure GetMousePos(window, *window.WindowStructure, *desktop.DesktopStructure, *gadgetActions.GadgetActionStructure)
  *gadgetActions\MX = GetMouseX(window, *window, *desktop)
  *gadgetActions\MY = GetMouseY(window, *window, *desktop)
EndProcedure

Procedure.i GadgetHover(window.i, Array gadgets.GadgetStructure(1), numGadgets.i, *desktop.DesktopStructure, *window.WindowStructure)
  ; returns gadget number if mouse is hovering over a gadget
  Protected c.i = 0
  Protected MX.i, MY.i
  Protected x.i, y.i
  Protected width.i, height.i
  Protected foundHover.i = -1
  MX = GetMouseX(window, *window, *desktop)
  MY = GetMouseY(window, *window, *desktop)
  ;Debug "MX: " + MX + " MY: " + MY
  For c = 0 To numGadgets
    If gadgets(c)\window = window
      ; first gadget found for this window
      Break
    EndIf
  Next c
  Repeat
    x = x2(window, c, gadgets(), numGadgets, *desktop)
    y = y2(window, c, gadgets(), numGadgets, *desktop)
    width = width2(window, c, @gadgets(), numGadgets, *desktop)
    height = height2(window, c, @gadgets(), numGadgets, *desktop, *window)
    If gadgets(c)\type = #GadgetTypeScrollArea
      If gadgets(c)\resizeRight ; only needed on the right side and bottom as they have scroll bars
        width = width + gadgets(c)\resizeHandleSize ; move the resize handle over outside of the gadget
      EndIf
      If gadgets(c)\resizeBottom ; only needed on the right side and bottom as they have scroll bars
        height = height + gadgets(c)\resizeHandleSize ; move the resize handle over outside of the gadget
      EndIf      
    EndIf
    If MX >= x And MX <= x + width And MY >= y And MY <= y + height
      foundHover = c
    EndIf
    c = c + 1
  Until c > numGadgets Or foundHover >= 0 Or gadgets(c)\window <> window
  ProcedureReturn foundHover
EndProcedure

Procedure.i ResizeGadget2(gadget.i, window.i, *gadgets.GadgetStructure, numGadgets.i, *desktop.DesktopStructure, *window.WindowStructure, *settings.SettingsStructure)
  ; Resizes a gadget and also allows for percentage of window
  ; Note: this procedure takes a pointer to the gadget array, not a gadget
  Protected width.i, height.i
  Protected x.i, y.i
  Protected *gadget.GadgetStructure
  *gadget = *gadgets + (SizeOf(GadgetStructure) * gadget)
  x = x2(window, gadget, *gadgets, numGadgets, *desktop)
  y = y2(window, gadget, *gadgets, numGadgets, *desktop)
  width = Width2(window, gadget, *gadgets, numGadgets, *desktop)
  height = Height2(window, gadget, *gadgets, numGadgets, *desktop, *window)
  If gadget < 0 Or gadget >= numGadgets
    Debug "ResizeGadget2: ERROR invalid gadget " + gadget
    ProcedureReturn 0
  EndIf
  If *gadget\id <> 0
    ResizeGadget(gadget, x, y, width, height)
  Else
    Debug "ResizeGadget2: ERROR gadget " + gadget + " is not initialised"
    ProcedureReturn 0
  EndIf
  Select *gadget\type
    Case #GadgetTypeCanvas
      StartDrawing(CanvasOutput(gadget))
      DrawingMode(#PB_2DDrawing_Default)
      Box(0, 0, width * *desktop\resolutionX, height * *desktop\resolutionY, GetColour(*gadget\backColour, *settings))
      If *settings\debugOutline
        DrawingMode(#PB_2DDrawing_Outlined)
        Box(0, 0, width * *desktop\resolutionX, height * *desktop\resolutionY, #Blue)
      EndIf
      StopDrawing() 
    Case #GadgetTypeScrollArea
  EndSelect
  ProcedureReturn 1
EndProcedure

Procedure GetEvents(Array events.EventStructure(1), *numEvents.Integer)
  Protected event.i
  *numEvents\i = 0
  event = WindowEvent()
  While event And *numEvents\i < #MAX_EVENTS
    events(*numEvents\i)\event = event
    events(*numEvents\i)\eventGadget = EventGadget()
    events(*numEvents\i)\eventType = EventType()
    *numEvents\i = *numEvents\i + 1
    event = WindowEvent()
  Wend
EndProcedure

Procedure WindowProcessEvents(*event.EventStructure, *desktop.DesktopStructure, Array windows.WindowStructure(1), *activeWindow.Integer, Array gadgets.GadgetStructure(1), numGadgets.i, *gadgetActions.GadgetActionStructure, *quit.Integer, *saveSettings.Integer, *lastWindowMove.Quad, *settings.SettingsStructure)
  Protected c.i
  Protected gadget.i
  Protected eventType.i
  Protected MX.i
  Protected MY.i
  Protected x.i, y.i
  Protected width.i, height.i
  Protected newWidth.i
  Protected newHeight.i
  Protected maxWidth.i ; takes into account whether it is a percentaged based max values
  Protected maxHeight.i
  Protected minWidth.i ; takes into account whether it is a percentaged based max values
  Protected minHeight.i  
  Protected wWidth.i
  Protected wHeight.i
  Protected gadgetHover.i
  Protected window.i = *activeWindow\i
  wWidth = WindowWidth(window)
  wHeight = WindowHeight(window)
  MX = *gadgetActions\MX
  MY = *gadgetActions\MY
  gadgetHover = GadgetHover(window, gadgets(), numGadgets, *desktop, @windows(window))  
  ;Debug "Hover: " + gadgetHover + " MX: " + MX + " MY: " + MY
  If gadgetHover >= 0
    x = x2(window, gadgetHover, @gadgets(), numGadgets, *desktop)
    y = y2(window, gadgetHover, @gadgets(), numGadgets, *desktop)
    width = Width2(window, gadgetHover, @gadgets(), numGadgets, *desktop)
    height = Height2(window, gadgetHover, @gadgets(), numGadgets, *desktop, @windows(window))
    If gadgets(gadgetHover)\resizeLeft And MX >= x And MX <= x + gadgets(gadgetHover)\resizeHandleSize
      SetGadgetAttribute(gadgetHover, #PB_Canvas_Cursor, #PB_Cursor_LeftRight)
      *gadgetActions\hoverEdge = #GadgetEdgeLeft
    ElseIf gadgets(gadgetHover)\resizeRight And MX >= x + width - gadgets(gadgetHover)\resizeHandleSize And MX <= x + width
      SetGadgetAttribute(gadgetHover, #PB_Canvas_Cursor, #PB_Cursor_LeftRight)
      ;SetCursor(WindowID(window), MOUSE_CURSOR_SIZEWE)
      *gadgetActions\hoverEdge = #GadgetEdgeRight
    ElseIf gadgets(gadgetHover)\resizeTop And MY >= y And MY <= y + gadgets(gadgetHover)\resizeHandleSize
      SetCursor(WindowID(window), MOUSE_CURSOR_SIZENS)
      SetGadgetAttribute(gadgetHover, #PB_Canvas_Cursor, #PB_Cursor_UpDown)
      *gadgetActions\hoverEdge = #GadgetEdgeTop
    ElseIf gadgets(gadgetHover)\resizeBottom And MY >= y + height - gadgets(gadgetHover)\resizeHandleSize And MY <= y + height
      SetGadgetAttribute(gadgetHover, #PB_Canvas_Cursor, #PB_Cursor_UpDown)
      *gadgetActions\hoverEdge = #GadgetEdgeBottom
    Else
      If gadgets(gadgetHover)\type = #GadgetTypeCanvas
        SetGadgetAttribute(gadgetHover, #PB_Canvas_Cursor, #PB_Cursor_Default)
      EndIf
    EndIf  
  Else
    If *gadgetActions\dragEdge = 0 ; only set cursor to normal if not dragging
      ; not hovering over a gadget therefore the mouse should be normal
      SetCursor(WindowID(window), MOUSE_CURSOR_ARROW)
      *gadgetActions\hoverEdge = 0
    EndIf
  EndIf
  
  If *gadgetActions\dragID >= 0 And *gadgetActions\dragEdge >= 0
    Select *gadgetActions\dragEdge
      Case #GadgetEdgeLeft, #GadgetEdgeRight
        newWidth = DragWidth(MX, window, gadgets(), *gadgetActions\dragID, *gadgetActions\dragEdge)
        gadgets(*gadgetActions\dragID)\width = newWidth
        ResizeGadget2(*gadgetActions\dragID, window, @gadgets(), numGadgets, *desktop, @windows(window), *settings)
        For c = 0 To numGadgets-1
          If gadgets(c)\widthType = #DimensionCalculated And gadgets(c)\widthCalc = *gadgetActions\dragID
            ; the gadget's width depends on the dragged gadget's x
            gadgets(c)\width = gadgets(*gadgetActions\dragID)\x - gadgets(c)\x
            ResizeGadget2(c, window, @gadgets(), numGadgets, *desktop, @windows(window), *settings)
          EndIf
          If gadgets(c)\widthType = #DimensionCalculated And gadgets(c)\xCalc = *gadgetActions\dragID
            ; the gadget's x depends on the dragged gadget's width
            gadgets(c)\x = gadgets(*gadgetActions\dragID)\x + gadgets(*gadgetActions\dragID)\width
            ResizeGadget2(c, window, @gadgets(), numGadgets, *desktop, @windows(window), *settings)
          EndIf          
        Next c
     Case #GadgetEdgeTop, #GadgetEdgeBottom
       newHeight = DragHeight(MY, window, gadgets(), *gadgetActions\dragID, *gadgetActions\dragEdge)
       gadgets(*gadgetActions\dragID)\height = newHeight
       ResizeGadget2(*gadgetActions\dragID, window, @gadgets(), numGadgets, *desktop, @windows(window), *settings)
   EndSelect
  EndIf
  
  Select *event\event
    Case #PB_Event_Gadget
      If *event\eventGadget >= 0  ; not sure why there are gadgets events with -1
        Select *event\eventType
          Case #PB_EventType_MouseMove
          Case #PB_EventType_MouseEnter
          Case #PB_EventType_MouseLeave
            ;*gadgetActions\hoverID = -1
            *gadgetActions\hoverEdge = 0
            ;Debug "Mouse leave gadget: " + *event\eventGadget
          Case #PB_EventType_LeftButtonDown
            Debug "Left button down"
            If *gadgetActions\hoverEdge > #GadgetEdgeNone
              ; a draggable gadget edge is being hovered over
              *gadgetActions\dragID = gadgetHover
              *gadgetActions\dragEdge = *gadgetActions\hoverEdge
            EndIf
          Case #PB_EventType_LeftButtonUp
            Debug "Left button up"
            *gadgetActions\dragID = -1         
            *gadgetActions\dragEdge = #GadgetEdgeNone
        EndSelect
      EndIf
    Case #PB_Event_LeftClick
    Case #PB_Event_CloseWindow
      Debug "WindowProcessEvents: closing window"
      WindowClose(*activeWindow, windows(window)\parent, *quit)
      *saveSettings\i = 1 ; trigger a save
    Case #PB_Event_MoveWindow
      WindowMove(window, @windows(window))
      *lastWindowMove\q = ElapsedMilliseconds()
    Case #PB_Event_MaximizeWindow
      WindowMaximise(@windows(window))
      *saveSettings\i = 1
    Case #PB_Event_RestoreWindow
      WindowRestore(window, @windows(), @gadgets(), numGadgets, *desktop, *settings)
      *saveSettings\i = 1
    Case #PB_Event_SizeWindow
      ; This event is not handled by WinApp, it should be handled using a Resize() callback
      *lastWindowMove\q = ElapsedMilliseconds()
    Case #PB_Event_ActivateWindow
      WindowActivate(*activeWindow)
  EndSelect
  ;Debug "Hover ID: " + *gadgetActions\hoverID + ", hover edge: " + *gadgetActions\hoverEdge + ", drag ID: " + *gadgetActions\dragID + ", drag edge: " + *gadgetActions\dragEdge
EndProcedure

Procedure WindowProcessMenus(*event.EventStructure, Array windows.WindowStructure(1), Array menuTitles.MenuTitleStructure(1), Array menuItems.MenuItemStructure(1), activeWindow.i, *settings.SettingsStructure)
  ; Used for macOS to automatically disable menus when a child window is opened
  Protected parent.i
  If activeWindow >= 0 And windows(activeWindow)\menuEnable ; dont process menus when the application has quit
    parent = windows(activeWindow)\parent
    Select *event\event
        Case #PB_Event_CloseWindow
        Case #PB_Event_ActivateWindow
          CompilerIf (#PB_Compiler_OS = #PB_OS_MacOS)
            ; make sure the parent menu is freed for macOS
            If parent >= 0
              Debug "WindowProcessMenus: freeing parent menu"
              ;MenuDisable(menuTitles(), menuItems(), parent, #DISABLE_MENU)
              If windows(parent)\menuInit
                FreeMenu(parent)
                windows(parent)\menuInit = 0
              EndIf
            Else
              ; display the menu on the main window
              MenuShow(activeWindow, windows(), menuTitles(), menuItems(), *settings)
            EndIf
          CompilerElse
            If activeWindow >= 0
            If windows(activeWindow)\menuInit
              Debug "WindowProcessMenus: enabling menu on active window"
              MenuDisable(menuTitles(), menuItems(), activeWindow, #ENABLE_MENU)
            Else
              ; menu has not been initialised
              Debug "WindowProcessMenus: initialising menu " + activeWindow
              MenuShow(activeWindow, windows(), menuTitles(), menuItems(), *settings)
              windows(activeWindow)\menuInit = 1
            EndIf
          EndIf
        CompilerEndIf
    EndSelect
  EndIf
EndProcedure

Procedure.i WindowProcessSaveSettings(Array windows.WindowStructure(1), *saveSettings.Integer, *lastWindowMove.Quad, settingsFilename.s, settingsLoaded.i, numWindows.i, Array gadgets.GadgetStructure(1), numGadgets.i)
  ; Saves settings based on various triggers
  ; save if saveSettings triggers it
  If *saveSettings\i
    Debug "WindowProcessSaveSettings: saving due to save flag"
    WindowSaveSettings(settingsFilename, settingsLoaded, numWindows, windows(), numGadgets, gadgets())
    *saveSettings\i = 0
    ProcedureReturn 1
  EndIf
  ; Save if window was moved 2 seconds ago
  If (*lastWindowMove\q > 0) And (ElapsedMilliseconds() - *lastWindowMove\q > #MS_TO_SAVE_MOVED_WINDOW)
    Debug "WindowProcessSaveSettings: saving due to window moving"
    WindowSaveSettings(settingsFilename, settingsLoaded, numWindows, windows(), numGadgets, gadgets())
    *lastWindowMove\q = 0
    ProcedureReturn 1
  EndIf
  ProcedureReturn 0
EndProcedure

Procedure GadgetShow(gadget.i, window.i, Array gadgets.GadgetStructure(1), numGadgets, *desktop.DesktopStructure, darkMode.i, *window.SettingsStructure, *settings.SettingsStructure)
  Protected wWidth.i, wHeight.i
  Protected width.i, height.i
  Protected x.i, y.i
  Protected unknownGadget.i
  Protected *gadget.GadgetStructure = @gadgets() + (SizeOf(GadgetStructure) * gadget)
  wWidth = WindowWidth(window, #PB_Window_InnerCoordinate)
  wHeight = WindowHeight(window, #PB_Window_InnerCoordinate)
  x = x2(window, gadget, @gadgets(), numGadgets, *desktop)
  y = y2(window, gadget, @gadgets(), numGadgets, *desktop)
  width = Width2(window, gadget, @gadgets(), numGadgets, *desktop)
  height = Height2(window, gadget, @gadgets(), numGadgets, *desktop, *window)
  Debug "GadgetShow: showing gadget: " + gadget + " of type: " + *gadget\type
  Select *gadget\type
    Case #GadgetTypeText
      If *gadget\parent >= 0:OpenGadgetList(*gadget\parent):EndIf
      *gadget\id = TextGadget(gadget, x, y, *gadget\width, *gadget\height, *gadget\text, *gadget\flags)
      SetGadgetColor(gadget, #PB_Gadget_BackColor, GetColour(*gadget\backColour, *settings))
      ;SetGadgetColor(gadget, #PB_Gadget_FrontColor, GetColour(*gadget\textColour, *settings))
      ;If darkMode
      ;  SetGadgetColor(gadget, #PB_Gadget_BackColor, #DARK_MODE_BACKGROUND)
      ;  SetGadgetColor(gadget, #PB_Gadget_FrontColor, #DARK_MODE_FOREGROUND)
      ;EndIf
      If *settings\debugOutline
        StartDrawing(WindowOutput(window))
        DrawingMode(#PB_2DDrawing_Outlined)
        Box(x * *desktop\resolutionX, y * *desktop\resolutionY, *gadget\width * *desktop\resolutionX, *gadget\height * *desktop\resolutionY, #Blue)
        StopDrawing()
      EndIf      
      If *gadget\parent >= 0:CloseGadgetList():EndIf      
    Case #GadgetTypeString
      If *gadget\parent >= 0:OpenGadgetList(*gadget\parent):EndIf
      *gadget\id = StringGadget(gadget, x, y, width, height, *gadget\text, *gadget\flags)
      If *gadget\parent >= 0:CloseGadgetList():EndIf      
    Case #GadgetTypeButton
      If *gadget\parent >= 0:OpenGadgetList(*gadget\parent):EndIf
      *gadget\id = ButtonGadget(gadget, x, y, width, height, *gadget\text, *gadget\flags)
      If *gadget\parent >= 0:CloseGadgetList():EndIf      
    Case #GadgetTypeButtonImage
      If *gadget\parent >= 0:OpenGadgetList(*gadget\parent):EndIf
      *gadget\id = ButtonImageGadget(gadget, x, y, width, height, ImageID(*gadget\imageID), *gadget\flags)
      If *gadget\toolTip <> ""
        GadgetToolTip(gadget, *gadget\toolTip)
      EndIf
      If *gadget\parent >= 0:CloseGadgetList():EndIf      
    Case #GadgetTypeCanvas
      If *gadget\parent >= 0:OpenGadgetList(*gadget\parent):EndIf
      ;Debug "Show canvas: x:"+x+"-y:"+y+"-w:"+width+"-h:"+height
      *gadget\id = CanvasGadget(gadget, x, y, width, height, *gadget\flags)
      If *gadget\flags & #PB_Canvas_Container <> 0
        ; canvas was flagged as a container so close gadget list
        CloseGadgetList()
      EndIf
      StartDrawing(CanvasOutput(gadget))
      DrawingMode(#PB_2DDrawing_Default)
      Box(0, 0, width * *desktop\resolutionX, height * *desktop\resolutionY, GetColour(*gadget\backColour, *settings))
      If *settings\debugOutline
        DrawingMode(#PB_2DDrawing_Outlined)
        Box(0, 0, width * *desktop\resolutionX, height * *desktop\resolutionY, #Blue)
      EndIf
      StopDrawing()        
      If *gadget\parent >= 0:CloseGadgetList():EndIf      
    Case #GadgetTypeScrollArea
      If *gadget\parent >= 0:OpenGadgetList(*gadget\parent):EndIf
      *gadget\id = ScrollAreaGadget(gadget, x, y, width, height, *gadget\scrollAreaWidth, *gadget\scrollAreaHeight, *gadget\scrollStep, *gadget\flags)
      SetGadgetColor(gadget, #PB_Gadget_BackColor, GetColour(*gadget\backColour, *settings))
      CloseGadgetList()
      If *gadget\parent >= 0:CloseGadgetList():EndIf      
    Default
      unknownGadget = 1
      Debug "GadgetShow: unknown gadget type: " + *gadget\type
  EndSelect
  If Not unknownGadget
    If *gadget\disabled
      DisableGadget(gadget, #True)
    Else
      DisableGadget(gadget, #False)
    EndIf
    If *gadget\active
      SetActiveGadget(gadget)
    EndIf
  EndIf
EndProcedure  

Procedure GadgetShowAll(window.i, *desktop.DesktopStructure, Array windows.WindowStructure(1), Array gadgets.GadgetStructure(1), numGadgets.i, darkMode.i, *settings.SettingsStructure)
  Protected c.i
  For c = 0 To numGadgets
    If gadgets(c)\window = window
      Break ; find the fist gadget for the current window
    EndIf
  Next c
  While gadgets(c)\window = window
    GadgetShow(c, window, gadgets(), numGadgets, *desktop, darkMode, windows(window), *settings)
    c = c + 1
  Wend
EndProcedure

Procedure ToolbarShow(toolBar.i, window.i, *window.WindowStructure, colour.i)
  CreateToolBar(toolBar, WindowID(window), #PB_ToolBar_Small)
  ;CompilerIf #PB_Compiler_OS = #PB_OS_Windows
  ;  *window\toolbarOverlay = ContainerGadget(#PB_Any, 0, -2, *window\width, 24, #PB_Container_BorderLess) 
  ;  SetClassLongPtr_(GadgetID(*window\toolbarOverlay), #GCL_HBRBACKGROUND, CreateSolidBrush_(colour))
  ;CompilerEndIf
EndProcedure

;- 90 - Initialisation

;- 100 - Self test code

CompilerIf #PB_Compiler_IsMainFile
  
  Enumeration Windows
    #WindowMain
    #WindowNew
    #WindowCount
  EndEnumeration
  
  Enumeration Menus
    #MenuNew
    #MenuBar1
    #MenuOpen
    #MenuBar2
    #MenuSave
    #MenuSaveAs
    #MenuBar3
    #MenuQuit
    #MenuAbout
  EndEnumeration
  
  Enumeration Gadgets
    #cnvToolbar
    #cnvLeftCanvas
    #cnvRightCanvas
    #cnvCentreCanvas
    #GadgetCount
  EndEnumeration
  
  Define c.i
  
  activeWindow = 0
  settingsLoaded = 0
  settingsFilename = "self-test.cfg" ; override the standard
  
  Initialise(windows(), #WindowCount, gadgets(), #GadgetCount, events(), @windowData, darkMode, @colours, @settings)
  InitDesktop(desktops(), @numMonitors, @windowData)
  InitWindows(windows(), #WindowCount, gadgets(), 0, @windowData)    
  InitMenus(menuTitles(), menuItems())
  InitGadgets(gadgets(), @gadgetActions)
    
  ; Window #0
  windows(0)\title = "Test"
  windows(0)\x = 30
  windows(0)\y = 30
  windows(0)\width = 640
  windows(0)\height = 480    
  windows(0)\backColour = #ColourPalette0
  windows(0)\systemMenu = 1
  windows(0)\minimiseEnable = 1
  windows(0)\maximiseEnable = 1
  windows(0)\minWidth = 600
  windows(0)\minHeight = 440
  windows(0)\allowResize = 1
  windows(0)\saveState = 1
  windows(0)\centerNew = 1
  windows(0)\parent = -1
  windows(0)\disableParent = 0
  windows(0)\centerParent = 0
  windows(0)\invisible = 0
  windows(0)\positionSet = 0
  windows(0)\restoreFix = 0 ; See bug #1
  windows(0)\maximised = 0
  windows(0)\ratio = windows(0)\height / windows(0)\width
  windows(0)\moved = 0
  windows(0)\menuInit = 0
  windows(0)\titleHeight = 50
  
  ; Window #1
  windows(1)\title = "Test2"
  windows(1)\x = 30
  windows(1)\y = 30
  windows(1)\width = 300
  windows(1)\height = 400    
  windows(1)\backColour = -1
  windows(1)\systemMenu = 1
  windows(1)\minimiseEnable = 0
  windows(1)\maximiseEnable = 0
  windows(1)\minWidth = 300
  windows(1)\minHeight = 400
  windows(1)\allowResize = 0
  windows(1)\saveState = 1
  windows(1)\centerNew = 1
  windows(1)\parent = 0
  windows(1)\disableParent = 1
  windows(1)\centerParent = 0
  windows(1)\invisible = 0
  windows(1)\positionSet = 0
  windows(1)\restoreFix = 0 ; See bug #1
  windows(1)\maximised = 0
  windows(1)\ratio = windows(1)\height / windows(1)\width
  windows(1)\moved = 0
  windows(1)\menuInit = 0
  
  ; Menu

  menuTitles(0)\text = "File"
  menuTitles(0)\window = 0
  menuTitles(1)\text = "Help"
  menuTitles(1)\window = 0  
  
  menuItems(#MenuNew)\menuID = 0
  menuItems(#MenuNew)\text = "New..."
  menuItems(#MenuNew)\checked = 0
  menuItems(#MenuNew)\enabled = 1
  menuItems(#MenuNew)\imageID = 0
  
  menuItems(#MenuBar1)\bar = 1
  menuItems(#MenuBar1)\menuID = 0
  
  menuItems(#MenuOpen)\menuID = 0
  menuItems(#MenuOpen)\text = "Open..."
  menuItems(#MenuOpen)\checked = 0
  menuItems(#MenuOpen)\enabled = 1
  menuItems(#MenuOpen)\imageID = 0
  
  menuItems(#MenuBar2)\bar = 1
  menuItems(#MenuBar2)\menuID = 0
    
  menuItems(#MenuSave)\menuID = 0
  menuItems(#MenuSave)\text = "Save"
  menuItems(#MenuSave)\checked = 0
  menuItems(#MenuSave)\enabled = 1
  menuItems(#MenuSave)\imageID = 0
  
  menuItems(#MenuSaveAs)\menuID = 0
  menuItems(#MenuSaveAs)\text = "Save As..."
  menuItems(#MenuSaveAs)\checked = 0
  menuItems(#MenuSaveAs)\enabled = 1
  menuItems(#MenuSaveAs)\imageID = 0  
  
  menuItems(#MenuBar3)\bar = 1
  menuItems(#MenuBar3)\menuID = 0  
  
  menuItems(#MenuQuit)\menuID = 0
  menuItems(#MenuQuit)\text = "Quit"
  menuItems(#MenuQuit)\checked = 0
  menuItems(#MenuQuit)\enabled = 1
  menuItems(#MenuQuit)\imageID = 0
  
  menuItems(#MenuAbout)\menuID = 1
  menuItems(#MenuAbout)\text = "About"
  menuItems(#MenuAbout)\checked = 0
  menuItems(#MenuAbout)\enabled = 1
  menuItems(#MenuAbout)\imageID = 0
  
  ; gadgets
  
  gadgets(#cnvToolbar)\window = #WindowMain
  gadgets(#cnvToolbar)\type = #GadgetTypeCanvas
  gadgets(#cnvToolbar)\backColour = #ColourPalette2
  gadgets(#cnvToolbar)\x = 0
  gadgets(#cnvToolbar)\y = 0
  gadgets(#cnvToolbar)\width = 100
  gadgets(#cnvToolbar)\widthType = 1 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvToolbar)\height = 32
  gadgets(#cnvToolbar)\heightType = 0 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvToolbar)\text = ""
  gadgets(#cnvToolbar)\flags = #PB_Canvas_Container ; toolbar is a container because it contains the search bar
  gadgets(#cnvToolbar)\active = 0
    
  gadgets(#cnvLeftCanvas)\window = #WindowMain
  gadgets(#cnvLeftCanvas)\type = #GadgetTypeCanvas
  gadgets(#cnvLeftCanvas)\backColour = #ColourPalette1
  gadgets(#cnvLeftCanvas)\x = 0
  gadgets(#cnvLeftCanvas)\y = 32
  gadgets(#cnvLeftCanvas)\width = 200
  gadgets(#cnvLeftCanvas)\widthType = 0 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvLeftCanvas)\height = 100
  gadgets(#cnvLeftCanvas)\heightType = 1 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvLeftCanvas)\text = ""
  gadgets(#cnvLeftCanvas)\flags = 0
  gadgets(#cnvLeftCanvas)\active = 0    
  gadgets(#cnvLeftCanvas)\resizeRight = 1
  gadgets(#cnvLeftCanvas)\resizeHandleSize = 8
  gadgets(#cnvLeftCanvas)\saveConfigWidth = 1
  gadgets(#cnvLeftCanvas)\minWidth = 100
  gadgets(#cnvLeftCanvas)\maxWidth = 40
  gadgets(#cnvLeftCanvas)\minWidthType = 0 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvLeftCanvas)\maxWidthType = 1 ; 0 = pixels, 1 = percent of window   
    
  gadgets(#cnvRightCanvas)\window = #WindowMain
  gadgets(#cnvRightCanvas)\type = #GadgetTypeCanvas
  gadgets(#cnvRightCanvas)\backColour = #ColourPalette1
  gadgets(#cnvRightCanvas)\x = 0 ; not used when right aligned
  gadgets(#cnvRightCanvas)\y = 32
  gadgets(#cnvRightCanvas)\xAlign = #AlignHorizontalRight
  gadgets(#cnvRightCanvas)\yAlign = 0
  gadgets(#cnvRightCanvas)\width = 200
  gadgets(#cnvRightCanvas)\widthType = 0 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvRightCanvas)\height = 100
  gadgets(#cnvRightCanvas)\heightType = 1 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvRightCanvas)\text = ""
  gadgets(#cnvRightCanvas)\flags = 0
  gadgets(#cnvRightCanvas)\active = 0    
  gadgets(#cnvRightCanvas)\resizeLeft = 1
  gadgets(#cnvRightCanvas)\resizeHandleSize = 8
  gadgets(#cnvRightCanvas)\saveConfigWidth = 1
  gadgets(#cnvRightCanvas)\minWidth = 100
  gadgets(#cnvRightCanvas)\maxWidth = 40
  gadgets(#cnvRightCanvas)\minWidthType = 0 ; 0 = pixels, 1 = percent of window
  gadgets(#cnvRightCanvas)\maxWidthType = 1 ; 0 = pixels, 1 = percent of window      
  
  gadgets(#cnvCentreCanvas)\window = #WindowMain
  gadgets(#cnvCentreCanvas)\type = #GadgetTypeCanvas
  gadgets(#cnvCentreCanvas)\backColour = #ColourPalette0
  gadgets(#cnvCentreCanvas)\x = 0
  gadgets(#cnvCentreCanvas)\xType = 1 ; 0 = normal, 1 = calculated from a gadget x+width
  gadgets(#cnvCentreCanvas)\xCalc = #cnvLeftCanvas
  gadgets(#cnvCentreCanvas)\y = 32
  gadgets(#cnvCentreCanvas)\yType = 0 ; 0 = normal, 1 = calculated from a gadget y+height
  gadgets(#cnvCentreCanvas)\width = 0
  gadgets(#cnvCentreCanvas)\widthType = #DimensionCalculated ; 0 = pixels, 1 = percent of window, 2 = calculated from a gadget x
  gadgets(#cnvCentreCanvas)\widthCalc = #cnvRightCanvas
  gadgets(#cnvCentreCanvas)\height = 100
  gadgets(#cnvCentreCanvas)\heightType = 1 ; 0 = pixels, 1 = percent of window, 2 = calculated from a gadget y
  gadgets(#cnvCentreCanvas)\flags = 0
  gadgets(#cnvCentreCanvas)\active = 0   
    
  If Not CheckGadgets(gadgets(), #GadgetCount)
    End 1
  EndIf
     
  WindowLoadSettings(settingsFilename, #WindowCount, @settingsLoaded, windows(), #GadgetCount, gadgets())
  WindowSaveSettings(settingsFilename, settingsLoaded, #WindowCount, windows(), #GadgetCount, gadgets())
  
  WindowShow(0, @desktops(activeDesktop), @windows(0), @activeWindow, @windowData, darkMode, @settings)
  MenuShow(activeWindow, windows(), menuTitles(), menuItems(), @settings)
  GadgetShowAll(activeWindow, desktops(activeDesktop), windows(), gadgets(), #GadgetCount, darkMode, @settings)
  InitResizeBinding()
  
  Repeat
    Delay(10)
    GetEvents(events(), @numEvents)
    GetMousePos(activeWindow, windows(activeWindow), @desktops(activeDesktop), @gadgetActions)
    ;Debug "MX: " + gadgetActions\MX + " MY: " + gadgetActions\MY
    For c = 0 To numEvents-1
      WindowProcessEvents(@events(c), @desktops(activeDesktop), windows(), @activeWindow, gadgets(), #GadgetCount, gadgetActions.GadgetActionStructure, @quit, @saveSettings, @lastWindowMove, @settings)
      WindowProcessSaveSettings(windows(), @saveSettings, @lastWindowMove, settingsFilename, settingsLoaded, #WindowCount, gadgets(), #GadgetCount)
      WindowProcessMenus(events(c), windows(), menuTitles(), menuItems(), activeWindow, @settings)
      If quit Or windowData\quit
        Break
      EndIf
    Next c
   Until quit
  
  WindowSaveSettings(settingsFilename, settingsLoaded, #WindowCount, windows(), #GadgetCount, gadgets())
  
  End 0
  
CompilerEndIf  

;- 110 - Data section

DataSection
  
  
EndDataSection