diff --git a/.gitignore b/.gitignore index ca38e6a..cf9af95 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ zathura zathura-debug zathura.pc +*.info +*.gcno +*.gcda +gcov/ diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..f52ac20 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,30 @@ +# See LICENSE file for license and copyright information + +# General information +PROJECT_NAME = zathura +OUTPUT_DIRECTORY = ./doc/ +OUTPUT_LANGUAGE = English +TAB_SIZE = 2 +EXTRACT_ALL = YES +OPTIMIZE_OUTPUT_FOR_C = YES +DOXYFILE_ENCODING = UTF-8 +TYPEDEF_HIDES_STRUCT = YES + +# Warning and progress messages +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES + +# Input files +INPUT = +EXCLUDE = ./tests +FILE_PATTERNS = *.h *.c +RECURSIVE = YES + +# Output files +GENERATE_HTML = YES +GENERATE_LATEX = NO +GENERATE_RTF = NO +GENERATE_XML = NO + +SOURCE_BROWSER = YES diff --git a/Makefile b/Makefile index 649c8bc..48949a2 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,21 @@ include config.mk include common.mk PROJECT = zathura -SOURCE = $(shell find . -iname "*.c") +SOURCE = $(shell find . -iname "*.c" -a ! -iname "database-*" ! -path "*tests*") +HEADER = $(shell find . -iname "*.h") OBJECTS = $(patsubst %.c, %.o, $(SOURCE)) DOBJECTS = $(patsubst %.c, %.do, $(SOURCE)) +ifeq (${DATABASE}, sqlite) +INCS += $(SQLITE_INC) +LIBS += $(SQLITE_LIB) +SOURCE += database-sqlite.c +else +ifeq (${DATABASE}, plain) +SOURCE += database-plain.c +endif +endif + all: options ${PROJECT} options: @@ -20,12 +31,15 @@ options: %.o: %.c $(ECHO) CC $< @mkdir -p .depend - $(QUIET)${CC} -c ${CFLAGS} -o $@ $< -MMD -MF .depend/$@.dep + $(QUIET)${CC} -c ${CPPFLAGS} ${CFLAGS} -o $@ $< -MMD -MF .depend/$@.dep %.do: %.c $(ECHO) CC $< @mkdir -p .depend - $(QUIET)${CC} -c ${CFLAGS} ${DFLAGS} -o $@ $< -MMD -MF .depend/$@.dep + $(QUIET)${CC} -c ${CPPFLAGS} ${CFLAGS} ${DFLAGS} -o $@ $< -MMD -MF .depend/$@.dep + +# force recompilation of database.o if DATABASE has changed +database.o: database-${DATABASE}.o ${OBJECTS}: config.mk ${DOBJECTS}: config.mk @@ -36,7 +50,8 @@ ${PROJECT}: ${OBJECTS} clean: $(QUIET)rm -rf ${PROJECT} ${OBJECTS} ${PROJECT}-${VERSION}.tar.gz \ - ${DOBJECTS} ${PROJECT}-debug .depend ${PROJECT}.pc + ${DOBJECTS} ${PROJECT}-debug .depend ${PROJECT}.pc doc *gcda *gcno $(PROJECT).info gcov + $(QUIET)make -C tests clean ${PROJECT}-debug: ${DOBJECTS} $(ECHO) CC -o $@ @@ -57,26 +72,45 @@ valgrind: debug gdb: debug cgdb ${PROJECT}-debug +tests: ${OBJECTS} + $(QUIET)make -C tests + dist: clean $(QUIET)mkdir -p ${PROJECT}-${VERSION} - $(QUIET)cp -R LICENSE Makefile config.mk README \ - ${PROJECT}.1 ${SOURCE} ${PROJECT}.pc.in \ + $(QUIET)cp -R LICENSE Makefile config.mk common.mk README Doxyfile \ + ${PROJECT}.1 ${SOURCE} ${HEADER} ${PROJECT}.pc.in tests \ ${PROJECT}-${VERSION} $(QUIET)tar -cf ${PROJECT}-${VERSION}.tar ${PROJECT}-${VERSION} $(QUIET)gzip ${PROJECT}-${VERSION}.tar $(QUIET)rm -rf ${PROJECT}-${VERSION} +doc: clean + $(QUIET)doxygen Doxyfile + +gcov: clean + $(QUIET)CFLAGS="${CFLAGS}-fprofile-arcs -ftest-coverage" LDFLAGS="${LDFLAGS} -fprofile-arcs" ${MAKE} $(PROJECT) + $(QUIET)${MAKE} -C tests + $(QUIET)lcov --directory . --capture --output-file $(PROJECT).info + $(QUIET)genhtml --output-directory gcov $(PROJECT).info + install: all ${PROJECT}.pc $(ECHO) installing executable file $(QUIET)mkdir -p ${DESTDIR}${PREFIX}/bin $(QUIET)install -m 755 ${PROJECT} ${DESTDIR}${PREFIX}/bin - $(ECHO) installing header file + $(ECHO) installing header files $(QUIET)mkdir -p ${DESTDIR}${PREFIX}/include/${PROJECT} $(QUIET)cp -f document.h ${DESTDIR}${PREFIX}/include/${PROJECT} $(QUIET)cp -f zathura.h ${DESTDIR}${PREFIX}/include/${PROJECT} - $(ECHO) installing manual page + $(ECHO) installing manual pages $(QUIET)mkdir -p ${DESTDIR}${MANPREFIX}/man1 $(QUIET)sed "s/VERSION/${VERSION}/g" < ${PROJECT}.1 > ${DESTDIR}${MANPREFIX}/man1/${PROJECT}.1 + $(QUIET)if which rst2man > /dev/null ; then \ + mkdir -p ${DESTDIR}${MANPREFIX}/man5 ; \ + rst2man ${PROJECT}rc.5.rst > ${DESTDIR}${MANPREFIX}/man5/${PROJECT}rc.5 ; \ + fi + $(QUIET)mkdir -p ${DESTDIR}${DESKTOPPREFIX} + $(ECHO) installing desktop file + $(QUIET)install -m 644 ${PROJECT}.desktop ${DESTDIR}${DESKTOPPREFIX} $(QUIET)chmod 644 ${DESTDIR}${MANPREFIX}/man1/${PROJECT}.1 $(ECHO) installing pkgconfig file $(QUIET)mkdir -p ${DESTDIR}${PREFIX}/lib/pkgconfig @@ -85,14 +119,16 @@ install: all ${PROJECT}.pc uninstall: $(ECHO) removing executable file $(QUIET)rm -f ${DESTDIR}${PREFIX}/bin/${PROJECT} - $(ECHO) removing header file - $(QUIET)rm -f ${DESTDIR}${PREFIX}/include/${PROJECT}/document.h - $(QUIET)rm -f ${DESTDIR}${PREFIX}/include/${PROJECT}/zathura.h - $(ECHO) removing manual page + $(ECHO) removing header files + $(QUIET)rm -rf ${DESTDIR}${PREFIX}/include/${PROJECT} + $(ECHO) removing manual pages $(QUIET)rm -f ${DESTDIR}${MANPREFIX}/man1/${PROJECT}.1 + $(QUIET)rm -f ${DESTDIR}${MANPREFIX}/man5/${PROJECT}rc.5 + $(ECHO) removing desktop file + $(QUIET)rm -f ${DESTDIR}${DESKTOPPREFIX}/${PROJECT}.desktop $(ECHO) removing pkgconfig file - $(QUIET)rm -f ${DESTDIR}${PREFIX}/lib/pkgconfig + $(QUIET)rm -f ${DESTDIR}${PREFIX}/lib/pkgconfig/${PROJECT}.pc -include $(wildcard .depend/*.dep) -.PHONY: all options clean debug valgrind gdb dist install uninstall +.PHONY: all options clean doc debug valgrind gdb dist doc install uninstall tests diff --git a/README b/README index ac777c5..3192c4d 100644 --- a/README +++ b/README @@ -7,9 +7,16 @@ Requirements ------------ gtk2 (>= 2.18.6) girara +sqlite3 +check (for tests) -Please note that you need to have a working pkg-config installation -and that the Makefile is only compatible with GNU make. +Please note that you need to have a working pkg-config installation and that the +Makefile is only compatible with GNU make. If you don't have a working +pkg-config installation please set the GTK_INC, GTK_LIB, GIRARA_INC, GIRARA_LIB, +SQLITE_INC and SQLITE_LIB variables accordingly. + +And also note that rst2man from python-docutils is needed to build zathurarc.5. +If it is not installed, zathurarc.5 won't be built. Installation ------------ diff --git a/bookmarks.c b/bookmarks.c index 3df9487..e0a90ed 100644 --- a/bookmarks.c +++ b/bookmarks.c @@ -5,18 +5,28 @@ #include "database.h" #include "document.h" +#include +#include + +static int +bookmark_compare_find(const void* item, const void* data) +{ + const zathura_bookmark_t* bookmark = item; + const char* id = data; + + return g_strcmp0(bookmark->id, id); +} + zathura_bookmark_t* zathura_bookmark_add(zathura_t* zathura, const gchar* id, unsigned int page) { g_return_val_if_fail(zathura && zathura->document && zathura->bookmarks.bookmarks, NULL); g_return_val_if_fail(id, NULL); - GIRARA_LIST_FOREACH(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark) - if (strcmp(bookmark->id, id) == 0) { - girara_list_iterator_free(iter); - return NULL; - } - GIRARA_LIST_FOREACH_END(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark) + zathura_bookmark_t* old = girara_list_find(zathura->bookmarks.bookmarks, bookmark_compare_find, id); + if (old != NULL) { + return NULL; + } zathura_bookmark_t* bookmark = g_malloc0(sizeof(zathura_bookmark_t)); bookmark->id = g_strdup(id); @@ -58,14 +68,7 @@ zathura_bookmark_get(zathura_t* zathura, const gchar* id) g_return_val_if_fail(zathura && zathura->bookmarks.bookmarks, NULL); g_return_val_if_fail(id, NULL); - GIRARA_LIST_FOREACH(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark) - if (strcmp(bookmark->id, id) == 0) { - girara_list_iterator_free(iter); - return bookmark; - } - GIRARA_LIST_FOREACH_END(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark) - - return NULL; + return girara_list_find(zathura->bookmarks.bookmarks, bookmark_compare_find, id); } void @@ -81,8 +84,11 @@ zathura_bookmark_free(zathura_bookmark_t* bookmark) bool zathura_bookmarks_load(zathura_t* zathura, const gchar* file) { - g_return_val_if_fail(zathura && zathura->database, false); + g_return_val_if_fail(zathura, false); g_return_val_if_fail(file, false); + if (zathura->database == NULL) { + return false; + } girara_list_t* bookmarks = zathura_db_load_bookmarks(zathura->database, file); if (!bookmarks) { @@ -94,3 +100,18 @@ zathura_bookmarks_load(zathura_t* zathura, const gchar* file) { return true; } +int +zathura_bookmarks_compare(zathura_bookmark_t* lhs, zathura_bookmark_t* rhs) +{ + if (lhs == NULL && rhs == NULL) { + return 0; + } + if (lhs == NULL) { + return -1; + } + if (rhs == NULL) { + return 1; + } + + return g_strcmp0(lhs->id, rhs->id); +} diff --git a/bookmarks.h b/bookmarks.h index 156375a..7cd2698 100644 --- a/bookmarks.h +++ b/bookmarks.h @@ -49,8 +49,16 @@ void zathura_bookmark_free(zathura_bookmark_t* bookmark); * Load bookmarks for a specific file. * @param zathura The zathura instance. * @param file The file. - * @param true on success, false otherwise + * @return true on success, false otherwise */ bool zathura_bookmarks_load(zathura_t* zathura, const gchar* file); +/** + * Compare two bookmarks. + * @param lhs a bookmark + * @param rhs a bookmark + * @returns g_strcmp0(lhs->id, rhs->id) + */ +int zathura_bookmarks_compare(zathura_bookmark_t* lhs, zathura_bookmark_t* rhs); + #endif // BOOKMARKS_H diff --git a/callbacks.c b/callbacks.c index 688a866..1ad6b7c 100644 --- a/callbacks.c +++ b/callbacks.c @@ -1,23 +1,30 @@ /* See LICENSE file for license and copyright information */ -#include +#include +#include +#include +#include #include #include +#include #include "callbacks.h" #include "zathura.h" #include "render.h" #include "document.h" #include "utils.h" +#include "shortcuts.h" +#include "page_widget.h" gboolean cb_destroy(GtkWidget* UNUSED(widget), gpointer UNUSED(data)) { + gtk_main_quit(); return TRUE; } void -buffer_changed(girara_session_t* session) +cb_buffer_changed(girara_session_t* session) { g_return_if_fail(session != NULL); g_return_if_fail(session->global.data != NULL); @@ -35,57 +42,255 @@ buffer_changed(girara_session_t* session) } void -cb_view_vadjustment_value_changed(GtkAdjustment *adjustment, gpointer data) +cb_view_vadjustment_value_changed(GtkAdjustment* GIRARA_UNUSED(adjustment), gpointer data) { zathura_t* zathura = data; - if (!zathura || !zathura->document || !zathura->document->pages || !zathura->ui.page_view) { + if (!zathura || !zathura->document || !zathura->document->pages || !zathura->ui.page_widget) { return; } - /* get current adjustment values */ - gdouble lower = gtk_adjustment_get_value(adjustment); - gdouble upper = lower + gtk_adjustment_get_page_size(adjustment); + GtkAdjustment* view_vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); + GtkAdjustment* view_hadjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); + GdkRectangle view_rect; + /* get current adjustment values */ + view_rect.y = gtk_adjustment_get_value(view_vadjustment); + view_rect.height = gtk_adjustment_get_page_size(view_vadjustment); + view_rect.x = gtk_adjustment_get_value(view_hadjustment); + view_rect.width = gtk_adjustment_get_page_size(view_hadjustment); + + int page_padding = 1; + girara_setting_get(zathura->ui.session, "page-padding", &page_padding); + + GdkRectangle center; + center.x = view_rect.x + (view_rect.width + 1) / 2; + center.y = view_rect.y + (view_rect.height + 1) / 2; + center.height = center.width = 2*page_padding + 1; + + bool updated = false; /* find page that fits */ for (unsigned int page_id = 0; page_id < zathura->document->number_of_pages; page_id++) { zathura_page_t* page = zathura->document->pages[page_id]; - page_offset_t* offset = page_calculate_offset(page); - if (offset == NULL) { - continue; - } + page_offset_t offset; + page_calculate_offset(page, &offset); - double begin = offset->y; - double end = offset->y + page->height; + GdkRectangle page_rect; + page_rect.x = offset.x; + page_rect.y = offset.y; + page_rect.width = page->width * zathura->document->scale; + page_rect.height = page->height * zathura->document->scale; - if ( ( (begin >= lower) && (end <= upper) ) /* [> page is in viewport <]*/ - || ( (begin <= lower) && (end >= lower) && (end <= upper) ) /* [> end of the page is in viewport <] */ - || ( (begin >= lower) && (end >= upper) && (begin <= upper) ) /* [> begin of the page is in viewport <] */ - ) { + if (gdk_rectangle_intersect(&view_rect, &page_rect, NULL) == TRUE) { page->visible = true; - if (page->surface == NULL) { - render_page(zathura->sync.render_thread, page); + if (updated == false && gdk_rectangle_intersect(¢er, &page_rect, NULL) == TRUE) { + zathura->document->current_page_number = page_id; + updated = true; } } else { page->visible = false; - cairo_surface_destroy(page->surface); - page->surface = NULL; } - - free(offset); } + + statusbar_page_number_update(zathura); } void -cb_pages_per_row_value_changed(girara_session_t* UNUSED(session), girara_setting_t* setting) +cb_pages_per_row_value_changed(girara_session_t* UNUSED(session), const char* UNUSED(name), girara_setting_type_t UNUSED(type), void* value, void* data) { - int pages_per_row = setting->value.i; - zathura_t* zathura = setting->data; + g_return_if_fail(value != NULL); + + int pages_per_row = *(int*) value; + zathura_t* zathura = data; if (pages_per_row < 1) { pages_per_row = 1; } - page_view_set_mode(zathura, pages_per_row); + page_widget_set_mode(zathura, pages_per_row); +} + +void +cb_index_row_activated(GtkTreeView* tree_view, GtkTreePath* path, + GtkTreeViewColumn* UNUSED(column), void* data) +{ + zathura_t* zathura = data; + if (tree_view == NULL || zathura == NULL || zathura->ui.session == NULL) { + return; + } + + GtkTreeModel *model; + GtkTreeIter iter; + + g_object_get(tree_view, "model", &model, NULL); + + if(gtk_tree_model_get_iter(model, &iter, path)) + { + zathura_index_element_t* index_element; + gtk_tree_model_get(model, &iter, 2, &index_element, -1); + + if (index_element == NULL) { + return; + } + + if (index_element->type == ZATHURA_LINK_TO_PAGE) { + sc_toggle_index(zathura->ui.session, NULL, NULL, 0); + page_set_delayed(zathura, index_element->target.page_number); + } else if (index_element->type == ZATHURA_LINK_EXTERNAL) { + if (girara_xdg_open(index_element->target.uri) == false) { + girara_notify(zathura->ui.session, GIRARA_ERROR, "Failed to run xdg-open."); + } + } + } + + g_object_unref(model); +} + +bool +cb_sc_follow(GtkEntry* entry, girara_session_t* session) +{ + g_return_val_if_fail(session != NULL, FALSE); + g_return_val_if_fail(session->global.data != NULL, FALSE); + + zathura_t* zathura = session->global.data; + bool eval = true; + + char* input = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1); + if (input == NULL || strlen(input) == 0) { + eval = false; + } + + int index = 0; + if (eval == true) { + index = atoi(input); + if (index == 0 && g_strcmp0(input, "0") != 0) { + girara_notify(session, GIRARA_WARNING, "Invalid input '%s' given.", input); + eval = false; + } + index = index - 1; + } + + /* set pages to draw links */ + bool invalid_index = true; + for (unsigned int page_id = 0; page_id < zathura->document->number_of_pages; page_id++) { + zathura_page_t* page = zathura->document->pages[page_id]; + if (page == NULL || page->visible == false) { + continue; + } + + g_object_set(page->drawing_area, "draw-links", FALSE, NULL); + + if (eval == true) { + zathura_link_t* link = zathura_page_widget_link_get(ZATHURA_PAGE(page->drawing_area), index); + if (link != NULL) { + switch (link->type) { + case ZATHURA_LINK_TO_PAGE: + page_set_delayed(zathura, link->target.page_number); + break; + case ZATHURA_LINK_EXTERNAL: + girara_xdg_open(link->target.value); + break; + } + + invalid_index = false; + } + } + } + + if (eval == true && invalid_index == true) { + girara_notify(session, GIRARA_WARNING, "Invalid index '%s' given.", input); + } + + g_free(input); + + return (eval == TRUE) ? TRUE : FALSE; +} + +void +cb_file_monitor(GFileMonitor* monitor, GFile* file, GFile* UNUSED(other_file), GFileMonitorEvent event, girara_session_t* session) +{ + g_return_if_fail(monitor != NULL); + g_return_if_fail(file != NULL); + g_return_if_fail(session != NULL); + + if (event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) { + return; + } + + sc_reload(session, NULL, NULL, 0); +} + +static gboolean +password_dialog(gpointer data) +{ + zathura_password_dialog_info_t* dialog = data; + if (dialog != NULL) { + girara_dialog(dialog->zathura->ui.session, "Incorrect password. Enter password:", true, NULL, + (girara_callback_inputbar_activate_t) cb_password_dialog, dialog); + } + return FALSE; +} + +bool +cb_password_dialog(GtkEntry* entry, zathura_password_dialog_info_t* dialog) +{ + if (entry == NULL || dialog == NULL) { + goto error_ret; + } + + if (dialog->path == NULL) { + free(dialog); + goto error_ret; + } + + if (dialog->zathura == NULL) { + goto error_free; + } + + char* input = gtk_editable_get_chars(GTK_EDITABLE(entry), 0, -1); + + /* no or empty password: ask again */ + if (input == NULL || strlen(input) == 0) { + if (input != NULL) { + g_free(input); + } + + g_idle_add(password_dialog, dialog); + return false; + } + + /* try to open document again */ + if (document_open(dialog->zathura, dialog->path, input) == false) { + g_idle_add(password_dialog, dialog); + } else { + g_free(dialog->path); + free(dialog); + } + + g_free(input); + + return true; + +error_free: + + g_free(dialog->path); + free(dialog); + +error_ret: + + return false; +} + +bool +cb_view_resized(GtkWidget* UNUSED(widget), GtkAllocation* UNUSED(allocation), zathura_t* zathura) +{ + if (zathura == NULL || zathura->document == NULL) { + return false; + } + + girara_argument_t argument = { zathura->document->adjust_mode, NULL }; + sc_adjust_window(zathura->ui.session, &argument, NULL, 0); + + return true; } diff --git a/callbacks.h b/callbacks.h index 1d960c6..19630d1 100644 --- a/callbacks.h +++ b/callbacks.h @@ -4,14 +4,17 @@ #define CALLBACKS_H #include -#include +#include +#include + +#include "document.h" /** * Quits the current zathura session * * @param widget The gtk window of zathura * @param data NULL - * @return TRUE + * @return true if no error occured and the event has been handled */ gboolean cb_destroy(GtkWidget* widget, gpointer data); @@ -20,7 +23,7 @@ gboolean cb_destroy(GtkWidget* widget, gpointer data); * * @param session The girara session */ -void buffer_changed(girara_session_t* session); +void cb_buffer_changed(girara_session_t* session); /** * This function gets called when the value of the vertical scrollbars @@ -35,8 +38,63 @@ void cb_view_vadjustment_value_changed(GtkAdjustment *adjustment, gpointer data) * variable changes * * @param session The current girara session - * @param setting The "pages-per-row" setting + * @param name The name of the row + * @param type The settings type + * @param value The value + * @param data Custom data */ -void cb_pages_per_row_value_changed(girara_session_t* session, girara_setting_t* setting); +void cb_pages_per_row_value_changed(girara_session_t* session, const char* name, + girara_setting_type_t type, void* value, void* data); + +/** + * Called when an index element is activated (e.g.: double click) + * + * @param tree_view Tree view + * @param path Path + * @param column Column + * @param zathura Zathura session + */ +void cb_index_row_activated(GtkTreeView* tree_view, GtkTreePath* path, + GtkTreeViewColumn* column, void* zathura); + +/** + * Called when input has been passed to the sc_follow dialog + * + * @param entry The dialog inputbar + * @param session The girara session + * @return true if no error occured and the event has been handled + */ +bool cb_sc_follow(GtkEntry* entry, girara_session_t* session); + +/** + * Emitted when file has been changed + * + * @param monitor The file monitor + * @param file The file + * @param other_file A file or NULL + * @param event The monitor event + * @param session The girara session + */ +void cb_file_monitor(GFileMonitor* monitor, GFile* file, GFile* other_file, + GFileMonitorEvent event, girara_session_t* session); + +/** + * Callback to read new password for file that should be opened + * + * @param entry The password entry + * @param dialog The dialog information + * @return true if input has been handled + */ +bool cb_password_dialog(GtkEntry* entry, zathura_password_dialog_info_t* dialog); + +/** + * Emitted when the view has been resized + * + * @param widget View + * @param allocation Allocation + * @param zathura Zathura session + * @return true if signal has been handled successfully + */ +bool cb_view_resized(GtkWidget* widget, GtkAllocation* allocation, zathura_t* zathura); #endif // CALLBACKS_H diff --git a/commands.c b/commands.c index 82ba01e..7455326 100644 --- a/commands.c +++ b/commands.c @@ -1,16 +1,54 @@ /* See LICENSE file for license and copyright information */ +#include + #include "commands.h" #include "bookmarks.h" #include "database.h" +#include "document.h" #include "zathura.h" #include "print.h" #include "document.h" +#include "utils.h" +#include "page_widget.h" + + +#include +#include +#include bool -cmd_bookmark_create(girara_session_t* UNUSED(session), girara_list_t* - UNUSED(argument_list)) +cmd_bookmark_create(girara_session_t* session, girara_list_t* argument_list) { + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + if (zathura->document == NULL) { + girara_notify(session, GIRARA_ERROR, "No document opened."); + return false; + } + + const unsigned int argc = girara_list_size(argument_list); + if (argc != 1) { + girara_notify(session, GIRARA_ERROR, "Invalid number of arguments given."); + return false; + } + + const char* bookmark_name = girara_list_nth(argument_list, 0); + zathura_bookmark_t* bookmark = zathura_bookmark_get(zathura, bookmark_name); + if (bookmark != NULL) { + bookmark->page = zathura->document->current_page_number + 1; + girara_notify(session, GIRARA_INFO, "Bookmark successfuly updated: %s", bookmark_name); + return true; + } + + bookmark = zathura_bookmark_add(zathura, bookmark_name, zathura->document->current_page_number + 1); + if (bookmark == NULL) { + girara_notify(session, GIRARA_ERROR, "Could not create bookmark: %s", bookmark_name); + return false; + } + + girara_notify(session, GIRARA_INFO, "Bookmark successfuly created: %s", bookmark_name); return true; } @@ -42,10 +80,30 @@ cmd_bookmark_delete(girara_session_t* session, girara_list_t* argument_list) } bool -cmd_bookmark_open(girara_session_t* UNUSED(session), girara_list_t* - UNUSED(argument_list)) +cmd_bookmark_open(girara_session_t* session, girara_list_t* argument_list) { - return true; + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + if (zathura->document == NULL) { + girara_notify(session, GIRARA_ERROR, "No document opened."); + return false; + } + + const unsigned int argc = girara_list_size(argument_list); + if (argc != 1) { + girara_notify(session, GIRARA_ERROR, "Invalid number of arguments given."); + return false; + } + + const char* bookmark_name = girara_list_nth(argument_list, 0); + zathura_bookmark_t* bookmark = zathura_bookmark_get(zathura, bookmark_name); + if (bookmark == NULL) { + girara_notify(session, GIRARA_ERROR, "No such bookmark: %s", bookmark_name); + return false; + } + + return page_set(zathura, bookmark->page - 1); } bool @@ -55,7 +113,6 @@ cmd_close(girara_session_t* session, girara_list_t* UNUSED(argument_list)) g_return_val_if_fail(session->global.data != NULL, false); zathura_t* zathura = session->global.data; if (zathura->document == NULL) { - // nothing needs to be done return true; } @@ -65,10 +122,61 @@ cmd_close(girara_session_t* session, girara_list_t* UNUSED(argument_list)) } bool -cmd_info(girara_session_t* UNUSED(session), girara_list_t* - UNUSED(argument_list)) +cmd_info(girara_session_t* session, girara_list_t* UNUSED(argument_list)) { - return true; + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + if (zathura->document == NULL) { + girara_notify(session, GIRARA_ERROR, "No document opened."); + return false; + } + + struct meta_field { + char* name; + zathura_document_meta_t field; + }; + + struct meta_field meta_fields[] = { + { "Title", ZATHURA_DOCUMENT_TITLE }, + { "Author", ZATHURA_DOCUMENT_AUTHOR }, + { "Subject", ZATHURA_DOCUMENT_SUBJECT }, + { "Keywords", ZATHURA_DOCUMENT_KEYWORDS }, + { "Creator", ZATHURA_DOCUMENT_CREATOR }, + { "Producer", ZATHURA_DOCUMENT_PRODUCER }, + { "Creation date", ZATHURA_DOCUMENT_CREATION_DATE }, + { "Modiciation date", ZATHURA_DOCUMENT_MODIFICATION_DATE } + }; + + GString* string = g_string_new(NULL); + if (string == NULL) { + return true; + } + + for (unsigned int i = 0; i < LENGTH(meta_fields); i++) { + char* tmp = zathura_document_meta_get(zathura->document, meta_fields[i].field, NULL); + if (tmp != NULL) { + char* text = g_strdup_printf("%s: %s\n", meta_fields[i].name, tmp); + if (text == NULL) { + g_free(tmp); + return true; + } + + g_string_append(string, text); + + g_free(text); + g_free(tmp); + } + } + + if (strlen(string->str) > 0) { + g_string_erase(string, strlen(string->str) - 1, 1); + girara_notify(session, GIRARA_INFO, "%s", string->str); + } + + g_string_free(string, TRUE); + + return false; } bool @@ -89,15 +197,13 @@ cmd_open(girara_session_t* session, girara_list_t* argument_list) if (argc > 2) { girara_notify(session, GIRARA_ERROR, "Too many arguments."); return false; - } - else if (argc >= 1) { + } else if (argc >= 1) { if (zathura->document) { document_close(zathura); } document_open(zathura, girara_list_nth(argument_list, 0), (argc == 2) ? girara_list_nth(argument_list, 1) : NULL); - } - else { + } else { girara_notify(session, GIRARA_ERROR, "No arguments given."); return false; } @@ -136,8 +242,7 @@ cmd_save(girara_session_t* session, girara_list_t* argument_list) if (girara_list_size(argument_list) == 1) { document_save(zathura, girara_list_nth(argument_list, 0), false); - } - else { + } else { girara_notify(session, GIRARA_ERROR, "Invalid number of arguments."); return false; } @@ -159,11 +264,82 @@ cmd_savef(girara_session_t* session, girara_list_t* argument_list) if (girara_list_size(argument_list) == 1) { document_save(zathura, girara_list_nth(argument_list, 0), true); - } - else { + } else { girara_notify(session, GIRARA_ERROR, "Invalid number of arguments."); return false; } return true; } + +bool +cmd_search(girara_session_t* session, const char* input, girara_argument_t* argument) +{ + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(input != NULL, false); + g_return_val_if_fail(argument != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + + if (zathura->document == NULL || strlen(input) == 0) { + return false; + } + + bool firsthit = true; + for (unsigned int page_id = 0; page_id < zathura->document->number_of_pages; ++page_id) { + zathura_page_t* page = zathura->document->pages[(page_id + zathura->document->current_page_number) % zathura->document->number_of_pages]; + if (page == NULL) { + continue; + } + + g_object_set(page->drawing_area, "draw-links", FALSE, NULL); + + girara_list_t* result = zathura_page_search_text(page, input, NULL); + if (result == NULL || girara_list_size(result) == 0) { + girara_list_free(result); + g_object_set(page->drawing_area, "search-results", NULL, NULL); + continue; + } + + g_object_set(page->drawing_area, "search-results", result, NULL); + if (firsthit == true) { + if (page_id != 0) { + page_set_delayed(zathura, page->number); + } + g_object_set(page->drawing_area, "search-current", 0, NULL); + firsthit = false; + } + } + + return true; +} + +bool +cmd_export(girara_session_t* session, girara_list_t* argument_list) +{ + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + if (zathura->document == NULL) { + girara_notify(session, GIRARA_ERROR, "No document opened."); + return false; + } + + const unsigned int argc = girara_list_size(argument_list); + if (argc != 2) { + girara_notify(session, GIRARA_ERROR, "Invalid number of arguments given."); + return false; + } + + const char* attachment_name = girara_list_nth(argument_list, 0); + const char* file_name = girara_list_nth(argument_list, 1); + char* file_name2 = girara_fix_path(file_name); + if (!zathura_document_attachment_save(zathura->document, attachment_name, file_name)) { + girara_notify(session, GIRARA_ERROR, "Couldn't write attachment '%s' to '%s'.", attachment_name, file_name); + } else { + girara_notify(session, GIRARA_INFO, "Wrote attachment '%s' to '%s'.", attachment_name, file_name2); + } + + g_free(file_name2); + return true; +} diff --git a/commands.h b/commands.h index 1f09318..f1dc4aa 100644 --- a/commands.h +++ b/commands.h @@ -4,7 +4,7 @@ #define COMMANDS_H #include -#include +#include /** * Create a bookmark @@ -96,5 +96,23 @@ bool cmd_save(girara_session_t* session, girara_list_t* argument_list); */ bool cmd_savef(girara_session_t* session, girara_list_t* argument_list); +/** + * Search the current file + * + * @param session The used girara session + * @param input The current input + * @param argument Passed argument + * @return true if no error occured + */ +bool cmd_search(girara_session_t* session, const char* input, girara_argument_t* argument); + +/** + * Save attachment to a file + * + * @param session The used girara session + * @param argument_list List of passed arguments + * @return true if no error occured + */ +bool cmd_export(girara_session_t* session, girara_list_t* argument_list); #endif // COMMANDS_H diff --git a/completion.c b/completion.c index 5fc32cf..cfcbd92 100644 --- a/completion.c +++ b/completion.c @@ -6,11 +6,82 @@ #include #include +#include "bookmarks.h" #include "completion.h" #include "utils.h" +#include +#include +#include +#include + +static int +compare_case_insensitive(const char* str1, const char* str2) +{ + char* ustr1 = g_utf8_casefold(str1, -1); + char* ustr2 = g_utf8_casefold(str2, -1); + int res = g_utf8_collate(ustr1, ustr2); + g_free(ustr1); + g_free(ustr2); + return res; +} + +static girara_list_t* +list_files(zathura_t* zathura, const char* current_path, const char* current_file, int current_file_length, bool is_dir) +{ + /* read directory */ + GDir* dir = g_dir_open(current_path, 0, NULL); + if (dir == NULL) { + return NULL; + } + + girara_list_t* res = girara_sorted_list_new2((girara_compare_function_t)compare_case_insensitive, + (girara_free_function_t)g_free); + + /* read files */ + char* name = NULL; + while ((name = (char*) g_dir_read_name(dir)) != NULL) { + char* e_name = g_filename_display_name(name); + if (e_name == NULL) { + goto error_free; + } + int e_length = strlen(e_name); + + if ((current_file_length > e_length) || strncmp(current_file, e_name, + current_file_length)) { + g_free(e_name); + continue; + } + + char* full_path = g_strdup_printf("%s%s%s", current_path, is_dir ? "" : "/", e_name); + if (full_path == NULL) { + g_free(e_name); + goto error_free; + } + + if (g_file_test(full_path, G_FILE_TEST_IS_DIR) == true) { + char* tmp_path = full_path; + full_path = g_strdup_printf("%s/", full_path); + g_free(tmp_path); + girara_list_append(res, full_path); + } else if (file_valid_extension(zathura, full_path) == true) { + girara_list_append(res, full_path); + } else { + g_free(full_path); + } + g_free(e_name); + } + + g_dir_close(dir); + return res; + +error_free: + girara_list_free(res); + return NULL; +} + girara_completion_t* -cc_open(girara_session_t* session, char* input) +cc_open(girara_session_t* session, const char* input) { g_return_val_if_fail(session != NULL, NULL); g_return_val_if_fail(session->global.data != NULL, NULL); @@ -34,7 +105,7 @@ cc_open(girara_session_t* session, char* input) /* If the path does not begin with a slash we update the path with the current * working directory */ if (strlen(path) == 0 || path[0] != '/') { - size_t path_max; + long path_max; #ifdef PATH_MAX path_max = PATH_MAX; #else @@ -44,7 +115,9 @@ cc_open(girara_session_t* session, char* input) #endif char cwd[path_max]; - getcwd(cwd, path_max); + if (getcwd(cwd, path_max) == NULL) { + goto error_free; + } char* tmp_path = g_strdup_printf("%s/%s", cwd, path); if (tmp_path == NULL) { @@ -79,50 +152,19 @@ cc_open(girara_session_t* session, char* input) /* read directory */ if (g_file_test(current_path, G_FILE_TEST_IS_DIR) == TRUE) { - GDir* dir = g_dir_open(current_path, 0, NULL); - if (dir == NULL) { + girara_list_t* names = list_files(zathura, current_path, current_file, current_file_length, is_dir); + if (!names) { goto error_free; } - /* read files */ - char* name = NULL; - while ((name = (char*) g_dir_read_name(dir)) != NULL) { - char* e_name = g_filename_display_name(name); - int e_length = strlen(e_name); - - if (e_name == NULL) { - goto error_free; - } - - if ((current_file_length > e_length) || strncmp(current_file, e_name, - current_file_length)) { - g_free(e_name); - continue; - } - - char* full_path = g_strdup_printf("%s%s%s", current_path, is_dir ? "" : "/", e_name); - if (full_path == NULL) { - g_free(e_name); - goto error_free; - } - - if (g_file_test(full_path, G_FILE_TEST_IS_DIR) == true) { - char* tmp_path = full_path; - full_path = g_strdup_printf("%s/", full_path); - g_free(tmp_path); - girara_completion_group_add_element(group, full_path, NULL); - } else if (file_valid_extension(zathura, full_path) == true) { - girara_completion_group_add_element(group, full_path, NULL); - } - - g_free(full_path); - g_free(e_name); - } - - g_dir_close(dir); + GIRARA_LIST_FOREACH(names, const char*, iter, file) + girara_completion_group_add_element(group, file, NULL); + GIRARA_LIST_FOREACH_END(names, const char*, iter, file); + girara_list_free(names); } g_free(path); + g_free(current_path); girara_completion_add_group(completion, group); @@ -142,3 +184,94 @@ error_free: return NULL; } + +girara_completion_t* +cc_bookmarks(girara_session_t* session, const char* input) +{ + if (input == NULL) { + return NULL; + } + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(session->global.data != NULL, NULL); + zathura_t* zathura = session->global.data; + + girara_completion_t* completion = girara_completion_init(); + girara_completion_group_t* group = girara_completion_group_create(session, NULL); + + if (completion == NULL || group == NULL) { + goto error_free; + } + + const size_t input_length = strlen(input); + GIRARA_LIST_FOREACH(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark) + if (input_length <= strlen(bookmark->id) && !strncmp(input, bookmark->id, input_length)) { + gchar* paged = g_strdup_printf("Page %d", bookmark->page); + girara_completion_group_add_element(group, bookmark->id, paged); + g_free(paged); + } + GIRARA_LIST_FOREACH_END(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark); + + girara_completion_add_group(completion, group); + + return completion; + +error_free: + + if (completion) { + girara_completion_free(completion); + } + + if (group) { + girara_completion_group_free(group); + } + + return NULL; +} + +girara_completion_t* +cc_export(girara_session_t* session, const char* input) +{ + if (input == NULL) { + return NULL; + } + + g_return_val_if_fail(session != NULL, NULL); + g_return_val_if_fail(session->global.data != NULL, NULL); + zathura_t* zathura = session->global.data; + + girara_completion_t* completion = girara_completion_init(); + girara_completion_group_t* group = girara_completion_group_create(session, NULL); + + if (completion == NULL || group == NULL) { + goto error_free; + } + + const size_t input_length = strlen(input); + girara_list_t* attachments = zathura_document_attachments_get(zathura->document, NULL); + if (attachments == NULL) { + goto error_free; + } + + GIRARA_LIST_FOREACH(attachments, const char*, iter, attachment) + if (input_length <= strlen(attachment) && !strncmp(input, attachment, input_length)) { + girara_completion_group_add_element(group, attachment, NULL); + } + GIRARA_LIST_FOREACH_END(zathura->bookmarks.bookmarks, zathura_bookmark_t*, iter, bookmark); + + girara_completion_add_group(completion, group); + girara_list_free(attachments); + return completion; + +error_free: + + if (completion) { + girara_completion_free(completion); + } + + if (group) { + girara_completion_group_free(group); + } + + return NULL; +} diff --git a/completion.h b/completion.h index 7041d8a..7b8f67a 100644 --- a/completion.h +++ b/completion.h @@ -3,7 +3,7 @@ #ifndef COMPLETION_H #define COMPLETION_H -#include +#include /** * Completion for the open command - Creates a list of accesible directories or @@ -13,6 +13,25 @@ * @param input The current input * @return The completion object or NULL if an error occured */ -girara_completion_t* cc_open(girara_session_t* session, char* input); +girara_completion_t* cc_open(girara_session_t* session, const char* input); + +/** + * Completion for the bmarks command - Creates a list of bookmarks + * + * @param session The used girara session + * @param input The current input + * @return The completion object or NULL if an error occured + */ +girara_completion_t* cc_bookmarks(girara_session_t* session, const char* input); + +/** + * Completion for the export command - Creates a list of attachments + * + * @param session the girara session + * @param input the current input + * @return completion object, NULL on error + */ +girara_completion_t* cc_export(girara_session_t* session, const char* input); + #endif // COMPLETION_H diff --git a/config.c b/config.c index b54aea8..3a3e3d6 100644 --- a/config.c +++ b/config.c @@ -7,15 +7,23 @@ #include "shortcuts.h" #include "zathura.h" +#include +#include +#include +#include +#include + void config_load_default(zathura_t* zathura) { - if (!zathura || !zathura->ui.session) { + if (zathura == NULL || zathura->ui.session == NULL) { return; } int int_value = 0; + float float_value = 0; char* string_value = NULL; + bool bool_value = false; girara_session_t* gsession = zathura->ui.session; /* mode settings */ @@ -33,90 +41,126 @@ config_load_default(zathura_t* zathura) /* zathura settings */ int_value = 10; - girara_setting_add(gsession, "zoom-step", &int_value, INT, false, "Zoom step", NULL, NULL); + girara_setting_add(gsession, "zoom-step", &int_value, INT, false, "Zoom step", NULL, NULL); int_value = 1; - girara_setting_add(gsession, "page-padding", &int_value, INT, true, "Padding between pages", NULL, NULL); - int_value = 2; - girara_setting_add(gsession, "pages-per-row", &int_value, INT, false, "Number of pages per row", cb_pages_per_row_value_changed, zathura); + girara_setting_add(gsession, "page-padding", &int_value, INT, true, "Padding between pages", NULL, NULL); + int_value = 1; + girara_setting_add(gsession, "pages-per-row", &int_value, INT, false, "Number of pages per row", cb_pages_per_row_value_changed, zathura); + float_value = 40; + girara_setting_add(gsession, "scroll-step", &float_value, FLOAT, false, "Scroll step", NULL, NULL); string_value = "#FFFFFF"; - girara_setting_add(gsession, "recolor-dark-color", string_value, STRING, false, "Recoloring (dark color)", NULL, NULL); + girara_setting_add(gsession, "recolor-darkcolor", string_value, STRING, false, "Recoloring (dark color)", NULL, NULL); string_value = "#000000"; - girara_setting_add(gsession, "recolor-light-color", string_value, STRING, false, "Recoloring (light color)", NULL, NULL); + girara_setting_add(gsession, "recolor-lightcolor", string_value, STRING, false, "Recoloring (light color)", NULL, NULL); + + string_value = "#9FBC00"; + girara_setting_add(gsession, "highlight-color", string_value, STRING, false, "Color for highlighting", NULL, NULL); + float_value = 0.5; + girara_setting_add(gsession, "highlight-transparency", &float_value, FLOAT, false, "Transparency for highlighting", NULL, NULL); + bool_value = true; + girara_setting_add(gsession, "render-loading", &bool_value, BOOLEAN, false, "Render 'Loading ...'", NULL, NULL); + int_value = ADJUST_BESTFIT; + girara_setting_add(gsession, "adjust-open", &int_value, INT, false, "Adjust to when opening file", NULL, NULL); /* define default shortcuts */ - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_c, NULL, sc_abort, 0, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_Escape, NULL, sc_abort, 0, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_a, NULL, sc_adjust_window, NORMAL, ADJUST_BESTFIT, NULL); - girara_shortcut_add(gsession, 0, GDK_s, NULL, sc_adjust_window, NORMAL, ADJUST_WIDTH, NULL); - girara_shortcut_add(gsession, 0, GDK_i, NULL, sc_change_mode, NORMAL, INSERT, NULL); - girara_shortcut_add(gsession, 0, GDK_m, NULL, sc_change_mode, NORMAL, ADD_MARKER, NULL); - girara_shortcut_add(gsession, 0, GDK_apostrophe, NULL, sc_change_mode, NORMAL, EVAL_MARKER, NULL); - girara_shortcut_add(gsession, 0, GDK_slash, NULL, girara_sc_focus_inputbar, NORMAL, 0, &("/")); - girara_shortcut_add(gsession, GDK_SHIFT_MASK, GDK_slash, NULL, girara_sc_focus_inputbar, NORMAL, 0, &("/")); - girara_shortcut_add(gsession, 0, GDK_question, NULL, girara_sc_focus_inputbar, NORMAL, 0, &("?")); - girara_shortcut_add(gsession, 0, GDK_colon, NULL, girara_sc_focus_inputbar, NORMAL, 0, &(":")); - girara_shortcut_add(gsession, 0, GDK_o, NULL, girara_sc_focus_inputbar, NORMAL, 0, &(":open ")); - girara_shortcut_add(gsession, 0, GDK_O, NULL, girara_sc_focus_inputbar, NORMAL, APPEND_FILEPATH, &(":open ")); - girara_shortcut_add(gsession, 0, GDK_f, NULL, sc_follow, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, 0, "gg", sc_goto, NORMAL | FULLSCREEN, TOP, NULL); - girara_shortcut_add(gsession, 0, 0, "G", sc_goto, NORMAL | FULLSCREEN, BOTTOM, NULL); - girara_shortcut_add(gsession, 0, GDK_J, NULL, sc_navigate, NORMAL, NEXT, NULL); - girara_shortcut_add(gsession, 0, GDK_K, NULL, sc_navigate, NORMAL, PREVIOUS, NULL); - girara_shortcut_add(gsession, GDK_MOD1_MASK, GDK_Right, NULL, sc_navigate, NORMAL, NEXT, NULL); - girara_shortcut_add(gsession, GDK_MOD1_MASK, GDK_Left, NULL, sc_navigate, NORMAL, PREVIOUS, NULL); - girara_shortcut_add(gsession, 0, GDK_Left, NULL, sc_navigate, FULLSCREEN, PREVIOUS, NULL); - girara_shortcut_add(gsession, 0, GDK_Up, NULL, sc_navigate, FULLSCREEN, PREVIOUS, NULL); - girara_shortcut_add(gsession, 0, GDK_Down, NULL, sc_navigate, FULLSCREEN, NEXT, NULL); - girara_shortcut_add(gsession, 0, GDK_Right, NULL, sc_navigate, FULLSCREEN, NEXT, NULL); - girara_shortcut_add(gsession, 0, GDK_k, NULL, sc_navigate_index, INDEX, UP, NULL); - girara_shortcut_add(gsession, 0, GDK_j, NULL, sc_navigate_index, INDEX, DOWN, NULL); - girara_shortcut_add(gsession, 0, GDK_h, NULL, sc_navigate_index, INDEX, COLLAPSE, NULL); - girara_shortcut_add(gsession, 0, GDK_l, NULL, sc_navigate_index, INDEX, EXPAND, NULL); - girara_shortcut_add(gsession, 0, GDK_space, NULL, sc_navigate_index, INDEX, SELECT, NULL); - girara_shortcut_add(gsession, 0, GDK_Return, NULL, sc_navigate_index, INDEX, SELECT, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_i, NULL, sc_recolor, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_R, NULL, sc_reload, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_r, NULL, sc_rotate, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_h, NULL, sc_scroll, NORMAL, LEFT, NULL); - girara_shortcut_add(gsession, 0, GDK_j, NULL, sc_scroll, NORMAL, DOWN, NULL); - girara_shortcut_add(gsession, 0, GDK_k, NULL, sc_scroll, NORMAL, UP, NULL); - girara_shortcut_add(gsession, 0, GDK_l, NULL, sc_scroll, NORMAL, RIGHT, NULL); - girara_shortcut_add(gsession, 0, GDK_Left, NULL, sc_scroll, NORMAL, LEFT, NULL); - girara_shortcut_add(gsession, 0, GDK_Up, NULL, sc_scroll, NORMAL, UP, NULL); - girara_shortcut_add(gsession, 0, GDK_Down, NULL, sc_scroll, NORMAL, DOWN, NULL); - girara_shortcut_add(gsession, 0, GDK_Right, NULL, sc_scroll, NORMAL, RIGHT, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_d, NULL, sc_scroll, NORMAL, HALF_DOWN, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_u, NULL, sc_scroll, NORMAL, HALF_UP, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_f, NULL, sc_scroll, NORMAL, FULL_DOWN, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_b, NULL, sc_scroll, NORMAL, FULL_UP, NULL); - girara_shortcut_add(gsession, 0, GDK_space, NULL, sc_scroll, NORMAL, FULL_DOWN, NULL); - girara_shortcut_add(gsession, GDK_SHIFT_MASK, GDK_space, NULL, sc_scroll, NORMAL, FULL_UP, NULL); - girara_shortcut_add(gsession, 0, GDK_n, NULL, sc_search, NORMAL, FORWARD, NULL); - girara_shortcut_add(gsession, 0, GDK_N, NULL, sc_search, NORMAL, BACKWARD, NULL); - girara_shortcut_add(gsession, 0, GDK_Tab, NULL, sc_toggle_index, NORMAL | INDEX, 0, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_m, NULL, girara_sc_toggle_inputbar, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_F5, NULL, sc_toggle_fullscreen, NORMAL | FULLSCREEN, 0, NULL); - girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_n, NULL, girara_sc_toggle_statusbar, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_q, NULL, sc_quit, NORMAL, 0, NULL); - girara_shortcut_add(gsession, 0, GDK_plus, NULL, sc_zoom, NORMAL | FULLSCREEN, ZOOM_IN, NULL); - girara_shortcut_add(gsession, 0, GDK_minus, NULL, sc_zoom, NORMAL | FULLSCREEN, ZOOM_OUT, NULL); - girara_shortcut_add(gsession, 0, GDK_equal, NULL, sc_zoom, NORMAL | FULLSCREEN, ZOOM_ORIGINAL, NULL); - girara_shortcut_add(gsession, 0, 0, "zI", sc_zoom, NORMAL | FULLSCREEN, ZOOM_IN, NULL); - girara_shortcut_add(gsession, 0, 0, "zO", sc_zoom, NORMAL | FULLSCREEN, ZOOM_OUT, NULL); - girara_shortcut_add(gsession, 0, 0, "z0", sc_zoom, NORMAL | FULLSCREEN, ZOOM_ORIGINAL, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_c, NULL, sc_abort, 0, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_Escape, NULL, sc_abort, 0, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_a, NULL, sc_adjust_window, NORMAL, ADJUST_BESTFIT, NULL); + girara_shortcut_add(gsession, 0, GDK_s, NULL, sc_adjust_window, NORMAL, ADJUST_WIDTH, NULL); + girara_shortcut_add(gsession, 0, GDK_i, NULL, sc_change_mode, NORMAL, INSERT, NULL); + girara_shortcut_add(gsession, 0, GDK_m, NULL, sc_change_mode, NORMAL, ADD_MARKER, NULL); + girara_shortcut_add(gsession, 0, GDK_apostrophe, NULL, sc_change_mode, NORMAL, EVAL_MARKER, NULL); + girara_shortcut_add(gsession, 0, GDK_slash, NULL, girara_sc_focus_inputbar, NORMAL, 0, &("/")); + girara_shortcut_add(gsession, GDK_SHIFT_MASK, GDK_slash, NULL, girara_sc_focus_inputbar, NORMAL, 0, &("/")); + girara_shortcut_add(gsession, 0, GDK_question, NULL, girara_sc_focus_inputbar, NORMAL, 0, &("?")); + girara_shortcut_add(gsession, 0, GDK_colon, NULL, girara_sc_focus_inputbar, NORMAL, 0, &(":")); + girara_shortcut_add(gsession, 0, GDK_o, NULL, girara_sc_focus_inputbar, NORMAL, 0, &(":open ")); + girara_shortcut_add(gsession, 0, GDK_O, NULL, girara_sc_focus_inputbar, NORMAL, APPEND_FILEPATH, &(":open ")); + girara_shortcut_add(gsession, 0, GDK_f, NULL, sc_follow, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, 0, "gg", sc_goto, NORMAL, TOP, NULL); + girara_shortcut_add(gsession, 0, 0, "gg", sc_goto, FULLSCREEN, TOP, NULL); + girara_shortcut_add(gsession, 0, 0, "G", sc_goto, NORMAL, BOTTOM, NULL); + girara_shortcut_add(gsession, 0, 0, "G", sc_goto, FULLSCREEN, BOTTOM, NULL); + girara_shortcut_add(gsession, 0, GDK_J, NULL, sc_navigate, NORMAL, NEXT, NULL); + girara_shortcut_add(gsession, 0, GDK_K, NULL, sc_navigate, NORMAL, PREVIOUS, NULL); + girara_shortcut_add(gsession, GDK_MOD1_MASK, GDK_Right, NULL, sc_navigate, NORMAL, NEXT, NULL); + girara_shortcut_add(gsession, GDK_MOD1_MASK, GDK_Left, NULL, sc_navigate, NORMAL, PREVIOUS, NULL); + girara_shortcut_add(gsession, 0, GDK_Left, NULL, sc_navigate, FULLSCREEN, PREVIOUS, NULL); + girara_shortcut_add(gsession, 0, GDK_Up, NULL, sc_navigate, FULLSCREEN, PREVIOUS, NULL); + girara_shortcut_add(gsession, 0, GDK_Down, NULL, sc_navigate, FULLSCREEN, NEXT, NULL); + girara_shortcut_add(gsession, 0, GDK_Right, NULL, sc_navigate, FULLSCREEN, NEXT, NULL); + girara_shortcut_add(gsession, 0, GDK_k, NULL, sc_navigate_index, INDEX, UP, NULL); + girara_shortcut_add(gsession, 0, GDK_j, NULL, sc_navigate_index, INDEX, DOWN, NULL); + girara_shortcut_add(gsession, 0, GDK_h, NULL, sc_navigate_index, INDEX, COLLAPSE, NULL); + girara_shortcut_add(gsession, 0, GDK_l, NULL, sc_navigate_index, INDEX, EXPAND, NULL); + girara_shortcut_add(gsession, 0, GDK_space, NULL, sc_navigate_index, INDEX, SELECT, NULL); + girara_shortcut_add(gsession, 0, GDK_Return, NULL, sc_navigate_index, INDEX, SELECT, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_i, NULL, sc_recolor, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_R, NULL, sc_reload, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_r, NULL, sc_rotate, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_h, NULL, sc_scroll, NORMAL, LEFT, NULL); + girara_shortcut_add(gsession, 0, GDK_j, NULL, sc_scroll, NORMAL, DOWN, NULL); + girara_shortcut_add(gsession, 0, GDK_k, NULL, sc_scroll, NORMAL, UP, NULL); + girara_shortcut_add(gsession, 0, GDK_l, NULL, sc_scroll, NORMAL, RIGHT, NULL); + girara_shortcut_add(gsession, 0, GDK_Left, NULL, sc_scroll, NORMAL, LEFT, NULL); + girara_shortcut_add(gsession, 0, GDK_Up, NULL, sc_scroll, NORMAL, UP, NULL); + girara_shortcut_add(gsession, 0, GDK_Down, NULL, sc_scroll, NORMAL, DOWN, NULL); + girara_shortcut_add(gsession, 0, GDK_Right, NULL, sc_scroll, NORMAL, RIGHT, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_d, NULL, sc_scroll, NORMAL, HALF_DOWN, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_u, NULL, sc_scroll, NORMAL, HALF_UP, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_f, NULL, sc_scroll, NORMAL, FULL_DOWN, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_b, NULL, sc_scroll, NORMAL, FULL_UP, NULL); + girara_shortcut_add(gsession, 0, GDK_space, NULL, sc_scroll, NORMAL, FULL_DOWN, NULL); + girara_shortcut_add(gsession, GDK_SHIFT_MASK, GDK_space, NULL, sc_scroll, NORMAL, FULL_UP, NULL); + girara_shortcut_add(gsession, 0, GDK_n, NULL, sc_search, NORMAL, FORWARD, NULL); + girara_shortcut_add(gsession, 0, GDK_N, NULL, sc_search, NORMAL, BACKWARD, NULL); + girara_shortcut_add(gsession, 0, GDK_Tab, NULL, sc_toggle_index, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_Tab, NULL, sc_toggle_index, INDEX, 0, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_m, NULL, girara_sc_toggle_inputbar, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_F5, NULL, sc_toggle_fullscreen, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_F5, NULL, sc_toggle_fullscreen, FULLSCREEN, 0, NULL); + girara_shortcut_add(gsession, GDK_CONTROL_MASK, GDK_n, NULL, girara_sc_toggle_statusbar, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_q, NULL, sc_quit, NORMAL, 0, NULL); + girara_shortcut_add(gsession, 0, GDK_plus, NULL, sc_zoom, NORMAL, ZOOM_IN, NULL); + girara_shortcut_add(gsession, 0, GDK_plus, NULL, sc_zoom, FULLSCREEN, ZOOM_IN, NULL); + girara_shortcut_add(gsession, 0, GDK_minus, NULL, sc_zoom, NORMAL, ZOOM_OUT, NULL); + girara_shortcut_add(gsession, 0, GDK_minus, NULL, sc_zoom, FULLSCREEN, ZOOM_OUT, NULL); + girara_shortcut_add(gsession, 0, GDK_equal, NULL, sc_zoom, NORMAL, ZOOM_ORIGINAL, NULL); + girara_shortcut_add(gsession, 0, GDK_equal, NULL, sc_zoom, FULLSCREEN, ZOOM_ORIGINAL, NULL); + girara_shortcut_add(gsession, 0, 0, "zI", sc_zoom, NORMAL, ZOOM_IN, NULL); + girara_shortcut_add(gsession, 0, 0, "zI", sc_zoom, FULLSCREEN, ZOOM_IN, NULL); + girara_shortcut_add(gsession, 0, 0, "zO", sc_zoom, NORMAL, ZOOM_OUT, NULL); + girara_shortcut_add(gsession, 0, 0, "zO", sc_zoom, FULLSCREEN, ZOOM_OUT, NULL); + girara_shortcut_add(gsession, 0, 0, "z0", sc_zoom, NORMAL, ZOOM_ORIGINAL, NULL); + girara_shortcut_add(gsession, 0, 0, "z0", sc_zoom, FULLSCREEN, ZOOM_ORIGINAL, NULL); + girara_shortcut_add(gsession, 0, GDK_equal, NULL, sc_zoom, NORMAL, ZOOM_SPECIFIC, NULL); + girara_shortcut_add(gsession, 0, GDK_equal, NULL, sc_zoom, FULLSCREEN, ZOOM_SPECIFIC, NULL); + + /* mouse events */ + girara_mouse_event_add(gsession, 0, 0, sc_mouse_scroll, NORMAL, GIRARA_EVENT_SCROLL, 0, NULL); + girara_mouse_event_add(gsession, 0, 0, sc_mouse_scroll, FULLSCREEN, GIRARA_EVENT_SCROLL, 0, NULL); + girara_mouse_event_add(gsession, GDK_CONTROL_MASK, 0, sc_mouse_zoom, NORMAL, GIRARA_EVENT_SCROLL, 0, NULL); + girara_mouse_event_add(gsession, GDK_CONTROL_MASK, 0, sc_mouse_zoom, FULLSCREEN, GIRARA_EVENT_SCROLL, 0, NULL); + girara_mouse_event_add(gsession, 0, GIRARA_MOUSE_BUTTON2, sc_mouse_scroll, NORMAL, GIRARA_EVENT_BUTTON_PRESS, 0, NULL); + girara_mouse_event_add(gsession, GDK_BUTTON2_MASK, GIRARA_MOUSE_BUTTON2, sc_mouse_scroll, NORMAL, GIRARA_EVENT_BUTTON_RELEASE, 0, NULL); + girara_mouse_event_add(gsession, GDK_BUTTON2_MASK, 0, sc_mouse_scroll, NORMAL, GIRARA_EVENT_MOTION_NOTIFY, 0, NULL); /* define default inputbar commands */ - girara_inputbar_command_add(gsession, "bmark", NULL, cmd_bookmark_create, NULL, "Add a bookmark"); - girara_inputbar_command_add(gsession, "bdelete", NULL, cmd_bookmark_delete, NULL, "Delete a bookmark"); - girara_inputbar_command_add(gsession, "blist", NULL, cmd_bookmark_open, NULL, "List all bookmarks"); - girara_inputbar_command_add(gsession, "close", NULL, cmd_close, NULL, "Close current file"); - girara_inputbar_command_add(gsession, "info", NULL, cmd_info, NULL, "Show file information"); - girara_inputbar_command_add(gsession, "help", NULL, cmd_help, NULL, "Show help"); - girara_inputbar_command_add(gsession, "open", "o", cmd_open, cc_open, "Open document"); - girara_inputbar_command_add(gsession, "print", NULL, cmd_print, NULL, "Print document"); - girara_inputbar_command_add(gsession, "write", NULL, cmd_save, NULL, "Save document"); - girara_inputbar_command_add(gsession, "write!", NULL, cmd_savef, NULL, "Save document (and force overwriting)"); + girara_inputbar_command_add(gsession, "bmark", NULL, cmd_bookmark_create, NULL, "Add a bookmark"); + girara_inputbar_command_add(gsession, "bdelete", NULL, cmd_bookmark_delete, cc_bookmarks, "Delete a bookmark"); + girara_inputbar_command_add(gsession, "blist", NULL, cmd_bookmark_open, cc_bookmarks, "List all bookmarks"); + girara_inputbar_command_add(gsession, "close", NULL, cmd_close, NULL, "Close current file"); + girara_inputbar_command_add(gsession, "info", NULL, cmd_info, NULL, "Show file information"); + girara_inputbar_command_add(gsession, "help", NULL, cmd_help, NULL, "Show help"); + girara_inputbar_command_add(gsession, "open", "o", cmd_open, cc_open, "Open document"); + girara_inputbar_command_add(gsession, "print", NULL, cmd_print, NULL, "Print document"); + girara_inputbar_command_add(gsession, "write", NULL, cmd_save, NULL, "Save document"); + girara_inputbar_command_add(gsession, "write!", NULL, cmd_savef, NULL, "Save document (and force overwriting)"); + girara_inputbar_command_add(gsession, "export", NULL, cmd_export, cc_export, "Save attachments"); + + girara_special_command_add(gsession, '/', cmd_search, true, FORWARD, NULL); + girara_special_command_add(gsession, '?', cmd_search, true, BACKWARD, NULL); /* add shortcut mappings */ girara_shortcut_mapping_add(gsession, "abort", sc_abort); @@ -137,6 +181,26 @@ config_load_default(zathura_t* zathura) girara_shortcut_mapping_add(gsession, "toggle_inputbar", girara_sc_toggle_inputbar); girara_shortcut_mapping_add(gsession, "toggle_statusbar", girara_sc_toggle_statusbar); girara_shortcut_mapping_add(gsession, "zoom", sc_zoom); + + /* add argument mappings */ + girara_argument_mapping_add(gsession, "bottom", BOTTOM); + girara_argument_mapping_add(gsession, "default", DEFAULT); + girara_argument_mapping_add(gsession, "down", DOWN); + girara_argument_mapping_add(gsession, "full-down", FULL_DOWN); + girara_argument_mapping_add(gsession, "full-up", FULL_UP); + girara_argument_mapping_add(gsession, "half-down", HALF_DOWN); + girara_argument_mapping_add(gsession, "half-up", HALF_UP); + girara_argument_mapping_add(gsession, "in", ZOOM_IN); + girara_argument_mapping_add(gsession, "left", LEFT); + girara_argument_mapping_add(gsession, "next", NEXT); + girara_argument_mapping_add(gsession, "out", ZOOM_OUT); + girara_argument_mapping_add(gsession, "previous", PREVIOUS); + girara_argument_mapping_add(gsession, "right", RIGHT); + girara_argument_mapping_add(gsession, "specific", ZOOM_SPECIFIC); + girara_argument_mapping_add(gsession, "top", TOP); + girara_argument_mapping_add(gsession, "up", UP); + girara_argument_mapping_add(gsession, "best-fit", ADJUST_BESTFIT); + girara_argument_mapping_add(gsession, "width", ADJUST_WIDTH); } void diff --git a/config.h b/config.h index 3e30f87..4587468 100644 --- a/config.h +++ b/config.h @@ -11,13 +11,14 @@ /** * This function loads the default values of the configuration * - * @param zathura the zathura session + * @param zathura The zathura session */ void config_load_default(zathura_t* zathura); /** * Loads and evaluates a configuration file * + * @param zathura The zathura session * @param path Path to the configuration file */ void config_load_file(zathura_t* zathura, char* path); diff --git a/config.mk b/config.mk index 1edeeec..da58c87 100644 --- a/config.mk +++ b/config.mk @@ -6,6 +6,9 @@ VERSION = 0.0.8.1 # paths PREFIX ?= /usr MANPREFIX ?= ${PREFIX}/share/man +DESKTOPPREFIX ?= ${PREFIX}/share/applications +# list of : seperated values +PLUGINDIR ?= ${PREFIX}/lib/zathura # libs @@ -19,13 +22,15 @@ GIRARA_LIB ?= $(shell pkg-config --libs girara-gtk2) SQLITE_INC ?= $(shell pkg-config --cflags sqlite3) SQLITE_LIB ?= $(shell pkg-config --libs sqlite3) +#set it to an empty value if you don't need to link against ld for dlopen and friends DL_LIB ?= -ldl -INCS = ${GIRARA_INC} ${GTK_INC} $(SQLITE_INC) -LIBS = ${GIRARA_LIB} ${GTK_LIB} $(SQLITE_LIB) $(DL_LIB) -lpthread -lm +INCS = ${GIRARA_INC} ${GTK_INC} +LIBS = ${GIRARA_LIB} ${GTK_LIB} $(DL_LIB) -lpthread -lm # flags CFLAGS += -std=c99 -pedantic -Wall -Wno-format-zero-length -Wextra $(INCS) +CPPFLAGS += -DZATHURA_PLUGINDIR=\"${PLUGINDIR}\" # debug DFLAGS ?= -g @@ -41,3 +46,7 @@ SFLAGS ?= -s # set to something != 0 if you want verbose build output VERBOSE ?= 0 + +# database +# possible values are sqlite and plain +DATABASE ?= plain diff --git a/database-plain.c b/database-plain.c new file mode 100644 index 0000000..804d626 --- /dev/null +++ b/database-plain.c @@ -0,0 +1,403 @@ +/* See LICENSE file for license and copyright information */ + +#define _POSIX_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "database.h" + +#define BOOKMARKS "bookmarks" +#define HISTORY "history" + +#define KEY_PAGE "page" +#define KEY_OFFSET "offset" +#define KEY_SCALE "scale" +#define KEY_ROTATE "rotate" + +#define file_lock_set(fd, cmd) \ + { \ + struct flock lock = { .l_type = cmd, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0}; \ + fcntl(fd, F_SETLK, lock); \ + } + +/* forward declaration */ +static bool zathura_db_check_file(const char* path); +static GKeyFile* zathura_db_read_key_file_from_file(const char* path); +static void zathura_db_write_key_file_to_file(const char* file, GKeyFile* key_file); +static void cb_zathura_db_watch_file(GFileMonitor* monitor, GFile* file, GFile* + other_file, GFileMonitorEvent event, zathura_database_t* database); + +struct zathura_database_s +{ + char* bookmark_path; + GKeyFile* bookmarks; + GFileMonitor* bookmark_monitor; + + char* history_path; + GKeyFile* history; + GFileMonitor* history_monitor; +}; + +zathura_database_t* +zathura_db_init(const char* dir) +{ + if (dir == NULL) { + goto error_ret; + } + + zathura_database_t* db = calloc(1, sizeof(zathura_database_t)); + if (db == NULL) { + goto error_ret; + } + + /* bookmarks */ + db->bookmark_path = g_build_filename(dir, BOOKMARKS, NULL); + if (db->bookmark_path == NULL || + zathura_db_check_file(db->bookmark_path) == false) { + goto error_free; + } + + GFile* bookmark_file = g_file_new_for_path(db->bookmark_path); + if (bookmark_file != NULL) { + db->bookmark_monitor = g_file_monitor(bookmark_file, G_FILE_MONITOR_NONE, NULL, NULL); + } else { + goto error_free; + } + g_object_unref(bookmark_file); + + g_signal_connect( + G_OBJECT(db->bookmark_monitor), + "changed", + G_CALLBACK(cb_zathura_db_watch_file), + db + ); + + db->bookmarks = zathura_db_read_key_file_from_file(db->bookmark_path); + if (db->bookmarks == NULL) { + goto error_free; + } + + /* history */ + db->history_path = g_build_filename(dir, HISTORY, NULL); + if (db->history_path == NULL || + zathura_db_check_file(db->history_path) == false) { + goto error_free; + } + + GFile* history_file = g_file_new_for_path(db->history_path); + if (history_file != NULL) { + db->history_monitor = g_file_monitor(history_file, G_FILE_MONITOR_NONE, NULL, NULL); + } else { + goto error_free; + } + g_object_unref(history_file); + + g_signal_connect( + G_OBJECT(db->history_monitor), + "changed", + G_CALLBACK(cb_zathura_db_watch_file), + db + ); + + db->history = zathura_db_read_key_file_from_file(db->history_path); + if (db->history == NULL) { + goto error_free; + } + + return db; + +error_free: + + zathura_db_free(db); + +error_ret: + + return NULL; +} + +void +zathura_db_free(zathura_database_t* db) +{ + if (db == NULL) { + return; + } + + /* bookmarks */ + g_free(db->bookmark_path); + + if (db->bookmark_monitor != NULL) { + g_object_unref(db->bookmark_monitor); + } + + if (db->bookmarks != NULL) { + g_key_file_free(db->bookmarks); + } + + /* history */ + g_free(db->history_path); + + if (db->history_monitor != NULL) { + g_object_unref(db->history_monitor); + } + + /* database */ + free(db); +} + +bool +zathura_db_add_bookmark(zathura_database_t* db, const char* file, + zathura_bookmark_t* bookmark) +{ + if (db == NULL || db->bookmarks == NULL || db->bookmark_path == NULL || file + == NULL || bookmark == NULL || bookmark->id == NULL) { + return false; + } + + g_key_file_set_integer(db->bookmarks, file, bookmark->id, bookmark->page); + + zathura_db_write_key_file_to_file(db->bookmark_path, db->bookmarks); + + return true; +} + +bool +zathura_db_remove_bookmark(zathura_database_t* db, const char* file, const char* + id) +{ + if (db == NULL || db->bookmarks == NULL || db->bookmark_path == NULL || file + == NULL || id == NULL) { + return false; + } + + if (g_key_file_has_group(db->bookmarks, file) == TRUE) { + g_key_file_remove_group(db->bookmarks, file, NULL); + + zathura_db_write_key_file_to_file(db->bookmark_path, db->bookmarks); + + return true; + } + + return false; +} + +girara_list_t* +zathura_db_load_bookmarks(zathura_database_t* db, const char* file) +{ + if (db == NULL || db->bookmarks == NULL || file == NULL) { + return NULL; + } + + if (g_key_file_has_group(db->bookmarks, file) == FALSE) { + return NULL; + } + + girara_list_t* result = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare, + (girara_free_function_t) zathura_bookmark_free); + if (result == NULL) { + return NULL; + } + + girara_list_set_free_function(result, (girara_free_function_t) zathura_bookmark_free); + + gsize length; + char** keys = g_key_file_get_keys(db->bookmarks, file, &length, NULL); + if (keys == NULL) { + girara_list_free(result); + return NULL; + } + + for (gsize i = 0; i < length; i++) { + zathura_bookmark_t* bookmark = g_malloc0(sizeof(zathura_bookmark_t)); + + bookmark->id = g_strdup(keys[i]); + bookmark->page = g_key_file_get_integer(db->bookmarks, file, keys[i], NULL); + + girara_list_append(result, bookmark); + } + + return result; +} + +bool +zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned int + page, int offset, double scale, int rotation) +{ + if (db == NULL || db->history == NULL || file == NULL) { + return false; + } + + char* tmp = g_strdup_printf("%f", scale); + if (tmp == NULL) { + return false; + } + + g_key_file_set_integer(db->history, file, KEY_PAGE, page); + g_key_file_set_integer(db->history, file, KEY_OFFSET, offset); + g_key_file_set_string (db->history, file, KEY_SCALE, tmp); + g_key_file_set_integer(db->history, file, KEY_ROTATE, rotation); + + g_free(tmp); + + zathura_db_write_key_file_to_file(db->history_path, db->history); + + return true; +} + +bool +zathura_db_get_fileinfo(zathura_database_t* db, const char* file, unsigned int* + page, int* offset, double* scale, int* rotation) +{ + if (db == NULL || db->history == NULL || file == NULL || page == NULL || + offset == NULL || scale == NULL || rotation == NULL) { + return false; + } + + if (g_key_file_has_group(db->history, file) == FALSE) { + return false; + } + + *page = g_key_file_get_integer(db->history, file, KEY_PAGE, NULL); + *offset = g_key_file_get_integer(db->history, file, KEY_OFFSET, NULL); + *scale = strtod(g_key_file_get_string(db->history, file, KEY_SCALE, NULL), NULL); + *rotation = g_key_file_get_integer(db->history, file, KEY_ROTATE, NULL); + + return true; +} + +static bool +zathura_db_check_file(const char* path) +{ + if (path == NULL) { + return false; + } + + if (g_file_test(path, G_FILE_TEST_EXISTS) == false) { + FILE* file = fopen(path, "w"); + if (file != NULL) { + fclose(file); + } else { + return false; + } + } else if (g_file_test(path, G_FILE_TEST_IS_REGULAR) == false) { + return false; + } + + return true; +} + +static GKeyFile* +zathura_db_read_key_file_from_file(const char* path) +{ + if (path == NULL) { + return NULL; + } + + /* open file */ + FILE* file = fopen(path, "r"); + if (file == NULL) { + return NULL; + } + + GKeyFile* key_file = g_key_file_new(); + if (key_file == NULL) { + fclose(file); + return NULL; + } + + /* read config file */ + file_lock_set(fileno(file), F_WRLCK); + char* content = girara_file_read2(file); + file_lock_set(fileno(file), F_UNLCK); + fclose(file); + if (content == NULL) { + g_key_file_free(key_file); + return NULL; + } + + /* parse config file */ + size_t contentlen = strlen(content); + if (contentlen == 0) { + static const char dummy_content[] = "# nothing"; + static const size_t dummy_len = sizeof(dummy_content) - 1; + + free(content); + content = malloc(sizeof(char) * (dummy_len + 1)); + content = memcpy(content, dummy_content, dummy_len + 1); + contentlen = dummy_len; + } + + GError* error = NULL; + if (g_key_file_load_from_data(key_file, content, contentlen, + G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &error) == + FALSE) { + if (error->code != 1) /* ignore empty file */ { + free(content); + g_key_file_free(key_file); + g_error_free(error); + return NULL; + } + + g_error_free(error); + } + + free(content); + + return key_file; +} + +static void +zathura_db_write_key_file_to_file(const char* file, GKeyFile* key_file) +{ + if (file == NULL || key_file == NULL) { + return; + } + + gchar* content = g_key_file_to_data(key_file, NULL, NULL); + if (content == NULL) { + return; + } + + /* open file */ + int fd = open(file, O_RDWR); + if (fd == -1) { + g_free(content); + return; + } + + file_lock_set(fd, F_WRLCK); + if (write(fd, content, strlen(content)) == 0) { + girara_error("Failed to write to %s", file); + } + file_lock_set(fd, F_UNLCK); + + close(fd); + + g_free(content); +} + +static void +cb_zathura_db_watch_file(GFileMonitor* UNUSED(monitor), GFile* file, GFile* UNUSED(other_file), + GFileMonitorEvent event, zathura_database_t* database) +{ + if (event != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT || database == NULL) { + return; + } + + char* path = g_file_get_path(file); + if (path == NULL) { + return; + } + + if (database->bookmark_path && strcmp(database->bookmark_path, path) == 0) { + database->bookmarks = zathura_db_read_key_file_from_file(database->bookmark_path); + } else if (database->history_path && strcmp(database->history_path, path) == 0) { + database->history = zathura_db_read_key_file_from_file(database->history_path); + } +} diff --git a/database-sqlite.c b/database-sqlite.c index 1cad534..3cd8e3b 100644 --- a/database-sqlite.c +++ b/database-sqlite.c @@ -2,18 +2,30 @@ #include #include -#include +#include +#include #include "database.h" +#define DATABASE "bookmarks.sqlite" + struct zathura_database_s { sqlite3* session; }; zathura_database_t* -zathura_db_init(const char* path) +zathura_db_init(const char* dir) { + if (dir == NULL) { + return NULL; + } + + char* path = g_build_filename(dir, DATABASE, NULL); + if (path == NULL) { + return NULL; + } + zathura_database_t* db = g_malloc0(sizeof(zathura_database_t)); /* create bookmarks database */ @@ -29,27 +41,32 @@ zathura_db_init(const char* path) "file TEXT PRIMARY KEY," "page INTEGER," "offset INTEGER," - "scale FLOAT);"; + "scale FLOAT," + "rotation INTEGER);"; if (sqlite3_open(path, &(db->session)) != SQLITE_OK) { girara_error("Could not open database: %s\n", path); - zathura_db_free(db); - return NULL; + goto error_free; } if (sqlite3_exec(db->session, SQL_BOOKMARK_INIT, NULL, 0, NULL) != SQLITE_OK) { girara_error("Failed to initialize database: %s\n", path); - zathura_db_free(db); - return NULL; + goto error_free; } if (sqlite3_exec(db->session, SQL_FILEINFO_INIT, NULL, 0, NULL) != SQLITE_OK) { girara_error("Failed to initialize database: %s\n", path); - zathura_db_free(db); - return NULL; + goto error_free; } return db; + +error_free: + + zathura_db_free(db); + g_free(path); + + return NULL; } void @@ -59,7 +76,10 @@ zathura_db_free(zathura_database_t* db) return; } - sqlite3_close(db->session); + if (db->session != NULL) { + sqlite3_close(db->session); + } + g_free(db); } @@ -73,7 +93,7 @@ prepare_statement(sqlite3* session, const char* statement) const char* pz_tail = NULL; sqlite3_stmt* pp_stmt = NULL; - if (sqlite3_prepare(session, statement, -1, &pp_stmt, &pz_tail) != SQLITE_OK) { + if (sqlite3_prepare_v2(session, statement, -1, &pp_stmt, &pz_tail) != SQLITE_OK) { girara_error("Failed to prepare query: %s", statement); sqlite3_finalize(pp_stmt); return NULL; @@ -87,7 +107,8 @@ prepare_statement(sqlite3* session, const char* statement) } bool -zathura_db_add_bookmark(zathura_database_t* db, const char* file, zathura_bookmark_t* bookmark) +zathura_db_add_bookmark(zathura_database_t* db, const char* file, + zathura_bookmark_t* bookmark) { g_return_val_if_fail(db && file && bookmark, false); @@ -109,16 +130,17 @@ zathura_db_add_bookmark(zathura_database_t* db, const char* file, zathura_bookma int res = sqlite3_step(stmt); sqlite3_finalize(stmt); - return res == SQLITE_OK; + return res == SQLITE_DONE; } bool -zathura_db_remove_bookmark(zathura_database_t* db, const char* file, const char* id) +zathura_db_remove_bookmark(zathura_database_t* db, const char* file, const char* + id) { g_return_val_if_fail(db && file && id, false); static const char SQL_BOOKMARK_ADD[] = - "DELETE FROM bookmarks WHERE file = ? && id = ?;"; + "DELETE FROM bookmarks WHERE file = ? AND id = ?;"; sqlite3_stmt* stmt = prepare_statement(db->session, SQL_BOOKMARK_ADD); if (stmt == NULL) { @@ -134,7 +156,7 @@ zathura_db_remove_bookmark(zathura_database_t* db, const char* file, const char* int res = sqlite3_step(stmt); sqlite3_finalize(stmt); - return res == SQLITE_OK; + return res == SQLITE_DONE; } girara_list_t* @@ -156,16 +178,17 @@ zathura_db_load_bookmarks(zathura_database_t* db, const char* file) return NULL; } - girara_list_t* result = girara_list_new(); + girara_list_t* result = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare, + (girara_free_function_t) zathura_bookmark_free); if (result == NULL) { sqlite3_finalize(stmt); return NULL; } - girara_list_set_free_function(result, (girara_free_function_t) zathura_bookmark_free); while (sqlite3_step(stmt) == SQLITE_ROW) { zathura_bookmark_t* bookmark = g_malloc0(sizeof(zathura_bookmark_t)); - bookmark->id = g_strdup((const char*) sqlite3_column_text(stmt, 0)); + + bookmark->id = g_strdup((const char*) sqlite3_column_text(stmt, 0)); bookmark->page = sqlite3_column_int(stmt, 1); girara_list_append(result, bookmark); @@ -175,12 +198,13 @@ zathura_db_load_bookmarks(zathura_database_t* db, const char* file) } bool -zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned int page, int offset, float scale) +zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned int + page, int offset, double scale, int rotation) { g_return_val_if_fail(db && file, false); static const char SQL_FILEINFO_SET[] = - "REPLACE INTO fileinfo (file, page, offset, scale) VALUES (?, ?, ?);"; + "REPLACE INTO fileinfo (file, page, offset, scale, rotation) VALUES (?, ?, ?, ?, ?);"; sqlite3_stmt* stmt = prepare_statement(db->session, SQL_FILEINFO_SET); if (stmt == NULL) { @@ -190,7 +214,8 @@ zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned int p if (sqlite3_bind_text(stmt, 1, file, -1, NULL) != SQLITE_OK || sqlite3_bind_int(stmt, 2, page) != SQLITE_OK || sqlite3_bind_int(stmt, 3, offset) != SQLITE_OK || - sqlite3_bind_double(stmt, 4, scale) != SQLITE_OK) { + sqlite3_bind_double(stmt, 4, scale) != SQLITE_OK || + sqlite3_bind_int(stmt, 5, rotation) != SQLITE_OK) { sqlite3_finalize(stmt); girara_error("Failed to bind arguments."); return false; @@ -198,16 +223,17 @@ zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned int p int res = sqlite3_step(stmt); sqlite3_finalize(stmt); - return res == SQLITE_OK; + return res == SQLITE_DONE; } bool -zathura_db_get_fileinfo(zathura_database_t* db, const char* file, unsigned int* page, int* offset, float* scale) +zathura_db_get_fileinfo(zathura_database_t* db, const char* file, unsigned int* + page, int* offset, double* scale, int* rotation) { - g_return_val_if_fail(db && file && page && offset && scale, false); + g_return_val_if_fail(db && file && page && offset && scale && rotation, false); static const char SQL_FILEINFO_GET[] = - "SELECT page, offset, scale FROM fileinfo WHERE file = ?;"; + "SELECT page, offset, scale, rotation FROM fileinfo WHERE file = ?;"; sqlite3_stmt* stmt = prepare_statement(db->session, SQL_FILEINFO_GET); if (stmt == NULL) { @@ -229,6 +255,8 @@ zathura_db_get_fileinfo(zathura_database_t* db, const char* file, unsigned int* *page = sqlite3_column_int(stmt, 0); *offset = sqlite3_column_int(stmt, 1); *scale = sqlite3_column_double(stmt, 2); + *rotation = sqlite3_column_int(stmt, 3); sqlite3_finalize(stmt); return true; } + diff --git a/database.h b/database.h index 1659662..44172e5 100644 --- a/database.h +++ b/database.h @@ -4,69 +4,83 @@ #define DATABASE_H #include -#include +#include #include "zathura.h" #include "bookmarks.h" /** * Initialize database system. - * @param path Path to the database file. + * + * @param dir Path to the directory where the database file should be located. * @return A valid zathura_database_t instance or NULL on failure */ -zathura_database_t* zathura_db_init(const char* path); +zathura_database_t* zathura_db_init(const char* dir); /** * Free database instance. - * @param The database instance to free. + * + * @param db The database instance to free. */ void zathura_db_free(zathura_database_t* db); /** * Add or update bookmark in the database. + * * @param db The database instance * @param file The file to which the bookmark belongs to. * @param bookmark The bookmark instance. * @return true on success, false otherwise */ -bool zathura_db_add_bookmark(zathura_database_t* db, const char* file, zathura_bookmark_t* bookmark); +bool zathura_db_add_bookmark(zathura_database_t* db, const char* file, + zathura_bookmark_t* bookmark); /** * Add or update bookmark in the database. + * * @param db The database instance * @param file The file to which the bookmark belongs to. - * @param bookmark The bookmark instance. + * @param id The id of the bookmark * @return true on success, false otherwise */ -bool zathura_db_remove_bookmark(zathura_database_t* db, const char* file, const char* id); +bool zathura_db_remove_bookmark(zathura_database_t* db, const char* file, const + char* id); /** * Loads all bookmarks from the database belonging to a specific file. + * * @param db The database instance. * @param file The file for which the bookmarks should be loaded. * @return List of zathura_bookmark_t* or NULL on failure. */ -girara_list_t* zathura_db_load_bookmarks(zathura_database_t* db, const char* file); +girara_list_t* zathura_db_load_bookmarks(zathura_database_t* db, const char* + file); /** * Set file info (last site, ...) in the database. + * * @param db The database instance * @param file The file to which the file info belongs to. * @param page The last page. * @param offset The last offset. * @param scale The last scale. + * @param rotation The last rotation. * @return true on success, false otherwise. */ -bool zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned int page, int offset, float scale); +bool zathura_db_set_fileinfo(zathura_database_t* db, const char* file, unsigned + int page, int offset, double scale, int rotation); /* Get file info (last site, ...) from the database. + * * @param db The database instance * @param file The file to which the file info belongs to. * @param page The last page. * @param offset The last offset. * @param scale The last scale. + * @param rotation The rotation. * @return true on success, false otherwise. */ -bool zathura_db_get_fileinfo(zathura_database_t* db, const char* file, unsigned int* page, int* offset, float* scale); +bool zathura_db_get_fileinfo(zathura_database_t* db, const char* file, unsigned + int* page, int* offset, double* scale, int* rotation); #endif // DATABASE_H diff --git a/document.c b/document.c index b7955fe..dcab1a5 100644 --- a/document.c +++ b/document.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -15,36 +16,46 @@ #include #include #include +#include #include "document.h" #include "utils.h" #include "zathura.h" #include "render.h" +#include "database.h" +#include "page_widget.h" -#define LENGTH(x) (sizeof(x)/sizeof((x)[0])) +#include +#include +#include +#include +#include + +/** + * Register document plugin + */ +static bool zathura_document_plugin_register(zathura_t* zathura, zathura_document_plugin_t* new_plugin); void zathura_document_plugins_load(zathura_t* zathura) { - girara_list_iterator_t* iter = girara_list_iterator(zathura->plugins.path); - if (iter == NULL) { - return; - } - - do - { - char* plugindir = girara_list_iterator_data(iter); - + GIRARA_LIST_FOREACH(zathura->plugins.path, char*, iter, plugindir) + /* TODO: rewrite with GDir */ /* read all files in the plugin directory */ DIR* dir = opendir(plugindir); if (dir == NULL) { girara_error("could not open plugin directory: %s", plugindir); + girara_list_iterator_next(iter); continue; } int fddir = dirfd(dir); struct dirent* entry; while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + struct stat statbuf; if (fstatat(fddir, entry->d_name, &statbuf, 0) != 0) { girara_error("failed to fstatat %s/%s; errno is %d.", plugindir, entry->d_name, errno); @@ -87,27 +98,18 @@ zathura_document_plugins_load(zathura_t* zathura) continue; } - plugin = malloc(sizeof(zathura_document_plugin_t)); - - if (plugin == NULL) { - g_error("failed to allocate memory!"); - break; - } - - plugin->open_function = NULL; - plugin->content_types = girara_list_new(); - girara_list_set_free_function(plugin->content_types, g_free); + plugin = g_malloc0(sizeof(zathura_document_plugin_t)); + plugin->content_types = girara_list_new2(g_free); + plugin->handle = handle; register_plugin(plugin); - bool r = zathura_document_plugin_register(zathura, plugin, handle); + bool r = zathura_document_plugin_register(zathura, plugin); if (r == false) { girara_error("could not register plugin %s", path); - free(plugin); - dlclose(handle); - } - else { + zathura_document_plugin_free(plugin); + } else { girara_info("successfully loaded plugin %s", path); } @@ -117,8 +119,7 @@ zathura_document_plugins_load(zathura_t* zathura) if (closedir(dir) == -1) { girara_error("could not close plugin directory %s", plugindir); } - } while (girara_list_iterator_next(iter)); - girara_list_iterator_free(iter); + GIRARA_LIST_FOREACH_END(zathura->plugins.path, char*, iter, plugindir); } void @@ -128,15 +129,15 @@ zathura_document_plugin_free(zathura_document_plugin_t* plugin) return; } + dlclose(plugin->handle); girara_list_free(plugin->content_types); - free(plugin); + g_free(plugin); } -bool -zathura_document_plugin_register(zathura_t* zathura, zathura_document_plugin_t* new_plugin, void* handle) +static bool +zathura_document_plugin_register(zathura_t* zathura, zathura_document_plugin_t* new_plugin) { - if( (new_plugin == NULL) || (new_plugin->content_types == NULL) || (new_plugin->open_function == NULL) - || (handle == NULL) ) { + if (new_plugin == NULL || new_plugin->content_types == NULL || new_plugin->open_function == NULL) { girara_error("plugin: could not register\n"); return false; } @@ -145,9 +146,10 @@ zathura_document_plugin_register(zathura_t* zathura, zathura_document_plugin_t* GIRARA_LIST_FOREACH(new_plugin->content_types, gchar*, iter, type) if (!zathura_type_plugin_mapping_new(zathura, type, new_plugin)) { girara_error("plugin: already registered for filetype %s\n", type); + } else { + atleastone = true; } - atleastone = true; - GIRARA_LIST_FOREACH_END(new_plugin->content_types, gchar*, iter, type) + GIRARA_LIST_FOREACH_END(new_plugin->content_types, gchar*, iter, type); if (atleastone) { girara_list_append(zathura->plugins.plugins, new_plugin); @@ -155,6 +157,47 @@ zathura_document_plugin_register(zathura_t* zathura, zathura_document_plugin_t* return atleastone; } +static const gchar* +guess_type(const char* path) +{ + gboolean uncertain; + const gchar* content_type = g_content_type_guess(path, NULL, 0, &uncertain); + if (content_type == NULL) { + return NULL; + } + + FILE* f = fopen(path, "r"); + if (f == NULL) { + return NULL; + } + + const int fd = fileno(f); + guchar* content = NULL; + size_t length = 0u; + while (uncertain == TRUE) { + g_free((void*)content_type); + content_type = NULL; + + content = g_realloc(content, length + BUFSIZ); + const ssize_t r = read(fd, content + length, BUFSIZ); + if (r == -1) { + break; + } + + length += r; + content_type = g_content_type_guess(NULL, content, length, &uncertain); + } + + fclose(f); + if (uncertain == TRUE) { + g_free((void*)content_type); + content_type = NULL; + } + + g_free(content); + return content_type; +} + zathura_document_t* zathura_document_open(zathura_t* zathura, const char* path, const char* password) { @@ -163,18 +206,20 @@ zathura_document_open(zathura_t* zathura, const char* path, const char* password } if (file_exists(path) == false) { - girara_error("File does not exist"); + girara_error("File '%s' does not exist", path); return NULL; } - const gchar* content_type = g_content_type_guess(path, NULL, 0, NULL); + const gchar* content_type = guess_type(path); if (content_type == NULL) { - girara_error("Could not determine file type"); + girara_error("Could not determine file type."); return NULL; } + char* file_uri = NULL; + /* determine real path */ - size_t path_max; + long path_max; #ifdef PATH_MAX path_max = PATH_MAX; #else @@ -204,52 +249,118 @@ zathura_document_open(zathura_t* zathura, const char* path, const char* password plugin = mapping->plugin; break; } - GIRARA_LIST_FOREACH_END(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_t*, iter, mapping) + GIRARA_LIST_FOREACH_END(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_t*, iter, mapping); g_free((void*)content_type); if (plugin == NULL) { girara_error("unknown file type\n"); - free(real_path); + goto error_free; } document = g_malloc0(sizeof(zathura_document_t)); - if (document == NULL) { - goto error_free; - } document->file_path = real_path; document->password = password; document->scale = 1.0; document->zathura = zathura; - if (plugin->open_function != NULL) { - if (plugin->open_function(document) == true) { - /* update statusbar */ - girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, real_path); - - /* read all pages */ - document->pages = calloc(document->number_of_pages, sizeof(zathura_page_t*)); - if (document->pages == NULL) { - goto error_free; - } - - for (unsigned int page_id = 0; page_id < document->number_of_pages; page_id++) { - zathura_page_t* page = zathura_page_get(document, page_id); - if (page == NULL) { - goto error_free; - } - - document->pages[page_id] = page; - } - - return document; - } + /* open document */ + if (plugin->open_function == NULL) { + girara_error("plugin has no open function\n"); + goto error_free; } - girara_error("could not open file\n"); + zathura_plugin_error_t error = plugin->open_function(document); + if (error != ZATHURA_PLUGIN_ERROR_OK) { + if (error == ZATHURA_PLUGIN_ERROR_INVALID_PASSWORD) { + zathura_password_dialog_info_t* password_dialog_info = malloc(sizeof(zathura_password_dialog_info_t)); + if (password_dialog_info != NULL) { + password_dialog_info->path = g_strdup(path); + password_dialog_info->zathura = zathura; + + if (path != NULL) { + girara_dialog(zathura->ui.session, "Enter password:", true, NULL, + (girara_callback_inputbar_activate_t) cb_password_dialog, password_dialog_info); + goto error_free; + } else { + free(password_dialog_info); + } + } + goto error_free; + } + + girara_error("could not open document\n"); + goto error_free; + } + + /* read history file */ + int offset = 0; + zathura_db_get_fileinfo(zathura->database, document->file_path, + &document->current_page_number, &offset, &document->scale, &document->rotate); + + /* check for valid scale value */ + if (document->scale <= FLT_EPSILON) { + girara_warning("document info: '%s' has non positive scale", document->file_path); + document->scale = 1; + } + + /* check current page number */ + if (document->current_page_number > document->number_of_pages) { + girara_warning("document info: '%s' has an invalid page number", document->file_path); + document->current_page_number = 0; + } + + /* update statusbar */ + girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, real_path); + + /* read all pages */ + document->pages = calloc(document->number_of_pages, sizeof(zathura_page_t*)); + if (document->pages == NULL) { + goto error_free; + } + + for (unsigned int page_id = 0; page_id < document->number_of_pages; page_id++) { + zathura_page_t* page = zathura_page_get(document, page_id, NULL); + if (page == NULL) { + goto error_free; + } + + document->pages[page_id] = page; + } + + /* install file monitor */ + file_uri = g_filename_to_uri(real_path, NULL, NULL); + if (file_uri == NULL) { + goto error_free; + } + + document->file_monitor.file = g_file_new_for_uri(file_uri); + if (document->file_monitor.file == NULL) { + goto error_free; + } + + document->file_monitor.monitor = g_file_monitor_file(document->file_monitor.file, G_FILE_MONITOR_NONE, NULL, NULL); + if (document->file_monitor.monitor == NULL) { + goto error_free; + } + + g_signal_connect(G_OBJECT(document->file_monitor.monitor), "changed", G_CALLBACK(cb_file_monitor), zathura->ui.session); + + + /* apply open adjustment */ + int adjust_open = ADJUST_BESTFIT; + girara_setting_get(zathura->ui.session, "adjust-open", &adjust_open); + + g_free(file_uri); + + return document; error_free: + if (file_uri != NULL) { + g_free(file_uri); + } + free(real_path); if (document != NULL && document->pages != NULL) { @@ -264,7 +375,7 @@ error_free: return NULL; } -bool +zathura_plugin_error_t zathura_document_free(zathura_document_t* document) { if (document == NULL) { @@ -280,19 +391,13 @@ zathura_document_free(zathura_document_t* document) free(document->pages); /* free document */ + bool r = true; if (document->functions.document_free == NULL) { girara_error("%s not implemented", __FUNCTION__); - - if (document->file_path != NULL) { - free(document->file_path); - } - - g_free(document); - return true; + } else { + r = document->functions.document_free(document); } - bool r = document->functions.document_free(document); - if (document->file_path != NULL) { free(document->file_path); } @@ -302,7 +407,7 @@ zathura_document_free(zathura_document_t* document) return r; } -bool +zathura_plugin_error_t zathura_document_save_as(zathura_document_t* document, const char* path) { if (document == NULL || path == NULL) { @@ -318,7 +423,7 @@ zathura_document_save_as(zathura_document_t* document, const char* path) } girara_tree_node_t* -zathura_document_index_generate(zathura_document_t* document) +zathura_document_index_generate(zathura_document_t* document, zathura_plugin_error_t* error) { if (document == NULL) { return NULL; @@ -329,11 +434,11 @@ zathura_document_index_generate(zathura_document_t* document) return NULL; } - return document->functions.document_index_generate(document); + return document->functions.document_index_generate(document, error); } -zathura_list_t* -zathura_document_attachments_get(zathura_document_t* document) +girara_list_t* +zathura_document_attachments_get(zathura_document_t* document, zathura_plugin_error_t* error) { if (document == NULL) { return NULL; @@ -344,17 +449,45 @@ zathura_document_attachments_get(zathura_document_t* document) return NULL; } - return document->functions.document_attachments_get(document); + return document->functions.document_attachments_get(document, error); } -bool -zathura_document_attachments_free(zathura_list_t* UNUSED(list)) +zathura_plugin_error_t +zathura_document_attachment_save(zathura_document_t* document, const char* attachment, const char* file) { - return false; + if (document == NULL) { + return ZATHURA_PLUGIN_ERROR_INVALID_ARGUMENTS; + } + + if (document->functions.document_attachment_save == NULL) { + return ZATHURA_PLUGIN_ERROR_NOT_IMPLEMENTED; + } + + return document->functions.document_attachment_save(document, attachment, file); +} + +char* +zathura_document_meta_get(zathura_document_t* document, zathura_document_meta_t meta, zathura_plugin_error_t* error) +{ + if (document == NULL) { + if (error != NULL) { + *error = ZATHURA_PLUGIN_ERROR_NOT_IMPLEMENTED; + } + return NULL; + } + + if (document->functions.document_meta_get == NULL) { + if (error != NULL) { + *error = ZATHURA_PLUGIN_ERROR_NOT_IMPLEMENTED; + } + return NULL; + } + + return document->functions.document_meta_get(document, meta, error); } zathura_page_t* -zathura_page_get(zathura_document_t* document, unsigned int page_id) +zathura_page_get(zathura_document_t* document, unsigned int page_id, zathura_plugin_error_t* error) { if (document == NULL) { return NULL; @@ -365,27 +498,25 @@ zathura_page_get(zathura_document_t* document, unsigned int page_id) return NULL; } - zathura_page_t* page = document->functions.page_get(document, page_id); + zathura_page_t* page = document->functions.page_get(document, page_id, error); if (page != NULL) { page->number = page_id; page->visible = false; - page->event_box = gtk_event_box_new(); - page->drawing_area = gtk_drawing_area_new(); - page->surface = NULL; + page->drawing_area = zathura_page_widget_new(page); page->document = document; - g_signal_connect(page->drawing_area, "expose-event", G_CALLBACK(page_expose_event), page); - gtk_widget_set_size_request(page->drawing_area, page->width * document->scale, page->height * document->scale); - gtk_container_add(GTK_CONTAINER(page->event_box), page->drawing_area); + unsigned int page_height = 0; + unsigned int page_width = 0; + page_calc_height_width(page, &page_height, &page_width, true); - g_static_mutex_init(&(page->lock)); + gtk_widget_set_size_request(page->drawing_area, page_width, page_height); } return page; } -bool +zathura_plugin_error_t zathura_page_free(zathura_page_t* page) { if (page == NULL || page->document == NULL) { @@ -397,13 +528,11 @@ zathura_page_free(zathura_page_t* page) return false; } - g_static_mutex_free(&(page->lock)); - return page->document->functions.page_free(page); } -zathura_list_t* -zathura_page_search_text(zathura_page_t* page, const char* text) +girara_list_t* +zathura_page_search_text(zathura_page_t* page, const char* text, zathura_plugin_error_t* error) { if (page == NULL || page->document == NULL || text == NULL) { return NULL; @@ -414,11 +543,11 @@ zathura_page_search_text(zathura_page_t* page, const char* text) return NULL; } - return page->document->functions.page_search_text(page, text); + return page->document->functions.page_search_text(page, text, error); } -zathura_list_t* -zathura_page_links_get(zathura_page_t* page) +girara_list_t* +zathura_page_links_get(zathura_page_t* page, zathura_plugin_error_t* error) { if (page == NULL || page->document == NULL) { return NULL; @@ -429,17 +558,17 @@ zathura_page_links_get(zathura_page_t* page) return NULL; } - return page->document->functions.page_links_get(page); + return page->document->functions.page_links_get(page, error); } -bool -zathura_page_links_free(zathura_list_t* UNUSED(list)) +zathura_plugin_error_t +zathura_page_links_free(girara_list_t* UNUSED(list)) { return false; } -zathura_list_t* -zathura_page_form_fields_get(zathura_page_t* page) +girara_list_t* +zathura_page_form_fields_get(zathura_page_t* page, zathura_plugin_error_t* error) { if (page == NULL || page->document == NULL) { return NULL; @@ -450,28 +579,78 @@ zathura_page_form_fields_get(zathura_page_t* page) return NULL; } - return page->document->functions.page_form_fields_get(page); + return page->document->functions.page_form_fields_get(page, error); } -bool -zathura_page_form_fields_free(zathura_list_t* UNUSED(list)) +zathura_plugin_error_t +zathura_page_form_fields_free(girara_list_t* UNUSED(list)) { return false; } -bool -zathura_page_render(zathura_page_t* page, cairo_t* cairo) +girara_list_t* +zathura_page_images_get(zathura_page_t* page, zathura_plugin_error_t* error) +{ + if (page == NULL || page->document == NULL) { + return NULL; + } + + if (page->document->functions.page_images_get == NULL) { + girara_error("%s not implemented", __FUNCTION__); + return false; + } + + return page->document->functions.page_images_get(page, error); +} + +zathura_plugin_error_t +zathura_page_image_save(zathura_page_t* page, zathura_image_t* image, const char* file) +{ + if (page == NULL || page->document == NULL || image == NULL || file == NULL) { + return false; + } + + if (page->document->functions.page_image_save == NULL) { + girara_error("%s not implemented", __FUNCTION__); + return false; + } + + return page->document->functions.page_image_save(page, image, file); +} + +char* zathura_page_get_text(zathura_page_t* page, zathura_rectangle_t rectangle, zathura_plugin_error_t* error) +{ + if (page == NULL || page->document == NULL) { + if (error) { + *error = ZATHURA_PLUGIN_ERROR_INVALID_ARGUMENTS; + } + return NULL; + } + + if (page->document->functions.page_get_text == NULL) { + girara_error("%s not implemented", __FUNCTION__); + if (error) { + *error = ZATHURA_PLUGIN_ERROR_NOT_IMPLEMENTED; + } + return NULL; + } + + return page->document->functions.page_get_text(page, rectangle, error); +} + +zathura_plugin_error_t +zathura_page_render(zathura_page_t* page, cairo_t* cairo, bool printing) { if (page == NULL || page->document == NULL || cairo == NULL) { - return NULL; + return ZATHURA_PLUGIN_ERROR_INVALID_ARGUMENTS; } if (page->document->functions.page_render_cairo == NULL) { girara_error("%s not implemented", __FUNCTION__); - return NULL; + return ZATHURA_PLUGIN_ERROR_NOT_IMPLEMENTED; } - return page->document->functions.page_render_cairo(page, cairo); + return page->document->functions.page_render_cairo(page, cairo, printing); } zathura_index_element_t* @@ -483,10 +662,6 @@ zathura_index_element_new(const char* title) zathura_index_element_t* res = g_malloc0(sizeof(zathura_index_element_t)); - if (res == NULL) { - return NULL; - } - res->title = g_strdup(title); return res; @@ -552,7 +727,7 @@ zathura_type_plugin_mapping_new(zathura_t* zathura, const gchar* type, zathura_d girara_list_iterator_free(iter); return false; } - GIRARA_LIST_FOREACH_END(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_t*, iter, mapping) + GIRARA_LIST_FOREACH_END(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_t*, iter, mapping); zathura_type_plugin_mapping_t* mapping = g_malloc(sizeof(zathura_type_plugin_mapping_t)); mapping->type = g_strdup(type); @@ -571,3 +746,16 @@ zathura_type_plugin_mapping_free(zathura_type_plugin_mapping_t* mapping) g_free((void*)mapping->type); g_free(mapping); } + +void +zathura_link_free(zathura_link_t* link) +{ + if (link == NULL) { + return; + } + + if (link->type == ZATHURA_LINK_EXTERNAL) { + g_free(link->target.value); + } + g_free(link); +} diff --git a/document.h b/document.h index 181199f..e0aac6f 100644 --- a/document.h +++ b/document.h @@ -7,32 +7,72 @@ #include #include -#include +#include #include "zathura.h" #define PLUGIN_REGISTER_FUNCTION "plugin_register" -typedef struct zathura_list_s zathura_list_t; -// typedef struct zathura_document_s zathura_document_t; +/** + * Error types for plugins + */ +typedef enum zathura_plugin_error_e +{ + ZATHURA_PLUGIN_ERROR_OK, /**< No error occured */ + ZATHURA_PLUGIN_ERROR_UNKNOWN, /**< An unknown error occured */ + ZATHURA_PLUGIN_ERROR_OUT_OF_MEMORY, /**< Out of memory */ + ZATHURA_PLUGIN_ERROR_NOT_IMPLEMENTED, /**< The called function has not been implemented */ + ZATHURA_PLUGIN_ERROR_INVALID_ARGUMENTS, /**< Invalid arguments have been passed */ + ZATHURA_PLUGIN_ERROR_INVALID_PASSWORD /**< The provided password is invalid */ +} zathura_plugin_error_t; -typedef bool (*zathura_document_open_t)(zathura_document_t* document); +/** + * Document open function + * + * @param document The document + * @return true if no error occured otherwise false + */ +typedef zathura_plugin_error_t (*zathura_document_open_t)(zathura_document_t* document); /** * Document plugin structure */ typedef struct zathura_document_plugin_s { - girara_list_t* content_types; /**> List of supported content types */ - zathura_document_open_t open_function; /**> Document open function */ - void* handle; /**> DLL handle */ + girara_list_t* content_types; /**< List of supported content types */ + zathura_document_open_t open_function; /**< Document open function */ + void* handle; /**< DLL handle */ } zathura_document_plugin_t; +/** + * Plugin mapping + */ typedef struct zathura_type_plugin_mapping_s { - const gchar* type; - zathura_document_plugin_t* plugin; + const gchar* type; /**< Plugin type */ + zathura_document_plugin_t* plugin; /**< Mapped plugin */ } zathura_type_plugin_mapping_t; +/** + * Meta data entries + */ +typedef enum zathura_document_meta_e +{ + ZATHURA_DOCUMENT_TITLE, /**< Title of the document */ + ZATHURA_DOCUMENT_AUTHOR, /**< Author of the document */ + ZATHURA_DOCUMENT_SUBJECT, /**< Subject of the document */ + ZATHURA_DOCUMENT_KEYWORDS, /**< Keywords of the document */ + ZATHURA_DOCUMENT_CREATOR, /**< Creator of the document */ + ZATHURA_DOCUMENT_PRODUCER, /**< Producer of the document */ + ZATHURA_DOCUMENT_CREATION_DATE, /**< Creation data */ + ZATHURA_DOCUMENT_MODIFICATION_DATE /**< Modification data */ +} zathura_document_meta_t; + +typedef struct zathura_password_dialog_info_s +{ + char* path; /**< Path to the file */ + zathura_t* zathura; /**< Zathura session */ +} zathura_password_dialog_info_t; + /** * Function prototype that is called to register a document plugin * @@ -40,24 +80,15 @@ typedef struct zathura_type_plugin_mapping_s */ typedef void (*zathura_plugin_register_service_t)(zathura_document_plugin_t*); -/** - * Structure for a list - */ -struct zathura_list_s -{ - void* data; /**> Data value */ - struct zathura_list_s* next; /**> Next element in the list */ -}; - /** * Image buffer */ typedef struct zathura_image_buffer_s { - unsigned char* data; /**> Image buffer data */ - unsigned int height; /**> Height of the image */ - unsigned int width; /**> Width of the image */ - unsigned int rowstride; /**> Rowstride of the image */ + unsigned char* data; /**< Image buffer data */ + unsigned int height; /**< Height of the image */ + unsigned int width; /**< Width of the image */ + unsigned int rowstride; /**< Rowstride of the image */ } zathura_image_buffer_t; /** @@ -72,28 +103,39 @@ zathura_image_buffer_t* zathura_image_buffer_create(unsigned int width, unsigned /** * Frees the image buffer * - * @param zathura_image_buffer_t + * @param buffer The image buffer */ -void zathura_image_buffer_free(zathura_image_buffer_t*); +void zathura_image_buffer_free(zathura_image_buffer_t* buffer); /** - * Rectangle structure + * Rectangle structure. + * The coordinate system has its origin in the left upper corner. The x axes + * goes to the right, the y access goes down. */ typedef struct zathura_rectangle_s { - double x1; /**> X coordinate of point 1 */ - double y1; /**> Y coordinate of point 1 */ - double x2; /**> X coordinate of point 2 */ - double y2; /**> Y coordinate of point 2 */ + double x1; /**< X coordinate of point 1 */ + double y1; /**< Y coordinate of point 1 */ + double x2; /**< X coordinate of point 2 */ + double y2; /**< Y coordinate of point 2 */ } zathura_rectangle_t; +/** + * Image structure + */ +typedef struct zathura_image_s +{ + zathura_rectangle_t position; /**< Coordinates of the image */ + void* data; /**< Custom data of the plugin */ +} zathura_image_t; + /** * Possible link types */ typedef enum zathura_link_type_e { - ZATHURA_LINK_TO_PAGE, /**> Links to a page */ - ZATHURA_LINK_EXTERNAL, /**> Links to an external source */ + ZATHURA_LINK_TO_PAGE, /**< Links to a page */ + ZATHURA_LINK_EXTERNAL, /**< Links to an external source */ } zathura_link_type_t; /** @@ -101,12 +143,12 @@ typedef enum zathura_link_type_e */ typedef struct zathura_link_s { - zathura_rectangle_t position; /**> Position of the link */ - zathura_link_type_t type; /**> Link type */ + zathura_rectangle_t position; /**< Position of the link */ + zathura_link_type_t type; /**< Link type */ union { - unsigned int page_number; /**> Page number */ - char* value; /**> Value */ + unsigned int page_number; /**< Page number */ + char* value; /**< Value */ } target; } zathura_link_t; @@ -115,12 +157,12 @@ typedef struct zathura_link_s */ typedef struct zathura_index_element_s { - char* title; /**> Title of the element */ - zathura_link_type_t type; /**> Type */ + char* title; /**< Title of the element */ + zathura_link_type_t type; /**< Type */ union { - unsigned int page_number; /**> Page number */ - char* uri; /**> Uri */ + unsigned int page_number; /**< Page number */ + char* uri; /**< Uri */ } target; } zathura_index_element_t; @@ -129,8 +171,8 @@ typedef struct zathura_index_element_s */ typedef enum zathura_form_type_e { - ZATHURA_FORM_CHECKBOX, /**> Checkbox */ - ZATHURA_FORM_TEXTFIELD /**> Textfield */ + ZATHURA_FORM_CHECKBOX, /**< Checkbox */ + ZATHURA_FORM_TEXTFIELD /**< Textfield */ } zathura_form_type_t; /** @@ -138,8 +180,8 @@ typedef enum zathura_form_type_e */ typedef struct zathura_form_s { - zathura_rectangle_t position; /**> Position */ - zathura_form_type_t type; /**> Type */ + zathura_rectangle_t position; /**< Position */ + zathura_form_type_t type; /**< Type */ } zathura_form_t; /** @@ -147,16 +189,13 @@ typedef struct zathura_form_s */ struct zathura_page_s { - double height; /**> Page height */ - double width; /**> Page width */ - unsigned int number; /**> Page number */ - zathura_document_t* document; /**> Document */ - void* data; /**> Custom data */ - bool visible; /**> Page is visible */ - GtkWidget* event_box; /**> Widget wrapper for mouse events */ - GtkWidget* drawing_area; /**> Drawing area */ - GStaticMutex lock; /**> Lock */ - cairo_surface_t* surface; /** Cairo surface */ + double height; /**< Page height */ + double width; /**< Page width */ + unsigned int number; /**< Page number */ + zathura_document_t* document; /**< Document */ + void* data; /**< Custom data */ + bool visible; /**< Page is visible */ + GtkWidget* drawing_area; /**< Drawing area */ }; /** @@ -164,77 +203,111 @@ struct zathura_page_s */ struct zathura_document_s { - char* file_path; /**> File path of the document */ - const char* password; /**> Password of the document */ - unsigned int current_page_number; /**> Current page number */ - unsigned int number_of_pages; /**> Number of pages */ - double scale; /**> Scale value */ - int rotate; /**> Rotation */ - void* data; /**> Custom data */ + char* file_path; /**< File path of the document */ + const char* password; /**< Password of the document */ + unsigned int current_page_number; /**< Current page number */ + unsigned int number_of_pages; /**< Number of pages */ + double scale; /**< Scale value */ + int rotate; /**< Rotation */ + void* data; /**< Custom data */ zathura_t* zathura; /** Zathura object */ + int adjust_mode; /**< Adjust mode (best-fit, width) */ struct { /** * Frees the document */ - bool (*document_free)(zathura_document_t* document); + zathura_plugin_error_t (*document_free)(zathura_document_t* document); /** * Generates the document index */ - girara_tree_node_t* (*document_index_generate)(zathura_document_t* document); + girara_tree_node_t* (*document_index_generate)(zathura_document_t* document, zathura_plugin_error_t* error); /** * Save the document */ - bool (*document_save_as)(zathura_document_t* document, const char* path); + zathura_plugin_error_t (*document_save_as)(zathura_document_t* document, const char* path); /** * Get list of attachments */ - zathura_list_t* (*document_attachments_get)(zathura_document_t* document); + girara_list_t* (*document_attachments_get)(zathura_document_t* document, zathura_plugin_error_t* error); + + /** + * Save attachment to a file + */ + zathura_plugin_error_t (*document_attachment_save)(zathura_document_t* document, const char* attachment, const char* file); + + /** + * Get document information + */ + char* (*document_meta_get)(zathura_document_t* document, zathura_document_meta_t info, zathura_plugin_error_t* error); /** * Gets the page object */ - zathura_page_t* (*page_get)(zathura_document_t* document, unsigned int page_id); + zathura_page_t* (*page_get)(zathura_document_t* document, unsigned int page_id, zathura_plugin_error_t* error); /** * Search text */ - zathura_list_t* (*page_search_text)(zathura_page_t* page, const char* text); + girara_list_t* (*page_search_text)(zathura_page_t* page, const char* text, zathura_plugin_error_t* error); /** * Get links on a page */ - zathura_list_t* (*page_links_get)(zathura_page_t* page); + girara_list_t* (*page_links_get)(zathura_page_t* page, zathura_plugin_error_t* error); /** * Get form fields */ - zathura_list_t* (*page_form_fields_get)(zathura_page_t* page); + girara_list_t* (*page_form_fields_get)(zathura_page_t* page, zathura_plugin_error_t* error); + + /** + * Get list of images + */ + girara_list_t* (*page_images_get)(zathura_page_t* page, zathura_plugin_error_t* error); + + /** + * Save image to a file + */ + zathura_plugin_error_t (*page_image_save)(zathura_page_t* page, zathura_image_t* image, const char* file); + + /** + * Get text for selection + */ + char* (*page_get_text)(zathura_page_t* page, zathura_rectangle_t rectangle, zathura_plugin_error_t* error); /** * Renders the page */ - zathura_image_buffer_t* (*page_render)(zathura_page_t* page); + zathura_image_buffer_t* (*page_render)(zathura_page_t* page, zathura_plugin_error_t* error); /** * Renders the page */ - bool (*page_render_cairo)(zathura_page_t* page, cairo_t* cairo); + zathura_plugin_error_t (*page_render_cairo)(zathura_page_t* page, cairo_t* cairo, bool printing); /** * Free page */ - bool (*page_free)(zathura_page_t* page); + zathura_plugin_error_t (*page_free)(zathura_page_t* page); } functions; /** * Document pages */ zathura_page_t** pages; + + /** + * File monitor + */ + struct { + GFileMonitor* monitor; /**< File monitor */ + GFile* file; /**< File for file monitor */ + } file_monitor; }; /** @@ -251,128 +324,189 @@ void zathura_document_plugins_load(zathura_t* zathura); */ void zathura_document_plugin_free(zathura_document_plugin_t* plugin); -/** - * Register document plugin - */ -bool zathura_document_plugin_register(zathura_t* zathura, zathura_document_plugin_t* new_plugin, void* handle); - /** * Open the document * + * @param zathura Zathura object * @param path Path to the document * @param password Password of the document or NULL * @return The document object */ -zathura_document_t* zathura_document_open(zathura_t* zathura, const char* path, const char* password); +zathura_document_t* zathura_document_open(zathura_t* zathura, const char* path, + const char* password); /** * Free the document * * @param document - * @return true if no error occured, otherwise false + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_document_free(zathura_document_t* document); +zathura_plugin_error_t zathura_document_free(zathura_document_t* document); /** * Save the document * * @param document The document object * @param path Path for the saved file - * @return true if no error occured, otherwise false + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_document_save_as(zathura_document_t* document, const char* path); +zathura_plugin_error_t zathura_document_save_as(zathura_document_t* document, const char* path); /** * Generate the document index * * @param document The document object + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured * @return Generated index */ -girara_tree_node_t* zathura_document_index_generate(zathura_document_t* document); +girara_tree_node_t* zathura_document_index_generate(zathura_document_t* document, zathura_plugin_error_t* error); /** * Get list of attachments * * @param document The document object + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured * @return List of attachments */ -zathura_list_t* zathura_document_attachments_get(zathura_document_t* document); +girara_list_t* zathura_document_attachments_get(zathura_document_t* document, zathura_plugin_error_t* error); /** - * Free document attachments + * Save document attachment * - * @param list list of document attachments - * @return + * @param document The document objects + * @param attachment name of the attachment + * @param file the target filename + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_document_attachments_free(zathura_list_t* list); +zathura_plugin_error_t zathura_document_attachment_save(zathura_document_t* document, const char* attachment, const char* file); + +/** + * Returns a string of the requested information + * + * @param document The zathura document + * @param meta The information field + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured + * @return String or NULL if information could not be retreived + */ +char* zathura_document_meta_get(zathura_document_t* document, zathura_document_meta_t meta, zathura_plugin_error_t* error); /** * Get the page object * * @param document The document * @param page_id Page number + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured * @return Page object or NULL if an error occured */ -zathura_page_t* zathura_page_get(zathura_document_t* document, unsigned int page_id); +zathura_page_t* zathura_page_get(zathura_document_t* document, unsigned int page_id, zathura_plugin_error_t* error); /** * Frees the page object * * @param page The page object - * @return true if no error occured, otherwise false + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_page_free(zathura_page_t* page); +zathura_plugin_error_t zathura_page_free(zathura_page_t* page); /** * Search page * * @param page The page object * @param text Search item + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured * @return List of results */ -zathura_list_t* zathura_page_search_text(zathura_page_t* page, const char* text); +girara_list_t* zathura_page_search_text(zathura_page_t* page, const char* text, zathura_plugin_error_t* error); /** * Get page links * * @param page The page object + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured * @return List of links */ -zathura_list_t* zathura_page_links_get(zathura_page_t* page); +girara_list_t* zathura_page_links_get(zathura_page_t* page, zathura_plugin_error_t* error); /** * Free page links * * @param list List of links - * @return true if no error occured, otherwise false + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_page_links_free(zathura_list_t* list); +zathura_plugin_error_t zathura_page_links_free(girara_list_t* list); /** * Get list of form fields * * @param page The page object + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured * @return List of form fields */ -zathura_list_t* zathura_page_form_fields_get(zathura_page_t* page); +girara_list_t* zathura_page_form_fields_get(zathura_page_t* page, zathura_plugin_error_t* error); /** * Free list of form fields * * @param list List of form fields - * @return true if no error occured, otherwise false + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_page_form_fields_free(zathura_list_t* list); +zathura_plugin_error_t zathura_page_form_fields_free(girara_list_t* list); + +/** + * Get list of images + * + * @param page Page + * @param error Set to an error value (see \ref zathura_plugin_error_t) if an + * error occured + * @return List of images or NULL if an error occured + */ +girara_list_t* zathura_page_images_get(zathura_page_t* page, zathura_plugin_error_t* error); + +/** + * Save image + * + * @param page Page + * @param image The image + * @param file Path to the file + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t + */ +zathura_plugin_error_t zathura_page_image_save(zathura_page_t* page, zathura_image_t* image, const char* file); + +/** + * Get text for selection + * @param page Page + * @param rectangle Selection + * @error Set to an error value (see \ref zathura_plugin_error_t) if an error + * occured + * @return The selected text (needs to be deallocated with g_free) + */ +char* zathura_page_get_text(zathura_page_t* page, zathura_rectangle_t rectangle, zathura_plugin_error_t* error); /** * Render page * * @param page The page object * @param cairo Cairo object - * @return True if no error occured, otherwise false + * @param printing render for printing + * @return ZATHURA_PLUGIN_ERROR_OK when no error occured, otherwise see + * zathura_plugin_error_t */ -bool zathura_page_render(zathura_page_t* page, cairo_t* cairo); +zathura_plugin_error_t zathura_page_render(zathura_page_t* page, cairo_t* cairo, bool printing); /** * Create new index element @@ -389,6 +523,13 @@ zathura_index_element_t* zathura_index_element_new(const char* title); */ void zathura_index_element_free(zathura_index_element_t* index); +/** + * Free link + * + * @param link The link + */ +void zathura_link_free(zathura_link_t* link); + /** * Add type -> plugin mapping * @param zathura zathura instance diff --git a/main.c b/main.c new file mode 100644 index 0000000..be57800 --- /dev/null +++ b/main.c @@ -0,0 +1,29 @@ +/* See LICENSE file for license and copyright information */ + +#include +#include + +#include "zathura.h" + +/* main function */ +int main(int argc, char* argv[]) +{ + g_thread_init(NULL); + gdk_threads_init(); + gtk_init(&argc, &argv); + + zathura_t* zathura = zathura_init(argc, argv); + if (zathura == NULL) { + fprintf(stderr, "error: could not initialize zathura\n"); + return -1; + } + + gdk_threads_enter(); + gtk_main(); + gdk_threads_leave(); + + zathura_free(zathura); + + return 0; +} + diff --git a/page_widget.c b/page_widget.c new file mode 100644 index 0000000..5c0f368 --- /dev/null +++ b/page_widget.c @@ -0,0 +1,535 @@ +/* See LICENSE file for license and copyright information */ + +#include +#include +#include + +#include "page_widget.h" +#include "render.h" +#include "utils.h" +#include "shortcuts.h" + +G_DEFINE_TYPE(ZathuraPage, zathura_page_widget, GTK_TYPE_DRAWING_AREA) + +typedef struct zathura_page_widget_private_s { + zathura_page_t* page; + zathura_t* zathura; + cairo_surface_t* surface; /** Cairo surface */ + GStaticMutex lock; /**< Lock */ + girara_list_t* links; /**< List of links on the page */ + bool links_got; /**< True if we already tried to retrieve the list of links */ + bool draw_links; /**< True if links should be drawn */ + unsigned int link_offset; /**< Offset to the links */ + unsigned int number_of_links; /**< Offset to the links */ + girara_list_t* search_results; /** A list if there are search results that should be drawn */ + int search_current; /** The index of the current search result */ + zathura_rectangle_t selection; /** Region selected with the mouse */ +} zathura_page_widget_private_t; + +#define ZATHURA_PAGE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ZATHURA_TYPE_PAGE, zathura_page_widget_private_t)) + +static gboolean zathura_page_widget_expose(GtkWidget* widget, GdkEventExpose* event); +static void zathura_page_widget_finalize(GObject* object); +static void zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec); +static void zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec); +static void zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation); +static void redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle); +static void redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles); +static gboolean cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button); +static gboolean cb_zathura_page_widget_button_release_event(GtkWidget* widget, GdkEventButton* button); +static gboolean cb_zathura_page_widget_motion_notify(GtkWidget* widget, GdkEventMotion* event); + +enum properties_e +{ + PROP_0, + PROP_PAGE, + PROP_DRAW_LINKS, + PROP_LINKS_OFFSET, + PROP_LINKS_NUMBER, + PROP_SEARCH_RESULT, + PROP_SEARCH_RESULTS, + PROP_SEARCH_RESULTS_LENGTH, + PROP_SEARCH_RESULTS_CURRENT +}; + +static void +zathura_page_widget_class_init(ZathuraPageClass* class) +{ + /* add private members */ + g_type_class_add_private(class, sizeof(zathura_page_widget_private_t)); + + /* overwrite methods */ + GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(class); + widget_class->expose_event = zathura_page_widget_expose; + widget_class->size_allocate = zathura_page_widget_size_allocate; + widget_class->button_press_event = cb_zathura_page_widget_button_press_event; + widget_class->button_release_event = cb_zathura_page_widget_button_release_event; + widget_class->motion_notify_event = cb_zathura_page_widget_motion_notify; + + GObjectClass* object_class = G_OBJECT_CLASS(class); + object_class->finalize = zathura_page_widget_finalize; + object_class->set_property = zathura_page_widget_set_property; + object_class->get_property = zathura_page_widget_get_property; + + /* add properties */ + g_object_class_install_property(object_class, PROP_PAGE, + g_param_spec_pointer("page", "page", "the page to draw", G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property(object_class, PROP_DRAW_LINKS, + g_param_spec_boolean("draw-links", "draw-links", "Set to true if links should be drawn", FALSE, G_PARAM_WRITABLE)); + g_object_class_install_property(object_class, PROP_LINKS_OFFSET, + g_param_spec_int("offset-links", "offset-links", "Offset for the link numbers", 0, INT_MAX, 0, G_PARAM_WRITABLE)); + g_object_class_install_property(object_class, PROP_LINKS_NUMBER, + g_param_spec_int("number-of-links", "number-of-links", "Number of links", 0, INT_MAX, 0, G_PARAM_READABLE)); + g_object_class_install_property(object_class, PROP_SEARCH_RESULTS, + g_param_spec_pointer("search-results", "search-results", "Set to the list of search results", G_PARAM_WRITABLE | G_PARAM_READABLE)); + g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_CURRENT, + g_param_spec_int("search-current", "search-current", "The current search result", -1, INT_MAX, 0, G_PARAM_WRITABLE | G_PARAM_READABLE)); + g_object_class_install_property(object_class, PROP_SEARCH_RESULTS_LENGTH, + g_param_spec_int("search-length", "search-length", "The number of search results", -1, INT_MAX, 0, G_PARAM_READABLE)); +} + +static void +zathura_page_widget_init(ZathuraPage* widget) +{ + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + priv->page = NULL; + priv->surface = NULL; + priv->links = NULL; + priv->links_got = false; + priv->link_offset = 0; + priv->search_results = NULL; + priv->search_current = INT_MAX; + priv->selection.x1 = -1; + g_static_mutex_init(&(priv->lock)); + + /* we want mouse events */ + gtk_widget_add_events(GTK_WIDGET(widget), + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK); +} + +GtkWidget* +zathura_page_widget_new(zathura_page_t* page) +{ + g_return_val_if_fail(page != NULL, NULL); + + return g_object_new(ZATHURA_TYPE_PAGE, "page", page, NULL); +} + +static void +zathura_page_widget_finalize(GObject* object) +{ + ZathuraPage* widget = ZATHURA_PAGE(object); + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + if (priv->surface) { + cairo_surface_destroy(priv->surface); + } + g_static_mutex_free(&(priv->lock)); + girara_list_free(priv->links); + + G_OBJECT_CLASS(zathura_page_widget_parent_class)->finalize(object); +} + +static void +zathura_page_widget_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) +{ + ZathuraPage* pageview = ZATHURA_PAGE(object); + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview); + + switch (prop_id) { + case PROP_PAGE: + priv->page = g_value_get_pointer(value); + priv->zathura = priv->page->document->zathura; + break; + case PROP_DRAW_LINKS: + priv->draw_links = g_value_get_boolean(value); + /* get links */ + if (priv->draw_links == true && priv->links_got == false) { + priv->links = zathura_page_links_get(priv->page, NULL); + priv->links_got = true; + priv->number_of_links = (priv->links == NULL) ? 0 : girara_list_size(priv->links); + } + + if (priv->links_got == true && priv->links != NULL) { + GIRARA_LIST_FOREACH(priv->links, zathura_link_t*, iter, link) + zathura_rectangle_t rectangle = recalc_rectangle(priv->page, link->position); + redraw_rect(pageview, &rectangle); + GIRARA_LIST_FOREACH_END(priv->links, zathura_link_t*, iter, link); + } + break; + case PROP_LINKS_OFFSET: + priv->link_offset = g_value_get_int(value); + break; + case PROP_SEARCH_RESULTS: + if (priv->search_results != NULL) { + redraw_all_rects(pageview, priv->search_results); + girara_list_free(priv->search_results); + } + priv->search_results = g_value_get_pointer(value); + if (priv->search_results != NULL) { + priv->draw_links = false; + redraw_all_rects(pageview, priv->search_results); + } + priv->search_current = -1; + break; + case PROP_SEARCH_RESULTS_CURRENT: { + g_return_if_fail(priv->search_results != NULL); + if (priv->search_current >= 0 && priv->search_current < (signed) girara_list_size(priv->search_results)) { + zathura_rectangle_t* rect = girara_list_nth(priv->search_results, priv->search_current); + zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect); + redraw_rect(pageview, &rectangle); + } + int val = g_value_get_int(value); + if (val < 0) { + priv->search_current = girara_list_size(priv->search_results); + } else { + priv->search_current = val; + zathura_rectangle_t* rect = girara_list_nth(priv->search_results, priv->search_current); + zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect); + redraw_rect(pageview, &rectangle); + } + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + +static void +zathura_page_widget_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) +{ + ZathuraPage* pageview = ZATHURA_PAGE(object); + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(pageview); + + switch (prop_id) { + case PROP_LINKS_NUMBER: + g_value_set_int(value, priv->number_of_links); + break; + case PROP_SEARCH_RESULTS_LENGTH: + g_value_set_int(value, priv->search_results == NULL ? 0 : girara_list_size(priv->search_results)); + break; + case PROP_SEARCH_RESULTS_CURRENT: + g_value_set_int(value, priv->search_results == NULL ? -1 : priv->search_current); + break; + case PROP_SEARCH_RESULTS: + g_value_set_pointer(value, priv->search_results); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + } +} + +static gboolean +zathura_page_widget_expose(GtkWidget* widget, GdkEventExpose* event) +{ + cairo_t* cairo = gdk_cairo_create(widget->window); + if (cairo == NULL) { + girara_error("Could not retreive cairo object"); + return FALSE; + } + + /* set clip region */ + cairo_rectangle(cairo, event->area.x, event->area.y, event->area.width, event->area.height); + cairo_clip(cairo); + + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + g_static_mutex_lock(&(priv->lock)); + if (priv->surface != NULL) { + cairo_save(cairo); + + unsigned int page_height = widget->allocation.height; + unsigned int page_width = widget->allocation.width; + + switch (priv->page->document->rotate) { + case 90: + cairo_translate(cairo, page_width, 0); + break; + case 180: + cairo_translate(cairo, page_width, page_height); + break; + case 270: + cairo_translate(cairo, 0, page_height); + break; + } + + if (priv->page->document->rotate != 0) { + cairo_rotate(cairo, priv->page->document->rotate * G_PI / 180.0); + } + + cairo_set_source_surface(cairo, priv->surface, 0, 0); + cairo_paint(cairo); + cairo_restore(cairo); + + /* draw rectangles */ + char* font = NULL; + girara_setting_get(priv->zathura->ui.session, "font", &font); + + float transparency = 0.5; + girara_setting_get(priv->zathura->ui.session, "highlight-transparency", &transparency); + + if (font != NULL) { + cairo_select_font_face(cairo, font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD); + } + g_free(font); + + /* draw links */ + if (priv->draw_links == true && priv->number_of_links != 0) { + unsigned int link_counter = 0; + GIRARA_LIST_FOREACH(priv->links, zathura_link_t*, iter, link) + zathura_rectangle_t rectangle = recalc_rectangle(priv->page, link->position); + + /* draw position */ + GdkColor color = priv->zathura->ui.colors.highlight_color; + cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency); + cairo_rectangle(cairo, rectangle.x1, rectangle.y1, + (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1)); + cairo_fill(cairo); + + /* draw text */ + cairo_set_source_rgba(cairo, 0, 0, 0, 1); + cairo_set_font_size(cairo, 10); + cairo_move_to(cairo, rectangle.x1 + 1, rectangle.y2 - 1); + char* link_number = g_strdup_printf("%i", priv->link_offset + ++link_counter); + cairo_show_text(cairo, link_number); + g_free(link_number); + GIRARA_LIST_FOREACH_END(priv->links, zathura_link_t*, iter, link); + } + + /* draw search results */ + if (priv->search_results != NULL) { + int idx = 0; + GIRARA_LIST_FOREACH(priv->search_results, zathura_rectangle_t*, iter, rect) + zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect); + + /* draw position */ + GdkColor color = priv->zathura->ui.colors.highlight_color; + if (idx == priv->search_current) { + cairo_set_source_rgba(cairo, 0, color.green, color.blue, transparency); + } else { + cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency); + } + cairo_rectangle(cairo, rectangle.x1, rectangle.y1, + (rectangle.x2 - rectangle.x1), (rectangle.y2 - rectangle.y1)); + cairo_fill(cairo); + ++idx; + GIRARA_LIST_FOREACH_END(priv->search_results, zathura_rectangle_t*, iter, rect); + } + /* draw selection */ + if (priv->selection.y2 != -1 && priv->selection.x2 != -1) { + GdkColor color = priv->zathura->ui.colors.highlight_color; + cairo_set_source_rgba(cairo, color.red, color.green, color.blue, transparency); + cairo_rectangle(cairo, priv->selection.x1, priv->selection.y1, + (priv->selection.x2 - priv->selection.x1), (priv->selection.y2 - priv->selection.y1)); + cairo_fill(cairo); + } + } else { + /* set background color */ + cairo_set_source_rgb(cairo, 255, 255, 255); + cairo_rectangle(cairo, 0, 0, widget->allocation.width, widget->allocation.height); + cairo_fill(cairo); + + bool render_loading = true; + girara_setting_get(priv->zathura->ui.session, "render-loading", &render_loading); + + /* write text */ + if (render_loading == true) { + cairo_set_source_rgb(cairo, 0, 0, 0); + const char* text = "Loading..."; + cairo_select_font_face(cairo, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); + cairo_set_font_size(cairo, 16.0); + cairo_text_extents_t extents; + cairo_text_extents(cairo, text, &extents); + double x = widget->allocation.width * 0.5 - (extents.width * 0.5 + extents.x_bearing); + double y = widget->allocation.height * 0.5 - (extents.height * 0.5 + extents.y_bearing); + cairo_move_to(cairo, x, y); + cairo_show_text(cairo, text); + } + + /* render real page */ + render_page(priv->zathura->sync.render_thread, priv->page); + } + cairo_destroy(cairo); + + g_static_mutex_unlock(&(priv->lock)); + return FALSE; +} + +static void +zathura_page_widget_redraw_canvas(ZathuraPage* pageview) +{ + GtkWidget* widget = GTK_WIDGET(pageview); + gtk_widget_queue_draw(widget); +} + +void +zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface) +{ + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + g_static_mutex_lock(&(priv->lock)); + if (priv->surface != NULL) { + cairo_surface_destroy(priv->surface); + } + priv->surface = surface; + g_static_mutex_unlock(&(priv->lock)); + /* force a redraw here */ + zathura_page_widget_redraw_canvas(widget); +} + +static void +zathura_page_widget_size_allocate(GtkWidget* widget, GdkRectangle* allocation) +{ + GTK_WIDGET_CLASS(zathura_page_widget_parent_class)->size_allocate(widget, allocation); + zathura_page_widget_update_surface(ZATHURA_PAGE(widget), NULL); +} + +static void +redraw_rect(ZathuraPage* widget, zathura_rectangle_t* rectangle) +{ + /* cause the rect to be drawn */ + GdkRectangle grect; + grect.x = rectangle->x1; + grect.y = rectangle->y1; + grect.width = rectangle->x2 - rectangle->x1; + grect.height = rectangle->y2 - rectangle->y1; + gdk_window_invalidate_rect(GTK_WIDGET(widget)->window, &grect, TRUE); +} + +static void +redraw_all_rects(ZathuraPage* widget, girara_list_t* rectangles) +{ + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + + GIRARA_LIST_FOREACH(rectangles, zathura_rectangle_t*, iter, rect) + zathura_rectangle_t rectangle = recalc_rectangle(priv->page, *rect); + redraw_rect(widget, &rectangle); + GIRARA_LIST_FOREACH_END(rectangles, zathura_recantgle_t*, iter, rect); +} + +zathura_link_t* +zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index) +{ + g_return_val_if_fail(widget != NULL, NULL); + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + g_return_val_if_fail(priv != NULL, NULL); + + if (priv->links != NULL && index >= priv->link_offset && + girara_list_size(priv->links) >= index - priv->link_offset) { + return girara_list_nth(priv->links, index - priv->link_offset); + } else { + return NULL; + } +} + +static gboolean +cb_zathura_page_widget_button_press_event(GtkWidget* widget, GdkEventButton* button) +{ + g_return_val_if_fail(widget != NULL, false); + g_return_val_if_fail(button != NULL, false); + if (button->button != 1) { + return false; + } + + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + + if (button->type == GDK_BUTTON_PRESS) { + /* start the selection */ + priv->selection.x1 = button->x; + priv->selection.y1 = button->y; + priv->selection.x2 = -1; + priv->selection.y2 = -1; + } else if (button->type == GDK_2BUTTON_PRESS || button->type == GDK_3BUTTON_PRESS) { + /* abort the selection */ + priv->selection.x1 = -1; + priv->selection.y1 = -1; + priv->selection.x2 = -1; + priv->selection.y2 = -1; + } + return false; +} + +static gboolean +cb_zathura_page_widget_button_release_event(GtkWidget* widget, GdkEventButton* button) +{ + g_return_val_if_fail(widget != NULL, false); + g_return_val_if_fail(button != NULL, false); + if (button->type != GDK_BUTTON_RELEASE || button->button != 1) { + return false; + } + + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + + if (priv->selection.y2 == -1 && priv->selection.x2 == -1 ) { + /* simple single click */ + /* get links */ + if (priv->links_got == false) { + priv->links = zathura_page_links_get(priv->page, NULL); + priv->links_got = true; + priv->number_of_links = (priv->links == NULL) ? 0 : girara_list_size(priv->links); + } + + if (priv->links != NULL && priv->number_of_links > 0) { + GIRARA_LIST_FOREACH(priv->links, zathura_link_t*, iter, link) + zathura_rectangle_t rect = recalc_rectangle(priv->page, link->position); + if (rect.x1 <= button->x && rect.x2 >= button->x + && rect.y1 <= button->y && rect.y2 >= button->y) { + switch (link->type) { + case ZATHURA_LINK_TO_PAGE: + page_set_delayed(priv->page->document->zathura, link->target.page_number); + return false; + case ZATHURA_LINK_EXTERNAL: + girara_xdg_open(link->target.value); + return false; + } + } + GIRARA_LIST_FOREACH_END(priv->links, zathura_link_t*, iter, link); + } + } else { + redraw_rect(ZATHURA_PAGE(widget), &priv->selection); + zathura_rectangle_t tmp = priv->selection; + tmp.x1 /= priv->page->document->scale; + tmp.x2 /= priv->page->document->scale; + tmp.y1 /= priv->page->document->scale; + tmp.y2 /= priv->page->document->scale; + + char* text = zathura_page_get_text(priv->page, tmp, NULL); + if (text != NULL) { + gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY), text, -1); + g_free(text); + } + } + priv->selection.x1 = -1; + priv->selection.y1 = -1; + priv->selection.x2 = -1; + priv->selection.y2 = -1; + + return false; +} + +static gboolean +cb_zathura_page_widget_motion_notify(GtkWidget* widget, GdkEventMotion* event) +{ + g_return_val_if_fail(widget != NULL, false); + g_return_val_if_fail(event != NULL, false); + if ((event->state & GDK_BUTTON1_MASK) == 0) { + return false; + } + + zathura_page_widget_private_t* priv = ZATHURA_PAGE_GET_PRIVATE(widget); + zathura_rectangle_t tmp = priv->selection; + if (event->x < priv->selection.x1) { + tmp.x1 = event->x; + } else { + tmp.x2 = event->x; + } + if (event->y < priv->selection.y1) { + tmp.y1 = event->y; + } else { + tmp.y2 = event->y; + } + + redraw_rect(ZATHURA_PAGE(widget), &priv->selection); + redraw_rect(ZATHURA_PAGE(widget), &tmp); + priv->selection = tmp; + + return false; +} diff --git a/page_widget.h b/page_widget.h new file mode 100644 index 0000000..e6d00e0 --- /dev/null +++ b/page_widget.h @@ -0,0 +1,81 @@ +/* See LICENSE file for license and copyright information */ + +#ifndef PAGE_WIDGET_H +#define PAGE_WIDGET_H + +#include +#include "document.h" + +/** + * The page view widget. The widget handles all the rendering on its own. It + * only has to be resized. The widget also manages and handles all the + * rectangles for highlighting. + * + * Before the properties contain the correct values, 'draw-links' has to be set + * to TRUE at least one time. + * */ +typedef struct zathura_page_widget_s ZathuraPage; +typedef struct zathura_page_widget_class_s ZathuraPageClass; + +struct zathura_page_widget_s { + GtkDrawingArea parent; +}; + +struct zathura_page_widget_class_s { + GtkDrawingAreaClass parent_class; +}; + +#define ZATHURA_TYPE_PAGE \ + (zathura_page_widget_get_type ()) +#define ZATHURA_PAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), ZATHURA_TYPE_PAGE, ZathuraPage)) +#define ZATHURA_PAGE_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_CAST ((obj), ZATHURA_PAGE, ZathuraPageClass)) +#define ZATHURA_IS_PAGE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), ZATHURA_PAGE)) +#define ZATHURA_IS_PAGE_WDIGET_CLASS(obj) \ + (G_TYPE_CHECK_CLASS_TYPE ((obj), ZATHURA_TYPE_PAGE)) +#define ZATHURA_PAGE_GET_CLASS \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), ZATHURA_TYPE_PAGE, ZathuraPageclass)) + +/** + * Returns the type of the page view widget. + * @return the type + */ +GType zathura_page_widget_get_type(void); +/** + * Create a page view widget. + * @param page the page to be displayed + * @return a page view widget + */ +GtkWidget* zathura_page_widget_new(zathura_page_t* page); +/** + * Update the widget's surface. This should only be called from the render + * thread. + * @param widget the widget + * @param surface the new surface + */ +void zathura_page_widget_update_surface(ZathuraPage* widget, cairo_surface_t* surface); +/** + * Draw a rectangle to mark links or search results + * @param widget the widget + * @param rectangle the rectangle + * @param linkid the link id if it's a link, -1 otherwise + */ +void zathura_page_widget_draw_rectangle(ZathuraPage* widget, zathura_rectangle_t* rectangle, int linkid); +/** + * Clear all rectangles. + * @param widget the widget + */ +void zathura_page_widget_clear_rectangles(ZathuraPage* widget); + +/** + * Returns the zathura link object at the given index + * + * @param widget the widget + * @param index Index of the link + * @return Link object or NULL if an error occured + */ +zathura_link_t* zathura_page_widget_link_get(ZathuraPage* widget, unsigned int index); + +#endif diff --git a/print.c b/print.c index 4d84982..e6d683f 100644 --- a/print.c +++ b/print.c @@ -1,5 +1,11 @@ +/* See LICENSE file for license and copyright information */ + #include "print.h" #include "document.h" +#include "render.h" + +#include +#include void print(zathura_t* zathura) @@ -24,15 +30,23 @@ print(zathura_t* zathura) /* print operation signals */ g_signal_connect(print_operation, "draw-page", G_CALLBACK(cb_print_draw_page), zathura); + g_signal_connect(print_operation, "end-print", G_CALLBACK(cb_print_end), zathura); /* print */ GtkPrintOperationResult result = gtk_print_operation_run(print_operation, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG, NULL, NULL); if (result == GTK_PRINT_OPERATION_RESULT_APPLY) { + if (zathura->print.settings != NULL) { + g_object_unref(zathura->print.settings); + } + if (zathura->print.page_setup != NULL) { + g_object_unref(zathura->print.page_setup); + } + /* save previous settings */ - zathura->print.settings = gtk_print_operation_get_print_settings(print_operation); - zathura->print.page_setup = gtk_print_operation_get_default_page_setup(print_operation); + zathura->print.settings = g_object_ref(gtk_print_operation_get_print_settings(print_operation)); + zathura->print.page_setup = g_object_ref(gtk_print_operation_get_default_page_setup(print_operation)); } else if (result == GTK_PRINT_OPERATION_RESULT_ERROR) { girara_error("Error occured while printing progress"); } @@ -41,43 +55,39 @@ print(zathura_t* zathura) } void -cb_print_begin(GtkPrintOperation* UNUSED(print_operation), GtkPrintContext* - UNUSED(context), zathura_t* UNUSED(zathura)) +cb_print_end(GtkPrintOperation* UNUSED(print_operation), GtkPrintContext* + UNUSED(context), zathura_t* zathura) { + if (zathura == NULL || zathura->ui.session == NULL || zathura->document == NULL + || zathura->document->file_path == NULL) { + return; + } + girara_statusbar_item_set_text(zathura->ui.session, + zathura->ui.statusbar.file, zathura->document->file_path); } void cb_print_draw_page(GtkPrintOperation* UNUSED(print_operation), GtkPrintContext* context, gint page_number, zathura_t* zathura) { - /* TODO: Implement with cairo */ - /*cairo_t* cairo = gtk_print_context_get_cairo_context(context);*/ + if (context == NULL || zathura == NULL || zathura->document->pages == NULL || + zathura->ui.session == NULL || zathura->ui.statusbar.file == NULL) { + return; + } - /*girara_info("Printing page %d", page_number);*/ + /* update statusbar */ + char* tmp = g_strdup_printf("Printing %d...", page_number); + girara_statusbar_item_set_text(zathura->ui.session, + zathura->ui.statusbar.file, tmp); + g_free(tmp); - /*zathura_page_t* page = zathura->document->pages[page_number];*/ + /* render page */ + cairo_t* cairo = gtk_print_context_get_cairo_context(context); + zathura_page_t* page = zathura->document->pages[page_number]; + if (cairo == NULL || page == NULL) { + return; + } - /*double requested_with = gtk_print_context_get_width(context);*/ - /*double tmp_scale = zathura->document->scale;*/ - /*zathura->document->scale = requested_with / page->width;*/ - - /*g_static_mutex_lock(&(page->lock));*/ - /*zathura_image_buffer_t* image_buffer = zathura_page_render(page);*/ - /*g_static_mutex_unlock(&(page->lock));*/ - - /*for (unsigned int y = 0; y < image_buffer->height; y++) {*/ - /*unsigned char* src = image_buffer->data + y * image_buffer->rowstride;*/ - /*for (unsigned int x = 0; x < image_buffer->width; x++) {*/ - /*if (src[0] != 255 && src[1] != 255 && src[2] != 255) {*/ - /*cairo_set_source_rgb(cairo, src[0], src[1], src[2]);*/ - /*cairo_rectangle(cairo, x, y, 1, 1);*/ - /*cairo_fill(cairo);*/ - /*}*/ - - /*src += 3;*/ - /*}*/ - /*}*/ - - /*zathura->document->scale = tmp_scale;*/ + zathura_page_render(page, cairo, true); } diff --git a/print.h b/print.h index 628d399..f7b603d 100644 --- a/print.h +++ b/print.h @@ -23,4 +23,14 @@ void print(zathura_t* zathura); void cb_print_draw_page(GtkPrintOperation* print_operation, GtkPrintContext* context, gint page_number, zathura_t* zathura); +/** + * Emitted after all pages have been rendered + * + * @param print_operation Print operation + * @param context Print context + * @param zathura Zathura object + */ +void cb_print_end(GtkPrintOperation* print_operation, GtkPrintContext* context, + zathura_t* zathura); + #endif // PRINT_H diff --git a/render.c b/render.c index c3503a5..2311398 100644 --- a/render.c +++ b/render.c @@ -1,6 +1,16 @@ +/* See LICENSE file for license and copyright information */ + +#include +#include +#include +#include +#include + #include "render.h" #include "zathura.h" #include "document.h" +#include "page_widget.h" +#include "utils.h" void* render_job(void* data); bool render(zathura_t* zathura, zathura_page_t* page); @@ -13,11 +23,11 @@ render_job(void* data) while (true) { g_mutex_lock(render_thread->lock); - if (girara_list_size(render_thread->list) <= 0) { + if (girara_list_size(render_thread->list) == 0) { g_cond_wait(render_thread->cond, render_thread->lock); } - if (girara_list_size(render_thread->list) <= 0) { + if (girara_list_size(render_thread->list) == 0) { /* * We've been signaled with g_cond_signal(), but the list * is still empty. This means that the signal came from @@ -43,16 +53,9 @@ render_job(void* data) render_thread_t* render_init(zathura_t* zathura) { - render_thread_t* render_thread = malloc(sizeof(render_thread_t)); - - if (!render_thread) { - goto error_ret; - } + render_thread_t* render_thread = g_malloc0(sizeof(render_thread_t)); /* init */ - render_thread->list = NULL; - render_thread->thread = NULL; - render_thread->cond = NULL; render_thread->zathura = zathura; /* setup */ @@ -96,9 +99,7 @@ error_free: g_mutex_free(render_thread->lock); } - free(render_thread); - -error_ret: + g_free(render_thread); return NULL; } @@ -128,7 +129,7 @@ render_free(render_thread_t* render_thread) bool render_page(render_thread_t* render_thread, zathura_page_t* page) { - if (!render_thread || !page || !render_thread->list || page->surface) { + if (!render_thread || !page || !render_thread->list) { return false; } @@ -150,24 +151,15 @@ render(zathura_t* zathura, zathura_page_t* page) } gdk_threads_enter(); - g_static_mutex_lock(&(page->lock)); /* create cairo surface */ unsigned int page_width = 0; unsigned int page_height = 0; - - if (page->document->rotate == 0 || page->document->rotate == 180) { - page_width = page->width * zathura->document->scale; - page_height = page->height * zathura->document->scale; - } else { - page_width = page->height * zathura->document->scale; - page_height = page->width * zathura->document->scale; - } + page_calc_height_width(page, &page_height, &page_width, false); cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, page_width, page_height); if (surface == NULL) { - g_static_mutex_unlock(&(page->lock)); gdk_threads_leave(); return false; } @@ -176,7 +168,6 @@ render(zathura_t* zathura, zathura_page_t* page) if (cairo == NULL) { cairo_surface_destroy(surface); - g_static_mutex_unlock(&(page->lock)); gdk_threads_leave(); return false; } @@ -188,26 +179,13 @@ render(zathura_t* zathura, zathura_page_t* page) cairo_restore(cairo); cairo_save(cairo); - switch(page->document->rotate) { - case 90: - cairo_translate(cairo, page_width, 0); - break; - case 180: - cairo_translate(cairo, page_width, page_height); - break; - case 270: - cairo_translate(cairo, 0, page_height); - break; + if (fabs(zathura->document->scale - 1.0f) > FLT_EPSILON) { + cairo_scale(cairo, zathura->document->scale, zathura->document->scale); } - if (page->document->rotate != 0) { - cairo_rotate(cairo, page->document->rotate * G_PI / 180.0); - } - - if (zathura_page_render(page, cairo) == false) { + if (zathura_page_render(page, cairo, false) != ZATHURA_PLUGIN_ERROR_OK) { cairo_destroy(cairo); cairo_surface_destroy(surface); - g_static_mutex_unlock(&(page->lock)); gdk_threads_leave(); return false; } @@ -215,7 +193,7 @@ render(zathura_t* zathura, zathura_page_t* page) cairo_restore(cairo); cairo_destroy(cairo); - int rowstride = cairo_image_surface_get_stride(surface); + const int rowstride = cairo_image_surface_get_stride(surface); unsigned char* image = cairo_image_surface_get_data(surface); /* recolor */ @@ -251,12 +229,9 @@ render(zathura_t* zathura, zathura_page_t* page) } } - /* draw to gtk widget */ - page->surface = surface; - gtk_widget_set_size_request(page->drawing_area, page_width, page_height); - gtk_widget_queue_draw(page->drawing_area); + /* update the widget */ + zathura_page_widget_update_surface(ZATHURA_PAGE(page->drawing_area), surface); - g_static_mutex_unlock(&(page->lock)); gdk_threads_leave(); return true; @@ -271,66 +246,11 @@ render_all(zathura_t* zathura) /* unmark all pages */ for (unsigned int page_id = 0; page_id < zathura->document->number_of_pages; page_id++) { - cairo_surface_destroy(zathura->document->pages[page_id]->surface); - zathura->document->pages[page_id]->surface = NULL; - } + zathura_page_t* page = zathura->document->pages[page_id]; + unsigned int page_height = 0, page_width = 0; + page_calc_height_width(page, &page_height, &page_width, true); - /* redraw current page */ - GtkAdjustment* view_vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); - cb_view_vadjustment_value_changed(view_vadjustment, zathura); -} - -gboolean -page_expose_event(GtkWidget* UNUSED(widget), GdkEventExpose* UNUSED(event), - gpointer data) -{ - zathura_page_t* page = data; - if (page == NULL) { - return FALSE; - } - - g_static_mutex_lock(&(page->lock)); - - cairo_t* cairo = gdk_cairo_create(page->drawing_area->window); - - if (cairo == NULL) { - girara_error("Could not retreive cairo object"); - g_static_mutex_unlock(&(page->lock)); - return FALSE; - } - - if (page->surface != NULL) { - cairo_set_source_surface(cairo, page->surface, 0, 0); - cairo_paint(cairo); - } else { - /* set background color */ - cairo_set_source_rgb(cairo, 255, 255, 255); - cairo_rectangle(cairo, 0, 0, page->width * page->document->scale, page->height * page->document->scale); - cairo_fill(cairo); - - /* write text */ - cairo_set_source_rgb(cairo, 0, 0, 0); - const char* text = "Loading..."; - cairo_select_font_face(cairo, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL); - cairo_set_font_size(cairo, 16.0); - cairo_text_extents_t extents; - cairo_text_extents(cairo, text, &extents); - double x = (page->width * page->document->scale) / 2 - (extents.width / 2 + extents.x_bearing); - double y = (page->height * page->document->scale) / 2 - (extents.height / 2 + extents.y_bearing); - cairo_move_to(cairo, x, y); - cairo_show_text(cairo, text); - - /* render real page */ - render_page(page->document->zathura->sync.render_thread, page); - - /* update statusbar */ - } - cairo_destroy(cairo); - - page->document->current_page_number = page->number; - statusbar_page_number_update(page->document->zathura); - - g_static_mutex_unlock(&(page->lock)); - - return TRUE; + gtk_widget_set_size_request(page->drawing_area, page_width, page_height); + gtk_widget_queue_resize(page->drawing_area); + } } diff --git a/render.h b/render.h index a8628cf..955ccb3 100644 --- a/render.h +++ b/render.h @@ -5,24 +5,24 @@ #include #include -#include +#include #include "zathura.h" #include "callbacks.h" struct render_thread_s { - girara_list_t* list; /**> The list of pages */ - GThread* thread; /**> The thread object */ - GMutex* lock; /**> Lock */ - GCond* cond; /**> Condition */ - zathura_t* zathura; /**> Zathura object */ + girara_list_t* list; /**< The list of pages */ + GThread* thread; /**< The thread object */ + GMutex* lock; /**< Lock */ + GCond* cond; /**< Condition */ + zathura_t* zathura; /**< Zathura object */ }; /** * This function initializes a render thread * - * @param Zathura object + * @param zathura object * @return The render thread object or NULL if an error occured */ render_thread_t* render_init(zathura_t* zathura); diff --git a/shortcuts.c b/shortcuts.c index 9aed074..c5d8a0a 100644 --- a/shortcuts.c +++ b/shortcuts.c @@ -1,6 +1,10 @@ /* See LICENSE file for license and copyright information */ -#include +#include +#include +#include +#include +#include #include #include "callbacks.h" @@ -9,30 +13,97 @@ #include "zathura.h" #include "render.h" #include "utils.h" +#include "page_widget.h" bool sc_abort(girara_session_t* session, girara_argument_t* UNUSED(argument), - unsigned int UNUSED(t)) + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + + if (zathura->document != NULL) { + for (unsigned int page_id = 0; page_id < zathura->document->number_of_pages; ++page_id) { + zathura_page_t* page = zathura->document->pages[page_id]; + if (page == NULL) { + continue; + } + + g_object_set(page->drawing_area, "draw-links", FALSE, NULL); + } + } girara_mode_set(session, session->modes.normal); + girara_sc_abort(session, NULL, NULL, 0); return false; } bool -sc_adjust_window(girara_session_t* session, girara_argument_t* UNUSED(argument), - unsigned int UNUSED(t)) +sc_adjust_window(girara_session_t* session, girara_argument_t* argument, + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + g_return_val_if_fail(argument != NULL, false); + + unsigned int pages_per_row = 1; + girara_setting_get(session, "pages-per-row", &pages_per_row); + + if (zathura->ui.page_widget == NULL || zathura->document == NULL) { + goto error_ret; + } + + zathura->document->adjust_mode = argument->n; + + /* get window size */ + GtkAllocation allocation; + gtk_widget_get_allocation(session->gtk.view, &allocation); + gint width = allocation.width; + gint height = allocation.height; + + /* calculate total width and max-height */ + double total_width = 0; + double max_height = 0; + + for (unsigned int page_id = 0; page_id < pages_per_row; page_id++) { + if (page_id == zathura->document->number_of_pages) { + break; + } + + zathura_page_t* page = zathura->document->pages[page_id]; + if (page == NULL) { + goto error_ret; + } + + if (page->height > max_height) { + max_height = page->height; + } + + total_width += page->width; + } + + if (argument->n == ADJUST_WIDTH) { + zathura->document->scale = width / total_width; + } else if (argument->n == ADJUST_BESTFIT) { + zathura->document->scale = height / max_height; + } else { + goto error_ret; + } + + /* re-render all pages */ + render_all(zathura); + +error_ret: return false; } bool -sc_change_mode(girara_session_t* session, girara_argument_t* argument, unsigned - int UNUSED(t)) +sc_change_mode(girara_session_t* session, girara_argument_t* argument, + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); @@ -43,15 +114,51 @@ sc_change_mode(girara_session_t* session, girara_argument_t* argument, unsigned bool sc_follow(girara_session_t* session, girara_argument_t* UNUSED(argument), - unsigned int UNUSED(t)) + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + + if (zathura->document == NULL) { + return false; + } + + /* set pages to draw links */ + bool show_links = false; + unsigned int page_offset = 0; + for (unsigned int page_id = 0; page_id < zathura->document->number_of_pages; page_id++) { + zathura_page_t* page = zathura->document->pages[page_id]; + if (page == NULL) { + continue; + } + + g_object_set(page->drawing_area, "search-results", NULL, NULL); + if (page->visible == true) { + g_object_set(page->drawing_area, "draw-links", TRUE, NULL); + + int number_of_links = 0; + g_object_get(page->drawing_area, "number-of-links", &number_of_links, NULL); + if (number_of_links != 0) { + show_links = true; + } + g_object_set(page->drawing_area, "offset-links", page_offset, NULL); + page_offset += number_of_links; + } else { + g_object_set(page->drawing_area, "draw-links", FALSE, NULL); + } + } + + /* ask for input */ + if (show_links == true) { + girara_dialog(zathura->ui.session, "Follow link:", FALSE, NULL, (girara_callback_inputbar_activate_t) cb_sc_follow, NULL); + } return false; } bool -sc_goto(girara_session_t* session, girara_argument_t* argument, unsigned int t) +sc_goto(girara_session_t* session, girara_argument_t* argument, girara_event_t* UNUSED(event), unsigned int t) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -60,27 +167,113 @@ sc_goto(girara_session_t* session, girara_argument_t* argument, unsigned int t) g_return_val_if_fail(zathura->document != NULL, false); if (argument->n == TOP) { - girara_argument_t arg = { TOP, NULL }; - sc_scroll(session, &arg, 0); - - return false; + page_set(zathura, 0); } else { if (t == 0) { - girara_argument_t arg = { BOTTOM, NULL }; - sc_scroll(session, &arg, 0); - - return true; + page_set(zathura, zathura->document->number_of_pages - 1); + } else { + page_set(zathura, t - 1); } - - page_set(zathura, t - 1); } return false; } bool -sc_navigate(girara_session_t* session, girara_argument_t* argument, unsigned int - UNUSED(t)) +sc_mouse_scroll(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t) +{ + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + g_return_val_if_fail(argument != NULL, false); + g_return_val_if_fail(event != NULL, false); + + static int x = 0; + static int y = 0; + + if (zathura->document == NULL) { + return false; + } + + /* scroll event */ + if (event->type == GIRARA_EVENT_SCROLL) { + switch (event->direction) { + case GIRARA_SCROLL_UP: + argument->n = UP; + break; + case GIRARA_SCROLL_DOWN: + argument->n = DOWN; + break; + case GIRARA_SCROLL_LEFT: + argument->n = LEFT; + break; + case GIRARA_SCROLL_RIGHT: + argument->n = RIGHT; + break; + } + + return sc_scroll(session, argument, NULL, t); + } else if (event->type == GIRARA_EVENT_BUTTON_PRESS) { + x = event->x; + y = event->y; + } else if (event->type == GIRARA_EVENT_BUTTON_RELEASE) { + x = 0; + y = 0; + } else if (event->type == GIRARA_EVENT_MOTION_NOTIFY) { + GtkAdjustment* x_adj = + gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(session->gtk.view)); + GtkAdjustment* y_adj = + gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(session->gtk.view)); + + if (x_adj == NULL || y_adj == NULL) { + return false; + } + + set_adjustment(x_adj, gtk_adjustment_get_value(x_adj) - (event->x - x)); + set_adjustment(y_adj, gtk_adjustment_get_value(y_adj) - (event->y - y)); + + x = event->x; + y = event->y; + } + + return false; +} + +bool +sc_mouse_zoom(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t) +{ + g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + g_return_val_if_fail(argument != NULL, false); + g_return_val_if_fail(event != NULL, false); + + if (zathura->document == NULL) { + return false; + } + + /* scroll event */ + if (event->type == GIRARA_EVENT_SCROLL) { + switch (event->direction) { + case GIRARA_SCROLL_UP: + argument->n = ZOOM_IN; + break; + case GIRARA_SCROLL_DOWN: + argument->n = ZOOM_OUT; + break; + default: + return false; + } + + return sc_zoom(session, argument, NULL, t); + } + + return false; +} + +bool +sc_navigate(girara_session_t* session, girara_argument_t* argument, + girara_event_t* UNUSED(event), unsigned int t) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -91,10 +284,11 @@ sc_navigate(girara_session_t* session, girara_argument_t* argument, unsigned int unsigned int number_of_pages = zathura->document->number_of_pages; unsigned int new_page = zathura->document->current_page_number; + t = (t == 0) ? 1 : t; if (argument->n == NEXT) { - new_page = (new_page + 1) % number_of_pages; + new_page = (new_page + t) % number_of_pages; } else if (argument->n == PREVIOUS) { - new_page = (new_page + number_of_pages - 1) % number_of_pages; + new_page = (new_page + number_of_pages - t) % number_of_pages; } page_set(zathura, new_page); @@ -104,7 +298,7 @@ sc_navigate(girara_session_t* session, girara_argument_t* argument, unsigned int bool sc_recolor(girara_session_t* session, girara_argument_t* UNUSED(argument), - unsigned int UNUSED(t)) + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -118,34 +312,58 @@ sc_recolor(girara_session_t* session, girara_argument_t* UNUSED(argument), bool sc_reload(girara_session_t* session, girara_argument_t* UNUSED(argument), - unsigned int UNUSED(t)) + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; + + if (zathura->document == NULL || zathura->document->file_path == NULL) { + return false; + } + + /* save current document path and password */ + char* path = g_strdup(zathura->document->file_path); + char* password = zathura->document->password ? g_strdup(zathura->document->password) : NULL; + + /* close current document */ + document_close(zathura); + + /* reopen document */ + document_open(zathura, path, password); + + /* clean up */ + g_free(path); + g_free(password); return false; } bool sc_rotate(girara_session_t* session, girara_argument_t* UNUSED(argument), - unsigned int UNUSED(t)) + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); zathura_t* zathura = session->global.data; g_return_val_if_fail(zathura->document != NULL, false); + unsigned int page_number = zathura->document->current_page_number; + /* update rotate value */ zathura->document->rotate = (zathura->document->rotate + 90) % 360; /* render all pages again */ render_all(zathura); + page_set_delayed(zathura, page_number); + return false; } bool -sc_scroll(girara_session_t* session, girara_argument_t* argument, unsigned int - UNUSED(t)) +sc_scroll(girara_session_t* session, girara_argument_t* argument, + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -156,38 +374,39 @@ sc_scroll(girara_session_t* session, girara_argument_t* argument, unsigned int } GtkAdjustment* adjustment = NULL; - if ( (argument->n == LEFT) || (argument->n == RIGHT) ) - adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); - else - adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); - - gdouble view_size = gtk_adjustment_get_page_size(adjustment); - gdouble value = gtk_adjustment_get_value(adjustment); - gdouble max = gtk_adjustment_get_upper(adjustment) - view_size; - gdouble scroll_step = 40; + if ( (argument->n == LEFT) || (argument->n == RIGHT) ) { + adjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(session->gtk.view)); + } else { + adjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(session->gtk.view)); + } + gdouble view_size = gtk_adjustment_get_page_size(adjustment); + gdouble value = gtk_adjustment_get_value(adjustment); + gdouble max = gtk_adjustment_get_upper(adjustment) - view_size; + float scroll_step = 40; + girara_setting_get(session, "scroll-step", &scroll_step); gdouble new_value; - switch(argument->n) - { + + switch(argument->n) { case FULL_UP: - new_value = (value - view_size) < 0 ? 0 : (value - view_size); + new_value = value - view_size; break; case FULL_DOWN: - new_value = (value + view_size) > max ? max : (value + view_size); + new_value = value + view_size; break; case HALF_UP: - new_value = (value - (view_size / 2)) < 0 ? 0 : (value - (view_size / 2)); + new_value = value - (view_size / 2); break; case HALF_DOWN: - new_value = (value + (view_size / 2)) > max ? max : (value + (view_size / 2)); + new_value = value + (view_size / 2); break; case LEFT: case UP: - new_value = (value - scroll_step) < 0 ? 0 : (value - scroll_step); + new_value = value - scroll_step; break; case RIGHT: case DOWN: - new_value = (value + scroll_step) > max ? max : (value + scroll_step); + new_value = value + scroll_step; break; case TOP: new_value = 0; @@ -196,17 +415,17 @@ sc_scroll(girara_session_t* session, girara_argument_t* argument, unsigned int new_value = max; break; default: - new_value = 0; + new_value = value; } - gtk_adjustment_set_value(adjustment, new_value); + set_adjustment(adjustment, new_value); return false; } bool -sc_search(girara_session_t* session, girara_argument_t* argument, unsigned int - UNUSED(t)) +sc_search(girara_session_t* session, girara_argument_t* argument, + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -214,12 +433,79 @@ sc_search(girara_session_t* session, girara_argument_t* argument, unsigned int g_return_val_if_fail(argument != NULL, false); g_return_val_if_fail(zathura->document != NULL, false); + const int num_pages = zathura->document->number_of_pages; + const int cur_page = zathura->document->current_page_number; + int diff = argument->n == FORWARD ? 1 : -1; + + zathura_page_t* target_page = NULL; + int target_idx = 0; + + for (int page_id = 0; page_id < num_pages; ++page_id) { + int tmp = cur_page + diff * page_id; + zathura_page_t* page = zathura->document->pages[(tmp + num_pages) % num_pages]; + if (page == NULL) { + continue; + } + + int num_search_results = 0, current = -1; + g_object_get(page->drawing_area, "search-current", ¤t, + "search-length", &num_search_results, NULL); + if (num_search_results == 0 || current == -1) { + continue; + } + + if (diff == 1 && current < num_search_results - 1) { + /* the next result is on the same page */ + target_page = page; + target_idx = current + 1; + // g_object_set(page->drawing_area, "search-current", current + 1, NULL); + } else if (diff == -1 && current >= 1) { + // g_object_set(page->drawing_area, "search-current", current - 1, NULL); + target_page = page; + target_idx = current - 1; + } else { + /* the next result is on a different page */ + g_object_set(page->drawing_area, "search-current", -1, NULL); + + for (int npage_id = 1; page_id < num_pages; ++npage_id) { + int ntmp = cur_page + diff * (page_id + npage_id); + zathura_page_t* npage = zathura->document->pages[(ntmp + 2*num_pages) % num_pages]; + g_object_get(npage->drawing_area, "search-length", &num_search_results, NULL); + if (num_search_results != 0) { + target_page = npage; + target_idx = diff == 1 ? 0 : num_search_results - 1; + break; + } + } + } + break; + } + + if (target_page != NULL) { + girara_list_t* results = NULL; + g_object_set(target_page->drawing_area, "search-current", target_idx, NULL); + g_object_get(target_page->drawing_area, "search-results", &results, NULL); + + zathura_rectangle_t* rect = girara_list_nth(results, target_idx); + zathura_rectangle_t rectangle = recalc_rectangle(target_page, *rect); + page_offset_t offset; + page_calculate_offset(target_page, &offset); + + GtkAdjustment* view_vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); + GtkAdjustment* view_hadjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); + + int x = offset.x - gtk_adjustment_get_page_size(view_hadjustment) / 2 + rectangle.x1; + int y = offset.y - gtk_adjustment_get_page_size(view_vadjustment) / 2 + rectangle.y1; + set_adjustment(view_hadjustment, x); + set_adjustment(view_vadjustment, y); + } + return false; } bool sc_navigate_index(girara_session_t* session, girara_argument_t* argument, - unsigned int UNUSED(t)) + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -227,23 +513,95 @@ sc_navigate_index(girara_session_t* session, girara_argument_t* argument, g_return_val_if_fail(argument != NULL, false); g_return_val_if_fail(zathura->document != NULL, false); + if(zathura->ui.index == NULL) { + return false; + } + + GtkTreeView *tree_view = gtk_container_get_children(GTK_CONTAINER(zathura->ui.index))->data; + GtkTreePath *path; + + gtk_tree_view_get_cursor(tree_view, &path, NULL); + if (path == NULL) { + return false; + } + + GtkTreeModel *model = gtk_tree_view_get_model(tree_view); + GtkTreeIter iter; + GtkTreeIter child_iter; + + gboolean is_valid_path = TRUE; + + switch(argument->n) { + case UP: + if(gtk_tree_path_prev(path) == FALSE) { + is_valid_path = gtk_tree_path_up(path); + } else { /* row above */ + while(gtk_tree_view_row_expanded(tree_view, path)) { + gtk_tree_model_get_iter(model, &iter, path); + /* select last child */ + gtk_tree_model_iter_nth_child(model, &child_iter, &iter, + gtk_tree_model_iter_n_children(model, &iter)-1); + gtk_tree_path_free(path); + path = gtk_tree_model_get_path(model, &child_iter); + } + } + break; + case COLLAPSE: + if(!gtk_tree_view_collapse_row(tree_view, path) + && gtk_tree_path_get_depth(path) > 1) { + gtk_tree_path_up(path); + gtk_tree_view_collapse_row(tree_view, path); + } + break; + case DOWN: + if(gtk_tree_view_row_expanded(tree_view, path)) { + gtk_tree_path_down(path); + } else { + do { + gtk_tree_model_get_iter(model, &iter, path); + if (gtk_tree_model_iter_next(model, &iter)) { + path = gtk_tree_model_get_path(model, &iter); + break; + } + } while((is_valid_path = (gtk_tree_path_get_depth(path) > 1)) + && gtk_tree_path_up(path)); + } + break; + case EXPAND: + if(gtk_tree_view_expand_row(tree_view, path, FALSE)) { + gtk_tree_path_down(path); + } + break; + case SELECT: + cb_index_row_activated(tree_view, path, NULL, zathura); + return false; + } + + if (is_valid_path) { + gtk_tree_view_set_cursor(tree_view, path, NULL, FALSE); + } + + gtk_tree_path_free(path); + return false; } bool -sc_toggle_index(girara_session_t* session, girara_argument_t* argument, unsigned - int UNUSED(t)) +sc_toggle_index(girara_session_t* session, girara_argument_t* UNUSED(argument), + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); zathura_t* zathura = session->global.data; - g_return_val_if_fail(argument != NULL, false); - g_return_val_if_fail(zathura->document != NULL, false); + if (zathura->document == NULL) { + return false; + } girara_tree_node_t* document_index = NULL; GtkWidget* treeview = NULL; GtkTreeModel* model = NULL; GtkCellRenderer* renderer = NULL; + GtkCellRenderer* renderer2 = NULL; if (zathura->ui.index == NULL) { /* create new index widget */ @@ -257,13 +615,13 @@ sc_toggle_index(girara_session_t* session, girara_argument_t* argument, unsigned GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); /* create index */ - document_index = zathura_document_index_generate(zathura->document); + document_index = zathura_document_index_generate(zathura->document, NULL); if (document_index == NULL) { // TODO: Error message goto error_free; } - model = GTK_TREE_MODEL(gtk_tree_store_new(2, G_TYPE_STRING, G_TYPE_POINTER)); + model = GTK_TREE_MODEL(gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER)); if (model == NULL) { goto error_free; } @@ -280,27 +638,37 @@ sc_toggle_index(girara_session_t* session, girara_argument_t* argument, unsigned goto error_free; } + renderer2 = gtk_cell_renderer_text_new(); + if (renderer2 == NULL) { + goto error_free; + } + document_index_build(model, NULL, document_index); girara_node_free(document_index); /* setup widget */ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW (treeview), 0, "Title", renderer, "markup", 0, NULL); + gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW (treeview), 1, "Target", renderer2, "text", 1, NULL); + gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE); g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL); g_object_set(G_OBJECT(gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), 0)), "expand", TRUE, NULL); + gtk_tree_view_column_set_alignment(gtk_tree_view_get_column(GTK_TREE_VIEW(treeview), 1), 1.0f); gtk_tree_view_set_cursor(GTK_TREE_VIEW(treeview), gtk_tree_path_new_first(), NULL, FALSE); - /*g_signal_connect(G_OBJECT(treeview), "row-activated", G_CALLBACK(cb_index_row_activated), NULL); TODO*/ + g_signal_connect(G_OBJECT(treeview), "row-activated", G_CALLBACK(cb_index_row_activated), zathura); gtk_container_add(GTK_CONTAINER(zathura->ui.index), treeview); gtk_widget_show(treeview); } if (gtk_widget_get_visible(GTK_WIDGET(zathura->ui.index))) { - girara_set_view(session, zathura->ui.page_view); + girara_set_view(session, zathura->ui.page_widget_alignment); gtk_widget_hide(GTK_WIDGET(zathura->ui.index)); + girara_mode_set(zathura->ui.session, zathura->modes.normal); } else { girara_set_view(session, zathura->ui.index); gtk_widget_show(GTK_WIDGET(zathura->ui.index)); + girara_mode_set(zathura->ui.session, zathura->modes.index); } return false; @@ -323,15 +691,49 @@ error_ret: bool sc_toggle_fullscreen(girara_session_t* session, girara_argument_t* - UNUSED(argument), unsigned int UNUSED(t)) + UNUSED(argument), girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); + g_return_val_if_fail(session->global.data != NULL, false); + zathura_t* zathura = session->global.data; - static bool fullscreen = false; + static bool fullscreen = false; + static int pages_per_row = 1; + static double zoom = 1.0; - if (fullscreen) { + if (fullscreen == true) { + /* reset pages per row */ + girara_setting_set(session, "pages-per-row", &pages_per_row); + + /* show status bar */ + gtk_widget_show(GTK_WIDGET(session->gtk.statusbar)); + + /* set full screen */ gtk_window_unfullscreen(GTK_WINDOW(session->gtk.window)); + + /* reset scale */ + zathura->document->scale = zoom; + render_all(zathura); } else { + /* backup pages per row */ + girara_setting_get(session, "pages-per-row", &pages_per_row); + + /* set single view */ + int int_value = 1; + girara_setting_set(session, "pages-per-row", &int_value); + + /* back up zoom */ + zoom = zathura->document->scale; + + /* adjust window */ + girara_argument_t argument = { ADJUST_BESTFIT, NULL }; + sc_adjust_window(session, &argument, NULL, 0); + + /* hide status and inputbar */ + gtk_widget_hide(GTK_WIDGET(session->gtk.inputbar)); + gtk_widget_hide(GTK_WIDGET(session->gtk.statusbar)); + + /* set full screen */ gtk_window_fullscreen(GTK_WINDOW(session->gtk.window)); } @@ -341,24 +743,22 @@ sc_toggle_fullscreen(girara_session_t* session, girara_argument_t* } bool -sc_quit(girara_session_t* session, girara_argument_t* UNUSED(argument), unsigned - int UNUSED(t)) +sc_quit(girara_session_t* session, girara_argument_t* UNUSED(argument), + girara_event_t* UNUSED(event), unsigned int UNUSED(t)) { g_return_val_if_fail(session != NULL, false); girara_argument_t arg = { GIRARA_HIDE, NULL }; - girara_isc_completion(session, &arg, 0); + girara_isc_completion(session, &arg, NULL, 0); cb_destroy(NULL, NULL); - gtk_main_quit(); - return false; } bool -sc_zoom(girara_session_t* session, girara_argument_t* argument, unsigned int - UNUSED(t)) +sc_zoom(girara_session_t* session, girara_argument_t* argument, girara_event_t* + UNUSED(event), unsigned int t) { g_return_val_if_fail(session != NULL, false); g_return_val_if_fail(session->global.data != NULL, false); @@ -366,23 +766,43 @@ sc_zoom(girara_session_t* session, girara_argument_t* argument, unsigned int g_return_val_if_fail(argument != NULL, false); g_return_val_if_fail(zathura->document != NULL, false); + zathura->document->adjust_mode = ADJUST_NONE; + /* retreive zoom step value */ - int* value = girara_setting_get(zathura->ui.session, "zoom-step"); - if (value == NULL) { - return false; - } + int value = 1; + girara_setting_get(zathura->ui.session, "zoom-step", &value); - float zoom_step = *value / 100.0f; + float zoom_step = value / 100.0f; + float oldzoom = zathura->document->scale; + /* specify new zoom value */ if (argument->n == ZOOM_IN) { zathura->document->scale += zoom_step; } else if (argument->n == ZOOM_OUT) { zathura->document->scale -= zoom_step; + } else if (argument->n == ZOOM_SPECIFIC) { + zathura->document->scale = t / 100.0f; } else { zathura->document->scale = 1.0; } + /* zoom limitations */ + if (zathura->document->scale < 0.1f) { + zathura->document->scale = 0.1f; + } else if (zathura->document->scale > 10.0f) { + zathura->document->scale = 10.0f; + } + + /* keep position */ + GtkAdjustment* view_vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); + GtkAdjustment* view_hadjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); + gdouble valx = gtk_adjustment_get_value(view_hadjustment) / oldzoom * zathura->document->scale; + gdouble valy = gtk_adjustment_get_value(view_vadjustment) / oldzoom * zathura->document->scale; + set_adjustment(view_hadjustment, valx); + set_adjustment(view_vadjustment, valy); + render_all(zathura); return false; } + diff --git a/shortcuts.h b/shortcuts.h index 7123b08..1629d74 100644 --- a/shortcuts.h +++ b/shortcuts.h @@ -3,166 +3,204 @@ #ifndef SHORTCUTS_H #define SHORTCUTS_H -#include +#include /** * Abort the current action and return to normal mode * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_abort(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_abort(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Adjust the rendered pages to the window * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_adjust_window(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_adjust_window(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Change the current mode * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_change_mode(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_change_mode(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Follow a link * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_follow(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_follow(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Go to a specific page or position * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_goto(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_goto(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); + +/** + * Handle mouse events + * + * @param session The used girara session + * @param argument The used argument + * @param event Girara event + * @param t Number of executions + * @return true if no error occured otherwise false + */ +bool sc_mouse_scroll(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); + +/** + * Handle mouse zoom events + * + * @param session The used girara session + * @param argument The used argument + * @param event Girara event + * @param t Number of executions + * @return true if no error occured otherwise false + */ +bool sc_mouse_zoom(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Navigate through the document * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_navigate(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_navigate(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Recolor the pages * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_recolor(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_recolor(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Reload the current document * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_reload(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_reload(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Rotate the pages * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_rotate(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_rotate(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Scroll through the pages * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_scroll(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_scroll(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Search through the document for the latest search item * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_search(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_search(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Navigate through the index of the document * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_navigate_index(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_navigate_index(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Show/Hide the index of the document * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_toggle_index(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_toggle_index(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Toggle fullscreen mode * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_toggle_fullscreen(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_toggle_fullscreen(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Quit zathura * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_quit(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_quit(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); /** * Change the zoom level * * @param session The used girara session * @param argument The used argument + * @param event Girara event * @param t Number of executions * @return true if no error occured otherwise false */ -bool sc_zoom(girara_session_t* session, girara_argument_t* argument, unsigned int t); +bool sc_zoom(girara_session_t* session, girara_argument_t* argument, girara_event_t* event, unsigned int t); #endif // SHORTCUTS_H diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..2b29f27 --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +tests diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..272a112 --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,53 @@ +# See LICENSE file for license and copyright information + +include ../config.mk +include ../common.mk +include config.mk + +PROJECT = tests +SOURCE = tests.c $(wildcard test_*.c) +OBJECTS = ${SOURCE:.c=.o} + +ZSOURCE = $(shell find ../ -iname "*.c" -a ! -iname "database-*" ! -iname "main.c" ! -path "*tests*") +ZOBJECTS = ${ZSOURCE:.c=.o} + +ifeq (${DATABASE}, sqlite) +INCS += $(SQLITE_INC) +LIBS += $(SQLITE_LIB) +ZSOURCE += ../database-sqlite.c +else +ifeq (${DATABASE}, plain) +ZSOURCE += ../database-plain.c +endif +endif + +all: ${PROJECT} run + +run: ${PROJECT} + $(QUIET)./${PROJECT} + +options: + @echo ${PROJECT} build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "DFLAGS = ${DFLAGS}" + @echo "CC = ${CC}" + +%.o: %.c + $(ECHO) CC $< + @mkdir -p .depend + $(QUIET)${CC} -c -I.. ${CPPFLAGS} ${CFLAGS} -o $@ $< -MMD -MF .depend/$@.dep + +${PROJECT}: options ${OBJECTS} + $(QUIET)make -C .. + $(ECHO) CC -o $@ + $(QUIET)${CC} ${SFLAGS} ${LDFLAGS} -o $@ ${OBJECTS} ${ZOBJECTS} ${LIBS} + +${OBJECTS}: ../config.mk + +clean: + $(QUIET)rm -rf ${OBJECTS} ${PROJECT} + +.PHONY: all options clean debug + +-include $(wildcard .depend/*.dep) diff --git a/tests/config.mk b/tests/config.mk new file mode 100644 index 0000000..00e774d --- /dev/null +++ b/tests/config.mk @@ -0,0 +1,9 @@ +# See LICENSE file for license and copyright information + +CHECK_INC ?= $(shell pkg-config --cflags check) +CHECK_LIB ?= $(shell pkg-config --libs check) + +LIBS += ${CHECK_LIB} + +CFLAGS += -fprofile-arcs -ftest-coverage +LDFLAGS += -fprofile-arcs diff --git a/tests/test_utils.c b/tests/test_utils.c new file mode 100644 index 0000000..1d449f5 --- /dev/null +++ b/tests/test_utils.c @@ -0,0 +1,44 @@ +/* See LICENSE file for license and copyright information */ + +#include + +#include "../utils.h" + +START_TEST(test_file_exists_null) { + fail_unless(file_exists(NULL) == false); +} END_TEST + +START_TEST(test_file_get_extension_null) { + fail_unless(file_get_extension(NULL) == NULL); +} END_TEST + +START_TEST(test_file_get_extension_none) { + const char* path = "test"; + fail_unless(file_get_extension(path) == NULL); +} END_TEST + +START_TEST(test_file_get_extension_single) { + const char* path = "test.pdf"; + const char* extension = file_get_extension(path); + fail_unless(strcmp(extension, "pdf") == 0); +} END_TEST + +Suite* suite_utils() +{ + TCase* tcase = NULL; + Suite* suite = suite_create("Utils"); + + /* file exists */ + tcase = tcase_create("file_exists"); + tcase_add_test(tcase, test_file_exists_null); + suite_add_tcase(suite, tcase); + + /* file exists */ + tcase = tcase_create("file_get_extension"); + tcase_add_test(tcase, test_file_get_extension_null); + tcase_add_test(tcase, test_file_get_extension_none); + tcase_add_test(tcase, test_file_get_extension_single); + suite_add_tcase(suite, tcase); + + return suite; +} diff --git a/tests/tests.c b/tests/tests.c new file mode 100644 index 0000000..f6a8dd6 --- /dev/null +++ b/tests/tests.c @@ -0,0 +1,22 @@ +/* See LICENSE file for license and copyright information */ + +#include +#include + +#define UNUSED(x) GIRARA_UNUSED(x) + +Suite* suite_utils(); + +int main(int UNUSED(argc), char *UNUSED(argv[])) +{ + Suite* suite = NULL; + SRunner* suite_runner = NULL; + + /* test utils */ + suite = suite_utils(); + suite_runner = srunner_create(suite); + srunner_run_all(suite_runner, CK_NORMAL); + srunner_free(suite_runner); + + return 0; +} diff --git a/utils.c b/utils.c index 53de7d2..36c9cc1 100644 --- a/utils.c +++ b/utils.c @@ -7,11 +7,14 @@ #include #include #include +#include "math.h" #include "utils.h" #include "zathura.h" #include "document.h" +#include + #define BLOCK_SIZE 64 bool @@ -66,9 +69,9 @@ file_valid_extension(zathura_t* zathura, const char* path) result = true; break; } - GIRARA_LIST_FOREACH_END(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_t*, iter, mapping) + GIRARA_LIST_FOREACH_END(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_t*, iter, mapping); - g_free(content_type); + g_free((void*)content_type); return result; } @@ -149,96 +152,130 @@ execute_command(char* const argv[], char** output) } void -document_index_build(GtkTreeModel* UNUSED(model), GtkTreeIter* UNUSED(parent), - girara_tree_node_t* UNUSED(tree)) +document_index_build(GtkTreeModel* model, GtkTreeIter* parent, + girara_tree_node_t* tree) { - /*girara_list_t* list = girara_node_get_children(tree);*/ - /*girara_list_iterator_t* it = girara_list_iterator(list);*/ + girara_list_t* list = girara_node_get_children(tree); + GIRARA_LIST_FOREACH(list, girara_tree_node_t*, iter, node) + zathura_index_element_t* index_element = (zathura_index_element_t*)girara_node_get_data(node); - /*do {*/ - /*zathura_index_element_t* index_element = (zathura_index_element_t*) girara_list_iterator_data(it);*/ - /*} while ((it = girara_list_iterator_next(it)));*/ + gchar* description = NULL; + if (index_element->type == ZATHURA_LINK_TO_PAGE) { + description = g_strdup_printf("Page %d", index_element->target.page_number); + } else { + description = g_strdup(index_element->target.uri); + } + + GtkTreeIter tree_iter; + gtk_tree_store_append(GTK_TREE_STORE(model), &tree_iter, parent); + gtk_tree_store_set(GTK_TREE_STORE(model), &tree_iter, 0, index_element->title, 1, description, 2, index_element, -1); + g_object_weak_ref(G_OBJECT(model), (GWeakNotify) zathura_index_element_free, index_element); + g_free(description); + + if (girara_node_get_num_children(node) > 0) { + document_index_build(model, &tree_iter, node); + } + + GIRARA_LIST_FOREACH_END(list, gchar*, iter, name); } -char* -string_concat(const char* string1, ...) +void +page_calculate_offset(zathura_page_t* page, page_offset_t* offset) { - if(!string1) { - return NULL; - } - - va_list args; - char* s; - int l = strlen(string1) + 1; - - /* calculate length */ - va_start(args, string1); - - s = va_arg(args, char*); - - while(s) { - l += strlen(s); - s = va_arg(args, char*); - } - - va_end(args); - - /* prepare */ - char* c = malloc(sizeof(char) * l); - char* p = c; - - /* copy */ - char* d = p; - char* x = (char*) string1; - - do { - *d++ = *x; - } while (*x++ != '\0'); - - p = d - 1; - - va_start(args, string1); - - s = va_arg(args, char*); - - while(s) { - d = p; - x = s; - - do { - *d++ = *x; - } while (*x++ != '\0'); - - p = d - 1; - s = va_arg(args, char*); - } - - va_end(args); - - return c; -} - - -page_offset_t* -page_calculate_offset(zathura_page_t* page) -{ - if (page == NULL || page->document == NULL || page->document->zathura == NULL) { - return NULL; - } - - page_offset_t* offset = malloc(sizeof(page_offset_t)); - - if (offset == NULL) { - return NULL; - } - + g_return_if_fail(page != NULL && page->document != NULL && page->document->zathura != NULL && offset != NULL); zathura_document_t* document = page->document; zathura_t* zathura = document->zathura; - if (gtk_widget_translate_coordinates(page->event_box, zathura->ui.page_view, 0, 0, &(offset->x), &(offset->y)) == false) { - free(offset); - return NULL; + g_return_if_fail(gtk_widget_translate_coordinates(page->drawing_area, + zathura->ui.page_widget, 0, 0, &(offset->x), &(offset->y)) == true); +} + +zathura_rectangle_t rotate_rectangle(zathura_rectangle_t rectangle, unsigned int degree, int height, int width) +{ + zathura_rectangle_t tmp; + switch (degree) { + case 90: + tmp.x1 = height - rectangle.y2; + tmp.x2 = height - rectangle.y1; + tmp.y1 = rectangle.x1; + tmp.y2 = rectangle.x2; + break; + case 180: + tmp.x1 = width - rectangle.x2; + tmp.x2 = width - rectangle.x1; + tmp.y1 = height - rectangle.y2; + tmp.y2 = height - rectangle.y1; + break; + case 270: + tmp.x1 = rectangle.y1; + tmp.x2 = rectangle.y2; + tmp.y1 = width - rectangle.x2; + tmp.y2 = width - rectangle.x1; + break; + default: + tmp.x1 = rectangle.x1; + tmp.x2 = rectangle.x2; + tmp.y1 = rectangle.y1; + tmp.y2 = rectangle.y2; } - return offset; + return tmp; +} + +zathura_rectangle_t +recalc_rectangle(zathura_page_t* page, zathura_rectangle_t rectangle) +{ + if (page == NULL || page->document == NULL) { + return rectangle; + } + + zathura_rectangle_t tmp; + + switch (page->document->rotate) { + case 90: + tmp.x1 = (page->height - rectangle.y2) * page->document->scale; + tmp.x2 = (page->height - rectangle.y1) * page->document->scale; + tmp.y1 = rectangle.x1 * page->document->scale; + tmp.y2 = rectangle.x2 * page->document->scale; + break; + case 180: + tmp.x1 = (page->width - rectangle.x2) * page->document->scale; + tmp.x2 = (page->width - rectangle.x1) * page->document->scale; + tmp.y1 = (page->height - rectangle.y2) * page->document->scale; + tmp.y2 = (page->height - rectangle.y1) * page->document->scale; + break; + case 270: + tmp.x1 = rectangle.y1 * page->document->scale; + tmp.x2 = rectangle.y2 * page->document->scale; + tmp.y1 = (page->width - rectangle.x2) * page->document->scale; + tmp.y2 = (page->width - rectangle.x1) * page->document->scale; + break; + default: + tmp.x1 = rectangle.x1 * page->document->scale; + tmp.x2 = rectangle.x2 * page->document->scale; + tmp.y1 = rectangle.y1 * page->document->scale; + tmp.y2 = rectangle.y2 * page->document->scale; + } + + return tmp; +} + +void +set_adjustment(GtkAdjustment* adjustment, gdouble value) +{ + gtk_adjustment_set_value(adjustment, MAX(adjustment->lower, MIN(adjustment->upper - adjustment->page_size, value))); +} + +void +page_calc_height_width(zathura_page_t* page, unsigned int* page_height, unsigned int* page_width, bool rotate) +{ + g_return_if_fail(page != NULL && page_height != NULL && page_width != NULL); + + if (rotate && page->document->rotate % 180) { + *page_width = ceil(page->height * page->document->scale); + *page_height = ceil(page->width * page->document->scale); + } else { + *page_width = ceil(page->width * page->document->scale); + *page_height = ceil(page->height * page->document->scale); + } } diff --git a/utils.h b/utils.h index 3637b58..38124ca 100644 --- a/utils.h +++ b/utils.h @@ -5,10 +5,12 @@ #include #include -#include +#include #include "document.h" +#define LENGTH(x) (sizeof(x)/sizeof((x)[0])) + typedef struct page_offset_s { int x; @@ -55,28 +57,58 @@ bool execute_command(char* const argv[], char** output); * object. * * @param model The tree model - * @param tree_it The Tree iterator - * @param list_it The index list iterator + * @param parent The tree iterator parent + * @param tree The Tree iterator */ void document_index_build(GtkTreeModel* model, GtkTreeIter* parent, girara_tree_node_t* tree); -/** - * This function is used to concatenate multiple strings. Argument list has to - * be ended with NULL. Returned string has to be freed with free(). - * - * @param string1 First string - * @param ... Additional strings - * @return Concatenated string or NULL if an error occured - */ -char* string_concat(const char* string1, ...); - /** * Calculates the offset of the page to the top of the viewing area as * well as to the left side of it. The result has to be freed. * * @param page The Page + * @param offset Applied offset * @return The calculated offset or NULL if an error occured */ -page_offset_t* page_calculate_offset(zathura_page_t* page); +void page_calculate_offset(zathura_page_t* page, page_offset_t* offset); + +/** + * Rotate a rectangle by 0, 90, 180 or 270 degree + * @param rect the rectangle to rotate + * @param degree rotation degree + * @param height the height of the enclosing rectangle + * @param width the width of the enclosing rectangle + * @return the rotated rectangle + */ +zathura_rectangle_t rotate_rectangle(zathura_rectangle_t rectangle, unsigned int degree, int height, int width); + +/** + * Calculates the new coordinates based on the rotation and scale level of the + * document for the given rectangle + * + * @param page Page where the rectangle should be + * @param rectangle The rectangle + * @return New rectangle + */ +zathura_rectangle_t recalc_rectangle(zathura_page_t* page, zathura_rectangle_t rectangle); + +/** + * Set adjustment of a GtkAdjustment respecting its limits. + * @param adjust the GtkAdkustment instance + * @param value the new adjustment + */ +void set_adjustment(GtkAdjustment* adjust, gdouble value); + +/** + * Calculate the page size according to the corrent scaling and rotation if + * desired. + * @param page the page + * @param page_height the resulting page height + * @param page_width the resultung page width + * @param rotate honor page's rotation + */ +void +page_calc_height_width(zathura_page_t* page, unsigned int* page_height, unsigned int* page_width, bool rotate); + #endif // UTILS_H diff --git a/zathura.c b/zathura.c index 287b9fb..a90177f 100644 --- a/zathura.c +++ b/zathura.c @@ -1,8 +1,18 @@ /* See LICENSE file for license and copyright information */ -#include +#define _BSD_SOURCE +#define _XOPEN_SOURCE 700 -#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include #include "bookmarks.h" #include "callbacks.h" @@ -21,7 +31,7 @@ typedef struct zathura_document_info_s const char* password; } zathura_document_info_t; -gboolean document_info_open(gpointer data); +static gboolean document_info_open(gpointer data); /* function implementation */ zathura_t* @@ -30,12 +40,14 @@ zathura_init(int argc, char* argv[]) /* parse command line options */ GdkNativeWindow embed = 0; gchar* config_dir = NULL, *data_dir = NULL, *plugin_path = NULL; + bool forkback = false; GOptionEntry entries[] = { { "reparent", 'e', 0, G_OPTION_ARG_INT, &embed, "Reparents to window specified by xid", "xid" }, { "config-dir", 'c', 0, G_OPTION_ARG_FILENAME, &config_dir, "Path to the config directory", "path" }, { "data-dir", 'd', 0, G_OPTION_ARG_FILENAME, &data_dir, "Path to the data directory", "path" }, { "plugins-dir", 'p', 0, G_OPTION_ARG_STRING, &plugin_path, "Path to the directories containing plugins", "path" }, + { "fork", '\0', 0, G_OPTION_ARG_NONE, &forkback, "Fork into the background" , NULL }, { NULL, '\0', 0, 0, NULL, NULL, NULL } }; @@ -48,26 +60,31 @@ zathura_init(int argc, char* argv[]) printf("Error parsing command line arguments: %s\n", error->message); g_option_context_free(context); g_error_free(error); - goto error_free; + return NULL; } g_option_context_free(context); - zathura_t* zathura = malloc(sizeof(zathura_t)); + /* Fork into the background if the user really wants to ... */ + if (forkback == true) + { + int pid = fork(); + if (pid > 0) { /* parent */ + exit(0); + } else if (pid < 0) { /* error */ + printf("Error: couldn't fork."); + } - if (zathura == NULL) { - return NULL; + setsid(); } - /* general */ - zathura->document = NULL; + zathura_t* zathura = g_malloc0(sizeof(zathura_t)); /* plugins */ - zathura->plugins.plugins = girara_list_new(); - girara_list_set_free_function(zathura->plugins.plugins, zathura_document_plugin_free); - zathura->plugins.path = girara_list_new(); - girara_list_set_free_function(zathura->plugins.path, g_free); - zathura->plugins.type_plugin_mapping = girara_list_new(); - girara_list_set_free_function(zathura->plugins.type_plugin_mapping, zathura_type_plugin_mapping_free); + zathura->plugins.plugins = girara_list_new2( + (girara_free_function_t)zathura_document_plugin_free); + zathura->plugins.path = girara_list_new2(g_free); + zathura->plugins.type_plugin_mapping = girara_list_new2( + (girara_free_function_t)zathura_type_plugin_mapping_free); if (config_dir) { zathura->config.config_dir = g_strdup(config_dir); @@ -85,16 +102,20 @@ zathura_init(int argc, char* argv[]) g_free(path); } + /* create zathura (config/data) directory */ + g_mkdir_with_parents(zathura->config.config_dir, 0771); + g_mkdir_with_parents(zathura->config.data_dir, 0771); + if (plugin_path) { - gchar** paths = g_strsplit(plugin_path, ":", 0); - for (unsigned int i = 0; paths[i] != '\0'; ++i) { - girara_list_append(zathura->plugins.path, g_strdup(paths[i])); - } - g_strfreev(paths); + girara_list_t* paths = girara_split_path_array(plugin_path); + girara_list_merge(zathura->plugins.path, paths); + girara_list_free(paths); } else { - /* XXX: this shouldn't be hard coded! */ - girara_list_append(zathura->plugins.path, g_strdup("/usr/local/lib/zathura")); - girara_list_append(zathura->plugins.path, g_strdup("/usr/lib/zathura")); +#ifdef ZATHURA_PLUGINDIR + girara_list_t* paths = girara_split_path_array(ZATHURA_PLUGINDIR); + girara_list_merge(zathura->plugins.path, paths); + girara_list_free(paths); +#endif } /* UI */ @@ -106,7 +127,7 @@ zathura_init(int argc, char* argv[]) zathura->ui.statusbar.file = NULL; zathura->ui.statusbar.buffer = NULL; zathura->ui.statusbar.page_number = NULL; - zathura->ui.page_view = NULL; + zathura->ui.page_widget = NULL; zathura->ui.index = NULL; /* print settings */ @@ -123,35 +144,54 @@ zathura_init(int argc, char* argv[]) config_load_default(zathura); /* load global configuration files */ + girara_list_t* config_dirs = girara_split_path_array(girara_get_xdg_path(XDG_CONFIG_DIRS)); + ssize_t size = girara_list_size(config_dirs) - 1; + for (; size >= 0; --size) { + const char* dir = girara_list_nth(config_dirs, size); + char* file = g_build_filename(dir, ZATHURA_RC, NULL); + config_load_file(zathura, file); + g_free(file); + } + girara_list_free(config_dirs); + config_load_file(zathura, GLOBAL_RC); /* load local configuration files */ char* configuration_file = g_build_filename(zathura->config.config_dir, ZATHURA_RC, NULL); config_load_file(zathura, configuration_file); - free(configuration_file); + g_free(configuration_file); /* initialize girara */ zathura->ui.session->gtk.embed = embed; - if (girara_session_init(zathura->ui.session) == false) { + if (girara_session_init(zathura->ui.session, "zathura") == false) { goto error_out; } /* girara events */ - zathura->ui.session->events.buffer_changed = buffer_changed; + zathura->ui.session->events.buffer_changed = cb_buffer_changed; /* page view */ - zathura->ui.page_view = gtk_table_new(0, 0, TRUE); - if (!zathura->ui.page_view) { + zathura->ui.page_widget = gtk_table_new(0, 0, TRUE); + if (!zathura->ui.page_widget) { goto error_free; } + g_signal_connect(G_OBJECT(zathura->ui.session->gtk.view), "size-allocate", G_CALLBACK(cb_view_resized), zathura); + /* callbacks */ GtkAdjustment* view_vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); g_signal_connect(G_OBJECT(view_vadjustment), "value-changed", G_CALLBACK(cb_view_vadjustment_value_changed), zathura); GtkAdjustment* view_hadjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); g_signal_connect(G_OBJECT(view_hadjustment), "value-changed", G_CALLBACK(cb_view_vadjustment_value_changed), zathura); - gtk_widget_show(zathura->ui.page_view); + /* page view alignment */ + zathura->ui.page_widget_alignment = gtk_alignment_new(0.5, 0.5, 0, 0); + if (!zathura->ui.page_widget_alignment) { + goto error_free; + } + gtk_container_add(GTK_CONTAINER(zathura->ui.page_widget_alignment), zathura->ui.page_widget); + + gtk_widget_show(zathura->ui.page_widget); /* statusbar */ zathura->ui.statusbar.file = girara_statusbar_item_add(zathura->ui.session, TRUE, TRUE, TRUE, NULL); @@ -175,64 +215,69 @@ zathura_init(int argc, char* argv[]) g_signal_connect(G_OBJECT(zathura->ui.session->gtk.window), "destroy", G_CALLBACK(cb_destroy), NULL); /* save page padding */ - int* page_padding = girara_setting_get(zathura->ui.session, "page-padding"); - zathura->global.page_padding = (page_padding) ? *page_padding : 1; + zathura->global.page_padding = 1; + girara_setting_get(zathura->ui.session, "page-padding", &zathura->global.page_padding); - gtk_table_set_row_spacings(GTK_TABLE(zathura->ui.page_view), zathura->global.page_padding); - gtk_table_set_col_spacings(GTK_TABLE(zathura->ui.page_view), zathura->global.page_padding); + gtk_table_set_row_spacings(GTK_TABLE(zathura->ui.page_widget), zathura->global.page_padding); + gtk_table_set_col_spacings(GTK_TABLE(zathura->ui.page_widget), zathura->global.page_padding); /* parse colors */ - char* string_value = girara_setting_get(zathura->ui.session, "recolor-dark-color"); + char* string_value = NULL; + girara_setting_get(zathura->ui.session, "recolor-darkcolor", &string_value); if (string_value != NULL) { gdk_color_parse(string_value, &(zathura->ui.colors.recolor_dark_color)); - free(string_value); + g_free(string_value); } - string_value = girara_setting_get(zathura->ui.session, "recolor-light-color"); + string_value = NULL; + girara_setting_get(zathura->ui.session, "recolor-lightcolor", &string_value); if (string_value != NULL) { gdk_color_parse(string_value, &(zathura->ui.colors.recolor_light_color)); - free(string_value); + g_free(string_value); + } + + string_value = NULL; + girara_setting_get(zathura->ui.session, "highlight-color", &string_value); + if (string_value != NULL) { + gdk_color_parse(string_value, &(zathura->ui.colors.highlight_color)); + g_free(string_value); } /* database */ - char* database_path = g_build_filename(zathura->config.data_dir, "bookmarks.sqlite", NULL); - zathura->database = zathura_db_init(database_path); + zathura->database = zathura_db_init(zathura->config.data_dir); if (zathura->database == NULL) { - girara_error("Unable to inizialize database. Bookmarks won't be available."); + girara_error("Unable to initialize database. Bookmarks won't be available."); } - g_free(database_path); /* bookmarks */ - zathura->bookmarks.bookmarks = girara_list_new(); - girara_list_set_free_function(zathura->bookmarks.bookmarks, (girara_free_function_t) zathura_bookmark_free); + zathura->bookmarks.bookmarks = girara_sorted_list_new2((girara_compare_function_t) zathura_bookmarks_compare, + (girara_free_function_t) zathura_bookmark_free); /* open document if passed */ if (argc > 1) { - zathura_document_info_t* document_info = malloc(sizeof(zathura_document_info_t)); + zathura_document_info_t* document_info = g_malloc0(sizeof(zathura_document_info_t)); - if (document_info != NULL) { - document_info->zathura = zathura; - document_info->path = argv[1]; - document_info->password = (argc >= 2) ? argv[2] : NULL; - g_idle_add(document_info_open, document_info); - } + document_info->zathura = zathura; + document_info->path = argv[1]; + document_info->password = (argc >= 2) ? argv[2] : NULL; + g_idle_add(document_info_open, document_info); } return zathura; error_free: - if (zathura->ui.page_view) { - g_object_unref(zathura->ui.page_view); + if (zathura->ui.page_widget) { + g_object_unref(zathura->ui.page_widget); } - girara_session_destroy(zathura->ui.session); - girara_list_free(zathura->bookmarks.bookmarks); - zathura_db_free(zathura->database); + if (zathura->ui.page_widget_alignment) { + g_object_unref(zathura->ui.page_widget_alignment); + } error_out: - free(zathura); + zathura_free(zathura); return NULL; } @@ -244,11 +289,17 @@ zathura_free(zathura_t* zathura) return; } + document_close(zathura); + if (zathura->ui.session != NULL) { girara_session_destroy(zathura->ui.session); } - document_close(zathura); + /* stdin support */ + if (zathura->stdin_support.file != NULL) { + g_unlink(zathura->stdin_support.file); + g_free(zathura->stdin_support.file); + } /* bookmarks */ girara_list_free(zathura->bookmarks.bookmarks); @@ -272,22 +323,91 @@ zathura_free(zathura_t* zathura) /* free config variables */ g_free(zathura->config.config_dir); g_free(zathura->config.data_dir); + + g_free(zathura); } -gboolean +static gchar* +prepare_document_open_from_stdin(zathura_t* zathura) +{ + g_return_val_if_fail(zathura, NULL); + + GError* error = NULL; + gchar* file = NULL; + gint handle = g_file_open_tmp("zathura.stdin.XXXXXX", &file, &error); + if (handle == -1) + { + if (error != NULL) { + girara_error("Can not create temporary file: %s", error->message); + g_error_free(error); + } + return NULL; + } + + // read from stdin and dump to temporary file + int stdinfno = fileno(stdin); + if (stdinfno == -1) + { + girara_error("Can not read from stdin."); + close(handle); + g_unlink(file); + g_free(file); + return NULL; + } + + char buffer[BUFSIZ]; + ssize_t count = 0; + while ((count = read(stdinfno, buffer, BUFSIZ)) > 0) + { + if (write(handle, buffer, count) != count) + { + girara_error("Can not write to temporary file: %s", file); + close(handle); + g_unlink(file); + g_free(file); + return NULL; + } + } + close(handle); + + if (count != 0) + { + girara_error("Can not read from stdin."); + g_unlink(file); + g_free(file); + return NULL; + } + + return file; +} + +static gboolean document_info_open(gpointer data) { zathura_document_info_t* document_info = data; g_return_val_if_fail(document_info != NULL, FALSE); - if (document_info->zathura == NULL || document_info->path == NULL) { - free(document_info); - return FALSE; + if (document_info->zathura != NULL && document_info->path != NULL) { + char* file = NULL; + if (g_strcmp0(document_info->path, "-") == 0) { + file = prepare_document_open_from_stdin(document_info->zathura); + if (file == NULL) { + girara_notify(document_info->zathura->ui.session, GIRARA_ERROR, + "Could not read file from stdin and write it to a temporary file."); + } else { + document_info->zathura->stdin_support.file = g_strdup(file); + } + } else { + file = g_strdup(document_info->path); + } + + if (file != NULL) { + document_open(document_info->zathura, file, document_info->password); + g_free(file); + } } - document_open(document_info->zathura, document_info->path, document_info->password); - free(document_info); - + g_free(document_info); return FALSE; } @@ -307,12 +427,11 @@ document_open(zathura_t* zathura, const char* path, const char* password) zathura->document = document; /* view mode */ - int* value = girara_setting_get(zathura->ui.session, "pages-per-row"); - int pages_per_row = (value) ? *value : 1; - free(value); - page_view_set_mode(zathura, pages_per_row); + int pages_per_row = 1; + girara_setting_get(zathura->ui.session, "pages-per-row", &pages_per_row); + page_widget_set_mode(zathura, pages_per_row); - girara_set_view(zathura->ui.session, zathura->ui.page_view); + girara_set_view(zathura->ui.session, zathura->ui.page_widget_alignment); /* threads */ zathura->sync.render_thread = render_init(zathura); @@ -324,7 +443,7 @@ document_open(zathura_t* zathura, const char* path, const char* password) /* create blank pages */ for (unsigned int page_id = 0; page_id < document->number_of_pages; page_id++) { zathura_page_t* page = document->pages[page_id]; - gtk_widget_realize(page->event_box); + gtk_widget_realize(page->drawing_area); } /* bookmarks */ @@ -332,6 +451,9 @@ document_open(zathura_t* zathura, const char* path, const char* password) girara_warning("Failed to load bookmarks for %s.\n", zathura->document->file_path); } + page_set_delayed(zathura, document->current_page_number - 1); + cb_view_vadjustment_value_changed(NULL, zathura); + return true; error_free: @@ -353,9 +475,8 @@ document_save(zathura_t* zathura, const char* path, bool overwrite) gchar* file_path = girara_fix_path(path); if (!overwrite && g_file_test(file_path, G_FILE_TEST_EXISTS)) { - gchar* message = g_strdup_printf("File already exists: %s. Use :write! to overwrite it.", file_path); - girara_error(message); - g_free(message); + girara_error("File already exists: %s. Use :write! to overwrite it.", file_path); + g_free(file_path); return false; } @@ -381,23 +502,65 @@ document_close(zathura_t* zathura) return false; } + /* store last seen page */ + zathura_db_set_fileinfo(zathura->database, zathura->document->file_path, zathura->document->current_page_number, + /* zathura->document->offset TODO */ 0, zathura->document->scale, + zathura->document->rotate); + render_free(zathura->sync.render_thread); zathura->sync.render_thread = NULL; - gtk_container_foreach(GTK_CONTAINER(zathura->ui.page_view), remove_page_from_table, (gpointer)1); + gtk_container_foreach(GTK_CONTAINER(zathura->ui.page_widget), remove_page_from_table, (gpointer)1); zathura_document_free(zathura->document); zathura->document = NULL; - gtk_widget_hide_all(zathura->ui.page_view); + gtk_widget_hide(zathura->ui.page_widget); + statusbar_page_number_update(zathura); + + if (zathura->ui.session != NULL && zathura->ui.statusbar.file != NULL) { + girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.file, "[No name]"); + } + + return true; +} + +typedef struct page_set_delayed_s +{ + zathura_t* zathura; + unsigned int page; +} page_set_delayed_t; + +static gboolean +page_set_delayed_impl(gpointer data) +{ + page_set_delayed_t* p = data; + page_set(p->zathura, p->page); + + g_free(p); + return FALSE; +} + +bool +page_set_delayed(zathura_t* zathura, unsigned int page_id) +{ + if (zathura == NULL || zathura->document == NULL || zathura->document->pages == NULL || + page_id >= zathura->document->number_of_pages) { + return false; + } + + page_set_delayed_t* p = g_malloc(sizeof(page_set_delayed_t)); + p->zathura = zathura; + p->page = page_id; + g_idle_add(page_set_delayed_impl, p); return true; } bool page_set(zathura_t* zathura, unsigned int page_id) { - if (!zathura->document || !zathura->document->pages) { + if (zathura == NULL || zathura->document == NULL || zathura->document->pages == NULL) { goto error_out; } @@ -412,19 +575,13 @@ page_set(zathura_t* zathura, unsigned int page_id) goto error_out; } - page_offset_t* offset = page_calculate_offset(page); - if (offset == NULL) { - goto error_out; - } + page_offset_t offset; + page_calculate_offset(page, &offset); GtkAdjustment* view_vadjustment = gtk_scrolled_window_get_vadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); GtkAdjustment* view_hadjustment = gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(zathura->ui.session->gtk.view)); - gtk_adjustment_set_value(view_hadjustment, offset->x); - gtk_adjustment_set_value(view_vadjustment, offset->y); - - /* update page number */ - zathura->document->current_page_number = page_id; - statusbar_page_number_update(zathura); + set_adjustment(view_hadjustment, offset.x); + set_adjustment(view_vadjustment, offset.y); return true; @@ -440,13 +597,17 @@ statusbar_page_number_update(zathura_t* zathura) return; } - char* page_number_text = g_strdup_printf("[%d/%d]", zathura->document->current_page_number + 1, zathura->document->number_of_pages); - girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.page_number, page_number_text); - g_free(page_number_text); + if (zathura->document != NULL) { + char* page_number_text = g_strdup_printf("[%d/%d]", zathura->document->current_page_number + 1, zathura->document->number_of_pages); + girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.page_number, page_number_text); + g_free(page_number_text); + } else { + girara_statusbar_item_set_text(zathura->ui.session, zathura->ui.statusbar.page_number, ""); + } } void -page_view_set_mode(zathura_t* zathura, unsigned int pages_per_row) +page_widget_set_mode(zathura_t* zathura, unsigned int pages_per_row) { /* show at least one page */ if (pages_per_row == 0) { @@ -457,37 +618,15 @@ page_view_set_mode(zathura_t* zathura, unsigned int pages_per_row) return; } - gtk_container_foreach(GTK_CONTAINER(zathura->ui.page_view), remove_page_from_table, (gpointer)0); + gtk_container_foreach(GTK_CONTAINER(zathura->ui.page_widget), remove_page_from_table, (gpointer)0); - gtk_table_resize(GTK_TABLE(zathura->ui.page_view), zathura->document->number_of_pages / pages_per_row + 1, pages_per_row); + gtk_table_resize(GTK_TABLE(zathura->ui.page_widget), ceil(zathura->document->number_of_pages / pages_per_row), pages_per_row); for (unsigned int i = 0; i < zathura->document->number_of_pages; i++) { int x = i % pages_per_row; int y = i / pages_per_row; - gtk_table_attach(GTK_TABLE(zathura->ui.page_view), zathura->document->pages[i]->event_box, x, x + 1, y, y + 1, GTK_EXPAND, GTK_EXPAND, 0, 0); + gtk_table_attach(GTK_TABLE(zathura->ui.page_widget), zathura->document->pages[i]->drawing_area, x, x + 1, y, y + 1, GTK_SHRINK, GTK_SHRINK, 0, 0); } - gtk_widget_show_all(zathura->ui.page_view); -} - -/* main function */ -int main(int argc, char* argv[]) -{ - g_thread_init(NULL); - gdk_threads_init(); - gtk_init(&argc, &argv); - - zathura_t* zathura = zathura_init(argc, argv); - if (zathura == NULL) { - printf("error: coult not initialize zathura\n"); - return -1; - } - - gdk_threads_enter(); - gtk_main(); - gdk_threads_leave(); - - zathura_free(zathura); - - return 0; + gtk_widget_show_all(zathura->ui.page_widget); } diff --git a/zathura.desktop b/zathura.desktop index 470bf8f..b2b7807 100644 --- a/zathura.desktop +++ b/zathura.desktop @@ -2,9 +2,9 @@ Version=1.0 Type=Application Name=Zathura -Comment=A minimalistic PDF viewer -Comment[de]=Ein minimalistischer PDF-Betrachter +Comment=A minimalistic document viewer +Comment[de]=Ein minimalistischer Dokumenten-Betrachter Exec=zathura %f Terminal=false Categories=Office;Viewer; -MimeType=application/pdf; +MimeType=application/pdf;application/postscript;image/vnd.djvu; diff --git a/zathura.h b/zathura.h index 163d094..c769474 100644 --- a/zathura.h +++ b/zathura.h @@ -4,16 +4,11 @@ #define ZATHURA_H #include -#include +#include +#include +#include -#ifdef UNUSED -#elif defined(__GNUC__) -# define UNUSED(x) UNUSED_ ## x __attribute__((unused)) -#elif defined(__LCINT__) -# define UNUSED(x) /*@unused@*/ x -#else -# define UNUSED(x) x -#endif +#define UNUSED(x) GIRARA_UNUSED(x) enum { NEXT, PREVIOUS, LEFT, RIGHT, UP, DOWN, BOTTOM, TOP, HIDE, HIGHLIGHT, DELETE_LAST_WORD, DELETE_LAST_CHAR, DEFAULT, ERROR, WARNING, NEXT_GROUP, @@ -41,71 +36,78 @@ typedef struct zathura_s { struct { - girara_session_t* session; /**> girara interface session */ + girara_session_t* session; /**< girara interface session */ struct { - girara_statusbar_item_t* buffer; /**> buffer statusbar entry */ - girara_statusbar_item_t* file; /**> file statusbar entry */ - girara_statusbar_item_t* page_number; /**> page number statusbar entry */ + girara_statusbar_item_t* buffer; /**< buffer statusbar entry */ + girara_statusbar_item_t* file; /**< file statusbar entry */ + girara_statusbar_item_t* page_number; /**< page number statusbar entry */ } statusbar; struct { - GdkColor recolor_dark_color; /**> Dark color for recoloring */ - GdkColor recolor_light_color; /**> Light color for recoloring */ + GdkColor recolor_dark_color; /**< Dark color for recoloring */ + GdkColor recolor_light_color; /**< Light color for recoloring */ + GdkColor highlight_color; /**< Color for highlighting */ } colors; - GtkWidget *page_view; /**> Widget that contains all rendered pages */ - GtkWidget *index; /**> Widget to show the index of the document */ + GtkWidget *page_widget_alignment; + GtkWidget *page_widget; /**< Widget that contains all rendered pages */ + GtkWidget *index; /**< Widget to show the index of the document */ } ui; struct { - render_thread_t* render_thread; /**> The thread responsible for rendering the pages */ + render_thread_t* render_thread; /**< The thread responsible for rendering the pages */ } sync; struct { - girara_list_t* plugins; /**> List of plugins */ - girara_list_t* path; /**> List of plugin paths */ - girara_list_t* type_plugin_mapping; /**> List of type -> plugin mappings */ + girara_list_t* plugins; /**< List of plugins */ + girara_list_t* path; /**< List of plugin paths */ + girara_list_t* type_plugin_mapping; /**< List of type -> plugin mappings */ } plugins; struct { - gchar* config_dir; /**> Path to the configuration directory */ - gchar* data_dir; /**> Path to the data directory */ + gchar* config_dir; /**< Path to the configuration directory */ + gchar* data_dir; /**< Path to the data directory */ } config; struct { - GtkPrintSettings* settings; /**> Print settings */ - GtkPageSetup* page_setup; /**> Saved page setup */ + GtkPrintSettings* settings; /**< Print settings */ + GtkPageSetup* page_setup; /**< Saved page setup */ } print; struct { - unsigned int page_padding; /**> Padding between the pages */ - bool recolor; /**> Recoloring mode switch */ + unsigned int page_padding; /**< Padding between the pages */ + bool recolor; /**< Recoloring mode switch */ } global; struct { - girara_mode_t normal; /**> Normal mode */ - girara_mode_t fullscreen; /**> Fullscreen mode */ - girara_mode_t index; /**> Index mode */ - girara_mode_t insert; /**> Insert mode */ + girara_mode_t normal; /**< Normal mode */ + girara_mode_t fullscreen; /**< Fullscreen mode */ + girara_mode_t index; /**< Index mode */ + girara_mode_t insert; /**< Insert mode */ } modes; struct { - gchar* file; /**> bookmarks file */ - girara_list_t* bookmarks; /**> bookmarks */ + gchar* file; /**< bookmarks file */ + girara_list_t* bookmarks; /**< bookmarks */ } bookmarks; - zathura_document_t* document; /**> The current document */ - zathura_database_t* database; /**> The database */ + struct + { + gchar* file; + } stdin_support; + + zathura_document_t* document; /**< The current document */ + zathura_database_t* database; /**< The database */ } zathura_t; /** @@ -158,17 +160,27 @@ bool document_close(zathura_t* zathura); * Opens the page with the given number * * @param zathura The zathura session + * @param page_id The id of the page that should be set * @return If no error occured true, otherwise false, is returned. */ bool page_set(zathura_t* zathura, unsigned int page_id); +/** + * Opens the page with the given number (delayed) + * + * @param zathura The zathura session + * @param page_id The id of the page that should be set + * @return If no error occured true, otherwise false, is returned. + */ +bool page_set_delayed(zathura_t* zathura, unsigned int page_id); + /** * Builds the box structure to show the rendered pages * * @param zathura The zathura session * @param pages_per_row Number of shown pages per row */ -void page_view_set_mode(zathura_t* zathura, unsigned int pages_per_row); +void page_widget_set_mode(zathura_t* zathura, unsigned int pages_per_row); /** * Updates the page number in the statusbar. Note that 1 will be added to the diff --git a/zathurarc.5.rst b/zathurarc.5.rst index 4070cd6..7449e73 100644 --- a/zathurarc.5.rst +++ b/zathurarc.5.rst @@ -6,188 +6,268 @@ zathura configuration file -------------------------- -:Author: Sebastian Ramacher -:Date: 19.8.2010 +:Author: pwmt.org +:Date: 02.02.2012 :Manual section: 5 SYNOPOSIS ========= -/etc/zathurarc, ~/.config/zathura/zathurarc +/etc/zathurarc, $XDG_CONFIG_HOME/zathura/zathurarc DESCRIPTION =========== -The zathurarc contains various options controlling the behavior of zathura. One -can use the ``set`` and ``map`` commands: +The zathurarc file is a simple plain text file that can be populated with +various commands to change the behaviour and the look of zathura which we are +going to describe in the following subsections. Each line (besides empty lines +and comments (which start with a prepended #)) is evaluated on its own, so it is +not possible to write multiple commands in one single line. -* ``set`` [id] [value] -* ``map`` [key] [function] [argument] [mode] +The following commands can be used: -They behave the same as the ``set`` and ``map`` commands in zathura. Any line -not starting with ``set`` or ``map`` will be ignored. +set - Changing the options +-------------------------- -set ---- +In addition to the build-in :set command zathura offers more options to be +changed and makes those changes permanent. To overwrite an option you just have +to add a line structured like the following: -[id] and the corresponding [value] can be one of +:: + set