-- Copyright 2011-18 Paul Kulchenko, ZeroBrane LLC
-- authors: Luxinia Dev (Eike Decker & Christoph Kubisch)
-- Lomtik Software (J. Winwood & John Labenski)
---------------------------------------------------------

local ide = ide
local unpack = table.unpack or unpack

do local font, config = wx.wxTreeCtrl():GetFont(), ide.config.filetree
  if config.fontsize then font:SetPointSize(config.fontsize) end
  if config.fontname then font:SetFaceName(config.fontname) end
  ide.font.tree = font
end

-- ----------------------------------------------------------------------------
-- Create the wxFrame
-- ----------------------------------------------------------------------------
local function createFrame()
  local frame = ide:GetMainFrame() -- retrieve or create as needed
  frame:Center()

  -- update best size of the toolbar after resizing
  frame:Connect(wx.wxEVT_SIZE, function(event)
      local mgr = ide:GetUIManager()
      local toolbar = mgr:GetPane("toolbar")
      if toolbar and toolbar:IsOk() then
        local tbheight = ide:GetToolBar():GetClientSize():GetHeight()
        -- some Linux desktop environments (ex LXQt) report wrong client size to wx, so skip those
        if tbheight > 1 then
          toolbar:BestSize(event:GetSize():GetWidth(), tbheight)
        end
        mgr:Update()
      end
    end)

  if wx.wxMenuBar.SetAutoWindowMenu then wx.wxMenuBar.SetAutoWindowMenu(false) end
  local menuBar = wx.wxMenuBar()
  local statusBar = frame:CreateStatusBar(5)
  local section_width = statusBar:GetTextExtent("OVRW")
  statusBar:SetStatusStyles({wx.wxSB_FLAT, wx.wxSB_FLAT, wx.wxSB_FLAT, wx.wxSB_FLAT, wx.wxSB_FLAT})
  statusBar:SetStatusWidths({-1, section_width, section_width, section_width*5, section_width*5})
  statusBar:SetStatusText(ide:GetProperty("statuswelcome", ""))
  statusBar:Connect(wx.wxEVT_LEFT_DOWN, function (event)
      local rect = wx.wxRect()
      statusBar:GetFieldRect(4, rect)
      if rect:Contains(event:GetPosition()) then -- click on the interpreter section
        local interpreter = ide:GetInterpreter()
        if interpreter and interpreter.takeparameters and interpreter:GetCommandLineArg() then
          rect:SetWidth(statusBar:GetTextExtent(ide:GetInterpreter():GetName()..": "))
          if not rect:Contains(event:GetPosition()) then
            local menuitem = ide:FindMenuItem(ID.COMMANDLINEPARAMETERS)
            if menuitem then
              local menu = ide:MakeMenu {
                { ID.COMMANDLINEPARAMETERS, TR("Command Line Parameters...")..KSC(ID.COMMANDLINEPARAMETERS) },
              }
              local cmdargs = ide:GetPackage("core.project"):GetCmdLines()
              local curargs = interpreter:GetCommandLineArg()
              if #cmdargs > 1 or cmdargs[1] ~= curargs then menu:PrependSeparator() end
              local function setParams(ev) ide:SetCommandLineParameters(menu:GetLabel(ev:GetId())) end
              -- do in the reverse order as `Prepend` is used;
              -- skip the currently set parameters
              for i = #cmdargs, 1, -1 do
                if cmdargs[i] ~= curargs then
                  local id = ID("status.commandparameters."..i)
                  menu:Prepend(id, cmdargs[i])
                  menu:Connect(id, wx.wxEVT_COMMAND_MENU_SELECTED, setParams)
                end
              end
              statusBar:PopupMenu(menu)
            end
            return
          end
        end
        local menuitem = ide:FindMenuItem(ID.INTERPRETER)
        if menuitem then
          local menu = ide:CloneMenu(menuitem:GetSubMenu())
          if menu then statusBar:PopupMenu(menu) end
        end
      end
    end)

  local mgr = wxaui.wxAuiManager()
  mgr:SetManagedWindow(frame)
  -- allow the panes to be larger than the defalt 1/3 of the main window size
  mgr:SetDockSizeConstraint(0.8,0.8)

  frame.menuBar = menuBar
  frame.statusBar = statusBar
  frame.uimgr = mgr

  return frame
