Bible eBook Prototype More details at: http://mbed.org/users/davervw/notebook/ebible-abstract/

Dependencies:   SDHCFileSystem TextLCD mbed BibleIO

BibleUI.cpp

Committer:
davervw
Date:
2011-02-27
Revision:
0:b9d9145827e2

File content as of revision 0:b9d9145827e2:

/* Bible UI Class Implementation - KJV Bible eBook Browser
 *
 * Copyright (c) 2011 David R. Van Wagner davervw@yahoo.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "BibleUI.h"

//extern Serial console;

BibleUI::BibleUI(BibleIO &bible, TextLCD &textlcd, DigitalIn& left, DigitalIn& right)
    :HolyBible(bible), lcd(textlcd), lb(left), rb(right), led1(LED1), led2(LED2), led3(LED3), led4(LED4)
{
    //console.printf("\n\n" "Bible LCD\n\n");
    lcd.cls();
    lcd.printf("BibleLCD");

    wait(0.5);

    show_title();
    
    book = 0;
    chapter = 0;
    verse = 0;
    offset = 0;
    disp_len = 0;
}
    
void BibleUI::start()
{
    main();
}
    
void BibleUI::display_nav()
{
    lcd.cls();
    char* book_name = HolyBible.title_book(book);
    if (strlen(book_name) >= lcd.columns())
        book_name[lcd.columns()] = 0;
    lcd.printf("%s%s%d:%d", book_name, strlen(book_name)==lcd.columns()?"":"\n", chapter+1, verse+1);
    //console.printf("%s %d:%d", abbrev, chapter+1, verse+1);
    delete [] book_name;
}

bool BibleUI::append_next_verse(char*& text, short len, short book, short chapter, short verse)
{
    int size = 0;
    do
    {
        // get next verse
        char* next_verse = HolyBible.text_verse(book, chapter, ++verse);
        if (next_verse == 0)
            return size>0; // stop at end of chapter, return true if text has lengthened at all
        size += strlen(next_verse);

        // combine            
        char* new_verse = new char[strlen(text)+strlen(next_verse)+2]; // add room for space delimiter and nul
        strcpy(new_verse, text);
        strcat(new_verse, " ");
        strcat(new_verse, next_verse);
        delete [] text;
        delete [] next_verse;
        text = new_verse;
    } while (size < len); // repeat until enough characters added
    
    return size>0;
}

void BibleUI::display_verse()
{
    // fix positioning, check ranges
    char* text = 0;
    if (offset < 0)
    {
        if (offset > -lcd.rows()*lcd.columns())
            offset = 0; // be sure to show beginning of verse
        else
        {
            if (verse > 0) // previous verse
                --verse;
            else
            {
                if (chapter > 0) // last verse in previous chapter
                {
                    --chapter;
                    verse = HolyBible.get_num_verses(book, chapter)-1;
                }
                else if (book > 0) // last verse in previous book
                {
                    --book;
                    chapter = HolyBible.get_num_chapters(book)-1;
                    verse = HolyBible.get_num_verses(book, chapter)-1;
                }
                else //end of bible
                {
                    book = HolyBible.get_num_books() - 1;
                    chapter = HolyBible.get_num_chapters(book)-1;
                    verse = HolyBible.get_num_verses(book, chapter)-1;
                }
                display_nav();
                wait(0.5);
            }
        }
        text = HolyBible.text_verse(book, chapter, verse);
        if (offset < 0)
        {
            int len = strlen(text);
            if (len > lcd.rows()*lcd.columns())
                offset = len-lcd.rows()*lcd.columns();
            else
                offset = 0;
        }
    }
    else
        text = HolyBible.text_verse(book, chapter, verse);
    if (offset == 0)
    {
        char* text_start = strchr(text, ' ');
        //console.printf("%s\n", text_start);
    }
    if (offset >= strlen(text))
    {
        offset = 0;
        delete [] text;
        text = HolyBible.text_verse(book, chapter, ++verse); // next verse
        if (text == 0)
        {
            text = HolyBible.text_verse(book, ++chapter, verse=0); // next chapter
            if (text == 0)
                text = HolyBible.text_verse(++book, chapter=0, verse=0); // next book
            if (text == 0)
                text = HolyBible.text_verse(book=0, chapter=0, verse=0); // end
            display_nav();
            wait(0.5);
        }
    }

    // append enough text to fill screen and then some
    append_next_verse(text, lcd.rows()*(lcd.columns()+1), book, chapter, verse);

    // find current position within verse(s)
    char* verse_text = text + offset;
    
    // make a copy starting at offset so it can be modified
    char* copy = new char[strlen(verse_text)+1];
    strcpy(copy, verse_text);
    delete [] text; // note: text was allocated, verse_text was just pointed into it
    text = 0;
    verse_text = 0;

    // word wrap
    disp_len = word_wrap(copy);
    /*
    if (strlen(copy) > lcd.rows()*lcd.columns())
        copy[lcd.rows()*lcd.columns()] = 0;
    disp_len = strlen(copy);
    */

    // display
    lcd.locate(0, 0);
    lcd.printf("%s", copy);
    
    // pad with spaces (avoids cls which can cause some flashing)
    int max_len = lcd.rows()*lcd.columns();
    for (int i=strlen(copy); i<max_len; ++i)
        lcd.printf(" ");

    // free up storage
    delete [] copy;
    copy = 0;
}

