diff options
Diffstat (limited to 'love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua')
-rw-r--r-- | love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua | 735 |
1 files changed, 735 insertions, 0 deletions
diff --git a/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua b/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua new file mode 100644 index 0000000..004bcd0 --- /dev/null +++ b/love2dToAPK/tools/tools/zbstudio-win/src/editor/package.lua @@ -0,0 +1,735 @@ +-- Copyright 2013-15 Paul Kulchenko, ZeroBrane LLC +--------------------------------------------------------- + +local ide = ide +local iscaseinsensitive = wx.wxFileName("A"):SameAs(wx.wxFileName("a")) +local unpack = table.unpack or unpack +local q = EscapeMagic + +local function eventHandle(handlers, event, ...) + local success + for package, handler in pairs(handlers) do + local ok, res = pcall(handler, package, ...) + if ok then + if res == false then success = false end + else + DisplayOutputLn(TR("%s event failed: %s"):format(event, res)) + end + end + return success +end + +local function getEventHandlers(packages, event) + local handlers = {} + for _, package in pairs(packages) do + if package[event] then handlers[package] = package[event] end + end + return handlers +end + +function PackageEventHandle(event, ...) + return eventHandle(getEventHandlers(ide.packages, event), event, ...) +end + +function PackageEventHandleOnce(event, ...) + -- copy packages as the event that is handled only once needs to be removed + local handlers = getEventHandlers(ide.packages, event) + -- remove all handlers as they need to be called only once + -- this allows them to be re-installed if needed + for _, package in pairs(ide.packages) do package[event] = nil end + return eventHandle(handlers, event, ...) +end + +local function PackageEventHandleOne(file, event, ...) + local package = ide.packages[file] + if package and type(package[event]) == 'function' then + local ok, res = pcall(package[event], package, ...) + if ok then + if res == false then return false end + else + DisplayOutputLn(TR("%s event failed: %s"):format(event, res)) + end + end +end + +function PackageUnRegister(file, ...) + PackageEventHandleOne(file, "onUnRegister", ...) + -- remove from the list of installed packages + local package = ide.packages[file] + ide.packages[file] = nil + return package +end + +function PackageRegister(file, ...) + if not ide.packages[file] then + local packages = {} + local package = MergeFullPath( + GetPathWithSep(ide.editorFilename), "packages/"..file..".lua") + LoadLuaFileExt(packages, package, ide.proto.Plugin) + packages[file].fname = file + ide.packages[file] = packages[file] + end + return PackageEventHandleOne(file, "onRegister", ...) +end + +function ide:GetRootPath(path) + return MergeFullPath(GetPathWithSep(self.editorFilename), path or '') +end +function ide:GetPackagePath(packname) + return MergeFullPath( + self.oshome and MergeFullPath(self.oshome, '.'..self:GetAppName()..'/') or self:GetRootPath(), + MergeFullPath('packages', packname or '') + ) +end +function ide:GetApp() return self.editorApp end +function ide:GetAppName() return self.appname end +function ide:GetEditor(index) return GetEditor(index) end +function ide:GetEditorWithFocus(...) return GetEditorWithFocus(...) end +function ide:GetEditorWithLastFocus() + -- make sure ide.infocus is still a valid component and not "some" userdata + return (self:IsValidCtrl(self.infocus) + and self.infocus:GetClassInfo():GetClassName() == "wxStyledTextCtrl" + and self.infocus:DynamicCast("wxStyledTextCtrl") or nil) +end +function ide:GetMenuBar() return self.frame.menuBar end +function ide:GetStatusBar() return self.frame.statusBar end +function ide:GetToolBar() return self.frame.toolBar end +function ide:GetDebugger() return self.debugger end +function ide:GetMainFrame() return self.frame end +function ide:GetUIManager() return self.frame.uimgr end +function ide:GetDocument(ed) return ed and self.openDocuments[ed:GetId()] end +function ide:GetDocuments() return self.openDocuments end +function ide:GetKnownExtensions(ext) + local knownexts, extmatch = {}, ext and ext:lower() + for _, spec in pairs(self.specs) do + for _, ext in ipairs(spec.exts or {}) do + if not extmatch or extmatch == ext:lower() then + table.insert(knownexts, ext) + end + end + end + table.sort(knownexts) + return knownexts +end + +function ide:DoWhenIdle(func) table.insert(self.onidle, func) end + +function ide:FindTopMenu(item) + local index = self:GetMenuBar():FindMenu((TR)(item)) + return self:GetMenuBar():GetMenu(index), index +end +function ide:FindMenuItem(itemid, menu) + local item, imenu = self:GetMenuBar():FindItem(itemid, menu) + if menu and not item then item = menu:FindItem(itemid) end + if not item then return end + menu = menu or imenu + + for pos = 0, menu:GetMenuItemCount()-1 do + if menu:FindItemByPosition(pos):GetId() == itemid then + return item, menu, pos + end + end + return +end +function ide:AttachMenu(...) + -- AttachMenu([targetmenu,] id, submenu) + -- `targetmenu` is only needed for menus not attached to the main menubar + local menu, id, submenu = ... + if select('#', ...) == 2 then menu, id, submenu = nil, ... end + local item, menu, pos = self:FindMenuItem(id, menu) + if not item then return end + + local menuitem = wx.wxMenuItem(menu, id, item:GetItemLabel(), item:GetHelp(), wx.wxITEM_NORMAL, submenu) + menu:Destroy(item) + return menu:Insert(pos, menuitem), pos +end +function ide:CloneMenu(menu) + if not menu then return end + local newmenu = wx.wxMenu() + local node = menu:GetMenuItems():GetFirst() + while node do + local item = node:GetData():DynamicCast("wxMenuItem") + newmenu:Append(item:GetId(), item:GetItemLabel(), item:GetHelp(), item:GetKind()) + node = node:GetNext() + end + return newmenu +end + +function ide:FindDocument(path) + local fileName = wx.wxFileName(path) + for _, doc in pairs(self:GetDocuments()) do + if doc.filePath and fileName:SameAs(wx.wxFileName(doc.filePath)) then + return doc + end + end + return +end +function ide:FindDocumentsByPartialPath(path) + local seps = "[\\/]" + -- add trailing path separator to make sure full directory match + if not path:find(seps.."$") then path = path .. GetPathSeparator() end + local pattern = "^"..q(path):gsub(seps, seps) + local lpattern = pattern:lower() + + local docs = {} + for _, doc in pairs(self:GetDocuments()) do + if doc.filePath + and (doc.filePath:find(pattern) + or iscaseinsensitive and doc.filePath:lower():find(lpattern)) then + table.insert(docs, doc) + end + end + return docs +end +function ide:GetInterpreter() return self.interpreter end +function ide:GetInterpreters() return self.interpreters end +function ide:GetConfig() return self.config end +function ide:GetOutput() return self.frame.bottomnotebook.errorlog end +function ide:GetConsole() return self.frame.bottomnotebook.shellbox end +function ide:GetEditorNotebook() return self.frame.notebook end +function ide:GetOutputNotebook() return self.frame.bottomnotebook end +function ide:GetOutline() return self.outline end +function ide:GetProjectNotebook() return self.frame.projnotebook end +function ide:GetProject() return FileTreeGetDir() end +function ide:GetProjectStartFile() + local projectdir = FileTreeGetDir() + local startfile = self.filetree.settings.startfile[projectdir] + return MergeFullPath(projectdir, startfile), startfile +end +function ide:GetLaunchedProcess() return self.debugger and self.debugger.pid end +function ide:GetProjectTree() return self.filetree.projtreeCtrl end +function ide:GetOutlineTree() return self.outline.outlineCtrl end +function ide:GetWatch() return self.debugger and self.debugger.watchCtrl end +function ide:GetStack() return self.debugger and self.debugger.stackCtrl end + +local statusreset +function ide:SetStatusFor(text, interval, field) + field = field or 0 + interval = interval or 2 + local statusbar = self:GetStatusBar() + if not ide.timers.status then + ide.timers.status = wx.wxTimer(statusbar) + statusbar:Connect(wx.wxEVT_TIMER, function(event) if statusreset then statusreset() end end) + end + statusreset = function() + if statusbar:GetStatusText(field) == text then statusbar:SetStatusText("", field) end + end + ide.timers.status:Start(interval*1000, wx.wxTIMER_ONE_SHOT) + statusbar:SetStatusText(text, field) +end +function ide:SetStatus(text, field) self:GetStatusBar():SetStatusText(text, field or 0) end +function ide:GetStatus(field) return self:GetStatusBar():GetStatusText(field or 0) end +function ide:PushStatus(text, field) self:GetStatusBar():PushStatusText(text, field or 0) end +function ide:PopStatus(field) self:GetStatusBar():PopStatusText(field or 0) end +function ide:Yield() wx.wxYield() end +function ide:CreateBareEditor() return CreateEditor(true) end + +local rawMethods = {"AddTextDyn", "InsertTextDyn", "AppendTextDyn", "SetTextDyn", + "GetTextDyn", "GetLineDyn", "GetSelectedTextDyn", "GetTextRangeDyn"} +local useraw = nil + +function ide:CreateStyledTextCtrl(...) + local editor = wxstc.wxStyledTextCtrl(...) + if not editor then return end + + if useraw == nil then + useraw = true + for _, m in ipairs(rawMethods) do + if not pcall(function() return editor[m:gsub("Dyn", "Raw")] end) then useraw = false; break end + end + end + + -- map all `GetTextDyn` to `GetText` or `GetTextRaw` if `*Raw` methods are present + editor.useraw = useraw + for _, m in ipairs(rawMethods) do + -- some `*Raw` methods return `nil` instead of `""` as their "normal" calls do + -- (for example, `GetLineRaw` and `GetTextRangeRaw` for parameters outside of text) + local def = m:find("^Get") and "" or nil + editor[m] = function(...) return editor[m:gsub("Dyn", useraw and "Raw" or "")](...) or def end + end + + local suffix = "\1\0" + function editor:CopyDyn() + if not self.useraw then return self:Copy() end + -- check if selected fragment is a valid UTF-8 sequence + local text = self:GetSelectedTextRaw() + if text == "" or wx.wxString.FromUTF8(text) ~= "" then return self:Copy() end + local tdo = wx.wxTextDataObject() + -- append suffix as wxwidgets (3.1+ on Windows) truncate last char for odd-length strings + local workaround = ide.osname == "Windows" and (#text % 2 > 0) and suffix or "" + tdo:SetData(wx.wxDataFormat(wx.wxDF_TEXT), text..workaround) + local clip = wx.wxClipboard.Get() + clip:Open() + clip:SetData(tdo) + clip:Close() + end + + function editor:PasteDyn() + if not self.useraw then return self:Paste() end + local tdo = wx.wxTextDataObject() + local clip = wx.wxClipboard.Get() + clip:Open() + clip:GetData(tdo) + clip:Close() + local ok, text = tdo:GetDataHere(wx.wxDataFormat(wx.wxDF_TEXT)) + -- check if the fragment being pasted is a valid UTF-8 sequence + if not ok or text == "" or wx.wxString.FromUTF8(text) ~= "" then return self:Paste() end + if ide.osname == "Windows" then text = text:gsub(suffix.."+$","") end + self:AddTextRaw(text) + self:GotoPos(self:GetCurrentPos()) + end + + function editor:GotoPosEnforcePolicy(pos) + self:GotoPos(pos) + self:EnsureVisibleEnforcePolicy(self:LineFromPosition(pos)) + end + + function editor:CanFold() + local foldable = false + for m = 0, ide.MAXMARGIN do + if editor:GetMarginWidth(m) > 0 + and editor:GetMarginMask(m) == wxstc.wxSTC_MASK_FOLDERS then + foldable = true + end + end + return foldable + end + + -- circle through "fold all" => "hide base lines" => "unfold all" + function editor:FoldSome() + editor:Colourise(0, -1) -- update doc's folding info + local foldall = false -- at least on header unfolded => fold all + local hidebase = false -- at least one base is visible => hide all + local lines = editor:GetLineCount() + + for ln = 0, lines-1 do + local foldRaw = editor:GetFoldLevel(ln) + local foldLvl = foldRaw % 4096 + local foldHdr = (math.floor(foldRaw / 8192) % 2) == 1 + + -- at least one header is expanded + foldall = foldall or (foldHdr and editor:GetFoldExpanded(ln)) + + -- at least one base can be hidden + hidebase = hidebase or ( + not foldHdr + and ln > 1 -- first line can't be hidden, so ignore it + and foldLvl == wxstc.wxSTC_FOLDLEVELBASE + and bit.band(foldRaw, wxstc.wxSTC_FOLDLEVELWHITEFLAG) == 0 + and editor:GetLineVisible(ln)) + end + + -- shows lines; this doesn't change fold status for folded lines + if not foldall and not hidebase then editor:ShowLines(0, lines-1) end + + for ln = 0, lines-1 do + local foldRaw = editor:GetFoldLevel(ln) + local foldLvl = foldRaw % 4096 + local foldHdr = (math.floor(foldRaw / 8192) % 2) == 1 + + if foldall then + if foldHdr and editor:GetFoldExpanded(ln) then + editor:ToggleFold(ln) + end + elseif hidebase then + if not foldHdr and (foldLvl == wxstc.wxSTC_FOLDLEVELBASE) then + editor:HideLines(ln, ln) + end + else -- unfold all + if foldHdr and not editor:GetFoldExpanded(ln) then + editor:ToggleFold(ln) + end + end + end + editor:EnsureCaretVisible() + end + + local function getMarginWidth(editor) + local width = 0 + for m = 0, ide.MAXMARGIN do width = width + editor:GetMarginWidth(m) end + return width + end + + function editor:ShowPosEnforcePolicy(pos) + local line = self:LineFromPosition(pos) + self:EnsureVisibleEnforcePolicy(line) + -- skip the rest if line wrapping is on + if editor:GetWrapMode() ~= wxstc.wxSTC_WRAP_NONE then return end + local xwidth = self:GetClientSize():GetWidth() - getMarginWidth(self) + local xoffset = self:GetTextExtent(self:GetLineDyn(line):sub(1, pos-self:PositionFromLine(line)+1)) + self:SetXOffset(xoffset > xwidth and xoffset-xwidth or 0) + end + + function editor:ClearAny() + local length = self:GetLength() + local selections = ide.wxver >= "2.9.5" and self:GetSelections() or 1 + self:Clear() -- remove selected fragments + + -- check if the modification has failed, which may happen + -- if there is "invisible" text in the selected fragment. + -- if there is only one selection, then delete manually. + if length == self:GetLength() and selections == 1 then + self:SetTargetStart(self:GetSelectionStart()) + self:SetTargetEnd(self:GetSelectionEnd()) + self:ReplaceTarget("") + end + end + + function editor:MarkerGetAll(mask, from, to) + mask = mask or 2^24-1 + local markers = {} + local line = editor:MarkerNext(from or 0, mask) + while line > -1 do + table.insert(markers, {line, editor:MarkerGet(line)}) + if to and line > to then break end + line = editor:MarkerNext(line + 1, mask) + end + return markers + end + + editor:Connect(wx.wxEVT_KEY_DOWN, + function (event) + local keycode = event:GetKeyCode() + local mod = event:GetModifiers() + if (keycode == wx.WXK_DELETE and mod == wx.wxMOD_SHIFT) + or (keycode == wx.WXK_INSERT and mod == wx.wxMOD_CONTROL) + or (keycode == wx.WXK_INSERT and mod == wx.wxMOD_SHIFT) then + local id = keycode == wx.WXK_DELETE and ID.CUT or mod == wx.wxMOD_SHIFT and ID.PASTE or ID.COPY + ide.frame:AddPendingEvent(wx.wxCommandEvent(wx.wxEVT_COMMAND_MENU_SELECTED, id)) + elseif keycode == wx.WXK_CAPITAL and mod == wx.wxMOD_CONTROL then + -- ignore Ctrl+CapsLock + else + event:Skip() + end + end) + return editor +end + +function ide:LoadFile(...) return LoadFile(...) end + +function ide:CopyToClipboard(text) + if wx.wxClipboard:Get():Open() then + wx.wxClipboard:Get():SetData(wx.wxTextDataObject(text)) + wx.wxClipboard:Get():Close() + return true + end + return false +end + +function ide:GetSetting(path, setting) + local settings = self.settings + local curpath = settings:GetPath() + settings:SetPath(path) + local ok, value = settings:Read(setting) + settings:SetPath(curpath) + return ok and value or nil +end + +function ide:RemoveMenuItem(id, menu) + local _, menu, pos = self:FindMenuItem(id, menu) + if menu then + self:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED) + self:GetMainFrame():Disconnect(id, wx.wxID_ANY, wx.wxEVT_UPDATE_UI) + menu:Disconnect(id, wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED) + menu:Disconnect(id, wx.wxID_ANY, wx.wxEVT_UPDATE_UI) + menu:Remove(id) + + local positem = menu:FindItemByPosition(pos) + if (not positem or positem:GetKind() == wx.wxITEM_SEPARATOR) + and pos > 0 + and (menu:FindItemByPosition(pos-1):GetKind() == wx.wxITEM_SEPARATOR) then + menu:Destroy(menu:FindItemByPosition(pos-1)) + end + return true + end + return false +end + +function ide:ExecuteCommand(cmd, wdir, callback, endcallback) + local proc = wx.wxProcess(self:GetOutput()) + proc:Redirect() + + local cwd + if (wdir and #wdir > 0) then -- ignore empty directory + cwd = wx.wxFileName.GetCwd() + cwd = wx.wxFileName.SetCwd(wdir) and cwd + end + + local pid = wx.wxExecute(cmd, wx.wxEXEC_ASYNC, proc) + pid = pid ~= -1 and pid ~= 0 and pid or nil + if cwd then wx.wxFileName.SetCwd(cwd) end -- restore workdir + if not pid then return pid, wx.wxSysErrorMsg() end + + OutputSetCallbacks(pid, proc, callback or function() end, endcallback) + return pid +end + +function ide:CreateImageList(group, ...) + local _ = wx.wxLogNull() -- disable error reporting in popup + local size = wx.wxSize(16,16) + local imglist = wx.wxImageList(16,16) + for i = 1, select('#', ...) do + local icon, file = self:GetBitmap(select(i, ...), group, size) + if imglist:Add(icon) == -1 then + DisplayOutputLn(("Failed to add image '%s' to the image list.") + :format(file or select(i, ...))) + end + end + return imglist +end + +local tintdef = 100 +local function iconFilter(bitmap, tint) + if type(tint) == 'function' then return tint(bitmap) end + if type(tint) ~= 'table' or #tint ~= 3 then return bitmap end + + local tr, tg, tb = tint[1]/255, tint[2]/255, tint[3]/255 + local pi = 0.299*tr + 0.587*tg + 0.114*tb -- pixel intensity + local perc = (tint[0] or tintdef)/tintdef + tr, tg, tb = tr*perc, tg*perc, tb*perc + + local img = bitmap:ConvertToImage() + for x = 0, img:GetWidth()-1 do + for y = 0, img:GetHeight()-1 do + if not img:IsTransparent(x, y) then + local r, g, b = img:GetRed(x, y)/255, img:GetGreen(x, y)/255, img:GetBlue(x, y)/255 + local gs = (r + g + b) / 3 + local weight = 1-4*(gs-0.5)*(gs-0.5) + r = math.max(0, math.min(255, math.floor(255 * (gs + (tr-pi) * weight)))) + g = math.max(0, math.min(255, math.floor(255 * (gs + (tg-pi) * weight)))) + b = math.max(0, math.min(255, math.floor(255 * (gs + (tb-pi) * weight)))) + img:SetRGB(x, y, r, g, b) + end + end + end + return wx.wxBitmap(img) +end + +local icons = {} -- icon cache to avoid reloading the same icons +function ide:GetBitmap(id, client, size) + local im = self.config.imagemap + local width = size:GetWidth() + local key = width.."/"..id + local keyclient = key.."-"..client + local mapped = im[keyclient] or im[id.."-"..client] or im[key] or im[id] + -- mapped may be a file name/path or wxImage object; take that into account + if type(im[id.."-"..client]) == 'string' then keyclient = width.."/"..im[id.."-"..client] + elseif type(im[keyclient]) == 'string' then keyclient = im[keyclient] + elseif type(im[id]) == 'string' then + id = im[id] + key = width.."/"..id + keyclient = key.."-"..client + end + + local fileClient = self:GetAppName() .. "/res/" .. keyclient .. ".png" + local fileKey = self:GetAppName() .. "/res/" .. key .. ".png" + local isImage = type(mapped) == 'userdata' and mapped:GetClassInfo():GetClassName() == 'wxImage' + local file + if mapped and (isImage or wx.wxFileName(mapped):FileExists()) then file = mapped + elseif wx.wxFileName(fileClient):FileExists() then file = fileClient + elseif wx.wxFileName(fileKey):FileExists() then file = fileKey + else return wx.wxArtProvider.GetBitmap(id, client, size) end + local icon = icons[file] or iconFilter(wx.wxBitmap(file), self.config.imagetint) + icons[file] = icon + return icon, file +end + +function ide:AddPackage(name, package) + self.packages[name] = setmetatable(package, self.proto.Plugin) + self.packages[name].fname = name + return self.packages[name] +end +function ide:RemovePackage(name) self.packages[name] = nil end + +function ide:AddWatch(watch, value) + local mgr = self.frame.uimgr + local pane = mgr:GetPane("watchpanel") + if (pane:IsOk() and not pane:IsShown()) then + pane:Show() + mgr:Update() + end + + local watchCtrl = self.debugger.watchCtrl + if not watchCtrl then return end + + local root = watchCtrl:GetRootItem() + if not root or not root:IsOk() then return end + + local item = watchCtrl:GetFirstChild(root) + while true do + if not item:IsOk() then break end + if watchCtrl:GetItemExpression(item) == watch then + if value then watchCtrl:SetItemText(item, watch .. ' = ' .. tostring(value)) end + return item + end + item = watchCtrl:GetNextSibling(item) + end + + item = watchCtrl:AppendItem(root, watch, 1) + watchCtrl:SetItemExpression(item, watch, value) + return item +end + +function ide:AddInterpreter(name, interpreter) + self.interpreters[name] = setmetatable(interpreter, self.proto.Interpreter) + ProjectUpdateInterpreters() +end +function ide:RemoveInterpreter(name) + self.interpreters[name] = nil + ProjectUpdateInterpreters() +end + +function ide:AddSpec(name, spec) + self.specs[name] = spec + UpdateSpecs() +end +function ide:RemoveSpec(name) self.specs[name] = nil end + +function ide:AddAPI(type, name, api) + self.apis[type] = self.apis[type] or {} + self.apis[type][name] = api +end +function ide:RemoveAPI(type, name) self.apis[type][name] = nil end + +function ide:AddConsoleAlias(alias, table) return ShellSetAlias(alias, table) end +function ide:RemoveConsoleAlias(alias) return ShellSetAlias(alias, nil) end + +function ide:AddMarker(...) return StylesAddMarker(...) end +function ide:GetMarker(marker) return StylesGetMarker(marker) end +function ide:RemoveMarker(marker) StylesRemoveMarker(marker) end + +local indicators = {} +function ide:AddIndicator(indic, num) + num = num or indicators[indic] + if not num then -- new indicator; find the smallest available number + local nums = {} + for _, indicator in pairs(indicators) do + if indicator >= wxstc.wxSTC_INDIC_CONTAINER then + nums[indicator-wxstc.wxSTC_INDIC_CONTAINER+1] = true + end + end + num = #nums + wxstc.wxSTC_INDIC_CONTAINER + if num > wxstc.wxSTC_INDIC_MAX then return end + end + indicators[indic] = num + return num +end +function ide:GetIndicator(indic) return indicators[indic] end +function ide:GetIndicators() return indicators end +function ide:RemoveIndicator(indic) indicators[indic] = nil end + +-- this provides a simple stack for saving/restoring current configuration +local configcache = {} +function ide:AddConfig(name, files) + if not name or configcache[name] then return end -- don't overwrite existing slots + if type(files) ~= "table" then files = {files} end -- allow to pass one value + configcache[name] = { + config = require('mobdebug').dump(self.config, {nocode = true}), + configmeta = getmetatable(self.config), + packages = {}, + } + -- build a list of existing packages + local packages = {} + for package in pairs(self.packages) do packages[package] = true end + -- load config file(s) + for _, file in pairs(files) do LoadLuaConfig(MergeFullPath(name, file)) end + -- register newly added packages (if any) + for package in pairs(self.packages) do + if not packages[package] then -- this is a newly added package + PackageEventHandleOne(package, "onRegister") + configcache[name].packages[package] = true + end + end + ReApplySpecAndStyles() -- apply current config to the UI +end +function ide:RemoveConfig(name) + if not name or not configcache[name] then return end + -- unregister cached packages + for package in pairs(configcache[name].packages) do PackageUnRegister(package) end + -- load original config + local ok, res = LoadSafe(configcache[name].config) + if ok then + self.config = res + if configcache[name].configmeta then setmetatable(self.config, configcache[name].configmeta) end + else + DisplayOutputLn(("Error while restoring configuration: '%s'."):format(res)) + end + configcache[name] = nil -- clear the slot after use + ReApplySpecAndStyles() -- apply current config to the UI +end + +local panels = {} +function ide:AddPanel(ctrl, panel, name, conf) + local width, height = 360, 200 + local notebook = wxaui.wxAuiNotebook(self.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) + notebook:AddPage(ctrl, name, true) + notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_BG_DCLICK, + function() PaneFloatToggle(notebook) end) + notebook:Connect(wxaui.wxEVT_COMMAND_AUINOTEBOOK_PAGE_CLOSE, + function(event) event:Veto() end) + + local mgr = self.frame.uimgr + mgr:AddPane(notebook, wxaui.wxAuiPaneInfo(): + Name(panel):Float():CaptionVisible(false):PaneBorder(false): + MinSize(width/2,height/2): + BestSize(width,height):FloatingSize(width,height): + PinButton(true):Hide()) + if type(conf) == "function" then conf(mgr:GetPane(panel)) end + mgr.defaultPerspective = mgr:SavePerspective() -- resave default perspective + + panels[name] = {ctrl, panel, name, conf} + return mgr:GetPane(panel), notebook +end + +function ide:RemovePanel(panel) + local mgr = self.frame.uimgr + local pane = mgr:GetPane(panel) + if pane:IsOk() then + local win = pane.window + mgr:DetachPane(win) + win:Destroy() + mgr:Update() + end +end + +function ide:AddPanelDocked(notebook, ctrl, panel, name, conf, activate) + notebook:AddPage(ctrl, name, activate ~= false) + panels[name] = {ctrl, panel, name, conf} + return notebook +end +function ide:IsPanelDocked(panel) + local layout = self:GetSetting("/view", "uimgrlayout") + return layout and not layout:find(panel) +end + +function ide:IsValidCtrl(ctrl) + return ctrl and pcall(function() ctrl:GetId() end) +end + +function ide:IsValidProperty(ctrl, prop) + return ide:IsValidCtrl(ctrl) and pcall(function() return ctrl[prop] end) +end + +function ide:IsWindowShown(win) + while win do + if not win:IsShown() then return false end + win = win:GetParent() + end + return true +end + +function ide:RestorePanelByLabel(name) + if not panels[name] then return end + return self:AddPanel(unpack(panels[name])) +end + +function ide:AddTool(name, command, updateui) + return ToolsAddTool(name, command, updateui) +end + +function ide:RemoveTool(name) + return ToolsRemoveTool(name) +end |