--[[------------------------------------------------
	-- Love Frames - A GUI library for LOVE --
	-- Copyright (c) 2013 Kenny Shields --
--]]------------------------------------------------

-- textinput object
local newobject = loveframes.NewObject("textinput", "loveframes_object_textinput", true)

--[[---------------------------------------------------------
	- func: initialize()
	- desc: initializes the object
--]]---------------------------------------------------------
function newobject:initialize()
	
	self.type = "textinput"
	self.keydown = "none"
	self.tabreplacement = "        "
	self.maskchar = "*"
	self.font = loveframes.basicfont
	self.width = 200
	self.height = 25
	self.delay = 0
	self.repeatdelay = 0.80
	self.repeatrate = 0.02
	self.offsetx = 0
	self.offsety = 0
	self.indincatortime = 0
	self.indicatornum = 0
	self.indicatorx = 0
	self.indicatory = 0
	self.textx = 0
	self.texty = 0
	self.textoffsetx = 5
	self.textoffsety = 5
	self.unicode = 0
	self.limit = 0
	self.line = 1
	self.itemwidth = 0
	self.itemheight = 0
	self.extrawidth = 0
	self.extraheight = 0
	self.rightpadding = 0
	self.bottompadding = 0
	self.lastclicktime = 0
	self.maxx = 0
	self.buttonscrollamount = 0.10
	self.mousewheelscrollamount = 5
	self.usable = {}
	self.unusable = {}
	self.lines = {""}
	self.placeholder = ""
	self.internals = {}
	self.showindicator = true
	self.focus = false
	self.multiline = false
	self.vbar = false
	self.hbar = false
	self.alltextselected = false
	self.linenumbers = true
	self.linenumberspanel = false
	self.editable = true
	self.internal = false
	self.autoscroll = false
	self.masked = false
	self.OnEnter = nil
	self.OnTextChanged = nil
	self.OnFocusGained = nil
	self.OnFocusLost = nil
	self.OnCopy = nil
	self.OnPaste = nil
	
end

--[[---------------------------------------------------------
	- func: update(deltatime)
	- desc: updates the object
--]]---------------------------------------------------------
function newobject:update(dt)

	local state = loveframes.state
	local selfstate = self.state
	
	if state ~= selfstate then
		return
	end
	
	local visible = self.visible
	local alwaysupdate = self.alwaysupdate
	
	if not visible then
		if not alwaysupdate then
			return
		end
	end
	
	-- check to see if the object is being hovered over
	self:CheckHover()
	
	local time = love.timer.getTime()
	local keydown = self.keydown
	local unicode = self.unicode
	local parent = self.parent
	local base = loveframes.base
	local update = self.Update
	local font = self.font
	local theight = font:getHeight()
	local delay = self.delay
	local lines = self.lines
	local numlines = #lines
	local multiline = self.multiline
	local width = self.width
	local height = self.height
	local vbar = self.vbar
	local hbar = self.hbar
	local inputobject = loveframes.inputobject
	local internals = self.internals
	local repeatrate = self.repeatrate
	local hover = self.hover
	local version = love._version
	
	-- move to parent if there is a parent
	if parent ~= base then
		local parentx = parent.x
		local parenty = parent.y
		local staticx = self.staticx
		local staticy = self.staticy
		self.x = parentx + staticx
		self.y = parenty + staticy
	end
	
	if inputobject ~= self then
		self.focus = false
		self.alltextselected = false
	end
	
	-- keydown check
	if keydown ~= "none" then
		if time > delay then
			if (loveframes.util.IsCtrlDown()) and keydown == "v" then
				self:Paste()
			else
				self:RunKey(keydown, unicode, true)
			end
			self.delay = time + repeatrate
		end
	end
	
	self:PositionText()
	self:UpdateIndicator()
	
	-- calculations for multiline mode
	if multiline then
		local twidth = 0
		local panel = self:GetLineNumbersPanel()
		local textoffsetx = self.textoffsetx
		local textoffsety = self.textoffsety
		local linenumbers = self.linenumbers
		local masked = self.masked
		local maskchar = self.maskchar
		-- get the longest line of text
		for k, v in ipairs(lines) do
			local linewidth = 0
			if masked then
				linewidth = font:getWidth(v:gsub(".", maskchar))
			else
				linewidth = font:getWidth(v)
			end
			if linewidth > twidth then
				twidth = linewidth
			end
		end
		-- item width calculation
		if vbar then
			self.itemwidth = twidth + 16 + textoffsetx * 2
		else
			self.itemwidth = twidth
		end
		if panel then
			local panelwidth = panel.width
			self.itemwidth = self.itemwidth + panelwidth + textoffsetx + 5
		end
		-- item height calculation
		if hbar then
			self.itemheight = theight * numlines + 16 + textoffsety * 2
		else
			self.itemheight = theight * numlines
		end
		-- extra width and height calculations
		self.extrawidth = self.itemwidth - width
		self.extraheight = self.itemheight - height
		local itemwidth = self.itemwidth
		local itemheight = self.itemheight
		if itemheight > height then
			if not vbar then
				local scrollbody = loveframes.objects["scrollbody"]:new(self, "vertical")
				scrollbody.internals[1].internals[1].autoscroll = self.autoscroll
				table.insert(self.internals, scrollbody)
				self.vbar = true
				if hbar then
					local vbody = self:GetVerticalScrollBody()
					local vbodyheight = vbody:GetHeight() - 15
					local hbody = self:GetHorizontalScrollBody()
					local hbodywidth = hbody:GetWidth() - 15
					vbody:SetHeight(vbodyheight)
					hbody:SetWidth(hbodywidth)
				end
			end
		else
			if vbar then
				self:GetVerticalScrollBody():Remove()
				self.vbar = false
				self.offsety = 0
				if self.hbar then
					local hbody = self:GetHorizontalScrollBody()
					local hbodywidth = hbody:GetWidth() - 15
					hbody:SetWidth(hbodywidth)
				end
			end
		end
		
		if itemwidth > width then
			if not hbar then
				local scrollbody = loveframes.objects["scrollbody"]:new(self, "horizontal")
				scrollbody.internals[1].internals[1].autoscroll = self.autoscroll
				table.insert(self.internals, scrollbody)
				self.hbar = true
				if self.vbar then
					local vbody = self:GetVerticalScrollBody()
					local hbody = self:GetHorizontalScrollBody()
					vbody:SetHeight(vbody:GetHeight() - 15)
					hbody:SetWidth(hbody:GetWidth() - 15)
				end
			end
		else
			if hbar then
				self:GetHorizontalScrollBody():Remove()
				self.hbar = false
				self.offsetx = 0
				if vbar then
					local vbody = self:GetVerticalScrollBody()
					if vbody then
						vbody:SetHeight(vbody:GetHeight() + 15)
					end
				end
			end
		end
		if linenumbers then
			if not self.linenumberspanel then
				local linenumberspanel = loveframes.objects["linenumberspanel"]:new(self)
				table.insert(internals, linenumberspanel)
				self.linenumberspanel = true
			end
		else
			if self.linenumberspanel then
				table.remove(internals, 1)
				self.linenumberspanel = false
			end
		end
	end
	
	for k, v in ipairs(internals) do
		v:update(dt)
	end
	
	if update then
		update(self, dt)
	end
	