static bool alphanumeric(char c)
{
    return c >= '0' && c <= '9'
        || c >= 'A' && c <= 'Z'
        || c >= 'a' && c <= 'z';
}

static bool punc_or_space(char c)
{
    return !alphanumeric(c);
}

int BibleUI::word_wrap(char* &text)
{
    // make copy of original text for later comparison
    char* orig = new char[strlen(text)+1];
    strcpy(orig, text);

    // remove unwanted strings
    remove_string(text, "# ");
    remove_string(text, "[");
    remove_string(text, "]");

    // scan each line
    for (int row=0; row<lcd.rows(); ++row)
    {
        int start = row * lcd.columns();
        int end = start + lcd.columns();

        // remove spaces at start of line
        while (start < strlen(text) && text[start]==' ')
        {
            // delete one character
            int left = start;
            int right = strlen(text+left+1);
            char* new_text = new char[left+right+1];
            memcpy(new_text, text, left);
            new_text[left] = 0;
            strcat(new_text, text+left+1);
            delete [] text;
            text = new_text;
        }

        // insert spaces to wrap words
        if (strlen(text) > end && !punc_or_space(text[end]))
        {
            int left;
            for (left=end-1; left>=start && !punc_or_space(text[left]); --left)
                ;
            if (left > start && left < end)
            {
                ++left;
                int right=strlen(text+left);
                int spaces=end-left;
                //printf("row=%d total=%d left=%d spaces=%d right=%d\n", row, strlen(text), left, spaces, right);
                char* new_text = new char[left+spaces+right+1];
                memcpy(new_text, text, left);
                new_text[left] = 0;
                for (int i=0; i<spaces; ++i)
                    strcat(new_text, " ");
                strcat(new_text, text+left);
                delete [] text;
                text = new_text;
            }
        }
    }

    // truncate to screen size
    int max_len = lcd.rows()*lcd.columns();
    if (strlen(text)>=max_len)
        text[max_len] = 0;

    // make copy with non-spaces
    char* nospaces = new char[strlen(text)+1];
    int len = 0;
    for (int i=0; i<strlen(text); ++i)
        if (text[i] != ' ')
            nospaces[len++] = text[i];
    nospaces[len] = 0;

    // calculate the number of characters displayed
    int src=0;
    for (int dest=0; dest<len; ++dest)
    {
        // non-space character must be there, advance in original until find it
        while (orig[src++] != nospaces[dest])
            ;
    }
    
    // free up storage
    delete [] nospaces;
    nospaces = 0;
    delete [] orig;
    orig = 0;

    // return length of source string that matches wrapped text, so know how to advance to next portion of verse
    return src;
}

void BibleUI::remove_string(char* &text, char* find)
{
    while (true)
    {
        char* p = strstr(text, find);
        if (p == 0)
            return;
        int left = p-text;
        int right = strlen(p+strlen(find));
        char* new_text = new char[left+right+1];
        memcpy(new_text, text, left);
        new_text[left] = 0;
        strcat(new_text, p+strlen(find));
        delete [] text;
        text = new_text;
    }
}

void BibleUI::show_title()
{
    lcd.cls();
    char* title = HolyBible.title();
    lcd.printf("%s", title);
    //console.printf("%s\n", title);
    delete [] title;
    title = 0;
}

