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

Dependencies:   SDHCFileSystem TextLCD mbed BibleIO

Revision:
0:b9d9145827e2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BibleUI.cpp	Sun Feb 27 18:54:37 2011 +0000
@@ -0,0 +1,621 @@
+/* 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);
+}