end

--[[---------------------------------------------------------
	- func: draw()
	- desc: draws the object
--]]---------------------------------------------------------
function newobject:draw()

	local state = loveframes.state
	local selfstate = self.state
	
	if state ~= selfstate then
		return
	end
	
	local visible = self.visible
	if not visible then
		return
	end
	
	local x = self.x
	local y = self.y
	local width = self.width
	local height = self.height
	local skins = loveframes.skins.available
	local skinindex = loveframes.config["ACTIVESKIN"]
	local defaultskin = loveframes.config["DEFAULTSKIN"]
	local stencilfunc = function() love.graphics.rectangle("fill", x, y, width, height) end
	local loveversion = love._version
	local selfskin = self.skin
	local skin = skins[selfskin] or skins[skinindex]
	local drawfunc = skin.DrawTextInput or skins[defaultskin].DrawTextInput
	local drawoverfunc = skin.DrawOverTextInput or skins[defaultskin].DrawOverTextInput
	local draw = self.Draw
	local drawcount = loveframes.drawcount
	local internals = self.internals
	local vbar = self.vbar
	local hbar = self.hbar
	
	-- set the object's draw order
	self:SetDrawOrder()
	
	if vbar and hbar then
		stencilfunc = function() love.graphics.rectangle("fill", x, y, width - 16, height - 16) end
	end
	
	if loveversion == "0.8.0" then
		local stencil = love.graphics.newStencil(stencilfunc)
		love.graphics.setStencil(stencil)
	else
		love.graphics.setStencil(stencilfunc)
	end
	
	if draw then
		draw(self)
	else
		drawfunc(self)
	end
	
	love.graphics.setStencil()
	
	for k, v in ipairs(internals) do
		v:draw()
	end
	
	if not draw then
		drawoverfunc(self)
	end

end

--[[---------------------------------------------------------
	- func: mousepressed(x, y, button)
	- desc: called when the player presses a mouse button
--]]---------------------------------------------------------
function newobject:mousepressed(x, y, button)

	local state = loveframes.state
	local selfstate = self.state
	
	if state ~= selfstate then
		return
	end
	
	local visible = self.visible
	if not visible then
		return
	end
	
	local hover = self.hover
	local internals = self.internals
	local vbar = self.vbar
	local hbar = self.hbar
	local scrollamount = self.mousewheelscrollamount
	local focus = self.focus
	local alltextselected = self.alltextselected
	local onfocusgained = self.OnFocusGained
	local onfocuslost = self.OnFocusLost
	local time = love.timer.getTime()
	local inputobject = loveframes.inputobject
	
	if hover then
		if button == "l" then
			if inputobject ~= self then
				loveframes.inputobject = self
			end
			if not alltextselected then
				local lastclicktime = self.lastclicktime
				if (time > lastclicktime) and time < (lastclicktime + 0.25) then
					self.alltextselected = true
				end
			else
				self.alltextselected = false
			end
			self.focus = true
			self.lastclicktime = time
			self:GetTextCollisions(x, y)
			if onfocusgained and not focus then
				onfocusgained(self)
			end
			local baseparent = self:GetBaseParent()
			if baseparent and baseparent.type == "frame" then
				baseparent:MakeTop()
			end
		elseif button == "wu" then
			if vbar and not hbar then
				local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
				vbar:Scroll(-scrollamount)
			elseif vbar and hbar then
				local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
				vbar:Scroll(-scrollamount)
			elseif not vbar and hbar then
				local hbar = self:GetHorizontalScrollBody().internals[1].internals[1]
				hbar:Scroll(-scrollamount)
			end
		elseif button == "wd" then
			if vbar and not hbar then
				local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
				vbar:Scroll(scrollamount)
			elseif vbar and hbar then
				local vbar = self:GetVerticalScrollBody().internals[1].internals[1]
				vbar:Scroll(scrollamount)
			elseif not vbar and hbar then
				local hbar = self:GetHorizontalScrollBody().internals[1].internals[1]
				hbar:Scroll(scrollamount)
			end
		end
	else
		if inputobject == self then
			loveframes.inputobject = false
			if onfocuslost then
				onfocuslost(self)
			end
		end
	end
	
	for k, v in ipairs(internals) do
		v:mousepressed(x, y, button)
	end
	
end

--[[---------------------------------------------------------
	- func: mousereleased(x, y, button)
	- desc: called when the player releases a mouse button
--]]---------------------------------------------------------
function newobject:mousereleased(x, y, button)

	local state = loveframes.state
	local selfstate = self.state
	
	if state ~= selfstate then
		return
	end
	
	local visible = self.visible
	if not visible then
		return
	end
	
	local internals = self.internals
	for k, v in ipairs(internals) do
		v:mousereleased(x, y, button)
	end
	
end

