-- Copyright 2011-16 Paul Kulchenko, ZeroBrane LLC
|
-- authors: Lomtik Software (J. Winwood & John Labenski)
|
-- Luxinia Dev (Eike Decker & Christoph Kubisch)
|
---------------------------------------------------------
|
|
local ide = ide
|
local layoutlabel = {
|
UIMANAGER = "uimgrlayout",
|
NOTEBOOK = "nblayout",
|
NOTEBOOKOUTPUT = "nbbtmlayout",
|
NOTEBOOKPROJECT = "nbprojlayout",
|
DOCKNOTEBOOK = "nbdocklayout",
|
DOCKNOTEBOOKOUTPUT = "nbbtmdocklayout",
|
DOCKNOTEBOOKPROJECT = "nbprojdocklayout",
|
STATUSBAR = "statusbar",
|
}
|
|
-- ----------------------------------------------------------------------------
|
-- Initialize the wxConfig for loading/saving the preferences
|
|
local ini = ide.config.ini
|
-- if ini path is relative and includes a directory name, make it relative to the IDE location
|
ini = ini and (not wx.wxIsAbsolutePath(ini) and wx.wxFileName(ini):GetDirCount() > 0
|
and MergeFullPath(GetPathWithSep(ide.editorFilename), ini) or ini)
|
-- check that the ini file doesn't point to a directory
|
if ini and (wx.wxFileName(ini):IsDir() or wx.wxIsAbsolutePath(ini) and wx.wxDirExists(ini)) then
|
ide:Print(("Can't use 'ini' configuration setting '%s' that points to a directory instead of a file; ignored.")
|
:format(ini))
|
ini = nil
|
end
|
-- check that the directory is writable
|
if ini and wx.wxIsAbsolutePath(ini) and not wx.wxFileName(ini):IsDirWritable() then
|
ide:Print(("Can't use 'ini' configuration setting '%s' that points to a non-writable directory; ignored.")
|
:format(ini))
|
ini = nil
|
end
|
|
local settings = wx.wxFileConfig(
|
ide:GetProperty("settingsapp"), ide:GetProperty("settingsvendor"), ini or "")
|
ide.settings = settings
|
|
local function settingsReadSafe(settings,what,default)
|
local cr,out = settings:Read(what,default)
|
return cr and out or default
|
end
|
|
-- ----------------------------------------------------------------------------
|
-- wxConfig load/save preferences functions
|
|
function SettingsRestoreFramePosition(window, windowName)
|
local path = settings:GetPath()
|
settings:SetPath("/"..windowName)
|
|
local clientX, clientY, clientWidth, clientHeight = wx.wxClientDisplayRect()
|
local s = tonumber(select(2,settings:Read("s", -1)))
|
local w = tonumber(select(2,settings:Read("w", math.floor(clientWidth*0.8))))
|
local h = tonumber(select(2,settings:Read("h", math.floor(clientHeight*0.8))))
|
local x = tonumber(select(2,settings:Read("x", clientX+math.floor(clientWidth-w)/2)))
|
local y = tonumber(select(2,settings:Read("y", clientY+math.floor(clientHeight-h)/2)))
|
|
if (s ~= -1) then
|
local clientX, clientY, clientWidth, clientHeight = wx.wxClientDisplayRect()
|
|
-- if left-top corner outside of the left-top side, reset it to the screen side
|
if x < clientX then x = clientX end
|
if y < clientY then y = clientY end
|
|
-- if the window is too wide for the screen, reset it to the screen size
|
if w > clientWidth then w = clientWidth end
|
if h > clientHeight then h = clientHeight end
|
|
-- if the right-bottom corner is still outside and there is only one display,
|
-- then reposition left-top corner, keeping the window centered
|
if wx.wxDisplay():GetCount() == 1 then
|
local outx = (x + w) - (clientX + clientWidth)
|
local outy = (y + h) - (clientY + clientHeight)
|
if outx > 0 then x = math.floor(0.5+(x - outx)/2) end
|
if outy > 0 then y = math.floor(0.5+(y - outy)/2) end
|
end
|
|
window:SetSize(x, y, w, h)
|
end
|
|
-- maximize after setting window position to make sure it's maximized on the correct monitor
|
if s == 1 then window:Maximize(true) end
|
|
settings:SetPath(path)
|
end
|
|
function SettingsSaveFramePosition(window, windowName)
|
local path = settings:GetPath()
|
settings:SetPath("/"..windowName)
|
|
local s = 0
|
local w, h = window:GetSizeWH()
|
local x, y = window:GetPositionXY()
|
|
if window:IsMaximized() then
|
s = 1
|
elseif window:IsIconized() then
|
s = 2
|
end
|
|
settings:Write("s", s==2 and 0 or s) -- iconized maybe - but that shouldnt be saved
|
settings:Write("x", x)
|
settings:Write("y", y)
|
settings:Write("w", w)
|
settings:Write("h", h)
|
|
settings:SetPath(path)
|
end
|
|
---
|
-- (table) SettingsRestoreFileHistory (function)
|
-- restores a list of recently loaded documents from the settings table
|
-- a table is returned which contains tables each with a filename key, pointing to
|
-- the filename
|
function SettingsRestoreFileHistory(fntab)
|
local path = settings:GetPath()
|
local listname = "/filehistory"
|
settings:SetPath(listname)
|
|
local outtab = {}
|
local inlist = {}
|
for id=1,ide.config.filehistorylength do
|
local couldread, name = settings:Read(tostring(id), "")
|
if not couldread or name == "" then break end
|
if not inlist[name] then
|
inlist[name] = true
|
table.insert(outtab,{filename = name})
|
end
|
end
|
|
if fntab then fntab(outtab) end
|
|
settings:SetPath(path)
|
|
return outtab
|
end
|
|
function SettingsSaveFileHistory (filehistory)
|
local listname = "/filehistory"
|
local path = settings:GetPath()
|
settings:DeleteGroup(listname)
|
settings:SetPath(listname)
|
|
for i,doc in ipairs(filehistory) do
|
settings:Write(tostring(i), doc.filename)
|
end
|
|
settings:SetPath(path)
|
end
|
|
---
|
-- () SettingsRestoreFileSession (function [, string section])
|
-- restores a list of opened files from the file settings
|
-- calls the given function with the restored table, a list
|
-- of tables containing tables like
|
-- {filename = "filename", cursorpos = <cursor position>}
|
function SettingsRestoreFileSession(fntab, section)
|
local listname = section or "/session"
|
local path = settings:GetPath()
|
settings:SetPath(listname)
|
local outtab = {}
|
local params = {}
|
local ismore, key, index = settings:GetFirstEntry("", 0)
|
while (ismore) do
|
local couldread, value = settings:Read(key, "")
|
if tonumber(key) then
|
local fname,cursorpos = value:match("^(.+);(.-)$")
|
if (couldread and value ~= "") then
|
outtab[tonumber(key)] =
|
{filename = fname or value, cursorpos = tonumber(cursorpos) or 0}
|
end
|
else
|
params[key] = tonumber(value) or value
|
end
|
ismore, key, index = settings:GetNextEntry(index)
|
end
|
|
if fntab then fntab(outtab, params) end
|
|
settings:SetPath(path)
|
|
return outtab
|
end
|
|
---
|
-- () SettingsSaveFileSession (table opendocs, table params [, string section])
|
-- saves the list of currently opened documents (passed in the opendocs table)
|
-- in the settings.
|
function SettingsSaveFileSession(opendocs, params, section)
|
local listname = section or "/session"
|
local path = settings:GetPath()
|
settings:DeleteGroup(listname)
|
settings:SetPath(listname)
|
|
for i,doc in ipairs(opendocs) do
|
settings:Write(tostring(i), doc.filename..";"..doc.cursorpos)
|
end
|
|
-- save all other parameters
|
for k,v in pairs(params) do settings:Write(k, v) end
|
|
settings:SetPath(path)
|
end
|
|
---
|
-- () SettingsRestoreProjectSession (function)
|
function SettingsRestoreProjectSession(fntab)
|
local listname = "/projectsession"
|
local path = settings:GetPath()
|
settings:SetPath(listname)
|
local outtab = {}
|
local couldread = true
|
local id = 1
|
local name
|
while (couldread) do
|
couldread, name = settings:Read(tostring(id), "")
|
couldread = couldread and name ~= ""
|
if (couldread) then
|
if (wx.wxDirExists(name)) then
|
table.insert(outtab,name)
|
|
local function projsession(...) ProjectConfig(name, {...}) end
|
SettingsRestoreFileSession(projsession, listname .. "/" .. tostring(id))
|
end
|
id = id + 1
|
end
|
end
|
|
if fntab then fntab(outtab) end
|
|
settings:SetPath(path)
|
|
return outtab
|
end
|
|
---
|
-- () SettingsSaveProjectSession (table projdirs)
|
-- saves the list of currently active projects
|
-- in the settings.
|
function SettingsSaveProjectSession(projdirs)
|
local listname = "/projectsession"
|
local path = settings:GetPath()
|
settings:DeleteGroup(listname)
|
settings:SetPath(listname)
|
|
for i,dir in ipairs(projdirs) do
|
settings:Write(tostring(i), dir)
|
|
local opendocs, params = ProjectConfig(dir)
|
if opendocs then
|
SettingsSaveFileSession(opendocs, params, listname .. "/" .. tostring(i))
|
end
|
end
|
|
settings:SetPath(path)
|
end
|
|
function SettingsRestorePackage(package)
|
local packagename = "/package/"..package
|
local path = settings:GetPath()
|
settings:SetPath(packagename)
|
local outtab = {}
|
local ismore, key, index = settings:GetFirstEntry("", 0)
|
while (ismore) do
|
local couldread, value = settings:Read(key, "")
|
if couldread then
|
local ok, res = LoadSafe("return "..value)
|
if ok then outtab[key] = res
|
else
|
outtab[key] = nil
|
ide:Print(("Couldn't load and ignored '%s' settings for package '%s': %s")
|
:format(key, package, res))
|
end
|
end
|
ismore, key, index = settings:GetNextEntry(index)
|
end
|
settings:SetPath(path)
|
return outtab
|
end
|
|
function SettingsSavePackage(package, values, opts)
|
local packagename = "/package/"..package
|
local path = settings:GetPath()
|
|
settings:DeleteGroup(packagename)
|
settings:SetPath(packagename)
|
for k,v in pairs(values or {}) do settings:Write(k, DumpPlain(v, opts)) end
|
settings:SetPath(path)
|
end
|
|
-----------------------------------
|
|
local function saveNotebook(nb)
|
local cnt = nb:GetPageCount()
|
|
local function addTo(tab,key,value)
|
local out = tab[key] or {}
|
table.insert(out,value)
|
tab[key] = out
|
end
|
|
local pagesX = {}
|
local pagesY = {}
|
|
local str = "nblayout|"
|
|
for i=1,cnt do
|
local pg = nb:GetPage(i-1)
|
local doc = ide:GetDocument(pg)
|
local id = doc and doc:GetTabText() or nb:GetPageText(i-1)
|
local x,y = pg:GetPosition():GetXY()
|
addTo(pagesX,x,id)
|
addTo(pagesY,y,id)
|
end
|
|
local function sortedPages(tab)
|
local t = {}
|
for i in pairs(tab) do
|
table.insert(t,i)
|
end
|
table.sort(t)
|
return t
|
end
|
|
local sortedX = sortedPages(pagesX)
|
local sortedY = sortedPages(pagesY)
|
|
-- for now only support "1D" splits and prefer
|
-- dimension which has more, anything else
|
-- requires a more complex algorithm, yet to do
|
|
local pagesUse
|
local sortedUse
|
local split
|
|
if ( #sortedX >= #sortedY) then
|
pagesUse = pagesX
|
sortedUse = sortedX
|
split = "<X>"
|
else
|
pagesUse = pagesY
|
sortedUse = sortedY
|
split = "<Y>"
|
end
|
|
for _, v in ipairs(sortedUse) do
|
local pages = pagesUse[v]
|
for _, id in ipairs(pages) do
|
str = str..id.."|"
|
end
|
str = str..split.."|"
|
end
|
|
return str
|
end
|
|
local function loadNotebook(nb,str,fnIdConvert)
|
str = str:match("nblayout|(.+)")
|
if (not str) then return end
|
local cnt = nb:GetPageCount()
|
local sel = nb:GetSelection()
|
|
-- store old pages
|
local currentpages, order = {}, {}
|
for i=1,cnt do
|
local pg = nb:GetPage(i-1)
|
local doc = ide:GetDocument(pg)
|
local id = doc and doc:GetTabText() or nb:GetPageText(i-1)
|
local newid = fnIdConvert and fnIdConvert(id) or id
|
currentpages[newid] = currentpages[newid] or {}
|
table.insert(currentpages[newid], {page = pg, text = id, index = i-1})
|
order[i] = newid
|
end
|
|
-- remove them
|
for i=cnt,1,-1 do nb:RemovePage(i-1) end
|
|
-- read them and perform splits
|
local t = 0
|
local newsel
|
local function finishPage(page)
|
if (page.index == sel) then
|
newsel = t
|
end
|
t = t + 1
|
end
|
|
local direction
|
local splits = { X = wx.wxRIGHT, Y = wx.wxBOTTOM }
|
for cmd in str:gmatch("([^|]+)") do
|
local instr = cmd:match("<(%w)>")
|
if (not instr) then
|
local id = fnIdConvert and fnIdConvert(cmd) or cmd
|
local pageind = next(currentpages[id] or {})
|
if (pageind) then
|
local page = currentpages[id][pageind]
|
currentpages[id][pageind] = nil
|
|
nb:AddPage(page.page, page.text)
|
if (direction) then nb:Split(t, direction) end
|
finishPage(page)
|
end
|
end
|
direction = instr and splits[instr]
|
end
|
|
-- add anything we forgot; make sure page groups are in the order specified
|
for i=1,cnt do
|
local pagelist = currentpages[order[i]]
|
for _,page in pairs(pagelist) do
|
nb:AddPage(page.page, page.text)
|
finishPage(page)
|
end
|
end
|
|
-- set the active page as it was before
|
if (newsel) then nb:SetSelection(newsel) end
|
end
|
|
function SettingsRestoreView()
|
local listname = "/view"
|
local path = settings:GetPath()
|
settings:SetPath(listname)
|
|
local frame = ide:GetMainFrame()
|
local uimgr = ide:GetUIManager()
|
|
local layoutcur = uimgr:SavePerspective()
|
local layout = settingsReadSafe(settings,layoutlabel.UIMANAGER,"")
|
if (layout ~= layoutcur) then
|
-- save the current toolbar besth and re-apply after perspective is loaded
|
-- bestw and besth have two separate issues:
|
-- (1) layout includes bestw that is only as wide as the toolbar size,
|
-- this leaves default background on the right side of the toolbar;
|
-- fix it by explicitly replacing with the screen width.
|
-- (2) besth may be wrong after icon size changes.
|
local toolbar = uimgr:GetPane("toolbar")
|
local besth = toolbar:IsOk() and tonumber(uimgr:SavePaneInfo(toolbar):match("besth=([^;]+)"))
|
|
-- reload the perspective if the saved one is not empty as it's different from the default
|
if #layout > 0 then uimgr:LoadPerspective(layout, true) end
|
|
local screenw = frame:GetClientSize():GetWidth()
|
if toolbar:IsOk() then toolbar:BestSize(screenw, besth or -1) end
|
|
-- check if debugging panes are not mentioned and float them
|
for _, name in pairs({"stackpanel", "watchpanel"}) do
|
local pane = uimgr:GetPane(name)
|
if pane:IsOk() and not layout:find(name) then pane:Float() end
|
end
|
|
-- check if the toolbar is not mentioned in the layout and show it
|
for _, name in pairs({"toolbar"}) do
|
local pane = uimgr:GetPane(name)
|
if pane:IsOk() and not layout:find(name) then pane:Show() end
|
end
|
|
-- remove captions from all panes
|
local panes = uimgr:GetAllPanes()
|
for index = 0, panes:GetCount()-1 do
|
uimgr:GetPane(panes:Item(index).name):CaptionVisible(false)
|
end
|
end
|
|
frame:GetStatusBar():Show(settingsReadSafe(settings,layoutlabel.STATUSBAR,true))
|
|
uimgr:Update()
|
|
layoutcur = saveNotebook(ide:GetOutputNotebook())
|
layout = settingsReadSafe(settings,layoutlabel.NOTEBOOKOUTPUT,layoutcur)
|
if (layout ~= layoutcur) then
|
loadNotebook(ide:GetOutputNotebook(),layout,
|
-- treat "Output (running)" same as "Output"
|
function(name) return name:match(TR("Output")) or name:match("Output") or name end)
|
end
|
|
layoutcur = saveNotebook(ide:GetProjectNotebook())
|
layout = settingsReadSafe(settings,layoutlabel.NOTEBOOKPROJECT,layoutcur)
|
if (layout ~= layoutcur) then
|
loadNotebook(ide:GetProjectNotebook(),layout)
|
end
|
|
-- always select Output tab
|
local bottomnotebook = ide:GetOutputNotebook()
|
local index = bottomnotebook:GetPageIndex(bottomnotebook.errorlog)
|
if index >= 0 then bottomnotebook:SetSelection(index) end
|
|
layoutcur = saveNotebook(frame.notebook)
|
layout = settingsReadSafe(settings,layoutlabel.NOTEBOOK,layoutcur)
|
if (layout ~= layoutcur) then
|
loadNotebook(ide.frame.notebook,layout)
|
local nb = frame.notebook
|
local cnt = nb:GetPageCount()
|
for i=0,cnt-1 do
|
ide:GetDocument(nb:GetPage(i)):SetTabText(nb:GetPageText(i))
|
end
|
end
|
|
-- restore configuration for notebook pages that have been split;
|
-- load saved dock_size values and update current values with saved ones
|
-- where dock_size configuration matches
|
for l, m in pairs({
|
[layoutlabel.DOCKNOTEBOOK] = ide:GetEditorNotebook():GetAuiManager(),
|
[layoutlabel.DOCKNOTEBOOKOUTPUT] = ide:GetOutputNotebook():GetAuiManager(),
|
[layoutlabel.DOCKNOTEBOOKPROJECT] = ide:GetProjectNotebook():GetAuiManager(),
|
}) do
|
-- ...|dock_size(5,0,0)=20|dock_size(2,1,0)=200|...
|
local prevlayout = settingsReadSafe(settings, l, "")
|
local curlayout = m:SavePerspective()
|
local newlayout = curlayout:gsub('(dock_size[^=]+=)(%d+)', function(t,v)
|
local val = prevlayout:match(EscapeMagic(t)..'(%d+)')
|
return t..(val or v)
|
end)
|
if newlayout ~= curlayout then m:LoadPerspective(newlayout) end
|
end
|
|
local editor = ide:GetEditor()
|
if editor then editor:SetFocus() end
|
|
settings:SetPath(path)
|
end
|
|
function SettingsSaveView()
|
local listname = "/view"
|
local path = settings:GetPath()
|
settings:DeleteGroup(listname)
|
settings:SetPath(listname)
|
|
local frame = ide.frame
|
local uimgr = frame.uimgr
|
|
settings:Write(layoutlabel.UIMANAGER, uimgr:SavePerspective())
|
settings:Write(layoutlabel.NOTEBOOK, saveNotebook(ide:GetEditorNotebook()))
|
settings:Write(layoutlabel.NOTEBOOKOUTPUT, saveNotebook(ide:GetOutputNotebook()))
|
settings:Write(layoutlabel.NOTEBOOKPROJECT, saveNotebook(ide:GetProjectNotebook()))
|
settings:Write(layoutlabel.DOCKNOTEBOOK, ide:GetEditorNotebook():GetAuiManager():SavePerspective())
|
settings:Write(layoutlabel.DOCKNOTEBOOKOUTPUT, ide:GetOutputNotebook():GetAuiManager():SavePerspective())
|
settings:Write(layoutlabel.DOCKNOTEBOOKPROJECT, ide:GetProjectNotebook():GetAuiManager():SavePerspective())
|
settings:Write(layoutlabel.STATUSBAR, frame:GetStatusBar():IsShown())
|
|
settings:SetPath(path)
|
end
|
|
function SettingsRestoreEditorSettings()
|
local listname = "/editor"
|
local path = settings:GetPath()
|
settings:SetPath(listname)
|
|
local interpreter = settingsReadSafe(settings, "interpreter",
|
ide.config.interpreter or ide.config.default.interpreter)
|
ProjectSetInterpreter(interpreter)
|
|
settings:SetPath(path)
|
end
|
|
function SettingsSaveEditorSettings()
|
local listname = "/editor"
|
local path = settings:GetPath()
|
settings:DeleteGroup(listname)
|
settings:SetPath(listname)
|
|
settings:Write("interpreter", ide.interpreter and ide.interpreter.fname or ide.config.default.interpreter)
|
|
settings:SetPath(path)
|
end
|
|
function SettingsSaveAll()
|
SettingsSaveFileSession(GetOpenFiles())
|
SettingsSaveEditorSettings()
|
SettingsSaveProjectSession(FileTreeGetProjects())
|
SettingsSaveFileHistory(GetFileHistory())
|
SettingsSaveView()
|
SettingsSaveFramePosition(ide.frame, "MainFrame")
|
end
|