Bump version numbers for 3.13
[maemo-rb.git] / apps / plugins / stopwatch.lua
blob578ba7f42cd2c85c96f2f6c1c106be25f06aa408
1 --[[
2 __________ __ ___.
3 Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 \/ \/ \/ \/ \/
8 $Id$
10 Port of Stopwatch to Lua for touchscreen targets.
11 Original copyright: Copyright (C) 2004 Mike Holden
13 Copyright (C) 2009 by Maurus Cuelenaere
15 This program is free software; you can redistribute it and/or
16 modify it under the terms of the GNU General Public License
17 as published by the Free Software Foundation; either version 2
18 of the License, or (at your option) any later version.
20 This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 KIND, either express or implied.
23 ]]--
25 require "actions"
26 require "buttons"
28 STOPWATCH_FILE = rb.PLUGIN_APPS_DATA_DIR .. "/stopwatch.dat"
31 local LapsView = {
32 lapTimes = {},
33 timer = {
34 counting = false,
35 prevTotal = 0,
36 startAt = 0,
37 current = 0
39 vp = {
40 x = 80,
41 y = 0,
42 width = rb.LCD_WIDTH - 80,
43 height = rb.LCD_HEIGHT,
44 font = rb.FONT_UI,
45 fg_pattern = rb.lcd_get_foreground()
47 scroll = {
48 prevY = 0,
49 cursorPos = 0
53 function LapsView:init()
54 local _, _, h = rb.font_getstringsize("", self.vp.font)
56 self.vp.maxLaps = self.vp.height / h
57 self.vp.lapHeight = h
59 self:loadState()
60 end
62 function LapsView:display()
63 rb.set_viewport(self.vp)
64 rb.clear_viewport()
66 local nrOfLaps = math.min(self.vp.maxLaps, #self.lapTimes)
67 rb.lcd_puts_scroll(0, 0, ticksToString(self.timer.current))
69 for i=1, nrOfLaps do
70 local idx = #self.lapTimes - self.scroll.cursorPos - i + 1
71 if self.lapTimes[idx] ~= nil then
72 rb.lcd_puts_scroll(0, i, ticksToString(self.lapTimes, idx))
73 end
74 end
76 rb.set_viewport(nil)
77 end
79 function LapsView:checkForScroll(btn, x, y)
80 if x > self.vp.x and x < self.vp.x + self.vp.width and
81 y > self.vp.y and y < self.vp.y + self.vp.height then
83 if bit.band(btn, rb.buttons.BUTTON_REL) == rb.buttons.BUTTON_REL then
84 self.scroll.prevY = 0
85 else
86 if #self.lapTimes > self.vp.maxLaps and self.scroll.prevY ~= 0 then
87 self.scroll.cursorPos = self.scroll.cursorPos -
88 (y - self.scroll.prevY) / self.vp.lapHeight
90 local maxLaps = math.min(self.vp.maxLaps, #self.lapTimes)
91 if self.scroll.cursorPos < 0 then
92 self.scroll.cursorPos = 0
93 elseif self.scroll.cursorPos >= maxLaps then
94 self.scroll.cursorPos = maxLaps
95 end
96 end
98 self.scroll.prevY = y
99 end
101 return true
102 else
103 return false
107 function LapsView:incTimer()
108 if self.timer.counting then
109 self.timer.current = self.timer.prevTotal + rb.current_tick()
110 - self.timer.startAt
111 else
112 self.timer.current = self.timer.prevTotal
116 function LapsView:startTimer()
117 self.timer.startAt = rb.current_tick()
118 self.timer.currentLap = self.timer.prevTotal
119 self.timer.counting = true
122 function LapsView:stopTimer()
123 self.timer.prevTotal = self.timer.prevTotal + rb.current_tick()
124 - self.timer.startAt
125 self.timer.counting = false
128 function LapsView:newLap()
129 table.insert(self.lapTimes, self.timer.current)
132 function LapsView:resetTimer()
133 self.lapTimes = {}
134 self.timer.counting = false
135 self.timer.current, self.timer.prevTotal, self.timer.startAt = 0, 0, 0
136 self.scroll.cursorPos = 0
139 function LapsView:saveState()
140 local fd = assert(io.open(STOPWATCH_FILE, "w"))
142 for _, v in ipairs({"current", "startAt", "prevTotal", "counting"}) do
143 assert(fd:write(tostring(self.timer[v]) .. "\n"))
145 for _, v in ipairs(self.lapTimes) do
146 assert(fd:write(tostring(v) .. "\n"))
149 fd:close()
152 function LapsView:loadState()
153 local fd = io.open(STOPWATCH_FILE, "r")
154 if fd == nil then return end
156 for _, v in ipairs({"current", "startAt", "prevTotal"}) do
157 self.timer[v] = tonumber(fd:read("*line"))
159 self.timer.counting = toboolean(fd:read("*line"))
161 local line = fd:read("*line")
162 while line do
163 table.insert(self.lapTimes, tonumber(line))
164 line = fd:read("*line")
167 fd:close()
170 local Button = {
171 x = 0,
172 y = 0,
173 width = 80,
174 height = 50,
175 label = ""
178 function Button:new(o)
179 local o = o or {}
181 if o.label then
182 local _, w, h = rb.font_getstringsize(o.label, LapsView.vp.font)
183 o.width = math.max(5 * w / 4,o.width)
184 o.height = 3 * h / 2
187 setmetatable(o, self)
188 self.__index = self
189 return o
192 function Button:draw()
193 local _, w, h = rb.font_getstringsize(self.label, LapsView.vp.font)
194 local x, y = (2 * self.x + self.width - w) / 2, (2 * self.y + self.height - h) / 2
196 rb.lcd_drawrect(self.x, self.y, self.width, self.height)
197 rb.lcd_putsxy(x, y, self.label)
200 function Button:isPressed(x, y)
201 return x > self.x and x < self.x + self.width and
202 y > self.y and y < self.y + self.height
205 -- Helper function
206 function ticksToString(laps, lap)
207 local ticks = type(laps) == "table" and laps[lap] or laps
208 lap = lap or 0
210 local hours = ticks / (rb.HZ * 3600)
211 ticks = ticks - (rb.HZ * hours * 3600)
212 local minutes = ticks / (rb.HZ * 60)
213 ticks = ticks - (rb.HZ * minutes * 60)
214 local seconds = ticks / rb.HZ
215 ticks = ticks - (rb.HZ * seconds)
216 local cs = ticks
218 if (lap == 0) then
219 return string.format("%2d:%02d:%02d.%02d", hours, minutes, seconds, cs)
220 else
221 if (lap > 1) then
222 local last_ticks = laps[lap] - laps[lap-1]
223 local last_hours = last_ticks / (rb.HZ * 3600)
224 last_ticks = last_ticks - (rb.HZ * last_hours * 3600)
225 local last_minutes = last_ticks / (rb.HZ * 60)
226 last_ticks = last_ticks - (rb.HZ * last_minutes * 60)
227 local last_seconds = last_ticks / rb.HZ
228 last_ticks = last_ticks - (rb.HZ * last_seconds)
229 local last_cs = last_ticks
231 return string.format("%2d %2d:%02d:%02d.%02d [%2d:%02d:%02d.%02d]",
232 lap, hours, minutes, seconds, cs, last_hours,
233 last_minutes, last_seconds, last_cs)
234 else
235 return string.format("%2d %2d:%02d:%02d.%02d", lap, hours, minutes, seconds, cs)
240 -- Helper function
241 function toboolean(v)
242 return v == "true"
245 function arrangeButtons(btns)
246 local totalWidth, totalHeight, maxWidth, maxHeight, vp = 0, 0, 0, 0
247 local width, row = 0, 0
248 local items, num_rows
250 for i, btn in pairs(btns) do
251 maxHeight = math.max(maxHeight, btn.height)
252 totalWidth = totalWidth + btn.width
253 items = i
256 for _, btn in pairs(btns) do
257 btn.height = maxHeight
260 num_rows = totalWidth / rb.LCD_WIDTH
262 for _, btn in pairs(btns) do
263 btn.x = width
264 btn.y = rb.LCD_HEIGHT - ((num_rows - row) * maxHeight)
265 width = width + btn.width
266 if (width > rb.LCD_WIDTH - 5) then -- 5 is rounding margin
267 width = 0
268 row = row+1
271 vp = {
272 x = 0,
273 y = 0,
274 width = rb.LCD_WIDTH,
275 height = rb.LCD_HEIGHT - num_rows*maxHeight - 2
278 for k, v in pairs(vp) do
279 LapsView.vp[k] = v
283 rb.touchscreen_set_mode(rb.TOUCHSCREEN_POINT)
285 LapsView:init()
287 local third = rb.LCD_WIDTH/3
289 local btns = {
290 Button:new({name = "startTimer", label = "Start", width = third}),
291 Button:new({name = "stopTimer", label = "Stop", width = third}),
292 Button:new({name = "resetTimer", label = "Reset", width = rb.LCD_WIDTH-third*2}), -- correct rounding error
293 Button:new({name = "newLap", label = "New Lap", width = third*2}),
294 Button:new({name = "exitApp", label = "Quit", width = rb.LCD_WIDTH-third*2}) -- correct rounding error
297 arrangeButtons(btns)
299 for _, btn in pairs(btns) do
300 btn:draw()
303 repeat
304 LapsView:incTimer()
306 local action = rb.get_action(rb.contexts.CONTEXT_STD, 0)
308 if (action == rb.actions.ACTION_TOUCHSCREEN) then
309 local btn, x, y = rb.action_get_touchscreen_press()
311 if LapsView:checkForScroll(btn, x, y) then
312 -- Don't do anything
313 elseif btn == rb.buttons.BUTTON_REL then
314 for _, btn in pairs(btns) do
315 local name = btn.name
316 if (btn:isPressed(x, y)) then
317 if name == "exitApp" then
318 action = rb.actions.ACTION_STD_CANCEL
319 else
320 LapsView[name](LapsView)
327 LapsView:display()
328 rb.lcd_update()
329 rb.sleep(rb.HZ/50)
330 until action == rb.actions.ACTION_STD_CANCEL
332 LapsView:saveState()