end

local function menuDropDownPosition(event)
  local tb = event:GetEventObject():DynamicCast('wxAuiToolBar')
  local rect = tb:GetToolRect(event:GetId())
  return ide.frame:ScreenToClient(tb:ClientToScreen(rect:GetBottomLeft()))
end

local function createToolBar(frame)
  local toolBar = wxaui.wxAuiToolBar(frame, wx.wxID_ANY, wx.wxDefaultPosition, wx.wxDefaultSize,
    wxaui.wxAUI_TB_PLAIN_BACKGROUND)

  local iconsize = ide:GetBestIconSize()
  local toolBmpSize = wx.wxSize(iconsize, iconsize)
  local icons = ide.config.toolbar.icons
  local needseparator = false
  for _, id in ipairs(icons) do
    if icons[id] ~= false then -- skip explicitly disabled icons
      if id == ID.SEPARATOR and toolBar:GetToolCount() > 0 then
        needseparator = true
      else
        local iconmap = ide.config.toolbar.iconmap[id]
        if iconmap then
          if needseparator then
            toolBar:AddSeparator()
            needseparator = false
          end
          local icon, description = unpack(iconmap)
          local isbitmap = type(icon) == "userdata" and icon:GetClassInfo():GetClassName() == "wxBitmap"
          local bitmap = isbitmap and icon or ide:GetBitmap(icon, "TOOLBAR", toolBmpSize)
          toolBar:AddTool(id, "", bitmap, (TR)(description).." ("..TSC(id)..")")
        end
      end
    end
  end

  toolBar:SetToolDropDown(ID.OPEN, true)
  toolBar:Connect(ID.OPEN, wxaui.wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, function(event)
      if event:IsDropDownClicked() then
        local menu = wx.wxMenu({})
        FileRecentListUpdate(menu)
        toolBar:PopupMenu(menu, menuDropDownPosition(event))
      else
        event:Skip()
      end
    end)

  toolBar:SetToolDropDown(ID.PROJECTDIRCHOOSE, true)
  toolBar:Connect(ID.PROJECTDIRCHOOSE, wxaui.wxEVT_COMMAND_AUITOOLBAR_TOOL_DROPDOWN, function(event)
      if event:IsDropDownClicked() then
        local menu = wx.wxMenu({})
        FileTreeProjectListUpdate(menu, 0)
        toolBar:PopupMenu(menu, menuDropDownPosition(event))
      else
        event:Skip()
      end
    end)

  toolBar:GetArtProvider():SetElementSize(wxaui.wxAUI_TBART_GRIPPER_SIZE, 0)
  toolBar:Realize()

  frame.toolBar = toolBar
  return toolBar
end

local function getTabWindow(event, nb)
  local tabctrl = event:GetEventObject():DynamicCast("wxAuiTabCtrl")
  local idx = event:GetSelection() -- index within the current tab ctrl
  local win = tabctrl:GetPage(idx).window
  return win and idx ~= wx.wxNOT_FOUND and nb:GetPageIndex(win) or wx.wxNOT_FOUND, tabctrl
end

local function isPreview(win)
  return ide.findReplace ~= nil and ide.findReplace:IsPreview(win)
end

