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
211 /* stuff for the bookmarking */
212 struct bookmarked_file_info
{
215 char filename
[MAX_PATH
];
218 struct bookmark_file_data
{
219 signed int bookmarked_files_count
;
220 struct bookmarked_file_info bookmarks
[];
233 REFLOW
, /* won't be set on charcell LCD, must be last */
256 } encoding
; /* FIXME: What should default encoding be? */
258 #ifdef HAVE_LCD_BITMAP
269 #endif /* HAVE_LCD_BITMAP */
276 int autoscroll_speed
;
280 static unsigned char buffer
[BUFFER_SIZE
+ 1];
281 static unsigned char line_break
[] = {0,0x20,9,0xB,0xC,'-'};
282 static int display_columns
; /* number of (pixel) columns on the display */
283 static int display_lines
; /* number of lines on the display */
284 static int draw_columns
; /* number of (pixel) columns available for text */
285 static int par_indent_spaces
; /* number of spaces to indent first paragraph */
287 static char *file_name
;
288 static long file_size
;
289 static bool mac_text
;
290 static long file_pos
; /* Position of the top of the buffer in the file */
291 static unsigned char *buffer_end
; /*Set to BUFFER_END() when file_pos changes*/
292 static int max_line_len
;
293 static unsigned char *screen_top_ptr
;
294 static unsigned char *next_screen_ptr
;
295 static unsigned char *next_screen_to_draw_ptr
;
296 static unsigned char *next_line_ptr
;
297 static struct plugin_api
* rb
;
298 #ifdef HAVE_LCD_BITMAP
299 static struct font
*pf
;
303 int glyph_width(int ch
)
308 #ifdef HAVE_LCD_BITMAP
309 return rb
->font_get_width(pf
, ch
);
315 unsigned char* get_ucs(const unsigned char* str
, unsigned short* ch
)
317 unsigned char utf8_tmp
[6];
320 if (prefs
.encoding
== UTF8
)
321 return (unsigned char*)rb
->utf8decode(str
, ch
);
323 count
= BUFFER_OOB(str
+2)? 1:2;
324 rb
->iso_decode(str
, utf8_tmp
, prefs
.encoding
, count
);
325 rb
->utf8decode(utf8_tmp
, ch
);
327 if ((prefs
.encoding
== SJIS
&& *str
> 0xA0 && *str
< 0xE0) || prefs
.encoding
< SJIS
)
328 return (unsigned char*)str
+1;
330 return (unsigned char*)str
+2;
336 #define ADVANCE_COUNTERS(c) { width += glyph_width(c); k++; }
337 #define LINE_IS_FULL ((k>=MAX_COLUMNS-1) ||( width >= draw_columns))
338 #define LINE_IS_NOT_FULL ((k<MAX_COLUMNS-1) &&( width < draw_columns))
339 static unsigned char* crop_at_width(const unsigned char* p
)
343 const unsigned char *oldp
= p
;
347 while (LINE_IS_NOT_FULL
) {
350 ADVANCE_COUNTERS(ch
);
353 return (unsigned char*)oldp
;
356 static unsigned char* find_first_feed(const unsigned char* p
, int size
)
360 for (i
=0; i
< size
; i
++)
362 return (unsigned char*) p
+i
;
367 static unsigned char* find_last_feed(const unsigned char* p
, int size
)
371 for (i
=size
-1; i
>=0; i
--)
373 return (unsigned char*) p
+i
;
378 static unsigned char* find_last_space(const unsigned char* p
, int size
)
382 k
= (prefs
.line_mode
==JOIN
) || (prefs
.line_mode
==REFLOW
) ? 0:1;
384 if (!BUFFER_OOB(&p
[size
]))
385 for (j
=k
; j
< ((int) sizeof(line_break
)) - 1; j
++)
386 if (p
[size
] == line_break
[j
])
387 return (unsigned char*) p
+size
;
389 for (i
=size
-1; i
>=0; i
--)
390 for (j
=k
; j
< (int) sizeof(line_break
); j
++)
392 if (!((p
[i
] == '-') && (prefs
.word_mode
== WRAP
)))
393 if (p
[i
] == line_break
[j
])
394 return (unsigned char*) p
+i
;
400 static unsigned char* find_next_line(const unsigned char* cur_line
, bool *is_short
)
402 const unsigned char *next_line
= NULL
;
403 int size
, i
, j
, k
, width
, search_len
, spaces
, newlines
;
407 if (is_short
!= NULL
)
410 if BUFFER_OOB(cur_line
)
413 if (prefs
.view_mode
== WIDE
) {
414 search_len
= MAX_WIDTH
;
416 else { /* prefs.view_mode == NARROW */
417 search_len
= crop_at_width(cur_line
) - cur_line
;
420 size
= BUFFER_OOB(cur_line
+search_len
) ? buffer_end
-cur_line
: search_len
;
422 if ((prefs
.line_mode
== JOIN
) || (prefs
.line_mode
== REFLOW
)) {
423 /* Need to scan ahead and possibly increase search_len and size,
424 or possibly set next_line at second hard return in a row. */
427 for (j
=k
=width
=spaces
=newlines
=0; ; j
++) {
428 if (BUFFER_OOB(cur_line
+j
))
431 size
= search_len
= j
;
438 if (prefs
.line_mode
== REFLOW
) {
441 next_line
= cur_line
+ size
;
442 return (unsigned char*) next_line
;
444 if (j
==0) /* i=1 is intentional */
445 for (i
=0; i
<par_indent_spaces
; i
++)
446 ADVANCE_COUNTERS(' ');
448 if (!first_chars
) spaces
++;
454 next_line
= cur_line
+ size
- spaces
;
455 if (next_line
!= cur_line
)
456 return (unsigned char*) next_line
;
462 if (BUFFER_OOB(cur_line
+size
) || size
> 2*search_len
)
465 spaces
= first_chars
? 0:1;
469 if (prefs
.line_mode
==JOIN
|| newlines
>0) {
472 ADVANCE_COUNTERS(' ');
474 size
= search_len
= j
;
480 /* REFLOW, multiple spaces between words: count only
481 * one. If more are needed, they will be added
485 ADVANCE_COUNTERS(' ');
487 size
= search_len
= j
;
498 /* find first hard return */
499 next_line
= find_first_feed(cur_line
, size
);
502 if (next_line
== NULL
)
503 if (size
== search_len
) {
504 if (prefs
.word_mode
== WRAP
) /* Find last space */
505 next_line
= find_last_space(cur_line
, size
);
507 if (next_line
== NULL
)
508 next_line
= crop_at_width(cur_line
);
510 if (prefs
.word_mode
== WRAP
)
512 i
<WRAP_TRIM
&& isspace(next_line
[0]) && !BUFFER_OOB(next_line
);
517 if (prefs
.line_mode
== EXPAND
)
518 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
519 if (next_line
[0] == 0)
520 if (next_line
!= cur_line
)
521 return (unsigned char*) next_line
;
523 /* If next_line is pointing to a zero, increment it; i.e.,
524 leave the terminator at the end of cur_line. If pointing
525 to a hyphen, increment only if there is room to display
526 the hyphen on current line (won't apply in WIDE mode,
527 since it's guarenteed there won't be room). */
528 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
529 if (next_line
[0] == 0)/* ||
530 (next_line[0] == '-' && next_line-cur_line < draw_columns)) */
533 if (BUFFER_OOB(next_line
))
539 return (unsigned char*) next_line
;
542 static unsigned char* find_prev_line(const unsigned char* cur_line
)
544 const unsigned char *prev_line
= NULL
;
545 const unsigned char *p
;
547 if BUFFER_OOB(cur_line
)
550 /* To wrap consistently at the same places, we must
551 start with a known hard return, then work downwards.
552 We can either search backwards for a hard return,
553 or simply start wrapping downwards from top of buffer.
554 If current line is not near top of buffer, this is
555 a file with long lines (paragraphs). We would need to
556 read earlier sectors before we could decide how to
557 properly wrap the lines above the current line, but
558 it probably is not worth the disk access. Instead,
559 start with top of buffer and wrap down from there.
560 This may result in some lines wrapping at different
561 points from where they wrap when scrolling down.
562 If buffer is at top of file, start at top of buffer. */
564 if ((prefs
.line_mode
== JOIN
) || (prefs
.line_mode
== REFLOW
))
565 prev_line
= p
= NULL
;
567 prev_line
= p
= find_last_feed(buffer
, cur_line
-buffer
-1);
568 /* Null means no line feeds in buffer above current line. */
570 if (prev_line
== NULL
)
571 if (BUFFER_BOF() || cur_line
- buffer
> READ_PREV_ZONE
)
572 prev_line
= p
= buffer
;
573 /* (else return NULL and read previous block) */
575 /* Wrap downwards until too far, then use the one before. */
576 while (p
< cur_line
&& p
!= NULL
) {
578 p
= find_next_line(prev_line
, NULL
);
581 if (BUFFER_OOB(prev_line
))
584 return (unsigned char*) prev_line
;
587 static void fill_buffer(long pos
, unsigned char* buf
, unsigned size
)
589 /* Read from file and preprocess the data */
590 /* To minimize disk access, always read on sector boundaries */
592 bool found_CR
= false;
594 rb
->lseek(fd
, pos
, SEEK_SET
);
595 numread
= rb
->read(fd
, buf
, size
);
596 rb
->button_clear_queue(); /* clear button queue */
598 for(i
= 0; i
< numread
; i
++) {
615 case 0: /* No break between case 0 and default, intentionally */
628 static int read_and_synch(int direction
)
630 /* Read next (or prev) block, and reposition global pointers. */
631 /* direction: 1 for down (i.e., further into file), -1 for up */
632 int move_size
, move_vector
, offset
;
633 unsigned char *fill_buf
;
635 if (direction
== -1) /* up */ {
636 move_size
= SMALL_BLOCK_SIZE
;
638 fill_buf
= TOP_SECTOR
;
639 rb
->memcpy(BOTTOM_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
640 rb
->memcpy(MID_SECTOR
, TOP_SECTOR
, SMALL_BLOCK_SIZE
);
643 if (prefs
.view_mode
== WIDE
) {
644 /* WIDE mode needs more buffer so we have to read smaller blocks */
645 move_size
= SMALL_BLOCK_SIZE
;
646 offset
= LARGE_BLOCK_SIZE
;
647 fill_buf
= BOTTOM_SECTOR
;
648 rb
->memcpy(TOP_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
649 rb
->memcpy(MID_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
652 move_size
= LARGE_BLOCK_SIZE
;
653 offset
= SMALL_BLOCK_SIZE
;
654 fill_buf
= MID_SECTOR
;
655 rb
->memcpy(TOP_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
658 move_vector
= direction
* move_size
;
659 screen_top_ptr
-= move_vector
;
660 file_pos
+= move_vector
;
661 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
662 fill_buffer(file_pos
+ offset
, fill_buf
, move_size
);
666 static void viewer_scroll_up(void)
670 p
= find_prev_line(screen_top_ptr
);
671 if (p
== NULL
&& !BUFFER_BOF()) {
673 p
= find_prev_line(screen_top_ptr
);
679 static void viewer_scroll_down(void)
681 if (next_screen_ptr
!= NULL
)
682 screen_top_ptr
= next_line_ptr
;
685 #ifdef HAVE_LCD_BITMAP
686 static void viewer_scrollbar(void) {
687 int items
, min_shown
, max_shown
;
689 items
= (int) file_size
; /* (SH1 int is same as long) */
690 min_shown
= (int) file_pos
+ (screen_top_ptr
- buffer
);
692 if (next_screen_ptr
== NULL
)
695 max_shown
= min_shown
+ (next_screen_ptr
- screen_top_ptr
);
697 rb
->gui_scrollbar_draw(rb
->screens
[SCREEN_MAIN
],0, 0, SCROLLBAR_WIDTH
-1,
698 LCD_HEIGHT
, items
, min_shown
, max_shown
, VERTICAL
);
702 static void viewer_draw(int col
)
704 int i
, j
, k
, line_len
, line_width
, resynch_move
, spaces
, left_col
=0;
705 int width
, extra_spaces
, indent_spaces
, spaces_per_word
;
706 bool multiple_spacing
, line_is_short
;
708 unsigned char *str
, *oldstr
;
709 unsigned char *line_begin
;
710 unsigned char *line_end
;
712 unsigned char scratch_buffer
[MAX_COLUMNS
+ 1];
713 unsigned char utf8_buffer
[MAX_COLUMNS
*4 + 1];
714 unsigned char *endptr
;
716 /* If col==-1 do all calculations but don't display */
718 #ifdef HAVE_LCD_BITMAP
719 left_col
= prefs
.need_scrollbar
? SCROLLBAR_WIDTH
:0;
723 rb
->lcd_clear_display();
726 line_begin
= line_end
= screen_top_ptr
;
728 for (i
= 0; i
< display_lines
; i
++) {
729 if (BUFFER_OOB(line_end
))
730 break; /* Happens after display last line at BUFFER_EOF() */
732 line_begin
= line_end
;
733 line_end
= find_next_line(line_begin
, &line_is_short
);
735 if (line_end
== NULL
) {
737 if (i
< display_lines
- 1 && !BUFFER_BOF()) {
739 rb
->lcd_clear_display();
741 for (; i
< display_lines
- 1; i
++)
744 line_begin
= line_end
= screen_top_ptr
;
749 line_end
= buffer_end
;
753 resynch_move
= read_and_synch(1); /* Read block & move ptrs */
754 line_begin
-= resynch_move
;
756 next_line_ptr
-= resynch_move
;
758 line_end
= find_next_line(line_begin
, NULL
);
759 if (line_end
== NULL
) /* Should not really happen */
763 line_len
= line_end
- line_begin
;
765 /* calculate line_len */
766 str
= oldstr
= line_begin
;
768 while (str
< line_end
) {
770 str
= crop_at_width(str
);
773 line_width
= j
*draw_columns
;
774 while (oldstr
< line_end
) {
775 oldstr
= get_ucs(oldstr
, &ch
);
776 line_width
+= glyph_width(ch
);
779 if (prefs
.line_mode
== JOIN
) {
780 if (line_begin
[0] == 0) {
782 if (prefs
.word_mode
== CHOP
)
787 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
788 if (k
== MAX_COLUMNS
)
798 scratch_buffer
[k
++] = ' ';
803 scratch_buffer
[k
++] = ' ';
804 if (k
== MAX_COLUMNS
- 1)
807 scratch_buffer
[k
++] = c
;
812 scratch_buffer
[k
] = 0;
813 endptr
= rb
->iso_decode(scratch_buffer
+ col
, utf8_buffer
,
814 prefs
.encoding
, draw_columns
/glyph_width('i'));
818 else if (prefs
.line_mode
== REFLOW
) {
819 if (line_begin
[0] == 0) {
821 if (prefs
.word_mode
== CHOP
)
828 if (!line_is_short
) {
829 multiple_spacing
= false;
831 for (str
= line_begin
; str
< line_end
; ) {
832 str
= get_ucs(str
, &ch
);
836 if ((str
== line_begin
) && (prefs
.word_mode
==WRAP
))
837 /* special case: indent the paragraph,
838 * don't count spaces */
839 indent_spaces
= par_indent_spaces
;
840 else if (!multiple_spacing
)
842 multiple_spacing
= true;
845 multiple_spacing
= false;
846 width
+= glyph_width(ch
);
850 if (multiple_spacing
) spaces
--;
853 /* total number of spaces to insert between words */
854 extra_spaces
= (draw_columns
-width
)/glyph_width(' ')
856 /* number of spaces between each word*/
857 spaces_per_word
= extra_spaces
/ spaces
;
858 /* number of words with n+1 spaces (to fill up) */
859 extra_spaces
= extra_spaces
% spaces
;
860 if (spaces_per_word
> 2) { /* too much spacing is awful */
864 } else { /* this doesn't matter much... no spaces anyway */
865 spaces_per_word
= extra_spaces
= 0;
867 } else { /* end of a paragraph: don't fill line */
872 multiple_spacing
= false;
873 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
874 if (k
== MAX_COLUMNS
)
881 if (j
==0 && prefs
.word_mode
==WRAP
) { /* indent paragraph */
882 for (j
=0; j
<par_indent_spaces
; j
++)
883 scratch_buffer
[k
++] = ' ';
886 else if (!multiple_spacing
) {
887 for (width
= spaces
<extra_spaces
? -1:0; width
< spaces_per_word
; width
++)
888 scratch_buffer
[k
++] = ' ';
891 multiple_spacing
= true;
894 scratch_buffer
[k
++] = c
;
895 multiple_spacing
= false;
901 scratch_buffer
[k
] = 0;
902 endptr
= rb
->iso_decode(scratch_buffer
+ col
, utf8_buffer
,
903 prefs
.encoding
, k
-col
);
907 else { /* prefs.line_mode != JOIN && prefs.line_mode != REFLOW */
909 if (line_width
> col
) {
910 str
= oldstr
= line_begin
;
913 while( (width
<draw_columns
) && (oldstr
<line_end
) )
915 oldstr
= get_ucs(oldstr
, &ch
);
917 k
-= glyph_width(ch
);
920 width
+= glyph_width(ch
);
924 if(prefs
.view_mode
==WIDE
)
925 endptr
= rb
->iso_decode(line_begin
, utf8_buffer
,
926 prefs
.encoding
, oldstr
-line_begin
);
928 endptr
= rb
->iso_decode(line_begin
, utf8_buffer
,
929 prefs
.encoding
, line_end
-line_begin
);
933 if (col
!= -1 && line_width
> col
)
934 #ifdef HAVE_LCD_BITMAP
935 rb
->lcd_putsxy(left_col
, i
*pf
->height
, utf8_buffer
);
937 rb
->lcd_puts(left_col
, i
, utf8_buffer
);
939 if (line_width
> max_line_len
)
940 max_line_len
= line_width
;
943 next_line_ptr
= line_end
;
945 next_screen_ptr
= line_end
;
946 if (BUFFER_OOB(next_screen_ptr
))
947 next_screen_ptr
= NULL
;
949 #ifdef HAVE_LCD_BITMAP
950 next_screen_to_draw_ptr
= prefs
.page_mode
==OVERLAP
? line_begin
: next_screen_ptr
;
952 if (prefs
.need_scrollbar
)
955 next_screen_to_draw_ptr
= next_screen_ptr
;
962 static void viewer_top(void)
964 /* Read top of file into buffer
965 and point screen pointer to top */
967 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
968 screen_top_ptr
= buffer
;
969 fill_buffer(0, buffer
, BUFFER_SIZE
);
972 static void viewer_bottom(void)
974 /* Read bottom of file into buffer
975 and point screen pointer to bottom */
978 if (file_size
> BUFFER_SIZE
) {
979 /* Find last buffer in file, round up to next sector boundary */
980 last_sectors
= file_size
- BUFFER_SIZE
+ SMALL_BLOCK_SIZE
;
981 last_sectors
/= SMALL_BLOCK_SIZE
;
982 last_sectors
*= SMALL_BLOCK_SIZE
;
987 file_pos
= last_sectors
;
988 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
989 screen_top_ptr
= buffer_end
-1;
990 fill_buffer(last_sectors
, buffer
, BUFFER_SIZE
);
993 #ifdef HAVE_LCD_BITMAP
994 static void init_need_scrollbar(void) {
995 /* Call viewer_draw in quiet mode to initialize next_screen_ptr,
996 and thus ONE_SCREEN_FITS_ALL(), and thus NEED_SCROLLBAR() */
998 prefs
.need_scrollbar
= NEED_SCROLLBAR();
999 draw_columns
= prefs
.need_scrollbar
? display_columns
-SCROLLBAR_WIDTH
: display_columns
;
1000 par_indent_spaces
= draw_columns
/(5*glyph_width(' '));
1003 #define init_need_scrollbar()
1006 static bool viewer_init(void)
1008 #ifdef HAVE_LCD_BITMAP
1010 pf
= rb
->font_get(FONT_UI
);
1012 display_lines
= LCD_HEIGHT
/ pf
->height
;
1013 draw_columns
= display_columns
= LCD_WIDTH
;
1015 /* REAL fixed pitch :) all chars use up 1 cell */
1017 draw_columns
= display_columns
= 11;
1018 par_indent_spaces
= 2;
1021 fd
= rb
->open(file_name
, O_RDONLY
);
1025 file_size
= rb
->filesize(fd
);
1029 /* Init mac_text value used in processing buffer */
1032 /* Set codepage to system default */
1033 prefs
.encoding
= rb
->global_settings
->default_codepage
;
1035 /* Read top of file into buffer;
1036 init file_pos, buffer_end, screen_top_ptr */
1039 /* Init prefs.need_scrollbar value */
1040 init_need_scrollbar();
1045 static void viewer_reset_settings(void)
1047 prefs
.word_mode
= WRAP
;
1048 prefs
.line_mode
= NORMAL
;
1049 prefs
.view_mode
= NARROW
;
1050 prefs
.scroll_mode
= PAGE
;
1051 #ifdef HAVE_LCD_BITMAP
1052 prefs
.page_mode
= NO_OVERLAP
;
1053 prefs
.scrollbar_mode
= SB_OFF
;
1055 prefs
.autoscroll_speed
= 1;
1058 static void viewer_load_settings(void) /* same name as global, but not the same file.. */
1061 struct bookmark_file_data
*data
;
1063 /* read settings file */
1064 settings_fd
=rb
->open(SETTINGS_FILE
, O_RDONLY
);
1065 if ((settings_fd
< 0) || (rb
->filesize(settings_fd
) != sizeof(struct preferences
)))
1067 rb
->splash(HZ
*2, "No Valid Settings File");
1071 rb
->read(settings_fd
, &prefs
, sizeof(struct preferences
));
1072 rb
->close(settings_fd
);
1075 data
= (struct bookmark_file_data
*)buffer
; /* grab the text buffer */
1076 data
->bookmarked_files_count
= 0;
1078 /* read bookmarks if file exists */
1079 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_RDONLY
);
1080 if (settings_fd
>= 0)
1082 /* figure out how many items to read */
1083 rb
->read(settings_fd
, &data
->bookmarked_files_count
, sizeof(signed int));
1084 if (data
->bookmarked_files_count
> MAX_BOOKMARKED_FILES
)
1085 data
->bookmarked_files_count
= MAX_BOOKMARKED_FILES
;
1086 rb
->read(settings_fd
, data
->bookmarks
,
1087 sizeof(struct bookmarked_file_info
) * data
->bookmarked_files_count
);
1088 rb
->close(settings_fd
);
1091 /* check if current file is in list */
1092 for (i
=0; i
< data
->bookmarked_files_count
; i
++)
1094 if (!rb
->strcmp(file_name
, data
->bookmarks
[i
].filename
))
1096 file_pos
= data
->bookmarks
[i
].file_position
;
1097 screen_top_ptr
= buffer
+ data
->bookmarks
[i
].top_ptr_pos
;
1102 /* prevent potential slot overflow */
1103 if (i
>= data
->bookmarked_files_count
)
1105 if (i
< MAX_BOOKMARKED_FILES
)
1106 data
->bookmarked_files_count
++;
1108 i
= MAX_BOOKMARKED_FILES
-1;
1111 /* write bookmark file with spare slot in first position
1112 to be filled in by viewer_save_settings */
1113 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_WRONLY
|O_CREAT
);
1114 if (settings_fd
>=0 )
1116 /* write count and skip first slot */
1117 rb
->write (settings_fd
, &data
->bookmarked_files_count
, sizeof(signed int));
1118 rb
->PREFIX(lseek
)(settings_fd
,sizeof(struct bookmarked_file_info
),SEEK_CUR
);
1120 /* shuffle up bookmarks */
1121 rb
->write (settings_fd
, data
->bookmarks
, sizeof(struct bookmarked_file_info
)*i
);
1122 rb
->close(settings_fd
);
1125 init_need_scrollbar();
1127 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
1129 if (BUFFER_OOB(screen_top_ptr
))
1131 screen_top_ptr
= buffer
;
1134 fill_buffer(file_pos
, buffer
, BUFFER_SIZE
);
1137 static void viewer_save_settings(void)/* same name as global, but not the same file.. */
1141 rb
->splash(1, "Saving Settings");
1143 settings_fd
= rb
->creat(SETTINGS_FILE
); /* create the settings file */
1145 rb
->write (settings_fd
, &prefs
, sizeof(struct preferences
));
1146 rb
->close(settings_fd
);
1148 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_WRONLY
|O_CREAT
);
1149 if (settings_fd
>= 0 )
1151 struct bookmarked_file_info b
;
1152 b
.file_position
= file_pos
;
1153 b
.top_ptr_pos
= screen_top_ptr
- buffer
;
1154 rb
->memset(&b
.filename
[0],0,MAX_PATH
);
1155 rb
->strcpy(b
.filename
,file_name
);
1156 rb
->PREFIX(lseek
)(settings_fd
,sizeof(signed int),SEEK_SET
);
1157 rb
->write (settings_fd
, &b
, sizeof(struct bookmarked_file_info
));
1158 rb
->close(settings_fd
);
1162 static void viewer_exit(void *parameter
)
1166 viewer_save_settings();
1170 static int col_limit(int col
)
1175 if (col
> max_line_len
- 2*glyph_width('o'))
1176 col
= max_line_len
- 2*glyph_width('o');
1181 /* settings helper functions */
1183 static bool encoding_setting(void)
1185 static const struct opt_items names
[] = {
1190 {"ISO-8859-11", -1},
1201 return rb
->set_option("Encoding", &prefs
.encoding
, INT
, names
,
1202 sizeof(names
) / sizeof(names
[0]), NULL
);
1205 static bool word_wrap_setting(void)
1207 static const struct opt_items names
[] = {
1209 {"Off (Chop Words)", -1},
1212 return rb
->set_option("Word Wrap", &prefs
.word_mode
, INT
,
1216 static bool line_mode_setting(void)
1218 static const struct opt_items names
[] = {
1221 {"Expand Lines", -1},
1222 #ifdef HAVE_LCD_BITMAP
1223 {"Reflow Lines", -1},
1227 return rb
->set_option("Line Mode", &prefs
.line_mode
, INT
, names
,
1228 sizeof(names
) / sizeof(names
[0]), NULL
);
1231 static bool view_mode_setting(void)
1233 static const struct opt_items names
[] = {
1234 {"No (Narrow)", -1},
1238 ret
= rb
->set_option("Wide View", &prefs
.view_mode
, INT
,
1240 if (prefs
.view_mode
== NARROW
)
1245 static bool scroll_mode_setting(void)
1247 static const struct opt_items names
[] = {
1248 {"Scroll by Page", -1},
1249 {"Scroll by Line", -1},
1252 return rb
->set_option("Scroll Mode", &prefs
.scroll_mode
, INT
,
1256 #ifdef HAVE_LCD_BITMAP
1257 static bool page_mode_setting(void)
1259 static const struct opt_items names
[] = {
1264 return rb
->set_option("Overlap Pages", &prefs
.page_mode
, INT
,
1268 static bool scrollbar_setting(void)
1270 static const struct opt_items names
[] = {
1275 return rb
->set_option("Show Scrollbar", &prefs
.scrollbar_mode
, INT
,
1280 static bool autoscroll_speed_setting(void)
1282 return rb
->set_int("Auto-scroll Speed", "", UNIT_INT
,
1283 &prefs
.autoscroll_speed
, NULL
, 1, 1, 10, NULL
);
1286 static bool viewer_options_menu(void)
1291 static const struct menu_item items
[] = {
1292 {"Encoding", encoding_setting
},
1293 {"Word Wrap", word_wrap_setting
},
1294 {"Line Mode", line_mode_setting
},
1295 {"Wide View", view_mode_setting
},
1296 #ifdef HAVE_LCD_BITMAP
1297 {"Show Scrollbar", scrollbar_setting
},
1298 {"Overlap Pages", page_mode_setting
},
1300 {"Scroll Mode", scroll_mode_setting
},
1301 {"Auto-Scroll Speed", autoscroll_speed_setting
},
1303 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
),
1304 NULL
, NULL
, NULL
, NULL
);
1306 result
= menu_run(m
);
1308 #ifdef HAVE_LCD_BITMAP
1309 rb
->lcd_setmargins(0,0);
1311 /* Show-scrollbar mode for current view-width mode */
1312 if (!ONE_SCREEN_FITS_ALL())
1313 if (prefs
.scrollbar_mode
== true)
1314 init_need_scrollbar();
1319 static void viewer_menu(void)
1323 static const struct menu_item items
[] = {
1325 {"Viewer Options", NULL
},
1326 {"Show Playback Menu", NULL
},
1330 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
), NULL
, NULL
, NULL
, NULL
);
1331 result
=menu_show(m
);
1339 case 1: /* change settings */
1340 done
= viewer_options_menu();
1342 case 2: /* playback control */
1343 playback_control(rb
);
1345 case 3: /* return */
1349 #ifdef HAVE_LCD_BITMAP
1350 rb
->lcd_setmargins(0,0);
1355 enum plugin_status
plugin_start(struct plugin_api
* api
, void* file
)
1358 int lastbutton
= BUTTON_NONE
;
1359 bool autoscroll
= false;
1363 old_tick
= *rb
->current_tick
;
1366 return PLUGIN_ERROR
;
1371 rb
->splash(HZ
, "Error opening file.");
1372 return PLUGIN_ERROR
;
1375 viewer_reset_settings(); /* load defaults first */
1376 viewer_load_settings(); /* .. then try to load from disk */
1379 rb
->lcd_set_backdrop(NULL
);
1388 if(old_tick
<= *rb
->current_tick
- (110-prefs
.autoscroll_speed
*10))
1390 viewer_scroll_down();
1392 old_tick
= *rb
->current_tick
;
1396 button
= rb
->button_get_w_tmo(HZ
/10);
1402 case VIEWER_AUTOSCROLL
:
1403 #ifdef VIEWER_AUTOSCROLL_PRE
1404 if (lastbutton
!= VIEWER_AUTOSCROLL_PRE
)
1407 autoscroll
= !autoscroll
;
1410 case VIEWER_PAGE_UP
:
1411 case VIEWER_PAGE_UP
| BUTTON_REPEAT
:
1412 if (prefs
.scroll_mode
== PAGE
)
1415 #ifdef HAVE_LCD_BITMAP
1416 for (i
= prefs
.page_mode
==OVERLAP
? 1:0; i
< display_lines
; i
++)
1418 for (i
= 0; i
< display_lines
; i
++)
1424 old_tick
= *rb
->current_tick
;
1428 case VIEWER_PAGE_DOWN
:
1429 case VIEWER_PAGE_DOWN
| BUTTON_REPEAT
:
1430 if (prefs
.scroll_mode
== PAGE
)
1433 if (next_screen_ptr
!= NULL
)
1434 screen_top_ptr
= next_screen_to_draw_ptr
;
1437 viewer_scroll_down();
1438 old_tick
= *rb
->current_tick
;
1442 case VIEWER_SCREEN_LEFT
:
1443 case VIEWER_SCREEN_LEFT
| BUTTON_REPEAT
:
1444 if (prefs
.view_mode
== WIDE
) {
1446 col
-= draw_columns
;
1447 col
= col_limit(col
);
1449 else { /* prefs.view_mode == NARROW */
1457 case VIEWER_SCREEN_RIGHT
:
1458 case VIEWER_SCREEN_RIGHT
| BUTTON_REPEAT
:
1459 if (prefs
.view_mode
== WIDE
) {
1461 col
+= draw_columns
;
1462 col
= col_limit(col
);
1464 else { /* prefs.view_mode == NARROW */
1465 /* Bottom of file */
1472 #ifdef VIEWER_LINE_UP
1473 case VIEWER_LINE_UP
:
1474 case VIEWER_LINE_UP
| BUTTON_REPEAT
:
1475 /* Scroll up one line */
1477 old_tick
= *rb
->current_tick
;
1481 case VIEWER_LINE_DOWN
:
1482 case VIEWER_LINE_DOWN
| BUTTON_REPEAT
:
1483 /* Scroll down one line */
1484 if (next_screen_ptr
!= NULL
)
1485 screen_top_ptr
= next_line_ptr
;
1486 old_tick
= *rb
->current_tick
;
1490 #ifdef VIEWER_COLUMN_LEFT
1491 case VIEWER_COLUMN_LEFT
:
1492 case VIEWER_COLUMN_LEFT
| BUTTON_REPEAT
:
1493 if (prefs
.view_mode
== WIDE
) {
1494 /* Scroll left one column */
1495 col
-= glyph_width('o');
1496 col
= col_limit(col
);
1501 case VIEWER_COLUMN_RIGHT
:
1502 case VIEWER_COLUMN_RIGHT
| BUTTON_REPEAT
:
1503 if (prefs
.view_mode
== WIDE
) {
1504 /* Scroll right one column */
1505 col
+= glyph_width('o');
1506 col
= col_limit(col
);
1512 #ifdef VIEWER_RC_QUIT
1513 case VIEWER_RC_QUIT
:
1521 if (rb
->default_event_handler_ex(button
, viewer_exit
, NULL
)
1522 == SYS_USB_CONNECTED
)
1523 return PLUGIN_USB_CONNECTED
;
1526 if (button
!= BUTTON_NONE
)
1528 lastbutton
= button
;