//////////////////////////////////////////////////////////////////////////// // **** WAVPACK **** // // Hybrid Lossless Wavefile Compressor // // Copyright (c) 1998 - 2005 Conifer Software. // // All Rights Reserved. // // Distributed under the BSD Software License (see license.txt) // //////////////////////////////////////////////////////////////////////////// // pack.c // This module actually handles the compression of the audio data, except for // the entropy coding which is handled by the words? modules. For efficiency, // the conversion is isolated to tight loops that handle an entire buffer. #include "wavpack.h" #include // This flag provides faster encoding speed at the expense of more code. The // improvement applies to 16-bit stereo lossless only. //////////////////////////////// local tables /////////////////////////////// // These two tables specify the characteristics of the decorrelation filters. // Each term represents one layer of the sequential filter, where positive // values indicate the relative sample involved from the same channel (1=prev), // 17 & 18 are special functions using the previous 2 samples, and negative // values indicate cross channel decorrelation (in stereo only). static const signed char default_terms [] = { 18,18,2,3,-2,0 }; static const signed char high_terms [] = { 18,18,2,3,-2,18,2,4,7,5,3,6,0 }; static const signed char fast_terms [] = { 17,17,0 }; ///////////////////////////// executable code //////////////////////////////// // This function initializes everything required to pack WavPack bitstreams // and must be called BEFORE any other function in this module. void pack_init (WavpackContext *wpc) { WavpackStream *wps = &wpc->stream; uint32_t flags = wps->wphdr.flags; struct decorr_pass *dpp; const signed char *term_string; int ti; wps->sample_index = 0; CLEAR (wps->decorr_passes); if (wpc->config.flags & CONFIG_HIGH_FLAG) term_string = high_terms; else if (wpc->config.flags & CONFIG_FAST_FLAG) term_string = fast_terms; else term_string = default_terms; for (dpp = wps->decorr_passes, ti = 0; term_string [ti]; ti++) if (term_string [ti] >= 0 || (flags & CROSS_DECORR)) { dpp->term = term_string [ti]; dpp++->delta = 2; } else if (!(flags & MONO_FLAG)) { dpp->term = -3; dpp++->delta = 2; } wps->num_terms = dpp - wps->decorr_passes; init_words (wps); } // Allocate room for and copy the decorrelation terms from the decorr_passes // array into the specified metadata structure. Both the actual term id and // the delta are packed into single characters. static void write_decorr_terms (WavpackStream *wps, WavpackMetadata *wpmd) { int tcount = wps->num_terms; struct decorr_pass *dpp; char *byteptr; byteptr = wpmd->data = wpmd->temp_data; wpmd->id = ID_DECORR_TERMS; for (dpp = wps->decorr_passes; tcount--; ++dpp) *byteptr++ = ((dpp->term + 5) & 0x1f) | ((dpp->delta << 5) & 0xe0); wpmd->byte_length = byteptr - (char *) wpmd->data; } // Allocate room for and copy the decorrelation term weights from the // decorr_passes array into the specified metadata structure. The weights // range +/-1024, but are rounded and truncated to fit in signed chars for // metadata storage. Weights are separate for the two channels static void write_decorr_weights (WavpackStream *wps, WavpackMetadata *wpmd) { int tcount = wps->num_terms; struct decorr_pass *dpp; signed char *byteptr; byteptr = wpmd->data = wpmd->temp_data; wpmd->id = ID_DECORR_WEIGHTS; for (dpp = wps->decorr_passes; tcount--; ++dpp) { dpp->weight_A = restore_weight (*byteptr++ = store_weight (dpp->weight_A)); if (!(wps->wphdr.flags & MONO_FLAG)) dpp->weight_B = restore_weight (*byteptr++ = store_weight (dpp->weight_B)); } wpmd->byte_length = byteptr - (signed char *) wpmd->data; } // Allocate room for and copy the decorrelation samples from the decorr_passes // array into the specified metadata structure. The samples are signed 32-bit // values, but are converted to signed log2 values for storage in metadata. // Values are stored for both channels and are specified from the first term // with unspecified samples set to zero. The number of samples stored varies // with the actual term value, so those must obviously be specified before // these in the metadata list. Any number of terms can have their samples // specified from no terms to all the terms, however I have found that // sending more than the first term's samples is a waste. The "wcount" // variable can be set to the number of terms to have their samples stored. static void write_decorr_samples (WavpackStream *wps, WavpackMetadata *wpmd) { int tcount = wps->num_terms, wcount = 1, temp; struct decorr_pass *dpp; uchar *byteptr; byteptr = wpmd->data = wpmd->temp_data; wpmd->id = ID_DECORR_SAMPLES; for (dpp = wps->decorr_passes; tcount--; ++dpp) if (wcount) { if (dpp->term > MAX_TERM) { dpp->samples_A [0] = exp2s (temp = log2s (dpp->samples_A [0])); *byteptr++ = temp; *byteptr++ = temp >> 8; dpp->samples_A [1] = exp2s (temp = log2s (dpp->samples_A [1])); *byteptr++ = temp; *byteptr++ = temp >> 8; if (!(wps->wphdr.flags & MONO_FLAG)) { dpp->samples_B [0] = exp2s (temp = log2s (dpp->samples_B [0])); *byteptr++ = temp; *byteptr++ = temp >> 8; dpp->samples_B [1] = exp2s (temp = log2s (dpp->samples_B [1])); *byteptr++ = temp; *byteptr++ = temp >> 8; } } else if (dpp->term < 0) { dpp->samples_A [0] = exp2s (temp = log2s (dpp->samples_A [0])); *byteptr++ = temp; *byteptr++ = temp >> 8; dpp->samples_B [0] = exp2s (temp = log2s (dpp->samples_B [0])); *byteptr++ = temp; *byteptr++ = temp >> 8; } else { int m = 0, cnt = dpp->term; while (cnt--) { dpp->samples_A [m] = exp2s (temp = log2s (dpp->samples_A [m])); *byteptr++ = temp; *byteptr++ = temp >> 8; if (!(wps->wphdr.flags & MONO_FLAG)) { dpp->samples_B [m] = exp2s (temp = log2s (dpp->samples_B [m])); *byteptr++ = temp; *byteptr++ = temp >> 8; } m++; } } wcount--; } else { CLEAR (dpp->samples_A); CLEAR (dpp->samples_B); } wpmd->byte_length = byteptr - (uchar *) wpmd->data; } // Allocate room for and copy the configuration information into the specified // metadata structure. Currently, we just store the upper 3 bytes of // config.flags and only in the first block of audio data. Note that this is // for informational purposes not required for playback or decoding (like // whether high or fast mode was specified). static void write_config_info (WavpackContext *wpc, WavpackMetadata *wpmd) { char *byteptr; byteptr = wpmd->data = wpmd->temp_data; wpmd->id = ID_CONFIG_BLOCK; *byteptr++ = (char) (wpc->config.flags >> 8); *byteptr++ = (char) (wpc->config.flags >> 16); *byteptr++ = (char) (wpc->config.flags >> 24); wpmd->byte_length = byteptr - (char *) wpmd->data; } // Pack an entire block of samples (either mono or stereo) into a completed // WavPack block. It is assumed that there is sufficient space for the // completed block at "wps->blockbuff" and that "wps->blockend" points to the // end of the available space. A return value of FALSE indicates an error. // Any unsent metadata is transmitted first, then required metadata for this // block is sent, and finally the compressed integer data is sent. If a "wpx" // stream is required for floating point data or large integer data, then this // must be handled outside this function. To find out how much data was written // the caller must look at the ckSize field of the written WavpackHeader, NOT // the one in the WavpackStream. int pack_start_block (WavpackContext *wpc) { WavpackStream *wps = &wpc->stream; WavpackMetadata wpmd; memcpy (wps->blockbuff, &wps->wphdr, sizeof (WavpackHeader)); ((WavpackHeader *) wps->blockbuff)->ckSize = sizeof (WavpackHeader) - 8; ((WavpackHeader *) wps->blockbuff)->block_index = wps->sample_index; ((WavpackHeader *) wps->blockbuff)->block_samples = 0; ((WavpackHeader *) wps->blockbuff)->crc = 0xffffffff; if (wpc->wrapper_bytes) { wpmd.id = ID_RIFF_HEADER; wpmd.byte_length = wpc->wrapper_bytes; wpmd.data = wpc->wrapper_data; copy_metadata (&wpmd, wps->blockbuff, wps->blockend); free_metadata (&wpmd); wpc->wrapper_data = NULL; wpc->wrapper_bytes = 0; } write_decorr_terms (wps, &wpmd); copy_metadata (&wpmd, wps->blockbuff, wps->blockend); free_metadata (&wpmd); write_decorr_weights (wps, &wpmd); copy_metadata (&wpmd, wps->blockbuff, wps->blockend); free_metadata (&wpmd); write_decorr_samples (wps, &wpmd); copy_metadata (&wpmd, wps->blockbuff, wps->blockend); free_metadata (&wpmd); write_entropy_vars (wps, &wpmd); copy_metadata (&wpmd, wps->blockbuff, wps->blockend); free_metadata (&wpmd); if ((wps->wphdr.flags & INITIAL_BLOCK) && !wps->sample_index) { write_config_info (wpc, &wpmd); copy_metadata (&wpmd, wps->blockbuff, wps->blockend); free_metadata (&wpmd); } bs_open_write (&wps->wvbits, wps->blockbuff + ((WavpackHeader *) wps->blockbuff)->ckSize + 12, wps->blockend); return TRUE; } static void decorr_stereo_pass (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr, int m); static void decorr_stereo_pass_18 (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr); static void decorr_stereo_pass_17 (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr); static void decorr_stereo_pass_m2 (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr); int pack_samples (WavpackContext *wpc, int32_t *buffer, uint32_t sample_count) { WavpackStream *wps = &wpc->stream; uint32_t flags = wps->wphdr.flags; struct decorr_pass *dpp; int32_t *bptr, *eptr; int tcount, m; uint32_t crc; if (!sample_count) return TRUE; eptr = buffer + sample_count * ((flags & MONO_FLAG) ? 1 : 2); m = ((WavpackHeader *) wps->blockbuff)->block_samples & (MAX_TERM - 1); crc = ((WavpackHeader *) wps->blockbuff)->crc; /////////////////////// handle lossless mono mode ///////////////////////// if (!(flags & HYBRID_FLAG) && (flags & MONO_FLAG)) for (bptr = buffer; bptr < eptr;) { int32_t code; crc = crc * 3 + (code = *bptr); for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) { int32_t sam; if (dpp->term > MAX_TERM) { if (dpp->term & 1) sam = 2 * dpp->samples_A [0] - dpp->samples_A [1]; else sam = (3 * dpp->samples_A [0] - dpp->samples_A [1]) >> 1; dpp->samples_A [1] = dpp->samples_A [0]; dpp->samples_A [0] = code; } else { sam = dpp->samples_A [m]; dpp->samples_A [(m + dpp->term) & (MAX_TERM - 1)] = code; } code -= apply_weight_i (dpp->weight_A, sam); update_weight (dpp->weight_A, 2, sam, code); } m = (m + 1) & (MAX_TERM - 1); *bptr++ = code; } //////////////////// handle the lossless stereo mode ////////////////////// else if (!(flags & HYBRID_FLAG) && !(flags & MONO_FLAG)) { if (flags & JOINT_STEREO) for (bptr = buffer; bptr < eptr; bptr += 2) { crc = crc * 9 + (bptr [0] * 3) + bptr [1]; bptr [1] += ((bptr [0] -= bptr [1]) >> 1); } else for (bptr = buffer; bptr < eptr; bptr += 2) crc = crc * 9 + (bptr [0] * 3) + bptr [1]; for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount-- ; dpp++) { if (dpp->term == 17) decorr_stereo_pass_17 (dpp, buffer, eptr); else if (dpp->term == 18) decorr_stereo_pass_18 (dpp, buffer, eptr); else if (dpp->term >= 1 && dpp->term <= 7) decorr_stereo_pass (dpp, buffer, eptr, m); else if (dpp->term == -2) decorr_stereo_pass_m2 (dpp, buffer, eptr); } } send_words (buffer, sample_count, flags, &wps->w, &wps->wvbits); ((WavpackHeader *) wps->blockbuff)->crc = crc; ((WavpackHeader *) wps->blockbuff)->block_samples += sample_count; wps->sample_index += sample_count; return TRUE; } static void decorr_stereo_pass (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr, int m) { int k = (m + dpp->term) & (MAX_TERM - 1); int32_t sam; while (bptr < eptr) { dpp->samples_A [k] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_A, (sam = dpp->samples_A [m])); update_weight (dpp->weight_A, 2, sam, bptr [0]); bptr++; dpp->samples_B [k] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_B, (sam = dpp->samples_B [m])); update_weight (dpp->weight_B, 2, sam, bptr [0]); bptr++; m = (m + 1) & (MAX_TERM - 1); k = (k + 1) & (MAX_TERM - 1); } } static void decorr_stereo_pass_18 (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr) { int32_t sam; while (bptr < eptr) { sam = (3 * dpp->samples_A [0] - dpp->samples_A [1]) >> 1; dpp->samples_A [1] = dpp->samples_A [0]; dpp->samples_A [0] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_A, sam); update_weight (dpp->weight_A, 2, sam, bptr [0]); bptr++; sam = (3 * dpp->samples_B [0] - dpp->samples_B [1]) >> 1; dpp->samples_B [1] = dpp->samples_B [0]; dpp->samples_B [0] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_B, sam); update_weight (dpp->weight_B, 2, sam, bptr [0]); bptr++; } } static void decorr_stereo_pass_m2 (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr) { int32_t sam_A, sam_B; for (; bptr < eptr; bptr += 2) { sam_A = bptr [1]; sam_B = dpp->samples_B [0]; dpp->samples_B [0] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_A, sam_A); update_weight_clip (dpp->weight_A, 2, sam_A, bptr [0]); bptr [1] -= apply_weight_i (dpp->weight_B, sam_B); update_weight_clip (dpp->weight_B, 2, sam_B, bptr [1]); } } static void decorr_stereo_pass_17 (struct decorr_pass *dpp, int32_t *bptr, int32_t *eptr) { int32_t sam; while (bptr < eptr) { sam = 2 * dpp->samples_A [0] - dpp->samples_A [1]; dpp->samples_A [1] = dpp->samples_A [0]; dpp->samples_A [0] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_A, sam); update_weight (dpp->weight_A, 2, sam, bptr [0]); bptr++; sam = 2 * dpp->samples_B [0] - dpp->samples_B [1]; dpp->samples_B [1] = dpp->samples_B [0]; dpp->samples_B [0] = bptr [0]; bptr [0] -= apply_weight_i (dpp->weight_B, sam); update_weight (dpp->weight_B, 2, sam, bptr [0]); bptr++; } } int pack_finish_block (WavpackContext *wpc) { WavpackStream *wps = &wpc->stream; struct decorr_pass *dpp; uint32_t data_count; int tcount, m; m = ((WavpackHeader *) wps->blockbuff)->block_samples & (MAX_TERM - 1); if (m) for (tcount = wps->num_terms, dpp = wps->decorr_passes; tcount--; dpp++) if (dpp->term > 0 && dpp->term <= MAX_TERM) { int32_t temp_A [MAX_TERM], temp_B [MAX_TERM]; int k; memcpy (temp_A, dpp->samples_A, sizeof (dpp->samples_A)); memcpy (temp_B, dpp->samples_B, sizeof (dpp->samples_B)); for (k = 0; k < MAX_TERM; k++) { dpp->samples_A [k] = temp_A [m]; dpp->samples_B [k] = temp_B [m]; m = (m + 1) & (MAX_TERM - 1); } } flush_word (&wps->w, &wps->wvbits); data_count = bs_close_write (&wps->wvbits); if (data_count) { if (data_count != (uint32_t) -1) { uchar *cptr = wps->blockbuff + ((WavpackHeader *) wps->blockbuff)->ckSize + 8; *cptr++ = ID_WV_BITSTREAM | ID_LARGE; *cptr++ = data_count >> 1; *cptr++ = data_count >> 9; *cptr++ = data_count >> 17; ((WavpackHeader *) wps->blockbuff)->ckSize += data_count + 4; } else return FALSE; } return TRUE; } a> 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2006 Jonathan Gordon
 *
 * 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 "action.h"
