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 TOP_SECTOR buffer
37 #define MID_SECTOR (buffer + SMALL_BLOCK_SIZE)
38 #define BOTTOM_SECTOR (buffer + 2*(SMALL_BLOCK_SIZE))
39 #define SCROLLBAR_WIDTH 6
41 #define MAX_BOOKMARKED_FILES ((buffer_size/(signed)sizeof(struct bookmarked_file_info))-1)
43 /* Out-Of-Bounds test for any pointer to data in the buffer */
44 #define BUFFER_OOB(p) ((p) < buffer || (p) >= buffer_end)
46 /* Does the buffer contain the beginning of the file? */
47 #define BUFFER_BOF() (file_pos==0)
49 /* Does the buffer contain the end of the file? */
50 #define BUFFER_EOF() (file_size-file_pos <= buffer_size)
52 /* Formula for the endpoint address outside of buffer data */
53 #define BUFFER_END() \
54 ((BUFFER_EOF()) ? (file_size-file_pos+buffer) : (buffer+buffer_size))
56 /* Is the entire file being shown in one screen? */
57 #define ONE_SCREEN_FITS_ALL() \
58 (next_screen_ptr==NULL && screen_top_ptr==buffer && BUFFER_BOF())
60 /* Is a scrollbar called for on the current screen? */
61 #define NEED_SCROLLBAR() \
62 ((!(ONE_SCREEN_FITS_ALL())) && (prefs.scrollbar_mode==SB_ON))
64 /* variable button definitions */
67 #if CONFIG_KEYPAD == RECORDER_PAD
68 #define VIEWER_QUIT BUTTON_OFF
69 #define VIEWER_PAGE_UP BUTTON_UP
70 #define VIEWER_PAGE_DOWN BUTTON_DOWN
71 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
72 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
73 #define VIEWER_MENU BUTTON_F1
74 #define VIEWER_AUTOSCROLL BUTTON_PLAY
75 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
76 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
77 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
78 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
80 #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD
81 #define VIEWER_QUIT BUTTON_OFF
82 #define VIEWER_PAGE_UP BUTTON_UP
83 #define VIEWER_PAGE_DOWN BUTTON_DOWN
84 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
85 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
86 #define VIEWER_MENU BUTTON_F1
87 #define VIEWER_AUTOSCROLL BUTTON_SELECT
88 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
89 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
90 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
91 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
94 #elif CONFIG_KEYPAD == ONDIO_PAD
95 #define VIEWER_QUIT BUTTON_OFF
96 #define VIEWER_PAGE_UP BUTTON_UP
97 #define VIEWER_PAGE_DOWN BUTTON_DOWN
98 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
99 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
100 #define VIEWER_MENU (BUTTON_MENU|BUTTON_REPEAT)
101 #define VIEWER_AUTOSCROLL_PRE BUTTON_MENU
102 #define VIEWER_AUTOSCROLL (BUTTON_MENU|BUTTON_REL)
105 #elif CONFIG_KEYPAD == PLAYER_PAD
106 #define VIEWER_QUIT BUTTON_STOP
107 #define VIEWER_PAGE_UP BUTTON_LEFT
108 #define VIEWER_PAGE_DOWN BUTTON_RIGHT
109 #define VIEWER_SCREEN_LEFT (BUTTON_ON|BUTTON_LEFT)
110 #define VIEWER_SCREEN_RIGHT (BUTTON_ON|BUTTON_RIGHT)
111 #define VIEWER_MENU BUTTON_MENU
112 #define VIEWER_AUTOSCROLL BUTTON_PLAY
114 /* iRiver H1x0 && H3x0 keys */
115 #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \
116 (CONFIG_KEYPAD == IRIVER_H300_PAD)
117 #define VIEWER_QUIT BUTTON_OFF
118 #define VIEWER_PAGE_UP BUTTON_UP
119 #define VIEWER_PAGE_DOWN BUTTON_DOWN
120 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
121 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
122 #define VIEWER_MENU BUTTON_MODE
123 #define VIEWER_AUTOSCROLL BUTTON_SELECT
124 #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP)
125 #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN)
126 #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT)
127 #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT)
129 #define VIEWER_RC_QUIT BUTTON_RC_STOP
132 #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \
133 (CONFIG_KEYPAD == IPOD_3G_PAD) || \
134 (CONFIG_KEYPAD == IPOD_1G2G_PAD)
135 #define VIEWER_QUIT_PRE BUTTON_SELECT
136 #define VIEWER_QUIT (BUTTON_SELECT | BUTTON_MENU)
137 #define VIEWER_PAGE_UP BUTTON_SCROLL_BACK
138 #define VIEWER_PAGE_DOWN BUTTON_SCROLL_FWD
139 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
140 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
141 #define VIEWER_MENU BUTTON_MENU
142 #define VIEWER_AUTOSCROLL BUTTON_PLAY
145 #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD
146 #define VIEWER_QUIT BUTTON_PLAY
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_MODE
152 #define VIEWER_AUTOSCROLL BUTTON_SELECT
155 #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD
156 #define VIEWER_QUIT BUTTON_POWER
157 #define VIEWER_PAGE_UP BUTTON_UP
158 #define VIEWER_PAGE_DOWN BUTTON_DOWN
159 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
160 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
161 #define VIEWER_MENU BUTTON_SELECT
162 #define VIEWER_AUTOSCROLL BUTTON_PLAY
165 #elif CONFIG_KEYPAD == GIGABEAT_PAD
166 #define VIEWER_QUIT BUTTON_POWER
167 #define VIEWER_PAGE_UP BUTTON_UP
168 #define VIEWER_PAGE_DOWN BUTTON_DOWN
169 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
170 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
171 #define VIEWER_MENU BUTTON_MENU
172 #define VIEWER_AUTOSCROLL BUTTON_A
174 /* Sansa E200 keys */
175 #elif CONFIG_KEYPAD == SANSA_E200_PAD
176 #define VIEWER_QUIT BUTTON_POWER
177 #define VIEWER_PAGE_UP BUTTON_UP
178 #define VIEWER_PAGE_DOWN BUTTON_DOWN
179 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
180 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
181 #define VIEWER_MENU BUTTON_SELECT
182 #define VIEWER_AUTOSCROLL BUTTON_REC
183 #define VIEWER_LINE_UP BUTTON_SCROLL_FWD
184 #define VIEWER_LINE_DOWN BUTTON_SCROLL_BACK
186 /* Sansa C200 keys */
187 #elif CONFIG_KEYPAD == SANSA_C200_PAD
188 #define VIEWER_QUIT BUTTON_POWER
189 #define VIEWER_PAGE_UP BUTTON_VOL_UP
190 #define VIEWER_PAGE_DOWN BUTTON_VOL_DOWN
191 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
192 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
193 #define VIEWER_MENU BUTTON_SELECT
194 #define VIEWER_AUTOSCROLL BUTTON_REC
195 #define VIEWER_LINE_UP BUTTON_UP
196 #define VIEWER_LINE_DOWN BUTTON_DOWN
198 /* iriver H10 keys */
199 #elif CONFIG_KEYPAD == IRIVER_H10_PAD
200 #define VIEWER_QUIT BUTTON_POWER
201 #define VIEWER_PAGE_UP BUTTON_SCROLL_UP
202 #define VIEWER_PAGE_DOWN BUTTON_SCROLL_DOWN
203 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
204 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
205 #define VIEWER_MENU BUTTON_REW
206 #define VIEWER_AUTOSCROLL BUTTON_PLAY
208 #elif CONFIG_KEYPAD == MROBE500_PAD
209 #define VIEWER_QUIT BUTTON_POWER
210 #define VIEWER_PAGE_UP BUTTON_RC_PLAY
211 #define VIEWER_PAGE_DOWN BUTTON_RC_DOWN
212 #define VIEWER_SCREEN_LEFT BUTTON_LEFT
213 #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT
214 #define VIEWER_MENU BUTTON_RC_HEART
215 #define VIEWER_AUTOSCROLL BUTTON_RC_MODE
219 /* stuff for the bookmarking */
220 struct bookmarked_file_info
{
223 char filename
[MAX_PATH
];
226 struct bookmark_file_data
{
227 signed int bookmarked_files_count
;
228 struct bookmarked_file_info bookmarks
[];
241 REFLOW
, /* won't be set on charcell LCD, must be last */
265 } encoding
; /* FIXME: What should default encoding be? */
267 #ifdef HAVE_LCD_BITMAP
278 #endif /* HAVE_LCD_BITMAP */
285 int autoscroll_speed
;
289 struct preferences prefs
;
290 struct preferences old_prefs
;
292 static unsigned char *buffer
;
293 static long buffer_size
;
294 static unsigned char line_break
[] = {0,0x20,9,0xB,0xC,'-'};
295 static int display_columns
; /* number of (pixel) columns on the display */
296 static int display_lines
; /* number of lines on the display */
297 static int draw_columns
; /* number of (pixel) columns available for text */
298 static int par_indent_spaces
; /* number of spaces to indent first paragraph */
300 static char *file_name
;
301 static long file_size
;
302 static long start_position
; /* position in the file after the viewer is started */
303 static bool mac_text
;
304 static long file_pos
; /* Position of the top of the buffer in the file */
305 static unsigned char *buffer_end
; /*Set to BUFFER_END() when file_pos changes*/
306 static int max_line_len
;
307 static unsigned char *screen_top_ptr
;
308 static unsigned char *next_screen_ptr
;
309 static unsigned char *next_screen_to_draw_ptr
;
310 static unsigned char *next_line_ptr
;
311 static struct plugin_api
* rb
;
312 #ifdef HAVE_LCD_BITMAP
313 static struct font
*pf
;
317 int glyph_width(int ch
)
322 #ifdef HAVE_LCD_BITMAP
323 return rb
->font_get_width(pf
, ch
);
329 unsigned char* get_ucs(const unsigned char* str
, unsigned short* ch
)
331 unsigned char utf8_tmp
[6];
334 if (prefs
.encoding
== UTF8
)
335 return (unsigned char*)rb
->utf8decode(str
, ch
);
337 count
= BUFFER_OOB(str
+2)? 1:2;
338 rb
->iso_decode(str
, utf8_tmp
, prefs
.encoding
, count
);
339 rb
->utf8decode(utf8_tmp
, ch
);
341 if ((prefs
.encoding
== SJIS
&& *str
> 0xA0 && *str
< 0xE0) || prefs
.encoding
< SJIS
)
342 return (unsigned char*)str
+1;
344 return (unsigned char*)str
+2;
350 #define ADVANCE_COUNTERS(c) { width += glyph_width(c); k++; }
351 #define LINE_IS_FULL ((k>=MAX_COLUMNS-1) ||( width >= draw_columns))
352 #define LINE_IS_NOT_FULL ((k<MAX_COLUMNS-1) &&( width < draw_columns))
353 static unsigned char* crop_at_width(const unsigned char* p
)
357 const unsigned char *oldp
= p
;
361 while (LINE_IS_NOT_FULL
) {
364 ADVANCE_COUNTERS(ch
);
367 return (unsigned char*)oldp
;
370 static unsigned char* find_first_feed(const unsigned char* p
, int size
)
374 for (i
=0; i
< size
; i
++)
376 return (unsigned char*) p
+i
;
381 static unsigned char* find_last_feed(const unsigned char* p
, int size
)
385 for (i
=size
-1; i
>=0; i
--)
387 return (unsigned char*) p
+i
;
392 static unsigned char* find_last_space(const unsigned char* p
, int size
)
396 k
= (prefs
.line_mode
==JOIN
) || (prefs
.line_mode
==REFLOW
) ? 0:1;
398 if (!BUFFER_OOB(&p
[size
]))
399 for (j
=k
; j
< ((int) sizeof(line_break
)) - 1; j
++)
400 if (p
[size
] == line_break
[j
])
401 return (unsigned char*) p
+size
;
403 for (i
=size
-1; i
>=0; i
--)
404 for (j
=k
; j
< (int) sizeof(line_break
); j
++)
406 if (!((p
[i
] == '-') && (prefs
.word_mode
== WRAP
)))
407 if (p
[i
] == line_break
[j
])
408 return (unsigned char*) p
+i
;
414 static unsigned char* find_next_line(const unsigned char* cur_line
, bool *is_short
)
416 const unsigned char *next_line
= NULL
;
417 int size
, i
, j
, k
, width
, search_len
, spaces
, newlines
;
421 if (is_short
!= NULL
)
424 if BUFFER_OOB(cur_line
)
427 if (prefs
.view_mode
== WIDE
) {
428 search_len
= MAX_WIDTH
;
430 else { /* prefs.view_mode == NARROW */
431 search_len
= crop_at_width(cur_line
) - cur_line
;
434 size
= BUFFER_OOB(cur_line
+search_len
) ? buffer_end
-cur_line
: search_len
;
436 if ((prefs
.line_mode
== JOIN
) || (prefs
.line_mode
== REFLOW
)) {
437 /* Need to scan ahead and possibly increase search_len and size,
438 or possibly set next_line at second hard return in a row. */
441 for (j
=k
=width
=spaces
=newlines
=0; ; j
++) {
442 if (BUFFER_OOB(cur_line
+j
))
445 size
= search_len
= j
;
452 if (prefs
.line_mode
== REFLOW
) {
455 next_line
= cur_line
+ size
;
456 return (unsigned char*) next_line
;
458 if (j
==0) /* i=1 is intentional */
459 for (i
=0; i
<par_indent_spaces
; i
++)
460 ADVANCE_COUNTERS(' ');
462 if (!first_chars
) spaces
++;
468 next_line
= cur_line
+ size
- spaces
;
469 if (next_line
!= cur_line
)
470 return (unsigned char*) next_line
;
476 if (BUFFER_OOB(cur_line
+size
) || size
> 2*search_len
)
479 spaces
= first_chars
? 0:1;
483 if (prefs
.line_mode
==JOIN
|| newlines
>0) {
486 ADVANCE_COUNTERS(' ');
488 size
= search_len
= j
;
494 /* REFLOW, multiple spaces between words: count only
495 * one. If more are needed, they will be added
499 ADVANCE_COUNTERS(' ');
501 size
= search_len
= j
;
512 /* find first hard return */
513 next_line
= find_first_feed(cur_line
, size
);
516 if (next_line
== NULL
)
517 if (size
== search_len
) {
518 if (prefs
.word_mode
== WRAP
) /* Find last space */
519 next_line
= find_last_space(cur_line
, size
);
521 if (next_line
== NULL
)
522 next_line
= crop_at_width(cur_line
);
524 if (prefs
.word_mode
== WRAP
)
526 i
<WRAP_TRIM
&& isspace(next_line
[0]) && !BUFFER_OOB(next_line
);
531 if (prefs
.line_mode
== EXPAND
)
532 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
533 if (next_line
[0] == 0)
534 if (next_line
!= cur_line
)
535 return (unsigned char*) next_line
;
537 /* If next_line is pointing to a zero, increment it; i.e.,
538 leave the terminator at the end of cur_line. If pointing
539 to a hyphen, increment only if there is room to display
540 the hyphen on current line (won't apply in WIDE mode,
541 since it's guarenteed there won't be room). */
542 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
543 if (next_line
[0] == 0)/* ||
544 (next_line[0] == '-' && next_line-cur_line < draw_columns)) */
547 if (BUFFER_OOB(next_line
))
553 return (unsigned char*) next_line
;
556 static unsigned char* find_prev_line(const unsigned char* cur_line
)
558 const unsigned char *prev_line
= NULL
;
559 const unsigned char *p
;
561 if BUFFER_OOB(cur_line
)
564 /* To wrap consistently at the same places, we must
565 start with a known hard return, then work downwards.
566 We can either search backwards for a hard return,
567 or simply start wrapping downwards from top of buffer.
568 If current line is not near top of buffer, this is
569 a file with long lines (paragraphs). We would need to
570 read earlier sectors before we could decide how to
571 properly wrap the lines above the current line, but
572 it probably is not worth the disk access. Instead,
573 start with top of buffer and wrap down from there.
574 This may result in some lines wrapping at different
575 points from where they wrap when scrolling down.
576 If buffer is at top of file, start at top of buffer. */
578 if ((prefs
.line_mode
== JOIN
) || (prefs
.line_mode
== REFLOW
))
579 prev_line
= p
= NULL
;
581 prev_line
= p
= find_last_feed(buffer
, cur_line
-buffer
-1);
582 /* Null means no line feeds in buffer above current line. */
584 if (prev_line
== NULL
)
585 if (BUFFER_BOF() || cur_line
- buffer
> READ_PREV_ZONE
)
586 prev_line
= p
= buffer
;
587 /* (else return NULL and read previous block) */
589 /* Wrap downwards until too far, then use the one before. */
590 while (p
< cur_line
&& p
!= NULL
) {
592 p
= find_next_line(prev_line
, NULL
);
595 if (BUFFER_OOB(prev_line
))
598 return (unsigned char*) prev_line
;
601 static void fill_buffer(long pos
, unsigned char* buf
, unsigned size
)
603 /* Read from file and preprocess the data */
604 /* To minimize disk access, always read on sector boundaries */
606 bool found_CR
= false;
608 rb
->lseek(fd
, pos
, SEEK_SET
);
609 numread
= rb
->read(fd
, buf
, size
);
610 rb
->button_clear_queue(); /* clear button queue */
612 for(i
= 0; i
< numread
; i
++) {
629 case 0: /* No break between case 0 and default, intentionally */
642 static int read_and_synch(int direction
)
644 /* Read next (or prev) block, and reposition global pointers. */
645 /* direction: 1 for down (i.e., further into file), -1 for up */
646 int move_size
, move_vector
, offset
;
647 unsigned char *fill_buf
;
649 if (direction
== -1) /* up */ {
650 move_size
= SMALL_BLOCK_SIZE
;
652 fill_buf
= TOP_SECTOR
;
653 rb
->memcpy(BOTTOM_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
654 rb
->memcpy(MID_SECTOR
, TOP_SECTOR
, SMALL_BLOCK_SIZE
);
657 if (prefs
.view_mode
== WIDE
) {
658 /* WIDE mode needs more buffer so we have to read smaller blocks */
659 move_size
= SMALL_BLOCK_SIZE
;
660 offset
= LARGE_BLOCK_SIZE
;
661 fill_buf
= BOTTOM_SECTOR
;
662 rb
->memcpy(TOP_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
663 rb
->memcpy(MID_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
666 move_size
= LARGE_BLOCK_SIZE
;
667 offset
= SMALL_BLOCK_SIZE
;
668 fill_buf
= MID_SECTOR
;
669 rb
->memcpy(TOP_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
672 move_vector
= direction
* move_size
;
673 screen_top_ptr
-= move_vector
;
674 file_pos
+= move_vector
;
675 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
676 fill_buffer(file_pos
+ offset
, fill_buf
, move_size
);
680 static void viewer_scroll_up(void)
684 p
= find_prev_line(screen_top_ptr
);
685 if (p
== NULL
&& !BUFFER_BOF()) {
687 p
= find_prev_line(screen_top_ptr
);
693 static void viewer_scroll_down(void)
695 if (next_screen_ptr
!= NULL
)
696 screen_top_ptr
= next_line_ptr
;
699 #ifdef HAVE_LCD_BITMAP
700 static void viewer_scrollbar(void) {
701 int items
, min_shown
, max_shown
;
703 items
= (int) file_size
; /* (SH1 int is same as long) */
704 min_shown
= (int) file_pos
+ (screen_top_ptr
- buffer
);
706 if (next_screen_ptr
== NULL
)
709 max_shown
= min_shown
+ (next_screen_ptr
- screen_top_ptr
);
711 rb
->gui_scrollbar_draw(rb
->screens
[SCREEN_MAIN
],0, 0, SCROLLBAR_WIDTH
-1,
712 LCD_HEIGHT
, items
, min_shown
, max_shown
, VERTICAL
);
716 static void viewer_draw(int col
)
718 int i
, j
, k
, line_len
, line_width
, resynch_move
, spaces
, left_col
=0;
719 int width
, extra_spaces
, indent_spaces
, spaces_per_word
;
720 bool multiple_spacing
, line_is_short
;
722 unsigned char *str
, *oldstr
;
723 unsigned char *line_begin
;
724 unsigned char *line_end
;
726 unsigned char scratch_buffer
[MAX_COLUMNS
+ 1];
727 unsigned char utf8_buffer
[MAX_COLUMNS
*4 + 1];
728 unsigned char *endptr
;
730 /* If col==-1 do all calculations but don't display */
732 #ifdef HAVE_LCD_BITMAP
733 left_col
= prefs
.need_scrollbar
? SCROLLBAR_WIDTH
:0;
737 rb
->lcd_clear_display();
740 line_begin
= line_end
= screen_top_ptr
;
742 for (i
= 0; i
< display_lines
; i
++) {
743 if (BUFFER_OOB(line_end
))
744 break; /* Happens after display last line at BUFFER_EOF() */
746 line_begin
= line_end
;
747 line_end
= find_next_line(line_begin
, &line_is_short
);
749 if (line_end
== NULL
) {
751 if (i
< display_lines
- 1 && !BUFFER_BOF()) {
753 rb
->lcd_clear_display();
755 for (; i
< display_lines
- 1; i
++)
758 line_begin
= line_end
= screen_top_ptr
;
763 line_end
= buffer_end
;
767 resynch_move
= read_and_synch(1); /* Read block & move ptrs */
768 line_begin
-= resynch_move
;
770 next_line_ptr
-= resynch_move
;
772 line_end
= find_next_line(line_begin
, NULL
);
773 if (line_end
== NULL
) /* Should not really happen */
777 line_len
= line_end
- line_begin
;
779 /* calculate line_len */
780 str
= oldstr
= line_begin
;
782 while (str
< line_end
) {
784 str
= crop_at_width(str
);
787 line_width
= j
*draw_columns
;
788 while (oldstr
< line_end
) {
789 oldstr
= get_ucs(oldstr
, &ch
);
790 line_width
+= glyph_width(ch
);
793 if (prefs
.line_mode
== JOIN
) {
794 if (line_begin
[0] == 0) {
796 if (prefs
.word_mode
== CHOP
)
801 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
802 if (k
== MAX_COLUMNS
)
812 scratch_buffer
[k
++] = ' ';
817 scratch_buffer
[k
++] = ' ';
818 if (k
== MAX_COLUMNS
- 1)
821 scratch_buffer
[k
++] = c
;
826 scratch_buffer
[k
] = 0;
827 endptr
= rb
->iso_decode(scratch_buffer
+ col
, utf8_buffer
,
828 prefs
.encoding
, draw_columns
/glyph_width('i'));
832 else if (prefs
.line_mode
== REFLOW
) {
833 if (line_begin
[0] == 0) {
835 if (prefs
.word_mode
== CHOP
)
842 if (!line_is_short
) {
843 multiple_spacing
= false;
845 for (str
= line_begin
; str
< line_end
; ) {
846 str
= get_ucs(str
, &ch
);
850 if ((str
== line_begin
) && (prefs
.word_mode
==WRAP
))
851 /* special case: indent the paragraph,
852 * don't count spaces */
853 indent_spaces
= par_indent_spaces
;
854 else if (!multiple_spacing
)
856 multiple_spacing
= true;
859 multiple_spacing
= false;
860 width
+= glyph_width(ch
);
864 if (multiple_spacing
) spaces
--;
867 /* total number of spaces to insert between words */
868 extra_spaces
= (draw_columns
-width
)/glyph_width(' ')
870 /* number of spaces between each word*/
871 spaces_per_word
= extra_spaces
/ spaces
;
872 /* number of words with n+1 spaces (to fill up) */
873 extra_spaces
= extra_spaces
% spaces
;
874 if (spaces_per_word
> 2) { /* too much spacing is awful */
878 } else { /* this doesn't matter much... no spaces anyway */
879 spaces_per_word
= extra_spaces
= 0;
881 } else { /* end of a paragraph: don't fill line */
886 multiple_spacing
= false;
887 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
888 if (k
== MAX_COLUMNS
)
895 if (j
==0 && prefs
.word_mode
==WRAP
) { /* indent paragraph */
896 for (j
=0; j
<par_indent_spaces
; j
++)
897 scratch_buffer
[k
++] = ' ';
900 else if (!multiple_spacing
) {
901 for (width
= spaces
<extra_spaces
? -1:0; width
< spaces_per_word
; width
++)
902 scratch_buffer
[k
++] = ' ';
905 multiple_spacing
= true;
908 scratch_buffer
[k
++] = c
;
909 multiple_spacing
= false;
915 scratch_buffer
[k
] = 0;
916 endptr
= rb
->iso_decode(scratch_buffer
+ col
, utf8_buffer
,
917 prefs
.encoding
, k
-col
);
921 else { /* prefs.line_mode != JOIN && prefs.line_mode != REFLOW */
923 if (line_width
> col
) {
924 str
= oldstr
= line_begin
;
927 while( (width
<draw_columns
) && (oldstr
<line_end
) )
929 oldstr
= get_ucs(oldstr
, &ch
);
931 k
-= glyph_width(ch
);
934 width
+= glyph_width(ch
);
938 if(prefs
.view_mode
==WIDE
)
939 endptr
= rb
->iso_decode(line_begin
, utf8_buffer
,
940 prefs
.encoding
, oldstr
-line_begin
);
942 endptr
= rb
->iso_decode(line_begin
, utf8_buffer
,
943 prefs
.encoding
, line_end
-line_begin
);
947 if (col
!= -1 && line_width
> col
)
948 #ifdef HAVE_LCD_BITMAP
949 rb
->lcd_putsxy(left_col
, i
*pf
->height
, utf8_buffer
);
951 rb
->lcd_puts(left_col
, i
, utf8_buffer
);
953 if (line_width
> max_line_len
)
954 max_line_len
= line_width
;
957 next_line_ptr
= line_end
;
959 next_screen_ptr
= line_end
;
960 if (BUFFER_OOB(next_screen_ptr
))
961 next_screen_ptr
= NULL
;
963 #ifdef HAVE_LCD_BITMAP
964 next_screen_to_draw_ptr
= prefs
.page_mode
==OVERLAP
? line_begin
: next_screen_ptr
;
966 if (prefs
.need_scrollbar
)
969 next_screen_to_draw_ptr
= next_screen_ptr
;
976 static void viewer_top(void)
978 /* Read top of file into buffer
979 and point screen pointer to top */
983 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
984 fill_buffer(0, buffer
, buffer_size
);
987 screen_top_ptr
= buffer
;
990 static void viewer_bottom(void)
992 /* Read bottom of file into buffer
993 and point screen pointer to bottom */
996 if (file_size
> buffer_size
) {
997 /* Find last buffer in file, round up to next sector boundary */
998 last_sectors
= file_size
- buffer_size
+ SMALL_BLOCK_SIZE
;
999 last_sectors
/= SMALL_BLOCK_SIZE
;
1000 last_sectors
*= SMALL_BLOCK_SIZE
;
1006 if (file_pos
!= last_sectors
)
1008 file_pos
= last_sectors
;
1009 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
1010 fill_buffer(last_sectors
, buffer
, buffer_size
);
1013 screen_top_ptr
= buffer_end
-1;
1016 #ifdef HAVE_LCD_BITMAP
1017 static void init_need_scrollbar(void) {
1018 /* Call viewer_draw in quiet mode to initialize next_screen_ptr,
1019 and thus ONE_SCREEN_FITS_ALL(), and thus NEED_SCROLLBAR() */
1021 prefs
.need_scrollbar
= NEED_SCROLLBAR();
1022 draw_columns
= prefs
.need_scrollbar
? display_columns
-SCROLLBAR_WIDTH
: display_columns
;
1023 par_indent_spaces
= draw_columns
/(5*glyph_width(' '));
1026 #define init_need_scrollbar()
1029 static bool viewer_init(void)
1031 #ifdef HAVE_LCD_BITMAP
1033 pf
= rb
->font_get(FONT_UI
);
1035 display_lines
= LCD_HEIGHT
/ pf
->height
;
1036 draw_columns
= display_columns
= LCD_WIDTH
;
1038 /* REAL fixed pitch :) all chars use up 1 cell */
1040 draw_columns
= display_columns
= 11;
1041 par_indent_spaces
= 2;
1044 fd
= rb
->open(file_name
, O_RDONLY
);
1048 file_size
= rb
->filesize(fd
);
1052 /* Init mac_text value used in processing buffer */
1058 static void viewer_default_settings(void)
1060 prefs
.word_mode
= WRAP
;
1061 prefs
.line_mode
= NORMAL
;
1062 prefs
.view_mode
= NARROW
;
1063 prefs
.scroll_mode
= PAGE
;
1064 #ifdef HAVE_LCD_BITMAP
1065 prefs
.page_mode
= NO_OVERLAP
;
1066 prefs
.scrollbar_mode
= SB_OFF
;
1068 prefs
.autoscroll_speed
= 1;
1069 /* Set codepage to system default */
1070 prefs
.encoding
= rb
->global_settings
->default_codepage
;
1073 static void viewer_load_settings(void) /* same name as global, but not the same file.. */
1076 struct bookmark_file_data
*data
;
1077 struct bookmarked_file_info this_bookmark
;
1079 /* read settings file */
1080 settings_fd
=rb
->open(SETTINGS_FILE
, O_RDONLY
);
1081 if ((settings_fd
>= 0) && (rb
->filesize(settings_fd
) == sizeof(struct preferences
)))
1083 rb
->read(settings_fd
, &prefs
, sizeof(struct preferences
));
1084 rb
->close(settings_fd
);
1088 /* load default settings if there is no settings file */
1089 viewer_default_settings();
1092 rb
->memcpy(&old_prefs
, &prefs
, sizeof(struct preferences
));
1094 data
= (struct bookmark_file_data
*)buffer
; /* grab the text buffer */
1095 data
->bookmarked_files_count
= 0;
1097 /* read bookmarks if file exists */
1098 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_RDONLY
);
1099 if (settings_fd
>= 0)
1101 /* figure out how many items to read */
1102 rb
->read(settings_fd
, &data
->bookmarked_files_count
, sizeof(signed int));
1103 if (data
->bookmarked_files_count
> MAX_BOOKMARKED_FILES
)
1104 data
->bookmarked_files_count
= MAX_BOOKMARKED_FILES
;
1105 rb
->read(settings_fd
, data
->bookmarks
,
1106 sizeof(struct bookmarked_file_info
) * data
->bookmarked_files_count
);
1107 rb
->close(settings_fd
);
1111 screen_top_ptr
= buffer
;
1113 /* check if current file is in list */
1114 for (i
=0; i
< data
->bookmarked_files_count
; i
++)
1116 if (!rb
->strcmp(file_name
, data
->bookmarks
[i
].filename
))
1118 int screen_pos
= data
->bookmarks
[i
].file_position
+ data
->bookmarks
[i
].top_ptr_pos
;
1119 int screen_top
= screen_pos
% buffer_size
;
1120 file_pos
= screen_pos
- screen_top
;
1121 screen_top_ptr
= buffer
+ screen_top
;
1126 this_bookmark
.file_position
= file_pos
;
1127 this_bookmark
.top_ptr_pos
= screen_top_ptr
- buffer
;
1129 rb
->memset(&this_bookmark
.filename
[0],0,MAX_PATH
);
1130 rb
->strcpy(this_bookmark
.filename
,file_name
);
1132 /* prevent potential slot overflow */
1133 if (i
>= data
->bookmarked_files_count
)
1135 if (i
< MAX_BOOKMARKED_FILES
)
1136 data
->bookmarked_files_count
++;
1138 i
= MAX_BOOKMARKED_FILES
-1;
1141 /* write bookmark file with spare slot in first position
1142 to be filled in by viewer_save_settings */
1143 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_WRONLY
|O_CREAT
);
1144 if (settings_fd
>=0 )
1147 rb
->write (settings_fd
, &data
->bookmarked_files_count
, sizeof(signed int));
1149 /* write the current bookmark */
1150 rb
->write (settings_fd
, &this_bookmark
, sizeof(struct bookmarked_file_info
));
1152 /* write everything that was before this bookmark */
1153 rb
->write (settings_fd
, data
->bookmarks
, sizeof(struct bookmarked_file_info
)*i
);
1155 rb
->close(settings_fd
);
1158 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
1160 if (BUFFER_OOB(screen_top_ptr
))
1162 screen_top_ptr
= buffer
;
1165 fill_buffer(file_pos
, buffer
, buffer_size
);
1167 /* remember the current position */
1168 start_position
= file_pos
+ screen_top_ptr
- buffer
;
1170 init_need_scrollbar();
1173 static void viewer_save_settings(void)/* same name as global, but not the same file.. */
1177 /* save the viewer settings if they have been changed */
1178 if (rb
->memcmp(&prefs
, &old_prefs
, sizeof(struct preferences
)))
1180 settings_fd
= rb
->creat(SETTINGS_FILE
); /* create the settings file */
1182 if (settings_fd
>= 0 )
1184 rb
->write (settings_fd
, &prefs
, sizeof(struct preferences
));
1185 rb
->close(settings_fd
);
1189 /* save the bookmark if the position has changed */
1190 if (file_pos
+ screen_top_ptr
- buffer
!= start_position
)
1192 settings_fd
= rb
->open(BOOKMARKS_FILE
, O_WRONLY
|O_CREAT
);
1194 if (settings_fd
>= 0 )
1196 struct bookmarked_file_info b
;
1197 b
.file_position
= file_pos
+ screen_top_ptr
- buffer
;
1198 b
.top_ptr_pos
= 0; /* this is only kept for legassy reasons */
1199 rb
->memset(&b
.filename
[0],0,MAX_PATH
);
1200 rb
->strcpy(b
.filename
,file_name
);
1201 rb
->PREFIX(lseek
)(settings_fd
,sizeof(signed int),SEEK_SET
);
1202 rb
->write (settings_fd
, &b
, sizeof(struct bookmarked_file_info
));
1203 rb
->close(settings_fd
);
1208 static void viewer_exit(void *parameter
)
1212 viewer_save_settings();
1216 static int col_limit(int col
)
1221 if (col
> max_line_len
- 2*glyph_width('o'))
1222 col
= max_line_len
- 2*glyph_width('o');
1227 /* settings helper functions */
1229 static bool encoding_setting(void)
1231 static const struct opt_items names
[] = {
1236 {"ISO-8859-11", -1},
1248 return rb
->set_option("Encoding", &prefs
.encoding
, INT
, names
,
1249 sizeof(names
) / sizeof(names
[0]), NULL
);
1252 static bool word_wrap_setting(void)
1254 static const struct opt_items names
[] = {
1256 {"Off (Chop Words)", -1},
1259 return rb
->set_option("Word Wrap", &prefs
.word_mode
, INT
,
1263 static bool line_mode_setting(void)
1265 static const struct opt_items names
[] = {
1268 {"Expand Lines", -1},
1269 #ifdef HAVE_LCD_BITMAP
1270 {"Reflow Lines", -1},
1274 return rb
->set_option("Line Mode", &prefs
.line_mode
, INT
, names
,
1275 sizeof(names
) / sizeof(names
[0]), NULL
);
1278 static bool view_mode_setting(void)
1280 static const struct opt_items names
[] = {
1281 {"No (Narrow)", -1},
1285 ret
= rb
->set_option("Wide View", &prefs
.view_mode
, INT
,
1287 if (prefs
.view_mode
== NARROW
)
1292 static bool scroll_mode_setting(void)
1294 static const struct opt_items names
[] = {
1295 {"Scroll by Page", -1},
1296 {"Scroll by Line", -1},
1299 return rb
->set_option("Scroll Mode", &prefs
.scroll_mode
, INT
,
1303 #ifdef HAVE_LCD_BITMAP
1304 static bool page_mode_setting(void)
1306 static const struct opt_items names
[] = {
1311 return rb
->set_option("Overlap Pages", &prefs
.page_mode
, INT
,
1315 static bool scrollbar_setting(void)
1317 static const struct opt_items names
[] = {
1322 return rb
->set_option("Show Scrollbar", &prefs
.scrollbar_mode
, INT
,
1327 static bool autoscroll_speed_setting(void)
1329 return rb
->set_int("Auto-scroll Speed", "", UNIT_INT
,
1330 &prefs
.autoscroll_speed
, NULL
, 1, 1, 10, NULL
);
1333 static bool viewer_options_menu(void)
1338 static const struct menu_item items
[] = {
1339 {"Encoding", encoding_setting
},
1340 {"Word Wrap", word_wrap_setting
},
1341 {"Line Mode", line_mode_setting
},
1342 {"Wide View", view_mode_setting
},
1343 #ifdef HAVE_LCD_BITMAP
1344 {"Show Scrollbar", scrollbar_setting
},
1345 {"Overlap Pages", page_mode_setting
},
1347 {"Scroll Mode", scroll_mode_setting
},
1348 {"Auto-Scroll Speed", autoscroll_speed_setting
},
1350 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
),
1351 NULL
, NULL
, NULL
, NULL
);
1353 result
= menu_run(m
);
1355 #ifdef HAVE_LCD_BITMAP
1356 rb
->lcd_setmargins(0,0);
1358 /* Show-scrollbar mode for current view-width mode */
1359 init_need_scrollbar();
1364 static void viewer_menu(void)
1368 static const struct menu_item items
[] = {
1370 {"Viewer Options", NULL
},
1371 {"Show Playback Menu", NULL
},
1375 m
= menu_init(rb
, items
, sizeof(items
) / sizeof(*items
), NULL
, NULL
, NULL
, NULL
);
1376 result
=menu_show(m
);
1384 case 1: /* change settings */
1385 done
= viewer_options_menu();
1387 case 2: /* playback control */
1388 playback_control(rb
);
1390 case 3: /* return */
1394 #ifdef HAVE_LCD_BITMAP
1395 rb
->lcd_setmargins(0,0);
1400 enum plugin_status
plugin_start(struct plugin_api
* api
, void* file
)
1403 int lastbutton
= BUTTON_NONE
;
1404 bool autoscroll
= false;
1408 old_tick
= *rb
->current_tick
;
1410 /* get the plugin buffer */
1411 buffer
= rb
->plugin_get_buffer((size_t *)&buffer_size
);
1414 return PLUGIN_ERROR
;
1419 rb
->splash(HZ
, "Error opening file.");
1420 return PLUGIN_ERROR
;
1423 viewer_load_settings(); /* load the preferences and bookmark */
1426 rb
->lcd_set_backdrop(NULL
);
1435 if(old_tick
<= *rb
->current_tick
- (110-prefs
.autoscroll_speed
*10))
1437 viewer_scroll_down();
1439 old_tick
= *rb
->current_tick
;
1443 button
= rb
->button_get_w_tmo(HZ
/10);
1449 case VIEWER_AUTOSCROLL
:
1450 #ifdef VIEWER_AUTOSCROLL_PRE
1451 if (lastbutton
!= VIEWER_AUTOSCROLL_PRE
)
1454 autoscroll
= !autoscroll
;
1457 case VIEWER_PAGE_UP
:
1458 case VIEWER_PAGE_UP
| BUTTON_REPEAT
:
1459 if (prefs
.scroll_mode
== PAGE
)
1462 #ifdef HAVE_LCD_BITMAP
1463 for (i
= prefs
.page_mode
==OVERLAP
? 1:0; i
< display_lines
; i
++)
1465 for (i
= 0; i
< display_lines
; i
++)
1471 old_tick
= *rb
->current_tick
;
1475 case VIEWER_PAGE_DOWN
:
1476 case VIEWER_PAGE_DOWN
| BUTTON_REPEAT
:
1477 if (prefs
.scroll_mode
== PAGE
)
1480 if (next_screen_ptr
!= NULL
)
1481 screen_top_ptr
= next_screen_to_draw_ptr
;
1484 viewer_scroll_down();
1485 old_tick
= *rb
->current_tick
;
1489 case VIEWER_SCREEN_LEFT
:
1490 case VIEWER_SCREEN_LEFT
| BUTTON_REPEAT
:
1491 if (prefs
.view_mode
== WIDE
) {
1493 col
-= draw_columns
;
1494 col
= col_limit(col
);
1496 else { /* prefs.view_mode == NARROW */
1504 case VIEWER_SCREEN_RIGHT
:
1505 case VIEWER_SCREEN_RIGHT
| BUTTON_REPEAT
:
1506 if (prefs
.view_mode
== WIDE
) {
1508 col
+= draw_columns
;
1509 col
= col_limit(col
);
1511 else { /* prefs.view_mode == NARROW */
1512 /* Bottom of file */
1519 #ifdef VIEWER_LINE_UP
1520 case VIEWER_LINE_UP
:
1521 case VIEWER_LINE_UP
| BUTTON_REPEAT
:
1522 /* Scroll up one line */
1524 old_tick
= *rb
->current_tick
;
1528 case VIEWER_LINE_DOWN
:
1529 case VIEWER_LINE_DOWN
| BUTTON_REPEAT
:
1530 /* Scroll down one line */
1531 if (next_screen_ptr
!= NULL
)
1532 screen_top_ptr
= next_line_ptr
;
1533 old_tick
= *rb
->current_tick
;
1537 #ifdef VIEWER_COLUMN_LEFT
1538 case VIEWER_COLUMN_LEFT
:
1539 case VIEWER_COLUMN_LEFT
| BUTTON_REPEAT
:
1540 if (prefs
.view_mode
== WIDE
) {
1541 /* Scroll left one column */
1542 col
-= glyph_width('o');
1543 col
= col_limit(col
);
1548 case VIEWER_COLUMN_RIGHT
:
1549 case VIEWER_COLUMN_RIGHT
| BUTTON_REPEAT
:
1550 if (prefs
.view_mode
== WIDE
) {
1551 /* Scroll right one column */
1552 col
+= glyph_width('o');
1553 col
= col_limit(col
);
1559 #ifdef VIEWER_RC_QUIT
1560 case VIEWER_RC_QUIT
:
1568 if (rb
->default_event_handler_ex(button
, viewer_exit
, NULL
)
1569 == SYS_USB_CONNECTED
)
1570 return PLUGIN_USB_CONNECTED
;
1573 if (button
!= BUTTON_NONE
)
1575 lastbutton
= button
;