void BibleUI::main()
{
    enum emode { MODE_OFFSET=0, MODE_VERSE=1, MODE_CHAPTER=2, MODE_BOOK=3, MODE_4xBOOK=4, MODE_BOOKMARK=5 };
    
    emode mode = MODE_OFFSET;

    display_nav();
    wait(0.5);
    display_verse();

    // debug: display all text
    /*
    int last_book = book;
    int pages = -1;
    while (book >= last_book)
    {
        ++pages;
        last_book = book;
    
        display_verse();

        char* book_name = HolyBible.title_book(book);
        console.printf("%s %hd:%hd                                                                  \r", book_name, chapter+1, verse+1);
        delete [] book_name;

        //wait(0.025);
        offset += disp_len;
    }
    lcd.cls();
    lcd.printf("%d", pages);
    return;
    */
    
    Timer timer;
    int old_buttons = 0; // start assuming nothing pressed
    while (true)
    {
        // wait for something to happen
        int new_buttons = rb + 2*lb;
        if (new_buttons != old_buttons)
        {
            // simple debounce logic, wait for 25ms of steady state else start over
            bool debounce = false;    
            int buttons = new_buttons;
            timer.reset();
            timer.start();

            led1 = lb;
            led2 = rb;
        
            while (timer.read_ms() < 25)
            {
                int new_buttons = rb + 2*lb;
                if (new_buttons != buttons)
                {
                    debounce = false;
                    break;
                }   
                else
                    debounce = true;
            }
            //console.printf("debounce: %d\n", new_buttons);

            if (debounce && new_buttons == 0)
            {
                while (true)
                {
                    int new_buttons = rb + 2*lb;
                    led1 = lb;
                    led2 = rb;

                    if (new_buttons != 0)
                        break;
                    if (timer.read_ms() > 2000)
                    {
                        mode = MODE_OFFSET;
                        display_verse();
                        break;
                    }
                }
                buttons = new_buttons = 0; // pretend we didn't see a change yet
            }
            else if (debounce && new_buttons != 0)
            {
                timer.reset();
                timer.start();

                emode next_mode = mode;

                int start_ms = 0;                
                while(true)
                {
                    int new_buttons = rb + 2*lb;
                    led1 = lb;
                    led2 = rb;

                    if (buttons != new_buttons)
                    {
                        mode = next_mode;
                        if (mode == MODE_OFFSET || mode == MODE_VERSE)
                            display_verse();
                        break;
                    }
    
                    {        
                        int elapsed = timer.read_ms();
                        int inc_elapsed = elapsed - start_ms;
                        if (elapsed >= 4000 && inc_elapsed >= 250 || mode == MODE_4xBOOK && inc_elapsed >= 25 && (start_ms == 0 || inc_elapsed >= 500) )
                        {
                            //console.printf("4xBOOK elapsed=%d inc_elapsed=%d\n", elapsed, inc_elapsed);
                            if (buttons == 3)
                            {
                            }
                            if (buttons == 2)
                            {
                                book-=4;
                                if (book < 0)
                                    book = 0;
                            }
                            if (buttons == 1)
                            {
                                book+=4;
                                if (book >= HolyBible.get_num_books())
                                    book = HolyBible.get_num_books()-1;
                            }
                            if (buttons != 3)
                            {
                                chapter = 0;
                                verse = 0;
                                offset = 0;
                                display_nav();
                                next_mode = MODE_4xBOOK;
                            }
                            start_ms += inc_elapsed;
                        }
                        else if (elapsed >= 3000 && inc_elapsed >= 250 || mode == MODE_BOOK && inc_elapsed >= 25 && (start_ms == 0 || inc_elapsed >= 500) )
                        {
                            //console.printf("BOOK elapsed=%d inc_elapsed=%d\n", elapsed, inc_elapsed);
                            if (buttons == 3)
                            {
                            }
                            if (buttons == 2)
                            {
                                if (chapter == 0 && --book < 0)
                                    book = 0;
                                chapter = 0;
                                verse = 0;
                                offset = 0;
                            }
                            if (buttons == 1)
                            {
                                if (++book >= HolyBible.get_num_books())
                                {
                                    book = HolyBible.get_num_books()-1;
                                    chapter = HolyBible.get_num_chapters(book)-1;
                                    verse = HolyBible.get_num_verses(book, chapter)-1;
                                    offset = 0;
                                }
                                else
                                {
                                    chapter = 0;
                                    verse = 0;
                                    offset = 0;
                                }
                            }
                            if (buttons != 3)
                            {
                                display_nav();
                                next_mode = MODE_BOOK;
                            }
                            start_ms += inc_elapsed;
                        }
                        else if (elapsed >= 2000 && inc_elapsed >= 250 || mode == MODE_CHAPTER && inc_elapsed >= 25 && (start_ms == 0 || inc_elapsed >= 500) )
                        {
                            //console.printf("CHAPTER elapsed=%d inc_elapsed=%d\n", elapsed, inc_elapsed);
                            if (buttons == 3)
                            {
                            }
                            if (buttons == 2)
                            {
                                if (verse == 0 && --chapter < 0)
                                {
                                    if (--book < 0)
                                    {
                                        book = 0;
                                        chapter = 0;
                                    }
                                    else
                                        chapter = HolyBible.get_num_chapters(book)-1;
                                }
                            }
                            if (buttons == 1)
                            {
                                if (++chapter >= HolyBible.get_num_chapters(book))
                                {
                                    if (++book >= HolyBible.get_num_books())
                                    {
                                        book = HolyBible.get_num_books()-1;
                                        chapter = HolyBible.get_num_chapters(book)-1;
                                    }
                                    else
                                        chapter = 0;
                                }
                            }
                            if (buttons != 3)
                            {
                                verse = 0;
                                offset = 0;
                                display_nav();
                                next_mode = MODE_CHAPTER;
                            }
                            start_ms += inc_elapsed;
                        }
                        else if (elapsed >= 1000 && inc_elapsed >= 250 || mode == MODE_VERSE && inc_elapsed >= 25 && (start_ms == 0 || inc_elapsed >= 500) )
                        {
                            //console.printf("VERSE elapsed=%d inc_elapsed=%d\n", elapsed, inc_elapsed);
                            if (buttons == 3 && next_mode == MODE_BOOKMARK)
                            {
                                if (!HolyBible.bookmark_add(book, chapter, verse))
                                    HolyBible.bookmark_del(book, chapter, verse);
                                for (int i=0; i<3; ++i)
                                {
                                    led4 = 1;
                                    wait(0.2);
                                    led4 = 0;
                                    wait(0.2);
                                }

                                next_mode = MODE_OFFSET;
                            }
                            if (buttons == 2)
                            {
                                if (offset > 0)
                                    offset = 0;
                                else if (--verse < 0)
                                {
                                    verse = 0;
                                    offset = -lcd.rows()*lcd.columns();
                                }
                                else
                                    offset = 0;
                            }
                            if (buttons == 1)
                            {
                                char* text = HolyBible.text_verse(book, chapter, verse);
                                offset = strlen(text);
                                delete [] text;
                            }
                            display_verse();
                            next_mode = MODE_VERSE;
                            start_ms += inc_elapsed;
                        }
                        else if (mode == MODE_OFFSET && elapsed >= 25 && elapsed < 1000 && (start_ms == 0 || inc_elapsed >= 500))
                        {            
                            //console.printf("OFFSET elapsed=%d inc_elapsed=%d\n", elapsed, inc_elapsed);
                            if (buttons == 3)
                                next_mode = MODE_BOOKMARK;
                            if (buttons == 2)
                                offset -= lcd.rows()*lcd.columns();
                            if (buttons == 1)
                                offset += disp_len;
                            if (buttons != 3)
                            {
                                display_verse();
                                next_mode = MODE_OFFSET;
                            }
                            start_ms += inc_elapsed;
                        }
                        else if (mode == MODE_BOOKMARK && elapsed >= 25 && (start_ms == 0 || inc_elapsed >= 500))
                        {
                            if (buttons == 2)
                            {
                                if (HolyBible.bookmark_prev(book, chapter, verse))
                                {
                                    offset = 0;
                                    display_nav();
                                }
                            }
                            if (buttons == 1)
                            {
                                if (HolyBible.bookmark_next(book, chapter, verse))
                                {
                                    offset = 0;
                                    display_nav();
                                }
                            }
                            start_ms += inc_elapsed;
                        }
                   }
                }
            }
            old_buttons = new_buttons;
        }
    }
}

void BibleUI::indexing(int progress, void* context)
{
    //console.printf("Indexing %d%%\n", progress);
    TextLCD& lcd = *(TextLCD*)context;
    lcd.cls();
    lcd.printf("Indexing");
    lcd.locate(0,1);
    lcd.printf("%d%%", progress);
}