mirror of
				https://github.com/kepler155c/opus
				synced 2025-10-31 07:33:00 +00:00 
			
		
		
		
	properly handle empty text entry fields (including transformations)
This commit is contained in:
		| @@ -2,39 +2,48 @@ local class = require('opus.class') | ||||
|  | ||||
| local os = _G.os | ||||
|  | ||||
| -- convert value to a string (supporting nils or numbers in value) | ||||
| local function _val(a) | ||||
| 	return a and tostring(a) or '' | ||||
| end | ||||
|  | ||||
| local Entry = class() | ||||
|  | ||||
| function Entry:init(args) | ||||
| 	self.pos = 0 | ||||
| 	self.scroll = 0 | ||||
| 	self.value = '' | ||||
| 	self.value = args.value | ||||
| 	self.width = args.width or 256 | ||||
| 	self.limit = args.limit or 1024 | ||||
| 	self.mark = { } | ||||
| 	self.offset = args.offset or 1 | ||||
| 	self.transform = args.transform or function(a) return a end | ||||
| end | ||||
|  | ||||
| function Entry:reset() | ||||
| 	self.pos = 0 | ||||
| 	self.scroll = 0 | ||||
| 	self.value = '' | ||||
| 	self.value = nil | ||||
| 	self.mark = { } | ||||
| end | ||||
|  | ||||
| function Entry:nextWord() | ||||
| 	return select(2, self.value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #self.value | ||||
| 	local value = _val(self.value) | ||||
| 	return select(2, value:find("[%s%p]?%w[%s%p]", self.pos + 1)) or #value | ||||
| end | ||||
|  | ||||
| function Entry:prevWord() | ||||
| 	local x = #self.value - (self.pos - 1) | ||||
| 	local _, n = self.value:reverse():find("[%s%p]?%w[%s%p]", x) | ||||
| 	return n and #self.value - n + 1 or 0 | ||||
| 	local value = _val(self.value) | ||||
| 	local x = #value - (self.pos - 1) | ||||
| 	local _, n = value:reverse():find("[%s%p]?%w[%s%p]", x) | ||||
| 	return n and #value - n + 1 or 0 | ||||
| end | ||||
|  | ||||
| function Entry:updateScroll() | ||||
| 	local ps = self.scroll | ||||
| 	if self.pos > #self.value then | ||||
| 		self.pos = #self.value | ||||
| 	local value = _val(self.value) | ||||
| 	if self.pos > #value then | ||||
| 		self.pos = #value | ||||
| 		self.scroll = 0 -- ?? | ||||
| 	end | ||||
| 	if self.pos - self.scroll > self.width then | ||||
| @@ -48,21 +57,25 @@ function Entry:updateScroll() | ||||
| end | ||||
|  | ||||
| function Entry:copyText(cx, ex) | ||||
| 	return self.value:sub(cx + 1, ex) | ||||
| 	-- this should be transformed (ie. if number) | ||||
| 	return _val(self.value):sub(cx + 1, ex) | ||||
| end | ||||
|  | ||||
| function Entry:insertText(x, text) | ||||
| 	if #self.value + #text > self.limit then | ||||
| 		text = text:sub(1, self.limit-#self.value) | ||||
| 	text = tostring(self.transform(text)) or '' | ||||
| 	local value = _val(self.value) | ||||
| 	if #value + #text > self.limit then | ||||
| 		text = text:sub(1, self.limit-#value) | ||||
| 	end | ||||
| 	self.value = self.value:sub(1, x) .. text .. self.value:sub(x + 1) | ||||
| 	self.value = self.transform(value:sub(1, x) .. text .. value:sub(x + 1)) | ||||
| 	self.pos = self.pos + #text | ||||
| end | ||||
|  | ||||
| function Entry:deleteText(sx, ex) | ||||
| 	local front = self.value:sub(1, sx) | ||||
| 	local back = self.value:sub(ex + 1, #self.value) | ||||
| 	self.value = front .. back | ||||
| 	local value = _val(self.value) | ||||
| 	local front = value:sub(1, sx) | ||||
| 	local back = value:sub(ex + 1, #value) | ||||
| 	self.value = self.transform(front .. back) | ||||
| 	self.pos = sx | ||||
| end | ||||
|  | ||||
| @@ -74,7 +87,7 @@ function Entry:moveLeft() | ||||
| end | ||||
|  | ||||
| function Entry:moveRight() | ||||
| 	if self.pos < #self.value then | ||||
| 	if self.pos < #_val(self.value) then | ||||
| 		self.pos = self.pos + 1 | ||||
| 		return true | ||||
| 	end | ||||
| @@ -88,14 +101,14 @@ function Entry:moveHome() | ||||
| end | ||||
|  | ||||
| function Entry:moveEnd() | ||||
| 	if self.pos ~= #self.value then | ||||
| 		self.pos = #self.value | ||||
| 	if self.pos ~= #_val(self.value) then | ||||
| 		self.pos = #_val(self.value) | ||||
| 		return true | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function Entry:moveTo(ie) | ||||
| 	self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #self.value)) | ||||
| 	self.pos = math.max(0, math.min(ie.x + self.scroll - self.offset, #_val(self.value))) | ||||
| end | ||||
|  | ||||
| function Entry:backspace() | ||||
| @@ -107,7 +120,7 @@ function Entry:backspace() | ||||
| end | ||||
|  | ||||
| function Entry:moveWordRight() | ||||
| 	if self.pos < #self.value then | ||||
| 	if self.pos < #_val(self.value) then | ||||
| 		self.pos = self:nextWord(self.value, self.pos + 1) | ||||
| 		return true | ||||
| 	end | ||||
| @@ -123,7 +136,7 @@ end | ||||
| function Entry:delete() | ||||
| 	if self.mark.active then | ||||
| 		self:deleteText(self.mark.x, self.mark.ex) | ||||
| 	elseif self.pos < #self.value then | ||||
| 	elseif self.pos < #_val(self.value) then | ||||
| 		self:deleteText(self.pos, self.pos + 1) | ||||
| 	end | ||||
| end | ||||
| @@ -137,15 +150,16 @@ function Entry:cutFromStart() | ||||
| end | ||||
|  | ||||
| function Entry:cutToEnd() | ||||
| 	if self.pos < #self.value then | ||||
| 		local text = self:copyText(self.pos, #self.value) | ||||
| 		self:deleteText(self.pos, #self.value) | ||||
| 	local value = _val(self.value) | ||||
| 	if self.pos < #value then | ||||
| 		local text = self:copyText(self.pos, #value) | ||||
| 		self:deleteText(self.pos, #value) | ||||
| 		os.queueEvent('clipboard_copy', text) | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function Entry:cutNextWord() | ||||
| 	if self.pos < #self.value then | ||||
| 	if self.pos < #_val(self.value) then | ||||
| 		local ex = self:nextWord(self.value, self.pos) | ||||
| 		local text = self:copyText(self.pos, ex) | ||||
| 		self:deleteText(self.pos, ex) | ||||
| @@ -170,7 +184,7 @@ function Entry:insertChar(ie) | ||||
| end | ||||
|  | ||||
| function Entry:copy() | ||||
| 	if #self.value > 0 then | ||||
| 	if #_val(self.value) > 0 then | ||||
| 		self.mark.continue = true | ||||
| 		if self.mark.active then | ||||
| 			self:copyMarked() | ||||
| @@ -202,7 +216,7 @@ function Entry:paste(ie) | ||||
| end | ||||
|  | ||||
| function Entry:clearLine() | ||||
| 	if #self.value > 0 then | ||||
| 	if #_val(self.value) > 0 then | ||||
| 		self:reset() | ||||
| 	end | ||||
| end | ||||
| @@ -233,10 +247,13 @@ function Entry:unmark() | ||||
| end | ||||
|  | ||||
| function Entry:markAnchor(ie) | ||||
| 	local wasMarking = self.mark.active | ||||
| 	self:unmark() | ||||
| 	self:moveTo(ie) | ||||
| 	self:markBegin() | ||||
| 	self:markFinish() | ||||
|  | ||||
| 	self.textChanged = wasMarking | ||||
| end | ||||
|  | ||||
| function Entry:markLeft() | ||||
| @@ -257,7 +274,7 @@ function Entry:markWord(ie) | ||||
| 	local index = 1 | ||||
| 	self:moveTo(ie) | ||||
| 	while true do | ||||
| 		local s, e = self.value:find('%w+', index) | ||||
| 		local s, e = _val(self.value):find('%w+', index) | ||||
| 		if not s or s - 1 > self.pos then | ||||
| 			break | ||||
| 		end | ||||
| @@ -288,12 +305,12 @@ function Entry:markPrevWord() | ||||
| end | ||||
|  | ||||
| function Entry:markAll() | ||||
| 	if #self.value > 0 then | ||||
| 	if #_val(self.value) > 0 then | ||||
| 		self.mark.anchor = { x = 1 } | ||||
| 		self.mark.active = true | ||||
| 		self.mark.continue = true | ||||
| 		self.mark.x = 0 | ||||
| 		self.mark.ex = #self.value | ||||
| 		self.mark.ex = #_val(self.value) | ||||
| 		self.textChanged = true | ||||
| 	end | ||||
| end | ||||
| @@ -373,6 +390,10 @@ function Entry:process(ie) | ||||
|  | ||||
| 		action(self, ie) | ||||
|  | ||||
| 		if not self.value or #_val(self.value) == 0 then | ||||
| 			self.value = nil | ||||
| 		end | ||||
| _syslog(tostring(line) .. ' ' .. tostring(self.value) .. ' ' .. tostring(self.textChanged)) | ||||
| 		self.textChanged = self.textChanged or self.value ~= line | ||||
| 		self.posChanged = pos ~= self.pos | ||||
| 		self:updateScroll() | ||||
|   | ||||
| @@ -32,7 +32,7 @@ function UI.Form:setValues(values) | ||||
| 			if child.setValue then | ||||
| 				child:setValue(self.values[child.formKey]) | ||||
| 			else | ||||
| 				child.value = self.values[child.formKey] or '' | ||||
| 				child.value = self.values[child.formKey] | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| @@ -56,7 +56,7 @@ function UI.Form:createForm() | ||||
| 	for _, child in pairs(self) do | ||||
| 		if type(child) == 'table' and child.UIElement then | ||||
| 			if child.formKey then | ||||
| 				child.value = self.values[child.formKey] or '' | ||||
| 				child.value = self.values[child.formKey] | ||||
| 			end | ||||
| 			if child.formLabel then | ||||
| 				child.x = self.labelWidth + self.margin - 1 | ||||
| @@ -99,14 +99,6 @@ function UI.Form:validateField(field) | ||||
| 			return false, 'Field is required' | ||||
| 		end | ||||
| 	end | ||||
| 	if field.validate == 'numeric' then | ||||
| 		field.value = field.value or '' | ||||
| 		if #tostring(field.value) > 0 then | ||||
| 			if not tonumber(field.value) then | ||||
| 				return false, 'Invalid number' | ||||
| 			end | ||||
| 		end | ||||
| 	end | ||||
| 	return true | ||||
| end | ||||
|  | ||||
| @@ -124,11 +116,7 @@ function UI.Form:save() | ||||
| 	end | ||||
| 	for _,child in pairs(self.children) do | ||||
| 		if child.formKey then | ||||
| 			if child.validate == 'numeric' then | ||||
| 				self.values[child.formKey] = tonumber(child.value) | ||||
| 			else | ||||
| 				self.values[child.formKey] = child.value | ||||
| 			end | ||||
| 			self.values[child.formKey] = child.value | ||||
| 		end | ||||
| 	end | ||||
|  | ||||
|   | ||||
| @@ -493,3 +493,43 @@ function UI.Grid:eventHandler(event) | ||||
| 	end | ||||
| 	return true | ||||
| end | ||||
|  | ||||
| function UI.Grid.example() | ||||
| 	local values = { | ||||
| 		{ key = 'key1', value = 'value1' }, | ||||
| 		{ key = 'key2', value = 'value2' }, | ||||
| 		{ key = 'key3', value = 'value3-longer value text' }, | ||||
| 		{ key = 'key4', value = 'value4' }, | ||||
| 		{ key = 'key5', value = 'value5' }, | ||||
| 	} | ||||
| 	return UI.Window { | ||||
| 		regular = UI.Grid { | ||||
| 			ex = '48%', ey = 4, | ||||
| 			values = values, | ||||
| 			sortColumn = 'key', | ||||
| 			inverseSort = true, | ||||
| 			columns = { | ||||
| 				{ heading = 'key', key = 'key' }, | ||||
| 				{ heading = 'value', key = 'value' }, | ||||
| 			}, | ||||
| 		}, | ||||
| 		noheader = UI.Grid { | ||||
| 			ex = '48%', y = 6, ey = -2, | ||||
| 			disableHeader = true, | ||||
| 			values = values, | ||||
| 			columns = { | ||||
| 				{ heading = 'key', key = 'key', width = 6,  }, | ||||
| 				{ heading = 'value', key = 'value', textColor = colors.yellow }, | ||||
| 			}, | ||||
| 		}, | ||||
| 		autospace = UI.Grid { | ||||
| 			x = '52%', ey = 4, | ||||
| 			autospace = true, | ||||
| 			values = values, | ||||
| 			columns = { | ||||
| 				{ heading = 'key', key = 'key' }, | ||||
| 				{ heading = 'value', key = 'value' }, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
| end | ||||
|   | ||||
| @@ -5,8 +5,15 @@ local Util  = require('opus.util') | ||||
|  | ||||
| local colors = _G.colors | ||||
| local _rep   = string.rep | ||||
| local _lower = string.lower | ||||
| local _upper = string.upper | ||||
|  | ||||
| local function transform(directive) | ||||
| 	local transforms = { | ||||
| 		lowercase = string.lower, | ||||
| 		uppercase = string.upper, | ||||
| 		number    = tonumber, | ||||
| 	} | ||||
| 	return transforms[directive] | ||||
| end | ||||
|  | ||||
| UI.TextEntry = class(UI.Window) | ||||
| UI.TextEntry.defaults = { | ||||
| @@ -25,7 +32,7 @@ UI.TextEntry.defaults = { | ||||
| 	} | ||||
| } | ||||
| function UI.TextEntry:postInit() | ||||
| 	self.entry = entry({ limit = self.limit, offset = 2 }) | ||||
| 	self.entry = entry({ limit = self.limit, offset = 2, transform = transform(self.transform) }) | ||||
| end | ||||
|  | ||||
| function UI.TextEntry:layout() | ||||
| @@ -36,13 +43,13 @@ end | ||||
| function UI.TextEntry:setValue(value) | ||||
| 	self.value = value --or '' | ||||
| 	self.entry:unmark() | ||||
| 	self.entry.value = tostring(value) | ||||
| 	self.entry.value = value --tostring(value or '') | ||||
| 	self.entry:updateScroll() | ||||
| end | ||||
|  | ||||
| function UI.TextEntry:setPosition(pos) | ||||
| 	self.entry.pos = pos | ||||
| 	self.entry.value = tostring(self.value or '') | ||||
| 	self.entry.value = self.value --tostring(self.value or '') -- WHY HERE ? | ||||
| 	self.entry:updateScroll() | ||||
| end | ||||
|  | ||||
| @@ -105,27 +112,15 @@ function UI.TextEntry:focus() | ||||
| 	end | ||||
| end | ||||
|  | ||||
| function UI.TextEntry:_transform(text) | ||||
| 	if self.transform == 'lowercase' then | ||||
| 		return _lower(text) | ||||
| 	elseif self.transform == 'uppercase' then | ||||
| 		return _upper(text) | ||||
| 	elseif self.transform == 'number' then | ||||
| 		return tonumber(text) --or 0 | ||||
| 	end | ||||
| 	return text | ||||
| end | ||||
|  | ||||
| function UI.TextEntry:eventHandler(event) | ||||
| 	local text = self.value --or '' | ||||
| 	self.entry.value = tostring(text or '') | ||||
| 	local text = self.value | ||||
| 	self.entry.value = text | ||||
| 	if event.ie and self.entry:process(event.ie) then | ||||
| 		if self.entry.textChanged then | ||||
| 			self.value = self:_transform(self.entry.value) | ||||
| --_syslog(tostring(self.entry.value) .. ' ' .. tostring(self.value)) | ||||
| 			self.value = self.entry.value | ||||
| 			self:draw() | ||||
| 			if text ~= self.value then | ||||
| 				self:emit({ type = 'text_change', text = self.value, element = self }) | ||||
| 			end | ||||
| 			self:emit({ type = 'text_change', text = self.value, element = self }) | ||||
| 		elseif self.entry.posChanged then | ||||
| 			self:updateCursor() | ||||
| 		end | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 kepler155c@gmail.com
					kepler155c@gmail.com