--------------------------------------------------------- -- Module declaration local HandyNotes = LibStub("AceAddon-3.0"):GetAddon("HandyNotes") local HN = HandyNotes:NewModule("HandyNotes", "AceEvent-3.0", "AceConsole-3.0") local L = LibStub("AceLocale-3.0"):GetLocale("HandyNotes", false) local HBD = LibStub("HereBeDragons-2.0") local PIN_DRAG_SCALE = 1.2 local WoWRetail = (WOW_PROJECT_ID == WOW_PROJECT_MAINLINE) --------------------------------------------------------- -- Our db upvalue and db defaults local db local dbdata local defaults = { global = { ["*"] = {}, -- ["mapFile"] = {[coord] = {note data}, [coord] = {note data}} }, profile = { icon_scale = 1.0, icon_alpha = 1.0, }, } --------------------------------------------------------- -- Localize some globals local next = next local wipe = wipe local GameTooltip = GameTooltip --------------------------------------------------------- -- Constants -- An addon can replace this table or add to it directly, but keep in mind -- notes are currently stored with the index number of the chosen icon. HN.icons = { [1] = {text = RAID_TARGET_1, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0, tCoordRight = 0.25, tCoordTop = 0, tCoordBottom = 0.25}, -- Star [2] = {text = RAID_TARGET_2, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.25, tCoordRight = 0.5, tCoordTop = 0, tCoordBottom = 0.25}, -- Circle [3] = {text = RAID_TARGET_3, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.5, tCoordRight = 0.75, tCoordTop = 0, tCoordBottom = 0.25}, -- Diamond [4] = {text = RAID_TARGET_4, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.75, tCoordRight = 1, tCoordTop = 0, tCoordBottom = 0.25}, -- Triangle [5] = {text = RAID_TARGET_5, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0, tCoordRight = 0.25, tCoordTop = 0.25, tCoordBottom = 0.5}, -- Moon [6] = {text = RAID_TARGET_6, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.25, tCoordRight = 0.5, tCoordTop = 0.25, tCoordBottom = 0.5}, -- Square [7] = {text = RAID_TARGET_7, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.5, tCoordRight = 0.75, tCoordTop = 0.25, tCoordBottom = 0.5}, -- Cross [8] = {text = RAID_TARGET_8, icon = "Interface\\TargetingFrame\\UI-RaidTargetingIcons", tCoordLeft = 0.75, tCoordRight = 1, tCoordTop = 0.25, tCoordBottom = 0.5}, -- Skull [9] = {text = MINIMAP_TRACKING_AUCTIONEER, icon = "Interface\\Minimap\\Tracking\\Auctioneer"}, [10] = {text = MINIMAP_TRACKING_BANKER, icon = "Interface\\Minimap\\Tracking\\Banker"}, [11] = {text = MINIMAP_TRACKING_BATTLEMASTER, icon = "Interface\\Minimap\\Tracking\\BattleMaster"}, [12] = {text = MINIMAP_TRACKING_FLIGHTMASTER, icon = "Interface\\Minimap\\Tracking\\FlightMaster"}, [13] = {text = MINIMAP_TRACKING_INNKEEPER, icon = "Interface\\Minimap\\Tracking\\Innkeeper"}, [14] = {text = MINIMAP_TRACKING_MAILBOX, icon = "Interface\\Minimap\\Tracking\\Mailbox"}, [15] = {text = MINIMAP_TRACKING_REPAIR, icon = "Interface\\Minimap\\Tracking\\Repair"}, [16] = {text = MINIMAP_TRACKING_STABLEMASTER, icon = "Interface\\Minimap\\Tracking\\StableMaster"}, [17] = {text = MINIMAP_TRACKING_TRAINER_CLASS, icon = "Interface\\Minimap\\Tracking\\Class"}, [18] = {text = MINIMAP_TRACKING_TRAINER_PROFESSION, icon = "Interface\\Minimap\\Tracking\\Profession"}, [19] = {text = MINIMAP_TRACKING_TRIVIAL_QUESTS, icon = "Interface\\Minimap\\Tracking\\TrivialQuests"}, [20] = {text = MINIMAP_TRACKING_VENDOR_AMMO, icon = "Interface\\Minimap\\Tracking\\Ammunition"}, [21] = {text = MINIMAP_TRACKING_VENDOR_FOOD, icon = "Interface\\Minimap\\Tracking\\Food"}, [22] = {text = MINIMAP_TRACKING_VENDOR_POISON, icon = "Interface\\Minimap\\Tracking\\Poisons"}, [23] = {text = MINIMAP_TRACKING_VENDOR_REAGENT, icon = "Interface\\Minimap\\Tracking\\Reagents"}, [24] = {text = FACTION_ALLIANCE, icon = "Interface\\TargetingFrame\\UI-PVP-Alliance", tCoordLeft = 0.05, tCoordRight = 0.65, tCoordTop = 0, tCoordBottom = 0.6}, [25] = {text = FACTION_HORDE, icon = "Interface\\TargetingFrame\\UI-PVP-Horde", tCoordLeft = 0.05, tCoordRight = 0.65, tCoordTop = 0, tCoordBottom = 0.6}, [26] = {text = FACTION_STANDING_LABEL4, icon = "Interface\\TargetingFrame\\UI-PVP-FFA", tCoordLeft = 0.05, tCoordRight = 0.65, tCoordTop = 0, tCoordBottom = 0.6}, [27] = {text = ARENA, icon = "Interface\\PVPFrame\\PVP-ArenaPoints-Icon"}, [28] = {text = L["Portal"], icon = "Interface\\Icons\\Spell_Arcane_PortalDalaran"}, } --------------------------------------------------------- -- Plugin Handlers to HandyNotes local HNHandler = {} function HNHandler:OnEnter(mapID, coord) if ( self:GetCenter() > UIParent:GetCenter() ) then -- compare X coordinate GameTooltip:SetOwner(self, "ANCHOR_LEFT") else GameTooltip:SetOwner(self, "ANCHOR_RIGHT") end local title = dbdata[mapID][coord].title local desc = dbdata[mapID][coord].desc if title == "" and desc == "" then title = L["(No Title)"] end if title == "" and desc ~= "" then title = desc desc = nil end GameTooltip:SetText(title) GameTooltip:AddLine(desc, nil, nil, nil, true) GameTooltip:Show() end function HNHandler:OnLeave(mapID, coord) GameTooltip:Hide() end local function deletePin(button, mapID, coord) local HNEditFrame = HN.HNEditFrame if HNEditFrame.coord == coord and HNEditFrame.mapID == mapID then HNEditFrame:Hide() end dbdata[mapID][coord] = nil HN:SendMessage("HandyNotes_NotifyUpdate", "HandyNotes") end local function editPin(button, mapID, coord) local HNEditFrame = HN.HNEditFrame HNEditFrame.x, HNEditFrame.y = HandyNotes:getXY(coord) HNEditFrame.coord = coord HNEditFrame.mapID = mapID HNEditFrame:Hide() -- Hide first to trigger the OnShow handler HNEditFrame:Show() end local function addTomTomWaypoint(button, mapID, coord) if TomTom then local x, y = HandyNotes:getXY(coord) TomTom:AddWaypoint(mapID, x, y, { title = dbdata[mapID][coord].title, persistent = nil, minimap = true, world = true }) end end do local isMoving = false local movingPinScale = nil local info = {} local clickedMapID = nil local clickedCoord = nil local function generateMenu(button, level) if (not level) then return end for k in pairs(info) do info[k] = nil end if (level == 1) then -- Create the title of the menu info.isTitle = 1 info.text = L["HandyNotes"] info.notCheckable = 1 local t = HN.icons[dbdata[clickedMapID][clickedCoord].icon] info.icon = t.icon info.tCoordLeft = t.tCoordLeft info.tCoordRight = t.tCoordRight info.tCoordTop = t.tCoordTop info.tCoordBottom = t.tCoordBottom UIDropDownMenu_AddButton(info, level) -- Edit menu item info.disabled = nil info.isTitle = nil info.notCheckable = nil info.icon = nil info.tCoordLeft = nil info.tCoordRight = nil info.tCoordTop = nil info.tCoordBottom = nil info.text = L["Edit Handy Note"] info.func = editPin info.arg1 = clickedMapID info.arg2 = clickedCoord UIDropDownMenu_AddButton(info, level) -- Delete menu item info.text = L["Delete Handy Note"] info.func = deletePin info.arg1 = clickedMapID info.arg2 = clickedCoord UIDropDownMenu_AddButton(info, level) if TomTom then info.text = L["Add this location to TomTom waypoints"] info.func = addTomTomWaypoint info.arg1 = clickedMapID info.arg2 = clickedCoord UIDropDownMenu_AddButton(info, level) end -- Close menu item info.text = CLOSE info.func = function() CloseDropDownMenus() end info.arg1 = nil info.arg2 = nil info.notCheckable = 1 UIDropDownMenu_AddButton(info, level) -- Add the dragging hint info.isTitle = 1 info.func = nil info.text = L["|cFF00FF00Hint: |cffeda55fCtrl+Shift+LeftDrag|cFF00FF00 to move a note"] UIDropDownMenu_AddButton(info, level) end end local HandyNotes_HandyNotesDropdownMenu = CreateFrame("Frame", "HandyNotes_HandyNotesDropdownMenu") HandyNotes_HandyNotesDropdownMenu.displayMode = "MENU" HandyNotes_HandyNotesDropdownMenu.initialize = generateMenu function HNHandler:OnClick(button, down, mapID, coord) if button == "RightButton" and not down then clickedMapID = mapID clickedCoord = coord ToggleDropDownMenu(1, nil, HandyNotes_HandyNotesDropdownMenu, self, 0, 0) elseif button == "LeftButton" and down and IsControlKeyDown() and IsShiftKeyDown() then -- Only move if we're viewing the same map as the icon's map if mapID == WorldMapFrame:GetMapID() then isMoving = true movingPinScale = self:GetScale() local x, y = self:GetCenter() local s = self:GetEffectiveScale() / UIParent:GetEffectiveScale() self:ClearAllPoints() self:SetParent(UIParent) self:SetScale(PIN_DRAG_SCALE) self:SetPoint("CENTER", UIParent, "BOTTOMLEFT", x * s / PIN_DRAG_SCALE, y * s / PIN_DRAG_SCALE) self:SetFrameStrata("TOOLTIP") self:StartMoving() end elseif isMoving and not down then isMoving = false self:StopMovingOrSizing() local x, y = self:GetCenter() if movingPinScale then x = x * PIN_DRAG_SCALE / movingPinScale y = y * PIN_DRAG_SCALE / movingPinScale self:SetScale(movingPinScale) movingPinScale = nil end local s = self:GetEffectiveScale() / WorldMapFrame.ScrollContainer.Child:GetEffectiveScale() x = x * s - WorldMapFrame.ScrollContainer.Child:GetLeft() y = y * s - WorldMapFrame.ScrollContainer.Child:GetTop() self:ClearAllPoints() self:SetParent(WorldMapFrame.ScrollContainer.Child) self:SetPoint("CENTER", WorldMapFrame.ScrollContainer.Child, "TOPLEFT", x / self:GetScale(), y / self:GetScale()) self:SetFrameStrata("TOOLTIP") self:SetUserPlaced(false) -- Get the new coordinate x = x / WorldMapFrame.ScrollContainer.Child:GetWidth() y = -y / WorldMapFrame.ScrollContainer.Child:GetHeight() -- Move the button back into the map if it was dragged outside if x < 0.001 then x = 0.001 end if x > 0.999 then x = 0.999 end if y < 0.001 then y = 0.001 end if y > 0.999 then y = 0.999 end local newCoord = HandyNotes:getCoord(x, y) -- Search in 4 directions till we find an unused coord local count = 0 local zoneData = dbdata[mapID] while true do if not zoneData[newCoord + count] then zoneData[newCoord + count] = zoneData[coord] break elseif not zoneData[newCoord - count] then zoneData[newCoord - count] = zoneData[coord] break elseif not zoneData[newCoord + count * 10000] then zoneData[newCoord + count*10000] = zoneData[coord] break elseif not zoneData[newCoord - count * 10000] then zoneData[newCoord - count*10000] = zoneData[coord] break end count = count + 1 end dbdata[mapID][coord] = nil HN:SendMessage("HandyNotes_NotifyUpdate", "HandyNotes") end end end do local tablepool = setmetatable({}, {__mode = 'k'}) -- This is a custom iterator we use to iterate over every node in a given zone local function iter(t, prestate) if not t then return end local data = t.data local state, value = next(data, prestate) if value then return state, nil, HN.icons[value.icon], db.icon_scale, db.icon_alpha end wipe(t) tablepool[t] = true end -- This is a funky custom iterator we use to iterate over every zone's nodes -- in a given continent + the continent itself local function iterCont(t, prestate) if not t then return end local zone = t.C[t.Z] local data = dbdata[zone] local state, value while zone do if data then -- Only if there is data for this zone state, value = next(data, prestate) while state do -- Have we reached the end of this zone? if value.cont or zone == t.contId then -- Show on continent? return state, zone, HN.icons[value.icon], db.icon_scale, db.icon_alpha end state, value = next(data, state) -- Get next data end end -- Get next zone t.Z = next(t.C, t.Z) zone = t.C[t.Z] data = dbdata[zone] prestate = nil end wipe(t) tablepool[t] = true end function HNHandler:GetNodes2(uiMapId, minimap) local C = HandyNotes:GetContinentZoneList(uiMapId) -- Is this a continent? if C then local tbl = next(tablepool) or {} tablepool[tbl] = nil tbl.C = C tbl.Z = next(C) tbl.contId = uiMapId return iterCont, tbl, nil else -- It is a zone local tbl = next(tablepool) or {} tablepool[tbl] = nil tbl.data = dbdata[uiMapId] return iter, tbl, nil end end end --------------------------------------------------------- -- HandyNotes core function HN.OnCanvasClicked(mapCanvas, button, cursorX, cursorY) local self = HN if button == "RightButton" and IsAltKeyDown() and not IsControlKeyDown() and not IsShiftKeyDown() then local coord = HandyNotes:getCoord(cursorX, cursorY) local x, y = HandyNotes:getXY(coord) -- Pass the data to the edit note frame local HNEditFrame = self.HNEditFrame HNEditFrame.x = x HNEditFrame.y = y HNEditFrame.coord = coord HNEditFrame.mapID = mapCanvas:GetMapID() HNEditFrame:Hide() -- Hide first to trigger the OnShow handler HNEditFrame:Show() return true end return false end -- Function to create a note where the player is function HN:CreateNoteHere(arg1) local mapID, x, y if arg1 ~= "" then -- Coordinates entered x, y = string.match(strtrim(arg1), "([%d.]+)[, ]+([%d.]+)") x, y = tonumber(x), tonumber(y) if x and y and x > 1 and x < 100 and y > 1 and y < 100 then -- Normalize coordinates to between 0 and 1 x, y = x/100, y/100 end if not x or not y or x <= 0 or x >= 1 or y <= 0 or y >= 1 then self:Print(L["Syntax:"].." /hnnew [x, y]") return end mapID = HBD:GetPlayerZone() else -- No coordinates entered, get the coordinates of player x, y, mapID = HBD:GetPlayerZonePosition() end if mapID and x and y then local coord = HandyNotes:getCoord(x, y) x, y = HandyNotes:getXY(coord) -- Pass the data to the edit note frame local HNEditFrame = self.HNEditFrame HNEditFrame.x = x HNEditFrame.y = y HNEditFrame.coord = coord HNEditFrame.mapID = mapID HNEditFrame:Hide() -- Hide first to trigger the OnShow handler HNEditFrame:Show() else self:Print(L["ERROR_CREATE_NOTE1"]) end end --------------------------------------------------------- -- Options table local options = { type = "group", name = L["HandyNotes"], desc = L["HandyNotes"], get = function(info) return db[info.arg] end, set = function(info, v) db[info.arg] = v HN:SendMessage("HandyNotes_NotifyUpdate", "HandyNotes") end, args = { desc = { name = L["These settings control the look and feel of the HandyNotes icons."], type = "description", order = 0, }, icon_scale = { type = "range", name = L["Icon Scale"], desc = L["The scale of the icons"], min = 0.25, max = 2, step = 0.01, arg = "icon_scale", order = 10, }, icon_alpha = { type = "range", name = L["Icon Alpha"], desc = L["The alpha transparency of the icons"], min = 0, max = 1, step = 0.01, arg = "icon_alpha", order = 20, }, }, } --------------------------------------------------------- -- Addon initialization, enabling and disabling function HN:OnInitialize() -- Set up our database self.db = LibStub("AceDB-3.0"):New("HandyNotes_HandyNotesDB", defaults) db = self.db.profile dbdata = self.db.global -- migrate data, if neccessary local HBDMigrate = LibStub("HereBeDragons-Migrate") local migration = {} for zone in pairs(dbdata) do if type(zone) == "string" then migration[zone] = true end end for zone in pairs(migration) do local data = dbdata[zone] dbdata[zone] = nil local stripped_zone = zone:gsub("_terrain%d+$", "") for coord, info in pairs(data) do local newzone = HBDMigrate:GetUIMapIDFromMapFile(stripped_zone, info.level) if newzone then info.level = nil if not dbdata[newzone] then dbdata[newzone] = {} end dbdata[newzone][coord] = info end end end HNData = dbdata -- Initialize our database with HandyNotes HandyNotes:RegisterPluginDB("HandyNotes", HNHandler, options) --WorldMapMagnifyingGlassButton:SetText(WorldMapMagnifyingGlassButton:GetText() .. L["\nAlt+Right Click To Add a HandyNote"]) if WoWRetail then -- Work-around for taint from canvas click handlers self.ClickHandlerFrame = CreateFrame("Frame", nil, WorldMapFrame.ScrollContainer) self.ClickHandlerFrame:SetAllPoints() self.ClickHandlerFrame.UpdateCapture = function(f) -- only Alt and no other keys, then show our capture frame if IsAltKeyDown() and not IsControlKeyDown() and not IsShiftKeyDown() then f:Show() else f:Hide() end end self.ClickHandlerFrame:RegisterEvent("MODIFIER_STATE_CHANGED") self.ClickHandlerFrame:SetScript("OnEvent", self.ClickHandlerFrame.UpdateCapture) -- fallback check OnUpdate. It'll only run while the capture frame is visible, and hide if the modifiers change self.ClickHandlerFrame:SetScript("OnUpdate", self.ClickHandlerFrame.UpdateCapture) -- always pass through buttons other then right click self.ClickHandlerFrame:SetPassThroughButtons("LeftButton", "MiddleButton", "Button4", "Button5") self.ClickHandlerFrame:UpdateCapture() self.ClickHandlerFrame:SetScript("OnMouseUp", function(f, button) HN.OnCanvasClicked(WorldMapFrame, button, WorldMapFrame.ScrollContainer:NormalizeUIPosition(WorldMapFrame.ScrollContainer:GetCursorPosition())) end) else WorldMapFrame:AddCanvasClickHandler(self.OnCanvasClicked) end -- Slash command self:RegisterChatCommand("hnnew", "CreateNoteHere") end function HN:OnEnable() end function HN:OnDisable() end