local function createNotebook(frame)
  -- notebook for editors
  local notebook = ide:CreateNotebook(frame, wx.wxID_ANY,
    wx.wxDefaultPosition, wx.wxDefaultSize,
    wxaui.wxAUI_NB_DEFAULT_STYLE + wxaui.wxAUI_NB_TAB_EXTERNAL_MOVE
    -- + wxaui.wxAUI_NB_WINDOWLIST_BUTTON + wx.wxNO_BORDER)
    + wx.wxNO_BORDER)

  -- there is a protected method in wxwidgets, but it's not available in wxlua,
  -- so use a workaround to find the tab control that the page belongs to.
  function notebook:GetTabCtrl(win)
    local pg = win and self:GetPageIndex(win) >= 0 and win or self:GetCurrentPage()
    if not pg then return nil end
    local px,py = pg:GetScreenPosition():GetXY()
    local point = wx.wxPoint(px,py-10) -- right above the page in the notebook
    local ctrl = wx.wxFindWindowAtPoint(point)
    return ctrl and ctrl:DynamicCast("wxAuiTabCtrl") or nil
  end

  -- wxEVT_SET_FOCUS could be used, but it only works on Windows with wx2.9.5+
  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED,
    function (event)
      -- skip setting focus when exiting, as it may cause looping on macOS
      if ide:IsExiting() then return end

      local doc = ide:GetDocument(notebook:GetCurrentPage())

      -- skip activation when any of the following is true:
      -- (1) there is no document yet, the editor tab was just added,
      -- so no changes needed as there will be a proper later call;
      -- (2) on OSX from AddPage event when changing from the last tab
      -- (this is to work around a duplicate event generated in this case
      -- that first activates the added tab and then some other tab (2.9.5)).

      local double = (ide.osname == 'Macintosh'
        and event:GetOldSelection() == notebook:GetPageCount()
        and debug:traceback():find("'AddPage'"))

      if doc and doc:GetTabIndex() and not double then
        doc:SetActive()
      end
    end)

  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE,
    function (event)
      local idx = event:GetSelection()
      if idx ~= wx.wxNOT_FOUND then
        local doc = ide:GetDocument(notebook:GetPage(idx))
        if doc then
          -- make sure that the doc is in the same notebook and is not moved somewhere
          local i, nb = doc:GetTabIndex()
          if i == idx and nb == notebook then doc:Close() end
        end
      end
      event:Veto() -- don't propagate the event as the page is already closed
    end)

  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK,
    function (event)
      -- as this event can be on different tab controls,
      -- need to find the control to add the page to
      local tabctrl = event:GetEventObject():DynamicCast("wxAuiTabCtrl")
      -- check if the active page is in the current control
      local active = tabctrl:GetActivePage()
      if (active >= 0 and tabctrl:GetPage(active).window
        ~= notebook:GetPage(notebook:GetSelection())) then
        -- if not, need to activate the control that was clicked on;
        -- find the last window and switch to it (assuming there is always one)
        assert(tabctrl:GetPageCount() >= 1, "Expected at least one page in a notebook tab control.")
        local lastwin = tabctrl:GetPage(tabctrl:GetPageCount()-1).window
        notebook:SetSelection(notebook:GetPageIndex(lastwin))
      end
      NewFile()
    end)

  -- tabs can be dragged around which may change their indexes;
  -- when this happens stored indexes need to be updated to reflect the change.
  -- there is DRAG_DONE event that I'd prefer to use, but it
  -- doesn't fire for some reason using wxwidgets 2.9.5 (tested on Windows).
  if ide.wxver >= "2.9.5" then
    notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_END_DRAG,
      function (event)
        local selection = getTabWindow(event, notebook)
        if selection == wx.wxNOT_FOUND then return end
        -- set the selection on the dragged tab to reset its state
        -- workaround for wxwidgets issue http://trac.wxwidgets.org/ticket/15071
        notebook:SetSelection(selection)
        -- select the content of the tab after drag is done
        local doc = ide:GetDocument(notebook:GetPage(selection))
        if doc then doc:SetActive() end
        event:Skip()
      end)
  end

  local selection
  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP,
    function (event)
      -- event:GetSelection() returns the index *inside the current tab*;
      -- for split notebooks, this may not be the same as the index
      -- in the notebook we are interested in here
      local idx = event:GetSelection()
      if idx == wx.wxNOT_FOUND then return end
      local tabctrl = event:GetEventObject():DynamicCast("wxAuiTabCtrl")

      -- save the editor from the tab initiating the event
      selection = tabctrl:GetPage(idx).window
      -- save tab index the event is for
      local curindex = notebook:GetSelection()
      local selindex = notebook:GetPageIndex(selection)
      if curindex ~= selindex then notebook:SetSelection(selindex) end

      local tree = ide:GetProjectTree()
      local startfile = tree:GetStartFile()

      local menu = ide:MakeMenu {
        { ID.CLOSE, TR("&Close Page") },
        { ID.CLOSEALL, TR("Close A&ll Pages") },
        { ID.CLOSEOTHER, TR("Close &Other Pages") },
        { ID.CLOSESEARCHRESULTS, TR("Close Search Results Pages") },
        { },
        { ID.SAVE, TR("&Save") },
        { ID.SAVEAS, TR("Save &As...") },
        { },
        { ID.SETSTARTFILE, TR("Set As Start File") },
        { ID.UNSETSTARTFILE, TR("Unset '%s' As Start File"):format(startfile or "<none>") },
        { },
        { ID.COPYFULLPATH, TR("Copy Full Path") },
        { ID.SHOWLOCATION, TR("Open Containing Folder") },
        { ID.REFRESHSEARCHRESULTS, TR("Refresh Search Results") },
      }

      local fpath = ide:GetDocument(selection):GetFilePath()
      if not fpath or not tree:FindItem(fpath) then menu:Enable(ID.SETSTARTFILE, false) end
      if not startfile then menu:Destroy(ID.UNSETSTARTFILE) end

      PackageEventHandle("onMenuEditorTab", menu, notebook, event, notebook:GetPageIndex(selection))

      if selection:GetClassInfo():GetClassName() == "wxStyledTextCtrl" then
        selection = selection:DynamicCast("wxStyledTextCtrl")
      end

      -- popup statuses are not refreshed on Linux, so do it manually
      if ide.osname == "Unix" then UpdateMenuUI(menu, notebook) end
      notebook:PopupMenu(menu)
      -- restore selection if it has changed
      if curindex ~= selindex then notebook:SetSelection(curindex) end
    end)

  local function IfAtLeastOneTab(event) event:Enable(notebook:GetPageCount() > 0) end

  notebook:Connect(ID.SETSTARTFILE, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      local fpath = ide:GetDocument(selection):GetFilePath()
      if fpath then ide:GetProjectTree():SetStartFile(fpath) end
    end)
  notebook:Connect(ID.UNSETSTARTFILE, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:GetProjectTree():SetStartFile()
    end)
  notebook:Connect(ID.SAVE, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:GetDocument(selection):Save()
    end)
  notebook:Connect(ID.SAVE, wx.wxEVT_UPDATE_UI, function(event)
      local doc = ide:GetDocument(selection)
      event:Enable(doc:IsModified() or doc:IsNew())
    end)
  notebook:Connect(ID.SAVEAS, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      SaveFileAs(selection)
    end)
  notebook:Connect(ID.SAVEAS, wx.wxEVT_UPDATE_UI, IfAtLeastOneTab)

  -- the following three methods require handling of closing in the idle event,
  -- because of wxwidgets issue that causes crash on OSX when the last page is closed
  -- (http://trac.wxwidgets.org/ticket/15417)
  notebook:Connect(ID.CLOSE, wx.wxEVT_UPDATE_UI, IfAtLeastOneTab)
  notebook:Connect(ID.CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:DoWhenIdle(function() ide:GetDocument(selection):Close() end)
    end)

  notebook:Connect(ID.CLOSEALL, wx.wxEVT_UPDATE_UI, IfAtLeastOneTab)
  notebook:Connect(ID.CLOSEALL, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:DoWhenIdle(function()
          ide:GetDocument(selection):CloseAll({scope = "section"})
        end)
    end)

  notebook:Connect(ID.CLOSEOTHER, wx.wxEVT_UPDATE_UI, function(event)
      event:Enable(notebook:GetPageCount() > 1)
    end)
  notebook:Connect(ID.CLOSEOTHER, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:DoWhenIdle(function()
          ide:GetDocument(selection):CloseAll({keep = true, scope = "section"})
        end)
    end)

  notebook:Connect(ID.CLOSESEARCHRESULTS, wx.wxEVT_UPDATE_UI, function(event)
      local ispreview = false
      for p = 0, notebook:GetPageCount()-1 do
        ispreview = ispreview or isPreview(notebook:GetPage(p))
      end
      event:Enable(ispreview)
    end)
  notebook:Connect(ID.CLOSESEARCHRESULTS, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:DoWhenIdle(function()
          for p = notebook:GetPageCount()-1, 0, -1 do
            local editor = notebook:GetPage(p)
            if isPreview(editor) then ide:GetDocument(editor):Close() end
          end
        end)
    end)

  notebook:Connect(ID.REFRESHSEARCHRESULTS, wx.wxEVT_UPDATE_UI, function(event)
      event:Enable(isPreview(selection))
    end)
  notebook:Connect(ID.REFRESHSEARCHRESULTS, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide.findReplace:RefreshResults(selection)
    end)

  notebook:Connect(ID.SHOWLOCATION, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ShowLocation(ide:GetDocument(selection):GetFilePath())
    end)
  notebook:Connect(ID.SHOWLOCATION, wx.wxEVT_UPDATE_UI, IfAtLeastOneTab)

  notebook:Connect(ID.COPYFULLPATH, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:CopyToClipboard(ide:GetDocument(selection):GetFilePath())
    end)

  frame.notebook = notebook
  return notebook
end

local function addDND(notebook)
  -- this handler allows dragging tabs into this notebook
  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND,
    function (event)
      local notebookfrom = event:GetDragSource()
      if notebookfrom ~= ide.frame.notebook then
        -- disable cross-notebook movement of specific tabs
        local idx = event:GetSelection()
        if idx == wx.wxNOT_FOUND then return end
        local win = notebookfrom:GetPage(idx)
        if not win then return end
        local winid = win:GetId()
        if (ide:IsValidCtrl(ide:GetOutput()) and winid == ide:GetOutput():GetId())
        or (ide:IsValidCtrl(ide:GetConsole()) and winid == ide:GetConsole():GetId())
        or (ide:IsValidCtrl(ide:GetProjectTree()) and winid == ide:GetProjectTree():GetId())
        or isPreview(win) -- search results preview
        then return end

        local mgr = ide.frame.uimgr
        local pane = mgr:GetPane(notebookfrom)
        if not pane:IsOk() then return end -- not a managed window
        if pane:IsFloating() then
          notebookfrom:GetParent():Hide()
        else
          pane:Hide()
          mgr:Update()
        end
        mgr:DetachPane(notebookfrom)

        -- this is a workaround for wxwidgets bug (2.9.5+) that combines
        -- content from two windows when tab is dragged over an active tab.
        local mouse = wx.wxGetMouseState()
        local mouseatpoint = wx.wxPoint(mouse:GetX(), mouse:GetY())
        local ok, tabs = pcall(function() return wx.wxFindWindowAtPoint(mouseatpoint):DynamicCast("wxAuiTabCtrl") end)
        if ok then tabs:SetNoneActive() end

        event:Allow()
      end
    end)

  -- these handlers allow dragging tabs out of this notebook.
  -- I couldn't find a good way to stop dragging event as it's not known
  -- where the event is going to end when it's started, so we manipulate
  -- the flag that allows splits and disable it when needed.
  -- It is then enabled in BEGIN_DRAG event.
  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_BEGIN_DRAG,
    function (event)
      event:Skip()

      -- allow dragging if it was disabled earlier
      local flags = notebook:GetWindowStyleFlag()
      if bit.band(flags, wxaui.wxAUI_NB_TAB_SPLIT) == 0 then
        notebook:SetWindowStyleFlag(flags + wxaui.wxAUI_NB_TAB_SPLIT)
      end
    end)

  -- there is currently no support in wxAuiNotebook for dragging tabs out.
  -- This is implemented as removing a tab that was dragged out and
  -- recreating it with the right control. This is complicated by the fact
  -- that tabs can be split, so if the destination is withing the area where
  -- splits happen, the tab is not removed.
  notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_END_DRAG,
    function (event)
      event:Skip()

      local mgr = ide.frame.uimgr
      local win = mgr:GetPane(notebook).window
      local x = win:GetScreenPosition():GetX()
      local y = win:GetScreenPosition():GetY()
      local w, h = win:GetSize():GetWidth(), win:GetSize():GetHeight()

      local selection, tabctrl = getTabWindow(event, notebook)
      if selection == wx.wxNOT_FOUND then return end

      local mouse = wx.wxGetMouseState()
      local mx, my = mouse:GetX(), mouse:GetY()
      if mx < x or mx > x + w or my < y or my > y + h then
        -- disallow split as the target is outside the notebook
        local flags = notebook:GetWindowStyleFlag()
        if bit.band(flags, wxaui.wxAUI_NB_TAB_SPLIT) ~= 0 then
          notebook:SetWindowStyleFlag(flags - wxaui.wxAUI_NB_TAB_SPLIT)
        end

        if ide.wxver < "3.0" then
          -- don't allow dragging out single tabs from tab ctrl
          -- as wxwidgets doesn't like removing pages from split notebooks.
          if tabctrl:GetPageCount() == 1 then return end
        end

        -- don't allow last pages to be dragged out from Project and Output notebooks
        if (notebook == ide:GetProjectNotebook() or notebook == ide:GetOutputNotebook())
        and notebook:GetPageCount() == 1 then
          return
        end

        local label = notebook:GetPageText(selection)
        local pane = ide:RestorePanelByLabel(label)
        if pane then
          pane:FloatingPosition(mx-10, my-10)
          pane:Show()
          notebook:RemovePage(selection)
          mgr:Update()
          return
        end
      end

      -- set the selection on the dragged tab to reset its state
      -- workaround for wxwidgets issue http://trac.wxwidgets.org/ticket/15071
      notebook:SetSelection(selection)
      -- set focus on the content of the selected tab
      notebook:GetPage(selection):SetFocus()
    end)
