diff options
author | Your Name <you@example.com> | 2020-11-07 09:30:53 -0500 |
---|---|---|
committer | Your Name <you@example.com> | 2020-11-07 09:30:53 -0500 |
commit | 89a449c2ab9d736c1f03c78b745e814424e01360 (patch) | |
tree | 25c4d03bd39a5ddbe1b7f28713e82bf174a29216 | |
download | biblereader-89a449c2ab9d736c1f03c78b745e814424e01360.tar.gz biblereader-89a449c2ab9d736c1f03c78b745e814424e01360.tar.bz2 biblereader-89a449c2ab9d736c1f03c78b745e814424e01360.zip |
Initial commit
-rw-r--r-- | Makefile | 18 | ||||
-rw-r--r-- | header.cc | 210 | ||||
-rw-r--r-- | header.h | 31 | ||||
-rw-r--r-- | main.cc | 45 | ||||
-rw-r--r-- | mods.cc | 208 | ||||
-rw-r--r-- | mods.h | 53 | ||||
-rw-r--r-- | readerview.cc | 98 | ||||
-rw-r--r-- | readerview.h | 32 | ||||
-rw-r--r-- | readme.md | 19 | ||||
-rw-r--r-- | sword.cc | 145 | ||||
-rw-r--r-- | sword.h | 29 |
11 files changed, 888 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7778cce --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +CC=g++ +LIBS=gtkmm-3.0 +CFLAGS=-c -Wall +LDFLAGS=-pthread -lbible +SOURCES=header.cc main.cc mods.cc readerview.cc sword.cc +OBJECTS=$(SOURCES:.cc=.o) +EXECUTABLE=biblereader + +all: $(SOURCES) $(EXECUTABLE) + +$(EXECUTABLE): $(OBJECTS) + $(CC) $(LDFLAGS) $(OBJECTS) -o $@ `pkg-config $(LIBS) --libs` + +.cc.o: + $(CC) $(CFLAGS) $< -o $@ `pkg-config $(LIBS) --cflags` + +clean: + rm -rf *.o $(EXECUTABLE) diff --git a/header.cc b/header.cc new file mode 100644 index 0000000..c40e44e --- /dev/null +++ b/header.cc @@ -0,0 +1,210 @@ +#include "header.h" +#include "readerview.h" +#include "mods.h" +#include <gtk/gtk.h> + +Header::Header(ReaderView *reader, Gtk::Window *window) +: back(), forward(), book(), menu(), bookMenu(), menuMenu() +{ + this->mods = new Mods(this, window); + this->reader = reader; + this->window = window; + set_show_close_button(true); + set_title ("Bible Reader"); + set_has_subtitle(false); + //this->override_background_color(*(new Gdk::RGBA("#1d1f21"))); + //this->override_color(*(new Gdk::RGBA("#c5c8c6"))); + + back.set_image_from_icon_name("go-previous-symbolic"); + forward.set_image_from_icon_name("go-next-symbolic"); + menu.set_image_from_icon_name("open-menu-symbolic"); + + back.signal_clicked().connect([reader, this]() { + reader->setChapter(reader->getChapter() - 1); + updateButtons(); + }); + + forward.signal_clicked().connect([reader, this]() { + reader->setChapter(reader->getChapter() + 1); + updateButtons(); + }); + + pack_start(back); + pack_start(book); + pack_start(forward); + pack_end(menu); + + book.set_popover(bookMenu); + menu.set_popover(menuMenu); + + updateButtons(); + updateMenus(); +} + +Header::~Header() {} + +void Header::updateButtons() { + back.set_sensitive(reader->getChapter() > 1); + forward.set_sensitive(reader->getChapter() < reader->getChapterMax()); + if(reader->getBook() == "") { + book.set_label(""); + book.set_sensitive(false); + } else { + book.set_label(reader->getBook() + " " + std::to_string(reader->getChapter())); + book.set_sensitive(true); + } +} + +void Header::updateMenus() { + // Wipe them out and start over + if(bookMenu.get_children().size() != 0) { + delete bookMenu.get_children()[0]; + } + if(menuMenu.get_children().size() != 0) { + delete menuMenu.get_children()[0]; + } + + // Populate bookMenu + Gtk::HBox *outer = Gtk::manage(new Gtk::HBox); + bookMenu.add(*outer); + + //TODO: Disgusting code duplication. Fix with strategy pattern? Or loop? + + Gtk::ScrolledWindow *swl = Gtk::manage(new Gtk::ScrolledWindow); + Gtk::VBox *boxl = Gtk::manage(new Gtk::VBox); + outer->add(*swl); + swl->add(*boxl); + swl->set_propagate_natural_width(true); + swl->set_min_content_height(300); + for(std::string bookName : reader->getAllBooks()) { + Gtk::Button *item = Gtk::manage(new Gtk::Button(bookName)); + item->set_relief(Gtk::ReliefStyle::RELIEF_NONE); + item->signal_clicked().connect([bookName, this]() { + bookMenu.popdown(); + reader->setChapter(1); + reader->setBook(bookName); + updateButtons(); + updateMenus(); + showText(); + }); + boxl->add(*item); + } + + Gtk::ScrolledWindow *swr = Gtk::manage(new Gtk::ScrolledWindow); + Gtk::VBox *boxr = Gtk::manage(new Gtk::VBox); + outer->add(*swr); + swr->add(*boxr); + swr->set_propagate_natural_width(true); + swr->set_min_content_height(300); + for(int chapter = 1; chapter <= reader->getChapterMax(); chapter++) { + Gtk::Button *item = Gtk::manage(new Gtk::Button(std::to_string(chapter))); + item->set_relief(Gtk::ReliefStyle::RELIEF_NONE); + item->signal_clicked().connect([chapter, this]() { + bookMenu.popdown(); + reader->setChapter(chapter); + updateButtons(); + showText(); + }); + boxr->add(*item); + } + + bool *hasAllocated = new bool(false); + outer->signal_size_allocate().connect([this, swl, swr, hasAllocated](Gdk::Rectangle rec) { + if(! *hasAllocated) { + // First figure out the books + auto allBooks = reader->getAllBooks(); + int position = std::find(allBooks.begin(), allBooks.end(), reader->getBookFull()) - allBooks.begin(); + swl->get_vadjustment()->set_value(swl->get_vadjustment()->get_upper() * position / allBooks.size()); + swr->get_vadjustment()->set_value(swr->get_vadjustment()->get_upper() * (reader->getChapter() - 1) / reader->getChapterMax()); + } + *hasAllocated = true; + }); + + bookMenu.show_all_children(); + book.signal_pressed().connect([this, hasAllocated, swl, swr]() { + *hasAllocated = false; + }); + + // Populate menuMenu + Gtk::VBox *outerBox = Gtk::manage(new Gtk::VBox); + menuMenu.add(*outerBox); + + Gtk::VBox *versionBox = Gtk::manage(new Gtk::VBox); + Gtk::Label *scaleLabel = Gtk::manage(new Gtk::Label); + scaleLabel->set_text("Text Size"); + versionBox->add(*scaleLabel); + Gtk::Scale *scale = Gtk::manage(new Gtk::Scale); + scale->set_range(8000, 20000); + scale->set_value(libbible::settingsReadInt("fontsize")); + scale->set_draw_value(false); + scale->signal_value_changed().connect([scale, this]() { + libbible::settingsWriteInt("fontsize", (int) scale->get_value()); + this->reader->refresh(); + }); + versionBox->add(*scale); + + vector<string> versions = reader->getAllVersions(); + if(versions.size() > 8) { + Gtk::ScrolledWindow *sw = Gtk::manage(new Gtk::ScrolledWindow); + sw->set_propagate_natural_width(true); + sw->set_min_content_height(300); + sw->add(*versionBox); + outerBox->add(*sw); + } else { + outerBox->add(*versionBox); + } + for(string version : versions) { + Gtk::Button *item = Gtk::manage(new Gtk::Button(version)); + item->set_relief(Gtk::ReliefStyle::RELIEF_NONE); + item->signal_clicked().connect([version, this]() { + menuMenu.popdown(); + this->reader->setVersion(version); + updateButtons(); + updateMenus(); + showText(); + }); + Gtk::Button *delVersion = Gtk::manage(new Gtk::Button); + delVersion->set_image_from_icon_name("list-remove-symbolic"); + delVersion->set_relief(Gtk::ReliefStyle::RELIEF_NONE); + delVersion->signal_clicked().connect([version, this]() { + menuMenu.popdown(); + std::vector<std::string> toDel {version}; + this->mods->uninstallMods(toDel); + reader->modsUpdated(); + updateButtons(); + updateMenus(); + }); + item->set_halign(Gtk::ALIGN_FILL); + delVersion->set_halign(Gtk::ALIGN_END); + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox); + hbox->add(*item); + hbox->add(*delVersion); + versionBox->add(*hbox); + item->set_sensitive(version != reader->getVersion()); + } + Gtk::Button *add = Gtk::manage(new Gtk::Button); + add->set_image_from_icon_name("list-add-symbolic"); + add->signal_clicked().connect([this]() { + menuMenu.popdown(); + showMods(); + updateMenus(); + updateButtons(); + }); + versionBox->add(*add); + + menuMenu.show_all_children(); +} + +void Header::showMods() { + window->remove(); + window->add(*this->mods); + //this->mods->displayMain(); + window->show_all_children(); +} + +void Header::showText() { + window->remove(); + window->add(*this->reader); + //this->reader->setChapter(1); + window->show_all_children(); +} diff --git a/header.h b/header.h new file mode 100644 index 0000000..fd88fe5 --- /dev/null +++ b/header.h @@ -0,0 +1,31 @@ +#pragma once +#include <gtkmm.h> + +class Mods; +class ReaderView; + +class Header : public Gtk::HeaderBar +{ +public: + Header(ReaderView *reader, Gtk::Window *window); + virtual ~Header(); + + void updateButtons(void); + void updateMenus(void); + ReaderView *reader; + void showMods(void); + void showText(void); + +protected: + //Buttons + Gtk::Button back; + Gtk::Button forward; + Gtk::MenuButton book; + Gtk::MenuButton menu; + //Menus + Gtk::PopoverMenu bookMenu; + Gtk::PopoverMenu menuMenu; + + Mods *mods; + Gtk::Window *window; +}; @@ -0,0 +1,45 @@ +/* main.cpp + * + * Copyright (C) 2018 Nathan Vance + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "readerview.h" +#include "header.h" +#include <gtkmm.h> + +int main(int argc, + char *argv[]) +{ + auto app = Gtk::Application::create(argc, argv, "org.homelinuxserver.vance.biblereader"); + + Gtk::Window window; + window.set_default_size(550, 400); + //auto theme = Gtk::IconTheme::get_default(); + //theme->append_search_path(std::string(DATADIR) + "/icons/hicolor/scalable/apps"); + // No clue what the size *should* be. The 100 is in magical units :/ + //window.set_icon(theme->load_icon("org.homelinuxserver.vance.biblereader-symbolic", 100)); + + ReaderView reader; + + window.add(reader); + + Header *header = new Header(&reader, &window); + window.set_titlebar(*header); + + window.show_all_children(); + + return app->run(window); +} @@ -0,0 +1,208 @@ +#include "mods.h" +#include "header.h" +#include "readerview.h" +#include <iostream> +#include <thread> +#include <sys/types.h> +#include <sys/stat.h> + +// We want: https://developer.gnome.org/gtkmm-tutorial/stable/sec-multithread-example.html.en +void Mods::update(unsigned long totalBytes, unsigned long completedBytes, std::string mess) +{ + std::lock_guard<std::mutex> lock(progressMutex); + if (!totalBytes) { + //printf("Received a 0 totalBytes\n"); + dispatcher.emit(); + return; + } + fracDone = (completedBytes / totalBytes); + message = mess; + //printf("Progress: %f\n", fracDone); + dispatcher.emit(); +} + +void Mods::getStatus(double *fractionDone, std::string *mess, bool *isComplete) const { + std::lock_guard<std::mutex> lock(progressMutex); + *fractionDone = fracDone; + *mess = message; + *isComplete = complete; +} + +void Mods::onNotify() { + double fractionDone; + std::string mess; + bool isComplete; + getStatus(&fractionDone, &mess, &isComplete); + if(isComplete) { + if(worker && worker->joinable()) { + worker->join(); + header->reader->modsUpdated(); + header->updateButtons(); + header->updateMenus(); + endProgress(); + } + delete worker; + worker = nullptr; + } else { + showProgress(mess); + if(fractionDone >= 0 && fractionDone <= 1) { + progressBar.set_fraction(fractionDone); + } else { + progressBar.pulse(); + } + } +} + +void Mods::showProgress(std::string message) { + progressDialog.set_message(message); + progressDialog.show(); + progressDialog.show_all_children(); +} + +void Mods::endProgress() { + progressDialog.hide(); + return; +} + +Mods::Mods(Header *header, Gtk::Window *window) : modsAvailable(), progressDialog("", false, Gtk::MessageType::MESSAGE_INFO, Gtk::ButtonsType::BUTTONS_CANCEL), progressBar(), dispatcher(), progressMutex(), worker(nullptr) { + this->header = header; + this->window = window; + dispatcher.connect(sigc::mem_fun(*this, &Mods::onNotify)); + progressDialog.signal_response().connect([this](int response) { + libbible::terminateDownload(); + }); + progressDialog.get_message_area()->add(progressBar); + progressBar.show(); + displayMain(); +} + +Mods::~Mods() {} + +void Mods::displayMain() { + remove(); + auto *scroll = Gtk::manage(new Gtk::ScrolledWindow); + add(*scroll); + auto *vbox = Gtk::manage(new Gtk::VBox); + scroll->add(*vbox); + auto *label = Gtk::manage(new Gtk::Label); + label->set_text("Install new mods"); + vbox->pack_start(*label, false, false); + auto *hbox = Gtk::manage(new Gtk::HBox); + vbox->pack_start(*hbox, false, false); + auto *network = Gtk::manage(new Gtk::VBox); + hbox->add(*network); + label = Gtk::manage(new Gtk::Label); + label->set_text("Download over the network"); + network->pack_start(*label, false, false); + auto *button = Gtk::manage(new Gtk::Button("Download")); + network->pack_start(*button, false, false); + button->signal_clicked().connect([this]() { + updateInstallable(); + displayDownload(); + }); + auto *local = Gtk::manage(new Gtk::VBox); + hbox->add(*local); + label = Gtk::manage(new Gtk::Label); + label->set_text("Install local .zip file"); + local->pack_start(*label, false, false); + Gtk::FileChooserButton *add = Gtk::manage(new Gtk::FileChooserButton("Install SWORD modules", Gtk::FILE_CHOOSER_ACTION_OPEN)); + local->pack_start(*add, false, false); + auto filter = Gtk::FileFilter::create(); + filter->add_mime_type("application/zip"); + add->set_filter(filter); + add->signal_file_set().connect([this, add]() { + installMods(add->get_filenames()); + auto newMods = this->header->reader->modsUpdated(); + if(! newMods.empty()) { + this->header->reader->setVersion(newMods[0]); + } + this->header->updateMenus(); + this->header->updateButtons(); + this->header->showText(); + }); + window->show_all_children(); +} + +void Mods::displayDownload() { + remove(); + auto *scroll = Gtk::manage(new Gtk::ScrolledWindow); + add(*scroll); + auto *vbox = Gtk::manage(new Gtk::VBox); + //vbox->set_homogeneous(false); + //vbox->set_hexpand(false); + scroll->add(*vbox); + auto *button = Gtk::manage(new Gtk::Button("Back")); + vbox->pack_start(*button, false, false); + button->signal_clicked().connect([this]() { + displayMain(); + }); + auto *label = Gtk::manage(new Gtk::Label); + label->set_text("Language Selection:"); + vbox->pack_start(*label, false, false); + auto *langMenuButton = Gtk::manage(new Gtk::MenuButton); + // Perhaps default to English? Or if translations for the app are added, default to the global language setting? + langMenuButton->set_label("Select Language"); + vbox->pack_start(*langMenuButton, false, false); + label = Gtk::manage(new Gtk::Label); + label->set_text("Mods Available:"); + vbox->pack_start(*label, false, false); + auto *modsSelection = Gtk::manage(new Gtk::VBox); + modsSelection->set_vexpand(true); + vbox->pack_end(*modsSelection); + auto *langMenu = Gtk::manage(new Gtk::PopoverMenu); + auto *sw = Gtk::manage(new Gtk::ScrolledWindow); + langMenu->add(*sw); + auto *langBox = Gtk::manage(new Gtk::VBox); + sw->add(*langBox); + sw->set_propagate_natural_width(true); + sw->set_min_content_height(300); + for(auto item : modsAvailable) { + string language = item.first; + auto *langButton = Gtk::manage(new Gtk::Button(language)); + langButton->set_relief(Gtk::ReliefStyle::RELIEF_NONE); + langButton->signal_clicked().connect([language, langMenuButton, langMenu, modsSelection, this]() { + langMenu->popdown(); + langMenuButton->set_label(language); + for(auto child : modsSelection->get_children()) { + modsSelection->remove(*child); + } + for(string name : modsAvailable[language]) { + auto *installButton = Gtk::manage(new Gtk::Button(name)); + installButton->signal_clicked().connect([language, name, this]() { + worker = new std::thread([language, name, this] { + complete = false; + libbible::installModFromInternet(language, name); + complete = true; + dispatcher.emit(); + header->showText(); + }); + }); + modsSelection->pack_start(*installButton, false, false); + } + modsSelection->show_all_children(); + }); + langBox->add(*langButton); + } + langMenuButton->set_popover(*langMenu); + langMenu->show_all_children(); + window->show_all_children(); +} + +void Mods::installMods(std::vector<std::string> filenames) { + for(auto filename : filenames) { + libbible::installModFromZip(filename); + } +} + +void Mods::uninstallMods(std::vector<std::string> modnames) { + for(auto mod : modnames) { + libbible::uninstallMod(mod); + } +} + +void Mods::updateInstallable() { + if(! modsAvailable.empty()) { + return; + } + modsAvailable = libbible::downloadModsAvailable(this); +} @@ -0,0 +1,53 @@ +#pragma once +#include <glib.h> +#include <thread> +#include <mutex> +#include <vector> +#include <string> +#include <gtkmm.h> +#include <libbible.h> + +using namespace std; + +class Header; + +/* + * Credit goes to the Xiphos project for this part of the code: + * https://github.com/crosswire/xiphos/ + */ + +class Mods : public Gtk::Frame, public libbible::Status +{ +public: + Mods(Header *header, Gtk::Window *window); + virtual ~Mods(); + + void installMods(std::vector<std::string> filenames); + void uninstallMods(std::vector<std::string> modnames); + void updateInstallable(); + void displayMain(); + void displayDownload(); + + // This is for Status. Huzzah for multiple inheritance! + virtual void update(unsigned long totalBytes, unsigned long completedBytes, std::string message); + +protected: + Header *header; + Gtk::Window *window; + std::map<std::string, std::vector<std::string>> modsAvailable; + + Gtk::MessageDialog progressDialog; + Gtk::ProgressBar progressBar; + Glib::Dispatcher dispatcher; + mutable std::mutex progressMutex; + double fracDone; + std::string message; + std::thread *worker; + bool complete; + void getStatus(double *fractionDone, std::string *mess, bool *isComplete) const; + void onNotify(); + + void showProgress(std::string message); + void endProgress(); + +}; diff --git a/readerview.cc b/readerview.cc new file mode 100644 index 0000000..aef003d --- /dev/null +++ b/readerview.cc @@ -0,0 +1,98 @@ +#include "readerview.h" +#include "sword.h" +#include <libbible.h> +#include <gdkmm/rgba.h> + +ReaderView::ReaderView() +: text() +{ + this->sword = new Sword(); + this->conf = new struct config; + text.set_editable(false); + text.set_cursor_visible(false); + text.set_wrap_mode(Gtk::WrapMode::WRAP_WORD_CHAR); + //text.override_background_color(*(new Gdk::RGBA("#1d1f21"))); + //text.override_color(*(new Gdk::RGBA("#c5c8c6"))); + auto scroll = new Gtk::ScrolledWindow(); + scroll->add(text); + add(*scroll); + // Open the passage we had last time + string book = libbible::settingsRead("book"); + int chapter = libbible::settingsReadInt("chapter"); + if(book.empty() || chapter == 0) { + book = "Genesis"; + chapter = 1; + } + sword->getConfig(book, chapter, conf); + refresh(); +} + +ReaderView::~ReaderView() {} + +// Returns all new mods +std::vector<std::string> ReaderView::modsUpdated() { + auto oldMods = getAllVersions(); + std::sort(oldMods.begin(), oldMods.end()); + sword = new Sword(); + auto newMods = getAllVersions(); + std::sort(newMods.begin(), newMods.end()); + std::vector<std::string> justNew; + std::set_difference(newMods.begin(), newMods.end(), oldMods.begin(), oldMods.end(), std::back_inserter(justNew)); + refresh(); + return justNew; +} + +void ReaderView::refresh() { + auto textBuffer = text.get_buffer(); + libbible::settingsWrite("book", conf->book); + libbible::settingsWriteInt("chapter", conf->chapter); + sword->getConfig(conf->book, conf->chapter, conf); + // Get passage back + string passage = conf->book + " " + std::to_string(conf->chapter); + textBuffer->set_text(""); // Clear contents + //auto iter = textBuffer->get_iter_at_offset(0); + sword->fillBuffer(passage, textBuffer); +} + +void ReaderView::setChapter(int chapter) { + conf->chapter = chapter; + refresh(); +} + +int ReaderView::getChapter() { + return conf->chapter; +} + +int ReaderView::getChapterMax() { + return conf->maxChapter; +} + +void ReaderView::setBook(std::string book) { + conf->book = book; + refresh(); +} + +std::string ReaderView::getBook() { + return conf->book; +} + +std::string ReaderView::getBookFull() { + return conf->bookFull; +} + +std::vector<std::string> ReaderView::getAllBooks() { + return sword->getBooks(); +} + +void ReaderView::setVersion(std::string version) { + sword->setModule(version); + refresh(); +} + +std::string ReaderView::getVersion() { + return conf->version; +} + +std::vector<std::string> ReaderView::getAllVersions() { + return sword->getModules(); +} diff --git a/readerview.h b/readerview.h new file mode 100644 index 0000000..0ea631e --- /dev/null +++ b/readerview.h @@ -0,0 +1,32 @@ +#pragma once +#include <gtkmm.h> + +class Sword; + +class ReaderView : public Gtk::Frame +{ +public: + ReaderView(); + virtual ~ReaderView(); + + void setChapter(int chapter); + int getChapter(void); + int getChapterMax(void); + void setBook(std::string book); + std::string getBook(void); + std::string getBookFull(void); + std::vector<std::string> getAllBooks(void); + + void setVersion(std::string version); + std::string getVersion(void); + std::vector<std::string> getAllVersions(void); + + void refresh(void); + + std::vector<std::string> modsUpdated(void); + +protected: + Sword *sword; + Gtk::TextView text; + struct config *conf; +}; diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..dd68d62 --- /dev/null +++ b/readme.md @@ -0,0 +1,19 @@ +# Biblereader + +Biblereader is a GTK frontend for libbible. + +Dependencies: + + * gtkmm + * [libbible](https://vance.fish/git/libbible) + +## Compiling and Installing + +```console +make +sudo cp biblereader /usr/bin/ +``` + +## Modules + +biblereader can install modules from the internet or from local .zip files. A collection of modules can be found [here](http://crosswire.org/sword/modules/ModDisp.jsp?modType=Bibles). diff --git a/sword.cc b/sword.cc new file mode 100644 index 0000000..0bf20e2 --- /dev/null +++ b/sword.cc @@ -0,0 +1,145 @@ +#include "sword.h" +#include <libbible.h> +#include <sstream> +#include <wctype.h> + +using namespace std; + +bool isNoMods; + +Sword::Sword() { + auto mods = libbible::getModules(); + isNoMods = mods.empty(); + defaultMod = libbible::settingsRead("module"); + if(mods.find(defaultMod) == mods.end()) { + defaultMod = string(); + if(! mods.empty()) { + defaultMod = mods.begin()->first; + } + } +} + +Sword::~Sword() {} + +vector<string> Sword::getModules() { + vector<string> mods; + auto modsInstalled = libbible::getModules(); + for(auto pair : modsInstalled) { + mods.push_back(pair.first); + } + return mods; +} + +void Sword::setModule(string version) { + defaultMod = version; +} + +void Sword::fillBuffer(string ref, Glib::RefPtr<Gtk::TextBuffer> buf) { + buf->set_text(""); // Clear contents + + if(isNoMods) { + auto iter = buf->get_iter_at_offset(0); + iter = buf->insert_markup(iter, "<big><b>No modules installed.</b></big>\n"); + iter = buf->insert_markup(iter, "Please download some modules at:\n"); + iter = buf->insert_markup(iter, "\thttp://crosswire.org/sword/modules/ModDisp.jsp?modType=Bibles\n"); + iter = buf->insert_markup(iter, "Then install them using the menu in the upper right corner, or use the built-in installer to download and install modules."); + return; + } + + auto texts = libbible::getText(libbible::passage{.modName = defaultMod, .reference = ref}); + + auto iter = buf->get_iter_at_offset(0); + + auto verseSize = buf->create_tag(); + verseSize->property_size() = libbible::settingsReadInt("fontsize"); + if(verseSize->property_size() == 0) { + verseSize->property_size() = 12000; + } + + auto verseScale = buf->create_tag(); + verseScale->property_scale() = 0.8; + auto verseOffset = buf->create_tag(); + verseOffset->property_rise() = 3000; + auto indent = buf->create_tag(); + indent->property_left_margin() = 40; + auto redletter = buf->create_tag(); + redletter->property_foreground_gdk() = Gdk::Color("red"); + + int verse = 0; + string indentString = " "; + bool isNewline = true; + for(auto tex : texts) { + std::vector<Glib::RefPtr<Gtk::TextBuffer::Tag>> tags = {verseSize}; + bool isParagraph = false; + bool isIndent = false; + bool isDivineName = false; + for(string modifier : tex.modifiers) { + if(modifier == "paragraph") { + isParagraph = true; + } else if (modifier == "line indent0") { + isIndent = true; + } else if (modifier == "divineName") { + isDivineName = true; + } else if (modifier == "wordsOfJesus") { + tags.push_back(redletter); + } + } + if(isIndent) { + isParagraph = false; + if(isNewline) { + tags.push_back(indent); + } + } + if(isParagraph) { + iter = buf->insert_with_tags(iter, indentString, tags); + } + if(tex.verse != verse) { + std::vector<Glib::RefPtr<Gtk::TextBuffer::Tag>> verseTags(tags.begin(), tags.end()); + verseTags.push_back(verseScale); + verseTags.push_back(verseOffset); + iter = buf->insert_with_tags(iter, " " + std::to_string(tex.verse), verseTags); + } + verse = tex.verse; + if(isDivineName) { + // There's no small caps support. Sigh. We do fake small caps instead. + // Because i lazy, first letter is normal caps and rest small caps, always. + transform(tex.text.begin(), tex.text.end(), tex.text.begin(), ::toupper); + iter = buf->insert_with_tags(iter, tex.text.substr(0, 1), tags); + auto tag = buf->create_tag(); + tag->property_scale() = 0.75; + tags.push_back(tag); + iter = buf->insert_with_tags(iter, tex.text.substr(1), tags); + } else { + iter = buf->insert_with_tags(iter, tex.text, tags); + } + if(tex.text.back() == '\n') { + isNewline = true; + } else { + isNewline = false; + } + } +} + +void Sword::getConfig(string book, int chapter, struct config *conf) { + if(isNoMods) { + conf->chapter = 0; + conf->book = ""; + conf->bookFull = ""; + conf->maxChapter = 0; + conf->version = ""; + } else { + auto passages = libbible::getPassages(defaultMod, book); + conf->chapter = chapter; + conf->book = passages[0].bookShort; + conf->bookFull = passages[0].book; + conf->maxChapter = passages.back().chapterStart; + conf->version = defaultMod; + } +} + +vector<string> Sword::getBooks() { + if(isNoMods) { + return vector<string>(); + } + return libbible::getModules()[defaultMod]; +} @@ -0,0 +1,29 @@ +#pragma once +#include <gtkmm.h> +#include <vector> + +using namespace::std; + +class Sword { +public: + Sword(); + virtual ~Sword(); + + std::vector<std::string> getModules(void); + std::vector<std::string> getBooks(void); + void setModule(std::string modName); + void fillBuffer(std::string ref, Glib::RefPtr<Gtk::TextBuffer> buf); + void getConfig(std::string book, int chapter, struct config *conf); + +protected: + std::string defaultMod; + +}; + +struct config { + int chapter; + std::string book; + std::string bookFull; + int maxChapter; + std::string version; +}; |