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 #ifdef HAVE_LCD_BITMAP
23 #include "recorder/widgets.h"
32 #if PLUGIN_API_VERSION < 3
33 #error Scrollbar function requires PLUGIN_API_VERSION 3 at least
36 #define WRAP_TRIM 44 /* Max number of spaces to trim (arbitrary) */
37 #define MAX_COLUMNS 64 /* Max displayable string len (over-estimate) */
38 #define MAX_WIDTH 910 /* Max line length in WIDE mode */
39 #define READ_PREV_ZONE 910 /* Arbitrary number less than SMALL_BLOCK_SIZE */
40 #define SMALL_BLOCK_SIZE 0x1000 /* 4k: Smallest file chunk we will read */
41 #define LARGE_BLOCK_SIZE 0x2000 /* 8k: Preferable size of file chunk to read */
42 #define BUFFER_SIZE 0x3000 /* 12k: Mem reserved for buffered file data */
43 #define TOP_SECTOR buffer
44 #define MID_SECTOR (buffer + SMALL_BLOCK_SIZE)
45 #define BOTTOM_SECTOR (buffer + 2*(SMALL_BLOCK_SIZE))
47 /* Out-Of-Bounds test for any pointer to data in the buffer */
48 #define BUFFER_OOB(p) ((p) < buffer || (p) >= buffer_end)
50 /* Does the buffer contain the beginning of the file? */
51 #define BUFFER_BOF() (file_pos==0)
53 /* Does the buffer contain the end of the file? */
54 #define BUFFER_EOF() (file_size-file_pos <= BUFFER_SIZE)
56 /* Formula for the endpoint address outside of buffer data */
57 #define BUFFER_END() \
58 ((BUFFER_EOF()) ? (file_size-file_pos+buffer) : (buffer+BUFFER_SIZE))
60 /* Is the entire file being shown in one screen? */
61 #define ONE_SCREEN_FITS_ALL() \
62 (next_screen_ptr==NULL && screen_top_ptr==buffer && BUFFER_BOF())
64 /* Is a scrollbar called for on the current screen? */
65 #define NEED_SCROLLBAR() ((!(ONE_SCREEN_FITS_ALL())) && \
66 (view_mode==WIDE? scrollbar_mode[WIDE]==SB_ON: scrollbar_mode[NARROW]==SB_ON))
73 static unsigned char *word_mode_str
[] = {"wrap", "chop", "words"};
81 static unsigned char *line_mode_str
[] = {"normal", "join", "expand", "lines"};
88 static unsigned char *view_mode_str
[] = {"narrow", "wide", "view"};
90 #ifdef HAVE_LCD_BITMAP
95 } scrollbar_mode
[VIEW_MODES
] = {SB_OFF
, SB_ON
};
96 static unsigned char *scrollbar_mode_str
[] = {"off", "on", "scrollbar"};
97 static bool need_scrollbar
;
103 static unsigned char *page_mode_str
[] = {"don't overlap", "overlap", "pages"};
106 static unsigned char buffer
[BUFFER_SIZE
+ 1];
107 static unsigned char line_break
[] = {0,0x20,'-',9,0xB,0xC};
108 static int display_columns
; /* number of columns on the display */
109 static int display_lines
; /* number of lines on the display */
111 static long file_size
;
112 static bool mac_text
;
113 static long file_pos
; /* Position of the top of the buffer in the file */
114 static unsigned char *buffer_end
; /*Set to BUFFER_END() when file_pos changes*/
115 static int max_line_len
;
116 static unsigned char *screen_top_ptr
;
117 static unsigned char *next_screen_ptr
;
118 static unsigned char *next_screen_to_draw_ptr
;
119 static unsigned char *next_line_ptr
;
120 static struct plugin_api
* rb
;
122 static unsigned char* find_first_feed(const unsigned char* p
, int size
)
126 for (i
=0; i
< size
; i
++)
128 return (unsigned char*) p
+i
;
133 static unsigned char* find_last_feed(const unsigned char* p
, int size
)
137 for (i
=size
-1; i
>=0; i
--)
139 return (unsigned char*) p
+i
;
144 static unsigned char* find_last_space(const unsigned char* p
, int size
)
148 k
= line_mode
==JOIN
? 0:1;
150 for (i
=size
-1; i
>=0; i
--)
151 for (j
=k
; j
< (int) sizeof(line_break
); j
++)
152 if (p
[i
] == line_break
[j
])
153 return (unsigned char*) p
+i
;
158 static unsigned char* find_next_line(const unsigned char* cur_line
)
160 const unsigned char *next_line
= NULL
;
161 int size
, i
, j
, k
, chop_len
, search_len
, spaces
, newlines
, draw_columns
;
164 if BUFFER_OOB(cur_line
)
167 #ifdef HAVE_LCD_BITMAP
168 draw_columns
= need_scrollbar
? display_columns
-1: display_columns
;
170 draw_columns
= display_columns
;
173 if (view_mode
== WIDE
) {
174 search_len
= chop_len
= MAX_WIDTH
;
176 else { /* view_mode == NARROW */
177 chop_len
= draw_columns
;
178 search_len
= chop_len
+ 1;
181 size
= BUFFER_OOB(cur_line
+search_len
) ? buffer_end
-cur_line
: search_len
;
183 if (line_mode
== JOIN
) {
184 /* Need to scan ahead and possibly increase search_len and size,
185 or possibly set next_line at second hard return in a row. */
187 for (j
=k
=spaces
=newlines
=0; j
< size
; j
++) {
188 if (k
== MAX_COLUMNS
)
200 next_line
= cur_line
+ size
- spaces
- 1;
201 if (next_line
!= cur_line
)
202 return (unsigned char*) next_line
;
207 if (BUFFER_OOB(cur_line
+size
) || size
> 2*search_len
)
220 if (k
== MAX_COLUMNS
- 1)
229 /* find first hard return */
230 next_line
= find_first_feed(cur_line
, size
);
233 if (next_line
== NULL
)
234 if (size
== search_len
) {
235 if (word_mode
== WRAP
) /* Find last space */
236 next_line
= find_last_space(cur_line
, size
);
238 if (next_line
== NULL
)
239 next_line
= cur_line
+ chop_len
;
241 if (word_mode
== WRAP
)
243 i
<WRAP_TRIM
&& isspace(next_line
[0]) && !BUFFER_OOB(next_line
);
248 if (line_mode
== EXPAND
)
249 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
250 if (next_line
[0] == 0)
251 if (next_line
!= cur_line
)
252 return (unsigned char*) next_line
;
254 /* If next_line is pointing to a zero, increment it; i.e.,
255 leave the terminator at the end of cur_line. If pointing
256 to a hyphen, increment only if there is room to display
257 the hyphen on current line (won't apply in WIDE mode,
258 since it's guarenteed there won't be room). */
259 if (!BUFFER_OOB(next_line
)) /* Not Null & not out of bounds */
260 if (next_line
[0] == 0 ||
261 (next_line
[0] == '-' && next_line
-cur_line
< draw_columns
))
264 if (BUFFER_OOB(next_line
))
267 return (unsigned char*) next_line
;
270 static unsigned char* find_prev_line(const unsigned char* cur_line
)
272 const unsigned char *prev_line
= NULL
;
273 const unsigned char *p
;
275 if BUFFER_OOB(cur_line
)
278 /* To wrap consistently at the same places, we must
279 start with a known hard return, then work downwards.
280 We can either search backwards for a hard return,
281 or simply start wrapping downwards from top of buffer.
282 If current line is not near top of buffer, this is
283 a file with long lines (paragraphs). We would need to
284 read earlier sectors before we could decide how to
285 properly wrap the lines above the current line, but
286 it probably is not worth the disk access. Instead,
287 start with top of buffer and wrap down from there.
288 This may result in some lines wrapping at different
289 points from where they wrap when scrolling down.
290 If buffer is at top of file, start at top of buffer. */
292 if (line_mode
== JOIN
)
293 prev_line
= p
= NULL
;
295 prev_line
= p
= find_last_feed(buffer
, cur_line
-buffer
-1);
296 /* Null means no line feeds in buffer above current line. */
298 if (prev_line
== NULL
)
299 if (BUFFER_BOF() || cur_line
- buffer
> READ_PREV_ZONE
)
300 prev_line
= p
= buffer
;
301 /* (else return NULL and read previous block) */
303 /* Wrap downwards until too far, then use the one before. */
304 while (p
< cur_line
&& p
!= NULL
) {
306 p
= find_next_line(prev_line
);
309 if (BUFFER_OOB(prev_line
))
312 return (unsigned char*) prev_line
;
315 static void fill_buffer(long pos
, unsigned char* buf
, unsigned size
)
317 /* Read from file and preprocess the data */
318 /* To minimize disk access, always read on sector boundaries */
320 bool found_CR
= false;
322 rb
->lseek(fd
, pos
, SEEK_SET
);
323 numread
= rb
->read(fd
, buf
, size
);
324 while (rb
->button_get(false)); /* clear button queue */
326 for(i
= 0; i
< numread
; i
++) {
343 case 0: /* No break between case 0 and default, intentionally */
356 static int read_and_synch(int direction
)
358 /* Read next (or prev) block, and reposition global pointers. */
359 /* direction: 1 for down (i.e., further into file), -1 for up */
360 int move_size
, move_vector
, offset
;
361 unsigned char *fill_buf
;
363 if (direction
== -1) /* up */ {
364 move_size
= SMALL_BLOCK_SIZE
;
366 fill_buf
= TOP_SECTOR
;
367 rb
->memcpy(BOTTOM_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
368 rb
->memcpy(MID_SECTOR
, TOP_SECTOR
, SMALL_BLOCK_SIZE
);
371 if (view_mode
== WIDE
) {
372 /* WIDE mode needs more buffer so we have to read smaller blocks */
373 move_size
= SMALL_BLOCK_SIZE
;
374 offset
= LARGE_BLOCK_SIZE
;
375 fill_buf
= BOTTOM_SECTOR
;
376 rb
->memcpy(TOP_SECTOR
, MID_SECTOR
, SMALL_BLOCK_SIZE
);
377 rb
->memcpy(MID_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
380 move_size
= LARGE_BLOCK_SIZE
;
381 offset
= SMALL_BLOCK_SIZE
;
382 fill_buf
= MID_SECTOR
;
383 rb
->memcpy(TOP_SECTOR
, BOTTOM_SECTOR
, SMALL_BLOCK_SIZE
);
386 move_vector
= direction
* move_size
;
387 screen_top_ptr
-= move_vector
;
388 file_pos
+= move_vector
;
389 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
390 fill_buffer(file_pos
+ offset
, fill_buf
, move_size
);
394 static void viewer_scroll_up(void)
398 p
= find_prev_line(screen_top_ptr
);
399 if (p
== NULL
&& !BUFFER_BOF()) {
401 p
= find_prev_line(screen_top_ptr
);
407 #ifdef HAVE_LCD_BITMAP
408 static void viewer_scrollbar(void) {
409 int w
, h
, items
, min_shown
, max_shown
;
411 rb
->lcd_getstringsize("o", &w
, &h
);
412 items
= (int) file_size
; /* (SH1 int is same as long) */
413 min_shown
= (int) file_pos
+ (screen_top_ptr
- buffer
);
415 if (next_screen_ptr
== NULL
)
418 max_shown
= min_shown
+ (next_screen_ptr
- screen_top_ptr
);
420 rb
->scrollbar(0, 0, w
-1, LCD_HEIGHT
, items
, min_shown
, max_shown
, VERTICAL
);
424 static void viewer_draw(int col
)
426 int i
, j
, k
, line_len
, resynch_move
, spaces
, left_col
=0;
427 unsigned char *line_begin
;
428 unsigned char *line_end
;
430 unsigned char scratch_buffer
[MAX_COLUMNS
+ 1];
432 /* If col==-1 do all calculations but don't display */
434 #ifdef HAVE_LCD_BITMAP
435 left_col
= need_scrollbar
? 1:0;
439 rb
->lcd_clear_display();
442 line_begin
= line_end
= screen_top_ptr
;
444 for (i
= 0; i
< display_lines
; i
++) {
445 if (BUFFER_OOB(line_end
))
446 break; /* Happens after display last line at BUFFER_EOF() */
448 line_begin
= line_end
;
449 line_end
= find_next_line(line_begin
);
451 if (line_end
== NULL
) {
453 if (i
< display_lines
- 1 && !BUFFER_BOF()) {
455 rb
->lcd_clear_display();
457 for (; i
< display_lines
- 1; i
++)
460 line_begin
= line_end
= screen_top_ptr
;
465 line_end
= buffer_end
;
469 resynch_move
= read_and_synch(1); /* Read block & move ptrs */
470 line_begin
-= resynch_move
;
472 next_line_ptr
-= resynch_move
;
474 line_end
= find_next_line(line_begin
);
475 if (line_end
== NULL
) /* Should not really happen */
479 line_len
= line_end
- line_begin
;
481 if (line_mode
== JOIN
) {
482 if (line_begin
[0] == 0) {
484 if (word_mode
== CHOP
)
487 for (j
=k
=spaces
=0; j
< line_len
; j
++) {
488 if (k
== MAX_COLUMNS
)
498 scratch_buffer
[k
++] = ' ';
503 scratch_buffer
[k
++] = ' ';
504 if (k
== MAX_COLUMNS
- 1)
507 scratch_buffer
[k
++] = c
;
513 scratch_buffer
[k
] = 0;
514 rb
->lcd_puts(left_col
, i
, scratch_buffer
+ col
);
519 if (line_len
> col
) {
522 rb
->lcd_puts(left_col
, i
, line_begin
+ col
);
526 if (line_len
> max_line_len
)
527 max_line_len
= line_len
;
530 next_line_ptr
= line_end
;
532 next_screen_ptr
= line_end
;
533 if (BUFFER_OOB(next_screen_ptr
))
534 next_screen_ptr
= NULL
;
536 #ifdef HAVE_LCD_BITMAP
537 next_screen_to_draw_ptr
= page_mode
==OVERLAP
? line_begin
: next_screen_ptr
;
545 next_screen_to_draw_ptr
= next_screen_ptr
;
549 static void viewer_top(void)
551 /* Read top of file into buffer
552 and point screen pointer to top */
554 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
555 screen_top_ptr
= buffer
;
556 fill_buffer(0, buffer
, BUFFER_SIZE
);
559 static void viewer_bottom(void)
561 /* Read bottom of file into buffer
562 and point screen pointer to bottom */
565 if (file_size
> BUFFER_SIZE
) {
566 /* Find last buffer in file, round up to next sector boundary */
567 last_sectors
= file_size
- BUFFER_SIZE
+ SMALL_BLOCK_SIZE
;
568 last_sectors
/= SMALL_BLOCK_SIZE
;
569 last_sectors
*= SMALL_BLOCK_SIZE
;
574 file_pos
= last_sectors
;
575 buffer_end
= BUFFER_END(); /* Update whenever file_pos changes */
576 screen_top_ptr
= buffer_end
-1;
577 fill_buffer(last_sectors
, buffer
, BUFFER_SIZE
);
580 #ifdef HAVE_LCD_BITMAP
581 static void init_need_scrollbar(void) {
582 /* Call viewer_draw in quiet mode to initialize next_screen_ptr,
583 and thus ONE_SCREEN_FITS_ALL(), and thus NEED_SCROLLBAR() */
585 need_scrollbar
= NEED_SCROLLBAR();
589 static bool viewer_init(char* file
)
591 #ifdef HAVE_LCD_BITMAP
594 rb
->lcd_getstringsize("o", &w
, &h
);
595 display_lines
= LCD_HEIGHT
/ h
;
596 display_columns
= LCD_WIDTH
/ w
;
599 display_columns
= 11;
601 /*********************
602 * (Could re-initialize settings here, if you
603 * wanted viewer to start the same way every time)
607 #ifdef HAVE_LCD_BITMAP
608 page_mode = NO_OVERLAP;
609 scrollbar_mode[NARROW] = SB_OFF;
610 scrollbar_mode[WIDE] = SB_ON;
612 **********************/
614 fd
= rb
->open(file
, O_RDONLY
);
618 file_size
= rb
->filesize(fd
);
622 /* Init mac_text value used in processing buffer */
625 /* Read top of file into buffer;
626 init file_pos, buffer_end, screen_top_ptr */
629 #ifdef HAVE_LCD_BITMAP
630 /* Init need_scrollbar value */
631 init_need_scrollbar();
637 static void viewer_exit(void)
642 static int col_limit(int col
)
647 if (col
> max_line_len
- 2)
648 col
= max_line_len
- 2;
653 #ifdef HAVE_LCD_BITMAP
654 static int viewer_recorder_on_button(int col
)
659 switch (rb
->button_get(true)) {
660 case BUTTON_ON
| BUTTON_F1
:
661 /* Page-overlap mode */
662 if (++page_mode
== PAGE_MODES
)
665 rb
->splash(HZ
, true, "%s %s",
666 page_mode_str
[page_mode
],
667 page_mode_str
[PAGE_MODES
]);
672 case BUTTON_ON
| BUTTON_F3
:
673 /* Show-scrollbar mode for current view-width mode */
674 if (!(ONE_SCREEN_FITS_ALL())) {
675 if (++scrollbar_mode
[view_mode
] == SCROLLBAR_MODES
)
676 scrollbar_mode
[view_mode
] = 0;
678 init_need_scrollbar();
681 rb
->splash(HZ
, true, "%s %s (%s %s)",
682 scrollbar_mode_str
[SCROLLBAR_MODES
],
683 scrollbar_mode_str
[scrollbar_mode
[view_mode
]],
684 view_mode_str
[view_mode
],
685 view_mode_str
[VIEW_MODES
]);
690 case BUTTON_ON
| BUTTON_UP
:
691 case BUTTON_ON
| BUTTON_UP
| BUTTON_REPEAT
:
692 /* Scroll up one line */
697 case BUTTON_ON
| BUTTON_DOWN
:
698 case BUTTON_ON
| BUTTON_DOWN
| BUTTON_REPEAT
:
699 /* Scroll down one line */
700 if (next_screen_ptr
!= NULL
)
701 screen_top_ptr
= next_line_ptr
;
706 case BUTTON_ON
| BUTTON_LEFT
:
707 case BUTTON_ON
| BUTTON_LEFT
| BUTTON_REPEAT
:
708 /* Scroll left one column */
710 col
= col_limit(col
);
714 case BUTTON_ON
| BUTTON_RIGHT
:
715 case BUTTON_ON
| BUTTON_RIGHT
| BUTTON_REPEAT
:
716 /* Scroll right one column */
718 col
= col_limit(col
);
722 case BUTTON_ON
| BUTTON_REL
:
723 case BUTTON_ON
| BUTTON_DOWN
| BUTTON_REL
:
724 case BUTTON_ON
| BUTTON_UP
| BUTTON_REL
:
725 /* Drop out of this loop (when ON btn released) */
734 enum plugin_status
plugin_start(struct plugin_api
* api
, void* file
)
741 TEST_PLUGIN_API(api
);
747 ok
= viewer_init(file
);
749 rb
->splash(HZ
, false, "Error");
757 switch (rb
->button_get(true)) {
758 #ifdef HAVE_RECORDER_KEYPAD
767 #ifdef HAVE_RECORDER_KEYPAD
770 case BUTTON_ON
| BUTTON_LEFT
:
772 /* Word-wrap mode: WRAP or CHOP */
773 if (++word_mode
== WORD_MODES
)
776 #ifdef HAVE_LCD_BITMAP
777 init_need_scrollbar();
781 rb
->splash(HZ
, true, "%s %s",
782 word_mode_str
[word_mode
],
783 word_mode_str
[WORD_MODES
]);
788 #ifdef HAVE_RECORDER_KEYPAD
791 case BUTTON_ON
| BUTTON_MENU
| BUTTON_RIGHT
:
793 /* Line-paragraph mode: NORMAL, JOIN or EXPAND */
794 if (++line_mode
== LINE_MODES
)
797 if (view_mode
== WIDE
)
798 if (line_mode
== JOIN
)
799 if (++line_mode
== LINE_MODES
)
802 #ifdef HAVE_LCD_BITMAP
803 init_need_scrollbar();
807 rb
->splash(HZ
, true, "%s %s",
808 line_mode_str
[line_mode
],
809 line_mode_str
[LINE_MODES
]);
814 #ifdef HAVE_RECORDER_KEYPAD
817 case BUTTON_ON
| BUTTON_RIGHT
:
819 /* View-width mode: NARROW or WIDE */
820 if (line_mode
== JOIN
)
821 rb
->splash(HZ
, true, "(no %s %s)",
823 line_mode_str
[JOIN
]);
825 if (++view_mode
== VIEW_MODES
)
830 /***** Could do this after change of word-wrap mode
831 * and after change of view-width mode, to normalize
833 if (screen_top_ptr > buffer + BUFFER_SIZE/2) {
834 screen_top_ptr = find_prev_line(screen_top_ptr);
835 screen_top_ptr = find_next_line(screen_top_ptr);
838 screen_top_ptr = find_next_line(screen_top_ptr);
839 screen_top_ptr = find_prev_line(screen_top_ptr);
843 #ifdef HAVE_LCD_BITMAP
844 init_need_scrollbar();
848 rb
->splash(HZ
, true, "%s %s",
849 view_mode_str
[view_mode
],
850 view_mode_str
[VIEW_MODES
]);
855 #ifdef HAVE_RECORDER_KEYPAD
857 case BUTTON_UP
| BUTTON_REPEAT
:
860 case BUTTON_LEFT
| BUTTON_REPEAT
:
863 #ifdef HAVE_LCD_BITMAP
864 for (i
= page_mode
==OVERLAP
? 1:0; i
< display_lines
; i
++)
866 for (i
= 0; i
< display_lines
; i
++)
873 #ifdef HAVE_RECORDER_KEYPAD
875 case BUTTON_DOWN
| BUTTON_REPEAT
:
878 case BUTTON_RIGHT
| BUTTON_REPEAT
:
881 if (next_screen_ptr
!= NULL
)
882 screen_top_ptr
= next_screen_to_draw_ptr
;
887 #ifdef HAVE_RECORDER_KEYPAD
889 case BUTTON_LEFT
| BUTTON_REPEAT
:
891 case BUTTON_MENU
| BUTTON_LEFT
:
892 case BUTTON_MENU
| BUTTON_LEFT
| BUTTON_REPEAT
:
894 if (view_mode
== WIDE
) {
896 col
-= display_columns
;
897 col
= col_limit(col
);
899 else { /* view_mode == NARROW */
907 #ifdef HAVE_RECORDER_KEYPAD
909 case BUTTON_RIGHT
| BUTTON_REPEAT
:
911 case BUTTON_MENU
| BUTTON_RIGHT
:
912 case BUTTON_MENU
| BUTTON_RIGHT
| BUTTON_REPEAT
:
914 if (view_mode
== WIDE
) {
916 col
+= display_columns
;
917 col
= col_limit(col
);
919 else { /* view_mode == NARROW */
927 #ifdef HAVE_RECORDER_KEYPAD
929 /*Go to On-btn combinations */
930 col
= viewer_recorder_on_button(col
);
934 case SYS_USB_CONNECTED
:
935 /* Release control to USB functions */
938 return PLUGIN_USB_CONNECTED
;