Index: apps/lang/english.lang =================================================================== --- apps/lang/english.lang (revision 25263) +++ apps/lang/english.lang (working copy) @@ -11028,6 +11028,20 @@ + id: LANG_DICTIONARIES + desc: in the main menu + user: core + + *: "Dictionaries" + + + *: "Dictionaries" + + + *: "Dictionaries" + + + id: VOICE_OF desc: spoken only, as in 3/8 => 3 of 8 user: core Index: apps/plugins/dict2.c =================================================================== --- apps/plugins/dict2.c (Revision 0) +++ apps/plugins/dict2.c (Revision 0) @@ -0,0 +1,966 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id: dict.c 14250 2007-08-08 23:32:35Z peter $ + * + * Copyright (C) 2005 Tomas Salfischberger, 2006 Timo Horstschäfer + * + * 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 "lib/pluginlib_actions.h" +#include "lib/configfile.h" +#include "lib/playback_control.h" +#include "lib/viewer.h" + +#include + +PLUGIN_HEADER + + +#define CONFIG_VERSION 3 +#define CACHE_VERSION 2 + +//! Global dict variables +static struct dict_s { + char path[255]; /**< Path of current dictionary */ + char name[255]; /**< Basename of current dictionary */ + + int DataLen; + + int fCache; + int fIndex; + int fDict; + //! Current description file (there may be more than one) + int fDict_n; + + //! Alphabet offset table + int32_t charoft[26]; +} Dict; + +#define DESC_BUFFER_ADD 512 + +#define OFT_DIR ROCKBOX_DIR + +#define CONFIG_FILENAME "dict2.cfg" +#define CONFIG_ITEMS ( sizeof(Conf_data) / sizeof (struct configdata) ) +struct conf_s { + int max_list; /**< Max number of articles listed */ + int viewer_scroll; /**< Number of lines to scroll */ + int viewer_backlight; + int viewer_shortcut; +} Conf; + +struct configdata Conf_data[] = { + { TYPE_INT, 1, 50, { .int_p = &Conf.max_list }, "max_list", NULL }, + { TYPE_INT, 0, 4, { .int_p = &Conf.viewer_scroll }, "scroll", NULL }, + { TYPE_INT, 0, 2, { .int_p = &Conf.viewer_backlight }, "backlight", NULL }, + { TYPE_INT, 0, 2, { .int_p = &Conf.viewer_shortcut }, "shortcut", NULL }, +}; + +#define MAX_LIST_DEFAULT 50 + +//! Malloc variables +static struct malloc_s { + void *buf; + size_t bufsize; + size_t bufpos; +} Malloc; + +struct cache_h { + char magic[9]; + uint16_t version; + int32_t charoft[26]; +}; + +static struct cache_h Cache_h = { + "DICT_OFT", + CACHE_VERSION, + { 0 }, +}; + +//! Length of word_str in stardict's DICT format +#define WORDLEN 256 + +//! Rockbox filesize limit +#define MAX_FILESIZE 2147483647 + +enum dict_action +{ + DICT_USB_CONNECTED = -4, + DICT_NOT_FOUND = -2, + DICT_QUIT = -3, + DICT_ERROR = -1, + DICT_OK, +}; + +#define VIEWER_NEW (VIEWER_CUSTOM) + +/** \brief Data structure in .idx files + * + * Structure of .idx files: + * \code + * char[] name; // variable length, zero-terminated, UTF-8 + * uint32_t offset; // beginning of the article in the .dict file, Big Endian + * uint32_t size; // article size, Big Endian + * \endcode + */ +struct WordData_s +{ + uint32_t offset; + uint32_t size; +}; + +struct WordData +{ + uint64_t offset; + uint32_t size; +}; + +//! Internal structure for the result +struct DictEntry +{ + char name[WORDLEN]; + int32_t index; + struct WordData data; +}; + +#ifndef betoh64 + +#ifdef ROCKBOX_LITTLE_ENDIAN +static inline uint64_t swap64(uint64_t value) +{ + uint64_t hi = swap32(value >> 32); + uint64_t lo = swap32(value & 0xffffffff); + return (lo << 32) | hi; +} +#define betoh64(x) swap64(x) +#else +#define betoh64(x) (x) +#endif + +#endif + +void str_toupper(const char *src, char *dst) +{ + while (*src) + *dst++ = toupper(*src++); + *dst = '\0'; +} + +//! Display an error message +#define dict_error(...) \ + DEBUGF(__VA_ARGS__); DEBUGF("\n"); \ + LOGF(__VA_ARGS__); \ + rb->splashf(HZ, __VA_ARGS__); + + +//! Initialize the plugin buffer +void dict_buf_init(void) +{ + Malloc.buf = rb->plugin_get_buffer((size_t *)&Malloc.bufsize); + Malloc.bufpos = 0; +} + +/** \brief Stack-like malloc + * + * This malloc implementation only keeps track of how much memory is allocated + */ +void *dict_malloc(size_t size) +{ + void *p; + if (Malloc.bufpos+size <= Malloc.bufsize) + { + p = Malloc.buf + Malloc.bufpos; + if ((int)p%4) { DEBUGF("\n%d %% 4 = %d\n", (int)p, (int)p%4); } + Malloc.bufpos += size; + return (void *)Malloc.buf + Malloc.bufpos - size; + } + else + { + dict_error("Out of Memory"); + return NULL; + } +} + +size_t dict_malloc_align(void) +{ + size_t align = 4 - (((size_t)Malloc.buf + Malloc.bufpos) % 4); + Malloc.bufpos += align; + return align; +} + +/** \brief Stack-like free + * + * @param size Nnumber of bytes to free, 0 will free the whole buffer + */ +void dict_free(size_t size) +{ + if (size == 0 || size > Malloc.bufpos) + Malloc.bufpos = 0; + else + Malloc.bufpos -= size; +} + +//! Extract the path and name of the given filename +void dict_get_name(const char *name) +{ + char *slash, *dot; + size_t n; + + slash = rb->strrchr(name, '/'); + dot = rb->strrchr(slash+1, '.'); + + /* get path */ + n = slash+1-name; + rb->strlcpy(Dict.path, name, n); + Dict.path[n] = '\0'; + + /* get filename without extension */ + n = dot-slash; + rb->strlcpy(Dict.name, slash+1, n); + Dict.name[n] = '\0'; + + /* get index type */ + if (rb->strcmp(dot, ".lidx") == 0) + Dict.DataLen = sizeof(struct WordData); + else + Dict.DataLen = sizeof(struct WordData_s); +} + +//! Generates the cache file to the current dictionary +enum dict_action dict_create_cache(void) +{ + uint32_t offset; + int len; + char word[WORDLEN]; + enum dict_action ret = DICT_OK; + int32_t count; + char a_c, a_cur = 'a'-1; + + int button; + const struct button_mapping *plugin_contexts[] = { + generic_actions, + }; + + rb->splash(0, "Creating offset table... may take some minutes"); + + rb->memset(Cache_h.charoft, 0, sizeof(Cache_h.charoft)); + /* write dummy header */ + rb->write(Dict.fCache, &Cache_h, sizeof(struct cache_h)); + + offset = count = 0; + while (ret == DICT_OK) + { + /* get name length */ + len = 0; + while (rb->read(Dict.fIndex, &word[len], 1) == 1) + { + if (word[len++] == '\0' || len >= WORDLEN) + break; + } + if (!len) + break; + + /* character offset table */ + a_c = tolower(word[0]); + if (a_c > a_cur && a_c >= 'a' && a_c <= 'z') + { + LOGF("'%s' (%ld)", word, count); + do + Cache_h.charoft[++a_cur-'a'] = count; + while (a_cur < a_c); + } + + /* write current position to file */ + rb->write(Dict.fCache, &offset, sizeof(uint32_t)); + + rb->lseek(Dict.fIndex, Dict.DataLen, SEEK_CUR); + offset += Dict.DataLen + len; + count++; + + /* don't take over all control */ + button = pluginlib_getaction(TIMEOUT_NOBLOCK, plugin_contexts, 1); + switch (button) + { + case PLA_QUIT: + ret = DICT_ERROR; + break; + default: + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + ret = DICT_USB_CONNECTED; + break; + } + rb->yield(); + } + + /* Finish table */ + if (a_cur < 'z') + { + do + Cache_h.charoft[++a_cur-'a'] = count; + while (a_cur < 'z'); + } + + /* write real header */ + rb->lseek(Dict.fCache, 0, SEEK_SET); + rb->write(Dict.fCache, &Cache_h, sizeof(struct cache_h)); + + return ret; +} + +//! Closes all file descriptors +void dict_close(void) +{ + rb->close(Dict.fCache); + rb->close(Dict.fIndex); + rb->close(Dict.fDict); + + Conf.viewer_scroll = viewer_get_scroll(); + Conf.viewer_backlight = viewer_get_backlight(); + Conf.viewer_shortcut = viewer_get_shortcut(); + configfile_save(CONFIG_FILENAME, Conf_data, + CONFIG_ITEMS, CONFIG_VERSION); +} + +enum dict_action dict_open(void) +{ + char fn[FAT_FILENAME_BYTES]; + + /* index file */ + if (Dict.DataLen == sizeof(struct WordData)) + rb->snprintf(fn, sizeof(fn), "%s/%s.lidx", Dict.path, Dict.name); // slash added + else + rb->snprintf(fn, sizeof(fn), "%s/%s.idx", Dict.path, Dict.name); // slash added + Dict.fIndex = rb->open(fn, O_RDONLY); + if (Dict.fIndex < 0) + { + dict_error("Failed to open index file: %s", fn); + dict_close(); + return DICT_ERROR; + } + + /* cache file */ + rb->snprintf(fn, sizeof(fn), "%s/%s.oft", OFT_DIR, Dict.name); // slash added + Dict.fCache = rb->open(fn, O_RDONLY); + if (Dict.fCache >= 0) + { + /* check the cache file */ + struct cache_h header; + + rb->read(Dict.fCache, &header, sizeof(struct cache_h)); + if (rb->strcmp(header.magic, Cache_h.magic) == 0 && + header.version == Cache_h.version) + { + rb->memcpy(Dict.charoft, header.charoft, sizeof(Dict.charoft)); + return PLUGIN_OK; + } + + dict_error("Cache file outdated"); + + rb->close(Dict.fCache); + rb->remove(fn); + } + + /* incorrect cache file, create a new one */ + Dict.fCache = rb->creat(fn); + if (Dict.fCache >= 0) + { + if (dict_create_cache() == DICT_OK) + { + rb->memcpy(Dict.charoft, Cache_h.charoft, sizeof(Dict.charoft)); + + /* reopen read-only */ + rb->close(Dict.fCache); + Dict.fCache = rb->open(fn, O_RDONLY); + return DICT_OK; + } + else + { + rb->close(Dict.fCache); + rb->remove(fn); + } + } + + dict_error("Failed to open cache file: %s", fn); + return DICT_ERROR; +} + +/** \brief Initializes everything related to dict. + * + * @param filename Filename of the .idx file for the dictionary + */ +enum dict_action dict_init(const char *file) +{ + /* malloc */ + dict_buf_init(); + + /* variables */ + Conf.viewer_scroll = viewer_get_scroll(); + Conf.viewer_backlight = viewer_get_backlight(); + Conf.viewer_shortcut = viewer_get_shortcut(); + Conf.max_list = MAX_LIST_DEFAULT; + Dict.fDict = Dict.fDict_n = -1; + + /* configuration data */ + configfile_load(CONFIG_FILENAME, Conf_data, + CONFIG_ITEMS, CONFIG_VERSION); + + /* dict name */ + dict_get_name(file); + + /* files */ + return dict_open(); +} + +/** \brief Returns offset for an entry in the idx file + * + * This function allows random access to the idx file + */ +uint32_t dict_cache_offset(int32_t index) +{ + uint32_t offset; + + /* read offset */ + rb->lseek(Dict.fCache, sizeof(uint32_t)*index + sizeof(struct cache_h), + SEEK_SET); + rb->read(Dict.fCache, &offset, sizeof(uint32_t)); + + return offset; +} + +//! Compare function, that represents the order in the .idx file +int dict_index_strcmp(const char *str1, const char *str2) +{ + int a; + + a = rb->strcasecmp(str1, str2); + if (!a) + { + return rb->strcmp(str1, str2); + } + + return a; +} + +/** \brief Reads up to n bytes from current fIndex position until a zero occurs + * + * \return Number of bytes read. + */ +int dict_index_gets(char *buf, int n) +{ + int i; + for (i=0; iread(Dict.fIndex, buf, 1)) + break; + if (! *buf++) + { + i++; + break; + } + } + return i; +} + +/** \brief Reads up to n bytes from an entry in the idx file until a zero occurs + * + * @param index The index in idx t read from. + * \return Number of bytes read. + */ +int dict_index_gets_from(int32_t index, char *buf, int n) +{ + rb->lseek(Dict.fIndex, dict_cache_offset(index), SEEK_SET); + return dict_index_gets(buf, n); +} + +/** \brief Extracts all data about an entry in idx + * + * \return length of the article name + */ +int dict_index_entry(int32_t index, struct DictEntry *out) +{ + int size; + char buf[sizeof(struct WordData)]; + + out->index = index; + + size = dict_index_gets_from(index, out->name, WORDLEN); + + rb->read(Dict.fIndex, buf, Dict.DataLen); + + /* convert to host endianess */ + if (Dict.DataLen == sizeof(struct WordData)) + { + out->data.offset = betoh64( ((struct WordData *)buf)->offset ); + out->data.size = betoh32( ((struct WordData *)buf)->size ); + } + else + { + out->data.offset = betoh32( ((struct WordData_s *)buf)->offset ); + out->data.size = betoh32( ((struct WordData_s *)buf)->size ); + } + + return size; +} + +/** \brief Binary search function + * + * @return If nothing was found, the last probe is returned negative. + */ +int32_t dict_index_binary_search(const char *search, + int(*cmpf)(const char *, const char *)) +{ + char word[WORDLEN]; + int32_t high, low, probe = 0; + int cmp; + + int count = 0; + + int i = tolower(search[0]) - 'a'; + if (i < 0) + { + low = -1; + high = Dict.charoft[0]; + } + else if (i >= 25) + { + low = Dict.charoft[25]; + high = (rb->filesize(Dict.fCache) - sizeof (struct cache_h)) / + sizeof(uint32_t); + } + else + { + low = Dict.charoft[i] - 1; + high = Dict.charoft[i+1] + 1; + } + + while (high - low > 1) + { + count++; + probe = (high + low) / 2; + + dict_index_gets_from(probe, word, WORDLEN); + LOGF("%ld: '%s'", probe, word); + + /* jump according to the found word. */ + cmp = cmpf(search, word); + if (cmp < 0) + high = probe; + else if (cmp > 0) + low = probe; + else + { + probe = -probe; + break; + } + } + + LOGF("probes: %d", count); + return -probe; +} + +/** \brief Shows a selection menu for all articles starting with the given word + * + * The selections screen is only displayed, if there's more than one result. + */ +int32_t dict_index_find_startingWith(const char *search) +{ + int32_t index; + char buf[WORDLEN]; + int len, new_len, cmp, needmem; + int buffer_size; + int n = 0, sel = 0; + + char **items; + buffer_size = dict_malloc_align() + Conf.max_list * sizeof(char *); + items = (char **) dict_malloc(buffer_size); + + /* make uppercase to find lowest value */ + str_toupper(search, buf); + index = dict_index_binary_search(buf, dict_index_strcmp); + if (index < 0) + index = -index; + + rb->lseek(Dict.fIndex, dict_cache_offset(index), SEEK_SET); + + /* find all words that start with the word */ + len = rb->strlen(search); + while (n 0) + needmem += 4 - needmem%4; + + /* seek to next article name */ + rb->lseek(Dict.fIndex, Dict.DataLen, SEEK_CUR); + + cmp = rb->strncasecmp(search, buf, len); + if (cmp > 0) + { + index++; + continue; + } + else if (cmp < 0) + break; + + items[n] = (char *)dict_malloc(needmem); + if (items[n] == NULL) + break; + buffer_size += needmem; + + rb->strlcpy(items[n], buf, new_len); + DEBUGF("%s\n", buf); + + n++; + } + + /* show the selection list */ + if (n > 1) + { + struct menu_callback_with_desc menu_ = {NULL,"Search Results", Icon_NOICON}; + struct menu_item_ex menu = { + MT_RETURN_ID|MENU_HAS_DESC|MENU_ITEM_COUNT(n), + {.strings = (const char **)items}, + {.callback_and_desc = &menu_} + }; + + sel = rb->do_menu(&menu, &sel, NULL, true); + } + + dict_free(buffer_size); + + if (n == 0) + return DICT_NOT_FOUND; + + switch (sel) + { + case MENU_ATTACHED_USB: + return DICT_USB_CONNECTED; + case MENU_SELECTED_EXIT: case GO_TO_PREVIOUS: case GO_TO_ROOT: + return DICT_QUIT; + default: + return index + sel; + } +} + +//! Wrapper to open the correct .desc file depending on the description offset +enum dict_action dict_desc_open(int n) +{ + char fn[FAT_FILENAME_BYTES]; + + if (n == Dict.fDict_n) + return DICT_OK; + + if (Dict.fDict > 0) + rb->close(Dict.fDict); + + if (n < 1) + rb->snprintf(fn, sizeof(fn), "%s/%s.dict", Dict.path, Dict.name); // slash added + else + rb->snprintf(fn, sizeof(fn), "%s/%s.dict.%d", Dict.path, Dict.name, n); // slash added + + Dict.fDict = rb->open(fn, O_RDONLY); + if (Dict.fDict < 0) + { + dict_error("Failed to open description file: %s", fn); + return DICT_ERROR; + } + + Dict.fDict_n = n; + + return DICT_OK; +} + +/** \brief Reads the article description + * + * @param article The article to be read. + */ +char *dict_desc_read(struct WordData *artl, char *buf) +{ + uint64_t offset = artl->offset; + int len, n = offset/MAX_FILESIZE; + + if (dict_desc_open(n) != DICT_OK) + return NULL; + + offset %= MAX_FILESIZE; + + rb->lseek(Dict.fDict, offset, SEEK_SET); + len = rb->read(Dict.fDict, buf, artl->size); + + /* check if article is splitted between two files */ + if (offset > MAX_FILESIZE - artl->size) + { + rb->close(Dict.fDict); + if (dict_desc_open(++n) != DICT_OK) + return NULL; + + rb->read(Dict.fDict, buf+len, artl->size-len); + } + buf[artl->size] = '\0'; + + /* And print it to debug. */ + DEBUGF("Description: %s\n", buf); + + return buf; +} + +void dict_menu(void) +{ + bool quit = false; + int selection = 0; + + MENUITEM_STRINGLIST(menu, "DICT Menu", NULL, + "Viewer control", + "Playback control", + "Max listed articles"); + + rb->button_clear_queue(); + rb->sleep(HZ/5); + while (!quit) { + selection = rb->do_menu(&menu, &selection, NULL, false); + switch (selection) + { + case 0: + quit = viewer_menu(); + break; + case 1: + quit = playback_control(NULL); + break; + case 2: + quit = rb->set_int("Max listed articles", "", UNIT_INT, + &Conf.max_list, NULL, 10, 10, 50, NULL); + break; + default: + quit = true; + break; + } + } +} + +void dict_viewer_callback(int button) +{ + switch (button) + { + case PLA_UP: case PLA_UP_REPEAT: + viewer_up(); + break; + case PLA_DOWN: case PLA_DOWN_REPEAT: + viewer_down(); + break; + case PLA_FIRE: + viewer_search(); + break; + case PLA_QUIT: + viewer_exit(VIEWER_EXIT); + break; + case PLA_START: + viewer_exit(VIEWER_NEW); + break; + case PLA_MENU: + dict_menu(); + break; +#ifdef VIEWER_HAS_SHORTCUT + default: + if (rb->button_status() & BUTTON_VIEWER_SHORTCUT) + viewer_shortcut(); + break; +#endif + + } +} + +//! Parses #redirect from MediaWiki +int parse_mw_redirect(struct DictEntry *a, const char *desc) +{ + char *p; + int len = 0; + int32_t index; + static const char redirect[] = "#redirect"; + + if ( rb->strncasecmp(desc, redirect, sizeof(redirect)-1) ) + return 0; + + /* get destination article */ + p = rb->strchr(desc+sizeof(redirect)-1, '[') + 2; + while (p[len] != ']' && p[len] != '#') + len++; + + rb->strlcpy(a->name, p, len); + a->name[len] = '\0'; + + /* replace '_' with ' ' */ + for (p=a->name; *p; p++) + { + if (*p == '_') + *p = ' '; + } + + + index = dict_index_binary_search(a->name, dict_index_strcmp); + if (index < 0) + { + dict_error("Illegal redirect: %s", a->name); + return 0; + } + dict_index_entry(index, a); + + return 1; +} + +//! Dict main loop +enum dict_action dict_main(void) +{ + struct DictEntry result; + char search[WORDLEN], old[WORDLEN]; + char *desc; + int n, desc_size = 0, header_len = 0; + bool new_search = false; + + search[0] = '\0'; + + viewer_init(rb); + viewer_set_scroll(Conf.viewer_scroll); + viewer_set_backlight(Conf.viewer_backlight); + viewer_set_shortcut(Conf.viewer_shortcut); + viewer_set_callback(dict_viewer_callback); + + while (true) + { +#ifndef HAVE_FLASH_STORAGE + /* keep the disk running */ + rb->storage_spindown(255); +#ifndef SIMULATOR +// rb->ata_wakeup(); +#endif +#endif /*HAVE_FLASH_STORAGE*/ + + rb->strcpy(old, search); + rb->kbd_input(search, WORDLEN); + /* exit if the search string is empty or the user didn't change it */ + if (!rb->strlen(search) || + (!new_search && !rb->strncmp(old, search, sizeof(old)) )) + { + return DICT_OK; + } + new_search = false; + + result.index = dict_index_find_startingWith(search); + switch (result.index) + { + case DICT_NOT_FOUND: + dict_error("No results for \"%s\"", search); + continue; + case DICT_QUIT: + continue; + case DICT_USB_CONNECTED: + return DICT_USB_CONNECTED; + } + + /* get result data */ + dict_index_entry(result.index, &result); + do { + if (desc_size > 0) + dict_free(desc_size); + + /* make title */ + header_len = rb->strlen(result.name) + 2; + /* increase the buffer to put a title in front of the article */ + desc_size = result.data.size + header_len + 1; + desc = (char *)dict_malloc(desc_size); + + /* set desc pointer to beginning of the description */ + if (!dict_desc_read(&result.data, desc+header_len)) + return DICT_ERROR; + + } while (parse_mw_redirect(&result, desc+header_len)); + + DEBUGF("\ +result:\n\ + name: %s\n\ + index: %ld\n\ + offset: %llu (%llx)\n\ + size: %lu\n", + result.name, result.index, result.data.offset, + result.data.offset, result.data.size); + +#ifndef HAVE_FLASH_STORAGE + rb->storage_spindown(rb->global_settings->disk_spindown); +#endif + + rb->snprintf(desc, header_len, "%s\n", result.name); + desc[header_len-1] = '\n'; + desc[desc_size-1] = '\0'; + + desc_size += dict_malloc_align(); + n = viewer_set_text(dict_malloc(0), desc); + dict_malloc(n); + desc_size += n; + + while (!new_search) + { + switch(viewer_run()) + { + case VIEWER_NEW: + new_search = true; + continue; + case VIEWER_ATTACHED_USB: + return DICT_USB_CONNECTED; + default: + return DICT_OK; + } + } + + dict_free(desc_size); + } +} + +enum plugin_status plugin_start(const void* file) +{ + enum plugin_status ret; + + switch (dict_init(file)) + { + case DICT_ERROR: + return PLUGIN_ERROR; + case DICT_USB_CONNECTED: + return PLUGIN_USB_CONNECTED; + default: + break; + } + + switch (dict_main()) + { + case DICT_OK: + ret = PLUGIN_OK; + break; + case DICT_USB_CONNECTED: + ret = PLUGIN_USB_CONNECTED; + break; + default: + ret = PLUGIN_ERROR; + break; + } + +#ifndef HAVE_FLASH_STORAGE + /* reset disk setting */ + rb->storage_spindown(rb->global_settings->disk_spindown); +#endif + + dict_close(); + + return ret; +} Index: apps/plugins/lib/viewer.c =================================================================== --- apps/plugins/lib/viewer.c (Revision 0) +++ apps/plugins/lib/viewer.c (Revision 0) @@ -0,0 +1,516 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id: viewer.c 12008 2007-01-14 13:48:09Z dave $ +* +* Copyright (C) 2007 Timo Horstschäfer +* +* +* 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 "pluginlib_actions.h" +#include "viewer.h" + +#define VIEWER_LINE_BUF 255 +#define VIEWER_SCROLLBAR_WIDTH 4 +#define MAX_SEARCHLEN 255 + +/** text structure */ +struct viewer_txt +{ + const char *p; /**< Text pointer */ + int len; /**< Text size */ + int pos; /**< Current line number */ + + int lines; /**< Number of lines */ + const char **line; /**< Array of line pointers */ + + int sections; /**< Number of sections */ + int *section; /**< Array of section lines */ + const char **section_name; /**< Name of each section */ +}; + +static void viewer_default_callback(int button); + +static struct viewer_txt *txt; + +/** Display and character dimensions*/ +static int cols, rows; +#ifdef HAVE_REMOTE_LCD +static int remote_cols, remote_rows; +#endif +#ifdef HAVE_LCD_BITMAP +static int row_height; +#endif + +static char search[255]; +static bool quit; +static int retval; + +/* Customization (also default settings) */ +void (*viewer_shortcut)(void) + = (void *)viewer_menu_search; +static void (*viewer_callback)(int button) + = viewer_default_callback; +static int scroll = 0; +static int backlight = 2; +static int shortcut = 0; + +inline int viewer(const struct plugin_api *newrb, const char *text) +{ + static long buffer[128]; + viewer_init(newrb); + viewer_set_text(buffer, text); + return viewer_run(); +} + +/** \brief Returns a pointer to the last character of a line. + * @param s Pointer to the actual line. + */ +static char *viewer_endl(const char *s, int width) +{ + const char *space = NULL; + + while (true) + { + if (*s == '\n' || *s == '\0') + return (char *)s; + if (*s == ' ') + space = s; + if (!--width) + break; + s += rb->utf8seek(s, 1); + } + + return (char *) ( (space) ? space : s ); +} + +/** \brief Draws text on the display. + * + * Handles newline characters and line wrapping. + */ +static void viewer_draw(const char *s) +{ + const char *p, *eol; + int line, len; + char buf[VIEWER_LINE_BUF]; + int left_margin=0; + + rb->lcd_clear_display(); + + /* Some Rockbox function restore the backlight settings; reset it */ + viewer_set_backlight(backlight); + +#ifdef HAVE_LCD_BITMAP + if (txt->lines > rows) + { + rb->gui_scrollbar_draw(rb->screens[SCREEN_MAIN], 0, 0, + VIEWER_SCROLLBAR_WIDTH-1, LCD_HEIGHT, + txt->lines+rows-1, txt->pos, txt->pos+rows, + VERTICAL); + left_margin=VIEWER_SCROLLBAR_WIDTH; + } +#endif + + /* draw main display */ + for (p=s, line=0; linestrlcpy(buf, p, len); + buf[len] = '\0'; +#ifdef HAVE_LCD_BITMAP + rb->lcd_putsxy(left_margin, line*row_height, buf); +#else + rb->lcd_puts(0,line,buf); +#endif + if (*eol) + p = eol + rb->utf8seek(eol, 1); + else + break; + } + + rb->lcd_update(); + +#ifdef HAVE_REMOTE_LCD + rb->lcd_remote_clear_display(); + + /* draw remote */ + for (p=s, line=0; linestrlcpy(buf, p, len); + buf[len] = '\0'; + + rb->lcd_remote_puts(0, line, buf); + + if (*eol) + p = eol + rb->utf8seek(eol, 1); + else + break; + } + + rb->lcd_remote_update(); +#endif +} + +static void viewer_default_callback(int button) +{ + switch (button) + { + case PLA_UP: case PLA_UP_REPEAT: + viewer_up(); + break; + case PLA_DOWN: case PLA_DOWN_REPEAT: + viewer_down(); + break; + case PLA_FIRE: + viewer_search(); + break; + case PLA_QUIT: + viewer_exit(VIEWER_EXIT); + break; + case PLA_MENU: + viewer_menu(); + break; +#ifdef VIEWER_HAS_SHORTCUT + default: + /* Could need some better way to access the REC-button */ + if (rb->button_status() & BUTTON_VIEWER_SHORTCUT) + viewer_shortcut(); + break; +#endif + } +} + +void viewer_init(const struct plugin_api *newrb) +{ + rb = newrb; + +#ifdef HAVE_LCD_BITMAP + rb->lcd_getstringsize("o", &cols, &row_height); +# ifdef HAVE_REMOTE_LCD + remote_cols = LCD_REMOTE_WIDTH / cols; + remote_rows = LCD_REMOTE_HEIGHT / row_height; +# endif + cols = (LCD_WIDTH-VIEWER_SCROLLBAR_WIDTH) / cols; + rows = LCD_HEIGHT / row_height; +#else + cols = 11; + rows = 2; +#endif + + viewer_callback = viewer_default_callback; + scroll = 0; +} + +int viewer_run(void) +{ + int button; + const struct button_mapping *viewer_contexts[] = { + generic_directions, + generic_actions, + }; + + viewer_redraw(); + + quit = false; + retval = VIEWER_EXIT; + + while (!quit) + { + rb->yield(); + button = pluginlib_getaction(HZ, viewer_contexts, 2); + + if (rb->default_event_handler(button) == SYS_USB_CONNECTED) + viewer_exit(VIEWER_ATTACHED_USB); + + viewer_callback(button); + viewer_redraw(); + } + + /* restore backlight setting */ + rb->backlight_set_timeout(rb->global_settings->backlight_timeout); + + return retval; +} + +int viewer_line(const char *p) +{ + int line; + + for (line=0; linelines; line++) + { + if (p < txt->line[line]) + break; + } + + return line ? line-1 : line; +} + +int viewer_set_text(void *buffer, const char *text) +{ + size_t buffer_used; + const char *p; + + txt = buffer; + buffer_used = sizeof(struct viewer_txt); + + txt->p = text; + txt->len = rb->strlen(txt->p); + txt->pos = 0; + + /* Create an array with the pointers to the beginning of each line */ + txt->lines = 0; + txt->line = buffer + buffer_used; + for (p = txt->p; *p;) + { + txt->line[txt->lines++] = p; + p = viewer_endl(p, cols); + if (*p) + p += rb->utf8seek(p, 1); + } + buffer_used += txt->lines * sizeof txt->line; + return buffer_used; +} + +void viewer_set_callback(void (*newcallback)(int button)) +{ + viewer_callback = newcallback; +} + +void viewer_set_scroll(int newscroll) +{ + scroll = newscroll < 0 ? 0 : newscroll; +} + +void viewer_set_pos(int line) +{ + if (line < 0) + txt->pos = txt->lines-1 - (line%txt->lines); + else if (line >= txt->lines) + txt->pos = txt->lines-1; + else + txt->pos = line; +} + +void viewer_set_backlight(int newbacklight) +{ + backlight = (newbacklight > 2) ? 2 : newbacklight; + switch (backlight) + { + case 0: + rb->backlight_set_timeout(0); + break; + case 1: + rb->backlight_set_timeout(1); + break; + default: + rb->backlight_set_timeout(rb->global_settings->backlight_timeout); + break; + } +} + +void viewer_set_shortcut(int shortcutindex) +{ + switch (shortcutindex) + { + default: + case 0: + shortcutindex = 0; + viewer_shortcut = (void *)viewer_menu_search; + break; + case 1: + viewer_shortcut = (void *)viewer_menu_backlight; + break; + case 2: + viewer_shortcut = (void *)viewer_menu_scroll; + break; + } + shortcut = shortcutindex; +} + +int viewer_get_scroll(void) +{ + return scroll; +} + +int viewer_get_pos(void) +{ + return txt->pos; +} + +int viewer_get_backlight(void) +{ + return backlight; +} + +int viewer_get_shortcut(void) +{ + return shortcut; +} + +void viewer_up(void) +{ + int n = scroll ? scroll : rows; + txt->pos = (txt->pos < n) ? 0 : txt->pos - n; +} + +void viewer_down(void) +{ + int n = scroll ? scroll : rows; + +#ifdef HAVE_REMOTE_LCD + if (txt->lines < remote_rows) +#else + if (txt->lines < rows) +#endif + return; + + if (txt->pos+n >= txt->lines) + txt->pos = txt->lines-1; + else + txt->pos += n; +} + +void viewer_redraw(void) +{ + viewer_draw(txt->line[txt->pos]); +} + +bool viewer_search(void) +{ + int line; + const char *p; + size_t n; + + if (!search[0]) + return false; + + n = rb->strlen(search); + line = (txt->pos == txt->lines-1) ? 0 : txt->pos+1; + p = txt->line[line]; + while (true) + { + if (!*p) + p = txt->p; + + /* just a slow linear search */ + if (rb->strncasecmp(search, p, n) == 0) + break; + + if (p == txt->line[txt->pos]) + return false; + + p += rb->utf8seek(p, 1); + } + + viewer_set_pos(viewer_line(p)); + return true; +} + +void viewer_exit(int newretval) +{ + retval = newretval; + quit = true; +} + +/* Viewer menu */ +bool viewer_menu_search(void) +{ + rb->kbd_input(search, sizeof(search)); + if (viewer_search()) + return true; + else + { + rb->splashf(HZ, "\"%s\" not found", search); + return false; + } +} + +bool viewer_menu_scroll(void) +{ + static const struct opt_items scroll_menu[] = { + { "Full page", -1 }, + { "1 line", -1 }, + { "2 lines", -1 }, + { "3 lines", -1 }, + { "4 lines", -1 }, + { "5 lines", -1 }, + }; + + return rb->set_option("Scrolling", &scroll, INT, scroll_menu, 6, NULL); +} + +bool viewer_menu_backlight(void) +{ + static const struct opt_items backlight_menu[] = { + { "Off", -1 }, + { "On", -1 }, + { "use Rockox setting", -1 }, + }; + + return rb->set_option("Backlight", &backlight, INT, backlight_menu, 3, + viewer_set_backlight); +} + +bool viewer_menu_shortcut(void) +{ + static const struct opt_items shortcut_menu[] = { + { "Search...", -1 }, + { "Backlight", -1 }, + { "Scrolling", -1 }, + }; + + return rb->set_option("REC shortcut", &shortcut, INT, shortcut_menu, 3, + viewer_set_shortcut); +} + +MENUITEM_FUNCTION(search_item, MENU_FUNC_CHECK_RETVAL, "Search...", + viewer_menu_search, NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(scroll_item, 0, "Scrolling", + viewer_menu_scroll, NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(backlight_item, 0, "Backlight", + viewer_menu_backlight, NULL, NULL, Icon_NOICON); +MENUITEM_FUNCTION(shortcut_item, 0, "REC shortcut", + viewer_menu_shortcut, NULL, NULL, Icon_NOICON); + +#ifdef VIEWER_HAS_SHORTCUT +#define VIEWER_SC_ITEM ,&shortcut_item +#else +#define VIEWER_SC_ITEM +#endif +MAKE_MENU(viewer_control_menu, "Viewer Control", NULL, Icon_NOICON, + &search_item, &scroll_item, &backlight_item VIEWER_SC_ITEM); + +bool viewer_menu(void) +{ + switch(rb->do_menu(&viewer_control_menu, NULL, NULL, false)) + { + case MENU_ATTACHED_USB: + case true: + return true; + default: + return false; + } +} Index: apps/plugins/lib/SOURCES =================================================================== --- apps/plugins/lib/SOURCES (revision 25263) +++ apps/plugins/lib/SOURCES (working copy) @@ -5,6 +5,7 @@ playback_control.c rgb_hsv.c buflib.c +viewer.c display_text.c strncpy.c #if defined(HAVE_LCD_BITMAP) && (LCD_DEPTH < 4) Index: apps/plugins/lib/viewer.h =================================================================== --- apps/plugins/lib/viewer.h (Revision 0) +++ apps/plugins/lib/viewer.h (Revision 0) @@ -0,0 +1,84 @@ +/*************************************************************************** +* __________ __ ___. +* Open \______ \ ____ ____ | | _\_ |__ _______ ___ +* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / +* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < +* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ +* \/ \/ \/ \/ \/ +* $Id: viewer.h 12008 2007-01-14 13:48:09Z dave $ +* +* Copyright (C) 2007 Timo Horstschäfer +* +* +* 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" + +#if (CONFIG_KEYPAD == IRIVER_H100_PAD) || \ + (CONFIG_KEYPAD == IRIVER_H300_PAD) || \ + (CONFIG_KEYPAD == SANSA_E200_PAD) +#define VIEWER_HAS_SHORTCUT +#define BUTTON_VIEWER_SHORTCUT BUTTON_REC +#endif + +/** \brief Shows a text viewer. + * + * How to view some text: + * viewer(api, textptr); // Text must be zero-terminated. + * + * For full control, call each function seperately: + * viewer_init(rb); // Must be done once in a plugin. + * viewer_set_text(bufferptr, textptr); + * viewer_run(); // Display the text. + * + * viewer() is just a short form for all three calls. + * + * @param text Zero-terminated string. + */ +int viewer(const struct plugin_api *api, const char *text); +#define VIEWER_ATTACHED_USB (-2) +#define VIEWER_EXIT 0 +#define VIEWER_CUSTOM (VIEWER_EXIT+1) +/**< Use values starting with VIEWER_CUSTOM for your own return values. */ + +void viewer_init(const struct plugin_api *api); +int viewer_run(void); + +/** Get the line to which the address belongs to. */ +int viewer_line(const char *p); + +/** Initialize the viewer with a new text. + * + * @param buffer A small buffer. + * @param text The new text to be displayed. + * @return Needed size of the buffer. + */ +int viewer_set_text(void *buffer, const char *txt); +void viewer_set_callback(void (*callback)(int button)); +void viewer_set_scroll(int newscroll); +void viewer_set_pos(int line); +void viewer_set_backlight(int newbacklight); +void viewer_set_shortcut(int shortcutindex); + +int viewer_get_scroll(void); +int viewer_get_pos(void); +int viewer_get_backlight(void); +int viewer_get_shortcut(void); + +/** Functions to control the viewer in the callback function. */ +void viewer_up(void); +void viewer_down(void); +bool viewer_search(void); +bool viewer_menu(void); +void viewer_redraw(void); +void (*viewer_shortcut)(void); +bool viewer_menu_search(void); +bool viewer_menu_scroll(void); +bool viewer_menu_backlight(void); +void viewer_exit(int retval); Index: apps/plugins/SOURCES =================================================================== --- apps/plugins/SOURCES (revision 25263) +++ apps/plugins/SOURCES (working copy) @@ -3,6 +3,7 @@ credits.c cube.c dict.c +dict2.c jackpot.c keybox.c logo.c Index: apps/plugins/viewers.config =================================================================== --- apps/plugins/viewers.config (revision 25263) +++ apps/plugins/viewers.config (working copy) @@ -1,6 +1,8 @@ ch8,viewers/chip8,0 txt,viewers/viewer,1 nfo,viewers/viewer,1 +idx,viewers/dict2,1 +lidx,viewers/dict2,1 txt,apps/text_editor,2 bmp,viewers/bmp,2 jpg,viewers/jpeg,2 Index: apps/settings.h =================================================================== --- apps/settings.h (revision 25263) +++ apps/settings.h (working copy) @@ -78,6 +78,7 @@ #define RECPRESETS_DIR ROCKBOX_DIR "/recpresets" #define FMPRESET_PATH ROCKBOX_DIR "/fmpresets" #define PLAYLIST_CATALOG_DEFAULT_DIR "/Playlists" +#define DICTS_DIR "/dicts" #define VIEWERS_CONFIG ROCKBOX_DIR "/viewers.config" #define CONFIGFILE ROCKBOX_DIR "/config.cfg" @@ -147,7 +148,7 @@ enum { SHOW_ALL, SHOW_SUPPORTED, SHOW_MUSIC, SHOW_PLAYLIST, SHOW_ID3DB, NUM_FILTER_MODES, SHOW_WPS, SHOW_RWPS, SHOW_SBS, SHOW_RSBS, SHOW_FMR, SHOW_CFG, - SHOW_LNG, SHOW_MOD, SHOW_FONT, SHOW_PLUGINS}; + SHOW_LNG, SHOW_MOD, SHOW_FONT, SHOW_DICTS, SHOW_PLUGINS}; /* file and dir sort options */ enum { SORT_ALPHA, SORT_DATE, SORT_DATE_REVERSED, SORT_TYPE, /* available as settings */ Index: apps/filetree.c =================================================================== --- apps/filetree.c (revision 25263) +++ apps/filetree.c (working copy) @@ -327,7 +327,8 @@ ((*c->dirfilter == SHOW_MUSIC && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_AUDIO) && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_M3U) || - (*c->dirfilter == SHOW_SUPPORTED && !filetype_supported(dptr->attr)))) || + ((*c->dirfilter == SHOW_SUPPORTED || *c->dirfilter == SHOW_DICTS)&& + !filetype_supported(dptr->attr)))) || (*c->dirfilter == SHOW_WPS && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_WPS) || #ifdef HAVE_LCD_BITMAP (*c->dirfilter == SHOW_FONT && (dptr->attr & FILE_ATTR_MASK) != FILE_ATTR_FONT) || @@ -676,6 +677,7 @@ if (*c->dirfilter > NUM_FILTER_MODES && *c->dirfilter != SHOW_CFG && *c->dirfilter != SHOW_FONT && + *c->dirfilter != SHOW_DICTS && *c->dirfilter != SHOW_PLUGINS) { exit_func = true; Index: apps/root_menu.c =================================================================== --- apps/root_menu.c (revision 25263) +++ apps/root_menu.c (working copy) @@ -243,6 +243,10 @@ tc->selected_item = last_db_selection; break; #endif + case GO_TO_BROWSEDICTS: + filter = SHOW_DICTS; + snprintf(folder, MAX_PATH, "%s/", DICTS_DIR); + break; case GO_TO_BROWSEPLUGINS: filter = SHOW_PLUGINS; strlcpy(folder, PLUGIN_DIR, MAX_PATH); @@ -396,6 +400,7 @@ #endif [GO_TO_RECENTBMARKS] = { load_bmarks, NULL, &bookmark_settings_menu }, + [GO_TO_BROWSEDICTS] = { browser, (void*)GO_TO_BROWSEDICTS, NULL }, [GO_TO_BROWSEPLUGINS] = { plugins_menu, NULL, NULL }, [GO_TO_PLAYLIST_VIEWER] = { playlist_view, NULL, NULL }, @@ -410,6 +415,8 @@ MENUITEM_RETURNVALUE(db_browser, ID2P(LANG_TAGCACHE), GO_TO_DBBROWSER, NULL, Icon_Audio); #endif +MENUITEM_RETURNVALUE(dicts_browser, ID2P(LANG_DICTIONARIES), GO_TO_BROWSEDICTS, + NULL, Icon_NOICON); MENUITEM_RETURNVALUE(rocks_browser, ID2P(LANG_PLUGINS), GO_TO_BROWSEPLUGINS, NULL, Icon_Plugin); static char *get_wps_item_name(int selected_item, void * data, char *buffer) @@ -461,7 +468,7 @@ #if CONFIG_TUNER &fm, #endif - &playlist_options, &rocks_browser, &info_menu + &playlist_options, &rocks_browser, &dicts_browser, &info_menu #ifdef HAVE_LCD_CHARCELLS ,&do_shutdown_item Index: apps/root_menu.h =================================================================== --- apps/root_menu.h (revision 25263) +++ apps/root_menu.h (working copy) @@ -50,6 +50,7 @@ /* Do Not add any items above here unless you want it to be able to be the "start screen" after a boot up. The setting in settings_list.c will need editing if this is the case. */ + GO_TO_BROWSEDICTS, GO_TO_BROWSEPLUGINS, GO_TO_TIMESCREEN, GO_TO_PLAYLIST_VIEWER,