end

local function createBottomNotebook(frame)
  -- bottomnotebook (errorlog,shellbox)
  local bottomnotebook = ide:CreateNotebook(frame, wx.wxID_ANY,
    wx.wxDefaultPosition, wx.wxDefaultSize,
    wxaui.wxAUI_NB_DEFAULT_STYLE + wxaui.wxAUI_NB_TAB_EXTERNAL_MOVE
    - wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB + wx.wxNO_BORDER)

  addDND(bottomnotebook)

  bottomnotebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CHANGED,
    function (event)
      -- skip setting focus when exiting, as it may cause looping on macOS
      if ide:IsExiting() then return end

      local nb = event:GetEventObject():DynamicCast("wxAuiNotebook")
      -- set focus on the new page
      local idx = event:GetSelection()
      if idx == wx.wxNOT_FOUND then return end
--      nb:GetPage(idx):SetFocus()

      local preview = isPreview(nb:GetPage(nb:GetSelection()))
      local flags = nb:GetWindowStyleFlag()
      if preview and bit.band(flags, wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB) == 0 then
        nb:SetWindowStyleFlag(flags + wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB)
      elseif not preview and bit.band(flags, wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB) ~= 0 then
        nb:SetWindowStyleFlag(flags - wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB)
      end
    end)

  -- disallow tabs closing
  bottomnotebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE,
    function (event)
      local nb = event:GetEventObject():DynamicCast("wxAuiNotebook")
      if isPreview(nb:GetPage(nb:GetSelection())) then
        event:Skip()
      else
        event:Veto()
      end
    end)

  local selection
  bottomnotebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_TAB_RIGHT_UP,
    function (event)
      -- event:GetSelection() returns the index *inside the current tab*;
      -- for split notebooks, this may not be the same as the index
      -- in the notebook we are interested in here
      local idx = event:GetSelection()
      if idx == wx.wxNOT_FOUND then return end
      local tabctrl = event:GetEventObject():DynamicCast("wxAuiTabCtrl")

      -- save tab index the event is for
      selection = bottomnotebook:GetPageIndex(tabctrl:GetPage(idx).window)

      local menu = ide:MakeMenu {
        { ID.CLOSE, TR("&Close Page") },
        { ID.CLOSESEARCHRESULTS, TR("Close Search Results Pages") },
        { },
        { ID.REFRESHSEARCHRESULTS, TR("Refresh Search Results") },
      }

      PackageEventHandle("onMenuOutputTab", menu, bottomnotebook, event, selection)

      -- popup statuses are not refreshed on Linux, so do it manually
      if ide.osname == "Unix" then UpdateMenuUI(menu, bottomnotebook) end
      bottomnotebook:PopupMenu(menu)
    end)

  bottomnotebook:Connect(ID.CLOSE, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:DoWhenIdle(function() bottomnotebook:DeletePage(selection) end)
    end)
  bottomnotebook:Connect(ID.CLOSE, wx.wxEVT_UPDATE_UI, function(event)
      event:Enable(isPreview(bottomnotebook:GetPage(selection)))
    end)

  bottomnotebook:Connect(ID.CLOSESEARCHRESULTS, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide:DoWhenIdle(function()
          for p = bottomnotebook:GetPageCount()-1, 0, -1 do
            if isPreview(bottomnotebook:GetPage(p)) then bottomnotebook:DeletePage(p) end
          end
        end)
    end)
  bottomnotebook:Connect(ID.CLOSESEARCHRESULTS, wx.wxEVT_UPDATE_UI, function(event)
      local ispreview = false
      for p = 0, bottomnotebook:GetPageCount()-1 do
        ispreview = ispreview or isPreview(bottomnotebook:GetPage(p))
      end
      event:Enable(ispreview)
    end)

  bottomnotebook:Connect(ID.REFRESHSEARCHRESULTS, wx.wxEVT_UPDATE_UI, function(event)
      event:Enable(isPreview(bottomnotebook:GetPage(selection)))
    end)
  bottomnotebook:Connect(ID.REFRESHSEARCHRESULTS, wx.wxEVT_COMMAND_MENU_SELECTED, function()
      ide.findReplace:RefreshResults(bottomnotebook:GetPage(selection))
    end)

  local errorlog = ide:CreateStyledTextCtrl(bottomnotebook, wx.wxID_ANY,
    wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxBORDER_NONE)

  errorlog:Connect(wx.wxEVT_CONTEXT_MENU,
    function (event)
      local menu = ide:MakeMenu {
          { ID.UNDO, TR("&Undo")..KSC(ID.UNDO) },
          { ID.REDO, TR("&Redo")..KSC(ID.REDO) },
          { },
          { ID.CUT, TR("Cu&t")..KSC(ID.CUT) },
          { ID.COPY, TR("&Copy")..KSC(ID.COPY) },
          { ID.PASTE, TR("&Paste")..KSC(ID.PASTE) },
          { ID.SELECTALL, TR("Select &All")..KSC(ID.SELECTALL) },
          { },
          { ID.CLEAROUTPUT, TR("C&lear Output Window")..KSC(ID.CLEAROUTPUT) },
        }
      PackageEventHandle("onMenuOutput", menu, errorlog, event)

      -- popup statuses are not refreshed on Linux, so do it manually
      if ide.osname == "Unix" then UpdateMenuUI(menu, errorlog) end
      errorlog:PopupMenu(menu)
    end)

  -- connect to the main frame, so it can be called from anywhere
  frame:Connect(ID.CLEAROUTPUT, wx.wxEVT_COMMAND_MENU_SELECTED,
    function(event) ide:GetOutput():Erase() end)

  local shellbox = ide:CreateStyledTextCtrl(bottomnotebook, wx.wxID_ANY,
    wx.wxDefaultPosition, wx.wxDefaultSize, wx.wxBORDER_NONE)

  local menupos
  shellbox:Connect(wx.wxEVT_CONTEXT_MENU,
    function (event)
      local menu = ide:MakeMenu {
          { ID.UNDO, TR("&Undo")..KSC(ID.UNDO) },
          { ID.REDO, TR("&Redo")..KSC(ID.REDO) },
          { },
          { ID.CUT, TR("Cu&t")..KSC(ID.CUT) },
          { ID.COPY, TR("&Copy")..KSC(ID.COPY) },
          { ID.PASTE, TR("&Paste")..KSC(ID.PASTE) },
          { ID.SELECTALL, TR("Select &All")..KSC(ID.SELECTALL) },
          { },
          { ID.SELECTCONSOLECOMMAND, TR("&Select Command") },
          { ID.CLEARCONSOLE, TR("C&lear Console Window")..KSC(ID.CLEARCONSOLE) },
        }
      menupos = event:GetPosition()
      PackageEventHandle("onMenuConsole", menu, shellbox, event)

      -- popup statuses are not refreshed on Linux, so do it manually
      if ide.osname == "Unix" then UpdateMenuUI(menu, shellbox) end
      shellbox:PopupMenu(menu)
    end)

  shellbox:Connect(ID.SELECTCONSOLECOMMAND, wx.wxEVT_COMMAND_MENU_SELECTED,
    function(event) ConsoleSelectCommand(menupos) end)

  -- connect to the main frame, so it can be called from anywhere
  frame:Connect(ID.CLEARCONSOLE, wx.wxEVT_COMMAND_MENU_SELECTED,
    function(event) ide:GetConsole():Erase() end)
  local iconsize = ide:GetBestIconSize()
  local bmp = ide:GetBitmap("OUTPUT", "PRINT", wx.wxSize(iconsize, iconsize))
  bottomnotebook:AddPage(errorlog, TR("Output"), true, bmp)
  bmp = ide:GetBitmap("OUTPUT", "CONSOLE", wx.wxSize(iconsize, iconsize))
  bottomnotebook:AddPage(shellbox, "Lua interpreter", false, bmp)
  bottomnotebook.errorlog = errorlog
  bottomnotebook.shellbox = shellbox

  frame.bottomnotebook = bottomnotebook
  return bottomnotebook
