view Squawk.lua @ 16:6c28e55a00cf

hmm
author wobin
date Sat, 02 May 2009 23:54:32 +1000
parents 9e61a930b822
children 2a73deb7bc54
line wrap: on
line source
-- A Twitter client of sorts for World of Warcraft
-- Author: Wobin
-- Email: wobster@gmail.com
--
Squawk = LibStub("AceAddon-3.0"):NewAddon("Squawk")

Squawk.Model = {}
Squawk.View = {}
Squawk.Controller = {}

local Model = Squawk.Model
local View = Squawk.View
local Controller = Squawk.Controller

Model.UserSettings = {}
local Settings = Model.UserSettings

local defaults = {
	profile = {
		Squawks = {},
		Follower = {},
		Following = {},
		Pending = {},
		Requested = {},
		Blocked = {},
	}
}

function Squawk:OnInitialize()
	Model.db = LibStub("AceDB-3.0"):New("SquawkDB", defaults)
	Model.Squawks = Model.db.profile.Squawks
	Settings.Follower = Model.db.profile.Follower
	Settings.Following = Model.db.profile.Following
	Settings.Pending = Model.db.profile.Pending
	Settings.Requested = Model.db.profile.Requested
	Settings.Blocked = Model.db.profile.Blocked
	Settings.Private = Model.db.profile.Private
	
	LibStub("AceComm-3.0"):Embed(Controller)
	LibStub("AceTimer-3.0"):Embed(Controller)
	Controller:RegisterComm("Squawk", Controller.ReceiveMessage)
	LibStub("AceConsole-3.0"):Embed(View)

end

-- Model --
--[[
--Each Squawk will have the following information:
-- * Owner (Name)
-- * Time (Epoch)
-- * Message (140 characters)
-- * ReplyTo (Name)
-- * Related (Names)
--
-- Each User will have the following lists:
-- * Follower
-- * Following
-- * Blocked
-- * Pending (Requests to follow that you haven't acted on)
-- * Requested (Requests to follow that you have made)
-- * Privacy State
--
-- A user can only request to follow an online person. Requests can be approved
-- on or offline, but the initial request must be made online.
--
-- When a user makes a request to follow a private user, the subsequent paths occur:
-- - Followee is added to Settings.Requested 
-- - Followee receives 'follow request' -> (their) Settings.Pending
-- - Followee acts on request -> (their) Settings.Pending cleared
--  1) Follwer is online
--		- Follower receives 'request accepted' -> Added to Settings.Following and
--		cleared from Settings.Requested
--  2) Follower is offline
--		- The next time Follower is online and recieves a Squawk we check if there
--		is a Settings.Requested for that name, and if so assume they have approved
--		and clear/add records appropriately.
--
--		For updating, there can be a few methods.
--
--		Guild open: you're not private, you broadcast to the guild your last X
--		squawks on login
--
--		followers:
--]]
Model.Squawks = {}
local Squawks = Model.Squawks
Squawks.Main = {}
Squawks.Owners = {}

local function wrap(str, limit)
  limit = limit or 72
  local here = 1
  return str:gsub("(%s+)()(%S+)()",
                          function(sp, st, word, fi)
                            if fi-here > limit then
                              here = st 
                              return "\n"..word
                            end
                          end)
end

function Squawks:new(Message, Owner)
	local o = {}
	o.Owner = Owner or UnitName("player")
	o.Message = wrap(Message)
	o.Time = time()
	local reply, to = strsplit("@", ((strsplit(" ", Message))))
	if reply == "" then
		o.ReplyTo = to
	end

	o.Related = {}

	for word in string.gmatch(Message, "@(%a+)") do
		if word ~= o.ReplyTo or "" then
			table.insert(o.Related, word)
		end
	end
	
	table.insert(self.Main, o)
	
	if not self.Owners[Owner] then
		self.Owners[Owner] = {}
	end
	table.insert(self.Owners[Owner], o)

	return o
end

function Squawks:Sort(Squawks)
	table.sort(Squawks or self.Main, function(a,b) return a.Time > b.Time end)
	return Squawks or self.Main
end

function Squawks:GetOwn(Squawks)
	local mine = {}
	for _, squawk in ipairs(Squawks or self.Main) do
		if squawk.Owner == UnitName("player") then
			table.insert(mine, squawk)
		end
	end
	return self:Sort(mine)
end

