/*************************************************************************** * * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * * * Copyright (C) 2002 Gilles Roux, 2003 Garrett Derner * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "plugin.h" #include #include "playback_control.h" #include "oldmenuapi.h" PLUGIN_HEADER #define SETTINGS_FILE VIEWERS_DIR "/viewer.dat" /* binary file, so dont use .cfg */ #define BOOKMARKS_FILE VIEWERS_DIR "/viewer_bookmarks.dat" #define WRAP_TRIM 44 /* Max number of spaces to trim (arbitrary) */ #define MAX_COLUMNS 64 /* Max displayable string len (over-estimate) */ #define MAX_WIDTH 910 /* Max line length in WIDE mode */ #define READ_PREV_ZONE 910 /* Arbitrary number less than SMALL_BLOCK_SIZE */ #define SMALL_BLOCK_SIZE 0x1000 /* 4k: Smallest file chunk we will read */ #define LARGE_BLOCK_SIZE 0x2000 /* 8k: Preferable size of file chunk to read */ #define BUFFER_SIZE 0x3000 /* 12k: Mem reserved for buffered file data */ #define TOP_SECTOR buffer #define MID_SECTOR (buffer + SMALL_BLOCK_SIZE) #define BOTTOM_SECTOR (buffer + 2*(SMALL_BLOCK_SIZE)) #define SCROLLBAR_WIDTH 6 #define MAX_BOOKMARKED_FILES (((signed)BUFFER_SIZE/(signed)sizeof(struct bookmarked_file_info))-1) /* Out-Of-Bounds test for any pointer to data in the buffer */ #define BUFFER_OOB(p) ((p) < buffer || (p) >= buffer_end) /* Does the buffer contain the beginning of the file? */ #define BUFFER_BOF() (file_pos==0) /* Does the buffer contain the end of the file? */ #define BUFFER_EOF() (file_size-file_pos <= BUFFER_SIZE) /* Formula for the endpoint address outside of buffer data */ #define BUFFER_END() \ ((BUFFER_EOF()) ? (file_size-file_pos+buffer) : (buffer+BUFFER_SIZE)) /* Is the entire file being shown in one screen? */ #define ONE_SCREEN_FITS_ALL() \ (next_screen_ptr==NULL && screen_top_ptr==buffer && BUFFER_BOF()) /* Is a scrollbar called for on the current screen? */ #define NEED_SCROLLBAR() \ ((!(ONE_SCREEN_FITS_ALL())) && (prefs.scrollbar_mode==SB_ON)) /* variable button definitions */ /* Recorder keys */ #if CONFIG_KEYPAD == RECORDER_PAD #define VIEWER_QUIT BUTTON_OFF #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_F1 #define VIEWER_AUTOSCROLL BUTTON_PLAY #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP) #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN) #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT) #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT) #elif CONFIG_KEYPAD == ARCHOS_AV300_PAD #define VIEWER_QUIT BUTTON_OFF #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_F1 #define VIEWER_AUTOSCROLL BUTTON_SELECT #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP) #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN) #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT) #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT) /* Ondio keys */ #elif CONFIG_KEYPAD == ONDIO_PAD #define VIEWER_QUIT BUTTON_OFF #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU (BUTTON_MENU|BUTTON_REPEAT) #define VIEWER_AUTOSCROLL_PRE BUTTON_MENU #define VIEWER_AUTOSCROLL (BUTTON_MENU|BUTTON_REL) /* Player keys */ #elif CONFIG_KEYPAD == PLAYER_PAD #define VIEWER_QUIT BUTTON_STOP #define VIEWER_PAGE_UP BUTTON_LEFT #define VIEWER_PAGE_DOWN BUTTON_RIGHT #define VIEWER_SCREEN_LEFT (BUTTON_ON|BUTTON_LEFT) #define VIEWER_SCREEN_RIGHT (BUTTON_ON|BUTTON_RIGHT) #define VIEWER_MENU BUTTON_MENU #define VIEWER_AUTOSCROLL BUTTON_PLAY /* iRiver H1x0 && H3x0 keys */ #elif (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ (CONFIG_KEYPAD == IRIVER_H300_PAD) #define VIEWER_QUIT BUTTON_OFF #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_MODE #define VIEWER_AUTOSCROLL BUTTON_SELECT #define VIEWER_LINE_UP (BUTTON_ON | BUTTON_UP) #define VIEWER_LINE_DOWN (BUTTON_ON | BUTTON_DOWN) #define VIEWER_COLUMN_LEFT (BUTTON_ON | BUTTON_LEFT) #define VIEWER_COLUMN_RIGHT (BUTTON_ON | BUTTON_RIGHT) #define VIEWER_RC_QUIT BUTTON_RC_STOP /* iPods */ #elif (CONFIG_KEYPAD == IPOD_4G_PAD) || \ (CONFIG_KEYPAD == IPOD_3G_PAD) || \ (CONFIG_KEYPAD == IPOD_1G2G_PAD) #define VIEWER_QUIT_PRE BUTTON_SELECT #define VIEWER_QUIT (BUTTON_SELECT | BUTTON_MENU) #define VIEWER_PAGE_UP BUTTON_SCROLL_BACK #define VIEWER_PAGE_DOWN BUTTON_SCROLL_FWD #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_MENU #define VIEWER_AUTOSCROLL BUTTON_PLAY /* iFP7xx keys */ #elif CONFIG_KEYPAD == IRIVER_IFP7XX_PAD #define VIEWER_QUIT BUTTON_PLAY #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_MODE #define VIEWER_AUTOSCROLL BUTTON_SELECT /* iAudio X5 keys */ #elif CONFIG_KEYPAD == IAUDIO_X5M5_PAD #define VIEWER_QUIT BUTTON_POWER #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_SELECT #define VIEWER_AUTOSCROLL BUTTON_PLAY /* GIGABEAT keys */ #elif CONFIG_KEYPAD == GIGABEAT_PAD #define VIEWER_QUIT BUTTON_POWER #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_MENU #define VIEWER_AUTOSCROLL BUTTON_A /* Sansa E200 keys */ #elif CONFIG_KEYPAD == SANSA_E200_PAD #define VIEWER_QUIT BUTTON_POWER #define VIEWER_PAGE_UP BUTTON_UP #define VIEWER_PAGE_DOWN BUTTON_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_SELECT #define VIEWER_AUTOSCROLL BUTTON_REC #define VIEWER_LINE_UP BUTTON_SCROLL_UP #define VIEWER_LINE_DOWN BUTTON_SCROLL_DOWN /* Sansa C200 keys */ #elif CONFIG_KEYPAD == SANSA_C200_PAD #define VIEWER_QUIT BUTTON_POWER #define VIEWER_PAGE_UP BUTTON_VOL_UP #define VIEWER_PAGE_DOWN BUTTON_VOL_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_SELECT #define VIEWER_AUTOSCROLL BUTTON_REC #define VIEWER_LINE_UP BUTTON_UP #define VIEWER_LINE_DOWN BUTTON_DOWN /* iriver H10 keys */ #elif CONFIG_KEYPAD == IRIVER_H10_PAD #define VIEWER_QUIT BUTTON_POWER #define VIEWER_PAGE_UP BUTTON_SCROLL_UP #define VIEWER_PAGE_DOWN BUTTON_SCROLL_DOWN #define VIEWER_SCREEN_LEFT BUTTON_LEFT #define VIEWER_SCREEN_RIGHT BUTTON_RIGHT #define VIEWER_MENU BUTTON_REW #define VIEWER_AUTOSCROLL BUTTON_PLAY #endif /* stuff for the bookmarking */ struct bookmarked_file_info { long file_position; int top_ptr_pos; char filename[MAX_PATH]; }; struct bookmark_file_data { signed int bookmarked_files_count; struct bookmarked_file_info bookmarks[]; }; struct preferences { enum { WRAP=0, CHOP, } word_mode; enum { NORMAL=0, JOIN, EXPAND, REFLOW, /* won't be set on charcell LCD, must be last */ } line_mode; enum { NARROW=0, WIDE, } view_mode; enum { ISO_8859_1=0, ISO_8859_7, ISO_8859_8, CP1251, ISO_8859_11, ISO_8859_6, ISO_8859_9, ISO_8859_2, CP1250, SJIS, GB2312, KSX1001, BIG5, UTF8, ENCODINGS } encoding; /* FIXME: What should default encoding be? */ #ifdef HAVE_LCD_BITMAP enum { SB_OFF=0, SB_ON, } scrollbar_mode; bool need_scrollbar; enum { NO_OVERLAP=0, OVERLAP, } page_mode; #endif /* HAVE_LCD_BITMAP */ enum { PAGE=0, LINE, } scroll_mode; int autoscroll_speed; } prefs; static unsigned char buffer[BUFFER_SIZE + 1]; static unsigned char line_break[] = {0,0x20,9,0xB,0xC,'-'}; static int display_columns; /* number of (pixel) columns on the display */ static int display_lines; /* number of lines on the display */ static int draw_columns; /* number of (pixel) columns available for text */ static int par_indent_spaces; /* number of spaces to indent first paragraph */ static int fd; static char *file_name; static long file_size; static bool mac_text; static long file_pos; /* Position of the top of the buffer in the file */ static unsigned char *buffer_end; /*Set to BUFFER_END() when file_pos changes*/ static int max_line_len; static unsigned char *screen_top_ptr; static unsigned char *next_screen_ptr; static unsigned char *next_screen_to_draw_ptr; static unsigned char *next_line_ptr; static struct plugin_api* rb; #ifdef HAVE_LCD_BITMAP static struct font *pf; #endif int glyph_width(int ch) { if (ch == 0) ch = ' '; #ifdef HAVE_LCD_BITMAP return rb->font_get_width(pf, ch); #else return 1; #endif } unsigned char* get_ucs(const unsigned char* str, unsigned short* ch) { unsigned char utf8_tmp[6]; int count; if (prefs.encoding == UTF8) return (unsigned char*)rb->utf8decode(str, ch); count = BUFFER_OOB(str+2)? 1:2; rb->iso_decode(str, utf8_tmp, prefs.encoding, count); rb->utf8decode(utf8_tmp, ch); if ((prefs.encoding == SJIS && *str > 0xA0 && *str < 0xE0) || prefs.encoding < SJIS) return (unsigned char*)str+1; else return (unsigned char*)str+2; } bool done = false; int col = 0; #define ADVANCE_COUNTERS(c) { width += glyph_width(c); k++; } #define LINE_IS_FULL ((k>=MAX_COLUMNS-1) ||( width >= draw_columns)) #define LINE_IS_NOT_FULL ((k=0; i--) if (p[i] == 0) return (unsigned char*) p+i; return NULL; } static unsigned char* find_last_space(const unsigned char* p, int size) { int i, j, k; k = (prefs.line_mode==JOIN) || (prefs.line_mode==REFLOW) ? 0:1; if (!BUFFER_OOB(&p[size])) for (j=k; j < ((int) sizeof(line_break)) - 1; j++) if (p[size] == line_break[j]) return (unsigned char*) p+size; for (i=size-1; i>=0; i--) for (j=k; j < (int) sizeof(line_break); j++) { if (!((p[i] == '-') && (prefs.word_mode == WRAP))) if (p[i] == line_break[j]) return (unsigned char*) p+i; } return NULL; } static unsigned char* find_next_line(const unsigned char* cur_line, bool *is_short) { const unsigned char *next_line = NULL; int size, i, j, k, width, search_len, spaces, newlines; bool first_chars; unsigned char c; if (is_short != NULL) *is_short = true; if BUFFER_OOB(cur_line) return NULL; if (prefs.view_mode == WIDE) { search_len = MAX_WIDTH; } else { /* prefs.view_mode == NARROW */ search_len = crop_at_width(cur_line) - cur_line; } size = BUFFER_OOB(cur_line+search_len) ? buffer_end-cur_line : search_len; if ((prefs.line_mode == JOIN) || (prefs.line_mode == REFLOW)) { /* Need to scan ahead and possibly increase search_len and size, or possibly set next_line at second hard return in a row. */ next_line = NULL; first_chars=true; for (j=k=width=spaces=newlines=0; ; j++) { if (BUFFER_OOB(cur_line+j)) return NULL; if (LINE_IS_FULL) { size = search_len = j; break; } c = cur_line[j]; switch (c) { case ' ': if (prefs.line_mode == REFLOW) { if (newlines > 0) { size = j; next_line = cur_line + size; return (unsigned char*) next_line; } if (j==0) /* i=1 is intentional */ for (i=0; i 0) { size = j; next_line = cur_line + size - spaces; if (next_line != cur_line) return (unsigned char*) next_line; break; } newlines++; size += spaces -1; if (BUFFER_OOB(cur_line+size) || size > 2*search_len) return NULL; search_len = size; spaces = first_chars? 0:1; break; default: if (prefs.line_mode==JOIN || newlines>0) { while (spaces) { spaces--; ADVANCE_COUNTERS(' '); if (LINE_IS_FULL) { size = search_len = j; break; } } newlines=0; } else if (spaces) { /* REFLOW, multiple spaces between words: count only * one. If more are needed, they will be added * while drawing. */ search_len = size; spaces=0; ADVANCE_COUNTERS(' '); if (LINE_IS_FULL) { size = search_len = j; break; } } first_chars = false; ADVANCE_COUNTERS(c); break; } } } else { /* find first hard return */ next_line = find_first_feed(cur_line, size); } if (next_line == NULL) if (size == search_len) { if (prefs.word_mode == WRAP) /* Find last space */ next_line = find_last_space(cur_line, size); if (next_line == NULL) next_line = crop_at_width(cur_line); else if (prefs.word_mode == WRAP) for (i=0; i READ_PREV_ZONE) prev_line = p = buffer; /* (else return NULL and read previous block) */ /* Wrap downwards until too far, then use the one before. */ while (p < cur_line && p != NULL) { prev_line = p; p = find_next_line(prev_line, NULL); } if (BUFFER_OOB(prev_line)) return NULL; return (unsigned char*) prev_line; } static void fill_buffer(long pos, unsigned char* buf, unsigned size) { /* Read from file and preprocess the data */ /* To minimize disk access, always read on sector boundaries */ unsigned numread, i; bool found_CR = false; rb->lseek(fd, pos, SEEK_SET); numread = rb->read(fd, buf, size); rb->button_clear_queue(); /* clear button queue */ for(i = 0; i < numread; i++) { switch(buf[i]) { case '\r': if (mac_text) { buf[i] = 0; } else { buf[i] = ' '; found_CR = true; } break; case '\n': buf[i] = 0; found_CR = false; break; case 0: /* No break between case 0 and default, intentionally */ buf[i] = ' '; default: if (found_CR) { buf[i - 1] = 0; found_CR = false; mac_text = true; } break; } } } static int read_and_synch(int direction) { /* Read next (or prev) block, and reposition global pointers. */ /* direction: 1 for down (i.e., further into file), -1 for up */ int move_size, move_vector, offset; unsigned char *fill_buf; if (direction == -1) /* up */ { move_size = SMALL_BLOCK_SIZE; offset = 0; fill_buf = TOP_SECTOR; rb->memcpy(BOTTOM_SECTOR, MID_SECTOR, SMALL_BLOCK_SIZE); rb->memcpy(MID_SECTOR, TOP_SECTOR, SMALL_BLOCK_SIZE); } else /* down */ { if (prefs.view_mode == WIDE) { /* WIDE mode needs more buffer so we have to read smaller blocks */ move_size = SMALL_BLOCK_SIZE; offset = LARGE_BLOCK_SIZE; fill_buf = BOTTOM_SECTOR; rb->memcpy(TOP_SECTOR, MID_SECTOR, SMALL_BLOCK_SIZE); rb->memcpy(MID_SECTOR, BOTTOM_SECTOR, SMALL_BLOCK_SIZE); } else { move_size = LARGE_BLOCK_SIZE; offset = SMALL_BLOCK_SIZE; fill_buf = MID_SECTOR; rb->memcpy(TOP_SECTOR, BOTTOM_SECTOR, SMALL_BLOCK_SIZE); } } move_vector = direction * move_size; screen_top_ptr -= move_vector; file_pos += move_vector; buffer_end = BUFFER_END(); /* Update whenever file_pos changes */ fill_buffer(file_pos + offset, fill_buf, move_size); return move_vector; } static void viewer_scroll_up(void) { unsigned char *p; p = find_prev_line(screen_top_ptr); if (p == NULL && !BUFFER_BOF()) { read_and_synch(-1); p = find_prev_line(screen_top_ptr); } if (p != NULL) screen_top_ptr = p; } static void viewer_scroll_down(void) { if (next_screen_ptr != NULL) screen_top_ptr = next_line_ptr; } #ifdef HAVE_LCD_BITMAP static void viewer_scrollbar(void) { int items, min_shown, max_shown; items = (int) file_size; /* (SH1 int is same as long) */ min_shown = (int) file_pos + (screen_top_ptr - buffer); if (next_screen_ptr == NULL) max_shown = items; else max_shown = min_shown + (next_screen_ptr - screen_top_ptr); rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN],0, 0, SCROLLBAR_WIDTH-1, LCD_HEIGHT, items, min_shown, max_shown, VERTICAL); } #endif static void viewer_draw(int col) { int i, j, k, line_len, line_width, resynch_move, spaces, left_col=0; int width, extra_spaces, indent_spaces, spaces_per_word; bool multiple_spacing, line_is_short; unsigned short ch; unsigned char *str, *oldstr; unsigned char *line_begin; unsigned char *line_end; unsigned char c; unsigned char scratch_buffer[MAX_COLUMNS + 1]; unsigned char utf8_buffer[MAX_COLUMNS*4 + 1]; unsigned char *endptr; /* If col==-1 do all calculations but don't display */ if (col != -1) { #ifdef HAVE_LCD_BITMAP left_col = prefs.need_scrollbar? SCROLLBAR_WIDTH:0; #else left_col = 0; #endif rb->lcd_clear_display(); } max_line_len = 0; line_begin = line_end = screen_top_ptr; for (i = 0; i < display_lines; i++) { if (BUFFER_OOB(line_end)) break; /* Happens after display last line at BUFFER_EOF() */ line_begin = line_end; line_end = find_next_line(line_begin, &line_is_short); if (line_end == NULL) { if (BUFFER_EOF()) { if (i < display_lines - 1 && !BUFFER_BOF()) { if (col != -1) rb->lcd_clear_display(); for (; i < display_lines - 1; i++) viewer_scroll_up(); line_begin = line_end = screen_top_ptr; i = -1; continue; } else { line_end = buffer_end; } } else { resynch_move = read_and_synch(1); /* Read block & move ptrs */ line_begin -= resynch_move; if (i > 0) next_line_ptr -= resynch_move; line_end = find_next_line(line_begin, NULL); if (line_end == NULL) /* Should not really happen */ break; } } line_len = line_end - line_begin; /* calculate line_len */ str = oldstr = line_begin; j = -1; while (str < line_end) { oldstr = str; str = crop_at_width(str); j++; } line_width = j*draw_columns; while (oldstr < line_end) { oldstr = get_ucs(oldstr, &ch); line_width += glyph_width(ch); } if (prefs.line_mode == JOIN) { if (line_begin[0] == 0) { line_begin++; if (prefs.word_mode == CHOP) line_end++; else line_len--; } for (j=k=spaces=0; j < line_len; j++) { if (k == MAX_COLUMNS) break; c = line_begin[j]; switch (c) { case ' ': spaces++; break; case 0: spaces = 0; scratch_buffer[k++] = ' '; break; default: while (spaces) { spaces--; scratch_buffer[k++] = ' '; if (k == MAX_COLUMNS - 1) break; } scratch_buffer[k++] = c; break; } } if (col != -1) { scratch_buffer[k] = 0; endptr = rb->iso_decode(scratch_buffer + col, utf8_buffer, prefs.encoding, draw_columns/glyph_width('i')); *endptr = 0; } } else if (prefs.line_mode == REFLOW) { if (line_begin[0] == 0) { line_begin++; if (prefs.word_mode == CHOP) line_end++; else line_len--; } indent_spaces = 0; if (!line_is_short) { multiple_spacing = false; width=spaces=0; for (str = line_begin; str < line_end; ) { str = get_ucs(str, &ch); switch (ch) { case ' ': case 0: if ((str == line_begin) && (prefs.word_mode==WRAP)) /* special case: indent the paragraph, * don't count spaces */ indent_spaces = par_indent_spaces; else if (!multiple_spacing) spaces++; multiple_spacing = true; break; default: multiple_spacing = false; width += glyph_width(ch); break; } } if (multiple_spacing) spaces--; if (spaces) { /* total number of spaces to insert between words */ extra_spaces = (draw_columns-width)/glyph_width(' ') - indent_spaces; /* number of spaces between each word*/ spaces_per_word = extra_spaces / spaces; /* number of words with n+1 spaces (to fill up) */ extra_spaces = extra_spaces % spaces; if (spaces_per_word > 2) { /* too much spacing is awful */ spaces_per_word = 3; extra_spaces = 0; } } else { /* this doesn't matter much... no spaces anyway */ spaces_per_word = extra_spaces = 0; } } else { /* end of a paragraph: don't fill line */ spaces_per_word = 1; extra_spaces = 0; } multiple_spacing = false; for (j=k=spaces=0; j < line_len; j++) { if (k == MAX_COLUMNS) break; c = line_begin[j]; switch (c) { case ' ': case 0: if (j==0 && prefs.word_mode==WRAP) { /* indent paragraph */ for (j=0; jiso_decode(scratch_buffer + col, utf8_buffer, prefs.encoding, k-col); *endptr = 0; } } else { /* prefs.line_mode != JOIN && prefs.line_mode != REFLOW */ if (col != -1) if (line_width > col) { str = oldstr = line_begin; k = col; width = 0; while( (width 0) { k -= glyph_width(ch); line_begin = oldstr; } else { width += glyph_width(ch); } } if(prefs.view_mode==WIDE) endptr = rb->iso_decode(line_begin, utf8_buffer, prefs.encoding, oldstr-line_begin); else endptr = rb->iso_decode(line_begin, utf8_buffer, prefs.encoding, line_end-line_begin); *endptr = 0; } } if (col != -1 && line_width > col) #ifdef HAVE_LCD_BITMAP rb->lcd_putsxy(left_col, i*pf->height, utf8_buffer); #else rb->lcd_puts(left_col, i, utf8_buffer); #endif if (line_width > max_line_len) max_line_len = line_width; if (i == 0) next_line_ptr = line_end; } next_screen_ptr = line_end; if (BUFFER_OOB(next_screen_ptr)) next_screen_ptr = NULL; #ifdef HAVE_LCD_BITMAP next_screen_to_draw_ptr = prefs.page_mode==OVERLAP? line_begin: next_screen_ptr; if (prefs.need_scrollbar) viewer_scrollbar(); #else next_screen_to_draw_ptr = next_screen_ptr; #endif if (col != -1) rb->lcd_update(); } static void viewer_top(void) { /* Read top of file into buffer and point screen pointer to top */ file_pos = 0; buffer_end = BUFFER_END(); /* Update whenever file_pos changes */ screen_top_ptr = buffer; fill_buffer(0, buffer, BUFFER_SIZE); } static void viewer_bottom(void) { /* Read bottom of file into buffer and point screen pointer to bottom */ long last_sectors; if (file_size > BUFFER_SIZE) { /* Find last buffer in file, round up to next sector boundary */ last_sectors = file_size - BUFFER_SIZE + SMALL_BLOCK_SIZE; last_sectors /= SMALL_BLOCK_SIZE; last_sectors *= SMALL_BLOCK_SIZE; } else { last_sectors = 0; } file_pos = last_sectors; buffer_end = BUFFER_END(); /* Update whenever file_pos changes */ screen_top_ptr = buffer_end-1; fill_buffer(last_sectors, buffer, BUFFER_SIZE); } #ifdef HAVE_LCD_BITMAP static void init_need_scrollbar(void) { /* Call viewer_draw in quiet mode to initialize next_screen_ptr, and thus ONE_SCREEN_FITS_ALL(), and thus NEED_SCROLLBAR() */ viewer_draw(-1); prefs.need_scrollbar = NEED_SCROLLBAR(); draw_columns = prefs.need_scrollbar? display_columns-SCROLLBAR_WIDTH : display_columns; par_indent_spaces = draw_columns/(5*glyph_width(' ')); } #else #define init_need_scrollbar() #endif static bool viewer_init(void) { #ifdef HAVE_LCD_BITMAP pf = rb->font_get(FONT_UI); display_lines = LCD_HEIGHT / pf->height; draw_columns = display_columns = LCD_WIDTH; #else /* REAL fixed pitch :) all chars use up 1 cell */ display_lines = 2; draw_columns = display_columns = 11; par_indent_spaces = 2; #endif fd = rb->open(file_name, O_RDONLY); if (fd==-1) return false; file_size = rb->filesize(fd); if (file_size==-1) return false; /* Init mac_text value used in processing buffer */ mac_text = false; /* Set codepage to system default */ prefs.encoding = rb->global_settings->default_codepage; /* Read top of file into buffer; init file_pos, buffer_end, screen_top_ptr */ viewer_top(); /* Init prefs.need_scrollbar value */ init_need_scrollbar(); return true; } static void viewer_reset_settings(void) { prefs.word_mode = WRAP; prefs.line_mode = NORMAL; prefs.view_mode = NARROW; prefs.scroll_mode = PAGE; #ifdef HAVE_LCD_BITMAP prefs.page_mode = NO_OVERLAP; prefs.scrollbar_mode = SB_OFF; #endif prefs.autoscroll_speed = 1; } static void viewer_load_settings(void) /* same name as global, but not the same file.. */ { int settings_fd, i; struct bookmark_file_data *data; /* read settings file */ settings_fd=rb->open(SETTINGS_FILE, O_RDONLY); if ((settings_fd < 0) || (rb->filesize(settings_fd) != sizeof(struct preferences))) { rb->splash(HZ*2, "No Valid Settings File"); } else { rb->read(settings_fd, &prefs, sizeof(struct preferences)); rb->close(settings_fd); } data = (struct bookmark_file_data*)buffer; /* grab the text buffer */ data->bookmarked_files_count = 0; /* read bookmarks if file exists */ settings_fd = rb->open(BOOKMARKS_FILE, O_RDONLY); if (settings_fd >= 0) { /* figure out how many items to read */ rb->read(settings_fd, &data->bookmarked_files_count, sizeof(signed int)); if (data->bookmarked_files_count > MAX_BOOKMARKED_FILES) data->bookmarked_files_count = MAX_BOOKMARKED_FILES; rb->read(settings_fd, data->bookmarks, sizeof(struct bookmarked_file_info) * data->bookmarked_files_count); rb->close(settings_fd); } /* check if current file is in list */ for (i=0; i < data->bookmarked_files_count; i++) { if (!rb->strcmp(file_name, data->bookmarks[i].filename)) { file_pos = data->bookmarks[i].file_position; screen_top_ptr = buffer + data->bookmarks[i].top_ptr_pos; break; } } /* prevent potential slot overflow */ if (i >= data->bookmarked_files_count) { if (i < MAX_BOOKMARKED_FILES) data->bookmarked_files_count++; else i = MAX_BOOKMARKED_FILES-1; } /* write bookmark file with spare slot in first position to be filled in by viewer_save_settings */ settings_fd = rb->open(BOOKMARKS_FILE, O_WRONLY|O_CREAT); if (settings_fd >=0 ) { /* write count and skip first slot */ rb->write (settings_fd, &data->bookmarked_files_count, sizeof(signed int)); rb->PREFIX(lseek)(settings_fd,sizeof(struct bookmarked_file_info),SEEK_CUR); /* shuffle up bookmarks */ rb->write (settings_fd, data->bookmarks, sizeof(struct bookmarked_file_info)*i); rb->close(settings_fd); } init_need_scrollbar(); buffer_end = BUFFER_END(); /* Update whenever file_pos changes */ if (BUFFER_OOB(screen_top_ptr)) { screen_top_ptr = buffer; } fill_buffer(file_pos, buffer, BUFFER_SIZE); } static void viewer_save_settings(void)/* same name as global, but not the same file.. */ { int settings_fd; rb->splash(1, "Saving Settings"); settings_fd = rb->creat(SETTINGS_FILE); /* create the settings file */ rb->write (settings_fd, &prefs, sizeof(struct preferences)); rb->close(settings_fd); settings_fd = rb->open(BOOKMARKS_FILE, O_WRONLY|O_CREAT); if (settings_fd >= 0 ) { struct bookmarked_file_info b; b.file_position = file_pos; b.top_ptr_pos = screen_top_ptr - buffer; rb->memset(&b.filename[0],0,MAX_PATH); rb->strcpy(b.filename,file_name); rb->PREFIX(lseek)(settings_fd,sizeof(signed int),SEEK_SET); rb->write (settings_fd, &b, sizeof(struct bookmarked_file_info)); rb->close(settings_fd); } } static void viewer_exit(void *parameter) { (void)parameter; viewer_save_settings(); rb->close(fd); } static int col_limit(int col) { if (col < 0) col = 0; else if (col > max_line_len - 2*glyph_width('o')) col = max_line_len - 2*glyph_width('o'); return col; } /* settings helper functions */ static bool encoding_setting(void) { static const struct opt_items names[] = { {"ISO-8859-1", -1}, {"ISO-8859-7", -1}, {"ISO-8859-8", -1}, {"CP1251", -1}, {"ISO-8859-11", -1}, {"ISO-8859-6", -1}, {"ISO-8859-9", -1}, {"ISO-8859-2", -1}, {"CP1250", -1}, {"SJIS", -1}, {"GB-2312", -1}, {"KSX-1001", -1}, {"BIG5", -1}, {"UTF-8", -1}, }; return rb->set_option("Encoding", &prefs.encoding, INT, names, sizeof(names) / sizeof(names[0]), NULL); } static bool word_wrap_setting(void) { static const struct opt_items names[] = { {"On", -1}, {"Off (Chop Words)", -1}, }; return rb->set_option("Word Wrap", &prefs.word_mode, INT, names, 2, NULL); } static bool line_mode_setting(void) { static const struct opt_items names[] = { {"Normal", -1}, {"Join Lines", -1}, {"Expand Lines", -1}, #ifdef HAVE_LCD_BITMAP {"Reflow Lines", -1}, #endif }; return rb->set_option("Line Mode", &prefs.line_mode, INT, names, sizeof(names) / sizeof(names[0]), NULL); } static bool view_mode_setting(void) { static const struct opt_items names[] = { {"No (Narrow)", -1}, {"Yes", -1}, }; bool ret; ret = rb->set_option("Wide View", &prefs.view_mode, INT, names , 2, NULL); if (prefs.view_mode == NARROW) col = 0; return ret; } static bool scroll_mode_setting(void) { static const struct opt_items names[] = { {"Scroll by Page", -1}, {"Scroll by Line", -1}, }; return rb->set_option("Scroll Mode", &prefs.scroll_mode, INT, names, 2, NULL); } #ifdef HAVE_LCD_BITMAP static bool page_mode_setting(void) { static const struct opt_items names[] = { {"No", -1}, {"Yes", -1}, }; return rb->set_option("Overlap Pages", &prefs.page_mode, INT, names, 2, NULL); } static bool scrollbar_setting(void) { static const struct opt_items names[] = { {"Off", -1}, {"On", -1} }; return rb->set_option("Show Scrollbar", &prefs.scrollbar_mode, INT, names, 2, NULL); } #endif static bool autoscroll_speed_setting(void) { return rb->set_int("Auto-scroll Speed", "", UNIT_INT, &prefs.autoscroll_speed, NULL, 1, 1, 10, NULL); } static bool viewer_options_menu(void) { int m; bool result; static const struct menu_item items[] = { {"Encoding", encoding_setting }, {"Word Wrap", word_wrap_setting }, {"Line Mode", line_mode_setting }, {"Wide View", view_mode_setting }, #ifdef HAVE_LCD_BITMAP {"Show Scrollbar", scrollbar_setting }, {"Overlap Pages", page_mode_setting }, #endif {"Scroll Mode", scroll_mode_setting}, {"Auto-Scroll Speed", autoscroll_speed_setting }, }; m = menu_init(rb, items, sizeof(items) / sizeof(*items), NULL, NULL, NULL, NULL); result = menu_run(m); menu_exit(m); #ifdef HAVE_LCD_BITMAP rb->lcd_setmargins(0,0); /* Show-scrollbar mode for current view-width mode */ if (!ONE_SCREEN_FITS_ALL()) if (prefs.scrollbar_mode == true) init_need_scrollbar(); #endif return result; } static void viewer_menu(void) { int m; int result; static const struct menu_item items[] = { {"Quit", NULL }, {"Viewer Options", NULL }, {"Show Playback Menu", NULL }, {"Return", NULL }, }; m = menu_init(rb, items, sizeof(items) / sizeof(*items), NULL, NULL, NULL, NULL); result=menu_show(m); switch (result) { case 0: /* quit */ menu_exit(m); viewer_exit(NULL); done = true; break; case 1: /* change settings */ done = viewer_options_menu(); break; case 2: /* playback control */ playback_control(rb); break; case 3: /* return */ break; } menu_exit(m); #ifdef HAVE_LCD_BITMAP rb->lcd_setmargins(0,0); #endif viewer_draw(col); } enum plugin_status plugin_start(struct plugin_api* api, void* file) { int button, i, ok; int lastbutton = BUTTON_NONE; bool autoscroll = false; long old_tick; rb = api; old_tick = *rb->current_tick; if (!file) return PLUGIN_ERROR; file_name = file; ok = viewer_init(); if (!ok) { rb->splash(HZ, "Error opening file."); return PLUGIN_ERROR; } viewer_reset_settings(); /* load defaults first */ viewer_load_settings(); /* .. then try to load from disk */ #if LCD_DEPTH > 1 rb->lcd_set_backdrop(NULL); #endif viewer_draw(col); while (!done) { if(autoscroll) { if(old_tick <= *rb->current_tick - (110-prefs.autoscroll_speed*10)) { viewer_scroll_down(); viewer_draw(col); old_tick = *rb->current_tick; } } button = rb->button_get_w_tmo(HZ/10); switch (button) { case VIEWER_MENU: viewer_menu(); break; case VIEWER_AUTOSCROLL: #ifdef VIEWER_AUTOSCROLL_PRE if (lastbutton != VIEWER_AUTOSCROLL_PRE) break; #endif autoscroll = !autoscroll; break; case VIEWER_PAGE_UP: case VIEWER_PAGE_UP | BUTTON_REPEAT: if (prefs.scroll_mode == PAGE) { /* Page up */ #ifdef HAVE_LCD_BITMAP for (i = prefs.page_mode==OVERLAP? 1:0; i < display_lines; i++) #else for (i = 0; i < display_lines; i++) #endif viewer_scroll_up(); } else viewer_scroll_up(); old_tick = *rb->current_tick; viewer_draw(col); break; case VIEWER_PAGE_DOWN: case VIEWER_PAGE_DOWN | BUTTON_REPEAT: if (prefs.scroll_mode == PAGE) { /* Page down */ if (next_screen_ptr != NULL) screen_top_ptr = next_screen_to_draw_ptr; } else viewer_scroll_down(); old_tick = *rb->current_tick; viewer_draw(col); break; case VIEWER_SCREEN_LEFT: case VIEWER_SCREEN_LEFT | BUTTON_REPEAT: if (prefs.view_mode == WIDE) { /* Screen left */ col -= draw_columns; col = col_limit(col); } else { /* prefs.view_mode == NARROW */ /* Top of file */ viewer_top(); } viewer_draw(col); break; case VIEWER_SCREEN_RIGHT: case VIEWER_SCREEN_RIGHT | BUTTON_REPEAT: if (prefs.view_mode == WIDE) { /* Screen right */ col += draw_columns; col = col_limit(col); } else { /* prefs.view_mode == NARROW */ /* Bottom of file */ viewer_bottom(); } viewer_draw(col); break; #ifdef VIEWER_LINE_UP case VIEWER_LINE_UP: case VIEWER_LINE_UP | BUTTON_REPEAT: /* Scroll up one line */ viewer_scroll_up(); old_tick = *rb->current_tick; viewer_draw(col); break; case VIEWER_LINE_DOWN: case VIEWER_LINE_DOWN | BUTTON_REPEAT: /* Scroll down one line */ if (next_screen_ptr != NULL) screen_top_ptr = next_line_ptr; old_tick = *rb->current_tick; viewer_draw(col); break; #endif #ifdef VIEWER_COLUMN_LEFT case VIEWER_COLUMN_LEFT: case VIEWER_COLUMN_LEFT | BUTTON_REPEAT: if (prefs.view_mode == WIDE) { /* Scroll left one column */ col -= glyph_width('o'); col = col_limit(col); viewer_draw(col); } break; case VIEWER_COLUMN_RIGHT: case VIEWER_COLUMN_RIGHT | BUTTON_REPEAT: if (prefs.view_mode == WIDE) { /* Scroll right one column */ col += glyph_width('o'); col = col_limit(col); viewer_draw(col); } break; #endif #ifdef VIEWER_RC_QUIT case VIEWER_RC_QUIT: #endif case VIEWER_QUIT: viewer_exit(NULL); done = true; break; default: if (rb->default_event_handler_ex(button, viewer_exit, NULL) == SYS_USB_CONNECTED) return PLUGIN_USB_CONNECTED; break; } if (button != BUTTON_NONE) { lastbutton = button; rb->yield(); } } return PLUGIN_OK; }