#include "oldmenuapi.h"

#if PLUGIN_BUFFER_SIZE > 0x45000
#define MAX_CHARS    0x40000 /* 128 kiB */
#else
#define MAX_CHARS   0x6000 /* 24 kiB */
#endif
#define MAX_LINE_LEN 2048
PLUGIN_HEADER
static struct plugin_api* rb;

static char buffer[MAX_CHARS];
static char eol[3];
static int char_count = 0;
static int line_count = 0;
static int last_action_line = 0;
static int last_char_index = 0;

#define ACTION_INSERT 0
#define ACTION_GET    1
#define ACTION_REMOVE 2
#define ACTION_UPDATE 3
#define ACTION_CONCAT 4

int _do_action(int action, char* str, int line);
#ifndef HAVE_ADJUSTABLE_CPU_FREQ
#define do_action _do_action
#else
int do_action(int action, char* str, int line)
{
    int r;
    rb->cpu_boost(1);
    r = _do_action(action,str,line);
    rb->cpu_boost(0);
    return r;
}
#endif

int _do_action(int action, char* str, int line)
{
    int len;
    int i=0,c=0;
    if (line>=last_action_line)
    {
        i = last_action_line;
        c = last_char_index;
    }
    while (i<line && i<line_count)
    {
        c += rb->strlen(&buffer[c])+1;
        i++;
    }
    switch (action)
    {
        case ACTION_INSERT:
            len = rb->strlen(str)+1;
            if ( char_count+ len > MAX_CHARS )
                return 0;
            rb->memmove(&buffer[c+len],&buffer[c],char_count);
            rb->strcpy(&buffer[c],str);
            char_count += len;
            line_count++;
            break;
        case ACTION_GET:
            if (line > line_count)
                return 0;
            last_action_line = i;
            last_char_index = c;
            return c;
            break;
        case ACTION_REMOVE:
            if (line > line_count)
                return 0;
            len = rb->strlen(&buffer[c])+1;
            char_count -= len;
            rb->memmove(&buffer[c],&buffer[c+len],char_count);
            line_count--;
            break;
        case ACTION_UPDATE:
            if (line > line_count)
                return 0;
            len = rb->strlen(&buffer[c])+1;
            rb->memmove(&buffer[c+rb->strlen(str)+1],&buffer[c+len],char_count);
            rb->strcpy(&buffer[c],str);
            char_count += rb->strlen(str)+1-len;
            break;
        case ACTION_CONCAT:
            if (line > line_count)
                return 0;
            rb->memmove(&buffer[c-1],&buffer[c],char_count);
            break;
        default:
            return 0;
    }
    last_action_line = i;
    last_char_index = c;
    return 1;
}
char *list_get_name_cb(int selected_item,void* data,char* buf)
{
    char *b = &buffer[do_action(ACTION_GET,0,selected_item)];
    (void)data;
    if (rb->strlen(b) >= MAX_PATH)
    {
        char t = b[MAX_PATH-10];
        b[MAX_PATH-10] = '\0';
        rb->snprintf(buf,MAX_PATH,"%s ...",b);
        b[MAX_PATH-10] = t;
    }
    else rb->strcpy(buf,b);
    return buf;
}
char filename[MAX_PATH];
int get_eol_string(char* fn)
{
    int fd=-1;
    char t;
    if (!fn)
        return 0;
    else if  (!fn[0])
        return 0;
    fd = rb->PREFIX(open(fn,O_RDONLY));
    if (fd<0)
        return 0;
    eol[0] = '\0';
    while (!eol[0])
    {
        if (!rb->read(fd,&t,1))
        {
            rb->strcpy(eol,"\n");
            return 0;
        }
        if (t == '\r')
        {
            if (rb->read(fd,&t,1) && t=='\n')
                rb->strcpy(eol,"\r\n");
            else rb->strcpy(eol,"\r");
        }
        else if (t == '\n')
        {
            rb->strcpy(eol,"\n");
        }
    }
    rb->close(fd);
    return 1;
}