function Squawks:GetLast10(Squawks)
	local mine = {}
	Squawks = Squawks or self.Main
	local limit = #Squawks < 10 and #Squawks or 10
	
	Squawks = Squawk:Sort(Squawks)

	for i=1,limit do
		table.insert(mine, Squawks[i])
	end
	return mine
end

-- initially called with no arguments to get the latest timestamp of 
-- my squawks, or with a name to find the latest timestamp of all 
-- squawks from that user
function Squawks:GetLatestTimestamp(Name, Squawks)
	if Name then
		if self.Owners[Name] then
			return self:GetLatestTimestamp(nil, self.Owners[Name])
		else
			return -1 -- No squawks exist for that name in our records
		end
	end

	Squawks = Squawks or self.Main or {}
	local latest = self:Sort(Squawks)
	return latest and #latest > 0 and latest[1].Time or -1
end

function Squawks:GetLatestSquawks(Timestamp)
	local latest = {}
	for i, squawk in ipairs(self:Sort()) do
		if squawk.Time > Timestamp and i < 10 then
			table.insert(latest, squawk)
		else
			return latest
		end
	end
end

function Settings:IsPrivate()
	return Settings.Private
end

function Settings:TogglePrivate()
	Settings.Private = not Settings.Private
end

function Settings:AddFollower(Name)
	Settings.Follower[Name] = 1
	self:RemovePending(Name)
end

function Settings:AddFollowing(Name)
	Settings.Following[Name] = 1
	self:RemoveRequested(Name)
end

function Settings:AddBlock(Name)
	Settings.Blocked[Name] = 1
	self:RemoveFollower(Name)
	self:RemoveFollowing(Name)
end

function Settings:AddPending(Name)
	Settings.Pending[Name] = 1
end

function Settings:AddRequested(Name)
	Settings.Requested[Name] = 1
end

function Settings:RemoveFollower(Name)
	if Settings.Follower[Name] then
		Settings.Follower[Name] = nil
	end
end

function Settings:RemoveFollowing(Name)
	if Settings.Following[Name] then
		Settings.Following[Name] = nil
	end
end

function Settings:RemoveBlock(Name)
	if Settings.Blocked[Name] then
		Settings.Blocked[Name] = nil
	end
end

function Settings:RemovePending(Name)
	if Settings.Pending[Name] then
		Settings.Pending[Name] = nil
	end
end

function Settings:RemoveRequested(Name)
	if Settings.Requested[Name] then
		Settings.Requested[Name] = nil
	end
end

--Controller--

function Controller:TheyWantToFollowMe(Name)
	if Settings:IsPrivate() then
		Settings:AddPending(Name)
		self:PutForwardFollowRequest(Name)
		self:SendMessageToTarget(Name, "#Pending|"..UnitName("player"))
	else
		Settings:AddFollower(Name)
		View:NotifyOfNewFollower(Name)
		self:SendMessageToTarget(Name, "#Follow|"..UnitName("player"))
	end
end

function Controller:TheyWantToUnfollowMe(Name)
	Settings:RemoveFollower(Name)
end

function Controller:IWantToFollowThem(Name)
	self:SendMessageToTarget(Name, "#Request|"..UnitName("player"))
	Settings:AddRequested(Name)
end

function Controller:IWantToUnfollowThem(Name)
	Settings:RemoveFollowing(Name)
	self:SendMessageToTarget(Name, "#Unfollow|"..UnitName("player"))
	View:NotifyOfUnfollowing(Name)
end

function Controller:IAmNowFollowingThem(Name)
	Settings:AddFollowing(Name)
	View:NotifyOfNewFollowing(Name)
end

function Controller:AddANewSquawk(Name, Message, Source)
	if not Settings.Blocked[Name] then
	
		if Source == "WHISPER" then
			if Settings.Requested[Name] then -- We've been approved offline!
				Settings:AddFollowing(Name)
			end

			if not Settings.Following[Name] then -- If we're no longer following this person
				self:SendMessageToTarget(Name, "#Unfollow|"..UnitName("player"))
				return
			end
		end	
		
		if Source == "GUILD" and Name == UnitName("player") then
			return
		end

		table.insert(Model.Squawks, Squawk:new(Message, Name))
		View:UpdateSquawkList()	
	end
end

local trigger
local function RepressFailure(frame, event, ...)
	if arg1:match(string.gsub(ERR_CHAT_PLAYER_NOT_FOUND_S, "%%s", "(.*)")) then
		if trigger then Controller:CancelTimer(trigger, true) end
		trigger = Controller:ScheduleTimer(
				function() 
					ChatFrame_RemoveMessageEventFilter("CHAT_MSG_SYSTEM", RepressFailure) 
				end, 3) -- Give it three seconds and then remove the filter.
		return true
	else
		return false, unpack(...)
	end
