diff options
author | Franklin Wei <git@fwei.tk> | 2017-03-11 17:00:47 -0500 |
---|---|---|
committer | Franklin Wei <git@fwei.tk> | 2017-03-15 15:02:53 -0400 |
commit | 1bab5562c2498b6273916e2d0271a9deca1a97b1 (patch) | |
tree | e9190e482c255d3edff240b16d50e5ecb4cb812c | |
parent | 2af6923dd6de62ec4a8684c120e961ee03cbe259 (diff) | |
download | rockbox-1bab5562c2498b6273916e2d0271a9deca1a97b1.tar.gz rockbox-1bab5562c2498b6273916e2d0271a9deca1a97b1.zip |
Speed-reading plugin
Partially based on `spread0r', an open-source ebook reader: https://github.com/xypiie/spread0r
Similar to Spritz(TM): http://spritzinc.com
Change-Id: I6aa54addd1910a83a266aea561406b6268449b67
24 files changed, 745 insertions, 0 deletions
diff --git a/apps/plugins/CATEGORIES b/apps/plugins/CATEGORIES index 3fa17432f0..6308065828 100644 --- a/apps/plugins/CATEGORIES +++ b/apps/plugins/CATEGORIES | |||
@@ -148,6 +148,7 @@ solitaire,games | |||
148 | sort,viewers | 148 | sort,viewers |
149 | spacerocks,games | 149 | spacerocks,games |
150 | splitedit,apps | 150 | splitedit,apps |
151 | spritz,viewers | ||
151 | star,games | 152 | star,games |
152 | starfield,demos | 153 | starfield,demos |
153 | stats,apps | 154 | stats,apps |
diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index a4e372fba9..a02b9cef69 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES | |||
@@ -145,6 +145,7 @@ metronome.c | |||
145 | #ifdef HAVE_LCD_BITMAP /* Not for the Archos Player */ | 145 | #ifdef HAVE_LCD_BITMAP /* Not for the Archos Player */ |
146 | 146 | ||
147 | 2048.c | 147 | 2048.c |
148 | |||
148 | /* Lua needs at least 160 KB to work in */ | 149 | /* Lua needs at least 160 KB to work in */ |
149 | #if PLUGIN_BUFFER_SIZE >= 0x80000 | 150 | #if PLUGIN_BUFFER_SIZE >= 0x80000 |
150 | boomshine.lua | 151 | boomshine.lua |
@@ -179,6 +180,7 @@ snake.c | |||
179 | snake2.c | 180 | snake2.c |
180 | solitaire.c | 181 | solitaire.c |
181 | sokoban.c | 182 | sokoban.c |
183 | speedread.c | ||
182 | star.c | 184 | star.c |
183 | starfield.c | 185 | starfield.c |
184 | vu_meter.c | 186 | vu_meter.c |
diff --git a/apps/plugins/speedread.c b/apps/plugins/speedread.c new file mode 100644 index 0000000000..c80839e4a9 --- /dev/null +++ b/apps/plugins/speedread.c | |||
@@ -0,0 +1,732 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2017 Franklin Wei | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ***************************************************************************/ | ||
21 | |||
22 | /* ideas for improvement: | ||
23 | * hyphenation of long words */ | ||
24 | |||
25 | #include "plugin.h" | ||
26 | |||
27 | #include "fixedpoint.h" | ||
28 | |||
29 | #include "lib/helper.h" | ||
30 | #include "lib/pluginlib_actions.h" | ||
31 | #include "lib/pluginlib_exit.h" | ||
32 | |||
33 | #define LINE_LEN 1024 | ||
34 | #define WORD_MAX 64 | ||
35 | |||
36 | #define MIN_WPM 100 | ||
37 | #define MAX_WPM 1000 | ||
38 | #define DEF_WPM 250 | ||
39 | #define WPM_INCREMENT 25 | ||
40 | |||
41 | /* mininum bytes to skip when seeking */ | ||
42 | #define SEEK_INTERVAL 100 | ||
43 | |||
44 | #define FOCUS_X (7 * LCD_WIDTH / 20) | ||
45 | #define FOCUS_Y 0 | ||
46 | |||
47 | #define FRAME_COLOR LCD_BLACK | ||
48 | #define BACKGROUND_COLOR LCD_WHITE /* inside frame */ | ||
49 | |||
50 | #ifdef HAVE_LCD_COLOR | ||
51 | #define WORD_COLOR LCD_RGBPACK(48,48,48) | ||
52 | #define FOCUS_COLOR LCD_RGBPACK(204,0,0) | ||
53 | #define OUTSIDE_COLOR LCD_RGBPACK(128,128,128) | ||
54 | #define BAR_COLOR LCD_RGBPACK(230,230,230) | ||
55 | #else | ||
56 | #define WORD_COLOR LCD_BLACK | ||
57 | #define OUTSIDE_COLOR BACKGROUND_COLOR | ||
58 | #endif | ||
59 | |||
60 | #define BOOKMARK_FILE VIEWERS_DATA_DIR "/speedread.dat" | ||
61 | #define CONFIG_FILE VIEWERS_DATA_DIR "/speedread.cfg" | ||
62 | |||
63 | #define ANIM_TIME (75 * HZ / 100) | ||
64 | |||
65 | int fd = -1; /* -1 = prescripted demo */ | ||
66 | |||
67 | int word_num; /* which word on a line */ | ||
68 | off_t line_offs, begin_offs; /* offsets from the "real" beginning of the file to the current line and end of BOM */ | ||
69 | |||
70 | int line_len = -1, custom_font = FONT_UI; | ||
71 | |||
72 | const char *last_word = NULL; | ||
73 | |||
74 | static const char *get_next_word(void) | ||
75 | { | ||
76 | if(fd >= 0) | ||
77 | { | ||
78 | static char line_buf[LINE_LEN]; | ||
79 | static char *end = NULL; | ||
80 | |||
81 | next_line: | ||
82 | |||
83 | if(line_len < 0) | ||
84 | { | ||
85 | line_offs = rb->lseek(fd, 0, SEEK_CUR); | ||
86 | line_len = rb->read_line(fd, line_buf, LINE_LEN); | ||
87 | if(line_len <= 0) | ||
88 | return NULL; | ||
89 | |||
90 | char *word = rb->strtok_r(line_buf, " ", &end); | ||
91 | |||
92 | word_num = 0; | ||
93 | |||
94 | if(!word) | ||
95 | goto next_line; | ||
96 | else | ||
97 | { | ||
98 | last_word = word; | ||
99 | return word; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | char *word = rb->strtok_r(NULL, " ", &end); | ||
104 | if(!word) | ||
105 | { | ||
106 | /* end of line */ | ||
107 | line_len = -1; | ||
108 | goto next_line; | ||
109 | } | ||
110 | ++word_num; | ||
111 | |||
112 | last_word = word; | ||
113 | return word; | ||
114 | } | ||
115 | else | ||
116 | { | ||
117 | /* feed the user a quick demo */ | ||
118 | static const char *words[] = { "This", "plugin", "is", "for", "speed-reading", "plain", "text", "files.", | ||
119 | "Please", "open", "a", "plain", "text", "file", "to", "read", "by", "using", "the", "context", "menu.", | ||
120 | "Have", "a", "nice", "day!" }; | ||
121 | static unsigned idx = 0; | ||
122 | if(idx + 1 > ARRAYLEN(words)) | ||
123 | return NULL; | ||
124 | last_word = words[idx++]; | ||
125 | return last_word; | ||
126 | } | ||
127 | } | ||
128 | |||
129 | static const char *get_last_word(void) | ||
130 | { | ||
131 | if(last_word) | ||
132 | return last_word; | ||
133 | else | ||
134 | { | ||
135 | last_word = get_next_word(); | ||
136 | return last_word; | ||
137 | } | ||
138 | } | ||
139 | |||
140 | static void cleanup(void) | ||
141 | { | ||
142 | if(custom_font != FONT_UI) | ||
143 | rb->font_unload(custom_font); | ||
144 | backlight_use_settings(); | ||
145 | } | ||
146 | |||
147 | /* returns height of drawn area */ | ||
148 | static int reset_drawing(long proportion) /* 16.16 fixed point, goes from 0 --> 1 over time */ | ||
149 | { | ||
150 | int h = -1, w; | ||
151 | if(h < 0) | ||
152 | rb->lcd_getstringsize("X", &w, &h); | ||
153 | |||
154 | /* clear word area */ | ||
155 | rb->lcd_set_foreground(BACKGROUND_COLOR); | ||
156 | rb->lcd_fillrect(0, 0, LCD_WIDTH, h * 3 / 2); | ||
157 | |||
158 | if(proportion) | ||
159 | { | ||
160 | #ifdef HAVE_LCD_COLOR | ||
161 | rb->lcd_set_foreground(BAR_COLOR); | ||
162 | #endif | ||
163 | rb->lcd_fillrect(fp_mul(proportion, FOCUS_X << 16, 16) >> 16, FOCUS_Y, fp_mul((1 << 16) - proportion, LCD_WIDTH << 16, 16) >> 16, h * 3 / 2); | ||
164 | } | ||
165 | |||
166 | rb->lcd_set_foreground(FRAME_COLOR); | ||
167 | |||
168 | /* draw frame */ | ||
169 | rb->lcd_fillrect(0, h * 3 / 2, LCD_WIDTH, w / 2); | ||
170 | rb->lcd_fillrect(FOCUS_X - w / 4, FOCUS_Y + h * 5 / 4, w / 2, h / 2); | ||
171 | |||
172 | rb->lcd_set_foreground(WORD_COLOR); | ||
173 | |||
174 | return h * 3 / 2 + w / 2; | ||
175 | } | ||
176 | |||
177 | static void render_word(const char *word, int focus) | ||
178 | { | ||
179 | /* focus char first */ | ||
180 | char buf[5] = { 0, 0, 0, 0, 0 }; | ||
181 | int idx = rb->utf8seek(word, focus); | ||
182 | rb->memcpy(buf, word + idx, MIN(rb->utf8seek(word, focus + 1) - idx, 4)); | ||
183 | |||
184 | int focus_w; | ||
185 | rb->lcd_getstringsize(buf, &focus_w, NULL); | ||
186 | |||
187 | #ifdef HAVE_LCD_COLOR | ||
188 | rb->lcd_set_foreground(FOCUS_COLOR); | ||
189 | #endif | ||
190 | |||
191 | rb->lcd_putsxy(FOCUS_X - focus_w / 2, FOCUS_Y, buf); | ||
192 | |||
193 | #ifdef HAVE_LCD_COLOR | ||
194 | rb->lcd_set_foreground(WORD_COLOR); | ||
195 | #endif | ||
196 | |||
197 | /* figure out how far left to shift */ | ||
198 | static char half[WORD_MAX]; | ||
199 | rb->strlcpy(half, word, rb->utf8seek(word, focus + 1)); | ||
200 | int w; | ||
201 | rb->lcd_getstringsize(half, &w, NULL); | ||
202 | |||
203 | int x = FOCUS_X - focus_w / 2 - w; | ||
204 | |||
205 | /* first half */ | ||
206 | rb->lcd_putsxy(x, FOCUS_Y, half); | ||
207 | |||
208 | /* second half */ | ||
209 | x = FOCUS_X + focus_w / 2; | ||
210 | rb->lcd_putsxy(x, FOCUS_Y, word + rb->utf8seek(word, focus + 1)); | ||
211 | } | ||
212 | |||
213 | static int calculate_focus(const char *word) | ||
214 | { | ||
215 | #if 0 | ||
216 | int len = rb->utf8length(word); | ||
217 | int focus = -1; | ||
218 | for(int i = len / 5; i < len / 2; ++i) | ||
219 | { | ||
220 | switch(tolower(word[rb->utf8seek(word, i)])) | ||
221 | { | ||
222 | case 'a': case 'e': case 'i': case 'o': case 'u': | ||
223 | focus = i; | ||
224 | break; | ||
225 | default: | ||
226 | break; | ||
227 | } | ||
228 | } | ||
229 | |||
230 | if(focus < 0) | ||
231 | focus = len / 2; | ||
232 | return focus; | ||
233 | #else | ||
234 | int len = rb->utf8length(word); | ||
235 | if(rb->utf8length(word) > 13) | ||
236 | return 4; | ||
237 | else | ||
238 | { | ||
239 | int tab[] = {0,1,1,1,1,2,2,2,2,3,3,3,3}; | ||
240 | return tab[len - 1]; | ||
241 | } | ||
242 | #endif | ||
243 | } | ||
244 | |||
245 | static int calculate_delay(const char *word, int wpm) | ||
246 | { | ||
247 | long base = 60 * HZ / wpm; | ||
248 | long timeout = base; | ||
249 | int len = rb->utf8length(word); | ||
250 | |||
251 | if(len > 6) | ||
252 | timeout += base / 5 * (len - 6); | ||
253 | |||
254 | if(rb->strchr(word, ',') || rb->strchr(word, '-')) | ||
255 | timeout += base / 2; | ||
256 | |||
257 | if(rb->strchr(word, '.') || rb->strchr(word, '!') || rb->strchr(word, '?') || rb->strchr(word, ';')) | ||
258 | timeout += 3 * base / 2; | ||
259 | return timeout; | ||
260 | } | ||
261 | |||
262 | static long render_screen(const char *word, int wpm) | ||
263 | { | ||
264 | /* significant inspiration taken from spread0r */ | ||
265 | long timeout = calculate_delay(word, wpm); | ||
266 | int focus = calculate_focus(word); | ||
267 | |||
268 | rb->lcd_setfont(custom_font); | ||
269 | |||
270 | int h = reset_drawing(0); | ||
271 | |||
272 | render_word(word, focus); | ||
273 | |||
274 | rb->lcd_setfont(FONT_UI); | ||
275 | |||
276 | rb->lcd_update_rect(0, 0, LCD_WIDTH, h); | ||
277 | return timeout; | ||
278 | } | ||
279 | |||
280 | static void begin_anim(void) | ||
281 | { | ||
282 | long start = *rb->current_tick; | ||
283 | long end = start + ANIM_TIME; | ||
284 | |||
285 | const char *word = get_last_word(); | ||
286 | |||
287 | int focus = calculate_focus(word); | ||
288 | |||
289 | rb->lcd_setfont(custom_font); | ||
290 | |||
291 | while(*rb->current_tick < end) | ||
292 | { | ||
293 | int h = reset_drawing(fp_div((*rb->current_tick - start) << 16, ANIM_TIME << 16, 16)); | ||
294 | |||
295 | render_word(word, focus); | ||
296 | rb->lcd_update_rect(0, 0, LCD_WIDTH, h); | ||
297 | } | ||
298 | |||
299 | rb->lcd_setfont(FONT_UI); | ||
300 | } | ||
301 | |||
302 | static void init_drawing(void) | ||
303 | { | ||
304 | backlight_ignore_timeout(); | ||
305 | atexit(cleanup); | ||
306 | |||
307 | rb->lcd_set_background(OUTSIDE_COLOR); | ||
308 | rb->lcd_set_backdrop(NULL); | ||
309 | rb->lcd_set_drawmode(DRMODE_FG); | ||
310 | rb->lcd_clear_display(); | ||
311 | |||
312 | rb->lcd_update(); | ||
313 | } | ||
314 | |||
315 | enum { NOTHING = 0, SLOWER, FASTER, FFWD, BACK, PAUSE, QUIT }; | ||
316 | |||
317 | static const struct button_mapping *plugin_contexts[] = { pla_main_ctx }; | ||
318 | |||
319 | static int get_useraction(void) | ||
320 | { | ||
321 | int button = pluginlib_getaction(0, plugin_contexts, ARRAYLEN(plugin_contexts)); | ||
322 | |||
323 | switch(button) | ||
324 | { | ||
325 | #ifdef HAVE_SCROLLWHEEL | ||
326 | case PLA_SCROLL_FWD: | ||
327 | case PLA_SCROLL_FWD_REPEAT: | ||
328 | #else | ||
329 | case PLA_UP: | ||
330 | #endif | ||
331 | return FASTER; | ||
332 | #ifdef HAVE_SCROLLWHEEL | ||
333 | case PLA_SCROLL_BACK: | ||
334 | case PLA_SCROLL_BACK_REPEAT: | ||
335 | #else | ||
336 | case PLA_DOWN: | ||
337 | #endif | ||
338 | return SLOWER; | ||
339 | case PLA_SELECT: | ||
340 | return PAUSE; | ||
341 | case PLA_CANCEL: | ||
342 | return QUIT; | ||
343 | case PLA_LEFT_REPEAT: | ||
344 | case PLA_LEFT: | ||
345 | return BACK; | ||
346 | case PLA_RIGHT_REPEAT: | ||
347 | case PLA_RIGHT: | ||
348 | return FFWD; | ||
349 | default: | ||
350 | exit_on_usb(button); /* handle poweroff and USB events */ | ||
351 | return 0; | ||
352 | } | ||
353 | } | ||
354 | |||
355 | static void save_bookmark(const char *fname, int wpm) | ||
356 | { | ||
357 | if(!fname) | ||
358 | return; | ||
359 | rb->splash(0, "Saving..."); | ||
360 | /* copy every line except the one to be changed */ | ||
361 | int bookmark_fd = rb->open(BOOKMARK_FILE, O_RDONLY); | ||
362 | int tmp_fd = rb->open(BOOKMARK_FILE ".tmp", O_WRONLY | O_CREAT | O_TRUNC, 0666); | ||
363 | if(bookmark_fd >= 0) | ||
364 | { | ||
365 | while(1) | ||
366 | { | ||
367 | /* space for the filename, 3, integers, and a null */ | ||
368 | static char line[MAX_PATH + 1 + 10 + 1 + 10 + 1 + 10 + 1]; | ||
369 | int len = rb->read_line(bookmark_fd, line, sizeof(line)); | ||
370 | if(len <= 0) | ||
371 | break; | ||
372 | |||
373 | char *end; | ||
374 | rb->strtok_r(line, " ", &end); | ||
375 | rb->strtok_r(NULL, " ", &end); | ||
376 | rb->strtok_r(NULL, " ", &end); | ||
377 | char *bookmark_name = rb->strtok_r(NULL, "", &end); | ||
378 | |||
379 | if(!bookmark_name) | ||
380 | continue; /* avoid crash */ | ||
381 | if(rb->strcmp(fname, bookmark_name)) | ||
382 | { | ||
383 | /* go back and clean up after strtok */ | ||
384 | for(int i = 0; i < len - 1; ++i) | ||
385 | if(!line[i]) | ||
386 | line[i] = ' '; | ||
387 | |||
388 | rb->write(tmp_fd, line, len); | ||
389 | rb->fdprintf(tmp_fd, "\n"); | ||
390 | } | ||
391 | } | ||
392 | rb->close(bookmark_fd); | ||
393 | } | ||
394 | rb->fdprintf(tmp_fd, "%ld %d %d %s\n", line_offs, word_num, wpm, fname); | ||
395 | rb->close(tmp_fd); | ||
396 | rb->rename(BOOKMARK_FILE ".tmp", BOOKMARK_FILE); | ||
397 | } | ||
398 | |||
399 | static bool load_bookmark(const char *fname, int *wpm) | ||
400 | { | ||
401 | int bookmark_fd = rb->open(BOOKMARK_FILE, O_RDONLY); | ||
402 | if(bookmark_fd >= 0) | ||
403 | { | ||
404 | while(1) | ||
405 | { | ||
406 | /* space for the filename, 2 integers, and a null */ | ||
407 | char line[MAX_PATH + 1 + 10 + 1 + 10 + 1]; | ||
408 | int len = rb->read_line(bookmark_fd, line, sizeof(line)); | ||
409 | if(len <= 0) | ||
410 | break; | ||
411 | |||
412 | char *end; | ||
413 | char *tok = rb->strtok_r(line, " ", &end); | ||
414 | if(!tok) | ||
415 | continue; | ||
416 | off_t offs = rb->atoi(tok); | ||
417 | |||
418 | tok = rb->strtok_r(NULL, " ", &end); | ||
419 | if(!tok) | ||
420 | continue; | ||
421 | int word = rb->atoi(tok); | ||
422 | |||
423 | tok = rb->strtok_r(NULL, " ", &end); | ||
424 | if(!tok) | ||
425 | continue; | ||
426 | *wpm = rb->atoi(tok); | ||
427 | if(*wpm < MIN_WPM) | ||
428 | *wpm = MIN_WPM; | ||
429 | if(*wpm > MAX_WPM) | ||
430 | *wpm = MAX_WPM; | ||
431 | |||
432 | char *bookmark_name = rb->strtok_r(NULL, "", &end); | ||
433 | |||
434 | if(!bookmark_name) | ||
435 | continue; | ||
436 | |||
437 | if(!rb->strcmp(fname, bookmark_name)) | ||
438 | { | ||
439 | rb->lseek(fd, offs, SEEK_SET); | ||
440 | for(int i = 0; i < word; ++i) | ||
441 | get_next_word(); | ||
442 | rb->close(bookmark_fd); | ||
443 | return true; | ||
444 | } | ||
445 | } | ||
446 | rb->close(bookmark_fd); | ||
447 | } | ||
448 | return false; | ||
449 | } | ||
450 | |||
451 | static void new_font(const char *path) | ||
452 | { | ||
453 | if(custom_font != FONT_UI) | ||
454 | rb->font_unload(custom_font); | ||
455 | custom_font = rb->font_load(path); | ||
456 | if(custom_font < 0) | ||
457 | custom_font = FONT_UI; | ||
458 | } | ||
459 | |||
460 | static void save_font(const char *path) | ||
461 | { | ||
462 | int font_fd = rb->open(CONFIG_FILE, O_WRONLY | O_TRUNC | O_CREAT, 0666); | ||
463 | rb->write(font_fd, path, rb->strlen(path)); | ||
464 | rb->close(font_fd); | ||
465 | } | ||
466 | |||
467 | static char font_buf[MAX_PATH + 1]; | ||
468 | |||
469 | static void load_font(void) | ||
470 | { | ||
471 | int font_fd = rb->open(CONFIG_FILE, O_RDONLY); | ||
472 | if(font_fd < 0) | ||
473 | return; | ||
474 | int len = rb->read(font_fd, font_buf, MAX_PATH); | ||
475 | font_buf[len] = '\0'; | ||
476 | rb->close(font_fd); | ||
477 | new_font(font_buf); | ||
478 | } | ||
479 | |||
480 | static void font_menu(void) | ||
481 | { | ||
482 | /* taken from text_viewer */ | ||
483 | struct browse_context browse; | ||
484 | char font[MAX_PATH], name[MAX_FILENAME+10]; | ||
485 | |||
486 | rb->snprintf(name, sizeof(name), "%s.fnt", rb->global_settings->font_file); | ||
487 | rb->browse_context_init(&browse, SHOW_FONT, | ||
488 | BROWSE_SELECTONLY|BROWSE_NO_CONTEXT_MENU, | ||
489 | "Font", Icon_Menu_setting, FONT_DIR, name); | ||
490 | |||
491 | browse.buf = font; | ||
492 | browse.bufsize = sizeof(font); | ||
493 | |||
494 | rb->rockbox_browse(&browse); | ||
495 | |||
496 | if (browse.flags & BROWSE_SELECTED) | ||
497 | { | ||
498 | new_font(font); | ||
499 | save_font(font); | ||
500 | } | ||
501 | } | ||
502 | |||
503 | static bool confirm_restart(void) | ||
504 | { | ||
505 | const struct text_message prompt = { (const char*[]) {"Are you sure?", "This will erase your current position."}, 2}; | ||
506 | enum yesno_res response = rb->gui_syncyesno_run(&prompt, NULL, NULL); | ||
507 | if(response == YESNO_NO) | ||
508 | return false; | ||
509 | else | ||
510 | return true; | ||
511 | } | ||
512 | |||
513 | static int config_menu(void) | ||
514 | { | ||
515 | MENUITEM_STRINGLIST(menu, "Speedread Menu", NULL, | ||
516 | "Resume Reading", | ||
517 | "Restart from Beginning", | ||
518 | "Change Font", | ||
519 | "Quit"); | ||
520 | int rc = 0; | ||
521 | int sel = 0; | ||
522 | while(!rc) | ||
523 | { | ||
524 | switch(rb->do_menu(&menu, &sel, NULL, false)) | ||
525 | { | ||
526 | case 0: | ||
527 | rc = 1; | ||
528 | break; | ||
529 | case 1: | ||
530 | if(fd >= 0 && confirm_restart()) | ||
531 | { | ||
532 | rb->lseek(fd, begin_offs, SEEK_SET); | ||
533 | line_len = -1; | ||
534 | get_next_word(); | ||
535 | rc = 1; | ||
536 | } | ||
537 | break; | ||
538 | case 2: | ||
539 | font_menu(); | ||
540 | break; | ||
541 | case 3: | ||
542 | rc = 2; | ||
543 | break; | ||
544 | default: | ||
545 | break; | ||
546 | } | ||
547 | } | ||
548 | return rc - 1; | ||
549 | } | ||
550 | |||
551 | enum { SKIP = -1, FINISH = -2 }; | ||
552 | |||
553 | static int poll_input(int *wpm, long *clear, const char *fname, off_t file_size) | ||
554 | { | ||
555 | switch(get_useraction()) | ||
556 | { | ||
557 | case FASTER: | ||
558 | if(*wpm + WPM_INCREMENT <= MAX_WPM) | ||
559 | *wpm += WPM_INCREMENT; | ||
560 | rb->splashf(0, "%d wpm", *wpm); | ||
561 | *clear = *rb->current_tick + HZ; | ||
562 | break; | ||
563 | case SLOWER: | ||
564 | if(*wpm - WPM_INCREMENT >= MIN_WPM) | ||
565 | *wpm -= WPM_INCREMENT; | ||
566 | rb->splashf(0, "%d wpm", *wpm); | ||
567 | *clear = *rb->current_tick + HZ; | ||
568 | break; | ||
569 | case FFWD: | ||
570 | if(fd >= 0) | ||
571 | { | ||
572 | off_t base_offs = rb->lseek(fd, 0, SEEK_CUR); | ||
573 | off_t offs = 0; | ||
574 | |||
575 | do { | ||
576 | offs += SEEK_INTERVAL; | ||
577 | if(offs >= 1000 * SEEK_INTERVAL) | ||
578 | offs += 199 * SEEK_INTERVAL; | ||
579 | else if(offs >= 100 * SEEK_INTERVAL) | ||
580 | offs += 99 * SEEK_INTERVAL; | ||
581 | else if(offs >= 10 * SEEK_INTERVAL) | ||
582 | offs += 9 * SEEK_INTERVAL; | ||
583 | rb->splashf(0, "%ld/%ld bytes", offs + base_offs, file_size); | ||
584 | rb->sleep(HZ/20); | ||
585 | } while(get_useraction() == FFWD && offs + base_offs < file_size && offs + base_offs >= 0); | ||
586 | |||
587 | *clear = *rb->current_tick + HZ; | ||
588 | |||
589 | rb->lseek(fd, offs, SEEK_CUR); | ||
590 | |||
591 | /* discard the next word (or more likely, portion of a word) */ | ||
592 | line_len = -1; | ||
593 | get_next_word(); | ||
594 | |||
595 | return SKIP; | ||
596 | } | ||
597 | break; | ||
598 | case BACK: | ||
599 | if(fd >= 0) | ||
600 | { | ||
601 | off_t base_offs = rb->lseek(fd, 0, SEEK_CUR); | ||
602 | off_t offs = 0; | ||
603 | |||
604 | do { | ||
605 | offs -= SEEK_INTERVAL; | ||
606 | if(offs <= -1000 * SEEK_INTERVAL) | ||
607 | offs -= 199 * SEEK_INTERVAL; | ||
608 | else if(offs <= -100 * SEEK_INTERVAL) | ||
609 | offs -= 99 * SEEK_INTERVAL; | ||
610 | else if(offs <= -10 * SEEK_INTERVAL) | ||
611 | offs -= 9 * SEEK_INTERVAL; | ||
612 | rb->splashf(0, "%ld/%ld bytes", offs + base_offs, file_size); | ||
613 | rb->sleep(HZ/20); | ||
614 | } while(get_useraction() == FFWD && offs + base_offs < file_size && offs + base_offs >= 0); | ||
615 | |||
616 | *clear = *rb->current_tick + HZ; | ||
617 | |||
618 | rb->lseek(fd, offs, SEEK_CUR); | ||
619 | |||
620 | /* discard the next word (or more likely, portion of a word) */ | ||
621 | line_len = -1; | ||
622 | get_next_word(); | ||
623 | |||
624 | return SKIP; | ||
625 | } | ||
626 | break; | ||
627 | case PAUSE: | ||
628 | case QUIT: | ||
629 | if(config_menu()) | ||
630 | { | ||
631 | save_bookmark(fname, *wpm); | ||
632 | return FINISH; | ||
633 | } | ||
634 | else | ||
635 | { | ||
636 | init_drawing(); | ||
637 | begin_anim(); | ||
638 | } | ||
639 | break; | ||
640 | case NOTHING: | ||
641 | default: | ||
642 | break; | ||
643 | } | ||
644 | return 0; | ||
645 | } | ||
646 | |||
647 | enum plugin_status plugin_start(const void *param) | ||
648 | { | ||
649 | const char *fname = param; | ||
650 | |||
651 | off_t file_size = 0; | ||
652 | |||
653 | load_font(); | ||
654 | |||
655 | bool loaded = false; | ||
656 | |||
657 | int wpm = DEF_WPM; | ||
658 | |||
659 | if(fname) | ||
660 | { | ||
661 | fd = rb->open_utf8(fname, O_RDONLY); | ||
662 | |||
663 | begin_offs = rb->lseek(fd, 0, SEEK_CUR); /* skip BOM */ | ||
664 | file_size = rb->lseek(fd, 0, SEEK_END); | ||
665 | rb->lseek(fd, begin_offs, SEEK_SET); | ||
666 | |||
667 | loaded = load_bookmark(fname, &wpm); | ||
668 | } | ||
669 | |||
670 | init_drawing(); | ||
671 | |||
672 | long clear = -1; | ||
673 | if(loaded) | ||
674 | { | ||
675 | rb->splash(0, "Loaded bookmark."); | ||
676 | clear = *rb->current_tick + HZ; | ||
677 | } | ||
678 | |||
679 | begin_anim(); | ||
680 | |||
681 | /* main loop */ | ||
682 | while(1) | ||
683 | { | ||
684 | switch(poll_input(&wpm, &clear, fname, file_size)) | ||
685 | { | ||
686 | case SKIP: | ||
687 | continue; | ||
688 | case FINISH: | ||
689 | goto done; | ||
690 | default: | ||
691 | break; | ||
692 | } | ||
693 | |||
694 | const char *word = get_next_word(); | ||
695 | if(!word) | ||
696 | break; | ||
697 | bool want_full_update = false; | ||
698 | if(TIME_AFTER(*rb->current_tick, clear) && clear != -1) | ||
699 | { | ||
700 | clear = -1; | ||
701 | rb->lcd_clear_display(); | ||
702 | want_full_update = true; | ||
703 | } | ||
704 | long interval = render_screen(word, wpm); | ||
705 | |||
706 | long frame_done = *rb->current_tick + interval; | ||
707 | |||
708 | if(want_full_update) | ||
709 | rb->lcd_update(); | ||
710 | |||
711 | while(!TIME_AFTER(*rb->current_tick, frame_done)) | ||
712 | { | ||
713 | switch(poll_input(&wpm, &clear, fname, file_size)) | ||
714 | { | ||
715 | case SKIP: | ||
716 | goto next_word; | ||
717 | case FINISH: | ||
718 | goto done; | ||
719 | default: | ||
720 | break; | ||
721 | } | ||
722 | rb->yield(); | ||
723 | } | ||
724 | next_word: | ||
725 | ; | ||
726 | } | ||
727 | |||
728 | done: | ||
729 | rb->close(fd); | ||
730 | |||
731 | return PLUGIN_OK; | ||
732 | } | ||
diff --git a/apps/plugins/viewers.config b/apps/plugins/viewers.config index 9eca2dab1a..c938eeb275 100644 --- a/apps/plugins/viewers.config +++ b/apps/plugins/viewers.config | |||
@@ -2,6 +2,7 @@ ch8,viewers/chip8,0 | |||
2 | txt,viewers/text_viewer,1 | 2 | txt,viewers/text_viewer,1 |
3 | txt,apps/text_editor,2 | 3 | txt,apps/text_editor,2 |
4 | txt,viewers/sort,- | 4 | txt,viewers/sort,- |
5 | txt,viewers/speedread,1 | ||
5 | nfo,viewers/text_viewer,1 | 6 | nfo,viewers/text_viewer,1 |
6 | bmp,viewers/imageviewer,2 | 7 | bmp,viewers/imageviewer,2 |
7 | bmp,apps/rockpaint,11 | 8 | bmp,apps/rockpaint,11 |
diff --git a/manual/plugins/images/ss-speedread-128x128x16.png b/manual/plugins/images/ss-speedread-128x128x16.png new file mode 100644 index 0000000000..5e0603175d --- /dev/null +++ b/manual/plugins/images/ss-speedread-128x128x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-128x128x2.png b/manual/plugins/images/ss-speedread-128x128x2.png new file mode 100644 index 0000000000..48402555b8 --- /dev/null +++ b/manual/plugins/images/ss-speedread-128x128x2.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-128x160x16.png b/manual/plugins/images/ss-speedread-128x160x16.png new file mode 100644 index 0000000000..273482238e --- /dev/null +++ b/manual/plugins/images/ss-speedread-128x160x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-128x96x16.png b/manual/plugins/images/ss-speedread-128x96x16.png new file mode 100644 index 0000000000..978535b4bf --- /dev/null +++ b/manual/plugins/images/ss-speedread-128x96x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-128x96x2.png b/manual/plugins/images/ss-speedread-128x96x2.png new file mode 100644 index 0000000000..cccca295bf --- /dev/null +++ b/manual/plugins/images/ss-speedread-128x96x2.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-132x80x16.png b/manual/plugins/images/ss-speedread-132x80x16.png new file mode 100644 index 0000000000..7c3d1461ff --- /dev/null +++ b/manual/plugins/images/ss-speedread-132x80x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-138x110x2.png b/manual/plugins/images/ss-speedread-138x110x2.png new file mode 100644 index 0000000000..4c5846af93 --- /dev/null +++ b/manual/plugins/images/ss-speedread-138x110x2.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-160x128x16.png b/manual/plugins/images/ss-speedread-160x128x16.png new file mode 100644 index 0000000000..71b8c849ea --- /dev/null +++ b/manual/plugins/images/ss-speedread-160x128x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-160x128x2.png b/manual/plugins/images/ss-speedread-160x128x2.png new file mode 100644 index 0000000000..ea1c896f83 --- /dev/null +++ b/manual/plugins/images/ss-speedread-160x128x2.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-176x132x16.png b/manual/plugins/images/ss-speedread-176x132x16.png new file mode 100644 index 0000000000..3ec9dbc902 --- /dev/null +++ b/manual/plugins/images/ss-speedread-176x132x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-176x220x16.png b/manual/plugins/images/ss-speedread-176x220x16.png new file mode 100644 index 0000000000..ac5b72fe49 --- /dev/null +++ b/manual/plugins/images/ss-speedread-176x220x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-220x176x16.png b/manual/plugins/images/ss-speedread-220x176x16.png new file mode 100644 index 0000000000..ba68fc9f57 --- /dev/null +++ b/manual/plugins/images/ss-speedread-220x176x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-240x320x16.png b/manual/plugins/images/ss-speedread-240x320x16.png new file mode 100644 index 0000000000..f716320df0 --- /dev/null +++ b/manual/plugins/images/ss-speedread-240x320x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-240x400x16.png b/manual/plugins/images/ss-speedread-240x400x16.png new file mode 100644 index 0000000000..c56efdb422 --- /dev/null +++ b/manual/plugins/images/ss-speedread-240x400x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-320x240x16.png b/manual/plugins/images/ss-speedread-320x240x16.png new file mode 100644 index 0000000000..845fc14f8a --- /dev/null +++ b/manual/plugins/images/ss-speedread-320x240x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-320x240x24.png b/manual/plugins/images/ss-speedread-320x240x24.png new file mode 100644 index 0000000000..86ea3ec903 --- /dev/null +++ b/manual/plugins/images/ss-speedread-320x240x24.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-640x480x16.png b/manual/plugins/images/ss-speedread-640x480x16.png new file mode 100644 index 0000000000..6ae773f276 --- /dev/null +++ b/manual/plugins/images/ss-speedread-640x480x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/images/ss-speedread-96x96x16.png b/manual/plugins/images/ss-speedread-96x96x16.png new file mode 100644 index 0000000000..3ebbcd2aa3 --- /dev/null +++ b/manual/plugins/images/ss-speedread-96x96x16.png | |||
Binary files differ | |||
diff --git a/manual/plugins/main.tex b/manual/plugins/main.tex index 3c25807424..cc3d9380ab 100644 --- a/manual/plugins/main.tex +++ b/manual/plugins/main.tex | |||
@@ -228,6 +228,8 @@ option from the \setting{Context Menu} (see \reference{ref:Contextmenu}).} | |||
228 | 228 | ||
229 | {\input{plugins/sort.tex}} | 229 | {\input{plugins/sort.tex}} |
230 | 230 | ||
231 | \opt{lcd_non-mono}{\input{plugins/speedread.tex}} | ||
232 | |||
231 | {\input{plugins/text_viewer.tex}} | 233 | {\input{plugins/text_viewer.tex}} |
232 | 234 | ||
233 | {\input{plugins/theme_remove.tex}} | 235 | {\input{plugins/theme_remove.tex}} |
diff --git a/manual/plugins/speedread.tex b/manual/plugins/speedread.tex new file mode 100644 index 0000000000..11ac24634f --- /dev/null +++ b/manual/plugins/speedread.tex | |||
@@ -0,0 +1,7 @@ | |||
1 | \subsection{Speedread} | ||
2 | \screenshot{plugins/images/ss-speedread}{speedread}{fig:Speedread in action} | ||
3 | |||
4 | This plugin is designed for reading plain-text files such as | ||
5 | ebooks. It works by using a form of Rapid Serial Visual Presentation | ||
6 | (RSVP) that has optimized word placement to reduce or eliminate eye | ||
7 | movement (saccades) when reading. | ||