void save_changes(int overwrite)
{
    int fd;
    int i;

    if (!filename[0] || !overwrite)
    {
        rb->strcpy(filename,"/");
        rb->kbd_input(filename,MAX_PATH);
    }

    fd = rb->open(filename,O_WRONLY|O_CREAT|O_TRUNC);
    if (fd < 0)
    {
        rb->splash(HZ*2, "Changes NOT saved");
        return;
    }

    if (!overwrite)
        /* current directory may have changed */
        rb->reload_directory();

    rb->lcd_clear_display();
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(1);
#endif
    for (i=0;i<line_count;i++)
    {
        rb->fdprintf(fd,"%s%s",&buffer[do_action(ACTION_GET,0,i)],eol);
    }
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(0);
#endif
    rb->close(fd);
}

void setup_lists(struct gui_synclist *lists, int sel)
{
    rb->gui_synclist_init(lists,list_get_name_cb,0, false, 1);
    rb->gui_synclist_set_icon_callback(lists,NULL);
    rb->gui_synclist_set_nb_items(lists,line_count);
    rb->gui_synclist_limit_scroll(lists,true);
    rb->gui_synclist_select_item(lists, sel);
    rb->gui_synclist_draw(lists);
}
enum {
    MENU_RET_SAVE = -1,
    MENU_RET_NO_UPDATE,
    MENU_RET_UPDATE,
};
int do_item_menu(int cur_sel, char* copy_buffer)
{
    int m, ret = 0;
    static const struct menu_item items[] = {
            { "Cut", NULL },
            { "Copy", NULL },
            { "", NULL },
            { "Insert Above", NULL },
            { "Insert Below", NULL },
            { "", NULL },
            { "Cat To Above",NULL },
            { "", NULL },
            { "Save", NULL },
    };
    m = menu_init(rb, items, sizeof(items) / sizeof(*items),
    NULL, NULL, NULL, NULL);

    switch (menu_show(m))
    {
        case 0: /* cut */
            rb->strcpy(copy_buffer,&buffer[do_action(ACTION_GET,0,cur_sel)]);
            do_action(ACTION_REMOVE,0,cur_sel);
            ret = MENU_RET_UPDATE;
        break;
        case 1: /* copy */
            rb->strcpy(copy_buffer,&buffer[do_action(ACTION_GET,0,cur_sel)]);
            ret = MENU_RET_NO_UPDATE;
        break;
        case 2: /* blank */
            ret = MENU_RET_NO_UPDATE;
        break;

        case 3: /* insert above */
            if (!rb->kbd_input(copy_buffer,MAX_LINE_LEN))
            {
                do_action(ACTION_INSERT,copy_buffer,cur_sel);
                copy_buffer[0]='\0';
                ret = MENU_RET_UPDATE;
            }
        break;
        case 4: /* insert below */
            if (!rb->kbd_input(copy_buffer,MAX_LINE_LEN))
            {
                do_action(ACTION_INSERT,copy_buffer,cur_sel+1);
                copy_buffer[0]='\0';
                ret = MENU_RET_UPDATE;
            }
        break;
        case 5: /* blank */
            ret = MENU_RET_NO_UPDATE;
        break;
        case 6: /* cat to above */
            if (cur_sel>0)
            {
                do_action(ACTION_CONCAT,0,cur_sel);
                ret = MENU_RET_UPDATE;
            }
        break;
        case 7: /* save */
            ret = MENU_RET_SAVE;
        break;
        default:
            ret = MENU_RET_NO_UPDATE;
        break;
    }
    menu_exit(m);
    return ret;
}

