From 89a449c2ab9d736c1f03c78b745e814424e01360 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sat, 7 Nov 2020 09:30:53 -0500 Subject: Initial commit --- Makefile | 18 +++++ header.cc | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ header.h | 31 +++++++++ main.cc | 45 +++++++++++++ mods.cc | 208 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ mods.h | 53 +++++++++++++++ readerview.cc | 98 +++++++++++++++++++++++++++ readerview.h | 32 +++++++++ readme.md | 19 ++++++ sword.cc | 145 ++++++++++++++++++++++++++++++++++++++++ sword.h | 29 ++++++++ 11 files changed, 888 insertions(+) create mode 100644 Makefile create mode 100644 header.cc create mode 100644 header.h create mode 100644 main.cc create mode 100644 mods.cc create mode 100644 mods.h create mode 100644 readerview.cc create mode 100644 readerview.h create mode 100644 readme.md create mode 100644 sword.cc create mode 100644 sword.h 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 + +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 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 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 + +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; +}; diff --git a/main.cc b/main.cc new file mode 100644 index 0000000..ad62814 --- /dev/null +++ b/main.cc @@ -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 . + */ + +#include "readerview.h" +#include "header.h" +#include + +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); +} diff --git a/mods.cc b/mods.cc new file mode 100644 index 0000000..e1228aa --- /dev/null +++ b/mods.cc @@ -0,0 +1,208 @@ +#include "mods.h" +#include "header.h" +#include "readerview.h" +#include +#include +#include +#include + +// 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 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 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 filenames) { + for(auto filename : filenames) { + libbible::installModFromZip(filename); + } +} + +void Mods::uninstallMods(std::vector modnames) { + for(auto mod : modnames) { + libbible::uninstallMod(mod); + } +} + +void Mods::updateInstallable() { + if(! modsAvailable.empty()) { + return; + } + modsAvailable = libbible::downloadModsAvailable(this); +} diff --git a/mods.h b/mods.h new file mode 100644 index 0000000..7373961 --- /dev/null +++ b/mods.h @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include + +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 filenames); + void uninstallMods(std::vector 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> 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 +#include + +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 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 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 ReaderView::getAllBooks() { + return sword->getBooks(); +} + +void ReaderView::setVersion(std::string version) { + sword->setModule(version); + refresh(); +} + +std::string ReaderView::getVersion() { + return conf->version; +} + +std::vector 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 + +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 getAllBooks(void); + + void setVersion(std::string version); + std::string getVersion(void); + std::vector getAllVersions(void); + + void refresh(void); + + std::vector 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 +#include +#include + +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 Sword::getModules() { + vector 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 buf) { + buf->set_text(""); // Clear contents + + if(isNoMods) { + auto iter = buf->get_iter_at_offset(0); + iter = buf->insert_markup(iter, "No modules installed.\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> 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> 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 Sword::getBooks() { + if(isNoMods) { + return vector(); + } + return libbible::getModules()[defaultMod]; +} diff --git a/sword.h b/sword.h new file mode 100644 index 0000000..aa52c13 --- /dev/null +++ b/sword.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include + +using namespace::std; + +class Sword { +public: + Sword(); + virtual ~Sword(); + + std::vector getModules(void); + std::vector getBooks(void); + void setModule(std::string modName); + void fillBuffer(std::string ref, Glib::RefPtr 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; +}; -- cgit v1.2.3