end

function Controller:SendNewSquawk(Message)
	if not Settings:IsPrivate() then
		self:SendMessageToGuild("#Squawk|"..UnitName("player").."|"..Message)
	end

	self:AddANewSquawk(UnitName("player"), Message)
	for name, _ in pairs(Settings.Following) do
		self:SendMessageToTarget(name, "#Squawk|"..UnitName("player").."|"..Message)
	end
end

function Controller:ImPending(Name)
	View:NotifyOfPending(Name)
end

function Controller:PutForwardFollowRequest(Name)
	View:NotifyOfPendingRequest(Name)
end

function Controller:ApprovePendingRequest(Name)
	Settings:AddFollower(Name)
	View:NotifyOfNewFollower(Name)
	self:SendMessageToTarget(Name, "#Follow|"..UnitName("player"))
end



function Controller:SendMessageToTarget(Name, Message)
	ChatFrame_AddMessageEventFilter("CHAT_MSG_SYSTEM", RepressFailure)
	self:SendCommMessage("Squawk", Message, "WHISPER", Name)
end

function Controller:SendMessageToGuild(Message)
	self:SendCommMessage("Squawk", Message, "GUILD")
end

local Parse = { 
		["#Pending"] = Controller.ImPending,
		["#Follow"] = Controller.IAmNowFollowingThem,
		["#Unfollow"] = Controller.TheyWantToUnfollowMe,
		["#Squawk"] = Controller.AddANewSquawk,
		["#Request"] = Controller.TheyWantToFollowMe,
	}

function Controller:ReceiveMessage(Message, Distribution, Sender)
	local command, name, info = strsplit("|",Message)
	View:Print(Distribution..":"..Message)
	Parse[command](Controller, name, info, Distribution)
end

-- View --

function View:UpdateSquawkList()
	self:Print("Updated Squawk List")
	self:ShowMeMySquawks()
end

function View:NotifyOfPending(Name)
	self:Print(Name.." will have to approve your request")
end

function View:NotifyOfPendingRequest(Name)
	self:Print(Name.." wants to follow you.")
end

function View:NotifyOfNewFollowing(Name)
	self:Print("You are now following "..Name)
end

function View:NotifyOfUnfollowing(Name)
	self:Print("You are no longer following "..Name)
end

function View:NotifyOfNewFollower(Name)
	self:Print(Name.." is now following you")
end

function View:ShowMeMySquawks()
	for _,squawk in ipairs(Model.Squawks.Main) do
		self:Print(squawk.Message)
	end
end

function View:ShowMeMyFollowers()
	self:Print("My followers are:")
	for name,_ in pairs(Settings.Follower) do
		self:Print(name)
	end
end

function View:ShowMeWhoImFollowing()
	self:Print("I am following:")
	for name,_ in pairs(Settings.Following) do
		self:Print(name)
	end
end

function View:ShowMeWhoIveBlocked()
	self:Print("I've blocked:")
	for name,_ in pairs(Settings.Blocked) do
		self:Print(name)
	end
end

local TimeSpan = {	[1] = {"second",  60, 1},
										[2] = {"minute", 3600, 60}, 
										[3] = {"hour", 86400, 3600} }

function View:GetTime(stime)
	local lapsed = difftime(time(), stime)
	if lapsed < 86400 then -- if we're still in the same day...
		for _,span in ipairs(TimeSpan) do
			if lapsed < span[2] then
				local timespan = math.floor(lapsed/span[3])
				if timespan == 1 then
					timespan = timespan .." ".. span[1]
				else
					timespan = timespan .. " ".. span[1].."s"
				end
				return timespan.. " ago"
			end
		end
	end
	return date("%I:%M %p %b %d", stime)
end

local LDBFeed = LibStub("LibDataBroker-1.1"):NewDataObject("Squawk", {type = "data source", text = "Awk!"})
local QTip = LibStub("LibQTip-1.0")
local QTipClick = LibStub("LibQTipClick-1.0")
local tooltip = {}

local function HideTooltip()
	if MouseIsOver(tooltip) then return end
	tooltip:SetScript("OnLeave", nil)
	tooltip:Hide()
	QTip:Release(tooltip)
	tooltip = nil
end

local function ReplyToMe(cell, Owner, event)
	View:Print("Replying to @"..Owner)
end