--[[---------------------------------------------------------
	- func: keypressed(key)
	- desc: called when the player presses a key
--]]---------------------------------------------------------
function newobject:keypressed(key, unicode)

	local state = loveframes.state
	local selfstate = self.state
	
	if state ~= selfstate then
		return
	end
	
	local visible = self.visible
	
	if not visible then
		return
	end
	
	local time = love.timer.getTime()
	local focus = self.focus
	local repeatdelay = self.repeatdelay
	local alltextselected = self.alltextselected
	local editable = self.editable
	local version = love._version
	
	self.delay = time + repeatdelay
	self.keydown = key
	
	if (loveframes.util.IsCtrlDown()) and focus then
		if key == "a" then
			self.alltextselected = true
		elseif key == "c" and alltextselected and version == "0.9.0" then
			local text = self:GetText()
			local oncopy = self.OnCopy
			love.system.setClipboardText(text)
			if oncopy then
				oncopy(self, text)
			end
		elseif key == "x" and alltextselected and version == "0.9.0" and editable then
			local text = self:GetText()
			local oncut = self.OnCut
			love.system.setClipboardText(text)
			if oncut then
				oncut(self, text)
			else
				self:SetText("")
			end
		elseif key == "v" and version == "0.9.0" and editable then
			self:Paste()
		end
	else
		local version = love._version
		if version == "0.8.0" then
			self:RunKey(key, unicode, true)
		else
			self:RunKey(key, unicode, false)
		end
	end
	
end

--[[---------------------------------------------------------
	- func: keyreleased(key)
	- desc: called when the player releases a key
--]]---------------------------------------------------------
function newobject:keyreleased(key)
	
	local state = loveframes.state
	local selfstate = self.state
	
	if state ~= selfstate then
		return
	end
	
	local visible = self.visible
	if not visible then
		return
	end
	
	self.keydown = "none"
	
end

--[[---------------------------------------------------------
	- func: textinput(text)
	- desc: called when the inputs text
--]]---------------------------------------------------------
function newobject:textinput(text)

	local unicode = unicode
	if text:find("kp.") then
		text = text:gsub("kp", "")
	end
	if text:len() == 1 then
		unicode = string.byte(text)
	else
		unicode = 0
	end
		
	self:RunKey(text, unicode, true)
	
end

