Viewer: Don't say 'yes' to every option ;-) (Voice ID must be -1 for 'no clip').
[Rockbox.git] / apps / plugins / viewer.c
blob6822f7386832a8fc7baf5a137430574de199bdbc
1 /***************************************************************************
3 * __________ __ ___.
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
8 * \/ \/ \/ \/ \/
11 * Copyright (C) 2002 Gilles Roux, 2003 Garrett Derner
13 * All files in this archive are subject to the GNU General Public License.
14 * See the file COPYING in the source tree root for full license agreement.
16 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
17 * KIND, either express or implied.
19 ****************************************************************************/
20 #include "plugin.h"
21 #include <ctype.h>
22 #include "playback_control.h"
24 PLUGIN_HEADER
26 #define SETTINGS_FILE "/.rockbox/viewers/viewer.cfg"
28 #define WRAP_TRIM 44 /* Max number of spaces to trim (arbitrary) */
29 #define MAX_COLUMNS 64 /* Max displayable string len (over-estimate) */
30 #define MAX_WIDTH 910 /* Max line length in WIDE mode */
31 #define READ_PREV_ZONE 910 /* Arbitrary number less than SMALL_BLOCK_SIZE */
32 #define SMALL_BLOCK_SIZE 0x1000 /* 4k: Smallest file chunk we will read */
33 #define LARGE_BLOCK_SIZE 0x2000 /* 8k: Preferable size of file chunk to read */
34 #define BUFFER_SIZE 0x3000 /* 12k: Mem reserved for buffered file data */
35 #define TOP_SECTOR buffer
36 #define MID_SECTOR (buffer + SMALL_BLOCK_SIZE)
37 #define BOTTOM_SECTOR (buffer + 2*(SMALL_BLOCK_SIZE))
39 /* Out-Of-Bounds test for any pointer to data in the buffer */
40 #define BUFFER_OOB(p) ((p) < buffer || (p) >= buffer_end)
42 /* Does the buffer contain the beginning of the file? */
43 #define BUFFER_BOF() (file_pos==0)
45 /* Does the buffer contain the end of the file? */
46 #define BUFFER_EOF() (file_size-file_pos <= BUFFER_SIZE)
48 /* Formula for the endpoint address outside of buffer data */
49 #define BUFFER_END() \
50 ((BUFFER_EOF()) ? (file_size-file_pos+buffer) : (buffer+BUFFER_SIZE))
52 /* Is the entire file being shown in one screen? */
53 #define ONE_SCREEN_FITS_ALL() \
54 (next_screen_ptr==NULL && screen_top_ptr==buffer && BUFFER_BOF())
56 /* Is a scrollbar called for on the current screen? */
57 #define NEED_SCROLLBAR() \
58 ((!(ONE_SCREEN_FITS_ALL())) && (prefs.scrollbar_mode==SB_ON))
60 /* variable button definitions */
62 /* Recorder keys */
63 #if CONFIG_KEYPAD == RECORDER_PAD
64 #define VIEWER_QUIT BUTTON_OFF
65 #define VIEWER_PAGE_UP BUTTON_UP
66 #define VIEWER_PAGE_DOWN BUTTON_DOWN
67 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
68 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
69 #define VIEWER_MENU BUTTON_F1
70 #define VIEWER_AUTOSCROLL BUTTON_PLAY
71 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
72 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
73 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
74 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
76 /* Ondio keys */
77 #elif CONFIG_KEYPAD == ONDIO_PAD
78 #define VIEWER_QUIT BUTTON_OFF
79 #define VIEWER_PAGE_UP BUTTON_UP
80 #define VIEWER_PAGE_DOWN BUTTON_DOWN
81 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
82 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
83 #define VIEWER_MENU (BUTTON_MENU|BUTTON_REPEAT)
84 #define VIEWER_AUTOSCROLL_PRE BUTTON_MENU
85 #define VIEWER_AUTOSCROLL (BUTTON_MENU|BUTTON_REL)
87 /* Player keys */
88 #elif CONFIG_KEYPAD == PLAYER_PAD
89 #define VIEWER_QUIT BUTTON_STOP
90 #define VIEWER_PAGE_UP BUTTON_LEFT
91 #define VIEWER_PAGE_DOWN BUTTON_RIGHT
92 #define VIEWER_SCREEN_LEFT (BUTTON_ON|BUTTON_LEFT)
93 #define VIEWER_SCREEN_RIGHT (BUTTON_ON|BUTTON_RIGHT)
94 #define VIEWER_MENU BUTTON_MENU
95 #define VIEWER_AUTOSCROLL BUTTON_PLAY
97 /* iRiver H1x0 && H3x0 keys */
98 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
99 (CONFIG_KEYPAD == IRIVER_H300_PAD)
100 #define VIEWER_QUIT BUTTON_OFF
101 #define VIEWER_PAGE_UP BUTTON_UP
102 #define VIEWER_PAGE_DOWN BUTTON_DOWN
103 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
104 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
105 #define VIEWER_MENU BUTTON_MODE
106 #define VIEWER_AUTOSCROLL BUTTON_SELECT
107 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
108 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
109 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
110 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
112 /* iPods with the 4G pad */
113 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
114 (CONFIG_KEYPAD == IPOD_3G_PAD)
115 #define VIEWER_QUIT_PRE BUTTON_SELECT
116 #define VIEWER_QUIT (BUTTON_SELECT | BUTTON_MENU)
117 #define VIEWER_PAGE_UP BUTTON_SCROLL_BACK
118 #define VIEWER_PAGE_DOWN BUTTON_SCROLL_FWD
119 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
120 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
121 #define VIEWER_MENU BUTTON_MENU
122 #define VIEWER_AUTOSCROLL BUTTON_PLAY
124 /* iFP7xx keys */
125 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
126 #define VIEWER_QUIT BUTTON_PLAY
127 #define VIEWER_PAGE_UP BUTTON_UP
128 #define VIEWER_PAGE_DOWN BUTTON_DOWN
129 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
130 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
131 #define VIEWER_MENU BUTTON_MODE
132 #define VIEWER_AUTOSCROLL BUTTON_SELECT
134 /* iAudio X5 keys */
135 #elif CONFIG_KEYPAD == IAUDIO_X5_PAD
136 #define VIEWER_QUIT BUTTON_POWER
137 #define VIEWER_PAGE_UP BUTTON_UP
138 #define VIEWER_PAGE_DOWN BUTTON_DOWN
139 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
140 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
141 #define VIEWER_MENU BUTTON_SELECT
142 #define VIEWER_AUTOSCROLL BUTTON_PLAY
144 /* GIGABEAT keys */
145 #elif CONFIG_KEYPAD == GIGABEAT_PAD
146 #define VIEWER_QUIT BUTTON_POWER
147 #define VIEWER_PAGE_UP BUTTON_UP
148 #define VIEWER_PAGE_DOWN BUTTON_DOWN
149 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
150 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
151 #define VIEWER_MENU BUTTON_MENU
152 #define VIEWER_AUTOSCROLL BUTTON_A
154 #endif
156 struct preferences {
157 enum {
158 WRAP=0,
159 CHOP,
160 } word_mode;
162 enum {
163 NORMAL=0,
164 JOIN,
165 EXPAND,
166 REFLOW, /* won't be set on charcell LCD, must be last */
167 } line_mode;
169 enum {
170 NARROW=0,
171 WIDE,
172 } view_mode;
174 #ifdef HAVE_LCD_BITMAP
175 enum {
176 SB_OFF=0,
177 SB_ON,
178 } scrollbar_mode;
179 bool need_scrollbar;
181 enum {
182 NO_OVERLAP=0,
183 OVERLAP,
184 } page_mode;
185 #endif /* HAVE_LCD_BITMAP */
187 enum {
188 PAGE=0,
189 LINE,
190 } scroll_mode;
192 int autoscroll_speed;
193 } prefs;
195 static unsigned char buffer[BUFFER_SIZE + 1];
196 static unsigned char line_break[] = {0,0x20,'-',9,0xB,0xC};
197 static int display_columns; /* number of (pixel) columns on the display */
198 static int display_lines; /* number of lines on the display */
199 static int draw_columns; /* number of (pixel) columns available for text */
200 static int par_indent_spaces; /* number of spaces to indent first paragraph */
201 static int fd;
202 static char *file_name;
203 static long file_size;
204 static bool mac_text;
205 static long file_pos; /* Position of the top of the buffer in the file */
206 static unsigned char *buffer_end; /*Set to BUFFER_END() when file_pos changes*/
207 static int max_line_len;
208 static unsigned char *screen_top_ptr;
209 static unsigned char *next_screen_ptr;
210 static unsigned char *next_screen_to_draw_ptr;
211 static unsigned char *next_line_ptr;
212 static struct plugin_api* rb;
214 static unsigned char glyph_width[256];
216 bool done = false;
217 int col = 0;
219 #define ADVANCE_COUNTERS(c) do { width += glyph_width[c]; k++; } while(0)
220 #define LINE_IS_FULL ((k>MAX_COLUMNS-1) || (width > draw_columns))
221 static unsigned char* crop_at_width(const unsigned char* p)
223 int k,width;
225 k=width=0;
226 while (!LINE_IS_FULL)
227 ADVANCE_COUNTERS(p[k]);
229 return (unsigned char*) p+k-1;
232 static unsigned char* find_first_feed(const unsigned char* p, int size)
234 int i;
236 for (i=0; i < size; i++)
237 if (p[i] == 0)
238 return (unsigned char*) p+i;
240 return NULL;
243 static unsigned char* find_last_feed(const unsigned char* p, int size)
245 int i;
247 for (i=size-1; i>=0; i--)
248 if (p[i] == 0)
249 return (unsigned char*) p+i;
251 return NULL;
254 static unsigned char* find_last_space(const unsigned char* p, int size)
256 int i, j, k;
258 k = (prefs.line_mode==JOIN) || (prefs.line_mode==REFLOW) ? 0:1;
260 for (i=size-1; i>=0; i--)
261 for (j=k; j < (int) sizeof(line_break); j++)
262 if (p[i] == line_break[j])
263 return (unsigned char*) p+i;
265 return NULL;
268 static unsigned char* find_next_line(const unsigned char* cur_line, bool *is_short)
270 const unsigned char *next_line = NULL;
271 int size, i, j, k, width, search_len, spaces, newlines;
272 bool first_chars;
273 unsigned char c;
275 if (is_short != NULL)
276 *is_short = true;
278 if BUFFER_OOB(cur_line)
279 return NULL;
281 if (prefs.view_mode == WIDE) {
282 search_len = MAX_WIDTH;
284 else { /* prefs.view_mode == NARROW */
285 search_len = crop_at_width(cur_line) - cur_line;
288 size = BUFFER_OOB(cur_line+search_len) ? buffer_end-cur_line : search_len;
290 if ((prefs.line_mode == JOIN) || (prefs.line_mode == REFLOW)) {
291 /* Need to scan ahead and possibly increase search_len and size,
292 or possibly set next_line at second hard return in a row. */
293 next_line = NULL;
294 first_chars=true;
295 for (j=k=width=spaces=newlines=0; ; j++) {
296 if (BUFFER_OOB(cur_line+j))
297 return NULL;
298 if (LINE_IS_FULL) {
299 size = search_len = j;
300 break;
303 c = cur_line[j];
304 switch (c) {
305 case ' ':
306 if (prefs.line_mode == REFLOW) {
307 if (newlines > 0) {
308 size = j;
309 next_line = cur_line + size;
310 return (unsigned char*) next_line;
312 if (j==0) /* i=1 is intentional */
313 for (i=0; i<par_indent_spaces; i++)
314 ADVANCE_COUNTERS(' ');
316 if (!first_chars) spaces++;
317 break;
319 case 0:
320 if (newlines > 0) {
321 size = j;
322 next_line = cur_line + size - spaces;
323 if (next_line != cur_line)
324 return (unsigned char*) next_line;
325 break;
328 newlines++;
329 size += spaces -1;
330 if (BUFFER_OOB(cur_line+size) || size > 2*search_len)
331 return NULL;
332 search_len = size;
333 spaces = first_chars? 0:1;
334 break;
336 default:
337 if (prefs.line_mode==JOIN || newlines>0) {
338 while (spaces) {
339 spaces--;
340 ADVANCE_COUNTERS(' ');
341 if (LINE_IS_FULL) {
342 size = search_len = j;
343 break;
346 newlines=0;
347 } else if (spaces) {
348 /* REFLOW, multiple spaces between words: count only
349 * one. If more are needed, they will be added
350 * while drawing. */
351 search_len = size;
352 spaces=0;
353 ADVANCE_COUNTERS(' ');
354 if (LINE_IS_FULL) {
355 size = search_len = j;
356 break;
359 first_chars = false;
360 ADVANCE_COUNTERS(c);
361 break;
365 else {
366 /* find first hard return */
367 next_line = find_first_feed(cur_line, size);
370 if (next_line == NULL)
371 if (size == search_len) {
372 if (prefs.word_mode == WRAP) /* Find last space */
373 next_line = find_last_space(cur_line, size);
375 if (next_line == NULL)
376 next_line = crop_at_width(cur_line);
377 else
378 if (prefs.word_mode == WRAP)
379 for (i=0;
380 i<WRAP_TRIM && isspace(next_line[0]) && !BUFFER_OOB(next_line);
381 i++)
382 next_line++;
385 if (prefs.line_mode == EXPAND)
386 if (!BUFFER_OOB(next_line)) /* Not Null & not out of bounds */
387 if (next_line[0] == 0)
388 if (next_line != cur_line)
389 return (unsigned char*) next_line;
391 /* If next_line is pointing to a zero, increment it; i.e.,
392 leave the terminator at the end of cur_line. If pointing
393 to a hyphen, increment only if there is room to display
394 the hyphen on current line (won't apply in WIDE mode,
395 since it's guarenteed there won't be room). */
396 if (!BUFFER_OOB(next_line)) /* Not Null & not out of bounds */
397 if (next_line[0] == 0 ||
398 (next_line[0] == '-' && next_line-cur_line < draw_columns))
399 next_line++;
401 if (BUFFER_OOB(next_line))
402 return NULL;
404 if (is_short)
405 *is_short = false;
407 return (unsigned char*) next_line;
410 static unsigned char* find_prev_line(const unsigned char* cur_line)
412 const unsigned char *prev_line = NULL;
413 const unsigned char *p;
415 if BUFFER_OOB(cur_line)
416 return NULL;
418 /* To wrap consistently at the same places, we must
419 start with a known hard return, then work downwards.
420 We can either search backwards for a hard return,
421 or simply start wrapping downwards from top of buffer.
422 If current line is not near top of buffer, this is
423 a file with long lines (paragraphs). We would need to
424 read earlier sectors before we could decide how to
425 properly wrap the lines above the current line, but
426 it probably is not worth the disk access. Instead,
427 start with top of buffer and wrap down from there.
428 This may result in some lines wrapping at different
429 points from where they wrap when scrolling down.
430 If buffer is at top of file, start at top of buffer. */
432 if ((prefs.line_mode == JOIN) || (prefs.line_mode == REFLOW))
433 prev_line = p = NULL;
434 else
435 prev_line = p = find_last_feed(buffer, cur_line-buffer-1);
436 /* Null means no line feeds in buffer above current line. */
438 if (prev_line == NULL)
439 if (BUFFER_BOF() || cur_line - buffer > READ_PREV_ZONE)
440 prev_line = p = buffer;
441 /* (else return NULL and read previous block) */
443 /* Wrap downwards until too far, then use the one before. */
444 while (p < cur_line && p != NULL) {
445 prev_line = p;
446 p = find_next_line(prev_line, NULL);
449 if (BUFFER_OOB(prev_line))
450 return NULL;
452 return (unsigned char*) prev_line;
455 static void fill_buffer(long pos, unsigned char* buf, unsigned size)
457 /* Read from file and preprocess the data */
458 /* To minimize disk access, always read on sector boundaries */
459 unsigned numread, i;
460 bool found_CR = false;
462 rb->lseek(fd, pos, SEEK_SET);
463 numread = rb->read(fd, buf, size);
464 rb->button_clear_queue(); /* clear button queue */
466 for(i = 0; i < numread; i++) {
467 switch(buf[i]) {
468 case '\r':
469 if (mac_text) {
470 buf[i] = 0;
472 else {
473 buf[i] = ' ';
474 found_CR = true;
476 break;
478 case '\n':
479 buf[i] = 0;
480 found_CR = false;
481 break;
483 case 0: /* No break between case 0 and default, intentionally */
484 buf[i] = ' ';
485 default:
486 if (found_CR) {
487 buf[i - 1] = 0;
488 found_CR = false;
489 mac_text = true;
491 break;
496 static int read_and_synch(int direction)
498 /* Read next (or prev) block, and reposition global pointers. */
499 /* direction: 1 for down (i.e., further into file), -1 for up */
500 int move_size, move_vector, offset;
501 unsigned char *fill_buf;
503 if (direction == -1) /* up */ {
504 move_size = SMALL_BLOCK_SIZE;
505 offset = 0;
506 fill_buf = TOP_SECTOR;
507 rb->memcpy(BOTTOM_SECTOR, MID_SECTOR, SMALL_BLOCK_SIZE);
508 rb->memcpy(MID_SECTOR, TOP_SECTOR, SMALL_BLOCK_SIZE);
510 else /* down */ {
511 if (prefs.view_mode == WIDE) {
512 /* WIDE mode needs more buffer so we have to read smaller blocks */
513 move_size = SMALL_BLOCK_SIZE;
514 offset = LARGE_BLOCK_SIZE;
515 fill_buf = BOTTOM_SECTOR;
516 rb->memcpy(TOP_SECTOR, MID_SECTOR, SMALL_BLOCK_SIZE);
517 rb->memcpy(MID_SECTOR, BOTTOM_SECTOR, SMALL_BLOCK_SIZE);
519 else {
520 move_size = LARGE_BLOCK_SIZE;
521 offset = SMALL_BLOCK_SIZE;
522 fill_buf = MID_SECTOR;
523 rb->memcpy(TOP_SECTOR, BOTTOM_SECTOR, SMALL_BLOCK_SIZE);
526 move_vector = direction * move_size;
527 screen_top_ptr -= move_vector;
528 file_pos += move_vector;
529 buffer_end = BUFFER_END(); /* Update whenever file_pos changes */
530 fill_buffer(file_pos + offset, fill_buf, move_size);
531 return move_vector;
534 static void viewer_scroll_up(void)
536 unsigned char *p;
538 p = find_prev_line(screen_top_ptr);
539 if (p == NULL && !BUFFER_BOF()) {
540 read_and_synch(-1);
541 p = find_prev_line(screen_top_ptr);
543 if (p != NULL)
544 screen_top_ptr = p;
547 static void viewer_scroll_down(void)
549 if (next_screen_ptr != NULL)
550 screen_top_ptr = next_line_ptr;
553 #ifdef HAVE_LCD_BITMAP
554 static void viewer_scrollbar(void) {
555 int w, h, items, min_shown, max_shown;
557 rb->lcd_getstringsize("o", &w, &h);
558 items = (int) file_size; /* (SH1 int is same as long) */
559 min_shown = (int) file_pos + (screen_top_ptr - buffer);
561 if (next_screen_ptr == NULL)
562 max_shown = items;
563 else
564 max_shown = min_shown + (next_screen_ptr - screen_top_ptr);
566 rb->scrollbar(0, 0, w-2, LCD_HEIGHT, items, min_shown, max_shown, VERTICAL);
568 #endif
570 static void viewer_draw(int col)
572 int i, j, k, line_len, resynch_move, spaces, left_col=0;
573 int width, extra_spaces, indent_spaces, spaces_per_word;
574 bool multiple_spacing, line_is_short;
575 unsigned char *line_begin;
576 unsigned char *line_end;
577 unsigned char c;
578 unsigned char scratch_buffer[MAX_COLUMNS + 1];
579 unsigned char utf8_buffer[MAX_COLUMNS*4 + 1];
580 int len;
581 unsigned char *endptr;
583 /* If col==-1 do all calculations but don't display */
584 if (col != -1) {
585 #ifdef HAVE_LCD_BITMAP
586 left_col = prefs.need_scrollbar? 1:0;
587 #else
588 left_col = 0;
589 #endif
590 rb->lcd_clear_display();
592 max_line_len = 0;
593 line_begin = line_end = screen_top_ptr;
595 for (i = 0; i < display_lines; i++) {
596 if (BUFFER_OOB(line_end))
597 break; /* Happens after display last line at BUFFER_EOF() */
599 line_begin = line_end;
600 line_end = find_next_line(line_begin, &line_is_short);
602 if (line_end == NULL) {
603 if (BUFFER_EOF()) {
604 if (i < display_lines - 1 && !BUFFER_BOF()) {
605 if (col != -1)
606 rb->lcd_clear_display();
608 for (; i < display_lines - 1; i++)
609 viewer_scroll_up();
611 line_begin = line_end = screen_top_ptr;
612 i = -1;
613 continue;
615 else {
616 line_end = buffer_end;
619 else {
620 resynch_move = read_and_synch(1); /* Read block & move ptrs */
621 line_begin -= resynch_move;
622 if (i > 0)
623 next_line_ptr -= resynch_move;
625 line_end = find_next_line(line_begin, NULL);
626 if (line_end == NULL) /* Should not really happen */
627 break;
630 line_len = line_end - line_begin;
632 if (prefs.line_mode == JOIN) {
633 if (line_begin[0] == 0) {
634 line_begin++;
635 if (prefs.word_mode == CHOP)
636 line_end++;
637 else
638 line_len--;
640 for (j=k=spaces=0; j < line_len; j++) {
641 if (k == MAX_COLUMNS)
642 break;
644 c = line_begin[j];
645 switch (c) {
646 case ' ':
647 spaces++;
648 break;
649 case 0:
650 spaces = 0;
651 scratch_buffer[k++] = ' ';
652 break;
653 default:
654 while (spaces) {
655 spaces--;
656 scratch_buffer[k++] = ' ';
657 if (k == MAX_COLUMNS - 1)
658 break;
660 scratch_buffer[k++] = c;
661 break;
665 if (col != -1)
666 if (k > col) {
667 scratch_buffer[k] = 0;
668 endptr = rb->iso_decode(scratch_buffer + col, utf8_buffer,
669 -1, k-col);
670 *endptr = 0;
671 len = rb->utf8length(utf8_buffer);
672 rb->lcd_puts(left_col, i, utf8_buffer);
675 else if (prefs.line_mode == REFLOW) {
676 if (line_begin[0] == 0) {
677 line_begin++;
678 if (prefs.word_mode == CHOP)
679 line_end++;
680 else
681 line_len--;
684 indent_spaces = 0;
685 if (!line_is_short) {
686 multiple_spacing = false;
687 for (j=width=spaces=0; j < line_len; j++) {
688 c = line_begin[j];
689 switch (c) {
690 case ' ':
691 case 0:
692 if ((j==0) && (prefs.word_mode==WRAP))
693 /* special case: indent the paragraph,
694 * don't count spaces */
695 indent_spaces = par_indent_spaces;
696 else if (!multiple_spacing)
697 spaces++;
698 multiple_spacing = true;
699 break;
700 default:
701 multiple_spacing = false;
702 width += glyph_width[c];
703 k++;
704 break;
707 if (multiple_spacing) spaces--;
709 if (spaces) {
710 /* total number of spaces to insert between words */
711 extra_spaces = (draw_columns-width) / glyph_width[' ']
712 - indent_spaces;
713 /* number of spaces between each word*/
714 spaces_per_word = extra_spaces / spaces;
715 /* number of words with n+1 spaces (to fill up) */
716 extra_spaces = extra_spaces % spaces;
717 if (spaces_per_word > 2) { /* too much spacing is awful */
718 spaces_per_word = 3;
719 extra_spaces = 0;
721 } else { /* this doesn't matter much... no spaces anyway */
722 spaces_per_word = extra_spaces = 0;
724 } else { /* end of a paragraph: don't fill line */
725 spaces_per_word = 1;
726 extra_spaces = 0;
729 multiple_spacing = false;
730 for (j=k=spaces=0; j < line_len; j++) {
731 if (k == MAX_COLUMNS)
732 break;
734 c = line_begin[j];
735 switch (c) {
736 case ' ':
737 case 0:
738 if (j==0 && prefs.word_mode==WRAP) { /* indent paragraph */
739 for (j=0; j<par_indent_spaces; j++)
740 scratch_buffer[k++] = ' ';
741 j=0;
743 else if (!multiple_spacing) {
744 for (width = spaces<extra_spaces ? -1:0; width < spaces_per_word; width++)
745 scratch_buffer[k++] = ' ';
746 spaces++;
748 multiple_spacing = true;
749 break;
750 default:
751 scratch_buffer[k++] = c;
752 multiple_spacing = false;
753 break;
757 if (col != -1)
758 if (k > col) {
759 scratch_buffer[k] = 0;
760 endptr = rb->iso_decode(scratch_buffer + col, utf8_buffer,
761 -1, k-col);
762 *endptr = 0;
763 len = rb->utf8length(utf8_buffer);
764 rb->lcd_puts(left_col, i, utf8_buffer);
767 else { /* prefs.line_mode != JOIN && prefs.line_mode != REFLOW */
768 if (col != -1)
769 if (line_len > col) {
770 c = line_end[0];
771 line_end[0] = 0;
772 endptr = rb->iso_decode(line_begin + col, utf8_buffer,
773 -1, line_end-line_begin);
774 *endptr = 0;
775 len = rb->utf8length(utf8_buffer);
776 rb->lcd_puts(left_col, i, utf8_buffer);
777 line_end[0] = c;
780 if (line_len > max_line_len)
781 max_line_len = line_len;
783 if (i == 0)
784 next_line_ptr = line_end;
786 next_screen_ptr = line_end;
787 if (BUFFER_OOB(next_screen_ptr))
788 next_screen_ptr = NULL;
790 #ifdef HAVE_LCD_BITMAP
791 next_screen_to_draw_ptr = prefs.page_mode==OVERLAP? line_begin: next_screen_ptr;
793 if (prefs.need_scrollbar)
794 viewer_scrollbar();
796 if (col != -1)
797 rb->lcd_update();
798 #else
799 next_screen_to_draw_ptr = next_screen_ptr;
800 #endif
803 static void viewer_top(void)
805 /* Read top of file into buffer
806 and point screen pointer to top */
807 file_pos = 0;
808 buffer_end = BUFFER_END(); /* Update whenever file_pos changes */
809 screen_top_ptr = buffer;
810 fill_buffer(0, buffer, BUFFER_SIZE);
813 static void viewer_bottom(void)
815 /* Read bottom of file into buffer
816 and point screen pointer to bottom */
817 long last_sectors;
819 if (file_size > BUFFER_SIZE) {
820 /* Find last buffer in file, round up to next sector boundary */
821 last_sectors = file_size - BUFFER_SIZE + SMALL_BLOCK_SIZE;
822 last_sectors /= SMALL_BLOCK_SIZE;
823 last_sectors *= SMALL_BLOCK_SIZE;
825 else {
826 last_sectors = 0;
828 file_pos = last_sectors;
829 buffer_end = BUFFER_END(); /* Update whenever file_pos changes */
830 screen_top_ptr = buffer_end-1;
831 fill_buffer(last_sectors, buffer, BUFFER_SIZE);
834 #ifdef HAVE_LCD_BITMAP
835 static void init_need_scrollbar(void) {
836 /* Call viewer_draw in quiet mode to initialize next_screen_ptr,
837 and thus ONE_SCREEN_FITS_ALL(), and thus NEED_SCROLLBAR() */
838 viewer_draw(-1);
839 prefs.need_scrollbar = NEED_SCROLLBAR();
840 draw_columns = prefs.need_scrollbar? display_columns-glyph_width['o'] : display_columns;
841 par_indent_spaces = draw_columns/(5*glyph_width[' ']);
843 #else
844 #define init_need_scrollbar()
845 #endif
847 static bool viewer_init(void)
849 #ifdef HAVE_LCD_BITMAP
850 int idx, ch;
851 struct font *pf;
853 pf = rb->font_get(FONT_UI);
855 if (pf->width != NULL)
856 { /* variable pitch font -- fill structure from font width data */
857 ch = pf->defaultchar - pf->firstchar;
858 rb->memset(glyph_width, pf->width[ch], 256);
859 idx = pf->firstchar;
860 rb->memcpy(&glyph_width[idx], pf->width, pf->size);
861 idx += pf->size;
862 rb->memset(&glyph_width[idx], pf->width[ch], 256-idx);
864 else /* fixed pitch font -- same width for all glyphs */
865 rb->memset(glyph_width, pf->maxwidth, 256);
867 display_lines = LCD_HEIGHT / pf->height;
868 display_columns = LCD_WIDTH;
869 #else
870 /* REAL fixed pitch :) all chars use up 1 cell */
871 display_lines = 2;
872 draw_columns = display_columns = 11;
873 par_indent_spaces = 2;
874 rb->memset(glyph_width, 1, 256);
875 #endif
877 fd = rb->open(file_name, O_RDONLY);
878 if (fd==-1)
879 return false;
881 file_size = rb->filesize(fd);
882 if (file_size==-1)
883 return false;
885 /* Init mac_text value used in processing buffer */
886 mac_text = false;
888 /* Read top of file into buffer;
889 init file_pos, buffer_end, screen_top_ptr */
890 viewer_top();
892 /* Init prefs.need_scrollbar value */
893 init_need_scrollbar();
895 return true;
898 static void viewer_reset_settings(void)
900 prefs.word_mode = WRAP;
901 prefs.line_mode = NORMAL;
902 prefs.view_mode = NARROW;
903 prefs.scroll_mode = PAGE;
904 #ifdef HAVE_LCD_BITMAP
905 prefs.page_mode = NO_OVERLAP;
906 prefs.scrollbar_mode = SB_OFF;
907 #endif
908 prefs.autoscroll_speed = 1;
911 static void viewer_load_settings(void) /* same name as global, but not the same file.. */
913 int settings_fd;
915 settings_fd=rb->open(SETTINGS_FILE, O_RDONLY);
916 if (settings_fd < 0)
918 rb->splash(HZ*2, true, "No Settings File");
919 return;
921 if (rb->filesize(settings_fd) != sizeof(struct preferences))
923 rb->splash(HZ*2, true, "Settings File Invalid");
924 return;
927 rb->read(settings_fd, &prefs, sizeof(struct preferences));
928 rb->close(settings_fd);
930 init_need_scrollbar();
932 file_pos=0;
933 buffer_end = BUFFER_END(); /* Update whenever file_pos changes */
935 screen_top_ptr = buffer;
936 if (BUFFER_OOB(screen_top_ptr)) {
937 screen_top_ptr = buffer;
940 fill_buffer(file_pos, buffer, BUFFER_SIZE);
943 static void viewer_save_settings(void)/* same name as global, but not the same file.. */
945 int settings_fd;
947 settings_fd = rb->creat(SETTINGS_FILE, O_WRONLY); /* create the settings file */
949 rb->write (settings_fd, &prefs, sizeof(struct preferences));
950 rb->close(settings_fd);
953 static void viewer_exit(void *parameter)
955 (void)parameter;
957 viewer_save_settings();
958 rb->close(fd);
961 static int col_limit(int col)
963 if (col < 0)
964 col = 0;
965 else
966 if (col > max_line_len - 2)
967 col = max_line_len - 2;
969 return col;
972 /* settings helper functions */
974 static bool word_wrap_setting(void)
976 static const struct opt_items names[] = {
977 {"On", -1},
978 {"Off (Chop Words)", -1},
981 return rb->set_option("Word Wrap", &prefs.word_mode, INT,
982 names, 2, NULL);
985 static bool line_mode_setting(void)
987 static const struct opt_items names[] = {
988 {"Normal", -1},
989 {"Join Lines", -1},
990 {"Expand Lines", -1},
991 #ifdef HAVE_LCD_BITMAP
992 {"Reflow Lines", -1},
993 #endif
996 return rb->set_option("Line Mode", &prefs.line_mode, INT, names,
997 sizeof(names) / sizeof(names[0]), NULL);
1000 static bool view_mode_setting(void)
1002 static const struct opt_items names[] = {
1003 {"No (Narrow)", -1},
1004 {"Yes", -1},
1007 return rb->set_option("Wide View", &prefs.view_mode, INT,
1008 names , 2, NULL);
1011 static bool scroll_mode_setting(void)
1013 static const struct opt_items names[] = {
1014 {"Scroll by Page", -1},
1015 {"Scroll by Line", -1},
1018 return rb->set_option("Scroll Mode", &prefs.scroll_mode, INT,
1019 names, 2, NULL);
1022 #ifdef HAVE_LCD_BITMAP
1023 static bool page_mode_setting(void)
1025 static const struct opt_items names[] = {
1026 {"No", -1},
1027 {"Yes", -1},
1030 return rb->set_option("Overlap Pages", &prefs.page_mode, INT,
1031 names, 2, NULL);
1034 static bool scrollbar_setting(void)
1036 static const struct opt_items names[] = {
1037 {"Off", -1},
1038 {"On", -1}
1041 return rb->set_option("Show Scrollbar", &prefs.scrollbar_mode, INT,
1042 names, 2, NULL);
1044 #endif
1046 static bool autoscroll_speed_setting(void)
1048 return rb->set_int("Auto-scroll Speed", "", UNIT_INT,
1049 &prefs.autoscroll_speed, NULL, 1, 1, 10, NULL);
1052 static bool viewer_options_menu(void)
1054 int m;
1055 bool result;
1057 static const struct menu_item items[] = {
1058 {"Word Wrap", word_wrap_setting },
1059 {"Line Mode", line_mode_setting },
1060 {"Wide View", view_mode_setting },
1061 #ifdef HAVE_LCD_BITMAP
1062 {"Show Scrollbar", scrollbar_setting },
1063 {"Overlap Pages", page_mode_setting },
1064 #endif
1065 {"Scroll Mode", scroll_mode_setting},
1066 {"Auto-Scroll Speed", autoscroll_speed_setting },
1068 m = rb->menu_init(items, sizeof(items) / sizeof(*items),
1069 NULL, NULL, NULL, NULL);
1071 result = rb->menu_run(m);
1072 rb->menu_exit(m);
1073 #ifdef HAVE_LCD_BITMAP
1074 rb->lcd_setmargins(0,0);
1076 /* Show-scrollbar mode for current view-width mode */
1077 if (!ONE_SCREEN_FITS_ALL())
1078 if (prefs.scrollbar_mode == true)
1079 init_need_scrollbar();
1080 #endif
1081 return result;
1084 static void viewer_menu(void)
1086 int m;
1087 int result;
1088 static const struct menu_item items[] = {
1089 {"Quit", NULL },
1090 {"Viewer Options", NULL },
1091 {"Show Playback Menu", NULL },
1092 {"Return", NULL },
1095 m = rb->menu_init(items, sizeof(items) / sizeof(*items), NULL, NULL, NULL, NULL);
1096 result=rb->menu_show(m);
1097 switch (result)
1099 case 0: /* quit */
1100 rb->splash(1, true, "Saving Settings");
1101 rb->menu_exit(m);
1102 viewer_exit(NULL);
1103 done = true;
1104 break;
1105 case 1: /* change settings */
1106 done = viewer_options_menu();
1107 break;
1108 case 2: /* playback control */
1109 playback_control(rb);
1110 break;
1111 case 3: /* return */
1112 break;
1114 rb->menu_exit(m);
1115 #ifdef HAVE_LCD_BITMAP
1116 rb->lcd_setmargins(0,0);
1117 #endif
1118 viewer_draw(col);
1121 enum plugin_status plugin_start(struct plugin_api* api, void* file)
1123 int button, i, ok;
1124 int lastbutton = BUTTON_NONE;
1125 bool autoscroll = false;
1126 long old_tick;
1128 rb = api;
1129 old_tick = *rb->current_tick;
1131 if (!file)
1132 return PLUGIN_ERROR;
1134 file_name = file;
1135 ok = viewer_init();
1136 if (!ok) {
1137 rb->splash(HZ, false, "Error");
1138 viewer_exit(NULL);
1139 return PLUGIN_OK;
1142 viewer_reset_settings(); /* load defaults first */
1143 viewer_load_settings(); /* .. then try to load from disk */
1145 viewer_draw(col);
1147 while (!done) {
1149 if(autoscroll)
1151 if(old_tick <= *rb->current_tick - (110-prefs.autoscroll_speed*10))
1153 viewer_scroll_down();
1154 viewer_draw(col);
1155 old_tick = *rb->current_tick;
1159 button = rb->button_get_w_tmo(HZ/10);
1160 switch (button) {
1161 case VIEWER_MENU:
1162 viewer_menu();
1163 break;
1165 case VIEWER_AUTOSCROLL:
1166 #ifdef VIEWER_AUTOSCROLL_PRE
1167 if (lastbutton != VIEWER_AUTOSCROLL_PRE)
1168 break;
1169 #endif
1170 autoscroll = !autoscroll;
1171 break;
1173 case VIEWER_PAGE_UP:
1174 case VIEWER_PAGE_UP | BUTTON_REPEAT:
1175 if (prefs.scroll_mode == PAGE)
1177 /* Page up */
1178 #ifdef HAVE_LCD_BITMAP
1179 for (i = prefs.page_mode==OVERLAP? 1:0; i < display_lines; i++)
1180 #else
1181 for (i = 0; i < display_lines; i++)
1182 #endif
1183 viewer_scroll_up();
1185 else
1186 viewer_scroll_up();
1187 old_tick = *rb->current_tick;
1188 viewer_draw(col);
1189 break;
1191 case VIEWER_PAGE_DOWN:
1192 case VIEWER_PAGE_DOWN | BUTTON_REPEAT:
1193 if (prefs.scroll_mode == PAGE)
1195 /* Page down */
1196 if (next_screen_ptr != NULL)
1197 screen_top_ptr = next_screen_to_draw_ptr;
1199 else
1200 viewer_scroll_down();
1201 old_tick = *rb->current_tick;
1202 viewer_draw(col);
1203 break;
1205 case VIEWER_SCREEN_LEFT:
1206 case VIEWER_SCREEN_LEFT | BUTTON_REPEAT:
1207 if (prefs.view_mode == WIDE) {
1208 /* Screen left */
1209 col -= draw_columns/glyph_width['o'];
1210 col = col_limit(col);
1212 else { /* prefs.view_mode == NARROW */
1213 /* Top of file */
1214 viewer_top();
1217 viewer_draw(col);
1218 break;
1220 case VIEWER_SCREEN_RIGHT:
1221 case VIEWER_SCREEN_RIGHT | BUTTON_REPEAT:
1222 if (prefs.view_mode == WIDE) {
1223 /* Screen right */
1224 col += draw_columns/glyph_width['o'];
1225 col = col_limit(col);
1227 else { /* prefs.view_mode == NARROW */
1228 /* Bottom of file */
1229 viewer_bottom();
1232 viewer_draw(col);
1233 break;
1235 #ifdef VIEWER_LINE_UP
1236 case VIEWER_LINE_UP:
1237 case VIEWER_LINE_UP | BUTTON_REPEAT:
1238 /* Scroll up one line */
1239 viewer_scroll_up();
1240 old_tick = *rb->current_tick;
1241 viewer_draw(col);
1242 break;
1244 case VIEWER_LINE_DOWN:
1245 case VIEWER_LINE_DOWN | BUTTON_REPEAT:
1246 /* Scroll down one line */
1247 if (next_screen_ptr != NULL)
1248 screen_top_ptr = next_line_ptr;
1249 old_tick = *rb->current_tick;
1250 viewer_draw(col);
1251 break;
1252 #endif
1253 #ifdef VIEWER_COLUMN_LEFT
1254 case VIEWER_COLUMN_LEFT:
1255 case VIEWER_COLUMN_LEFT | BUTTON_REPEAT:
1256 /* Scroll left one column */
1257 col--;
1258 col = col_limit(col);
1259 viewer_draw(col);
1260 break;
1262 case VIEWER_COLUMN_RIGHT:
1263 case VIEWER_COLUMN_RIGHT | BUTTON_REPEAT:
1264 /* Scroll right one column */
1265 col++;
1266 col = col_limit(col);
1267 viewer_draw(col);
1268 break;
1269 #endif
1271 case VIEWER_QUIT:
1272 viewer_exit(NULL);
1273 done = true;
1274 break;
1276 default:
1277 if (rb->default_event_handler_ex(button, viewer_exit, NULL)
1278 == SYS_USB_CONNECTED)
1279 return PLUGIN_USB_CONNECTED;
1280 break;
1282 if (button != BUTTON_NONE)
1283 lastbutton = button;
1285 return PLUGIN_OK;