diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c355a12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Gradle +.gradle +build/ + +# Android built artifacts +*.apk +*.ap_ +*.dex + +# Java build artifacts class files +*.class + +# other generated files +gen/ +target/ + +# local configuration file (for Android sdk path, etc) +local.properties + +# OSX files +.DS_Store + +# Eclipse +/.classpath +/.settings/ +/.project +/.metadata +bin/ + +# IntelliJ +*.iws +*.uml +.idea/ +out/ + +# NDK +obj/ + +# Misc +twitch/ +*.log +*.graphml +coverage.db* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6c9ba21 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +# Grichelde + diff --git a/Grichelde.lua b/Grichelde.lua new file mode 100644 index 0000000..7a89836 --- /dev/null +++ b/Grichelde.lua @@ -0,0 +1,500 @@ +--[[-------------------------------------------------------------------------- + Grichelde + Copyright 2020 Teilzeit-Jedi + + based on Misspelled developed by Nathan Pieper - nrpieper (@) gmail (dot) com + + This code freely distributed for your use in any GPL compliant project. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--------------------------------------------------------------------------]] -- + +local Grichelde = {} +Grichelde = LibStub("AceAddon-3.0"):NewAddon("Grichelde", "AceEvent-3.0", "AceHook-3.0") +Grichelde.Version = "0.1.0" + +local AceGUI = LibStub("AceGUI-3.0") +local L = LibStub("AceLocale-3.0"):GetLocale("Grichelde", true) + +-- faster function lookups by mapping to local refs +local string_find = string.find +local string_gsub = string.gsub +local string_len = string.len +local string_rep = string.rep +local string_sub = string.sub +local tostring = string.tostring +local tContains = tContains +local strtrim = strtrim +local pairs = table.pairs +local ipairs = table.ipairs + +local Grichelde_Saved_CTL_SendChatMessage +local Grichelde_CTL_hookedversion = 0 + +local Grichelde_ChatTypes = { "SAY", "EMOTE", "YELL", "PARTY", "GUILD", "OFFICER", "RAID", "RAID_WARNING", "INSTANCE_CHAT", "BATTLEGROUND", "WHISPER" } +local Grichelde_ChatCommands = { "/s", "/e", "/me", "/y", "/p", "/pl", "/g", "/o", "/raid", "/rl", "/rw", "/i", "bg", "/w", "/r", "/tt" } + +-- do not replace these patterns +local Grichelde_IgnorePatterns = { + "|[Cc]%x%x%x%x%x%x%x%x.-|r", -- colored items (or links) + "|H.-|h", -- item links (http://www.wowwiki.com/ItemLink) + "|T.-|t", -- textures + "|n", -- newline + "{rt[1-8]}", -- rumbered raid target icons + "{Star}", -- named raid target icon 1 + "{Circle}", -- named raid target icon 2 + "{Coin}", -- named raid target icon 2 + "{Diamond}", -- named raid target icon 3 + "{Triangle}", -- named raid target icon 4 + "{Moon}", -- named raid target icon 5 + "{Square}", -- named raid target icon 6 + "{Cross}", -- named raid target icon 7 + "{X}", -- named raid target icon 7 + "{Skull}" -- named raid target icon 8 +} + +function Grichelde:OnInitialize() + -- print("Grichelde was loaded") + + -- Build Interface Options window + self:CreateInterfaceOptions() + + -- Watch for WIM and Prat to Load, then integrate + Grichelde:RegisterEvent("ADDON_LOADED") + + -- hooks for removing any misspelled word highlighting in the text before the chat message is sent + -- The WoW client will disconnect if you attempt to send a color tags in a chat message. + Grichelde:RawHook("SendChatMessage", true) +end + +--- @param event string +--- @param addonName string +function Grichelde:ADDON_LOADED(event, addonName) + if event == "ADDON_LOADED" and addonName == "WIM" then + WIM.RegisterWidgetTrigger("msg_box", "whisper,chat,w2w", "OnEnterPressed", Grichelde.EditBox_OnEnterPressed) + + -- WIM sends its chat messages via the API ChatThrottleLib, which itself hooks the default SendChatMessage api + -- many times before Grichelde will. ChatThrottleLib might potentially load before Grichelde, so we just hook + -- into ChatThrottleLib to be on the safe side. + + if (WIM.RegisterPreSendFilterText) then -- avoid error if WIM not up to date. + WIM.RegisterPreSendFilterText(function(text) + return Grichelde:ReplaceText(text) + end) + else + if (ChatThrottleLib and Grichelde_CTL_hookedversion < ChatThrottleLib.version) then + Grichelde_Saved_CTL_SendChatMessage = ChatThrottleLib.SendChatMessage + + function ChatThrottleLib:SendChatMessage(prio, prefix, text, ...) + text = Grichelde:ReplaceText(text) + -- print("Grichelde Hooked ChatThrottleLib_SendChatMessaged called") + return Grichelde_Saved_CTL_SendChatMessage(ChatThrottleLib, prio, prefix, text, ...) + end + + Grichelde_CTL_hookedversion = ChatThrottleLib.version + end + end + end +end + +--- Before a chat message is sent, check if replacement is required and replace the text accordingly. +--- @param message string +--- @param type string +--- @param language string +--- @param channel string +function Grichelde:SendChatMessage(message, type, language, channel, ...) + local text = strtrim(message) + + if (Grichelde:CheckReplacement(message, type)) then + text = Grichelde:ReplaceText(text) + end + + self.hooks["SendChatMessage"](text, type, language, channel, ...); +end + +function Grichelde:CheckReplacement(text, type) + -- todo: globally disabled? + + -- check type + if (not tContains(Grichelde_ChatTypes, type)) then + return false + end + + -- don't replace slash commands except chat related commands + if string_sub(text, 1, 1) == "/" then + local firstWord, _ = Grichelde:GetNextWord(text) + if (firstWord == nil or not tContains(Grichelde_ChatCommands, firstWord)) then + return false + end + end + + return true +end + +--- Replaces all character occurrences for which replacements have been defined in the options, +--- while preserving any itemLinks or textures. (http://www.wowwiki.com/ItemLink) +--- @param text string +--- @return string +function Grichelde:ReplaceText(text) + local finalText = "" + local newText = text + + -- don't replace non-chat related slash commands + local firstWord, line = Grichelde:GetNextWord(text) + if (tContains(Grichelde_ChatCommands, firstWord)) then + -- skip chat slash command + finalText = finalText .. firstWord .. ' ' + newText = line + end + + local current = 1 + local lastStart = 1 + + while current < string_len(newText) do + local currentChar = string_sub(newText, current, current) + if currentChar ~= '|' and currentChar ~= '{' then + current = current + 1 + else + -- lookahead-check for itemLinks, textures and raid target icons + local textAhead = string_sub(newText, current) + local posEnd = Grichelde:CheckForPreversableText(textAhead) + if posEnd > 0 then + local textBehind = string_sub(newText, lastStart, current - 1) + local replacement = Grichelde:ReplaceCharacters(textBehind) + local preservedText = string_sub(textAhead, 1, posEnd) + + finalText = finalText .. replacement .. preservedText + current = current + posEnd + lastStart = current + else + -- no corresponding end was found to start pattern, continue loop with next char + current = current + 1 + end + end + end + + -- cleanup to the end + local remainingText = newText + if lastStart ~= 1 then + remainingText = string_sub(newText, lastStart) + end + + local replacement = Grichelde:ReplaceCharacters(remainingText) + finalText = finalText .. replacement + return finalText +end + +--- Checks if the text starts with a preversable ignore pattern, such as itemLinks, textures or raid target icons +--- @param text string +--- @return number +function Grichelde:CheckForPreversableText(text) + -- Calling find on ever pattern might be inefficient but its way less code. + for _, pattern in ipairs(Grichelde_IgnorePatterns) do + local pos1, pos2 = string_find(text, pattern) + if pos1 == 1 and pos2 ~= nil then + return pos2 + end + end + return 0 +end + +--- Replaces all character occurrences for which replacements have been defined in the options +--- @param text string +--- @return string +function Grichelde:ReplaceCharacters(text) + -- todo: read from options + -- todo: case (in)sensitivity + local replacement = text + replacement = string_gsub(replacement, "s", "ch") + replacement = string_gsub(replacement, "S", "Ch") + replacement = string_gsub(replacement, "t", "k") + replacement = string_gsub(replacement, "T", "K") + return replacement +end + + +--[[ Interface Options Window ]] -- +function Grichelde:CreateInterfaceOptions() + local cfgFrame = CreateFrame("FRAME", nil, UIParent) + cfgFrame.name = "Grichelde" + + local cfgFrameHeader = cfgFrame:CreateFontString("OVERLAY", nil, "GameFontNormalLarge") + cfgFrameHeader:SetPoint("TOPLEFT", 15, -15) + cfgFrameHeader:SetText(self.Version) + + local cfgAutoSelectDict = CreateFrame("CHECKBUTTON", "Misspelled_cfgAutoSelectDict", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgAutoSelectDict:SetPoint("TOPLEFT", 20, -40) + Misspelled_cfgAutoSelectDictText:SetText(L["Auto Select Dictionary to Load"]) + Misspelled_cfgAutoSelectDict:SetChecked(Misspelled_DB.AutoSelectDictionary) + Misspelled_cfgAutoSelectDict:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + Misspelled_DB.AutoSelectDictionary = not Misspelled_DB.AutoSelectDictionary + --Toggle the sub options + if Misspelled_DB.AutoSelectDictionary == true then + Misspelled_cfgDictdeDE:Disable() + Misspelled_cfgDictenGB:Disable() + Misspelled_cfgDictenUS:Disable() + Misspelled_cfgDictesES:Disable() + Misspelled_cfgDictfrFR:Disable() + Misspelled_cfgDictruRU:Disable() + Misspelled_cfgDictitIT:Disable() + else + Misspelled_cfgDictdeDE:Enable() + Misspelled_cfgDictenGB:Enable() + Misspelled_cfgDictenUS:Enable() + Misspelled_cfgDictesES:Enable() + Misspelled_cfgDictfrFR:Enable() + Misspelled_cfgDictruRU:Enable() + Misspelled_cfgDictitIT:Enable() + if Misspelled_DB.LoadDictionary == nil or #Misspelled_DB.LoadDictionary == 0 then + Misspelled_DB.LoadDictionary = "enUS" + Misspelled_cfgDictenUS:setChecked(true) + end + end + end) + + local cfgDictdeDE = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictdeDE", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictdeDE:SetPoint("TOPLEFT", 40, -64) + Misspelled_cfgDictdeDEText:SetText("deDE") + Misspelled_cfgDictdeDE:SetChecked(Misspelled_DB.LoadDictionary == "deDE") + Misspelled_cfgDictdeDE:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "deDE" + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + local cfgDictenGB = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictenGB", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictenGB:SetPoint("TOPLEFT", 40, -88) + Misspelled_cfgDictenGBText:SetText("enGB") + Misspelled_cfgDictenGB:SetChecked(Misspelled_DB.LoadDictionary == "enGB") + Misspelled_cfgDictenGB:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "enGB" + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + local cfgDictenUS = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictenUS", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictenUS:SetPoint("TOPLEFT", 40, -112) + Misspelled_cfgDictenUSText:SetText("enUS") + Misspelled_cfgDictenUS:SetChecked(Misspelled_DB.LoadDictionary == "enUS") + Misspelled_cfgDictenUS:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "enUS" + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + local cfgDictesES = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictesES", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictesES:SetPoint("TOPLEFT", 40, -136) + Misspelled_cfgDictesESText:SetText("esES") + Misspelled_cfgDictesES:SetChecked(Misspelled_DB.LoadDictionary == "esES") + Misspelled_cfgDictesES:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "esES" + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + local cfgDictfrFR = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictfrFR", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictfrFR:SetPoint("TOPLEFT", 40, -160) + Misspelled_cfgDictfrFRText:SetText("frFR") + Misspelled_cfgDictfrFR:SetChecked(Misspelled_DB.LoadDictionary == "frFR") + Misspelled_cfgDictfrFR:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "frFR" + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + local cfgDictruRU = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictruRU", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictruRU:SetPoint("TOPLEFT", 40, -184) + Misspelled_cfgDictruRUText:SetText("ruRU") + Misspelled_cfgDictruRU:SetChecked(Misspelled_DB.LoadDictionary == "ruRU") + Misspelled_cfgDictruRU:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "ruRU" + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + local cfgDictitIT = CreateFrame("CHECKBUTTON", "Misspelled_cfgDictitIT", cfgFrame, "InterfaceOptionsCheckButtonTemplate") + Misspelled_cfgDictitIT:SetPoint("TOPLEFT", 40, -208) + Misspelled_cfgDictitITText:SetText("itIT") + Misspelled_cfgDictitIT:SetChecked(Misspelled_DB.LoadDictionary == "itIT") + Misspelled_cfgDictitIT:SetScript("OnClick", function(self) + if self:GetChecked() then + PlaySound(856) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON + Misspelled_DB.LoadDictionary = "itIT" + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + else + PlaySound(857) -- SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF + end + end) + + --Edit User Dictionary Button + local cfgEditUserDict = CreateFrame("Button", "EdutUserDictButton", cfgFrame, "UIPanelButtonTemplate") + cfgEditUserDict:SetPoint("TOPLEFT", 20, -287) + cfgEditUserDict:SetText(L["Edit User Dictionary..."]) + cfgEditUserDict:SetWidth(200) + cfgEditUserDict:SetHeight(24) + cfgEditUserDict:SetScript("OnClick", function(self) + --PlaySound("igMainMenuOptionCheckBoxOn") + Misspelled:EditUserDict() + end) + + --set options on startup + Misspelled_cfgDictdeDE:SetChecked(false) + Misspelled_cfgDictenUS:SetChecked(false) + Misspelled_cfgDictenGB:SetChecked(false) + Misspelled_cfgDictesES:SetChecked(false) + Misspelled_cfgDictfrFR:SetChecked(false) + Misspelled_cfgDictruRU:SetChecked(false) + Misspelled_cfgDictitIT:SetChecked(false) + + if Misspelled_DB.LoadDictionary == "deDE" then + Misspelled_cfgDictdeDE:SetChecked(true) + elseif Misspelled_DB.LoadDictionary == "enGB" then + Misspelled_cfgDictenGB:SetChecked(true) + elseif Misspelled_DB.LoadDictionary == "enUS" then + Misspelled_cfgDictenUS:SetChecked(true) + elseif Misspelled_DB.LoadDictionary == "esES" then + Misspelled_cfgDictesES:SetChecked(true) + elseif Misspelled_DB.LoadDictionary == "frFR" then + Misspelled_cfgDictfrFR:SetChecked(true) + elseif Misspelled_DB.LoadDictionary == "ruRU" then + Misspelled_cfgDictruRU:SetChecked(true) + elseif Misspelled_DB.LoadDictionary == "itIT" then + Misspelled_cfgDictitIT:SetChecked(true) + end + + if Misspelled_DB.AutoSelectDictionary == true then + Misspelled_cfgDictdeDE:Disable() + Misspelled_cfgDictenGB:Disable() + Misspelled_cfgDictenUS:Disable() + Misspelled_cfgDictesES:Disable() + Misspelled_cfgDictfrFR:Disable() + Misspelled_cfgDictruRU:Disable() + Misspelled_cfgDictitIT:Disable() + else + Misspelled_cfgDictdeDE:Enable() + Misspelled_cfgDictenGB:Enable() + Misspelled_cfgDictenUS:Enable() + Misspelled_cfgDictesES:Enable() + Misspelled_cfgDictfrFR:Enable() + Misspelled_cfgDictruRU:Enable() + Misspelled_cfgDictitIT:Enable() + end + + InterfaceOptions_AddCategory(cfgFrame) +end + + +-- split first word of a text line +function Grichelde:GetNextWord(line) + -- need to add a trailing separator to catch the last value. + local wordPattern = "[%s%c]*(%w+)[%s%c]*" + local s, e = string_find(line, wordPattern) + local word = string_sub(line, s, e) + local rest = string_sub(line, e) + return word, rest +end + +function Grichelde:tprint(t, indent, done) + -- in case we run it standalone + local Note = Note or print + + -- show strings differently to distinguish them from numbers + local function show(val) + if type(val) == "string" then + return '"' .. val .. '"' + else + return tostring(val) + end + end + + -- entry point here + done = done or {} + indent = indent or 0 + for key, value in pairs(t) do + print(string_rep(" ", indent)) -- indent it + if type(value) == "table" and not done[value] then + done[value] = true + Note(show(key), ":"); + Grichelde:tprint(value, indent + 2, done) + else + print(show(key), "=") + print(show(value)) + end + end +end + +function Grichelde:print(...) + SELECTED_DOCK_FRAME:AddMessage(...) +end \ No newline at end of file diff --git a/Grichelde.toc b/Grichelde.toc new file mode 100644 index 0000000..cf4d980 --- /dev/null +++ b/Grichelde.toc @@ -0,0 +1,21 @@ +## Interface: 11304 + +## Title: Grichelde +## Notes: Replaces characters you type in the chat box +## Notes-de: Ersetzt eingegebene Zeichen in der Chat-Zeile +## Version: 1.0 +## Author: Teilzeit-Jedi +## eMail: tj@teilzeit-jedi.de + +## X-Build: Classic +## X-Curse-Project-ID: 385480 +## X-Category: Chat/Communication +## X-Credits: Teilzeit-Jedi, Nathan Pieper + +## SavedVariables: GrichseldeOptions +## SavedVariablesPerCharacter: GrichseldeCharOptions + +libs.xml +localisation.xml + +Grichelde.lua \ No newline at end of file diff --git a/libs.xml b/libs.xml new file mode 100644 index 0000000..7e65ccb --- /dev/null +++ b/libs.xml @@ -0,0 +1,11 @@ + + +