--[[---------------------------------------------------------
	- func: RunKey(key, unicode)
	- desc: runs a key event on the object
--]]---------------------------------------------------------
function newobject:RunKey(key, unicode, is_text)
	
	local visible = self.visible
	local focus = self.focus
	
	if not visible then
		return
	end
	
	if not focus then
		return
	end
	
	local x = self.x
	local offsetx = self.offsetx
	local lines = self.lines
	local line = self.line
	local numlines = #lines
	local curline = lines[line]
	local text = curline
	local ckey = ""
	local font = self.font
	local swidth = self.width
	local textoffsetx = self.textoffsetx
	local indicatornum = self.indicatornum
	local multiline = self.multiline
	local alltextselected = self.alltextselected
	local editable = self.editable
	local initialtext = self:GetText()
	local ontextchanged = self.OnTextChanged
	local onenter = self.OnEnter
	
	if key == "left" then
		indicatornum = self.indicatornum
		if not multiline then
			self:MoveIndicator(-1)
			local indicatorx = self.indicatorx
			if indicatorx <= x and indicatornum ~= 0 then
				local width = font:getWidth(text:sub(indicatornum, indicatornum + 1))
				self.offsetx = offsetx - width
			elseif indicatornum == 0 and offsetx ~= 0 then
				self.offsetx = 0
			end
		else
			if indicatornum == 0 then
				if line > 1 then
					self.line = line - 1
					local numchars = #lines[self.line]
					self:MoveIndicator(numchars)
				end
			else
				self:MoveIndicator(-1)
			end
		end
		if alltextselected then
			self.line = 1
			self.indicatornum = 0
			self.alltextselected = false
		end
		return
	elseif key == "right" then
		indicatornum = self.indicatornum
		if not multiline then
			self:MoveIndicator(1)
			local indicatorx = self.indicatorx
			if indicatorx >= (x + swidth) and indicatornum ~= #text then
				local width = font:getWidth(text:sub(indicatornum, indicatornum))
				self.offsetx = offsetx + width
			elseif indicatornum == #text and offsetx ~= ((font:getWidth(text)) - swidth + 10) and font:getWidth(text) + textoffsetx > swidth then
				self.offsetx = ((font:getWidth(text)) - swidth + 10)
			end
		else
			if indicatornum == #text then
				if line < numlines then
					self.line = line + 1
					self:MoveIndicator(0, true)
				end
			else
				self:MoveIndicator(1)
			end
		end
		if alltextselected then
			self.line = #lines
			self.indicatornum = lines[#lines]:len()
			self.alltextselected = false
		end
		return
	elseif key == "up" then
		if multiline then
			if line > 1 then
				self.line = line - 1
				if indicatornum > #lines[self.line] then
					self.indicatornum = #lines[self.line]
				end
			end
		end
		return
	elseif key == "down" then
		if multiline then
			if line < #lines then
				self.line = line + 1
				if indicatornum > #lines[self.line] then
					self.indicatornum = #lines[self.line]
				end
			end
		end
		return
	end
	
	if not editable then
		return
	end
	
	-- key input checking system
	if key == "backspace" then
		ckey = key
		if alltextselected then
			self:Clear()
			self.alltextselected = false
			indicatornum = self.indicatornum
		else
			if text ~= "" and indicatornum ~= 0 then
				text = self:RemoveFromText(indicatornum)
				self:MoveIndicator(-1)
				lines[line] = text
			end
			if multiline then
				if line > 1 and indicatornum == 0 then
					local newindicatornum = 0
					local oldtext = lines[line]
					table.remove(lines, line)
					self.line = line - 1
					if #oldtext > 0 then
						newindicatornum = #lines[self.line]
						lines[self.line] = lines[self.line] .. oldtext
						self:MoveIndicator(newindicatornum)
					else
						self:MoveIndicator(#lines[self.line])
					end
				end
			end
			local masked = self.masked
			local cwidth = 0
			if masked then
				local maskchar = self.maskchar
				cwidth = font:getWidth(text:sub(#text):gsub(".", maskchar))
			else
				cwidth = font:getWidth(text:sub(#text))
			end
			if self.offsetx > 0 then
				self.offsetx = self.offsetx - cwidth
			elseif self.offsetx < 0 then
				self.offsetx = 0
			end
		end
	elseif key == "delete" then
		if not editable then
			return
		end
		ckey = key
		if alltextselected then
			self:Clear()
			self.alltextselected = false
			indicatornum = self.indicatornum
		else
			if text ~= "" and indicatornum < #text then
				text = self:RemoveFromText(indicatornum + 1)
				lines[line] = text
			elseif indicatornum == #text and line < #lines then
				local oldtext = lines[line + 1]
				if #oldtext > 0 then
					newindicatornum = #lines[self.line]
					lines[self.line] = lines[self.line] .. oldtext
				end
				table.remove(lines, line + 1)
			end
		end
	elseif key == "return" or key == "kpenter" then
		ckey = key
		-- call onenter if it exists
		if onenter then
			onenter(self, text)
		end
		-- newline calculations for multiline mode
		if multiline then
			if alltextselected then
				self.alltextselected = false
				self:Clear()
				indicatornum = self.indicatornum
				line = self.line
			end
			local newtext = "" 
			if indicatornum == 0 then
				newtext = self.lines[line]
				self.lines[line] = ""
			elseif indicatornum > 0 and indicatornum < #self.lines[line] then
				newtext = self.lines[line]:sub(indicatornum + 1, #self.lines[line])
				self.lines[line] = self.lines[line]:sub(1, indicatornum)
			end
			if line ~= #lines then
				table.insert(self.lines, line + 1, newtext)
				self.line = line + 1
			else
				table.insert(self.lines, newtext)
				self.line = line + 1
			end
			self.indicatornum = 0
		end
	elseif key == "tab" then
		ckey = key
		for i=1, #self.tabreplacement do
			local number = string.byte(self.tabreplacement:sub(i, i))
			self.lines[self.line] = self:AddIntoText(number, self.indicatornum)
			self:MoveIndicator(1)
		end
	else
		if not is_text then
			return
		end
		self.unicode = unicode
		if unicode > 31 and unicode < 127 then
			-- do not continue if the text limit has been reached or exceeded
			if #text >= self.limit and self.limit ~= 0 then
				return
			end
			-- set the current key
			ckey = string.char(unicode)
			-- check for unusable characters
			if #self.usable > 0 then
				local found = false
				for k, v in ipairs(self.usable) do
					if v == ckey then
						found = true
					end
				end
				if not found then
					return
				end
			end
			-- check for usable characters
			if #self.unusable > 0 then
				local found = false
				for k, v in ipairs(self.unusable) do
					if v == ckey then
						found = true
					end
				end
				if found then
					return
				end
			end
			if alltextselected then
				self.alltextselected = false
				self:Clear()
				indicatornum = self.indicatornum
				text = ""
				lines = self.lines
				line = self.line
			end
			if indicatornum ~= 0 and indicatornum ~= #text then
				text = self:AddIntoText(unicode, indicatornum)
				lines[line] = text
				self:MoveIndicator(1)
			elseif indicatornum == #text then
				text = text .. ckey
				lines[line] = text
				self:MoveIndicator(1)
			elseif indicatornum == 0 then
				text = self:AddIntoText(unicode, indicatornum)
				lines[line] = text
				self:MoveIndicator(1)
			end
			lines = self.lines
			line = self.line
			curline = lines[line]
			text = curline
			if not multiline then
				local masked = self.masked
				local twidth = 0
				local cwidth = 0
				if masked then
					local maskchar = self.maskchar
					twidth = font:getWidth(text:gsub(".", maskchar))
					cwidth = font:getWidth(ckey:gsub(".", maskchar))
				else
					twidth = font:getWidth(text)
					cwidth = font:getWidth(ckey)
				end
				-- swidth - 1 is for the "-" character
				if (twidth + textoffsetx) >= (swidth - 1) then
					self.offsetx = self.offsetx + cwidth
				end
			end
		end
	end
	
	local curtext = self:GetText()
	if ontextchanged and initialtext ~= curtext then
		ontextchanged(self, ckey)
	end
	
end

--[[---------------------------------------------------------
	- func: MoveIndicator(num, exact)
	- desc: moves the object's indicator
--]]---------------------------------------------------------
function newobject:MoveIndicator(num, exact)

	local lines = self.lines
	local line = self.line
	local curline = lines[line]
	local text = curline
	local indicatornum = self.indicatornum
	
	if not exact then
		self.indicatornum = indicatornum + num
	else
		self.indicatornum = num
	end
	
	if self.indicatornum > #text then
		self.indicatornum = #text
	elseif self.indicatornum < 0 then
		self.indicatornum = 0
	end
	
	self.showindicator = true
	self:UpdateIndicator()
	
end

--[[---------------------------------------------------------
	- func: UpdateIndicator()
	- desc: updates the object's text insertion position 
			indicator
--]]---------------------------------------------------------
function newobject:UpdateIndicator()

	local time = love.timer.getTime()
	local indincatortime = self.indincatortime
	local indicatornum = self.indicatornum
	local lines = self.lines
	local line = self.line
	local curline = lines[line]
	local text = curline
	local font = self.font
	local theight = font:getHeight()
	local offsetx = self.offsetx
	local multiline = self.multiline
	local showindicator = self.showindicator
	local alltextselected = self.alltextselected
	local textx = self.textx
	local texty = self.texty
	local masked = self.masked
	
	if indincatortime < time then
		if showindicator then
			self.showindicator = false
		else
			self.showindicator = true
		end
		self.indincatortime = time + 0.50
	end
	
	if alltextselected then
		self.showindicator = false
	end
	
	local width = 0
	
	for i=1, indicatornum do
		if masked then
			local char = self.maskchar
			width = width + font:getWidth(char)
		else
			local char = text:sub(i, i)
			width = width + font:getWidth(char)
		end
	end
	
	if multiline then
		self.indicatorx = textx + width
		self.indicatory	= texty + theight * line - theight
	else
		self.indicatorx = textx + width
		self.indicatory	= texty
	end
	
end

--[[---------------------------------------------------------
	- func: AddIntoText(t, p)
	- desc: adds text into the object's text at a given 
			position
--]]---------------------------------------------------------
function newobject:AddIntoText(t, p)

	local lines = self.lines
	local line = self.line
	local curline = lines[line]
	local text = curline
	local part1 = text:sub(1, p)
	local part2 = text:sub(p + 1)
	local new = part1 .. string.char(t) .. part2
	
	return new
	
end

--[[---------------------------------------------------------
	- func: RemoveFromText(p)
	- desc: removes text from the object's text a given 
			position
--]]---------------------------------------------------------
function newobject:RemoveFromText(p)

	local lines = self.lines
	local line = self.line
	local curline = lines[line]
	local text = curline
	local part1 = text:sub(1, p - 1)
	local part2 = text:sub(p + 1)
	local new = part1 .. part2
	return new
	
end

--[[---------------------------------------------------------
	- func: GetTextCollisions(x, y)
	- desc: gets text collisions with the mouse
--]]---------------------------------------------------------
function newobject:GetTextCollisions(x, y)

	local font = self.font
	local lines = self.lines
	local numlines = #lines
	local line = self.line
	local curline = lines[line]
	local text = curline
	local xpos = 0
	local line = 0
	local vbar = self.vbar
	local hbar = self.hbar
	local multiline = self.multiline
	local selfx = self.x
	local selfy = self.y
	local selfwidth = self.width
	local masked = self.masked
			
	if multiline then
		local theight = font:getHeight()
		local liney = 0
		local selfcol
		if vbar and not hbar then
			selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth - 16, 1, self.height, 1)
		elseif hbar and not vbar then
			selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth, 1, self.height - 16, 1)
		elseif not vbar and not hbar then
			selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth, 1, self.height, 1)
		elseif vbar and hbar then
			selfcol = loveframes.util.BoundingBox(selfx, x, selfy, y, selfwidth - 16, 1, self.height - 16, 1)
		end
		if selfcol then
			local offsety = self.offsety
			local textoffsety = self.textoffsety
			for i=1, numlines do
				local linecol = loveframes.util.BoundingBox(selfx, x, (selfy - offsety) + textoffsety + (theight * i) - theight, y, self.width, 1, theight, 1)
				if linecol then
					liney = (selfy - offsety) + textoffsety + (theight * i) - theight
					self.line = i
				end
			end
			local line = self.line
			local curline = lines[line]
			for i=1, #curline do
				local char = text:sub(i, i)
				local width = 0
				if masked then
					local maskchar = self.maskchar
					width = font:getWidth(maskchar)
				else
					width = font:getWidth(char)
				end
				local height = font:getHeight()
				local tx = self.textx + xpos
				local ty = self.texty
				local col = loveframes.util.BoundingBox(tx, x, liney, y, width, 1, height, 1)
				
				xpos = xpos + width
				
				if col then
					self:MoveIndicator(i - 1, true)
					break
				else
					self.indicatornum = #curline
				end
				
				if x < tx then
					self:MoveIndicator(0, true)
				end
				
				if x > (tx + width) then
					self:MoveIndicator(#curline, true)
				end
			end
			
			if #curline == 0 then
				self.indicatornum = 0
			end
		end
	else
		for i=1, #text do
			local char = text:sub(i, i)
			local width = 0
			if masked then
				local maskchar = self.maskchar
				width = font:getWidth(maskchar)
			else
				width = font:getWidth(char)
			end
			local height = font:getHeight()
			local tx = self.textx + xpos
			local ty = self.texty
			local col = loveframes.util.BoundingBox(tx, x, ty, y, width, 1, height, 1)
			xpos = xpos + width
			if col then
				self:MoveIndicator(i - 1, true)
				break
			end
			if x < tx then
				self:MoveIndicator(0, true)
			end
			if x > (tx + width) then
				self:MoveIndicator(#text, true)
			end
		end
	end
	
end

--[[---------------------------------------------------------
	- func: PositionText()
	- desc: positions the object's text
--]]---------------------------------------------------------
function newobject:PositionText()

	local multiline = self.multiline
	local x = self.x
	local y = self.y
	local offsetx = self.offsetx
	local offsety = self.offsety
	local textoffsetx = self.textoffsetx
	local textoffsety = self.textoffsety
	local linenumberspanel = self.linenumberspanel
	
	if multiline then
		if linenumberspanel then
			local panel = self:GetLineNumbersPanel()
			self.textx = ((x + panel.width) - offsetx) + textoffsetx
			self.texty = (y - offsety) + textoffsety
		else
			self.textx = (x - offsetx) + textoffsetx
			self.texty = (y - offsety) + textoffsety
		end
	else
		self.textx = (x - offsetx) + textoffsetx
		self.texty = (y - offsety) + textoffsety
	end
	
end

--[[---------------------------------------------------------
	- func: SetTextOffsetX(num)
	- desc: sets the object's text x offset
--]]---------------------------------------------------------
function newobject:SetTextOffsetX(num)

	self.textoffsetx = num
	
end

--[[---------------------------------------------------------
	- func: SetTextOffsetY(num)
	- desc: sets the object's text y offset
--]]---------------------------------------------------------
function newobject:SetTextOffsetY(num)

	self.textoffsety = num
	
end

--[[---------------------------------------------------------
	- func: SetFont(font)
	- desc: sets the object's font
--]]---------------------------------------------------------
function newobject:SetFont(font)

	self.font = font
	
end

--[[---------------------------------------------------------
	- func: GetFont()
	- desc: gets the object's font
--]]---------------------------------------------------------
function newobject:GetFont()

	return self.font
	
end

--[[---------------------------------------------------------
	- func: SetFocus(focus)
	- desc: sets the object's focus
--]]---------------------------------------------------------
function newobject:SetFocus(focus)

	local inputobject = loveframes.inputobject
	local onfocusgained = self.OnFocusGained
	local onfocuslost = self.OnFocusLost
	
	self.focus = focus
	
	if focus then
		loveframes.inputobject = self
		if onfocusgained then
			onfocusgained(self)
		end
	else
		if inputobject == self then
			loveframes.inputobject = false
		end
		if onfocuslost then
			onfocuslost(self)
		end
	end
	
end

--[[---------------------------------------------------------
	- func: GetFocus()
	- desc: gets the object's focus
--]]---------------------------------------------------------
function newobject:GetFocus()

	return self.focus
	
end

--[[---------------------------------------------------------
	- func: GetIndicatorVisibility()
	- desc: gets the object's indicator visibility
--]]---------------------------------------------------------
function newobject:GetIndicatorVisibility()

	return self.showindicator
	
end

--[[---------------------------------------------------------
	- func: SetLimit(limit)
	- desc: sets the object's text limit
--]]---------------------------------------------------------
function newobject:SetLimit(limit)

	self.limit = limit
	
end

--[[---------------------------------------------------------
	- func: SetUsable(usable)
	- desc: sets what characters can be used for the 
			object's text
--]]---------------------------------------------------------
function newobject:SetUsable(usable)

	self.usable = usable
	
end

--[[---------------------------------------------------------
	- func: GetUsable()
	- desc: gets what characters can be used for the 
			object's text
--]]---------------------------------------------------------
function newobject:GetUsable()

	return self.usable
	
end

--[[---------------------------------------------------------
	- func: SetUnusable(unusable)
	- desc: sets what characters can not be used for the 
			object's text
--]]---------------------------------------------------------
function newobject:SetUnusable(unusable)

	self.unusable = unusable
	
end

--[[---------------------------------------------------------
	- func: GetUnusable()
	- desc: gets what characters can not be used for the 
			object's text
--]]---------------------------------------------------------
function newobject:GetUnusable()

	return self.unusable
	
end

--[[---------------------------------------------------------
	- func: Clear()
	- desc: clears the object's text
--]]---------------------------------------------------------
function newobject:Clear()

	self.lines = {""}
	self.line = 1
	self.offsetx = 0
	self.offsety = 0
	self.indicatornum = 0
	
end

--[[---------------------------------------------------------
	- func: SetText(text)
	- desc: sets the object's text
--]]---------------------------------------------------------
function newobject:SetText(text)

	local tabreplacement = self.tabreplacement
	local multiline = self.multiline
	
	text = tostring(text)
	text = text:gsub(string.char(9), tabreplacement)
	text = text:gsub(string.char(13), "")
	
	if multiline then
		text = text:gsub(string.char(92) .. string.char(110), string.char(10))
		local t = loveframes.util.SplitString(text, string.char(10))
		if #t > 0 then
			self.lines = t
		else
			self.lines = {""}
		end
		self.line = #self.lines
		self.indicatornum = #self.lines[#self.lines]
	else
		text = text:gsub(string.char(92) .. string.char(110), "")
		text = text:gsub(string.char(10), "")
		self.lines = {text}
		self.line = 1
		self.indicatornum = #text
	end
	
end

--[[---------------------------------------------------------
	- func: GetText()
	- desc: gets the object's text
--]]---------------------------------------------------------
function newobject:GetText()

	local multiline = self.multiline
	local lines = self.lines
	local text = ""
	
	if multiline then
		for k, v in ipairs(lines) do
			text = text .. v
			if k ~= #lines then
				text = text .. "\n"
			end
		end
	else
		text = lines[1]
	end
	
	return text
	
end

--[[---------------------------------------------------------
	- func: SetMultiline(bool)
	- desc: enables or disables allowing multiple lines for
			text entry
--]]---------------------------------------------------------
function newobject:SetMultiline(bool)

	local text = ""
	local lines = self.lines
	
	self.multiline = bool
	
	if bool then
		self:Clear()
	else
		for k, v in ipairs(lines) do
			text = text .. v
		end
		self:SetText(text)
		self.internals = {}
		self.vbar = false
		self.hbar = false
		self.linenumberspanel = false
	end

end

--[[---------------------------------------------------------
	- func: GetMultiLine()
	- desc: gets whether or not the object is using multiple
			lines
--]]---------------------------------------------------------
function newobject:GetMultiLine()

	return self.multiline
	
end

--[[---------------------------------------------------------
	- func: GetVerticalScrollBody()
	- desc: gets the object's vertical scroll body
--]]---------------------------------------------------------
function newobject:GetVerticalScrollBody()

	local vbar = self.vbar
	local internals = self.internals
	local item = false
	
	if vbar then
		for k, v in ipairs(internals) do
			if v.type == "scrollbody" and v.bartype == "vertical" then
				item = v
			end
		end
	end
	
	return item

end

--[[---------------------------------------------------------
	- func: GetHorizontalScrollBody()
	- desc: gets the object's horizontal scroll body
--]]---------------------------------------------------------
function newobject:GetHorizontalScrollBody()

	local hbar = self.hbar
	local internals = self.internals
	local item = false
	
	if hbar then
		for k, v in ipairs(internals) do
			if v.type == "scrollbody" and v.bartype == "horizontal" then
				item = v
			end
		end
	end
	
	return item

end

--[[---------------------------------------------------------
	- func: HasVerticalScrollBar()
	- desc: gets whether or not the object has a vertical
			scroll bar
--]]---------------------------------------------------------
function newobject:HasVerticalScrollBar()

	return self.vbar
	
end

--[[---------------------------------------------------------
	- func: HasHorizontalScrollBar()
	- desc: gets whether or not the object has a horizontal
			scroll bar
--]]---------------------------------------------------------
function newobject:HasHorizontalScrollBar()

	return self.hbar
	
end

--[[---------------------------------------------------------
	- func: GetLineNumbersPanel()
	- desc: gets the object's line numbers panel
--]]---------------------------------------------------------
function newobject:GetLineNumbersPanel()

	local panel = self.linenumberspanel
	local internals = self.internals
	local item = false
	
	if panel then
		for k, v in ipairs(internals) do
			if v.type == "linenumberspanel" then
				item = v
			end
		end
	end
	
	return item
	
end

--[[---------------------------------------------------------
	- func: ShowLineNumbers(bool)
	- desc: sets whether or not to show line numbers when
			using multiple lines
--]]---------------------------------------------------------
function newobject:ShowLineNumbers(bool)

	local multiline = self.multiline
	
	if multiline then
		self.linenumbers = bool
	end
	
end

--[[---------------------------------------------------------
	- func: GetTextX()
	- desc: gets the object's text x
--]]---------------------------------------------------------
function newobject:GetTextX()

	return self.textx
	
end

--[[---------------------------------------------------------
	- func: GetTextY()
	- desc: gets the object's text y
--]]---------------------------------------------------------
function newobject:GetTextY()

	return self.texty
	
end

--[[---------------------------------------------------------
	- func: IsAllTextSelected()
	- desc: gets whether or not all of the object's text is
			selected
--]]---------------------------------------------------------
function newobject:IsAllTextSelected()

	return self.alltextselected
	
end

--[[---------------------------------------------------------
	- func: GetLines()
	- desc: gets the object's lines
--]]---------------------------------------------------------
function newobject:GetLines()

	return self.lines
	
end

--[[---------------------------------------------------------
	- func: GetOffsetX()
	- desc: gets the object's x offset
--]]---------------------------------------------------------
function newobject:GetOffsetX()

	return self.offsetx
	
end

--[[---------------------------------------------------------
	- func: GetOffsetY()
	- desc: gets the object's y offset
--]]---------------------------------------------------------
function newobject:GetOffsetY()

	return self.offsety
	
end

--[[---------------------------------------------------------
	- func: GetIndicatorX()
	- desc: gets the object's indicator's xpos
--]]---------------------------------------------------------
function newobject:GetIndicatorX()

	return self.indicatorx
	
end

--[[---------------------------------------------------------
	- func: GetIndicatorY()
	- desc: gets the object's indicator's ypos
--]]---------------------------------------------------------
function newobject:GetIndicatorY()

	return self.indicatory
	
end

--[[---------------------------------------------------------
	- func: GetLineNumbersEnabled()
	- desc: gets whether line numbers are enabled on the
			object or not
--]]---------------------------------------------------------
function newobject:GetLineNumbersEnabled()

	return self.linenumbers
	
end

--[[---------------------------------------------------------
	- func: GetItemWidth()
	- desc: gets the object's item width
--]]---------------------------------------------------------
function newobject:GetItemWidth()

	return self.itemwidth
	
end

--[[---------------------------------------------------------
	- func: GetItemHeight()
	- desc: gets the object's item height
--]]---------------------------------------------------------
function newobject:GetItemHeight()

	return self.itemheight
	
end

--[[---------------------------------------------------------
	- func: SetTabReplacement(tabreplacement)
	- desc: sets a string to replace tabs with
--]]---------------------------------------------------------
function newobject:SetTabReplacement(tabreplacement)

	self.tabreplacement = tabreplacement
	
end

--[[---------------------------------------------------------
	- func: GetTabReplacement()
	- desc: gets the object's tab replacement
--]]---------------------------------------------------------
function newobject:GetTabReplacement()

	return self.tabreplacement
	
end

--[[---------------------------------------------------------
	- func: SetEditable(bool)
	- desc: sets whether or not the user can edit the
			object's text
--]]---------------------------------------------------------
function newobject:SetEditable(bool)

	self.editable = bool
	
end

--[[---------------------------------------------------------
	- func: GetEditable
	- desc: gets whether or not the user can edit the
			object's text
--]]---------------------------------------------------------
function newobject:GetEditable()

	return self.editable
	
end

--[[---------------------------------------------------------
	- func: SetButtonScrollAmount(speed)
	- desc: sets the scroll amount of the object's scrollbar
			buttons
--]]---------------------------------------------------------
function newobject:SetButtonScrollAmount(amount)

	self.buttonscrollamount = amount
	
end

--[[---------------------------------------------------------
	- func: GetButtonScrollAmount()
	- desc: gets the scroll amount of the object's scrollbar
			buttons
--]]---------------------------------------------------------
function newobject:GetButtonScrollAmount()

	return self.buttonscrollamount
	
end

--[[---------------------------------------------------------
	- func: SetMouseWheelScrollAmount(amount)
	- desc: sets the scroll amount of the mouse wheel
--]]---------------------------------------------------------
function newobject:SetMouseWheelScrollAmount(amount)

	self.mousewheelscrollamount = amount
	
end

--[[---------------------------------------------------------
	- func: GetMouseWheelScrollAmount()
	- desc: gets the scroll amount of the mouse wheel
--]]---------------------------------------------------------
function newobject:GetButtonScrollAmount()

	return self.mousewheelscrollamount
	
end

--[[---------------------------------------------------------
	- func: SetAutoScroll(bool)
	- desc: sets whether or not the object should autoscroll
			when in multiline mode
--]]---------------------------------------------------------
function newobject:SetAutoScroll(bool)
	
	local internals = self.internals
	
	self.autoscroll = bool
	
	if internals[2] then
		internals[2].internals[1].internals[1].autoscroll = bool
	end
	
end

--[[---------------------------------------------------------
	- func: GetAutoScroll()
	- desc: gets whether or not the object should autoscroll
			when in multiline mode
--]]---------------------------------------------------------
function newobject:GetAutoScroll()

	return self.autoscroll
	
end

--[[---------------------------------------------------------
	- func: SetRepeatDelay(delay)
	- desc: sets the object's repeat delay
--]]---------------------------------------------------------
function newobject:SetRepeatDelay(delay)

	self.repeatdelay = delay
	
end

--[[---------------------------------------------------------
	- func: GetRepeatDelay()
	- desc: gets the object's repeat delay
--]]---------------------------------------------------------
function newobject:GetRepeatDelay()

	return self.repeatdelay
	
end

--[[---------------------------------------------------------
	- func: SetRepeatRate(rate)
	- desc: sets the object's repeat rate
--]]---------------------------------------------------------
function newobject:SetRepeatRate(rate)

	self.repeatrate = rate
	
end

--[[---------------------------------------------------------
	- func: GetRepeatRate()
	- desc: gets the object's repeat rate
--]]---------------------------------------------------------
function newobject:GetRepeatRate()

	return self.repeatrate
	
end

--[[---------------------------------------------------------
	- func: SetValue(value)
	- desc: sets the object's value (alias of SetText)
--]]---------------------------------------------------------
function newobject:SetValue(value)

	self:SetText(value)
	
end

--[[---------------------------------------------------------
	- func: GetValue()
	- desc: gets the object's value (alias of GetText)
--]]---------------------------------------------------------
function newobject:GetValue()

	return self:GetText()
	
end

--[[---------------------------------------------------------
	- func: SetVisible(bool)
	- desc: sets the object's visibility
--]]---------------------------------------------------------
function newobject:SetVisible(bool)

	self.visible = bool
	
	if not bool then
		self.keydown = "none"
	end
	
end

--[[---------------------------------------------------------
	- func: Copy()
	- desc: copies the object's text to the user's clipboard
--]]---------------------------------------------------------
function newobject:Copy()

	local version = love._version
	
	if version == "0.9.0" then
		local text = self:GetText()
		love.system.setClipboardText(text)
	end
	
end

--[[---------------------------------------------------------
	- func: Paste()
	- desc: pastes the current contents of the clipboard 
			into the object's text
--]]---------------------------------------------------------
function newobject:Paste()
	
	local text = love.system.getClipboardText()
	local usable = self.usable
	local unusable = self.unusable
	local limit = self.limit
	local alltextselected = self.alltextselected
	local onpaste = self.OnPaste
	local ontextchanged = self.OnTextChanged
	
	if limit > 0 then
		local curtext = self:GetText()
		local curlength = curtext:len()
		if curlength == limit then
			return
		else
			local inputlimit = limit - curlength
			if text:len() > inputlimit then
				text = text:sub(1, inputlimit)
			end
		end
	end
	local charcheck = function(a)
		if #usable > 0 then
			if not loveframes.util.TableHasValue(usable, a) then
				return ""
			end
		elseif #unusable > 0 then
			if loveframes.util.TableHasValue(unusable, a) then
				return ""
			end
		end
	end
	if #usable > 0 or #unusable > 0 then
		text = text:gsub(".", charcheck)
	end
	if alltextselected then
		self:SetText(text)
		self.alltextselected = false
		if ontextchanged then
			ontextchanged(self, text)
		end
	else
		local tabreplacement = self.tabreplacement
		local indicatornum = self.indicatornum
		local lines = self.lines
		local multiline = self.multiline
		if multiline then
			local parts = loveframes.util.SplitString(text, string.char(10))
			local numparts = #parts
			local oldlinedata = {}
			local line = self.line
			local first = lines[line]:sub(0, indicatornum)
			local last = lines[line]:sub(indicatornum + 1)
			if numparts > 1 then
				for i=1, numparts do
					local part = parts[i]:gsub(string.char(13),  "")
					part = part:gsub(string.char(9), "    ")
					if i ~= 1 then
						table.insert(oldlinedata, lines[line])
						lines[line] = part
						if i == numparts then
							self.indicatornum = part:len()
							lines[line] = lines[line] .. last
							self.line = line
						end
					else
						lines[line] = first .. part
					end
					line = line + 1
				end
				for i=1, #oldlinedata do
					lines[line] = oldlinedata[i]
					line = line + 1
				end
				if ontextchanged then
					ontextchanged(self, text)
				end
			elseif numparts == 1 then
				text = text:gsub(string.char(10), " ")
				text = text:gsub(string.char(13), " ")
				text = text:gsub(string.char(9), tabreplacement)
				local length = text:len()
				local new = first .. text .. last
				lines[line] = new
				self.indicatornum = indicatornum + length
				if ontextchanged then
					ontextchanged(self, text)
				end
			end
		else
			text = text:gsub(string.char(10), " ")
			text = text:gsub(string.char(13), " ")
			text = text:gsub(string.char(9), tabreplacement)
			local length = text:len()
			local linetext = lines[1]
			local part1 = linetext:sub(1, indicatornum)
			local part2 = linetext:sub(indicatornum + 1)
			local new = part1 .. text .. part2
			lines[1] = new
			self.indicatornum = indicatornum + length
			if ontextchanged then
				ontextchanged(self, text)
			end
		end
	end
	if onpaste then
		onpaste(self, text)
	end
	
end

--[[---------------------------------------------------------
	- func: SelectAll()
	- desc: selects all of the object's text
--]]---------------------------------------------------------
function newobject:SelectAll()

	self.alltextselected = true
	
end

--[[---------------------------------------------------------
	- func: DeselectAll()
	- desc: deselects all of the object's text
--]]---------------------------------------------------------
function newobject:DeselectAll()

	self.alltextselected = false
	
end

--[[---------------------------------------------------------
	- func: SetMasked(masked)
	- desc: sets whether or not the object is masked
--]]---------------------------------------------------------
function newobject:SetMasked(masked)

	self.masked = masked
	
end

--[[---------------------------------------------------------
	- func: GetMasked()
	- desc: gets whether or not the object is masked
--]]---------------------------------------------------------
function newobject:GetMasked()

	return self.masked
	
end

--[[---------------------------------------------------------
	- func: SetMaskChar(char)
	- desc: sets the object's mask character
--]]---------------------------------------------------------
function newobject:SetMaskChar(char)

	self.maskchar = char
	
end

--[[---------------------------------------------------------
	- func: GetMaskChar()
	- desc: gets the object's mask character
--]]---------------------------------------------------------
function newobject:GetMaskChar()

	return self.maskchar
	
end

--[[---------------------------------------------------------
	- func: SetPlaceholderText(text)
	- desc: sets the object's placeholder text
--]]---------------------------------------------------------
function newobject:SetPlaceholderText(text)

	self.placeholder = text
	
end

--[[---------------------------------------------------------
	- func: GetPlaceholderText()
	- desc: gets the object's placeholder text
--]]---------------------------------------------------------
function newobject:GetPlaceholderText()

	return self.placeholder
	
end