Librarian = ZO_SortFilterList:Subclass() Librarian.defaults = {} ZO_CreateStringId("SI_BINDING_NAME_TOGGLE_LIBRARIAN", "Toggle Librarian") ZO_CreateStringId("SI_BINDING_NAME_RELOAD_UI", "Reload UI") ZO_CreateStringId("SI_WINDOW_TITLE_LIBRARIAN", "Librarian") ZO_CreateStringId("SI_LIBRARIAN_SORT_TYPE_UNREAD", "Unread") ZO_CreateStringId("SI_LIBRARIAN_SORT_TYPE_FOUND", "Found") ZO_CreateStringId("SI_LIBRARIAN_SORT_TYPE_TITLE", "Title") ZO_CreateStringId("SI_LIBRARIAN_SORT_TYPE_WORD_COUNT", "Words") ZO_CreateStringId("SI_LIBRARIAN_MARK_UNREAD", "Mark as Unread") ZO_CreateStringId("SI_LIBRARIAN_MARK_READ", "Mark as Read") ZO_CreateStringId("SI_LIBRARIAN_CREDIT", "Librarian 1.0.18 by Flamage") ZO_CreateStringId("SI_LIBRARIAN_BOOK_COUNT", "%d Books") ZO_CreateStringId("SI_LIBRARIAN_UNREAD_COUNT", "%s (%d Unread)") ZO_CreateStringId("SI_LIBRARIAN_SHOW_ALL_BOOKS", "Show books for all characters") ZO_CreateStringId("SI_LIBRARIAN_NEW_BOOK_FOUND", "Book added to librarian") ZO_CreateStringId("SI_LIBRARIAN_NEW_BOOK_FOUND_WITH_TITLE", "Book added to librarian: %s") ZO_CreateStringId("SI_LIBRARIAN_FULLTEXT_SEARCH", "Full-text Search:") ZO_CreateStringId("SI_LIBRARIAN_SEARCH_HINT", "Enter text to search for.") ZO_CreateStringId("SI_LIBRARIAN_RELOAD_REMINDER", "ReloadUI suggested to update Librarian database.") local SORT_ARROW_UP = "EsoUI/Art/Miscellaneous/list_sortUp.dds" local SORT_ARROW_DOWN = "EsoUI/Art/Miscellaneous/list_sortDown.dds" local LIBRARIAN_DATA = 1 local LIBRARIAN_SEARCH = 1 local ENTRY_SORT_KEYS = { ["title"] = { }, ["unread"] = { tiebreaker = "timeStamp" }, ["timeStamp"] = { tiebreaker = "title" }, ["wordCount"] = { tiebreaker = "title" } } function Librarian:New() local librarian = ZO_SortFilterList.New(self, LibrarianFrame) librarian:Initialise() return librarian end function Librarian:Initialise() self.masterList = {} self.newBookCount = 0 self.sortHeaderGroup:SelectHeaderByKey("timeStamp") ZO_ScrollList_AddDataType(self.list, LIBRARIAN_DATA, "LibrarianBookRow", 30, function(control, data) self:SetupBookRow(control, data) end) ZO_ScrollList_EnableHighlight(self.list, "ZO_ThinListHighlight") self.localSavedVars = ZO_SavedVars:New("Librarian_SavedVariables", 1, nil, self.defaults, nil) self.globalSavedVars = ZO_SavedVars:NewAccountWide("Librarian_SavedVariables", 1, nil, self.defaults, nil) if not self.globalSavedVars.settings then self.globalSavedVars.settings = {} end self.settings = self.globalSavedVars.settings if not self.globalSavedVars.books then self.globalSavedVars.books = {} end self.books = self.globalSavedVars.books if not self.localSavedVars.characterBooks then self.localSavedVars.characterBooks = {} end self.characterBooks = self.localSavedVars.characterBooks self.searchBox = GetControl(LibrarianFrame, "SearchBox") self.searchBox:SetHandler("OnTextChanged", function() self:OnSearchTextChanged() end) self.search = ZO_StringSearch:New() self.search:AddProcessor(LIBRARIAN_SEARCH, function(stringSearch, data, searchTerm, cache) return self:ProcessBookEntry(stringSearch, data, searchTerm, cache) end) self.sortFunction = function(listEntry1, listEntry2) return ZO_TableOrderingFunction(listEntry1.data, listEntry2.data, self.currentSortKey, ENTRY_SORT_KEYS, self.currentSortOrder) end self:UpdateSavedVariables() local settings = LibrarianSettings:New(self.settings) local function OnShowAllBooksClicked(checkButton, isChecked) self.settings.showAllBooks = isChecked self:RefreshFilters() end local function GetShowAllBooks() return self.settings.showAllBooks end local showAllBooks = LibrarianFrameShowAllBooks ZO_CheckButton_SetToggleFunction(showAllBooks, OnShowAllBooksClicked) ZO_CheckButton_SetCheckState(showAllBooks, GetShowAllBooks()) --self:ImportFromLoreLibrary() self:RefreshData() self:InitializeKeybindStripDescriptors() self:InitializeScene() self:AddLoreReaderUnreadToggle() end function Librarian:AddLoreReaderUnreadToggle() if LORE_READER.keybindStripDescriptor then local toggleKeybind = { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = function() local book = self:FindBook(LORE_READER.titleText) if not book or book.unread then if self.settings.showUnreadIndicatorInReader then self.unreadIndicator:SetHidden(false) else self.unreadIndicator:SetHidden(true) end return GetString(SI_LIBRARIAN_MARK_READ) else self.unreadIndicator:SetHidden(true) return GetString(SI_LIBRARIAN_MARK_UNREAD) end end, keybind = "UI_SHORTCUT_SECONDARY", callback = function() local book = self:FindBook(LORE_READER.titleText) if not book then return end book.unread = not book.unread KEYBIND_STRIP:UpdateKeybindButtonGroup(LORE_READER.keybindStripDescriptor) self:RefreshData() end } table.insert(LORE_READER.keybindStripDescriptor, toggleKeybind) end self.unreadIndicator = WINDOW_MANAGER:CreateControl("LibrarianUnreadIndicator", ZO_LoreReaderBookContainer, CT_TEXTURE) self.unreadIndicator:SetAnchor(TOPLEFT, ZO_LoreReaderBookContainerFirstPage, TOPLEFT, -32, 3) self.unreadIndicator:SetDimensions(32, 32) self.unreadIndicator:SetHidden(true) self.unreadIndicator:SetTexture([[EsoUI/Art/Inventory/newitem_icon.dds]]) end function Librarian:UpdateSavedVariables() -- Version 1.0.4 - Settings moved to global variables. if self.localSavedVars.setting_time_format then self.globalSavedVars.settings.time_format = self.localSavedVars.setting_time_format self.localSavedVars.setting_time_format = nil end -- Version 1.0.4 - Book data moved to global variables if self.localSavedVars.books then for _,book in ipairs(self.localSavedVars.books) do local timeStamp = book.timeStamp local unread = book.unread self:AddBook(book) local characterBook = self:FindCharacterBook(book.title) if characterBook then characterBook.timeStamp = timeStamp end local globalBook = self:FindBook(book.title) if globalBook then globalBook.timeStamp = timeStamp globalBook.unread = unread end end self.localSavedVars.books = nil self:RefreshData() end -- Version 1.0.16 - Fixed a couple of settings names. if self.globalSavedVars.settings.alert_style then self.globalSavedVars.settings.alertStyle = self.globalSavedVars.settings.alert_style self.globalSavedVars.settings.alert_style = nil end if self.globalSavedVars.settings.time_format then self.globalSavedVars.settings.timeFormat = self.globalSavedVars.settings.time_format self.globalSavedVars.settings.time_format = nil end end function Librarian:InitializeKeybindStripDescriptors() self.keybindStripDescriptor = { { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = GetString(SI_LORE_LIBRARY_READ), keybind = "UI_SHORTCUT_PRIMARY", visible = function() return self.mouseOverRow end, callback = function() self:ReadBook(self.mouseOverRow.data.title) end, }, { alignment = KEYBIND_STRIP_ALIGN_RIGHT, name = function() if not self.mouseOverRow then return nil end local book = self:FindBook(self.mouseOverRow.data.title) if book.unread then return GetString(SI_LIBRARIAN_MARK_READ) else return GetString(SI_LIBRARIAN_MARK_UNREAD) end end, keybind = "UI_SHORTCUT_SECONDARY", visible = function() return self.mouseOverRow end, callback = function() local book = self:FindBook(self.mouseOverRow.data.title) book.unread = not book.unread self:RefreshData() end, } } end function Librarian:InitializeScene() if not LIBRARIAN_SCENE then LIBRARIAN_TITLE_FRAGMENT = ZO_SetTitleFragment:New(SI_WINDOW_TITLE_LIBRARIAN) LIBRARIAN_SCENE = ZO_Scene:New("librarian", SCENE_MANAGER) LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.UI_WINDOW) LIBRARIAN_SCENE:AddFragmentGroup(FRAGMENT_GROUP.FRAME_TARGET_STANDARD_RIGHT_PANEL) LIBRARIAN_SCENE:AddFragment(ZO_FadeSceneFragment:New(LibrarianFrame)) LIBRARIAN_SCENE:AddFragment(RIGHT_BG_FRAGMENT) LIBRARIAN_SCENE:AddFragment(TITLE_FRAGMENT) LIBRARIAN_SCENE:AddFragment(LIBRARIAN_TITLE_FRAGMENT) LIBRARIAN_SCENE:AddFragment(EXPERIENCE_BAR_FRAGMENT) LIBRARIAN_SCENE:AddFragment(CODEX_WINDOW_SOUNDS) LIBRARIAN_SCENE:RegisterCallback("StateChange", function(oldState, newState) if(newState == SCENE_SHOWING) then KEYBIND_STRIP:AddKeybindButtonGroup(self.keybindStripDescriptor) elseif(newState == SCENE_HIDDEN) then KEYBIND_STRIP:RemoveKeybindButtonGroup(self.keybindStripDescriptor) end end) end end function Librarian:ImportFromLoreLibrary() local hasImportedBooks = false local chatEnabled = self.settings.chatEnabled local alertEnabled = self.settings.alertEnabled self.settings.chatEnabled = true self.settings.alertEnabled = false for categoryIndex = 1, GetNumLoreCategories() do local categoryName, numCollections = GetLoreCategoryInfo(categoryIndex) for collectionIndex = 1, numCollections do local collectionName, description, numKnownBooks, totalBooks, hidden = GetLoreCollectionInfo(categoryIndex, collectionIndex) if not hidden then for bookIndex = 1, totalBooks do local title, icon, known = GetLoreBookInfo(categoryIndex, collectionIndex, bookIndex) if string.sub(book.title, -1) == "]" then book.title = book.title .. " " end if known then if not self:FindCharacterBook(title) then local body, medium, showTitle = ReadLoreBook(categoryIndex, collectionIndex, bookIndex) local book = {title = title, body = body, medium = medium, showTitle = showTitle} self:AddBook(book) end end end end end end self.settings.chatEnabled = chatEnabled self.settings.alertEnabled = alertEnabled end function Librarian:BuildMasterList() for i, book in ipairs(self.books) do local data = {} for k,v in pairs(book) do data[k] = v end data.type = LIBRARIAN_SEARCH local characterBook = self:FindCharacterBook(book.title) if characterBook then data.seenByCurrentCharacter = true data.timeStamp = characterBook.timeStamp else data.seenByCurrentCharacter = false data.timeStamp = book.timeStamp end self.masterList[i] = data end end function Librarian:FilterScrollList() local scrollData = ZO_ScrollList_GetDataList(self.list) ZO_ClearNumericallyIndexedTable(scrollData) local bookCount = 0 local unreadCount = 0 local searchTerm = self.searchBox:GetText() for i = 1, #self.masterList do local data = self.masterList[i] if self.settings.showAllBooks or data.seenByCurrentCharacter then if(searchTerm == "" or self.search:IsMatch(searchTerm, data)) then table.insert(scrollData, ZO_ScrollList_CreateDataEntry(LIBRARIAN_DATA, data)) bookCount = bookCount + 1 if data.unread then unreadCount = unreadCount + 1 end end end end local message = string.format(GetString(SI_LIBRARIAN_BOOK_COUNT), bookCount) if unreadCount > 0 then message = string.format(GetString(SI_LIBRARIAN_UNREAD_COUNT), message, unreadCount) end LibrarianFrameBookCount:SetText(message) end function Librarian:SortScrollList() local scrollData = ZO_ScrollList_GetDataList(self.list) table.sort(scrollData, self.sortFunction) end function Librarian:SetupBookRow(control, data) control.data = data control.unread = GetControl(control, "Unread") control.found = GetControl(control, "Found") control.title = GetControl(control, "Title") control.wordCount = GetControl(control, "WordCount") control.unread.nonRecolorable = true if data.unread then control.unread:SetAlpha(1) else control.unread:SetAlpha(0) end control.found.normalColor = ZO_NORMAL_TEXT control.found:SetText(self:FormatClockTime(data.timeStamp)) control.title.normalColor = ZO_NORMAL_TEXT control.title:SetText(data.title) control.wordCount.normalColor = ZO_NORMAL_TEXT control.wordCount:SetText(data.wordCount) ZO_SortFilterList.SetupRow(self, control, data) end function Librarian:ProcessBookEntry(stringSearch, data, searchTerm, cache) local lowerSearchTerm = searchTerm:lower() if(zo_plainstrfind(data.title:lower(), lowerSearchTerm)) then return true end if(zo_plainstrfind(data.body:lower(), lowerSearchTerm)) then return true end return false end function Librarian:FindCharacterBook(title) if not self.characterBooks then return nil end for _,book in pairs(self.characterBooks) do if book.title == title then return book end end end function Librarian:FindBook(title) for _,book in pairs(self.books) do if book.title == title then return book end end end function Librarian:AddBook(book) if string.sub(book.title, -1) == "]" then book.title = book.title .. " " end if not self:FindCharacterBook(book.title) then if string.sub(book.body, -1) == "]" then book.body = book.body .. " " end if not self:FindBook(book.title) then book.timeStamp = GetTimeStamp() book.unread = true local wordCount = 0 for w in book.body:gmatch("%S+") do wordCount = wordCount + 1 end book.wordCount = wordCount table.insert(self.books, book) end local characterBook = { title = book.title, timeStamp = GetTimeStamp() } table.insert(self.characterBooks, characterBook) self:RefreshData() if self.settings.alertEnabled then ZO_CenterScreenAnnounce_GetAnnounceObject():AddMessage(EVENT_SKILL_RANK_UPDATE, CSA_EVENT_LARGE_TEXT, SOUNDS.BOOK_ACQUIRED, GetString(SI_LIBRARIAN_NEW_BOOK_FOUND)) end if self.settings.chatEnabled then d(string.format(GetString(SI_LIBRARIAN_NEW_BOOK_FOUND_WITH_TITLE), book.title)) end self.newBookCount = self.newBookCount + 1 if self.settings.reloadReminderBookCount and self.settings.reloadReminderBookCount > 0 and self.settings.reloadReminderBookCount <= self.newBookCount then d(GetString(SI_LIBRARIAN_RELOAD_REMINDER)) end end end function Librarian:Toggle() if LibrarianFrame:IsControlHidden() then SCENE_MANAGER:Show("librarian") else SCENE_MANAGER:Hide("librarian") end end function Librarian:ReadBook(title) local book = self:FindBook(title) LORE_READER:SetupBook(book.title, book.body, book.medium, book.showTitle) LORE_READER.returnScene = "librarian" SCENE_MANAGER:Show("loreReaderInteraction") PlaySound(LORE_READER.OpenSound) end function Librarian:FormatClockTime(time) local midnightSeconds = GetSecondsSinceMidnight() local utcSeconds = GetTimeStamp() % 86400 local offset = midnightSeconds - utcSeconds if offset < -43200 then offset = offset + 86400 end local dateString = GetDateStringFromTimestamp(time) local timeString = ZO_FormatTime((time + offset) % 86400, TIME_FORMAT_STYLE_CLOCK_TIME, self.settings.timeFormat) return string.format("%s %s", dateString, timeString) end function Librarian:OnSearchTextChanged() ZO_EditDefaultText_OnTextChanged(self.searchBox) self:RefreshFilters() end local function SlashCommand(args) Librarian:Toggle() end local function OnAddonLoaded(event, addon) if addon == "Librarian" then LIBRARIAN = Librarian:New() end end local function OnShowBook(eventCode, title, body, medium, showTitle) local book = { title = title, body = body, medium = medium, showTitle = showTitle } LIBRARIAN:AddBook(book) end function LibrarianRow_OnMouseEnter(control) LIBRARIAN:Row_OnMouseEnter(control) end function LibrarianRow_OnMouseExit(control) LIBRARIAN:Row_OnMouseExit(control) end function LibrarianRow_OnMouseUp(control, button, upInside) LIBRARIAN:ReadBook(control.data.title) end SLASH_COMMANDS["/librarian"] = SlashCommand EVENT_MANAGER:RegisterForEvent("Librarian", EVENT_ADD_ON_LOADED, OnAddonLoaded) EVENT_MANAGER:RegisterForEvent("Librarian", EVENT_SHOW_BOOK, OnShowBook)