TourGuide - Added better support for quest chains. Now uses the syntax "C Quest...
[WoW-TourGuide.git] / OptionHouse.lua
blob37e69b2769a3cded6316a8ab6c661f7d8de78987
1 --[[-------------------------------------------------------------------------
2 Copyright (c) 2006-2007, Dongle Development Team
3 All rights reserved.
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
9 * Redistributions of source code must retain the above copyright
10 notice, this list of conditions and the following disclaimer.
11 * Redistributions in binary form must reproduce the above
12 copyright notice, this list of conditions and the following
13 disclaimer in the documentation and/or other materials provided
14 with the distribution.
15 * Neither the name of the Dongle Development Team nor the names of
16 its contributors may be used to endorse or promote products derived
17 from this software without specific prior written permission.
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 ---------------------------------------------------------------------------]]
31 local major = "DongleStub"
32 local minor = tonumber(string.match("$Revision: 313 $", "(%d+)") or 1)
34 local g = getfenv(0)
36 if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
37 local lib = setmetatable({}, {
38 __call = function(t,k)
39 if type(t.versions) == "table" and t.versions[k] then
40 return t.versions[k].instance
41 else
42 error("Cannot find a library with name '"..tostring(k).."'", 2)
43 end
44 end
47 function lib:IsNewerVersion(major, minor)
48 local versionData = self.versions and self.versions[major]
50 -- If DongleStub versions have differing major version names
51 -- such as DongleStub-Beta0 and DongleStub-1.0-RC2 then a second
52 -- instance will be loaded, with older logic. This code attempts
53 -- to compensate for that by matching the major version against
54 -- "^DongleStub", and handling the version check correctly.
56 if major:match("^DongleStub") then
57 local oldmajor,oldminor = self:GetVersion()
58 if self.versions and self.versions[oldmajor] then
59 return minor > oldminor
60 else
61 return true
62 end
63 end
65 if not versionData then return true end
66 local oldmajor,oldminor = versionData.instance:GetVersion()
67 return minor > oldminor
68 end
70 local function NilCopyTable(src, dest)
71 for k,v in pairs(dest) do dest[k] = nil end
72 for k,v in pairs(src) do dest[k] = v end
73 end
75 function lib:Register(newInstance, activate, deactivate)
76 assert(type(newInstance.GetVersion) == "function",
77 "Attempt to register a library with DongleStub that does not have a 'GetVersion' method.")
79 local major,minor = newInstance:GetVersion()
80 assert(type(major) == "string",
81 "Attempt to register a library with DongleStub that does not have a proper major version.")
82 assert(type(minor) == "number",
83 "Attempt to register a library with DongleStub that does not have a proper minor version.")
85 -- Generate a log of all library registrations
86 if not self.log then self.log = {} end
87 table.insert(self.log, string.format("Register: %s, %s", major, minor))
89 if not self:IsNewerVersion(major, minor) then return false end
90 if not self.versions then self.versions = {} end
92 local versionData = self.versions[major]
93 if not versionData then
94 -- New major version
95 versionData = {
96 ["instance"] = newInstance,
97 ["deactivate"] = deactivate,
100 self.versions[major] = versionData
101 if type(activate) == "function" then
102 table.insert(self.log, string.format("Activate: %s, %s", major, minor))
103 activate(newInstance)
105 return newInstance
108 local oldDeactivate = versionData.deactivate
109 local oldInstance = versionData.instance
111 versionData.deactivate = deactivate
113 local skipCopy
114 if type(activate) == "function" then
115 table.insert(self.log, string.format("Activate: %s, %s", major, minor))
116 skipCopy = activate(newInstance, oldInstance)
119 -- Deactivate the old libary if necessary
120 if type(oldDeactivate) == "function" then
121 local major, minor = oldInstance:GetVersion()
122 table.insert(self.log, string.format("Deactivate: %s, %s", major, minor))
123 oldDeactivate(oldInstance, newInstance)
126 -- Re-use the old table, and discard the new one
127 if not skipCopy then
128 NilCopyTable(newInstance, oldInstance)
130 return oldInstance
133 function lib:GetVersion() return major,minor end
135 local function Activate(new, old)
136 -- This code ensures that we'll move the versions table even
137 -- if the major version names are different, in the case of
138 -- DongleStub
139 if not old then old = g.DongleStub end
141 if old then
142 new.versions = old.versions
143 new.log = old.log
145 g.DongleStub = new
148 -- Actually trigger libary activation here
149 local stub = g.DongleStub or lib
150 lib = stub:Register(lib, Activate)
153 --[[-------------------------------------------------------------------------
154 Begin Library Implementation
155 ---------------------------------------------------------------------------]]
156 local major = "OptionHouse-1.0"
157 local minor = tonumber(string.match("$Revision: 526 $", "(%d+)") or 1)
159 assert(DongleStub, string.format("%s requires DongleStub.", major))
161 if not DongleStub:IsNewerVersion(major, minor) then return end
163 local L = {
164 ["ERROR_NO_FRAME"] = "No frame returned for the addon \"%s\", category \"%s\", sub category \"%s\".",
165 ["NO_FUNC_PASSED"] = "You must associate a function with a category.",
166 ["IS_PRIVATEAPI"] = "You are trying to call a private api from a non-OptionHouse module.",
167 ["BAD_ARGUMENT"] = "bad argument #%d to '%s' (%s expected, got %s)",
168 ["MUST_CALL"] = "You must call '%s' from an OptionHouse addon object.",
169 ["ADDON_ALREADYREG"] = "The addon '%s' is already registered with OptionHouse.",
170 ["UNKNOWN_TAB"] = "No tab with the id %d exists, only %d tabs are registered.",
171 ["CATEGORY_ALREADYREG"] = "A category named '%s' already exists in '%s'",
172 ["NO_CATEGORYEXISTS"] = "No category named '%s' in '%s' exists.",
173 ["NO_SUBCATEXISTS"] = "No sub-category '%s' exists in '%s' for the addon '%s'.",
174 ["FRAME_DRAGGING"] = "Left Click + Drag to move.\nRight click to reset position.",
175 ["NO_PARENTCAT"] = "No parent category named '%s' exists in %s'",
176 ["SUBCATEGORY_ALREADYREG"] = "The sub-category named '%s' already exists in the category '%s' for '%s'",
177 ["OPTION_HOUSE"] = "Option House",
178 ["ENTERED_COMBAT"] = "|cFF33FF99Option House|r: Configuration window closed due to entering combat.",
179 ["SEARCH"] = "Search...",
180 ["ADDON_OPTIONS"] = "Addons",
181 ["VERSION"] = "Version: %s",
182 ["AUTHOR"] = "Author: %s",
183 ["TOTAL_CATEGORIES"] = "Categories: %d",
184 ["TOTAL_SUBCATEGORIES"] = "Sub Categories: %d",
185 ["TAB_MANAGEMENT"] = "Management",
186 ["TAB_PERFORMANCE"] = "Performance",
189 local function assert(level,condition,message)
190 if not condition then
191 error(message,level)
195 local function argcheck(value, num, ...)
196 if type(num) ~= "number" then
197 error(L["BAD_ARGUMENT"]:format(2, "argcheck", "number", type(num)), 1)
200 for i=1,select("#", ...) do
201 if type(value) == select(i, ...) then return end
204 local types = strjoin(", ", ...)
205 local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]")
206 error(L["BAD_ARGUMENT"]:format(num, name, types, type(value)), 3)
209 -- OptionHouse
210 local OptionHouse = {}
211 local tabfunctions = {}
212 local methods = {"RegisterCategory", "RegisterSubCategory", "RemoveCategory", "RemoveSubCategory"}
213 local addons = {}
214 local evtFrame
215 local frame
217 local function tabOnClick(self)
218 local id
219 if( type(self) ~= "number" ) then
220 id = self:GetID()
221 else
222 id = self
225 PanelTemplates_SetTab(frame, id)
227 for tabID, tab in pairs( tabfunctions ) do
228 if( tabID == id ) then
229 if( type( tab.func ) == "function" ) then
230 tab.func()
231 else
232 tab.handler[tab.func](tab.handler)
235 if( tab.type == "browse" ) then
236 OptionHouseFrameTopLeft:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-TopLeft")
237 OptionHouseFrameTop:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-Top")
238 OptionHouseFrameTopRight:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-TopRight")
239 OptionHouseFrameBotLeft:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-BotLeft")
240 OptionHouseFrameBot:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-Bot")
241 OptionHouseFrameBotRight:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-BotRight")
242 elseif( tab.type == "bid" ) then
243 OptionHouseFrameTopLeft:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Bid-TopLeft")
244 OptionHouseFrameTop:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Bid-Top")
245 OptionHouseFrameTopRight:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Bid-TopRight")
246 OptionHouseFrameBotLeft:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Bid-BotLeft")
247 OptionHouseFrameBot:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Bid-Bot")
248 OptionHouseFrameBotRight:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Bid-BotRight")
251 elseif( type( tab.func ) == "function" ) then
252 tab.func(true)
253 else
254 tab.handler[tab.func](tab.handler, true)
259 local function createTab(text, id)
260 local tab = getglobal("OptionHouseFrameTab" .. id)
261 if( not tab ) then
262 tab = CreateFrame("Button", "OptionHouseFrameTab" .. id, frame, "CharacterFrameTabButtonTemplate" )
263 frame.totalTabs = frame.totalTabs + 1
266 tab:SetID(id)
267 tab:SetScript("OnClick", tabOnClick)
268 tab:SetText(text)
269 tab:Show()
271 PanelTemplates_TabResize(0, tab)
272 PanelTemplates_SetNumTabs(frame, id)
274 if( id == 1 ) then
275 tab:SetPoint("TOPLEFT", frame, "BOTTOMLEFT", 15, 11)
276 else
277 tab:SetPoint("TOPLEFT", "OptionHouseFrameTab" .. id-1, "TOPRIGHT", -8, 0 )
281 local function focusGained(self)
282 if( self.searchText ) then
283 self.searchText = nil
284 self:SetText("")
285 self:SetTextColor(1, 1, 1, 1)
289 local function focusLost(self)
290 if( not self.searchText and string.trim(self:GetText()) == "" ) then
291 self.searchText = true
292 self:SetText(L["SEARCH"])
293 self:SetTextColor(0.90, 0.90, 0.90, 0.80)
297 local function createSearchInput(frame, onChange)
298 local input = CreateFrame("EditBox", frame:GetName() .. "Search", frame, "InputBoxTemplate")
299 input:SetHeight(19)
300 input:SetWidth(150)
301 input:SetAutoFocus(false)
302 input:ClearAllPoints()
303 input:SetPoint("CENTER", frame, "BOTTOMLEFT", 100, 25)
305 input.searchText = true
306 input:SetText(L["SEARCH"])
307 input:SetTextColor(0.90, 0.90, 0.90, 0.80)
308 input:SetScript("OnTextChanged", onChange)
309 input:SetScript("OnEditFocusGained", focusGained)
310 input:SetScript("OnEditFocusLost", focusLost)
312 return input
315 -- ADDON CONFIGURATION
316 local function showTooltip(self)
317 if( self.tooltip ) then
318 GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
319 GameTooltip:SetText(self.tooltip, nil, nil, nil, nil, 1)
323 local function hideTooltip()
324 GameTooltip:Hide()
327 local function sortCategories(a, b)
328 if( not b ) then
329 return false
332 return ( a.name < b.name )
335 -- Adds the actual row, will attempt to reuse the current row if able to
336 local function addCategoryRow(type, name, tooltip, data, parent, addon)
337 local frame = OptionHouseOptionsFrame
338 for i=1, #(frame.categories) do
339 -- Match type/name first
340 if( frame.categories[i].type == type and frame.categories[i].name == name ) then
341 -- Then make sure it's correct addons parent, if it's a category
342 if( ( parent and frame.categories[i].parent and frame.categories[i].parent == parent ) or ( not parent and not frame.categories[i].parent ) ) then
343 -- Now make sure it's the correct addon if it's a sub category
344 if( ( addon and frame.categories[i].addon and frame.categories[i].addon == addon ) or ( not addon and not frame.categories[i].addon ) ) then
345 frame.categories[i].tooltip = tooltip
346 frame.categories[i].data = data
347 return
353 table.insert(frame.categories, { name = name, type = type, tooltip = tooltip, data = data, parent = parent, addon = addon } )
354 frame.resortList = true
357 -- Remove a specific category and/or sub category listing
358 -- without needing to recreate the entire list
359 local function removeCategoryListing(addon, name)
360 local frame = OptionHouseOptionsFrame
361 for i=1, #(frame.categories) do
362 -- Remove the category requested
363 if( frame.categories[i]['type'] == "category" and frame.categories[i].name == name and frame.categories[i].addon == addon ) then
364 table.remove(frame.categories, i)
366 -- Remove all of it's sub categories
367 elseif( frame.categories[i]['type'] == "subcat" and frame.categories[i].parent == name and frame.categories[i].addon == addon ) then
368 table.remove(frame.categories, i)
373 local function removeSubCategoryListing(addon, parentCat, name)
374 local categories = OptionHouseOptionsFrame.categories
375 for i=1, #(categories) do
376 local cat = categories[i]
377 -- Remove the specific sub category
378 if( cat and cat['type'] == "subcat" and cat.name == name and cat.parent == parentCat and cat.addon == addon ) then
379 table.remove(categories, i)
380 i = i - 1
385 -- We have a seperate function for adding addons
386 -- so we can update a single addon out of the entire list
387 -- if it's categories/sub categories get changed, or a new ones added
388 local function addCategoryListing(name, addon)
389 local tooltip = addon.title or name
390 local data
392 if( addon.version ) then
393 tooltip = tooltip .. "\n" .. string.format(L["VERSION"], addon.version)
396 if( addon.author ) then
397 tooltip = tooltip .. "\n" .. string.format(L["AUTHOR"], addon.author)
400 -- One category, make clicking the addon open that category
401 if( addon.totalCats == 1 and addon.totalSubs == 0 ) then
402 for catName, cat in pairs(addon.categories) do
403 data = cat
404 data.parentCat = catName
405 break
408 -- Multiple categories, or sub categories
409 else
410 for catName, cat in pairs(addon.categories) do
411 cat.parentCat = catName
412 addCategoryRow("category", catName, cat.totalSubs > 0 and string.format(L["TOTAL_SUBCATEGORIES"], cat.totalSubs), cat, name)
414 for subCatName, subCat in pairs(cat.sub) do
415 subCat.parentCat = catName
416 addCategoryRow("subcat", subCatName, nil, subCat, catName, name)
421 if( not data ) then data = {} end
422 data.orderID = string.lower(name)
424 addCategoryRow("addon", name, addon.version and addon.author and tooltip, data)
427 -- Recreates the entire listing
428 local function createCategoryListing()
429 local frame = OptionHouseOptionsFrame
430 frame.categories = {}
432 for name, addon in pairs(addons) do
433 addCategoryListing(name, addon)
437 -- Displays the actual button
438 local function displayCategoryRow(type, text, data, tooltip, highlighted)
439 local frame = OptionHouseOptionsFrame
440 frame.totalRows = frame.totalRows + 1
441 if( frame.totalRows <= frame.offset or frame.rowID >= 15 ) then
442 return
445 frame.rowID = frame.rowID + 1
447 local button = OptionHouseOptionsFrame.buttons[frame.rowID]
448 local line = OptionHouseOptionsFrame.lines[frame.rowID]
450 if( highlighted ) then
451 button:LockHighlight()
452 else
453 button:UnlockHighlight()
456 if( type == "addon" ) then
457 button:SetText(text)
458 button:GetFontString():SetPoint("LEFT", button, "LEFT", 4, 0)
459 button:GetNormalTexture():SetAlpha(1.0)
460 line:Hide()
462 elseif( type == "category" ) then
463 button:SetText(HIGHLIGHT_FONT_COLOR_CODE..text..FONT_COLOR_CODE_CLOSE)
464 button:GetFontString():SetPoint("LEFT", button, "LEFT", 12, 0)
465 button:GetNormalTexture():SetAlpha(0.4)
466 line:Hide()
468 elseif( type == "subcat" ) then
469 button:SetText(HIGHLIGHT_FONT_COLOR_CODE..text..FONT_COLOR_CODE_CLOSE)
470 button:GetFontString():SetPoint("LEFT", button, "LEFT", 20, 0)
471 button:GetNormalTexture():SetAlpha(0.0)
472 line:SetTexCoord(0, 0.4375, 0, 0.625)
473 line:Show()
476 button.tooltip = tooltip
477 button.data = data
478 button.type = type
479 button.catText = text
480 button:Show()
483 local function updateConfigList()
484 local frame = OptionHouseOptionsFrame
485 frame.offset = FauxScrollFrame_GetOffset(frame.scroll)
486 frame.rowID = 0
487 frame.totalRows = 0
489 local searchBy = string.trim(string.lower(OptionHouseOptionsFrameSearch:GetText()))
490 if( searchBy == "" or OptionHouseOptionsFrameSearch.searchText ) then
491 searchBy = nil
494 -- Make sure stuff matches our search results
495 for id, row in pairs(frame.categories) do
496 if( searchBy and not string.match(string.lower(row.name), searchBy) ) then
497 frame.categories[id].hide = true
498 else
499 frame.categories[id].hide = nil
503 -- Resort list if needed
504 if( frame.resortList ) then
505 table.sort(frame.categories, sortCategories)
506 frame.resortList = true
509 -- Now display
510 for _, addon in pairs(frame.categories) do
511 if( not addon.hide and addon.type == "addon" ) then
512 -- Total addons
513 if( addon.name == frame.selectedAddon ) then
514 displayCategoryRow(addon.type, addon.name, addon.data, addon.tooltip, true)
516 for _, cat in pairs(frame.categories) do
517 -- Show all the categories with the addon as the parent
518 if( not cat.hide and cat.parent == addon.name and cat.type == "category" ) then
519 -- Total categories of the selected addon
520 if( cat.name == frame.selectedCategory ) then
521 displayCategoryRow(cat.type, cat.name, cat.data, cat.tooltip, true)
523 local rowID
524 for _, subCat in pairs(frame.categories) do
525 -- We don't have to check type, because it's the only one that has .addon set
526 if( not subCat.hide and subCat.parent == cat.name and subCat.addon == addon.name ) then
527 -- Total sub categories of the selected addons selected category
528 displayCategoryRow(subCat.type, subCat.name, subCat.data, subCat.tooltip, subCat.name == frame.selectedSubCat)
529 lastID = frame.rowID
533 -- Turns the line from straight down to a curve at the end
534 if( lastID ) then
535 frame.lines[lastID]:SetTexCoord(0.4375, 0.875, 0, 0.625)
537 else
538 displayCategoryRow(cat.type, cat.name, cat.data, cat.tooltip)
542 else
543 displayCategoryRow(addon.type, addon.name, addon.data, addon.tooltip)
548 FauxScrollFrame_Update(frame.scroll, frame.totalRows, 15, 20)
550 for i=1, 15 do
551 if( frame.totalRows > 15 ) then
552 frame.buttons[i]:SetWidth(140)
553 else
554 frame.buttons[i]:SetWidth(156)
557 -- We have less then 15 rows used
558 -- and our index is equal or past our current
559 if( frame.rowID < 15 and i > frame.rowID ) then
560 frame.buttons[i]:Hide()
565 local function openConfigFrame(data)
566 local frame = OptionHouseOptionsFrame
568 -- Clicking on an addon with multiple categories or sub categories will cause no data
569 if( not data ) then
570 -- Make sure the frames hidden when only the addon button is selected
571 if( frame.shownFrame ) then
572 frame.shownFrame:Hide()
574 return
577 if( data.handler or data.func ) then
578 data.frame = nil
580 if( type(data.func) == "string" ) then
581 data.frame = data.handler[data.func](data.handler, data.parentCat or frame.selectedCategory, frame.selectedSubCat)
582 elseif( type(data.handler) == "function" ) then
583 data.frame = data.handler(data.parentCat or frame.selectedCategory, frame.selectedSubCat)
586 -- Mostly this is for authors, but it lets us clean up the logic a bit
587 if( not data.frame ) then
588 error(string.format(L["ERROR_NO_FRAME"], frame.selectedAddon, data.parentCat or frame.selectedCategory, frame.selectedSubCat), 3)
591 -- Validate location/width/height and force parent
592 if( not data.frame:GetPoint() ) then
593 data.frame:SetPoint("TOPLEFT", frame, "TOPLEFT", 190, -103)
596 if( data.frame:GetWidth() > 630 or data.frame:GetWidth() == 0 ) then
597 data.frame:SetWidth(630)
600 if( data.frame:GetHeight() > 305 or data.frame:GetHeight() == 0 ) then
601 data.frame:SetHeight(305)
604 data.frame:SetParent(frame)
605 data.frame:SetFrameStrata("HIGH")
607 if( not data.noCache ) then
608 local category
610 -- Figure out which category we're modifying
611 if( frame.selectedSubCat ~= "" ) then
612 category = addons[frame.selectedAddon].categories[frame.selectedCategory].sub[frame.selectedSubCat]
613 elseif( frame.selectedCategory ~= "" ) then
614 category = addons[frame.selectedAddon].categories[frame.selectedCategory]
615 elseif( frame.selectedAddon ~= "" ) then
616 for catName, _ in pairs(addons[frame.selectedAddon].categories) do
617 category = addons[frame.selectedAddon].categories[catName]
621 -- Remove the handler/func and save the frame for next time
622 if( category ) then
623 data.frame.time = GetTime()
624 category.handler = nil
625 category.func = nil
626 category.frame = data.frame
631 if( frame.shownFrame ) then
632 frame.shownFrame:Hide()
635 -- Now show the current one
636 if( data.frame and frame.selectedAddon ~= "" ) then
637 data.frame:Show()
638 frame.shownFrame = data.frame
642 local function expandConfigList(self)
643 local frame = OptionHouseOptionsFrame
645 if( self.type == "addon" ) then
646 if( frame.selectedAddon == self.catText ) then
647 frame.selectedAddon = ""
648 else
649 frame.selectedAddon = self.catText
652 frame.selectedCategory = ""
653 frame.selectedSubCat = ""
655 elseif( self.type == "category" ) then
656 if( frame.selectedCategory == self.catText ) then
657 frame.selectedCategory = ""
658 self.data = nil
659 else
660 frame.selectedCategory = self.catText
663 frame.selectedSubCat = ""
665 elseif( self.type == "subcat" ) then
666 if( frame.selectedSubCat == self.catText ) then
667 frame.selectedSubCat = ""
669 -- Make sure the frame gets hidden when deselecting
670 self.data = addons[frame.selectedAddon].categories[frame.selectedCategory]
671 else
672 frame.selectedSubCat = self.catText
676 openConfigFrame(self.data)
677 updateConfigList()
681 local function addonConfigTab(hide)
682 local name = "OptionHouseOptionsFrame"
683 local frame = getglobal(name)
685 if( frame and hide ) then
686 frame:Hide()
687 return
688 elseif( hide ) then
689 return
690 elseif( not frame ) then
691 frame = CreateFrame("Frame", name, OptionHouseFrame)
692 frame:SetFrameStrata("MEDIUM")
693 frame:SetAllPoints(OptionHouseFrame)
695 frame.buttons = {}
696 frame.lines = {}
697 for i=1, 15 do
698 local button = CreateFrame("Button", name.."FilterButton"..i, frame)
699 frame.buttons[i] = button
701 button:SetHighlightFontObject(GameFontHighlightSmall)
702 button:SetTextFontObject(GameFontNormalSmall)
703 button:SetScript("OnClick", expandConfigList)
704 button:SetScript("OnEnter", showTooltip)
705 button:SetScript("OnLeave", hideTooltip)
706 button:SetWidth(140)
707 button:SetHeight(20)
709 button:SetNormalTexture("Interface\\AuctionFrame\\UI-AuctionFrame-FilterBG")
710 button:GetNormalTexture():SetTexCoord(0, 0.53125, 0, 0.625)
712 button:SetHighlightTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight")
713 button:GetHighlightTexture():SetBlendMode("ADD")
715 -- For sub categories only
716 local line = button:CreateTexture(name.."FilterButton"..i.."Line", "BACKGROUND")
717 frame.lines[i] = line
719 line:SetWidth(7)
720 line:SetHeight(20)
721 line:SetPoint("LEFT", 13, 0)
722 line:SetTexture( "Interface\\AuctionFrame\\UI-AuctionFrame-FilterLines" )
723 line:SetTexCoord(0, 0.4375, 0, 0.625)
725 if( i > 1 ) then
726 button:SetPoint("TOPLEFT", name.."FilterButton"..i - 1, "BOTTOMLEFT", 0, 0)
727 else
728 button:SetPoint("TOPLEFT", 23, -105)
732 frame.scroll = CreateFrame("ScrollFrame", name.."FilterScrollFrame", frame, "FauxScrollFrameTemplate")
733 frame.scroll:SetWidth(160)
734 frame.scroll:SetHeight(305)
735 frame.scroll:SetPoint("TOPRIGHT", frame, "TOPLEFT", 158, -105)
736 frame.scroll:SetScript("OnVerticalScroll", function()
737 FauxScrollFrame_OnVerticalScroll(20, updateConfigList)
738 end )
740 local texture = frame.scroll:CreateTexture(name.."FilterScrollFrameScrollUp", "ARTWORK")
741 texture:SetWidth(31)
742 texture:SetHeight(256)
743 texture:SetPoint("TOPLEFT", frame.scroll, "TOPRIGHT", -2, 5 )
744 texture:SetTexCoord(0, 0.484375, 0, 1.0)
746 local texture = frame.scroll:CreateTexture(name.."FilterScrollFrameScrollDown", "ARTWORK")
747 texture:SetWidth(31)
748 texture:SetHeight(256)
749 texture:SetPoint("BOTTOMLEFT", frame.scroll, "BOTTOMRIGHT", -2, -2 )
750 texture:SetTexCoord(0.515625, 1.0, 0, 0.4140625)
752 createSearchInput(frame, updateConfigList )
753 createCategoryListing()
756 -- Reset selection
757 frame.selectedAddon = ""
758 frame.selectedCategory = ""
759 frame.selectedSubCat = ""
761 -- Hide the open config frame
762 if( frame.shownFrame ) then
763 frame.shownFrame:Hide()
766 updateConfigList()
767 frame:Show()
770 local function createOHFrame()
771 local name = "OptionHouseFrame"
773 if( getglobal(name) ) then
774 return
777 table.insert(UISpecialFrames, name)
779 frame = CreateFrame("Frame", name, UIParent)
780 frame:CreateTitleRegion()
781 frame:SetClampedToScreen(true)
782 frame:SetFrameStrata("MEDIUM")
783 frame:SetWidth(832)
784 frame:SetHeight(447)
785 frame:SetPoint("TOPLEFT", 0, -104)
786 frame.totalTabs = 0
788 local title = frame:GetTitleRegion()
789 title:SetWidth(757)
790 title:SetHeight(20)
791 title:SetPoint("TOPLEFT", 75, -15)
793 -- Embedded version wont include the icon cause authors are more whiny then users
794 -- Also, we want to use different methods of frame dragging
795 if( not IsAddOnLoaded("OptionHouse") ) then
796 local texture = frame:CreateTexture(name.."PortraitTexture", "OVERLAY")
797 texture:SetWidth(57)
798 texture:SetHeight(57)
799 texture:SetPoint("TOPLEFT", 9, -7)
800 SetPortraitTexture(texture, "player")
802 frame:EnableMouse(true)
803 else
804 local texture = frame:CreateTexture(name.."PortraitTexture", "OVERLAY")
805 texture:SetWidth(128)
806 texture:SetHeight(128)
807 texture:SetPoint("TOPLEFT", 9, -2)
808 texture:SetTexture("Interface\\AddOns\\OptionHouse\\GnomePortrait")
810 frame:EnableMouse(false)
811 frame:SetMovable(not OptionHouseDB.locked)
813 if( OptionHouseDB.position ) then
814 frame:SetPoint("TOPLEFT", nil, "BOTTOMLEFT", OptionHouseDB.position.x, OptionHouseDB.position.y)
817 -- This goes in the entire bar where "Option House" title text is
818 local mover = CreateFrame("Button", name .. "Dragger", frame)
819 mover:SetPoint("TOP", 25, -15)
820 mover:SetHeight(19)
821 mover:SetWidth(730)
822 --mover.tooltip = L["FRAME_DRAGGING"]
823 mover:SetScript("OnLeave", hideTooltip)
824 mover:SetScript("OnEnter", showTooltip)
825 mover:SetScript("OnMouseUp", function(self)
826 local parent = self:GetParent()
827 parent:StopMovingOrSizing()
828 OptionHouseDB.position = {x = parent:GetLeft(), y = parent:GetTop()}
829 end)
830 mover:SetScript("OnMouseDown", function(self, mouse)
831 local parent = self:GetParent()
832 if( mouse == "LeftButton" and parent:IsMovable() ) then
833 parent:StartMoving()
834 elseif( mouse == "RightButton" ) then
835 OptionHouseDB.position = nil
836 parent:ClearAllPoints()
837 parent:SetPoint("TOPLEFT", 0, -104)
839 end)
842 local title = frame:CreateFontString(name.."Title", "OVERLAY")
843 title:SetFontObject(GameFontNormal)
844 title:SetPoint("TOP", 0, -18)
845 title:SetText(L["OPTION_HOUSE"])
847 local texture = frame:CreateTexture(name.."TopLeft", "ARTWORK")
848 texture:SetWidth(256)
849 texture:SetHeight(256)
850 texture:SetPoint("TOPLEFT", 0, 0)
851 texture:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-TopLeft")
853 local texture = frame:CreateTexture(name.."Top", "ARTWORK")
854 texture:SetWidth(320)
855 texture:SetHeight(256)
856 texture:SetPoint("TOPLEFT", 256, 0)
857 texture:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-Top")
859 local texture = frame:CreateTexture(name.."TopRight", "ARTWORK")
860 texture:SetWidth(256)
861 texture:SetHeight(256)
862 texture:SetPoint("TOPLEFT", name.."Top", "TOPRIGHT", 0, 0)
863 texture:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-TopRight")
865 local texture = frame:CreateTexture(name.."BotLeft", "ARTWORK")
866 texture:SetWidth(256)
867 texture:SetHeight(256)
868 texture:SetPoint("TOPLEFT", 0, -256)
869 texture:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-BotLeft")
871 local texture = frame:CreateTexture(name.."Bot", "ARTWORK")
872 texture:SetWidth(320)
873 texture:SetHeight(256)
874 texture:SetPoint("TOPLEFT", 256, -256)
875 texture:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-Bot")
877 local texture = frame:CreateTexture(name.."BotRight", "ARTWORK")
878 texture:SetWidth(256)
879 texture:SetHeight(256)
880 texture:SetPoint("TOPLEFT", name.."Bot", "TOPRIGHT", 0, 0)
881 texture:SetTexture("Interface\\AuctionFrame\\UI-AuctionFrame-Browse-BotRight")
883 local tabs = {{func = addonConfigTab, text = L["ADDON_OPTIONS"], type = "browse"}}
884 createTab(L["ADDON_OPTIONS"], 1)
886 for id, tab in pairs(tabfunctions) do
887 table.insert(tabs, tab)
888 createTab(tab.text, id + 1)
891 tabfunctions = tabs
894 local button = CreateFrame("Button", name.."CloseButton", frame, "UIPanelCloseButton")
895 button:SetPoint("TOPRIGHT", 3, -8)
896 button:SetScript("OnClick", function()
897 HideUIPanel(frame)
898 end)
901 -- PRIVATE API's
902 -- These are only to be used for the standalone OptionHouse modules
903 function OptionHouse:CreateSearchInput(frame, onChange)
904 return createSearchInput(frame, onChange)
907 function OptionHouse.RegisterTab(self, text, func, type)
908 -- Simple, effective you can't register a tab unless we list it here
909 -- I highly doubt will ever need to add another one
910 if( text ~= L["TAB_MANAGEMENT"] and text ~= L["TAB_PERFORMANCE"] ) then return end
912 table.insert(tabfunctions, {func = func, handler = self, text = text, type = type})
914 -- Will create all of the tabs when the frame is created if needed
915 if( not frame ) then
916 return
919 createTab(text, #(tabfunctions))
922 function OptionHouse.UnregisterTab(self, text)
923 for i=#(tabfunctions), 1, -1 do
924 if( tabfunctions[i].text == text ) then
925 table.remove(tabfunctions, i)
929 for i=1, frame.totalTabs do
930 if( tabfunctions[i] ) then
931 createTab(tabfunctions[i].text, i)
932 else
933 getglobal("OptionHouseFrameTab" .. i):Hide()
938 function OptionHouse.GetAddOnData(self, name)
939 if( not addons[name] ) then
940 return nil, nil, nil
943 return addons[name].title, addons[name].author, addons[name].version
946 -- PUBLIC API's
947 function OptionHouse:Open(addonName, parentCat, childCat)
948 argcheck(addonName, 1, "string", "nil")
949 argcheck(parentCat, 2, "string", "nil")
950 argcheck(childCat, 3, "string", "nil")
952 createOHFrame()
953 tabOnClick(1)
955 if( not addonName ) then
956 frame:Show()
957 return
960 -- Cleanest method for getting the func/handler/ect
961 -- to auto open to a page
962 for name, addon in pairs(addons) do
963 if( name == addonName ) then
964 OptionHouseOptionsFrame.selectedAddon = addonName
965 for catName, cat in pairs(addon.categories) do
966 if( catName == parentCat ) then
967 OptionHouseOptionsFrame.selectedCategory = catName
968 -- Searching for a sub cat
969 if( subCat and cat.totalSubs > 0 ) then
970 for subCatName, subCat in pairs(cat.sub) do
971 -- Found sub cat, open it
972 if( subCatName == childCat ) then
973 OptionHouseOptionsFrame.selectedSubCat = subCatName
974 openConfigFrame(subCat)
975 break
979 -- Searching for a category not a sub cat
980 elseif( not subCat ) then
981 openConfigFrame(cat)
983 break
986 break
990 -- Now expand anything that was selected
991 updateConfigList()
992 frame:Show()
995 function OptionHouse:OpenTab(id)
996 argcheck(id, 1, "number")
997 assert(3, #(tabfunctions) > id, string.format(L["UNKNOWN_TAB"], id, #(tabfunctions)))
999 createOHFrame()
1000 tabOnClick(id)
1001 frame:Show()
1004 function OptionHouse:RegisterAddOn( name, title, author, version )
1005 argcheck(name, 1, "string")
1006 argcheck(title, 2, "string", "nil")
1007 argcheck(author, 3, "string", "nil")
1008 argcheck(version, 4, "string", "number", "nil")
1009 assert(3, not addons[name], string.format(L["ADDON_ALREADYREG"], name))
1011 addons[name] = {title = title, author = author, version = version, totalCats = 0, totalSubs = 0, categories = {}}
1012 addons[name].obj = {name = name}
1014 -- So we can upgrade the function pointer if a newer rev is found
1015 for id, method in pairs(methods) do
1016 addons[name].obj[method] = OptionHouse[method]
1019 if( OptionHouseOptionsFrame ) then
1020 addCategoryListing(name, addons[name])
1021 updateConfigList()
1024 return addons[name].obj
1027 function OptionHouse.RegisterCategory( addon, name, handler, func, noCache )
1028 argcheck(name, 2, "string")
1029 argcheck(handler, 3, "string", "function", "table")
1030 argcheck(func, 4, "string", "function", "nil")
1031 argcheck(noCache, 5, "boolean", "number", "nil")
1032 assert(3, handler or func, L["NO_FUNC_PASSED"])
1033 assert(3, addons[addon.name], string.format(L["MUST_CALL"], addon.name))
1034 assert(3, addons[addon.name].categories, string.format(L["CATEGORY_ALREADYREG"], name, addon.name))
1036 -- Category numbers are required so we know when to skip it because only one category/sub cat exists
1037 addons[addon.name].totalCats = addons[addon.name].totalCats + 1
1038 addons[addon.name].categories[name] = {func = func, handler = handler, noCache = noCache, sub = {}, orderID = addons[addon.name].totalCats, totalSubs = 0}
1040 if( OptionHouseOptionsFrame ) then
1041 addCategoryListing(addon.name, addons[addon.name])
1042 updateConfigList()
1046 function OptionHouse.RegisterSubCategory( addon, parentCat, name, handler, func, noCache )
1047 argcheck(parentCat, 2, "string")
1048 argcheck(name, 3, "string")
1049 argcheck(handler, 4, "string", "function", "table")
1050 argcheck(func, 5, "string", "function", "nil")
1051 argcheck(noCache, 6, "boolean", "number", "nil")
1052 assert(3, handler or func, L["NO_FUNC_PASSED"])
1053 assert(3, addons[addon.name], string.format(L["MUST_CALL"], addon.name))
1054 assert(3, addons[addon.name].categories[parentCat], string.format(L["NO_PARENTCAT"], parentCat, addon.name))
1055 assert(3, not addons[addon.name].categories[parentCat].sub[name], string.format(L["SUBCATEGORY_ALREADYREG"], name, parentCat, addon.name))
1057 addons[addon.name].totalSubs = addons[addon.name].totalSubs + 1
1058 addons[addon.name].categories[parentCat].totalSubs = addons[addon.name].categories[parentCat].totalSubs + 1
1059 addons[addon.name].categories[parentCat].sub[name] = {handler = handler, func = func, noCache = noCache, orderID = addons[addon.name].categories[parentCat].totalSubs}
1061 if( OptionHouseOptionsFrame ) then
1062 addCategoryListing(addon.name, addons[addon.name])
1063 updateConfigList()
1067 -- I'll improve the Remove* to only remove the category from the listing instead of
1068 -- recreating the entire list
1069 function OptionHouse.RemoveCategory( addon, name )
1070 argcheck(name, 2, "string")
1071 assert(3, addons[addon.name], string.format(L["MUST_CALL"], addon.name))
1072 assert(3, not addons[addon.name].categories[name], string.format(L["NO_CATEGORYEXISTS"], name, addon.name))
1074 addons[addon.name].totalCats = addons[addon.name].totalCats - 1
1075 addons[addon.name].categories[name] = nil
1077 if( OptionHouseOptionsFrame ) then
1078 removeCategoryListing(addon.name, name)
1079 updateConfigList()
1083 function OptionHouse.RemoveSubCategory( addon, parentCat, name )
1084 argcheck(parentCat, 2, "string")
1085 argcheck(name, 2, "string")
1086 assert(3, addons[addon.name], string.format(L["MUST_CALL"], addon.name))
1087 assert(3, addons[addon.name].categories[parentCat], string.format(L["NO_PARENTCAT"], name, addon.name))
1088 assert(3, addons[addon.name].categories[parentCat].sub[name], string.format(L["NO_SUBCATEXISTS"], name, parentCat, addon.name))
1090 addons[addon.name].totalSubs = addons[addon.name].totalSubs - 1
1091 addons[addon.name].categories[parentCat].totalSubs = addons[addon.name].categories[parentCat].totalSubs - 1
1092 addons[addon.name].categories[parentCat].sub[name] = nil
1094 if( OptionHouseOptionsFrame ) then
1095 removeSubCategoryListing(addon.name, parentCat, name)
1096 updateConfigList()
1100 function OptionHouse:GetVersion() return major, minor end
1103 local function Activate(self, old)
1104 if( old ) then
1105 addons = old.addons or addons
1106 evtFrame = old.evtFrame or evtFrame
1107 tabfunctions = old.tabfunctions or tabfunctions
1108 else
1109 -- Secure headers are supported so don't want the window stuck open in combat
1110 evtFrame = CreateFrame("Frame")
1111 evtFrame:RegisterEvent("PLAYER_REGEN_DISABLED")
1112 evtFrame:SetScript("OnEvent",function()
1113 if( frame and frame:IsShown() ) then
1114 frame:Hide()
1115 DEFAULT_CHAT_FRAME:AddMessage(L["ENTERED_COMBAT"])
1117 end )
1119 -- Make sure it hasn't been created already.
1120 -- don't have to upgrade the referance because it just uses the slash command
1121 -- which will upgrade below to use the current version anyway
1122 if( not GameMenuButtonOptionHouse ) then
1123 local menubutton = CreateFrame("Button", "GameMenuButtonOptionHouse", GameMenuFrame, "GameMenuButtonTemplate")
1124 menubutton:SetText(L["OPTION_HOUSE"])
1125 menubutton:SetScript("OnClick", function()
1126 PlaySound("igMainMenuOption")
1127 HideUIPanel(GameMenuFrame)
1128 SlashCmdList["OPTHOUSE"]()
1129 end)
1131 -- Position below "Interface Options"
1132 local a1, fr, a2, x, y = GameMenuButtonKeybindings:GetPoint()
1133 menubutton:SetPoint(a1, fr, a2, x, y)
1135 GameMenuButtonKeybindings:SetPoint(a1, menubutton, a2, x, y)
1136 GameMenuFrame:SetHeight(GameMenuFrame:GetHeight() + 25)
1140 self.addons = addons
1141 self.evtFrame = evtFrame
1142 self.tabfunctions = tabfunctions
1144 -- Upgrade functions to point towards the latest revision
1145 for name, addon in pairs(addons) do
1146 for _, method in pairs(methods) do
1147 addon.obj[method] = OptionHouse[method]
1151 SLASH_OPTHOUSE1 = "/opthouse"
1152 SLASH_OPTHOUSE2 = "/oh"
1153 SlashCmdList["OPTHOUSE"] = OptionHouse.Open
1156 OptionHouse = DongleStub:Register(OptionHouse, Activate)