end

local function createProjNotebook(frame)
  local projnotebook = ide:CreateNotebook(frame, wx.wxID_ANY,
    wx.wxDefaultPosition, wx.wxDefaultSize,
    wxaui.wxAUI_NB_DEFAULT_STYLE + wxaui.wxAUI_NB_TAB_EXTERNAL_MOVE
    - wxaui.wxAUI_NB_CLOSE_ON_ACTIVE_TAB + wx.wxNO_BORDER)

  addDND(projnotebook)

  -- disallow tabs closing
  projnotebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE,
    function (event) event:Veto() end)

  frame.projnotebook = projnotebook
  return projnotebook
end

-- ----------------------------------------------------------------------------
-- Add the child windows to the frame

local frame = createFrame()
createToolBar(frame)
createNotebook(frame)
createProjNotebook(frame)
createBottomNotebook(frame)

do
  local mgr = frame.uimgr

  mgr:AddPane(frame.toolBar, wxaui.wxAuiPaneInfo():
    Name("toolbar"):Caption("Toolbar"):
    ToolbarPane():Top():CloseButton(false):PaneBorder(false):
    LeftDockable(false):RightDockable(false))
  mgr:AddPane(frame.notebook, wxaui.wxAuiPaneInfo():
    Name("notebook"):
    CenterPane():PaneBorder(false))
  mgr:AddPane(frame.projnotebook, wxaui.wxAuiPaneInfo():
    Name("projpanel"):CaptionVisible(false):
    MinSize(200,200):BestSize(300,300):FloatingSize(200,400):
    Left():Layer(1):Position(1):PaneBorder(false):
    CloseButton(true):MaximizeButton(false):PinButton(true))
  mgr:AddPane(frame.bottomnotebook, wxaui.wxAuiPaneInfo():
    Name("bottomnotebook"):CaptionVisible(false):
    MinSize(100,100):BestSize(200,200):FloatingSize(400,200):
    Bottom():Layer(1):Position(1):PaneBorder(false):
    CloseButton(true):MaximizeButton(false):PinButton(true))
  mgr:GetPane("watchpanel"):Hide()
  if type(ide.config.bordersize) == 'number' then
    for _, uimgr in pairs {mgr, frame.notebook:GetAuiManager(),
      frame.bottomnotebook:GetAuiManager(), frame.projnotebook:GetAuiManager()} do
      uimgr:GetArtProvider():SetMetric(wxaui.wxAUI_DOCKART_SASH_SIZE,
        -- scale bordersize on Windows, as macOS and Linux/GTK3 do the scaling
        ide.config.bordersize*(ide.osname == "Windows" and ide:GetContentScaleFactor() or 1))
    end
  end

  for _, nb in pairs {frame.bottomnotebook, frame.projnotebook} do
    nb:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK,
      function() PaneFloatToggle(nb) end)
  end

  for _, nb in pairs {frame.notebook, frame.bottomnotebook, frame.projnotebook} do
    nb:SetArtProvider(ide:GetTabArt())
  end

  mgr.defaultPerspective = mgr:SavePerspective()
end
