1 /***************************************************************************
4 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
5 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
6 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
7 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
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 ****************************************************************************/
22 #include "playback_control.h"
23 #include "oldmenuapi.h"
27 #define SETTINGS_FILE VIEWERS_DIR "/viewer.dat" /* binary file, so dont use .cfg */
28 #define BOOKMARKS_FILE VIEWERS_DIR "/viewer_bookmarks.dat"
30 #define WRAP_TRIM 44 /* Max number of spaces to trim (arbitrary) */
31 #define MAX_COLUMNS 64 /* Max displayable string len (over-estimate) */
32 #define MAX_WIDTH 910 /* Max line length in WIDE mode */
33 #define READ_PREV_ZONE 910 /* Arbitrary number less than SMALL_BLOCK_SIZE */
34 #define SMALL_BLOCK_SIZE 0x1000 /* 4k: Smallest file chunk we will read */
35 #define LARGE_BLOCK_SIZE 0x2000 /* 8k: Preferable size of file chunk to read */
36 #define BUFFER_SIZE 0x3000 /* 12k: Mem reserved for buffered file data */
37 #define TOP_SECTOR buffer
38 #define MID_SECTOR (buffer + SMALL_BLOCK_SIZE)
39 #define BOTTOM_SECTOR (buffer + 2*(SMALL_BLOCK_SIZE))
40 #define SCROLLBAR_WIDTH 6
42 #define MAX_BOOKMARKED_FILES (((signed)BUFFER_SIZE/(signed)sizeof(struct bookmarked_file_info))-1)
44 /* Out-Of-Bounds test for any pointer to data in the buffer */
45 #define BUFFER_OOB(p) ((p) < buffer || (p) >= buffer_end)
47 /* Does the buffer contain the beginning of the file? */
48 #define BUFFER_BOF() (file_pos==0)
50 /* Does the buffer contain the end of the file? */
51 #define BUFFER_EOF() (file_size-file_pos <= BUFFER_SIZE)
53 /* Formula for the endpoint address outside of buffer data */
54 #define BUFFER_END() \
55 ((BUFFER_EOF()) ? (file_size-file_pos+buffer) : (buffer+BUFFER_SIZE))
57 /* Is the entire file being shown in one screen? */
58 #define ONE_SCREEN_FITS_ALL() \
59 (next_screen_ptr==NULL && screen_top_ptr==buffer && BUFFER_BOF())
61 /* Is a scrollbar called for on the current screen? */
62 #define NEED_SCROLLBAR() \
63 ((!(ONE_SCREEN_FITS_ALL())) && (prefs.scrollbar_mode==SB_ON))
65 /* variable button definitions */
68 #if CONFIG_KEYPAD == RECORDER_PAD
69 #define VIEWER_QUIT BUTTON_OFF
70 #define VIEWER_PAGE_UP BUTTON_UP
71 #define VIEWER_PAGE_DOWN BUTTON_DOWN
72 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
73 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
74 #define VIEWER_MENU BUTTON_F1
75 #define VIEWER_AUTOSCROLL BUTTON_PLAY
76 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
77 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
78 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
79 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
81 #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
82 #define VIEWER_QUIT BUTTON_OFF
83 #define VIEWER_PAGE_UP BUTTON_UP
84 #define VIEWER_PAGE_DOWN BUTTON_DOWN
85 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
86 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
87 #define VIEWER_MENU BUTTON_F1
88 #define VIEWER_AUTOSCROLL BUTTON_SELECT
89 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
90 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
91 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
92 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
95 #elif CONFIG_KEYPAD == ONDIO_PAD
96 #define VIEWER_QUIT BUTTON_OFF
97 #define VIEWER_PAGE_UP BUTTON_UP
98 #define VIEWER_PAGE_DOWN BUTTON_DOWN
99 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
100 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
101 #define VIEWER_MENU (BUTTON_MENU|BUTTON_REPEAT)
102 #define VIEWER_AUTOSCROLL_PRE BUTTON_MENU
103 #define VIEWER_AUTOSCROLL (BUTTON_MENU|BUTTON_REL)
106 #elif CONFIG_KEYPAD == PLAYER_PAD
107 #define VIEWER_QUIT BUTTON_STOP
108 #define VIEWER_PAGE_UP BUTTON_LEFT
109 #define VIEWER_PAGE_DOWN BUTTON_RIGHT
110 #define VIEWER_SCREEN_LEFT (BUTTON_ON|BUTTON_LEFT)
111 #define VIEWER_SCREEN_RIGHT (BUTTON_ON|BUTTON_RIGHT)
112 #define VIEWER_MENU BUTTON_MENU
113 #define VIEWER_AUTOSCROLL BUTTON_PLAY
115 /* iRiver H1x0 && H3x0 keys */
116 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
117 (CONFIG_KEYPAD == IRIVER_H300_PAD)
118 #define VIEWER_QUIT BUTTON_OFF
119 #define VIEWER_PAGE_UP BUTTON_UP
120 #define VIEWER_PAGE_DOWN BUTTON_DOWN
121 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
122 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
123 #define VIEWER_MENU BUTTON_MODE
124 #define VIEWER_AUTOSCROLL BUTTON_SELECT
125 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
126 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
127 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
128 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
130 #define VIEWER_RC_QUIT BUTTON_RC_STOP
133 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
134 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
135 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
136 #define VIEWER_QUIT_PRE BUTTON_SELECT
137 #define VIEWER_QUIT (BUTTON_SELECT | BUTTON_MENU)
138 #define VIEWER_PAGE_UP BUTTON_SCROLL_BACK
139 #define VIEWER_PAGE_DOWN BUTTON_SCROLL_FWD
140 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
141 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
142 #define VIEWER_MENU BUTTON_MENU
143 #define VIEWER_AUTOSCROLL BUTTON_PLAY
146 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
147 #define VIEWER_QUIT BUTTON_PLAY
148 #define VIEWER_PAGE_UP BUTTON_UP
149 #define VIEWER_PAGE_DOWN BUTTON_DOWN
150 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
151 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
152 #define VIEWER_MENU BUTTON_MODE
153 #define VIEWER_AUTOSCROLL BUTTON_SELECT
156 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
157 #define VIEWER_QUIT BUTTON_POWER
158 #define VIEWER_PAGE_UP BUTTON_UP
159 #define VIEWER_PAGE_DOWN BUTTON_DOWN
160 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
161 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
162 #define VIEWER_MENU BUTTON_SELECT
163 #define VIEWER_AUTOSCROLL BUTTON_PLAY
166 #elif CONFIG_KEYPAD == GIGABEAT_PAD
167 #define VIEWER_QUIT BUTTON_POWER
168 #define VIEWER_PAGE_UP BUTTON_UP
169 #define VIEWER_PAGE_DOWN BUTTON_DOWN
170 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
171 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
172 #define VIEWER_MENU BUTTON_MENU
173 #define VIEWER_AUTOSCROLL BUTTON_A
175 /* Sansa E200 keys */
176 #elif CONFIG_KEYPAD == SANSA_E200_PAD
177 #define VIEWER_QUIT BUTTON_POWER
178 #define VIEWER_PAGE_UP BUTTON_UP
179 #define VIEWER_PAGE_DOWN BUTTON_DOWN
180 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
181 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
182 #define VIEWER_MENU BUTTON_SELECT
183 #define VIEWER_AUTOSCROLL BUTTON_REC
184 #define VIEWER_LINE_UP BUTTON_SCROLL_UP
185 #define VIEWER_LINE_DOWN BUTTON_SCROLL_DOWN
187 /* Sansa C200 keys */
188 #elif CONFIG_KEYPAD == SANSA_C200_PAD
189 #define VIEWER_QUIT BUTTON_POWER
190 #define VIEWER_PAGE_UP BUTTON_VOL_UP
191 #define VIEWER_PAGE_DOWN BUTTON_VOL_DOWN
192 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
193 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
194 #define VIEWER_MENU BUTTON_SELECT
195 #define VIEWER_AUTOSCROLL BUTTON_REC
196 #define VIEWER_LINE_UP BUTTON_UP
197 #define VIEWER_LINE_DOWN BUTTON_DOWN
199 /* iriver H10 keys */
200 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
201 #define VIEWER_QUIT BUTTON_POWER
202 #define VIEWER_PAGE_UP BUTTON_SCROLL_UP
203 #define VIEWER_PAGE_DOWN BUTTON_SCROLL_DOWN
204 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
205 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
206 #define VIEWER_MENU BUTTON_REW
207 #define VIEWER_AUTOSCROLL BUTTON_PLAY
209 #elif CONFIG_KEYPAD == MROBE500_PAD
210 #define VIEWER_QUIT BUTTON_POWER
211 #define VIEWER_PAGE_UP BUTTON_RC_PLAY
212 #define VIEWER_PAGE_DOWN BUTTON_RC_DOWN
213 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
214 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
215 #define VIEWER_MENU BUTTON_RC_HEART
216 #define VIEWER_AUTOSCROLL BUTTON_RC_MODE
220 /* stuff for the bookmarking */
221 struct bookmarked_file_info
{
224 char filename
[MAX_PATH
];
227 struct bookmark_file_data
{
228 signed int bookmarked_files_count
;
229 struct bookmarked_file_info bookmarks
[];
242 REFLOW
, /* won't be set on charcell LCD, must be last */
266 } encoding
; /* FIXME: What should default encoding be? */
268 #ifdef HAVE_LCD_BITMAP
279 #endif /* HAVE_LCD_BITMAP */
286 int autoscroll_speed
;
290 static unsigned char buffer
[BUFFER_SIZE
+ 1];
291 static unsigned char line_break
[] = {0,0x20,9,0xB,0xC,'-'};
292 static int display_columns
; /* number of (pixel) columns on the display */
293 static int display_lines
; /* number of lines on the display */
294 static int draw_columns
; /* number of (pixel) columns available for text */
295 static int par_indent_spaces
; /* number of spaces to indent first paragraph */
297 static char *file_name
;
298 static long file_size
;
299 static bool mac_text
;
300 static long file_pos
; /* Position of the top of the buffer in the file */
301 static unsigned char *buffer_end
; /*Set to BUFFER_END() when file_pos changes*/
302 static int max_line_len
;
303 static unsigned char *screen_top_ptr
;
304 static unsigned char *next_screen_ptr
;
305 static unsigned char *next_screen_to_draw_ptr
;
306 static unsigned char *next_line_ptr
;
307 static struct plugin_api
* rb
;
308 #ifdef HAVE_LCD_BITMAP
309 static struct font
*pf
;
313 int glyph_width(int ch
)
318 #ifdef HAVE_LCD_BITMAP
319 return rb
->font_get_width(pf
, ch
);
325 unsigned char* get_ucs(const unsigned char* str
, unsigned short* ch
)
327 unsigned char utf8_tmp
[6];
330 if (prefs
.encoding
== UTF8
)
331 return (unsigned char*)rb
->utf8decode(str
, ch
);
333 count
= BUFFER_OOB(str
+2)? 1:2;
334 rb
->iso_decode(str
, utf8_tmp
, prefs
.encoding
, count
);
335 rb
->utf8decode(utf8_tmp
, ch
);
337 if ((prefs
.encoding
== SJIS
&& *str
> 0xA0 && *str
< 0xE0) || prefs
.encoding
< SJIS
)
338 return (unsigned char*)str
+1;
340 return (unsigned char*)str
+2;
346 #define ADVANCE_COUNTERS(c) { width += glyph_width(c); k++; }
347 #define LINE_IS_FULL ((k>=MAX_COLUMNS-1) ||( width >= draw_columns))
348 #define LINE_IS_NOT_FULL ((k<MAX_COLUMNS-1) &&( width < draw_columns))
349 static unsigned char* crop_at_width(const unsigned char* p
)
353 const unsigned char *oldp
= p
;
357 while (LINE_IS_NOT_FULL
) {
360 ADVANCE_COUNTERS(ch
);
363 return (unsigned char*)oldp
;
366 static unsigned char* find_first_feed(const unsigned char* p
, int size
)
370 for (i
=0; i
< size
; i
++)
372 return (unsigned char*) p
+i
;
377 static unsigned char* find_last_feed(const unsigned char* p
, int size
)
381 for (i
=size
-1; i
>=0; i
--)
383 return (unsigned char*) p
+i
;
388 static unsigned char* find_last_space(const unsigned char* p
, int size
)
392 k
= (prefs
.line_mode
==JOIN
) || (prefs
.line_mode
==REFLOW
) ? 0:1;
394 if (!BUFFER_OOB(&p
[size
]))
395 for (j
=k
; j
< ((int) sizeof(line_break
)) - 1; j
++)
396 if (p
[size
] == line_break
[j
])
397 return (unsigned char*) p
+size
;
399 for (i
=size
-1; i
>=0; i
--)
400 for (j
=k
; j
< (int) sizeof(line_break
); j
++)
402 if (!((p
[i
] == '-') && (prefs
.word_mode
== WRAP
)))
403 if (p
[i
] == line_break
[j
])
404 return (unsigned char*) p
+i
;
410 static unsigned char* find_next_line(const unsigned char* cur_line
, bool *is_short
)
412 const unsigned char *next_line
= NULL
;
413 int size
, i
, j
, k
, width
, search_len
, spaces
, newlines
;
417 if (is_short
!= NULL
)
420 if BUFFER_OOB(cur_line
)
423 if (prefs
.view_mode
== WIDE
) {
424 search_len
= MAX_WIDTH
;
426 else { /* prefs.view_mode == NARROW */
427 search_len
= crop_at_width(cur_line
) - cur_line
;
430 size
= BUFFER_OOB(cur_line
+search_len
) ? buffer_end
-cur_line
: search_len
;
432 if ((prefs
.line_mode
== JOIN
) || (prefs
.line_mode
== REFLOW
)) {
433 /* Need to scan ahead and possibly increase search_len and size,
434 or possibly set next_line at second hard return in a row. */
437 for (j
=k
=width
=spaces
=newlines
=0; ; j
++) {
438 if (BUFFER_OOB(cur_line
+j
))
441 size
= search_len
= j
;
448 if (prefs
.line_mode
== REFLOW
) {
451 next_line
= cur_line
+ size
;
452 return (unsigned char*) next_line
;
454 if (j
==0) /* i=1 is intentional */
455 for (i
=0; i
<par_indent_spaces
; i
++)
456 ADVANCE_COUNTERS(' ');
458 if (!first_chars
) spaces
++;
464 next_line
= cur_line
+ size
- spaces
;
465 if (next_line
!= cur_line
)
466 return (unsigned char*) next_line
;
472 if (BUFFER_OOB(cur_line
+size
) || size
> 2*search_len
)
475 spaces
= first_chars
? 0:1;
479 if (prefs
.line_mode
==JOIN
|| newlines
>0) {
482 ADVANCE_COUNTERS(' ');
484 size
= search_len
= j
;
490 /* REFLOW, multiple spaces between words: count only
491 * one. If more are needed, they will be added
495 ADVANCE_COUNTERS(' ');
497 size
= search_len
= j
;
508 /* find first hard return */
509 next_line
= find_first_feed(cur_line
, size
);
512 if (next_line
== NULL
)
513 if (size
== search_len
) {
514 if (prefs
.word_mode
== WRAP
) /* Find last space */
515 next_line
= find_last_space(cur_line
, size
);
517 if (next_line
== NULL
)
518 next_line
= crop_at_width(cur_line
);
520 if (prefs
.word_mode
== WRAP
)
522 i
<WRAP_TRIM
&& isspace(next_line
[0]) && !BUFFER_OOB(next_line
);
527 if (prefs
.line_mode
== EXPAND
)
528 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
529 if (next_line
[0] == 0)
530 if (next_line
!= cur_line
)
531 return (unsigned char*) next_line
;
533 /* If next_line is pointing to a zero, increment it; i.e.,
534 leave the terminator at the end of cur_line. If pointing
535 to a hyphen, increment only if there is room to display
536 the hyphen on current line (won't apply in WIDE mode,
537 since it's guarenteed there won't be room). */
538 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
539 if (next_line
[0] == 0)/* ||
540 (next_line[0] == '-' && next_line-cur_line < draw_columns)) */
543 if (BUFFER_OOB(next_line
))
549 return (unsigned char*) next_line
;
552 static unsigned char* find_prev_line(const unsigned char* cur_line
)
554 const unsigned char *prev_line
= NULL
;
555 const unsigned char *p
;
557 if BUFFER_OOB(cur_line
)
560 /* To wrap consistently at the same places, we must
561 start with a known hard return, then work downwards.
562 We can either search backwards for a hard return,
563 or simply start wrapping downwards from top of buffer.
564 If current line is not near top of buffer, this is
565 a file with long lines (paragraphs). We would need to
566 read earlier sectors before we could decide how to
567 properly wrap the lines above the current line, but
568 it probably is not worth the disk access. Instead,
569 start with top of buffer and wrap down from there.
570 This may result in some lines wrapping at different
571 points from where they wrap when scrolling down.
572 If buffer is at top of file, start at top of buffer. */
574 if ((prefs
.line_mode
== JOIN
) || (prefs
.line_mode
== REFLOW
))
575 prev_line
= p
= NULL
;
577 prev_line
= p
= find_last_feed(buffer
, cur_line
-buffer
-1);
578 /* Null means no line feeds in buffer above current line. */
580 if (prev_line
== NULL
)
581 if (BUFFER_BOF() || cur_line
- buffer
> READ_PREV_ZONE
)
582 prev_line
= p
= buffer
;
583 /* (else return NULL and read previous block) */
585 /* Wrap downwards until too far, then use the one before. */
586 while (p
< cur_line
&& p
!= NULL
) {
588 p
= find_next_line(prev_line
, NULL
);
591 if (BUFFER_OOB(prev_line
))
594 return (unsigned char*) prev_line
;
597 static void fill_buffer(long pos
, unsigned char* buf
, unsigned size
)
599 /* Read from file and preprocess the data */
600 /* To minimize disk access, always read on sector boundaries */
602 bool found_CR
= false;
604 rb
->lseek(fd
, pos
, SEEK_SET
);
605 numread
= rb
->read(fd
, buf
, size
);
606 rb
->button_clear_queue(); /* clear button queue */
608 for(i
= 0; i
< numread
; i
++) {
625 case 0: /* No break between case 0 and default, intentionally */
638 static int read_and_synch(int direction
)
640 /* Read next (or prev) block, and reposition global pointers. */
641 /* direction: 1 for down (i.e., further into file), -1 for up */
642 int move_size
, move_vector
, offset
;
643 unsigned char *fill_buf
;
645 if (direction
== -1) /* up */ {
646 move_size
= SMALL_BLOCK_SIZE
;
648 fill_buf
= TOP_SECTOR
;
649 rb
->memcpy(BOTTOM_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
650 rb
->memcpy(MID_SECTOR
, TOP_SECTOR
, SMALL_BLOCK_SIZE
);
653 if (prefs
.view_mode
== WIDE
) {
654 /* WIDE mode needs more buffer so we have to read smaller blocks */
655 move_size
= SMALL_BLOCK_SIZE
;
656 offset
= LARGE_BLOCK_SIZE
;
657 fill_buf
= BOTTOM_SECTOR
;
658 rb
->memcpy(TOP_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
659 rb
->memcpy(MID_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
662 move_size
= LARGE_BLOCK_SIZE
;
663 offset
= SMALL_BLOCK_SIZE
;
664 fill_buf
= MID_SECTOR
;
665 rb
->memcpy(TOP_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
668 move_vector
= direction
* move_size
;
669 screen_top_ptr
-= move_vector
;
670 file_pos
+= move_vector
;
671 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
672 fill_buffer(file_pos
+ offset
, fill_buf
, move_size
);
676 static void viewer_scroll_up(void)
680 p
= find_prev_line(screen_top_ptr
);
681 if (p
== NULL
&& !BUFFER_BOF()) {
683 p
= find_prev_line(screen_top_ptr
);
689 static void viewer_scroll_down(void)
691 if (next_screen_ptr
!= NULL
)
692 screen_top_ptr
= next_line_ptr
;
695 #ifdef HAVE_LCD_BITMAP
696 static void viewer_scrollbar(void) {
697 int items
, min_shown
, max_shown
;
699 items
= (int) file_size
; /* (SH1 int is same as long) */
700 min_shown
= (int) file_pos
+ (screen_top_ptr
- buffer
);
702 if (next_screen_ptr
== NULL
)
705 max_shown
= min_shown
+ (next_screen_ptr
- screen_top_ptr
);
707 rb
->gui_scrollbar_draw(rb
->screens
[SCREEN_MAIN
],0, 0, SCROLLBAR_WIDTH
-1,
708 LCD_HEIGHT
, items
, min_shown
, max_shown
, VERTICAL
);
712 static void viewer_draw(int col
)
714 int i
, j
, k
, line_len
, line_width
, resynch_move
, spaces
, left_col
=0;
715 int width
, extra_spaces
, indent_spaces
, spaces_per_word
;
716 bool multiple_spacing
, line_is_short
;
718 unsigned char *str
, *oldstr
;
719 unsigned char *line_begin
;
720 unsigned char *line_end
;
722 unsigned char scratch_buffer
[MAX_COLUMNS
+ 1];
723 unsigned char utf8_buffer
[MAX_COLUMNS
*4 + 1];
724 unsigned char *endptr
;
726 /* If col==-1 do all calculations but don't display */
728 #ifdef HAVE_LCD_BITMAP
729 left_col
= prefs
.need_scrollbar
? SCROLLBAR_WIDTH
:0;
733 rb
->lcd_clear_display();
736 line_begin
= line_end
= screen_top_ptr
;
738 for (i
= 0; i
< display_lines
; i
++) {
739 if (BUFFER_OOB(line_end
))
740 break; /* Happens after display last line at BUFFER_EOF() */
742 line_begin
= line_end
;
743 line_end
= find_next_line(line_begin
, &line_is_short
);
745 if (line_end
== NULL
) {
747 if (i
< display_lines
- 1 && !BUFFER_BOF()) {
749 rb
->lcd_clear_display();
751 for (; i
< display_lines
- 1; i
++)
754 line_begin
= line_end
= screen_top_ptr
;
759 line_end
= buffer_end
;
763 resynch_move
= read_and_synch(1); /* Read block & move ptrs */
764 line_begin
-= resynch_move
;
766 next_line_ptr
-= resynch_move
;
768 line_end
= find_next_line(line_begin
, NULL
);
769 if (line_end
== NULL
) /* Should not really happen */
773 line_len
= line_end
- line_begin
;
775 /* calculate line_len */
776 str
= oldstr
= line_begin
;
778 while (str
< line_end
) {
780 str
= crop_at_width(str
);
783 line_width
= j
*draw_columns
;
784 while (oldstr
< line_end
) {
785 oldstr
= get_ucs(oldstr
, &ch
);
786 line_width
+= glyph_width(ch
);
789 if (prefs
.line_mode
== JOIN
) {
790 if (line_begin
[0] == 0) {
792 if (prefs
.word_mode
== CHOP
)
797 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
798 if (k
== MAX_COLUMNS
)
808 scratch_buffer
[k
++] = ' ';
813 scratch_buffer
[k
++] = ' ';
814 if (k
== MAX_COLUMNS
- 1)
817 scratch_buffer
[k
++] = c
;
822 scratch_buffer
[k
] = 0;
823 endptr
= rb
->iso_decode(scratch_buffer
+ col
, utf8_buffer
,
824 prefs
.encoding
, draw_columns
/glyph_width('i'));
828 else if (prefs
.line_mode
== REFLOW
) {
829 if (line_begin
[0] == 0) {
831 if (prefs
.word_mode
== CHOP
)
838 if (!line_is_short
) {
839 multiple_spacing
= false;
841 for (str
= line_begin
; str
< line_end
; ) {
842 str
= get_ucs(str
, &ch
);
846 if ((str
== line_begin
) && (prefs
.word_mode
==WRAP
))
847 /* special case: indent the paragraph,
848 * don't count spaces */
849 indent_spaces
= par_indent_spaces
;
850 else if (!multiple_spacing
)
852 multiple_spacing
= true;
855 multiple_spacing
= false;
856 width
+= glyph_width(ch
);
860 if (multiple_spacing
) spaces
--;
863 /* total number of spaces to insert between words */
864 extra_spaces
= (draw_columns
-width
)/glyph_width(' ')
866 /* number of spaces between each word*/
867 spaces_per_word
= extra_spaces
/ spaces
;
868 /* number of words with n+1 spaces (to fill up) */
869 extra_spaces
= extra_spaces
% spaces
;
870 if (spaces_per_word
> 2) { /* too much spacing is awful */
874 } else { /* this doesn't matter much... no spaces anyway */
875 spaces_per_word
= extra_spaces
= 0;
877 } else { /* end of a paragraph: don't fill line */
882 multiple_spacing
= false;
883 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
884 if (k
== MAX_COLUMNS
)
891 if (j
==0 && prefs
.word_mode
==WRAP
) { /* indent paragraph */
892 for (j
=0; j
<par_indent_spaces
; j
++)
893 scratch_buffer
[k
++] = ' ';
896 else if (!multiple_spacing
) {
897 for (width
= spaces
<extra_spaces
? -1:0; width
< spaces_per_word
; width
++)
898 scratch_buffer
[k
++] = ' ';
901 multiple_spacing
= true;
904 scratch_buffer
[k
++] = c
;
905 multiple_spacing
= false;
911 scratch_buffer
[k
] = 0;
912 endptr
= rb
->iso_decode(scratch_buffer
+ col
, utf8_buffer
,
913 prefs
.encoding
, k
-col
);
917 else { /* prefs.line_mode != JOIN && prefs.line_mode != REFLOW */
919 if (line_width
> col
) {
920 str
= oldstr
= line_begin
;
923 while( (width
<draw_columns
) && (oldstr
<line_end
) )
925 oldstr
= get_ucs(oldstr
, &ch
);
927 k
-= glyph_width(ch
);
930 width
+= glyph_width(ch
);
934 if(prefs
.view_mode
==WIDE
)
935 endptr
= rb
->iso_decode(line_begin
, utf8_buffer
,
936 prefs
.encoding
, oldstr
-line_begin
);
938 endptr
= rb
->iso_decode(line_begin
, utf8_buffer
,
939 prefs
.encoding
, line_end
-line_begin
);
943 if (col
!= -1 && line_width
> col
)
944 #ifdef HAVE_LCD_BITMAP
945 rb
->lcd_putsxy(left_col
, i
*pf
->height
, utf8_buffer
);
947 rb
->lcd_puts(left_col
, i
, utf8_buffer
);
949 if (line_width
> max_line_len
)
950 max_line_len
= line_width
;
953 next_line_ptr
= line_end
;
955 next_screen_ptr
= line_end
;
956 if (BUFFER_OOB(next_screen_ptr
))
957 next_screen_ptr
= NULL
;
959 #ifdef HAVE_LCD_BITMAP
960 next_screen_to_draw_ptr
= prefs
.page_mode
==OVERLAP
? line_begin
: next_screen_ptr
;
962 if (prefs
.need_scrollbar
)
965 next_screen_to_draw_ptr
= next_screen_ptr
;
972 static void viewer_top(void)
974 /* Read top of file into buffer
975 and point screen pointer to top */
977 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
978 screen_top_ptr
= buffer
;
979 fill_buffer(0, buffer
, BUFFER_SIZE
);
982 static void viewer_bottom(void)
984 /* Read bottom of file into buffer
985 and point screen pointer to bottom */
988 if (file_size
> BUFFER_SIZE
) {
989 /* Find last buffer in file, round up to next sector boundary */
990 last_sectors
= file_size
- BUFFER_SIZE
+ SMALL_BLOCK_SIZE
;
991 last_sectors
/= SMALL_BLOCK_SIZE
;
992 last_sectors
*= SMALL_BLOCK_SIZE
;
997 file_pos
= last_sectors
;
998 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
999 screen_top_ptr
= buffer_end
-1;
1000 fill_buffer(last_sectors
, buffer
, BUFFER_SIZE
);
1003 #ifdef HAVE_LCD_BITMAP
1004 static void init_need_scrollbar(void) {
1005 /* Call viewer_draw in quiet mode to initialize next_screen_ptr,
1006 and thus ONE_SCREEN_FITS_ALL(), and thus NEED_SCROLLBAR() */
1008 prefs
.need_scrollbar
= NEED_SCROLLBAR();
1009 draw_columns
= prefs
.need_scrollbar
? display_columns
-SCROLLBAR_WIDTH
: display_columns
;
1010 par_indent_spaces
= draw_columns
/(5*glyph_width(' '));
1013 #define init_need_scrollbar()
1016 static bool viewer_init(void)
1018 #ifdef HAVE_LCD_BITMAP
1020 pf
= rb
->font_get(FONT_UI
);
1022 display_lines
= LCD_HEIGHT
/ pf
->height
;
1023 draw_columns
= display_columns
= LCD_WIDTH
;
1025 /* REAL fixed pitch :) all chars use up 1 cell */
1027 draw_columns
= display_columns
= 11;
1028 par_indent_spaces
= 2;
1031 fd
= rb
->open(file_name
, O_RDONLY
);
1035 file_size
= rb
->filesize(fd
);
1039 /* Init mac_text value used in processing buffer */
1042 /* Set codepage to system default */
1043 prefs
.encoding
= rb
->global_settings
->default_codepage
;
1045 /* Read top of file into buffer;
1046 init file_pos, buffer_end, screen_top_ptr */
1049 /* Init prefs.need_scrollbar value */
1050 init_need_scrollbar();
1055 static void viewer_reset_settings(void)
1057 prefs
.word_mode
= WRAP
;
1058 prefs
.line_mode
= NORMAL
;
1059 prefs
.view_mode
= NARROW
;
1060 prefs
.scroll_mode
= PAGE
;
1061 #ifdef HAVE_LCD_BITMAP
1062 prefs
.page_mode
= NO_OVERLAP
;
1063 prefs
.scrollbar_mode
= SB_OFF
;
1065 prefs
.autoscroll_speed
= 1;
1068 static void viewer_load_settings(void) /* same name as global, but not the same file.. */
1071 struct bookmark_file_data
*data
;
1073 /* read settings file */
1074 settings_fd
=rb
->open(SETTINGS_FILE
, O_RDONLY
);
1075 if ((settings_fd
< 0) || (rb
->filesize(settings_fd
) != sizeof(struct preferences
)))
1077 rb
->splash(HZ
*2, "No Valid Settings File");
1081 rb
->read(settings_fd
, &prefs
, sizeof(struct preferences
));
1082 rb
->close(settings_fd
);
1085 data
= (struct bookmark_file_data
*)buffer
; /* grab the text buffer */
1086 data
->bookmarked_files_count
= 0;
1088 /* read bookmarks if file exists */
1089 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_RDONLY
);
1090 if (settings_fd
>= 0)
1092 /* figure out how many items to read */
1093 rb
->read(settings_fd
, &data
->bookmarked_files_count
, sizeof(signed int));
1094 if (data
->bookmarked_files_count
> MAX_BOOKMARKED_FILES
)
1095 data
->bookmarked_files_count
= MAX_BOOKMARKED_FILES
;
1096 rb
->read(settings_fd
, data
->bookmarks
,
1097 sizeof(struct bookmarked_file_info
) * data
->bookmarked_files_count
);
1098 rb
->close(settings_fd
);
1101 /* check if current file is in list */
1102 for (i
=0; i
< data
->bookmarked_files_count
; i
++)
1104 if (!rb
->strcmp(file_name
, data
->bookmarks
[i
].filename
))
1106 file_pos
= data
->bookmarks
[i
].file_position
;
1107 screen_top_ptr
= buffer
+ data
->bookmarks
[i
].top_ptr_pos
;
1112 /* prevent potential slot overflow */
1113 if (i
>= data
->bookmarked_files_count
)
1115 if (i
< MAX_BOOKMARKED_FILES
)
1116 data
->bookmarked_files_count
++;
1118 i
= MAX_BOOKMARKED_FILES
-1;
1121 /* write bookmark file with spare slot in first position
1122 to be filled in by viewer_save_settings */
1123 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_WRONLY
|O_CREAT
);
1124 if (settings_fd
>=0 )
1126 /* write count and skip first slot */
1127 rb
->write (settings_fd
, &data
->bookmarked_files_count
, sizeof(signed int));
1128 rb
->PREFIX(lseek
)(settings_fd
,sizeof(struct bookmarked_file_info
),SEEK_CUR
);
1130 /* shuffle up bookmarks */
1131 rb
->write (settings_fd
, data
->bookmarks
, sizeof(struct bookmarked_file_info
)*i
);
1132 rb
->close(settings_fd
);
1135 init_need_scrollbar();
1137 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
1139 if (BUFFER_OOB(screen_top_ptr
))
1141 screen_top_ptr
= buffer
;
1144 fill_buffer(file_pos
, buffer
, BUFFER_SIZE
);
1147 static void viewer_save_settings(void)/* same name as global, but not the same file.. */
1151 settings_fd
= rb
->creat(SETTINGS_FILE
); /* create the settings file */
1153 rb
->write (settings_fd
, &prefs
, sizeof(struct preferences
));
1154 rb
->close(settings_fd
);
1156 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_WRONLY
|O_CREAT
);
1157 if (settings_fd
>= 0 )
1159 struct bookmarked_file_info b
;
1160 b
.file_position
= file_pos
;
1161 b
.top_ptr_pos
= screen_top_ptr
- buffer
;
1162 rb
->memset(&b
.filename
[0],0,MAX_PATH
);
1163 rb
->strcpy(b
.filename
,file_name
);
1164 rb
->PREFIX(lseek
)(settings_fd
,sizeof(signed int),SEEK_SET
);
1165 rb
->write (settings_fd
, &b
, sizeof(struct bookmarked_file_info
));
1166 rb
->close(settings_fd
);
1170 static void viewer_exit(void *parameter
)
1174 viewer_save_settings();
1178 static int col_limit(int col
)
1183 if (col
> max_line_len
- 2*glyph_width('o'))
1184 col
= max_line_len
- 2*glyph_width('o');
1189 /* settings helper functions */
1191 static bool encoding_setting(void)
1193 static const struct opt_items names
[] = {
1198 {"ISO-8859-11", -1},
1210 return rb
->set_option("Encoding", &prefs
.encoding
, INT
, names
,
1211 sizeof(names
) / sizeof(names
[0]), NULL
);
1214 static bool word_wrap_setting(void)
1216 static const struct opt_items names
[] = {
1218 {"Off (Chop Words)", -1},
1221 return rb
->set_option("Word Wrap", &prefs
.word_mode
, INT
,
1225 static bool line_mode_setting(void)
1227 static const struct opt_items names
[] = {
1230 {"Expand Lines", -1},
1231 #ifdef HAVE_LCD_BITMAP
1232 {"Reflow Lines", -1},
1236 return rb
->set_option("Line Mode", &prefs
.line_mode
, INT
, names
,
1237 sizeof(names
) / sizeof(names
[0]), NULL
);
1240 static bool view_mode_setting(void)
1242 static const struct opt_items names
[] = {
1243 {"No (Narrow)", -1},
1247 ret
= rb
->set_option("Wide View", &prefs
.view_mode
, INT
,
1249 if (prefs
.view_mode
== NARROW
)
1254 static bool scroll_mode_setting(void)
1256 static const struct opt_items names
[] = {
1257 {"Scroll by Page", -1},
1258 {"Scroll by Line", -1},
1261 return rb
->set_option("Scroll Mode", &prefs
.scroll_mode
, INT
,
1265 #ifdef HAVE_LCD_BITMAP
1266 static bool page_mode_setting(void)
1268 static const struct opt_items names
[] = {
1273 return rb
->set_option("Overlap Pages", &prefs
.page_mode
, INT
,
1277 static bool scrollbar_setting(void)
1279 static const struct opt_items names
[] = {
1284 return rb
->set_option("Show Scrollbar", &prefs
.scrollbar_mode
, INT
,
1289 static bool autoscroll_speed_setting(void)
1291 return rb
->set_int("Auto-scroll Speed", "", UNIT_INT
,
1292 &prefs
.autoscroll_speed
, NULL
, 1, 1, 10, NULL
);
1295 static bool viewer_options_menu(void)
1300 static const struct menu_item items
[] = {
1301 {"Encoding", encoding_setting
},
1302 {"Word Wrap", word_wrap_setting
},
1303 {"Line Mode", line_mode_setting
},
1304 {"Wide View", view_mode_setting
},
1305 #ifdef HAVE_LCD_BITMAP
1306 {"Show Scrollbar", scrollbar_setting
},
1307 {"Overlap Pages", page_mode_setting
},
1309 {"Scroll Mode", scroll_mode_setting
},
1310 {"Auto-Scroll Speed", autoscroll_speed_setting
},
1312 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
),
1313 NULL
, NULL
, NULL
, NULL
);
1315 result
= menu_run(m
);
1317 #ifdef HAVE_LCD_BITMAP
1318 rb
->lcd_setmargins(0,0);
1320 /* Show-scrollbar mode for current view-width mode */
1321 if (!ONE_SCREEN_FITS_ALL())
1322 if (prefs
.scrollbar_mode
== true)
1323 init_need_scrollbar();
1328 static void viewer_menu(void)
1332 static const struct menu_item items
[] = {
1334 {"Viewer Options", NULL
},
1335 {"Show Playback Menu", NULL
},
1339 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
), NULL
, NULL
, NULL
, NULL
);
1340 result
=menu_show(m
);
1348 case 1: /* change settings */
1349 done
= viewer_options_menu();
1351 case 2: /* playback control */
1352 playback_control(rb
);
1354 case 3: /* return */
1358 #ifdef HAVE_LCD_BITMAP
1359 rb
->lcd_setmargins(0,0);
1364 enum plugin_status
plugin_start(struct plugin_api
* api
, void* file
)
1367 int lastbutton
= BUTTON_NONE
;
1368 bool autoscroll
= false;
1372 old_tick
= *rb
->current_tick
;
1375 return PLUGIN_ERROR
;
1380 rb
->splash(HZ
, "Error opening file.");
1381 return PLUGIN_ERROR
;
1384 viewer_reset_settings(); /* load defaults first */
1385 viewer_load_settings(); /* .. then try to load from disk */
1388 rb
->lcd_set_backdrop(NULL
);
1397 if(old_tick
<= *rb
->current_tick
- (110-prefs
.autoscroll_speed
*10))
1399 viewer_scroll_down();
1401 old_tick
= *rb
->current_tick
;
1405 button
= rb
->button_get_w_tmo(HZ
/10);
1411 case VIEWER_AUTOSCROLL
:
1412 #ifdef VIEWER_AUTOSCROLL_PRE
1413 if (lastbutton
!= VIEWER_AUTOSCROLL_PRE
)
1416 autoscroll
= !autoscroll
;
1419 case VIEWER_PAGE_UP
:
1420 case VIEWER_PAGE_UP
| BUTTON_REPEAT
:
1421 if (prefs
.scroll_mode
== PAGE
)
1424 #ifdef HAVE_LCD_BITMAP
1425 for (i
= prefs
.page_mode
==OVERLAP
? 1:0; i
< display_lines
; i
++)
1427 for (i
= 0; i
< display_lines
; i
++)
1433 old_tick
= *rb
->current_tick
;
1437 case VIEWER_PAGE_DOWN
:
1438 case VIEWER_PAGE_DOWN
| BUTTON_REPEAT
:
1439 if (prefs
.scroll_mode
== PAGE
)
1442 if (next_screen_ptr
!= NULL
)
1443 screen_top_ptr
= next_screen_to_draw_ptr
;
1446 viewer_scroll_down();
1447 old_tick
= *rb
->current_tick
;
1451 case VIEWER_SCREEN_LEFT
:
1452 case VIEWER_SCREEN_LEFT
| BUTTON_REPEAT
:
1453 if (prefs
.view_mode
== WIDE
) {
1455 col
-= draw_columns
;
1456 col
= col_limit(col
);
1458 else { /* prefs.view_mode == NARROW */
1466 case VIEWER_SCREEN_RIGHT
:
1467 case VIEWER_SCREEN_RIGHT
| BUTTON_REPEAT
:
1468 if (prefs
.view_mode
== WIDE
) {
1470 col
+= draw_columns
;
1471 col
= col_limit(col
);
1473 else { /* prefs.view_mode == NARROW */
1474 /* Bottom of file */
1481 #ifdef VIEWER_LINE_UP
1482 case VIEWER_LINE_UP
:
1483 case VIEWER_LINE_UP
| BUTTON_REPEAT
:
1484 /* Scroll up one line */
1486 old_tick
= *rb
->current_tick
;
1490 case VIEWER_LINE_DOWN
:
1491 case VIEWER_LINE_DOWN
| BUTTON_REPEAT
:
1492 /* Scroll down one line */
1493 if (next_screen_ptr
!= NULL
)
1494 screen_top_ptr
= next_line_ptr
;
1495 old_tick
= *rb
->current_tick
;
1499 #ifdef VIEWER_COLUMN_LEFT
1500 case VIEWER_COLUMN_LEFT
:
1501 case VIEWER_COLUMN_LEFT
| BUTTON_REPEAT
:
1502 if (prefs
.view_mode
== WIDE
) {
1503 /* Scroll left one column */
1504 col
-= glyph_width('o');
1505 col
= col_limit(col
);
1510 case VIEWER_COLUMN_RIGHT
:
1511 case VIEWER_COLUMN_RIGHT
| BUTTON_REPEAT
:
1512 if (prefs
.view_mode
== WIDE
) {
1513 /* Scroll right one column */
1514 col
+= glyph_width('o');
1515 col
= col_limit(col
);
1521 #ifdef VIEWER_RC_QUIT
1522 case VIEWER_RC_QUIT
:
1530 if (rb
->default_event_handler_ex(button
, viewer_exit
, NULL
)
1531 == SYS_USB_CONNECTED
)
1532 return PLUGIN_USB_CONNECTED
;
1535 if (button
!= BUTTON_NONE
)
1537 lastbutton
= button
;