From f3e579cb57daadeb1052c450916e5da502a85f37 Mon Sep 17 00:00:00 2001 From: Andrew6810 <16847730+andrew6180@users.noreply.github.com> Date: Sat, 5 Nov 2022 21:19:42 -0700 Subject: [PATCH] init --- README.md | 4 +- TradeSkillMaster/Assistant/Assistant.lua | 289 ++ TradeSkillMaster/Assistant/Questions.lua | 191 + TradeSkillMaster/Assistant/Steps.lua | 629 +++ TradeSkillMaster/Auction/AuctionControl.lua | 824 ++++ TradeSkillMaster/Auction/AuctionFrame.lua | 298 ++ TradeSkillMaster/Auction/AuctionItem.lua | 433 ++ TradeSkillMaster/Auction/AuctionQueryUtil.lua | 314 ++ .../Auction/AuctionResultsTable.lua | 730 ++++ TradeSkillMaster/Auction/AuctionScanning.lua | 628 +++ TradeSkillMaster/Auction/AuctionUtil.lua | 116 + TradeSkillMaster/ChangeLog.txt | 442 ++ TradeSkillMaster/Core/ErrorHandler.lua | 415 ++ TradeSkillMaster/Core/EventLogger.lua | 49 + TradeSkillMaster/Core/Events.lua | 62 + TradeSkillMaster/Core/Groups.lua | 1687 ++++++++ TradeSkillMaster/Core/Modules.lua | 368 ++ TradeSkillMaster/Core/Mover.lua | 699 ++++ TradeSkillMaster/Core/Options.lua | 1183 ++++++ TradeSkillMaster/Core/Prices.lua | 458 +++ TradeSkillMaster/Core/Sync.lua | 205 + TradeSkillMaster/Core/Templates.xml | 21 + TradeSkillMaster/Core/Threading.lua | 113 + TradeSkillMaster/Core/Tooltips.lua | 300 ++ TradeSkillMaster/Data/ConnectedRealms.lua | 134 + TradeSkillMaster/Data/Conversions.lua | 717 ++++ TradeSkillMaster/Data/Disenchanting.lua | 1636 ++++++++ TradeSkillMaster/Data/ItemData.lua | 32 + TradeSkillMaster/Data/Vendor.lua | 104 + TradeSkillMaster/GUI/BankUI.lua | 346 ++ TradeSkillMaster/GUI/BuildPage.lua | 433 ++ TradeSkillMaster/GUI/Design.lua | 128 + TradeSkillMaster/GUI/GroupTree.lua | 373 ++ TradeSkillMaster/GUI/Gui.lua | 338 ++ TradeSkillMaster/GUI/MainFrame.lua | 140 + TradeSkillMaster/GUI/ScrollingTable.lua | 390 ++ TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua | 120 + .../GUI/TSMWidgets/TSMCheckBox.lua | 181 + .../GUI/TSMWidgets/TSMColorPicker.lua | 187 + .../GUI/TSMWidgets/TSMDropdown-Items.lua | 258 ++ .../GUI/TSMWidgets/TSMDropdown-Pullout.lua | 315 ++ .../GUI/TSMWidgets/TSMDropdown.lua | 413 ++ .../GUI/TSMWidgets/TSMEditBox.lua | 283 ++ .../GUI/TSMWidgets/TSMGroupBox.lua | 231 ++ .../GUI/TSMWidgets/TSMGroupItemList.lua | 515 +++ TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua | 71 + .../GUI/TSMWidgets/TSMInlineGroup.lua | 68 + .../GUI/TSMWidgets/TSMInteractiveLabel.lua | 113 + TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua | 107 + .../GUI/TSMWidgets/TSMMainFrame.lua | 388 ++ .../GUI/TSMWidgets/TSMMultiLabel.lua | 85 + .../GUI/TSMWidgets/TSMMultiLineEditBox.lua | 21 + .../GUI/TSMWidgets/TSMScrollFrame.lua | 223 + .../GUI/TSMWidgets/TSMSimpleGroup.lua | 21 + TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua | 279 ++ .../GUI/TSMWidgets/TSMTabGroup.lua | 312 ++ .../GUI/TSMWidgets/TSMTreeGroup.lua | 735 ++++ TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua | 169 + TradeSkillMaster/Items.lua | 186 + TradeSkillMaster/LICENSE.txt | 21 + .../Libs/AccurateTime/!AccurateTime.toc | 12 + .../Libs/AccurateTime/AccurateTime.lua | 153 + .../Libs/AceAddon-3.0/AceAddon-3.0.lua | 674 +++ .../Libs/AceAddon-3.0/AceAddon-3.0.xml | 4 + .../Libs/AceComm-3.0/AceComm-3.0.lua | 308 ++ .../Libs/AceComm-3.0/AceComm-3.0.xml | 5 + .../Libs/AceComm-3.0/ChatThrottleLib.lua | 523 +++ .../Libs/AceConfig-3.0/AceConfig-3.0.lua | 58 + .../Libs/AceConfig-3.0/AceConfig-3.0.xml | 8 + .../AceConfigCmd-3.0/AceConfigCmd-3.0.lua | 794 ++++ .../AceConfigCmd-3.0/AceConfigCmd-3.0.xml | 4 + .../AceConfigDialog-3.0.lua | 1962 +++++++++ .../AceConfigDialog-3.0.xml | 4 + .../AceConfigRegistry-3.0.lua | 371 ++ .../AceConfigRegistry-3.0.xml | 4 + .../Libs/AceConsole-3.0/AceConsole-3.0.lua | 250 ++ .../Libs/AceConsole-3.0/AceConsole-3.0.xml | 4 + TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.lua | 741 ++++ TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.xml | 4 + .../AceDBOptions-3.0/AceDBOptions-3.0.lua | 460 +++ .../AceDBOptions-3.0/AceDBOptions-3.0.xml | 4 + .../Libs/AceEvent-3.0/AceEvent-3.0.lua | 126 + .../Libs/AceEvent-3.0/AceEvent-3.0.xml | 4 + .../Libs/AceGUI-3.0/AceGUI-3.0.lua | 1031 +++++ .../Libs/AceGUI-3.0/AceGUI-3.0.xml | 29 + .../AceGUIContainer-BlizOptionsGroup.lua | 138 + .../widgets/AceGUIContainer-DropDownGroup.lua | 157 + .../widgets/AceGUIContainer-Frame.lua | 316 ++ .../widgets/AceGUIContainer-InlineGroup.lua | 103 + .../widgets/AceGUIContainer-ScrollFrame.lua | 215 + .../widgets/AceGUIContainer-SimpleGroup.lua | 69 + .../widgets/AceGUIContainer-TabGroup.lua | 349 ++ .../widgets/AceGUIContainer-TreeGroup.lua | 705 ++++ .../widgets/AceGUIContainer-Window.lua | 336 ++ .../widgets/AceGUIWidget-Button-ElvUI.lua | 179 + .../widgets/AceGUIWidget-Button.lua | 103 + .../widgets/AceGUIWidget-CheckBox.lua | 295 ++ .../widgets/AceGUIWidget-ColorPicker.lua | 205 + .../widgets/AceGUIWidget-DropDown-Items.lua | 471 +++ .../widgets/AceGUIWidget-DropDown.lua | 771 ++++ .../widgets/AceGUIWidget-EditBox.lua | 263 ++ .../widgets/AceGUIWidget-Heading.lua | 78 + .../AceGUI-3.0/widgets/AceGUIWidget-Icon.lua | 140 + .../widgets/AceGUIWidget-InteractiveLabel.lua | 94 + .../widgets/AceGUIWidget-Keybinding.lua | 249 ++ .../AceGUI-3.0/widgets/AceGUIWidget-Label.lua | 178 + .../widgets/AceGUIWidget-MultiLineEditBox.lua | 366 ++ .../widgets/AceGUIWidget-Slider.lua | 280 ++ .../Libs/AceHook-3.0/AceHook-3.0.lua | 511 +++ .../Libs/AceHook-3.0/AceHook-3.0.xml | 4 + .../Libs/AceLocale-3.0/AceLocale-3.0.lua | 137 + .../Libs/AceLocale-3.0/AceLocale-3.0.xml | 4 + .../AceSerializer-3.0/AceSerializer-3.0.lua | 281 ++ .../AceSerializer-3.0/AceSerializer-3.0.xml | 4 + .../CallbackHandler-1.0.lua | 238 ++ .../CallbackHandler-1.0.xml | 4 + .../Libs/LibAuctionScan-1.0/AuctionItem.lua | 476 +++ .../Libs/LibAuctionScan-1.0/ChangeLog.txt | 1 + .../LibAuctionScan-1.0/LibAuctionScan-1.0.lua | 892 ++++ .../LibAuctionScan-1.0/LibAuctionScan-1.0.toc | 12 + .../Libs/LibAuctionScan-1.0/Libs/LibStub.lua | 51 + .../Libs/LibAuctionScan-1.0/Libs/LibStub.toc | 13 + .../LibAuctionScan-1.0/Libs/tests/test.lua | 41 + .../LibAuctionScan-1.0/Libs/tests/test2.lua | 27 + .../LibAuctionScan-1.0/Libs/tests/test3.lua | 14 + .../LibAuctionScan-1.0/Libs/tests/test4.lua | 41 + .../Libs/LibAuctionScan-1.0/Load.xml | 5 + .../Libs/LibChatAnims/LibChatAnims.lua | 191 + .../Libs/LibChatAnims/LibChatAnims.xml | 7 + .../Libs/LibCompress/LibCompress.lua | 1255 ++++++ .../Libs/LibCompress/LibCompress.toc | 14 + TradeSkillMaster/Libs/LibCompress/lib.xml | 4 + .../Libs/LibCompress/lib/LibStub/LibStub.lua | 30 + .../Libs/LibCompress/lib/LibStub/LibStub.toc | 13 + .../Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua | 276 ++ TradeSkillMaster/Libs/LibDBIcon-1.0/lib.xml | 7 + .../Changelog-libdatabroker-1-1-v1.1.4.txt | 33 + .../LibDataBroker-1.1/LibDataBroker-1.1.lua | 90 + .../Libs/LibDataBroker-1.1/README.textile | 13 + .../Libs/LibExtraTip/LibExtraTip.lua | 1525 +++++++ .../Libs/LibExtraTip/LibExtraTip.toc | 20 + .../Libs/LibExtraTip/LibMoneyFrame.lua | 235 ++ TradeSkillMaster/Libs/LibExtraTip/LibStub.lua | 30 + TradeSkillMaster/Libs/LibExtraTip/Load.xml | 5 + .../Libs/LibExtraTip/Support/LibRevision.lua | 90 + .../Libs/LibExtraTip/Support/LibStub.lua | 30 + .../Libs/LibExtraTip/Support/Load.xml | 5 + TradeSkillMaster/Libs/LibGraph-2.0/1-1.tga | Bin 0 -> 21663 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-128.tga | Bin 0 -> 13781 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-16.tga | Bin 0 -> 14332 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-2.tga | Bin 0 -> 18611 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-32.tga | Bin 0 -> 13995 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-4.tga | Bin 0 -> 14824 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-64.tga | Bin 0 -> 13859 bytes TradeSkillMaster/Libs/LibGraph-2.0/1-8.tga | Bin 0 -> 14863 bytes .../Libs/LibGraph-2.0/LibGraph-2.0.lua | 2202 ++++++++++ TradeSkillMaster/Libs/LibGraph-2.0/line.tga | Bin 0 -> 764 bytes TradeSkillMaster/Libs/LibGraph-2.0/sline.tga | Bin 0 -> 684 bytes .../Libs/LibGraph-2.0/triangle.tga | Bin 0 -> 6152 bytes TradeSkillMaster/Libs/LibParse/ChangeLog.txt | 9 + TradeSkillMaster/Libs/LibParse/LibParse.lua | 492 +++ TradeSkillMaster/Libs/LibParse/LibParse.toc | 12 + .../Libs/LibParse/Libs/LibStub.lua | 51 + .../Libs/LibParse/Libs/LibStub.toc | 13 + .../Libs/LibParse/Libs/tests/test.lua | 41 + .../Libs/LibParse/Libs/tests/test2.lua | 27 + .../Libs/LibParse/Libs/tests/test3.lua | 14 + .../Libs/LibParse/Libs/tests/test4.lua | 41 + TradeSkillMaster/Libs/LibParse/Load.xml | 4 + TradeSkillMaster/Libs/LibStub/LibStub.lua | 51 + TradeSkillMaster/Libs/lib-st/Core.lua | 772 ++++ .../Libs/lib-st/LibStub/LibStub.lua | 51 + .../Libs/lib-st/LibStub/LibStub.toc | 13 + .../Libs/lib-st/LibStub/tests/test.lua | 41 + .../Libs/lib-st/LibStub/tests/test2.lua | 27 + .../Libs/lib-st/LibStub/tests/test3.lua | 14 + .../Libs/lib-st/LibStub/tests/test4.lua | 41 + TradeSkillMaster/Libs/lib-st/lib-st.toc | 15 + TradeSkillMaster/Libs/lib-st/lib-st.xml | 4 + TradeSkillMaster/Locale/deDE.lua | 625 +++ TradeSkillMaster/Locale/enUS.lua | 601 +++ TradeSkillMaster/Locale/esES.lua | 629 +++ TradeSkillMaster/Locale/esMX.lua | 621 +++ TradeSkillMaster/Locale/frFR.lua | 621 +++ TradeSkillMaster/Locale/koKR.lua | 621 +++ TradeSkillMaster/Locale/ptBR.lua | 627 +++ TradeSkillMaster/Locale/ruRU.lua | 626 +++ TradeSkillMaster/Locale/zhCN.lua | 627 +++ TradeSkillMaster/Locale/zhTW.lua | 621 +++ TradeSkillMaster/Media/AppBanner.blp | Bin 0 -> 175940 bytes TradeSkillMaster/Media/DroidSans-Bold.ttf | Bin 0 -> 194488 bytes TradeSkillMaster/Media/Sizer.blp | Bin 0 -> 6660 bytes TradeSkillMaster/Media/TSM_Icon.blp | Bin 0 -> 2564 bytes TradeSkillMaster/Media/TSM_Icon2.blp | Bin 0 -> 6660 bytes TradeSkillMaster/Media/TSM_Icon_Big.blp | Bin 0 -> 88580 bytes TradeSkillMaster/Media/TSM_Icon_Pocket.tga | Bin 0 -> 262188 bytes TradeSkillMaster/Media/banner.blp | Bin 0 -> 175940 bytes TradeSkillMaster/Media/striped.tga | Bin 0 -> 32812 bytes TradeSkillMaster/TradeSkillMaster.lua | 673 +++ TradeSkillMaster/TradeSkillMaster.toc | 117 + TradeSkillMaster/Util/Delay.lua | 183 + TradeSkillMaster/Util/Inventory.lua | 214 + TradeSkillMaster/Util/Items.lua | 199 + TradeSkillMaster/Util/Money.lua | 232 ++ TradeSkillMaster/Util/Util.lua | 100 + TradeSkillMaster_Accounting/ChangeLog.txt | 167 + TradeSkillMaster_Accounting/LICENSE.txt | 21 + TradeSkillMaster_Accounting/Locale/deDE.lua | 157 + TradeSkillMaster_Accounting/Locale/enUS.lua | 153 + TradeSkillMaster_Accounting/Locale/esES.lua | 157 + TradeSkillMaster_Accounting/Locale/esMX.lua | 152 + TradeSkillMaster_Accounting/Locale/frFR.lua | 154 + TradeSkillMaster_Accounting/Locale/koKR.lua | 152 + TradeSkillMaster_Accounting/Locale/ptBR.lua | 16 + TradeSkillMaster_Accounting/Locale/ruRU.lua | 154 + TradeSkillMaster_Accounting/Locale/zhCN.lua | 156 + TradeSkillMaster_Accounting/Locale/zhTW.lua | 152 + TradeSkillMaster_Accounting/Modules/data.lua | 1268 ++++++ TradeSkillMaster_Accounting/Modules/gui.lua | 1361 ++++++ .../TradeSkillMaster_Accounting.lua | 520 +++ .../TradeSkillMaster_Accounting.toc | 26 + TradeSkillMaster_AuctionDB/AppData.lua | 5 + TradeSkillMaster_AuctionDB/ChangeLog.txt | 159 + TradeSkillMaster_AuctionDB/LICENSE.txt | 21 + TradeSkillMaster_AuctionDB/Locale/deDE.lua | 92 + TradeSkillMaster_AuctionDB/Locale/enUS.lua | 91 + TradeSkillMaster_AuctionDB/Locale/esES.lua | 92 + TradeSkillMaster_AuctionDB/Locale/esMX.lua | 92 + TradeSkillMaster_AuctionDB/Locale/frFR.lua | 92 + TradeSkillMaster_AuctionDB/Locale/koKR.lua | 92 + TradeSkillMaster_AuctionDB/Locale/ptBR.lua | 92 + TradeSkillMaster_AuctionDB/Locale/ruRU.lua | 92 + TradeSkillMaster_AuctionDB/Locale/zhCN.lua | 92 + TradeSkillMaster_AuctionDB/Locale/zhTW.lua | 92 + TradeSkillMaster_AuctionDB/Modules/GUI.lua | 185 + .../Modules/Scanning.lua | 229 ++ TradeSkillMaster_AuctionDB/Modules/config.lua | 489 +++ TradeSkillMaster_AuctionDB/Modules/data.lua | 225 + .../TradeSkillMaster_AuctionDB.lua | 551 +++ .../TradeSkillMaster_AuctionDB.toc | 29 + TradeSkillMaster_Auctioning/LICENSE.txt | 21 + .../TradeSkillMaster_Auctioning.lua | 187 + .../TradeSkillMaster_Auctioning.toc | 33 + TradeSkillMaster_Auctioning/changelog.txt | 241 ++ TradeSkillMaster_Auctioning/locale/deDE.lua | 273 ++ TradeSkillMaster_Auctioning/locale/enUS.lua | 287 ++ TradeSkillMaster_Auctioning/locale/esES.lua | 268 ++ TradeSkillMaster_Auctioning/locale/esMX.lua | 268 ++ TradeSkillMaster_Auctioning/locale/frFR.lua | 268 ++ TradeSkillMaster_Auctioning/locale/koKR.lua | 268 ++ TradeSkillMaster_Auctioning/locale/ptBR.lua | 274 ++ TradeSkillMaster_Auctioning/locale/ruRU.lua | 273 ++ TradeSkillMaster_Auctioning/locale/zhCN.lua | 274 ++ TradeSkillMaster_Auctioning/locale/zhTW.lua | 273 ++ .../modules/CancelScan.lua | 511 +++ TradeSkillMaster_Auctioning/modules/GUI.lua | 957 +++++ TradeSkillMaster_Auctioning/modules/Log.lua | 86 + .../modules/Options.lua | 823 ++++ .../modules/PostScan.lua | 527 +++ .../modules/ResetScan.lua | 735 ++++ .../modules/ScanUtil.lua | 200 + TradeSkillMaster_Auctioning/modules/Util.lua | 332 ++ .../modules/manage.lua | 179 + TradeSkillMaster_Crafting/ChangeLog.txt | 305 ++ TradeSkillMaster_Crafting/LICENSE.txt | 21 + TradeSkillMaster_Crafting/Locale/deDE.lua | 155 + TradeSkillMaster_Crafting/Locale/enUS.lua | 178 + TradeSkillMaster_Crafting/Locale/esES.lua | 154 + TradeSkillMaster_Crafting/Locale/esMX.lua | 154 + TradeSkillMaster_Crafting/Locale/frFR.lua | 154 + TradeSkillMaster_Crafting/Locale/koKR.lua | 154 + TradeSkillMaster_Crafting/Locale/ptBR.lua | 155 + TradeSkillMaster_Crafting/Locale/ruRU.lua | 155 + TradeSkillMaster_Crafting/Locale/zhCN.lua | 155 + TradeSkillMaster_Crafting/Locale/zhTW.lua | 154 + TradeSkillMaster_Crafting/Modules/Cost.lua | 155 + .../Modules/CraftingGUI.lua | 2760 +++++++++++++ .../Modules/EnchantingInfo.lua | 625 +++ TradeSkillMaster_Crafting/Modules/Gather.lua | 188 + .../Modules/Inventory.lua | 340 ++ TradeSkillMaster_Crafting/Modules/Options.lua | 1070 +++++ TradeSkillMaster_Crafting/Modules/Queue.lua | 257 ++ .../Modules/SpellNames2IDs.lua | 3660 +++++++++++++++++ TradeSkillMaster_Crafting/Modules/Sync.lua | 63 + TradeSkillMaster_Crafting/Modules/Util.lua | 330 ++ .../Modules/VellumInfo.lua | 312 ++ .../TradeSkillMaster_Crafting.lua | 361 ++ .../TradeSkillMaster_Crafting.toc | 32 + TradeSkillMaster_Destroying/ChangeLog.txt | 131 + TradeSkillMaster_Destroying/LICENSE.txt | 4 + TradeSkillMaster_Destroying/Locale/deDE.lua | 49 + TradeSkillMaster_Destroying/Locale/enUS.lua | 52 + TradeSkillMaster_Destroying/Locale/esES.lua | 49 + TradeSkillMaster_Destroying/Locale/esMX.lua | 48 + TradeSkillMaster_Destroying/Locale/frFR.lua | 48 + TradeSkillMaster_Destroying/Locale/koKR.lua | 48 + TradeSkillMaster_Destroying/Locale/ptBR.lua | 49 + TradeSkillMaster_Destroying/Locale/ruRU.lua | 48 + TradeSkillMaster_Destroying/Locale/zhCN.lua | 48 + TradeSkillMaster_Destroying/Locale/zhTW.lua | 48 + .../Modules/Options.lua | 412 ++ TradeSkillMaster_Destroying/Modules/gui.lua | 283 ++ .../TradeSkillMaster_Destroying.lua | 93 + .../TradeSkillMaster_Destroying.toc | 26 + TradeSkillMaster_ItemTracker/ChangeLog.txt | 52 + TradeSkillMaster_ItemTracker/LICENSE.txt | 21 + TradeSkillMaster_ItemTracker/Locale/deDE.lua | 43 + TradeSkillMaster_ItemTracker/Locale/enUS.lua | 44 + TradeSkillMaster_ItemTracker/Locale/esES.lua | 43 + TradeSkillMaster_ItemTracker/Locale/esMX.lua | 43 + TradeSkillMaster_ItemTracker/Locale/frFR.lua | 43 + TradeSkillMaster_ItemTracker/Locale/koKR.lua | 43 + TradeSkillMaster_ItemTracker/Locale/ptBR.lua | 44 + TradeSkillMaster_ItemTracker/Locale/ruRU.lua | 43 + TradeSkillMaster_ItemTracker/Locale/zhCN.lua | 43 + TradeSkillMaster_ItemTracker/Locale/zhTW.lua | 43 + .../Modules/config.lua | 368 ++ TradeSkillMaster_ItemTracker/Modules/data.lua | 444 ++ TradeSkillMaster_ItemTracker/Modules/sync.lua | 92 + .../TradeSkillMaster_ItemTracker.lua | 345 ++ .../TradeSkillMaster_ItemTracker.toc | 23 + TradeSkillMaster_Mailing/ChangeLog.txt | 102 + TradeSkillMaster_Mailing/LICENSE.txt | 21 + TradeSkillMaster_Mailing/Locale/deDE.lua | 131 + TradeSkillMaster_Mailing/Locale/enUS.lua | 142 + TradeSkillMaster_Mailing/Locale/esES.lua | 131 + TradeSkillMaster_Mailing/Locale/esMX.lua | 129 + TradeSkillMaster_Mailing/Locale/frFR.lua | 131 + TradeSkillMaster_Mailing/Locale/koKR.lua | 129 + TradeSkillMaster_Mailing/Locale/ptBR.lua | 130 + TradeSkillMaster_Mailing/Locale/ruRU.lua | 130 + TradeSkillMaster_Mailing/Locale/zhCN.lua | 131 + TradeSkillMaster_Mailing/Locale/zhTW.lua | 131 + TradeSkillMaster_Mailing/Modules/AutoMail.lua | 234 ++ TradeSkillMaster_Mailing/Modules/Groups.lua | 147 + TradeSkillMaster_Mailing/Modules/Inbox.lua | 715 ++++ TradeSkillMaster_Mailing/Modules/MailTab.lua | 225 + TradeSkillMaster_Mailing/Modules/Options.lua | 352 ++ TradeSkillMaster_Mailing/Modules/Other.lua | 198 + .../Modules/QuickSend.lua | 221 + .../TradeSkillMaster_Mailing.lua | 92 + .../TradeSkillMaster_Mailing.toc | 31 + TradeSkillMaster_Shopping/ChangeLog.txt | 193 + TradeSkillMaster_Shopping/LICENSE.txt | 21 + TradeSkillMaster_Shopping/Locale/deDE.lua | 154 + TradeSkillMaster_Shopping/Locale/enUS.lua | 161 + TradeSkillMaster_Shopping/Locale/esES.lua | 154 + TradeSkillMaster_Shopping/Locale/esMX.lua | 154 + TradeSkillMaster_Shopping/Locale/frFR.lua | 154 + TradeSkillMaster_Shopping/Locale/koKR.lua | 154 + TradeSkillMaster_Shopping/Locale/ptBR.lua | 156 + TradeSkillMaster_Shopping/Locale/ruRU.lua | 155 + TradeSkillMaster_Shopping/Locale/zhCN.lua | 156 + TradeSkillMaster_Shopping/Locale/zhTW.lua | 154 + .../TradeSkillMaster_Shopping.lua | 158 + .../TradeSkillMaster_Shopping.toc | 38 + .../modules/Destroying.lua | 274 ++ TradeSkillMaster_Shopping/modules/Options.lua | 362 ++ TradeSkillMaster_Shopping/modules/Search.lua | 589 +++ TradeSkillMaster_Shopping/modules/Util.lua | 459 +++ .../sidebar/CustomFilter.lua | 239 ++ TradeSkillMaster_Shopping/sidebar/Groups.lua | 113 + TradeSkillMaster_Shopping/sidebar/Other.lua | 259 ++ .../sidebar/QuickPosting.lua | 123 + TradeSkillMaster_Shopping/sidebar/Saved.lua | 159 + .../sidebar/ShoppingLog.lua | 64 + TradeSkillMaster_Shopping/sidebar/Sidebar.lua | 82 + TradeSkillMaster_Warehousing/ChangeLog.txt | 153 + TradeSkillMaster_Warehousing/LICENSE.txt | 4 + TradeSkillMaster_Warehousing/Locale/deDE.lua | 93 + TradeSkillMaster_Warehousing/Locale/enUS.lua | 107 + TradeSkillMaster_Warehousing/Locale/esES.lua | 16 + TradeSkillMaster_Warehousing/Locale/esMX.lua | 93 + TradeSkillMaster_Warehousing/Locale/frFR.lua | 93 + TradeSkillMaster_Warehousing/Locale/koKR.lua | 92 + TradeSkillMaster_Warehousing/Locale/ptBR.lua | 92 + TradeSkillMaster_Warehousing/Locale/ruRU.lua | 94 + TradeSkillMaster_Warehousing/Locale/zhCN.lua | 92 + TradeSkillMaster_Warehousing/Locale/zhTW.lua | 94 + .../Modules/bankui.lua | 127 + TradeSkillMaster_Warehousing/Modules/data.lua | 235 ++ TradeSkillMaster_Warehousing/Modules/move.lua | 160 + .../Modules/options.lua | 432 ++ TradeSkillMaster_Warehousing/Modules/util.lua | 125 + .../TradeSkillMaster_Warehousing.lua | 240 ++ .../TradeSkillMaster_Warehousing.toc | 26 + 386 files changed, 93729 insertions(+), 2 deletions(-) create mode 100644 TradeSkillMaster/Assistant/Assistant.lua create mode 100644 TradeSkillMaster/Assistant/Questions.lua create mode 100644 TradeSkillMaster/Assistant/Steps.lua create mode 100644 TradeSkillMaster/Auction/AuctionControl.lua create mode 100644 TradeSkillMaster/Auction/AuctionFrame.lua create mode 100644 TradeSkillMaster/Auction/AuctionItem.lua create mode 100644 TradeSkillMaster/Auction/AuctionQueryUtil.lua create mode 100644 TradeSkillMaster/Auction/AuctionResultsTable.lua create mode 100644 TradeSkillMaster/Auction/AuctionScanning.lua create mode 100644 TradeSkillMaster/Auction/AuctionUtil.lua create mode 100644 TradeSkillMaster/ChangeLog.txt create mode 100644 TradeSkillMaster/Core/ErrorHandler.lua create mode 100644 TradeSkillMaster/Core/EventLogger.lua create mode 100644 TradeSkillMaster/Core/Events.lua create mode 100644 TradeSkillMaster/Core/Groups.lua create mode 100644 TradeSkillMaster/Core/Modules.lua create mode 100644 TradeSkillMaster/Core/Mover.lua create mode 100644 TradeSkillMaster/Core/Options.lua create mode 100644 TradeSkillMaster/Core/Prices.lua create mode 100644 TradeSkillMaster/Core/Sync.lua create mode 100644 TradeSkillMaster/Core/Templates.xml create mode 100644 TradeSkillMaster/Core/Threading.lua create mode 100644 TradeSkillMaster/Core/Tooltips.lua create mode 100644 TradeSkillMaster/Data/ConnectedRealms.lua create mode 100644 TradeSkillMaster/Data/Conversions.lua create mode 100644 TradeSkillMaster/Data/Disenchanting.lua create mode 100644 TradeSkillMaster/Data/ItemData.lua create mode 100644 TradeSkillMaster/Data/Vendor.lua create mode 100644 TradeSkillMaster/GUI/BankUI.lua create mode 100644 TradeSkillMaster/GUI/BuildPage.lua create mode 100644 TradeSkillMaster/GUI/Design.lua create mode 100644 TradeSkillMaster/GUI/GroupTree.lua create mode 100644 TradeSkillMaster/GUI/Gui.lua create mode 100644 TradeSkillMaster/GUI/MainFrame.lua create mode 100644 TradeSkillMaster/GUI/ScrollingTable.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMCheckBox.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMColorPicker.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Items.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Pullout.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMDropdown.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMEditBox.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMGroupBox.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMGroupItemList.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMInlineGroup.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMInteractiveLabel.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMMainFrame.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMMultiLabel.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMMultiLineEditBox.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMScrollFrame.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMSimpleGroup.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMTabGroup.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMTreeGroup.lua create mode 100644 TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua create mode 100644 TradeSkillMaster/Items.lua create mode 100644 TradeSkillMaster/LICENSE.txt create mode 100644 TradeSkillMaster/Libs/AccurateTime/!AccurateTime.toc create mode 100644 TradeSkillMaster/Libs/AccurateTime/AccurateTime.lua create mode 100644 TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceComm-3.0/AceComm-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceComm-3.0/AceComm-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceComm-3.0/ChatThrottleLib.lua create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfig-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfig-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfigCmd-3.0/AceConfigCmd-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfigDialog-3.0/AceConfigDialog-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceConfig-3.0/AceConfigRegistry-3.0/AceConfigRegistry-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceConsole-3.0/AceConsole-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceConsole-3.0/AceConsole-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceDB-3.0/AceDB-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceDBOptions-3.0/AceDBOptions-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceDBOptions-3.0/AceDBOptions-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceEvent-3.0/AceEvent-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceEvent-3.0/AceEvent-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/AceGUI-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/AceGUI-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-BlizOptionsGroup.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-DropDownGroup.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-Frame.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-SimpleGroup.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIContainer-Window.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button-ElvUI.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Button.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown-Items.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-DropDown.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Heading.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Icon.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-InteractiveLabel.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Keybinding.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Label.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-MultiLineEditBox.lua create mode 100644 TradeSkillMaster/Libs/AceGUI-3.0/widgets/AceGUIWidget-Slider.lua create mode 100644 TradeSkillMaster/Libs/AceHook-3.0/AceHook-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceHook-3.0/AceHook-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceLocale-3.0/AceLocale-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceLocale-3.0/AceLocale-3.0.xml create mode 100644 TradeSkillMaster/Libs/AceSerializer-3.0/AceSerializer-3.0.lua create mode 100644 TradeSkillMaster/Libs/AceSerializer-3.0/AceSerializer-3.0.xml create mode 100644 TradeSkillMaster/Libs/CallbackHandler-1.0/CallbackHandler-1.0.lua create mode 100644 TradeSkillMaster/Libs/CallbackHandler-1.0/CallbackHandler-1.0.xml create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/AuctionItem.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/ChangeLog.txt create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/LibAuctionScan-1.0.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/LibAuctionScan-1.0.toc create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Libs/LibStub.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Libs/LibStub.toc create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Libs/tests/test.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Libs/tests/test2.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Libs/tests/test3.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Libs/tests/test4.lua create mode 100644 TradeSkillMaster/Libs/LibAuctionScan-1.0/Load.xml create mode 100644 TradeSkillMaster/Libs/LibChatAnims/LibChatAnims.lua create mode 100644 TradeSkillMaster/Libs/LibChatAnims/LibChatAnims.xml create mode 100644 TradeSkillMaster/Libs/LibCompress/LibCompress.lua create mode 100644 TradeSkillMaster/Libs/LibCompress/LibCompress.toc create mode 100644 TradeSkillMaster/Libs/LibCompress/lib.xml create mode 100644 TradeSkillMaster/Libs/LibCompress/lib/LibStub/LibStub.lua create mode 100644 TradeSkillMaster/Libs/LibCompress/lib/LibStub/LibStub.toc create mode 100644 TradeSkillMaster/Libs/LibDBIcon-1.0/LibDBIcon-1.0.lua create mode 100644 TradeSkillMaster/Libs/LibDBIcon-1.0/lib.xml create mode 100644 TradeSkillMaster/Libs/LibDataBroker-1.1/Changelog-libdatabroker-1-1-v1.1.4.txt create mode 100644 TradeSkillMaster/Libs/LibDataBroker-1.1/LibDataBroker-1.1.lua create mode 100644 TradeSkillMaster/Libs/LibDataBroker-1.1/README.textile create mode 100644 TradeSkillMaster/Libs/LibExtraTip/LibExtraTip.lua create mode 100644 TradeSkillMaster/Libs/LibExtraTip/LibExtraTip.toc create mode 100644 TradeSkillMaster/Libs/LibExtraTip/LibMoneyFrame.lua create mode 100644 TradeSkillMaster/Libs/LibExtraTip/LibStub.lua create mode 100644 TradeSkillMaster/Libs/LibExtraTip/Load.xml create mode 100644 TradeSkillMaster/Libs/LibExtraTip/Support/LibRevision.lua create mode 100644 TradeSkillMaster/Libs/LibExtraTip/Support/LibStub.lua create mode 100644 TradeSkillMaster/Libs/LibExtraTip/Support/Load.xml create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-1.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-128.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-16.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-2.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-32.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-4.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-64.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/1-8.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/LibGraph-2.0.lua create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/line.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/sline.tga create mode 100644 TradeSkillMaster/Libs/LibGraph-2.0/triangle.tga create mode 100644 TradeSkillMaster/Libs/LibParse/ChangeLog.txt create mode 100644 TradeSkillMaster/Libs/LibParse/LibParse.lua create mode 100644 TradeSkillMaster/Libs/LibParse/LibParse.toc create mode 100644 TradeSkillMaster/Libs/LibParse/Libs/LibStub.lua create mode 100644 TradeSkillMaster/Libs/LibParse/Libs/LibStub.toc create mode 100644 TradeSkillMaster/Libs/LibParse/Libs/tests/test.lua create mode 100644 TradeSkillMaster/Libs/LibParse/Libs/tests/test2.lua create mode 100644 TradeSkillMaster/Libs/LibParse/Libs/tests/test3.lua create mode 100644 TradeSkillMaster/Libs/LibParse/Libs/tests/test4.lua create mode 100644 TradeSkillMaster/Libs/LibParse/Load.xml create mode 100644 TradeSkillMaster/Libs/LibStub/LibStub.lua create mode 100644 TradeSkillMaster/Libs/lib-st/Core.lua create mode 100644 TradeSkillMaster/Libs/lib-st/LibStub/LibStub.lua create mode 100644 TradeSkillMaster/Libs/lib-st/LibStub/LibStub.toc create mode 100644 TradeSkillMaster/Libs/lib-st/LibStub/tests/test.lua create mode 100644 TradeSkillMaster/Libs/lib-st/LibStub/tests/test2.lua create mode 100644 TradeSkillMaster/Libs/lib-st/LibStub/tests/test3.lua create mode 100644 TradeSkillMaster/Libs/lib-st/LibStub/tests/test4.lua create mode 100644 TradeSkillMaster/Libs/lib-st/lib-st.toc create mode 100644 TradeSkillMaster/Libs/lib-st/lib-st.xml create mode 100644 TradeSkillMaster/Locale/deDE.lua create mode 100644 TradeSkillMaster/Locale/enUS.lua create mode 100644 TradeSkillMaster/Locale/esES.lua create mode 100644 TradeSkillMaster/Locale/esMX.lua create mode 100644 TradeSkillMaster/Locale/frFR.lua create mode 100644 TradeSkillMaster/Locale/koKR.lua create mode 100644 TradeSkillMaster/Locale/ptBR.lua create mode 100644 TradeSkillMaster/Locale/ruRU.lua create mode 100644 TradeSkillMaster/Locale/zhCN.lua create mode 100644 TradeSkillMaster/Locale/zhTW.lua create mode 100644 TradeSkillMaster/Media/AppBanner.blp create mode 100644 TradeSkillMaster/Media/DroidSans-Bold.ttf create mode 100644 TradeSkillMaster/Media/Sizer.blp create mode 100644 TradeSkillMaster/Media/TSM_Icon.blp create mode 100644 TradeSkillMaster/Media/TSM_Icon2.blp create mode 100644 TradeSkillMaster/Media/TSM_Icon_Big.blp create mode 100644 TradeSkillMaster/Media/TSM_Icon_Pocket.tga create mode 100644 TradeSkillMaster/Media/banner.blp create mode 100644 TradeSkillMaster/Media/striped.tga create mode 100644 TradeSkillMaster/TradeSkillMaster.lua create mode 100644 TradeSkillMaster/TradeSkillMaster.toc create mode 100644 TradeSkillMaster/Util/Delay.lua create mode 100644 TradeSkillMaster/Util/Inventory.lua create mode 100644 TradeSkillMaster/Util/Items.lua create mode 100644 TradeSkillMaster/Util/Money.lua create mode 100644 TradeSkillMaster/Util/Util.lua create mode 100644 TradeSkillMaster_Accounting/ChangeLog.txt create mode 100644 TradeSkillMaster_Accounting/LICENSE.txt create mode 100644 TradeSkillMaster_Accounting/Locale/deDE.lua create mode 100644 TradeSkillMaster_Accounting/Locale/enUS.lua create mode 100644 TradeSkillMaster_Accounting/Locale/esES.lua create mode 100644 TradeSkillMaster_Accounting/Locale/esMX.lua create mode 100644 TradeSkillMaster_Accounting/Locale/frFR.lua create mode 100644 TradeSkillMaster_Accounting/Locale/koKR.lua create mode 100644 TradeSkillMaster_Accounting/Locale/ptBR.lua create mode 100644 TradeSkillMaster_Accounting/Locale/ruRU.lua create mode 100644 TradeSkillMaster_Accounting/Locale/zhCN.lua create mode 100644 TradeSkillMaster_Accounting/Locale/zhTW.lua create mode 100644 TradeSkillMaster_Accounting/Modules/data.lua create mode 100644 TradeSkillMaster_Accounting/Modules/gui.lua create mode 100644 TradeSkillMaster_Accounting/TradeSkillMaster_Accounting.lua create mode 100644 TradeSkillMaster_Accounting/TradeSkillMaster_Accounting.toc create mode 100644 TradeSkillMaster_AuctionDB/AppData.lua create mode 100644 TradeSkillMaster_AuctionDB/ChangeLog.txt create mode 100644 TradeSkillMaster_AuctionDB/LICENSE.txt create mode 100644 TradeSkillMaster_AuctionDB/Locale/deDE.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/enUS.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/esES.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/esMX.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/frFR.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/koKR.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/ptBR.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/ruRU.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/zhCN.lua create mode 100644 TradeSkillMaster_AuctionDB/Locale/zhTW.lua create mode 100644 TradeSkillMaster_AuctionDB/Modules/GUI.lua create mode 100644 TradeSkillMaster_AuctionDB/Modules/Scanning.lua create mode 100644 TradeSkillMaster_AuctionDB/Modules/config.lua create mode 100644 TradeSkillMaster_AuctionDB/Modules/data.lua create mode 100644 TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.lua create mode 100644 TradeSkillMaster_AuctionDB/TradeSkillMaster_AuctionDB.toc create mode 100644 TradeSkillMaster_Auctioning/LICENSE.txt create mode 100644 TradeSkillMaster_Auctioning/TradeSkillMaster_Auctioning.lua create mode 100644 TradeSkillMaster_Auctioning/TradeSkillMaster_Auctioning.toc create mode 100644 TradeSkillMaster_Auctioning/changelog.txt create mode 100644 TradeSkillMaster_Auctioning/locale/deDE.lua create mode 100644 TradeSkillMaster_Auctioning/locale/enUS.lua create mode 100644 TradeSkillMaster_Auctioning/locale/esES.lua create mode 100644 TradeSkillMaster_Auctioning/locale/esMX.lua create mode 100644 TradeSkillMaster_Auctioning/locale/frFR.lua create mode 100644 TradeSkillMaster_Auctioning/locale/koKR.lua create mode 100644 TradeSkillMaster_Auctioning/locale/ptBR.lua create mode 100644 TradeSkillMaster_Auctioning/locale/ruRU.lua create mode 100644 TradeSkillMaster_Auctioning/locale/zhCN.lua create mode 100644 TradeSkillMaster_Auctioning/locale/zhTW.lua create mode 100644 TradeSkillMaster_Auctioning/modules/CancelScan.lua create mode 100644 TradeSkillMaster_Auctioning/modules/GUI.lua create mode 100644 TradeSkillMaster_Auctioning/modules/Log.lua create mode 100644 TradeSkillMaster_Auctioning/modules/Options.lua create mode 100644 TradeSkillMaster_Auctioning/modules/PostScan.lua create mode 100644 TradeSkillMaster_Auctioning/modules/ResetScan.lua create mode 100644 TradeSkillMaster_Auctioning/modules/ScanUtil.lua create mode 100644 TradeSkillMaster_Auctioning/modules/Util.lua create mode 100644 TradeSkillMaster_Auctioning/modules/manage.lua create mode 100644 TradeSkillMaster_Crafting/ChangeLog.txt create mode 100644 TradeSkillMaster_Crafting/LICENSE.txt create mode 100644 TradeSkillMaster_Crafting/Locale/deDE.lua create mode 100644 TradeSkillMaster_Crafting/Locale/enUS.lua create mode 100644 TradeSkillMaster_Crafting/Locale/esES.lua create mode 100644 TradeSkillMaster_Crafting/Locale/esMX.lua create mode 100644 TradeSkillMaster_Crafting/Locale/frFR.lua create mode 100644 TradeSkillMaster_Crafting/Locale/koKR.lua create mode 100644 TradeSkillMaster_Crafting/Locale/ptBR.lua create mode 100644 TradeSkillMaster_Crafting/Locale/ruRU.lua create mode 100644 TradeSkillMaster_Crafting/Locale/zhCN.lua create mode 100644 TradeSkillMaster_Crafting/Locale/zhTW.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Cost.lua create mode 100644 TradeSkillMaster_Crafting/Modules/CraftingGUI.lua create mode 100644 TradeSkillMaster_Crafting/Modules/EnchantingInfo.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Gather.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Inventory.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Options.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Queue.lua create mode 100644 TradeSkillMaster_Crafting/Modules/SpellNames2IDs.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Sync.lua create mode 100644 TradeSkillMaster_Crafting/Modules/Util.lua create mode 100644 TradeSkillMaster_Crafting/Modules/VellumInfo.lua create mode 100644 TradeSkillMaster_Crafting/TradeSkillMaster_Crafting.lua create mode 100644 TradeSkillMaster_Crafting/TradeSkillMaster_Crafting.toc create mode 100644 TradeSkillMaster_Destroying/ChangeLog.txt create mode 100644 TradeSkillMaster_Destroying/LICENSE.txt create mode 100644 TradeSkillMaster_Destroying/Locale/deDE.lua create mode 100644 TradeSkillMaster_Destroying/Locale/enUS.lua create mode 100644 TradeSkillMaster_Destroying/Locale/esES.lua create mode 100644 TradeSkillMaster_Destroying/Locale/esMX.lua create mode 100644 TradeSkillMaster_Destroying/Locale/frFR.lua create mode 100644 TradeSkillMaster_Destroying/Locale/koKR.lua create mode 100644 TradeSkillMaster_Destroying/Locale/ptBR.lua create mode 100644 TradeSkillMaster_Destroying/Locale/ruRU.lua create mode 100644 TradeSkillMaster_Destroying/Locale/zhCN.lua create mode 100644 TradeSkillMaster_Destroying/Locale/zhTW.lua create mode 100644 TradeSkillMaster_Destroying/Modules/Options.lua create mode 100644 TradeSkillMaster_Destroying/Modules/gui.lua create mode 100644 TradeSkillMaster_Destroying/TradeSkillMaster_Destroying.lua create mode 100644 TradeSkillMaster_Destroying/TradeSkillMaster_Destroying.toc create mode 100644 TradeSkillMaster_ItemTracker/ChangeLog.txt create mode 100644 TradeSkillMaster_ItemTracker/LICENSE.txt create mode 100644 TradeSkillMaster_ItemTracker/Locale/deDE.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/enUS.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/esES.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/esMX.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/frFR.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/koKR.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/ptBR.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/ruRU.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/zhCN.lua create mode 100644 TradeSkillMaster_ItemTracker/Locale/zhTW.lua create mode 100644 TradeSkillMaster_ItemTracker/Modules/config.lua create mode 100644 TradeSkillMaster_ItemTracker/Modules/data.lua create mode 100644 TradeSkillMaster_ItemTracker/Modules/sync.lua create mode 100644 TradeSkillMaster_ItemTracker/TradeSkillMaster_ItemTracker.lua create mode 100644 TradeSkillMaster_ItemTracker/TradeSkillMaster_ItemTracker.toc create mode 100644 TradeSkillMaster_Mailing/ChangeLog.txt create mode 100644 TradeSkillMaster_Mailing/LICENSE.txt create mode 100644 TradeSkillMaster_Mailing/Locale/deDE.lua create mode 100644 TradeSkillMaster_Mailing/Locale/enUS.lua create mode 100644 TradeSkillMaster_Mailing/Locale/esES.lua create mode 100644 TradeSkillMaster_Mailing/Locale/esMX.lua create mode 100644 TradeSkillMaster_Mailing/Locale/frFR.lua create mode 100644 TradeSkillMaster_Mailing/Locale/koKR.lua create mode 100644 TradeSkillMaster_Mailing/Locale/ptBR.lua create mode 100644 TradeSkillMaster_Mailing/Locale/ruRU.lua create mode 100644 TradeSkillMaster_Mailing/Locale/zhCN.lua create mode 100644 TradeSkillMaster_Mailing/Locale/zhTW.lua create mode 100644 TradeSkillMaster_Mailing/Modules/AutoMail.lua create mode 100644 TradeSkillMaster_Mailing/Modules/Groups.lua create mode 100644 TradeSkillMaster_Mailing/Modules/Inbox.lua create mode 100644 TradeSkillMaster_Mailing/Modules/MailTab.lua create mode 100644 TradeSkillMaster_Mailing/Modules/Options.lua create mode 100644 TradeSkillMaster_Mailing/Modules/Other.lua create mode 100644 TradeSkillMaster_Mailing/Modules/QuickSend.lua create mode 100644 TradeSkillMaster_Mailing/TradeSkillMaster_Mailing.lua create mode 100644 TradeSkillMaster_Mailing/TradeSkillMaster_Mailing.toc create mode 100644 TradeSkillMaster_Shopping/ChangeLog.txt create mode 100644 TradeSkillMaster_Shopping/LICENSE.txt create mode 100644 TradeSkillMaster_Shopping/Locale/deDE.lua create mode 100644 TradeSkillMaster_Shopping/Locale/enUS.lua create mode 100644 TradeSkillMaster_Shopping/Locale/esES.lua create mode 100644 TradeSkillMaster_Shopping/Locale/esMX.lua create mode 100644 TradeSkillMaster_Shopping/Locale/frFR.lua create mode 100644 TradeSkillMaster_Shopping/Locale/koKR.lua create mode 100644 TradeSkillMaster_Shopping/Locale/ptBR.lua create mode 100644 TradeSkillMaster_Shopping/Locale/ruRU.lua create mode 100644 TradeSkillMaster_Shopping/Locale/zhCN.lua create mode 100644 TradeSkillMaster_Shopping/Locale/zhTW.lua create mode 100644 TradeSkillMaster_Shopping/TradeSkillMaster_Shopping.lua create mode 100644 TradeSkillMaster_Shopping/TradeSkillMaster_Shopping.toc create mode 100644 TradeSkillMaster_Shopping/modules/Destroying.lua create mode 100644 TradeSkillMaster_Shopping/modules/Options.lua create mode 100644 TradeSkillMaster_Shopping/modules/Search.lua create mode 100644 TradeSkillMaster_Shopping/modules/Util.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/CustomFilter.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/Groups.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/Other.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/QuickPosting.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/Saved.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/ShoppingLog.lua create mode 100644 TradeSkillMaster_Shopping/sidebar/Sidebar.lua create mode 100644 TradeSkillMaster_Warehousing/ChangeLog.txt create mode 100644 TradeSkillMaster_Warehousing/LICENSE.txt create mode 100644 TradeSkillMaster_Warehousing/Locale/deDE.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/enUS.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/esES.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/esMX.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/frFR.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/koKR.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/ptBR.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/ruRU.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/zhCN.lua create mode 100644 TradeSkillMaster_Warehousing/Locale/zhTW.lua create mode 100644 TradeSkillMaster_Warehousing/Modules/bankui.lua create mode 100644 TradeSkillMaster_Warehousing/Modules/data.lua create mode 100644 TradeSkillMaster_Warehousing/Modules/move.lua create mode 100644 TradeSkillMaster_Warehousing/Modules/options.lua create mode 100644 TradeSkillMaster_Warehousing/Modules/util.lua create mode 100644 TradeSkillMaster_Warehousing/TradeSkillMaster_Warehousing.lua create mode 100644 TradeSkillMaster_Warehousing/TradeSkillMaster_Warehousing.toc diff --git a/README.md b/README.md index 226db33..e508197 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# Addon Name +# TradeSkillMaster -This is the repository for . Modified for Ascension.gg. \ No newline at end of file +This is the repository for Trade Skill Master (TSM). Modified for Ascension.gg. \ No newline at end of file diff --git a/TradeSkillMaster/Assistant/Assistant.lua b/TradeSkillMaster/Assistant/Assistant.lua new file mode 100644 index 0000000..30c1871 --- /dev/null +++ b/TradeSkillMaster/Assistant/Assistant.lua @@ -0,0 +1,289 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local Assistant = TSM:NewModule("Assistant") +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Assistant_private") +local MAX_ASSISTANT_BUTTONS = 6 + + +function Assistant:Open() + if not private.frame then + if not private:ValidateQuestions(Assistant.INFO) then + TSM:Print(L["No Assistant guides available for the modules which you have installed."]) + return + end + private.frame = private:CreateAssistantFrame() + end + private.frame:Show() +end + +-- Removes questions which aren't possible due to missing steps (probably due to missing modules) +function private:ValidateQuestions(questionInfo) + if not questionInfo.buttons then return false end + + for i=#questionInfo.buttons, 1, -1 do + if questionInfo.buttons[i].guides then + local hasAllGuides = true + for _, guide in ipairs(questionInfo.buttons[i].guides) do + if not Assistant.STEPS[guide] then + hasAllGuides = false + break + end + end + if not hasAllGuides then + tremove(questionInfo.buttons, i) + end + elseif questionInfo.buttons[i].children then + if not private:ValidateQuestions(questionInfo.buttons[i].children) then + tremove(questionInfo.buttons, i) + end + end + end + + return #questionInfo.buttons > 0 +end + +function private:CreateAssistantFrame() + local frameDefaults = { + x = 50, + y = 300, + width = 400, + height = 250, + scale = 1, + } + local frame = TSMAPI:CreateMovableFrame("TSMAssistantFrame", frameDefaults) + TSMAPI.Design:SetFrameBackdropColor(frame) + frame:Hide() + frame:SetScript("OnShow", function(self) + self.guideFrame:Hide() + self.questionFrame:Show() + end) + frame:SetScript("OnHide", function(self) + private.currentStep = nil + end) + + local title = frame:CreateFontString() + title:SetFont(TSMAPI.Design:GetContentFont(), 18) + TSMAPI.Design:SetWidgetLabelColor(title) + title:SetPoint("TOP", frame, 0, -3) + title:SetText(L["TSM Assistant"]) + + TSMAPI.GUI:CreateHorizontalLine(frame, -25) + + local closeBtn = TSMAPI.GUI:CreateButton(frame, 18) + closeBtn:SetPoint("TOPRIGHT", -3, -3) + closeBtn:SetWidth(19) + closeBtn:SetHeight(19) + closeBtn:SetText("X") + closeBtn:SetScript("OnClick", function() frame:Hide() end) + + frame.questionFrame = private:CreateQuestionFrame(frame) + frame.guideFrame = private:CreateGuideFrame(frame) + return frame +end + +function private:CreateQuestionFrame(parent) + local frame = CreateFrame("Frame", nil, parent) + frame:SetAllPoints() + frame:Hide() + frame:SetScript("OnShow", function(self) + private.pageInfo = Assistant.INFO + self.restartButton:Hide() + self:Update() + end) + + function frame.Update(self) + -- update the title question + self.questionText:SetText(private.pageInfo.title) + + -- hide all the buttons + for _, button in ipairs(self.buttons) do + button:Hide() + end + + -- update buttons + for i, buttonInfo in ipairs(private.pageInfo.buttons) do + self.buttons[i]:Show() + self.buttons[i]:SetText(buttonInfo.text) + self.buttons[i].info = buttonInfo + end + end + + local questionText = TSMAPI.GUI:CreateTitleLabel(frame, 16) + questionText:SetPoint("TOPLEFT", 5, -30) + questionText:SetPoint("TOPRIGHT", -5, -30) + questionText:SetHeight(20) + questionText:SetJustifyH("LEFT") + questionText:SetJustifyV("CENTER") + frame.questionText = questionText + + local function OnAnswerButtonClicked(self) + if self.info.children then + private.frame.questionFrame.restartButton:Show() + private.pageInfo = self.info.children + private.frame.questionFrame:Update() + elseif self.info.guides then + private.steps = {} + for _, guideKey in ipairs(self.info.guides) do + for _, step in ipairs(Assistant.STEPS[guideKey]) do + tinsert(private.steps, step) + end + end + private.frame.questionFrame:Hide() + private.frame.guideFrame:Show() + end + end + + frame.buttons = {} + for i=1, MAX_ASSISTANT_BUTTONS do + local button = TSMAPI.GUI:CreateButton(frame, 14) + button:SetHeight(20) + if i == 1 then + button:SetPoint("TOPLEFT", frame.questionText, "BOTTOMLEFT", 0, -5) + button:SetPoint("TOPRIGHT", frame.questionText, "BOTTOMRIGHT", 0, -5) + else + button:SetPoint("TOPLEFT", frame.buttons[i-1], "BOTTOMLEFT", 0, -5) + button:SetPoint("TOPRIGHT", frame.buttons[i-1], "BOTTOMRIGHT", 0, -5) + end + button:SetScript("OnClick", OnAnswerButtonClicked) + tinsert(frame.buttons, button) + end + + local restartButton = TSMAPI.GUI:CreateButton(frame, 14) + restartButton:SetHeight(20) + restartButton:SetPoint("BOTTOMLEFT", 5, 5) + restartButton:SetPoint("BOTTOMRIGHT", -5, 5) + restartButton:SetText(L["Restart Assistant"]) + restartButton:SetScript("OnClick", function(self) + self:Hide() + private.pageInfo = Assistant.INFO + private.frame.questionFrame:Update() + end) + frame.restartButton = restartButton + + return frame +end + +function private:CreateGuideFrame(parent) + local frame = CreateFrame("Frame", nil, parent) + frame:SetAllPoints() + frame:Hide() + frame:SetScript("OnShow", function(self) + private.currentStep = 1 + private.checkPoint = 1 + Assistant:ClearStepData() + self:Update() + private:StartStepWaitThread() + end) + + function frame.Update(self) + if private.currentStep == -1 then + self.stepTitle:SetText(L["Done!"]) + self.stepDesc:SetText(L["You have successfully completed this guide. If you require further assistance, visit out our website:"].."\n\n".."http://tradeskillmaster.com") + self.button:Hide() + self.restartButton:Show() + else + local stepInfo = private.steps[private.currentStep] + self.stepTitle:SetText(stepInfo.title) + if stepInfo.getDescArgs then + self.stepDesc:SetText(format(stepInfo.description, stepInfo.getDescArgs())) + else + self.stepDesc:SetText(stepInfo.description) + end + if stepInfo.doneButton then + self.button:Show() + self.button:SetText(stepInfo.doneButton) + self.button:SetScript("OnClick", function() stepInfo:onDoneButtonClicked() end) + self.stepDesc:SetWidth(min(self.stepDesc:GetStringWidth(), self:GetWidth()-10)) + else + self.button:Hide() + self.stepDesc:SetWidth(self:GetWidth()-10) + end + self.restartButton:Hide() + end + end + + local stepTitle = TSMAPI.GUI:CreateTitleLabel(frame, 16) + stepTitle:SetPoint("TOPLEFT", 5, -30) + stepTitle:SetPoint("TOPRIGHT", -5, -30) + stepTitle:SetHeight(20) + stepTitle:SetJustifyH("LEFT") + stepTitle:SetJustifyV("CENTER") + stepTitle:SetText("DEFAULT") + frame.stepTitle = stepTitle + + local stepDesc = TSMAPI.GUI:CreateLabel(frame, "normal") + stepDesc:SetPoint("TOPLEFT", 5, -55) + stepDesc:SetJustifyH("LEFT") + stepDesc:SetJustifyV("TOP") + frame.stepDesc = stepDesc + + local button = TSMAPI.GUI:CreateButton(frame, 14) + button:SetHeight(20) + button:SetPoint("TOPLEFT", frame.stepDesc, "BOTTOMLEFT", 0, -5) + button:SetPoint("TOPRIGHT", frame.stepDesc, "BOTTOMRIGHT", 0, -5) + button:SetScript("OnClick", function() end) + frame.button = button + + local restartButton = TSMAPI.GUI:CreateButton(frame, 14) + restartButton:SetHeight(20) + restartButton:SetPoint("BOTTOMLEFT", 5, 5) + restartButton:SetPoint("BOTTOMRIGHT", -5, 5) + restartButton:SetText(L["Restart Assistant"]) + restartButton:SetScript("OnClick", function(self) + parent:Hide() + Assistant:Open() + end) + frame.restartButton = restartButton + + return frame +end + + +function private:StartStepWaitThread() + TSMAPI.Threading:Start(private.GuideThread, 0.1, private.StepComplete) +end + +function private:IsStepDone(step) + if step.isDone and step:isDone() then + return true + end +end + +function private:GetCurrentStep() + for i=private.checkPoint, #private.steps do + if not private:IsStepDone(private.steps[i]) then + return i + elseif private.steps[i].isCheckPoint then + private.checkPoint = i+1 + end + end +end + +function private.GuideThread(self) + -- loop until the player finishes the step or we abort + while private.currentStep do + local stepNum = private:GetCurrentStep() + if not stepNum then return end + if stepNum ~= private.currentStep then + private.currentStep = stepNum + end + private.frame.guideFrame:Update() + self:Sleep(0.1) + end +end + +function private.StepComplete() + if private.currentStep then + private.currentStep = -1 + private.frame.guideFrame:Update() + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Assistant/Questions.lua b/TradeSkillMaster/Assistant/Questions.lua new file mode 100644 index 0000000..ed38408 --- /dev/null +++ b/TradeSkillMaster/Assistant/Questions.lua @@ -0,0 +1,191 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local Assistant = TSM.modules.Assistant +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Questions_private") + +local MAKE_GROUP_STEPS = { + title = L["How would you like to create the group?"], + buttons = { + { + text = L["Make a new group from items in my bags"], + guides = {"openTSM", "openGroups", "newGroup", "selectGroup", "groupAddFromBags"}, + }, + { + text = L["Make a new group from an import list I have"], + guides = {"openTSM", "openGroups", "newGroup", "selectGroup", "groupImportItems"}, + }, + { + text = L["Use an existing group"], + guides = {"openTSM", "openGroups", "selectGroup"}, + }, + { + text = L["Use a subset of items from an existing group by creating a subgroup"], + guides = {"openTSM", "openGroups", "selectGroup"}, + }, + }, +} + +function private:GetMakeGroupSteps(steps) + local stepInfo = CopyTable(MAKE_GROUP_STEPS) + for _, button in ipairs(stepInfo.buttons) do + for _, step in ipairs(steps) do + tinsert(button.guides, step) + end + end + return stepInfo +end + +Assistant.INFO = { + title = L["What do you want to do?"], + buttons = { + { + text = L["Craft items with my professions"], + children = { + title = L["How would you like to craft?"], + buttons = { + { + text = L["Set up TSM to automatically queue things to craft"], + children = private:GetMakeGroupSteps({"craftingOperation", "openProfession", "professionRestock", "useProfessionQueue"}), + }, + { + text = L["Look at what's profitable to craft and manually add things to a queue"], + guides = {"openTSM", "openCrafting", "craftingCraftsTab", "openProfession", "useProfessionQueue"}, + }, + { + text = L["Craft specific one-off items without making a queue"], + guides = {"openProfession", "craftFromProfession"}, + }, + }, + }, + }, + { + text = L["Buy items from the AH"], + children = { + title = L["How would you like to shop?"], + buttons = { + { + text = L["Set up TSM to find cheap items on the AH"], + children = private:GetMakeGroupSteps({"shoppingOperation", "openShoppingAHTab", "shoppingGroupSearch", "shoppingWaitForScan"}), + }, + { + text = L["Search the AH for items to buy"], + guides = {"openShoppingAHTab", "shoppingFilterSearch", "shoppingWaitForScan"}, + }, + -- { + -- text = L["Buy materials for my TSM_Crafting queue"], + -- guides = {"notYetImplemented"}, + -- }, + { + text = L["Advanced topics..."], + children = { + title = L["How would you like to shop?"], + buttons = { + { + text = L["Snipe items as they are being posted to the AH"], + guides = {"openShoppingAHTab", "shoppingOtherSidebar", "shoppingSniperSearch"}, + }, + { + text = L["Look for items which can be destroyed to get raw mats"], + guides = {"openShoppingAHTab", "shoppingDestroySearch", "shoppingWaitForScan"}, + }, + { + text = L["Look for items which can be vendored for a profit"], + guides = {"openShoppingAHTab", "shoppingOtherSidebar", "shoppingVendorSearch", "shoppingWaitForScan"}, + }, + -- { + -- text = L["Setup TSM to automatically reset specific markets"], + -- children = private:GetMakeGroupSteps({"auctioningOperationReset", "openAuctioningAHTab", "notYetImplemented", "auctioningWaitForScan"}), + -- }, + }, + }, + }, + }, + }, + }, + { + text = L["Sell items on the AH and manage my auctions"], + children = { + title = L["What do you want to do?"], + buttons = { + { + text = L["Set up TSM to automatically post auctions"], + children = private:GetMakeGroupSteps({"auctioningOperationPost", "openAuctioningAHTab", "auctioningPostScan", "auctioningWaitForScan"}), + }, + { + text = L["Set up TSM to automatically cancel undercut auctions"], + children = private:GetMakeGroupSteps({"auctioningOperationCancel", "openAuctioningAHTab", "auctioningCancelScan", "auctioningWaitForScan"}), + }, + { + text = L["Post items manually from my bags"], + children = { + title = L["How would you like to post?"], + buttons = { + { + text = L["View current auctions and choose what price to post at"], + guides = {"openShoppingAHTab", "shoppingSearchFromBags", "shoppingWaitForScanSilent", "shoppingPostFromResults"}, + }, + { + text = L["Quickly post my items at some pre-determined price"], + guides = {"openTSM", "openShoppingOptions", "shoppingQuickPostingSettings", "openShoppingAHTab", "shoppingQuickPosting"}, + }, + }, + }, + }, + }, + }, + }, + -- { + -- text = "Mail items to another character", + -- children = { + -- title = "How would you like to mail items?", + -- buttons = { + -- { + -- text = "Setup TSM to mail items automatically", + -- guides = {"notYetImplemented"}, + -- }, + -- { + -- text = "Quickly send a specific item to another player (with or without COD)", + -- guides = {"notYetImplemented"}, + -- }, + -- { + -- text = "Mail disenchantable items to another character", + -- guides = {"notYetImplemented"}, + -- }, + -- { + -- text = "Send excess gold to another character", + -- guides = {"notYetImplemented"}, + -- }, + -- }, + -- }, + -- }, + -- { + -- text = "Move items between my bags, bank, and guild bank", + -- children = { + -- title = "How would you like to move items?", + -- buttons = { + -- { + -- text = "Setup TSM to move items automatically", + -- guides = {"notYetImplemented"}, + -- }, + -- { + -- text = "Move a few items quickly between my bags and bank or guild bank", + -- guides = {"notYetImplemented"}, + -- }, + -- { + -- text = "Get items out of the bank or guild bank to post on the AH", + -- guides = {"notYetImplemented"}, + -- }, + -- }, + -- }, + -- }, + }, +} \ No newline at end of file diff --git a/TradeSkillMaster/Assistant/Steps.lua b/TradeSkillMaster/Assistant/Steps.lua new file mode 100644 index 0000000..fa6d1cc --- /dev/null +++ b/TradeSkillMaster/Assistant/Steps.lua @@ -0,0 +1,629 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local Assistant = TSM.modules.Assistant +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local private = {stepData = {recentEvents = {}}} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Steps_private") +local eventObj = TSMAPI:GetEventObject() + +function Assistant:ClearStepData() + private.stepData = {recentEvents={}} +end + +function private.OnEvent(event, arg) + if not private.stepData then return end + local order = (private.stepData.recentEvents._order or 0) + 1 + private.stepData.recentEvents[event] = {arg=arg, order=order} + private.stepData.recentEvents._order = order +end +eventObj:SetCallbackAnyEvent(private.OnEvent) + +function private:IsTSMFrameIconSelected(iconText) + local path = TSM:GetTSMFrameSelectionPath() + return path and path[1].value == iconText +end +function private:GetPathLevelValue(iconText, level) + local path = TSM:GetTSMFrameSelectionPath() + return path and path[1] and path[1].value == iconText and path[level] and path[level].value +end +function private:GetGroupTreeSelection() + return private:GetPathLevelValue(L["Groups"], 2) +end +function private:GetGroupTab() + local temp = private:GetPathLevelValue(L["Groups"], 2) + return temp and temp[#temp] == private.stepData.selectedGroup and private:GetPathLevelValue(L["Groups"], 3) +end +function private:GetOperationModuleSelection() + return private:GetPathLevelValue(L["Module Operations / Options"], 2) +end +function private:GetOperationTreeSelection(module) + if private:GetOperationModuleSelection() ~= module then return end + return private:GetPathLevelValue(L["Module Operations / Options"], 3) +end +function private:GetOperationTab(module) + if not private:GetOperationTreeSelection(module) then return end + local temp = private:GetPathLevelValue(L["Module Operations / Options"], 3) + return temp and temp and temp[#temp] == private.stepData.selectedOperation and private:GetPathLevelValue(L["Module Operations / Options"], 4) +end + +function private:GetIsDoneStep(title, description, isDoneFunc) + local step = { + title = title, + description = description, + doneButton = L["I'm done."], + isDone = function(self) return private.stepData[self] and (not isDoneFunc or isDoneFunc()) end, + onDoneButtonClicked = function(self) private.stepData[self] = true end, + isCheckPoint = true + } + return step +end + +function private:GetEventIsDone(targetEvent) + local function isDone(self) + if private.stepData[self] then return true end + if not private.stepData.recentEvents[targetEvent] then return end + wipe(private.stepData.recentEvents) + private.stepData[self] = true + return true + end + + return isDone +end + +function private:PrependCreateOperationSteps(tbl, moduleLong, moduleShort, description, operationsIndex) + local steps = { + { + title = L["Go to the 'Operations' Tab"], + description = format(L["We will add a %s operation to this group through its 'Operations' tab. Click on that tab now."], moduleLong), + isDone = function() return private:GetGroupTab() == 1 end, + isCheckPoint = true, + }, + { + title = format(L["Create a %s Operation %d/5"], moduleShort, 1), + description = description, + isDone = function() return private:GetOperationTreeSelection(moduleShort) end, + }, + { + title = format(L["Create a %s Operation %d/5"], moduleShort, 2), + description = L["Select the 'Operations' page from the list on the left of the TSM window."], + isDone = function() + local selection = private:GetOperationTreeSelection(moduleShort) + if selection and #selection == 1 and selection[1] == tostring(operationsIndex) then + private.stepData.operationsPageClicked = true + end + return private.stepData.operationsPageClicked + end, + }, + { + title = format(L["Create a %s Operation %d/5"], moduleShort, 3), + description = format(L["Create a new %s operation by typing a name for the operation into the 'Operation Name' box and pressing the key."], moduleLong), + isDone = function() + local selection = private:GetOperationTreeSelection(moduleShort) + return selection and #selection > 1 and selection[1] == tostring(operationsIndex) + end, + }, + { + title = format(L["Create a %s Operation %d/5"], moduleShort, 4), + description = L["Assign this operation to the group you previously created by clicking on the 'Yes' button in the popup that's now being shown."], + isDone = function() + for i=1, 100 do + local popup = _G["StaticPopup"..i] + if not popup then break end + if popup:IsVisible() and popup.which == "TSM_NEW_OPERATION_ADD" then + return + end + end + return true + end, + }, + { + title = format(L["Create a %s Operation %d/5"], moduleShort, 5), + description = L["Select your new operation in the list of operation along the left of the TSM window (if it's not selected automatically) and click on the button below.\n\nCurrently Selected Operation: %s"], + getDescArgs = function() + local selection = private:GetOperationTreeSelection(moduleShort) + if selection and #selection > 1 then + return TSMAPI.Design:GetInlineColor("link")..selection[#selection].."|r" + else + return TSMAPI.Design:GetInlineColor("link")..L[""].."|r" + end + end, + isDone = function() return private:GetOperationTab(moduleShort) end, + doneButton = L["My new operation is selected."], + onDoneButtonClicked = function() + local selection = private:GetOperationTreeSelection(moduleShort) + if selection and #selection > 1 then + private.stepData.selectedOperation = selection[#selection] + else + TSM:Print(L["Please select the new operation you've created."]) + end + end, + isCheckPoint = true, + }, + } + + -- prepend all the steps to the passed table + for i, step in ipairs(steps) do + tinsert(tbl, i, step) + end +end + +local tsmSteps = { + ["notYetImplemented"] = { + private:GetIsDoneStep( + "Not Yet Implemented", + "This step is not yet implemented.\n\nTHIS SHOULD NEVER BE SEEN IN A RELEASE VERSION OF TSM!" + ), + }, + ["openTSM"] = { + { + title = L["Open the TSM Window"], + description = L["Type '/tsm' or click on the minimap icon to open the main TSM window."], + isDone = function() return TSM:TSMFrameIsVisible() end, + }, + }, + ["openGroups"] = { + { + title = L["Click on the Groups Icon"], + description = L["Along top of the window, on the left side, click on the 'Groups' icon to open up the TSM group settings."], + isDone = function() return private:IsTSMFrameIconSelected(L["Groups"]) end, + }, + }, + ["newGroup"] = { + { + title = L["Go to the 'Groups' Page"], + description = L["In the list on the left, select the top-level 'Groups' page."], + isDone = function() + local selection = private:GetGroupTreeSelection() + if selection and #selection == 1 and selection[1] == "1" then + private.stepData.groupsPageClicked = true + end + return private.stepData.groupsPageClicked + end, + }, + { + title = L["Create a New Group"], + description = L["Create a new group by typing a name for the group into the 'Group Name' box and pressing the key."], + isDone = private:GetEventIsDone("TSM:GROUPS:NEWGROUP"), + }, + }, + ["selectGroup"] = { + { + title = L["Select Existing Group"], + description = L["Select the group you'd like to use. Once you have done this, click on the button below.\n\nCurrently Selected Group: %s"], + getDescArgs = function() + local selection = private:GetGroupTreeSelection() + if selection and #selection > 1 then + return TSMAPI:FormatGroupPath(selection[#selection], true) + else + return TSMAPI.Design:GetInlineColor("link")..L[""].."|r" + end + end, + isDone = function() return private:GetGroupTab() end, + doneButton = L["My group is selected."], + onDoneButtonClicked = function() + local selection = private:GetGroupTreeSelection() + if selection and #selection > 1 then + private.stepData.selectedGroup = selection[#selection] + else + TSM:Print(L["Please select the group you'd like to use."]) + end + end, + }, + }, + ["groupImportItems"] = { + { + title = L["Go to the 'Import/Export' Tab"], + description = L["We will import items into this group using the import list you have."], + isDone = function() return private:GetGroupTab() == 3 or private.stepData.importedItems end, + }, + { + title = L["Enter Import String"], + description = L["Paste your import string into the 'Import String' box and hit the key to import the list of items."], + isDone = function() + if private.stepData.importedItems then return true end + if not private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"] then return end + local arg = CopyTable(private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"].arg) + wipe(private.stepData.recentEvents) + if arg.isImport then + if arg.num == 0 then + TSM:Print(L["Looks like no items were imported. This might be because they are already in another group in which case you might consider checking the 'Move Already Grouped Items' box to force them to move to this group."]) + else + private.stepData.importedItems = true + return true + end + end + end, + }, + }, + ["groupAddFromBags"] = { + { + title = L["Go to the 'Items' Tab"], + description = L["We will add items to this group through its 'Items' tab. Click on that tab now."], + isDone = function() return private:GetGroupTab() == 2 or private.stepData.addedItems end, + }, + { + title = L["Add Items to this Group"], + description = L["Select the items you want to add in the left column and then click on the 'Add >>>' button at the top to add them to this group."], + isDone = function() + if private.stepData.addedItems then return true end + if not private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"] then return end + local arg = CopyTable(private.stepData.recentEvents["TSM:GROUPS:ADDITEMS"].arg) + wipe(private.stepData.recentEvents) + if not arg.isImport then + private.stepData.addedItems = true + return true + end + end, + }, + }, +} + +local craftingSteps = { + ["openCrafting"] = { + { + title = L["Click on the Crafting Icon"], + description = L["Along top of the window, on the right side, click on the 'Crafting' icon to open up the TSM_Crafting page."], + isDone = function() return private:GetPathLevelValue("Crafting", 2) == 1 end, + }, + }, + ["craftingCraftsTab"] = { + { + title = L["Select the 'Crafts' Tab"], + description = L["At the top, switch to the 'Crafts' tab in order to view a list of crafts you can make."], + isDone = function() return TSM:TSMFrameIsVisible() end, + }, + private:GetIsDoneStep( + L["Queue Profitable Crafts"], + L["You can use the filters at the top of the page to narrow down your search and click on a column to sort by that column. Then, left-click on a row to add one of that item to the queue, and right-click to remove one.\n\nOnce you're done adding items to the queue, click the button below."] + ), + }, + ["openProfession"] = { + { + title = L["Open up Your Profession"], + description = L["Open one of the professions which you would like to use to craft items."], + isDone = function() return TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") end, + }, + }, + ["useProfessionQueue"] = { + { + title = L["Show the Queue"], + description = L["Click on the 'Show Queue' button at the top of the TSM_Crafting window to show the queue if it's not already visible."], + isDone = function() local status = TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") return status and status.queue end, + }, + private:GetIsDoneStep( + L["Craft Items from Queue"], + L["You can craft items either by clicking on rows in the queue which are green (meaning you can craft all) or blue (meaning you can craft some) or by clicking on the 'Craft Next' button at the bottom.\n\nClick on the button below when you're done reading this. There is another guide which tells you how to buy mats required for your queue."] + ), + }, + ["craftingOperation"] = { + { + title = L["Select the 'General' Tab"], + description = "Select the 'General' tab within the operation to set the general options for the TSM_Crafting operation.", + isDone = function() return private:GetOperationTab("Crafting") == 1 end + }, + private:GetIsDoneStep( + L["Set Max Restock Quantity"], + L["The 'Max Restock Quantity' defines how many of each item you want to restock up to when using the restock queue, taking your inventory into account.\n\nOnce you're done adjusting this setting, click the button below."] + ), + private:GetIsDoneStep( + L["Set Minimum Profit"], + L["If you'd like, you can adjust the value in the 'Minimum Profit' box in order to specify the minimum profit before Crafting will queue these items.\n\nOnce you're done adjusting this setting, click the button below."] + ), + private:GetIsDoneStep( + L["Set Other Options"], + L["You can look through the tooltips of the other options to see what they do and decide if you want to change their values for this operation.\n\nOnce you're done, click on the button below."] + ), + }, + ["professionRestock"] = { + { + title = L["Switch to the 'TSM Groups' Tab"], + description = L["Along the top of the TSM_Crafting window, click on the 'TSM Groups' button."], + isDone = function() local status = TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") return status and status.page == "groups" end, + }, + { + title = L["Select Group and Click Restock Button"], + description = L["First, ensure your new group is selected in the group-tree and then click on the 'Restock Selected Groups' button at the bottom."], + isDone = function(self) + if private.stepData[self] then return true end + if not private.stepData.recentEvents["CRAFTING:QUEUE:RESTOCKED"] then return end + local arg = private.stepData.recentEvents["CRAFTING:QUEUE:RESTOCKED"].arg + wipe(private.stepData.recentEvents) + if arg == 0 then + TSM:Print(L["Looks like no items were added to the queue. This may be because you are already at or above your restock levels, or there is nothing profitable to queue."]) + else + private.stepData[self] = true + return true + end + end, + }, + }, + ["craftFromProfession"] = { + { + title = L["Switch to the 'Professions' Tab"], + description = L["Along the top of the TSM_Crafting window, click on the 'Professions' button."], + isDone = function() local status = TSMAPI:ModuleAPI("Crafting", "getCraftingFrameStatus") return status and status.page == "profession" end, + }, + private:GetIsDoneStep( + L["Select the Craft"], + L["Just like the default profession UI, you can select what you want to craft from the list of crafts for this profession. Click on the one you want to craft.\n\nOnce you're done, click the button below."] + ), + private:GetIsDoneStep( + L["Create the Craft"], + L["You can now use the buttons near the bottom of the TSM_Crafting window to create this craft.\n\nOnce you're done, click the button below."] + ), + }, +} +private:PrependCreateOperationSteps(craftingSteps["craftingOperation"], "TSM_Crafting", "Crafting", L["A TSM_Crafting operation will allow you automatically queue profitable items from the group you just made. To create one for this group, scroll down to the 'Crafting' section, and click on the 'Create Crafting Operation' button."], 2) + +local auctioningSteps = { + ["auctioningOperationPost"] = { + { + title = L["Select the Post Tab"], + description = L["Select the 'Post' tab within the operation to set the posting options for the TSM_Auctioning operation."], + isDone = function() return private:GetOperationTab("Auctioning") == 2 end + }, + private:GetIsDoneStep( + L["Set Auction Settings"], + L["The first set of posting settings are under the 'Auction Settings' header. These control things like stack size and auction duration. Read the tooltips of the individual settings to see what they do and set them appropriately."] + ), + private:GetIsDoneStep( + L["Set Auction Price Settings"], + L["The second set of posting settings are under the 'Auction Price Settings' header. These include the percentage of the buyout which the bid will be set to, and how much you want to undercut by. Read the tooltips of the individual settings to see what they do and set them appropriately."] + ), + private:GetIsDoneStep( + L["Set Posting Price Settings"], + L["The final set of posting settings are under the 'Posting Price Settings' header. These define the price ranges which Auctioning will post your items within. Read the tooltips of the individual settings to see what they do and set them appropriately."] + ), + }, + ["auctioningOperationCancel"] = { + { + title = L["Select the Cancel Tab"], + description = L["Select the 'Cancel' tab within the operation to set the canceling options for the TSM_Auctioning operation."], + isDone = function() return private:GetOperationTab("Auctioning") == 3 end + }, + private:GetIsDoneStep( + L["Set Cancel Settings"], + L["These settings control when TSM_Auctioning will cancel your auctions. Read the tooltips of the individual settings to see what they do and set them appropriately."] + ), + }, + ["openAuctioningAHTab"] = { + { + title = L["Open the Auction House"], + description = L["Go to the Auction House and open it."], + isDone = function() return AuctionFrame and AuctionFrame:IsVisible() end, + }, + { + title = L["Click on the Auctioning Tab"], + description = L["Along the bottom of the AH are various tabs. Click on the 'Auctioning' AH tab."], + isDone = function() return TSMAPI:AHTabIsVisible("Auctioning") end, + }, + }, + ["auctioningPostScan"] = { + { + title = L["Select Group and Start Scan"], + description = L["First, ensure your new group is selected in the group-tree and then click on the 'Start Post Scan' button at the bottom of the tab."], + isDone = private:GetEventIsDone("AUCTIONING:POST:START"), + }, + }, + ["auctioningCancelScan"] = { + { + title = L["Select Group and Start Scan"], + description = L["First, ensure your new group is selected in the group-tree and then click on the 'Start Cancel Scan' button at the bottom of the tab."], + isDone = private:GetEventIsDone("AUCTIONING:CANCEL:START"), + }, + }, + ["auctioningWaitForScan"] = { + { + title = L["Waiting for Scan to Finish"], + description = L["Please wait..."], + isDone = private:GetEventIsDone("AUCTIONING:SCANDONE"), + }, + private:GetIsDoneStep( + L["Act on Scan Results"], + L["Now that the scan is finished, you can look through the results shown in the log, and for each item, decide what action you want to take.\n\nOnce you're done, click on the button below."] + ), + }, +} +private:PrependCreateOperationSteps(auctioningSteps["auctioningOperationPost"], "TSM_Auctioning", "Auctioning", L["A TSM_Auctioning operation will allow you to set rules for how auctionings are posted/canceled/reset on the auction house. To create one for this group, scroll down to the 'Auctioning' section, and click on the 'Create Auctioning Operation' button."], 3) +private:PrependCreateOperationSteps(auctioningSteps["auctioningOperationCancel"], "TSM_Auctioning", "Auctioning", L["A TSM_Auctioning operation will allow you to set rules for how auctionings are posted/canceled/reset on the auction house. To create one for this group, scroll down to the 'Auctioning' section, and click on the 'Create Auctioning Operation' button."], 3) + +local shoppingSteps = { + ["shoppingOperation"] = { + { + title = L["Select the 'General' Tab"], + description = L["Select the 'General' tab within the operation to set the general options for the TSM_Shopping operation."], + isDone = function() return private:GetOperationTab("Shopping") == 1 end + }, + private:GetIsDoneStep( + L["Set a Maximum Price"], + L["The 'Maxium Auction Price (per item)' is the most you want to pay for the items you've added to your group. If you're not sure what to set this to and have TSM_AuctionDB installed (and it contains data from recent scans), you could try '90% dbmarket' for this option.\n\nOnce you're done adjusting this setting, click the button below."] + ), + private:GetIsDoneStep( + L["Set Other Options"], + L["You can look through the tooltips of the other options to see what they do and decide if you want to change their values for this operation.\n\nOnce you're done, click on the button below."] + ), + }, + ["openShoppingOptions"] = { + { + title = L["Click on the Module Operations / Options Icon"], + description = L["Along top of the window, on the left side, click on the 'Module Operations / Options' icon to open up the TSM module settings."], + isDone = function() return private:IsTSMFrameIconSelected(L["Module Operations / Options"]) end, + }, + { + title = L["Click on the Shopping Tab"], + description = L["Select the 'Shopping' tab to open up the settings for TSM_Shopping."], + isDone = function() return private:GetOperationModuleSelection() == "Shopping" end, + }, + { + title = L["Select the Options Page"], + description = L["Select the 'Options' page to change general settings for TSM_Shopping"], + isDone = function() return private:GetOperationTreeSelection("Shopping")[1] == "1" end, + }, + }, + ["openShoppingAHTab"] = { + { + title = L["Open the Auction House"], + description = L["Go to the Auction House and open it."], + isDone = function() return AuctionFrame and AuctionFrame:IsVisible() end, + }, + { + title = L["Click on the Shopping Tab"], + description = L["Along the bottom of the AH are various tabs. Click on the 'Shopping' AH tab."], + isDone = function() return TSMAPI:AHTabIsVisible("Shopping") end, + }, + }, + ["shoppingGroupSearch"] = { + { + title = L["Show the 'TSM Groups' Sidebar Tab"], + description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'TSM Groups' one."], + isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "groups" end, + }, + { + title = L["Select Group and Start Scan"], + description = L["First, ensure your new group is selected in the group-tree and then click on the 'Start Search' button at the bottom of the sidebar window."], + isDone = private:GetEventIsDone("SHOPPING:GROUPS:STARTSCAN"), + }, + }, + ["shoppingFilterSearch"] = { + { + title = L["Show the 'Custom Filter' Sidebar Tab"], + description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'Custom Filter' one."], + isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "custom" end, + }, + { + title = L["Enter Filters and Start Scan"], + description = L["You can use this sidebar window to help build AH searches. You can also type the filter directly in the search bar at the top of the AH window.\n\nEnter your filter and start the search."], + isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTFILTERSCAN"), + }, + }, + ["shoppingSearchFromBags"] = { + { + title = L["Shift-Click Item in Your Bags"], + description = L["If you open your bags and shift-click the item in your bags, it will be placed in Shopping's search bar. You may need to put your cursor in the search bar first. Alternatively, you can type the name of the item manually in the search bar and then hit enter or click the 'Search' button."], + isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTFILTERSCAN"), + }, + }, + ["shoppingOtherSidebar"] = { + { + title = L["Show the 'Other' Sidebar Tab"], + description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'Other' one."], + isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "other" end, + }, + }, + ["shoppingVendorSearch"] = { + { + title = L["Start Vendor Search"], + description = L["Click on the 'Start Vendor Search' button in the sidebar window."], + isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTVENDORSCAN"), + }, + }, + ["shoppingSniperSearch"] = { + { + title = L["Start Sniper"], + description = L["Click on the 'Start Sniper' button in the sidebar window."], + isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTSNIPER"), + }, + private:GetIsDoneStep( + L["Sniping Scan in Progress"], + L["The 'Sniper' feature will constantly search the last page of the AH which shows items as they are being posted. This does not search existing auctions, but lets you buy items which are posted cheaply right as they are posted and buy them before anybody else can.\n\nYou can adjust the settings for what auctions are shown in TSM_Shopping's options.\n\nClick the button below when you're done reading this."] + ), + }, + ["shoppingDestroySearch"] = { + { + title = L["Switch to Destroy Mode"], + description = L["Under the search bar, on the left, you can switch between normal and destroy mode for TSM_Shopping. Switch to 'Destroy Mode' now."], + isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSearchMode") == "destroy" end, + }, + { + title = L["Start a Destroy Search"], + description = L["Type a raw material you would like to obtain via destroying in the search bar and start the search. For example: 'Ink of Dreams' or 'Spirit Dust'."], + isDone = private:GetEventIsDone("SHOPPING:SEARCH:STARTDESTROYSCAN"), + }, + }, + ["shoppingWaitForScan"] = { + { + title = L["Waiting for Scan to Finish"], + description = L["Please wait..."], + isDone = function(self) + if private.stepData[self] then return true end + if not AuctionFrame:IsVisible() or not TSMAPI:AHTabIsVisible("Shopping") then return end + if not private.stepData.recentEvents["SHOPPING:SEARCH:SCANDONE"] then return end + local arg = private.stepData.recentEvents["SHOPPING:SEARCH:SCANDONE"].arg + wipe(private.stepData.recentEvents) + if arg == 0 then + TSM:Print(L["Looks like no items were found. You can either try searching for something else, or simply close the Assistant window if you're done."]) + else + private.stepData[self] = true + return true + end + end, + }, + }, + ["shoppingWaitForScanSilent"] = { + { + title = L["Waiting for Scan to Finish"], + description = L["Please wait..."], + isDone = private:GetEventIsDone("SHOPPING:SEARCH:SCANDONE"), + }, + }, + ["shoppingPostFromResults"] = { + { + title = L["Post Your Items"], + description = L["If there are no auctions currently posted for this item, simmply click the 'Post' button at the bottom of the AH window. Otherwise, select the auction you'd like to undercut first."], + isDone = private:GetEventIsDone("TSM:AUCTIONCONTROL:POSTSHOWN"), + }, + { + title = L["Adjust Post Parameters"], + description = L["In the confirmation window, you can adjust the buyout price, stack sizes, and auction duration. Once you're done, click the 'Post' button to post your items to the AH."], + isDone = private:GetEventIsDone("TSM:AUCTIONCONTROL:ITEMPOSTED"), + }, + }, + ["shoppingQuickPostingSettings"] = { + private:GetIsDoneStep( + L["Set Quick Posting Duration"], + L["Underneath the 'Posting Options' header, there are two settings which control the Quick Posting feature of TSM_Shopping. The first one is the duration which Quick Posting should use when posting your items to the AH. Change this to your preferred duration for Quick Posting."] + ), + private:GetIsDoneStep( + L["Set Quick Posting Price"], + L["Underneath the 'Posting Options' header, there are two settings which control the Quick Posting feature of TSM_Shopping. The second one is the price at which the Quick Posting will post items to the AH. This should generally not be a fixed gold value, since it will apply to every item. Change this setting to what you'd like to post items at with Quick Posting."] + ), + }, + ["shoppingQuickPosting"] = { + { + title = L["Show the 'Quick Posting' Sidebar Tab"], + description = L["Underneath the search bar at the top of the 'Shopping' AH tab are a handful of buttons which change what's displayed in the sidebar window. Click on the 'Custom Filter' one."], + isDone = function() return TSMAPI:ModuleAPI("Shopping", "getSidebarPage") == "quick" end, + }, + { + title = L["Post an Item"], + description = L["Shift-Click an item in the sidebar window to immediately post it at your quick posting price."], + isDone = private:GetEventIsDone("SHOPPING:QUICKPOST:POSTEDITEM"), + }, + }, +} +private:PrependCreateOperationSteps(shoppingSteps["shoppingOperation"], "TSM_Shopping", "Shopping", L["A TSM_Shopping operation will allow you to set a maximum price we want to pay for the items in the group you just made. To create one for this group, scroll down to the 'Shopping' section, and click on the 'Create Shopping Operation' button."], 2) + +do + Assistant.STEPS = {} + local addonSteps = { + ["TradeSkillMaster"] = tsmSteps, + ["TradeSkillMaster_Auctioning"] = auctioningSteps, + ["TradeSkillMaster_Crafting"] = craftingSteps, + ["TradeSkillMaster_Shopping"] = shoppingSteps, + } + for addon, moduleSteps in pairs(addonSteps) do + if select(4, GetAddOnInfo(addon)) then + for key, steps in pairs(moduleSteps) do + assert(not Assistant.STEPS[key], format("Multiples steps with key '%s' exist!", key)) + Assistant.STEPS[key] = steps + end + end + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionControl.lua b/TradeSkillMaster/Auction/AuctionControl.lua new file mode 100644 index 0000000..fdaea22 --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionControl.lua @@ -0,0 +1,824 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +TSMAPI.AuctionControl = {} +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionControl_private") +LibStub("AceEvent-3.0"):Embed(private) +private.matchList = {} +private.currentPage = {} + + +local function GetNumInBags(baseItemString) + local num = 0 + for _, _, itemString, quantity in TSMAPI:GetBagIterator() do + if TSMAPI:GetBaseItemString(itemString) == baseItemString then + num = num + quantity + end + end + return num +end + +local function ValidateAuction(index, list) + if not private.currentAuction then return end + local itemString, count, buyout, data, _ + if type(list) == "table" then + itemString, count, buyout = unpack(list) + elseif type(list) == "string" then + itemString = TSMAPI:GetItemString(GetAuctionItemLink(list, index)) + -- _, _, count, _, _, _, _, _, _, buyout = GetAuctionItemInfo(list, index) + _, _, count, _, _, _, _, _, buyout = GetAuctionItemInfo(list, index) + data = {itemString, count, buyout} + else + return + end + return count == private.currentAuction.count and buyout == private.currentAuction.buyout and itemString == private.currentAuction.itemString, data +end + +local diffFrame = CreateFrame("Frame") +diffFrame:Hide() +diffFrame.num = 0 +diffFrame:RegisterEvent("CHAT_MSG_SYSTEM") +diffFrame:RegisterEvent("UI_ERROR_MESSAGE") +diffFrame:SetScript("OnEvent", function(self, event, arg) + if event == "UI_ERROR_MESSAGE" then + if arg == ERR_ITEM_NOT_FOUND then + local auctionExists + for i=1, GetNumAuctionItems("list") do + if ValidateAuction(i, "list") then + auctionExists = true + break + end + end + if not auctionExists then + self.num = self.num - 1 + end + elseif arg == ERR_AUCTION_HIGHER_BID then + local auctionExists + for i=1, GetNumAuctionItems("list") do + if ValidateAuction(i, "list") then + auctionExists = true + break + end + end + if not auctionExists then + self.num = self.num - 1 + end + end + elseif event == "CHAT_MSG_SYSTEM" then + if arg == ERR_AUCTION_BID_PLACED then + self.num = self.num - 1 + end + end +end) + +local customPriceWarned +function private:SetCurrentAuction(record) + if not record then + private.currentAuction = nil + return + end + + local buyout = record.buyout + if private.confirmationMode == "Post" and not record:IsPlayer() then + local undercut = TSMAPI:ParseCustomPrice(private.postUndercut) + undercut = undercut and undercut(record.parent:GetItemString()) + if not undercut and not customPriceWarned then + TSM:Print(L["Invalid custom price for undercut amount. Using 1c instead."]) + customPriceWarned = true + undercut = 1 + end + buyout = buyout - undercut + end + private.currentAuction = { + link = record.parent.itemLink, + itemString = record.parent:GetItemString(), + buyout = buyout, + count = record.count, + numAuctions = record.numAuctions, + seller = record.seller, + isPlayer = record:IsPlayer(), + num = 1, + destroyingNum = record.parent.destroyingNum, + } +end + +local count = 0 +function private:FindCurrentAuctionForBuyout(noCache, resetCount) + if not private.currentAuction then return end + if diffFrame.num > 0 then + return TSMAPI:CreateTimeDelay(0.2, private.FindCurrentAuctionForBuyout) + end + if resetCount then + count = 0 + end + count = count + 1 + + private:UpdateMatchList(true) + if #private.matchList > 0 then + -- the next item is on the current page + private:UpdateAuctionConfirmation() + return + end + + private.matchList = {} + private.currentPage = {} + + if count > 3 then + -- auction no longer exists + TSM:Print(L["Skipping auction which no longer exists."]) + diffFrame.num = diffFrame.num - 1 + private.justBought = true + private:AUCTION_ITEM_LIST_UPDATE() + return + end + TSMAPI.AuctionScan:FindAuction(private.OnAuctionFound, {itemString=private.currentAuction.itemString, buyout=private.currentAuction.buyout, count=private.currentAuction.count, seller=private.currentAuction.seller}, not noCache) + private.isSearching = true +end + +function private:DoBuyout() + if private.isSearching or not private.currentAuction or not private.confirmationFrame:IsVisible() then return end + + for i=#private.matchList, 1, -1 do + local aucIndex = private.matchList[i] + tremove(private.matchList, i) + tremove(private.currentPage, aucIndex) + if ValidateAuction(aucIndex, "list") then + PlaceAuctionBid("list", aucIndex, private.currentAuction.buyout) + private.justBought = true + diffFrame.num = diffFrame.num + 1 + return + end + end + + private:FindCurrentAuctionForBuyout() +end + +function private:DoCancel() + if private.isSearching or not private.currentAuction or not private.confirmationFrame:IsVisible() then return end + + local function OnCancel() + private.justBought = true + private:AUCTION_ITEM_LIST_UPDATE() + end + + for i=GetNumAuctionItems("owner"), 1, -1 do + if ValidateAuction(i, "owner") then + CancelAuction(i) + -- wait for all the events that are triggered by this action + private:RegisterMessage("TSM_AH_EVENTS", OnCancel) + TSMAPI:WaitForAuctionEvents("Cancel") + return + end + end + + TSM:Print(L["Auction not found. Skipped."]) + private.justBought = true + private:AUCTION_ITEM_LIST_UPDATE() +end + +function private:DoPost(postInfo) + if private.isSearching or not postInfo or not private.postFrame:IsVisible() then return end + + if not AuctionFrameAuctions.duration then + -- Fix in case Blizzard_AuctionUI hasn't set this value yet (which could cause an error) + AuctionFrameAuctions.duration = postInfo.duration + end + + local bag, slot + for b, s, itemString in TSMAPI:GetBagIterator() do + if postInfo.itemString == itemString then + bag, slot = b, s + break + end + end + if not bag then + TSM:Print(L["Item not found in bags. Skipping"]) + return + end + + local function OnPost() + private.postFrame:Hide() + postInfo.duration = postInfo.duration == 1 and 3 or 4 + TSM:AuctionControlCallback("OnPost", postInfo) + TSMAPI:FireEvent("TSM:AUCTIONCONTROL:ITEMPOSTED", postInfo) + end + private:RegisterMessage("TSM_AH_EVENTS", OnPost) + TSMAPI:WaitForAuctionEvents("Post", postInfo.numAuctions > 1) + + PickupContainerItem(bag, slot) + ClickAuctionSellItemButton(AuctionsItemButton, "LeftButton") + StartAuction(postInfo.bid, postInfo.buyout, postInfo.duration, postInfo.stackSize, postInfo.numAuctions) +end + +function private:UpdateMatchList(noPageScanning) + private.matchList = {} + + if noPageScanning then + for i=1, #private.currentPage do + if ValidateAuction(i, private.currentPage[i]) then + tinsert(private.matchList, i) + end + end + else + private.currentPage = {} + for i=1, GetNumAuctionItems("list") do + local isValid, data = ValidateAuction(i, "list") + private.currentPage[i] = data + if isValid then + tinsert(private.matchList, i) + end + end + end +end + +function private:OnAuctionFound(cacheIndex) + if not private.isSearching or not private.currentAuction then return end + private.isSearching = nil + + private:UpdateMatchList() + + if #private.matchList == 0 then + private:FindCurrentAuctionForBuyout(true) + else + private.currentCacheIndex = cacheIndex + private:UpdateAuctionConfirmation() + end +end + +function private:AUCTION_ITEM_LIST_UPDATE() + if not private.currentAuction or not TSMAPI:AHTabIsVisible(private.module) then return end + + if private.justBought then + private.justBought = nil + private.currentAuction.num = private.currentAuction.num + 1 + local prevAuction = CopyTable(private.currentAuction) + if private.currentAuction.num > private.currentAuction.numAuctions then + TSMAPI.AuctionControl:HideConfirmation() + else + if #private.matchList > 0 then + private:UpdateAuctionConfirmation() + else + private:FindCurrentAuctionForBuyout(nil, true) + end + end + + if private.currentCacheIndex then + TSMAPI.AuctionScan:CacheRemove(prevAuction.itemString, private.currentCacheIndex) + private.currentCacheIndex = nil + end + + TSM:AuctionControlCallback("OnBuyout", prevAuction) + end +end + +function TSM:AuctionControlCallback(...) + if not private.callback then return end + private.callback(...) +end + + +-- ************************************************************************** +-- Utility TSMAPI Functions +-- ************************************************************************** + +function TSMAPI.AuctionControl:IsConfirmationVisible() + return (private.confirmationFrame and private.confirmationFrame:IsVisible()) or (private.postFrame and private.postFrame:IsVisible()) +end + +function TSMAPI.AuctionControl:IsBuyingComplete() + return diffFrame.num <= 0 +end + + +-- ************************************************************************** +-- GUI Show/Hide/Update Functions +-- ************************************************************************** + +function TSMAPI.AuctionControl:ShowControlButtons(parent, rt, callback, module, postBidPercent, postUndercut) + private.confirmationFrame = private.confirmationFrame or private:CreateConfirmationFrame(parent) + private.postFrame = private.postFrame or private:CreatePostFrame(parent) + private.controlButtons = private.controlButtons or private:CreateControlButtons(parent) + private.controlButtons:Show() + private.rt = rt + private.callback = callback + private.module = module + private.postBidPercent = postBidPercent + private.postUndercut = postUndercut + return private.controlButtons +end + +function TSMAPI.AuctionControl:HideControlButtons() + private.controlButtons:Hide() + private.rt = nil + private.callback = nil + TSMAPI.AuctionControl:HideConfirmation() +end + +function TSMAPI.AuctionControl:SetNoResultItem(itemString, buyout) + if not itemString or not buyout then return end + local link = select(2, TSMAPI:GetSafeItemInfo(itemString)) + private.currentAuction = { + link = link, + itemString = itemString, + buyout = buyout, + count = 1, + numAuctions = 1, + num = 1, + isNoResult = true, + } +end + +function private:ShowConfirmationWindow() + if private.confirmationFrame:IsVisible() then + private.confirmationFrame:UpdateStrata() + return + elseif private.postFrame:IsVisible() then + private.postFrame:UpdateStrata() + return + end + private:SetCurrentAuction(private.rt:GetSelectedAuction()) + if not private.currentAuction then return end + + private:RegisterEvent("AUCTION_ITEM_LIST_UPDATE") + diffFrame.num = 0 + diffFrame:Show() + private.confirmationFrame:Show() + private.confirmationFrame.proceed:Disable() + private.confirmationFrame.linkText:SetText("") + private.confirmationFrame.quantityText:SetText("") + private.confirmationFrame.buyoutText:SetText("") + private.confirmationFrame.buyoutText2:SetText("") + private.confirmationFrame.purchasedText:SetText("") + private.confirmationFrame.searchingText:SetText(L["Searching for item..."]) + if private.confirmationMode == "Buyout" then + private:FindCurrentAuctionForBuyout(nil, true) + else + private:UpdateAuctionConfirmation() + end +end + +function private:ShowPostWindow() + if private.confirmationFrame:IsVisible() then + private.confirmationFrame:UpdateStrata() + return + elseif private.postFrame:IsVisible() then + private.postFrame:UpdateStrata() + return + end + if not private.currentAuction or not private.currentAuction.isNoResult then + private:SetCurrentAuction(private.rt:GetSelectedAuction()) + end + if not private.currentAuction then return end + + private:RegisterEvent("AUCTION_ITEM_LIST_UPDATE") + diffFrame.num = 0 + diffFrame:Show() + private.postFrame:Show() + private:UpdatePostFrame() + TSMAPI:FireEvent("TSM:AUCTIONCONTROL:POSTSHOWN") +end + +function TSMAPI.AuctionControl:HideConfirmation() + private:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + if private.confirmationFrame then private.confirmationFrame:Hide() end + if private.postFrame then private.postFrame:Hide() end + diffFrame:Hide() + private.isSearching = nil + private:SetCurrentAuction() + TSMAPI.AuctionScan:StopFindScan() +end + +function private:UpdateAuctionConfirmation() + local buyoutText = TSMAPI:FormatTextMoneyIcon(private.currentAuction.buyout, nil, true) + local itemBuyoutText = TSMAPI:FormatTextMoneyIcon(floor(private.currentAuction.buyout/private.currentAuction.count), nil, true) + + private.confirmationFrame.searchingText:SetText("") + private.confirmationFrame.linkText:SetText(private.currentAuction.link) + private.confirmationFrame.quantityText:SetText("x"..private.currentAuction.count) + private.confirmationFrame.buyoutText:SetText(format(L["Item Buyout: %s"], itemBuyoutText)) + private.confirmationFrame.buyoutText2:SetText(format(L["Auction Buyout: %s"], buyoutText)) + if private.confirmationMode == "Buyout" then + private.confirmationFrame.proceed:SetText(BUYOUT) + private.confirmationFrame.purchasedText:SetText(format(L["Purchasing Auction: %d/%d"], private.currentAuction.num, private.currentAuction.numAuctions)) + elseif private.confirmationMode == "Cancel" then + private.confirmationFrame.proceed:SetText(CANCEL) + private.confirmationFrame.purchasedText:SetText(format(L["Canceling Auction: %d/%d"], private.currentAuction.num, private.currentAuction.numAuctions)) + end + private.confirmationFrame.proceed:Enable() +end + +function private:UpdatePostFrame() + local maxQuantity = select(8, TSMAPI:GetSafeItemInfo(private.currentAuction.link)) + local numInBags = GetNumInBags(TSMAPI:GetBaseItemString(private.currentAuction.itemString)) + local stackSize = min(private.currentAuction.count, numInBags) + local currentPerItem = floor(private.currentAuction.buyout/private.currentAuction.count) + local currentBuyout = stackSize == private.currentAuction.count and private.currentAuction.buyout or (currentPerItem*stackSize) + + private.postFrame.numInBags = numInBags + private.postFrame.linkText:SetText(private.currentAuction.link) + private.postFrame.proceed:Enable() + private.postFrame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(currentBuyout, nil, nil, nil, true)) + private.postFrame.perItemInputBox:SetText(TSMAPI:FormatTextMoney(currentPerItem, nil, nil, nil, true)) + private.postFrame.numAuctionsInputBox.max = numInBags + private.postFrame.numAuctionsInputBox.btn:SetText(format(L["max %d"], floor(numInBags/stackSize))) + private.postFrame.numAuctionsInputBox:SetNumber(1) + private.postFrame.stackSizeInputBox.max = min(numInBags, maxQuantity) + private.postFrame.stackSizeInputBox.btn:SetText(format(L["max %d"], private.postFrame.stackSizeInputBox.max)) + private.postFrame.stackSizeInputBox:SetNumber(stackSize) + private.postFrame.durationDropdown:SetValue(TSM.db.profile.postDuration) +end + + +-- ************************************************************************** +-- GUI Creation Code +-- ************************************************************************** + +function private:CreateConfirmationFrame(parent) + local frame = CreateFrame("Frame", nil, parent) + TSMAPI.Design:SetFrameBackdropColor(frame) + frame:Hide() + -- frame:SetPoint("CENTER") + frame:SetPoint("BOTTOMRIGHT") + frame:SetFrameStrata("DIALOG") + frame:SetWidth(300) + frame:SetHeight(150) + frame.UpdateStrata = function() + frame:SetFrameStrata("DIALOG") + frame.bg:SetFrameStrata("HIGH") + end + frame:SetScript("OnShow", frame.UpdateStrata) + frame:SetScript("OnUpdate", function() + if not TSMAPI:AHTabIsVisible(private.module) then + TSMAPI.AuctionControl:HideConfirmation() + end + end) + + local bg = CreateFrame("Frame", nil, frame) + bg:SetFrameStrata("HIGH") + bg:SetPoint("TOPLEFT", parent.content) + bg:SetPoint("BOTTOMRIGHT", parent.content) + bg:EnableMouse(true) + TSMAPI.Design:SetFrameBackdropColor(bg) + bg:SetAlpha(.2) + frame.bg = bg + + local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHConfirmationActionButton") + btn:SetPoint("BOTTOMLEFT", 10, 10) + btn:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -2, 10) + btn:SetHeight(25) + btn:SetText("") + btn:SetScript("OnClick", function(self) + if not TSMAPI:AHTabIsVisible(private.module) then return end + self:Disable() + if private.confirmationMode == "Buyout" then + private:DoBuyout() + elseif private.confirmationMode == "Cancel" then + private:DoCancel() + end + end) + frame.proceed = btn + + local btn = TSMAPI.GUI:CreateButton(frame, 18) + btn:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 2, 10) + btn:SetPoint("BOTTOMRIGHT", -10, 10) + btn:SetHeight(25) + btn:SetText(CLOSE) + btn:SetScript("OnClick", function() frame:Hide() end) + frame.close = btn + + local linkText = TSMAPI.GUI:CreateLabel(frame) + linkText:SetFontObject(GameFontNormal) + linkText:SetPoint("TOP", -10, -10) + frame.linkText = linkText + + local bg = frame:CreateTexture(nil, "BACKGROUND") + bg:SetPoint("TOPLEFT", linkText, -2, 2) + bg:SetPoint("BOTTOMRIGHT", linkText, 2, -2) + TSMAPI.Design:SetContentColor(bg) + linkText.bg = bg + bg:Show() + + local quantityText = TSMAPI.GUI:CreateLabel(frame) + quantityText:SetPoint("LEFT", linkText, "RIGHT") + frame.quantityText = quantityText + + local buyoutText = TSMAPI.GUI:CreateLabel(frame) + buyoutText:SetPoint("TOPLEFT", 10, -41) + buyoutText:SetJustifyH("LEFT") + frame.buyoutText = buyoutText + + local buyoutText2 = TSMAPI.GUI:CreateLabel(frame) + buyoutText2:SetPoint("TOPLEFT", buyoutText, "BOTTOMLEFT") + buyoutText2:SetJustifyH("LEFT") + frame.buyoutText2 = buyoutText2 + + local purchasedText = TSMAPI.GUI:CreateLabel(frame) + purchasedText:SetPoint("TOPLEFT", 10, -70) + frame.purchasedText = purchasedText + + local searchingText = TSMAPI.GUI:CreateLabel(frame) + searchingText:SetPoint("CENTER") + frame.searchingText = searchingText + + return frame +end + +function private:CreatePostFrame(parent) + local frame = CreateFrame("Frame", nil, parent) + TSMAPI.Design:SetFrameBackdropColor(frame) + frame:Hide() + frame:SetPoint("CENTER") + frame:SetFrameStrata("DIALOG") + frame:SetWidth(250) + frame:SetHeight(245) + frame.UpdateStrata = function() + frame:SetFrameStrata("DIALOG") + frame.bg:SetFrameStrata("HIGH") + end + frame:SetScript("OnShow", frame.UpdateStrata) + frame:SetScript("OnUpdate", function() + if not TSMAPI:AHTabIsVisible(private.module) then + TSMAPI.AuctionControl:HideConfirmation() + end + end) + + local bg = CreateFrame("Frame", nil, frame) + bg:SetFrameStrata("HIGH") + bg:SetPoint("TOPLEFT", parent.content) + bg:SetPoint("BOTTOMRIGHT", parent.content) + bg:EnableMouse(true) + TSMAPI.Design:SetFrameBackdropColor(bg) + bg:SetAlpha(0.2) + frame.bg = bg + + local btn = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHConfirmationPostButton") + btn:SetPoint("BOTTOMLEFT", 10, 10) + btn:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -2, 10) + btn:SetHeight(25) + btn:SetText(L["Post"]) + btn:SetScript("OnClick", function(self) + if not TSMAPI:AHTabIsVisible(private.module) then return end + self:Disable() + local postInfo = {} + postInfo.itemString = private.currentAuction.itemString + postInfo.buyout = TSMAPI:UnformatTextMoney(frame.buyoutInputBox:GetText()) + postInfo.bid = max(floor(postInfo.buyout*private.postBidPercent), 1) + postInfo.stackSize = frame.stackSizeInputBox:GetNumber() + postInfo.numAuctions = frame.numAuctionsInputBox:GetNumber() + postInfo.duration = TSM.db.profile.postDuration + + private:DoPost(postInfo) + end) + frame.proceed = btn + + local btn = TSMAPI.GUI:CreateButton(frame, 18) + btn:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 2, 10) + btn:SetPoint("BOTTOMRIGHT", -10, 10) + btn:SetHeight(25) + btn:SetText(CLOSE) + btn:SetScript("OnClick", function() frame:Hide() end) + frame.close = btn + + local linkText = TSMAPI.GUI:CreateLabel(frame) + linkText:SetFontObject(GameFontNormal) + linkText:SetPoint("TOP", -10, -10) + frame.linkText = linkText + + local bg = frame:CreateTexture(nil, "BACKGROUND") + bg:SetPoint("TOPLEFT", linkText, -2, 2) + bg:SetPoint("BOTTOMRIGHT", linkText, 2, -2) + TSMAPI.Design:SetContentColor(bg) + linkText.bg = bg + bg:Show() + + + local function OnPriceInputBoxTextChanged() + local buyout = TSMAPI:UnformatTextMoney(frame.buyoutInputBox:GetText()) + local perItem = TSMAPI:UnformatTextMoney(frame.perItemInputBox:GetText()) + if not buyout or not perItem or buyout == 0 then + frame.proceed:Disable() + else + frame.proceed:Enable() + end + end + + local function OnPriceInputBoxEditFocusLost(self) + local copper = TSMAPI:UnformatTextMoney(self:GetText()) + if copper then + local stackSize = frame.stackSizeInputBox:GetNumber() + if self == frame.buyoutInputBox then + frame.perItemInputBox:SetText(TSMAPI:FormatTextMoney(floor(copper/stackSize), nil, nil, nil, true)) + elseif self == frame.perItemInputBox then + frame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(copper*stackSize, nil, nil, nil, true)) + end + self:SetText(TSMAPI:FormatTextMoney(copper, nil, nil, nil, true)) + self:ClearFocus() + else + self:SetFocus() + end + end + + local function OnInputBoxTabPressed(self) + local boxes = {"buyoutInputBox", "perItemInputBox", "numAuctionsInputBox", "stackSizeInputBox"} + self:ClearFocus() + for i=1, #boxes-1 do + if self == frame[boxes[i]] then + frame[boxes[i+1]]:SetFocus() + end + end + end + + local buyoutLabel = TSMAPI.GUI:CreateLabel(frame) + buyoutLabel:SetPoint("TOPLEFT", 10, -40) + buyoutLabel:SetHeight(20) + buyoutLabel:SetJustifyH("LEFT") + buyoutLabel:SetText(L["Auction Buyout:"]) + + local buyoutInputBox = TSMAPI.GUI:CreateInputBox(frame) + buyoutInputBox:SetJustifyH("RIGHT") + buyoutInputBox:SetPoint("TOPRIGHT", -10, -40) + buyoutInputBox:SetPoint("TOPLEFT", buyoutLabel, "TOPRIGHT", 10, 0) + buyoutInputBox:SetHeight(20) + buyoutInputBox:SetScript("OnEnterPressed", buyoutInputBox.ClearFocus) + buyoutInputBox:SetScript("OnEscapePressed", buyoutInputBox.ClearFocus) + buyoutInputBox:SetScript("OnEditFocusLost", OnPriceInputBoxEditFocusLost) + buyoutInputBox:SetScript("OnTextChanged", OnPriceInputBoxTextChanged) + buyoutInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed) + frame.buyoutInputBox = buyoutInputBox + + local perItemLabel = TSMAPI.GUI:CreateLabel(frame) + perItemLabel:SetPoint("TOPLEFT", 10, -65) + perItemLabel:SetHeight(20) + perItemLabel:SetJustifyH("LEFT") + perItemLabel:SetText(L["Per Item:"]) + + local perItemInputBox = TSMAPI.GUI:CreateInputBox(frame) + perItemInputBox:SetJustifyH("RIGHT") + perItemInputBox:SetPoint("TOPRIGHT", -10, -65) + perItemInputBox:SetPoint("TOPLEFT", perItemLabel, "TOPRIGHT", 10, 0) + perItemInputBox:SetHeight(20) + perItemInputBox:SetScript("OnEnterPressed", perItemInputBox.ClearFocus) + perItemInputBox:SetScript("OnEscapePressed", perItemInputBox.ClearFocus) + perItemInputBox:SetScript("OnEditFocusLost", OnPriceInputBoxEditFocusLost) + perItemInputBox:SetScript("OnTextChanged", OnPriceInputBoxTextChanged) + perItemInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed) + frame.perItemInputBox = perItemInputBox + + + local function OnCountInputBoxEditFocusLost(self) + local numAuctions = max(1, min(frame.numAuctionsInputBox:GetNumber(), frame.numAuctionsInputBox.max)) + local stackSize = max(1, min(frame.stackSizeInputBox:GetNumber(), frame.stackSizeInputBox.max)) + + if self == frame.stackSizeInputBox then + numAuctions = min(numAuctions, floor(frame.numInBags/stackSize)) + elseif self == frame.numAuctionsInputBox then + stackSize = min(stackSize, floor(frame.numInBags/numAuctions)) + end + frame.numAuctionsInputBox:SetNumber(numAuctions) + frame.stackSizeInputBox:SetNumber(stackSize) + frame.numAuctionsInputBox.btn:SetText(format(L["max %d"], floor(frame.numInBags/stackSize))) + frame.stackSizeInputBox.btn:SetText(format(L["max %d"], min(frame.stackSizeInputBox.max, floor(frame.numInBags/numAuctions)))) + local perItem = TSMAPI:UnformatTextMoney(frame.perItemInputBox:GetText()) + frame.buyoutInputBox:SetText(TSMAPI:FormatTextMoney(perItem*stackSize, nil, nil, nil, true)) + end + + local function OnCountInputBoxTextChanged(self) + local numAuctions = frame.numAuctionsInputBox:GetNumber() + local stackSize = frame.stackSizeInputBox:GetNumber() + if numAuctions <= 0 or stackSize <= 0 or numAuctions*stackSize > frame.numInBags then + frame.proceed:Disable() + else + frame.proceed:Enable() + end + end + + local function OnMaxButtonClicked(self) + self.inputBox:SetNumber(self.inputBox.max) + self.inputBox:SetFocus() + self.inputBox:ClearFocus() + end + + local numAuctionsInputBox = TSMAPI.GUI:CreateInputBox(frame) + numAuctionsInputBox:SetJustifyH("CENTER") + numAuctionsInputBox:SetNumeric(true) + numAuctionsInputBox:SetPoint("TOPLEFT", 10, -110) + numAuctionsInputBox:SetHeight(20) + numAuctionsInputBox:SetScript("OnEnterPressed", numAuctionsInputBox.ClearFocus) + numAuctionsInputBox:SetScript("OnEscapePressed", numAuctionsInputBox.ClearFocus) + numAuctionsInputBox:SetScript("OnEditFocusLost", OnCountInputBoxEditFocusLost) + numAuctionsInputBox:SetScript("OnTextChanged", OnCountInputBoxTextChanged) + numAuctionsInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed) + frame.numAuctionsInputBox = numAuctionsInputBox + + local stackSizeInputBox = TSMAPI.GUI:CreateInputBox(frame) + stackSizeInputBox:SetJustifyH("CENTER") + stackSizeInputBox:SetNumeric(true) + stackSizeInputBox:SetPoint("TOPRIGHT", -10, -110) + stackSizeInputBox:SetHeight(20) + stackSizeInputBox:SetScript("OnEnterPressed", stackSizeInputBox.ClearFocus) + stackSizeInputBox:SetScript("OnEscapePressed", stackSizeInputBox.ClearFocus) + stackSizeInputBox:SetScript("OnEditFocusLost", OnCountInputBoxEditFocusLost) + stackSizeInputBox:SetScript("OnTextChanged", OnCountInputBoxTextChanged) + stackSizeInputBox:SetScript("OnTabPressed", OnInputBoxTabPressed) + frame.stackSizeInputBox = stackSizeInputBox + + local countLabel = TSMAPI.GUI:CreateLabel(frame) + countLabel:SetPoint("TOPLEFT", numAuctionsInputBox, "TOPRIGHT", 10, 0) + countLabel:SetPoint("TOPRIGHT", stackSizeInputBox, "TOPLEFT", -10, 0) + countLabel:SetHeight(20) + countLabel:SetJustifyH("CENTER") + countLabel:SetText(L["stacks of"]) + + local editboxWidth = (frame:GetWidth() - 40 - countLabel:GetStringWidth()) / 2 + numAuctionsInputBox:SetWidth(editboxWidth) + stackSizeInputBox:SetWidth(editboxWidth) + + local maxStackSizeBtn = TSMAPI.GUI:CreateButton(frame, 12) + maxStackSizeBtn:SetPoint("TOPLEFT", stackSizeInputBox, "BOTTOMLEFT", 5, -3) + maxStackSizeBtn:SetPoint("TOPRIGHT", stackSizeInputBox, "BOTTOMRIGHT", -5, -3) + maxStackSizeBtn:SetHeight(14) + maxStackSizeBtn:SetText("") + maxStackSizeBtn:SetScript("OnClick", OnMaxButtonClicked) + maxStackSizeBtn.inputBox = stackSizeInputBox + stackSizeInputBox.btn = maxStackSizeBtn + + local maxNumAuctionsBtn = TSMAPI.GUI:CreateButton(frame, 12) + maxNumAuctionsBtn:SetPoint("TOPLEFT", numAuctionsInputBox, "BOTTOMLEFT", 5, -3) + maxNumAuctionsBtn:SetPoint("TOPRIGHT", numAuctionsInputBox, "BOTTOMRIGHT", -5, -3) + maxNumAuctionsBtn:SetHeight(14) + maxNumAuctionsBtn:SetText("") + maxNumAuctionsBtn:SetScript("OnClick", OnMaxButtonClicked) + maxNumAuctionsBtn.inputBox = numAuctionsInputBox + numAuctionsInputBox.btn = maxNumAuctionsBtn + + local durationLabel = TSMAPI.GUI:CreateLabel(frame) + durationLabel:SetPoint("TOPLEFT", 10, -165) + durationLabel:SetHeight(20) + durationLabel:SetJustifyH("LEFT") + durationLabel:SetText(L["Duration:"]) + + local list = {AUCTION_DURATION_ONE, AUCTION_DURATION_TWO, AUCTION_DURATION_THREE} + local durationDropdown = TSMAPI.GUI:CreateDropdown(frame, list) + durationDropdown:SetPoint("TOPLEFT", durationLabel, "TOPRIGHT", 10, 0) + durationDropdown:SetPoint("TOPRIGHT", 0, -165) + durationDropdown:SetHeight(20) + durationDropdown:SetCallback("OnValueChanged", function(self, _, value) TSM.db.profile.postDuration = value end) + frame.durationDropdown = durationDropdown + + return frame +end + +function private:CreateControlButtons(parent) + local frame = CreateFrame("Frame", nil, parent) + frame:SetHeight(24) + frame:SetWidth(390) + frame:SetPoint("BOTTOMRIGHT", -20, 6) + + local function OnClick(self) + if not private.rt or not private.callback then return end + private.confirmationMode = self.which + if self.which == "Post" then + private:ShowPostWindow() + else + private:ShowConfirmationWindow() + end + end + + local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabCancelButton") + button:SetPoint("TOPLEFT", 0, 0) + button:SetWidth(100) + button:SetHeight(24) + button:SetText(CANCEL) + button.which = "Cancel" + button:SetScript("OnClick", OnClick) + frame.cancel = button + + local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabPostButton") + button:SetPoint("TOPLEFT", 104, 0) + button:SetWidth(100) + button:SetHeight(24) + button:SetText(L["Post"]) + button.which = "Post" + button:SetScript("OnClick", OnClick) + frame.post = button + + local button = TSMAPI.GUI:CreateButton(frame, 18, "TSMAHTabBuyoutButton") + button:SetPoint("TOPLEFT", 208, 0) + button:SetWidth(100) + button:SetHeight(24) + button:SetText(BUYOUT) + button.which = "Buyout" + button:SetScript("OnClick", OnClick) + frame.buyout = button + + return frame +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionFrame.lua b/TradeSkillMaster/Auction/AuctionFrame.lua new file mode 100644 index 0000000..fe349a5 --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionFrame.lua @@ -0,0 +1,298 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +local private = {auctionTabs={}, queuedTabs={}} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionFrame_private") +LibStub("AceEvent-3.0"):Embed(private) +LibStub("AceHook-3.0"):Embed(private) + +local registeredModules = {} +function TSM:RegisterAuctionFunction(moduleName, callbackShow, callbackHide) + if registeredModules[moduleName] then return end + registeredModules[moduleName] = true + if AuctionFrame then + private:CreateTSMAHTab(moduleName, callbackShow, callbackHide) + else + tinsert(private.queuedTabs, {moduleName, callbackShow, callbackHide}) + end +end + +function private:CreateTSMAHTab(moduleName, callbackShow, callbackHide) + local auctionTab = CreateFrame("Frame", nil, AuctionFrame) + auctionTab:Hide() + auctionTab:SetAllPoints() + auctionTab:EnableMouse(true) + auctionTab:SetMovable(true) + auctionTab:SetScript("OnMouseDown", function() if AuctionFrame:IsMovable() then AuctionFrame:StartMoving() end end) + auctionTab:SetScript("OnMouseUp", function() if AuctionFrame:IsMovable() then AuctionFrame:StopMovingOrSizing() end end) + + TSMAPI:CancelFrame("blizzAHLoadedDelay") + local n = AuctionFrame.numTabs + 1 + + local tab = CreateFrame("Button", "AuctionFrameTab"..n, AuctionFrame, "AuctionTabTemplate") + tab:Hide() + tab:SetID(n) + tab:SetText(TSMAPI.Design:GetInlineColor("link2")..moduleName.."|r") + tab:SetNormalFontObject(GameFontHighlightSmall) + tab.isTSMTab = moduleName + tab:SetPoint("LEFT", _G["AuctionFrameTab"..n-1], "RIGHT", -8, 0) + tab:Show() + PanelTemplates_SetNumTabs(AuctionFrame, n) + PanelTemplates_EnableTab(AuctionFrame, n) + auctionTab.tab = tab + + local closeBtn = TSMAPI.GUI:CreateButton(auctionTab, 18) + closeBtn:SetPoint("BOTTOMRIGHT", -5, 5) + closeBtn:SetWidth(75) + closeBtn:SetHeight(24) + closeBtn:SetText(CLOSE) + closeBtn:SetScript("OnClick", CloseAuctionHouse) + + local iconFrame = CreateFrame("Frame", nil, auctionTab) + iconFrame:SetPoint("CENTER", auctionTab, "TOPLEFT", 30, -30) + iconFrame:SetHeight(100) + iconFrame:SetWidth(100) + local icon = iconFrame:CreateTexture(nil, "ARTWORK") + icon:SetAllPoints() + icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Big") + local textFrame = CreateFrame("Frame", nil, auctionTab) + local iconText = textFrame:CreateFontString(nil, "OVERLAY") + iconText:SetPoint("CENTER", iconFrame) + iconText:SetHeight(15) + iconText:SetJustifyH("CENTER") + iconText:SetJustifyV("CENTER") + iconText:SetFont(TSMAPI.Design:GetContentFont("normal")) + iconText:SetTextColor(165/255, 168/255, 188/255, .7) + local version = TSM._version + iconText:SetText(version) + local ag = iconFrame:CreateAnimationGroup() + local spin = ag:CreateAnimation("Rotation") + spin:SetOrder(1) + spin:SetDuration(2) + spin:SetDegrees(90) + local spin = ag:CreateAnimation("Rotation") + spin:SetOrder(2) + spin:SetDuration(4) + spin:SetDegrees(-180) + local spin = ag:CreateAnimation("Rotation") + spin:SetOrder(3) + spin:SetDuration(2) + spin:SetDegrees(90) + ag:SetLooping("REPEAT") + iconFrame:SetScript("OnEnter", function() ag:Play() end) + iconFrame:SetScript("OnLeave", function() ag:Stop() end) + + local moneyText = TSMAPI.GUI:CreateTitleLabel(auctionTab, 16) + moneyText:SetJustifyH("CENTER") + moneyText:SetJustifyV("CENTER") + moneyText:SetPoint("CENTER", auctionTab, "BOTTOMLEFT", 85, 17) + TSMAPI.Design:SetIconRegionColor(moneyText) + moneyText.SetMoney = function(self, money) + self:SetText(TSMAPI:FormatTextMoneyIcon(money)) + end + auctionTab.moneyText = moneyText + + local moneyTextFrame = CreateFrame("Frame", nil, auctionTab) + moneyTextFrame:SetAllPoints(moneyText) + moneyTextFrame:EnableMouse(true) + moneyTextFrame:SetScript("OnEnter", function(self) + local currentTotal = 0 + local incomingTotal = 0 + for i=1, GetNumAuctionItems("owner") do + -- local count, _, _, _, _, _, _, buyoutAmount = select(3, GetAuctionItemInfo("owner", i)) + local count, _, _, _, _, _, buyoutAmount = select(3, GetAuctionItemInfo("owner", i)) + if count == 0 then + incomingTotal = incomingTotal + buyoutAmount + else + currentTotal = currentTotal + buyoutAmount + end + end + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + GameTooltip:AddLine("Gold Info:") + GameTooltip:AddDoubleLine("Player Gold", TSMAPI:FormatTextMoneyIcon(GetMoney()), 1, 1, 1, 1, 1, 1) + GameTooltip:AddDoubleLine("Incoming Auction Sales", TSMAPI:FormatTextMoneyIcon(incomingTotal), 1, 1, 1, 1, 1, 1) + GameTooltip:AddDoubleLine("Current Auctions Value", TSMAPI:FormatTextMoneyIcon(currentTotal), 1, 1, 1, 1, 1, 1) + GameTooltip:Show() + end) + moneyTextFrame:SetScript("OnLeave", function() + GameTooltip:ClearLines() + GameTooltip:Hide() + end) + + auctionTab:SetScript("OnShow", function(self) + self:SetAllPoints() + if not self.minimized then + callbackShow(self) + end + end) + auctionTab:SetScript("OnHide", function(self) + if not self.minimized then + callbackHide() + end + end) + + local contentFrame = CreateFrame("Frame", nil, auctionTab) + contentFrame:SetPoint("TOPLEFT", 4, -80) + contentFrame:SetPoint("BOTTOMRIGHT", -4, 35) + TSMAPI.Design:SetContentColor(contentFrame) + auctionTab.content = contentFrame + + tinsert(private.auctionTabs, auctionTab) +end + +function private:InitializeAuctionFrame(auctionTab) + -- make the AH movable if this option is enabled + AuctionFrame:SetMovable(TSM.db.profile.auctionFrameMovable) + AuctionFrame:EnableMouse(true) + AuctionFrame:SetScript("OnMouseDown", function(self) if self:IsMovable() then self:StartMoving() end end) + AuctionFrame:SetScript("OnMouseUp", function(self) if self:IsMovable() then self:StopMovingOrSizing() end end) + + -- scale the auction frame according to the TSM option + if AuctionFrame:GetScale() ~= 1 and TSM.db.profile.auctionFrameScale == 1 then TSM.db.profile.auctionFrameScale = AuctionFrame:GetScale() end + AuctionFrame:SetScale(TSM.db.profile.auctionFrameScale) + + local prevTab + local function TabChangeHook(self) + if self.isTSMTab then + for _, tabFrame in ipairs(private.auctionTabs) do + if tabFrame.minimized and tabFrame.tab ~= self then + tabFrame:Show() + tabFrame.minimized = nil + tabFrame:Hide() + elseif tabFrame:IsShown() then + tabFrame:Hide() + end + end + local tabAuctionFrame = private:GetAuctionFrame(self) + private:OnTabClick(tabAuctionFrame) + AuctionFrame:SetFrameLevel(1) + tabAuctionFrame:SetFrameStrata(AuctionFrame:GetFrameStrata()) + tabAuctionFrame:SetFrameLevel(AuctionFrame:GetFrameLevel() + 1) + elseif prevTab and prevTab.isTSMTab then + local prevTabAuctionFrame = private:GetAuctionFrame(prevTab) + prevTabAuctionFrame.minimized = true + prevTabAuctionFrame:Hide() + private:TabHidden() + end + prevTab = self + + end + private:Hook("AuctionFrameTab_OnClick", TabChangeHook, true) + + -- Makes sure the TSM tab hides correctly when used with addons that hook this function to change tabs (ie Auctionator) + -- This probably doesn't have to be a SecureHook, but does need to be a Post-Hook. + private:SecureHook("ContainerFrameItemButton_OnModifiedClick", function() + if _G["AuctionFrameTab"..PanelTemplates_GetSelectedTab(AuctionFrame)].isTSMTab then return end + TabChangeHook(_G["AuctionFrameTab"..PanelTemplates_GetSelectedTab(AuctionFrame)]) + end) +end + +function private:GetAuctionFrame(targetTab) + for _, tabFrame in ipairs(private.auctionTabs) do + if tabFrame.tab == targetTab then + return tabFrame + end + end +end + +function private:InitializeAHTab() + for _, info in ipairs(private.queuedTabs) do + private:CreateTSMAHTab(unpack(info)) + end + private.queuedTabs = {} + private:InitializeAuctionFrame() + private.isInitialized = true + if AuctionHouse and AuctionHouse:IsVisible() then + private:AUCTION_HOUSE_SHOW() + end +end + +function TSMAPI:AHTabIsVisible(module) + return module and _G["AuctionFrameTab"..AuctionFrame.selectedTab].isTSMTab == module +end + +function private:AUCTION_HOUSE_SHOW() + if private.isInitialized then + for i = AuctionFrame.numTabs, 1, -1 do + local text = gsub(_G["AuctionFrameTab"..i]:GetText(), "|r", "") + text = gsub(text, "|c[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]", "") + if text == TSM.db.profile.defaultAuctionTab then + _G["AuctionFrameTab"..i]:Click() + return + end + end + _G["AuctionFrameTab1"]:Click() + end +end + +function private:OnTabClick(tab) + AuctionFrameTopLeft:Hide() + AuctionFrameTop:Hide() + AuctionFrameTopRight:Hide() + AuctionFrameBotLeft:Hide() + AuctionFrameBot:Hide() + AuctionFrameBotRight:Hide() + AuctionFrameMoneyFrame:Hide() + AuctionFrameCloseButton:Hide() + private:RegisterEvent("PLAYER_MONEY") + + if TSM.db.profile.openAllBags then + OpenAllBags(true) + end + TSMAPI:CreateTimeDelay("hideAHMoneyFrame", 0.1, function() AuctionFrameMoneyFrame:Hide() end) + + TSMAPI.Design:SetFrameBackdropColor(tab) + AuctionFrameTab1:SetPoint("TOPLEFT", AuctionFrame, "BOTTOMLEFT", 15, 1) + + tab:Show() + tab.minimized = nil + tab.moneyText:SetMoney(GetMoney()) +end + +function private:TabHidden() + AuctionFrameTopLeft:Show() + AuctionFrameTop:Show() + AuctionFrameTopRight:Show() + AuctionFrameBotLeft:Show() + AuctionFrameBot:Show() + AuctionFrameBotRight:Show() + AuctionFrameMoneyFrame:Show() + AuctionFrameCloseButton:Show() + AuctionFrameTab1:SetPoint("TOPLEFT", AuctionFrame, "BOTTOMLEFT", 15, 12) +end + +function private:PLAYER_MONEY() + for _, tab in ipairs(private.auctionTabs) do + if tab:IsVisible() then + tab.moneyText:SetMoney(GetMoney()) + end + end +end + +function private:ADDON_LOADED(event, addonName) + if addonName == "Blizzard_AuctionUI" then + private:UnregisterEvent("ADDON_LOADED") + if TSM.db then + private:InitializeAHTab() + else + TSMAPI:CreateTimeDelay("blizzAHLoadedDelay", 0.2, private.InitializeAHTab, 0.2) + end + end +end + +do + private:RegisterEvent("AUCTION_HOUSE_SHOW") + if IsAddOnLoaded("Blizzard_AuctionUI") then + private:InitializeAHTab() + else + private:RegisterEvent("ADDON_LOADED") + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionItem.lua b/TradeSkillMaster/Auction/AuctionItem.lua new file mode 100644 index 0000000..4533461 --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionItem.lua @@ -0,0 +1,433 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains code for the AuctionItem objects +local TSM = select(2, ...) + +local NewRecord, sortHelpers +local AuctionRecord = { + Initialize = function(self) + self.objType = "AuctionRecord" + end, + + SetData = function(self, parent, count, minBid, minIncrement, buyout, bid, highBidder, seller, timeLeft) + self.parent = parent + self.count = count + self.minBid = minBid + self.minIncrement = minIncrement + self.buyout = buyout + self.bid = bid + self.highBidder = highBidder + self.seller = seller + self.timeLeft = timeLeft + end, + + IsPlayer = function(self) + return TSMAPI:IsPlayer(self.seller) or self.parent.alts[self.seller] + end, + + GetPercent = function(self) + local itemBuyout = self:GetItemBuyout() + local marketValue = self.parent.marketValue + if itemBuyout and marketValue then + return (itemBuyout / marketValue) * 100 + end + end, + + GetDisplayedBid = function(self) + local displayedBid + if self.bid == 0 then + displayedBid = self.minBid + else + displayedBid = self.bid + end + return displayedBid + end, + + GetRequiredBid = function(self) + local requiredBid + if self.bid == 0 then + requiredBid = self.minBid + else + requiredBid = self.bid + self.minIncrement + end + return requiredBid + end, + + GetItemBuyout = function(self) + if not self.buyout or self.buyout == 0 then return end + return floor(self.buyout / self.count) + end, + + GetItemDisplayedBid = function(self) + return floor(self:GetDisplayedBid() / self.count) + end, + + GetItemDestroyingBuyout = function(self) + local itemBuyout = self:GetItemBuyout() + if itemBuyout then + return itemBuyout * self.parent.destroyingNum + end + end, + + GetItemDestroyingDisplayedBid = function(self) + local itemBid = self:GetItemDisplayedBid() + if itemBid then + return itemBid * self.parent.destroyingNum + end + end, + + Copy = function(self) + local o = NewRecord() + o:SetData(self.parent, self.count, self.minBid, self.minIncrement, self.buyout, self.bid, self.highBidder, self.seller, self.timeLeft) + o.uniqueID = self.uniqueID + return o + end, + + Equals = function(self, other) + if self == other then + return true + end + + local params = self.parent.recordParams + for _, key in ipairs(params) do + if type(self[key]) == "function" then + if self[key](self) ~= other[key](other) then + return false + end + else + if self[key] ~= other[key] then + return false + end + end + end + + return true + end, +} + +NewRecord = function() + local o = {} + setmetatable(o, AuctionRecord) + AuctionRecord.__index = AuctionRecord + o:Initialize() + return o +end + + +--- Test Documentation +-- @name AuctionItem +-- @description Test description for the Auction Item object. +local AuctionItem = { + -- @field Test field + Initialize = function(self) + self.objType = "AuctionItem" + self.itemLink = nil + self.marketValue = nil + self.playerAuctions = 0 + self.records = {} + self.alts = {} + self.recordParams = {"buyout", "count", "seller"} + self.shouldCompact = true + self.texture = "" + end, + + -- sets the item (or battle pet's) texture + SetTexture = function(self, texture) + self.texture = texture + end, + + -- gets the item (or battle pet's) texture + GetTexture = function(self) + return self.texture + end, + + -- sets the alts table used for making other players count as the current player + SetAlts = function(self, alts) + self.alts = alts + end, + + -- sets the list of params we care about + SetRecordParams = function(self, params) + self.recordParams = params + end, + + -- sets the itemLink + SetItemLink = function(self, itemLink) + self.itemLink = itemLink + end, + + -- returns the itemString + GetItemString = function(self) + return TSMAPI:GetItemString(self.itemLink) + end, + + -- returns the itemID + GetItemID = function(self) + return TSMAPI:GetItemID(self.itemLink) + end, + + -- adds a record + AddAuctionRecord = function(self, ...) + local record = NewRecord() + record:SetData(self, ...) + -- if strfind(self.itemLink, "battlepet") then + -- record.uniqueID = table.concat({TSMAPI:Select({2, 3, 4, 5, 6, 7}, (":"):split(self.itemLink))}, ".") + -- else + record.uniqueID = select(9, (":"):split(self.itemLink)) + -- end + self:AddRecord(record) + end, + + -- adds a record + AddRecord = function(self, record) + self.shouldCompact = true + if record:IsPlayer() then + self.playerAuctions = self.playerAuctions + 1 + end + tinsert(self.records, record) + end, + + -- sorts the records using the passed sortFunc + SortRecords = function(self, sortFunc) + sort(self.records, sortFunc) + end, + + -- sets the market value of this item + SetMarketValue = function(self, value) + self.marketValue = value + end, + + -- sorts all the records in ascending order by buyout > bid > count > seller + DoDefaultSort = function(self) + self:SortRecords(function(a, b) + local aBuyout = a:GetItemBuyout() + local bBuyout = b:GetItemBuyout() + if not aBuyout or aBuyout == 0 then + return false + end + if not bBuyout or bBuyout == 0 then + return true + end + if aBuyout == bBuyout then + if a.seller == b.seller then + if a.count == b.count then + local aBid = a:GetItemDisplayedBid() + local bBid = b:GetItemDisplayedBid() + return aBid < bBid + end + return a.count < b.count + end + return a.seller < b.seller + end + return aBuyout < bBuyout + end) + end, + + -- populates the compactRecords table + PopulateCompactRecords = function(self, sortParams, isAscending) + if self.shouldCompact then + self.shouldCompact = false + self.compactRecords = {} + self:DoDefaultSort() + local currentRecord + for _, record in ipairs(self.records) do + local temp = record:Copy() + if not currentRecord or not temp:Equals(currentRecord) then + currentRecord = temp + currentRecord.numAuctions = 1 + currentRecord.totalQuantity = currentRecord.count + tinsert(self.compactRecords, currentRecord) + else + currentRecord.numAuctions = currentRecord.numAuctions + 1 + currentRecord.totalQuantity = currentRecord.totalQuantity + temp.count + end + end + end + + if sortParams then + sort(self.compactRecords, function(a, b) + for _, key in ipairs(sortParams) do + local sortVal = sortHelpers[key](a, b) + if sortVal < 0 then + return isAscending + elseif sortVal > 0 then + return not isAscending + end + end + end) + end + end, + + -- removes all records for which shouldFilter(record) returns true + FilterRecords = function(self, shouldFilter) + self.shouldCompact = true + local toRemove = {} + for index, record in ipairs(self.records) do + if shouldFilter(record) then + tinsert(toRemove, index) + end + end + + for i=#toRemove, 1, -1 do + self:RemoveRecord(toRemove[i]) + end + end, + + -- removes a record at the given index + RemoveRecord = function(self, index) + local toRemove = self.records[index] + if not toRemove then return end + self.shouldCompact = true + + if self.compactRecords then + for i, record in ipairs(self.compactRecords) do + if record:Equals(toRemove) then + if record.numAuctions > 1 then + record.numAuctions = record.numAuctions - 1 + else + tremove(self.compactRecords, i) + end + break + end + end + end + + if toRemove:IsPlayer() then + self.playerAuctions = self.playerAuctions - 1 + end + + tremove(self.records, index) + end, + + -- adds up all the counts from all the records + GetTotalItemQuantity = function(self) + local totalQuantity = 0 + for _, record in ipairs(self.records) do + totalQuantity = totalQuantity + record.count + end + return totalQuantity + end, + + -- counts up the number of items (not auctions) the player has + GetPlayerItemQuantity = function(self) + local totalQuantity = 0 + for _, record in ipairs(self.records) do + if record:IsPlayer() then + totalQuantity = totalQuantity + record.count + end + end + return totalQuantity + end, + + IsPlayerOnly = function(self) + for _, record in ipairs(self.records) do + if not record:IsPlayer() then + return false + end + end + return true + end, + + SetDestroyingNum = function(self, num) + self.destroyingNum = num + end, +} + +function TSMAPI.AuctionScan:NewAuctionItem() + local o = {} + setmetatable(o, AuctionItem) + AuctionItem.__index = AuctionItem + o:Initialize() + return o +end + + + +local function CompareStrings(a, b) + if a < b then + return -1 + elseif a > b then + return 1 + else + return 0 + end +end + +-- bunch of helper functions for AuctionItem sorting +-- negative return means a < b +-- possitive return means a > b +-- zero return means a == b +sortHelpers = { + Percent = function(a, b) + return (a:GetPercent() or math.huge) - (b:GetPercent() or math.huge) + end, + + Buyout = function(a, b) + return (a.buyout or math.huge) - (b.buyout or math.huge) + end, + + DisplayedBid = function(a, b) + return a:GetDisplayedBid() - b:GetDisplayedBid() + end, + + ItemBuyout = function(a, b) + return (a:GetItemBuyout() or math.huge) - (b:GetItemBuyout() or math.huge) + end, + + ItemDisplayedBid = function(a, b) + return a:GetItemDisplayedBid() - b:GetItemDisplayedBid() + end, + + Count = function(a, b) + return a.count - b.count + end, + + Seller = function(a, b) + return CompareStrings(a.seller, b.seller) + end, + + TimeLeft = function(a, b) + return a.timeLeft - b.timeLeft + end, + + NumAuctions = function(a, b) + return a.numAuctions - b.numAuctions + end, + + Name = function(a, b) + local aName = TSMAPI:GetSafeItemInfo(a.parent.itemLink) + local bName = TSMAPI:GetSafeItemInfo(a.parent.itemLink) + return CompareStrings(aName, bName) + end, + + DestroyingBuyout = function(a, b) + return (a:GetItemDestroyingBuyout() or math.huge) - (b:GetItemDestroyingBuyout() or math.huge) + end, +} + +function TSMAPI.AuctionScan:SortAuctions(data, sortParams, useCompactRecords, isAscending) + local function compareSort(a, b) + for _, key in ipairs(sortParams) do + local sortVal + if useCompactRecords then + sortVal = sortHelpers[key](a.compactRecords[1], b.compactRecords[1]) + else + sortVal = sortHelpers[key](a.records[1], b.records[1]) + end + + if sortVal < 0 then + return isAscending + elseif sortVal > 0 then + return not isAscending + end + end + end + + sort(data, compareSort) +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionQueryUtil.lua b/TradeSkillMaster/Auction/AuctionQueryUtil.lua new file mode 100644 index 0000000..674d80a --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionQueryUtil.lua @@ -0,0 +1,314 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains code for scanning the auction house +local TSM = select(2, ...) +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionQueryUtil_private") + + +local ITEM_CLASS_LOOKUP = {} +for i, class in ipairs({GetAuctionItemClasses()}) do + ITEM_CLASS_LOOKUP[class] = {} + ITEM_CLASS_LOOKUP[class].index = i + for j, subclass in pairs({GetAuctionItemSubClasses(i)}) do + ITEM_CLASS_LOOKUP[class][subclass] = j + end +end + +local function GetItemClasses(itemString) + local class, subClass = select(6, TSMAPI:GetSafeItemInfo(itemString)) + if not class or not ITEM_CLASS_LOOKUP[class] then return end + return ITEM_CLASS_LOOKUP[class].index, ITEM_CLASS_LOOKUP[class][subClass] +end + +function TSMAPI:GetAuctionQueryInfo(itemString) + local name, _, rarity, _, minLevel, class, subClass, _, equipLoc = TSMAPI:GetSafeItemInfo(itemString) + local class, subClass = GetItemClasses(itemString) + if not name then return end + return {name=name, minLevel=minLevel, maxLevel=minLevel, invType=0, class=class, subClass=subClass, quality=rarity} +end + +local function GetCommonQueryInfo(name, items) + local queries = {} + for _, itemString in ipairs(items) do + local itemQuery = TSMAPI:GetAuctionQueryInfo(itemString) + local existingQuery + for _, query in ipairs(queries) do + if query.class == itemQuery.class then + existingQuery = query + break + end + end + if existingQuery then + existingQuery.minLevel = min(existingQuery.minLevel, itemQuery.minLevel) + existingQuery.maxLevel = max(existingQuery.maxLevel, itemQuery.maxLevel) + existingQuery.quality = min(existingQuery.quality, itemQuery.quality) + if existingQuery.subClass ~= itemQuery.subClass then + existingQuery.subClass = nil + end + tinsert(existingQuery.items, itemString) + else + itemQuery.name = name + itemQuery.items = {itemString} + tinsert(queries, itemQuery) + end + end + return queries +end + +local function GetCommonQueryInfoClass(class, items) + local resultQuery = TSMAPI:GetAuctionQueryInfo(items[1]) + resultQuery.name = "" + resultQuery.class = class + for i=2, #items do + local itemQuery = TSMAPI:GetAuctionQueryInfo(items[i]) + resultQuery.minLevel = min(resultQuery.minLevel, itemQuery.minLevel) + resultQuery.maxLevel = max(resultQuery.maxLevel, itemQuery.maxLevel) + resultQuery.quality = min(resultQuery.quality, itemQuery.quality) + if resultQuery.subClass ~= itemQuery.subClass then resultQuery.subClass = nil end + end + resultQuery.items = items + return {resultQuery} +end + +local function GreatestSubstring(str1, str2) + local parts1 = {(" "):split(str1)} + local parts2 = {(" "):split(str2)} + for i=1, #parts1 do + if parts1[i] ~= parts2[i] then + local subStr = table.concat(parts1, " ", 1, i-1) + return subStr ~= "" and subStr + end + end + return table.concat(parts1, " ") +end + +local function ReduceStrings(strList) + local didReduction = true + while didReduction do + didReduction = false + for i=1, #strList-1 do + if i > #strList-1 then break end + local subStr = GreatestSubstring(strList[i], strList[i+1]) + if subStr then + strList[i] = subStr + tremove(strList, i+1) + didReduction = true + end + end + if not private.thread then return end + private.thread:Yield() + end + return true +end + +local function NumPagesCallback(event, numPages) + if event == "NUM_PAGES" then + local skippedItems = {} + local score = max(#private.combinedQueries[1].items-numPages, 0) + if private.combinedQueries[1].name == "" then + -- This is a common class term so determine if we should use this or not. + local cost = 0 + for _, query in ipairs(private.queries) do + if query.score and query.class == private.combinedQueries[1].class then + cost = cost + query.score + end + end + if score >= cost and score > 0 then + -- use the common class term + for i=#private.queries, 1, -1 do + local query = private.queries[i] + local shouldRemove = (query.class == private.combinedQueries[1].class) + if shouldRemove then + tremove(private.queries, i) + end + end + tinsert(private.queries, private.combinedQueries[1]) + end + else + if numPages > #private.combinedQueries[1].items then + for _, itemString in ipairs(private.combinedQueries[1].items) do + local query = TSMAPI:GetAuctionQueryInfo(itemString) + query.items = {itemString} + query.score = 0 + tinsert(private.queries, query) + end + elseif numPages == 0 then + for _, itemString in ipairs(private.combinedQueries[1].items) do + tinsert(skippedItems, itemString) + end + else + -- use the common search term + private.combinedQueries[1].score = score + tinsert(private.queries, private.combinedQueries[1]) + end + end + tremove(private.combinedQueries, 1) + private.callback("QUERY_UPDATE", private.totalQueries-#private.combinedQueries, private.totalQueries, skippedItems) + end + private:CheckNextCombinedQuery() +end + +function private:CheckNextCombinedQuery() + if not private.isScanning then return end + + if #private.combinedQueries == 0 then + -- we're done + sort(private.queries, function(a, b) return a.name < b.name end) + TSM:StopGeneratingQueries() + TSMAPI:CreateTimeDelay("queryUtilCallbackDelay", 0.05, function() private.callback("QUERY_COMPLETE", private.queries) end) + return + end + + for _, itemString in ipairs(private.combinedQueries[1].items) do + if strlower(private.combinedQueries[1].name) == strlower(TSMAPI:GetSafeItemInfo(itemString)) then + -- One of the items in this combined query is the same as the common search term, + -- so it's always worth using this common search term. + NumPagesCallback("NUM_PAGES", 1) + return + end + end + + TSMAPI.AuctionScan:GetNumPages(private.combinedQueries[1], NumPagesCallback) +end + +local function GenerateSearchTerms(names, itemList, isReversed) + sort(names) + if not ReduceStrings(names) then return end -- run the reduction + + -- create a table associating all the reduced names to a list of items + local temp = {} + for i, filterName in ipairs(names) do + for j, itemString in ipairs(itemList) do + local itemName = TSMAPI:GetSafeItemInfo(itemString) + itemName = itemName and isReversed and strrev(itemName) or itemName -- reverse item name if necessary + if itemName and strfind(itemName, "^"..TSMAPI:StrEscape(filterName)) then + temp[filterName] = temp[filterName] or {} + tinsert(temp[filterName], itemString) + end + end + if not private.thread then return end + private.thread:Yield() + end + + return temp +end + +local function GenerateQueriesThread(self) + private.thread = self + local function GenerateFilters(reverse) + -- create a list of all item names + local names = {} + for _, itemString in ipairs(private.itemList) do + local name = TSMAPI:GetSafeItemInfo(itemString) + if type(name) == "string" and name ~= "" then + tinsert(names, reverse and strrev(name) or name) + end + end + if not private.thread then return end + + local filters, tempFilters, tempItems = {}, {}, {} + local numFilters = 0 + local tbl = GenerateSearchTerms(names, private.itemList, reverse) + if not tbl then return end + for filterName, items in pairs(tbl) do + if #items > 1 then + filters[reverse and strrev(filterName) or filterName] = items + numFilters = numFilters + 1 + else + tinsert(tempFilters, strrev(filterName)) -- reverse name for second pass + for _, itemString in ipairs(items) do + tinsert(tempItems, itemString) + end + end + end + + -- try to find common search terms of reversed item names + local tbl = GenerateSearchTerms(tempFilters, tempItems, not reverse) + if not tbl then return end + for filterName, items in pairs(tbl) do + filters[reverse and filterName or strrev(filterName)] = items + numFilters = numFilters + 1 + end + + return filters, numFilters + end + + local endTime = debugprofilestop() + 5000 + while debugprofilestop() < endTime do + -- request all the item info + local tryAgain = false + for _, itemString in ipairs(private.itemList) do + if not TSMAPI:GetSafeItemInfo(itemString) then + tryAgain = true + end + end + if not tryAgain then break end + self:Sleep(0.1) + end + + local filters1, num1 = GenerateFilters() + local filters2, num2 = GenerateFilters(true) + if not filters1 or not filters2 then return end + local filters = num2 < num1 and filters2 or filters1 + + -- generate class filters + local itemClasses = {} + local classes = {GetAuctionItemClasses()} + for _, itemString in ipairs(private.itemList) do + local classIndex = GetItemClasses(itemString) + if classIndex then + itemClasses[classIndex] = itemClasses[classIndex] or {} + tinsert(itemClasses[classIndex], itemString) + end + end + + -- create the actual queries + local queries, combinedQueries = {}, {} + for filterName, items in pairs(filters) do + for _, query in ipairs(GetCommonQueryInfo(filterName, items)) do + if #query.items > 1 then + tinsert(combinedQueries, query) + else + tinsert(queries, query) + end + end + end + for class, items in pairs(itemClasses) do + for _, query in ipairs(GetCommonQueryInfoClass(class, items)) do + if #query.items > 1 then + tinsert(combinedQueries, query) + end + end + end + + private.isScanning = true + private.queries = queries + private.combinedQueries = combinedQueries + private.totalQueries = #combinedQueries +end + +function TSMAPI:GenerateQueries(itemList, callback) + if private.thread then return end + private.itemList = itemList + private.callback = callback + + local function ThreadDone() + if private.thread then + private.thread = nil + private:CheckNextCombinedQuery() + end + end + TSMAPI.Threading:Start(GenerateQueriesThread, 0.5, ThreadDone) +end + +function TSM:StopGeneratingQueries() + private.thread = nil + private.isScanning = nil +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionResultsTable.lua b/TradeSkillMaster/Auction/AuctionResultsTable.lua new file mode 100644 index 0000000..baed70d --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionResultsTable.lua @@ -0,0 +1,730 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +local RT_COUNT = 1 + +local HEAD_HEIGHT = 27 +local HEAD_SPACE = 2 +local purchaseCache = {} +local resultsTables = {} + + +local function OnSizeChanged(rt, width) + for i, col in ipairs(rt.headCols) do + col:SetWidth(col.info.width*width) + end + + for _, row in ipairs(rt.rows) do + for i, col in ipairs(row.cols) do + col:SetWidth(rt.headCols[i].info.width*width) + end + end +end + +local rowTextFunctions = { + GetPriceText = function(buyout, displayBid) + local bidLine = TSMAPI:FormatTextMoney(displayBid, "|cff999999", true) or "|cff999999---|r" + local buyoutLine = buyout and buyout > 0 and TSMAPI:FormatTextMoney(buyout, nil, true) or "---" + + if TSM.db.profile.showBids then + return bidLine.."\n"..buyoutLine + else + return buyoutLine + end + end, + + GetTimeLeftText = function(timeLeft) + return _G["AUCTION_TIME_LEFT"..(timeLeft or "")] or "" + end, + + GetNameText = function(_, link) + return gsub(gsub(link, "%[", ""), "%]", "") + end, + + GetAuctionsText = function(num, player, isExpandable, totalNum) + num = totalNum or num + local playerText = player and (" |cffffff00("..player..")|r") or "" + + if isExpandable then + return TSMAPI.Design:GetInlineColor("link2")..num.."|r"..playerText + else + return num..playerText + end + end, + + GetSellerText = function(seller) + if TSMAPI:IsPlayer(seller) then + return "|cffffff00"..seller.."|r" + else + return seller or "" + end + end, + + GetPercentText = function(pct) + if not pct then return "---" end + return TSMAPI:GetAuctionPercentColor(pct)..floor(pct+0.5).."%|r" + end +} + +local function GetRowTable(rt, auction, isExpandable) + if not auction then return end + + local bid, buyout + if TSM.db.profile.pricePerUnit then + bid = auction:GetItemDisplayedBid() + buyout = auction:GetItemBuyout() + else + bid = auction:GetDisplayedBid() + buyout = auction.buyout + end + + local auctionsData, rowTable + local itemString = auction.parent:GetItemString() + if rt.expanded[itemString] then + auctionsData = {#auction.parent.records, nil, nil, auction.numAuctions} + else + auctionsData = {#auction.parent.records, auction.parent.records[1].playerAuctions, isExpandable} + end + local name, iLvl + -- if strmatch(auction.parent.itemLink, "battlepet") then + -- local _, speciesID, itemLvl = strsplit(":", auction.parent.itemLink) + -- local itemName = C_PetJournal.GetPetInfoBySpeciesID(speciesID) + -- name, iLvl = itemName, itemLvl + -- else + local itemName, _, _, itemLvl = GetItemInfo(auction.parent.itemLink) + name, iLvl = itemName, itemLvl + -- end + local pct = auction:GetPercent() + if not pct or pct < 0 or pct == math.huge then + pct = nil + end + + if rt.isDestroying then + local destroyingBid = auction:GetItemDestroyingDisplayedBid() + local destroyingBuyout = auction:GetItemDestroyingBuyout() + rowTable = { + {value=rowTextFunctions.GetNameText, args={name, auction.parent.itemLink}}, + {value=rowTextFunctions.GetAuctionsText, args=auctionsData}, + {value=auction.count, args={auction.count}}, + {value=rowTextFunctions.GetSellerText, args={auction.seller}}, + {value=rowTextFunctions.GetPriceText, args={destroyingBuyout, destroyingBid}}, + {value=rowTextFunctions.GetPriceText, args={buyout, bid}}, + {value=rowTextFunctions.GetPercentText, args={pct}}, + } + else + rowTable = { + {value=rowTextFunctions.GetNameText, args={name, auction.parent.itemLink}}, + {value=(iLvl or "---"), args={iLvl or 0}}, + {value=rowTextFunctions.GetAuctionsText, args=auctionsData}, + {value=auction.count, args={auction.count}}, + {value=rowTextFunctions.GetTimeLeftText, args={auction.timeLeft}}, + {value=rowTextFunctions.GetSellerText, args={auction.seller}}, + {value=rowTextFunctions.GetPriceText, args={buyout, bid}}, + {value=rowTextFunctions.GetPercentText, args={pct}}, + } + end + + rowTable.itemString = itemString + rowTable.auctionRecord = auction + rowTable.expandable = isExpandable + rowTable.texture = auction.parent:GetTexture() + rowTable.link = auction.parent.itemLink + + return rowTable +end + +local function GetTableIndex(tbl, value) + for i, v in pairs(tbl) do + if value == v then + return i + end + end +end + +local function OnColumnClick(self, ...) + local button = ... + local rt = self.rt + local column = GetTableIndex(rt.headCols, self) + + if button == "RightButton" and column == #rt.headCols-1 then + TSM.db.profile.pricePerUnit = not TSM.db.profile.pricePerUnit + local priceColName = TSM.db.profile.pricePerUnit and L["Price Per Item"] or L["Price Per Stack"] + self:SetText(priceColName) + rt:RefreshRowData() + return + end + + local ascending = rt.sortInfo.ascending + rt:SetSort(column, rt.sortInfo.column ~= column or not ascending) + + local handler = self.rt.handlers.OnColumnClick + if handler then + handler(self.rt, self.row.data, self, ...) + end +end + +local methods = { + DrawRows = function(rt) + if not rt.auctionData then return end + for i=1, rt.NUM_ROWS do + rt.rows[i]:Hide() + end + + wipe(rt.displayRows) + local itemsUsed = {} + for i, data in ipairs(rt.data) do + local itemString = data.itemString + if not itemsUsed[itemString] or rt.expanded[itemString] then + tinsert(rt.displayRows, data) + itemsUsed[itemString] = true + elseif i == rt.selected then + rt.selected = nil + end + end + + FauxScrollFrame_Update(rt.scrollFrame, #rt.displayRows, rt.NUM_ROWS, rt.ROW_HEIGHT) + local offset = FauxScrollFrame_GetOffset(rt.scrollFrame) + rt.offset = offset + + for i=1, min(rt.NUM_ROWS, #rt.displayRows) do + rt.rows[i]:Show() + local data = rt.displayRows[i+offset] + local cols = rt.rows[i].cols + rt.rows[i].data = data + + if rt.selected == GetTableIndex(rt.data, data) then + rt.rows[i].highlight:Show() + else + rt.rows[i].highlight:Hide() + end + + for j, col in ipairs(rt.rows[i].cols) do + local colData = data[j] + + if j == 1 then + col.icon:SetTexture(data.texture) + if data.indented then + col.spacer:SetWidth(10) + col.icon:SetAlpha(0.5) + col:GetFontString():SetAlpha(0.7) + else + col.spacer:SetWidth(1) + col.icon:SetAlpha(1) + col:GetFontString():SetAlpha(1) + end + end + + if type(colData.value) == "function" then + col:SetText(colData.value(unpack(colData.args))) + else + col:SetText(colData.value) + end + end + end + + rt:UpdateActiveRows() + end, + + RefreshRowData = function(rt) + if not rt.auctionData then return end + wipe(rt.data) + wipe(rt.displayRows) + + local function RowSort(a, b) + if a[rt.sortInfo.column].args[1] == nil then return end + + local aVal + local bVal + if getn(a[rt.sortInfo.column].args) == 4 then + aVal = a[rt.sortInfo.column].args[4] + bVal = b[rt.sortInfo.column].args[4] + else + aVal = a[rt.sortInfo.column].args[1] + bVal = b[rt.sortInfo.column].args[1] + end + -- local aVal = a[rt.sortInfo.column].args[1] + -- local bVal = b[rt.sortInfo.column].args[1] + + if type(aVal) ~= "string" or type(bVal) ~= "string" then + aVal = tonumber(aVal) or 0 + bVal = tonumber(bVal) or 0 + end + if aVal == bVal then + -- make this a stable sort (abitrarily) by using table reference strings + return tostring(a) < tostring(b) + end + if rt.sortInfo.ascending then + return aVal < bVal + else + return aVal > bVal + end + end + + local tmp = {} + for _, auction in ipairs(rt.auctionData) do + local itemString = auction:GetItemString() + local itemRowData = {} + for i, data in ipairs(auction.compactRecords) do + local rowTbl = GetRowTable(rt, data, #auction.compactRecords > 1) + rowTbl.indented = true + tinsert(itemRowData, rowTbl) + end + + sort(itemRowData, RowSort) + if itemRowData[1] then + itemRowData[1].indented = false + end + tinsert(tmp, itemRowData) + end + + sort(tmp, function(a, b) return RowSort(a[1], b[1]) end) + + for _, itemRows in ipairs(tmp) do + for _, row in ipairs(itemRows) do + tinsert(rt.data, row) + end + end + + rt:DrawRows() + end, + + SetData = function(rt, auctionData) + rt.auctionData = auctionData + rt:RefreshRowData() + end, + + ClearSelection = function(rt) + rt.selected = nil + rt:DrawRows() + end, + + SetSelectedAuction = function(rt, auction) + rt.selected = nil + for i, data in ipairs(rt.data) do + if type(auction) == "table" then + if data.auctionRecord == auction or data.auctionRecord:Equals(auction) then + rt.selected = i + break + end + elseif type(auction) == "string" then + if data.itemString == auction then + rt.selected = i + break + end + end + end + rt:DrawRows() + end, + + GetSelectedAuction = function(rt) + if not rt.selected or not rt.data[rt.selected] then return end + return rt.data[rt.selected].auctionRecord + end, + + SetExpanded = function(rt, itemString, expanded) + rt.expanded[itemString] = expanded + rt:RefreshRowData() + end, + + ToggleExpanded = function(rt, itemString) + rt.expanded[itemString] = not rt.expanded[itemString] + rt:RefreshRowData() + end, + + SetSort = function(rt, column, ascending) + if not rt.headCols[column or 0] then return end + rt.sortInfo.column = column + rt.sortInfo.ascending = ascending + + for _, col in ipairs(rt.headCols) do + local tex = col:GetNormalTexture() + tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight") + tex:SetTexCoord(0.017, 1, 0.083, 0.909) + tex:SetAlpha(0.5) + end + + if ascending then + rt.headCols[column]:GetNormalTexture():SetTexture(0.6, 0.8, 1, 0.8) + else + rt.headCols[column]:GetNormalTexture():SetTexture(0.8, 0.6, 1, 0.8) + end + rt:RefreshRowData() + end, + + SetDisabled = function(rt, disabled) + rt.disabled = disabled + end, + + SetColHeadText = function(rt, column, text) + rt.headCols[column]:SetText(text) + end, + + UpdateActiveRows = function(rt) + if not rt.quickBuyout then return end + for _, row in ipairs(rt.rows) do + row:HideActiveBorder() + if row.data then + local rowRecord = row.data.auctionRecord + for i=1, GetNumAuctionItems("list") do + local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", i)) + -- local _, _, count, _, _, _, _, _, _, buyout, _, _, _, seller = GetAuctionItemInfo("list", i) + local _, _, count, _, _, _, _, _, buyout, _, _, seller = GetAuctionItemInfo("list", i) + if itemString == row.data.itemString and rowRecord.count == count and rowRecord.buyout == buyout and rowRecord.seller == seller then + row:ShowActiveBorder() + break + end + end + end + end + wipe(purchaseCache) + end, +} + +local defaultColScripts = { + OnEnter = function(self, ...) + if self.rt.disabled then return end + + if self ~= self.row.cols[1] or not self.rt.isShowingItemTooltip then + GameTooltip:SetOwner(self, "ANCHOR_NONE") + GameTooltip:SetPoint("BOTTOMLEFT", self, "TOPLEFT") + + local data = self.row.data + local extra = "" + if self.row.isActive then + extra = TSMAPI.Design:GetInlineColor("link").."\n\n"..L["Alt-Click to immediately buyout this auction."].."|r" + end + if self.rt.expanded[data.itemString] then + GameTooltip:AddLine(L["Double-click to collapse this item and show only the cheapest auction."]..extra, 1, 1, 1, true) + elseif data.expandable then + GameTooltip:AddLine(L["Double-click to expand this item and show all the auctions."]..extra, 1, 1, 1, true) + else + GameTooltip:AddLine(L["There is only one price level and seller for this item."]..extra, 1, 1, 1, true) + end + GameTooltip:Show() + end + + self.row.highlight:Show() + + local handler = self.rt.handlers.OnEnter + if handler then + handler(self.rt, self.row.data, self, ...) + end + end, + + OnLeave = function(self, ...) + if self.rt.disabled then return end + + if self ~= self.row.cols[1] or not self.rt.isShowingItemTooltip then + GameTooltip:Hide() + end + + if not self.rt.selected or self.rt.selected ~= GetTableIndex(self.rt.data, self.row.data) then + self.row.highlight:Hide() + end + + local handler = self.rt.handlers.OnLeave + if handler then + handler(self.rt, self.row.data, self, ...) + end + end, + + OnClick = function(self, button, ...) + if self.rt.disabled then return end + self.rt:ClearSelection() + self.rt.selected = GetTableIndex(self.rt.data, self.row.data) + self.row.highlight:Show() + + if self.rt.quickBuyout and IsAltKeyDown() then + local rowRecord = self.row.data.auctionRecord + for i=GetNumAuctionItems("list"), 1, -1 do + local link = GetAuctionItemLink("list", i) + if not purchaseCache[link] then + local itemString = TSMAPI:GetItemString(link) + -- local _, _, count, _, _, _, _, _, _, buyout, _, _, _, seller = GetAuctionItemInfo("list", i) + local _, _, count, _, _, _, _, _, buyout, _, _, seller = GetAuctionItemInfo("list", i) + if itemString == self.row.data.itemString and rowRecord.count == count and rowRecord.buyout == buyout and rowRecord.seller == seller then + PlaceAuctionBid("list", i, rowRecord.buyout) + TSM:AuctionControlCallback("OnBuyout", {itemString=TSMAPI:GetItemString(rowRecord.parent.itemLink), link=rowRecord.parent.itemLink, count=rowRecord.count, seller=rowRecord.seller, buyout=rowRecord.buyout, destroyingNum=rowRecord.parent.destroyingNum}) + purchaseCache[link] = true + return + end + end + end + end + + local handler = self.rt.handlers.OnClick + if handler then + handler(self.rt, self.row.data, self, button, ...) + end + end, + + OnDoubleClick = function(self, ...) + if self.rt.disabled then return end + local data = self.row.data + if data.expandable then + self.rt:ToggleExpanded(data.itemString) + end + + local handler = self.rt.handlers.OnDoubleClick + if handler then + handler(self.rt, self.row.data, self, ...) + end + end, +} + +function TSMAPI:CreateAuctionResultsTable(parent, handlers, quickBuyout, isDestroying) + local priceColName = TSM.db.profile.pricePerUnit and "Price Per Item" or "Price Per Stack" + local colInfo = isDestroying and { + {name=L["Item"], width=0.43}, + {name=L["Auctions"], width=0.07, align="CENTER"}, + {name=L["Stack Size"], width=0.05, align="CENTER"}, + {name=L["Seller"], width=0.11, align="CENTER"}, + {name=L["Price Per Target Item"], width=0.13, align="RIGHT", isPrice=true}, + {name=priceColName, width=0.13, align="RIGHT", isPrice=true}, + {name=L["% Market Value"], width=0.08, align="CENTER"}, + } or { + {name=L["Item"], width=0.42}, + {name=L["Item Level"], width=0.05, align="CENTER"}, + {name=L["Auctions"], width=0.07, align="CENTER"}, + {name=L["Stack Size"], width=0.05, align="CENTER"}, + {name=L["Time Left"], width=0.09, align="CENTER"}, + {name=L["Seller"], width=0.11, align="CENTER"}, + {name=priceColName, width=0.13, align="RIGHT", isPrice=true}, + {name=L["% Market Value"], width=0.08, align="CENTER"}, + } + + local rtName = "TSMAuctionResultsTable"..RT_COUNT + RT_COUNT = RT_COUNT + 1 + local rt = CreateFrame("Frame", rtName, parent) + rt.NUM_ROWS = TSM.db.profile.auctionResultRows + rt.ROW_HEIGHT = (parent:GetHeight()-HEAD_HEIGHT-HEAD_SPACE)/rt.NUM_ROWS + + rt:SetScript("OnShow", function() + local priceColName = TSM.db.profile.pricePerUnit and L["Price Per Item"] or L["Price Per Stack"] + rt:SetColHeadText(#rt.headCols-1, priceColName) + rt:RefreshRowData() + end) + + local contentFrame = CreateFrame("Frame", rtName.."Content", rt) + contentFrame:SetPoint("TOPLEFT") + contentFrame:SetPoint("BOTTOMRIGHT", -15, 0) + contentFrame:SetScript("OnSizeChanged", function(_, width) OnSizeChanged(rt, width) end) + rt.contentFrame = contentFrame + + -- frame to hold the header columns and the rows + local scrollFrame = CreateFrame("ScrollFrame", rtName.."ScrollFrame", rt, "FauxScrollFrameTemplate") + scrollFrame:SetScript("OnVerticalScroll", function(self, offset) + FauxScrollFrame_OnVerticalScroll(self, offset, rt.ROW_HEIGHT, function() rt:DrawRows() end) + end) + scrollFrame:SetAllPoints(contentFrame) + rt.scrollFrame = scrollFrame + + -- make the scroll bar consistent with the TSM theme + local scrollBar = _G[scrollFrame:GetName().."ScrollBar"] + scrollBar:ClearAllPoints() + scrollBar:SetPoint("BOTTOMRIGHT", rt, -1, 1) + scrollBar:SetPoint("TOPRIGHT", rt, -1, -HEAD_HEIGHT) + scrollBar:SetWidth(12) + local thumbTex = scrollBar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetFrameColor(thumbTex) + thumbTex:SetHeight(150) + thumbTex:SetWidth(scrollBar:GetWidth()) + _G[scrollBar:GetName().."ScrollUpButton"]:Hide() + _G[scrollBar:GetName().."ScrollDownButton"]:Hide() + + -- create the header columns + rt.headCols = {} + for i, info in ipairs(colInfo) do + local col = CreateFrame("Button", rtName.."HeadCol"..i, rt.contentFrame) + col:SetHeight(HEAD_HEIGHT) + if i == 1 then + col:SetPoint("TOPLEFT") + else + col:SetPoint("TOPLEFT", rt.headCols[i-1], "TOPRIGHT") + end + col.info = info + col.rt = rt + col:RegisterForClicks("AnyUp") + col:SetScript("OnClick", OnColumnClick) + + local text = col:CreateFontString() + text:SetJustifyH("CENTER") + text:SetJustifyV("CENTER") + text:SetFont(TSMAPI.Design:GetContentFont("small")) + TSMAPI.Design:SetWidgetTextColor(text) + col:SetFontString(text) + col:SetText(info.name or "") + text:SetAllPoints() + + local tex = col:CreateTexture() + tex:SetAllPoints() + tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight") + tex:SetTexCoord(0.017, 1, 0.083, 0.909) + tex:SetAlpha(0.5) + col:SetNormalTexture(tex) + + local tex = col:CreateTexture() + tex:SetAllPoints() + tex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight") + tex:SetTexCoord(0.025, 0.957, 0.087, 0.931) + tex:SetAlpha(0.2) + col:SetHighlightTexture(tex) + + tinsert(rt.headCols, col) + end + + -- create the rows + rt.rows = {} + for i=1, rt.NUM_ROWS do + local row = CreateFrame("Frame", rtName.."Row"..i, rt.contentFrame) + row:SetHeight(rt.ROW_HEIGHT) + if i == 1 then + row:SetPoint("TOPLEFT", 0, -(HEAD_HEIGHT+HEAD_SPACE)) + row:SetPoint("TOPRIGHT", 0, -(HEAD_HEIGHT+HEAD_SPACE)) + else + row:SetPoint("TOPLEFT", rt.rows[i-1], "BOTTOMLEFT") + row:SetPoint("TOPRIGHT", rt.rows[i-1], "BOTTOMRIGHT") + end + local highlight = row:CreateTexture() + highlight:SetAllPoints() + highlight:SetTexture(1, .9, 0, .5) + highlight:Hide() + row.highlight = highlight + row.rt = rt + + row.cols = {} + for j=1, #colInfo do + local col = CreateFrame("Button", nil, row) + local text = col:CreateFontString() + if TSM.db.profile.showBids and colInfo[j].isPrice then + text:SetFont(TSMAPI.Design:GetContentFont(), min(13, rt.ROW_HEIGHT/2 - 2)) + else + text:SetFont(TSMAPI.Design:GetContentFont(), min(14, rt.ROW_HEIGHT)) + end + text:SetJustifyH(colInfo[j].align or "LEFT") + text:SetJustifyV("CENTER") + text:SetPoint("TOPLEFT", 1, -1) + text:SetPoint("BOTTOMRIGHT", -1, 1) + col:SetFontString(text) + col:SetHeight(rt.ROW_HEIGHT) + col:RegisterForClicks("AnyUp") + for name, func in pairs(defaultColScripts) do + col:SetScript(name, func) + end + col.rt = rt + col.row = row + col.rowNum = i + + if j == 1 then + col:SetPoint("TOPLEFT") + else + col:SetPoint("TOPLEFT", row.cols[j-1], "TOPRIGHT") + end + + if j%2 == 1 then + local tex = col:CreateTexture() + tex:SetAllPoints() + tex:SetTexture(1, 1, 1, .03) + col:SetNormalTexture(tex) + end + + -- special first column to hold spacer / item name / item icon + if j == 1 then + local spacer = CreateFrame("Frame", nil, col) + spacer:SetPoint("TOPLEFT") + spacer:SetHeight(rt.ROW_HEIGHT) + spacer:SetWidth(1) + col.spacer = spacer + + local iconBtn = CreateFrame("Button", nil, col) + iconBtn:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=1.5}) + iconBtn:SetBackdropBorderColor(0, 1, 0, 0) + iconBtn:SetPoint("TOPLEFT", spacer, "TOPRIGHT") + iconBtn:SetHeight(rt.ROW_HEIGHT) + iconBtn:SetWidth(rt.ROW_HEIGHT) + iconBtn:SetScript("OnEnter", function(self) + if row.data.link then + GameTooltip:SetOwner(self, "ANCHOR_RIGHT") + TSMAPI:SafeTooltipLink(row.data.link) + GameTooltip:Show() + rt.isShowingItemTooltip = true + end + end) + iconBtn:SetScript("OnLeave", function(self) + -- BattlePetTooltip:Hide() + GameTooltip:ClearLines() + GameTooltip:Hide() + rt.isShowingItemTooltip = false + end) + iconBtn:SetScript("OnClick", function(_, ...) + if IsModifiedClick() then + HandleModifiedItemClick(row.data.auctionRecord.parent.itemLink) + else + col:GetScript("OnClick")(col, ...) + end + end) + iconBtn:SetScript("OnDoubleClick", function(_, ...) + col:GetScript("OnDoubleClick")(col, ...) + end) + local icon = iconBtn:CreateTexture(nil, "ARTWORK") + icon:SetPoint("TOPLEFT", 2, -2) + icon:SetPoint("BOTTOMRIGHT", -2, 2) + col.iconBtn = iconBtn + col.icon = icon + + row.ShowActiveBorder = function() + if rt.quickBuyout then + row.isActive = true + iconBtn:SetBackdropBorderColor(0, 1, 0, .7) + end + end + + row.HideActiveBorder = function() + row.isActive = nil + iconBtn:SetBackdropBorderColor(0, 0, 0, 0) + end + + text:ClearAllPoints() + text:SetPoint("TOPLEFT", iconBtn, "TOPRIGHT", 2, 0) + text:SetPoint("BOTTOMRIGHT") + end + tinsert(row.cols, col) + end + + if i%2 == 0 then + local tex = row:CreateTexture() + tex:SetAllPoints() + tex:SetTexture("Interface\\WorldStateFrame\\WorldStateFinalScore-Highlight") + tex:SetTexCoord(0.017, 1, 0.083, 0.909) + tex:SetAlpha(0.3) + end + + tinsert(rt.rows, row) + end + + rt:SetAllPoints() + rt.data = {} + rt.expanded = {} + rt.displayRows = {} + rt.handlers = handlers or {} + rt.sortInfo = {} + rt.quickBuyout = quickBuyout + rt.isDestroying = isDestroying + tinsert(resultsTables, rt) + + for name, func in pairs(methods) do + rt[name] = func + end + LibStub("AceEvent-3.0").RegisterEvent(rt, "AUCTION_ITEM_LIST_UPDATE", "UpdateActiveRows") + + return rt +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionScanning.lua b/TradeSkillMaster/Auction/AuctionScanning.lua new file mode 100644 index 0000000..879cba7 --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionScanning.lua @@ -0,0 +1,628 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains code for scanning the auction house +local TSM = select(2, ...) +local AuctionScanning = TSM:NewModule("AuctionScanning", "AceEvent-3.0") +TSMAPI.AuctionScan = {} + +local RETRY_DELAY = 2 +local MAX_RETRIES = 4 +local BASE_DELAY = 0.10 -- time to delay for before trying to scan a page again when it isn't fully loaded +local private = { callbackHandler = nil, query = {}, options = {}, data = {}, isScanning = nil } +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionScanning_private") +local scanCache = {} + +local CACHE_DECAY_PER_DAY = 5 +local CACHE_AUTO_HIT_TIME = 10 * 60 +local SECONDS_PER_DAY = 60 * 60 * 24 + + +local function DoCallback(...) + if type(private.callbackHandler) == "function" then + private.callbackHandler(...) + end +end + +local function eventHandler(event) + if event == "AUCTION_HOUSE_CLOSED" then + -- auction house was closed, make sure all scanning is stopped + AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + private.auctionHouseShown = false + DoCallback("INTERRUPTED") + private:StopScanning() + elseif event == "AUCTION_ITEM_LIST_UPDATE" then + -- gets called whenever the AH window is updated (something is shown in the results section) + AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + TSMAPI:CancelFrame("updateDelay") + -- now that our query was successful, we can get our data + private:ScanAuctions() + end +end + +function AuctionScanning:OnEnable() + AuctionScanning:RegisterEvent("AUCTION_HOUSE_CLOSED", eventHandler) +end + +function private:ScanAuctionPage(resolveSellers) + local shown = GetNumAuctionItems("list") + local badData = false + local auctions = {} + + for i = 1, shown do + -- checks to make sure all the data has been sent to the client + -- if not, the data is bad and we'll wait / try again + -- local count, _, _, _, _, _, _, buyout, _, _, _, seller = select(3, GetAuctionItemInfo("list", i)) + local count, _, _, _, _, _, buyout, _, _, seller = select(3, GetAuctionItemInfo("list", i)) + local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", i)) + auctions[i] = { itemString = itemString, index = i, count = count, buyout = buyout, seller = seller } + if not (itemString and buyout and count and (seller or not resolveSellers or buyout == 0)) then + badData = true + end + end + + return badData, auctions +end + +function IsDuplicatePage() + if not private.pageTemp or GetNumAuctionItems("list") == 0 then return false end + + local numLinks, prevLink = 0, nil + for i = 1, GetNumAuctionItems("list") do + -- local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, _, seller = GetAuctionItemInfo("list", i) + local _, _, count, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i) + local link = GetAuctionItemLink("list", i) + local temp = private.pageTemp[i] + + if not prevLink then + prevLink = link + elseif prevLink ~= link then + prevLink = link + numLinks = numLinks + 1 + end + + if not temp or temp.count ~= count or temp.minBid ~= minBid or temp.minInc ~= minInc or temp.buyout ~= buyout or temp.bid ~= bid or temp.seller ~= seller or temp.link ~= link then + return false + end + end + + if numLinks > 1 and private.pageTemp.shown == GetNumAuctionItems("list") then + return false + end + + return true +end + +local function PopulatePageTemp() + local shown = GetNumAuctionItems("list") + private.pageTemp = { numShown = shown } + + for i = 1, shown do + -- checks to make sure all the data has been sent to the client + -- if not, the data is bad and we'll wait / try again + -- local _, _, count, _, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i) + local _, _, count, _, _, _, minBid, minInc, buyout, bid, _, seller = GetAuctionItemInfo("list", i) + local link = GetAuctionItemLink("list", i) + + private.pageTemp[i] = { count = count, minBid = minBid, minInc = minInc, buyout = buyout, bid = bid, seller = seller, link = link } + end +end + +-- Starts a scan of the auction house. +-- query - A single query containing QueryAuctionItem paramters: +-- name, minLevel, maxLevel, invType, class, subClass, usable, quality +-- resolveSellers - whether or not to resolve seller names +-- maxPrice - stop scanning when prices go above this price +function TSMAPI.AuctionScan:RunQuery(query, callbackHandler, resolveSellers, maxPrice, doCache) + TSMAPI.AuctionScan:StopScan() -- stop any scan in progress + + if not AuctionFrame:IsVisible() then + return -1 -- the auction house isn't open (return code -1) + elseif type(query) ~= "table" then + return -2 -- the scan queue is not a table (return code -2) + elseif not CanSendAuctionQuery() then + TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:RunQuery(query, callbackHandler, resolveSellers, maxPrice, doCache) end) + return 0 -- the query will start as soon as it can but did not start immediately (return code 0) + end + + -- sort by buyout + SortAuctionItems("list", "buyout") + if IsAuctionSortReversed("list", "buyout") then + SortAuctionItems("list", "buyout") + end + + -- setup the query + private.query = CopyTable(query) + private.query.page = 0 -- the current page of this query we're scanning + private.query.timeDelay = 0 -- a delay used to wait for information to show up + private.query.retries = 0 -- how many times we've done a hard retry so far + private.query.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query + private.cache = doCache and { query = CopyTable(query), items = {} } or nil + + -- setup other stuff + wipe(private.data) + private.isScanning = true + private.callbackHandler = callbackHandler + private.resolveSellers = resolveSellers + private.scanType = "query" + private.maxPrice = maxPrice or math.huge + + --starts scanning + private:SendQuery() + return 1 -- scan started successfully (return code 1) +end + +function TSMAPI.AuctionScan:ScanLastPage(callbackHandler) + private:StopScanning() -- stop any scan in progress + + if not AuctionFrame:IsVisible() then + return -1 -- the auction house isn't open (return code -1) + elseif not CanSendAuctionQuery() then + TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:ScanLastPage(callbackHandler) end) + return 0 -- the query will start as soon as it can but did not start immediately (return code 0) + end + + -- clear the auction sort + SortAuctionClearSort("list") + + -- setup the query + private.query = {name="", page=0} + private.query.timeDelay = 0 -- a delay used to wait for information to show up + private.query.retries = 0 -- how many times we've done a hard retry so far + private.query.hardRetry = nil -- if a page hasn't loaded after we've tried a delay, we'll do a hard retry and re-send the query + + -- setup other stuff + wipe(private.data) + private.isScanning = true + private.callbackHandler = callbackHandler + private.scanType = "lastPage" + + --starts scanning + private:SendQuery() + return 1 -- scan started successfully (return code 1) +end + +-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay) +function private:SendQuery() + if not private.isScanning then return end + + if CanSendAuctionQuery() then + -- stop delay timer + TSMAPI:CancelFrame("queryDelay") + + -- Query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire) + AuctionScanning:RegisterEvent("AUCTION_ITEM_LIST_UPDATE", eventHandler) + -- [exact] cardinal ruby 0 0 nil 0 0 0 0 0 + -- [normal] cardinal ruby nil nil nil nil nil 0 nil nil + QueryAuctionItems(private.query.name, private.query.minLevel, private.query.maxLevel, private.query.invType, private.query.class, private.query.subClass, private.query.page, private.query.usable, private.query.quality) + else + -- run delay timer then try again to scan + TSMAPI:CreateTimeDelay("queryDelay", 0.05, private.SendQuery) + end +end + +--scans the currently shown page of auctions and collects all the data +function private:ScanAuctions() + if not private.isScanning then return end + local shown, total = GetNumAuctionItems("list") + local totalPages = ceil(total / NUM_AUCTION_ITEMS_PER_PAGE) + + if private.scanType == "numPages" then + local cacheData = TSM.db.factionrealm.numPagesCache[private.query.cacheKey] + cacheData.lastScan = time() + local confidence = (120 - cacheData.confidence) / (CACHE_DECAY_PER_DAY * 2) + local diff = abs(cacheData.avg - totalPages) + if diff <= 1 and diff > 0.5 then + confidence = floor(confidence * (1.5 - diff)) + elseif diff > 1 then + confidence = floor(confidence - CACHE_DECAY_PER_DAY * diff) + end + cacheData.confidence = max(floor(cacheData.confidence + confidence), 0) + cacheData.avg = (cacheData.avg * cacheData.numScans + totalPages) / (cacheData.numScans + 1) + cacheData.numScans = cacheData.numScans + 1 + + private:StopScanning() + return DoCallback("NUM_PAGES", totalPages) + elseif private.scanType == "lastPage" then + local lastPage = floor(total / NUM_AUCTION_ITEMS_PER_PAGE) + if private.query.page ~= lastPage then + private.query.page = lastPage + return private:SendQuery() + end + end + + local dataIsBad, auctions = private:ScanAuctionPage(private.resolveSellers) + + -- check that we have good data + if dataIsBad or IsDuplicatePage() then + if private.query.retries < MAX_RETRIES then + if private.query.hardRetry then + -- Hard retry + -- re-sends the entire query + private.query.retries = private.query.retries + 1 + private.query.timeDelay = 0 + private.query.hardRetry = nil + private:SendQuery() + else + -- Soft retry + -- runs a delay and then tries to scan the query again + private.query.timeDelay = private.query.timeDelay + BASE_DELAY + TSMAPI:CreateTimeDelay("updateDelay", BASE_DELAY, private.ScanAuctions) + + -- If after 2 seconds of retrying we still don't have data, will go and requery to try and solve the issue + -- if we still don't have data, we try to scan it anyway and move on. + if private.query.timeDelay >= RETRY_DELAY then + private.query.hardRetry = true + end + end + return + end + end + + if private.cache then + -- store info in cache + for i, v in ipairs(auctions) do + local cacheTmp = CopyTable(v) + cacheTmp.index = private.query.page * 50 + i + tinsert(private.cache, cacheTmp) + private.cache.items[cacheTmp.itemString] = true + end + end + + private.query.hardRetry = nil + private.query.retries = 0 + private.query.timeDelay = 0 + if private.scanType ~= "lastPage" then + private.query.page = private.query.page + 1 -- increment current page + if totalPages > 0 then + DoCallback("SCAN_PAGE_UPDATE", private.query.page, totalPages) + end + end + PopulatePageTemp() + + -- now that we know our query is good, time to verify and then store our data + for _, v in ipairs(auctions) do + if private:AddAuctionRecord(v.index) then + -- we've hit the max price so we're done scanning + private:StopScanning() + return DoCallback("SCAN_COMPLETE", private.data) + end + end + + if private.scanType == "lastPage" then + return DoCallback("SCAN_LAST_PAGE_COMPLETE", private.data) + elseif private.query.page >= totalPages then + -- we have finished scanning this query + private:StopScanning() + return DoCallback("SCAN_COMPLETE", private.data) + end + + -- query the next page and continue scanning + private:SendQuery() +end + +-- Add a new record to the private.data table +function private:AddAuctionRecord(index) + -- local name, texture, count, _, _, _, _, minBid, minIncrement, buyout, bid, highBidder, highBidder_full, seller, seller_full = GetAuctionItemInfo("list", index) + local name, texture, count, _, _, _, minBid, minIncrement, buyout, bid, highBidder, seller = GetAuctionItemInfo("list", index) + seller = TSM:GetAuctionPlayer(seller, null) + highBidder = TSM:GetAuctionPlayer(highBidder, null) + local timeLeft = GetAuctionItemTimeLeft("list", index) + local link = GetAuctionItemLink("list", index) + local itemString = TSMAPI:GetItemString(link) + if not itemString then return end + + -- Create a new entry in the table + if not private.data[itemString] then + private.data[itemString] = TSMAPI.AuctionScan:NewAuctionItem() + private.data[itemString]:SetItemLink(link) + private.data[itemString]:SetTexture(texture) + end + private.data[itemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or "?", timeLeft) + + -- add the base item if necessary + local baseItemString = TSMAPI:GetBaseItemString(itemString) + if baseItemString ~= itemString then + -- Create a new entry in the table + if not private.data[baseItemString] then + private.data[baseItemString] = TSMAPI.AuctionScan:NewAuctionItem() + private.data[baseItemString]:SetItemLink(link) + private.data[baseItemString]:SetTexture(texture) + end + private.data[baseItemString]:AddAuctionRecord(count, minBid, minIncrement, buyout, bid, highBidder, seller or "?", timeLeft) + private.data[baseItemString].isBaseItem = true + end + + if select(8, TSMAPI:GetSafeItemInfo(link)) == count then + return (buyout or 0) / count > (private.maxPrice or math.huge) + end +end + +-- stops the scan when we are finished scanning, it was interrupted, or somebody stopped it +function private:StopScanning() + TSMAPI:CancelFrame("cantSendAuctionQueryDelay") + if not private.isScanning then return end + + if private.cache then + -- store the cache info + sort(private.cache, function(a, b) return a.index < b.index end) + for itemString in pairs(private.cache.items) do + scanCache[itemString] = private.cache + end + wipe(private.cache.items) + private.cache = nil + end + + -- cancel any delays that might still be running + TSMAPI:CancelFrame("queryDelay") + TSMAPI:CancelFrame("updateDelay") + AuctionScanning:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + private.isScanning = nil + private.pageTemp = nil +end + +-- API for stopping the scan +-- returns true/false if we were/weren't actually scanning +function TSMAPI.AuctionScan:StopScan() + private:StopScanning() + TSM:StopGeneratingQueries() +end + + +-- Gets the number of pages for a given query +function TSMAPI.AuctionScan:GetNumPages(query, callbackHandler) + private:StopScanning() -- stop any scan in progress + + if not AuctionFrame:IsVisible() then + return -1 -- the auction house isn't open (return code -1) + elseif type(query) ~= "table" then + return -2 -- the scan queue is not a table (return code -2) + elseif not CanSendAuctionQuery() then + TSMAPI:CreateTimeDelay("cantSendAuctionQueryDelay", 0.1, function() TSMAPI.AuctionScan:GetNumPages(query, callbackHandler) end) + return 0 -- the query will start as soon as it can but did not start immediately (return code 0) + end + + -- fancy caching + local temp = {} + for i, field in ipairs({ "name", "minLevel", "maxLevel", "invType", "class", "subClass", "usable", "quality" }) do + temp[i] = tostring(query[field]) + end + local cacheKey = table.concat(temp, "~") + local cacheData = TSM.db.factionrealm.numPagesCache[cacheKey] + if cacheData then + local cacheHit + if time() - cacheData.lastScan < CACHE_AUTO_HIT_TIME then + -- auto cache hit + cacheHit = true + elseif random(1, 100) <= cacheData.confidence then + -- cache hit + cacheData.confidence = cacheData.confidence - floor(((time() - cacheData.lastScan) / SECONDS_PER_DAY) * CACHE_DECAY_PER_DAY + 0.5) + cacheData.confidence = max(cacheData.confidence, 0) -- ensure >= 0 + cacheHit = true + end + + if cacheHit then + local numPages = max(ceil(cacheData.avg), 1) -- round avg num of pages up and ensure >= 1 + TSMAPI:CreateTimeDelay("numPagesCacheDelay", 0, function() callbackHandler("NUM_PAGES", numPages) end) + return 2 + end + else + TSM.db.factionrealm.numPagesCache[cacheKey] = { avg = 0, confidence = 0, numScans = 0, lastScan = 0 } + end + + -- setup the query + private.query = CopyTable(query) + private.query.cacheKey = cacheKey + + -- setup other stuff + wipe(private.data) + private.isScanning = true + private.callbackHandler = callbackHandler + private.scanType = "numPages" + + --starts scanning + private:SendQuery() + return 1 -- scan started successfully (return code 1) +end + +function TSMAPI.AuctionScan:CacheRemove(itemString, index) + if scanCache[itemString] then + tremove(scanCache[itemString], index) + end +end + +function TSMAPI.AuctionScan:ClearCache() + wipe(scanCache) +end + + + + +local findPrivate = {} +findPrivate.findFrame = findPrivate.findFrame or CreateFrame("Frame") + +local function eventHandler(frame, event) + if event == "AUCTION_HOUSE_SHOW" then + -- auction house was opened + elseif event == "AUCTION_HOUSE_CLOSED" then + frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + if findPrivate.isScanning then -- stop scanning if we were scanning (pass true to specify it was interrupted) + TSMAPI.AuctionScan:StopFindScan() + end + elseif event == "AUCTION_ITEM_LIST_UPDATE" then + frame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + if findPrivate.isScanning then + findPrivate.timeDelay = 0 + TSMAPI:CancelFrame("auctionFindScanDelay") + + -- now that our query was successful we can get our data + findPrivate:ScanAuctions() + end + end +end + +findPrivate.findFrame:SetScript("OnEvent", eventHandler) +findPrivate.findFrame:RegisterEvent("AUCTION_HOUSE_CLOSED") +findPrivate.findFrame:RegisterEvent("AUCTION_HOUSE_SHOW") + +local function CompareTableKeys(tbl1, tbl2) + for _, key in ipairs(findPrivate.keys) do + if tbl1[key] ~= tbl2[key] then + return + end + end + return true +end + +local function IsTargetAuction(index) + local itemString = TSMAPI:GetItemString(GetAuctionItemLink("list", index)) + -- local _, _, count, _, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, _, seller, seller_full = GetAuctionItemInfo("list", index) + local _, _, count, _, _, _, minBid, bidIncrement, buyout, bidAmount, _, _, seller = GetAuctionItemInfo("list", index) + seller = TSM:GetAuctionPlayer(seller, nil) + local bid = bidAmount == 0 and minBid or bidAmount + local tmp = { itemString = itemString, count = count, bid = bid, buyout = buyout, seller = seller } + return CompareTableKeys(tmp, findPrivate.targetInfo) +end + +-- valid targetInfo keys: itemString, count, bid, buyout, seller +function TSMAPI.AuctionScan:FindAuction(callback, targetInfo, useCache) + if findPrivate.isScanning then TSMAPI.AuctionScan:StopFindScan() end + + findPrivate.keys = { "itemString", "count", "bid", "buyout", "seller" } + for i = #findPrivate.keys, 1, -1 do + if not targetInfo[findPrivate.keys[i]] then + tremove(findPrivate.keys, i) + end + end + + local cacheIndex + if useCache and scanCache[targetInfo.itemString] then + for i, v in ipairs(scanCache[targetInfo.itemString]) do + if CompareTableKeys(v, targetInfo) then + cacheIndex = i + break + end + end + end + + if cacheIndex then + findPrivate.page = floor((cacheIndex - 1) / 50) + findPrivate.query = scanCache[targetInfo.itemString].query + else + local name, _, rarity, _, minLevel, class, subClass = TSMAPI:GetSafeItemInfo(targetInfo.itemString) + findPrivate.query = { name = name, minLevel = minLevel, maxLevel = minLevel, class = class, subClass = subClass, rarity = rarity } + findPrivate.page = 0 + end + findPrivate.targetInfo = targetInfo + findPrivate.callback = callback + findPrivate.cacheIndex = cacheIndex + findPrivate.isScanning = targetInfo.itemString + findPrivate.retries = 0 + findPrivate.hardRetry = nil + + -- check if the item is on the current page + for i = 1, GetNumAuctionItems("list") do + if IsTargetAuction(i) then + TSMAPI.AuctionScan:StopFindScan() + TSMAPI:CreateTimeDelay("queryFoundDelay", 0.1, function() findPrivate.callback(i) end) + return + end + end + + findPrivate:SendQuery() +end + +-- sends a query to the AH frame once it is ready to be queried (uses frame as a delay) +function findPrivate:SendQuery() + if not findPrivate.isScanning then return end + if CanSendAuctionQuery() then + -- stop delay timer + TSMAPI:CancelFrame("auctionFindQueryDelay") + + -- query the auction house (then waits for AUCTION_ITEM_LIST_UPDATE to fire) + findPrivate.findFrame:RegisterEvent("AUCTION_ITEM_LIST_UPDATE") + local q = findPrivate.query + QueryAuctionItems(q.name, q.minLevel, q.maxLevel, q.invType, q.class, q.subClass, findPrivate.page, 0, q.rarity) + else + -- run delay timer then try again to scan + TSMAPI:CreateTimeDelay("auctionFindQueryDelay", 0.05, function() findPrivate:SendQuery() end) + end +end + +-- scans the currently shown page of auctions and collects all the data +function findPrivate:ScanAuctions() + if not findPrivate.isScanning then return end + -- collects data on the query: + -- # of auctions on current page + -- # of pages total + local shown, total = GetNumAuctionItems("list") + local totalPages = math.ceil(total / 50) + local dataIsBad, temp = private:ScanAuctionPage(findPrivate.targetInfo.seller) + + -- Check for bad data + if findPrivate.retries < 3 then + if dataIsBad then + if findPrivate.hardRetry then + -- Hard retry + -- re-sends the entire query + findPrivate.retries = findPrivate.retries + 1 + findPrivate:SendQuery() + else + -- Soft retry + -- runs a delay and then tries to scan the query again + findPrivate.timeDelay = findPrivate.timeDelay + BASE_DELAY + TSMAPI:CreateTimeDelay("auctionFindScanDelay", BASE_DELAY, findPrivate.ScanAuctions) + + -- If after 4 seconds of retrying we still don't have data, will go and requery to try and solve the issue + -- if we still don't have data, we try to scan it anyway and move on. + if findPrivate.timeDelay >= 4 then + findPrivate.hardRetry = true + findPrivate.retries = 0 + end + end + + return + end + end + + findPrivate.hardRetry = nil + findPrivate.retries = 0 + + -- now that we know our query is good, time to verify and then store our data + for i = 1, shown do + if IsTargetAuction(temp[i].index) then + TSMAPI.AuctionScan:StopFindScan() + return findPrivate.callback(temp[i].index, findPrivate.cacheIndex == findPrivate.page and findPrivate.page * 50 + temp[i].index) + end + end + + -- This query has more pages to scan + -- increment the page # and send the new query + if not findPrivate.cacheIndex and totalPages > (findPrivate.page + 1) then + findPrivate.page = findPrivate.page + 1 + findPrivate:SendQuery() + return + end + + -- we are done scanning! + TSMAPI.AuctionScan:StopFindScan() + return findPrivate.callback() +end + +-- returns whether or not we're currently doing a find scan +function TSMAPI.AuctionScan:IsFindScanning() + return findPrivate.isScanning +end + +-- stops the scan because it was either interrupted or it was completed successfully +function TSMAPI.AuctionScan:StopFindScan() + findPrivate.findFrame:UnregisterEvent("AUCTION_ITEM_LIST_UPDATE") + findPrivate.isScanning = nil + TSMAPI:CancelFrame("auctionFindQueryDelay") + TSMAPI:CancelFrame("auctionFindScanDelay") +end \ No newline at end of file diff --git a/TradeSkillMaster/Auction/AuctionUtil.lua b/TradeSkillMaster/Auction/AuctionUtil.lua new file mode 100644 index 0000000..e321b67 --- /dev/null +++ b/TradeSkillMaster/Auction/AuctionUtil.lua @@ -0,0 +1,116 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.AuctionUtil_private") +LibStub("AceEvent-3.0"):Embed(private) + + +local eventFrame = CreateFrame("Frame") +eventFrame:Hide() +eventFrame.data = {} +eventFrame.callback = function() end +eventFrame:SetScript("OnEvent", function(self, event, ...) + if self.interrupt and event == self.interrupt.event and self.interrupt.callback() then + self:UnregisterAllEvents() + self.data = {} + end + for i=1, #self.data do + if self.data[i].event == event then + if self.data[i].callback then + if self.data[i].callback(event, ...) then + tremove(self.data, i) + self:UnregisterEvent(event) + end + else + tremove(self.data, i) + self:UnregisterEvent(event) + end + break + end + end + if #self.data == 0 then + self:Hide() + self.callback() + end + end) + +local function WaitForEvents(data, callback, interrupt) + eventFrame.data = data + eventFrame.callback = callback + for i=1, #data do + eventFrame:RegisterEvent(data[i].event) + end + if interrupt then + eventFrame.interrupt = interrupt + eventFrame:RegisterEvent(interrupt.event) + end + eventFrame:Show() +end + +function TSMAPI:CreateEventDelay(event, callback, timeout, validator) + if not event then return end + local eventName = "eventDelay"..random() + if timeout then + TSMAPI:CreateTimeDelay(eventName, timeout, function() eventFrame:Hide() end) + callback() + end + + WaitForEvents({event=event, callback=validator}, function() callback() TSMAPI:CancelFrame(eventName) end) +end + +-- Sends the "TSM_AH_EVENTS" message once the action (buyout/bid/cancel/post) +-- has been acknowledged by the server and the client has been notified +function TSMAPI:WaitForAuctionEvents(mode, isMultiPost) + local function ValidateEvent(_, msg) + if mode == "Buyout" then + return msg:match(gsub(ERR_AUCTION_BID_PLACED, "%%s", "")) + elseif mode == "Cancel" then + return msg == ERR_AUCTION_REMOVED + elseif mode == "Post" then + return msg == ERR_AUCTION_STARTED + end + end + + local events, interrupt + if mode == "Buyout" then + events = {{event="AUCTION_ITEM_LIST_UPDATE"}, {event="CHAT_MSG_SYSTEM", callback=ValidateEvent}} + interrupt = {event="UI_ERROR_MESSAGE", callback=function(_,msg) return msg == ERR_AUCTION_HIGHER_BID end} + elseif mode == "Cancel" then + events = {{event="CHAT_MSG_SYSTEM", callback=ValidateEvent}, {event="AUCTION_OWNED_LIST_UPDATE"}} + elseif mode == "Post" then + if isMultiPost then + events = {{event="AUCTION_MULTISELL_UPDATE", callback=function(_,arg1,arg2) return arg1 == arg2 end}} + else + events = {{event="CHAT_MSG_SYSTEM", callback=ValidateEvent}} + end + end + if events then + WaitForEvents(events, function() private:SendMessage("TSM_AH_EVENTS", mode) end, interrupt) + end +end + + +function TSMAPI:GetAuctionPercentColor(percent) + local colors = { + {color="|cff2992ff", value=50}, -- blue + {color="|cff16ff16", value=80}, -- green + {color="|cffffff00", value=110}, -- yellow + {color="|cffff9218", value=135}, -- orange + {color="|cffff0000", value=math.huge}, -- red + } + + for i=1, #colors do + if percent < colors[i].value then + return colors[i].color + end + end + + return "|cffffffff" +end \ No newline at end of file diff --git a/TradeSkillMaster/ChangeLog.txt b/TradeSkillMaster/ChangeLog.txt new file mode 100644 index 0000000..813a068 --- /dev/null +++ b/TradeSkillMaster/ChangeLog.txt @@ -0,0 +1,442 @@ +v2.8.3 +*Made TSMAPI:IsSoulbound() MUCH more efficient which will GREATLY reduce the interface log of all modules.. + +v2.8.2 +*Added LibChatAnims to fix Blizzard bug with adding chat filters. + +v2.8.1 +*Updated connected realms. +*Added some more validation to price sources to avoid lua errors. + +v2.8 +*Fixed issue with TSM error handler. +*Removed 1.x dealfinding / shopping list support for importing items to groups. +*Added options for displaying prospect and mill values in tooltips (enabled by default). +*Reorganized general tooltip options slightly. +*Fixed bug with "/tsm version" not going in the right chat tab. +*Added confirmation for copying a profile. + +v2.7 +*Added new "check" function for doing logic operations in custom prices. +*Fixed bug with multiple fixed gold values in custom prices being invalid in certain situations. +*Removed "Status / Credits" tab and put credits at the bottom of the first tab instead. +*Modified the "New Group Name" editbox to contain the current group name by default. +*Made external price sources (mainly from Auctioneer) more consistent with TSM price sources. + +v2.6.2 +*Added verify for a sync setup which has been corrupted due to manual copying of saved variables. +*Added help button to main TSM frame for opening TSM assistant. +*Made lines under icons in main TSM frame shorter so they don't interfere with the title text. + +v2.6.1 +*Fixed typo in Assistant step. +*Made Assistant window wider. + +v2.6 +*Added new TSM Assistant feature! +*Removed SetUnit hook from LibExtraTip to avoid errors. +*Updated localized strings. +*Updated hard-coded list of connected realms. + +v2.5.14 +*Added line to TSM Info / Help page. + +v2.5.13 +*Fixed issue with importing groups with spaces in the subgroup names. +*Fixed bug with post confirmation window and removed coloring of g/s/c letters. + +v2.5.12 +*Fixed bug introduced in v2.5.11 with percentages in price sources. + +v2.5.11 +*Fixed bug with parsing price sources with multiple percentages. + +v2.5.10 +*Added functionality to TSMAPI functions including auto-complete support for editboxes. +*Minor code cleanup. + +v2.5.9 +*Fixed bug which was causing lua errors in TSM_Shopping. + +v2.5.8 +*Minor bug fix. + +v2.5.7 +*Improved TSM:GROUPS:* events for external usage. + +v2.5.6 +*Added TUJ as optional dependency to ensure it loads first. + +v2.5.5 +*Fixed bug with importing operations. + +v2.5.4 +*Cleaned up some code in the error handler and made it more resilient. + +v2.5.3 +*No change - fixing issue with curse packager. + +v2.5.2 +*Fixed bug with new change in v2.5.1. + +v2.5.1 +*Fixed bug with preparing filters taking a very long time. +*Removed name from auction results row. +*Removed some debug code. + +v2.5 +*Added error message when you try and move a group to one of its subgroups. +*Reorganized and cleaned up the code. +*Added option for embedding TSM's tooltip lines (enabled by default to maintain prior behavior). +*TSM's error handler will now ignore errors from auc-stat-wowuction. +*Fixed issue with multi-account syncing in patch 5.4.7. + +v2.4.5 +*Fixed issue with resizing the TSM window. +*Added some debug code. +*Potentially fixed bug with scans scanning too many pages due to missing item info. +*Fixed bug with main window not correctly saving its position and size between sessions. +*Fixed bug with moving the main window from the TSM icon +*Added TSMAPI:Assert for unexpected conditions which should be reported as errors to the TSM team. +*Added TSMAPI:Verify for conditions which require corrective action by the user and are not addon bugs. +*Soulbound items will no longer be moved for warehousing/auctioning options on the BankUI. +*Added events and event logger. Improved error handler. +*Fixed bug with tooltips having a delayed update after modifier keys are pressed. +*Errors occurring within threads should now be properly reported. +*Added vanilla gems to conversions. +*Added TSMAPI:GetConnectedRealms() to allow for basic connected realm support. +*Fixed bug with multi-account code constantly trying to add people to the friends list. + +v2.4.4 +*Fixed bug with TSM tooltip. +*Cleaned up some code. + +v2.4.3 +*Added tooltip options for displaying custom price sources in tooltips. + +v2.4.2 +*Fixed stack overflow issue with importing of large groups. + +v2.4.1 +*No change, trying to fix issue with curse. + +v2.4 +*Implemented new method for modules to export data to the TSM app. +*Changed red group color to be orange instead. +*Added support for scanning the last page (used by the Sniper feature of TSM_Shopping). +*Cleaned up much of the Auction scanning code. +*Made the "max" labels in the post frame clickable buttons. +*Added support for tabbing between fields in the post frame. +*Fixed some issues with prices changing incorrectly in the post frame when certain fields were modified. +*Added caching of battlepet names on-load to reduce errors from incomplete battlepet info. + +v2.3.2 +*Fixed some display issues with slider tooltips. + +v2.3.1 +*Fixed bug with common search term generation for items which have overlapping names (ie gems). +*Fixed bug in group import code. + +v2.3 +*TSM will now take into account common classes when generating AH query. +*Added option (enabled by default) to color group names within group trees based on their sub-group depth. +*Added display of locals to error handler. +*Fixed bug in common search term code. + +v2.2.10 +*Added [Jard's Peculiar Energy Source] to soulbound mats. + +v2.2.9 +*Fixed bug with adding a special character when renaming a group. + +v2.2.8 +*Fixed bug with battle pets. + +v2.2.7 +*Groups in grouptrees will now be selected by default. +*Greatly improved TSM's display of error messages. +*Fixed bug in TSMAPI:CreateTimeDelay which caused label-less timers to collide. + +v2.2.6 +*Fixed an issue with common search terms for items whose name matches the common search term (ie uncut gems). + +v2.2.5 +*Fixed bug with the current profile not being saved. +*Fixed bug with getting battle pet item info. +*Fixed sorting of auction result table. +*Removing an operation from a group will no longer switch to the new operation page. + +v2.2.4 +*Made group trees select all groups by default. + +v2.2.3 +*Fixed bug with formatted gold amounts in custom prices. + +v2.2.2 +*Fixed issue with using itemlinks in custom prices. + +v2.2.1 +*Fixed issue with group trees not remember their selection status in some situations. + +v2.2 +*Fixed bug with getting battle pet item info. +*Removed TSMAPI function to disable TSM error handler from packaged versions. +*Added list of groups which an operation is currently applied to, along with a remove button for each group, to the management tab of operations. +*Typing "/tsm freset" will now reset the position of all movable frames from all modules. +*Group trees will now remember which groups are selected (all will be deselected by default). +*Custom prices will now support any number of formatted gold values (instead of just one). +*Renamed TSMSelectionList to TSMGroupItemList interally. +*Added "avg()" function support to custom prices. + +v2.1.14 +*Fixed typo in code which caused issues with the bank UI and the DufUIBank addon. +*Fixed issues with sliders and treegroups caused by 5.4 changes. + +v2.1.13 +*Grabbing latest version of AccurateTime (again). + +v2.1.12 +*Grabbing latest version of AccurateTime. + +v2.1.11 +*Added some missing soulbound mats to internal lookup table. +*AccurateTime will now be embedded instead of standalone. + +v2.1.10 +*Fixed issue with '/tsm bankui' creating duplicate windows. +*Fixed bug with auction result sorting. +*Fixed some memory leaks. + +v2.1.9 +*Added new !AccurateTime library and fixed some issues around debugprofilestart/stop usage. +*Cleaned up .toc file a bit. +*Added bankui support for DufUIBank addon. + +v2.1.8 +*The BankUI will now remember its position (independantly for bank and guild bank). +*Added /tsm bankuireset to reset the BankUI frame position + +v2.1.7 +*Added caching to various commonly used APIs. +*Bug fix with item info caching. +*Added disenchant mats to tooltip. + +v2.1.6 +*added bankUI support for cargBags Nivaya + +v2.1.5 +*Fixed bug with clicking on scrolling table columns. + +v2.1.4 +*Added latent kor'kron pieces as non-disenchantable. +*Fixed bug with tree groups. + +v2.1.3 +*Fixed some conversion/destroying ratios. +*Fixed some patch 5.4 issues. +*Fixed a bug with switching profiles. +*Limited quantity items from a vendor will no longer be tracked. +*Updated TOC for patch 5.4. + +v2.1.2 +*Fixed various issues with disenchanting ratios. +*Fixed issue with soulbound materials. +*Made room for shopping tooltip options. +*Fixed bug with importing of subgroup structure. + +v2.1.1 +*Fixed bug with multiple occurrences of a custom price sources within a custom price. +*Fixed disenchanting ratios. +*Fixed tooltips for auction result rows. +*Improved how module icons are displayed in the main TSM window. + +v2.1 +*Some advanced features will now be designated as such with red title text. +*Added theme option for the color used to designate advanced features. +*Added the ability to import and export operations. +*Fixed issue with detecting disenchantable items on non-enUS clients. +*Added support for decimal places in percentages in custom prices. +*Added option to export/import subgroup structure when exporting/importing groups. +*Group selection trees will now remember their expanded/collapsed status info on a per-module basis. +*Shift-clicking the "<<< Remove" button in the "Items" tab of a group will now remove the items from all groups rather than move the items to their parent group. +*Filtering the item selection list in the "Items" tab for groups will now hide filtered-out rows. +*Added option for changing the chat tab which TSM and its modules use for printing messages. +*Added loop detection to custom price code. +*Fixed a bug with custom prices with specific items in them. +*Fixed error from missing localization phrase. +*Fixed stack overflow with very long custom prices. +*Added the ability to create custom price sources. +*Added more error checking to the custom price validation code. + +v2.0.10 +*Fix for error when alt-clicking buying in destroy mode. + +v2.0.9 +*Fixed some divide by zero issues found on 5.4 PTR. +*Fixed issue with bank updates going out to modules after bank was closed. +*Fixed bug with alt-click buying in destroy mode for TSM_Shopping. + +v2.0.8 +*Fixed bug with certain gold amounts not getting correctly formatted. + +v2.0.7 +*All scrolling tables will now have constant-height rows. +*Fixed bug with tooltip for battlepets. + +v2.0.6 +*Fixed issue with spaces in itemStrings. + +v2.0.5 +*Added option for including soulbound items in movement APIs. +*Fixed bug with syncing code trying to sync with the current character. +*Fixed various issues with manual posting. + +v2.0.4 +*Updated LibExtraTip. +*Fixed a bug with auction result tooltips not working for battlepets. +*TSM tooltip data will now be shown for battlepets as well as items. + +v2.0.3 +*Fixed a bug with vendorsell price source. + +v2.0.2 +*Fixed a bug with being unable to add ungrouped random enchant items as the base item. +*Fixed issue with first() and vendorsell in custom prices. + +v2.0.1 +*Fixed a bug with the version showing as "Dev" instead of v2.0. + +v2.0 +*First 2.0 Version! +\\ + +v1.6 +*Updated TOC for patch 5.2. +*Added quick buyout feature to auction result frames. +*Added more theme options and preset themes. +*Adjusted milling/prospecting/disenchanting ratios as necessary. +*Added basic battle pet support. +*Improved the TSM error handler. +*Many other minor bug fixes and improvements. + +v1.5 +*Updated existing themes and added some new ones. +*Changed default theme to "Goblineer" as voted on by forum members. +*Updated TOC for patch 5.0.4 +*TSM will now block all chat messages related to creating and canceling auctions. +*Added a slash command for resetting the position of the main TSM frame - '/tsm freset'. +*Fixed the green +/- buttons in treegroups. +*Made all the progress bars across the modules consistent. +*Rewrote all the auction results table code to address multiple issues. +*Added a slider to the TSM options tab for adjusting how many rows are shown in auction results tables. +*Many other minor bug fixes and improvements. + +v1.4 +*Redesigned the look and feel of the main TSM window and the auction house tab. +*Added a bunch of new options for customizing the new look. +*Added the ability to import / export appearance settings. +*Added a list of importable preset themes for TSM. +*There is now a hidden gem somewhere in TSM. Find it and something cool will happen :). +*Removed a few things which will cause errors in patch 5.0.4. +*Added support for the general stats which are now also included in the TUJ realm edition. +*TSM will now be packaged with a new version of LibAuctionScan which has significant scan speed improvements. +*Many other minor bug fixes and improvements. + +v1.3 +*Added further warnings for users who may still have TSM_Gathering installed. +*Fixed error caused by having Auctioneer enabled by not the Appraiser module. +*Performed a major reorganization of TSM's code to make it easier to find specific functions. +*Added tooltip support for selection lists. +*Fixed a bug with SelectionLists not adding/removing items that were selected by not visible. +*Updated all the disenchanting tables to fix a handful of inaccuracies. +*Fixed an error caused by changing the Auction House scale before opening it for the first time. +*SelectionList filters will now be parsed to avoid string pattern errors. +*Many other minor bug fixes and improvements. + +v1.2 +*Added destroying data for essences / shards / crystals for the Destroying feature in the Shopping module. +*Fixed various bugs with the TSM auction house tab / detached frame not displaying correctly. +*Added option for opening all your bags when the AH is shown. +*Added option for detaching the TSM auction house tab by default. +*The TSM auction house tab will now remember its detached possition throughout a single session. +*Removed localized strings from error handler and added client locale info. +*Many other minor bug fixes and improvements. + +v1.1 +*Improved TSM's error catcher. +*TSM's AH tab is now it's own window which can be detached from the main AH frame. +*When TSM's tab is attached, it'll hide behind the main AH frame when you switch tabs, allowing your TSM scan to continue "in the background". +*Added options for making the main AH frame movable and for changing the scale of the AH frame. +*Added an option for removing the bids from auction results to make the buyouts easier to read (bids are now hidden by default). +*Updated the LDBIcon to use a different texture than the minimap icon. +*Many other minor bug fixes and improvements. + +v1.0 +*First Release Version! +\\ + +**Beta Versions:** + +v0.2.4 +*Updated some APIs for a new feature that's in the works. +*Updated the TOC for patch 4.2. + +v0.2.3 +*Added support for spell tooltips. +*Removed the extra "v" that would show up infront of version numbers. +*Took out the Destroying button code (was moved to the Destroying module's code). +*Added some new info messages that'll appear in popups when the user logs in. +*Many other minor changes. + +v0.2.2 +*Added AceHook and lib-st libraries. +*Fixed a typo in one of the tips. +*Many other minor changes. + +v0.2.1 +*Finally fixed the bug where selectionlists (such as the one used for adding or removing items to Auctioning groups / categories) were only displaying the first 4 items under some circumstances. +*Updated the TOC for patch 4.1 + +v0.2 +*Added TSM tips to the status bar of the main TSM window. The tip will change everytime the TSM window is open. +*Added a ton of support code for the AuctionDB and Destroying modules. +*Cleaned up the unused slash command code as well as a bunch of other parts of the code. +*Updated the credits. +*Many other changes that aren't evident to the user but better the addon's code as a whole. + +v0.1.7 +*Removed the slash command line from the minimap button tooltip as it was causing some errors. +*Tooltip functions can now access stack size info. +*Cleaned up the TSMMacroButton code and fixed some bugs. + +v0.1.6 +*SelectionLists should now clear the selected row on release. +*Added VersionKey APIs. +*Removed a few extra libraries from the TOC file / addon folder. +*Turned on no-lib creation. +*Updated the credits. +*Cleaned up some of the code. + +v0.1.5 +*Fixed a bug with the main TSM window on non-english clients. +*Minor improvements to the GUI functions. +*Added an option to the "Status" page for hiding the minimap icon. + +v0.1.4 +*Fixed some minor bugs with two of the TSMAPI functions. +*This update is required for using the latest version of Crafting + +v0.1.3 +*Adjusted the sidebar so it should resize more in order to not be blocked by right action bars. +*Fixed a bug with the selectionlists in auctioning sometimes only showing 4 items. +*Updated the included TSM Guidebook pdf. + +v0.1.2 +*The sidebar frame should no longer go off the screen. +*Added another popup to help new users. + +v0.1.1 +*Should be way more obvious when no modules are installed. + +v0.1 +*First Beta Release! \ No newline at end of file diff --git a/TradeSkillMaster/Core/ErrorHandler.lua b/TradeSkillMaster/Core/ErrorHandler.lua new file mode 100644 index 0000000..6739141 --- /dev/null +++ b/TradeSkillMaster/Core/ErrorHandler.lua @@ -0,0 +1,415 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- TSM's error handler. + +local TSM = select(2, ...) +local AceGUI = LibStub("AceGUI-3.0") +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") + + +local origErrorHandler, ignoreErrors, isErrorFrameVisible, isAssert +TSMERRORLOG = {} +local tsmStack = {} +local stackNameLookup = {} + +local addonSuites = { + {name="ArkInventory"}, + {name="AtlasLoot"}, + {name="Altoholic"}, + {name="Auc-Advanced", commonTerm="Auc-"}, + {name="Bagnon"}, + {name="BigWigs"}, + {name="Broker"}, + {name="ButtonFacade"}, + {name="Carbonite"}, + {name="DataStore"}, + {name="DBM"}, + {name="Dominos"}, + {name="DXE"}, + {name="EveryQuest"}, + {name="Forte"}, + {name="FuBar"}, + {name="GatherMate2"}, + {name="Grid"}, + {name="LightHeaded"}, + {name="LittleWigs"}, + {name="Masque"}, + {name="MogIt"}, + {name="Odyssey"}, + {name="Overachiever"}, + {name="PitBull4"}, + {name="Prat-3.0"}, + {name="RaidAchievement"}, + {name="Skada"}, + {name="SpellFlash"}, + {name="TidyPlates"}, + {name="TipTac"}, + {name="Titan"}, + {name="UnderHood"}, + {name="WowPro"}, + {name="ZOMGBuffs"}, +} + +local function StrStartCmp(str, startStr) + local startLen = strlen(startStr) + + if startLen <= strlen(str) then + return strsub(str, 1, startLen) == startStr + end +end + + +local function GetModule(msg) + if strfind(msg, "TradeSkillMaster_") then + return strmatch(msg, "TradeSkillMaster_[A-Za-z]+") + elseif strfind(msg, "TradeSkillMaster\\") then + return "TradeSkillMaster" + end + return "?" +end + +local function ExtractErrorMessage(...) + local msg = "" + + for _, var in ipairs({...}) do + local varStr + local varType = type(var) + + if varType == "boolean" then + varStr = var and "true" or "false" + elseif varType == "table" then + varStr = "" + elseif varType == "function" then + varStr = "" + elseif var == nil then + varStr = "" + else + varStr = var + end + + msg = msg.." "..varStr + end + + return msg +end + +local function GetDebugStack() + local stackInfo = {} + local stackString = "" + local stack = debugstack(2) or debugstack(1) + + if type(stack) == "string" then + local lines = {("\n"):split(stack)} + for _, line in ipairs(lines) do + local strStart = strfind(line, "in function") + if strStart and not strfind(line, "ErrorHandler.lua") then + line = gsub(line, "`", "<", 1) + line = gsub(line, "'", ">", 1) + local inFunction = strmatch(line, "<[^>]*>", strStart) + if inFunction then + inFunction = gsub(gsub(inFunction, ".*\\", ""), "<", "") + if inFunction ~= "" then + local str = strsub(line, 1, strStart-2) + str = strsub(str, strfind(str, "TradeSkillMaster") or 1) + if strfind(inFunction, "`") then + inFunction = strsub(inFunction, 2, -2)..">" + end + str = gsub(str, "TradeSkillMaster", "TSM") + tinsert(stackInfo, str.." <"..inFunction) + end + end + end + end + end + + return table.concat(stackInfo, "\n") +end + +local function GetTSMStack() + local stackInfo = {} + local index = #tsmStack + for i=1, 10 do -- only show up to 10 lines + if not tsmStack[index] then break end + tinsert(stackInfo, tsmStack[index]) + index = index - 1 + end + return table.concat(stackInfo, "\n") +end + +local function GetEventLog() + local eventInfo = {} + local eventLog = TSM:GetEventLog() + for i, entry in ipairs(eventLog) do + tinsert(eventInfo, format("%d | %s | %s", i, entry.event, tostring(entry.arg))) + end + return table.concat(eventInfo, "\n") +end + +local function GetAddonList() + local hasAddonSuite = {} + local addons = {} + local addonString = "" + + for i = 1, GetNumAddOns() do + local name, _, _, enabled = GetAddOnInfo(i) + local version = GetAddOnMetadata(name, "X-Curse-Packaged-Version") or GetAddOnMetadata(name, "Version") or "" + if enabled then + local isSuite + + for _, addonSuite in ipairs(addonSuites) do + local commonTerm = addonSuite.commonTerm or addonSuite.name + + if StrStartCmp(name, commonTerm) then + isSuite = commonTerm + break + end + end + + if isSuite then + if not hasAddonSuite[isSuite] then + tinsert(addons, {name=name, version=version}) + hasAddonSuite[isSuite] = true + end + elseif StrStartCmp(name, "TradeSkillMaster") then + tinsert(addons, {name=gsub(name, "TradeSkillMaster", "TSM"), version=version}) + else + tinsert(addons, {name=name, version=version}) + end + end + end + + for i, addonInfo in ipairs(addons) do + local info = addonInfo.name .. " (" .. addonInfo.version .. ")" + if i == #addons then + addonString = addonString .. " " .. info + else + addonString = addonString .. " " .. info .. "\n" + end + end + + return addonString +end + +local function ShowError(msg, isVerify) + if not AceGUI then + TSMAPI:CreateTimeDelay("errHandlerShowDelay", 0.1, function() + if AceGUI and UIParent then + CancelFrame("errHandlerShowDelay") + ShowError(msg, isVerify) + end + end, 0.1) + return + end + + local f = AceGUI:Create("TSMWindow") + f:SetCallback("OnClose", function(self) isErrorFrameVisible = false AceGUI:Release(self) end) + f:SetTitle(L["TradeSkillMaster Error Window"]) + f:SetLayout("Flow") + f:SetWidth(500) + f:SetHeight(400) + + local l = AceGUI:Create("Label") + l:SetFullWidth(true) + l:SetFontObject(GameFontNormal) + if isVerify then + l:SetText(L["Looks like TradeSkillMaster has detected an error with your configuration. Please address this in order to ensure TSM remains functional."].."\n"..L["|cffffff00DO NOT report this as an error to the developers.|r If you require assistance with this, make a post on the TSM forums instead."].."|r") + else + l:SetText(L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by copying the entire error below and following the instructions for reporting bugs listed here (unless told elsewhere by the author):"].." |cffffff00http://tradeskillmaster.com/wiki|r") + end + f:AddChild(l) + + local heading = AceGUI:Create("Heading") + heading:SetText("") + heading:SetFullWidth(true) + f:AddChild(heading) + + local eb = AceGUI:Create("MultiLineEditBox") + eb:SetLabel(L["Error Info:"]) + eb:SetMaxLetters(0) + eb:SetFullWidth(true) + eb:SetText(msg) + eb:DisableButton(true) + eb:SetFullHeight(true) + f:AddChild(eb) + + f.frame:SetFrameStrata("FULLSCREEN_DIALOG") + f.frame:SetFrameLevel(100) + isErrorFrameVisible = true +end + +function TSM:IsValidError(...) + if ignoreErrors then return end + ignoreErrors = true + local msg = ExtractErrorMessage(...) + ignoreErrors = false + if not strfind(msg, "TradeSkillMaster") then return end + if strfind(msg, "auc%-stat%-wowuction") then return end + return msg +end + +function TSMAPI:Verify(cond, err) + if cond then return end + + ignoreErrors = true + + tinsert(TSMERRORLOG, err) + if not isErrorFrameVisible then + TSM:Print(L["Looks like TradeSkillMaster has detected an error with your configuration. Please address this in order to ensure TSM remains functional."]) + ShowError(err, true) + elseif isErrorFrameVisible == true then + TSM:Print(L["Additional error suppressed"]) + isErrorFrameVisible = 1 + end + + ignoreErrors = false +end + +local function TSMErrorHandler(msg) + -- ignore errors while we are handling this error + ignoreErrors = true + TSMERRORTEMP = msg + + local color = TSMAPI.Design and TSMAPI.Design:GetInlineColor("link2") or "" + local color2 = TSMAPI.Design and TSMAPI.Design:GetInlineColor("advanced") or "" + local errorMessage = "" + errorMessage = errorMessage..color.."Addon:|r "..color2..GetModule(msg).."|r\n" + errorMessage = errorMessage..color.."Message:|r "..msg.."\n" + errorMessage = errorMessage..color.."Date:|r "..date("%m/%d/%y %H:%M:%S").."\n" + errorMessage = errorMessage..color.."Client:|r "..GetBuildInfo().."\n" + errorMessage = errorMessage..color.."Locale:|r "..GetLocale().."\n" + errorMessage = errorMessage..color.."Stack:|r\n"..GetDebugStack().."\n" + errorMessage = errorMessage..color.."TSM Stack:|r\n"..GetTSMStack().."\n" + errorMessage = errorMessage..color.."Local Variables:|r\n"..(debuglocals(isAssert and 5 or 4) or "").."\n" + errorMessage = errorMessage..color.."TSM Event Log:|r\n"..GetEventLog().."\n" + errorMessage = errorMessage..color.."Addons:|r\n"..GetAddonList().."\n" + tinsert(TSMERRORLOG, errorMessage) + if not isErrorFrameVisible then + TSM:Print(L["Looks like TradeSkillMaster has encountered an error. Please help the author fix this error by following the instructions shown."]) + ShowError(errorMessage) + elseif isErrorFrameVisible == true then + TSM:Print(L["Additional error suppressed"]) + isErrorFrameVisible = 1 + end + + -- need to clear the stack + tsmStack = {} + ignoreErrors = false +end + +function TSMAPI:Assert(cond, err) + if cond then return end + isAssert = true + TSMErrorHandler(err) + isAssert = false +end + +do + origErrorHandler = geterrorhandler() + local errHandlerFrame = CreateFrame("Frame", nil, nil, "TSMErrorHandlerTemplate") + errHandlerFrame.errorHandler = TSMErrorHandler + errHandlerFrame.origErrorHandler = origErrorHandler + seterrorhandler(errHandlerFrame.handler) +end + +--[===[@debug@ +--- Disables TSM's error handler until the game is reloaded. +-- This is mainly used for debugging errors with TSM's error handler and should not be used in actual code. +function TSMAPI:DisableErrorHandler() + seterrorhandler(origErrorHandler) +end +--@end-debug@]===] + + + +-- other debug functions +TSMAPI.Debug = {} + +local dumpDefaults = { + DEVTOOLS_MAX_ENTRY_CUTOFF = 30, -- Maximum table entries shown + DEVTOOLS_LONG_STRING_CUTOFF = 200, -- Maximum string size shown + DEVTOOLS_DEPTH_CUTOFF = 10, -- Maximum table depth +} + +function TSMAPI.Debug:DumpTable(tbl, maxDepth, maxItems, maxStr) + DEVTOOLS_DEPTH_CUTOFF = maxDepth or dumpDefaults.DEVTOOLS_DEPTH_CUTOFF + DEVTOOLS_MAX_ENTRY_CUTOFF = maxItems or dumpDefaults.DEVTOOLS_MAX_ENTRY_CUTOFF + DEVTOOLS_DEPTH_CUTOFF = maxStr or dumpDefaults.DEVTOOLS_DEPTH_CUTOFF + + if not IsAddOnLoaded("Blizzard_DebugTools") then + LoadAddOn("Blizzard_DebugTools") + end + + DevTools_Dump(tbl) + + for i, v in pairs(dumpDefaults) do + _G[i] = v + end +end + + +-- stack tracing functions +local function FormatTSMStack(obj, name, ...) + local args + for i=2, select('#', ...) do + local arg = select(i, ...) + local str + if stackNameLookup[arg] then + str = "<"..stackNameLookup[arg]..">" + elseif type(arg) == "table" then + if getmetatable(arg) and getmetatable(arg).__tostring then + str = "<"..tostring(arg)..">" + else + local _, addr = (":"):split(tostring(arg)) + str = "table:"..tonumber(addr, 16) + end + elseif type(arg) == "string" then + str = '"'..tostring(arg)..'"' + elseif type(arg) == "function" then + local _, addr = (":"):split(tostring(arg)) + str = "function:"..tonumber(addr, 16) + else + str = tostring(arg) + end + + if args then + args = args..", "..str + else + args = str + end + end + + local funcCall = "?" + if obj == select(1, ...) and args then + funcCall = (stackNameLookup[obj] or tostring(obj))..":"..name.."("..args..")" + end + return funcCall +end + +-- this must be a separate function so we can return the ... after popping off the stack +local function TrackPopStack(...) + tremove(tsmStack, #tsmStack) + return ... +end + +local function RegisterForTracing(obj, name) + stackNameLookup[obj] = name + for name, v in pairs(obj) do + if type(v) == "function" then + TSM:RawHook(obj, name, function(...) + tinsert(tsmStack, FormatTSMStack(obj, name, ...)) + return TrackPopStack(v(...)) + end) + end + end +end + +function TSMAPI:RegisterForTracing(obj, name) + -- wait one frame to ensure all functions are declared + TSMAPI:CreateTimeDelay(0, function() RegisterForTracing(obj, name) end) +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/EventLogger.lua b/TradeSkillMaster/Core/EventLogger.lua new file mode 100644 index 0000000..d7f7f39 --- /dev/null +++ b/TradeSkillMaster/Core/EventLogger.lua @@ -0,0 +1,49 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- TSM's error handler. + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") + +local eventObj = TSMAPI:GetEventObject() +local currentIndex = 1 +local NUM_LOG_ENTRIES = 20 +local debugLog = {} + +local alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_=" +local base = #alpha +local alphaTable = {} +local alphaTableLookup = {} +for i = 1, base do + local char = strsub(alpha, i, i) + tinsert(alphaTable, char) + alphaTableLookup[char] = i +end + +local function EventCallback(event, arg) + debugLog[currentIndex] = {event=event, arg=arg} + currentIndex = currentIndex + 1 + if currentIndex > NUM_LOG_ENTRIES then + currentIndex = 1 + end +end +eventObj:SetCallbackAnyEvent(EventCallback) + + +function TSM:GetEventLog() + local temp = {} + for i=1, #debugLog do + local index = currentIndex - i + if index <= 0 then + index = index + NUM_LOG_ENTRIES + end + tinsert(temp, debugLog[index]) + end + return temp +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Events.lua b/TradeSkillMaster/Core/Events.lua new file mode 100644 index 0000000..c20dea3 --- /dev/null +++ b/TradeSkillMaster/Core/Events.lua @@ -0,0 +1,62 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- TSM's event handler. + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Events_private") +private.objects = {} + + +private.eventObjectCallbacks = { + SetCallbackAnyEvent = function(self, callback) + self._anyEventCallback = callback + end, + SetCallback = function(self, event, callback, matchAll) + self._callbacks[event] = {func = callback, matchAll = (matchAll and true or false)} -- need to convert matchAll to a boolean + end, + ClearAllCallbacks = function(self) + wipe(self._callbacks) + end +} + +function TSMAPI:GetEventObject() + local obj = {} + obj._callbacks = {} + obj._anyEventCallback = nil + for name, func in pairs(private.eventObjectCallbacks) do + obj[name] = func + end + tinsert(private.objects, obj) + return obj +end + +function private:OnEventFired(event, arg, fullEvent) + local isPartial = event ~= fullEvent and true or false + for _, obj in ipairs(private.objects) do + if not isPartial and obj._anyEventCallback then + obj._anyEventCallback(fullEvent, arg) + end + local callback = obj._callbacks[event] + if callback then + if isPartial == callback.matchAll then + callback.func(fullEvent, arg) + end + end + end +end + +function TSMAPI:FireEvent(event, arg) + local parts = {(":"):split(event)} + for i=1, #parts do + local partialEvent = table.concat(parts, ":", 1, i) + private:OnEventFired(partialEvent, arg, event) + end +end diff --git a/TradeSkillMaster/Core/Groups.lua b/TradeSkillMaster/Core/Groups.lua new file mode 100644 index 0000000..c77d0d5 --- /dev/null +++ b/TradeSkillMaster/Core/Groups.lua @@ -0,0 +1,1687 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries +local lib = TSMAPI + +TSM.GROUP_SEP = "`" + +function TSMAPI:FormatGroupPath(path, useColor) + if not path then return end + if useColor then + return TSMAPI.Design:GetInlineColor("link")..gsub(path, TSM.GROUP_SEP, "->").."|r" + else + return gsub(path, TSM.GROUP_SEP, "->") + end +end + +local GROUP_LEVEL_COLORS = { + "FCF141", + "BDAEC6", + "06A2CB", + "FFB85C", + "51B599", +} +function TSMAPI:ColorGroupName(groupName, level) + local color = GROUP_LEVEL_COLORS[(level-1) % #GROUP_LEVEL_COLORS + 1] + return "|cFF"..color..groupName.."|r" +end + +function TSMAPI:JoinGroupPath(...) + return strjoin(TSM.GROUP_SEP, ...) +end + + +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Groups_private") +private.operationInfo = {} + +function TSM:RegisterOperationInfo(module, info) + info = CopyTable(info) + info.module = module + tinsert(private.operationInfo, info) +end + +-- Splits the given group path into the parent path and group name +-- Parent will be nil if there is no parent +local function SplitGroupPath(path) + local parts = {TSM.GROUP_SEP:split(path)} + local parent = table.concat(parts, TSM.GROUP_SEP, 1, #parts-1) + parent = parent ~= "" and parent or nil + local groupName = parts[#parts] + return parent, groupName +end + +local function GetSubGroups(groupPath) + local subGroups = {} + local hasSubGroup + for group in pairs(TSM.db.profile.groups) do + local _, _, subGroupName = strfind(group, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP.."([^`]+)$") + if subGroupName then + subGroups[group] = subGroupName + hasSubGroup = true + end + end + if hasSubGroup then + return subGroups + end +end + +-- Creates a new group with the specified path +local SetOperationOverride +local function CreateGroup(groupPath) + if TSM.db.profile.groups[groupPath] then return end + TSM.db.profile.groups[groupPath] = {} + for _, info in ipairs(private.operationInfo) do + TSM.db.profile.groups[groupPath][info.module] = TSM.db.profile.groups[groupPath][info.module] or {} + if SplitGroupPath(groupPath) then + for _, info in ipairs(private.operationInfo) do + SetOperationOverride(groupPath, info.module, nil) + end + end + end +end + +-- Deletes a group with the specified path and everything (items/subGroups) below it +local function DeleteGroup(groupPath) + if not TSM.db.profile.groups[groupPath] then return end + + -- delete this group and all subgroups + for path in pairs(TSM.db.profile.groups) do + if path == groupPath or strfind(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + TSM.db.profile.groups[path] = nil + end + end + + local parent = SplitGroupPath(groupPath) + if parent and TSM.db.profile.keepInParent then + -- move all items in this group its subgroups to the parent + local changes = {} + for itemString, path in pairs(TSM.db.profile.items) do + if path == groupPath or strfind(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + changes[itemString] = parent + end + end + for itemString, newPath in pairs(changes) do + TSM.db.profile.items[itemString] = newPath + end + else + -- delete all items in this group or subgroup + for itemString, path in pairs(TSM.db.profile.items) do + if path == groupPath or strfind(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + TSM.db.profile.items[itemString] = nil + end + end + end +end + +-- Moves (renames) a group at the given path to the newPath +local function MoveGroup(groupPath, newPath) + if not TSM.db.profile.groups[groupPath] then return end + if TSM.db.profile.groups[newPath] then return end + + -- change the path of all subgroups + local changes = {} + for path, groupData in pairs(TSM.db.profile.groups) do + if path == groupPath or strfind(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + changes[path] = gsub(path, "^"..TSMAPI:StrEscape(groupPath), TSMAPI:StrEscape(newPath)) + end + end + for oldPath, newPath in pairs(changes) do + TSM.db.profile.groups[newPath] = TSM.db.profile.groups[oldPath] + TSM.db.profile.groups[oldPath] = nil + end + + -- change the path for all items in this group (and subgroups) + changes = {} + for itemString, path in pairs(TSM.db.profile.items) do + if path == groupPath or strfind(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + changes[itemString] = gsub(path, "^"..TSMAPI:StrEscape(groupPath), newPath) + end + end + for itemString, newPath in pairs(changes) do + TSM.db.profile.items[itemString] = newPath + end +end + +-- Adds an item to the group at the specified path. +local function AddItem(itemString, path) + if not (strfind(path, TSM.GROUP_SEP) or not TSM.db.profile.items[itemString]) then return end + if not TSM.db.profile.groups[path] then return end + + TSM.db.profile.items[itemString] = path +end + +-- Deletes an item from the group at the specified path. +local function DeleteItem(itemString) + if not TSM.db.profile.items[itemString] then return end + TSM.db.profile.items[itemString] = nil +end + +-- Moves an item from an existing group to the group at the specified path. +local function MoveItem(itemString, path) + if not TSM.db.profile.items[itemString] then return end + if not TSM.db.profile.groups[path] then return end + TSM.db.profile.items[itemString] = path +end + +local function SetOperationHelper(path, module, origPath) + if TSM.db.profile.groups[path][module] and TSM.db.profile.groups[path][module].override then return end + TSM.db.profile.groups[path][module] = CopyTable(TSM.db.profile.groups[origPath][module]) + TSM.db.profile.groups[path][module].override = nil + local subGroups = GetSubGroups(path) + if subGroups then + for subGroupPath in pairs(subGroups) do + SetOperationHelper(subGroupPath, module, origPath) + end + end +end +local function SetOperation(path, module, operation, index) + if not TSM.db.profile.groups[path] then return end + if not TSM.db.profile.groups[path][module] then return end + + TSM.db.profile.groups[path][module][index] = operation + local subGroups = GetSubGroups(path) + if subGroups then + for subGroupPath in pairs(subGroups) do + SetOperationHelper(subGroupPath, module, path) + end + end +end + +local function AddOperation(path, module) + if not TSM.db.profile.groups[path] then return end + + tinsert(TSM.db.profile.groups[path][module], "") + local subGroups = GetSubGroups(path) + if subGroups then + for subGroupPath in pairs(subGroups) do + SetOperationHelper(subGroupPath, module, path) + end + end +end + +local function DeleteOperation(path, module, index) + if not TSM.db.profile.groups[path] then return end + local numOperations = #TSM.db.profile.groups[path][module] + for i=index+1, numOperations do + SetOperation(path, module, TSM.db.profile.groups[path][module][i], i-1) + end + SetOperation(path, module, nil, numOperations) +end + +function SetOperationOverride(path, module, override) + if not TSM.db.profile.groups[path] then return end + + -- clear all operations for this path/module + TSM.db.profile.groups[path][module] = {override=(override or nil)} + -- set this group's (and all applicable subgroups') operation to the parent's + local parentPath = SplitGroupPath(path) + if not parentPath then return end + TSM.db.profile.groups[parentPath][module] = TSM.db.profile.groups[parentPath][module] or {override=(override or nil)} + for index, operation in ipairs(TSM.db.profile.groups[parentPath][module]) do + SetOperation(path, module, operation, index) + end +end + + +function TSM:GetGroupItems(path) + local items = {} + for itemString, groupPath in pairs(TSM.db.profile.items) do + if groupPath == path then + items[itemString] = true + end + end + return items +end + +function TSM:GetGroupPathList(module) + local list, disabled = {}, {} + for groupPath in pairs(TSM.db.profile.groups) do + if module then + local operations = TSM:GetGroupOperations(groupPath, module) + if not operations then + disabled[groupPath] = true + end + end + tinsert(list, groupPath) + end + + for groupPath in pairs(TSM.db.profile.groups) do + if not disabled[groupPath] then + local pathParts = {TSM.GROUP_SEP:split(groupPath)} + for i=1, #pathParts-1 do + local path = table.concat(pathParts, TSM.GROUP_SEP, 1, i) + disabled[path] = nil + end + end + end + + sort(list, function(a,b) return strlower(gsub(a, TSM.GROUP_SEP, "\001")) < strlower(gsub(b, TSM.GROUP_SEP, "\001")) end) + return list, disabled +end + +function TSM:GetGroupOperations(path, module) + if not TSM.db.profile.groups[path] then return end + + if module and TSM.db.profile.groups[path][module] then + local operations = CopyTable(TSM.db.profile.groups[path][module]) + for i=#operations, 1, -1 do + if operations[i] == "" or TSM:IsOperationIgnored(module, operations[i]) then + tremove(operations, i) + end + end + if #operations > 0 then + return operations + end + end +end + +-- Takes a list of itemString/groupPath k,v pairs and adds them to new groups. +function TSMAPI:CreatePresetGroups(itemList, moduleName, operationInfo) + for itemString, groupPath in pairs(itemList) do + if not TSM.db.profile.items[itemString] and not TSMAPI:IsSoulbound(itemString) then + local pathParts = {TSM.GROUP_SEP:split(groupPath)} + for i=1, #pathParts do + local path = table.concat(pathParts, TSM.GROUP_SEP, 1, i) + if not TSM.db.profile.groups[path] then + CreateGroup(path) + end + end + AddItem(itemString, groupPath) + if moduleName and operationInfo and operationInfo[groupPath] then + if strfind(groupPath, TSM.GROUP_SEP) then + SetOperationOverride(groupPath, moduleName, true) + end + SetOperation(groupPath, moduleName, operationInfo[groupPath], 1) + end + end + end + + private:UpdateTree() +end + +function TSMAPI:GetItemOperation(itemString, module) + local groupPath = TSM.db.profile.items[itemString] + if not groupPath then return end + local operations = TSM:GetGroupOperations(groupPath, module) + if not operations then return end + local result = CopyTable(operations) + result.override = nil + return result +end + +function TSMAPI:GetGroupPath(itemString) + return TSM.db.profile.items[itemString] +end + +-- gets all items which have an operation for the given module assigned to their groups +function TSMAPI:GetModuleItems(module) + if not module then return end + local result = {} + for itemString in pairs(TSM.db.profile.items) do + result[itemString] = TSMAPI:GetItemOperation(itemString, module) + end + return result +end + + +local function ShowExportFrame(text) + local f = AceGUI:Create("TSMWindow") + f:SetCallback("OnClose", function(self) AceGUI:Release(self) end) + f:SetTitle("TradeSkillMaster - "..L["Export Group Items"]) + f:SetLayout("Fill") + f:SetHeight(300) + + local eb = AceGUI:Create("TSMMultiLineEditBox") + eb:SetLabel(L["Group Item Data"]) + eb:SetMaxLetters(0) + eb:SetText(text) + f:AddChild(eb) + + f.frame:SetFrameStrata("FULLSCREEN_DIALOG") + f.frame:SetFrameLevel(100) +end + +local function ModuleOptionsRefresh(TSMObj, ...) + TSMObj.Options:UpdateTree() + TSMObj.Options.treeGroup:SelectByPath(#TSMObj.Options.treeGroup.tree, ...) + if select('#', ...) > 0 then + TSMObj.Options.treeGroup.children[1]:SelectTab(#TSMObj.Options.treeGroup.children[1].tablist) + end +end + +function TSMAPI:DrawOperationManagement(TSMObj, container, operationName) + local moduleName = gsub(TSMObj.name, "TSM_", "") + local operation = TSMObj.operations[operationName] + + local playerList = {} + local factionrealmKey = TSM.db.keys.factionrealm + for playerName in pairs(TSM.db.factionrealm.characters) do + playerList[playerName.." - "..factionrealmKey] = playerName + end + + local factionrealmList = {} + for factionrealm in pairs(TSM.db.sv.factionrealm) do + factionrealmList[factionrealm] = factionrealm + end + + local groupList = {} + for path, modules in pairs(TSM.db.profile.groups) do + if modules[moduleName] then + for i=1, #modules[moduleName] do + if modules[moduleName][i] == operationName then + tinsert(groupList, path) + end + end + end + end + sort(groupList, function(a,b) return strlower(gsub(a, TSM.GROUP_SEP, "\001")) < strlower(gsub(b, TSM.GROUP_SEP, "\001")) end) + + local groupWidgets = { + { + type = "Label", + relativeWidth = 1, + text = L["Below is a list of groups which this operation is currently applied to. Clicking on the 'Remove' button next to the group name will remove the operation from that group."], + }, + { + type = "HeadingLine", + }, + } + for _, groupPath in ipairs(groupList) do + tinsert(groupWidgets, { + type = "Button", + relativeWidth = 0.2, + text = L["Remove"], + callback = function() + for i=#TSM.db.profile.groups[groupPath][moduleName], 1, -1 do + if TSM.db.profile.groups[groupPath][moduleName][i] == operationName then + DeleteOperation(groupPath, moduleName, i) + end + end + TSM:CheckOperationRelationships(moduleName) + ModuleOptionsRefresh(TSMObj, operationName) + end, + tooltip = L["Click this button to completely remove this operation from the specified group."], + }) + tinsert(groupWidgets, { + type = "Label", + relativeWidth = 0.05, + text = "", + }) + tinsert(groupWidgets, { + type = "Label", + relativeWidth = 0.75, + text = TSMAPI:FormatGroupPath(groupPath, true), + }) + end + + local page = { + { + type = "ScrollFrame", + layout = "Flow", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["Operation Management"], + children = { + { + type = "Dropdown", + label = L["Ignore Operation on Faction-Realms:"], + list = factionrealmList, + relativeWidth = 0.5, + settingInfo = {operation, "ignoreFactionrealm"}, + multiselect = true, + tooltip = L["This operation will be ignored when you're on any character which is checked in this dropdown."], + }, + { + type = "Dropdown", + label = L["Ignore Operation on Characters:"], + list = playerList, + relativeWidth = 0.5, + settingInfo = {operation, "ignorePlayer"}, + multiselect = true, + tooltip = L["This operation will be ignored when you're on any character which is checked in this dropdown."], + }, + { + type = "HeadingLine" + }, + { + type = "EditBox", + label = L["Rename Operation"], + value = operationName, + relativeWidth = 0.5, + callback = function(self,_,name) + name = (name or ""):trim() + if name == "" then return end + if TSMObj.operations[name] then + self:SetText("") + return TSMObj:Printf(L["Error renaming operation. Operation with name '%s' already exists."], name) + end + TSMObj.operations[name] = TSMObj.operations[operationName] + TSMObj.operations[operationName] = nil + for _, groupPath in ipairs(groupList) do + for i=1, #TSM.db.profile.groups[groupPath][moduleName] do + if TSM.db.profile.groups[groupPath][moduleName][i] == operationName then + TSM.db.profile.groups[groupPath][moduleName][i] = name + end + end + end + TSM:CheckOperationRelationships(moduleName) + ModuleOptionsRefresh(TSMObj, name) + end, + tooltip = L["Give this operation a new name. A descriptive name will help you find this operation later."], + }, + { + type = "EditBox", + label = L["Duplicate Operation"], + relativeWidth = 0.5, + callback = function(self,_,name) + name = (name or ""):trim() + if name == "" then return end + if TSMObj.operations[name] then + self:SetText("") + return TSMObj:Printf(L["Error duplicating operation. Operation with name '%s' already exists."], name) + end + TSMObj.operations[name] = CopyTable(TSMObj.operations[operationName]) + TSM:CheckOperationRelationships(moduleName) + ModuleOptionsRefresh(TSMObj, name) + end, + tooltip = L["Type in the name of a new operation you wish to create with the same settings as this operation."], + }, + { + type = "HeadingLine" + }, + { + type = "GroupBox", + label = L["Apply Operation to Group"], + relativeWidth = .5, + callback = function(self, _, path) + TSM.db.profile.groups[path][moduleName] = TSM.db.profile.groups[path][moduleName] or {} + local operations = TSM.db.profile.groups[path][moduleName] + local num = #operations + if num == 0 then + SetOperationOverride(path, moduleName, true) + AddOperation(path, moduleName) + SetOperation(path, moduleName, operationName, 1) + TSM:Printf(L["Applied %s to %s."], TSMAPI.Design:GetInlineColor("link")..operationName.."|r", TSMAPI:FormatGroupPath(path, true)) + elseif operations[num] == "" then + SetOperationOverride(path, moduleName, true) + SetOperation(path, moduleName, operationName, num) + TSM:Printf(L["Applied %s to %s."], TSMAPI.Design:GetInlineColor("link")..operationName.."|r", TSMAPI:FormatGroupPath(path, true)) + else + local canAdd + for _, info in ipairs(private.operationInfo) do + if moduleName == info.module then + canAdd = num < info.maxOperations + break + end + end + if canAdd then + StaticPopupDialogs["TSM_APPLY_OPERATION_ADD"] = StaticPopupDialogs["TSM_APPLY_OPERATION_ADD"] or { + text = L["This group already has operations. Would you like to add another one or replace the last one?"], + button1 = ADD, + button2 = L["Replace"], + button3 = CANCEL, + timeout = 0, + OnAccept = function() + -- the "add" button + local path, moduleName, operationName, num = unpack(StaticPopupDialogs["TSM_APPLY_OPERATION_ADD"].tsmInfo) + SetOperationOverride(path, moduleName, true) + AddOperation(path, moduleName) + SetOperation(path, moduleName, operationName, num+1) + TSM:Printf(L["Applied %s to %s."], TSMAPI.Design:GetInlineColor("link")..operationName.."|r", TSMAPI:FormatGroupPath(path, true)) + end, + OnCancel = function() + -- the "replace" button + local path, moduleName, operationName, num = unpack(StaticPopupDialogs["TSM_APPLY_OPERATION_ADD"].tsmInfo) + SetOperationOverride(path, moduleName, true) + SetOperation(path, moduleName, operationName, num) + TSM:Printf(L["Applied %s to %s."], TSMAPI.Design:GetInlineColor("link")..operationName.."|r", TSMAPI:FormatGroupPath(path, true)) + end, + preferredIndex = 3, + } + StaticPopupDialogs["TSM_APPLY_OPERATION_ADD"].tsmInfo = {path, moduleName, operationName, num} + TSMAPI:ShowStaticPopupDialog("TSM_APPLY_OPERATION_ADD") + else + StaticPopupDialogs["TSM_APPLY_OPERATION"] = StaticPopupDialogs["TSM_APPLY_OPERATION"] or { + text = L["This group already has the max number of operation. Would you like to replace the last one?"], + button1 = L["Replace"], + button2 = CANCEL, + timeout = 0, + OnAccept = function() + -- the "replace" button + local path, moduleName, operationName, num = unpack(StaticPopupDialogs["TSM_APPLY_OPERATION"].tsmInfo) + SetOperation(path, moduleName, operationName, num) + TSM:Printf(L["Applied %s to %s."], TSMAPI.Design:GetInlineColor("link")..operationName.."|r", TSMAPI:FormatGroupPath(path, true)) + end, + preferredIndex = 3, + } + StaticPopupDialogs["TSM_APPLY_OPERATION"].tsmInfo = {path, moduleName, operationName, num} + TSMAPI:ShowStaticPopupDialog("TSM_APPLY_OPERATION") + end + end + self:SetText() + ModuleOptionsRefresh(TSMObj, operationName) + end, + }, + { + type = "Button", + text = L["Delete Operation"], + relativeWidth = 0.5, + callback = function() + TSMObj.operations[operationName] = nil + for _, groupPath in ipairs(groupList) do + for i=#TSM.db.profile.groups[groupPath][moduleName], 1, -1 do + if TSM.db.profile.groups[groupPath][moduleName][i] == operationName then + DeleteOperation(groupPath, moduleName, i) + end + end + end + TSM:CheckOperationRelationships(moduleName) + ModuleOptionsRefresh(TSMObj) + end, + }, + { + type = "HeadingLine" + }, + { + type = "EditBox", + label = L["Import Operation Settings"], + relativeWidth = 1, + callback = function(self, _, value) + value = value:trim() + if value == "" then return end + local valid, data = LibStub("AceSerializer-3.0"):Deserialize(value) + if not valid then + TSM:Print(L["Invalid import string."]) + self:SetFocus() + return + elseif data.module ~= moduleName then + TSM:Print(L["Invalid import string."].." "..L["You appear to be attempting to import an operation from a different module."]) + self:SetText("") + return + end + data.module = nil + data.ignorePlayer = {} + data.ignoreFactionrealm = {} + data.relationships = {} + TSMObj.operations[operationName] = data + self:SetText("") + TSM:Print(L["Successfully imported operation settings."]) + ModuleOptionsRefresh(TSMObj, operationName) + end, + tooltip = L["Paste the exported operation settings into this box and hit enter or press the 'Okay' button. Imported settings will irreversibly replace existing settings for this operation."], + }, + { + type = "Button", + text = L["Export Operation"], + relativeWidth = 1, + callback = function() + local data = CopyTable(operation) + data.module = moduleName + data.ignorePlayer = nil + data.ignoreFactionrealm = nil + data.relationships = nil + ShowExportFrame(LibStub("AceSerializer-3.0"):Serialize(data)) + end, + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Groups"], + children = groupWidgets, + }, + }, + }, + } + + TSMAPI:BuildPage(container, page) +end + +function TSMAPI:NewOperationCallback(moduleName, group, operationName) + if not group then return end + StaticPopupDialogs["TSM_NEW_OPERATION_ADD"] = StaticPopupDialogs["TSM_NEW_OPERATION_ADD"] or { + button1 = YES, + button2 = NO, + timeout = 0, + OnAccept = function() + -- the "add" button + local group, moduleName, operationName = unpack(StaticPopupDialogs["TSM_NEW_OPERATION_ADD"].tsmInfo) + SetOperation(group, moduleName, operationName, #TSM.db.profile.groups[group][moduleName]) + TSM:Printf(L["Applied %s to %s."], TSMAPI.Design:GetInlineColor("link")..operationName.."|r", TSMAPI:FormatGroupPath(group, true)) + end, + preferredIndex = 3, + } + StaticPopupDialogs["TSM_NEW_OPERATION_ADD"].text = format(L["Would you like to add this new operation to %s?"], TSMAPI:FormatGroupPath(group, true)) + StaticPopupDialogs["TSM_NEW_OPERATION_ADD"].tsmInfo = {group, moduleName, operationName} + TSMAPI:ShowStaticPopupDialog("TSM_NEW_OPERATION_ADD") +end + +function TSMAPI:UpdateOperation(moduleName, operationName) + if not TSM.operations[moduleName][operationName] then return end + for key in pairs(TSM.operations[moduleName][operationName].relationships) do + local operation = TSM.operations[moduleName][operationName] + while operation.relationships[key] do + local newOperation = TSM.operations[moduleName][operation.relationships[key]] + if not newOperation then break end + operation = newOperation + end + TSM.operations[moduleName][operationName][key] = operation[key] + end +end + +local function IsCircularRelationship(moduleName, operation, key, visited) + visited = visited or {} + if visited[operation] then return true end + visited[operation] = true + if not operation.relationships[key] then return end + return IsCircularRelationship(moduleName, TSM.operations[moduleName][operation.relationships[key]], key, visited) +end + +function TSMAPI:ShowOperationRelationshipTab(obj, container, operation, settingInfo) + local moduleName = gsub(obj.name, "TradeSkillMaster_", "") + moduleName = gsub(obj.name, "TSM_", "") + local operationList = {[""]=L[""]} + local operationListOrder = {""} + local incomingRelationships = {} + for name, data in pairs(obj.operations) do + if data ~= operation then + operationList[name] = name + tinsert(operationListOrder, name) + end + for key, targetOperation in pairs(data.relationships) do + if obj.operations[targetOperation] == operation then + incomingRelationships[key] = name + end + end + end + sort(operationListOrder) + + local target = "" + local children = { + { + type = "InlineGroup", + layout = "Flow", + children = { + { + type = "Label", + text = L["Here you can setup relationships between the settings of this operation and other operations for this module. For example, if you have a relationship set to OperationA for the stack size setting below, this operation's stack size setting will always be equal to OperationA's stack size setting."], + relativeWidth = 1, + }, + { + type = "HeadingLine", + }, + { + type = "Dropdown", + label = L["Target Operation"], + list = operationList, + order = operationListOrder, + relativeWidth = 0.5, + value = target, + callback = function(self, _, value) + target = value + end, + tooltip = L["Creating a relationship for this setting will cause the setting for this operation to be equal to the equivalent setting of another operation."], + }, + { + type = "Button", + text = L["Set All Relationships to Target"], + relativeWidth = 0.49, + callback = function() + for _, inline in ipairs(settingInfo) do + for _, widget in ipairs(inline) do + local prev = operation.relationships[widget.key] + if target == "" then + operation.relationships[widget.key] = nil + else + operation.relationships[widget.key] = target + if IsCircularRelationship(moduleName, operation, widget.key) then + operation.relationships[widget.key] = prev + end + end + end + end + container:ReloadTab() + end, + tooltip = L["Sets all relationship dropdowns below to the operation selected."], + }, + }, + }, + } + for _, inlineData in ipairs(settingInfo) do + local inlineChildren = {} + for _, dropdownData in ipairs(inlineData) do + local dropdown = { + type = "Dropdown", + label = dropdownData.label, + list = operationList, + order = operationListOrder, + relativeWidth = 0.5, + value = operation.relationships[dropdownData.key] or "", + callback = function(self, _, value) + local previousValue = operation.relationships[dropdownData.key] + if value == "" then + operation.relationships[dropdownData.key] = nil + else + operation.relationships[dropdownData.key] = value + end + if IsCircularRelationship(moduleName, operation, dropdownData.key) then + operation.relationships[dropdownData.key] = previousValue + obj:Print("This relationship cannot be applied because doing so would create a circular relationship.") + self:SetValue(operation.relationships[dropdownData.key] or "") + end + end, + tooltip = L["Creating a relationship for this setting will cause the setting for this operation to be equal to the equivalent setting of another operation."], + } + tinsert(inlineChildren, dropdown) + end + local inlineGroup = { + type = "InlineGroup", + layout = "flow", + title = inlineData.label, + children = inlineChildren, + } + tinsert(children, inlineGroup) + end + + + local page = { + { + type = "ScrollFrame", + layout = "list", + children = children, + }, + } + + TSMAPI:BuildPage(container, page) +end + + + +-- operation options + +function TSM:LoadOperationOptions(parent) + local tabs = {} + local next = next + + for _, info in ipairs(private.operationInfo) do + tinsert(tabs, {text=info.module, value=info.module}) + end + + if next(tabs) then + sort(tabs, function(a, b) + return a.text < b.text + end) + end + + tinsert(tabs, 1, {text=L["Help"], value="Help"}) + + local tabGroup = AceGUI:Create("TSMTabGroup") + tabGroup:SetLayout("Fill") + tabGroup:SetTabs(tabs) + tabGroup:SetCallback("OnGroupSelected", function(_, _, value) + tabGroup:ReleaseChildren() + if value == "Help" then + private:DrawOperationHelp(tabGroup) + else + for _, info in ipairs(private.operationInfo) do + if info.module == value then + info.callbackOptions(tabGroup, TSM.loadModuleOptionsTab and TSM.loadModuleOptionsTab.operation, TSM.loadModuleOptionsTab and TSM.loadModuleOptionsTab.group) + end + end + end + end) + parent:AddChild(tabGroup) + + tabGroup:SelectTab(TSM.loadModuleOptionsTab and TSM.loadModuleOptionsTab.module or "Help") +end + +function private:DrawOperationHelp(container) + local page = { + { -- scroll frame to contain everything + type = "ScrollFrame", + layout = "List", + children = { + { + type = "InlineGroup", + layout = "List", + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Use the tabs above to select the module for which you'd like to configure operations and general options."], + }, + }, + }, + }, + }, + } + + TSMAPI:BuildPage(container, page) +end + + + +-- group options + +local treeGroup +function TSM:LoadGroupOptions(parent) + treeGroup = AceGUI:Create("TSMTreeGroup") + treeGroup:SetLayout("Fill") + treeGroup:SetCallback("OnGroupSelected", function(...) private:SelectTree(...) end) + treeGroup:SetStatusTable(TSM.db.profile.groupTreeStatus) + parent:AddChild(treeGroup) + + private:UpdateTree() + treeGroup:SelectByPath(1) +end + +local function UpdateTreeHelper(currentPath, groupPathList, index, treeGroupChildren, level) + for i=index, #groupPathList do + local groupPath = groupPathList[i] + -- make sure this group is under the current parent we're interested in + local parent, groupName = SplitGroupPath(groupPath) + if parent == currentPath then + if TSM.db.profile.colorGroupName then + groupName = TSMAPI:ColorGroupName(groupName, level) + end + local row = {value=groupPath, text=groupName} + if groupPathList[i+1] and (groupPath == groupPathList[i+1] or strfind(groupPathList[i+1], "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP)) then + row.children = {} + UpdateTreeHelper(groupPath, groupPathList, i+1, row.children, level+1) + end + tinsert(treeGroupChildren, row) + end + end + sort(treeGroupChildren, function(a, b) return strlower(a.text) < strlower(b.text) end) +end +function private:UpdateTree() + if not treeGroup then return end + + local groupChildren = {} + local groupPathList = TSM:GetGroupPathList() + UpdateTreeHelper(nil, groupPathList, 1, groupChildren, 1) + local treeGroups = {{value=1, text=L["Groups"], children=groupChildren}} + treeGroup:SetTree(treeGroups) +end + +function private:SelectGroup(name) + if not treeGroup then return end + local tmp = {1} + local groupPathParts = {TSM.GROUP_SEP:split(name)} + for i=1, #groupPathParts do + tinsert(tmp, table.concat(groupPathParts, TSM.GROUP_SEP, 1, i)) + end + treeGroup:SelectByPath(unpack(tmp)) +end + +local scrollFrameStatus = {} +function private:SelectTree(treeGroup, _, selection) + treeGroup:ReleaseChildren() + + selection = {("\001"):split(selection)} + if #selection == 1 then + private:DrawNewGroup(treeGroup) + else + local group = selection[#selection] + local tabGroup = AceGUI:Create("TSMTabGroup") + tabGroup:SetLayout("Fill") + tabGroup:SetTabs({{text=L["Operations"], value=1}, {text=L["Items"], value=2}, {text=L["Import/Export"], value=3}, {text=L["Management"], value=4}}) + tabGroup:SetCallback("OnGroupSelected", function(self, _, value) + tabGroup:ReleaseChildren() + if value == 1 then + -- load operations page + private:DrawGroupOperationsPage(self, group) + self.children[1]:SetStatusTable(scrollFrameStatus) + elseif value == 2 then + -- load items page + private:DrawGroupItemsPage(self, group) + elseif value == 3 then + -- load import/export page + private:DrawGroupImportExportPage(self, group) + elseif value == 4 then + -- load management page + private:DrawGroupManagementPage(self, group) + end + end) + tabGroup:SetCallback("OnRelease", function() scrollFrameStatus = {} end) + treeGroup:AddChild(tabGroup) + tabGroup:SelectTab(TSM.db.profile.defaultGroupTab) + end +end + +function private:DrawNewGroup(container) + local page = { + { -- scroll frame to contain everything + type = "ScrollFrame", + layout = "List", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["New Group"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["A group is a collection of items which will be treated in a similar way by TSM's modules."], + }, + { + type = "HeadingLine", + }, + { + type = "EditBox", + label = L["Group Name"], + relativeWidth = 0.8, + callback = function(self,_,value) + value = (value or ""):trim() + if value == "" then return end + if strfind(value, TSM.GROUP_SEP) then + return TSM:Printf(L["Group names cannot contain %s characters."], TSM.GROUP_SEP) + end + if TSM.db.profile.groups[value] then + return TSM:Printf(L["Error creating group. Group with name '%s' already exists."], value) + end + CreateGroup(value) + private:UpdateTree() + if TSM.db.profile.gotoNewGroup then + private:SelectGroup(value) + else + self:SetText() + self:SetFocus() + end + TSMAPI:FireEvent("TSM:GROUPS:NEWGROUP", value) + end, + tooltip = L["Give the new group a name. A descriptive name will help you find this group later."], + }, + { + type = "CheckBox", + label = L["Switch to New Group After Creation"], + relativeWidth = 1, + settingInfo = {TSM.db.profile, "gotoNewGroup"}, + }, + }, + }, + { + type = "Spacer" + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Settings"], + children = { + { + type = "Dropdown", + label = L["Default Group Tab"], + list = {L["Operations"], L["Items"], L["Import/Export"], L["Management"]}, + settingInfo = {TSM.db.profile, "defaultGroupTab"}, + tooltip = L["This dropdown determines the default tab when you visit a group."], + }, + { + type = "CheckBox", + label = L["Show Ungrouped Items for Adding to Subgroups"], + relativeWidth = 1, + settingInfo = {TSM.db.profile, "directSubgroupAdd"}, + tooltip = L["If checked, ungrouped items will be displayed in the left list of selection lists used to add items to subgroups. This allows you to add an ungrouped item directly to a subgroup rather than having to add to the parent group(s) first."], + }, + }, + }, + }, + }, + } + + TSMAPI:BuildPage(container, page) +end + +function private:DrawGroupOperationsPage(container, groupPath) + local isSubGroup = strfind(groupPath, TSM.GROUP_SEP) + local moduleInlines = {} + local addRef, deleteRef = {}, {} + for _, info in ipairs(private.operationInfo) do + local moduleName = info.module + local ddList = {} + ddList[""] = L[""] + for name in pairs(TSM.operations[moduleName] or {}) do + ddList[name] = name + end + + TSM.db.profile.groups[groupPath][moduleName] = TSM.db.profile.groups[groupPath][moduleName] or {} + local operations = TSM.db.profile.groups[groupPath][moduleName] + for i=#operations, 1, -1 do + if not ddList[operations[i]] then + DeleteOperation(groupPath, moduleName, i) + end + end + operations[1] = operations[1] or "" + if #operations > 1 then + ddList[TSM.GROUP_SEP] = L[""] + end + + local moduleInline = { + type = "InlineGroup", + layout = "Flow", + title = moduleName, + children = {}, + } + + local addOperationWidget + if #operations < info.maxOperations then + addOperationWidget = { + type = "Button", + text = L["Add Additional Operation"], + relativeWidth = 1, + disabled = isSubGroup and not operations.override, + callback = function() + AddOperation(groupPath, moduleName) + container:ReloadTab() + end, + } + else + addOperationWidget = {type="Label", relativeWidth=1} + end + if isSubGroup then + tinsert(moduleInline.children, { + type = "CheckBox", + label = L["Override Module Operations"], + value = operations.override, + relativeWidth = 1, + callback = function(_,_,value) + SetOperationOverride(groupPath, moduleName, value) + container:ReloadTab() + end, + tooltip = L["Check this box to override this group's operation(s) for this module."], + }) + else + tinsert(moduleInline.children, {type="Label", relativeWidth=1}) + end + + for i=1, #operations do + tinsert(moduleInline.children, { + type = "Dropdown", + label = format(L["Operation #%d"], i), + list = ddList, + value = operations[i], + relativeWidth = 0.6, + disabled = isSubGroup and not operations.override, + callback = function(_,_,value) + if value == "" then + SetOperation(groupPath, moduleName, nil, i) + elseif value == TSM.GROUP_SEP then + DeleteOperation(groupPath, moduleName, i) + else + SetOperation(groupPath, moduleName, value, i) + end + container:ReloadTab() + end, + tooltip = L["Select an operation to apply to this group."], + }) + if operations[i] ~= "" then + tinsert(moduleInline.children, { + type = "Button", + text = L["View Operation Options"], + relativeWidth = 0.39, + callback = function() + TSMAPI:ShowOperationOptions(moduleName, operations[i]) + end, + tooltip = L["Click this button to configure the currently selected operation."], + }) + elseif not isSubGroup or operations.override then + tinsert(moduleInline.children, { + type = "Button", + text = format(L["Create %s Operation"], moduleName), + relativeWidth = 0.39, + callback = function() + TSMAPI:ShowOperationOptions(moduleName, "", groupPath) + end, + tooltip = L["Click this button to create a new operation for this module."], + }) + end + end + tinsert(moduleInline.children, addOperationWidget) + + local opStrs = {} + for _, name in ipairs(operations) do + if name ~= "" then + local str = info.callbackInfo(name) or "---" + tinsert(opStrs, TSMAPI.Design:GetInlineColor("link")..name.."|r: "..str) + end + end + tinsert(moduleInline.children, {type="HeadingLine"}) + tinsert(moduleInline.children, { + type = "Label", + text = #opStrs > 0 and table.concat(opStrs, "\n") or format(L["Select a %s operation using the dropdown above."], moduleName), + relativeWidth = 1, + }) + + tinsert(moduleInlines, moduleInline) + end + + sort(moduleInlines, function(a, b) return a.title < b.title end) + + local children = {} + for i, inline in ipairs(moduleInlines) do + tinsert(children, inline) + if i ~= #moduleInlines then + tinsert(children, {type="Spacer"}) + end + end + + local page = { + { + type = "ScrollFrame", + layout = "List", + children = children, + }, + } + + TSMAPI:BuildPage(container, page) +end + +local alreadyLoaded = {} +function private:DrawGroupItemsPage(container, groupPath) + if not alreadyLoaded[groupPath] then + alreadyLoaded[groupPath] = true + TSMAPI:CreateTimeDelay("itemsTabLoad", 0.1, function() container:ReloadTab() end) + end + + local parentPath, groupName = SplitGroupPath(groupPath) + local function GetItemList(side) + local list = {} + if side == "left" then + if parentPath then + -- this is a subgroup so add all items from parent group + for itemString, path in pairs(TSM.db.profile.items) do + if path == parentPath then + local link = select(2, TSMAPI:GetSafeItemInfo(itemString)) + if link then + tinsert(list, link) + end + end + end + end + if not parentPath or TSM.db.profile.directSubgroupAdd then + -- add all items in bags + local usedLinks = {} + for bag, slot, itemString in TSMAPI:GetBagIterator() do + if not usedLinks[itemString] then + local baseItemString = TSMAPI:GetBaseItemString(itemString) + local link = GetContainerItemLink(bag, slot) + if itemString ~= baseItemString and TSM.db.global.ignoreRandomEnchants then -- a random enchant item + itemString = baseItemString + link = select(2, TSMAPI:GetSafeItemInfo(itemString)) + end + if link and not TSM.db.profile.items[itemString] then + tinsert(list, link) + usedLinks[itemString] = true + end + end + end + end + elseif side == "right" then + for itemString, path in pairs(TSM.db.profile.items) do + if path == groupPath or strfind(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + local link = select(2, TSMAPI:GetSafeItemInfo(itemString)) + if link then + tinsert(list, link) + end + end + end + end + return list + end + + local leftTitle, rightTitle + if parentPath then + if TSM.db.profile.directSubgroupAdd then + leftTitle = L["Parent/Ungrouped Items:"] + else + leftTitle = L["Parent Group Items:"] + end + rightTitle = L["Subgroup Items:"] + else + leftTitle = L["Ungrouped Items:"] + rightTitle = L["Group Items:"] + end + + local page = { + { -- scroll frame to contain everything + type = "SimpleGroup", + layout = "Fill", + children = { + { + type = "GroupItemList", + leftTitle = leftTitle, + rightTitle = rightTitle, + listCallback = GetItemList, + showIgnore = not parentPath or TSM.db.profile.directSubgroupAdd, + onAdd = function(_,_,selected) + for i=#selected, 1, -1 do + AddItem(selected[i], groupPath) + end + TSMAPI:FireEvent("TSM:GROUPS:ADDITEMS", {num=#selected, group=groupPath}) + container:ReloadTab() + end, + onRemove = function(_,_,selected) + if parentPath and not IsShiftKeyDown() then + for i=#selected, 1, -1 do + MoveItem(selected[i], parentPath) + end + TSMAPI:FireEvent("TSM:GROUPS:MOVEITEMS", {num=#selected, group=groupPath}) + else + for i=#selected, 1, -1 do + DeleteItem(selected[i]) + end + TSMAPI:FireEvent("TSM:GROUPS:REMOVEITEMS", {num=#selected, group=groupPath}) + end + container:ReloadTab() + end, + }, + }, + }, + } + TSMAPI:BuildPage(container, page) +end + +function TSM:ImportGroup(importStr, groupPath) + if not importStr then return end + importStr = importStr:trim() + if importStr == "" then return end + local parentPath = strfind(groupPath, TSM.GROUP_SEP) and SplitGroupPath(groupPath) + + if strfind(importStr, "^|c") then + local itemString = TSMAPI:GetItemString(importStr) + if not itemString then return end + if parentPath and TSM.db.profile.importParentOnly and TSM.db.profile.items[itemString] ~= parentPath then return 0 end + if TSM.db.profile.items[itemString] and TSM.db.profile.moveImportedItems then + MoveItem(itemString, groupPath) + return 1 + elseif not TSM.db.profile.items[itemString] then + AddItem(itemString, groupPath) + return 1 + end + return 0 + end + + local items = {} + local currentSubPath = "" + for _, str in ipairs(TSMAPI:SafeStrSplit(importStr, ",")) do + str = str:trim() + local noSpaceStr = gsub(str, " ", "") -- forums like to add spaces + local itemString, subPath + if tonumber(noSpaceStr) then + itemString = "item:"..tonumber(noSpaceStr)..":0:0:0:0:0:0" + elseif strfind(noSpaceStr, "^group:") then + subPath = strsub(str, strfind(str, ":")+1, -1) + subPath = gsub(subPath, TSM.GROUP_SEP.."[ ]*"..TSM.GROUP_SEP, ",") + elseif strfind(noSpaceStr, "p") then + itemString = gsub(noSpaceStr, "p", "battlepet") + elseif strfind(noSpaceStr, ":") then + local itemID, randomEnchant = (":"):split(noSpaceStr) + if not tonumber(itemID) or not tonumber(randomEnchant) then return end + itemString = "item:"..tonumber(itemID)..":0:0:0:0:0:"..tonumber(randomEnchant) + end + + if subPath then + currentSubPath = subPath + elseif itemString then + items[itemString] = currentSubPath + else + return + end + end + + local num = 0 + for itemString, subPath in pairs(items) do + if not (parentPath and TSM.db.profile.moveImportedItems and TSM.db.profile.importParentOnly and TSM.db.profile.items[itemString] ~= parentPath) then + local path = groupPath + if subPath ~= "" then + -- create necessary parent groups + local subParts = {TSM.GROUP_SEP:split(subPath)} + for i=1, #subParts-1 do + CreateGroup(path..TSM.GROUP_SEP..table.concat(subParts, TSM.GROUP_SEP, 1, i)) + end + path = path..TSM.GROUP_SEP..subPath + end + CreateGroup(path) + if TSM.db.profile.items[itemString] and TSM.db.profile.moveImportedItems then + MoveItem(itemString, path) + num = num + 1 + elseif not TSM.db.profile.items[itemString] then + AddItem(itemString, path) + num = num + 1 + end + end + end + return num +end + +function TSM:ExportGroup(groupPath) + local temp = {} + for itemString, group in pairs(TSM.db.profile.items) do + if group == groupPath or strfind(group, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP) then + tinsert(temp, itemString) + end + end + sort(temp, function(a, b) + local groupA = strlower(gsub(TSM.db.profile.items[a], TSM.GROUP_SEP, "\001")) + local groupB = strlower(gsub(TSM.db.profile.items[b], TSM.GROUP_SEP, "\001")) + if groupA == groupB then + return a < b + end + return groupA < groupB + end) + + local items = {} + local currentPath = "" + for _, itemString in pairs(temp) do + if TSM.db.profile.exportSubGroups then + local path = TSM.db.profile.items[itemString] + if path == groupPath then + path = "" + else + path = gsub(path, "^"..TSMAPI:StrEscape(groupPath)..TSM.GROUP_SEP, "") + end + path = gsub(path, ",", TSM.GROUP_SEP..TSM.GROUP_SEP) + if path ~= currentPath then + tinsert(items, "group:"..path) + currentPath = path + end + end + if strfind(itemString, "^item:") then + local _, itemID, _, _, _, _, _, randomEnchant = (":"):split(itemString) + if tonumber(randomEnchant) and tonumber(randomEnchant) > 0 then + tinsert(items, itemID..":"..randomEnchant) + else + tinsert(items, itemID) + end + elseif strfind(itemString, "^battlepet:") then + itemString = gsub(itemString, "battlepet", "p") + tinsert(items, itemString) + end + end + return table.concat(items, ",") +end + +function private:DrawGroupImportExportPage(container, groupPath) + local page = { + { -- scroll frame to contain everything + type = "ScrollFrame", + layout = "list", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["Import Items"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Paste the list of items into the box below and hit enter or click on the 'Okay' button.\n\nYou can also paste an itemLink into the box below to add a specific item to this group."], + }, + { + type = "EditBox", + label = L["Import String"], + relativeWidth = 1, + callback = function(self, _, value) + local num = TSM:ImportGroup(value, groupPath) + if not num then + TSM:Print(L["Invalid import string."]) + return self:SetFocus() + end + self:SetText("") + TSM:Printf(L["Successfully imported %d items to %s."], num, TSMAPI:FormatGroupPath(groupPath, true)) + private:UpdateTree() + private:SelectGroup(groupPath) + TSMAPI:FireEvent("TSM:GROUPS:ADDITEMS", {num=num, group=groupPath, isImport=true}) + end, + tooltip = L["Paste the exported items into this box and hit enter or press the 'Okay' button. The recommended format for the list of items is a comma separated list of itemIDs for general items. For battle pets, the entire battlepet string should be used. For randomly enchanted items, the format is : (ex: 38472:-29)."], + }, + { + type = "CheckBox", + label = L["Move Already Grouped Items"], + relativeWidth = 0.5, + settingInfo = {TSM.db.profile, "moveImportedItems"}, + callback = function() container:ReloadTab() end, + tooltip = L["If checked, any items you import that are already in a group will be moved out of their current group and into this group. Otherwise, they will simply be ignored."], + }, + { + type = "CheckBox", + disabled = not strfind(groupPath, TSM.GROUP_SEP) or not TSM.db.profile.moveImportedItems, + label = L["Only Import Items from Parent Group"], + relativeWidth = 0.5, + settingInfo = {TSM.db.profile, "importParentOnly"}, + tooltip = L["If checked, only items which are in the parent group of this group will be imported."], + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Export Items in Group"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Click the button below to open the export frame for this group."], + }, + { + type = "Button", + text = L["Export Group Items"], + relativeWidth = 1, + callback = function() + ShowExportFrame(TSM:ExportGroup(groupPath)) + end, + tooltip = L["Click this button to show a frame for easily exporting the list of items which are in this group."], + }, + { + type = "CheckBox", + label = L["Include Subgroup Structure in Export"], + relativeWidth = 0.5, + settingInfo = {TSM.db.profile, "exportSubGroups"}, + tooltip = L["If checked, the structure of the subgroups will be included in the export. Otherwise, the items in this group (and all subgroups) will be exported as a flat list."], + }, + }, + }, + }, + }, + } + TSMAPI:BuildPage(container, page) +end + +function private:DrawGroupManagementPage(container, groupPath) + local page = { + { -- scroll frame to contain everything + type = "ScrollFrame", + layout = "list", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["Create New Subgroup"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Subgroups contain a subset of the items in their parent groups and can be used to further refine how different items are treated by TSM's modules."], + }, + { + type = "EditBox", + label = L["New Subgroup Name"], + relativeWidth = 0.8, + callback = function(self,_,value) + value = (value or ""):trim() + if value == "" then return end + if strfind(value, TSM.GROUP_SEP) then + return TSM:Printf(L["Group names cannot contain %s characters."], TSM.GROUP_SEP) + end + local newPath = groupPath..TSM.GROUP_SEP..value + if TSM.db.profile.groups[newPath] then + return TSM:Printf(L["Error creating subgroup. Subgroup with name '%s' already exists."], value) + end + CreateGroup(newPath) + private:UpdateTree() + if TSM.db.profile.gotoNewGroup then + private:SelectGroup(newPath) + else + self:SetText() + self:SetFocus() + end + end, + tooltip = L["Give the group a new name. A descriptive name will help you find this group later."], + }, + { + type = "CheckBox", + label = L["Switch to New Group After Creation"], + relativeWidth = 1, + settingInfo = {TSM.db.profile, "gotoNewGroup"}, + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Rename Group"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Use the editbox below to give this group a new name."], + }, + { + type = "EditBox", + label = L["New Group Name"], + relativeWidth = 0.8, + value = select(2, SplitGroupPath(groupPath)), + callback = function(_,_,value) + value = (value or ""):trim() + if value == "" then return end + if value == select(2, SplitGroupPath(groupPath)) then return end -- same name + if strfind(value, TSM.GROUP_SEP) then + return TSM:Printf(L["Group names cannot contain %s characters."], TSM.GROUP_SEP) + end + local newPath + local parent = SplitGroupPath(groupPath) + if parent then + newPath = parent..TSM.GROUP_SEP..value + else + newPath = value + end + if TSM.db.profile.groups[newPath] then + return TSM:Printf(L["Error renaming group. Group with name '%s' already exists."], value) + end + MoveGroup(groupPath, newPath) + TSMAPI:FireEvent("TSM:GROUPS:MOVEGROUP", {old=groupPath, new=newPath}) + private:UpdateTree() + private:SelectGroup(newPath) + end, + tooltip = L["Give the group a new name. A descriptive name will help you find this group later."], + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Move Group"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Use the group box below to move this group and all subgroups of this group. Moving a group will cause all items in the group (and its subgroups) to be removed from its current parent group and added to the new parent group."], + }, + { + type = "GroupBox", + label = L["New Parent Group"], + relativeWidth = .5, + callback = function(self, _, value) + self:SetText() + if value and value ~= groupPath then + if strfind(value, "^"..groupPath) then + return TSM:Printf(L["Error moving group. You cannot move this group to one of its subgroups."]) + end + local _, groupName = SplitGroupPath(groupPath) + local newPath = value..TSM.GROUP_SEP..groupName + if TSM.db.profile.groups[newPath] then + return TSM:Printf(L["Error moving group. Group '%s' already exists."], TSMAPI:FormatGroupPath(newPath, true)) + end + + TSM:Printf(L["Moved %s to %s."], TSMAPI:FormatGroupPath(groupPath, true), TSMAPI:FormatGroupPath(value, true)) + MoveGroup(groupPath, newPath) + TSMAPI:FireEvent("TSM:GROUPS:MOVEGROUP", {old=groupPath, new=newPath}) + private:UpdateTree() + private:SelectGroup(newPath) + end + end, + }, + { + type = "Button", + text = L["Move to Top Level"], + relativeWidth = 0.49, + disabled = groupPath == select(2, SplitGroupPath(groupPath)), + callback = function() + local _, groupName = SplitGroupPath(groupPath) + local newPath = groupName + if TSM.db.profile.groups[newPath] then + return TSM:Printf(L["Error moving group. Group '%s' already exists."], TSMAPI:FormatGroupPath(newPath, true)) + end + + TSM:Printf(L["Moved %s to %s."], TSMAPI:FormatGroupPath(groupPath, true), TSMAPI:FormatGroupPath(newPath, true)) + MoveGroup(groupPath, newPath) + TSMAPI:FireEvent("TSM:GROUPS:MOVEGROUP", {old=groupPath, new=newPath}) + private:UpdateTree() + private:SelectGroup(newPath) + end, + tooltip = L["When clicked, makes this group a top-level group with no parent."], + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Delete Group"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Use the button below to delete this group. Any subgroups of this group will also be deleted, with all items being returned to the parent of this group or removed completely if this group has no parent."], + }, + { + type = "CheckBox", + label = L["Keep Items in Parent Group"], + relativeWidth = 1, + settingInfo = {TSM.db.profile, "keepInParent"}, + }, + { + type = "Button", + text = L["Delete Group"], + relativeWidth = 0.8, + callback = function() + DeleteGroup(groupPath) + TSMAPI:FireEvent("TSM:GROUPS:DELETEGROUP", groupPath) + private:UpdateTree() + local parent = SplitGroupPath(groupPath) + if parent then + private:SelectGroup(parent) + else + treeGroup:SelectByPath(1) + end + end, + tooltip = L["Any subgroups of this group will also be deleted, with all items being returned to the parent of this group or removed completely if this group has no parent."], + }, + }, + }, + }, + }, + } + TSMAPI:BuildPage(container, page) +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Modules.lua b/TradeSkillMaster/Core/Modules.lua new file mode 100644 index 0000000..42ddcd9 --- /dev/null +++ b/TradeSkillMaster/Core/Modules.lua @@ -0,0 +1,368 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains all the code for the new standardized module registration / format + +local TSM = select(2, ...) +local Modules = TSM:NewModule("Modules", "AceConsole-3.0") +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local moduleObjects = TSM.moduleObjects +local moduleNames = TSM.moduleNames + + +-- initialization stuff +function Modules:OnEnable() + -- register the chat commands (slash commands) - whenver '/tsm' or '/tradeskillmaster' is typed by the user, Modules:ChatCommand() will be called + Modules:RegisterChatCommand("tsm", "ChatCommand") + Modules:RegisterChatCommand("tradeskillmaster", "ChatCommand") + + -- tooltip setup + TSM:SetupTooltips() + + -- no modules popup + TSMAPI:CreateTimeDelay("noModulesPopup", 3, function() + if #moduleNames == 1 then + StaticPopupDialogs["TSMInfoPopup"] = { + text = L["|cffffff00Important Note:|r You do not currently have any modules installed / enabled for TradeSkillMaster! |cff77ccffYou must download modules for TradeSkillMaster to have some useful functionality!|r\n\nPlease visit http://www.curse.com/addons/wow/tradeskill-master and check the project description for links to download modules."], + button1 = L["I'll Go There Now!"], + timeout = 0, + whileDead = true, + OnAccept = function() TSM:Print(L["Just incase you didn't read this the first time:"]) TSM:Print(L["|cffffff00Important Note:|r You do not currently have any modules installed / enabled for TradeSkillMaster! |cff77ccffYou must download modules for TradeSkillMaster to have some useful functionality!|r\n\nPlease visit http://www.curse.com/addons/wow/tradeskill-master and check the project description for links to download modules."]) end, + preferredIndex = 3, + } + TSMAPI:ShowStaticPopupDialog("TSMInfoPopup") + end + end) +end + + +-- ************************************************************************** +-- TSMAPI:NewModule API +-- ************************************************************************** + +-- info on all the possible fields of the module objects which TSM core cares about +local moduleFieldInfo = { + -- operation fields + { key = "operations", type = "table", subFieldInfo = { maxOperations = "number", callbackOptions = "function", callbackInfo = "function" } }, + -- tooltip fields + { key = "GetTooltip", type = "function" }, + -- tooltip options + { key = "tooltipOptions", type = "table", subFieldInfo = { callback = "function" } }, + -- shared feature fields + { key = "slashCommands", type = "table", subTableInfo = { key = "string", label = "string", callback = "function" } }, + { key = "icons", type = "table", subTableInfo = { side = "string", desc = "string", callback = "function", icon = "string" } }, + { key = "auctionTab", type = "table", subFieldInfo = { callbackShow = "function", callbackHide = "function" } }, + { key = "bankUiButton", type = "table", subFieldInfo = { callback = "function" } }, + -- data access fields + { key = "priceSources", type = "table", subTableInfo = { key = "string", label = "string", callback = "function" } }, + { key = "moduleAPIs", type = "table", subTableInfo = { key = "string", callback = "function" } }, + -- multi-account sync fields + { key = "sync", type = "table", subFieldInfo = { callback = "function" } }, +} + +-- if the passed function is a string, will check if it's a method of the object and return a wrapper function +function Modules:GetFunction(obj, func) + if type(func) == "string" then + local part1, part2 = (":"):split(func) + if part2 and obj[part1] and obj[part1][part2] then + return function(...) return obj[part1][part2](obj[part1], ...) end + elseif obj[part1] then + return function(...) return obj[part1](obj, ...) end + end + end + return func +end + +-- validates a simple list of sub-tables which have the basic key/label/callback fields +function Modules:ValidateList(obj, val, keys) + for i, v in ipairs(val) do + if type(v) ~= "table" then + return "invalid entry in list at index " .. i + end + for key, valType in pairs(keys) do + if valType == "function" then + v[key] = Modules:GetFunction(obj, v[key]) + end + if type(v[key]) ~= valType then + return format("expected %s type for field %s, got %s at index %d", valType, key, type(v[key]), i) + end + end + end +end + +function Modules:ValidateModuleObject(obj) + -- make sure it's a table + if type(obj) ~= "table" then + return format("Expected table, got %s.", type(obj)) + end + -- simple check that it's an AceAddon object which stores the name in .name and implements a .__tostring metamethod. + if tostring(obj) ~= obj.name then + return "Passed object is not an AceAddon-3.0 object." + end + + -- validate all the fields + for _, fieldInfo in ipairs(moduleFieldInfo) do + local val = obj[fieldInfo.key] + if val then + -- make sure it's of the correct type + if type(val) ~= fieldInfo.type then + return format("For field '%s', expected type of %s, got %s.", fieldInfo.key, fieldInfo.type, type(val)) + end + -- if there's required subfields, check them + if fieldInfo.subFieldInfo then + for key, valType in pairs(fieldInfo.subFieldInfo) do + if valType == "function" then + val[key] = Modules:GetFunction(obj, val[key]) + end + if type(val[key]) ~= valType then + return format("expected %s type for field %s, got %s at index %d", valType, key, type(val[key]), key) + end + end + end + -- if there's subTableInfo specified, run Modules:ValidateList on this field + if fieldInfo.subTableInfo then + local errMsg = Modules:ValidateList(obj, val, fieldInfo.subTableInfo) + if errMsg then + return format("Invalid value for '%s': %s.", fieldInfo.key, errMsg) + end + end + end + end +end + +function Modules:GetInfo() + local info = {} + for _, name in ipairs(moduleNames) do + local obj = moduleObjects[name] + tinsert(info, { name = name, version = obj._version, author = obj._author, desc = obj._desc }) + end + return info +end + +function TSMAPI:NewModule(obj) + local errMsg + if obj == TSM then + local tmp = TSM.operations + TSM.operations = nil + errMsg = Modules:ValidateModuleObject(obj) + TSM.operations = tmp + else + errMsg = Modules:ValidateModuleObject(obj) + end + if errMsg then + error(errMsg, 2) + end + + -- register the db callback + if obj.db and obj.OnTSMDBShutdown then + obj.db:RegisterCallback("OnDatabaseShutdown", TSM.ModuleOnDatabaseShutdown) + end + + -- register it for debug tracing + TSMAPI:RegisterForTracing(obj) + for _, subModule in pairs(obj.modules or {}) do + local name = obj.name.."."..subModule.moduleName + TSMAPI:RegisterForTracing(subModule, name) + end + + -- sets the _version, _author, and _desc fields + local fullName = gsub(obj.name, "TSM_", "TradeSkillMaster_") + obj._version = GetAddOnMetadata(fullName, "X-Curse-Packaged-Version") or GetAddOnMetadata(fullName, "Version") + if strsub(obj._version, 1, 1) == "@" then + obj._version = "Dev" + end + obj._author = GetAddOnMetadata(fullName, "Author") + obj._desc = GetAddOnMetadata(fullName, "Notes") + + -- store the object in the local table + local moduleName = gsub(obj.name, "TradeSkillMaster_", "") + moduleName = gsub(obj.name, "TSM_", "") + moduleObjects[moduleName] = obj + tinsert(moduleNames, moduleName) + sort(moduleNames, function(a, b) + if a == "TradeSkillMaster" then + return true + elseif b == "TradeSkillMaster" then + return false + else + return a < b + end + end) + + -- register icons with main frame code + if obj.icons then + for _, info in ipairs(obj.icons) do + if info.slashCommand then + obj.slashCommands = obj.slashCommands or {} + tinsert(obj.slashCommands, {key=info.slashCommand, label=format("Opens the TSM window to the '%s' page", info.desc), callback=function() TSMAPI:OpenFrame() TSMAPI:SelectIcon(obj.name, info.desc) end}) + end + TSM:RegisterMainFrameIcon(info.desc, info.icon, info.callback, obj.name, info.side) + end + end + + -- register auction buttons with auction frame code + if obj.auctionTab then + TSM:RegisterAuctionFunction(moduleName, obj.auctionTab.callbackShow, obj.auctionTab.callbackHide) + end + if obj ~= TSM and obj.operations then + -- conversion code from early beta versions + if obj.db and obj.db.global.operations then + TSM.operations[moduleName] = CopyTable(obj.db.global.operations) + obj.db.global.operations = nil + end + TSM:RegisterOperationInfo(moduleName, obj.operations) + TSM.operations[moduleName] = TSM.operations[moduleName] or {} + obj.operations = TSM.operations[moduleName] + for _, operation in pairs(obj.operations) do + operation.ignorePlayer = operation.ignorePlayer or {} + operation.ignoreFactionrealm = operation.ignoreFactionrealm or {} + operation.relationships = operation.relationships or {} + end + TSM:CheckOperationRelationships(moduleName) + end + -- register tooltip options + if obj.tooltipOptions then + TSM:RegisterTooltipInfo(moduleName, obj.tooltipOptions) + end + -- register bankUi Tabs + if obj.bankUiButton then + TSM:RegisterBankUiButton(moduleName, obj.bankUiButton.callback) + end + -- -- register sync callback + -- if obj.sync then + -- TSM:RegisterSyncCallback(moduleName, obj.sync.callback) + -- end + + -- replace default Print and Printf functions + local Print = obj.Print + obj.Print = function(self, ...) Print(self, TSMAPI:GetChatFrame(), ...) end + local Printf = obj.Printf + obj.Printf = function(self, ...) Printf(self, TSMAPI:GetChatFrame(), ...) end +end + +function TSM:UpdateModuleProfiles() + if TSM.db.global.globalOperations then + for moduleName, obj in pairs(moduleObjects) do + if obj.operations then + TSM.db.global.operations[moduleName] = TSM.db.global.operations[moduleName] or {} + obj.operations = TSM.db.global.operations[moduleName] + end + end + TSM.operations = TSM.db.global.operations + else + for moduleName, obj in pairs(moduleObjects) do + if obj.operations then + TSM.db.profile.operations[moduleName] = TSM.db.profile.operations[moduleName] or {} + obj.operations = TSM.db.profile.operations[moduleName] + end + end + TSM.operations = TSM.db.profile.operations + end + for module, operations in pairs(TSM.operations) do + for _, operation in pairs(operations) do + operation.ignorePlayer = operation.ignorePlayer or {} + operation.ignoreFactionrealm = operation.ignoreFactionrealm or {} + operation.relationships = operation.relationships or {} + end + TSM:CheckOperationRelationships(module) + end + if not TSM.db.profile.design then + TSM:LoadDefaultDesign() + end +end + +local didDBShutdown = false +function TSM:ModuleOnDatabaseShutdown() + if didDBShutdown then return end + didDBShutdown = true + local originalProfile = TSM.db:GetCurrentProfile() + for _, obj in pairs(moduleObjects) do + -- erroring here would cause the profile to be reset, so use pcall + if obj.OnTSMDBShutdown and not pcall(obj.OnTSMDBShutdown) then + -- the callback hit an error, so ensure the correct profile is restored + TSM.db:SetProfile(originalProfile) + end + end + -- ensure we're back on the correct profile + TSM.db:SetProfile(originalProfile) +end + +function TSM:IsOperationIgnored(module, operationName) + local obj = moduleObjects[module] + local operation = obj.operations[operationName] + if not operation then return end + local factionrealm = TSM.db.keys.factionrealm + local playerKey = UnitName("player").." - "..factionrealm + return operation.ignorePlayer[playerKey] or operation.ignoreFactionrealm[factionrealm] +end + +function TSM:CheckOperationRelationships(moduleName) + for _, operation in pairs(TSM.operations[moduleName]) do + for key, target in pairs(operation.relationships or {}) do + if not TSM.operations[moduleName][target] then + operation.relationships[key] = nil + end + end + end +end + + +-- ************************************************************************** +-- Module APIs +-- ************************************************************************** + +function TSMAPI:ModuleAPI(moduleName, key, ...) + if type(moduleName) ~= "string" or type(key) ~= "string" then return nil, "Invalid args" end + if not moduleObjects[moduleName] then return nil, "Invalid module" end + + for _, info in ipairs(moduleObjects[moduleName].moduleAPIs or {}) do + if info.key == key then + return info.callback(...) + end + end + return nil, "Key not found" +end + + +-- ************************************************************************** +-- Slash Commands +-- ************************************************************************** + +function Modules:ChatCommand(input) + local parts = { (" "):split(input) } + local cmd, args = strlower(parts[1] or ""), table.concat(parts, " ", 2) + + if cmd == "" then + TSMAPI:OpenFrame() + TSMAPI:SelectIcon("TradeSkillMaster", L["TSM Status / Options"]) + else + local foundCmd + for _, obj in pairs(moduleObjects) do + if obj.slashCommands then + for _, info in ipairs(obj.slashCommands) do + if strlower(info.key) == cmd then + info.callback(args) + foundCmd = true + end + end + end + end + -- If not a registered command, print out slash command help + if not foundCmd then + local chatFrame = TSMAPI:GetChatFrame() + TSM:Print(L["Slash Commands:"]) + chatFrame:AddMessage("|cffffaa00" .. L["/tsm|r - opens the main TSM window."]) + chatFrame:AddMessage("|cffffaa00" .. L["/tsm help|r - Shows this help listing"]) + for _, name in ipairs(moduleNames) do + for _, info in ipairs(moduleObjects[name].slashCommands or {}) do + chatFrame:AddMessage("|cffffaa00/tsm " .. info.key .. "|r - " .. info.label) + end + end + end + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Mover.lua b/TradeSkillMaster/Core/Mover.lua new file mode 100644 index 0000000..17728aa --- /dev/null +++ b/TradeSkillMaster/Core/Mover.lua @@ -0,0 +1,699 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains all the code for moving items between bags / bank. + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries +local lib = TSMAPI + +local bankType +local fullMoves, splitMoves, bagState = {}, {}, {} +local callbackMsg = {} + +-- this is a set of wrapper functions so that I can switch +-- between guildbank and bank function easily (taken from warehousing) + +TSM.pickupContainerItemSrc = nil +TSM.getContainerItemIDSrc = nil +TSM.getContainerNumSlotsSrc = nil +TSM.getContainerItemLinkSrc = nil +TSM.getContainerNumFreeSlotsSrc = nil +TSM.splitContainerItemSrc = nil + +TSM.pickupContainerItemDest = nil +TSM.getContainerItemIDDest = nil +TSM.getContainerNumSlotsDest = nil +TSM.getContainerItemLinkDest = nil +TSM.getContainerNumFreeSlotsDest = nil + + +TSM.autoStoreItem = nil +TSM.getContainerItemQty = nil + +function TSM:OnEnable() + local next = next + + TSM:RegisterEvent("GUILDBANKFRAME_OPENED", function(event) + bankType = "guildbank" + end) + + TSM:RegisterEvent("BANKFRAME_OPENED", function(event) + bankType = "bank" + end) + + TSM:RegisterEvent("GUILDBANKFRAME_CLOSED", function(event, addon) + bankType = nil + TSM:UnregisterEvent("GUILDBANKBAGSLOTS_CHANGED") + end) + + TSM:RegisterEvent("BANKFRAME_CLOSED", function(event) + bankType = nil + TSM:UnregisterEvent("BAG_UPDATE") + end) +end + +local function setSrcBagFunctions(bagType) + if bagType == "guildbank" then + TSM.autoStoreItem = function(bag, slot) AutoStoreGuildBankItem(bag, slot) + end + TSM.getContainerItemQty = function(bag, slot) return select(2, GetGuildBankItemInfo(bag, slot)) + end + TSM.splitContainerItemSrc = function(bag, slot, need) SplitGuildBankItem(bag, slot, need); + end + TSM.pickupContainerItemSrc = function(bag, slot) PickupGuildBankItem(bag, slot) + end + TSM.getContainerNumSlotsSrc = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98 + end + TSM.getContainerItemLinkSrc = function(bag, slot) return GetGuildBankItemLink(bag, slot) + end + TSM.getContainerNumFreeSlotsSrc = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98 + end --need to change this eventually + TSM.getContainerItemIDSrc = function(bag, slot) + local tmpLink = GetGuildBankItemLink(bag, slot) + local quantity = select(2, GetGuildBankItemInfo(bag, slot)) + if tmpLink then + return TSMAPI:GetBaseItemString(tmpLink, true), quantity + else + return nil + end + end + else + TSM.autoStoreItem = function(bag, slot) UseContainerItem(bag, slot) + end + TSM.getContainerItemQty = function(bag, slot) return select(2, GetContainerItemInfo(bag, slot)) + end + TSM.splitContainerItemSrc = function(bag, slot, need) SplitContainerItem(bag, slot, need) + end + TSM.pickupContainerItemSrc = function(bag, slot) PickupContainerItem(bag, slot) + end + TSM.getContainerItemIDSrc = function(bag, slot) + local tmpLink = GetContainerItemLink(bag, slot) + local quantity = select(2, GetContainerItemInfo(bag, slot)) + return TSMAPI:GetBaseItemString(tmpLink, true), quantity + end + TSM.getContainerNumSlotsSrc = function(bag) return GetContainerNumSlots(bag) + end + TSM.getContainerItemLinkSrc = function(bag, slot) return GetContainerItemLink(bag, slot) + end + TSM.getContainerNumFreeSlotsSrc = function(bag) return GetContainerNumFreeSlots(bag) + end + end +end + +local function setDestBagFunctions(bagType) + if bagType == "guildbank" then + TSM.pickupContainerItemDest = function(bag, slot) PickupGuildBankItem(bag, slot) + end + TSM.getContainerNumSlotsDest = function(bag) return MAX_GUILDBANK_SLOTS_PER_TAB or 98 + end + TSM.getContainerNumFreeSlotsDest = function(bag) return GetEmptySlotCount(bag) + end --need to change this eventually + TSM.getContainerItemLinkDest = function(bag, slot) return GetGuildBankItemLink(bag, slot) + end + TSM.getContainerItemIDDest = function(bag, slot) + local tmpLink = GetGuildBankItemLink(bag, slot) + local quantity = select(2, GetGuildBankItemInfo(bag, slot)) + if tmpLink then + return TSMAPI:GetBaseItemString(tmpLink, true), quantity + else + return nil + end + end + else + TSM.pickupContainerItemDest = function(bag, slot) PickupContainerItem(bag, slot) + end + TSM.getContainerItemIDDest = function(bag, slot) + local tmpLink = GetContainerItemLink(bag, slot) + local quantity = select(2, GetContainerItemInfo(bag, slot)) + return TSMAPI:GetBaseItemString(tmpLink, true), quantity + end + TSM.getContainerNumSlotsDest = function(bag) return GetContainerNumSlots(bag) + end + TSM.getContainerItemLinkDest = function(bag, slot) return GetContainerItemLink(bag, slot) + end + TSM.getContainerNumFreeSlotsDest = function(bag) return GetContainerNumFreeSlots(bag) + end + end +end + +local function getContainerTable(cnt) + local t = {} + + if cnt == "bank" then + local numSlots, _ = GetNumBankSlots() + + for i = 1, numSlots + 1 do + if i == 1 then + t[i] = -1 + else + t[i] = i + 3 + end + end + + return t + + elseif cnt == "guildbank" then + for i = 1, GetNumGuildBankTabs() do + local canView, canDeposit, stacksPerDay = GetGuildBankTabInfo(i); + if canView and canDeposit and stacksPerDay then + t[i] = i + end + end + + return t + elseif cnt == "bags" then + for i = 1, NUM_BAG_SLOTS + 1 do t[i] = i - 1 + end + return t + end +end + +local function GetEmptySlots(container) + local emptySlots = {} + for i, bag in ipairs(getContainerTable(container)) do + if TSM.getContainerNumSlotsDest(bag) > 0 then + for slot = 1, TSM.getContainerNumSlotsDest(bag) do + if not TSM.getContainerItemIDDest(bag, slot) then + if not emptySlots[bag] then emptySlots[bag] = {} + end + table.insert(emptySlots[bag], slot) + end + end + end + end + return emptySlots +end + +local function GetEmptySlotCount(bag) + local count = 0 + for slot = 1, TSM.getContainerNumSlotsDest(bag) do + if not TSM.getContainerItemLinkDest(bag, slot) then + count = count + 1 + end + end + if count ~= 0 then + return count + else + return false + end +end + +local function canGoInBag(itemString, destTable) + local itemFamily = GetItemFamily(itemString) + local default + for _, bag in pairs(destTable) do + local bagFamily = GetItemFamily(GetBagName(bag)) or 0 + if itemFamily and bagFamily and bagFamily > 0 and bit.band(itemFamily, bagFamily) > 0 then + if GetEmptySlotCount(bag) then + return bag + end + elseif bagFamily == 0 then + if GetEmptySlotCount(bag) then + if not default then + default = bag + end + end + end + end + return default +end + +local function findExistingStack(itemLink, dest, quantity, gbank) + for i, bag in ipairs(getContainerTable(dest)) do + if gbank then + if bag == GetCurrentGuildBankTab() then + for slot = 1, TSM.getContainerNumSlotsDest(bag) do + if TSM.getContainerItemIDDest(bag, slot) == TSMAPI:GetBaseItemString(itemLink, true) then + local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemLink)) + local _, currentQuantity = TSM.getContainerItemIDDest(bag, slot) + if currentQuantity and (currentQuantity + quantity) <= maxStack then + return bag, slot + end + end + end + end + else + for slot = 1, TSM.getContainerNumSlotsDest(bag) do + if TSM.getContainerItemIDDest(bag, slot) == TSMAPI:GetBaseItemString(itemLink, true) then + local maxStack = select(8, TSMAPI:GetSafeItemInfo(itemLink)) + local _, currentQuantity = TSM.getContainerItemIDDest(bag, slot) + if currentQuantity and (currentQuantity + quantity) <= maxStack then + return bag, slot + end + end + end + end + end +end + +local function getTotalItems(src) + local results = {} + if src == "bank" then + for _, _, itemString, quantity in TSMAPI:GetBankIterator(true, true) do + results[itemString] = (results[itemString] or 0) + quantity + end + + return results + elseif src == "guildbank" then + for bag = 1, GetNumGuildBankTabs() do + for slot = 1, MAX_GUILDBANK_SLOTS_PER_TAB or 98 do + local link = GetGuildBankItemLink(bag, slot) + local itemString = TSMAPI:GetBaseItemString(link, true) + if itemString then + local quantity = select(2, GetGuildBankItemInfo(bag, slot)) + results[itemString] = (results[itemString] or 0) + quantity + end + end + end + + return results + elseif src == "bags" then + for _, _, itemString, quantity in TSMAPI:GetBagIterator(true, true) do + results[itemString] = (results[itemString] or 0) + quantity + end + + return results + end +end + +function TSM.generateMoves(includeSoulbound) + if not TSM:areBanksVisible() then + wipe(splitMoves) + wipe(fullMoves) + for _, callback in ipairs(callbackMsg) do + callback(L["Cancelled - You must be at a bank or guildbank"]) + end + wipe(callbackMsg) + return + end + + local next = next + local bagsFull, bankFull = false, false + local bagMoves, bankMoves = {}, {} + wipe(splitMoves) + wipe(fullMoves) + + local currentBagState = getTotalItems("bags") + + for itemString, quantity in pairs(bagState) do + local currentQty = currentBagState[itemString] or 0 + if quantity < currentQty then + bagMoves[itemString] = currentQty - quantity + elseif quantity > currentQty then + bankMoves[itemString] = quantity - currentQty + end + end + + if next(bagMoves) ~= nil then -- generate moves from bags to bank + setSrcBagFunctions("bags") + setDestBagFunctions(bankType) + for item, _ in pairs(bagMoves) do + for i, bag in ipairs(getContainerTable("bags")) do + for slot = 1, TSM.getContainerNumSlotsSrc(bag) do + local itemLink = TSM.getContainerItemLinkSrc(bag, slot) + local itemString = TSMAPI:GetBaseItemString(itemLink, true) + if itemString and itemString == item then + if not TSMAPI:IsSoulbound(bag, slot) or includeSoulbound then + local have = TSM.getContainerItemQty(bag, slot) + local need = bagMoves[itemString] + if have and need then + -- check if the source item stack can fit into a destination bag + local destBag + if bankType == "guildbank" then + destBag = findExistingStack(itemLink, bankType, min(have, need), true) + if not destBag then + if GetEmptySlotCount(GetCurrentGuildBankTab()) ~= false then + destBag = GetCurrentGuildBankTab() + end + end + else + destBag = findExistingStack(itemLink, bankType, min(have, need)) + if not destBag then + if next(GetEmptySlots(bankType)) ~= nil then + destBag = canGoInBag(itemString, getContainerTable(bankType)) + end + end + end + if destBag then + if have > need then + tinsert(splitMoves, { src = "bags", bag = bag, slot = slot, quantity = need }) + bagMoves[itemString] = nil + else + tinsert(fullMoves, { src = "bags", bag = bag, slot = slot, quantity = have }) + bagMoves[itemString] = bagMoves[itemString] - have + if bagMoves[itemString] <= 0 then + bagMoves[itemString] = nil + end + end + else + bankFull = true + end + end + end + end + end + end + end + end + + + if next(bankMoves) ~= nil then -- generate moves from bank to bags + setSrcBagFunctions(bankType) + setDestBagFunctions("bags") + for item, _ in pairs(bankMoves) do + for i, bag in ipairs(getContainerTable(bankType)) do + for slot = 1, TSM.getContainerNumSlotsSrc(bag) do + local itemLink = TSM.getContainerItemLinkSrc(bag, slot) + local itemString = TSMAPI:GetBaseItemString(itemLink, true) + if itemString and itemString == item then + local have = TSM.getContainerItemQty(bag, slot) + local need = bankMoves[itemString] + if have and need then + if not TSMAPI:IsSoulbound(bag, slot) or includeSoulbound then + local destBag = findExistingStack(itemLink, "bags", min(have, need)) or canGoInBag(itemString, getContainerTable("bags")) + if destBag then + if have > need then + tinsert(splitMoves, { src = bankType, bag = bag, slot = slot, quantity = need }) + bankMoves[itemString] = nil + else + tinsert(fullMoves, { src = bankType, bag = bag, slot = slot, quantity = have }) + bankMoves[itemString] = bankMoves[itemString] - have + if bankMoves[itemString] <= 0 then + bankMoves[itemString] = nil + end + end + else + bagsFull = true + end + end + end + end + end + end + end + end + + if next(fullMoves) ~= nil then + if bankType == "guildbank" then + TSMAPI:CreateTimeDelay("moveItem", 0.05, TSM.moveItem, 0.35) + else + TSMAPI:CreateTimeDelay("moveItem", 0.05, TSM.moveItem, 0.05) + end + elseif next(splitMoves) ~= nil then + if bankType == "guildbank" then + TSMAPI:CreateTimeDelay("moveSplitItem", 0.05, TSM.moveSplitItem, 0.75) + else + TSMAPI:CreateTimeDelay("moveSplitItem", 0.05, TSM.moveSplitItem, 0.4) + end + else + if bagsFull and not bankFull then + for _, callback in ipairs(callbackMsg) do + callback(L["Cancelled - Bags are full"]) + end + elseif bankFull and not bagsFull then + for _, callback in ipairs(callbackMsg) do + if bankType == "guildbank" then + callback(L["Cancelled - Guildbank is full"]) + elseif bankType == "bank" then + callback(L["Cancelled - Bank is full"]) + else + callback("Cancelled - " .. bankType .. " is full") + end + end + elseif bagsFull and bankFull then + for _, callback in ipairs(callbackMsg) do + if bankType == "guildbank" then + callback(L["Cancelled - Bags and guildbank are full"]) + elseif bankType == "bank" then + callback(L["Cancelled - Bags and bank are full"]) + else + callback("Cancelled - Bags and " .. bankType .. " are full") + end + end + else + for _, callback in ipairs(callbackMsg) do + callback(L["Done"]) + end + end + wipe(callbackMsg) + end +end + +function TSM.moveItem() + if not TSM:areBanksVisible() then + wipe(fullMoves) + wipe(splitMoves) + TSMAPI:CancelFrame("moveItem") + for _, callback in ipairs(callbackMsg) do + callback(L["Cancelled - You must be at a bank or guildbank"]) + end + wipe(callbackMsg) + return + end + + local next = next + if #fullMoves > 0 then + local i = next(fullMoves) + if fullMoves[i].src == "bags" then + setSrcBagFunctions("bags") + setDestBagFunctions(bankType) + local itemString = TSMAPI:GetBaseItemString(TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot), true) + local itemLink = TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot) + local have = TSM.getContainerItemQty(fullMoves[i].bag, fullMoves[i].slot) + local need = fullMoves[i].quantity + if have and need then + if bankType == "guildbank" then + if findExistingStack(itemLink, bankType, need, true) then + TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot) + elseif GetEmptySlotCount(GetCurrentGuildBankTab()) then + TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot) + else + TSMAPI:CancelFrame("moveItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves) + end + else + if findExistingStack(itemLink, bankType, need) then + TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot) + elseif next(GetEmptySlots(bankType)) ~= nil and canGoInBag(itemString, getContainerTable(bankType)) then + TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot) + else + TSMAPI:CancelFrame("moveItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves) + end + end + end + else + setSrcBagFunctions(bankType) + setDestBagFunctions("bags") + local itemString = TSMAPI:GetBaseItemString(TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot), true) + local itemLink = TSM.getContainerItemLinkSrc(fullMoves[i].bag, fullMoves[i].slot) + local have = TSM.getContainerItemQty(fullMoves[i].bag, fullMoves[i].slot) + local need = fullMoves[i].quantity + if have and need then + if findExistingStack(itemLink, "bags", need) then + if bankType == "guildbank" then + if GetCurrentGuildBankTab() ~= fullMoves[i].bag then + SetCurrentGuildBankTab(fullMoves[i].bag) + end + end + TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot) + elseif next(GetEmptySlots("bags")) ~= nil and canGoInBag(itemString, getContainerTable("bags")) then + if bankType == "guildbank" then + if GetCurrentGuildBankTab() ~= fullMoves[i].bag then + SetCurrentGuildBankTab(fullMoves[i].bag) + end + end + TSM.autoStoreItem(fullMoves[i].bag, fullMoves[i].slot) + else + TSMAPI:CancelFrame("moveItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves) + end + end + end + tremove(fullMoves, i) + else + TSMAPI:CancelFrame("moveItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves) + end +end + +function TSM.moveSplitItem() + if not TSM:areBanksVisible() then + wipe(fullMoves) + wipe(splitMoves) + TSMAPI:CancelFrame("moveSplitItem") + for _, callback in ipairs(callbackMsg) do + callback(L["Cancelled - You must be at a bank or guildbank"]) + end + wipe(callbackMsg) + return + end + local next = next + --if next(moves) ~= nil then + if #splitMoves > 0 then + local i = next(splitMoves) + if splitMoves[i].src == "bags" then + setSrcBagFunctions("bags") + setDestBagFunctions(bankType) + local itemLink = TSM.getContainerItemLinkSrc(splitMoves[i].bag, splitMoves[i].slot) + local itemString = TSMAPI:GetBaseItemString(itemLink, true) + local have = TSM.getContainerItemQty(splitMoves[i].bag, splitMoves[i].slot) + local need = splitMoves[i].quantity + if have and need then + local destBag, destSlot + destBag, destSlot = findExistingStack(itemLink, bankType, need) + if destBag and destSlot then + TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need) + TSM.pickupContainerItemDest(destBag, destSlot) + else + local emptyBankSlots = GetEmptySlots(bankType) + destBag = canGoInBag(itemString, getContainerTable(bankType)) + if emptyBankSlots[destBag] then + destSlot = emptyBankSlots[destBag][1] + end + if destBag and destSlot then + if bankType == "guildbank" then + if GetCurrentGuildBankTab() ~= destBag then + SetCurrentGuildBankTab(destBag) + end + end + if GetEmptySlotCount(destBag) then + TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need) + TSM.pickupContainerItemDest(destBag, destSlot) + else + TSMAPI:CancelFrame("moveSplitItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves) + end + else + if next(GetEmptySlots(bankType)) ~= nil then + TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need) + TSM.pickupContainerItemDest(destBag, destSlot) + else + TSMAPI:CancelFrame("moveSplitItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.4, TSM.generateMoves) + end + end + end + else + TSMAPI:CancelFrame("moveSplitItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves) + end + else + setSrcBagFunctions(bankType) + setDestBagFunctions("bags") + local itemLink = TSM.getContainerItemLinkSrc(splitMoves[i].bag, splitMoves[i].slot) + local itemString = TSMAPI:GetBaseItemString(itemLink, true) + local have = TSM.getContainerItemQty(splitMoves[i].bag, splitMoves[i].slot) + local need = splitMoves[i].quantity + if have and need then + local destBag, destSlot + destBag, destSlot = findExistingStack(itemLink, "bags", need) + if destBag and destSlot then + TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need) + TSM.pickupContainerItemDest(destBag, destSlot) + else + local emptyBagSlots = GetEmptySlots("bags") + destBag = canGoInBag(itemString, getContainerTable("bags")) + if emptyBagSlots[destBag] then + destSlot = emptyBagSlots[destBag][1] + end + if destBag and destSlot then + if bankType == "guildbank" then + if GetCurrentGuildBankTab() ~= splitMoves[i].bag then + SetCurrentGuildBankTab(splitMoves[i].bag) + end + end + TSM.splitContainerItemSrc(splitMoves[i].bag, splitMoves[i].slot, need) + TSM.pickupContainerItemDest(destBag, destSlot) + end + end + else + TSMAPI:CancelFrame("moveSplitItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves) + end + end + tremove(splitMoves, i) + else + TSMAPI:CancelFrame("moveSplitItem") + TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves) + end +end + +function TSMAPI:MoveItems(requestedItems, callback, includeSoulbound) + wipe(bagState) + + if callback then + assert(type(callback) == "function", format("Expected function, got %s.", type(callback))) + tinsert(callbackMsg, callback) + end + + + bagState = getTotalItems("bags") -- create initial bagstate + + -- iterates over the requested items and adjusts bagState quantities , negative removes from bagState, positive adds to bagState + -- this gives the final states to generate the moves from + for itemString, qty in pairs(requestedItems) do + if not bagState[itemString] then bagState[itemString] = 0 + end + bagState[itemString] = bagState[itemString] + qty + if bagState[itemString] < 0 then + bagState[itemString] = 0 + end + end + + TSMAPI:CreateTimeDelay("generateMoves", 0.2, TSM.generateMoves(includeSoulbound)) +end + +function TSM:areBanksVisible() + if BagnonFrameguildbank and BagnonFrameguildbank:IsVisible() then + return true + elseif BagnonFramebank and BagnonFramebank:IsVisible() then + return true + elseif GuildBankFrame and GuildBankFrame:IsVisible() then + return true + elseif BankFrame and BankFrame:IsVisible() then + return true + elseif (ARKINV_Frame4 and ARKINV_Frame4:IsVisible()) or (ARKINV_Frame3 and ARKINV_Frame3:IsVisible()) then + return true + elseif (BagginsBag8 and BagginsBag8:IsVisible()) or (BagginsBag9 and BagginsBag9:IsVisible()) or (BagginsBag10 and BagginsBag10:IsVisible()) or (BagginsBag11 and BagginsBag11:IsVisible()) or (BagginsBag12 and BagginsBag12:IsVisible()) then + return true + elseif (CombuctorFrame2 and CombuctorFrame2:IsVisible()) then + return true + elseif (BaudBagContainer2_1 and BaudBagContainer2_1:IsVisible()) then + return true + elseif (AdiBagsContainer2 and AdiBagsContainer2:IsVisible()) then + return true + elseif (OneBankFrame and OneBankFrame:IsVisible()) then + return true + elseif (EngBank_frame and EngBank_frame:IsVisible()) then + return true + elseif (TBnkFrame and TBnkFrame:IsVisible()) then + return true + elseif (famBankFrame and famBankFrame:IsVisible()) then + return true + elseif (LUIBank and LUIBank:IsVisible()) then + return true + elseif (ElvUI_BankContainerFrame and ElvUI_BankContainerFrame:IsVisible()) then + return true + elseif (TukuiBank and TukuiBank:IsShown()) then + return true + elseif (AdiBagsContainer1 and AdiBagsContainer1.isBank and AdiBagsContainer1:IsVisible()) or (AdiBagsContainer2 and AdiBagsContainer2.isBank and AdiBagsContainer2:IsVisible()) then + return true + elseif BagsFrameBank and BagsFrameBank:IsVisible() then + return true + elseif AspUIBank and AspUIBank:IsVisible() then + return true + elseif NivayacBniv_Bank and NivayacBniv_Bank:IsVisible() then + return true + elseif DufUIBank and DufUIBank:IsVisible() then + return true + end + return nil +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Options.lua b/TradeSkillMaster/Core/Options.lua new file mode 100644 index 0000000..1b53c72 --- /dev/null +++ b/TradeSkillMaster/Core/Options.lua @@ -0,0 +1,1183 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains all the code for the stuff that shows under the "Status" icon in the main TSM window. + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries + +local lib = TSMAPI +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Options_private") + +local presetThemes = { + light = { L["Light (by Ravanys - The Consortium)"], "inlineColors{link{49,56,133,1}link2{153,255,255,1}category{36,106,36,1}category2{85,180,8,1}}textColors{iconRegion{enabled{105,105,105,1}}title{enabled{49,56,85,1}}label{enabled{45,44,40,1}disabled{150,148,140,1}}text{enabled{245,244,240,1}disabled{95,98,90,1}}link{enabled{49,56,133,1}}}fontSizes{normal{15}medium{13}small{12}}edgeSize{1.5}frameColors{frameBG{backdrop{219,219,219,1}border{30,30,30,1}}content{backdrop{60,60,60,1}border{40,40,40,1}}frame{backdrop{228,228,228,1}border{199,199,199,1}}}" }, + goblineer = { L["Goblineer (by Sterling - The Consortium)"], "inlineColors{link{153,255,255,1}link2{153,255,255,1}category{36,106,36,1}category2{85,180,8,1}}textColors{iconRegion{enabled{249,255,247,1}}title{enabled{132,219,9,1}}label{enabled{216,225,211,1}disabled{150,148,140,1}}text{enabled{255,254,250,1}disabled{147,151,139,1}}link{enabled{49,56,133,1}}}fontSizes{normal{15}medium{13}small{12}}edgeSize{1.5}frameColors{frameBG{backdrop{24,24,24,0.93}border{30,30,30,1}}content{backdrop{42,42,42,1}border{0,0,0,0}}frame{backdrop{24,24,24,1}border{255,255,255,0.03}}}" }, + jaded = { L["Jaded (by Ravanys - The Consortium)"], "frameColors{frameBG{backdrop{0,0,0,0.6}border{0,0,0,0.4}}content{backdrop{62,62,62,1}border{72,72,72,1}}frame{backdrop{32,32,32,1}border{2,2,2,0.48}}}textColors{text{enabled{99,219,136,1}disabled{95,98,90,1}}iconRegion{enabled{43,255,156,1}}title{enabled{75,255,150,1}}label{enabled{99,219,136,1}disabled{177,176,168,1}}}edgeSize{1}fontSizes{normal{15}medium{13}small{12}}" }, + tsmdeck = { L["TSMDeck (by Jim Younkin - Power Word: Gold)"], "inlineColors{link2{153,255,255,1}category2{85,180,8,1}link{89,139,255,1}category{80,222,22,1}}textColors{iconRegion{enabled{117,117,122,1}}title{enabled{247,248,255,1}}label{enabled{238,249,237,1}disabled{110,110,110,1}}text{enabled{245,240,251,1}disabled{115,115,115,1}}link{enabled{49,56,133,1}}}fontSizes{normal{14}medium{13}small{12}}edgeSize{1}frameColors{frameBG{backdrop{29,29,29,1}border{20,20,20,1}}content{backdrop{27,27,27,1}border{67,67,65,1}}frame{backdrop{39,39,40,1}border{20,20,20,1}}}" }, + tsmclassic = { L["TSM Classic (by Jim Younkin - Power Word: Gold)"], "inlineColors{link{89,139,255,1}link2{153,255,255,1}category{80,222,22,1}category2{85,180,8,1}}textColors{text{enabled{245,240,251,1}disabled{115,115,115,1}}iconRegion{enabled{216,216,224,1}}title{enabled{247,248,255,1}}label{enabled{238,249,237,1}disabled{110,110,110,1}}}fontSizes{normal{14}medium{13}small{12}}edgeSize{1}frameColors{frameBG{backdrop{8,8,8,1}border{4,2,147,1}}content{backdrop{18,18,18,1}border{102,108,105,1}}frame{backdrop{2,2,2,1}border{4,2,147,1}}}" }, + functional = { L["The Functional Gold Maker (by Xsinthis - The Golden Crusade)"], "inlineColors{category{3,175,222,1}link2{153,255,255,1}tooltip{130,130,250}link{89,139,255,1}category2{6,24,180,1}}textColors{iconRegion{enabled{216,216,224,1}}title{enabled{247,248,255,1}}label{enabled{238,249,237,1}disabled{110,110,110,1}}text{enabled{245,240,251,1}disabled{115,115,115,1}}link{enabled{49,56,133,1}}}fontSizes{normal{14}small{12}}edgeSize{0.5}frameColors{frameBG{backdrop{28,28,28,1}border{74,5,0,1}}content{backdrop{18,18,18,0.64000001549721}border{84,7,3,1}}frame{backdrop{2,2,2,0.48000001907349}border{72,9,4,1}}}" }, +} +local defaultTheme = presetThemes.goblineer + +function private:LoadHelpPage(parent) + local color = lib.Design:GetInlineColor("link") + local moduleText = { + TSMAPI.Design:ColorText("Accounting", "link") .. " - " .. L["Keeps track of all your sales and purchases from the auction house allowing you to easily track your income and expenditures and make sure you're turning a profit."] .. "\n", + TSMAPI.Design:ColorText("Additions", "link") .. " - " .. L["Provides extra functionality that doesn't fit well in other modules."] .. "\n", + TSMAPI.Design:ColorText("AuctionDB", "link") .. " - " .. L["Performs scans of the auction house and calculates the market value of items as well as the minimum buyout. This information can be shown in items' tooltips as well as used by other modules."] .. "\n", + TSMAPI.Design:ColorText("Auctioning", "link") .. " - " .. L["Posts and cancels your auctions to / from the auction house according to pre-set rules. Also, this module can show you markets which are ripe for being reset for a profit."] .. "\n", + TSMAPI.Design:ColorText("Crafting", "link") .. " - " .. L["Allows you to build a queue of crafts that will produce a profitable, see what materials you need to obtain, and actually craft the items."] .. "\n", + TSMAPI.Design:ColorText("Destroying", "link") .. " - " .. L["Mills, prospects, and disenchants items at super speed!"] .. "\n", + TSMAPI.Design:ColorText("ItemTracker", "link") .. " - " .. L["Tracks and manages your inventory across multiple characters including your bags, bank, and guild bank."] .. "\n", + TSMAPI.Design:ColorText("Mailing", "link") .. " - " .. L["Allows you to quickly and easily empty your mailbox as well as automatically send items to other characters with the single click of a button."] .. "\n", + TSMAPI.Design:ColorText("Shopping", "link") .. " - " .. L["Provides interfaces for efficiently searching for items on the auction house. When an item is found, it can easily be bought, canceled (if it's yours), or even posted from your bags."] .. "\n", + TSMAPI.Design:ColorText("Warehousing", "link") .. " - " .. L["Manages your inventory by allowing you to easily move stuff between your bags, bank, and guild bank."] .. "\n", + --TSMAPI.Design:ColorText("WoWuction", "link") .. " - " .. L["Allows you to use data from http://wowuction.com in other TSM modules and view its various price points in your item tooltips."] .. "\n", + } + + local page = { + { + type = "ScrollFrame", + layout = "flow", + children = { + -- { + -- type = "InlineGroup", + -- title = L["Resources:"], + -- layout = "flow", + -- relativeWidth = 1, + -- noBorder = true, + -- children = { + -- { + -- type = "Label", + -- relativeWidth = .499, + -- text = L["Using our website you can get help with TSM, suggest features, and give feedback."].."\n", + -- }, + -- { + -- type = "Image", + -- sizeRatio = .15625, + -- relativeWidth = .5, + -- image = "Interface\\Addons\\TradeSkillMaster\\Media\\banner", + -- }, + -- { + -- type = "HeadingLine" + -- }, + -- { + -- type = "Image", + -- sizeRatio = .15628, + -- relativeWidth = 1, + -- image = "Interface\\Addons\\TradeSkillMaster\\Media\\AppBanner", + -- }, + -- { + -- type = "Label", + -- relativeWidth = 1, + -- text = format("\n" .. L["Check out our completely free, desktop application which has tons of features including deal notification emails, automatic updating of AuctionDB and WoWuction prices, automatic TSM setting backup, and more! You can find this app by going to %s."], TSMAPI.Design:ColorText("http://tradeskillmaster.com/tsm_app", "link")), + -- } + -- }, + -- }, + -- { + -- type = "Spacer", + -- }, + { + type = "InlineGroup", + title = L["Module Information:"], + layout = "List", + relativeWidth = 1, + noBorder = true, + children = {}, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["TradeSkillMaster Team"], + children = { + { + type = "Label", + text = TSMAPI.Design:ColorText(L["Lead Developer and Co-Founder:"], "link") .. " Sapu94 [US-Tichondrius(H)]", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:ColorText(L["Application and Addon Developer:"], "link") .. " Bart39 [EU-Darkspear(A)]", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:ColorText(L["Web Master:"], "link") .. " Drethic [US-Sentinels(A)]", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:ColorText("Logo / Graphic Designer:", "link") .. " Pwnstein", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:ColorText(L["Testers (Special Thanks):"], "link") .. " Cryan, GoblinRaset, Mithrildar, PhatLewts, WoWProfitz", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:ColorText(L["Past Contributors:"], "link") .. " Geemoney, Mischanix, Xubera, cduhn, cjo20", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:ColorText(L["Co-Founder:"], "link") .. " Cente [US-Illidan(H)]", + relativeWidth = 1, + }, + }, + }, + }, + }, + } + + for _, text in ipairs(moduleText) do + tinsert(page[1].children[#page[1].children-1].children, { + type = "Label", + text = text, + relativeWidth = 1, + }) + end + + lib:BuildPage(parent, page) +end + +local function GetSubStr(str) + if not str then return end + local startIndex, endIndex + local balance = 0 + + for i = 1, #str do + local c = strsub(str, i, i) + if c == '{' then + if startIndex then + balance = balance + 1 + else + startIndex = i + end + elseif c == '}' then + if balance > 0 then + balance = balance - 1 + else + endIndex = i + break + end + end + end + + if not startIndex or not endIndex then return end + return strsub(str, startIndex + 1, endIndex - 1), startIndex, endIndex +end + +local function StringToTable(data) + local result = {} + while true do + local value, s, e = GetSubStr(data, { '{', '}' }) + if not value then return end + local key = strsub(data, 1, s - 1) + value = tonumber(value) or value + + if type(value) == "string" and strfind(value, "{") then + value = StringToTable(value) + elseif type(value) == "string" and strfind(value, ",") then + value = { (","):split(value) } + for i = 1, 4 do + value[i] = tonumber(value[i]) + end + end + + if type(value) == "nil" then + return + end + + result[key] = value + if e + 1 > #data then + break + end + data = strsub(data, e + 1, #data) + end + return result +end + +function TSM:SetDesignDefaults(src, dest) + for i, v in pairs(src) do + if dest[i] then + if type(v) == "table" then + TSM:SetDesignDefaults(v, dest[i]) + end + else + if type(v) == "table" then + dest[i] = CopyTable(v) + else + dest[i] = v + end + end + end +end + +local function DecodeAppearanceData(encodedData) + if not encodedData then return end + encodedData = gsub(encodedData, " ", "") + + local result = StringToTable(encodedData, 1) + if not result then return TSM:Print(L["Invalid appearance data."]) end + TSM.db.profile.design = result + TSM:SetDesignDefaults(TSM.designDefaults, TSM.db.profile.design) + TSMAPI:UpdateDesign() +end + +function TSM:LoadDefaultDesign() + DecodeAppearanceData(defaultTheme[2]) +end + +local function ShowImportFrame() + local data + + local f = AceGUI:Create("TSMWindow") + f:SetCallback("OnClose", function(self) AceGUI:Release(self) end) + f:SetTitle("TradeSkillMaster - " .. L["Import Appearance Settings"]) + f:SetLayout("Flow") + f:SetHeight(200) + f:SetHeight(300) + + local spacer = AceGUI:Create("Label") + spacer:SetFullWidth(true) + spacer:SetText(" ") + f:AddChild(spacer) + + local btn = AceGUI:Create("TSMButton") + + local eb = AceGUI:Create("MultiLineEditBox") + eb:SetLabel(L["Appearance Data"]) + eb:SetFullWidth(true) + eb:SetMaxLetters(0) + eb:SetCallback("OnEnterPressed", function(_, _, val) btn:SetDisabled(false) data = val end) + f:AddChild(eb) + + btn:SetDisabled(true) + btn:SetText(L["Import Appearance Settings"]) + btn:SetFullWidth(true) + btn:SetCallback("OnClick", function() DecodeAppearanceData(data) f:Hide() end) + f:AddChild(btn) + + f.frame:SetFrameStrata("FULLSCREEN_DIALOG") + f.frame:SetFrameLevel(100) +end + +local function TblToStr(tbl) + local tmp = {} + for key, value in pairs(tbl) do + tinsert(tmp, key .. "{") + if tonumber(value) then + tinsert(tmp, value) + elseif #value == 0 then + tinsert(tmp, TblToStr(value)) + else + for _, colorVal in ipairs(value) do + tinsert(tmp, tostring(colorVal)) + tinsert(tmp, ",") + end + tremove(tmp, #tmp) + end + tinsert(tmp, "}") + end + return table.concat(tmp, "") +end + +local function EncodeAppearanceData() + local keys = { "frameColors", "textColors", "inlineColors", "edgeSize", "fontSizes" } + local testTbl = {} + for _, key in ipairs(keys) do + testTbl[key] = TSM.db.profile.design[key] + end + return TblToStr(testTbl) +end + +local function ShowExportFrame() + local f = AceGUI:Create("TSMWindow") + f:SetCallback("OnClose", function(self) AceGUI:Release(self) end) + f:SetTitle("TradeSkillMaster - " .. L["Export Appearance Settings"]) + f:SetLayout("Fill") + f:SetHeight(300) + + local eb = AceGUI:Create("TSMMultiLineEditBox") + eb:SetLabel(L["Appearance Data"]) + eb:SetMaxLetters(0) + eb:SetText(EncodeAppearanceData()) + f:AddChild(eb) + + f.frame:SetFrameStrata("FULLSCREEN_DIALOG") + f.frame:SetFrameLevel(100) +end + +function private:LoadOptionsPage(parent) + local presetThemeList = {} + for key, tbl in pairs(presetThemes) do + presetThemeList[key] = tbl[1] + end + local savedThemeList = {} + for _, info in ipairs(TSM.db.profile.savedThemes) do + tinsert(savedThemeList, info.name) + end + local themeName = "" + + local auctionTabs, auctionTabOrder + if AuctionFrame and AuctionFrame.numTabs then + auctionTabs, auctionTabOrder = {}, {} + for i = 1, AuctionFrame.numTabs do + local text = gsub(_G["AuctionFrameTab" .. i]:GetText(), "|r", "") + text = gsub(text, "|c[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]", "") + auctionTabs[text] = text + tinsert(auctionTabOrder, text) + end + end + + local characterList = {} + for character in pairs(TSMAPI:GetCharacters()) do + tinsert(characterList, character) + end + + local chatFrameList = {} + local chatFrameValue, defaultValue + for i=1, NUM_CHAT_WINDOWS do + if DEFAULT_CHAT_FRAME == _G["ChatFrame"..i] then + defaultValue = i + end + local name = strlower(GetChatWindowInfo(i) or "") + if name ~= "" then + if name == TSM.db.global.chatFrame then + chatFrameValue = i + end + tinsert(chatFrameList, name) + end + end + chatFrameValue = chatFrameValue or defaultValue + + local page = { + { + type = "ScrollFrame", + layout = "flow", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["General Settings"], + children = { + { + type = "CheckBox", + label = L["Hide Minimap Icon"], + settingInfo = { TSM.db.profile.minimapIcon, "hide" }, + relativeWidth = 0.5, + callback = function(_, _, value) + if value then + TSM.LDBIcon:Hide("TradeSkillMaster") + else + TSM.LDBIcon:Show("TradeSkillMaster") + end + end, + }, + { + type = "CheckBox", + label = L["Color Group Names by Depth"], + settingInfo = { TSM.db.profile, "colorGroupName" }, + relativeWidth = 0.49, + tooltip = L["If checked, group names will be colored based on their subgroup depth in group trees."], + }, + { + type = "CheckBox", + label = L["Store Operations Globally"], + value = TSM.db.global.globalOperations, + relativeWidth = 0.5, + callback = function(_, _, value) + StaticPopupDialogs["TSM_GLOBAL_OPERATIONS"] = StaticPopupDialogs["TSM_GLOBAL_OPERATIONS"] or { + text = L["If you have multiple profile set up with operations, enabling this will cause all but the current profile's operations to be irreversibly lost. Are you sure you want to continue?"], + button1 = YES, + button2 = CANCEL, + timeout = 0, + hideOnEscape = true, + OnAccept = function() + TSM.db.global.globalOperations = value + if TSM.db.global.globalOperations then + -- move current profile to global + TSM.db.global.operations = CopyTable(TSM.db.profile.operations) + -- clear out old operations + for profile in TSMAPI:GetTSMProfileIterator() do + TSM.db.profile.operations = nil + end + else + -- move global to all profiles + for profile in TSMAPI:GetTSMProfileIterator() do + TSM.db.profile.operations = CopyTable(TSM.db.global.operations) + end + -- clear out old operations + TSM.db.global.operations = nil + end + TSM:UpdateModuleProfiles() + if parent.frame:IsVisible() then + parent:ReloadTab() + end + end, + preferredIndex = 3, + } + parent:ReloadTab() + TSMAPI:ShowStaticPopupDialog("TSM_GLOBAL_OPERATIONS") + end, + tooltip = L["If checked, operations will be stored globally rather than by profile. TSM groups are always stored by profile. Note that if you have multiple profiles setup already with separate operation information, changing this will cause all but the current profile's operations to be lost."], + }, + { + type = "Dropdown", + label = L["Forget Characters:"], + list = characterList, + relativeWidth = 0.49, + callback = function(_, _, value) + local name = characterList[value] + TSM.db.factionrealm.characters[name] = nil + TSM:Printf("%s removed.", name) + parent:ReloadTab() + end, + tooltip = L["If you delete, rename, or transfer a character off the current faction/realm, you should remove it from TSM's list of characters using this dropdown."], + }, + { + type = "Dropdown", + label = L["Default BankUI Tab"], + list = TSM:getBankTabs(), + settingInfo = { TSM.db.global, "bankUITab" }, + relativeWidth = 0.5, + tooltip = L["The default tab shown in the 'BankUI' frame."], + }, + { + type = "Dropdown", + label = L["Chat Tab"], + list = chatFrameList, + value = chatFrameValue, + callback = function(_, _, value) + TSM.db.global.chatFrame = chatFrameList[value] + end, + relativeWidth = 0.49, + tooltip = L["This option sets which tab TSM and its modules will use for printing chat messages."], + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Multi-Account Settings"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Various modules can sync their data between multiple accounts automatically whenever you're logged into both accounts."], + }, + { + type = "Spacer", + }, + { + type = "Label", + relativeWidth = 1, + text = L["First, log into a character on the same realm (and faction) on both accounts. Type the name of the OTHER character you are logged into in the box below. Once you have done this on both accounts, TSM will do the rest automatically. Once setup, syncing will automatically happen between the two accounts while on any character on the account (not only the one you entered during this setup)."], + }, + { + type = "EditBox", + relativeWidth = 1, + label = L["Character Name on Other Account"], + callback = function(_, _, value) + TSM:DoSyncSetup(value:trim()) + end, + tooltip = L["See instructions above this editbox."], + }, + { + type = "HeadingLine", + } + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Auction House Tab Settings"], + children = { + { + type = "Dropdown", + label = auctionTabs and L["Default Tab"] or L["Default Tab (Open Auction House to Enable)"], + list = auctionTabs or {}, + order = auctionTabOrder, + disabled = not auctionTabs, + settingInfo = { TSM.db.profile, "defaultAuctionTab" }, + relativeWidth = 0.5, + }, + { + type = "CheckBox", + label = L["Open All Bags with Auction House"], + settingInfo = { TSM.db.profile, "openAllBags" }, + relativeWidth = 0.5, + tooltip = L["If checked, your bags will be automatically opened when you open the auction house."], + }, + { + type = "HeadingLine", + }, + { + type = "CheckBox", + label = L["Show Bids in Auction Results Table (Requires Reload)"], + settingInfo = { TSM.db.profile, "showBids" }, + tooltip = L["If checked, all tables listing auctions will display the bid as well as the buyout of the auctions. This will not take effect immediately and may require a reload."], + }, + { + type = "Slider", + label = L["Number of Auction Result Rows (Requires Reload)"], + settingInfo = { TSM.db.profile, "auctionResultRows" }, + relativeWidth = 0.5, + min = 8, + max = 25, + step = 1, + tooltip = L["Changes how many rows are shown in the auction results tables."], + }, + { + type = "HeadingLine", + }, + { + type = "CheckBox", + label = L["Make Auction Frame Movable"], + settingInfo = { TSM.db.profile, "auctionFrameMovable" }, + callback = function(_, _, value) + if AuctionFrame then + AuctionFrame:SetMovable(value) + end + end, + }, + { + type = "Slider", + label = L["Auction Frame Scale"], + settingInfo = { TSM.db.profile, "auctionFrameScale" }, + isPercent = true, + relativeWidth = 0.5, + min = 0.1, + max = 2, + step = 0.05, + callback = function(_, _, value) if AuctionFrame then AuctionFrame:SetScale(value) end end, + tooltip = L["Changes the size of the auction frame. The size of the detached TSM auction frame will always be the same as the main auction frame."], + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["TSM Appearance Options"], + children = { + { + type = "Label", + text = L["Use the options below to change and tweak the appearance of TSM."], + relativeWidth = 1, + }, + { + type = "Dropdown", + label = L["Import Preset TSM Theme"], + list = presetThemeList, + relativeWidth = 1, + callback = function(_, _, key) + if presetThemes[key] then + DecodeAppearanceData(presetThemes[key][2]) + end + end, + tooltip = L["Select a theme from this dropdown to import one of the preset TSM themes."], + }, + { + type = "Dropdown", + label = L["Load Saved Theme"], + list = savedThemeList, + relativeWidth = 1, + callback = function(_, _, index) + DecodeAppearanceData(TSM.db.profile.savedThemes[index].theme) + end, + tooltip = L["Select a theme from this dropdown to import one of your saved TSM themes."], + }, + { + type = "EditBox", + label = L["Theme Name"], + relativeWidth = 0.5, + callback = function(_, _, value) themeName = value:trim() end, + }, + { + type = "Button", + text = L["Save Theme"], + relativeWidth = 0.5, + callback = function(_, _, value) + if themeName == "" then + return TSM:Print(L["Theme name is empty."]) + end + TSM:Printf(L["Saved theme: %s."], themeName) + tinsert(TSM.db.profile.savedThemes, { name = themeName, theme = EncodeAppearanceData() }) + parent:ReloadTab() + end, + }, + { + type = "Button", + text = L["Restore Default Colors"], + relativeWidth = 1, + callback = function() TSM:LoadDefaultDesign() parent:ReloadTab() end, + tooltip = L["Restores all the color settings below to their default values."], + }, + { + type = "Button", + text = L["Import Appearance Settings"], + relativeWidth = 0.5, + callback = ShowImportFrame, + tooltip = L["This allows you to import appearance settings which other people have exported."], + }, + { + type = "Button", + text = L["Export Appearance Settings"], + relativeWidth = 0.5, + callback = ShowExportFrame, + tooltip = L["This allows you to export your appearance settings to share with others."], + }, + { + type = "HeadingLine" + }, + }, + }, + }, + }, + } + + -- extra multi-account syncing widgets + for account, players in pairs(TSM.db.factionrealm.syncAccounts) do + local playerList = {} + for player in pairs(players) do + tinsert(playerList, player) + end + local widgets = { + { + type = "Button", + text = DELETE, + relativeWidth = 0.2, + callback = function() + TSM.db.factionrealm.syncAccounts[account] = nil + parent:ReloadTab() + end, + }, + { + type = "Label", + relativeWidth = 0.79, + text = table.concat(playerList, ", "), + } + } + for _, widget in ipairs(widgets) do + tinsert(page[1].children[2].children, widget) + end + end + + + local function expandColor(tbl) + return { tbl[1] / 255, tbl[2] / 255, tbl[3] / 255, tbl[4] } + end + + local function compressColor(r, g, b, a) + return { r * 255, g * 255, b * 255, a } + end + + local frameColorOptions = { + { L["Frame Background - Backdrop"], "frameBG", "backdrop" }, + { L["Frame Background - Border"], "frameBG", "border" }, + { L["Region - Backdrop"], "frame", "backdrop" }, + { L["Region - Border"], "frame", "border" }, + { L["Content - Backdrop"], "content", "backdrop" }, + { L["Content - Border"], "content", "border" }, + } + for _, optionInfo in ipairs(frameColorOptions) do + local label, key, subKey = unpack(optionInfo) + + local widget = { + type = "ColorPicker", + label = label, + relativeWidth = 0.5, + hasAlpha = true, + value = expandColor(TSM.db.profile.design.frameColors[key][subKey]), + callback = function(_, _, ...) + TSM.db.profile.design.frameColors[key][subKey] = compressColor(...) + TSMAPI:UpdateDesign() + end, + } + tinsert(page[1].children[4].children, widget) + end + + tinsert(page[1].children[4].children, { type = "HeadingLine" }) + + local textColorOptions = { + { L["Icon Region"], "iconRegion", "enabled" }, + { L["Title"], "title", "enabled" }, + { L["Label Text - Enabled"], "label", "enabled" }, + { L["Label Text - Disabled"], "label", "disabled" }, + { L["Content Text - Enabled"], "text", "enabled" }, + { L["Content Text - Disabled"], "text", "disabled" }, + } + for _, optionInfo in ipairs(textColorOptions) do + local label, key, subKey = unpack(optionInfo) + + local widget = { + type = "ColorPicker", + label = label, + relativeWidth = 0.5, + hasAlpha = true, + value = expandColor(TSM.db.profile.design.textColors[key][subKey]), + callback = function(_, _, ...) + TSM.db.profile.design.textColors[key][subKey] = compressColor(...) + TSMAPI:UpdateDesign() + end, + } + tinsert(page[1].children[4].children, widget) + end + + tinsert(page[1].children[4].children, { type = "HeadingLine" }) + + local inlineColorOptions = { + { L["Link Text (Requires Reload)"], "link" }, + { L["Link Text 2 (Requires Reload)"], "link2" }, + { L["Category Text (Requires Reload)"], "category" }, + { L["Category Text 2 (Requires Reload)"], "category2" }, + { L["Item Tooltip Text"], "tooltip" }, + { L["Advanced Option Text"], "advanced" }, + } + for _, optionInfo in ipairs(inlineColorOptions) do + local label, key = unpack(optionInfo) + + local widget = { + type = "ColorPicker", + label = label, + relativeWidth = 0.5, + hasAlpha = true, + value = expandColor(TSM.db.profile.design.inlineColors[key]), + callback = function(_, _, ...) + TSM.db.profile.design.inlineColors[key] = compressColor(...) + TSMAPI:UpdateDesign() + end, + } + tinsert(page[1].children[4].children, widget) + end + + tinsert(page[1].children[4].children, { type = "HeadingLine" }) + + local miscWidgets = { + { + type = "Slider", + relativeWidth = 0.5, + label = L["Small Text Size (Requires Reload)"], + min = 6, + max = 30, + step = 1, + settingInfo = { TSM.db.profile.design.fontSizes, "small" }, + }, + { + type = "Slider", + relativeWidth = 0.5, + label = L["Medium Text Size (Requires Reload)"], + min = 6, + max = 30, + step = 1, + settingInfo = { TSM.db.profile.design.fontSizes, "medium" }, + }, + { + type = "Slider", + relativeWidth = 0.5, + label = L["Normal Text Size (Requires Reload)"], + min = 6, + max = 30, + step = 1, + settingInfo = { TSM.db.profile.design.fontSizes, "normal" }, + }, + { + type = "Slider", + relativeWidth = 0.5, + label = L["Border Thickness (Requires Reload)"], + min = 0, + max = 3, + step = .1, + settingInfo = { TSM.db.profile.design, "edgeSize" }, + }, + } + for _, widget in ipairs(miscWidgets) do + tinsert(page[1].children[4].children, widget) + end + + lib:BuildPage(parent, page) +end + +function private:LoadProfilesPage(container) + -- Popup Confirmation Window used in this module + StaticPopupDialogs["TSMDeleteConfirm"] = StaticPopupDialogs["TSMDeleteConfirm"] or { + text = L["Are you sure you want to delete the selected profile?"], + button1 = ACCEPT, + button2 = CANCEL, + timeout = 0, + whileDead = true, + hideOnEscape = true, + OnCancel = false, + -- OnAccept defined later + } + StaticPopupDialogs["TSMCopyProfileConfirm"] = StaticPopupDialogs["TSMCopyProfileConfirm"] or { + text = L["Are you sure you want to overwrite the current profile with the selected profile?"], + button1 = ACCEPT, + button2 = CANCEL, + timeout = 0, + whileDead = true, + hideOnEscape = true, + OnCancel = false, + -- OnAccept defined later + } + + -- profiles page + local text = { + default = L["Default"], + intro = L["You can change the active database profile, so you can have different settings for every character."], + reset_desc = L["Reset the current profile back to its default values, in case your configuration is broken, or you simply want to start over."], + reset = L["Reset Profile"], + choose_desc = L["You can either create a new profile by entering a name in the editbox, or choose one of the already exisiting profiles."], + new = L["New"], + new_sub = L["Create a new empty profile."], + choose = L["Existing Profiles"], + copy_desc = L["Copy the settings from one existing profile into the currently active profile."], + copy = L["Copy From"], + delete_desc = L["Delete existing and unused profiles from the database to save space, and cleanup the SavedVariables file."], + delete = L["Delete a Profile"], + profiles = L["Profiles"], + current = L["Current Profile:"] .. " " .. TSMAPI.Design:ColorText(TSM.db:GetCurrentProfile(), "link"), + } + + -- Returns a list of all the current profiles with common and nocurrent modifiers. + -- This code taken from AceDBOptions-3.0.lua + local function GetProfileList(db, common, nocurrent) + local profiles = {} + local tmpprofiles = {} + local defaultProfiles = { ["Default"] = "Default" } + + -- copy existing profiles into the table + local currentProfile = db:GetCurrentProfile() + for i, v in pairs(db:GetProfiles(tmpprofiles)) do + if not (nocurrent and v == currentProfile) then + profiles[v] = v + end + end + + -- add our default profiles to choose from ( or rename existing profiles) + for k, v in pairs(defaultProfiles) do + if (common or profiles[k]) and not (nocurrent and k == currentProfile) then + profiles[k] = v + end + end + + return profiles + end + + local page = { + { + -- scroll frame to contain everything + type = "ScrollFrame", + layout = "Flow", + children = { + { + type = "Label", + text = text["intro"] .. "\n\n", + relativeWidth = 1, + }, + { + type = "Label", + text = text["reset_desc"], + relativeWidth = 1, + }, + { + type = "Button", + text = text["reset"], + relativeWidth = .5, + callback = function() TSM.db:ResetProfile() end, + }, + { + type = "Label", + text = text["current"], + relativeWidth = .49, + }, + { + type = "HeadingLine", + }, + { + type = "Label", + text = text["choose_desc"], + relativeWidth = 1, + }, + { + type = "EditBox", + label = text["new"], + value = "", + relativeWidth = .5, + callback = function(_, _, value) + value = value:trim() + if value == "" then + return TSM:Print(L["You cannot create a profile with an empty name."]) + end + TSM.db:SetProfile(value) + container:ReloadTab() + end, + }, + { + type = "Dropdown", + label = text["choose"], + list = GetProfileList(TSM.db, true, nil), + value = TSM.db:GetCurrentProfile(), + relativeWidth = .49, + callback = function(_, _, value) + if value ~= TSM.db:GetCurrentProfile() then + TSM.db:SetProfile(value) + container:ReloadTab() + end + end, + }, + { + type = "HeadingLine", + }, + { + type = "Label", + text = text["copy_desc"], + relativeWidth = 1, + }, + { + type = "Dropdown", + label = text["copy"], + list = GetProfileList(TSM.db, true, nil), + value = "", + disabled = not GetProfileList(TSM.db, true, nil) and true, + callback = function(_, _, value) + if value == TSM.db:GetCurrentProfile() then return end + StaticPopupDialogs["TSMCopyProfileConfirm"].OnAccept = function() + TSM.db:CopyProfile(value) + container:ReloadTab() + end + TSMAPI:ShowStaticPopupDialog("TSMCopyProfileConfirm") + end, + }, + { + type = "HeadingLine", + }, + { + type = "Label", + text = text["delete_desc"], + relativeWidth = 1, + }, + { + type = "Dropdown", + label = text["delete"], + list = GetProfileList(TSM.db, true, nil), + value = "", + disabled = not GetProfileList(TSM.db, true, nil) and true, + callback = function(_, _, value) + if TSM.db:GetCurrentProfile() == value then + TSM:Print(L["Cannot delete currently active profile!"]) + return + end + StaticPopupDialogs["TSMDeleteConfirm"].OnAccept = function() + TSM.db:DeleteProfile(value) + container:ReloadTab() + end + TSMAPI:ShowStaticPopupDialog("TSMDeleteConfirm") + end, + }, + }, + }, + } + + TSMAPI:BuildPage(container, page) +end + +local treeGroup +function private:LoadCustomPriceSources(parent) + private.treeGroup = AceGUI:Create("TSMTreeGroup") + private.treeGroup:SetLayout("Fill") + private.treeGroup:SetCallback("OnGroupSelected", private.SelectTree) + private.treeGroup:SetStatusTable(TSM.db.profile.customPriceSourceTreeStatus) + parent:AddChild(private.treeGroup) + + private:UpdateTree() + private.treeGroup:SelectByPath(1) +end + +function private:UpdateTree() + if not private.treeGroup then return end + + local children = {} + for name in pairs(TSM.db.global.customPriceSources) do + tinsert(children, {value=name, text=name}) + end + sort(children, function(a, b) return strlower(a.value) < strlower(b.value) end) + private.treeGroup:SetTree({{value=1, text=L["Sources"], children=children}}) +end + +function private.SelectTree(treeGroup, _, selection) + treeGroup:ReleaseChildren() + + selection = {("\001"):split(selection)} + if #selection == 1 then + private:DrawNewCustomPriceSource(treeGroup) + else + local name = selection[#selection] + private:DrawCustomPriceSourceOptions(treeGroup, name) + end +end + +function private:DrawNewCustomPriceSource(container) + local page = { + { -- scroll frame to contain everything + type = "ScrollFrame", + layout = "List", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["New Custom Price Source"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Custom price sources allow you to create more advanced custom prices throughout all of the TSM modules. Just as you can use the built-in price sources such as 'vendorsell' and 'vendorbuy' in your custom prices, you can use ones you make here (which themselves are custom prices)."], + }, + { + type = "HeadingLine", + }, + { + type = "EditBox", + label = L["Custom Price Source Name"], + relativeWidth = 0.8, + callback = function(self,_,value) + value = strlower((value or ""):trim()) + if value == "" then return end + if gsub(value, "([a-z]+)", "") ~= "" then + return TSM:Print(L["The name can ONLY contain letters. No spaces, numbers, or special characters."]) + end + if TSM.db.global.customPriceSources[value] then + return TSM:Printf(L["Error creating custom price source. Custom price source with name '%s' already exists."], value) + end + TSM.db.global.customPriceSources[value] = "" + private:UpdateTree() + if TSM.db.profile.gotoNewCustomPriceSource then + private.treeGroup:SelectByPath(1, value) + else + self:SetText() + self:SetFocus() + end + end, + tooltip = L["Give your new custom price source a name. This is what you will type in to custom prices and is case insensitive (everything will be saved as lower case)."].."\n\n"..TSMAPI.Design:ColorText(L["The name can ONLY contain letters. No spaces, numbers, or special characters."], "link"), + }, + { + type = "CheckBox", + label = L["Switch to New Custom Price Source After Creation"], + relativeWidth = 1, + settingInfo = {TSM.db.profile, "gotoNewCustomPriceSource"}, + }, + }, + }, + }, + }, + } + TSMAPI:BuildPage(container, page) +end + +function private:DrawCustomPriceSourceOptions(container, customPriceName) + local page = { + { -- scroll frame to contain everything + type = "ScrollFrame", + layout = "List", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["Custom Price Source"], + children = { + { + type = "Label", + relativeWidth = 1, + text = L["Below, set the custom price that will be evaluated for this custom price source."], + }, + { + type = "HeadingLine", + }, + { + type = "EditBox", + label = L["Custom Price for this Source"], + settingInfo = {TSM.db.global.customPriceSources, customPriceName}, + relativeWidth = 0.49, + acceptCustom = true, + tooltip = "", + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Management"], + children = { + { + type = "EditBox", + label = L["Rename Custom Price Source"], + value = operationName, + relativeWidth = 0.5, + callback = function(self,_,name) + name = strlower((name or ""):trim()) + if name == "" then return end + if gsub(name, "([a-z]+)", "") ~= "" then + return TSM:Print(L["The name can ONLY contain letters. No spaces, numbers, or special characters."]) + end + if TSM.db.global.customPriceSources[name] then + return TSM:Printf(L["Error renaming custom price source. Custom price source with name '%s' already exists."], name) + end + TSM.db.global.customPriceSources[name] = TSM.db.global.customPriceSources[customPriceName] + TSM.db.global.customPriceSources[customPriceName] = nil + private:UpdateTree() + private.treeGroup:SelectByPath(1, name) + end, + tooltip = L["Give your new custom price source a name. This is what you will type in to custom prices and is case insensitive (everything will be saved as lower case)."].."\n\n"..TSMAPI.Design:ColorText(L["The name can ONLY contain letters. No spaces, numbers, or special characters."], "link"), + }, + { + type = "Button", + text = L["Delete Custom Price Source"], + relativeWidth = 0.5, + callback = function() + TSM.db.global.customPriceSources[customPriceName] = nil + private:UpdateTree() + private.treeGroup:SelectByPath(1) + TSM:Printf(L["Removed '%s' as a custom price source. Be sure to update any custom prices that were using this source."], customPriceName) + end, + }, + }, + }, + }, + }, + } + TSMAPI:BuildPage(container, page) +end + + +function TSM:LoadOptions(parent) + local tg = AceGUI:Create("TSMTabGroup") + tg:SetLayout("Fill") + tg:SetFullWidth(true) + tg:SetFullHeight(true) + tg:SetTabs({ { value = 1, text = L["TSM Info / Help"] }, { value = 2, text = L["Options"] }, { value = 3, text = L["Profiles"] }, { value = 4, text = TSMAPI.Design:ColorText(L["Custom Price Sources"], "advanced") } }) + tg:SetCallback("OnGroupSelected", function(self, _, value) + tg:ReleaseChildren() + StaticPopup_Hide("TSM_GLOBAL_OPERATIONS") + + if value == 1 then + private:LoadHelpPage(self) + elseif value == 2 then + private:LoadOptionsPage(self) + elseif value == 3 then + private:LoadProfilesPage(self) + elseif value == 4 then + private:LoadCustomPriceSources(self) + end + end) + parent:AddChild(tg) + tg:SelectTab(1) +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Prices.lua b/TradeSkillMaster/Core/Prices.lua new file mode 100644 index 0000000..041d782 --- /dev/null +++ b/TradeSkillMaster/Core/Prices.lua @@ -0,0 +1,458 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains price related TSMAPI functions. + +local TSM = select(2, ...) +local moduleObjects = TSM.moduleObjects +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +TSM_PRICE_TEMP = {loopError=function(str) TSM:Printf(L["Loop detected in the following custom price:"].." "..TSMAPI.Design:GetInlineColor("link")..str.."|r") end} +local MONEY_PATTERNS = { + "([0-9]+g[ ]*[0-9]+s[ ]*[0-9]+c)", -- g/s/c + "([0-9]+g[ ]*[0-9]+s)", -- g/s + "([0-9]+g[ ]*[0-9]+c)", -- g/c + "([0-9]+s[ ]*[0-9]+c)", -- s/c + "([0-9]+g)", -- g + "([0-9]+s)", -- s + "([0-9]+c)", -- c +} +local MATH_FUNCTIONS = { + ["avg"] = "_avg", + ["min"] = "_min", + ["max"] = "_max", + ["first"] = "_first", + ["check"] = "_check", +} + +function TSMAPI:GetPriceSources() + local sources = {} + for _, obj in pairs(moduleObjects) do + if obj.priceSources then + for _, info in ipairs(obj.priceSources) do + sources[info.key] = info.label + end + end + end + return sources +end + +local itemValueKeyCache = {} +function TSMAPI:GetItemValue(link, key) + local itemLink = select(2, TSMAPI:GetSafeItemInfo(link)) or link + if not itemLink then return end + + -- look in module objects for this key + if itemValueKeyCache[key] then + local info = itemValueKeyCache[key] + return info.callback(itemLink, info.arg) + end + for _, obj in pairs(moduleObjects) do + if obj.priceSources then + for _, info in ipairs(obj.priceSources) do + if info.key == key then + itemValueKeyCache[key] = info + local value = info.callback(itemLink, info.arg) + return (type(value) == "number" and value > 0) and value or nil + end + end + end + end +end + + + +-- validates a price string that was passed into TSMAPI:ParseCustomPrice +local supportedOperators = { "+", "-", "*", "/" } +local function ParsePriceString(str, badPriceSource) + if tonumber(str) then + return function() return tonumber(str) end + end + + local origStr = str + + -- make everything lower case + str = strlower(str) + + + -- remove any colors around gold/silver/copper + str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])g|r", "g") + str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])s|r", "s") + str = gsub(str, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])c|r", "c") + + -- replace all formatted gold amount with their copper value + local start = 1 + local goldAmountContinue = true + while goldAmountContinue do + goldAmountContinue = false + local minFind = {} + for _, pattern in ipairs(MONEY_PATTERNS) do + local s, e, sub = strfind(str, pattern, start) + if s and (not minFind.s or minFind.s > s) then + minFind.s = s + minFind.e = e + minFind.sub = sub + end + end + if minFind.s then + local value = TSMAPI:UnformatTextMoney(minFind.sub) + if not value then return end -- sanity check + local preStr = strsub(str, 1, minFind.s-1) + local postStr = strsub(str, minFind.e+1) + str = preStr .. value .. postStr + start = #str - #postStr + 1 + goldAmountContinue = true + end + end + + -- remove up to 1 occurance of convert(priceSource[, item]) + local convertPriceSource, convertItem + local _, _, convertParams = strfind(str, "convert%(([^%)]+)%)") + if convertParams then + local source + local s = strfind(convertParams, "|c") + if s then + local _, e = strfind(convertParams, "|r") + local itemString = e and TSMAPI:GetItemString(strsub(convertParams, s, e)) + if not itemString then return nil, L["Invalid item link."] end -- there's an invalid item link in the convertParams + convertItem = itemString + source = strsub(convertParams, 1, s - 1) + elseif strfind(convertParams, "item:") then + local s, e = strfind(convertParams, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)") + convertItem = strsub(convertParams, s, e) + source = strsub(convertParams, 1, s - 1) + elseif strfind(convertParams, "battlepet:") then + local s, e = strfind(convertParams, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)") + convertItem = strsub(convertParams, s, e) + source = strsub(convertParams, 1, s - 1) + else + source = convertParams + end + source = gsub(source:trim(), ",$", ""):trim() + for key in pairs(TSMAPI:GetPriceSources()) do + if strlower(key) == source then + convertPriceSource = key + break + end + end + if not convertPriceSource then + return nil, L["Invalid price source in convert."] + end + local num = 0 + str, num = gsub(str, "convert%(([^%)]+)%)", "~convert~") + if num > 1 then + return nil, L["A maximum of 1 convert() function is allowed."] + end + end + + -- replace all item links with "~item~" + local items = {} + while true do + local s = strfind(str, "|c") + if not s then break end -- no more item links + local _, e = strfind(str, "|r") + local itemString = e and TSMAPI:GetItemString(strsub(str, s, e)) + if not itemString then return nil, L["Invalid item link."] end -- there's an invalid item link in the str + tinsert(items, itemString) + str = strsub(str, 1, s - 1) .. "~item~" .. strsub(str, e + 1) + end + + -- replace all itemStrings with "~item~" + while true do + local s, e + if strfind(str, "item:") then + s, e = strfind(str, "item:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)") + elseif strfind(str, "battlepet:") then + s, e = strfind(str, "battlepet:([0-9]+):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*):?([0-9]*)") + else + break + end + local itemString = strsub(str, s, e) + tinsert(items, itemString) + str = strsub(str, 1, s - 1) .. "~item~" .. strsub(str, e + 1) + end + + -- make sure there's spaces on either side of operators + for _, operator in ipairs(supportedOperators) do + str = gsub(str, TSMAPI:StrEscape(operator), " " .. operator .. " ") + end + + -- add space to start of string for percentage matching + str = " "..str + -- fix any whitespace issues around item links and remove parenthesis + str = gsub(str, "%(([ ]*)~item~([ ]*)%)", " ~item~") + -- ensure a space on either side of parenthesis and commas + str = gsub(str, "%(", " ( ") + str = gsub(str, "%)", " ) ") + str = gsub(str, ",", " , ") + -- remove any occurances of more than one consecutive space + str = gsub(str, "([ ]+)", " ") + + -- ensure equal number of left/right parenthesis + if select(2, gsub(str, "%(", "")) ~= select(2, gsub(str, "%)", "")) then return nil, L["Unbalanced parentheses."] end + + -- convert percentages to decimal numbers + for leading, pctValue in gmatch(str, "([^0-9%.])([0-9%.]+)%%") do + if tonumber(pctValue) then + local number = tonumber(pctValue) / 100 + str = gsub(str, leading..pctValue.."%%", leading .. number .. " *") + end + end + + -- create array of valid price sources + local priceSourceKeys = {} + for key in pairs(TSMAPI:GetPriceSources()) do + tinsert(priceSourceKeys, strlower(key)) + end + for key in pairs(TSM.db.global.customPriceSources) do + tinsert(priceSourceKeys, strlower(key)) + end + + -- validate all words in the string + local parts = TSMAPI:SafeStrSplit(str:trim(), " ") + for i, word in ipairs(parts) do + if tContains(supportedOperators, word) then + if i == #parts then + return nil, L["Invalid operator at end of custom price."] + end + -- valid operand + elseif badPriceSource == word then + -- price source that's explicitly invalid + return nil, format(L["You cannot use %s as part of this custom price."], word) + elseif tContains(priceSourceKeys, word) then + -- valid price source + elseif tonumber(word) then + -- make sure it's not an itemID (incorrect) + if i > 2 and parts[i-1] == "(" and tContains(priceSourceKeys, parts[i-2]) then + return nil, L["Invalid parameter to price source."] + end + -- valid number + elseif word == "~item~" then + -- make sure previous word was a price source + if i > 1 and tContains(priceSourceKeys, parts[i-1]) then + -- valid item parameter + else + return nil, L["Item links may only be used as parameters to price sources."] + end + elseif word == "(" or word == ")" then + -- valid parenthesis + elseif word == "," then + if not parts[i+1] or parts[i+1] == ")" then + return nil, L["Misplaced comma"] + else + -- we're hoping this is a valid comma within a function, will be caught by loadstring otherwise + end + elseif MATH_FUNCTIONS[word] then + if not parts[i+1] or parts[i+1] ~= "(" then + return nil, format(L["Invalid word: '%s'"], word) + end + -- valid math function + elseif word == "~convert~" then + -- valid convert statement + elseif word:trim() == "" then + -- harmless extra spaces + else + return nil, format(L["Invalid word: '%s'"], word) + end + end + + for key in pairs(TSMAPI:GetPriceSources()) do + -- replace all " ~item~" occurances with the parameters to TSMAPI:GetItemValue (with "~item~" left in for the item) + for match in gmatch(" "..str.." ", " "..strlower(key).." ~item~") do + match = match:trim() + str = gsub(str, match, "(\"~item~\",\"" .. key .. "\",\"reg\")") + end + -- replace all "" occurances with the parameters to TSMAPI:GetItemValue (with _item for the item) + for match in gmatch(" "..str.." ", " "..strlower(key).." ") do + match = match:trim() + str = gsub(str, match, "(\"_item\",\"" .. key .. "\",\"reg\")") + end + end + + for key in pairs(TSM.db.global.customPriceSources) do + -- price sources need to have at least 1 capital letter for this algorithm to work, so temporarily give it one + key = strupper(strsub(key, 1, 1))..strsub(key, 2) + -- replace all " ~item~" occurances with the parameters to TSMAPI:GetCustomPriceSourceValue (with "~item~" left in for the item) + for match in gmatch(" "..str.." ", " " .. strlower(key) .. " ~item~") do + match = match:trim() + str = gsub(str, match, "(\"~item~\",\"" .. key .. "\",\"custom\")") + end + -- replace all "" occurances with the parameters to TSMAPI:GetCustomPriceSourceValue (with _item for the item) + for match in gmatch(" "..str.." ", " " .. strlower(key) .. " ") do + match = match:trim() + str = gsub(str, match, "(\"_item\",\"" .. key .. "\",\"custom\")") + end + + -- change custom price sources back to lower case + str = gsub(str, TSMAPI:StrEscape("(\"~item~\",\"" .. key .. "\",\"custom\")"), strlower("(\"~item~\",\"" .. key .. "\",\"custom\")")) + str = gsub(str, TSMAPI:StrEscape("(\"_item\",\"" .. key .. "\",\"custom\")"), strlower("(\"_item\",\"" .. key .. "\",\"custom\")")) + end + + -- replace all occurances of "~item~" with the item link + for match in gmatch(str, "~item~") do + local itemString = tremove(items, 1) + if not itemString then return nil, L["Wrong number of item links."] end + str = gsub(str, match, itemString, 1) + end + + -- replace any itemValue API calls with a lookup in the 'values' array + local itemValues = {} + for match in gmatch(str, "%(\"([^%)]+)%)") do + local index = #itemValues + 1 + itemValues[index] = "{\"" .. match .. "}" + str = gsub(str, TSMAPI:StrEscape("(\"" .. match .. ")"), "values[" .. index .. "]") + end + + -- replace "~convert~" appropriately + if convertPriceSource then + tinsert(itemValues, "{\"" .. (convertItem or "_item") .. "\",\"convert\",\"" .. convertPriceSource .. "\"}") + str = gsub(str, "~convert~", "values[" .. #itemValues .. "]") + end + + -- replace math functions special custom function names + for word, funcName in pairs(MATH_FUNCTIONS) do + str = gsub(str, word, funcName) + end + + -- remove any unused values + for i in ipairs(itemValues) do + if not strfind(" "..str.." ", " values%["..i.."%] ") then + itemValues[i] = "{}" + end + end + + -- finally, create and return the function + local funcTemplate = [[ + return function(_item) + local NAN = math.huge*0 + local origStr = %s + local isTop + if not TSM_PRICE_TEMP.num then + TSM_PRICE_TEMP.num = 0 + isTop = true + end + TSM_PRICE_TEMP.num = TSM_PRICE_TEMP.num + 1 + if TSM_PRICE_TEMP.num > 100 then + if (TSM_PRICE_TEMP.lastPrint or 0) + 1 < time() then + TSM_PRICE_TEMP.lastPrint = time() + TSM_PRICE_TEMP.loopError(origStr) + end + return + end + local function isNAN(num) + return tostring(num) == tostring(NAN) + end + local function _min(...) + local nums = {...} + for i=#nums, 1, -1 do + if isNAN(nums[i]) then + tremove(nums, i) + end + end + if #nums == 0 then return NAN end + return min(unpack(nums)) + end + local function _max(...) + local nums = {...} + for i=#nums, 1, -1 do + if isNAN(nums[i]) then + tremove(nums, i) + end + end + if #nums == 0 then return NAN end + return max(unpack(nums)) + end + local function _first(...) + local nums = {...} + for i=1, #nums do + if type(nums[i]) == "number" and not isNAN(nums[i]) then + return nums[i] + end + end + return NAN + end + local function _avg(...) + local nums = {...} + local total, count = 0, 0 + for i=#nums, 1, -1 do + if type(nums[i]) == "number" and not isNAN(nums[i]) then + total = total + nums[i] + count = count + 1 + end + end + if count == 0 then return NAN end + return floor(total / count + 0.5) + end + local function _check(...) + if select('#', ...) > 3 then return end + local check, ifValue, elseValue = ... + check = check or NAN + ifValue = ifValue or NAN + elseValue = elseValue or NAN + return check > 0 and ifValue or elseValue + end + local values = {} + for i, params in ipairs({%s}) do + local itemString, key, extraParam = unpack(params) + if itemString then + itemString = (itemString == "_item") and _item or itemString + if key == "convert" then + values[i] = TSMAPI:GetConvertCost(itemString, extraParam) + elseif extraParam == "custom" then + values[i] = TSMAPI:GetCustomPriceSourceValue(itemString, key) + else + values[i] = TSMAPI:GetItemValue(itemString, key) + end + values[i] = values[i] or NAN + end + end + local result = floor((%s) + 0.5) + if TSM_PRICE_TEMP.num then + TSM_PRICE_TEMP.num = TSM_PRICE_TEMP.num - 1 + end + if isTop then + TSM_PRICE_TEMP.num = nil + end + return not isNAN(result) and result or nil + end + ]] + local func, loadErr = loadstring(format(funcTemplate, "\""..origStr.."\"", table.concat(itemValues, ","), str)) + if loadErr then + loadErr = gsub(loadErr:trim(), "([^:]+):.", "") + return nil, L["Invalid function."].." Details: "..loadErr + end + local success, func = pcall(func) + if not success then return nil, L["Invalid function."] end + return func +end + +local customPriceCache = {} +local badCustomPriceCache = {} +function TSMAPI:ParseCustomPrice(priceString, badPriceSource) + priceString = strlower(tostring(priceString):trim()) + if priceString == "" then return nil, L["Empty price string."] end + if badCustomPriceCache[priceString] then return nil, badCustomPriceCache[priceString] end + if customPriceCache[priceString] then return customPriceCache[priceString] end + + local func, err = ParsePriceString(priceString, badPriceSource) + if err then + badCustomPriceCache[priceString] = err + return nil, err + end + + customPriceCache[priceString] = func + return func +end + +function TSMAPI:GetCustomPriceSourceValue(itemString, key) + local source = TSM.db.global.customPriceSources[key] + if not source then return end + local func = TSMAPI:ParseCustomPrice(source) + if not func then return end + return func(itemString) +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Sync.lua b/TradeSkillMaster/Core/Sync.lua new file mode 100644 index 0000000..c960dff --- /dev/null +++ b/TradeSkillMaster/Core/Sync.lua @@ -0,0 +1,205 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file handled multi-account syncing and communication + +-- register this file with Ace libraries +local TSM = select(2, ...) +local Sync = TSM:NewModule("Sync", "AceComm-3.0", "AceEvent-3.0") +TSMAPI.Sync = {} +local private = {callbacks={}, addedFriends={}, invalidPlayers={}} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Sync_private") +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +-- Request friend info from the server +ShowFriends() + + +function Sync:OnEnable() + Sync:RegisterComm("TSMSyncData") + Sync:RegisterEvent("CHAT_MSG_SYSTEM") + + local data = {characters={}, accountKey=TSMAPI.Sync:GetAccountKey()} + for name in pairs(TSM.db.factionrealm.characters) do + data.characters[name] = TSMAPI.Sync:GetAccountKey() + end + TSMAPI:CreateTimeDelay("syncSetupDelay", 3, function() TSMAPI.Sync:BroadcastData("TradeSkillMaster", "SETUP", data) end) +end + +--Load the libraries +local libS = LibStub:GetLibrary("AceSerializer-3.0") +local libC = LibStub:GetLibrary("LibCompress") +local libCE = libC:GetAddonEncodeTable() + +function Sync:OnCommReceived(_, data, _, source) + -- remove realm name from source + source = ("-"):split(source) + + -- Decode the compressed data + data = libCE:Decode(data) + + -- Decompress the decoded data + local data = libC:Decompress(data) + if not data then return end + + -- Deserialize the decompressed data + local success + local tmpData = data + success, data = libS:Deserialize(data) + if not success or not data then return end + + -- data is good, do callback + local key = data.__key + local module = data.__module + local account = data.__account + if not key or not module or not private.callbacks[module] then return end + data.__key = nil + data.__module = nil + data.__account = nil + + -- make sure we are getting this from a known source + if not TSM.db.factionrealm.syncAccounts[account] and (module ~= "TradeSkillMaster" and not data.isSetup) then return end + private.callbacks[module](key, data, source) +end + +function Sync:CHAT_MSG_SYSTEM(_, msg) + if #private.addedFriends == 0 then return end + if msg == ERR_FRIEND_NOT_FOUND then + tremove(private.addedFriends, 1) + else + for i, v in ipairs(private.addedFriends) do + if format(ERR_FRIEND_ADDED_S, v) == msg then + tremove(private.addedFriends, i) + private.invalidPlayers[strlower(v)] = true + end + end + end +end + + +function TSMAPI.Sync:GetAccountKey() + return TSM.db.factionrealm.accountKey +end + +function TSM:RegisterSyncCallback(module, callback) + private.callbacks[module] = callback +end + +function private:IsPlayerOnline(target, noAdd) + for i=1, GetNumFriends() do + local name, _, _, _, connected = GetFriendInfo(i) + if name and strlower(name) == strlower(target) then + return connected + end + end + + if not noAdd and not private.invalidPlayers[strlower(target)] and GetNumFriends() ~= 50 then + -- add them as a friend + AddFriend(target) + tinsert(private.addedFriends, target) + for i=1, GetNumFriends() do + local name, _, _, _, connected = GetFriendInfo(i) + if name and strlower(name) == strlower(target) then + return connected + end + end + end +end + +function TSMAPI.Sync:SendData(module, key, data, target) + local playerOnline = private:IsPlayerOnline(target) + if not playerOnline then return end + + data.__module = module + data.__key = key + data.__account = TSMAPI.Sync:GetAccountKey() + + -- we will encode using Huffman, LZW, and no compression separately + -- this is to deal with a bug in the compression code + local orig = data + local serialized = libS:Serialize(data) + local encodedData = {} + encodedData[1] = libCE:Encode(libC:CompressHuffman(serialized)) + encodedData[2] = libCE:Encode(libC:CompressLZW(serialized)) + encodedData[3] = libCE:Encode("\001"..serialized) + + -- verify each compresion and pick the shorted valid one + local minIndex = -1 + local minLen = math.huge + for i=#encodedData, 1, -1 do + local test = libC:Decompress(libCE:Decode(encodedData[i])) + if test and test == serialized and #encodedData[i] < minLen then + minLen = #encodedData[i] + minIndex = i + end + end + + -- sanity check + if not encodedData[minIndex] then error("Could not encode data.") end + -- send off the serialized, compressed, and encoded data + Sync:SendCommMessage("TSMSyncData", encodedData[minIndex], "WHISPER", target) +end + + +function TSMAPI.Sync:BroadcastData(module, key, data) + for account, players in pairs(TSM.db.factionrealm.syncAccounts) do + if account ~= TSMAPI.Sync:GetAccountKey() then + local sent + for player in pairs(players) do + if private:IsPlayerOnline(player, true) then + TSMAPI.Sync:SendData(module, key, data, player) + sent = true + break + end + end + if not sent then + for player in pairs(players) do + if private:IsPlayerOnline(player) then + TSMAPI.Sync:SendData(module, key, data, player) + sent = true + break + end + end + end + end + end +end + + +function private:SendSetupData(target, isResponse, isSetup) + local data = {isResponse=isResponse, isSetup=isSetup, characters={}, accountKey=TSMAPI.Sync:GetAccountKey()} + for name in pairs(TSM.db.factionrealm.characters) do + data.characters[name] = true + end + TSMAPI.Sync:SendData("TradeSkillMaster", "SETUP", data, target) +end + +function TSM:DoSyncSetup(target) + if target == "" then + private.syncSetupTarget = nil + return + end + private.syncSetupTarget = target + private:SendSetupData(target, nil, true) +end + +function TSM:SyncCallback(key, data, source) + if key == "SETUP" then + if (data.isSetup and strlower(source) ~= strlower(private.syncSetupTarget or "")) or (not data.isSetup and not TSM.db.factionrealm.syncAccounts[data.accountKey]) then + return + end + TSMAPI:Verify(data.accountKey ~= TSMAPI.Sync:GetAccountKey(), "It appears that you've manually copied your saved variables between accounts which will cause TSM's automatic sync'ing to not work. You'll need to undo this, and/or delete the TradeSkillMaster, TSM_Crafting, and TSM_ItemTracker saved variables files on both accounts (with WoW closed) in order to fix this.") + TSM.db.factionrealm.syncAccounts[data.accountKey] = data.characters + if data.isSetup then + TSMAPI:CloseFrame() + TSM:Printf(L["Setup account sync'ing with the account which '%s' is on."], source) + end + if data.isResponse then return end -- already responded + private:SendSetupData(source, true, data.isSetup) + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Templates.xml b/TradeSkillMaster/Core/Templates.xml new file mode 100644 index 0000000..ff34b22 --- /dev/null +++ b/TradeSkillMaster/Core/Templates.xml @@ -0,0 +1,21 @@ + + + + + self.handler = function(...) + local msg = LibStub("AceAddon-3.0"):GetAddon("TradeSkillMaster"):IsValidError(...) + if msg then + local status, ret = pcall(self.errorHandler, msg, ...) + if status then + return ret + else + self.origErrorHandler(...) + end + elseif self.origErrorHandler then + return self.origErrorHandler(...) + end + end + + + + diff --git a/TradeSkillMaster/Core/Threading.lua b/TradeSkillMaster/Core/Threading.lua new file mode 100644 index 0000000..2e86dda --- /dev/null +++ b/TradeSkillMaster/Core/Threading.lua @@ -0,0 +1,113 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains code for running stuff in a pseudo-thread + +local TSM = select(2, ...) +local private = {frames={}, threads={}} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Threading_private") +TSMAPI.Threading = {} +local STATUS_YIELD = {} +local STATUS_DEAD = {} +local MAX_QUANTUM = 50 + + +local ThreadPrototype = { + Resume = function(self, runTime) + self._endTime = debugprofilestop() + runTime + local noErr, status = coroutine.resume(self._co, self, self._param) + if noErr then + return status + else + -- throw error in delay so we don't kill this execution path + TSMAPI:CreateTimeDelay(0, function() error(status) end) + return STATUS_DEAD + end + end, + + Yield = function(self, force) + local currentTime = debugprofilestop() + if force or currentTime > self._endTime then + coroutine.yield(STATUS_YIELD) + end + end, + + Sleep = function(self, seconds) + self._sleeping = seconds + coroutine.yield(STATUS_YIELD) + end, +} + + + +local quantums = {} +function private.RunScheduler(_, elapsed) + -- deal with sleeping threads and try and assign requested quantums + local totalTime = min(elapsed * 1000, MAX_QUANTUM) + local usedTime = 0 + for i, thread in ipairs(private.threads) do + if thread._sleeping then + thread._sleeping = thread._sleeping - elapsed + if thread._sleeping <= 0 then + thread._sleeping = nil + end + end + quantums[i] = thread._sleeping and 0 or (thread._percent * totalTime) + usedTime = usedTime + quantums[i] + end + + -- scale everything down if the total is > the total time + if usedTime > totalTime then + for i=1, #quantums do + quantums[i] = quantums[i] * (totalTime / usedTime) + end + end + + -- run the threads that aren't sleeping + for i, thread in ipairs(private.threads) do + if quantums[i] > 0 then + -- resume running for a quantum based on its percent + local status = thread:Resume(quantums[i]) + if status ~= STATUS_YIELD then + tremove(private.threads, i) + if thread._callback and status ~= STATUS_DEAD then thread._callback() end + end + end + end +end + +function TSMAPI.Threading:Start(func, percent, callback, param) + local thread = CopyTable(ThreadPrototype) + thread._co = coroutine.create(func) + thread._percent = percent + thread._callback = callback + thread._param = param + thread._sleeping = nil + tinsert(private.threads, thread) +end + +do + local frame = CreateFrame("Frame") + frame:SetScript("OnUpdate", private.RunScheduler) +end + +-- -- EXAMPLE USAGE: + +-- local function TestFunc(self, letter) + -- for i = 1, 10 do + -- self:Sleep(1) + -- print(letter, i) + -- if letter == "B" and i == 10 then print(_G[i].nonExistant) end + -- end +-- end + +-- function TSMTest() + -- local start = GetTime() + -- TSMAPI.Threading:Start(TestFunc, 1, function() print("DONE", GetTime()-start) end, "A") + -- TSMAPI.Threading:Start(TestFunc, 1, function() print("DONE", GetTime()-start) end, "B") +-- end \ No newline at end of file diff --git a/TradeSkillMaster/Core/Tooltips.lua b/TradeSkillMaster/Core/Tooltips.lua new file mode 100644 index 0000000..335819d --- /dev/null +++ b/TradeSkillMaster/Core/Tooltips.lua @@ -0,0 +1,300 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains all the code for the new tooltip options + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries +local lib = TSMAPI +local tooltipLib = LibStub("LibExtraTip-1") +local moduleObjects = TSM.moduleObjects +local moduleNames = TSM.moduleNames + +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Tooltips_private") +private.tooltipInfo = {} + +-- ************************************************************************** +-- LibExtraTip Functions +-- ************************************************************************** + +function TSM:SetupTooltips() + -- tooltipLib:AddCallback({type = "battlepet", callback = private.LoadTooltip}) + tooltipLib:AddCallback({type = "item", callback = private.LoadTooltip}) + tooltipLib:RegisterTooltip(GameTooltip) + tooltipLib:RegisterTooltip(ItemRefTooltip) + -- tooltipLib:RegisterTooltip(BattlePetTooltip) + local orig = OpenMailAttachment_OnEnter + OpenMailAttachment_OnEnter = function(self, index) + private.lastMailTooltipUpdate = private.lastMailTooltipUpdate or 0 + if private.lastMailTooltipIndex ~= index or private.lastMailTooltipUpdate + 0.1 < GetTime() then + private.lastMailTooltipUpdate = GetTime() + private.lastMailTooltipIndex = index + orig(self, index) + end + end +end + +local tooltipLines = {lastUpdate = 0, modifier=0} +local function GetTooltipLines(itemString, quantity) + local modifier = (IsShiftKeyDown() and 4 or 0) + (IsAltKeyDown() and 2 or 0) + (IsControlKeyDown() and 1 or 0) + if modifier ~= tooltipLines.modifier then + tooltipLines.modifier = modifier + tooltipLines.lastUpdate = 0 + end + if tooltipLines.itemString ~= itemString or tooltipLines.quantity ~= quantity or (tooltipLines.lastUpdate + 0.5) < GetTime() then + wipe(tooltipLines) + for _, moduleName in ipairs(moduleNames) do + if moduleObjects[moduleName].GetTooltip then + local moduleLines = moduleObjects[moduleName]:GetTooltip(itemString, quantity) + if type(moduleLines) ~= "table" then moduleLines = {} end + for _, line in ipairs(moduleLines) do + tinsert(tooltipLines, line) + end + end + end + tooltipLines.itemString = itemString + tooltipLines.quantity = quantity + tooltipLines.lastUpdate = GetTime() + end + return tooltipLines +end + +function private.LoadTooltip(tipFrame, link, quantity) + local itemString = TSMAPI:GetItemString(link) + if not itemString then return end + local lines = GetTooltipLines(itemString, quantity) + if #lines > 0 then + tooltipLib:AddLine(tipFrame, " ", 1, 1, 0, TSM.db.profile.embeddedTooltip) + local r, g, b = unpack(TSM.db.profile.design.inlineColors.tooltip or { 130, 130, 250 }) + + for i = 1, #lines do + if type(lines[i]) == "table" then + tooltipLib:AddDoubleLine(tipFrame, lines[i].left, lines[i].right, r / 255, g / 255, b / 255, r / 255, g / 255, b / 255, TSM.db.profile.embeddedTooltip) + else + tooltipLib:AddLine(tipFrame, lines[i], r / 255, g / 255, b / 255, TSM.db.profile.embeddedTooltip) + end + end + tooltipLib:AddLine(tipFrame, " ", 1, 1, 0, TSM.db.profile.embeddedTooltip) + end +end + + +-- ************************************************************************** +-- TSM Tooltip Options +-- ************************************************************************** + +function TSM:RegisterTooltipInfo(module, info) + info = CopyTable(info) + info.module = module + tinsert(private.tooltipInfo, info) +end + +function TSMAPI:GetMoneyCoinsTooltip() + return TSM.db.profile.moneyCoinsTooltip +end + +local loadTooltipOptionsTab +function TSM:LoadTooltipOptions(parent) + local tabs = {} + local next = next + + for _, info in ipairs(private.tooltipInfo) do + tinsert(tabs, { text = info.module, value = info.module }) + end + + if next(tabs) then + sort(tabs, function(a, b) + return a.text < b.text + end) + end + + tinsert(tabs, 1, { text = L["General"], value = "Help" }) + + local tabGroup = AceGUI:Create("TSMTabGroup") + tabGroup:SetLayout("Fill") + tabGroup:SetTabs(tabs) + tabGroup:SetCallback("OnGroupSelected", function(_, _, value) + tabGroup:ReleaseChildren() + if value == "Help" then + private:DrawTooltipHelp(tabGroup) + else + for _, info in ipairs(private.tooltipInfo) do + if info.module == value then + info.callback(tabGroup, loadTooltipOptionsTab and loadTooltipOptionsTab.tooltip) + end + end + end + end) + parent:AddChild(tabGroup) + + tabGroup:SelectTab(loadTooltipOptionsTab and loadTooltipOptionsTab.module or "Help") +end + +function private:DrawTooltipHelp(container) + local priceSources = TSMAPI:GetPriceSources() + priceSources["Crafting"] = nil + priceSources["VendorBuy"] = nil + priceSources["VendorSell"] = nil + priceSources["Disenchant"] = nil + local page = { + { + -- scroll frame to contain everything + type = "ScrollFrame", + layout = "List", + children = { + { + type = "InlineGroup", + layout = "flow", + title = L["General Options"], + children = { + { + type = "Label", + text = L["Display prices in tooltips as:"], + relativeWidth = 0.25, + }, + { + type = "CheckBox", + label = L["Coins:"], + relativeWidth = 0.09, + settingInfo = {TSM.db.profile, "moneyCoinsTooltip"}, + callback = function(_, _, value) + if value == true then + TSM.db.profile.moneyTextTooltip = false + end + container:ReloadTab() + end, + }, + { + type = "Label", + relativeWidth = 0.22, + text = TSMAPI:FormatTextMoneyIcon(3451267, "|cffffffff", false, true), + }, + { + type = "CheckBox", + label = L["Text:"], + relativeWidth = 0.09, + settingInfo = {TSM.db.profile, "moneyTextTooltip"}, + callback = function(_, _, value) + if value == true then + TSM.db.profile.moneyCoinsTooltip = false + end + container:ReloadTab() + end, + }, + { + type = "Label", + text = TSMAPI:FormatTextMoney(3451267, "|cffffffff", false, true), + }, + { + type = "HeadingLine", + }, + { + type = "CheckBox", + label = L["Embed TSM Tooltips"], + settingInfo = {TSM.db.profile, "embeddedTooltip"}, + tooltip = L["If checked, TSM's tooltip lines will be embedded in the item tooltip. Otherwise, it will show as a separate box below the item's tooltip."], + }, + { + type = "CheckBox", + label = L["Display Group / Operation Info in Tooltips"], + settingInfo = {TSM.db.profile, "tooltip"}, + }, + { + type = "CheckBox", + label = L["Display vendor buy price in tooltip."], + settingInfo = { TSM.db.profile, "vendorBuyTooltip" }, + tooltip = L["If checked, the price of buying the item from a vendor is displayed."], + }, + { + type = "CheckBox", + label = L["Display vendor sell price in tooltip."], + settingInfo = { TSM.db.profile, "vendorSellTooltip" }, + tooltip = L["If checked, the price of selling the item to a vendor displayed."], + }, + }, + }, + { + type = "InlineGroup", + layout = "flow", + title = L["Destroy Values"], + children = { + { + type = "Dropdown", + label = L["Destroy Value Source:"], + settingInfo = {TSM.db.profile, "destroyValueSource"}, + list = priceSources, + relativeWidth = 0.5, + tooltip = L["Select the price source for calculating destroy values."], + }, + { + type = "CheckBox", + label = L["Display Detailed Destroy Tooltips"], + settingInfo = { TSM.db.profile, "detailedDestroyTooltip" }, + relativeWidth = 0.49, + tooltip = L["If checked, a detailed list of items which an item destroys into will be displayed below the destroy value in the tooltip."], + }, + { + type = "HeadingLine", + }, + { + type = "CheckBox", + label = L["Display mill value in tooltip."], + settingInfo = { TSM.db.profile, "millTooltip" }, + relativeWidth = 0.5, + tooltip = L["If checked, the mill value of the item will be shown. This value is calculated using the average market value of materials the item will mill into."], + }, + { + type = "CheckBox", + label = L["Display prospect value in tooltip."], + settingInfo = { TSM.db.profile, "prospectTooltip" }, + relativeWidth = 0.5, + tooltip = L["If checked, the prospect value of the item will be shown. This value is calculated using the average market value of materials the item will prospect into."], + }, + { + type = "CheckBox", + label = L["Display disenchant value in tooltip."], + settingInfo = { TSM.db.profile, "deTooltip" }, + relativeWidth = 0.5, + tooltip = L["If checked, the disenchant value of the item will be shown. This value is calculated using the average market value of materials the item will disenchant into."], + }, + }, + }, + }, + }, + } + + if next(TSM.db.global.customPriceSources) then + local inlineGroup = { + type = "InlineGroup", + layout = "flow", + title = L["Custom Price Sources"], + children = { + { + type = "Label", + text = L["Custom price sources to display in item tooltips:"], + relativeWidth = 1, + }, + }, + } + for name in pairs(TSM.db.global.customPriceSources) do + local checkbox = { + type = "CheckBox", + label = name, + relativeWidth = 0.5, + settingInfo = { TSM.db.global.customPriceTooltips, name }, + tooltip = L["If checked, this custom price will be displayed in item tooltips."], + } + tinsert(inlineGroup.children, checkbox) + end + tinsert(page[1].children, inlineGroup) + end + + TSMAPI:BuildPage(container, page) +end \ No newline at end of file diff --git a/TradeSkillMaster/Data/ConnectedRealms.lua b/TradeSkillMaster/Data/ConnectedRealms.lua new file mode 100644 index 0000000..5582c91 --- /dev/null +++ b/TradeSkillMaster/Data/ConnectedRealms.lua @@ -0,0 +1,134 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskillmaster_warehousing -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains various utility related to connected realms + +local TSM = select(2, ...) +local lib = TSMAPI + +local CONNECTED_REALMS = { + US = { + {"Aegwynn", "Bonechewer", "Daggerspine", "Gurubashi", "Hakkar"}, + {"Agamaggan", "Archimode", "Jaedenar", "The Underbog"}, + {"Akama", "Dragonmaw ", "Mug'thol"}, + {"Aggramar", "Fizzcrank"}, + {"Alexstrasza", "Terokkar"}, + {"Alleria", "Khadgar"}, + {"Altar of Storms", "Anetheron", "Magtheridon", "Ysondre"}, + {"Andorhal", "Scilla", "Ursin"}, + {"Antonidas", "Uldum"}, + {"Anub'arak", "Chromaggus", "Crushridge", "Garithos", "Nathrezim", "Smolderthorn"}, + {"Anvilmar", "Undermine"}, + {"Arygos", "Llane"}, + {"Auchindoun", "Cho'gall", "Laughing Skull"}, + {"Azgalor", "Azshara", "Destromath", "Thunderlord"}, + {"Azjol-Nerub", "Khaz Modan"}, + {"Balnazzar", "Gorgonnash", "The Forgotten Coast", "Warsong"}, + {"Black Dragonflight", "Gul'dan", "Skullcrusher"}, + {"Blackhand", "Galakrond"}, + {"Blackwing Lair", "Dethecus", "Detheroc", "Haomarush", "Lethon"}, + {"Bladefist", "Kul Tiras"}, + {"Blade's Edge", "Thunderhorn"}, + {"Blood Furnace", "Mannoroth", "Nazjatar"}, + {"Bloodscalp", "Boulderfist", "Dunemaul", "Maiev", "Stonemaul"}, + {"Borean Tundra", "Shadowsong"}, + {"Burning Blade", "Lightning's Blade", "Onyxia"}, + {"Bronzebeard", "Shandris"}, + {"Cairne", "Perenolde"}, + {"Coilfang", "Dalvengyr", "Dark Iron", "Demon Soul"}, + {"Darrowmere", "Windrunner"}, + {"Dath'Remar", "Khaz'goroth"}, + {"Dentarg", "Whisperwind"}, + {"Draenor", "Echo Isles"}, + {"Dragonblight", "Fenris"}, + {"Drak'Tharon", "Firetree", "Malorne", "Rivendare", "Spirestone", "Stormscale"}, + {"Drak'thul", "Skywall"}, + {"Draka", "Suramar"}, + {"Dreadmaul", "Thaurissan"}, + {"Eitrigg", "Shu'halo"}, + {"Eldre'Thalas", "Korialstrasz"}, + {"Eonar", "Velen"}, + {"Eredar", "Gorefiend", "Spinebreaker", "Wildhammer"}, + {"Executus", "Kalecgos", "Shattered Halls"}, + {"Exodar", "Medivh"}, + {"Farstriders", "Silver Hand", "Thorium Brotherhood"}, + {"Feathermoon", "Scarlet Crusade"}, + {"Frostmane", "Ner'zhul", "Tortheldrin"}, + {"Frostwolf", "Varshj"}, + {"Ghostlands", "Kael'thas"}, + {"Gundrak", "Jubei'Thos"}, + {"Hellscream", "Zangarmarsh"}, + {"Hydraxis", "Terenas"}, + {"Icecrown", "Malygos"}, + {"Kargath", "Norgannon"}, + {"Kilrogg", "Winterhoof"}, + {"Kirin Tor", "Sentinels", "Steamwheedle Cartel"}, + {"Misha", "Rexxar"}, + {"Mok'Nathal", "Silvermoon"}, + {"Muradin", "Nordrassil"}, + {"Nazgrel", "Nesingwary", "Vek'nilash"}, + {"Quel'dorei", "Sen'jin"}, + {"Runetotem", "Uther"}, + {"Ravencrest", "Uldaman"}, + }, + EU = { + {"Aggra (Portugus)", "Grim Batol"}, + {"Agamaggan", "Bloodscalp", "Crushridge", "Emeriss", "Hakkar"}, + {"Ahn'Qiraj", "Balnazzar", "Boulderfist", "Chromaggus", "Daggerspine", "Laughing Skull", "Shattered Halls", "Sunstrider", "Talnivarr", "Trollbane"}, + {"Alexstrasza", "Nethersturm"}, + {"Anetheron", "Festung der Strme", "Gul'dan", "Rajaxx"}, + {"Arak-arahm", "Rashgarroth", "Throk'Feroth"}, + {"Arathi", "Illidan", "Naxxramas", "Temple noir"}, + {"Arthas", "Blutkessel", "Vek'lor"}, + {"Auchindoun", "Dunemaul", "Jaedenar"}, + {"Area 52", "Un'Goro"}, + {"Bladefist", "Zenedar"}, + {"Bloodfeathre", "Burning Steppes", "Executus", "Kor'gall", "Shattered Hand"}, + {"Burning Blade", "Drak'thul"}, + {"Cho'gall", "Eldre'Thalas", "Sinstralis"}, + {"Colinas Pardas", "Los Errantes", "Tyrande"}, + {"Conseil des Ombres", "Culte de la Rive noire", "La Croisade carlate"}, + {"Dalaran", "Marcage de Zangar"}, + {"Dalvengyr", "Nazjatar"}, + {"Darksorrow", "Genjuros", "Neptulon"}, + {"Das Syndikat", "Der abyssiche Rat", "Die Arguswacht", "Die Todeskrallen"}, + {"Deepholm", "Razuvious"}, + {"Deathwing", "Karazhan", "Lightning's Blade"}, + {"Dethecus", "Mug'thol", "Terrordar", "Theradras"}, + {"Dragonmaw", "Haomarush", "Spinebreaker", "Stormreaver", "Vashj"}, + {"Echsenkessel", "Mal'Ganis", "Taerar"}, + {"Eitrigg", "Krasus"}, + {"Elune", "Varimathras"}, + {"Exodar", "Minahonda"}, + {"Garona", "Ner'zhul"}, + {"Garrosh", "Nozdormu", "Shattrath"}, + {"Gilneas", "Ulduar"}, + {"Kilrogg", "Nagrand", "Runetotem"}, + {"Moonglade", "The Sha'tar"}, + {"Ravenholdt", "Scarshield Legion", "Sporeggar", "The Venture Co"}, + {"Sanguino", "Shen'dralar", "Uldum", "Zul'jin"}, + {"Skullcrusher", "Xavius"}, + {"Thunderhorn", "Wildhammer"}, + }, +} + +function TSMAPI:GetConnectedRealms() + local region = strupper(strsub(GetCVar("realmList"), 1, 2)) + if not CONNECTED_REALMS[region] then return end + local currentRealm = GetRealmName() + + for _, realms in ipairs(CONNECTED_REALMS[region]) do + for i, realm in ipairs(realms) do + if realm == currentRealm then + local result = CopyTable(realms) + tremove(result, i) + return result + end + end + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Data/Conversions.lua b/TradeSkillMaster/Data/Conversions.lua new file mode 100644 index 0000000..6b75015 --- /dev/null +++ b/TradeSkillMaster/Data/Conversions.lua @@ -0,0 +1,717 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + + +local conversions = { + -- Epic WotLK gems + ["item:36919:0:0:0:0:0:0"] = { -- Cardinal Ruby + ["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:36922:0:0:0:0:0:0"] = { -- King's Amber + ["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:36925:0:0:0:0:0:0"] = { -- Majestic Zircon + ["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:36928:0:0:0:0:0:0"] = { -- Dreadstone + ["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:36931:0:0:0:0:0:0"] = { -- Ametrine + ["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:36934:0:0:0:0:0:0"] = { -- Eye of Zul + ["item:36910:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + -- common pigments (inks) + ["item:39151:0:0:0:0:0:0"] = { -- Alabaster Pigment (Ivory / Moonglow Ink) + ["item:765:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:2447:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:2449:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:39343:0:0:0:0:0:0"] = { -- Azure Pigment (Ink of the Sea) + ["item:39969:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:36904:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:36907:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:36901:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:39970:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:37921:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:36905:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:36906:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:36903:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:61979:0:0:0:0:0:0"] = { -- Ashen Pigment (Blackfallow Ink) + ["item:52983:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:52984:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:52985:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:52986:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:52987:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:52988:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:39334:0:0:0:0:0:0"] = { -- Dusky Pigment (Midnight Ink) + ["item:785:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:2450:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:2452:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:2453:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:3820:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:39339:0:0:0:0:0:0"] = { -- Emerald Pigment (Jadefire Ink) + ["item:3818:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:3821:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:3358:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:3819:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:39338:0:0:0:0:0:0"] = { -- Golden Pigment (Lion's Ink) + ["item:3355:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:3369:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:3356:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:3357:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:39342:0:0:0:0:0:0"] = { -- Nether Pigment (Ethereal Ink) + ["item:22786:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:22785:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:22789:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:22787:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:22790:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:22793:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:22791:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:22792:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:79251:0:0:0:0:0:0"] = { -- Shadow Pigment (Ink of Dreams) + ["item:72237:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:72234:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:79010:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:72235:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:79011:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:89639:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + }, + ["item:39341:0:0:0:0:0:0"] = { -- Silvery Pigment (Shimmering Ink) + ["item:13464:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:13463:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:13465:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:13466:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:13467:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + ["item:39340:0:0:0:0:0:0"] = { -- Violet Pigment (Celestial Ink) + ["item:4625:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:8831:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:8838:0:0:0:0:0:0"] = {rate=.5, source="mill"}, + ["item:8839:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:8845:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + ["item:8846:0:0:0:0:0:0"] = {rate=.6, source="mill"}, + }, + + -- rare pigments (inks) + ["item:43109:0:0:0:0:0:0"] = { -- Icy Pigment (Snowfall Ink) + ["item:39969:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:36904:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:36907:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:36901:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:39970:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:37921:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:36905:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:36906:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:36903:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + }, + ["item:61980:0:0:0:0:0:0"] = { -- Burning Embers (Inferno Ink) + ["item:52983:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:52984:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:52985:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:52986:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:52987:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:52988:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + }, + ["item:43104:0:0:0:0:0:0"] = { -- Burnt Pigment (Dawnstar Ink) + ["item:3356:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:3357:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:3369:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:3355:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + }, + ["item:43108:0:0:0:0:0:0"] = { -- Ebon Pigment (Darkflame Ink) + ["item:22792:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:22790:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:22791:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:22793:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:22786:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:22785:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:22787:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:22789:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + }, + ["item:43105:0:0:0:0:0:0"] = { -- Indigo Pigment (Royal Ink) + ["item:3358:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:3819:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:3821:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:3818:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + }, + ["item:79253:0:0:0:0:0:0"] = { -- Misty Pigment (Starlight Ink) + ["item:72237:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:72234:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:79010:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:72235:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:79011:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:89639:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + }, + ["item:43106:0:0:0:0:0:0"] = { -- Ruby Pigment (Fiery Ink) + ["item:4625:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:8838:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:8831:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:8845:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:8846:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:8839:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + }, + ["item:43107:0:0:0:0:0:0"] = { -- Sapphire Pigment (Ink of the Sky) + ["item:13463:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:13464:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:13465:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:13466:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:13467:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + }, + ["item:43103:0:0:0:0:0:0"] = { -- Verdant Pigment (Hunter's Ink) + ["item:2453:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:3820:0:0:0:0:0:0"] = {rate=.1, source="mill"}, + ["item:2450:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:785:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + ["item:2452:0:0:0:0:0:0"] = {rate=.05, source="mill"}, + }, + + --Vanilla Gems + ["item:774:0:0:0:0:0:0"] = { -- malachite + ["item:2770:0:0:0:0:0:0"] = {rate=.5, source="prospect"}, + }, + ["item:818:0:0:0:0:0:0"] = { -- Tigerseye + ["item:2770:0:0:0:0:0:0"] = {rate=.5, source="prospect"}, + }, + ["item:1210:0:0:0:0:0:0"] = { -- Shadowgem + ["item:2771:0:0:0:0:0:0"] = {rate=.4, source="prospect"}, + ["item:2770:0:0:0:0:0:0"] = {rate=.1, source="prospect"}, + }, + ["item:1206:0:0:0:0:0:0"] = { -- Moss Agate + ["item:2771:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, + }, + ["item:1705:0:0:0:0:0:0"] = { -- Lesser moonstone + ["item:2771:0:0:0:0:0:0"] = {rate=.4, source="prospect"}, + ["item:2772:0:0:0:0:0:0"] = { rate=.3, source="prospect"}, + }, + ["item:1529:0:0:0:0:0:0"] = { -- Jade + ["item:2772:0:0:0:0:0:0"] = {rate=.4, source="prospect"}, + ["item:2771:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:3864:0:0:0:0:0:0"] = { -- Citrine + ["item:2772:0:0:0:0:0:0"] = {rate=.4, source="prospect"}, -- iron + ["item:3858:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, -- mith + ["item:2771:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, -- tin + }, + ["item:7909:0:0:0:0:0:0"] = { -- Aquamarine + ["item:3858:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, + ["item:2772:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + ["item:2771:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:7910:0:0:0:0:0:0"] = { -- Star Ruby + ["item:3858:0:0:0:0:0:0"] = {rate=.4, source="prospect"}, + ["item:10620:0:0:0:0:0:0"] = {rate=.1, source="prospect"}, + ["item:2772:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + }, + ["item:12361:0:0:0:0:0:0"] = { -- Blue Sapphire + ["item:10620:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, + ["item:3858:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, + }, + ["item:12799:0:0:0:0:0:0"] = { -- Large Opal + ["item:10620:0:0:0:0:0:0"] = {rate =.3, source="prospect"}, -- thorium + ["item:3858:0:0:0:0:0:0"] = {rate=.03, source="prospect"}, -- Mith + }, + ["item:12800:0:0:0:0:0:0"] = { -- Azerothian Diamond + ["item:10620:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, + ["item:3858:0:0:0:0:0:0"] = {rate=.02, source="prospect"}, + }, + ["item:12364:0:0:0:0:0:0"] = { -- Huge Emerald + ["item:10620:0:0:0:0:0:0"] = {rate=.3, source="prospect"}, + ["item:3858:0:0:0:0:0:0"] = {rate=.02, source="prospect"}, + }, + + -- uncommon gems + ["item:23117:0:0:0:0:0:0"] = { -- Azure Moonstone + ["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + }, + ["item:23077:0:0:0:0:0:0"] = { -- Blood Garnet + ["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + }, + ["item:23079:0:0:0:0:0:0"] = { -- Deep Peridot + ["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + }, + ["item:21929:0:0:0:0:0:0"] = { -- Flame Spessarite + ["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + }, + ["item:23112:0:0:0:0:0:0"] = { -- Golden Draenite + ["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + }, + ["item:23107:0:0:0:0:0:0"] = { -- Shadow Draenite + ["item:23424:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + }, + ["item:36917:0:0:0:0:0:0"] = { -- Bloodstone + ["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + }, + ["item:36923:0:0:0:0:0:0"] = { -- Chalcedony + ["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + }, + ["item:36932:0:0:0:0:0:0"] = { -- Dark Jade + ["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + }, + ["item:36929:0:0:0:0:0:0"] = { -- Huge Citrine + ["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + }, + ["item:36926:0:0:0:0:0:0"] = { -- Shadow Crystal + ["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + }, + ["item:36920:0:0:0:0:0:0"] = { -- Sun Crystal + ["item:36909:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + }, + -- ["item:52182:0:0:0:0:0:0"] = { -- Jasper + -- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:52180:0:0:0:0:0:0"] = { -- Nightstone + -- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:52178:0:0:0:0:0:0"] = { -- Zephyrite + -- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:52179:0:0:0:0:0:0"] = { -- Alicite + -- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:52177:0:0:0:0:0:0"] = { -- Carnelian + -- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:52181:0:0:0:0:0:0"] = { -- Hessonite + -- ["item:53038:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:76130:0:0:0:0:0:0"] = { -- Tiger Opal + -- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:76133:0:0:0:0:0:0"] = { -- Lapis Lazuli + -- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:76134:0:0:0:0:0:0"] = { -- Sunstone + -- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:76135:0:0:0:0:0:0"] = { -- Roguestone + -- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:76136:0:0:0:0:0:0"] = { -- Pandarian Garnet + -- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + -- ["item:76137:0:0:0:0:0:0"] = { -- Alexandrite + -- ["item:72092:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.25, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.2, source="prospect"}, + -- }, + + --Rare Gems + ["item:23440:0:0:0:0:0:0"] = { -- Dawnstone + ["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:23436:0:0:0:0:0:0"] = { -- Living Ruby + ["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:23441:0:0:0:0:0:0"] = { -- Nightseye + ["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:23439:0:0:0:0:0:0"] = { -- Noble Topaz + ["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:23438:0:0:0:0:0:0"] = { -- Star of Elune + ["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:23437:0:0:0:0:0:0"] = { -- Talasite + ["item:23424:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:23425:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:36921:0:0:0:0:0:0"] = { -- Autumn's Glow + ["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:36933:0:0:0:0:0:0"] = { -- Forest Emerald + ["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:36930:0:0:0:0:0:0"] = { -- Monarch Topaz + ["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:36918:0:0:0:0:0:0"] = { -- Scarlet Ruby + ["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:36924:0:0:0:0:0:0"] = { -- Sky Sapphire + ["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + ["item:36927:0:0:0:0:0:0"] = { -- Twilight Opal + ["item:36909:0:0:0:0:0:0"] = {rate=.01, source="prospect"}, + ["item:36912:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + ["item:36910:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + }, + -- ["item:52192:0:0:0:0:0:0"] = { -- Dream Emerald + -- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- }, + -- ["item:52193:0:0:0:0:0:0"] = { -- Ember Topaz + -- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- }, + -- ["item:52190:0:0:0:0:0:0"] = { -- Inferno Ruby + -- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- }, + -- ["item:52195:0:0:0:0:0:0"] = { -- Amberjewel + -- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- }, + -- ["item:52194:0:0:0:0:0:0"] = { -- Demonseye + -- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- }, + -- ["item:52191:0:0:0:0:0:0"] = { -- Ocean Sapphire + -- ["item:53038:0:0:0:0:0:0"] = {rate=.08, source="prospect"}, + -- ["item:52185:0:0:0:0:0:0"] = {rate=.05, source="prospect"}, + -- ["item:52183:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- }, + -- ["item:76131:0:0:0:0:0:0"] = { -- Primordial Ruby + -- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- }, + -- ["item:76138:0:0:0:0:0:0"] = { -- River's Heart + -- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- }, + -- ["item:76139:0:0:0:0:0:0"] = { -- Wild Jade + -- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- }, + -- ["item:76140:0:0:0:0:0:0"] = { -- Vermillion Onyx + -- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- }, + -- ["item:76141:0:0:0:0:0:0"] = { -- Imperial Amethyst + -- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- }, + -- ["item:76142:0:0:0:0:0:0"] = { -- Sun's Radiance + -- ["item:72092:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72093:0:0:0:0:0:0"] = {rate=.04, source="prospect"}, + -- ["item:72103:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- ["item:72094:0:0:0:0:0:0"] = {rate=.15, source="prospect"}, + -- }, + + --transformations + -- ["item:52719:0:0:0:0:0:0"] = { -- Greater Celestial Essence + -- ["item:52718:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + -- }, + ["item:52718:0:0:0:0:0:0"] = { -- Lesser Celestial Essence + ["item:52719:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:34055:0:0:0:0:0:0"] = { -- Greater Cosmic Essence + ["item:34056:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:34056:0:0:0:0:0:0"] = { -- Lesser Cosmic Essence + ["item:34055:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:22446:0:0:0:0:0:0"] = { -- Greater Planar Essence + ["item:22447:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:22447:0:0:0:0:0:0"] = { -- Lesser Planar Essence + ["item:22446:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:16203:0:0:0:0:0:0"] = { -- Greater Eternal Essence + ["item:16202:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:16202:0:0:0:0:0:0"] = { -- Lesser Eternal Essence + ["item:16203:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:11175:0:0:0:0:0:0"] = { -- Greater Nether Essence + ["item:11174:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:11174:0:0:0:0:0:0"] = { -- Lesser Nether Essence + ["item:11175:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:11135:0:0:0:0:0:0"] = { -- Greater Mystic Essence + ["item:11134:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:11134:0:0:0:0:0:0"] = { -- Lesser Mystic Essence + ["item:11135:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:11082:0:0:0:0:0:0"] = { -- Greater Astral Essence + ["item:10998:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:10998:0:0:0:0:0:0"] = { -- Lesser Astral Essence + ["item:11082:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:10939:0:0:0:0:0:0"] = { -- Greater Magic Essence + ["item:10938:0:0:0:0:0:0"] = {rate=3, source="transform"}, + }, + ["item:10938:0:0:0:0:0:0"] = { -- Lesser Magic Essence + ["item:10939:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:52721:0:0:0:0:0:0"] = { -- Heavenly Shard + ["item:52720:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + ["item:34052:0:0:0:0:0:0"] = { -- Dream Shard + ["item:34053:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + }, + -- ["item:74247:0:0:0:0:0:0"] = { -- Ethereal Shard + -- ["item:74252:0:0:0:0:0:0"] = {rate=1/3, source="transform"}, + -- }, + ["item:22578:0:0:0:0:0:0"] = { -- Mote of Water + ["item:21885:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:21885:0:0:0:0:0:0"] = { -- Primal Water + ["item:22578:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:22577:0:0:0:0:0:0"] = { -- Mote of Shadow + ["item:22456:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:22456:0:0:0:0:0:0"] = { -- Primal Shadow + ["item:22577:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:22576:0:0:0:0:0:0"] = { -- Mote of Mana + ["item:22457:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:22457:0:0:0:0:0:0"] = { -- Primal Mana + ["item:22576:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:22575:0:0:0:0:0:0"] = { -- Mote of Life + ["item:21886:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:21886:0:0:0:0:0:0"] = { -- Primal Life + ["item:22575:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:22573:0:0:0:0:0:0"] = { -- Mote of Earth + ["item:22452:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:22452:0:0:0:0:0:0"] = { -- Primal Earth + ["item:22573:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:22574:0:0:0:0:0:0"] = { -- Mote of Air + ["item:21884:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:21884:0:0:0:0:0:0"] = { -- Primal Air + ["item:22574:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:37700:0:0:0:0:0:0"] = { -- Crystallized Air + ["item:35623:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:35623:0:0:0:0:0:0"] = { -- Eternal Air + ["item:37700:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:37701:0:0:0:0:0:0"] = { -- Crystallized Earth + ["item:35624:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:35624:0:0:0:0:0:0"] = { -- Eternal Earth + ["item:37701:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:37702:0:0:0:0:0:0"] = { -- Crystallized Fire + ["item:36860:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:36860:0:0:0:0:0:0"] = { -- Eternal Fire + ["item:37702:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:37703:0:0:0:0:0:0"] = { -- Crystallized Shadow + ["item:35627:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:35627:0:0:0:0:0:0"] = { -- Eternal Shadow + ["item:37703:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:37704:0:0:0:0:0:0"] = { -- Crystallized Life + ["item:35625:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:35625:0:0:0:0:0:0"] = { -- Eternal Life + ["item:37704:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + ["item:37705:0:0:0:0:0:0"] = { -- Crystallized Water + ["item:35622:0:0:0:0:0:0"] = {rate=10, source="transform"}, + }, + ["item:35622:0:0:0:0:0:0"] = { -- Eternal Water + ["item:37705:0:0:0:0:0:0"] = {rate=1/10, source="transform"}, + }, + + --vendor trades + ["item:37101:0:0:0:0:0:0"] = { -- Ivory Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:39469:0:0:0:0:0:0"] = { -- Moonglow Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:39774:0:0:0:0:0:0"] = { -- Midnight Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43116:0:0:0:0:0:0"] = { -- Lion's Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43118:0:0:0:0:0:0"] = { -- Jadefire Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43120:0:0:0:0:0:0"] = { -- Celestial Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43122:0:0:0:0:0:0"] = { -- Shimmering Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43124:0:0:0:0:0:0"] = { -- Ethereal Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43126:0:0:0:0:0:0"] = { -- Ink of the Sea + ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + }, + ["item:43127:0:0:0:0:0:0"] = { -- Snowfall Ink + ["item:79254:0:0:0:0:0:0"] = {rate=1/10, source="vendortrade"}, + }, + -- ["item:61978:0:0:0:0:0:0"] = { -- Blackfallow Ink + -- ["item:79254:0:0:0:0:0:0"] = {rate=1, source="vendortrade"}, + -- }, + -- ["item:61981:0:0:0:0:0:0"] = { -- Inferno Ink + -- ["item:79254:0:0:0:0:0:0"] = {rate=1/10, source="vendortrade"}, + -- }, + -- ["item:79255:0:0:0:0:0:0"] = { -- Starlight Ink + -- ["item:79254:0:0:0:0:0:0"] = {rate=1/10, source="vendortrade"}, + -- }, +} +TSMAPI.Conversions = conversions + + +local inks = { + -- uncommon inks + ["item:37101:0:0:0:0:0:0"] = {pigment="item:39151:0:0:0:0:0:0", pigmentPerInk=1}, -- Ivory Ink + ["item:39469:0:0:0:0:0:0"] = {pigment="item:39151:0:0:0:0:0:0", pigmentPerInk=2}, -- Moonglow Ink + ["item:39774:0:0:0:0:0:0"] = {pigment="item:39334:0:0:0:0:0:0", pigmentPerInk=2}, -- Midnight Ink + ["item:43116:0:0:0:0:0:0"] = {pigment="item:39338:0:0:0:0:0:0", pigmentPerInk=2}, -- Lion's Ink + ["item:43118:0:0:0:0:0:0"] = {pigment="item:39339:0:0:0:0:0:0", pigmentPerInk=2}, -- Jadefire Ink + ["item:43120:0:0:0:0:0:0"] = {pigment="item:39340:0:0:0:0:0:0", pigmentPerInk=2}, -- Celestial Ink + ["item:43122:0:0:0:0:0:0"] = {pigment="item:39341:0:0:0:0:0:0", pigmentPerInk=2}, -- Shimmering Ink + ["item:43124:0:0:0:0:0:0"] = {pigment="item:39342:0:0:0:0:0:0", pigmentPerInk=2}, -- Ethereal Ink + ["item:43126:0:0:0:0:0:0"] = {pigment="item:39343:0:0:0:0:0:0", pigmentPerInk=2}, -- Ink of the Sea + -- ["item:61978:0:0:0:0:0:0"] = {pigment="item:61979:0:0:0:0:0:0", pigmentPerInk=2}, -- Blackfallow Ink + -- ["item:79254:0:0:0:0:0:0"] = {pigment="item:79251:0:0:0:0:0:0", pigmentPerInk=2}, -- Ink of Dreams + + -- rare inks + ["item:43115:0:0:0:0:0:0"] = {pigment="item:43103:0:0:0:0:0:0", pigmentPerInk=1}, -- Hunter's Ink + ["item:43117:0:0:0:0:0:0"] = {pigment="item:43104:0:0:0:0:0:0", pigmentPerInk=1}, -- Dawnstar Ink + ["item:43119:0:0:0:0:0:0"] = {pigment="item:43105:0:0:0:0:0:0", pigmentPerInk=1}, -- Royal Ink + ["item:43121:0:0:0:0:0:0"] = {pigment="item:43106:0:0:0:0:0:0", pigmentPerInk=1}, -- Fiery Ink + ["item:43123:0:0:0:0:0:0"] = {pigment="item:43107:0:0:0:0:0:0", pigmentPerInk=1}, -- Ink of the Sky + ["item:43125:0:0:0:0:0:0"] = {pigment="item:43108:0:0:0:0:0:0", pigmentPerInk=1}, -- Darkflame Ink + ["item:43127:0:0:0:0:0:0"] = {pigment="item:43109:0:0:0:0:0:0", pigmentPerInk=2}, -- Snowfall Ink + -- ["item:61981:0:0:0:0:0:0"] = {pigment="item:61980:0:0:0:0:0:0", pigmentPerInk=2}, -- Inferno Ink + -- ["item:79255:0:0:0:0:0:0"] = {pigment="item:79253:0:0:0:0:0:0", pigmentPerInk=2}, -- Starlight Ink +} +TSMAPI.InkConversions = inks + + +-- returns the conversion info for a given target item +function TSMAPI:GetItemConversions(itemString) + if not itemString or not conversions[itemString] then return end + return CopyTable(conversions[itemString]) +end + +function TSMAPI:GetConvertCost(targetItem, priceSource) + local conversions = TSMAPI:GetItemConversions(targetItem) + if not conversions then return end + + local prices = {} + for itemString, info in pairs(conversions) do + local price = TSMAPI:GetItemValue(itemString, priceSource) + if price then + tinsert(prices, price/info.rate) + end + end + if #prices == 0 then return end + return min(unpack(prices)) +end + +function TSMAPI:GetConversionTargetItems(source) + local result = {} + for itemString, items in pairs(conversions) do + for _, info in pairs(items) do + if info.source == source then + tinsert(result, itemString) + break + end + end + end + return result +end \ No newline at end of file diff --git a/TradeSkillMaster/Data/Disenchanting.lua b/TradeSkillMaster/Data/Disenchanting.lua new file mode 100644 index 0000000..a8bc3e2 --- /dev/null +++ b/TradeSkillMaster/Data/Disenchanting.lua @@ -0,0 +1,1636 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") +TSMAPI.DisenchantingData = {} +local data = TSMAPI.DisenchantingData +local WEAPON, ARMOR = GetAuctionItemClasses() + +data.disenchant = { + { + desc = L["Dust"], + ["item:10940:0:0:0:0:0:0"] = { + -- Strange Dust + name = GetItemInfo("item:10940:0:0:0:0:0:0"), + minLevel = 0, + maxLevel = 24, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 5, + maxItemLevel = 15, + amountOfMats = 1.2 + }, + { + minItemLevel = 16, + maxItemLevel = 20, + amountOfMats = 1.875 + }, + { + minItemLevel = 21, + maxItemLevel = 25, + amountOfMats = 3.75 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 5, + maxItemLevel = 15, + amountOfMats = 0.3 + }, + { + minItemLevel = 16, + maxItemLevel = 20, + amountOfMats = 0.5 + }, + { + minItemLevel = 21, + maxItemLevel = 25, + amountOfMats = 0.75 + }, + }, + }, + }, + }, + ["item:11083:0:0:0:0:0:0"] = { + -- Soul Dust + name = GetItemInfo("item:11083:0:0:0:0:0:0"), + minLevel = 20, + maxLevel = 30, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 1.125 + }, + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 2.625 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 0.3 + }, + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 0.7 + }, + }, + }, + }, + }, + ["item:11137:0:0:0:0:0:0"] = { + -- Vision Dust + name = GetItemInfo("item:11137:0:0:0:0:0:0"), + minLevel = 30, + maxLevel = 40, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 1.125 + }, + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 2.625 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 0.3 + }, + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 0.7 + }, + }, + }, + }, + }, + ["item:11176:0:0:0:0:0:0"] = { + -- Dream Dust + name = GetItemInfo("item:11176:0:0:0:0:0:0"), + minLevel = 41, + maxLevel = 50, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 1.125 + }, + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 2.625 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 0.3 + }, + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 0.77 + }, + }, + }, + }, + }, + ["item:16204:0:0:0:0:0:0"] = { + -- Illusion Dust + name = GetItemInfo("item:16204:0:0:0:0:0:0"), + minLevel = 51, + maxLevel = 60, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 56, + maxItemLevel = 60, + amountOfMats = 1.125 + }, + { + minItemLevel = 61, + maxItemLevel = 65, + amountOfMats = 2.625 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 56, + maxItemLevel = 60, + amountOfMats = 0.33 + }, + { + minItemLevel = 61, + maxItemLevel = 65, + amountOfMats = 0.77 + }, + }, + }, + }, + }, + ["item:22445:0:0:0:0:0:0"] = { + -- Arcane Dust + name = GetItemInfo("item:22445:0:0:0:0:0:0"), + minLevel = 57, + maxLevel = 70, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 79, + maxItemLevel = 79, + amountOfMats = 1.5 + }, + { + minItemLevel = 80, + maxItemLevel = 99, + amountOfMats = 1.875 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 2.625 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 80, + maxItemLevel = 99, + amountOfMats = 0.55 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 0.77 + }, + }, + }, + }, + }, + ["item:34054:0:0:0:0:0:0"] = { + -- Infinite Dust + name = GetItemInfo("item:34054:0:0:0:0:0:0"), + minLevel = 67, + maxLevel = 80, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 130, + maxItemLevel = 151, + -- amountOfMats = 1.5 + amountOfMats = 1.875 -- 2-3, 75% chance = 2.5*0.75 + }, + { + minItemLevel = 152, + maxItemLevel = 200, + -- amountOfMats = 3.375 + amountOfMats = 4.125 -- 4-7, 75% chance = 5.5*0.75 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 130, + maxItemLevel = 151, + -- amountOfMats = 0.55 + amountOfMats = 0.55 -- 2-3, 22% chance = 2.5*0.22 + }, + { + minItemLevel = 152, + maxItemLevel = 200, + -- amountOfMats = 1.1 + amountOfMats = 1.21 -- 4-7, 22% chance = 5.5*0.22 + }, + }, + }, + }, + }, + -- ["item:52555:0:0:0:0:0:0"] = { + -- -- Hypnotic Dust + -- name = GetItemInfo("item:52555:0:0:0:0:0:0"), + -- minLevel = 77, + -- maxLevel = 85, + -- itemTypes = { + -- [ARMOR] = { + -- [2] = { + -- { + -- minItemLevel = 272, + -- maxItemLevel = 275, + -- amountOfMats = 1.125 + -- }, + -- { + -- minItemLevel = 276, + -- maxItemLevel = 290, + -- amountOfMats = 1.5 + -- }, + -- { + -- minItemLevel = 291, + -- maxItemLevel = 305, + -- amountOfMats = 1.875 + -- }, + -- { + -- minItemLevel = 306, + -- maxItemLevel = 315, + -- amountOfMats = 2.25 + -- }, + -- { + -- minItemLevel = 316, + -- maxItemLevel = 325, + -- amountOfMats = 2.625 + -- }, + -- { + -- minItemLevel = 326, + -- maxItemLevel = 350, + -- amountOfMats = 3 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [2] = { + -- { + -- minItemLevel = 272, + -- maxItemLevel = 275, + -- amountOfMats = 0.375 + -- }, + -- { + -- minItemLevel = 276, + -- maxItemLevel = 290, + -- amountOfMats = 0.5 + -- }, + -- { + -- minItemLevel = 291, + -- maxItemLevel = 305, + -- amountOfMats = 0.625 + -- }, + -- { + -- minItemLevel = 306, + -- maxItemLevel = 315, + -- amountOfMats = 0.75 + -- }, + -- { + -- minItemLevel = 316, + -- maxItemLevel = 325, + -- amountOfMats = 0.875 + -- }, + -- { + -- minItemLevel = 326, + -- maxItemLevel = 350, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- }, + -- }, + -- ["item:74249:0:0:0:0:0:0"] = { + -- -- Spirit Dust + -- name = GetItemInfo("item:74249:0:0:0:0:0:0"), + -- minLevel = 83, + -- maxLevel = 88, + -- itemTypes = { + -- [ARMOR] = { + -- [2] = { + -- { + -- minItemLevel = 364, + -- maxItemLevel = 390, + -- amountOfMats = 2.125 + -- }, + -- { + -- minItemLevel = 391, + -- maxItemLevel = 410, + -- amountOfMats = 2.55 + -- }, + -- { + -- minItemLevel = 411, + -- maxItemLevel = 450, + -- amountOfMats = 3.4 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [2] = { + -- { + -- minItemLevel = 377, + -- maxItemLevel = 390, + -- amountOfMats = 2.125 + -- }, + -- { + -- minItemLevel = 391, + -- maxItemLevel = 410, + -- amountOfMats = 2.55 + -- }, + -- { + -- minItemLevel = 411, + -- maxItemLevel = 450, + -- amountOfMats = 3.4 + -- }, + -- }, + -- }, + -- }, + -- }, + }, + { + desc = L["Essences"], + ["item:10939:0:0:0:0:0:0"] = { + -- Greater Magic Essence + name = GetItemInfo("item:10939:0:0:0:0:0:0"), + minLevel = 1, + maxLevel = 15, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 5, + maxItemLevel = 15, + amountOfMats = 0.1 + }, + { + minItemLevel = 16, + maxItemLevel = 20, + amountOfMats = 0.3 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 5, + maxItemLevel = 15, + amountOfMats = 0.4 + }, + { + minItemLevel = 16, + maxItemLevel = 20, + amountOfMats = 1.125 + }, + }, + }, + }, + }, + ["item:11082:0:0:0:0:0:0"] = { + -- Greater Astral Essence + name = GetItemInfo("item:11082:0:0:0:0:0:0"), + minLevel = 16, + maxLevel = 25, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 21, + maxItemLevel = 25, + amountOfMats = .075 + }, + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 0.3 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 21, + maxItemLevel = 25, + amountOfMats = 0.375 + }, + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 1.125 + }, + }, + }, + }, + }, + ["item:11135:0:0:0:0:0:0"] = { + -- Greater Mystic Essence + name = GetItemInfo("item:11135:0:0:0:0:0:0"), + minLevel = 26, + maxLevel = 35, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 0.1 + }, + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 0.3 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 0.375 + }, + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 1.125 + }, + }, + }, + }, + }, + ["item:11175:0:0:0:0:0:0"] = { + -- Greater Nether Essence + name = GetItemInfo("item:11175:0:0:0:0:0:0"), + minLevel = 36, + maxLevel = 45, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 0.1 + }, + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 0.3 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 0.375 + }, + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 1.125 + }, + }, + }, + }, + }, + ["item:16203:0:0:0:0:0:0"] = { + -- Greater Eternal Essence + name = GetItemInfo("item:16203:0:0:0:0:0:0"), + minLevel = 46, + maxLevel = 60, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 0.1 + }, + { + minItemLevel = 56, + maxItemLevel = 60, + amountOfMats = 0.3 + }, + { + minItemLevel = 61, + maxItemLevel = 65, + amountOfMats = 0.5 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 0.375 + }, + { + minItemLevel = 56, + maxItemLevel = 60, + amountOfMats = 0.125 + }, + { + minItemLevel = 61, + maxItemLevel = 65, + amountOfMats = 1.875 + }, + }, + }, + }, + }, + ["item:22446:0:0:0:0:0:0"] = { + -- Greater Planar Essence + name = GetItemInfo("item:22446:0:0:0:0:0:0"), + minLevel = 58, + maxLevel = 70, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 66, + maxItemLevel = 99, + amountOfMats = 0.167 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 0.3 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 79, + maxItemLevel = 79, + amountOfMats = 0.625 + }, + { + minItemLevel = 80, + maxItemLevel = 99, + amountOfMats = 0.625 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 1.125 + }, + }, + }, + }, + }, + ["item:34055:0:0:0:0:0:0"] = { + -- Greater Cosmic Essence + name = GetItemInfo("item:34055:0:0:0:0:0:0"), + minLevel = 67, + maxLevel = 80, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 130, + maxItemLevel = 151, + -- amountOfMats = 0.1 + amountOfMats = 0.11 -- 1-2 Lesser, 22% Chance = 1.5*0.22/3 + }, + { + minItemLevel = 152, + maxItemLevel = 200, + -- amountOfMats = 0.3 + amountOfMats = 0.33 -- 1-2 Greater, 22% Chance = 1.5*0.22 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 130, + maxItemLevel = 151, + -- amountOfMats = 0.375 + amountOfMats = 0.375 -- 1-2 Lesser, 75% chance = 1.5*0.75/3 + }, + { + minItemLevel = 152, + maxItemLevel = 200, + -- amountOfMats = 1.125 + amountOfMats = 1.125 -- 1-2 Greater, 75% chance = 1.5*0.75 + }, + }, + }, + }, + }, + -- ["item:52719:0:0:0:0:0:0"] = { + -- -- Greater Celestial Essence + -- name = GetItemInfo("item:52719:0:0:0:0:0:0"), + -- minLevel = 77, + -- maxLevel = 85, + -- itemTypes = { + -- [ARMOR] = { + -- [2] = { + -- { + -- minItemLevel = 201, + -- maxItemLevel = 275, + -- amountOfMats = 0.125 + -- }, + -- { + -- minItemLevel = 276, + -- maxItemLevel = 290, + -- amountOfMats = 0.167 + -- }, + -- { + -- minItemLevel = 291, + -- maxItemLevel = 305, + -- amountOfMats = 0.208 + -- }, + -- { + -- minItemLevel = 306, + -- maxItemLevel = 315, + -- amountOfMats = 0.375 + -- }, + -- { + -- minItemLevel = 316, + -- maxItemLevel = 325, + -- amountOfMats = 0.625 + -- }, + -- { + -- minItemLevel = 326, + -- maxItemLevel = 350, + -- amountOfMats = 0.75 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [2] = { + -- { + -- minItemLevel = 201, + -- maxItemLevel = 275, + -- amountOfMats = 0.375 + -- }, + -- { + -- minItemLevel = 276, + -- maxItemLevel = 290, + -- amountOfMats = 0.5 + -- }, + -- { + -- minItemLevel = 291, + -- maxItemLevel = 305, + -- amountOfMats = 0.625 + -- }, + -- { + -- minItemLevel = 306, + -- maxItemLevel = 315, + -- amountOfMats = 1.125 + -- }, + -- { + -- minItemLevel = 316, + -- maxItemLevel = 325, + -- amountOfMats = 1.875 + -- }, + -- { + -- minItemLevel = 326, + -- maxItemLevel = 350, + -- amountOfMats = 2.25 + -- }, + -- }, + -- }, + -- }, + -- }, + -- ["item:74250:0:0:0:0:0:0"] = { + -- -- Mysterious Essence + -- name = GetItemInfo("item:74250:0:0:0:0:0:0"), + -- minLevel = 83, + -- maxLevel = 88, + -- itemTypes = { + -- [ARMOR] = { + -- [2] = { + -- { + -- minItemLevel = 364, + -- maxItemLevel = 390, + -- amountOfMats = 0.15 + -- }, + -- { + -- minItemLevel = 391, + -- maxItemLevel = 410, + -- amountOfMats = 0.225 + -- }, + -- { + -- minItemLevel = 411, + -- maxItemLevel = 450, + -- amountOfMats = 0.3 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [2] = { + -- { + -- minItemLevel = 377, + -- maxItemLevel = 390, + -- amountOfMats = 0.15 + -- }, + -- { + -- minItemLevel = 391, + -- maxItemLevel = 410, + -- amountOfMats = 0.225 + -- }, + -- { + -- minItemLevel = 411, + -- maxItemLevel = 450, + -- amountOfMats = 0.3 + -- }, + -- }, + -- }, + -- }, + -- }, + }, + { + desc = L["Shards"], + ["item:10978:0:0:0:0:0:0"] = { + -- Small Glimmering Shard + name = GetItemInfo("item:10978:0:0:0:0:0:0"), + minLevel = 1, + maxLevel = 20, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 1, + maxItemLevel = 20, + amountOfMats = 0.05 + }, + { + minItemLevel = 21, + maxItemLevel = 25, + amountOfMats = 0.1 + }, + }, + [3] = { + { + minItemLevel = 1, + maxItemLevel = 25, + amountOfMats = 1.000 + }, + }, + }, + [WEAPON] = { + [3] = { + { + minItemLevel = 1, + maxItemLevel = 25, + amountOfMats = 1.000 + }, + }, + }, + }, + }, + ["item:11084:0:0:0:0:0:0"] = { + -- Large Glimmering Shard + name = GetItemInfo("item:11084:0:0:0:0:0:0"), + minLevel = 16, + maxLevel = 25, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 1.000 + }, + }, + }, + [WEAPON] = { + [3] = { + { + minItemLevel = 26, + maxItemLevel = 30, + amountOfMats = 1.000 + }, + }, + }, + }, + }, + ["item:11138:0:0:0:0:0:0"] = { + -- Small Glowing Shard + name = GetItemInfo("item:11138:0:0:0:0:0:0"), + minLevel = 26, + maxLevel = 30, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 1.000 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 31, + maxItemLevel = 35, + amountOfMats = 1.000 + }, + }, + }, + }, + }, + ["item:11139:0:0:0:0:0:0"] = { + -- Large Glowing Shard + name = GetItemInfo("item:11139:0:0:0:0:0:0"), + minLevel = 31, + maxLevel = 35, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 1.000 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 1.000 + }, + }, + }, + }, + }, + ["item:11177:0:0:0:0:0:0"] = { + -- Small Radiant Shard + name = GetItemInfo("item:11177:0:0:0:0:0:0"), + minLevel = 36, + maxLevel = 40, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 1.000 + }, + }, + [4] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 3 + }, + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 3.5 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 1.000 + }, + }, + [4] = { + { + minItemLevel = 36, + maxItemLevel = 40, + amountOfMats = 3 + }, + { + minItemLevel = 41, + maxItemLevel = 45, + amountOfMats = 3.5 + }, + }, + }, + }, + }, + ["item:11178:0:0:0:0:0:0"] = { + -- Large Radiant Shard + name = GetItemInfo("item:11178:0:0:0:0:0:0"), + minLevel = 41, + maxLevel = 45, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 1.000 + }, + }, + [4] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 3.5 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 1.000 + }, + }, + [4] = { + { + minItemLevel = 46, + maxItemLevel = 50, + amountOfMats = 3.5 + }, + }, + }, + }, + }, + ["item:14343:0:0:0:0:0:0"] = { + -- Small Brilliant Shard + name = GetItemInfo("item:14343:0:0:0:0:0:0"), + minLevel = 46, + maxLevel = 50, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 1.000 + }, + }, + [4] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 3.5 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 1.000 + }, + }, + [4] = { + { + minItemLevel = 51, + maxItemLevel = 55, + amountOfMats = 3.5 + }, + }, + }, + }, + }, + ["item:14344:0:0:0:0:0:0"] = { + -- Large Brilliant Shard + name = GetItemInfo("item:14344:0:0:0:0:0:0"), + minLevel = 56, + maxLevel = 75, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 56, + maxItemLevel = 65, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 56, + maxItemLevel = 65, + amountOfMats = 0.995 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 56, + maxItemLevel = 65, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 56, + maxItemLevel = 65, + amountOfMats = 0.995 + }, + }, + }, + }, + }, + ["item:22449:0:0:0:0:0:0"] = { + -- Large Prismatic Shard + name = GetItemInfo("item:22449:0:0:0:0:0:0"), + minLevel = 56, + maxLevel = 70, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 66, + maxItemLevel = 99, + amountOfMats = 0.0167 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 66, + maxItemLevel = 99, + amountOfMats = 0.33 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 1 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 66, + maxItemLevel = 99, + amountOfMats = 0.0167 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 0.05 + }, + }, + [3] = { + { + minItemLevel = 66, + maxItemLevel = 99, + amountOfMats = 0.33 + }, + { + minItemLevel = 100, + maxItemLevel = 120, + amountOfMats = 1 + }, + }, + }, + }, + }, + ["item:34052:0:0:0:0:0:0"] = { + -- Dream Shard + -- 2 is uncommon, 3 is rare, 4 is epic + name = GetItemInfo("item:34052:0:0:0:0:0:0"), + minLevel = 68, + maxLevel = 80, + itemTypes = { + [ARMOR] = { + [2] = { + { + minItemLevel = 121, + maxItemLevel = 151, + -- amountOfMats = 0.0167 + amountOfMats = 0.01 -- 1 Small, 3% Chance = 1/3*0.03 + }, + { + minItemLevel = 152, + maxItemLevel = 200, + -- amountOfMats = 0.05 + amountOfMats = 0.03 -- 1 Large, 3% Chance = 1*0.03 + }, + }, + [3] = { + { + minItemLevel = 121, + maxItemLevel = 164, + amountOfMats = 0.333 -- 1 Small, 100% Chance = 1/3*1 + }, + { + minItemLevel = 165, + maxItemLevel = 200, + amountOfMats = 1 -- 1 Large, 100% Chance = 1*1 + }, + }, + }, + [WEAPON] = { + [2] = { + { + minItemLevel = 121, + maxItemLevel = 151, + -- amountOfMats = 0.0167 + amountOfMats = 0.01 -- 1 Small, 3% Chance = 1/3*0.03 + }, + { + minItemLevel = 152, + maxItemLevel = 200, + -- amountOfMats = 0.05 + amountOfMats = 0.03 -- 1 Large, 3% Chance = 1*0.03 + }, + }, + [3] = { + { + minItemLevel = 121, + maxItemLevel = 164, + amountOfMats = 0.333 -- 1 Small, 100% Chance = 1/3*1 + }, + { + minItemLevel = 165, + maxItemLevel = 200, + amountOfMats = 1 -- 1 Large, 100% Chance = 1*1 + }, + }, + }, + }, + }, + -- ["item:52720:0:0:0:0:0:0"] = { + -- -- Small Heavenly Shard + -- name = GetItemInfo("item:52720:0:0:0:0:0:0"), + -- minLevel = 78, + -- maxLevel = 85, + -- itemTypes = { + -- [ARMOR] = { + -- [3] = { + -- { + -- minItemLevel = 282, + -- maxItemLevel = 316, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [3] = { + -- { + -- minItemLevel = 282, + -- maxItemLevel = 316, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- }, + -- }, + -- ["item:52721:0:0:0:0:0:0"] = { + -- -- Heavenly Shard + -- name = GetItemInfo("item:52721:0:0:0:0:0:0"), + -- minLevel = 78, + -- maxLevel = 85, + -- itemTypes = { + -- [ARMOR] = { + -- [3] = { + -- { + -- minItemLevel = 282, + -- maxItemLevel = 316, + -- amountOfMats = 0.33 + -- }, + -- { + -- minItemLevel = 317, + -- maxItemLevel = 377, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [3] = { + -- { + -- minItemLevel = 282, + -- maxItemLevel = 316, + -- amountOfMats = 0.33 + -- }, + -- { + -- minItemLevel = 317, + -- maxItemLevel = 377, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- }, + -- }, + -- ["item:74252:0:0:0:0:0:0"] = { + -- --Small Ethereal Shard + -- name = GetItemInfo("item:74252:0:0:0:0:0:0"), + -- minLevel = 85, + -- maxLevel = 90, + -- itemTypes = { + -- [ARMOR] = { + -- [3] = { + -- { + -- minItemLevel = 384, + -- maxItemLevel = 429, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [3] = { + -- { + -- minItemLevel = 384, + -- maxItemLevel = 429, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- }, + -- }, + -- ["item:74247:0:0:0:0:0:0"] = { + -- --Ethereal Shard + -- name = GetItemInfo("item:74247:0:0:0:0:0:0"), + -- minLevel = 85, + -- maxLevel = 90, + -- itemTypes = { + -- [ARMOR] = { + -- [3] = { + -- { + -- minItemLevel = 384, + -- maxItemLevel = 429, + -- amountOfMats = 0.33 + -- }, + -- { + -- minItemLevel = 430, + -- maxItemLevel = 500, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [3] = { + -- { + -- minItemLevel = 384, + -- maxItemLevel = 429, + -- amountOfMats = 0.33 + -- }, + -- { + -- minItemLevel = 430, + -- maxItemLevel = 500, + -- amountOfMats = 1 + -- }, + -- }, + -- }, + -- }, + -- }, + }, + { + desc = L["Crystals"], + ["item:20725:0:0:0:0:0:0"] = { + -- Nexus Crystal + name = GetItemInfo("item:20725:0:0:0:0:0:0"), + minLevel = 56, + maxLevel = 60, + itemTypes = { + [ARMOR] = { + [4] = { + { + minItemLevel = 56, + maxItemLevel = 60, + amountOfMats = 1.000 + }, + { + minItemLevel = 61, + maxItemLevel = 94, + amountOfMats = 1.5 + }, + }, + }, + [WEAPON] = { + [4] = { + { + minItemLevel = 56, + maxItemLevel = 60, + amountOfMats = 1.000 + }, + { + minItemLevel = 61, + maxItemLevel = 94, + amountOfMats = 1.5 + }, + }, + }, + }, + }, + ["item:22450:0:0:0:0:0:0"] = { + -- Void Crystal + name = GetItemInfo("item:22450:0:0:0:0:0:0"), + minLevel = 70, + maxLevel = 70, + itemTypes = { + [ARMOR] = { + [4] = { + { + minItemLevel = 95, + maxItemLevel = 99, + amountOfMats = 1 + }, + { + minItemLevel = 100, + maxItemLevel = 164, + amountOfMats = 1.5 + }, + }, + }, + [WEAPON] = { + [4] = { + { + minItemLevel = 95, + maxItemLevel = 99, + amountOfMats = 1 + }, + { + minItemLevel = 100, + maxItemLevel = 164, + amountOfMats = 1.5 + }, + }, + }, + }, + }, + ["item:34057:0:0:0:0:0:0"] = { + -- Abyss Crystal + name = GetItemInfo("item:34057:0:0:0:0:0:0"), + minLevel = 80, + maxLevel = 80, + itemTypes = { + [ARMOR] = { + [4] = { + { + minItemLevel = 165, + maxItemLevel = 299, + amountOfMats = 1.000 + }, + }, + }, + [WEAPON] = { + [4] = { + { + minItemLevel = 165, + maxItemLevel = 299, + amountOfMats = 1.000 + }, + }, + }, + }, + }, + -- ["item:52722:0:0:0:0:0:0"] = { + -- -- Maelstrom Crystal + -- name = GetItemInfo("item:52722:0:0:0:0:0:0"), + -- minLevel = 85, + -- maxLevel = 85, + -- itemTypes = { + -- [ARMOR] = { + -- [4] = { + -- { + -- minItemLevel = 300, + -- maxItemLevel = 419, + -- amountOfMats = 1.000 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [4] = { + -- { + -- minItemLevel = 285, + -- maxItemLevel = 419, + -- amountOfMats = 1.000 + -- }, + -- }, + -- }, + -- }, + -- }, + -- ["item:74248:0:0:0:0:0:0"] = { + -- -- Sha Crystal + -- name = GetItemInfo("item:74248:0:0:0:0:0:0"), + -- minLevel = 85, + -- maxLevel = 90, + -- itemTypes = { + -- [ARMOR] = { + -- [4] = { + -- { + -- minItemLevel = 420, + -- maxItemLevel = 600, + -- amountOfMats = 1.000 + -- }, + -- }, + -- }, + -- [WEAPON] = { + -- [4] = { + -- { + -- minItemLevel = 420, + -- maxItemLevel = 600, + -- amountOfMats = 1.000 + -- }, + -- }, + -- }, + -- }, + -- }, + }, +} + +data.notDisenchantable = { + ["item:11290:0:0:0:0:0:0"] = true, + ["item:11289:0:0:0:0:0:0"] = true, + ["item:11288:0:0:0:0:0:0"] = true, + ["item:11287:0:0:0:0:0:0"] = true, + -- ["item:60223:0:0:0:0:0:0"] = true, + -- ["item:52252:0:0:0:0:0:0"] = true, + ["item:20406:0:0:0:0:0:0"] = true, + ["item:20407:0:0:0:0:0:0"] = true, + ["item:20408:0:0:0:0:0:0"] = true, + ["item:21766:0:0:0:0:0:0"] = true, + -- ["item:52485:0:0:0:0:0:0"] = true, + -- ["item:52486:0:0:0:0:0:0"] = true, + -- ["item:52487:0:0:0:0:0:0"] = true, + -- ["item:52488:0:0:0:0:0:0"] = true, + -- ["item:97826:0:0:0:0:0:0"] = true, + -- ["item:97827:0:0:0:0:0:0"] = true, + -- ["item:97828:0:0:0:0:0:0"] = true, + -- ["item:97829:0:0:0:0:0:0"] = true, + -- ["item:97830:0:0:0:0:0:0"] = true, + -- ["item:97831:0:0:0:0:0:0"] = true, + -- ["item:97832:0:0:0:0:0:0"] = true, +} + +function TSMAPI:GetEnchantingConversionNum(targetID, matID) + if targetID == matID then return 1 end + + if data.notDisenchantable[matID] then return end + local rarity, ilvl, _, class = select(3, GetItemInfo(matID)) + for i = 1, #data.disenchant do + local mat = data.disenchant[i][targetID] + if mat and mat.itemTypes and mat.itemTypes[class] and mat.itemTypes[class][rarity] then + for _, iData in ipairs(mat.itemTypes[class][rarity]) do + if ilvl >= iData.minItemLevel and ilvl <= iData.maxItemLevel then + return iData.amountOfMats + end + end + end + end +end + +function TSMAPI:GetEnchantingTargetItems() + local items = {} + for _, data in pairs(data.disenchant) do + for itemString in pairs(data) do + if itemString ~= "desc" then + tinsert(items, itemString) + end + end + end + return items +end + +function TSMAPI:GetDisenchantData(targetItem) + for i = 1, #data.disenchant do + if data.disenchant[i][targetItem] then + return data.disenchant[i][targetItem] + end + end +end \ No newline at end of file diff --git a/TradeSkillMaster/Data/ItemData.lua b/TradeSkillMaster/Data/ItemData.lua new file mode 100644 index 0000000..a33d071 --- /dev/null +++ b/TradeSkillMaster/Data/ItemData.lua @@ -0,0 +1,32 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- random lookup tables and other functions that don't have a home go in here + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") + +TSMAPI.EquipLocLookup = { + [INVTYPE_HEAD]=1, [INVTYPE_NECK]=2, [INVTYPE_SHOULDER]=3, [INVTYPE_BODY]=4, [INVTYPE_CHEST]=5, + [INVTYPE_WAIST]=6, [INVTYPE_LEGS]=7, [INVTYPE_FEET]=8, [INVTYPE_WRIST]=9, [INVTYPE_HAND]=10, + [INVTYPE_FINGER]=11, [INVTYPE_TRINKET]=12, [INVTYPE_CLOAK]=13, [INVTYPE_HOLDABLE]=14, + [INVTYPE_WEAPONMAINHAND]=15, [INVTYPE_ROBE]=16, [INVTYPE_TABARD]=17, [INVTYPE_BAG]=18, + [INVTYPE_2HWEAPON]=19, [INVTYPE_RANGED]=20, [INVTYPE_SHIELD]=21, [INVTYPE_WEAPON]=22 +} + +TSMAPI.SOULBOUND_MATS = { + -- ["item:79731:0:0:0:0:0:0"] = true, -- Scroll of Wisdom + -- ["item:76061:0:0:0:0:0:0"] = true, -- Spirit of Harmony + -- ["item:82447:0:0:0:0:0:0"] = true, -- Imperial Silk + -- ["item:54440:0:0:0:0:0:0"] = true, -- Dreamcloth + -- ["item:94111:0:0:0:0:0:0"] = true, -- Lightning Steel Ingot + -- ["item:94113:0:0:0:0:0:0"] = true, -- Jard's Peculiar Energy Source + -- ["item:98717:0:0:0:0:0:0"] = true, -- Balanced Trillium Ingot + -- ["item:98619:0:0:0:0:0:0"] = true, -- Celestial Cloth + -- ["item:98617:0:0:0:0:0:0"] = true, -- Hardened Magnificent Hide +} \ No newline at end of file diff --git a/TradeSkillMaster/Data/Vendor.lua b/TradeSkillMaster/Data/Vendor.lua new file mode 100644 index 0000000..39b72bc --- /dev/null +++ b/TradeSkillMaster/Data/Vendor.lua @@ -0,0 +1,104 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local Vendor = TSM:NewModule("Vendor", "AceEvent-3.0") +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +function Vendor:OnEnable() + Vendor:RegisterEvent("MERCHANT_SHOW", "ScanMerchant") +end + +local vendorItems = { + ["item:2320:0:0:0:0:0:0"] = 10, + ["item:2321:0:0:0:0:0:0"] = 10, + ["item:2324:0:0:0:0:0:0"] = 25, + ["item:2325:0:0:0:0:0:0"] = 1000, + ["item:2604:0:0:0:0:0:0"] = 50, + ["item:2605:0:0:0:0:0:0"] = 10, + ["item:2678:0:0:0:0:0:0"] = 10, + ["item:2880:0:0:0:0:0:0"] = 100, + ["item:3371:0:0:0:0:0:0"] = 100, + ["item:3466:0:0:0:0:0:0"] = 2000, + ["item:4289:0:0:0:0:0:0"] = 50, + ["item:4291:0:0:0:0:0:0"] = 500, + ["item:4340:0:0:0:0:0:0"] = 350, + ["item:4341:0:0:0:0:0:0"] = 500, + ["item:4342:0:0:0:0:0:0"] = 2500, + ["item:4399:0:0:0:0:0:0"] = 200, + ["item:4400:0:0:0:0:0:0"] = 2000, + ["item:4470:0:0:0:0:0:0"] = 38, + ["item:6260:0:0:0:0:0:0"] = 50, + ["item:6261:0:0:0:0:0:0"] = 100, + ["item:8343:0:0:0:0:0:0"] = 2000, + ["item:10290:0:0:0:0:0:0"] = 2500, + ["item:10647:0:0:0:0:0:0"] = 2000, + ["item:10648:0:0:0:0:0:0"] = 100, + ["item:11291:0:0:0:0:0:0"] = 4500, + ["item:14341:0:0:0:0:0:0"] = 5000, + ["item:17020:0:0:0:0:0:0"] = 1000, + ["item:17194:0:0:0:0:0:0"] = 10, + ["item:17196:0:0:0:0:0:0"] = 50, + ["item:30817:0:0:0:0:0:0"] = 25, + ["item:34412:0:0:0:0:0:0"] = 1000, + ["item:35949:0:0:0:0:0:0"] = 8500, + ["item:38426:0:0:0:0:0:0"] = 30000, + ["item:38682:0:0:0:0:0:0"] = 1000, + ["item:39354:0:0:0:0:0:0"] = 15, + ["item:39501:0:0:0:0:0:0"] = 1200, + ["item:39502:0:0:0:0:0:0"] = 5000, + ["item:39684:0:0:0:0:0:0"] = 9000, + ["item:40533:0:0:0:0:0:0"] = 50000, + ["item:44835:0:0:0:0:0:0"] = 10, + ["item:44853:0:0:0:0:0:0"] = 25, + -- ["item:52188:0:0:0:0:0:0"] = 15000, + -- ["item:58274:0:0:0:0:0:0"] = 11000, + -- ["item:58278:0:0:0:0:0:0"] = 16000, + -- ["item:62323:0:0:0:0:0:0"] = 60000, + -- ["item:62786:0:0:0:0:0:0"] = 1000, + -- ["item:62787:0:0:0:0:0:0"] = 1000, + -- ["item:62788:0:0:0:0:0:0"] = 1000, + -- ["item:67319:0:0:0:0:0:0"] = 328990, + -- ["item:67335:0:0:0:0:0:0"] = 445561, + -- ["item:67348:0:0:0:0:0:0"] = 394755, + -- ["item:68047:0:0:0:0:0:0"] = 170437, + -- ["item:74659:0:0:0:0:0:0"] = 30000, + -- ["item:74660:0:0:0:0:0:0"] = 15000, + -- ["item:74832:0:0:0:0:0:0"] = 12000, + -- ["item:74845:0:0:0:0:0:0"] = 35000, + -- ["item:74851:0:0:0:0:0:0"] = 14000, + -- ["item:74852:0:0:0:0:0:0"] = 16000, + -- ["item:74854:0:0:0:0:0:0"] = 7000, + -- ["item:79740:0:0:0:0:0:0"] = 23, + -- ["item:83092:0:0:0:0:0:0"] = 20000.0000, + -- ["item:85583:0:0:0:0:0:0"] = 12000, + -- ["item:85584:0:0:0:0:0:0"] = 17000, + -- ["item:85585:0:0:0:0:0:0"] = 27000, +} + +-- returns the vendor cost for a given target item +function TSMAPI:GetVendorCost(itemString) + return itemString and TSM.db.global.vendorItems[itemString] or vendorItems[itemString] +end + +function Vendor:ScanMerchant(first) + for i=1, GetMerchantNumItems() do + local itemString = TSMAPI:GetItemString(GetMerchantItemLink(i)) + if itemString then + local _, _, price, _, numAvailable, _, extendedCost = GetMerchantItemInfo(i) + if price > 0 and not extendedCost and numAvailable == -1 then + TSM.db.global.vendorItems[itemString] = price + else + TSM.db.global.vendorItems[itemString] = nil + end + end + end + if first then + TSMAPI:CreateTimeDelay("scanMerchantDelay", 1, function() Vendor:ScanMerchant() end) + end +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/BankUI.lua b/TradeSkillMaster/GUI/BankUI.lua new file mode 100644 index 0000000..6e5830a --- /dev/null +++ b/TradeSkillMaster/GUI/BankUI.lua @@ -0,0 +1,346 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains function for the bank/gbank frame + +-- loads the localization table -- +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") + +-- load the parent file (TSM) into a local variable and register this file as a module +local TSM = select(2, ...) +local BankUI = TSM:NewModule("BankUI", "AceEvent-3.0") +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI librarie + +local ui +local bankFrame, bankType +local bFrame = nil +local container = nil +local registeredModules = {} +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.BankUI_private") +private.bankUiButtons = {} + +function BankUI:OnEnable() + BankUI:RegisterEvent("GUILDBANKFRAME_OPENED", function(event) + bankType = "guild" + TSMAPI:CreateTimeDelay(0.1, function() + bankFrame = BankUI:getBankFrame("guildbank") + if TSM.db.profile.isBankui then + if #private.bankUiButtons > 0 then + if ui then + BankUI:resetPoints(ui) + ui:Show() + if TSM.db.global.bankUITab then + for index, info in ipairs(private.bankUiButtons) do + if info.moduleName == TSM.db.global.bankUITab then + ui.buttons[index]:Click() + break + end + end + else + ui.buttons[1]:Click() + end + return + end + ui = BankUI:getFrame(bankFrame) + end + end + end) + end) + + BankUI:RegisterEvent("BANKFRAME_OPENED", function(event) + bankType = "bank" + TSMAPI:CreateTimeDelay(0.1, function() + bankFrame = BankUI:getBankFrame("bank") + if TSM.db.profile.isBankui then + if #private.bankUiButtons > 0 then + if ui then + BankUI:resetPoints(ui) + ui:Show() + if TSM.db.global.bankUITab then + for index, info in ipairs(private.bankUiButtons) do + if info.moduleName == TSM.db.global.bankUITab then + ui.buttons[index]:Click() + break + end + end + else + ui.buttons[1]:Click() + end + return + end + ui = BankUI:getFrame(bankFrame) + end + end + end) + end) + + BankUI:RegisterEvent("GUILDBANKFRAME_CLOSED", function(event, addon) + if ui then ui:Hide() end + bankType = nil + end) + + BankUI:RegisterEvent("BANKFRAME_CLOSED", function(event) + if ui then ui:Hide() end + bankType = nil + end) +end + +local function createCloseButton(text, parent, func) + local btn = TSMAPI.GUI:CreateButton(bFrame, 18, "Button") + btn:SetText(text) + btn:SetHeight(20) + btn:SetWidth(20) + return btn +end + +function TSM:RegisterBankUiButton(moduleName, callback) + if registeredModules[moduleName] then return end + registeredModules[moduleName] = true + local info = {} + info.moduleName = moduleName + info.callback = callback + tinsert(private.bankUiButtons, info) + sort(private.bankUiButtons, function(a, b) + if a.moduleName == "Warehousing" then + return true + elseif b.moduleName == "Warehousing" then + return false + else + return a.moduleName < b.moduleName + end + end) +end + +function BankUI:getBankFrame(bank) + if BagnonFrameguildbank and BagnonFrameguildbank:IsVisible() then + return BagnonFrameguildbank + elseif BagnonFramebank and BagnonFramebank:IsVisible() then + return BagnonFramebank + elseif GuildBankFrame and GuildBankFrame:IsVisible() then + return GuildBankFrame + elseif BankFrame and BankFrame:IsVisible() then + return BankFrame + elseif (famBankFrame and famBankFrame:IsVisible()) then + return famBankFrame + elseif (ARKINV_Frame4 and ARKINV_Frame4:IsVisible()) then + return ARKINV_Frame4 + elseif (ARKINV_Frame3 and ARKINV_Frame3:IsVisible()) then + return ARKINV_Frame3 + elseif (OneBankFrame and OneBankFrame:IsVisible()) then + return OneBankFrame + elseif (TukuiBank and TukuiBank:IsShown()) then + return TukuiBank + elseif (ElvUI_BankContainerFrame and ElvUI_BankContainerFrame:IsVisible()) then + return ElvUI_BankContainerFrame + elseif (LUIBank and LUIBank:IsVisible()) then + return LUIBank + elseif (AdiBagsContainer1 and AdiBagsContainer1.isBank and AdiBagsContainer1:IsVisible()) then + return AdiBagsContainer1 + elseif (AdiBagsContainer2 and AdiBagsContainer2.isBank and AdiBagsContainer2:IsVisible()) then + return AdiBagsContainer2 + elseif (BagsFrameBank and BagsFrameBank:IsVisible()) then + return BagsFrameBank + elseif AspUIBank and AspUIBank:IsVisible() then + return AspUIBank + elseif NivayacBniv_Bank and NivayacBniv_Bank:IsVisible() then + return NivayacBniv_Bank + elseif DufUIBank and DufUIBank:IsVisible() then + return DufUIBank + end + + return nil +end + +function BankUI:getFrame(frameType) + bFrame = CreateFrame("Frame", nil, UIParent) + bFrame:Hide() + --size-- + bFrame:SetWidth(275) + bFrame:SetHeight(470) + bFrame:SetPoint("CENTER", UIParent) + + --for moving-- + bFrame:SetScript("OnMouseDown", bFrame.StartMoving) + bFrame:SetScript("OnMouseUp", function(...) bFrame.StopMovingOrSizing(...) + if bankType == "guild" then + TSM.db.factionrealm.bankUIGBankFramePosition = { bFrame:GetLeft(), bFrame:GetBottom() } + else + TSM.db.factionrealm.bankUIBankFramePosition = { bFrame:GetLeft(), bFrame:GetBottom() } + end + end) + bFrame:SetMovable(true) + bFrame:EnableMouse(true) + + bFrame:SetPoint("CENTER", UIParent) + + local function OnFrameShow(self) + self:SetFrameLevel(0) + self:ClearAllPoints() + if bankType == "guild" then + self:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIGBankFramePosition)) + else + self:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIBankFramePosition)) + end + end + + TSMAPI.Design:SetFrameBackdropColor(bFrame) + bFrame:SetScript("OnShow", OnFrameShow) + bFrame:Show() + + local title = TSMAPI.GUI:CreateLabel(bFrame) + title:SetPoint("TOPLEFT", 40, -3) + title:SetPoint("BOTTOMRIGHT", bFrame, "TOPRIGHT", -5, -23) + title:SetJustifyH("CENTER") + title:SetJustifyV("CENTER") + title:SetText("TradeSkillMaster - " .. TSM._version) + TSMAPI.Design:SetTitleTextColor(title) + + local title2 = TSMAPI.GUI:CreateLabel(bFrame) + title2:SetPoint("TOPLEFT", title, "BOTTOMLEFT") + title2:SetPoint("TOPRIGHT", title, "BOTTOMRIGHT") + title2:SetJustifyH("CENTER") + title2:SetJustifyV("CENTER") + title2:SetText(L["BankUI"]) + TSMAPI.Design:SetTitleTextColor(title2) + + + bFrame.btnClose = createCloseButton("X", bFrame, nil) + bFrame.btnClose:SetPoint("TOPRIGHT", bFrame, "TOPRIGHT") + bFrame.btnClose:SetScript("OnClick", function(self) + if bFrame then bFrame:Hide() end + TSM.db.profile.isBankui = false + TSM:Print(L["You have closed the bankui. Use '/tsm bankui' to view again."]) + end) + + -- module buttons + bFrame.buttons = {} + + local iconFrame = CreateFrame("Frame", nil, bFrame) + iconFrame:SetPoint("CENTER", bFrame, "TOPLEFT", 25, -25) + iconFrame:SetHeight(80) + iconFrame:SetWidth(80) + local icon = iconFrame:CreateTexture(nil, "ARTWORK") + icon:SetAllPoints() + icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Big") + local ag = iconFrame:CreateAnimationGroup() + local spin = ag:CreateAnimation("Rotation") + spin:SetOrder(1) + spin:SetDuration(2) + spin:SetDegrees(90) + local spin = ag:CreateAnimation("Rotation") + spin:SetOrder(2) + spin:SetDuration(4) + spin:SetDegrees(-180) + local spin = ag:CreateAnimation("Rotation") + spin:SetOrder(3) + spin:SetDuration(2) + spin:SetDegrees(90) + ag:SetLooping("REPEAT") + iconFrame:SetScript("OnEnter", function() ag:Play() end) + iconFrame:SetScript("OnLeave", function() ag:Stop() end) + + container = CreateFrame("Frame", nil, bFrame) + container:SetPoint("TOPLEFT", 5, -60) + container:SetPoint("BOTTOMRIGHT", -5, 5) + TSMAPI.Design:SetFrameColor(container) + + for _, info in ipairs(private.bankUiButtons) do + info.bankTab = info.callback(container) + private:CreateBankButton(info.moduleName) + end + + if TSM.db.global.bankUITab then + for index, info in ipairs(private.bankUiButtons) do + if info.moduleName == TSM.db.global.bankUITab then + bFrame.buttons[index]:Click() + break + end + end + else + bFrame.buttons[1]:Click() + end + return bFrame +end + +function BankUI:resetPoints(container) + if bankType == "guild" then + container:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIGBankFramePosition)) + else + container:SetPoint("BOTTOMLEFT", UIParent, unpack(TSM.db.factionrealm.bankUIBankFramePosition)) + end +end + +function private:CreateBankButton(module) + local buttonIndex + for record, info in ipairs(private.bankUiButtons) do + if info.moduleName == module then + buttonIndex = record + end + end + + local function OnButtonClick(self) + for _, info in ipairs(private.bankUiButtons) do + info.bankTab:Hide() + end + + for index, button in ipairs(bFrame.buttons) do + button:UnlockHighlight() + if self == button then + private.bankUiButtons[index].bankTab:Show() + end + end + self:LockHighlight() + end + + local button = TSMAPI.GUI:CreateButton(bFrame, 12) + if buttonIndex == 1 then + button:SetPoint("TOPLEFT", 70, -40) + else + button:SetPoint("TOPLEFT", bFrame.buttons[buttonIndex - 1], "TOPRIGHT", 5, 0) + end + button:SetHeight(20) + button:SetWidth(70) + button:SetText(module) + button:SetScript("OnClick", OnButtonClick) + tinsert(bFrame.buttons, button) +end + +function TSM:toggleBankUI() + if TSM:areBanksVisible() then + if ui then + if ui:IsShown() then + ui:Hide() + else + ui:Show() + end + else + ui = BankUI:getFrame(bankFrame) + TSM.db.profile.isBankui = true + end + else + TSM:Print(L["There are no visible banks."]) + end +end + +function TSM:getBankTabs() + local tabs = {} + for record, info in ipairs(private.bankUiButtons) do + tabs[info.moduleName] = info.moduleName + end + return tabs +end + +function TSM:ResetBankUIFramePosition() + TSM.db.factionrealm.bankUIGBankFramePosition = { 100, 300 } + TSM.db.factionrealm.bankUIBankFramePosition = { 100, 300 } + if ui then + ui:Hide() + ui:Show() + end +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/BuildPage.lua b/TradeSkillMaster/GUI/BuildPage.lua new file mode 100644 index 0000000..4d3bbba --- /dev/null +++ b/TradeSkillMaster/GUI/BuildPage.lua @@ -0,0 +1,433 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local lib = TSMAPI +local customPriceFrame + + +--[[----------------------------------------------------------------------------- +TSMAPI:BuildPage() Support Functions +-------------------------------------------------------------------------------]] + +local function CreateCustomPriceFrame() + local frame = CreateFrame("Frame", nil, TSMMainFrame1) + TSMAPI.Design:SetFrameBackdropColor(frame) + frame:Hide() + frame:SetPoint("TOPLEFT", TSMMainFrame1, "TOPRIGHT", 2, 0) + frame:SetWidth(300) + frame:SetHeight(400) + + local container = AceGUI:Create("TSMScrollFrame") + container:SetLayout("Flow") + container.frame:SetParent(frame) + container.frame:SetPoint("TOPLEFT", 5, -5) + container.frame:SetPoint("BOTTOMRIGHT", -5, 5) + + local page = { + { + type = "Label", + relativeWidth = 1, + text = L["Below are various ways you can set the value of the current editbox. Any combination of these methods is also supported."], + }, + { + type = "HeadingLine", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:GetInlineColor("category")..L["Fixed Gold Value"].."|r", + relativeWidth = 1, + }, + { + type = "Label", + text = L["A simple, fixed gold amount."], + relativeWidth = 1, + }, + { + type = "HeadingLine", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:GetInlineColor("category")..L["Percent of Price Source"].."|r", + relativeWidth = 1, + }, + { + type = "Label", + text = L["Type '/tsm sources' to print out all available price sources."], + relativeWidth = 1, + }, + { + type = "HeadingLine", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:GetInlineColor("category")..L["More Advanced Methods"].."|r", + relativeWidth = 1, + }, + { + type = "Label", + text = format("See %s for more info.", TSMAPI.Design:GetInlineColor("link").."http://bit.ly/TSMCP|r"), + relativeWidth = 1, + }, + { + type = "HeadingLine", + relativeWidth = 1, + }, + { + type = "Label", + text = TSMAPI.Design:GetInlineColor("category")..L["Examples"].."|r", + relativeWidth = 1, + }, + { + type = "Label", + text = "20g50s", + relativeWidth = 1, + }, + { + type = "Label", + text = "120% crafting", + relativeWidth = 1, + }, + { + type = "Label", + text = "100% vendor + 5g", + relativeWidth = 1, + }, + { + type = "Label", + text = "max(150% dbmarket, 1.2 * crafting)", + relativeWidth = 1, + }, + { + type = "Label", + text = "max(vendor, 120% crafting)", + relativeWidth = 1, + }, + } + + TSMAPI:BuildPage(container, page) + + return frame +end + +local function FormatCopperCustomPrice(value) + value = gsub(value, TSMAPI:StrEscape(TSM.GOLD_TEXT), "g") + value = gsub(value, TSMAPI:StrEscape(TSM.SILVER_TEXT), "s") + value = gsub(value, TSMAPI:StrEscape(TSM.COPPER_TEXT), "c") + local goldPart = select(3, strfind(value, "([0-9]+g)")) + local silverPart = select(3, strfind(value, "([0-9]+s)")) + local copperPart = select(3, strfind(value, "([0-9]+c)")) + if copperPart then + value = gsub(value, copperPart, gsub(copperPart, "c", TSM.COPPER_TEXT)) + end + if silverPart then + value = gsub(value, silverPart, gsub(silverPart, "s", TSM.SILVER_TEXT)) + end + if goldPart then + value = gsub(value, goldPart, gsub(goldPart, "g", TSM.GOLD_TEXT)) + end + return value +end + +local function AddTooltip(widget, text, title) + if not text then return end + widget:SetCallback("OnEnter", function(self) + GameTooltip:SetOwner(self.frame, "ANCHOR_NONE") + GameTooltip:SetPoint("BOTTOM", self.frame, "TOP") + if title then + GameTooltip:SetText(title, 1, .82, 0, 1) + end + if type(text) == "number" then + GameTooltip:SetHyperlink("item:" .. text) + elseif tonumber(text) then + GameTooltip:SetHyperlink("enchant:"..text) + elseif type(text) == "string" and (strfind(text, "item:")) then + TSMAPI:SafeTooltipLink(text) + else + GameTooltip:AddLine(text, 1, 1, 1, 1) + end + GameTooltip:Show() + end) + widget:SetCallback("OnLeave", function() + -- BattlePetTooltip:Hide() + GameTooltip:ClearLines() + GameTooltip:Hide() + end) +end + +local function CreateContainer(cType, parent, args) + local container = AceGUI:Create(cType) + if not container then return end + container:SetLayout(args.layout) + if args.title then container:SetTitle(args.title) end + container:SetRelativeWidth(args.relativeWidth or 1) + container:SetFullHeight(args.fullHeight) + parent:AddChild(container) + return container +end + +local function CreateWidget(wType, parent, args) + local widget = AceGUI:Create(wType) + if args.settingInfo then + args.value = args.value or args.settingInfo[1][args.settingInfo[2]] + if args.acceptCustom then + if tonumber(args.value) then + args.value = TSMAPI:FormatTextMoney(args.value) + elseif args.value then + args.value = FormatCopperCustomPrice(args.value) + end + end + local oldCallback = args.callback + args.callback = function(...) + local value = select(3, ...) + if type(value) == "string" then value = value:trim() end + if args.multiselect then + local key = value + value = select(4, ...) + args.settingInfo[1][args.settingInfo[2]][key] = value + else + args.settingInfo[1][args.settingInfo[2]] = value + end + if oldCallback then oldCallback(...) end + end + end + if args.text then widget:SetText(args.text) end + if args.label then widget:SetLabel(args.label) end + if args.width then + widget:SetWidth(args.width) + elseif args.relativeWidth then + if args.relativeWidth == 1 then + widget:SetFullWidth(true) + else + widget:SetRelativeWidth(args.relativeWidth) + end + end + if args.height then widget:SetHeight(args.height) end + if widget.SetDisabled then widget:SetDisabled(args.disabled) end + AddTooltip(widget, args.tooltip, args.label) + parent:AddChild(widget) + return widget +end + +local Add = { + InlineGroup = function(parent, args) + local container = CreateContainer("TSMInlineGroup", parent, args) + container:HideTitle(not args.title) + container:HideBorder(args.noBorder) + container:SetBackdrop(args.backdrop) + return container + end, + + SimpleGroup = function(parent, args) + local container = CreateContainer("TSMSimpleGroup", parent, args) + if args.height then container:SetHeight(args.height) end + return container + end, + + ScrollFrame = function(parent, args) + return CreateContainer("TSMScrollFrame", parent, args) + end, + + Image = function(parent, args) + local image = CreateWidget("TSMImage", parent, args) + image:SetImage(args.image) + image:SetSizeRatio(args.sizeRatio) + return image + end, + + Label = function(parent, args) + local labelWidget = CreateWidget("TSMLabel", parent, args) + labelWidget:SetColor(args.colorRed, args.colorGreen, args.colorBlue) + return labelWidget + end, + + MultiLabel = function(parent, args) + local labelWidget = CreateWidget("TSMMultiLabel", parent, args) + labelWidget:SetLabels(args.labelInfo) + return labelWidget + end, + + InteractiveLabel = function(parent, args) + local iLabelWidget = CreateWidget("TSMInteractiveLabel", parent, args) + iLabelWidget:SetCallback("OnClick", args.callback) + return iLabelWidget + end, + + Button = function(parent, args) + local buttonWidget = CreateWidget("TSMButton", parent, args) + buttonWidget:SetCallback("OnClick", args.callback) + return buttonWidget + end, + + GroupItemList = function(parent, args) + local groupItemList = CreateWidget("TSMGroupItemList", parent, args) + groupItemList:SetIgnoreVisible(args.showIgnore) + groupItemList:SetTitle("left", args.leftTitle) + groupItemList:SetTitle("right", args.rightTitle) + groupItemList:SetListCallback(args.listCallback) + groupItemList:SetCallback("OnAddClicked", args.onAdd) + groupItemList:SetCallback("OnRemoveClicked", args.onRemove) + return groupItemList + end, + + MacroButton = function(parent, args) + local macroButtonWidget = CreateWidget("TSMMacroButton", parent, args) + macroButtonWidget.frame:SetAttribute("type", "macro") + macroButtonWidget.frame:SetAttribute("macrotext", args.macroText) + return macroButtonWidget + end, + + EditBox = function(parent, args) + local editBoxWidget = CreateWidget("TSMEditBox", parent, args) + editBoxWidget:SetText(args.value) + editBoxWidget:DisableButton(args.onTextChanged) + editBoxWidget:SetAutoComplete(args.autoComplete) + local function callback(self, event, value) + if args.acceptCustom then + local badPriceSource = type(args.acceptCustom) == "string" and strlower(args.acceptCustom) + local customPrice, err = TSMAPI:ParseCustomPrice(value, badPriceSource) + if customPrice then + self:SetText(FormatCopperCustomPrice(value)) + self:ClearFocus() + args.callback(self, event, value) + else + TSM:Print(L["Invalid custom price."].." "..err) + self:SetFocus() + end + else + args.callback(self, event, value) + end + end + editBoxWidget:SetCallback(args.onTextChanged and "OnTextChanged" or "OnEnterPressed", callback) + if args.acceptCustom then + customPriceFrame = customPriceFrame or CreateCustomPriceFrame() + editBoxWidget:SetCallback("OnEditFocusGained", function() customPriceFrame:Show() end) + editBoxWidget:SetCallback("OnEditFocusLost", function() customPriceFrame:Hide() end) + end + return editBoxWidget + end, + + GroupBox = function(parent, args) + local groupBoxWidget = CreateWidget("TSMGroupBox", parent, args) + groupBoxWidget:SetText(args.value) + groupBoxWidget:SetCallback("OnValueChanged", args.callback) + return groupBoxWidget + end, + + CheckBox = function(parent, args) + local checkBoxWidget = CreateWidget("TSMCheckBox", parent, args) + checkBoxWidget:SetType(args.cbType or "checkbox") + checkBoxWidget:SetValue(args.value) + if args.label then checkBoxWidget:SetLabel(args.label) end + if not args.width and not args.relativeWidth then + checkBoxWidget:SetRelativeWidth(0.5) + end + checkBoxWidget:SetCallback("OnValueChanged", args.callback) + return checkBoxWidget + end, + + Slider = function(parent, args) + local sliderWidget = CreateWidget("TSMSlider", parent, args) + sliderWidget:SetValue(args.value) + sliderWidget:SetSliderValues(args.min, args.max, args.step) + sliderWidget:SetIsPercent(args.isPercent) + sliderWidget:SetCallback("OnValueChanged", args.callback) + return sliderWidget + end, + + Icon = function(parent, args) + local iconWidget = CreateWidget("Icon", parent, args) + iconWidget:SetImage(args.image) + iconWidget:SetImageSize(args.imageWidth, args.imageHeight) + iconWidget:SetCallback("OnClick", args.callback) + return iconWidget + end, + + Dropdown = function(parent, args) + local dropdownWidget = CreateWidget("TSMDropdown", parent, args) + dropdownWidget:SetList(args.list, args.order) + dropdownWidget:SetMultiselect(args.multiselect) + if type(args.value) == "table" then + for name, value in pairs(args.value) do + dropdownWidget:SetItemValue(name, value) + end + else + dropdownWidget:SetValue(args.value) + end + dropdownWidget:SetCallback("OnValueChanged", args.callback) + return dropdownWidget + end, + + ColorPicker = function(parent, args) + local colorPicker = CreateWidget("TSMColorPicker", parent, args) + colorPicker:SetHasAlpha(args.hasAlpha) + if type(args.value) == "table" then + colorPicker:SetColor(unpack(args.value)) + end + colorPicker:SetCallback("OnValueChanged", args.callback) + colorPicker:SetCallback("OnValueConfirmed", args.callback) + return colorPicker + end, + + Spacer = function(parent, args) + args.quantity = args.quantity or 1 + for i=1, args.quantity do + local spacer = parent:Add({type="Label", text=" ", relativeWidth=1}) + end + end, + + HeadingLine = function(parent, args) + local heading = AceGUI:Create("Heading") + heading:SetText("") + heading:SetRelativeWidth(args.relativeWidth or 1) + parent:AddChild(heading) + end, +} + +-- creates a widget or container as detailed in the passed table (iTable) and adds it as a child of the passed parent +function lib.AddGUIElement(parent, iTable) + assert(Add[iTable.type], "Invalid Widget or Container Type: "..iTable.type) + return Add[iTable.type](parent, iTable) +end + +-- goes through a page-table and draws out all the containers and widgets for that page +function TSMAPI:BuildPage(oContainer, oPageTable, noPause) + local function recursive(container, pageTable) + for _, data in pairs(pageTable) do + local parentElement = container:Add(data) + if data.children then + parentElement:PauseLayout() + -- yay recursive function calls! + recursive(parentElement, data.children) + parentElement:ResumeLayout() + parentElement:DoLayout() + end + end + end + if not oContainer.Add then + local container = AceGUI:Create("TSMSimpleGroup") + container:SetLayout("fill") + container:SetFullWidth(true) + container:SetFullHeight(true) + oContainer:AddChild(container) + oContainer = container + end + if not noPause then + oContainer:PauseLayout() + recursive(oContainer, oPageTable) + oContainer:ResumeLayout() + oContainer:DoLayout() + else + recursive(oContainer, oPageTable) + end +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/Design.lua b/TradeSkillMaster/GUI/Design.lua new file mode 100644 index 0000000..98a7cfb --- /dev/null +++ b/TradeSkillMaster/GUI/Design.lua @@ -0,0 +1,128 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains support code for the custom TSM widgets +local TSM = select(2, ...) +local lib = TSMAPI + +TSMAPI.Design = {} +local Design = TSMAPI.Design +local coloredFrames = {} +local coloredTexts = {} + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function expandColor(tbl) + tbl = CopyTable(tbl) + for i=1, 3 do + tbl[i] = tbl[i] / 255 + end + return unpack(tbl) +end + +local function SetFrameColor(obj, colorKey) + local color = TSM.db.profile.design.frameColors[colorKey] + if not obj then return expandColor(color.backdrop) end + coloredFrames[obj] = {obj, colorKey} + if obj:IsObjectType("Frame") then + obj:SetBackdrop({bgFile="Interface\\Buttons\\WHITE8X8", edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=TSM.db.profile.design.edgeSize}) + obj:SetBackdropColor(expandColor(color.backdrop)) + obj:SetBackdropBorderColor(expandColor(color.border)) + else + obj:SetTexture(expandColor(color.backdrop)) + end +end + +local function SetTextColor(obj, colorKey, isDisabled) + local color = TSM.db.profile.design.textColors[colorKey] + if not obj then return expandColor(color.enabled) end + coloredTexts[obj] = {obj, colorKey, isDisabled} + if obj:IsObjectType("Texture") then + obj:SetTexture(expandColor(color.enabled)) + else + if isDisabled then + obj:SetTextColor(expandColor(color.disabled)) + else + obj:SetTextColor(expandColor(color.enabled)) + end + end +end + +--[[----------------------------------------------------------------------------- +Design API functions +-------------------------------------------------------------------------------]] + +function Design:SetFrameBackdropColor(obj) + return SetFrameColor(obj, "frameBG") +end + +function Design:SetFrameColor(obj) + return SetFrameColor(obj, "frame") +end + +function Design:SetContentColor(obj) + return SetFrameColor(obj, "content") +end + +function Design:SetIconRegionColor(obj) + return SetTextColor(obj, "iconRegion") +end + +function Design:SetWidgetTextColor(obj, isDisabled) + return SetTextColor(obj, "text", isDisabled) +end + +function Design:SetWidgetLabelColor(obj, isDisabled) + return SetTextColor(obj, "label", isDisabled) +end + +function Design:SetTitleTextColor(obj) + return SetTextColor(obj, "title") +end + +function Design:GetContentFont(size) + size = size or "normal" + TSM.db.profile.design.fontSizes[size] = TSM.db.profile.design.fontSizes[size] or TSM.designDefaults.fontSizes[size] + assert(TSM.db.profile.design.fontSizes[size], format("Invalid font size '%s", tostring(size))) + return TSM.db.profile.design.fonts.content, TSM.db.profile.design.fontSizes[size] +end + +function Design:GetBoldFont() + return TSM.db.profile.design.fonts.bold +end + +function Design:GetInlineColor(key) + TSM.db.profile.design.inlineColors[key] = TSM.db.profile.design.inlineColors[key] or CopyTable(TSM.designDefaults.inlineColors[key]) + local r, g, b, a = unpack(TSM.db.profile.design.inlineColors[key]) + return format("|c%02X%02X%02X%02X", a, r, g, b) +end + +function Design:ColorText(text, key) + local color = Design:GetInlineColor(key) + return color..text.."|r" +end + + +function TSMAPI:UpdateDesign() + -- set any missing fields + TSM:SetDesignDefaults(TSM.designDefaults, TSM.db.profile.design) + local oldTbl = coloredFrames + coloredFrames = {} + for _, args in pairs(oldTbl) do + SetFrameColor(unpack(args)) + end + + oldTbl = coloredTexts + coloredTexts = {} + for _, args in pairs(oldTbl) do + SetTextColor(unpack(args)) + end +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/GroupTree.lua b/TradeSkillMaster/GUI/GroupTree.lua new file mode 100644 index 0000000..b698cf5 --- /dev/null +++ b/TradeSkillMaster/GUI/GroupTree.lua @@ -0,0 +1,373 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +local COUNT = 1 +local ROW_HEIGHT = 14 + + +local function UpdateTree(self) + self.statusText:SetText("") + local rowData = {} + local groupPathList, disabledGroupPaths = TSM:GetGroupPathList(self.module) + + for i, groupPath in ipairs(groupPathList) do + if not disabledGroupPaths[groupPath] then + local pathParts = { TSM.GROUP_SEP:split(groupPath) } + local leader = "" + for i = 1, #pathParts - 1 do + leader = leader .. " " + end + local hasSubGroups = (groupPathList[i + 1] and (groupPathList[i + 1] == groupPath or strfind(groupPathList[i + 1], "^" .. TSMAPI:StrEscape(groupPath) .. TSM.GROUP_SEP))) + local parent = #pathParts > 1 and table.concat(pathParts, TSM.GROUP_SEP, 1, #pathParts - 1) or nil + local index = #rowData + 1 + if self.selectedGroups[groupPath] == nil then + -- select group by default + self.selectedGroups[groupPath] = true + end + local groupNameText = pathParts[#pathParts] + if TSM.db.profile.colorGroupName then + groupNameText = TSMAPI:ColorGroupName(groupNameText, #pathParts) + end + rowData[index] = { + value = leader .. format("%s %s%s|r", groupNameText, TSMAPI.Design:GetInlineColor("link"), hasSubGroups and (self.collapsed[groupPath] and "[+]" or "[-]") or ""), + groupName = pathParts[#pathParts], + parent = parent, + groupPath = groupPath, + hasSubGroups = hasSubGroups, + index = index, + isSelected = not self.isGroupBox and self.selectedGroups[groupPath], -- select all rows by default (unless it's for a GroupBox) + } + end + end + + if #rowData == 0 then + if #groupPathList == 0 then + self.statusText:SetText(L["You currently don't have any groups setup. Type '/tsm' and click on the 'TradeSkillMaster Groups' button to setup TSM groups."]) + else + self.statusText:SetText(format(L["None of your groups have %s operations assigned. Type '/tsm' and click on the 'TradeSkillMaster Groups' button to assign operations to your TSM groups."], self.module)) + end + else + self.statusText:SetText("") + end + + self.rowData = rowData + self:RefreshRows() +end + +local function SelectAll(self) + for i = 1, #self.st.rowData do + self.st.selectedGroups[self.st.rowData[i].groupPath] = true + self.st.rowData[i].isSelected = true + end + self.st:RefreshRows() + for _, row in ipairs(self.st.rows) do + row.highlight:Show() + end +end + +local function DeselectAll(self) + for i = 1, #self.st.rowData do + self.st.selectedGroups[self.st.rowData[i].groupPath] = false + self.st.rowData[i].isSelected = nil + end + self.st:RefreshRows() + for _, row in ipairs(self.st.rows) do + row.highlight:Hide() + end +end + +local methods = { + GetRowIndex = function(self, value) + for i, v in pairs(self.rowData) do + if v.groupPath == value then + return i + end + end + end, + RefreshRows = function(self) + local offset = FauxScrollFrame_GetOffset(self.scrollFrame) + self.offset = offset + + for i = #self.rowData, 1, -1 do + local data = self.rowData[i] + if not self.isGroupBox and not data.isSelected and data.parent then + local index = self:GetRowIndex(data.parent) + if index then + self.rowData[index].isSelected = self.selectedGroups[self.rowData[index].groupPath] + end + end + end + + local displayRows = {} + for i = 1, #self.rowData do + local pathParts = { TSM.GROUP_SEP:split(self.rowData[i].groupPath) } + local isCollapsed = false + for i = 1, #pathParts - 1 do + local path = table.concat(pathParts, TSM.GROUP_SEP, 1, i) + if self.collapsed[path] then + isCollapsed = true + break + end + end + if not isCollapsed then + if self.collapsed[self.rowData[i].groupPath] then + self.rowData[i].value = gsub(self.rowData[i].value, TSMAPI:StrEscape("[-]"), "[+]") + else + self.rowData[i].value = gsub(self.rowData[i].value, TSMAPI:StrEscape("[+]"), "[-]") + end + tinsert(displayRows, self.rowData[i]) + end + end + FauxScrollFrame_Update(self.scrollFrame, #displayRows, self.NUM_ROWS, ROW_HEIGHT) + + for i = 1, self.NUM_ROWS do + if i > #displayRows then + self.rows[i]:Hide() + self.rows[i].data = nil + else + self.rows[i]:Show() + local data = displayRows[i + offset] + if not data then return end + self.rows[i].data = data + + if data.isSelected or self.rows[i]:IsMouseOver() then + self.rows[i].highlight:Show() + else + self.rows[i].highlight:Hide() + end + self.rows[i]:SetText(data.value) + end + end + end, + SetSelection = function(self, rowNum, isSelected) + self.selectedGroups[self.rowData[rowNum].groupPath] = isSelected or false + self.rowData[rowNum].isSelected = isSelected + self:RefreshRows() + end, + GetSelectedGroupInfo = function(self, rowNum) + local groupInfo = {} + for _, data in ipairs(self.rowData) do + if data.isSelected then + groupInfo[data.groupPath] = { operations = TSM:GetGroupOperations(data.groupPath, self.module), items = TSM:GetGroupItems(data.groupPath) } + if self.module and not groupInfo[data.groupPath].operations then + groupInfo[data.groupPath] = nil + end + end + end + return groupInfo + end, + ClearSelection = function(self) + for i = 1, #self.rowData do + self.selectedGroups[self.rowData[i].groupPath] = false + self.rowData[i].isSelected = nil + end + self.groupBoxSelection = nil + self:RefreshRows() + end, + SetGropBoxSelection = function(self, groupPath) + if self.groupBoxSelection then + self.groupBoxSelection.isSelected = nil + self.groupBoxSelection = nil + end + for i = 1, #self.rowData do + if self.rowData[i].groupPath == groupPath then + self.rowData[i].isSelected = true + self.groupBoxSelection = self.rowData[i] + break + end + end + end, + GetGroupBoxSelection = function(self) + return self.groupBoxSelection and self.groupBoxSelection.groupPath + end, +} + +local defaultColScripts = { + OnEnter = function(self) + local tooltipLines = {} + tinsert(tooltipLines, format(L["%sLeft-Click|r to select / deselect this group."], TSMAPI.Design:GetInlineColor("link"))) + if self.data.hasSubGroups then + tinsert(tooltipLines, format(L["%sRight-Click|r to collapse / expand this group."], TSMAPI.Design:GetInlineColor("link"))) + end + + local operations = TSM:GetGroupOperations(self.data.groupPath, self.st.module) + local operationLine = operations and table.concat(operations, ", ") or L[""] + tinsert(tooltipLines, "") + tinsert(tooltipLines, format(L["Operations: %s"], operationLine)) + + GameTooltip:SetOwner(self, "ANCHOR_TOP") + GameTooltip:AddLine(table.concat(tooltipLines, "\n"), 1, 1, 1) + GameTooltip:Show() + + self.highlight:Show() + end, + OnLeave = function(self) + GameTooltip:Hide() + if not self.data.isSelected then + self.highlight:Hide() + end + end, + OnClick = function(self, button) + if button == "RightButton" then + self.st.collapsed[self.data.groupPath] = not self.st.collapsed[self.data.groupPath] + self.st:RefreshRows() + return + end + if self.st.isGroupBox then + if self.data ~= self.st.groupBoxSelection then + if self.st.groupBoxSelection then + self.st.groupBoxSelection.isSelected = false + end + self.st.groupBoxSelection = self.data + end + self.data.isSelected = true + else + self.data.isSelected = not self.data.isSelected + self.st.selectedGroups[self.data.groupPath] = self.data.isSelected or false + if self.data.hasSubGroups then + for i = 1, #self.st.rowData do + if self.st.rowData[i].groupPath == self.data.groupPath or strfind(self.st.rowData[i].groupPath, "^" .. TSMAPI:StrEscape(self.data.groupPath) .. TSM.GROUP_SEP) then + self.st.selectedGroups[self.st.rowData[i].groupPath] = self.data.isSelected or false + self.st.rowData[i].isSelected = self.data.isSelected + end + end + end + end + self.st:RefreshRows() + if self.data.isSelected then + self.highlight:Show() + else + self.highlight:Hide() + end + end, +} + +function TSMAPI:CreateGroupTree(parent, module, label, isGroupBox) + assert(type(parent) == "table", format(L["Invalid parent argument type. Expected table, got %s."], type(parent))) + + local name = "TSMGroupTree" .. COUNT + COUNT = COUNT + 1 + local st = CreateFrame("Frame", name, parent) + st:SetAllPoints() + st:SetScript("OnShow", UpdateTree) + st.NUM_ROWS = floor((parent:GetHeight() - (isGroupBox and 0 or 20)) / ROW_HEIGHT) + st.isGroupBox = isGroupBox + st.groupBoxSelection = nil + st.module = module + if label or module then + label = label or module + if not TSM.db.profile.groupTreeSelectedGroupStatus[label] then + TSMAPI:CreateTimeDelay(0, function() SelectAll({st=st}) end) + end + TSM.db.profile.groupTreeCollapsedStatus[label] = TSM.db.profile.groupTreeCollapsedStatus[label] or {} + TSM.db.profile.groupTreeSelectedGroupStatus[label] = TSM.db.profile.groupTreeSelectedGroupStatus[label] or {} + st.collapsed = TSM.db.profile.groupTreeCollapsedStatus[label] + st.selectedGroups = TSM.db.profile.groupTreeSelectedGroupStatus[label] + else + st.collapsed = {} + st.selectedGroups = {} + end + + local contentFrame = CreateFrame("Frame", name .. "Content", st) + contentFrame:SetPoint("TOPLEFT") + contentFrame:SetPoint("BOTTOMRIGHT", -15, isGroupBox and 0 or 18) + st.contentFrame = contentFrame + + if not isGroupBox then + local btn = TSMAPI.GUI:CreateButton(st, 14) + btn:SetPoint("BOTTOMLEFT", 0, 2) + btn:SetPoint("BOTTOMRIGHT", st, "BOTTOM", -2, 2) + btn:SetHeight(16) + btn:SetText(L["Select All Groups"]) + btn:SetScript("OnClick", SelectAll) + btn.st = st + + local btn = TSMAPI.GUI:CreateButton(st, 14) + btn:SetPoint("BOTTOMLEFT", st, "BOTTOM", 2, 2) + btn:SetPoint("BOTTOMRIGHT", 0, 2) + btn:SetHeight(16) + btn:SetText(L["Deselect All Groups"]) + btn:SetScript("OnClick", DeselectAll) + btn.st = st + end + + -- frame to hold the rows + local scrollFrame = CreateFrame("ScrollFrame", name .. "ScrollFrame", st, "FauxScrollFrameTemplate") + scrollFrame:SetScript("OnVerticalScroll", function(self, offset) + FauxScrollFrame_OnVerticalScroll(self, offset, ROW_HEIGHT, function() st:RefreshRows() end) + end) + scrollFrame:SetAllPoints(contentFrame) + st.scrollFrame = scrollFrame + + -- make the scroll bar consistent with the TSM theme + local scrollBar = _G[scrollFrame:GetName() .. "ScrollBar"] + scrollBar:ClearAllPoints() + scrollBar:SetPoint("BOTTOMRIGHT", st, -1, isGroupBox and 1 or 19) + scrollBar:SetPoint("TOPRIGHT", st, -1, -1) + scrollBar:SetWidth(12) + local thumbTex = scrollBar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetContentColor(thumbTex) + thumbTex:SetHeight(50) + thumbTex:SetWidth(scrollBar:GetWidth()) + _G[scrollBar:GetName() .. "ScrollUpButton"]:Hide() + _G[scrollBar:GetName() .. "ScrollDownButton"]:Hide() + + local text = st:CreateFontString() + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + text:SetJustifyH("CENTER") + text:SetJustifyV("CENTER") + text:SetPoint("LEFT", 5, 0) + text:SetPoint("RIGHT", -5, 0) + text:SetHeight(100) + text:SetNonSpaceWrap(true) + st.statusText = text + + -- create the rows + st.rows = {} + for i = 1, st.NUM_ROWS do + local row = CreateFrame("Button", name .. "Row" .. i, st.contentFrame) + row:SetHeight(ROW_HEIGHT) + row:RegisterForClicks("AnyUp") + if i == 1 then + row:SetPoint("TOPLEFT") + row:SetPoint("TOPRIGHT") + else + row:SetPoint("TOPLEFT", st.rows[i - 1], "BOTTOMLEFT") + row:SetPoint("TOPRIGHT", st.rows[i - 1], "BOTTOMRIGHT") + end + local highlight = row:CreateTexture() + highlight:SetAllPoints() + highlight:SetTexture(1, .9, 0, .2) + highlight:Hide() + row.highlight = highlight + local text = row:CreateFontString() + text:SetFont(TSMAPI.Design:GetContentFont("medium")) + text:SetJustifyH("LEFT") + text:SetJustifyV("CENTER") + text:SetPoint("TOPLEFT", 1, -1) + text:SetPoint("BOTTOMRIGHT", -1, 1) + row:SetFontString(text) + for name, func in pairs(defaultColScripts) do + row:SetScript(name, func) + end + row.st = st + tinsert(st.rows, row) + end + + for name, func in pairs(methods) do + st[name] = func + end + + UpdateTree(st) + + return st +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/Gui.lua b/TradeSkillMaster/GUI/Gui.lua new file mode 100644 index 0000000..2652ebf --- /dev/null +++ b/TradeSkillMaster/GUI/Gui.lua @@ -0,0 +1,338 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This module holds some GUI helper functions for modules to use. + +local TSM = select(2, ...) +local private = {} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.Gui_private") +private.frames = {} + +TSMAPI.GUI = {} + + +-- Tooltips! +local function ShowTooltip(self) + if self.link then + GameTooltip:SetOwner(self, "ANCHOR_TOPRIGHT") + TSMAPI:SafeTooltipLink(self.link) + GameTooltip:Show() + elseif type(self.tooltip) == "function" then + local text = self.tooltip(self) + if type(text) == "string" then + GameTooltip:SetOwner(self, "ANCHOR_TOP") + --GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT") + GameTooltip:SetText(text, 1, 1, 1, 1, true) + GameTooltip:Show() + end + elseif self.tooltip then + GameTooltip:SetOwner(self, "ANCHOR_TOP") + --GameTooltip:SetOwner(self, "ANCHOR_BOTTOMRIGHT") + GameTooltip:SetText(self.tooltip, 1, 1, 1, 1, true) + GameTooltip:Show() + elseif self.frame.tooltip then + GameTooltip:SetOwner(self.frame, "ANCHOR_TOP") + --GameTooltip:SetOwner(self.frame, "ANCHOR_BOTTOMRIGHT") + GameTooltip:SetText(self.frame.tooltip, 1, 1, 1, 1, true) + GameTooltip:Show() + end +end + +local function HideTooltip() + -- BattlePetTooltip:Hide() + GameTooltip:Hide() +end + +function TSMAPI.GUI:CreateButton(parent, textHeight, name, isSecure) + local btn = CreateFrame("Button", name, parent, isSecure and "SecureActionButtonTemplate") + TSMAPI.Design:SetContentColor(btn) + local highlight = btn:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + highlight:SetBlendMode("BLEND") + btn.highlight = highlight + btn:SetScript("OnEnter", function(self) if self.tooltip then ShowTooltip(self) end end) + btn:SetScript("OnLeave", HideTooltip) + btn:Show() + local label = btn:CreateFontString() + label:SetFont(TSMAPI.Design:GetContentFont(), textHeight) + label:SetPoint("CENTER") + label:SetJustifyH("CENTER") + label:SetJustifyV("CENTER") + label:SetHeight(textHeight) + TSMAPI.Design:SetWidgetTextColor(label) + btn:SetFontString(label) + TSM:Hook(btn, "Enable", function() TSMAPI.Design:SetWidgetTextColor(label) end, true) + TSM:Hook(btn, "Disable", function() TSMAPI.Design:SetWidgetTextColor(label, true) end, true) + return btn +end + +function TSMAPI.GUI:CreateHorizontalLine(parent, ofsy, relativeFrame, invertedColor) + relativeFrame = relativeFrame or parent + local barTex = parent:CreateTexture() + barTex:SetPoint("TOPLEFT", relativeFrame, "TOPLEFT", 2, ofsy) + barTex:SetPoint("TOPRIGHT", relativeFrame, "TOPRIGHT", -2, ofsy) + barTex:SetHeight(2) + if invertedColor then + TSMAPI.Design:SetFrameColor(barTex) + else + TSMAPI.Design:SetContentColor(barTex) + end + return barTex +end + +function TSMAPI.GUI:CreateVerticalLine(parent, ofsx, relativeFrame, invertedColor) + relativeFrame = relativeFrame or parent + local barTex = parent:CreateTexture() + barTex:SetPoint("TOPLEFT", relativeFrame, "TOPLEFT", ofsx, -2) + barTex:SetPoint("BOTTOMLEFT", relativeFrame, "BOTTOMLEFT", ofsx, 2) + barTex:SetWidth(2) + if invertedColor then + TSMAPI.Design:SetFrameColor(barTex) + else + TSMAPI.Design:SetContentColor(barTex) + end + return barTex +end + +function TSMAPI.GUI:CreateInputBox(parent, name, autoComplete) + local function OnEscapePressed(self) + self:ClearFocus() + self:HighlightText(0, 0) + end + + local eb = CreateFrame("EditBox", name, parent) + eb:SetFont(TSMAPI.Design:GetContentFont("normal")) + eb:SetShadowColor(0, 0, 0, 0) + TSMAPI.Design:SetContentColor(eb) + eb:SetAutoFocus(false) + eb:SetScript("OnEscapePressed", function(self) self:ClearFocus() self:HighlightText(0, 0) end) + eb:SetScript("OnEnter", function(self) if self.tooltip then ShowTooltip(self) end end) + eb:SetScript("OnLeave", HideTooltip) + return eb +end + +function TSMAPI.GUI:SetAutoComplete(inputBox, params) + local autoCompleteHandlers = {"OnTabPressed", "OnEnterPressed", "OnTextChanged", "OnChar", "OnEditFocusLost", "OnEscapePressed"}--, "OnArrowPressed"} + if params then + if inputBox._priorTSMHandlers then return end -- already done + inputBox.autoCompleteParams = params + inputBox._priorTSMHandlers = {} + for _, name in ipairs(autoCompleteHandlers) do + inputBox._priorTSMHandlers[name] = inputBox:GetScript(name) + inputBox:SetScript(name, function(self, ...) return _G["AutoCompleteEditBox_"..name](self, ...) or (self._priorTSMHandlers[name] and self._priorTSMHandlers[name](self, ...)) end) + end + else + if not inputBox._priorTSMHandlers then return end -- already done + for _, name in ipairs(autoCompleteHandlers) do + inputBox:SetScript(name, inputBox._priorTSMHandlers[name]) + end + inputBox._priorTSMHandlers = nil + end +end + +function TSMAPI.GUI:CreateLabel(parent, size) + local label = parent:CreateFontString() + label:SetFont(TSMAPI.Design:GetContentFont(size)) + TSMAPI.Design:SetWidgetLabelColor(label) + return label +end + +function TSMAPI.GUI:CreateTitleLabel(parent, size) + local label = parent:CreateFontString() + label:SetFont(TSMAPI.Design:GetBoldFont(), size) + TSMAPI.Design:SetTitleTextColor(label) + return label +end + +function TSMAPI.GUI:CreateStatusBar(parent, baseName) + local function UpdateStatus(self, majorStatus, minorStatus) + if majorStatus then + self.majorStatusBar:SetValue(majorStatus) + if majorStatus == 100 then + self.majorStatusBar.ag:Stop() + elseif not self.majorStatusBar.ag:IsPlaying() then + self.majorStatusBar.ag:Play() + end + end + if minorStatus then + self.minorStatusBar:SetValue(minorStatus) + if minorStatus == 100 then + self.minorStatusBar.ag:Stop() + elseif not self.minorStatusBar.ag:IsPlaying() then + self.minorStatusBar.ag:Play() + end + end + end + + local function SetStatusText(self, text) + self.text:SetText(text) + end + + local level = parent:GetFrameLevel() + local frame = CreateFrame("Frame", nil, parent) + frame:SetHeight(25) + frame:SetPoint("TOPLEFT", 2, -3) + frame:SetPoint("TOPRIGHT", -2, -3) + frame:SetFrameLevel(level+1) + frame.UpdateStatus = UpdateStatus + frame.SetStatusText = SetStatusText + + -- minor status bar (gray one) + local statusBar = CreateFrame("STATUSBAR", baseName.."-Minor", frame, "TextStatusBar") + statusBar:SetOrientation("HORIZONTAL") + statusBar:SetMinMaxValues(0, 100) + statusBar:SetAllPoints() + statusBar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8") + statusBar:SetStatusBarColor(.42, .42, .42, .7) + statusBar:SetFrameLevel(level+2) + local ag = statusBar:CreateAnimationGroup() + local alpha = ag:CreateAnimation("Alpha") + alpha:SetDuration(1) + alpha:SetChange(-.5) + ag:SetLooping("Bounce") + statusBar.ag = ag + frame.minorStatusBar = statusBar + + -- major status bar (main blue one) + local statusBar = CreateFrame("STATUSBAR", baseName.."-Major", frame, "TextStatusBar") + statusBar:SetOrientation("HORIZONTAL") + statusBar:SetMinMaxValues(0, 100) + statusBar:SetAllPoints() + statusBar:SetStatusBarTexture("Interface\\Buttons\\WHITE8X8") + statusBar:SetStatusBarColor(.19, .22, .33, .9) + statusBar:SetFrameLevel(level+3) + local ag = statusBar:CreateAnimationGroup() + local alpha = ag:CreateAnimation("Alpha") + alpha:SetDuration(1) + alpha:SetChange(-.5) + ag:SetLooping("Bounce") + statusBar.ag = ag + frame.majorStatusBar = statusBar + + local textFrame = CreateFrame("Frame", nil, frame) + textFrame:SetFrameLevel(level+4) + textFrame:SetAllPoints(frame) + -- Text for the StatusBar + local text = TSMAPI.GUI:CreateLabel(textFrame) + TSMAPI.Design:SetWidgetTextColor(text) + text:SetPoint("CENTER") + frame.text = text + + return frame +end + +function TSMAPI.GUI:CreateDropdown(parent, list, tooltip) + local dd = LibStub("AceGUI-3.0"):Create("TSMDropdown") + dd:SetDisabled() + dd:SetMultiselect(false) + dd:SetList(list) + dd.frame:SetParent(parent) + dd.frame:Show() + dd.frame.tooltip = tooltip + dd:SetCallback("OnEnter", ShowTooltip) + dd:SetCallback("OnLeave", HideTooltip) + return dd +end + +function TSMAPI.GUI:CreateCheckBox(parent, tooltip) + local cb = LibStub("AceGUI-3.0"):Create("TSMCheckBox") + cb.frame:SetParent(parent) + cb.frame:Show() + cb.frame.tooltip = tooltip + cb:SetCallback("OnEnter", ShowTooltip) + cb:SetCallback("OnLeave", HideTooltip) + return cb +end + +function TSMAPI.GUI:CreateItemLinkLabel(parent, textHeight) + local btn = CreateFrame("Button", nil, parent) + btn:SetScript("OnEnter", function(self) if self.link then ShowTooltip(self) end end) + btn:SetScript("OnLeave", HideTooltip) + btn:SetScript("OnClick", function(self) if self.link then HandleModifiedItemClick(self.link) end end) + btn:SetHeight(textHeight) + btn:Show() + local text = btn:CreateFontString() + text:SetFont(TSMAPI.Design:GetContentFont(), textHeight) + text:SetPoint("TOPLEFT") + text:SetPoint("BOTTOMLEFT") + text:SetJustifyH("LEFT") + text:SetJustifyV("CENTER") + btn:SetFontString(text) + return btn +end + + + +-- Registers a movable/resizable frame which TSM will keep track of and persistently store its position / size. +-- The frame must be named for this function to work. +-- Required defaults +-- x - x position +-- y - y position +-- width - width +-- height - height +-- scale - scale +function TSMAPI:CreateMovableFrame(name, defaults, parent) + local options = TSM.db.global.frameStatus[name] or CopyTable(defaults) + options.defaults = defaults + TSM.db.global.frameStatus[name] = options + options.hasLoaded = nil + + local frame = CreateFrame("Frame", name, parent) + frame:Hide() + frame:SetHeight(options.height) + frame:SetWidth(options.width) + frame:SetScale(UIParent:GetScale()*options.scale) + frame:SetPoint("CENTER", frame:GetParent()) + frame:SetToplevel(true) + frame:EnableMouse(true) + frame:SetMovable(true) + frame:SetClampedToScreen(true) + frame:SetClampRectInsets(options.width-50, -(options.width-50), -(options.height-50), options.height-50) + frame.SavePositionAndSize = function(self) + if not options.hasLoaded then return end + options.width = self:GetWidth() + options.height = self:GetHeight() + options.x = self:GetLeft() + options.y = self:GetBottom() + self:SetClampRectInsets(options.width-50, -(options.width-50), -(options.height-50), options.height-50) + end + frame:SetScript("OnMouseDown", frame.StartMoving) + frame:SetScript("OnMouseUp", function(self) self:StopMovingOrSizing() self:SavePositionAndSize() end) + frame:SetScript("OnSizeChanged", frame.SavePositionAndSize) + frame.RefreshPosition = function(self) + options.hasLoaded = true + self:SetScale(UIParent:GetScale()*options.scale) + self:SetFrameLevel(0) + self:ClearAllPoints() + self:SetPoint("BOTTOMLEFT", UIParent, options.x, options.y) + self:SetWidth(options.width) + self:SetHeight(options.height) + end + frame:SetScript("OnShow", frame.RefreshPosition) + frame.options = options + tinsert(private.frames, frame) + + return frame +end + +function TSM:ResetFrames() + for _, frame in ipairs(private.frames) do + -- reset all fields to the default values without breaking any table references + local options = TSM.db.global.frameStatus[frame:GetName()] + options.hasLoaded = true + local defaults = options.defaults + for i, v in pairs(defaults) do options[i] = v end + if frame and frame:IsVisible() then + frame:RefreshPosition() + end + end + + -- explicitly reset bankui since it can't easily use TSMAPI:CreateMovableFrame + TSM:ResetBankUIFramePosition() +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/MainFrame.lua b/TradeSkillMaster/GUI/MainFrame.lua new file mode 100644 index 0000000..b2b87e0 --- /dev/null +++ b/TradeSkillMaster/GUI/MainFrame.lua @@ -0,0 +1,140 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains all the APIs regarding TSM's main frame (what shows when you type '/tsm'). + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub("AceGUI-3.0") -- load the AceGUI libraries + +local private = {icons={}, currentIcon=0} +TSMAPI:RegisterForTracing(private, "TradeSkillMaster.MainFrame_private") +local lib = TSMAPI + + +--- Opens the main TSM window. +function TSMAPI:OpenFrame() + if not TSM.Frame then return end + TSM.Frame:Show() + if #TSM.Frame.children > 0 then + TSM.Frame:ReleaseChildren() + else + TSMAPI:SelectIcon("TradeSkillMaster", L["TSM Status / Options"]) + end + if TSM.db.global.infoMessage < 1001 then + TSM.db.global.infoMessage = 1001 + StaticPopupDialogs["TSM_INFO_MESSAGE"] = StaticPopupDialogs["TSM_INFO_MESSAGE"] or { + text = format(L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced")), + button1 = OKAY, + OnAccept = function() + TSM:Printf(L["More advanced options are now designated by %sred text|r. Beginners are encourages to come back to these once they have a solid understanding of the basics."], TSMAPI.Design:GetInlineColor("advanced")) + end, + timeout = 0, + preferredIndex = 3, + } + TSMAPI:ShowStaticPopupDialog("TSM_INFO_MESSAGE") + end +end + +--- Closes the main TSM window. +function TSMAPI:CloseFrame() + TSM.Frame:Hide() +end + +function TSM:RegisterMainFrameIcon(displayName, icon, loadGUI, moduleName, side) + if not (displayName and icon and loadGUI and moduleName) then + return nil, "invalid args", displayName, icon, loadGUI, moduleName + end + + if side and not (side == "module" or side == "options") then + return nil, "invalid side", side + end + + local icon = {name=displayName, moduleName=moduleName, icon=icon, loadGUI=loadGUI, side=(strlower(side or "module"))} + if TSM.Frame then + icon.texture = icon.icon + if icon.side == "options" then + icon.where = "topLeft" + else + icon.where = "topRight" + end + + TSM.Frame:AddIcon(icon) + end + + tinsert(private.icons, icon) +end + +--- Selects an icon in the main TSM window once it's open. +-- @param moduleName Which module the icon belongs to (unlocalized). +-- @param iconName The text that shows in the tooltip of the icon to be clicked (localized). +-- @return Returns an error message as the second return value upon error. +function TSMAPI:SelectIcon(moduleName, iconName) + if not moduleName then return nil, "no moduleName passed" end + + for _, data in ipairs(private.icons) do + if data.moduleName == moduleName and data.name == iconName then + data.frame:Click() + end + end +end + +function TSMAPI:ShowOperationOptions(moduleName, operation, groupPath) + TSMAPI:OpenFrame() + TSM.loadModuleOptionsTab = {module=moduleName, operation=operation, group=groupPath} + TSMAPI:SelectIcon("TradeSkillMaster", L["Module Operations / Options"]) + TSM.loadModuleOptionsTab = nil +end + + +function TSM:CreateMainFrame() + local mainFrame = AceGUI:Create("TSMMainFrame") + local version = TSM._version + mainFrame:SetIconText(version) + mainFrame:SetIconLabels(L["Options"], L["Modules"]) + mainFrame:SetLayout("Fill") + + for _, icon in ipairs(private.icons) do + icon.texture = icon.icon + if icon.side == "crafting" then + icon.where = "bottom" + elseif icon.side == "options" then + icon.where = "topLeft" + else + icon.where = "topRight" + end + + mainFrame:AddIcon(icon) + end + TSM.Frame = mainFrame + + TSMAPI:CreateTimeDelay("mainFrameSize", .5, function() mainFrame:SetWidth(mainFrame.frame.options.width) mainFrame:SetHeight(mainFrame.frame.options.height) end) +end + + +function TSM:TSMFrameIsVisible() + return TSM.Frame and TSM.Frame:IsVisible() +end + +function private:FramePathHelper(frame, path) + if not frame.children or not frame.children[1] then return end + frame = frame.children[1] + if frame.type == "TSMTreeGroup" then + tinsert(path, {type="TreeGroup", value={("\001"):split(frame.status.selected)}}) + elseif frame.type == "TSMTabGroup" then + tinsert(path, {type="TabGroup", value=frame.localstatus.selected}) + end + return private:FramePathHelper(frame, path) +end +function TSM:GetTSMFrameSelectionPath() + if not TSM:TSMFrameIsVisible() then return end + local path = {} + tinsert(path, {type="Icon", value=TSM.Frame.selected.info.name}) + private:FramePathHelper(TSM.Frame, path) + return path +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/ScrollingTable.lua b/TradeSkillMaster/GUI/ScrollingTable.lua new file mode 100644 index 0000000..d9216ec --- /dev/null +++ b/TradeSkillMaster/GUI/ScrollingTable.lua @@ -0,0 +1,390 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table + +local RT_COUNT = 1 + +local HEAD_HEIGHT = 27 +local HEAD_SPACE = 4 + + +local function OnSizeChanged(st, width, height) + width = width - 14 + if st.headCols then + -- adjust head col widths + for i, col in ipairs(st.headCols) do + col:SetWidth(col.info.width*width) + end + end + + -- calculate new number of rows + st.NUM_ROWS = max(floor((height-st.HEAD_HEIGHT-HEAD_SPACE)/st.ROW_HEIGHT), 0) + + -- hide all extra rows and clear their data + for i=st.NUM_ROWS+1, #st.rows do + st.rows[i]:Hide() + st.rows[i].data = nil + end + + while #st.rows < st.NUM_ROWS do + st:AddRow() + end + + -- adjust rows widths + for _, row in ipairs(st.rows) do + for i, col in ipairs(row.cols) do + if st.headCols then + col:SetWidth(st.headCols[i].info.width*width) + else + col:SetWidth(width) + end + if col.text.fontHeight < 13 then + col.text:SetFont(TSMAPI.Design:GetContentFont(), 13) + col.text.fontHeight = 13 + end + end + end + + st:RefreshRows() +end + +local function GetTableIndex(tbl, value) + for i, v in pairs(tbl) do + if value == v then + return i + end + end +end + +local function OnColumnClick(self, button, ...) + if self.st.sortInfo.enabled and button == "LeftButton" then + if self.st.sortInfo.col == self.colNum then + self.st.sortInfo.ascending = not self.st.sortInfo.ascending + else + self.st.sortInfo.col = self.colNum + self.st.sortInfo.ascending = true + end + self.st.updateSort = true + self.st:RefreshRows() + end + if self.st.handlers.OnColumnClick then + self.st.handlers.OnColumnClick(self, button, ...) + end +end + + +local defaultColScripts = { + OnEnter = function(self, ...) + if not self.row.data then return end + if not self.st.highlightDisabled then + self.row.highlight:Show() + end + + local handler = self.st.handlers.OnEnter + if handler then + handler(self.st, self.row.data, self, ...) + end + end, + + OnLeave = function(self, ...) + if not self.row.data then return end + if self.st.selectionDisabled or not self.st.selected or self.st.selected ~= GetTableIndex(self.st.rowData, self.row.data) then + self.row.highlight:Hide() + end + + local handler = self.st.handlers.OnLeave + if handler then + handler(self.st, self.row.data, self, ...) + end + end, + + OnClick = function(self, ...) + if not self.row.data then return end + self.st:ClearSelection() + self.st.selected = GetTableIndex(self.st.rowData, self.row.data) + self.row.highlight:Show() + + local handler = self.st.handlers.OnClick + if handler then + handler(self.st, self.row.data, self, ...) + end + end, + + OnDoubleClick = function(self, ...) + if not self.row.data then return end + local handler = self.st.handlers.OnDoubleClick + if handler then + handler(self.st, self.row.data, self, ...) + end + end, +} + +local methods = { + RefreshRows = function(st) + if not st.rowData then return end + FauxScrollFrame_Update(st.scrollFrame, #st.rowData, st.NUM_ROWS, st.ROW_HEIGHT) + local offset = FauxScrollFrame_GetOffset(st.scrollFrame) + st.offset = offset + + -- hide all rows and clear their data + for i=1, st.NUM_ROWS do + st.rows[i]:Hide() + st.rows[i].data = nil + end + + -- do sorting if enabled + if st.sortInfo.enabled and st.sortInfo.col and st.updateSort then + local function SortHelper(rowA, rowB) + local sortArgA = rowA.cols[st.sortInfo.col].sortArg + local sortArgB = rowB.cols[st.sortInfo.col].sortArg + + if st.sortInfo.ascending then + return sortArgA < sortArgB + else + return sortArgA > sortArgB + end + end + sort(st.rowData, SortHelper) + st.updateSort = nil + end + + -- set row data + for i=1, min(st.NUM_ROWS, #st.rowData) do + st.rows[i]:Show() + local data = st.rowData[i+offset] + if not data then break end + st.rows[i].data = data + + if (st.selected == GetTableIndex(st.rowData, data) and not st.selectionDisabled) or st.rows[i]:IsMouseOver() or (st.highlighted and st.highlighted == GetTableIndex(st.rowData, data)) then + st.rows[i].highlight:Show() + else + st.rows[i].highlight:Hide() + end + + for j, col in ipairs(st.rows[i].cols) do + local colData = data.cols[j] + if type(colData.value) == "function" then + col:SetText(colData.value(unpack(colData.args))) + else + col:SetText(colData.value) + end + end + end + end, + + SetData = function(st, rowData) + st.rowData = rowData + st.updateSort = true + st:RefreshRows() + end, + + SetSelection = function(st, rowNum) + st.selected = rowNum + st:RefreshRows() + end, + + GetSelection = function(st) + return st.selected + end, + + ClearSelection = function(st) + st.selected = nil + st:RefreshRows() + end, + + DisableSelection = function(st, value) + st.selectionDisabled = value + end, + + EnableSorting = function(st, value, defaultCol) + st.sortInfo.enabled = value + st.sortInfo.col = abs(defaultCol or 1) + st.sortInfo.ascending = not defaultCol or defaultCol > 0 + st.updateSort = true + st:RefreshRows() + end, + + DisableHighlight = function(st, value) + st.highlightDisabled = value + end, + + SetScrollOffset = function(st, offset) + local maxOffset = max(#st.rowData - st.NUM_ROWS, 0) + if not offset or offset < 0 or offset > maxOffset then + return -- invalid offset + end + + local scrollPercent = offset / maxOffset + local maxPixelOffset = st.scrollFrame:GetVerticalScrollRange() + st.ROW_HEIGHT * 2 + local pixelOffset = scrollPercent * maxPixelOffset + FauxScrollFrame_SetOffset(st.scrollFrame, offset) + st.scrollFrame:SetVerticalScroll(pixelOffset) + end, + + SetHighlighted = function(st, row) + st.highlighted = row + st:RefreshRows() + end, + + AddRow = function(st) + local row = CreateFrame("Frame", nil, st.contentFrame) + row:SetHeight(st.ROW_HEIGHT) + if #st.rows == 0 then + row:SetPoint("TOPLEFT", 0, -(st.HEAD_HEIGHT+HEAD_SPACE)) + row:SetPoint("TOPRIGHT", 0, -(st.HEAD_HEIGHT+HEAD_SPACE)) + else + row:SetPoint("TOPLEFT", st.rows[#st.rows], "BOTTOMLEFT") + row:SetPoint("TOPRIGHT", st.rows[#st.rows], "BOTTOMRIGHT") + end + local highlight = row:CreateTexture() + highlight:SetAllPoints() + highlight:SetTexture(1, .9, 0, .2) + highlight:Hide() + row.highlight = highlight + row.st = st + + row.cols = {} + for j, info in ipairs(st.colInfo or {{}}) do + local col = CreateFrame("Button", nil, row) + local text = col:CreateFontString() + text:SetFont(TSMAPI.Design:GetContentFont(), min(13, st.ROW_HEIGHT)) + text:SetJustifyH(info.align or "LEFT") + text:SetJustifyV("CENTER") + text:SetPoint("TOPLEFT", 1, -1) + text:SetPoint("BOTTOMRIGHT", -1, 1) + text.fontHeight = min(13, st.ROW_HEIGHT) + col.text = text + col:SetFontString(text) + col:SetHeight(st.ROW_HEIGHT) + col:RegisterForClicks("AnyUp") + for name, func in pairs(defaultColScripts) do + col:SetScript(name, func) + end + col.st = st + col.row = row + + if j == 1 then + col:SetPoint("TOPLEFT") + else + col:SetPoint("TOPLEFT", row.cols[j-1], "TOPRIGHT") + end + tinsert(row.cols, col) + end + + tinsert(st.rows, row) + end, +} + +function TSMAPI:CreateScrollingTable(parent, colInfo, handlers, headFontSize) + assert(type(parent) == "table", format("Invalid parent argument. Type is %s.", type(parent))) + + local rtName = "TSMScrollingTable"..RT_COUNT + RT_COUNT = RT_COUNT + 1 + local st = CreateFrame("Frame", rtName, parent) + if not colInfo then + st.HEAD_HEIGHT = -HEAD_SPACE + elseif headFontSize then + st.HEAD_HEIGHT = headFontSize + 4 + else + st.HEAD_HEIGHT = HEAD_HEIGHT + end + st.ROW_HEIGHT = 15 + st.NUM_ROWS = floor((parent:GetHeight()-st.HEAD_HEIGHT-HEAD_SPACE)/st.ROW_HEIGHT) + st.colInfo = colInfo + st:SetScript("OnSizeChanged", OnSizeChanged) + + local contentFrame = CreateFrame("Frame", rtName.."Content", st) + contentFrame:SetPoint("TOPLEFT") + contentFrame:SetPoint("BOTTOMRIGHT", -15, 0) + st.contentFrame = contentFrame + + -- frame to hold the header columns and the rows + local scrollFrame = CreateFrame("ScrollFrame", rtName.."ScrollFrame", st, "FauxScrollFrameTemplate") + scrollFrame:SetScript("OnVerticalScroll", function(self, offset) + FauxScrollFrame_OnVerticalScroll(self, offset, st.ROW_HEIGHT, function() st:RefreshRows() end) + end) + scrollFrame:SetAllPoints(contentFrame) + st.scrollFrame = scrollFrame + + -- make the scroll bar consistent with the TSM theme + local scrollBar = _G[scrollFrame:GetName().."ScrollBar"] + scrollBar:ClearAllPoints() + scrollBar:SetPoint("BOTTOMRIGHT", st, -1, 1) + scrollBar:SetPoint("TOPRIGHT", st, -1, -st.HEAD_HEIGHT-HEAD_SPACE) + scrollBar:SetWidth(12) + local thumbTex = scrollBar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetContentColor(thumbTex) + thumbTex:SetHeight(50) + thumbTex:SetWidth(scrollBar:GetWidth()) + _G[scrollBar:GetName().."ScrollUpButton"]:Hide() + _G[scrollBar:GetName().."ScrollDownButton"]:Hide() + + -- create the header columns + if colInfo then + st.headCols = {} + for i, info in ipairs(colInfo) do + local col = CreateFrame("Button", rtName.."HeadCol"..i, st.contentFrame) + col:SetHeight(st.HEAD_HEIGHT) + if i == 1 then + col:SetPoint("TOPLEFT") + else + col:SetPoint("TOPLEFT", st.headCols[i-1], "TOPRIGHT") + end + col.info = info + col.st = st + col.colNum = i + col:RegisterForClicks("AnyUp") + col:SetScript("OnClick", OnColumnClick) + + local text = col:CreateFontString() + text:SetJustifyH(info.headAlign or "CENTER") + text:SetJustifyV("CENTER") + if headFontSize then + text:SetFont(TSMAPI.Design:GetContentFont("normal"), headFontSize) + else + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + end + TSMAPI.Design:SetWidgetTextColor(text) + col:SetFontString(text) + col:SetText(info.name or "") + text:SetAllPoints() + + local tex = col:CreateTexture() + tex:SetAllPoints() + tex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight") + tex:SetTexCoord(0.025, 0.957, 0.087, 0.931) + tex:SetAlpha(0.2) + col:SetHighlightTexture(tex) + + tinsert(st.headCols, col) + end + + TSMAPI.GUI:CreateHorizontalLine(st, -st.HEAD_HEIGHT) + end + + -- set all methods + for name, func in pairs(methods) do + st[name] = func + end + + -- create the rows + st.rows = {} + for i=1, st.NUM_ROWS do + st:AddRow() + end + + st:SetAllPoints() + st.displayRows = {} + st.handlers = handlers or {} + st.sortInfo = {enabled=nil} + + return st +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua new file mode 100644 index 0000000..56cc56f --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMButton.lua @@ -0,0 +1,120 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Button.lua +-- This Button widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMButton", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + + +-- Lua APIs +local pairs = pairs + +-- WoW APIs +local _G = _G +local PlaySound, CreateFrame, UIParent = PlaySound, CreateFrame, UIParent + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Button_OnClick(frame, ...) + AceGUI:ClearFocus() + PlaySound("igMainMenuOption") + frame.obj:Fire("OnClick", ...) +end + +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + self:SetHeight(24) + self:SetWidth(200) + self:SetDisabled(false) + self:SetText() + self.btn:GetFontString():SetTextColor(1, 1, 1, 1) + end, + + ["SetText"] = function(self, text) + self.btn:SetText(text) + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + if disabled then + self.btn:Disable() + self.btn:GetFontString():SetTextColor(1, 1, 1, 0.5) + else + self.btn:Enable() + self.btn:GetFontString():SetTextColor(1, 1, 1, 1) + end + end +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local name = "TSMButton" .. AceGUI:GetNextWidgetNum(Type) + + local frame = CreateFrame("Frame", nil, UIParent) + local btn = CreateFrame("Button", name, frame) + btn:Hide() + btn:EnableMouse(true) + btn:SetPoint("TOPLEFT", 0, -2) + btn:SetPoint("BOTTOMRIGHT", 0, 2) + TSMAPI.Design:SetContentColor(btn) + local highlight = btn:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + highlight:SetBlendMode("BLEND") + btn.highlight = highlight + btn:SetScript("OnClick", Button_OnClick) + btn:SetScript("OnEnter", Control_OnEnter) + btn:SetScript("OnLeave", Control_OnLeave) + btn:Show() + + local label = btn:CreateFontString() + label:SetPoint("CENTER") + label:SetHeight(15) + label:SetJustifyH("CENTER") + label:SetJustifyV("CENTER") + label:SetFont(TSMAPI.Design:GetContentFont("normal")) + TSMAPI.Design:SetWidgetTextColor(label) + btn:SetFontString(label) + + local widget = { + frame = frame, + btn = btn, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + btn.obj = widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMCheckBox.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMCheckBox.lua new file mode 100644 index 0000000..b0a0926 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMCheckBox.lua @@ -0,0 +1,181 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-CheckBox.lua +-- This CheckBox widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMCheckBox", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local select, pairs = select, pairs + +-- WoW APIs +local PlaySound = PlaySound +local CreateFrame, UIParent = CreateFrame, UIParent + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + +local function CheckBox_OnMouseDown(frame) + local self = frame.obj + if not self.disabled then + self.text:SetPoint("LEFT", self.btn, "RIGHT", 1, -1) + end + AceGUI:ClearFocus() +end + +local function CheckBox_OnMouseUp(frame, button) + local self = frame.obj + if not self.disabled then + self.text:SetPoint("LEFT", self.btn, "RIGHT", 0, 0) + self:ToggleChecked() + + if self.checked then + PlaySound("igMainMenuOptionCheckBoxOn") + else + PlaySound("igMainMenuOptionCheckBoxOff") + end + + self:Fire("OnValueChanged", self.checked) + end +end + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + self:SetType() + self:SetValue() + self:SetWidth(200) + self:SetDisabled() + self:SetHeight(24) + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetLabelColor(self.text, disabled) + if disabled then + self.frame:Disable() + local r, g, b = self.btn:GetBackdropColor() + self.btn:SetBackdropColor(r, g, b, .5) + SetDesaturation(self.check, true) + else + self.frame:Enable() + local r, g, b = self.btn:GetBackdropColor() + self.btn:SetBackdropColor(r, g, b, 1) + SetDesaturation(self.check, false) + end + end, + + ["SetValue"] = function(self,value) + local check = self.check + self.checked = value + SetDesaturation(self.check, false) + if value then + self.check:Show() + else + self.check:Hide() + end + self:SetDisabled(self.disabled) + end, + + ["GetValue"] = function(self) + return self.checked + end, + + ["SetType"] = function(self, type) + local check = self.check + + if type == "radio" then + self.btn:SetWidth(10) + self.btn:SetHeight(10) + self.btn:SetPoint("TOPLEFT", 7, -7) + self.check:SetPoint("TOPLEFT", -1, 5) + else + self.btn:SetWidth(16) + self.btn:SetHeight(16) + self.btn:SetPoint("TOPLEFT", 4, -4) + self.check:SetPoint("TOPLEFT") + end + end, + + ["ToggleChecked"] = function(self) + self:SetValue(not self:GetValue()) + end, + + ["SetLabel"] = function(self, label) + self.text:SetText(label) + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local frame = CreateFrame("Button", nil, UIParent) + frame:Hide() + frame:EnableMouse(true) + frame:SetScript("OnEnter", Control_OnEnter) + frame:SetScript("OnLeave", Control_OnLeave) + frame:SetScript("OnMouseDown", CheckBox_OnMouseDown) + frame:SetScript("OnMouseUp", CheckBox_OnMouseUp) + + local btn = CreateFrame("Button", nil, frame) + btn:EnableMouse(false) + btn:SetWidth(16) + btn:SetHeight(16) + btn:SetPoint("TOPLEFT", 4, -4) + TSMAPI.Design:SetContentColor(btn) + local highlight = btn:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + highlight:SetBlendMode("BLEND") + + local check = btn:CreateTexture(nil, "OVERLAY") + check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + check:SetTexCoord(.12, .88, .12, .88) + check:SetBlendMode("BLEND") + check:SetPoint("BOTTOMRIGHT") + + local text = frame:CreateFontString(nil, "OVERLAY") + text:SetJustifyH("LEFT") + text:SetHeight(18) + text:SetPoint("LEFT", btn, "RIGHT") + text:SetPoint("RIGHT") + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + + local widget = { + btn = btn, + check = check, + text = text, + highlight = highlight, + frame = frame, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + frame.obj = widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMColorPicker.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMColorPicker.lua new file mode 100644 index 0000000..69e02d0 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMColorPicker.lua @@ -0,0 +1,187 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-ColorPicker.lua +-- This ColorPicker widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMColorPicker", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local pairs = pairs + +-- WoW APIs +local CreateFrame, UIParent = CreateFrame, UIParent + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: ShowUIPanel, HideUIPanel, ColorPickerFrame, OpacitySliderFrame + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] +local function ColorCallback(self, r, g, b, a, isAlpha) + if not self.HasAlpha then + a = 1 + end + self:SetColor(r, g, b, a) + if ColorPickerFrame:IsVisible() then + --colorpicker is still open + self:Fire("OnValueChanged", r, g, b, a) + else + --colorpicker is closed, color callback is first, ignore it, + --alpha callback is the final call after it closes so confirm now + if isAlpha then + self:Fire("OnValueConfirmed", r, g, b, a) + end + end +end + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + +local function ColorSwatch_OnClick(frame) + HideUIPanel(ColorPickerFrame) + local self = frame.obj + if not self.disabled then + ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG") + + ColorPickerFrame.func = function() + local r, g, b = ColorPickerFrame:GetColorRGB() + local a = 1 - OpacitySliderFrame:GetValue() + ColorCallback(self, r, g, b, a) + end + + ColorPickerFrame.hasOpacity = self.HasAlpha + ColorPickerFrame.opacityFunc = function() + local r, g, b = ColorPickerFrame:GetColorRGB() + local a = 1 - OpacitySliderFrame:GetValue() + ColorCallback(self, r, g, b, a, true) + end + + local r, g, b, a = self.r, self.g, self.b, self.a + if self.HasAlpha then + ColorPickerFrame.opacity = 1 - (a or 0) + end + ColorPickerFrame:SetColorRGB(r, g, b) + + ColorPickerFrame.cancelFunc = function() + ColorCallback(self, r, g, b, a, true) + end + + ShowUIPanel(ColorPickerFrame) + end + AceGUI:ClearFocus() +end + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + self:SetHeight(24) + self:SetWidth(200) + self:SetHasAlpha(false) + self:SetColor(0, 0, 0, 1) + self:SetDisabled(nil) + self:SetLabel(nil) + end, + + ["SetLabel"] = function(self, text) + self.text:SetText(text) + end, + + ["SetColor"] = function(self, r, g, b, a) + self.r = r + self.g = g + self.b = b + self.a = a or 1 + self.colorSwatch:SetVertexColor(r, g, b, a) + end, + + ["SetHasAlpha"] = function(self, HasAlpha) + self.HasAlpha = HasAlpha + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetLabelColor(self.text, disabled) + if self.disabled then + self.frame:Disable() + else + self.frame:Enable() + end + end +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] +local function Constructor() + local frame = CreateFrame("Button", nil, UIParent) + frame:Hide() + + frame:EnableMouse(true) + frame:SetScript("OnEnter", Control_OnEnter) + frame:SetScript("OnLeave", Control_OnLeave) + frame:SetScript("OnClick", ColorSwatch_OnClick) + + local colorSwatch = frame:CreateTexture(nil, "OVERLAY") + colorSwatch:SetWidth(19) + colorSwatch:SetHeight(19) + colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") + colorSwatch:SetPoint("LEFT") + + local texture = frame:CreateTexture(nil, "BACKGROUND") + texture:SetWidth(16) + texture:SetHeight(16) + texture:SetTexture(1, 1, 1) + texture:SetPoint("CENTER", colorSwatch) + texture:Show() + + local checkers = frame:CreateTexture(nil, "BACKGROUND") + checkers:SetWidth(14) + checkers:SetHeight(14) + checkers:SetTexture("Tileset\\Generic\\Checkers") + checkers:SetTexCoord(.25, 0, 0.5, .25) + checkers:SetDesaturated(true) + checkers:SetVertexColor(1, 1, 1, 0.75) + checkers:SetPoint("CENTER", colorSwatch) + checkers:Show() + + local text = frame:CreateFontString(nil,"OVERLAY") + text:SetHeight(24) + text:SetJustifyH("LEFT") + text:SetTextColor(1, 1, 1) + text:SetPoint("LEFT", colorSwatch, "RIGHT", 2, 0) + text:SetPoint("RIGHT") + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + + local widget = { + colorSwatch = colorSwatch, + text = text, + frame = frame, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Items.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Items.lua new file mode 100644 index 0000000..b6ce553 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Items.lua @@ -0,0 +1,258 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Dropdown-Items.lua +-- This Dropdown-Items widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) + +-- Lua APIs +local select, assert = select, assert + +-- WoW APIs +local PlaySound = PlaySound +local CreateFrame = CreateFrame + + +local ItemBase = {} +do + --[[----------------------------------------------------------------------------- + Support functions + -------------------------------------------------------------------------------]] + + local function fixlevels(parent,...) + local i = 1 + local child = select(i, ...) + while child do + child:SetFrameLevel(parent:GetFrameLevel()+1) + fixlevels(child, child:GetChildren()) + i = i + 1 + child = select(i, ...) + end + end + + + --[[----------------------------------------------------------------------------- + Scripts + -------------------------------------------------------------------------------]] + + local function Frame_OnEnter(this) + local self = this.obj + + if self.useHighlight then + self.highlight:Show() + end + self:Fire("OnEnter") + + if self.specialOnEnter then + self.specialOnEnter(self) + end + end + + local function Frame_OnLeave(this) + local self = this.obj + + self.highlight:Hide() + self:Fire("OnLeave") + + if self.specialOnLeave then + self.specialOnLeave(self) + end + end + + + --[[----------------------------------------------------------------------------- + Methods + -------------------------------------------------------------------------------]] + + local methods = { + ["OnAcquire"] = function(self) + self:SetDisabled() + self.frame:SetToplevel(true) + self.frame:SetFrameStrata("FULLSCREEN_DIALOG") + end, + + ["OnRelease"] = function(self) + self:SetDisabled() + self.pullout = nil + self.frame:SetParent(nil) + self.frame:ClearAllPoints() + self.frame:Hide() + end, + + ["SetPullout"] = function(self, pullout) + self.pullout = pullout + + self.frame:SetParent(nil) + self.frame:SetParent(pullout.itemFrame) + self.parent = pullout.itemFrame + fixlevels(pullout.itemFrame, pullout.itemFrame:GetChildren()) + end, + + ["SetText"] = function(self, text) + self.text:SetText(text or "") + end, + + ["GetText"] = function(self) + return self.text:GetText() + end, + + ["SetPoint"] = function(self, ...) + self.frame:SetPoint(...) + end, + + ["Show"] = function(self) + self.frame:Show() + end, + + ["Hide"] = function(self) + self.frame:Hide() + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetTextColor(self.text, disabled) + self.useHighlight = not disabled + end, + + ["SetOnLeave"] = function(self, func) + self.specialOnLeave = func + end, + + ["SetOnEnter"] = function(self, func) + self.specialOnEnter = func + end, + } + + function ItemBase.Create(type) + local count = AceGUI:GetNextWidgetNum(type) + local frame = CreateFrame("Button", "TSMDropDownItem"..count) + + frame:SetHeight(17) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + + local text = frame:CreateFontString(nil,"OVERLAY") + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + text:SetJustifyH("LEFT") + text:SetPoint("TOPLEFT",frame,"TOPLEFT",18,0) + text:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-8,0) + + local highlight = frame:CreateTexture(nil, "OVERLAY") + highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + highlight:SetBlendMode("ADD") + highlight:SetHeight(14) + highlight:ClearAllPoints() + highlight:SetPoint("RIGHT",frame,"RIGHT",-3,0) + highlight:SetPoint("LEFT",frame,"LEFT",5,0) + highlight:Hide() + + local check = frame:CreateTexture("OVERLAY") + check:SetWidth(16) + check:SetHeight(16) + check:SetPoint("LEFT",frame,"LEFT",3,-1) + check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + check:Hide() + + local sub = frame:CreateTexture("OVERLAY") + sub:SetWidth(16) + sub:SetHeight(16) + sub:SetPoint("RIGHT",frame,"RIGHT",-3,-1) + sub:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") + sub:Hide() + + frame:SetScript("OnEnter", Frame_OnEnter) + frame:SetScript("OnLeave", Frame_OnLeave) + + local widget = { + frame = frame, + text = text, + highlight = highlight, + check = check, + sub = sub, + useHighlight = true, + origMethods = methods, + type = type, + } + for method, func in pairs(methods) do + widget[method] = func + end + frame.obj = widget + + return widget + end +end + + +do + local Type, Version = "TSMDropdown-Item-Execute", 2 + + local function Frame_OnClick(this) + local self = this.obj + if self.disabled then return end + self:Fire("OnClick") + if self.pullout then + self.pullout:Close() + end + end + + local function Constructor() + local item = ItemBase.Create(Type) + item.frame:SetScript("OnClick", Frame_OnClick) + return AceGUI:RegisterAsWidget(item) + end + + AceGUI:RegisterWidgetType(Type, Constructor, Version) +end + + +do + local Type, Version = "TSMDropdown-Item-Toggle", 2 + + local function Frame_OnClick(this, button) + local self = this.obj + if self.disabled then return end + self.value = not self.value + if self.value then + PlaySound("igMainMenuOptionCheckBoxOn") + else + PlaySound("igMainMenuOptionCheckBoxOff") + end + self:UpdateToggle() + self:Fire("OnValueChanged", self.value) + end + + local methods = { + ["UpdateToggle"] = function(self) + if self.value then + self.check:Show() + else + self.check:Hide() + end + end, + + ["SetValue"] = function(self, value) + self.value = value + self:UpdateToggle() + end, + + ["GetValue"] = function(self) + return self.value + end, + } + + local function Constructor() + local item = ItemBase.Create(Type) + item.frame:SetScript("OnClick", Frame_OnClick) + for method, func in pairs(methods) do + item[method] = func + end + return AceGUI:RegisterAsWidget(item) + end + + AceGUI:RegisterWidgetType(Type, Constructor, Version) +end \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Pullout.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Pullout.lua new file mode 100644 index 0000000..10d694a --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown-Pullout.lua @@ -0,0 +1,315 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Dropdown.lua +-- This Dropdown-Pullout widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +local Type, Version = "TSMDropdown-Pullout", 2 +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local min, max, floor = math.min, math.max, math.floor +local select, pairs, ipairs, type = select, pairs, ipairs, type +local tsort = table.sort + +-- WoW APIs +local PlaySound = PlaySound +local UIParent, CreateFrame = UIParent, CreateFrame +local _G = _G + + +--[[----------------------------------------------------------------------------- +Globals +-------------------------------------------------------------------------------]] + +local sliderBackdrop = { + bgFile = "Interface\\Buttons\\UI-SliderBar-Background", + edgeFile = "Interface\\Buttons\\UI-SliderBar-Border", + tile = true, tileSize = 8, edgeSize = 8, + insets = { left = 3, right = 3, top = 3, bottom = 3 } +} + +local DEFAULT_WIDTH = 200 +local DEFAULT_MAX_HEIGHT = 600 + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function fixstrata(strata, parent, ...) + local i = 1 + local child = select(i, ...) + parent:SetFrameStrata(strata) + while child do + fixstrata(strata, child, child:GetChildren()) + i = i + 1 + child = select(i, ...) + end +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function OnEnter(item) + local self = item.pullout + for k, v in ipairs(self.items) do + if v.CloseMenu and v ~= item then + v:CloseMenu() + end + end +end + +-- See the note in Constructor() for each scroll related function +local function OnMouseWheel(this, value) + this.obj:MoveScroll(value) +end + +local function OnScrollValueChanged(this, value) + this.obj:SetScroll(value) +end + +local function OnSizeChanged(this) + this.obj:FixScroll() +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + self.frame:SetParent(UIParent) + end, + + ["OnRelease"] = function(self) + self:Clear() + self.frame:ClearAllPoints() + self.frame:Hide() + end, + + ["SetScroll"] = function(self, value) + local status = self.scrollStatus + local frame, child = self.scrollFrame, self.itemFrame + local height, viewheight = frame:GetHeight(), child:GetHeight() + + local offset + if height > viewheight then + offset = 0 + else + offset = floor((viewheight - height) / 1000 * value) + end + child:ClearAllPoints() + child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset) + child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", self.slider:IsShown() and -12 or 0, offset) + status.offset = offset + status.scrollvalue = value + end, + + ["MoveScroll"] = function(self, value) + local status = self.scrollStatus + local frame, child = self.scrollFrame, self.itemFrame + local height, viewheight = frame:GetHeight(), child:GetHeight() + + if height > viewheight then + self.slider:Hide() + else + self.slider:Show() + local diff = height - viewheight + local delta = 1 + if value < 0 then + delta = -1 + end + self.slider:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000)) + end + end, + + ["FixScroll"] = function(self) + local status = self.scrollStatus + local frame, child = self.scrollFrame, self.itemFrame + local height, viewheight = frame:GetHeight(), child:GetHeight() + local offset = status.offset or 0 + + if viewheight < height then + self.slider:Hide() + child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, offset) + self.slider:SetValue(0) + else + self.slider:Show() + local value = (offset / (viewheight - height) * 1000) + if value > 1000 then value = 1000 end + self.slider:SetValue(value) + self:SetScroll(value) + if value < 1000 then + child:ClearAllPoints() + child:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, offset) + child:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, offset) + status.offset = offset + end + end + end, + + ["AddItem"] = function(self, item) + self.items[#self.items + 1] = item + + local h = #self.items * 16 + self.itemFrame:SetHeight(h) + self.frame:SetHeight(min(h + 20, self.maxHeight)) + + item.frame:SetPoint("LEFT", self.itemFrame, "LEFT") + item.frame:SetPoint("RIGHT", self.itemFrame, "RIGHT") + + item:SetPullout(self) + item:SetOnEnter(OnEnter) + end, + + ["Open"] = function(self, point, relFrame, relPoint, x, y) + local items = self.items + local frame = self.frame + local itemFrame = self.itemFrame + + frame:SetPoint(point, relFrame, relPoint, x, y) + + local height = 8 + for i, item in pairs(items) do + if i == 1 then + item:SetPoint("TOP", itemFrame, "TOP", 0, -2) + else + item:SetPoint("TOP", items[i-1].frame, "BOTTOM", 0, 1) + end + + item:Show() + + height = height + 16 + end + itemFrame:SetHeight(height) + fixstrata("TOOLTIP", frame, frame:GetChildren()) + frame:Show() + self:Fire("OnOpen") + end, + + ["Close"] = function(self) + self.frame:Hide() + self:Fire("OnClose") + end, + + ["Clear"] = function(self) + local items = self.items + for i, item in pairs(items) do + AceGUI:Release(item) + items[i] = nil + end + end, + + ["IterateItems"] = function(self) + return ipairs(self.items) + end, + + ["SetHideOnLeave"] = function(self, val) + self.hideOnLeave = val + end, + + ["SetMaxHeight"] = function(self, height) + self.maxHeight = height or DEFAULT_MAX_HEIGHT + if self.frame:GetHeight() > height then + self.frame:SetHeight(height) + elseif (self.itemFrame:GetHeight()+20) < height then + self.frame:SetHeight(self.itemFrame:GetHeight()+20) -- see :AddItem + end + end, + + ["GetRightBorderWidth"] = function(self) + return 6 + (self.slider:IsShown() and 12 or 0) + end, + + ["GetLeftBorderWidth"] = function(self) + return 6 + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local count = AceGUI:GetNextWidgetNum(Type) + + local frame = CreateFrame("Frame", "TSMPullout"..count, UIParent) + TSMAPI.Design:SetContentColor(frame) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:SetClampedToScreen(true) + frame:SetWidth(DEFAULT_WIDTH) + frame:SetHeight(DEFAULT_MAX_HEIGHT) + + local scrollFrame = CreateFrame("ScrollFrame", nil, frame) + local itemFrame = CreateFrame("Frame", nil, scrollFrame) + + local slider = CreateFrame("Slider", "TSMPulloutScrollbar"..count, scrollFrame) + slider:SetOrientation("VERTICAL") + slider:SetHitRectInsets(0, 0, -10, 0) + slider:SetBackdrop(sliderBackdrop) + slider:SetWidth(8) + slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical") + slider:SetFrameStrata("FULLSCREEN_DIALOG") + + scrollFrame:SetScrollChild(itemFrame) + scrollFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4) + scrollFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -4, 4) + scrollFrame:EnableMouseWheel(true) + scrollFrame:SetScript("OnMouseWheel", OnMouseWheel) + scrollFrame:SetScript("OnSizeChanged", OnSizeChanged) + scrollFrame:SetToplevel(true) + scrollFrame:SetFrameStrata("FULLSCREEN_DIALOG") + + itemFrame:SetPoint("TOPLEFT", scrollFrame, "TOPLEFT", 0, 0) + itemFrame:SetPoint("TOPRIGHT", scrollFrame, "TOPRIGHT", -12, 0) + itemFrame:SetHeight(400) + itemFrame:SetToplevel(true) + itemFrame:SetFrameStrata("FULLSCREEN_DIALOG") + + slider:SetPoint("TOPLEFT", scrollFrame, "TOPRIGHT", -16, 0) + slider:SetPoint("BOTTOMLEFT", scrollFrame, "BOTTOMRIGHT", -16, 0) + + scrollFrame:Show() + itemFrame:Show() + slider:Hide() + + local widget = { + frame = frame, + slider = slider, + scrollFrame = scrollFrame, + itemFrame = itemFrame, + maxHeight = DEFAULT_MAX_HEIGHT, + scrollStatus = {scrollvalue=0}, + count = count, + items = {}, + type = Type, + } + for method, func in pairs(methods) do + widget[method] = func + end + frame.obj = widget + scrollFrame.obj = widget + itemFrame.obj = widget + slider.obj = widget + + slider:SetScript("OnValueChanged", OnScrollValueChanged) + slider:SetMinMaxValues(0, 1000) + slider:SetValueStep(1) + slider:SetValue(0) + widget:FixScroll() + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown.lua new file mode 100644 index 0000000..9c9d390 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMDropdown.lua @@ -0,0 +1,413 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Dropdown.lua +-- This Dropdown widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +local Type, Version = "TSMDropdown", 2 +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local min, max, floor = math.min, math.max, math.floor +local select, pairs, ipairs, type = select, pairs, ipairs, type +local tsort = table.sort + +-- WoW APIs +local PlaySound = PlaySound +local UIParent, CreateFrame = UIParent, CreateFrame +local _G = _G + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function fixlevels(parent,...) + local i = 1 + local child = select(i, ...) + while child do + child:SetFrameLevel(parent:GetFrameLevel()+1) + fixlevels(child, child:GetChildren()) + i = i + 1 + child = select(i, ...) + end +end + +local function fixstrata(strata, parent, ...) + local i = 1 + local child = select(i, ...) + parent:SetFrameStrata(strata) + while child do + fixstrata(strata, child, child:GetChildren()) + i = i + 1 + child = select(i, ...) + end +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Control_OnEnter(this) + this.obj:Fire("OnEnter") +end + +local function Control_OnLeave(this) + this.obj:Fire("OnLeave") +end + +local function Dropdown_OnHide(this) + local self = this.obj + if self.open then + self.pullout:Close() + end +end + +local function Dropdown_TogglePullout(this, button) + local self = this.obj + if self.disabled then return end + PlaySound("igMainMenuOptionCheckBoxOn") -- missleading name, but the Blizzard code uses this sound + if self.open then + self.open = nil + self.pullout:Close() + AceGUI:ClearFocus() + else + self.open = true + -- self.pullout:SetWidth(self.dropdown:GetWidth()) + self.pullout:SetWidth(self.frame:GetWidth()-8) + self.pullout:Open("TOPLEFT", self.frame, "BOTTOMLEFT", 0, self.label:IsShown() and -2 or 0) + AceGUI:SetFocus(self) + end +end + +local function OnPulloutOpen(this) + local self = this.userdata.obj + local value = self.value + + if not self.multiselect then + for i, item in this:IterateItems() do + item:SetValue(item.userdata.value == value) + end + end + + self.open = true +end + +local function OnPulloutClose(this) + local self = this.userdata.obj + self.open = nil + self:Fire("OnClosed") +end + +local function ShowMultiText(self) + local text + for i, widget in self.pullout:IterateItems() do + if widget.type == "TSMDropdown-Item-Toggle" then + if widget:GetValue() then + if text then + text = text..", "..widget:GetText() + else + text = widget:GetText() + end + end + end + end + self:SetText(text) +end + +local function OnItemValueChanged(this, event, checked) + local self = this.userdata.obj + + if self.multiselect then + self:Fire("OnValueChanged", this.userdata.value, checked) + ShowMultiText(self) + else + if checked then + self:SetValue(this.userdata.value) + self:Fire("OnValueChanged", this.userdata.value) + else + this:SetValue(true) + end + if self.open then + self.pullout:Close() + end + end +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + local pullout = AceGUI:Create("TSMDropdown-Pullout") + self.pullout = pullout + pullout.userdata.obj = self + pullout:SetCallback("OnClose", OnPulloutClose) + pullout:SetCallback("OnOpen", OnPulloutOpen) + self.pullout.frame:SetFrameLevel(self.frame:GetFrameLevel() + 1) + fixlevels(self.pullout.frame, self.pullout.frame:GetChildren()) + + self:SetHeight(44) + self:SetWidth(200) + self:SetLabel() + self:ClearMultiselectChecked() + end, + + ["OnRelease"] = function(self) + if self.open then + self.pullout:Close() + end + AceGUI:Release(self.pullout) + self.pullout = nil + + self:SetText("") + self:SetDisabled(false) + self:SetMultiselect(false) + + self.value = nil + self.list = nil + self.open = nil + self.hasClose = nil + + self.frame:ClearAllPoints() + self.frame:Hide() + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetTextColor(self.text, disabled) + TSMAPI.Design:SetWidgetLabelColor(self.label, disabled) + if disabled then + self.button:Disable() + else + self.button:Enable() + end + end, + + ["ClearFocus"] = function(self) + if self.open then + self.pullout:Close() + end + end, + + ["SetText"] = function(self, text) + self.text:SetText(text or "") + end, + + ["SetLabel"] = function(self, text) + if text and text ~= "" then + self.label:SetText(text) + self.label:Show() + self.dropdown:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 2, -18) + self:SetHeight(44) + self.alignoffset = 30 + else + self.label:SetText("") + self.label:Hide() + self.dropdown:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 2, 0) + self:SetHeight(26) + self.alignoffset = 12 + end + end, + + ["SetValue"] = function(self, value) + if self.list then + self:SetText(self.list[value] or "") + end + self.value = value + end, + + ["GetValue"] = function(self) + return self.value + end, + + ["SetItemValue"] = function(self, item, value) + if not self.multiselect then return end + for i, widget in self.pullout:IterateItems() do + if widget.userdata.value == item then + if widget.SetValue then + widget:SetValue(value) + end + end + end + ShowMultiText(self) + end, + + ["SetItemDisabled"] = function(self, item, disabled) + for i, widget in self.pullout:IterateItems() do + if widget.userdata.value == item then + widget:SetDisabled(disabled) + end + end + end, + + ["AddListItem"] = function(self, value, text, itemType) + itemType = itemType or "TSMDropdown-Item-Toggle" + local exists = AceGUI:GetWidgetVersion(itemType) + if not exists then error(("The given item type, %q, does not exist within AceGUI-3.0"):format(tostring(itemType)), 2) end + + local item = AceGUI:Create(itemType) + item:SetText(text) + item.userdata.obj = self + item.userdata.value = value + item:SetValue() + item:SetCallback("OnValueChanged", OnItemValueChanged) + self.pullout:AddItem(item) + end, + + ["AddCloseButton"] = function(self) + if not self.hasClose then + local close = AceGUI:Create("TSMDropdown-Item-Execute") + close:SetText(CLOSE) + self.pullout:AddItem(close) + self.hasClose = true + end + end, + + ["SetList"] = function(self, list, order, itemType) + self.sortlist = self.sortlist or {} + self.list = list + self.pullout:Clear() + self.hasClose = nil + if not list then return end + + if type(order) ~= "table" then + for v in pairs(list) do + self.sortlist[#self.sortlist + 1] = v + end + tsort(self.sortlist) + + for i, key in ipairs(self.sortlist) do + self:AddListItem(key, list[key], itemType) + self.sortlist[i] = nil + end + else + for i, key in ipairs(order) do + self:AddListItem(key, list[key], itemType) + end + end + if self.multiselect then + ShowMultiText(self) + self:AddCloseButton() + end + end, + + ["AddItem"] = function(self, value, text, itemType) + if self.list then + self.list[value] = text + self:AddListItem(value, text, itemType) + end + end, + + ["SetMultiselect"] = function(self, multi) + self.multiselect = multi + if multi then + ShowMultiText(self) + self:AddCloseButton() + end + end, + + ["GetMultiselect"] = function(self) + return self.multiselect + end, + + ["ClearMultiselectChecked"] = function(self) + for i, widget in self.pullout:IterateItems() do + if widget.type == "TSMDropdown-Item-Toggle" then + widget:SetValue() + end + end + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local count = AceGUI:GetNextWidgetNum(Type) + + local frame = CreateFrame("Frame", nil, UIParent) + local dropdown = CreateFrame("Frame", "TSMDropDown"..count, frame, "UIDropDownMenuTemplate") + + frame:SetScript("OnHide", Dropdown_OnHide) + + dropdown:ClearAllPoints() + dropdown:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -7, 0) + dropdown:SetScript("OnHide", nil) + dropdown:SetScript("OnEnter", Control_OnEnter) + dropdown:SetScript("OnLeave", Control_OnLeave) + dropdown:SetScript("OnMouseUp", function(self, button) Dropdown_TogglePullout(self.obj.button, button) end) + TSMAPI.Design:SetContentColor(dropdown) + + local left = _G[dropdown:GetName().."Left"] + local middle = _G[dropdown:GetName().."Middle"] + local right = _G[dropdown:GetName().."Right"] + + middle:ClearAllPoints() + right:ClearAllPoints() + + middle:SetPoint("LEFT", left, "RIGHT", 0, 0) + middle:SetPoint("RIGHT", right, "LEFT", 0, 0) + right:SetPoint("TOPRIGHT", dropdown, "TOPRIGHT", 0, 17) + + local button = _G[dropdown:GetName().."Button"] + button:RegisterForClicks("AnyUp") + button:SetScript("OnEnter", Control_OnEnter) + button:SetScript("OnLeave", Control_OnLeave) + button:SetScript("OnClick", Dropdown_TogglePullout) + button:ClearAllPoints() + button:SetPoint("RIGHT", dropdown, 0, 0) + + local text = _G[dropdown:GetName().."Text"] + text:ClearAllPoints() + text:SetPoint("RIGHT", button, "LEFT", -2, 0) + text:SetPoint("LEFT", dropdown, "LEFT", 8, 0) + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + text:SetShadowColor(0, 0, 0, 0) + + local label = frame:CreateFontString(nil, "OVERLAY") + label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0) + label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0) + label:SetJustifyH("LEFT") + label:SetHeight(18) + label:SetFont(TSMAPI.Design:GetContentFont("small")) + label:SetShadowColor(0, 0, 0, 0) + label:Hide() + + left:Hide() + middle:Hide() + right:Hide() + + local widget = { + frame = frame, + label = label, + dropdown = dropdown, + text = text, + button = button, + count = count, + alignoffset = 30, + type = Type, + } + for method, func in pairs(methods) do + widget[method] = func + end + frame.obj = widget + dropdown.obj = widget + text.obj = widget + button.obj = widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMEditBox.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMEditBox.lua new file mode 100644 index 0000000..7168b02 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMEditBox.lua @@ -0,0 +1,283 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua +-- This EditBox widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMEditBox", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local tostring, pairs = tostring, pairs + +-- WoW APIs +local PlaySound = PlaySound +local GetCursorInfo, ClearCursor, GetSpellInfo = GetCursorInfo, ClearCursor, GetSpellInfo +local CreateFrame, UIParent = CreateFrame, UIParent +local _G = _G + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +if not TSMEditBoxInsertLink then + -- upgradeable hook + hooksecurefunc("ChatEdit_InsertLink", function(...) return _G.TSMEditBoxInsertLink(...) end) +end + +function _G.TSMEditBoxInsertLink(text) + for i = 1, AceGUI:GetWidgetCount(Type) do + local editbox = _G["TSMEditBox"..i] + if editbox and editbox:IsVisible() and editbox:HasFocus() then + editbox:Insert(text) + return true + end + end +end + +local function ShowButton(self) + if not self.disablebutton then + self.button:Show() + self.editbox:SetTextInsets(0, 20, 3, 3) + end +end + +local function HideButton(self) + self.button:Hide() + self.editbox:SetTextInsets(0, 0, 3, 3) +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + +local function Frame_OnShowFocus(frame) + frame.obj.editbox:SetFocus() + frame:SetScript("OnShow", nil) +end + +local function EditBox_OnEscapePressed(frame) + AceGUI:ClearFocus() +end + +local function EditBox_OnEnterPressed(frame) + local self = frame.obj + local value = frame:GetText() + self:ClearFocus() + local cancel = self:Fire("OnEnterPressed", value) + if not cancel then + PlaySound("igMainMenuOptionCheckBoxOn") + HideButton(self) + end +end + +local function EditBox_OnReceiveDrag(frame) + local self = frame.obj + local type, id, info = GetCursorInfo() + if type == "item" then + self:SetText(info) + self:Fire("OnEnterPressed", info) + ClearCursor() + elseif type == "spell" then + local name = GetSpellInfo(id, info) + self:SetText(name) + self:Fire("OnEnterPressed", name) + ClearCursor() + end + HideButton(self) + AceGUI:ClearFocus() +end + +local function EditBox_OnTextChanged(frame) + local self = frame.obj + local value = frame:GetText() + if tostring(value) ~= tostring(self.lasttext) then + self:Fire("OnTextChanged", value) + self.lasttext = value + ShowButton(self) + end +end + +local function EditBox_OnFocusGained(frame) + AceGUI:SetFocus(frame.obj) + frame.obj:Fire("OnEditFocusGained") +end + +local function EditBox_OnFocusLost(frame) + frame.obj:Fire("OnEditFocusLost") +end + +local function Button_OnClick(frame) + local editbox = frame.obj.editbox + editbox:ClearFocus() + EditBox_OnEnterPressed(editbox) +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + -- height is controlled by SetLabel + self:SetWidth(200) + self:SetDisabled() + self:SetLabel() + self:SetText() + self:DisableButton() + self:SetMaxLetters(0) + TSMAPI.GUI:SetAutoComplete(self.editbox, nil) + end, + + ["OnRelease"] = function(self) + self:ClearFocus() + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetTextColor(self.editbox, disabled) + self.editbox:EnableMouse(not disabled) + TSMAPI.Design:SetWidgetLabelColor(self.label, disabled) + if disabled then + self.editbox:ClearFocus() + end + end, + + ["SetText"] = function(self, text) + if self.disabled and text then + text = gsub(text, "|cff([0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])", "") + text = gsub(text, "|r", "") + end + self.lasttext = text or "" + self.editbox:SetText(text or "") + self.editbox:SetCursorPosition(0) + HideButton(self) + end, + + ["GetText"] = function(self, text) + return self.editbox:GetText() + end, + + ["SetLabel"] = function(self, text) + if text and text ~= "" then + self.label:SetText(text) + self.label:Show() + self.editbox:SetPoint("TOPLEFT", 2, -18) + self:SetHeight(44) + self.alignoffset = 30 + else + self.label:SetText("") + self.label:Hide() + self.editbox:SetPoint("TOPLEFT", 2, 0) + self:SetHeight(26) + self.alignoffset = 12 + end + end, + + ["DisableButton"] = function(self, disabled) + self.disablebutton = disabled + if disabled then + HideButton(self) + end + end, + + ["SetMaxLetters"] = function (self, num) + self.editbox:SetMaxLetters(num or 0) + end, + + ["ClearFocus"] = function(self) + self.editbox:ClearFocus() + self.frame:SetScript("OnShow", nil) + end, + + ["SetFocus"] = function(self) + self.editbox:SetFocus() + if not self.frame:IsShown() then + self.frame:SetScript("OnShow", Frame_OnShowFocus) + end + end, + + ["SetAutoComplete"] = function(self, params) + TSMAPI.GUI:SetAutoComplete(self.editbox, params) + end, +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local num = AceGUI:GetNextWidgetNum(Type) + local frame = CreateFrame("Frame", nil, UIParent) + frame:Hide() + + local editbox = CreateFrame("EditBox", "TSMEditBox"..num, frame) + editbox:SetAutoFocus(false) + editbox:SetScript("OnEnter", Control_OnEnter) + editbox:SetScript("OnLeave", Control_OnLeave) + editbox:SetScript("OnEscapePressed", EditBox_OnEscapePressed) + editbox:SetScript("OnEnterPressed", EditBox_OnEnterPressed) + editbox:SetScript("OnTextChanged", EditBox_OnTextChanged) + editbox:SetScript("OnReceiveDrag", EditBox_OnReceiveDrag) + editbox:SetScript("OnMouseDown", EditBox_OnReceiveDrag) + editbox:SetScript("OnEditFocusGained", EditBox_OnFocusGained) + editbox:SetScript("OnEditFocusLost", EditBox_OnFocusLost) + editbox:SetTextInsets(0, 0, 3, 3) + editbox:SetMaxLetters(256) + editbox:SetPoint("BOTTOMRIGHT", -6, 0) + editbox:SetHeight(19) + editbox:SetFont(TSMAPI.Design:GetContentFont("normal")) + editbox:SetShadowColor(0, 0, 0, 0) + TSMAPI.Design:SetContentColor(editbox) + + local label = frame:CreateFontString(nil, "OVERLAY") + label:SetPoint("TOPLEFT", 0, -2) + label:SetPoint("TOPRIGHT", 0, -2) + label:SetJustifyH("LEFT") + label:SetJustifyV("CENTER") + label:SetHeight(18) + label:SetFont(TSMAPI.Design:GetContentFont("normal")) + label:SetShadowColor(0, 0, 0, 0) + + local button = CreateFrame("Button", nil, editbox, "UIPanelButtonTemplate") + button:SetWidth(40) + button:SetHeight(20) + button:SetPoint("RIGHT", -2, 0) + button:SetText(OKAY) + button:SetScript("OnClick", Button_OnClick) + button:Hide() + + local widget = { + frame = frame, + alignoffset = 30, + editbox = editbox, + label = label, + button = button, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + editbox.obj, button.obj = widget, widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMGroupBox.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMGroupBox.lua new file mode 100644 index 0000000..2dee107 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMGroupBox.lua @@ -0,0 +1,231 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-EditBox.lua +-- This EditBox widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMGroupBox", 2 +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local tostring, pairs = tostring, pairs + +-- WoW APIs +local PlaySound = PlaySound +local GetCursorInfo, ClearCursor, GetSpellInfo = GetCursorInfo, ClearCursor, GetSpellInfo +local CreateFrame, UIParent = CreateFrame, UIParent +local _G = _G + +-- local variables +local groupSelectionFrame + + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function CreateGroupSelectionFrame() + if groupSelectionFrame then return end + groupSelectionFrame = CreateFrame("Frame", nil, TSMMainFrame1) + TSMAPI.Design:SetFrameBackdropColor(groupSelectionFrame) + groupSelectionFrame:SetWidth(300) + groupSelectionFrame:SetHeight(400) + groupSelectionFrame:SetPoint("CENTER") + + local label = TSMAPI.GUI:CreateLabel(groupSelectionFrame) + label:SetPoint("TOPLEFT", 5, -2) + label:SetPoint("TOPRIGHT", -5, -2) + label:SetHeight(40) + label:SetJustifyV("CENTER") + label:SetJustifyH("CENTER") + label:SetText(L["Select a group from the list below and click 'OK' at the bottom."]) + + local container = CreateFrame("Frame", nil, groupSelectionFrame) + container:SetPoint("TOPLEFT", 5, -45) + container:SetPoint("BOTTOMRIGHT", -5, 45) + TSMAPI.Design:SetFrameColor(container) + groupSelectionFrame.groupTree = TSMAPI:CreateGroupTree(container, nil, nil, true) + + local function OnBtnClick(btn) + if btn.which == "clear" then + groupSelectionFrame.groupTree:ClearSelection() + elseif btn.which == "cancel" then + groupSelectionFrame:Hide() + elseif btn.which == "okay" then + local groupBox = groupSelectionFrame.obj + local groupPath = groupSelectionFrame.groupTree:GetGroupBoxSelection() + groupBox:SetText(TSMAPI:FormatGroupPath(groupPath)) + groupBox:Fire("OnValueChanged", groupPath) + groupSelectionFrame:Hide() + end + end + + local btn = TSMAPI.GUI:CreateButton(groupSelectionFrame, 14) + btn:SetPoint("BOTTOMLEFT", 5, 5) + btn:SetWidth(90) + btn:SetHeight(24) + btn:SetText(L["Clear"]) + btn:SetScript("OnClick", OnBtnClick) + btn.which = "clear" + groupSelectionFrame.clearBtn = btn + + local btn = TSMAPI.GUI:CreateButton(groupSelectionFrame, 14) + btn:SetPoint("BOTTOMLEFT", groupSelectionFrame.clearBtn, "BOTTOMRIGHT", 5, 0) + btn:SetWidth(90) + btn:SetHeight(24) + btn:SetText(CANCEL) + btn:SetScript("OnClick", OnBtnClick) + btn.which = "cancel" + groupSelectionFrame.cancelBtn = btn + + local btn = TSMAPI.GUI:CreateButton(groupSelectionFrame, 14) + btn:SetPoint("BOTTOMLEFT", groupSelectionFrame.cancelBtn, "BOTTOMRIGHT", 5, 0) + btn:SetPoint("BOTTOMRIGHT", -5, 5) + btn:SetWidth(90) + btn:SetHeight(24) + btn:SetText(OKAY) + btn:SetScript("OnClick", OnBtnClick) + btn.which = "okay" + groupSelectionFrame.okBtn = btn + + groupSelectionFrame:Hide() +end + +local function ShowGroupSelectionFrame(parent, obj) + groupSelectionFrame:SetFrameStrata("FULLSCREEN_DIALOG") + groupSelectionFrame:Show() + groupSelectionFrame:SetPoint("CENTER", parent) + groupSelectionFrame.obj = obj + groupSelectionFrame.groupTree:SetGropBoxSelection(obj:GetText()) +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + +local function Control_OnClick(frame) + frame.obj.editbox:ClearFocus() + ShowGroupSelectionFrame(frame, frame.obj) +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + -- height is controlled by SetLabel + self:SetWidth(200) + self:SetDisabled() + self:SetLabel() + self:SetText() + self:SetMaxLetters(0) + CreateGroupSelectionFrame() + end, + + ["OnRelease"] = function(self) + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetTextColor(self.editbox, disabled) + self.editbox:EnableMouse(not disabled) + TSMAPI.Design:SetWidgetLabelColor(self.label, disabled) + end, + + ["SetText"] = function(self, text) + self.editbox:SetText(text or "") + self.editbox:SetCursorPosition(0) + end, + + ["GetText"] = function(self, text) + return self.editbox:GetText() + end, + + ["SetLabel"] = function(self, text) + if text and text ~= "" then + self.label:SetText(text) + self.label:Show() + self.editbox:SetPoint("TOPLEFT", 2, -18) + self:SetHeight(44) + self.alignoffset = 30 + else + self.label:SetText("") + self.label:Hide() + self.editbox:SetPoint("TOPLEFT", 2, 0) + self:SetHeight(26) + self.alignoffset = 12 + end + end, + + ["SetMaxLetters"] = function (self, num) + self.editbox:SetMaxLetters(num or 0) + end, +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local num = AceGUI:GetNextWidgetNum(Type) + local frame = CreateFrame("Frame", nil, UIParent) + frame:Hide() + + local editbox = CreateFrame("EditBox", "TSMGroupBox"..num, frame) + editbox:SetAutoFocus(false) + editbox:SetScript("OnEnter", Control_OnEnter) + editbox:SetScript("OnLeave", Control_OnLeave) + editbox:SetScript("OnEditFocusGained", Control_OnClick) + editbox:SetTextInsets(0, 0, 3, 3) + editbox:SetMaxLetters(256) + editbox:SetPoint("BOTTOMRIGHT", -6, 0) + editbox:SetHeight(19) + editbox:SetFont(TSMAPI.Design:GetContentFont("small")) + editbox:SetShadowColor(0, 0, 0, 0) + TSMAPI.Design:SetContentColor(editbox) + + local label = frame:CreateFontString(nil, "OVERLAY") + label:SetPoint("TOPLEFT", 0, -2) + label:SetPoint("TOPRIGHT", 0, -2) + label:SetJustifyH("LEFT") + label:SetJustifyV("CENTER") + label:SetHeight(18) + label:SetFont(TSMAPI.Design:GetContentFont("normal")) + label:SetShadowColor(0, 0, 0, 0) + + local widget = { + frame = frame, + alignoffset = 30, + editbox = editbox, + label = label, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + frame.obj = widget + editbox.obj = widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMGroupItemList.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMGroupItemList.lua new file mode 100644 index 0000000..0571613 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMGroupItemList.lua @@ -0,0 +1,515 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +--[[----------------------------------------------------------------------------- +Group Item List Widget +Provides two scroll lists with buttons to move selected items from one list to the other. +-------------------------------------------------------------------------------]] +local TSM = select(2, ...) +local Type, Version = "TSMGroupItemList", 1 +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +local ROW_HEIGHT = 16 + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function ShowIcon(row) + row.iconFrame:Show() + row.label:SetPoint("TOPLEFT", 20, 0) + row.label:SetPoint("BOTTOMRIGHT") +end + +local function HideIcon(row) + row.iconFrame:Hide() + row.label:SetPoint("TOPLEFT", 0, 0) + row.label:SetPoint("BOTTOMRIGHT") +end + +local function UpdateScrollFrame(self) + local parent = self:GetParent() + if not parent.obj.GetListCallback then return end + parent.items = parent.obj.GetListCallback(parent == parent.obj.leftFrame and "left" or "right") + if not parent.list then + parent.list = {} + local usedItems = {} + for _, itemLink in ipairs(parent.items) do + local itemString = TSMAPI:GetItemString(itemLink) + local name, link, _, _, _, _, _, _, _, texture = TSMAPI:GetSafeItemInfo(itemString) + if itemString and name and texture and not usedItems[itemString] then + usedItems[itemString] = true + tinsert(parent.list, {value=itemString, link=link, icon=texture, sortText=strlower(name)}) + end + end + sort(parent.list, function(a, b) return a.sortText < b.sortText end) + end + + local rows = self.rows + -- clear all the rows + for _, v in pairs(rows) do + v.value = nil + v.label:SetText("") + v.iconFrame.icon:SetTexture("") + v:Hide() + end + + local rowData = {} + for _, data in ipairs(self:GetParent().list) do + if not data.filtered then + tinsert(rowData, data) + end + end + + local maxRows = floor((self.height-5)/(ROW_HEIGHT+2)) + FauxScrollFrame_Update(self, #(rowData), maxRows-1, ROW_HEIGHT) + local offset = FauxScrollFrame_GetOffset(self) + local displayIndex = 0 + + -- make the rows bigger if the scroller isn't showing + if self:IsVisible() then + rows[1]:SetPoint("TOPRIGHT", self:GetParent(), -26, 0) + else + rows[1]:SetPoint("TOPRIGHT", self:GetParent(), -10, 0) + end + + for index, data in ipairs(rowData) do + if index >= offset and displayIndex < maxRows then + displayIndex = displayIndex + 1 + local row = rows[displayIndex] + + row.label:SetText(data.link) + row.value = data.value + row.data = data + + if data.selected then + row:LockHighlight() + else + row:UnlockHighlight() + end + + if data.icon then + row.iconFrame.icon:SetTexture(data.icon) + ShowIcon(row) + else + HideIcon(row) + end + row:Show() + end + end +end + +local function UpdateRows(parent) + local numRows = floor((parent.height-5)/(ROW_HEIGHT+2)) + parent.rows = parent.rows or {} + for i=1, numRows do + if not parent.rows[i] then + local row = CreateFrame("Button", parent:GetName().."Row"..i, parent:GetParent()) + row:SetHeight(ROW_HEIGHT) + row:SetScript("OnClick", function(self) + self.data.selected = not self.data.selected + if self.data.selected then + self:LockHighlight() + else + self:UnlockHighlight() + end + end) + row:SetScript("OnEnter", function(self) + GameTooltip:SetOwner(self, "ANCHOR_NONE") + GameTooltip:SetPoint("LEFT", parent:GetParent():GetParent(), "RIGHT") + TSMAPI:SafeTooltipLink(self.data.link) + GameTooltip:Show() + end) + -- row:SetScript("OnLeave", function() GameTooltip:Hide() BattlePetTooltip:Hide() end) + row:SetScript("OnLeave", function() GameTooltip:Hide() end) + + if i > 1 then + row:SetPoint("TOPLEFT", parent.rows[i-1], "BOTTOMLEFT", 0, -2) + row:SetPoint("TOPRIGHT", parent.rows[i-1], "BOTTOMRIGHT", 0, -2) + else + row:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0) + row:SetPoint("TOPRIGHT", parent, "TOPRIGHT", 4, 0) + end + + -- highlight / selection texture for the row + local highlightTex = row:CreateTexture() + highlightTex:SetTexture("Interface\\Buttons\\UI-Listbox-Highlight") + highlightTex:SetPoint("TOPRIGHT", row, "TOPRIGHT", 0, 0) + highlightTex:SetPoint("BOTTOMLEFT") + highlightTex:SetAlpha(0.7) + row:SetHighlightTexture(highlightTex) + + -- icon that goes to the left of the text + local iconFrame = CreateFrame("Frame", nil, row) + iconFrame:SetHeight(ROW_HEIGHT-2) + iconFrame:SetWidth(ROW_HEIGHT-2) + iconFrame:SetPoint("TOPLEFT") + row.iconFrame = iconFrame + + -- texture that goes inside the iconFrame + local iconTexture = iconFrame:CreateTexture(nil, "BACKGROUND") + iconTexture:SetAllPoints(iconFrame) + iconTexture:SetVertexColor(1, 1, 1) + iconFrame.icon = iconTexture + + local label = row:CreateFontString(nil, "OVERLAY") + label:SetFont(TSMAPI.Design:GetContentFont("normal")) + label:SetJustifyH("LEFT") + label:SetJustifyV("CENTER") + label:SetPoint("TOPLEFT", 20, 0) + label:SetPoint("BOTTOMRIGHT", 10, 0) + TSMAPI.Design:SetWidgetTextColor(label) + row.label = label + + parent.rows[i] = row + end + end + UpdateScrollFrame(parent) +end + +local function OnButtonClick(self) + local selected = {} + local rows, rowData + + if self.type == "Add" then + rows = self.obj.leftFrame.scrollFrame.rows + rowData = self.obj.leftFrame.list + elseif self.type == "Remove" then + rows = self.obj.rightFrame.scrollFrame.rows + rowData = self.obj.rightFrame.list + end + if not rows then error("Invalid type") end + + local temp = {} + for _, row in pairs(rows) do + if row.data and row.data.selected and row.value then + row.data.selected = false + row:UnlockHighlight() + temp[row.value] = true + tinsert(selected, row.value) + end + end + + for _, data in pairs(rowData) do + if data.selected and data.value and not temp[data.value] then + data.selected = false + tinsert(selected, data.value) + end + end + + self.obj:Fire("On"..self.type.."Clicked", selected) +end + +local function OnFilterSet(self) + self:ClearFocus() + local text = strlower(TSMAPI:StrEscape(self:GetText():trim())) + + local filterStr, minLevel, maxLevel, minILevel, maxILevel + for _, part in ipairs({("/"):split(text)}) do + part = part:trim() + if part ~= "" then + local lvl = tonumber(part) + local ilvl = gsub(part, "^i", "") + ilvl = tonumber(ilvl) + if lvl then + if not minLevel then + minLevel = lvl + elseif not maxLevel then + maxLevel = lvl + else + return TSM:Print(L["Invalid filter."]) + end + elseif ilvl then + if not minILevel then + minILevel = ilvl + elseif not maxILevel then + maxILevel = ilvl + else + return TSM:Print(L["Invalid filter."]) + end + else + if filterStr then + return TSM:Print(L["Invalid filter."]) + end + filterStr = part + end + end + end + filterStr = filterStr or "" + minLevel = minLevel or 0 + maxLevel = maxLevel or math.huge + minILevel = minILevel or 0 + maxILevel = maxILevel or math.huge + + for _, info in ipairs(self.obj.leftFrame.list) do + local name, _, _, ilvl, lvl = TSMAPI:GetSafeItemInfo(info.link) + local selected = (strfind(strlower(name), filterStr) and ilvl >= minILevel and ilvl <= maxILevel and lvl >= minLevel and lvl <= maxLevel) + info.selected = selected + info.filtered = not selected + end + for _, info in ipairs(self.obj.rightFrame.list) do + local name, _, _, ilvl, lvl = TSMAPI:GetSafeItemInfo(info.link) + local selected = (strfind(strlower(name), filterStr) and ilvl >= minILevel and ilvl <= maxILevel and lvl >= minLevel and lvl <= maxLevel) + info.selected = selected + info.filtered = not selected + end + FauxScrollFrame_SetOffset(self.obj.leftFrame.scrollFrame, 0) + FauxScrollFrame_SetOffset(self.obj.rightFrame.scrollFrame, 0) + UpdateScrollFrame(self.obj.leftFrame.scrollFrame) + UpdateScrollFrame(self.obj.rightFrame.scrollFrame) +end + +local function OnClearButtonClicked(self) + for _, info in ipairs(self.obj.leftFrame.list) do + info.selected = false + end + for _, info in ipairs(self.obj.rightFrame.list) do + info.selected = false + end + UpdateScrollFrame(self.obj.leftFrame.scrollFrame) + UpdateScrollFrame(self.obj.rightFrame.scrollFrame) +end + +local function OnIgnoreChanged(self, _, value) + TSM.db.global.ignoreRandomEnchants = value + self.obj.leftFrame.list = nil + UpdateScrollFrame(self.obj.leftFrame.scrollFrame) +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + -- restore default values + self:SetHeight(550) + TSMAPI:CreateTimeDelay(0.05, function() self.parent:DoLayout() end) + self.filter:SetText("") + self.ignoreCheckBox:SetValue(TSM.db.global.ignoreRandomEnchants) + end, + + ["OnRelease"] = function(self) + -- clear any points / other values + wipe(self.leftFrame.list) + wipe(self.rightFrame.list) + self.frame.leftTitle:SetText("") + self.frame.rightTitle:SetText("") + end, + + ["OnHeightSet"] = function(self, height) + if height == 100 then return end + self.leftScrollFrame.height = self.frame:GetHeight() - 85 + self.rightScrollFrame.height = self.frame:GetHeight() - 85 + UpdateRows(self.leftScrollFrame) + UpdateRows(self.rightScrollFrame) + end, + + ["SetListCallback"] = function(self, callback) + self.GetListCallback = callback + self.leftFrame.list = nil + self.rightFrame.list = nil + UpdateScrollFrame(self.leftScrollFrame) + UpdateScrollFrame(self.rightScrollFrame) + end, + + ["SetTitle"] = function(self, side, title) + if strlower(side) == "left" then + self.frame.leftTitle:SetText(title) + elseif strlower(side) == "right" then + self.frame.rightTitle:SetText(title) + elseif title then + error("Invalid side passed. Expected 'left' or 'right'") + end + end, + + ["SetIgnoreVisible"] = function(self, shown) + if shown then + self.ignoreCheckBox.frame:Show() + else + self.ignoreCheckBox.frame:Hide() + end + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local borderColor = TSM.db.profile.frameBackdropColor + local name = "TSMGroupItemList" .. AceGUI:GetNextWidgetNum(Type) + local frame = CreateFrame("Frame", name, UIParent) + frame:Hide() + + local leftFrame = CreateFrame("Frame", name.."LeftFrame", frame) + leftFrame:SetPoint("TOPLEFT", 0, -80) + leftFrame:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -7, 0) + TSMAPI.Design:SetContentColor(leftFrame) + leftFrame.list = {} + frame.leftFrame = leftFrame + + local leftTitle = frame:CreateFontString(nil, "OVERLAY") + leftTitle:SetFont(TSMAPI.Design:GetContentFont("normal")) + TSMAPI.Design:SetTitleTextColor(leftTitle) + leftTitle:SetJustifyH("LEFT") + leftTitle:SetJustifyV("BOTTOM") + leftTitle:SetHeight(15) + leftTitle:SetPoint("BOTTOMLEFT", leftFrame, "TOPLEFT", 8, 0) + leftTitle:SetPoint("BOTTOMRIGHT", leftFrame, "TOPRIGHT", -8, 0) + frame.leftTitle = leftTitle + + local leftSF = CreateFrame("ScrollFrame", name.."LeftFrameScrollFrame", leftFrame, "FauxScrollFrameTemplate") + leftSF:SetPoint("TOPLEFT", 5, -5) + leftSF:SetPoint("BOTTOMRIGHT", -5, 5) + leftSF:SetScript("OnVerticalScroll", function(self, offset) + FauxScrollFrame_OnVerticalScroll(self, offset, ROW_HEIGHT, function() UpdateScrollFrame(self) end) + end) + leftFrame.scrollFrame = leftSF + + local leftScrollBar = _G[leftSF:GetName().."ScrollBar"] + leftScrollBar:ClearAllPoints() + leftScrollBar:SetPoint("BOTTOMRIGHT") + leftScrollBar:SetPoint("TOPRIGHT") + leftScrollBar:SetWidth(12) + + local thumbTex = leftScrollBar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetFrameColor(thumbTex) + thumbTex:SetHeight(150) + thumbTex:SetWidth(leftScrollBar:GetWidth()) + _G[leftScrollBar:GetName().."ScrollUpButton"]:Hide() + _G[leftScrollBar:GetName().."ScrollDownButton"]:Hide() + + local rightFrame = CreateFrame("Frame", name.."RightFrame", frame) + rightFrame:SetPoint("TOPLEFT", frame, "TOP", 7, -80) + rightFrame:SetPoint("BOTTOMRIGHT", 0, 0) + TSMAPI.Design:SetContentColor(rightFrame) + rightFrame.list = {} + frame.rightFrame = rightFrame + + local rightTitle = frame:CreateFontString(nil, "OVERLAY") + rightTitle:SetFont(TSMAPI.Design:GetContentFont("normal")) + TSMAPI.Design:SetTitleTextColor(rightTitle) + rightTitle:SetJustifyH("LEFT") + rightTitle:SetJustifyV("BOTTOM") + rightTitle:SetHeight(15) + rightTitle:SetPoint("BOTTOMLEFT", rightFrame, "TOPLEFT", 8, 0) + rightTitle:SetPoint("BOTTOMRIGHT", rightFrame, "TOPRIGHT", -8, 0) + frame.rightTitle = rightTitle + + local rightSF = CreateFrame("ScrollFrame", name.."RightFrameScrollFrame", rightFrame, "FauxScrollFrameTemplate") + rightSF:SetPoint("TOPLEFT", 5, -5) + rightSF:SetPoint("BOTTOMRIGHT", -5, 5) + rightSF:SetScript("OnVerticalScroll", function(self, offset) + FauxScrollFrame_OnVerticalScroll(self, offset, ROW_HEIGHT, function() UpdateScrollFrame(self) end) + end) + rightFrame.scrollFrame = rightSF + + local rightScrollBar = _G[rightSF:GetName().."ScrollBar"] + rightScrollBar:ClearAllPoints() + rightScrollBar:SetPoint("BOTTOMRIGHT") + rightScrollBar:SetPoint("TOPRIGHT") + rightScrollBar:SetWidth(12) + + local thumbTex = rightScrollBar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetFrameColor(thumbTex) + thumbTex:SetHeight(150) + thumbTex:SetWidth(rightScrollBar:GetWidth()) + _G[rightScrollBar:GetName().."ScrollUpButton"]:Hide() + _G[rightScrollBar:GetName().."ScrollDownButton"]:Hide() + + + + local label = TSMAPI.GUI:CreateLabel(frame, "normal") + label:SetText("Filter:") + label:SetPoint("TOPLEFT", 0, -5) + label:SetHeight(20) + label:SetJustifyV("CENTER") + + local filter = TSMAPI.GUI:CreateInputBox(frame) + filter:SetPoint("BOTTOMLEFT", label, "BOTTOMRIGHT", 2, 0) + filter:SetHeight(20) + filter:SetWidth(150) + filter:SetScript("OnEnterPressed", OnFilterSet) + filter.tooltip = L["All items with names containing the specified filter will be selected. This makes it easier to add/remove multiple items at a time."] + + local line = TSMAPI.GUI:CreateHorizontalLine(frame, 0) + line:SetPoint("TOPLEFT", 0, -58) + line:SetPoint("TOPRIGHT", 0, -58) + local line = TSMAPI.GUI:CreateVerticalLine(frame, 0) + line:ClearAllPoints() + line:SetPoint("TOP", 0, -60) + line:SetPoint("BOTTOM") + + local ignoreCheckBox = TSMAPI.GUI:CreateCheckBox(frame, L["When checked, random enchants will be ignored for ungrouped items.\n\nNB: This will not affect parent group items that were already added with random enchants\n\nIf you have this checked when adding an ungrouped randomly enchanted item, it will act as all possible random enchants of that item."]) + ignoreCheckBox:SetLabel(L["Ignore Random Enchants on Ungrouped Items"]) + ignoreCheckBox:SetPoint("BOTTOMLEFT", filter, "BOTTOMRIGHT", 20, 5) + ignoreCheckBox:SetPoint("TOPRIGHT", 0, -2) + ignoreCheckBox:SetCallback("OnValueChanged", OnIgnoreChanged) + + local addBtn = TSMAPI.GUI:CreateButton(frame, 18) + addBtn:SetPoint("TOPLEFT", 0, -33) + addBtn:SetWidth(170) + addBtn:SetHeight(20) + addBtn:SetText(L["Add >>>"]) + addBtn.type = "Add" + addBtn:SetScript("OnClick", OnButtonClick) + + local removeBtn = TSMAPI.GUI:CreateButton(frame, 18) + removeBtn:SetPoint("TOPRIGHT", 0, -33) + removeBtn:SetWidth(170) + removeBtn:SetHeight(20) + removeBtn:SetText(L["<<< Remove"]) + removeBtn.type = "Remove" + removeBtn:SetScript("OnClick", OnButtonClick) + removeBtn.tooltip = L["You can hold shift while clicking this button to remove the items from ALL groups rather than keeping them in the parent group (if one exists)."] + + local clearBtn = TSMAPI.GUI:CreateButton(frame, 16) + clearBtn:SetPoint("BOTTOMLEFT", addBtn, "BOTTOMRIGHT", 15, 0) + clearBtn:SetPoint("BOTTOMRIGHT", removeBtn, "BOTTOMLEFT", -15, 0) + clearBtn:SetHeight(20) + clearBtn:SetText(L["Clear Selection"]) + clearBtn:SetScript("OnClick", OnClearButtonClicked) + clearBtn.tooltip = L["Deselects all items in both columns."] + + + local widget = { + leftFrame = leftFrame, + leftScrollFrame = leftSF, + rightFrame = rightFrame, + rightScrollFrame = rightSF, + ignoreCheckBox = ignoreCheckBox, + filter = filter, + clearBtn = clearBtn, + frame = frame, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + + addBtn.obj = widget + removeBtn.obj = widget + widget.ignoreCheckBox.obj = widget + widget.filter.obj = widget + widget.clearBtn.obj = widget + widget.leftFrame.obj = widget + widget.rightFrame.obj = widget + widget.frame.obj = widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua new file mode 100644 index 0000000..ea3a8cf --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMImage.lua @@ -0,0 +1,71 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +local TSM = select(2, ...) +local Type, Version = "TSMImage", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + self:SetSizeRatio(0.5) + self:SetWidth(100) + self:SetText() + self:SetImage() + end, + + ["OnWidthSet"] = function(self, w) + self:SetHeight(w*self.ratio) + end, + + ["SetSizeRatio"] = function(self, ratio) + self.ratio = ratio + end, + + ["SetImage"] = function(self, image) + self.image:SetTexture(image) + end, + + ["SetText"] = function(self, text) + self.text:SetText(text) + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local frame = CreateFrame("Frame", nil, UIParent) + local image = frame:CreateTexture(nil, "ARTWORK") + image:SetAllPoints() + local text = frame:CreateFontString() + text:SetFont(TSMAPI.Design:GetContentFont("normal")) + text:SetTextColor(1, 1, 1, 1) + text:SetPoint("BOTTOMRIGHT", -2, 2) + + local widget = { + frame = frame, + image = image, + text = text, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMInlineGroup.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMInlineGroup.lua new file mode 100644 index 0000000..879259e --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMInlineGroup.lua @@ -0,0 +1,68 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-InlineGroup.lua +-- This InlineGroup container is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local AceGUI = LibStub("AceGUI-3.0") +local Type, Version = "TSMInlineGroup", 2 +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +local function Constructor() + local container = AceGUI:Create("InlineGroup") + container.type = Type + container.Add = TSMAPI.AddGUIElement + + container.bgFrame = container.content:GetParent() + container.border = container.content:GetParent() + container.content:SetParent(container.frame) + + local title = container.frame:CreateFontString(nil, "BACKGROUND") + title:SetPoint("TOPLEFT", 10, 0) + title:SetPoint("TOPRIGHT", -14, 0) + title:SetJustifyH("LEFT") + title:SetJustifyV("BOTTOM") + title:SetFont(TSMAPI.Design:GetBoldFont(), 18) + TSMAPI.Design:SetTitleTextColor(title) + container.titletext = title + + container.HideTitle = function(self, hideTitle) + local frame = self.content:GetParent() + frame:ClearAllPoints() + if hideTitle then + self:SetTitle() + frame:SetPoint("TOPLEFT", 0, 0) + frame:SetPoint("BOTTOMRIGHT", -1, 3) + else + frame:SetPoint("TOPLEFT", 0, -17) + frame:SetPoint("BOTTOMRIGHT", -1, 3) + end + end + + container.HideBorder = function(self, hideBorder) + if hideBorder then + self.border:Hide() + else + self.border:Show() + end + end + + container.SetBackdrop = function(self, backdrop) + if backdrop then + TSMAPI.Design:SetContentColor(self.bgFrame) + else + TSMAPI.Design:SetFrameColor(self.bgFrame) + self.bgFrame:SetBackdropColor(0, 0, 0, 0) + end + end + + AceGUI:RegisterAsContainer(container) + return container +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMInteractiveLabel.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMInteractiveLabel.lua new file mode 100644 index 0000000..7580e60 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMInteractiveLabel.lua @@ -0,0 +1,113 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Interactive.lua +-- This InteractiveLabel widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMInteractiveLabel", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + + +-- Lua APIs +local select, pairs = select, pairs + +-- WoW APIs +local CreateFrame, UIParent = CreateFrame, UIParent + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: GameFontHighlightSmall + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + +local function Label_OnClick(frame, button) + frame.obj:Fire("OnClick", button) + AceGUI:ClearFocus() +end + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + self:LabelOnAcquire() + self:SetHighlight() + self:SetHighlightTexCoord() + self:SetDisabled(false) + end, + + ["SetHighlight"] = function(self, ...) + self.highlight:SetTexture(...) + end, + + ["SetHighlightTexCoord"] = function(self, ...) + local c = select("#", ...) + if c == 4 or c == 8 then + self.highlight:SetTexCoord(...) + else + self.highlight:SetTexCoord(0, 1, 0, 1) + end + end, + + ["SetDisabled"] = function(self,disabled) + self.disabled = disabled + if disabled then + self.frame:EnableMouse(false) + self.label:SetTextColor(0.5, 0.5, 0.5) + else + self.frame:EnableMouse(true) + self.label:SetTextColor(1, 1, 1) + end + end +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + -- create a Label type that we will hijack + local label = AceGUI:Create("TSMLabel") + + local frame = label.frame + frame:EnableMouse(true) + frame:SetScript("OnEnter", Control_OnEnter) + frame:SetScript("OnLeave", Control_OnLeave) + frame:SetScript("OnMouseDown", Label_OnClick) + + local bg = frame:CreateTexture(nil, "BACKGROUND") + bg:SetPoint("TOPLEFT", -2, 2) + bg:SetPoint("BOTTOMRIGHT", label.label, 2, -2) + TSMAPI.Design:SetContentColor(bg) + + local highlight = frame:CreateTexture(nil, "HIGHLIGHT") + highlight:SetTexture(nil) + highlight:SetAllPoints() + highlight:SetBlendMode("ADD") + + label.highlight = highlight + label.type = Type + label.LabelOnAcquire = label.OnAcquire + for method, func in pairs(methods) do + label[method] = func + end + + return label +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua new file mode 100644 index 0000000..fb0c56a --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMLabel.lua @@ -0,0 +1,107 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Label.lua +-- This Label widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMLabel", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local max, select, pairs = math.max, select, pairs + +-- WoW APIs +local CreateFrame, UIParent = CreateFrame, UIParent + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function UpdateLabelAnchor(self) + if self.resizing then return end + local frame = self.frame + local width = frame.width or frame:GetWidth() or 0 + local label = self.label + local height + + label:ClearAllPoints() + label:SetPoint("TOPLEFT") + label:SetWidth(min(label:GetStringWidth(), width)) + height = label:GetHeight() + + self.resizing = true + frame:SetHeight(height) + frame.height = height + self.resizing = nil +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + -- set the flag to stop constant size updates + self.resizing = true + -- height is set dynamically by the text and image size + self:SetWidth(200) + self:SetText() + self:SetColor() + + -- reset the flag + self.resizing = nil + -- run the update explicitly + UpdateLabelAnchor(self) + end, + + ["OnWidthSet"] = function(self, width) + UpdateLabelAnchor(self) + end, + + ["SetText"] = function(self, text) + self.label:SetText(text) + UpdateLabelAnchor(self) + end, + + ["SetColor"] = function(self, r, g, b) + if not (r and b and g) then + TSMAPI.Design:SetWidgetLabelColor(self.label) + else + self.label:SetTextColor(r, g, b) + end + end, +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] +local function Constructor() + local frame = CreateFrame("Frame", nil, UIParent) + frame:Hide() + + local label = frame:CreateFontString(nil, "BACKGROUND") + label:SetJustifyH("LEFT") + label:SetJustifyV("TOP") + label:SetFont(TSMAPI.Design:GetContentFont("normal")) + + -- create widget + local widget = { + label = label, + frame = frame, + type = Type, + } + for method, func in pairs(methods) do + widget[method] = func + end + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMMainFrame.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMMainFrame.lua new file mode 100644 index 0000000..7ad5054 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMMainFrame.lua @@ -0,0 +1,388 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-Frame.lua +-- This Frame container is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMMainFrame", 2 +local AceGUI = LibStub("AceGUI-3.0") +local L = LibStub("AceLocale-3.0"):GetLocale("TradeSkillMaster") -- loads the localization table +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +local ICON_TEXT_COLOR = {165/255, 168/255, 188/255, .7} + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +local function Frame_OnClose(frame) + frame.obj:Fire("OnClose") +end + +local function CloseButton_OnClick(frame) + PlaySound("gsTitleOptionExit") + frame.obj:Hide() +end + +local function Frame_OnMouseDown(frame) + frame.toMove:GetScript("OnMouseDown")(frame.toMove) + AceGUI:ClearFocus() +end + +local function Frame_OnMouseUp(frame) + frame.toMove:GetScript("OnMouseUp")(frame.toMove) + AceGUI:ClearFocus() +end + +local function Sizer_OnMouseUp(mover) + local frame = mover:GetParent() + frame:StopMovingOrSizing() + frame:SavePositionAndSize() + local self = frame.obj + local status = self.status or self.localstatus + status.width = frame:GetWidth() + status.height = frame:GetHeight() + status.top = frame:GetTop() + status.left = frame:GetLeft() +end + +local function Sizer_OnMouseDown(frame) + frame:GetParent():StartSizing("BOTTOMRIGHT") + AceGUI:ClearFocus() +end + +local function Icon_OnEnter(btn) + btn.dark:Hide() + GameTooltip:SetOwner(btn, btn:GetParent().tooltipAnchor) + GameTooltip:SetText(btn.title) + GameTooltip:Show() +end + +local function Icon_OnLeave(btn) + if btn.obj.selected ~= btn then + btn.dark:Show() + end + GameTooltip:Hide() +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + self.frame:RefreshPosition() + self.frame:SetFrameStrata("MEDIUM") + self:SetTitle() + self:ApplyStatus() + self:Show() + end, + + ["OnRelease"] = function(self) + self.status = nil + wipe(self.localstatus) + end, + + ["LayoutIcons"] = function(self) + for _, container in ipairs({self.topLeftIcons, self.topRightIcons}) do + if type(container.icons) == "table" and container.icons[1] then + local numIcons = #container.icons + local iconSize = container.icons[1]:GetHeight() + local spacing = (container:GetWidth() - numIcons * iconSize) / (numIcons + 1) + local width = iconSize + if spacing < 1 then + spacing = 1 + width = (container:GetWidth() - (numIcons - 1) * spacing) / numIcons + end + for i, icon in ipairs(container.icons) do + icon:SetPoint("TOPLEFT", spacing+(i-1)*(width+spacing), 0) + icon:SetWidth(width) + end + end + end + end, + + ["OnWidthSet"] = function(self, width) + self.content.width = self.content:GetWidth() + + self.topLeftIcons:ClearAllPoints() + self.topLeftIcons:SetPoint("TOPLEFT", 5, 20) + self.topLeftIcons:SetPoint("TOPRIGHT", self.frame, "TOP", -115, 20) + self.topLeftIcons:SetHeight(51) + + self.topRightIcons:ClearAllPoints() + self.topRightIcons:SetPoint("TOPRIGHT", -5, 20) + self.topRightIcons:SetPoint("TOPLEFT", self.frame, "TOP", 115, 20) + self.topRightIcons:SetHeight(51) + + self:LayoutIcons() + end, + + ["OnHeightSet"] = function(self, height) + self.content.height = self.content:GetHeight() + end, + + ["SetTitle"] = function(self, title) + self.titletext:SetText(title) + end, + + ["SetIconText"] = function(self, title) + self.icontext:SetText(title) + end, + + ["SetIconLabels"] = function(self, topLeft, topRight) + self.topLeftIcons.label = topLeft + self.topRightIcons.label = topRight + end, + + ["Hide"] = function(self) + self.frame:Hide() + end, + + ["Show"] = function(self) + self.frame:Show() + end, + + ["UpdateSelected"] = function(self) + for _, container in ipairs({self.topLeftIcons, self.topRightIcons}) do + if type(container.icons) == "table" then + for _, icon in ipairs(container.icons) do + icon.dark:Show() + end + end + end + self.selected.dark:Hide() + end, + + ["AddIcon"] = function(self, info) + local container = self[info.where.."Icons"] + assert(container, "Invalid icon container.") + + local size = 51 + + local btn = CreateFrame("Button", nil, container) + btn:SetBackdrop({edgeFile="Interface\\Buttons\\WHITE8X8", edgeSize=2}) + btn:SetBackdropBorderColor(0, 0, 0, 0.5) + btn:SetHeight(size) + btn:SetWidth(size) + btn.title = info.name + btn.info = info + btn.obj = self + info.frame = btn + + local image = btn:CreateTexture(nil, "BACKGROUND") + image:SetAllPoints() + image:SetTexture(info.texture) + image:SetTexCoord(0.08, 0.922, 0.09, 0.918) + image:SetVertexColor(1, 1, 1) + btn.image = image + + local dark = btn:CreateTexture(nil, "OVERLAY") + dark:SetAllPoints(image) + dark:SetTexture(0, 0, 0, .3) + dark:SetBlendMode("BLEND") + btn.dark = dark + btn:SetScript("OnEnter", Icon_OnEnter) + btn:SetScript("OnLeave", Icon_OnLeave) + btn:SetScript("OnClick", function(btn) + if #self.children > 0 then + self:ReleaseChildren() + end + self:SetTitle(btn.title) + btn.info.loadGUI(self) + self.selected = btn + self:UpdateSelected() + end) + + local highlight = btn:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints(image) + highlight:SetTexture(1, 1, 1, .2) + highlight:SetBlendMode("ADD") + btn.highlight = highlight + + container.icons = container.icons or {} + tinsert(container.icons, btn) + + self:LayoutIcons() + + if not container.textLabel then + local label = container:CreateFontString() + label:SetHeight(12) + label:SetJustifyH("CENTER") + label:SetJustifyV("CENTER") + label:SetFont(TSMAPI.Design:GetContentFont("small")) + TSMAPI.Design:SetIconRegionColor(label) + label:SetText(container.label) + label:SetPoint("TOP", 0, -53) + container.tooltipAnchor = "ANCHOR_TOP" + container.textLabel = label + + -- make the lines that extend the width of the container out from the label + local leftHLine = container:CreateTexture() + leftHLine:SetPoint("TOPRIGHT", label, "TOPLEFT", -2, -6) + leftHLine:SetHeight(1) + TSMAPI.Design:SetIconRegionColor(leftHLine) + local rightHLine = container:CreateTexture() + rightHLine:SetPoint("TOPLEFT", label, "TOPRIGHT", 2, -6) + rightHLine:SetHeight(1) + TSMAPI.Design:SetIconRegionColor(rightHLine) + leftHLine:SetPoint("TOPLEFT", 20, -59) + rightHLine:SetPoint("TOPRIGHT", -20, -59) + end + end, + + -- called to set an external table to store status in + ["SetStatusTable"] = function(self, status) + assert(type(status) == "table") + self.status = status + self:ApplyStatus() + end, + + ["ApplyStatus"] = function(self) + local status = self.status or self.localstatus + local frame = self.frame + self:SetWidth(status.width or self.frame:GetWidth()) + self:SetHeight(status.height or self.frame:GetHeight()) + frame:ClearAllPoints() + if status.top and status.left then + frame:SetPoint("TOP", UIParent, "BOTTOM", 0, status.top) + frame:SetPoint("LEFT", UIParent, "LEFT", status.left, 0) + else + frame:SetPoint("CENTER") + end + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] +local function Constructor() + local frameName = Type..AceGUI:GetNextWidgetNum(Type) + + local frameDefaults = { + x = UIParent:GetWidth()/2, + y = UIParent:GetHeight()/2, + width = 823, + height = 686, + scale = 1, + } + local frame = TSMAPI:CreateMovableFrame(frameName, frameDefaults) + frame:SetFrameStrata("MEDIUM") + TSMAPI.Design:SetFrameBackdropColor(frame) + frame:SetResizable(true) + frame:SetMinResize(600, 400) + frame:SetScript("OnHide", Frame_OnClose) + frame.toMove = frame + tinsert(UISpecialFrames, frameName) + + local closebutton = CreateFrame("Button", nil, frame) + TSMAPI.Design:SetContentColor(closebutton) + local highlight = closebutton:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + highlight:SetBlendMode("BLEND") + closebutton.highlight = highlight + closebutton:SetPoint("BOTTOMRIGHT", -29, -14) + closebutton:SetHeight(29) + closebutton:SetWidth(86) + closebutton:SetScript("OnClick", CloseButton_OnClick) + closebutton:Show() + local label = closebutton:CreateFontString() + label:SetPoint("TOP") + label:SetJustifyH("CENTER") + label:SetJustifyV("CENTER") + label:SetHeight(28) + label:SetFont(TSMAPI.Design:GetContentFont(), 28) + TSMAPI.Design:SetWidgetTextColor(label) + label:SetText(CLOSE) + closebutton:SetFontString(label) + + local iconBtn = CreateFrame("Button", nil, frame) + iconBtn:SetWidth(286) + iconBtn:SetHeight(286) + iconBtn:SetPoint("TOP", 0, 174) + iconBtn:SetScript("OnMouseDown", Frame_OnMouseDown) + iconBtn:SetScript("OnMouseUp", Frame_OnMouseUp) + iconBtn.toMove = frame + local icon = iconBtn:CreateTexture() + icon:SetAllPoints() + icon:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\TSM_Icon_Pocket") + frame.icon = icon + + local sizer = CreateFrame("Frame", nil, frame) + sizer:SetPoint("BOTTOMRIGHT", -2, 2) + sizer:SetWidth(20) + sizer:SetHeight(20) + sizer:EnableMouse() + sizer:SetScript("OnMouseDown",Sizer_OnMouseDown) + sizer:SetScript("OnMouseUp", Sizer_OnMouseUp) + local image = sizer:CreateTexture(nil, "BACKGROUND") + image:SetAllPoints() + image:SetTexture("Interface\\Addons\\TradeSkillMaster\\Media\\Sizer") + + local content = CreateFrame("Frame", nil, frame) + content:SetPoint("TOPLEFT", 11, -62) + content:SetPoint("BOTTOMRIGHT", -11, 20) + + local titletext = frame:CreateFontString() + titletext:SetPoint("TOP", 0, -32) + titletext:SetHeight(22) + titletext:SetJustifyH("CENTER") + titletext:SetJustifyV("CENTER") + titletext:SetFont(TSMAPI.Design:GetContentFont(), 22) + TSMAPI.Design:SetTitleTextColor(titletext) + + local icontext = iconBtn:CreateFontString(nil, "OVERLAY") + icontext:SetPoint("TOP", frame, "TOP", 0, 14) + icontext:SetHeight(29) + icontext:SetJustifyH("CENTER") + icontext:SetJustifyV("CENTER") + icontext:SetFont(TSMAPI.Design:GetContentFont(), 27) + icontext:SetTextColor(unpack(ICON_TEXT_COLOR)) + + -- local helpButton = CreateFrame("Button", nil, frame, "MainHelpPlateButton") + local helpButton = CreateFrame("Button", nil, frame, "UIPanelButtonTemplate") + helpButton:SetPoint("BOTTOMLEFT", -10, -30) + helpButton:SetScript("OnEnter", function(self) + HelpPlateTooltip.ArrowRIGHT:Show() + HelpPlateTooltip.ArrowGlowRIGHT:Show() + HelpPlateTooltip:SetPoint("LEFT", self, "RIGHT", 10, 0) + HelpPlateTooltip.Text:SetText(L["Click this to open TSM Assistant."]) + HelpPlateTooltip:Show() + end) + helpButton:SetScript("OnLeave", function(self) + HelpPlateTooltip.ArrowRIGHT:Hide() + HelpPlateTooltip.ArrowGlowRIGHT:Hide() + HelpPlateTooltip:ClearAllPoints() + HelpPlateTooltip:Hide() + end) + helpButton:SetScript("OnClick", TSM.Assistant.Open) + + local widget = { + type = Type, + localstatus = {}, + frame = frame, + -- container for children + content = content, + -- changable labels + titletext = titletext, + icontext = icontext, + -- containers for the icons - size/pos set by OnWidthSet + topLeftIcons = CreateFrame("Frame", nil, frame), + topRightIcons = CreateFrame("Frame", nil, frame), + } + for method, func in pairs(methods) do + widget[method] = func + end + closebutton.obj = widget + + return AceGUI:RegisterAsContainer(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMMultiLabel.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMMultiLabel.lua new file mode 100644 index 0000000..a940e41 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMMultiLabel.lua @@ -0,0 +1,85 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-MultiLabel.lua +-- This MultiLabel widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMMultiLabel", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + -- height is set dynamically by the text size + self:SetWidth(200) + for i=1, #self.labels do + self.labels[i]:SetText() + end + end, + + ["OnWidthSet"] = function(self, width) + self:SetLabels(self.info) + end, + + ["SetLabels"] = function(self, info) + self.info = info + local totalWidth = self.frame:GetWidth() or 0 + local usedWidth = 0 + local maxHeight = 0 + for i=1, #info do + if not self.labels[i] then + self.labels[i] = self.frame:CreateFontString(nil, "BACKGROUND") + self.labels[i]:SetFont(TSMAPI.Design:GetContentFont("normal")) + TSMAPI.Design:SetWidgetLabelColor(self.labels[i]) + self.labels[i]:SetJustifyH("LEFT") + self.labels[i]:SetJustifyV("TOP") + end + self.labels[i]:SetText(info[i].text) + self.labels[i]:SetPoint("TOPLEFT", self.frame, "TOPLEFT", usedWidth, 0) + + local labelWidth = totalWidth*(info[i].relativeWidth or 0) + labelWidth = min(labelWidth, totalWidth-usedWidth) + self.labels[i]:SetWidth(labelWidth) + usedWidth = usedWidth + labelWidth + + if self.labels[i]:GetHeight() > maxHeight then + maxHeight = self.labels[i]:GetHeight() + end + end + self.frame:SetHeight(maxHeight) + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local frame = CreateFrame("Frame", nil, UIParent) + frame:Hide() + + local widget = { + labels = {}, + info = {}, + frame = frame, + type = Type, + } + for method, func in pairs(methods) do + widget[method] = func + end + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMMultiLineEditBox.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMMultiLineEditBox.lua new file mode 100644 index 0000000..2a8cbff --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMMultiLineEditBox.lua @@ -0,0 +1,21 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-TSMMultiLineEditBox.lua +-- This TSMMultiLineEditBox widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMMultiLineEditBox", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +local function Constructor() + local widget = AceGUI:Create("MultiLineEditBox") + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMScrollFrame.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMScrollFrame.lua new file mode 100644 index 0000000..f9ce117 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMScrollFrame.lua @@ -0,0 +1,223 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-ScrollFrame.lua +-- This ScrollFrame container is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMScrollFrame", 2 +local AceGUI = LibStub("AceGUI-3.0") +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local pairs, assert, type = pairs, assert, type +local min, max, floor, abs = math.min, math.max, math.floor, math.abs + +-- WoW APIs +local CreateFrame, UIParent = CreateFrame, UIParent + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] +local function FixScrollOnUpdate(frame) + frame:SetScript("OnUpdate", nil) + frame.obj:FixScroll() +end + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] +local function ScrollFrame_OnMouseWheel(frame, value) + frame.obj:MoveScroll(value) +end + +local function ScrollFrame_OnSizeChanged(frame) + frame:SetScript("OnUpdate", FixScrollOnUpdate) +end + +local function ScrollBar_OnScrollValueChanged(frame, value) + frame.obj:SetScroll(value) +end + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] +local methods = { + ["OnAcquire"] = function(self) + self:SetScroll(0) + self:FixScroll() + end, + + ["OnRelease"] = function(self) + self.status = nil + for k in pairs(self.localstatus) do + self.localstatus[k] = nil + end + self.scrollframe:SetPoint("BOTTOMRIGHT") + self.scrollbar:Hide() + self.scrollBarShown = nil + self.content.height, self.content.width = nil, nil + end, + + ["SetScroll"] = function(self, value) + local status = self.status or self.localstatus + local viewheight = self.scrollframe:GetHeight() + local height = self.content:GetHeight() + local offset + + if viewheight > height then + offset = 0 + else + offset = floor((height - viewheight) / 1000.0 * value) + end + self.content:ClearAllPoints() + self.content:SetPoint("TOPLEFT", 0, offset) + self.content:SetPoint("TOPRIGHT", 0, offset) + status.offset = offset + status.scrollvalue = value + end, + + ["MoveScroll"] = function(self, value) + local status = self.status or self.localstatus + local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight() + + if self.scrollBarShown then + local diff = height - viewheight + local delta = 1 + if value < 0 then + delta = -1 + end + self.scrollbar:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000)) + end + end, + + ["FixScroll"] = function(self) + if self.updateLock then return end + self.updateLock = true + local status = self.status or self.localstatus + local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight() + local offset = status.offset or 0 + local curvalue = self.scrollbar:GetValue() + -- Give us a margin of error of 2 pixels to stop some conditions that i would blame on floating point inaccuracys + -- No-one is going to miss 2 pixels at the bottom of the frame, anyhow! + if viewheight < height + 2 then + if self.scrollBarShown then + self.scrollBarShown = nil + self.scrollbar:Hide() + self.scrollbar:SetValue(0) + self.scrollframe:SetPoint("BOTTOMRIGHT") + self:DoLayout() + end + else + if not self.scrollBarShown then + self.scrollBarShown = true + self.scrollbar:Show() + self.scrollframe:SetPoint("BOTTOMRIGHT", -20, 0) + self:DoLayout() + end + local value = (offset / (viewheight - height) * 1000) + if value > 1000 then value = 1000 end + self.scrollbar:SetValue(value) + self:SetScroll(value) + if value < 1000 then + self.content:ClearAllPoints() + self.content:SetPoint("TOPLEFT", 0, offset) + self.content:SetPoint("TOPRIGHT", 0, offset) + status.offset = offset + end + end + self.updateLock = nil + end, + + ["LayoutFinished"] = function(self, width, height) + self.content:SetHeight(height or (0 + 20)) + self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate) + + -- no idea why, but this MUST be here to avoid glitches with the scrollbar not showing up when it should + self.content:GetHeight() + end, + + ["SetStatusTable"] = function(self, status) + assert(type(status) == "table") + self.status = status + if not status.scrollvalue then + status.scrollvalue = 0 + end + end, + + ["OnWidthSet"] = function(self, width) + local content = self.content + content.width = width + end, + + ["OnHeightSet"] = function(self, height) + local content = self.content + content.height = height + end +} +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] +local function Constructor() + local frame = CreateFrame("Frame", nil, UIParent) + local num = AceGUI:GetNextWidgetNum(Type) + + local scrollframe = CreateFrame("ScrollFrame", nil, frame) + scrollframe:SetPoint("TOPLEFT") + scrollframe:SetPoint("BOTTOMRIGHT") + scrollframe:EnableMouseWheel(true) + scrollframe:SetScript("OnMouseWheel", ScrollFrame_OnMouseWheel) + scrollframe:SetScript("OnSizeChanged", ScrollFrame_OnSizeChanged) + + local scrollbar = CreateFrame("Slider", ("TSMScrollFrame%dScrollBar"):format(num), scrollframe, "UIPanelScrollBarTemplate") + scrollbar:SetPoint("TOPLEFT", scrollframe, "TOPRIGHT", 6, -4) + scrollbar:SetPoint("BOTTOMLEFT", scrollframe, "BOTTOMRIGHT", 6, 4) + scrollbar:SetMinMaxValues(0, 1000) + scrollbar:SetValueStep(1) + scrollbar:SetValue(0) + scrollbar:SetWidth(12) + scrollbar:Hide() + scrollbar:SetScript("OnValueChanged", ScrollBar_OnScrollValueChanged) + + local thumbTex = scrollbar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetContentColor(thumbTex) + thumbTex:SetHeight(150) + thumbTex:SetWidth(scrollbar:GetWidth()) + + local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND") + scrollbg:SetAllPoints(scrollbar) + TSMAPI.Design:SetFrameColor(scrollbg) + + _G[scrollbar:GetName().."ScrollUpButton"]:Hide() + _G[scrollbar:GetName().."ScrollDownButton"]:Hide() + + --Container Support + local content = CreateFrame("Frame", nil, scrollframe) + content:SetPoint("TOPLEFT") + content:SetPoint("TOPRIGHT") + content:SetHeight(400) + scrollframe:SetScrollChild(content) + + local widget = { + localstatus = { scrollvalue = 0 }, + scrollframe = scrollframe, + scrollbar = scrollbar, + content = content, + frame = frame, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + scrollframe.obj, scrollbar.obj = widget, widget + widget.Add = TSMAPI.AddGUIElement + + return AceGUI:RegisterAsContainer(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMSimpleGroup.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMSimpleGroup.lua new file mode 100644 index 0000000..83750fb --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMSimpleGroup.lua @@ -0,0 +1,21 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This SimpleGroup container is modified to fit TSM's theme / needs +local Type, Version = "TSMSimpleGroup", 2 +local AceGUI = LibStub("AceGUI-3.0") +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +local function Constructor() + local container = AceGUI:Create("SimpleGroup") + container.type = Type + container.Add = TSMAPI.AddGUIElement + return AceGUI:RegisterAsContainer(container) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua new file mode 100644 index 0000000..6b0e65b --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMSlider.lua @@ -0,0 +1,279 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Slider.lua +-- This Slider widget is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMSlider", 2 +local AceGUI = LibStub and LibStub("AceGUI-3.0", true) +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local min, max, floor = math.min, math.max, math.floor +local tonumber, pairs = tonumber, pairs + +-- WoW APIs +local PlaySound = PlaySound +local CreateFrame, UIParent = CreateFrame, UIParent + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function UpdateText(self) + local value = self.value or 0 + if self.ispercent then + self.editbox:SetText(("%s%%"):format(floor(value * 1000 + 0.5) / 10)) + else + self.editbox:SetText(floor(value * 100 + 0.5) / 100) + end +end + +local function UpdateLabels(self) + local min, max = (self.min or 0), (self.max or 100) + if self.ispercent then + self.lowtext:SetFormattedText("%s%%", (min * 100)) + self.hightext:SetFormattedText("%s%%", (max * 100)) + else + self.lowtext:SetText(min) + self.hightext:SetText(max) + end +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Control_OnEnter(frame) + frame.obj:Fire("OnEnter") +end + +local function Control_OnLeave(frame) + frame.obj:Fire("OnLeave") +end + +local function Frame_OnMouseDown(frame) + frame.obj.slider:EnableMouseWheel(true) + AceGUI:ClearFocus() +end + +local function Slider_OnValueChanged(frame) + local self = frame.obj + if not frame.setup then + local newvalue = frame:GetValue() + -- workaround for 5.4 issues + if self.step and self.step > 0 then + local min_value = self.min or 0 + newvalue = floor((newvalue - min_value) / self.step + 0.5) * self.step + min_value + end + if newvalue ~= self.value and not self.disabled then + self.value = newvalue + self:Fire("OnValueChanged", newvalue) + end + if self.value then + UpdateText(self) + end + end +end + +local function Slider_OnMouseUp(frame, button) + local self = frame.obj + self.slider:EnableMouseWheel(true) + self:Fire("OnMouseUp", self.value) +end + +local function Slider_OnMouseWheel(frame, v) + local self = frame.obj + if not self.disabled then + local value = self.value + if v > 0 then + value = min(value + (self.step or 1), self.max) + else + value = max(value - (self.step or 1), self.min) + end + self.slider:SetValue(value) + end +end + +local function EditBox_OnMouseUp(frame) + local self = frame.obj +end + +local function EditBox_OnEscapePressed(frame) + frame:ClearFocus() +end + +local function EditBox_OnEnterPressed(frame) + local self = frame.obj + local value = frame:GetText() + if self.ispercent then + value = value:gsub('%%', '') + value = tonumber(value) / 100 + else + value = tonumber(value) + end + + if value then + PlaySound("igMainMenuOptionCheckBoxOn") + self.slider:SetValue(value) + self:Fire("OnMouseUp", value) + frame:ClearFocus() + end +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + self:SetWidth(200) + self:SetHeight(44) + self:SetDisabled(false) + self:SetIsPercent(nil) + self:SetSliderValues(0,100,1) + self:SetValue(0) + self.slider:EnableMouseWheel(false) + end, + + ["SetDisabled"] = function(self, disabled) + self.disabled = disabled + TSMAPI.Design:SetWidgetTextColor(self.editbox, disabled) + self.slider:EnableMouse(not disabled) + self.editbox:EnableMouse(not disabled) + TSMAPI.Design:SetWidgetLabelColor(self.label, disabled) + TSMAPI.Design:SetWidgetLabelColor(self.hightext, disabled) + TSMAPI.Design:SetWidgetLabelColor(self.lowtext, disabled) + if disabled then + self.editbox:ClearFocus() + end + end, + + ["SetValue"] = function(self, value) + self.slider.setup = true + self.slider:SetValue(value) + self.value = value + UpdateText(self) + self.slider.setup = nil + end, + + ["GetValue"] = function(self) + return self.value + end, + + ["SetLabel"] = function(self, text) + self.label:SetText(text) + end, + + ["SetSliderValues"] = function(self, min, max, step) + local frame = self.slider + frame.setup = true + self.min = min + self.max = max + self.step = step + frame:SetMinMaxValues(min or 0,max or 100) + UpdateLabels(self) + frame:SetValueStep(step or 1) + if self.value then + frame:SetValue(self.value) + end + frame.setup = nil + end, + + ["SetIsPercent"] = function(self, value) + self.ispercent = value + UpdateLabels(self) + UpdateText(self) + end, +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local frame = CreateFrame("Frame", nil, UIParent) + + frame:EnableMouse(true) + frame:SetScript("OnMouseDown", Frame_OnMouseDown) + frame:SetScript("OnEnter", Control_OnEnter) + frame:SetScript("OnLeave", Control_OnLeave) + + local label = frame:CreateFontString(nil, "OVERLAY") + label:SetPoint("TOPLEFT", 0, -2) + label:SetPoint("TOPRIGHT", 0, -2) + label:SetJustifyH("CENTER") + label:SetHeight(15) + label:SetFont(TSMAPI.Design:GetContentFont("normal")) + + local slider = CreateFrame("Slider", nil, frame) + slider:SetOrientation("HORIZONTAL") + slider:SetHeight(6) + slider:SetHitRectInsets(0, 0, -10, 0) + slider:SetPoint("TOPLEFT", label, "BOTTOMLEFT", 3, -4) + slider:SetPoint("TOPRIGHT", label, "BOTTOMRIGHT", -20, -4) + slider:SetValue(0) + slider:SetScript("OnValueChanged",Slider_OnValueChanged) + slider:SetScript("OnEnter", Control_OnEnter) + slider:SetScript("OnLeave", Control_OnLeave) + slider:SetScript("OnMouseUp", Slider_OnMouseUp) + slider:SetScript("OnMouseWheel", Slider_OnMouseWheel) + TSMAPI.Design:SetFrameColor(slider) + local thumbTex = slider:CreateTexture(nil, "ARTWORK") + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetContentColor(thumbTex) + thumbTex:SetHeight(15) + thumbTex:SetWidth(8) + slider:SetThumbTexture(thumbTex) + + local lowtext = slider:CreateFontString(nil, "ARTWORK") + lowtext:SetFont(TSMAPI.Design:GetContentFont("small")) + lowtext:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 2, -4) + + local hightext = slider:CreateFontString(nil, "ARTWORK") + hightext:SetFont(TSMAPI.Design:GetContentFont("small")) + hightext:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", -2, -4) + + local editbox = CreateFrame("EditBox", nil, frame) + editbox:SetAutoFocus(false) + editbox:SetPoint("TOP", slider, "BOTTOM", 0, -6) + editbox:SetHeight(15) + editbox:SetWidth(70) + editbox:SetJustifyH("CENTER") + editbox:EnableMouse(true) + TSMAPI.Design:SetContentColor(editbox) + editbox:SetScript("OnEnterPressed", EditBox_OnEnterPressed) + editbox:SetScript("OnEscapePressed", EditBox_OnEscapePressed) + editbox:SetScript("OnEnter", Control_OnEnter) + editbox:SetScript("OnLeave", Control_OnLeave) + editbox:SetFont(TSMAPI.Design:GetContentFont("normal")) + editbox:SetShadowColor(0, 0, 0, 0) + + local widget = { + label = label, + slider = slider, + lowtext = lowtext, + hightext = hightext, + editbox = editbox, + alignoffset = 25, + frame = frame, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + slider.obj, editbox.obj, frame.obj = widget, widget, widget + + return AceGUI:RegisterAsWidget(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMTabGroup.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMTabGroup.lua new file mode 100644 index 0000000..790043d --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMTabGroup.lua @@ -0,0 +1,312 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-TabGroup.lua +-- This TabGroup container is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMTabGroup", 2 +local AceGUI = LibStub("AceGUI-3.0") +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function TabResize(tab, padding, width) + local tabName = tab:GetName() + + local sideWidths = 8 + tab:SetWidth(width + padding + sideWidths) +end + +local function UpdateTabLook(frame) + if frame.disabled then + TSMAPI.Design:SetWidgetLabelColor(frame.text, true) + frame:Disable() + frame.text = frame:GetText() + frame.bottom:Hide() + elseif frame.selected then + TSMAPI.Design:SetWidgetLabelColor(frame.text) + frame:Disable() + TSMAPI.Design:SetFrameColor(frame.image) + frame.bottom:Show() + frame:SetHeight(29) + + if GameTooltip:IsOwned(frame) then + GameTooltip:Hide() + end + else + TSMAPI.Design:SetWidgetTextColor(frame.text) + frame:Enable() + TSMAPI.Design:SetContentColor(frame.image) + frame.bottom:Hide() + frame:SetHeight(24) + end +end + +local function Tab_SetText(frame, text) + frame:_SetText(text) + local width = frame.obj.frame.width or frame.obj.frame:GetWidth() or 0 + TabResize(frame, 0, frame:GetFontString():GetStringWidth()) +end + +local function Tab_SetSelected(frame, selected) + frame.selected = selected + UpdateTabLook(frame) +end + +local function Tab_SetDisabled(frame, disabled) + frame.disabled = disabled + UpdateTabLook(frame) +end + +local function BuildTabsOnUpdate(frame) + local self = frame.obj + self:BuildTabs() + frame:SetScript("OnUpdate", nil) +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Tab_OnClick(frame) + if not (frame.selected or frame.disabled) then + PlaySound("igCharacterInfoTab") + frame.obj:SelectTab(frame.value) + end +end + +local function Tab_OnEnter(frame) + local self = frame.obj + self:Fire("OnTabEnter", self.tabs[frame.id].value, frame) +end + +local function Tab_OnLeave(frame) + local self = frame.obj + self:Fire("OnTabLeave", self.tabs[frame.id].value, frame) +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) end, + + ["OnRelease"] = function(self) + self.status = nil + for k in pairs(self.localstatus) do + self.localstatus[k] = nil + end + self.tablist = nil + for _, tab in pairs(self.tabs) do + tab:Hide() + end + end, + + ["CreateTab"] = function(self, id) + local tabname = ("TSMTabGroup%dTab%d"):format(self.num, id) + local tab = CreateFrame("Button", tabname, self.border) + tab.obj = self + tab.id = id + tab:SetHeight(24) + TSMAPI.Design:SetFrameColor(tab) + tab:SetBackdropColor(0, 0, 0, 0) + local image = tab:CreateTexture(nil, "BACKGROUND") + image:SetAllPoints() + TSMAPI.Design:SetContentColor(image) + tab.image = image + local bottom = tab:CreateTexture(nil, "OVERLAY") + bottom:SetHeight(2) + bottom:SetPoint("BOTTOMLEFT", 1, -1) + bottom:SetPoint("BOTTOMRIGHT", -1, -1) + TSMAPI.Design:SetFrameColor(bottom) + tab.bottom = bottom + local highlight = tab:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints() + highlight:SetTexture(1, 1, 1, .2) + highlight:SetBlendMode("BLEND") + tab.highlight = highlight + + tab.text = tab:CreateFontString() + tab.text:SetPoint("LEFT", 3, -1) + tab.text:SetPoint("RIGHT", -3, -1) + tab.text:SetJustifyH("CENTER") + tab.text:SetJustifyV("CENTER") + tab.text:SetFont(TSMAPI.Design:GetContentFont(), 18) + tab:SetFontString(tab.text) + + tab:SetScript("OnClick", Tab_OnClick) + tab:SetScript("OnEnter", Tab_OnEnter) + tab:SetScript("OnLeave", Tab_OnLeave) + + tab._SetText = tab.SetText + tab.SetText = Tab_SetText + tab.SetSelected = Tab_SetSelected + tab.SetDisabled = Tab_SetDisabled + + return tab + end, + + ["SetStatusTable"] = function(self, status) + assert(type(status) == "table") + self.status = status + end, + + ["SelectTab"] = function(self, value) + local status = self.status or self.localstatus + local found + for i, v in ipairs(self.tabs) do + if v.value == value then + v:SetSelected(true) + found = true + else + v:SetSelected(false) + end + end + status.selected = value + if found then + self:Fire("OnGroupSelected", value) + end + end, + + ["SetTabs"] = function(self, tabs) + self.tablist = tabs + self:BuildTabs() + end, + + ["ReloadTab"] = function(self) + local status = self.status or self.localstatus + if status and status.selected then + self:Fire("OnGroupSelected", status.selected) + end + end, + + ["BuildTabs"] = function(self) + local status = self.status or self.localstatus + local tablist = self.tablist + local tabs = self.tabs + + if not tablist then return end + + local numTabs = #tablist + local width = self.frame.width or self.frame:GetWidth() or 0 + + -- Show tabs and set text. + for i, v in ipairs(tablist) do + local tab = tabs[i] + if not tab then + tab = self:CreateTab(i) + tabs[i] = tab + end + + tab:Show() + tab:SetText(v.text) + tab:SetDisabled(v.disabled) + tab.value = v.value + end + + -- hide tabs which aren't in use + for i=numTabs+1, #tabs do + tabs[i]:Hide() + end + + --anchor the rows as defined and resize tabs + for i=1, numTabs do + local tab = tabs[i] + tab:ClearAllPoints() + if i == 1 then + tab:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", 5, -31) + else + tab:SetPoint("BOTTOMLEFT", tabs[i-1], "BOTTOMRIGHT", 4, 0) + end + end + + for i=1, numTabs do + TabResize(tabs[i], 4, tabs[i]:GetFontString():GetStringWidth()) + end + end, + + ["OnWidthSet"] = function(self, width) + local content = self.content + local contentwidth = width - 60 + if contentwidth < 0 then + contentwidth = 0 + end + content:SetWidth(contentwidth) + content.width = contentwidth + self:BuildTabs(self) + self.frame:SetScript("OnUpdate", BuildTabsOnUpdate) + end, + + ["OnHeightSet"] = function(self, height) + local content = self.content + local contentheight = height - 30 + if contentheight < 0 then + contentheight = 0 + end + content:SetHeight(contentheight) + content.height = contentheight + end, + + ["LayoutFinished"] = function(self, width, height) + if self.noAutoHeight then return end + self:SetHeight((height or 0) + 30) + end +} + + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local PaneBackdrop = { + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 16, edgeSize = 16, + insets = { left = 3, right = 3, top = 5, bottom = 3 } +} + +local function Constructor() + local num = AceGUI:GetNextWidgetNum(Type) + local frame = CreateFrame("Frame",nil,UIParent) + frame:SetHeight(100) + frame:SetWidth(100) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + + local border = CreateFrame("Frame", nil, frame) + border:SetPoint("TOPLEFT", 1, -30) + border:SetPoint("BOTTOMRIGHT", -1, 3) + TSMAPI.Design:SetFrameColor(border) + + local content = CreateFrame("Frame", nil, border) + content:SetPoint("TOPLEFT", 8, -8) + content:SetPoint("BOTTOMRIGHT", -8, 8) + + local widget = { + num = num, + frame = frame, + localstatus = {}, + border = border, + tabs = {}, + content = content, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + + widget.Add = TSMAPI.AddGUIElement + return AceGUI:RegisterAsContainer(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMTreeGroup.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMTreeGroup.lua new file mode 100644 index 0000000..9a2c6b7 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMTreeGroup.lua @@ -0,0 +1,735 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIContainer-TreeGroup.lua +-- This TreeGroup container is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMTreeGroup", 2 +local AceGUI = LibStub("AceGUI-3.0") +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type +local math_min, math_max, floor = math.min, math.max, floor +local select, tremove, unpack, tconcat = select, table.remove, unpack, table.concat + +-- WoW APIs +local CreateFrame, UIParent = CreateFrame, UIParent + +-- Recycling functions +local new, del +do + local pool = setmetatable({},{__mode='k'}) + function new() + local t = next(pool) + if t then + pool[t] = nil + return t + else + return {} + end + end + function del(t) + for k in pairs(t) do + t[k] = nil + end + pool[t] = true + end +end + +local DEFAULT_TREE_WIDTH = 175 +local DEFAULT_TREE_SIZABLE = true +local HIGHLIGHT_COLOR = {80/255, 180/255, 180/255} + + +--[[----------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function GetButtonUniqueValue(line) + local parent = line.parent + if parent and parent.value then + return GetButtonUniqueValue(parent).."\001"..line.value + else + return line.value + end +end + +local function UpdateButton(button, treeline, selected, canExpand, isExpanded) + local self = button.obj + local toggle = button.toggle + local frame = self.frame + local text = treeline.text or "" + local icon = treeline.icon + local iconCoords = treeline.iconCoords + local level = treeline.level + local value = treeline.value + local uniquevalue = treeline.uniquevalue + local disabled = treeline.disabled + + button.treeline = treeline + button.value = value + button.uniquevalue = uniquevalue + if selected then + button:LockHighlight() + button.selected = true + else + button:UnlockHighlight() + button.selected = false + end + local normalTexture = button:GetNormalTexture() + local line = button.line + button.level = level + if level == 1 then + button.text:SetFont(TSMAPI.Design:GetBoldFont(), 15) + TSMAPI.Design:SetTitleTextColor(button.text) + button.text:SetPoint("LEFT", (icon and 16 or 0) + 8, 2) + else + button.text:SetFont(TSMAPI.Design:GetContentFont("normal")) + TSMAPI.Design:SetWidgetLabelColor(button.text) + button.text:SetPoint("LEFT", (icon and 16 or 0) + 8 * level, 2) + end + + if disabled then + button:EnableMouse(false) + button.text:SetText("|cff808080"..text..FONT_COLOR_CODE_CLOSE) + else + button.text:SetText(text) + button:EnableMouse(true) + end + + if icon then + button.icon:SetTexture(icon) + button.icon:SetPoint("LEFT", 8 * level, (level == 1) and 0 or 1) + else + button.icon:SetTexture(nil) + end + + if iconCoords then + button.icon:SetTexCoord(unpack(iconCoords)) + else + button.icon:SetTexCoord(0, 1, 0, 1) + end + + if canExpand then + if not isExpanded then + toggle.text:SetFont(TSMAPI.Design:GetBoldFont(), 24) + toggle.text:SetText("+") + else + toggle.text:SetFont(TSMAPI.Design:GetBoldFont(), 30) + toggle.text:SetText("-") + end + toggle:Show() + else + toggle:Hide() + end +end + +local function ShouldDisplayLevel(tree) + local result = false + for k, v in ipairs(tree) do + if v.children == nil and v.visible ~= false then + result = true + elseif v.children then + result = result or ShouldDisplayLevel(v.children) + end + if result then return result end + end + return false +end + +local function addLine(self, v, tree, level, parent) + local line = new() + line.value = v.value + line.text = v.text + line.icon = v.icon + line.iconCoords = v.iconCoords + line.disabled = v.disabled + line.tree = tree + line.level = level + line.parent = parent + line.visible = v.visible + line.uniquevalue = GetButtonUniqueValue(line) + if v.children then + line.hasChildren = true + else + line.hasChildren = nil + end + self.lines[#self.lines+1] = line + return line +end + +--fire an update after one frame to catch the treeframes height +local function FirstFrameUpdate(frame) + local self = frame.obj + frame:SetScript("OnUpdate", nil) + self:RefreshTree() +end + +local function BuildUniqueValue(...) + local n = select('#', ...) + if n == 1 then + return ... + else + return (...).."\001"..BuildUniqueValue(select(2,...)) + end +end + + +--[[----------------------------------------------------------------------------- +Scripts +-------------------------------------------------------------------------------]] + +local function Expand_OnClick(frame) + local button = frame.button + local self = button.obj + local status = (self.status or self.localstatus).groups + status[button.uniquevalue] = not status[button.uniquevalue] + self:RefreshTree() +end + +local function Button_OnClick(frame) + local self = frame.obj + self:Fire("OnClick", frame.uniquevalue, frame.selected) + if not frame.selected then + self:SetSelected(frame.uniquevalue) + frame.selected = true + frame:LockHighlight() + self:RefreshTree() + end + AceGUI:ClearFocus() +end + +local function Button_OnDoubleClick(button) + local self = button.obj + local status = self.status or self.localstatus + local status = (self.status or self.localstatus).groups + status[button.uniquevalue] = not status[button.uniquevalue] + self:RefreshTree() +end + +local function Button_OnEnter(frame) + local self = frame.obj + self:Fire("OnButtonEnter", frame.uniquevalue, frame) + + if self.enabletooltips then + GameTooltip:SetOwner(frame, "ANCHOR_NONE") + GameTooltip:SetPoint("LEFT",frame,"RIGHT") + -- strip out color codes + local text = frame.text:GetText() or "" + text = gsub(text, "|cff[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]", "") + text = gsub(text, "|r", "") + GameTooltip:SetText(text, 1, .82, 0, 1) + GameTooltip:Show() + end +end + +local function Button_OnLeave(frame) + local self = frame.obj + self:Fire("OnButtonLeave", frame.uniquevalue, frame) + + if self.enabletooltips then + GameTooltip:Hide() + end +end + +local function OnScrollValueChanged(frame, value) + if frame.obj.noupdate then return end + local self = frame.obj + local status = self.status or self.localstatus + status.scrollvalue = floor(value + 0.5) + self:RefreshTree() + AceGUI:ClearFocus() +end + +local function Tree_OnSizeChanged(frame) + frame.obj:RefreshTree() +end + +local function Tree_OnMouseWheel(frame, delta) + local self = frame.obj + if self.showscroll then + local scrollbar = self.scrollbar + local min, max = scrollbar:GetMinMaxValues() + local value = scrollbar:GetValue() + local newvalue = math_min(max,math_max(min,value - delta)) + if value ~= newvalue then + scrollbar:SetValue(newvalue) + end + end +end + +local function Dragger_OnLeave(frame) + frame:SetBackdropColor(1, 1, 1, 0) +end + +local function Dragger_OnEnter(frame) + frame:SetBackdropColor(1, 1, 1, 0.8) +end + +local function Dragger_OnMouseDown(frame) + local treeframe = frame:GetParent() + treeframe:StartSizing("RIGHT") +end + +local function Dragger_OnMouseUp(frame) + local treeframe = frame:GetParent() + local self = treeframe.obj + local frame = treeframe:GetParent() + treeframe:StopMovingOrSizing() + treeframe:SetUserPlaced(false) + --Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize + treeframe:SetHeight(0) + treeframe:SetPoint("TOPLEFT", frame, "TOPLEFT",0,0) + treeframe:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT",0,0) + + local status = self.status or self.localstatus + status.treewidth = treeframe:GetWidth() + + treeframe.obj:Fire("OnTreeResize",treeframe:GetWidth()) + -- recalculate the content width + treeframe.obj:OnWidthSet(status.fullwidth) + -- update the layout of the content + treeframe.obj:DoLayout() +end + + +--[[----------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + self:SetTreeWidth(DEFAULT_TREE_WIDTH, DEFAULT_TREE_SIZABLE) + self:EnableButtonTooltips(true) + end, + + ["OnRelease"] = function(self) + self.status = nil + for k, v in pairs(self.localstatus) do + if k == "groups" then + for k2 in pairs(v) do + v[k2] = nil + end + else + self.localstatus[k] = nil + end + end + self.localstatus.scrollvalue = 0 + self.localstatus.treewidth = DEFAULT_TREE_WIDTH + self.localstatus.treesizable = DEFAULT_TREE_SIZABLE + end, + + ["EnableButtonTooltips"] = function(self, enable) + self.enabletooltips = enable + end, + + ["CreateButton"] = function(self) + local num = AceGUI:GetNextWidgetNum("TreeGroupButton") + local button = CreateFrame("Button", ("TSMTreeButton%d"):format(num), self.treeframe, "OptionsListButtonTemplate") + button.obj = self + + local icon = button:CreateTexture(nil, "OVERLAY") + icon:SetWidth(14) + icon:SetHeight(14) + button.icon = icon + + button.text:SetShadowColor(0, 0, 0, 0) + button.text:SetPoint("CENTER") + + button:SetScript("OnClick",Button_OnClick) + button:SetScript("OnDoubleClick", Button_OnDoubleClick) + button:SetScript("OnEnter",Button_OnEnter) + button:SetScript("OnLeave",Button_OnLeave) + + HIGHLIGHT_COLOR[4] = .5 + button.highlight:SetVertexColor(unpack(HIGHLIGHT_COLOR)) + + button.toggle.button = button + button.toggle:SetScript("OnClick", Expand_OnClick) + local tex = button.toggle:CreateTexture() + tex:SetTexture(0, 0, 0, 0) + button.toggle:SetNormalTexture(tex) + button.toggle:SetPushedTexture(tex) + HIGHLIGHT_COLOR[4] = 1 + button.toggle:GetHighlightTexture():SetVertexColor(unpack(HIGHLIGHT_COLOR)) + button.toggle:GetHighlightTexture():SetBlendMode("ADD") + local text = button.toggle:CreateFontString() + TSMAPI.Design:SetWidgetLabelColor(text) + text:SetPoint("CENTER") + button.toggle.text = text + + return button + end, + + ["SetStatusTable"] = function(self, status) + assert(type(status) == "table") + self.status = status + if not status.groups then + status.groups = {} + end + if not status.scrollvalue then + status.scrollvalue = 0 + end + if not status.treewidth then + status.treewidth = DEFAULT_TREE_WIDTH + end + if status.treesizable == nil then + status.treesizable = DEFAULT_TREE_SIZABLE + end + self:SetTreeWidth(status.treewidth,status.treesizable) + self:RefreshTree() + end, + + --sets the tree to be displayed + ["SetTree"] = function(self, tree, filter) + self.filter = filter + if tree then + assert(type(tree) == "table") + end + self.tree = tree + self:RefreshTree() + end, + + ["BuildLevel"] = function(self, tree, level, parent) + local groups = (self.status or self.localstatus).groups + local hasChildren = self.hasChildren + + for i, v in ipairs(tree) do + if v.children then + if not self.filter or ShouldDisplayLevel(v.children) then + local line = addLine(self, v, tree, level, parent) + if groups[line.uniquevalue] then + self:BuildLevel(v.children, level+1, line) + end + end + elseif v.visible ~= false or not self.filter then + addLine(self, v, tree, level, parent) + end + end + end, + + ["RefreshTree"] = function(self,scrollToSelection) + local buttons = self.buttons + local lines = self.lines + + for i, v in ipairs(buttons) do + v:Hide() + end + while lines[1] do + local t = tremove(lines) + for k in pairs(t) do + t[k] = nil + end + del(t) + end + + if not self.tree then return end + --Build the list of visible entries from the tree and status tables + local status = self.status or self.localstatus + local groupstatus = status.groups + local tree = self.tree + + local treeframe = self.treeframe + + status.scrollToSelection = status.scrollToSelection or scrollToSelection -- needs to be cached in case the control hasn't been drawn yet (code bails out below) + + self:BuildLevel(tree, 1) + + local numlines = #lines + + local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18)) + if maxlines <= 0 then return end + + local first, last + + scrollToSelection = status.scrollToSelection + status.scrollToSelection = nil + + if numlines <= maxlines then + --the whole tree fits in the frame + status.scrollvalue = 0 + self:ShowScroll(false) + first, last = 1, numlines + else + self:ShowScroll(true) + --scrolling will be needed + self.noupdate = true + self.scrollbar:SetMinMaxValues(0, numlines - maxlines) + --check if we are scrolled down too far + if numlines - status.scrollvalue < maxlines then + status.scrollvalue = numlines - maxlines + end + self.noupdate = nil + first, last = status.scrollvalue+1, status.scrollvalue + maxlines + --show selection? + if scrollToSelection and status.selected then + local show + for i,line in ipairs(lines) do -- find the line number + if line.uniquevalue==status.selected then + show=i + end + end + if not show then + -- selection was deleted or something? + elseif show>=first and show<=last then + -- all good + else + -- scrolling needed! + if show 100 and status.treewidth > maxtreewidth then + self:SetTreeWidth(maxtreewidth, status.treesizable) + end + treeframe:SetMaxResize(maxtreewidth, 1600) + end, + + ["OnHeightSet"] = function(self, height) + local content = self.content + local contentheight = height - 20 + if contentheight < 0 then + contentheight = 0 + end + content:SetHeight(contentheight) + content.height = contentheight + end, + + ["SetTreeWidth"] = function(self, treewidth, resizable) + if not resizable then + if type(treewidth) == 'number' then + resizable = false + elseif type(treewidth) == 'boolean' then + resizable = treewidth + treewidth = DEFAULT_TREE_WIDTH + else + resizable = false + treewidth = DEFAULT_TREE_WIDTH + end + end + self.treeframe:SetWidth(treewidth) + self.dragger:EnableMouse(resizable) + + local status = self.status or self.localstatus + status.treewidth = treewidth + status.treesizable = resizable + + -- recalculate the content width + if status.fullwidth then + self:OnWidthSet(status.fullwidth) + end + end, + + ["GetTreeWidth"] = function(self) + local status = self.status or self.localstatus + return status.treewidth or DEFAULT_TREE_WIDTH + end, + + ["LayoutFinished"] = function(self, width, height) + if self.noAutoHeight then return end + self:SetHeight((height or 0) + 20) + end +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local DraggerBackdrop = { + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = nil, + tile = true, tileSize = 16, edgeSize = 0, + insets = { left = 3, right = 3, top = 7, bottom = 7 } +} + +local function Constructor() + local num = AceGUI:GetNextWidgetNum(Type) + local frame = CreateFrame("Frame", nil, UIParent) + + local treeframe = CreateFrame("Frame", nil, frame) + treeframe:SetPoint("TOPLEFT") + treeframe:SetPoint("BOTTOMLEFT") + treeframe:SetWidth(DEFAULT_TREE_WIDTH) + treeframe:EnableMouseWheel(true) + treeframe:SetResizable(true) + treeframe:SetMinResize(100, 1) + treeframe:SetMaxResize(400, 1600) + treeframe:SetScript("OnUpdate", FirstFrameUpdate) + treeframe:SetScript("OnSizeChanged", Tree_OnSizeChanged) + treeframe:SetScript("OnMouseWheel", Tree_OnMouseWheel) + TSMAPI.Design:SetFrameColor(treeframe) + + local dragger = CreateFrame("Frame", nil, treeframe) + dragger:SetWidth(10) + dragger:SetPoint("TOPLEFT", treeframe, "TOPRIGHT") + dragger:SetPoint("BOTTOMLEFT", treeframe, "BOTTOMRIGHT") + dragger:SetBackdrop(DraggerBackdrop) + dragger:SetBackdropColor(1, 1, 1, 0) + dragger:SetScript("OnEnter", Dragger_OnEnter) + dragger:SetScript("OnLeave", Dragger_OnLeave) + dragger:SetScript("OnMouseDown", Dragger_OnMouseDown) + dragger:SetScript("OnMouseUp", Dragger_OnMouseUp) + + local scrollbar = CreateFrame("Slider", ("TSMTreeGroup%dScrollBar"):format(num), treeframe, "UIPanelScrollBarTemplate") + scrollbar:SetScript("OnValueChanged", nil) + scrollbar:SetPoint("TOPRIGHT", -10, -26) + scrollbar:SetPoint("BOTTOMRIGHT", -10, 26) + scrollbar:SetMinMaxValues(0,0) + scrollbar:SetValueStep(1) + scrollbar:SetValue(0) + scrollbar:SetWidth(12) + scrollbar:SetScript("OnValueChanged", OnScrollValueChanged) + + local thumbTex = scrollbar:GetThumbTexture() + thumbTex:SetPoint("CENTER") + TSMAPI.Design:SetContentColor(thumbTex) + thumbTex:SetHeight(150) + thumbTex:SetWidth(scrollbar:GetWidth()) + + local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND") + scrollbg:SetAllPoints(scrollbar) + TSMAPI.Design:SetFrameColor(scrollbg) + + _G[scrollbar:GetName().."ScrollUpButton"]:Hide() + _G[scrollbar:GetName().."ScrollDownButton"]:Hide() + + local border = CreateFrame("Frame",nil,frame) + border:SetPoint("TOPLEFT", dragger, "TOPRIGHT", -1, 0) + border:SetPoint("BOTTOMRIGHT") + TSMAPI.Design:SetFrameColor(border) + + --Container Support + local content = CreateFrame("Frame", nil, border) + content:SetPoint("TOPLEFT", 6, -6) + content:SetPoint("BOTTOMRIGHT", -6, 6) + + local widget = { + frame = frame, + lines = {}, + levels = {}, + buttons = {}, + hasChildren = {}, + localstatus = {groups={}, scrollvalue=0}, + filter = false, + treeframe = treeframe, + dragger = dragger, + scrollbar = scrollbar, + border = border, + content = content, + type = Type + } + for method, func in pairs(methods) do + widget[method] = func + end + treeframe.obj, dragger.obj, scrollbar.obj = widget, widget, widget + + return AceGUI:RegisterAsContainer(widget) +end + +AceGUI:RegisterWidgetType(Type, Constructor, Version) \ No newline at end of file diff --git a/TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua b/TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua new file mode 100644 index 0000000..f1191a5 --- /dev/null +++ b/TradeSkillMaster/GUI/TSMWidgets/TSMWindow.lua @@ -0,0 +1,169 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskill-master -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- Much of this code is copied from .../AceGUI-3.0/widgets/AceGUIWidget-Window.lua +-- This Window container is modified to fit TSM's theme / needs +local TSM = select(2, ...) +local Type, Version = "TSMWindow", 2 +local AceGUI = LibStub("AceGUI-3.0") +if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end + +-- Lua APIs +local pairs, assert, type = pairs, assert, type + +-- WoW APIs +local PlaySound = PlaySound +local CreateFrame, UIParent = CreateFrame, UIParent + + +--[[---------------------------------------------------------------------------- +Support functions +-------------------------------------------------------------------------------]] + +local function frameOnClose(this) + this.obj:Fire("OnClose") +end + +local function closeOnClick(this) + PlaySound("gsTitleOptionExit") + this.obj:Hide() +end + +local function frameOnMouseDown(this) + this:StartMoving() + AceGUI:ClearFocus() +end + +local function frameOnMouseUp(this) + this:StopMovingOrSizing() +end + + +--[[---------------------------------------------------------------------------- +Methods +-------------------------------------------------------------------------------]] + +local methods = { + ["OnAcquire"] = function(self) + self.frame:SetParent(UIParent) + self.frame:SetFrameStrata("FULLSCREEN_DIALOG") + self:ApplyStatus() + self:Show() + end, + + ["OnRelease"] = function(self) + self.status = nil + for k in pairs(self.localstatus) do + self.localstatus[k] = nil + end + end, + + ["Show"] = function(self) + self.frame:Show() + end, + + ["Hide"] = function(self) + self.frame:Hide() + end, + + ["SetTitle"] = function(self,title) + self.titletext:SetText(title) + end, + + ["ApplyStatus"] = function(self) + local status = self.status or self.localstatus + local frame = self.frame + self:SetWidth(status.width or 700) + self:SetHeight(status.height or 500) + if status.top and status.left then + frame:SetPoint("TOP",UIParent,"BOTTOM",0,status.top) + frame:SetPoint("LEFT",UIParent,"LEFT",status.left,0) + else + frame:SetPoint("CENTER",UIParent,"CENTER") + end + end, + + ["OnWidthSet"] = function(self, width) + local content = self.content + local contentwidth = width - 34 + if contentwidth < 0 then + contentwidth = 0 + end + content:SetWidth(contentwidth) + content.width = contentwidth + end, + + ["OnHeightSet"] = function(self, height) + local content = self.content + local contentheight = height - 57 + if contentheight < 0 then + contentheight = 0 + end + content:SetHeight(contentheight) + content.height = contentheight + end, +} + +--[[----------------------------------------------------------------------------- +Constructor +-------------------------------------------------------------------------------]] + +local function Constructor() + local frame = CreateFrame("Frame") + frame:SetWidth(700) + frame:SetHeight(500) + frame:SetPoint("CENTER") + frame:EnableMouse(true) + frame:SetMovable(true) + frame:SetResizable(true) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:SetScript("OnMouseDown", frameOnMouseDown) + frame:SetScript("OnMouseUp", frameOnMouseUp) + frame:SetScript("OnHide", frameOnClose) + TSMAPI.Design:SetFrameBackdropColor(frame) + + local close = CreateFrame("Button", nil, frame, "UIPanelCloseButton") + close:SetPoint("TOPRIGHT", 2, 1) + close:SetScript("OnClick", closeOnClick) + + local titletext = frame:CreateFontString(nil, "ARTWORK") + titletext:SetFont(TSMAPI.Design:GetBoldFont(), 18) + TSMAPI.Design:SetTitleTextColor(titletext) + titletext:SetPoint("TOP", 0, -4) + + local line = frame:CreateTexture() + line:SetPoint("TOPLEFT", 2, -28) + line:SetPoint("TOPRIGHT", -2, -28) + line:SetHeight(2) + TSMAPI.Design:SetIconRegionColor(line) + + --Container Support + local content = CreateFrame("Frame", nil, frame) + content:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -32) + content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 13) + + local widget = { + frame = frame, + type = Type, + localstatus = {}, + content = content, + title = title, + titletext = titletext, + closebutton = close, + } + for method, func in pairs(methods) do + widget[method] = func + end + frame.obj, content.obj, close.obj = widget, widget, widget + + widget.Add = TSMAPI.AddGUIElement + + return AceGUI:RegisterAsContainer(widget) +end + +AceGUI:RegisterWidgetType(Type,Constructor,Version) \ No newline at end of file diff --git a/TradeSkillMaster/Items.lua b/TradeSkillMaster/Items.lua new file mode 100644 index 0000000..973bed7 --- /dev/null +++ b/TradeSkillMaster/Items.lua @@ -0,0 +1,186 @@ +-- ------------------------------------------------------------------------------ -- +-- TradeSkillMaster -- +-- http://www.curse.com/addons/wow/tradeskillmaster_warehousing -- +-- -- +-- A TradeSkillMaster Addon (http://tradeskillmaster.com) -- +-- All Rights Reserved* - Detailed license information included with addon. -- +-- ------------------------------------------------------------------------------ -- + +-- This file contains APIs related to items (itemLinks/itemStrings/etc) + +local TSM = select(2, ...) + + +function TSMAPI:SafeTooltipLink(link) + -- if strmatch(link, "battlepet") then + -- local _, speciesID, level, breedQuality, maxHealth, power, speed, battlePetID = strsplit(":", link) + -- BattlePetToolTip_Show(tonumber(speciesID), tonumber(level), tonumber(breedQuality), tonumber(maxHealth), tonumber(power), tonumber(speed), gsub(gsub(link, "^(.*)%[", ""), "%](.*)$", "")) + -- else + GameTooltip:SetHyperlink(link) + -- end +end + +function TSMAPI:GetItemString(item) + if type(item) == "string" then + item = item:trim() + end + + if type(item) ~= "string" and type(item) ~= "number" then + return nil, "invalid arg type" + end + item = select(2, TSMAPI:GetSafeItemInfo(item)) or item + if tonumber(item) then + return "item:"..item..":0:0:0:0:0:0" + end + + local itemInfo = {strfind(item, "|?c?f?f?(%x*)|?H?([^:]*):?(%d+):?(%d*):?(%d*):?(%d*):?(%d*):?(%d*):?(%-?%d*):?(%-?%d*):?(%-?%d*):?(%d*)|?h?%[?([^%[%]]*)%]?|?h?|?r?")} + if not itemInfo[11] then return nil, "invalid link" end + itemInfo[11] = tonumber(itemInfo[11]) or 0 + + if itemInfo[4] == "item" then + for i=6, 10 do itemInfo[i] = 0 end + return table.concat(itemInfo, ":", 4, 11) + else + return table.concat(itemInfo, ":", 4, 7) + end +end + +function TSMAPI:GetBaseItemString(itemString, doGroupLookup) + if type(itemString) ~= "string" then return end + if strsub(itemString, 1, 2) == "|c" then + -- this is an itemLink so get the itemString first + itemString = TSMAPI:GetItemString(itemString) + if not itemString then return end + end + + local parts = {(":"):split(itemString)} + for i=3, #parts do + parts[i] = 0 + end + local baseItemString = table.concat(parts, ":") + if not doGroupLookup then return baseItemString end + + if TSM.db.profile.items[itemString] and TSM.db.profile.items[baseItemString] then + return itemString + elseif TSM.db.profile.items[baseItemString] then + return baseItemString + else + return itemString + end +end + +local itemInfoCache = {} +local PET_CAGE_ITEM_INFO = {isDefault=true, 0, "Battle Pets", "", 1, "", "", 0} +function TSMAPI:GetSafeItemInfo(link) + if type(link) ~= "string" then return end + + if not itemInfoCache[link] then + -- if strmatch(link, "battlepet:") then + -- local _, speciesID, level, quality, health, power, speed, petID = strsplit(":", link) + -- if not tonumber(speciesID) then return end + -- level, quality, health, power, speed, petID = level or 0, quality or 0, health or 0, power or 0, speed or 0, petID or "0" + + -- local name, texture = C_PetJournal.GetPetInfoBySpeciesID(tonumber(speciesID)) + -- if name == "" then return end + -- level, quality = tonumber(level), tonumber(quality) + -- petID = strsub(petID, 1, (strfind(petID, "|") or #petID)-1) + -- link = ITEM_QUALITY_COLORS[quality].hex.."|Hbattlepet:"..speciesID..":"..level..":"..quality..":"..health..":"..power..":"..speed..":"..petID.."|h["..name.."]|h|r" + -- if PET_CAGE_ITEM_INFO.isDefault then + -- local data = {select(5, GetItemInfo(82800))} + -- if #data > 0 then + -- PET_CAGE_ITEM_INFO = data + -- end + -- end + -- local minLvl, iType, _, stackSize, _, _, vendorPrice = unpack(PET_CAGE_ITEM_INFO) + -- local subType, equipLoc = 0, "" + -- itemInfoCache[link] = {name, link, quality, level, minLvl, iType, subType, stackSize, equipLoc, texture, vendorPrice} + --elseif strmatch(link, "item:") then + if strmatch(link, "item:") then + itemInfoCache[link] = {GetItemInfo(link)} + end + if itemInfoCache[link] and #itemInfoCache[link] == 0 then itemInfoCache[link] = nil end + end + if not itemInfoCache[link] then return end + return unpack(itemInfoCache[link]) +end + +--- Attempts to get the itemID from a given itemLink/itemString. +-- @param itemLink The link or itemString for the item. +-- @param ignoreGemID If true, will not attempt to get the equivalent id for the item (ie for old gems where there are multiple ids for a single item). +-- @return Returns the itemID as the first parameter. On error, will return nil as the first parameter and an error message as the second. +function TSMAPI:GetItemID(itemLink) + if not itemLink or type(itemLink) ~= "string" then return nil, "invalid args" end + + local test = select(2, strsplit(":", itemLink)) + if not test then return nil, "invalid link" end + + local s, e = strfind(test, "[0-9]+") + if not (s and e) then return nil, "not an itemLink" end + + local itemID = tonumber(strsub(test, s, e)) + if not itemID then return nil, "invalid number" end + + return itemID +end + + +-- check if an item is soulbound or not +local function GetTooltipCharges(tooltip) + for id=1, tooltip:NumLines() do + local text = _G["TSMSoulboundScanTooltipTextLeft" .. id] + if text and text:GetText() then + local maxCharges = strmatch(text:GetText(), "^([0-9]+) Charges?$") + if maxCharges then + return maxCharges + end + end + end +end +local scanTooltip +local resultsCache = {lastClear=GetTime()} +function TSMAPI:IsSoulbound(bag, slot) + if GetTime() - resultsCache.lastClear > 0.5 then + resultsCache = {lastClear=GetTime()} + end + + if not scanTooltip then + scanTooltip = CreateFrame("GameTooltip", "TSMSoulboundScanTooltip", UIParent, "GameTooltipTemplate") + scanTooltip:SetOwner(UIParent, "ANCHOR_NONE") + end + scanTooltip:ClearLines() + + local slotID + if type(bag) == "string" then + if strfind(bag, "battlepet") then return end + slotID = bag + scanTooltip:SetHyperlink(slotID) + elseif bag and slot then + slotID = bag.."@"..slot + local itemID = GetContainerItemID(bag, slot) + local maxCharges + if itemID then + scanTooltip:SetHyperlink("item:"..itemID) + maxCharges = GetTooltipCharges(scanTooltip) + end + scanTooltip:SetBagItem(bag, slot) + if maxCharges then + if GetTooltipCharges(scanTooltip) ~= maxCharges then + resultsCache[slotID] = true + return resultsCache[slotID] + end + end + else + return + end + + if resultsCache[slotID] ~= nil then return resultsCache[slotID] end + resultsCache[slotID] = false + for id=1, scanTooltip:NumLines() do + local text = _G["TSMSoulboundScanTooltipTextLeft" .. id] + if text and ((text:GetText() == ITEM_BIND_ON_PICKUP and id < 4) or text:GetText() == ITEM_SOULBOUND or text:GetText() == ITEM_BIND_QUEST) then + resultsCache[slotID] = true + break + end + end + return resultsCache[slotID] +end \ No newline at end of file diff --git a/TradeSkillMaster/LICENSE.txt b/TradeSkillMaster/LICENSE.txt new file mode 100644 index 0000000..5be0fc7 --- /dev/null +++ b/TradeSkillMaster/LICENSE.txt @@ -0,0 +1,21 @@ + +All rights are reserved unless explicitly stated below. The "license +holder" is the manager of this project, Sapu94 (sapu94@gmail.com). + +Exceptions: +1) The use of this addon in accordance with all applicable terms set by +Blizzard Entertainment for addon use and game play is permitted. +2) Modifications for personal use or submission to license holder are +permitted. Modified versions of the works, derivative works, modified +sections of the works, and instructions for how to modify the works are +all prohibited unless the express consent of the license holder is +granted. + +Comments: +1) Permission to use sections of the works in your own work is very +likely to be granted upon contacting the license holder. +2) The right to distribute the works is reserved by the license holder. +In no way or form may a person other than the license holder distribute +the works. +3) Please contact the license holder if you have any questions at all +regarding this license at the following email address: sapu94@gmail.com \ No newline at end of file diff --git a/TradeSkillMaster/Libs/AccurateTime/!AccurateTime.toc b/TradeSkillMaster/Libs/AccurateTime/!AccurateTime.toc new file mode 100644 index 0000000..22d45cb --- /dev/null +++ b/TradeSkillMaster/Libs/AccurateTime/!AccurateTime.toc @@ -0,0 +1,12 @@ +## Interface: 30300 +## Title: AccurateTime +## Notes: Provides accurate, millisecond-level timing. +## Author: Sapu94 +## Version: v1.6 +## X-Curse-Packaged-Version: v1.6 +## X-Curse-Project-Name: AccurateTime +## X-Curse-Project-ID: accuratetime +## X-Curse-Repository-ID: wow/accuratetime/mainline + + +AccurateTime.lua \ No newline at end of file diff --git a/TradeSkillMaster/Libs/AccurateTime/AccurateTime.lua b/TradeSkillMaster/Libs/AccurateTime/AccurateTime.lua new file mode 100644 index 0000000..77b763b --- /dev/null +++ b/TradeSkillMaster/Libs/AccurateTime/AccurateTime.lua @@ -0,0 +1,153 @@ +--[[ + +This library is intended to fix the shortfalls of using debugprofilestop() for +getting accurate sub-second timing in addons. Specifically, this library aims +to prevent any conflicts that may arrise with multiple addons using +debugprofilestart and debugprofilestop. While perfect accuracy is not +guarenteed due to the potential for an addon to load before this library and +use the original debugprofilestart/debugprofilestop functions, this library +provides a best-effort means of correcting any issues if this is the case. +The best solution is for addons to NOT use debugprofilestart() and to NOT store +a local reference to debugprofilestop(), even if they aren't using this library +directly. + +------------------------------------------------------------------------------- + +AccurateTime is hereby placed in the Public Domain +See the wowace page for usage and documentation. +Author: Sapu94 (sapu94@gmail.com) +Website: http://www.wowace.com/addons/accuratetime/ +--]] + +local _G = _G +local AT_VERSION = 4 + + +-- Check if we're already loaded +-- If this is a newer version, remove the old hooks and we'll re-hook +if _G.AccurateTime then + if _G.AccurateTime.version > AT_VERSION then + -- newer (or same) version already loaded - abort + return + end + + -- undo hook so we can re-hook + debugprofilestart = _G.AccurateTime._debugprofilestart + debugprofilestop = _G.AccurateTime._debugprofilestop +else + -- reset timer in the back-end so we avoid potential (unlikely) overflows + -- only do this the first time we're loaded + debugprofilestart() +end + + +-- setup global library reference +_G.AccurateTime = {} +AccurateTime = _G.AccurateTime +AccurateTime.version = AT_VERSION + +-- Store original functions. +-- debugprofilestart should never be called, but we'll store it just in case. +AccurateTime._debugprofilestop = debugprofilestop +AccurateTime._debugprofilestart = debugprofilestart + +-- key to use for direct, non-library calls to +-- debugprofilestart/debugprofilestop +AccurateTime.DEFAULT_KEY = AccurateTime.DEFAULT_KEY or {} + +-- other internal variables +AccurateTime._errorTime = AccurateTime._errorTime or 0 +AccurateTime._timers = AccurateTime._timers or {} + + +-- Gets the current time in milliseconds. Will be directly from the original +-- debugprofilestop() with any error we've detected added in. This error would +-- come solely from an addon calling the unhooked debugprofilestart(). +function AccurateTime:GetAbsTime() + return AccurateTime._debugprofilestop() + AccurateTime._errorTime +end + +-- It is up to the caller to ensure the key they are using is unique. +-- Using table reference or description strings is preferable. +-- If no key is specified, a unique key will be created and returned. +-- If the timer is already running, restart it. +-- Usage: local key = AccurateTime:GetTimer([key]) +function AccurateTime:StartTimer(key) + key = key or {} + AccurateTime._timers[key] = AccurateTime._timers[key] or AccurateTime:GetAbsTime() + return key +end + +-- gets the current value of a timer +-- Usage: local value = AccurateTime:GetTimer(key[, silent]) +function AccurateTime:GetTimer(key, silent) + assert(key, "No key specified.") + if silent and not AccurateTime._timers[key] then return end + assert(AccurateTime._timers[key], "No timer currently running for the given key.") + return AccurateTime:GetAbsTime() - AccurateTime._timers[key] +end + +-- Removes a timer and returns its current value. +-- Usage: local value = AccurateTime:StopTimer(key) +function AccurateTime:StopTimer(key) + if key == AccurateTime.DEFAULT_KEY and not AccurateTime._timers[key] then + -- Don't assert if somebody calls debugprofilestop() without starting + -- This is a best-effort attempt to give them an accurate time + return AccurateTime:GetAbsTime() + end + assert(key, "No key specified.") + assert(AccurateTime._timers[key], "No timer currently running for the given key.") + + local value = AccurateTime:GetTimer(key) + AccurateTime._timers[key] = nil + return value +end + + +-- apply hooks +debugprofilestart = function() AccurateTime:StartTimer(AccurateTime.DEFAULT_KEY) end +debugprofilestop = function() return AccurateTime:StopTimer(AccurateTime.DEFAULT_KEY) end + + +-- Create an OnUpdate script to detect and attempt to correct other addons +-- which use the original (non-hooked) debugprofilestart(). This should in +-- theory never happen, but we'll do a best-effort correction if it does. +local function OnUpdate(self) + local absTime = AccurateTime:GetAbsTime() + if absTime < self.lastUpdateTime then + -- debugprofilestart() was called and the back-end timer was reset + -- Estimate what the absolute time should be using GetTime() (converted + -- to ms) and add it to AccurateTime._errorTime. + local realAbsTime = self.lastUpdateAbsTime + (GetTime() - self.lastUpdateTime) * 1000 + AccurateTime._errorTime = AccurateTime._errorTime + (realAbsTime - absTime) + end + self.lastUpdateAbsTime = absTime + self.lastUpdateTime = GetTime() +end +if not AccurateTime._frame then + -- create frame just once + AccurateTime._frame = CreateFrame("Frame") + AccurateTime._frame.lastUpdateTime = GetTime() + AccurateTime._frame.lastUpdateAbsTime = 0 +end +-- upgrade the frame +AccurateTime._frame:SetScript("OnUpdate", OnUpdate) + +--[===[@debug@ +function AccurateTimeTest() + local start = debugprofilestop() + for i=1, 10000000 do + end + print("loop", debugprofilestop()-start) + start = debugprofilestop() + for i=1, 10000000 do + debugprofilestop() + end + print("overriden", debugprofilestop()-start) + start = debugprofilestop() + for i=1, 10000000 do + AccurateTime._debugprofilestop() + end + print("raw", debugprofilestop()-start) +end +--@end-debug@]===] \ No newline at end of file diff --git a/TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.lua b/TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.lua new file mode 100644 index 0000000..3dbaa1c --- /dev/null +++ b/TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.lua @@ -0,0 +1,674 @@ +--- **AceAddon-3.0** provides a template for creating addon objects. +-- It'll provide you with a set of callback functions that allow you to simplify the loading +-- process of your addon.\\ +-- Callbacks provided are:\\ +-- * **OnInitialize**, which is called directly after the addon is fully loaded. +-- * **OnEnable** which gets called during the PLAYER_LOGIN event, when most of the data provided by the game is already present. +-- * **OnDisable**, which is only called when your addon is manually being disabled. +-- @usage +-- -- A small (but complete) addon, that doesn't do anything, +-- -- but shows usage of the callbacks. +-- local MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- +-- function MyAddon:OnInitialize() +-- -- do init tasks here, like loading the Saved Variables, +-- -- or setting up slash commands. +-- end +-- +-- function MyAddon:OnEnable() +-- -- Do more initialization here, that really enables the use of your addon. +-- -- Register Events, Hook functions, Create Frames, Get information from +-- -- the game that wasn't available in OnInitialize +-- end +-- +-- function MyAddon:OnDisable() +-- -- Unhook, Unregister Events, Hide frames that you created. +-- -- You would probably only use an OnDisable if you want to +-- -- build a "standby" mode, or be able to toggle modules on/off. +-- end +-- @class file +-- @name AceAddon-3.0.lua +-- @release $Id$ + +local MAJOR, MINOR = "AceAddon-3.0", 12 +local AceAddon, oldminor = LibStub:NewLibrary(MAJOR, MINOR) + +if not AceAddon then return end -- No Upgrade needed. + +AceAddon.frame = AceAddon.frame or CreateFrame("Frame", "AceAddon30Frame") -- Our very own frame +AceAddon.addons = AceAddon.addons or {} -- addons in general +AceAddon.statuses = AceAddon.statuses or {} -- statuses of addon. +AceAddon.initializequeue = AceAddon.initializequeue or {} -- addons that are new and not initialized +AceAddon.enablequeue = AceAddon.enablequeue or {} -- addons that are initialized and waiting to be enabled +AceAddon.embeds = AceAddon.embeds or setmetatable({}, {__index = function(tbl, key) tbl[key] = {} return tbl[key] end }) -- contains a list of libraries embedded in an addon + +-- Lua APIs +local tinsert, tconcat, tremove = table.insert, table.concat, table.remove +local fmt, tostring = string.format, tostring +local select, pairs, next, type, unpack = select, pairs, next, type, unpack +local loadstring, assert, error = loadstring, assert, error +local setmetatable, getmetatable, rawset, rawget = setmetatable, getmetatable, rawset, rawget + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: LibStub, IsLoggedIn, geterrorhandler + +--[[ + xpcall safecall implementation +]] +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local xpcall, eh = ... + local method, ARGS + local function call() return method(ARGS) end + + local function dispatch(func, ...) + method = func + if not method then return end + ARGS = ... + return xpcall(call, eh) + end + + return dispatch + ]] + + local ARGS = {} + for i = 1, argCount do ARGS[i] = "arg"..i end + code = code:gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) +Dispatchers[0] = function(func) + return xpcall(func, errorhandler) +end + +local function safecall(func, ...) + -- we check to see if the func is passed is actually a function here and don't error when it isn't + -- this safecall is used for optional functions like OnInitialize OnEnable etc. When they are not + -- present execution should continue without hinderance + if type(func) == "function" then + return Dispatchers[select('#', ...)](func, ...) + end +end + +-- local functions that will be implemented further down +local Enable, Disable, EnableModule, DisableModule, Embed, NewModule, GetModule, GetName, SetDefaultModuleState, SetDefaultModuleLibraries, SetEnabledState, SetDefaultModulePrototype + +-- used in the addon metatable +local function addontostring( self ) return self.name end + +-- Check if the addon is queued for initialization +local function queuedForInitialization(addon) + for i = 1, #AceAddon.initializequeue do + if AceAddon.initializequeue[i] == addon then + return true + end + end + return false +end + +--- Create a new AceAddon-3.0 addon. +-- Any libraries you specified will be embeded, and the addon will be scheduled for +-- its OnInitialize and OnEnable callbacks. +-- The final addon object, with all libraries embeded, will be returned. +-- @paramsig [object ,]name[, lib, ...] +-- @param object Table to use as a base for the addon (optional) +-- @param name Name of the addon object to create +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a simple addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon", "AceEvent-3.0") +-- +-- -- Create a Addon object based on the table of a frame +-- local MyFrame = CreateFrame("Frame") +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon(MyFrame, "MyAddon", "AceEvent-3.0") +function AceAddon:NewAddon(objectorname, ...) + local object,name + local i=1 + if type(objectorname)=="table" then + object=objectorname + name=... + i=2 + else + name=objectorname + end + if type(name)~="string" then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) + end + if self.addons[name] then + error(("Usage: NewAddon([object,] name, [lib, lib, lib, ...]): 'name' - Addon '%s' already exists."):format(name), 2) + end + + object = object or {} + object.name = name + + local addonmeta = {} + local oldmeta = getmetatable(object) + if oldmeta then + for k, v in pairs(oldmeta) do addonmeta[k] = v end + end + addonmeta.__tostring = addontostring + + setmetatable( object, addonmeta ) + self.addons[name] = object + object.modules = {} + object.orderedModules = {} + object.defaultModuleLibraries = {} + Embed( object ) -- embed NewModule, GetModule methods + self:EmbedLibraries(object, select(i,...)) + + -- add to queue of addons to be initialized upon ADDON_LOADED + tinsert(self.initializequeue, object) + return object +end + + +--- Get the addon object by its name from the internal AceAddon registry. +-- Throws an error if the addon object cannot be found (except if silent is set). +-- @param name unique name of the addon object +-- @param silent if true, the addon is optional, silently return nil if its not found +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +function AceAddon:GetAddon(name, silent) + if not silent and not self.addons[name] then + error(("Usage: GetAddon(name): 'name' - Cannot find an AceAddon '%s'."):format(tostring(name)), 2) + end + return self.addons[name] +end + +-- - Embed a list of libraries into the specified addon. +-- This function will try to embed all of the listed libraries into the addon +-- and error if a single one fails. +-- +-- **Note:** This function is for internal use by :NewAddon/:NewModule +-- @paramsig addon, [lib, ...] +-- @param addon addon object to embed the libs in +-- @param lib List of libraries to embed into the addon +function AceAddon:EmbedLibraries(addon, ...) + for i=1,select("#", ... ) do + local libname = select(i, ...) + self:EmbedLibrary(addon, libname, false, 4) + end +end + +-- - Embed a library into the addon object. +-- This function will check if the specified library is registered with LibStub +-- and if it has a :Embed function to call. It'll error if any of those conditions +-- fails. +-- +-- **Note:** This function is for internal use by :EmbedLibraries +-- @paramsig addon, libname[, silent[, offset]] +-- @param addon addon object to embed the library in +-- @param libname name of the library to embed +-- @param silent marks an embed to fail silently if the library doesn't exist (optional) +-- @param offset will push the error messages back to said offset, defaults to 2 (optional) +function AceAddon:EmbedLibrary(addon, libname, silent, offset) + local lib = LibStub:GetLibrary(libname, true) + if not lib and not silent then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Cannot find a library instance of %q."):format(tostring(libname)), offset or 2) + elseif lib and type(lib.Embed) == "function" then + lib:Embed(addon) + tinsert(self.embeds[addon], libname) + return true + elseif lib then + error(("Usage: EmbedLibrary(addon, libname, silent, offset): 'libname' - Library '%s' is not Embed capable"):format(libname), offset or 2) + end +end + +--- Return the specified module from an addon object. +-- Throws an error if the addon object cannot be found (except if silent is set) +-- @name //addon//:GetModule +-- @paramsig name[, silent] +-- @param name unique name of the module +-- @param silent if true, the module is optional, silently return nil if its not found (optional) +-- @usage +-- -- Get the Addon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- -- Get the Module +-- MyModule = MyAddon:GetModule("MyModule") +function GetModule(self, name, silent) + if not self.modules[name] and not silent then + error(("Usage: GetModule(name, silent): 'name' - Cannot find module '%s'."):format(tostring(name)), 2) + end + return self.modules[name] +end + +local function IsModuleTrue(self) return true end + +--- Create a new module for the addon. +-- The new module can have its own embeded libraries and/or use a module prototype to be mixed into the module.\\ +-- A module has the same functionality as a real addon, it can have modules of its own, and has the same API as +-- an addon object. +-- @name //addon//:NewModule +-- @paramsig name[, prototype|lib[, lib, ...]] +-- @param name unique name of the module +-- @param prototype object to derive this module from, methods and values from this table will be mixed into the module (optional) +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create a module with some embeded libraries +-- MyModule = MyAddon:NewModule("MyModule", "AceEvent-3.0", "AceHook-3.0") +-- +-- -- Create a module with a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- MyModule = MyAddon:NewModule("MyModule", prototype, "AceEvent-3.0", "AceHook-3.0") +function NewModule(self, name, prototype, ...) + if type(name) ~= "string" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - string expected got '%s'."):format(type(name)), 2) end + if type(prototype) ~= "string" and type(prototype) ~= "table" and type(prototype) ~= "nil" then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'prototype' - table (prototype), string (lib) or nil expected got '%s'."):format(type(prototype)), 2) end + + if self.modules[name] then error(("Usage: NewModule(name, [prototype, [lib, lib, lib, ...]): 'name' - Module '%s' already exists."):format(name), 2) end + + -- modules are basically addons. We treat them as such. They will be added to the initializequeue properly as well. + -- NewModule can only be called after the parent addon is present thus the modules will be initialized after their parent is. + local module = AceAddon:NewAddon(fmt("%s_%s", self.name or tostring(self), name)) + + module.IsModule = IsModuleTrue + module:SetEnabledState(self.defaultModuleState) + module.moduleName = name + + if type(prototype) == "string" then + AceAddon:EmbedLibraries(module, prototype, ...) + else + AceAddon:EmbedLibraries(module, ...) + end + AceAddon:EmbedLibraries(module, unpack(self.defaultModuleLibraries)) + + if not prototype or type(prototype) == "string" then + prototype = self.defaultModulePrototype or nil + end + + if type(prototype) == "table" then + local mt = getmetatable(module) + mt.__index = prototype + setmetatable(module, mt) -- More of a Base class type feel. + end + + safecall(self.OnModuleCreated, self, module) -- Was in Ace2 and I think it could be a cool thing to have handy. + self.modules[name] = module + tinsert(self.orderedModules, module) + + return module +end + +--- Returns the real name of the addon or module, without any prefix. +-- @name //addon//:GetName +-- @paramsig +-- @usage +-- print(MyAddon:GetName()) +-- -- prints "MyAddon" +function GetName(self) + return self.moduleName or self.name +end + +--- Enables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:EnableAddon(), thus dispatching a OnEnable callback +-- and enabling all modules of the addon (unless explicitly disabled).\\ +-- :Enable() also sets the internal `enableState` variable to true +-- @name //addon//:Enable +-- @paramsig +-- @usage +-- -- Enable MyModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +function Enable(self) + self:SetEnabledState(true) + + -- nevcairiel 2013-04-27: don't enable an addon/module if its queued for init still + -- it'll be enabled after the init process + if not queuedForInitialization(self) then + return AceAddon:EnableAddon(self) + end +end + +--- Disables the Addon, if possible, return true or false depending on success. +-- This internally calls AceAddon:DisableAddon(), thus dispatching a OnDisable callback +-- and disabling all modules of the addon.\\ +-- :Disable() also sets the internal `enableState` variable to false +-- @name //addon//:Disable +-- @paramsig +-- @usage +-- -- Disable MyAddon +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:Disable() +function Disable(self) + self:SetEnabledState(false) + return AceAddon:DisableAddon(self) +end + +--- Enables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Enable` on the module object. +-- @name //addon//:EnableModule +-- @paramsig name +-- @usage +-- -- Enable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Enable() +-- +-- -- Enable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:EnableModule("MyModule") +function EnableModule(self, name) + local module = self:GetModule( name ) + return module:Enable() +end + +--- Disables the Module, if possible, return true or false depending on success. +-- Short-hand function that retrieves the module via `:GetModule` and calls `:Disable` on the module object. +-- @name //addon//:DisableModule +-- @paramsig name +-- @usage +-- -- Disable MyModule using :GetModule +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyModule = MyAddon:GetModule("MyModule") +-- MyModule:Disable() +-- +-- -- Disable MyModule using the short-hand +-- MyAddon = LibStub("AceAddon-3.0"):GetAddon("MyAddon") +-- MyAddon:DisableModule("MyModule") +function DisableModule(self, name) + local module = self:GetModule( name ) + return module:Disable() +end + +--- Set the default libraries to be mixed into all modules created by this object. +-- Note that you can only change the default module libraries before any module is created. +-- @name //addon//:SetDefaultModuleLibraries +-- @paramsig lib[, lib, ...] +-- @param lib List of libraries to embed into the addon +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Configure default libraries for modules (all modules need AceEvent-3.0) +-- MyAddon:SetDefaultModuleLibraries("AceEvent-3.0") +-- -- Create a module +-- MyModule = MyAddon:NewModule("MyModule") +function SetDefaultModuleLibraries(self, ...) + if next(self.modules) then + error("Usage: SetDefaultModuleLibraries(...): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleLibraries = {...} +end + +--- Set the default state in which new modules are being created. +-- Note that you can only change the default state before any module is created. +-- @name //addon//:SetDefaultModuleState +-- @paramsig state +-- @param state Default state for new modules, true for enabled, false for disabled +-- @usage +-- -- Create the addon object +-- MyAddon = LibStub("AceAddon-3.0"):NewAddon("MyAddon") +-- -- Set the default state to "disabled" +-- MyAddon:SetDefaultModuleState(false) +-- -- Create a module and explicilty enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +function SetDefaultModuleState(self, state) + if next(self.modules) then + error("Usage: SetDefaultModuleState(state): cannot change the module defaults after a module has been registered.", 2) + end + self.defaultModuleState = state +end + +--- Set the default prototype to use for new modules on creation. +-- Note that you can only change the default prototype before any module is created. +-- @name //addon//:SetDefaultModulePrototype +-- @paramsig prototype +-- @param prototype Default prototype for the new modules (table) +-- @usage +-- -- Define a prototype +-- local prototype = { OnEnable = function(self) print("OnEnable called!") end } +-- -- Set the default prototype +-- MyAddon:SetDefaultModulePrototype(prototype) +-- -- Create a module and explicitly Enable it +-- MyModule = MyAddon:NewModule("MyModule") +-- MyModule:Enable() +-- -- should print "OnEnable called!" now +-- @see NewModule +function SetDefaultModulePrototype(self, prototype) + if next(self.modules) then + error("Usage: SetDefaultModulePrototype(prototype): cannot change the module defaults after a module has been registered.", 2) + end + if type(prototype) ~= "table" then + error(("Usage: SetDefaultModulePrototype(prototype): 'prototype' - table expected got '%s'."):format(type(prototype)), 2) + end + self.defaultModulePrototype = prototype +end + +--- Set the state of an addon or module +-- This should only be called before any enabling actually happend, e.g. in/before OnInitialize. +-- @name //addon//:SetEnabledState +-- @paramsig state +-- @param state the state of an addon or module (enabled=true, disabled=false) +function SetEnabledState(self, state) + self.enabledState = state +end + + +--- Return an iterator of all modules associated to the addon. +-- @name //addon//:IterateModules +-- @paramsig +-- @usage +-- -- Enable all modules +-- for name, module in MyAddon:IterateModules() do +-- module:Enable() +-- end +local function IterateModules(self) return pairs(self.modules) end + +-- Returns an iterator of all embeds in the addon +-- @name //addon//:IterateEmbeds +-- @paramsig +local function IterateEmbeds(self) return pairs(AceAddon.embeds[self]) end + +--- Query the enabledState of an addon. +-- @name //addon//:IsEnabled +-- @paramsig +-- @usage +-- if MyAddon:IsEnabled() then +-- MyAddon:Disable() +-- end +local function IsEnabled(self) return self.enabledState end +local mixins = { + NewModule = NewModule, + GetModule = GetModule, + Enable = Enable, + Disable = Disable, + EnableModule = EnableModule, + DisableModule = DisableModule, + IsEnabled = IsEnabled, + SetDefaultModuleLibraries = SetDefaultModuleLibraries, + SetDefaultModuleState = SetDefaultModuleState, + SetDefaultModulePrototype = SetDefaultModulePrototype, + SetEnabledState = SetEnabledState, + IterateModules = IterateModules, + IterateEmbeds = IterateEmbeds, + GetName = GetName, +} +local function IsModule(self) return false end +local pmixins = { + defaultModuleState = true, + enabledState = true, + IsModule = IsModule, +} +-- Embed( target ) +-- target (object) - target object to embed aceaddon in +-- +-- this is a local function specifically since it's meant to be only called internally +function Embed(target, skipPMixins) + for k, v in pairs(mixins) do + target[k] = v + end + if not skipPMixins then + for k, v in pairs(pmixins) do + target[k] = target[k] or v + end + end +end + + +-- - Initialize the addon after creation. +-- This function is only used internally during the ADDON_LOADED event +-- It will call the **OnInitialize** function on the addon object (if present), +-- and the **OnEmbedInitialize** function on all embeded libraries. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- @param addon addon object to intialize +function AceAddon:InitializeAddon(addon) + safecall(addon.OnInitialize, addon) + + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedInitialize, lib, addon) end + end + + -- we don't call InitializeAddon on modules specifically, this is handled + -- from the event handler and only done _once_ +end + +-- - Enable the addon after creation. +-- Note: This function is only used internally during the PLAYER_LOGIN event, or during ADDON_LOADED, +-- if IsLoggedIn() already returns true at that point, e.g. for LoD Addons. +-- It will call the **OnEnable** function on the addon object (if present), +-- and the **OnEmbedEnable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is disabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Enable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:EnableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if self.statuses[addon.name] or not addon.enabledState then return false end + + -- set the statuses first, before calling the OnEnable. this allows for Disabling of the addon in OnEnable. + self.statuses[addon.name] = true + + safecall(addon.OnEnable, addon) + + -- make sure we're still enabled before continueing + if self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedEnable, lib, addon) end + end + + -- enable possible modules. + local modules = addon.orderedModules + for i = 1, #modules do + self:EnableAddon(modules[i]) + end + end + return self.statuses[addon.name] -- return true if we're disabled +end + +-- - Disable the addon +-- Note: This function is only used internally. +-- It will call the **OnDisable** function on the addon object (if present), +-- and the **OnEmbedDisable** function on all embeded libraries.\\ +-- This function does not toggle the enable state of the addon itself, and will return early if the addon is still enabled. +-- +-- **Note:** Do not call this function manually, unless you're absolutely sure that you know what you are doing. +-- Use :Disable on the addon itself instead. +-- @param addon addon object to enable +function AceAddon:DisableAddon(addon) + if type(addon) == "string" then addon = AceAddon:GetAddon(addon) end + if not self.statuses[addon.name] then return false end + + -- set statuses first before calling OnDisable, this allows for aborting the disable in OnDisable. + self.statuses[addon.name] = false + + safecall( addon.OnDisable, addon ) + + -- make sure we're still disabling... + if not self.statuses[addon.name] then + local embeds = self.embeds[addon] + for i = 1, #embeds do + local lib = LibStub:GetLibrary(embeds[i], true) + if lib then safecall(lib.OnEmbedDisable, lib, addon) end + end + -- disable possible modules. + local modules = addon.orderedModules + for i = 1, #modules do + self:DisableAddon(modules[i]) + end + end + + return not self.statuses[addon.name] -- return true if we're disabled +end + +--- Get an iterator over all registered addons. +-- @usage +-- -- Print a list of all installed AceAddon's +-- for name, addon in AceAddon:IterateAddons() do +-- print("Addon: " .. name) +-- end +function AceAddon:IterateAddons() return pairs(self.addons) end + +--- Get an iterator over the internal status registry. +-- @usage +-- -- Print a list of all enabled addons +-- for name, status in AceAddon:IterateAddonStatus() do +-- if status then +-- print("EnabledAddon: " .. name) +-- end +-- end +function AceAddon:IterateAddonStatus() return pairs(self.statuses) end + +-- Following Iterators are deprecated, and their addon specific versions should be used +-- e.g. addon:IterateEmbeds() instead of :IterateEmbedsOnAddon(addon) +function AceAddon:IterateEmbedsOnAddon(addon) return pairs(self.embeds[addon]) end +function AceAddon:IterateModulesOfAddon(addon) return pairs(addon.modules) end + +-- Event Handling +local function onEvent(this, event, arg1) + -- 2011-08-17 nevcairiel - ignore the load event of Blizzard_DebugTools, so a potential startup error isn't swallowed up + if (event == "ADDON_LOADED" and arg1 ~= "Blizzard_DebugTools") or event == "PLAYER_LOGIN" then + -- if a addon loads another addon, recursion could happen here, so we need to validate the table on every iteration + while(#AceAddon.initializequeue > 0) do + local addon = tremove(AceAddon.initializequeue, 1) + -- this might be an issue with recursion - TODO: validate + if event == "ADDON_LOADED" then addon.baseName = arg1 end + AceAddon:InitializeAddon(addon) + tinsert(AceAddon.enablequeue, addon) + end + + if IsLoggedIn() then + while(#AceAddon.enablequeue > 0) do + local addon = tremove(AceAddon.enablequeue, 1) + AceAddon:EnableAddon(addon) + end + end + end +end + +AceAddon.frame:RegisterEvent("ADDON_LOADED") +AceAddon.frame:RegisterEvent("PLAYER_LOGIN") +AceAddon.frame:SetScript("OnEvent", onEvent) + +-- upgrade embeded +for name, addon in pairs(AceAddon.addons) do + Embed(addon, true) +end + +-- 2010-10-27 nevcairiel - add new "orderedModules" table +if oldminor and oldminor < 10 then + for name, addon in pairs(AceAddon.addons) do + addon.orderedModules = {} + for module_name, module in pairs(addon.modules) do + tinsert(addon.orderedModules, module) + end + end +end diff --git a/TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.xml b/TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.xml new file mode 100644 index 0000000..e6ad639 --- /dev/null +++ b/TradeSkillMaster/Libs/AceAddon-3.0/AceAddon-3.0.xml @@ -0,0 +1,4 @@ + +