local function AddLine(tooltip, Line, Number, Owner, TimeStamp)
	local x,y
	if #Line < 79 then
		y,x = tooltip:AddNormalLine(Number, Owner, Line, TimeStamp)
	else
		y,x = tooltip:AddNormalLine(Number, Owner, Line:sub(1, 80).."-", TimeStamp)
		AddLine(tooltip, Line:sub(81))
	end
	if not TimeStamp then return end

	-- Now add the reply clickback
	tooltip:SetCell(y, 5, "    ", Owner)
	tooltip.lines[y].cells[5]:SetBackdrop({bgFile= "Interface\\Addons\\Squawk\\reply"})
	if not tooltip.lines[y].cells[5]:GetScript("OnHide") then
		tooltip.lines[y].cells[5]:SetScript("OnHide", function(self) self:SetBackdrop(nil) self:SetScript("OnHide", nil) end)
	end
	-- Reply clickback finished
end

function LDBFeed:OnEnter()
	tooltip = QTipClick:Acquire("Squawk",5, "LEFT", "CENTER", "LEFT", "RIGHT", "RIGHT")
	tooltip:Clear()
	tooltip:SetCallback("OnMouseDown", ReplyToMe)
	self.tooltip = tooltip
	for i,squawk in ipairs(Squawk:GetLast10(Model.Squawks)) do
			local head = true
			local message = {strsplit("\n",squawk.Message)}
			for _,line in ipairs(message) do
				if head then
					AddLine(tooltip, line, i..".", squawk.Owner, View:GetTime(squawk.Time))
					head = false
				else
					AddLine(tooltip, line)
				end
			end
	end
	tooltip:SmartAnchorTo(self)
	tooltip:SetScript("OnLeave", HideTooltip)
	tooltip:Show()
end

function LDBFeed:OnLeave()
	HideTooltip()
end
--[[ 

function LDBFeed:OnClick(button)
	editbox:ClearAllPoints()
	editbox:SetPoint(GetTipAnchor(self))
	editbox:Show()
end

local function GetTipAnchor(frame)
	if not x or not y then return "TOPLEFT", frame, "BOTTOMLEFT" end
	local hhalf = (x > UIParent:GetWidth()*2/3) and "RIGHT" or (x < UIParent:GetWidth()/3) and "LEFT" or ""
	local vhalf = (y > UIParent:GetHeight()/2) and "TOP" or "BOTTOM"
	return vhalf..hhalf, frame, (vhalf == "TOP" and "BOTTOM" or "TOP")..hhalf
end

local editbox = CreateFrame('EditBox', nil, UIParent)
editbox:Hide()
editbox:SetAutoFocus(true)
editbox:SetHeight(32)
editbox:SetWidth(350)
editbox:SetFrameStrata("HIGH")
editbox:SetFontObject('GameFontHighlightSmall')
lib.editbox = editbox

editbox:SetScript("OnEscapePressed", editbox.ClearFocus)
editbox:SetScript("OnEnterPressed", editbox.ClearFocus)
editbox:SetScript("OnEditFocusLost", editbox.Hide)
editbox:SetScript("OnEditFocusGained", editbox.HighlightText)
editbox:SetScript("OnTextChanged", function(self)
  self:SetText(self:GetParent().val)
  self:HighlightText()
end)

	local left = editbox:CreateTexture(nil, "BACKGROUND")
	left:SetWidth(8) left:SetHeight(20)
	left:SetPoint("LEFT", -5, 0)
	left:SetTexture("Interface\\Common\\Common-Input-Border")
	left:SetTexCoord(0, 0.0625, 0, 0.625)

	local right = editbox:CreateTexture(nil, "BACKGROUND")
	right:SetWidth(8) right:SetHeight(20)
	right:SetPoint("RIGHT", 0, 0)
	right:SetTexture("Interface\\Common\\Common-Input-Border")
	right:SetTexCoord(0.9375, 1, 0, 0.625)

	local center = editbox:CreateTexture(nil, "BACKGROUND")
	center:SetHeight(20)
	center:SetPoint("RIGHT", right, "LEFT", 0, 0)
	center:SetPoint("LEFT", left, "RIGHT", 0, 0)
	center:SetTexture("Interface\\Common\\Common-Input-Border")
	center:SetTexCoord(0.0625, 0.9375, 0, 0.625)

function lib.OpenEditbox(self)
  editbox:SetText(self.val)
  editbox:SetParent(self)
  editbox:SetPoint("LEFT", self)
  editbox:SetPoint("RIGHT", self)
  editbox:Show()
end
--]]