#ifdef HAVE_LCD_COLOR
/* in misc.h but no need to polute the api */
#define toupper(c) (((c >= 'a') && (c <= 'z'))?c+'A':c)
#define isxdigit(c) ((c>='a' && c<= 'f') || (c>='A' && c<= 'F') \
                    || (c>='0' && c<= '9'))
#define hex2dec(c) (((c) >= '0' && ((c) <= '9')) ? (toupper(c)) - '0' : \
                                                   (toupper(c)) - 'A' + 10)
int hex_to_rgb(const char* hex)
{   int ok = 1;
    int i;
    int red, green, blue;

    if (rb->strlen(hex) == 6) {
        for (i=0; i < 6; i++ ) {
            if (!isxdigit(hex[i])) {
                ok=0;
                break;
            }
        }

        if (ok) {
            red = (hex2dec(hex[0]) << 4) | hex2dec(hex[1]);
            green = (hex2dec(hex[2]) << 4) | hex2dec(hex[3]);
            blue = (hex2dec(hex[4]) << 4) | hex2dec(hex[5]);
            return LCD_RGBPACK(red,green,blue);
        }
    }

    return 0;
}
#endif /* HAVE_LCD_COLOR */

/* this is the plugin entry point */
enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
{
    int fd;
    static char temp_line[MAX_LINE_LEN];

    struct gui_synclist lists;
    bool exit = false;
    int button;
    bool changed = false;
    int cur_sel=0;
    static char copy_buffer[MAX_LINE_LEN];
    bool prev_show_statusbar;
#ifdef HAVE_LCD_COLOR
    bool edit_colors_file = false;
#endif

    rb = api;

    copy_buffer[0]='\0';
    prev_show_statusbar = rb->global_settings->statusbar;
    rb->global_settings->statusbar = false;

#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(1);
#endif
    if (parameter)
    {
#ifdef HAVE_LCD_COLOR
        char *c = NULL;
#endif
        rb->strcpy(filename,(char*)parameter);
        if (!get_eol_string(filename))
        {
            rb->strcpy(eol,"\n");
        }
        fd = rb->open(filename,O_RDONLY);
        if (fd<0)
        {
            rb->splash(HZ*2,"Couldnt open file: %s",(char*)parameter);
            return PLUGIN_ERROR;
        }
#ifdef HAVE_LCD_COLOR
        c = rb->strrchr(filename, '.');
        if (c && !rb->strcmp(c, ".colours"))
            edit_colors_file = true;
#endif
        /* read in the file */
        while (rb->read_line(fd,temp_line,MAX_LINE_LEN))
        {
            if (!do_action(ACTION_INSERT,temp_line,line_count))
            {
                rb->splash(HZ*2,"Error reading file: %s",(char*)parameter);
                rb->close(fd);
                return PLUGIN_ERROR;
            }
        }
        rb->close(fd);
    }
    else
    {
        filename[0] = '\0';
        rb->strcpy(eol,"\n");
    }
#ifdef HAVE_ADJUSTABLE_CPU_FREQ
    rb->cpu_boost(0);
#endif
    /* now dump it in the list */
    setup_lists(&lists,0);
    rb->lcd_update();
    while (!exit)
    {
        rb->gui_synclist_draw(&lists);
        cur_sel = rb->gui_synclist_get_sel_pos(&lists);
        button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
        if (rb->gui_synclist_do_button(&lists,button,LIST_WRAP_UNLESS_HELD))
            continue;
        switch (button)
        {
            case ACTION_STD_OK:
            {
                bool edit_text = true;
#ifdef HAVE_LCD_COLOR
                int color;
#endif
                if (line_count)
                    rb->strcpy(temp_line,&buffer[do_action(ACTION_GET,0,cur_sel)]);
#ifdef HAVE_LCD_COLOR
                if (edit_colors_file)
                {
                    char *name = temp_line, *value = NULL;
                    char extension[MAX_LINE_LEN];
                    rb->settings_parseline(temp_line, &name, &value);
                    if (line_count)
                    {
                        MENUITEM_STRINGLIST(menu, "Edit What?", NULL, 
                                            "Extension", "Color",);
                        switch (rb->do_menu(&menu, NULL))
                        {
                            case 0:
                                edit_text = true;
                                break;
                            case 1:
                                edit_text = false;
                                if (value)
                                    color = hex_to_rgb(value);
                                else color = 0;
                                rb->strcpy(extension, name);
                                rb->set_color(rb->screens[SCREEN_MAIN], name, &color, -1);
                                rb->snprintf(temp_line, MAX_LINE_LEN, "%s: %02X%02X%02X",
                                             extension, RGB_UNPACK_RED(color),
                                             RGB_UNPACK_GREEN(color),
                                             RGB_UNPACK_BLUE(color));
                                if (line_count)
                                {
                                    do_action(ACTION_UPDATE,temp_line,cur_sel);
                                }
                                else do_action(ACTION_INSERT,temp_line,cur_sel);
                                changed = true;
                                break;
                        }
                    }
                }
#endif
                if (edit_text &&!rb->kbd_input(temp_line,MAX_LINE_LEN))
                {
                    if (line_count)
                    {
                        do_action(ACTION_UPDATE,temp_line,cur_sel);
                    }
                    else do_action(ACTION_INSERT,temp_line,cur_sel);
                    changed = true;
                }
            }
            break;
#ifdef TEXT_EDITOR_DELETE
            case TEXT_EDITOR_DELETE:
#ifdef TEXT_EDITOR_DELETE_PRE
                if (last_button != TEXT_EDITOR_DELETE_PRE)
                    break;
#endif
                if (!line_count) break;
                rb->strcpy(copy_buffer,&buffer[do_action(ACTION_GET,0,cur_sel)]);
                do_action(ACTION_REMOVE,0,cur_sel);
                changed = true;
            break;
#endif
            case ACTION_STD_MENU:
            { /* do the item menu */
                switch (do_item_menu(cur_sel, copy_buffer))
                {
                    case MENU_RET_SAVE:
                        save_changes(1);
                        changed = false;
                    break;
                    case MENU_RET_UPDATE:
                        changed = true;
                    break;
                    case MENU_RET_NO_UPDATE:
                    break;
                }
            }
            break;
            case ACTION_STD_CANCEL:
                if (changed)
                {
                    int m;
                    int result;

                    static const struct menu_item items[] = {
                            { "Return", NULL },
                            { " ", NULL },
                            { "Save Changes", NULL },
                            { "Save As...", NULL },
                            { " ", NULL },
                            { "Save and Exit", NULL },
                            { "Ignore Changes and Exit", NULL },
                    };

                    m = menu_init(rb, items, sizeof(items) / sizeof(*items),
                    NULL, NULL, NULL, NULL);

                    result=menu_show(m);

                    switch (result)
                    {
                        case 0:
                        break;
                        case 2: //save to disk
                            save_changes(1);
                            changed = 0;
                        break;
                        case 3:
                            save_changes(0);
                            changed = 0;
                        break;

                        case 5:
                            save_changes(1);
                            exit=1;
                        break;
                        case 6:
                            exit=1;
                        break;
                    }
                    menu_exit(m);
                }
                else exit=1;
            break;
        }
        rb->gui_synclist_set_nb_items(&lists,line_count);
    }