summaryrefslogtreecommitdiff
path: root/platform/gl
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2018-02-21 22:00:10 +0100
committerRobin Watts <robin.watts@artifex.com>2018-06-22 16:48:46 +0100
commit41050d282409f86f425ac4a157fc07d889e4b54d (patch)
treeff0611853ff17214a3c6e3e472b7ee2dfb425107 /platform/gl
parentbde703f83b83232f830426b45957b10e4e44043d (diff)
downloadmupdf-41050d282409f86f425ac4a157fc07d889e4b54d.tar.xz
gl: Add annotation editor.
* Select with right mouse, edit with left mouse. * Clamp movement to page area. * Integrate page scroll offset into matrices. * Edit Ink annotations. * Initialise important annotation properties when creating them. * Deselect annotation when pressing ESC. * Add polygon/polyline annotation editing. * Tweak polygon editing. * Edit square/circle annotations. * Cancel canvas edits on right click. * Expand selectable areas and grab zones for rects. * Edit line annotations. * Edit caret annotations. * Return true when value has changed (checkbox and slider). * Add popup menu widget: Menu items are drawn at ui_end to overlay other widgets properly. * Use popup menu to set icons. * Fix text selection translation offset. * Edit quad point annotations. * Set user and modification date when creating new annotations. * Add select widget. * Edit line endings. * Edit highlight opacity. * Create properly sized stamp annotations. * Edit FreeText annotations. * Trigger list selection on mouse-up instead of mouse-down. * Use index in ui_select. * Edit Freetext quadding. * Fix windows build issues. * Use 'const void *' for ids. * Add file dialog to choose file when none given on command line. * Add save file dialog. * Add pdfwrite options to save dialog. * Add error dialog instead of dying silently on exceptions. * Add password dialog. * Add warning dialog that does not exit the program. * Show in title bar when document is modified. * Separate motion and passive motion callbacks. * Add /Volumes 'disk' for MacOS X mount points. * Tweak input focus/blur handling. * Use popup menu to create annotations instead of big list of buttons. * Update appearance after canvas edits too. * Release old grab before checking for new grab and taking focus. * Set cursor shape depending on hot item. * Draw prettier widgets. * Use integers for slider to allow snapping to values. * Add 'ui.gridsize' to ease layout of buttons and text fields. * Tweak file dialog layout. * Bevels around lists and scroll bars. * Only add new points to the ink list when drawing. * Use named color constants instead of hardcoding color values. * Adjust layout and which properties to edit for each annotation. * Use a panel for search field. * Add splitter, dialogs, and panel padding. * Popup menus above the button if they don't fit below it. * Use triangle strip to draw check mark.
Diffstat (limited to 'platform/gl')
-rw-r--r--platform/gl/gl-annotate.c1123
-rw-r--r--platform/gl/gl-app.h77
-rw-r--r--platform/gl/gl-file.c506
-rw-r--r--platform/gl/gl-input.c49
-rw-r--r--platform/gl/gl-main.c568
-rw-r--r--platform/gl/gl-ui.c432
-rw-r--r--platform/gl/gl-win32.c19
7 files changed, 2433 insertions, 341 deletions
diff --git a/platform/gl/gl-annotate.c b/platform/gl/gl-annotate.c
new file mode 100644
index 00000000..f23de5ba
--- /dev/null
+++ b/platform/gl/gl-annotate.c
@@ -0,0 +1,1123 @@
+#include "gl-app.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+#include <limits.h>
+
+#ifndef PATH_MAX
+#define PATH_MAX 2048
+#endif
+
+static char save_filename[PATH_MAX];
+static pdf_write_options save_opts;
+
+static int pdf_filter(const char *fn)
+{
+ const char *suffix = strrchr(fn, '.');
+ if (suffix && !fz_strcasecmp(suffix, ".pdf"))
+ return 1;
+ return 0;
+}
+
+static void init_save_pdf_options(void)
+{
+ memset(&save_opts, 0, sizeof save_opts);
+ save_opts.do_garbage = 1;
+}
+
+static void save_pdf_options(void)
+{
+ ui_layout(T, X, NW, 2, 2);
+ ui_label("PDF write options:");
+ ui_layout(T, X, NW, 4, 2);
+ ui_checkbox("Incremental", &save_opts.do_incremental);
+ ui_checkbox("Pretty-print", &save_opts.do_pretty);
+ ui_checkbox("Ascii", &save_opts.do_ascii);
+ ui_checkbox("Compress", &save_opts.do_compress);
+ ui_checkbox("Compress images", &save_opts.do_compress_images);
+ ui_checkbox("Compress fonts", &save_opts.do_compress_fonts);
+ ui_checkbox("Decompress", &save_opts.do_decompress);
+ ui_checkbox("Garbage collect", &save_opts.do_garbage);
+ ui_checkbox("Linearize", &save_opts.do_linear);
+ ui_checkbox("Clean syntax", &save_opts.do_clean);
+ ui_checkbox("Sanitize syntax", &save_opts.do_sanitize);
+}
+
+static void save_pdf_dialog(void)
+{
+ if (ui_save_file(save_filename, save_pdf_options))
+ {
+ ui.dialog = NULL;
+ if (save_filename[0] != 0)
+ {
+ if (save_opts.do_garbage)
+ save_opts.do_garbage = 2;
+ fz_try(ctx)
+ {
+ pdf_save_document(ctx, pdf, save_filename, &save_opts);
+ fz_strlcpy(filename, save_filename, PATH_MAX);
+ update_title();
+ }
+ fz_catch(ctx)
+ {
+ ui_show_warning_dialog(fz_caught_message(ctx));
+ }
+ }
+ }
+}
+
+static int rects_differ(const fz_rect *a, const fz_rect *b, float threshold)
+{
+ if (fz_abs(a->x0 - b->x0) > threshold) return 1;
+ if (fz_abs(a->y0 - b->y0) > threshold) return 1;
+ if (fz_abs(a->x1 - b->x1) > threshold) return 1;
+ if (fz_abs(a->y1 - b->y1) > threshold) return 1;
+ return 0;
+}
+
+static int points_differ(const fz_point *a, const fz_point *b, float threshold)
+{
+ if (fz_abs(a->x - b->x) > threshold) return 1;
+ if (fz_abs(a->y - b->y) > threshold) return 1;
+ return 0;
+}
+
+static const char *getuser(void)
+{
+ const char *u;
+ u = getenv("USER");
+ if (!u) u = getenv("USERNAME");
+ if (!u) u = "user";
+ return u;
+}
+
+static void new_annot(int type)
+{
+ static const float black[3] = { 0, 0, 0 };
+ static const float red[3] = { 1, 0, 0 };
+ static const float green[3] = { 0, 1, 0 };
+ static const float blue[3] = { 0, 0, 1 };
+ static const float yellow[3] = { 1, 1, 0 };
+ static const float magenta[3] = { 1, 0, 1 };
+
+ selected_annot = pdf_create_annot(ctx, page, type);
+
+ pdf_set_annot_modification_date(ctx, selected_annot, time(NULL));
+ if (pdf_annot_has_author(ctx, selected_annot))
+ pdf_set_annot_author(ctx, selected_annot, getuser());
+
+ switch (type)
+ {
+ case PDF_ANNOT_TEXT:
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ case PDF_ANNOT_SOUND:
+ {
+ fz_rect icon_rect = { 12, 12, 12+20, 12+20 };
+ pdf_set_annot_rect(ctx, selected_annot, &icon_rect);
+ pdf_set_annot_color(ctx, selected_annot, 3, yellow);
+ }
+ break;
+
+ case PDF_ANNOT_FREE_TEXT:
+ {
+ fz_rect text_rect = { 12, 12, 12+200, 12+100 };
+ pdf_set_annot_rect(ctx, selected_annot, &text_rect);
+ pdf_set_annot_border(ctx, selected_annot, 0);
+ pdf_set_annot_default_appearance(ctx, selected_annot, "Helv", 12, black);
+ }
+ break;
+
+ case PDF_ANNOT_STAMP:
+ {
+ fz_rect stamp_rect = { 12, 12, 12+190, 12+50 };
+ pdf_set_annot_rect(ctx, selected_annot, &stamp_rect);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ }
+ break;
+
+ case PDF_ANNOT_CARET:
+ {
+ fz_rect caret_rect = { 12, 12, 12+18, 12+15 };
+ pdf_set_annot_rect(ctx, selected_annot, &caret_rect);
+ pdf_set_annot_color(ctx, selected_annot, 3, blue);
+ }
+ break;
+
+ case PDF_ANNOT_LINE:
+ {
+ fz_point a = { 12, 12 }, b = { 12 + 100, 12 + 50 };
+ pdf_set_annot_line(ctx, selected_annot, a, b);
+ pdf_set_annot_border(ctx, selected_annot, 1);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ }
+ break;
+
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ {
+ fz_rect shape_rect = { 12, 12, 12+100, 12+50 };
+ pdf_set_annot_rect(ctx, selected_annot, &shape_rect);
+ pdf_set_annot_border(ctx, selected_annot, 1);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ }
+ break;
+
+ case PDF_ANNOT_POLYGON:
+ case PDF_ANNOT_POLY_LINE:
+ case PDF_ANNOT_INK:
+ pdf_set_annot_border(ctx, selected_annot, 1);
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ break;
+
+ case PDF_ANNOT_HIGHLIGHT:
+ pdf_set_annot_color(ctx, selected_annot, 3, yellow);
+ break;
+ case PDF_ANNOT_UNDERLINE:
+ pdf_set_annot_color(ctx, selected_annot, 3, green);
+ break;
+ case PDF_ANNOT_STRIKE_OUT:
+ pdf_set_annot_color(ctx, selected_annot, 3, red);
+ break;
+ case PDF_ANNOT_SQUIGGLY:
+ pdf_set_annot_color(ctx, selected_annot, 3, magenta);
+ break;
+ }
+
+ pdf_update_appearance(ctx, selected_annot);
+ render_page();
+}
+
+static void do_annotate_flags(void)
+{
+ char buf[4096];
+ int f = pdf_annot_flags(ctx, selected_annot);
+ fz_strlcpy(buf, "Flags:", sizeof buf);
+ if (f & PDF_ANNOT_IS_INVISIBLE) fz_strlcat(buf, " inv", sizeof buf);
+ if (f & PDF_ANNOT_IS_HIDDEN) fz_strlcat(buf, " hidden", sizeof buf);
+ if (f & PDF_ANNOT_IS_PRINT) fz_strlcat(buf, " print", sizeof buf);
+ if (f & PDF_ANNOT_IS_NO_ZOOM) fz_strlcat(buf, " nz", sizeof buf);
+ if (f & PDF_ANNOT_IS_NO_ROTATE) fz_strlcat(buf, " nr", sizeof buf);
+ if (f & PDF_ANNOT_IS_NO_VIEW) fz_strlcat(buf, " nv", sizeof buf);
+ if (f & PDF_ANNOT_IS_READ_ONLY) fz_strlcat(buf, " ro", sizeof buf);
+ if (f & PDF_ANNOT_IS_LOCKED) fz_strlcat(buf, " lock", sizeof buf);
+ if (f & PDF_ANNOT_IS_TOGGLE_NO_VIEW) fz_strlcat(buf, " tnv", sizeof buf);
+ if (f & PDF_ANNOT_IS_LOCKED_CONTENTS) fz_strlcat(buf, " lc", sizeof buf);
+ if (!f) fz_strlcat(buf, " none", sizeof buf);
+ ui_label("%s", buf);
+}
+
+static const char *color_names[] = {
+ "None",
+ "Aqua",
+ "Black",
+ "Blue",
+ "Fuchsia",
+ "Gray",
+ "Green",
+ "Lime",
+ "Maroon",
+ "Navy",
+ "Olive",
+ "Orange",
+ "Purple",
+ "Red",
+ "Silver",
+ "Teal",
+ "White",
+ "Yellow",
+};
+
+static unsigned int color_values[] = {
+ 0x00000000, /* transparent */
+ 0xff00ffff, /* aqua */
+ 0xff000000, /* black */
+ 0xff0000ff, /* blue */
+ 0xffff00ff, /* fuchsia */
+ 0xff808080, /* gray */
+ 0xff008000, /* green */
+ 0xff00ff00, /* lime */
+ 0xff800000, /* maroon */
+ 0xff000080, /* navy */
+ 0xff808000, /* olive */
+ 0xffffa500, /* orange */
+ 0xff800080, /* purple */
+ 0xffff0000, /* red */
+ 0xffc0c0c0, /* silver */
+ 0xff008080, /* teal */
+ 0xffffffff, /* white */
+ 0xffffff00, /* yellow */
+};
+
+static unsigned int hex_from_color(int n, float color[4])
+{
+ float rgb[4];
+ int r, g, b;
+ switch (n)
+ {
+ default:
+ return 0;
+ case 1:
+ r = color[0] * 255;
+ return 0xff000000 | (r<<16) | (r<<8) | r;
+ case 3:
+ r = color[0] * 255;
+ g = color[1] * 255;
+ b = color[2] * 255;
+ return 0xff000000 | (r<<16) | (g<<8) | b;
+ case 4:
+ fz_convert_color(ctx, NULL, NULL, fz_device_rgb(ctx), rgb, fz_device_cmyk(ctx), color);
+ r = rgb[0] * 255;
+ g = rgb[1] * 255;
+ b = rgb[2] * 255;
+ return 0xff000000 | (r<<16) | (g<<8) | b;
+ }
+}
+
+static const char *name_from_hex(unsigned int hex)
+{
+ static char buf[10];
+ int i;
+ for (i = 0; i < nelem(color_names); ++i)
+ if (color_values[i] == hex)
+ return color_names[i];
+ fz_snprintf(buf, sizeof buf, "#%06x", hex & 0xffffff);
+ return buf;
+}
+
+static void do_annotate_color(char *label,
+ void (*get_color)(fz_context *ctx, pdf_annot *annot, int *n, float color[4]),
+ void (*set_color)(fz_context *ctx, pdf_annot *annot, int n, const float color[4]))
+{
+ float color[4];
+ int hex, choice, n;
+ get_color(ctx, selected_annot, &n, color);
+ ui_label("%s:", label);
+ choice = ui_select(label, name_from_hex(hex_from_color(n, color)), color_names, nelem(color_names));
+ if (choice != -1)
+ {
+ hex = color_values[choice];
+ if (hex == 0)
+ set_color(ctx, selected_annot, 0, color);
+ else
+ {
+ color[0] = ((hex>>16)&0xff) / 255.0f;
+ color[1] = ((hex>>8)&0xff) / 255.0f;
+ color[2] = ((hex)&0xff) / 255.0f;
+ set_color(ctx, selected_annot, 3, color);
+ }
+ }
+}
+
+static void do_annotate_author(void)
+{
+ if (pdf_annot_has_author(ctx, selected_annot))
+ {
+ char *author = pdf_copy_annot_author(ctx, selected_annot);
+ if (author && strlen(author) > 0)
+ ui_label("Author: %s", author);
+ fz_free(ctx, author);
+ }
+}
+
+static void do_annotate_date(void)
+{
+ time_t secs = pdf_annot_modification_date(ctx, selected_annot);
+ if (secs > 0)
+ {
+#ifdef _POSIX_SOURCE
+ struct tm tmbuf, *tm = gmtime_r(&secs, &tmbuf);
+#else
+ struct tm *tm = gmtime(&secs);
+#endif
+ char buf[100];
+ if (tm)
+ {
+ strftime(buf, sizeof buf, "%Y-%m-%d %H:%M UTC", tm);
+ ui_label("Date: %s", buf);
+ }
+ }
+}
+
+static void do_annotate_contents(void)
+{
+ static pdf_annot *last_annot = NULL;
+ static struct input input;
+ char *contents;
+
+ if (selected_annot != last_annot)
+ {
+ last_annot = selected_annot;
+ contents = pdf_copy_annot_contents(ctx, selected_annot);
+ ui_input_init(&input, contents);
+ fz_free(ctx, contents);
+ }
+
+ ui_label("Contents:");
+ if (ui_input(&input, 0) >= UI_INPUT_EDIT)
+ pdf_set_annot_contents(ctx, selected_annot, input.text);
+}
+
+static const char *file_attachment_icons[] = { "Graph", "Paperclip", "PushPin", "Tag" };
+static const char *sound_icons[] = { "Speaker", "Mic" };
+static const char *stamp_icons[] = {
+ "Approved", "AsIs", "Confidential", "Departmental", "Draft",
+ "Experimental", "Expired", "Final", "ForComment", "ForPublicRelease",
+ "NotApproved", "NotForPublicRelease", "Sold", "TopSecret" };
+static const char *text_icons[] = {
+ "Comment", "Help", "Insert", "Key", "NewParagraph", "Note", "Paragraph" };
+static const char *line_ending_styles[] = {
+ "None", "Square", "Circle", "Diamond", "OpenArrow", "ClosedArrow", "Butt",
+ "ROpenArrow", "RClosedArrow", "Slash" };
+static const char *quadding_names[] = { "Left", "Center", "Right" };
+
+static int should_edit_border(enum pdf_annot_type subtype)
+{
+ switch (subtype) {
+ default:
+ return 0;
+ case PDF_ANNOT_FREE_TEXT:
+ return 1;
+ case PDF_ANNOT_INK:
+ case PDF_ANNOT_LINE:
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ case PDF_ANNOT_POLYGON:
+ case PDF_ANNOT_POLY_LINE:
+ return 1;
+ }
+}
+
+static int should_edit_color(enum pdf_annot_type subtype)
+{
+ switch (subtype) {
+ default:
+ return 0;
+ case PDF_ANNOT_STAMP:
+ case PDF_ANNOT_TEXT:
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ case PDF_ANNOT_SOUND:
+ case PDF_ANNOT_CARET:
+ return 1;
+ case PDF_ANNOT_FREE_TEXT:
+ return 1;
+ case PDF_ANNOT_INK:
+ case PDF_ANNOT_LINE:
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ case PDF_ANNOT_POLYGON:
+ case PDF_ANNOT_POLY_LINE:
+ return 1;
+ case PDF_ANNOT_HIGHLIGHT:
+ case PDF_ANNOT_UNDERLINE:
+ case PDF_ANNOT_STRIKE_OUT:
+ case PDF_ANNOT_SQUIGGLY:
+ return 1;
+ }
+}
+
+static int should_edit_icolor(enum pdf_annot_type subtype)
+{
+ switch (subtype) {
+ default:
+ return 0;
+ case PDF_ANNOT_LINE:
+ case PDF_ANNOT_SQUARE:
+ case PDF_ANNOT_CIRCLE:
+ return 1;
+ }
+}
+
+void do_annotate_panel(void)
+{
+ static struct list annot_list;
+ pdf_annot *annot;
+ int n;
+
+ int was_dirty = pdf->dirty;
+
+ ui_layout(T, X, NW, 2, 2);
+
+ if (ui_popup("CreateAnnotPopup", "Create...", 1, 14))
+ {
+ if (ui_popup_item("Text")) new_annot(PDF_ANNOT_TEXT);
+ if (ui_popup_item("FreeText")) new_annot(PDF_ANNOT_FREE_TEXT);
+ if (ui_popup_item("Stamp")) new_annot(PDF_ANNOT_STAMP);
+ if (ui_popup_item("Caret")) new_annot(PDF_ANNOT_CARET);
+ if (ui_popup_item("Ink")) new_annot(PDF_ANNOT_INK);
+ if (ui_popup_item("Square")) new_annot(PDF_ANNOT_SQUARE);
+ if (ui_popup_item("Circle")) new_annot(PDF_ANNOT_CIRCLE);
+ if (ui_popup_item("Line")) new_annot(PDF_ANNOT_LINE);
+ if (ui_popup_item("Polygon")) new_annot(PDF_ANNOT_POLYGON);
+ if (ui_popup_item("PolyLine")) new_annot(PDF_ANNOT_POLY_LINE);
+ if (ui_popup_item("Highlight")) new_annot(PDF_ANNOT_HIGHLIGHT);
+ if (ui_popup_item("Underline")) new_annot(PDF_ANNOT_UNDERLINE);
+ if (ui_popup_item("StrikeOut")) new_annot(PDF_ANNOT_STRIKE_OUT);
+ if (ui_popup_item("Squiggly")) new_annot(PDF_ANNOT_SQUIGGLY);
+ ui_popup_end();
+ }
+
+ n = 0;
+ for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+ ++n;
+
+ ui_list_begin(&annot_list, n, 0, ui.lineheight * 10 + 4);
+ for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+ {
+ char buf[256];
+ int num = pdf_to_num(ctx, annot->obj);
+ const char *type = pdf_string_from_annot_type(ctx, pdf_annot_type(ctx, annot));
+ fz_snprintf(buf, sizeof buf, "%d: %s", num, type);
+ if (ui_list_item(&annot_list, annot->obj, buf, selected_annot == annot))
+ selected_annot = annot;
+ }
+ ui_list_end(&annot_list);
+
+ if (selected_annot)
+ {
+ enum pdf_annot_type subtype = pdf_annot_type(ctx, selected_annot);
+ fz_rect rect;
+ fz_irect irect;
+ int n, choice;
+ pdf_obj *obj;
+
+ if (ui_button("Delete"))
+ {
+ pdf_delete_annot(ctx, page, selected_annot);
+ selected_annot = NULL;
+ render_page();
+ return;
+ }
+
+ ui_spacer();
+
+ /* common annotation properties */
+
+ pdf_annot_rect(ctx, selected_annot, &rect);
+ fz_irect_from_rect(&irect, &rect);
+ ui_label("Rect: %d %d %d %d", irect.x0, irect.y0, irect.x1, irect.y1);
+
+ do_annotate_flags();
+ do_annotate_author();
+ do_annotate_date();
+
+ obj = pdf_dict_get(ctx, selected_annot->obj, PDF_NAME(Popup));
+ if (obj)
+ ui_label("Popup: %d 0 R", pdf_to_num(ctx, obj));
+
+ ui_spacer();
+
+ do_annotate_contents();
+
+ ui_spacer();
+
+ if (subtype == PDF_ANNOT_FREE_TEXT)
+ {
+ int q = pdf_annot_quadding(ctx, selected_annot);
+ ui_label("Text Alignment:");
+ choice = ui_select("Q", quadding_names[q], quadding_names, nelem(quadding_names));
+ if (choice != -1)
+ pdf_set_annot_quadding(ctx, selected_annot, choice);
+ }
+
+ if (subtype == PDF_ANNOT_LINE)
+ {
+ enum pdf_line_ending s, e;
+ int s_choice, e_choice;
+
+ pdf_annot_line_ending_styles(ctx, selected_annot, &s, &e);
+
+ ui_label("Line Start:");
+ s_choice = ui_select("LE0", line_ending_styles[s], line_ending_styles, nelem(line_ending_styles));
+
+ ui_label("Line End:");
+ e_choice = ui_select("LE1", line_ending_styles[e], line_ending_styles, nelem(line_ending_styles));
+
+ if (s_choice != -1 || e_choice != -1)
+ {
+ if (s_choice != -1) s = s_choice;
+ if (e_choice != -1) e = e_choice;
+ pdf_set_annot_line_ending_styles(ctx, selected_annot, s, e);
+ }
+ }
+
+ if (pdf_annot_has_icon_name(ctx, selected_annot))
+ {
+ const char *name = pdf_annot_icon_name(ctx, selected_annot);
+ ui_label("Icon:");
+ switch (pdf_annot_type(ctx, selected_annot))
+ {
+ case PDF_ANNOT_TEXT:
+ choice = ui_select("Icon", name, text_icons, nelem(text_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, text_icons[choice]);
+ break;
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ choice = ui_select("Icon", name, file_attachment_icons, nelem(file_attachment_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, file_attachment_icons[choice]);
+ break;
+ case PDF_ANNOT_SOUND:
+ choice = ui_select("Icon", name, sound_icons, nelem(sound_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, sound_icons[choice]);
+ break;
+ case PDF_ANNOT_STAMP:
+ choice = ui_select("Icon", name, stamp_icons, nelem(stamp_icons));
+ if (choice != -1)
+ pdf_set_annot_icon_name(ctx, selected_annot, stamp_icons[choice]);
+ break;
+ }
+ }
+
+ if (should_edit_border(subtype))
+ {
+ static int border;
+ border = pdf_annot_border(ctx, selected_annot);
+ ui_label("Border: %d", border);
+ if (ui_slider(&border, 0, 10, 100))
+ pdf_set_annot_border(ctx, selected_annot, border);
+ }
+
+ if (should_edit_color(subtype))
+ do_annotate_color("Color", pdf_annot_color, pdf_set_annot_color);
+ if (should_edit_icolor(subtype))
+ do_annotate_color("Interior Color", pdf_annot_interior_color, pdf_set_annot_interior_color);
+
+ if (subtype == PDF_ANNOT_HIGHLIGHT)
+ {
+ static int opacity;
+ opacity = pdf_annot_opacity(ctx, selected_annot) * 255;
+ ui_label("Opacity:");
+ if (ui_slider(&opacity, 0, 255, 256))
+ pdf_set_annot_opacity(ctx, selected_annot, opacity / 255.0f);
+ }
+
+ if (pdf_annot_has_open(ctx, selected_annot))
+ {
+ int is_open = pdf_annot_is_open(ctx, selected_annot);
+ int start_is_open = is_open;
+ ui_checkbox("Open", &is_open);
+ if (start_is_open != is_open)
+ pdf_set_annot_is_open(ctx, selected_annot, is_open);
+ }
+
+ ui_spacer();
+
+ if (pdf_annot_has_quad_points(ctx, selected_annot))
+ {
+ n = pdf_annot_quad_point_count(ctx, selected_annot);
+ ui_label("QuadPoints: %d", n);
+ if (ui_button("Clear"))
+ pdf_clear_annot_quad_points(ctx, selected_annot);
+ }
+
+ if (pdf_annot_has_vertices(ctx, selected_annot))
+ {
+ n = pdf_annot_vertex_count(ctx, selected_annot);
+ ui_label("Vertices: %d", n);
+ if (ui_button("Clear"))
+ pdf_clear_annot_vertices(ctx, selected_annot);
+ }
+
+ if (pdf_annot_has_ink_list(ctx, selected_annot))
+ {
+ n = pdf_annot_ink_list_count(ctx, selected_annot);
+ ui_label("InkList: %d strokes", n);
+ if (ui_button("Clear"))
+ pdf_clear_annot_ink_list(ctx, selected_annot);
+ }
+
+ if (selected_annot && selected_annot->needs_new_ap)
+ {
+ pdf_update_appearance(ctx, selected_annot);
+ render_page();
+ }
+ }
+
+ ui_layout(B, X, NW, 2, 2);
+ if (ui_button("Save PDF..."))
+ {
+ init_save_pdf_options();
+ ui_init_save_file(filename, pdf_filter);
+ ui.dialog = save_pdf_dialog;
+ }
+
+ if (was_dirty != pdf->dirty)
+ update_title();
+}
+
+static void do_edit_icon(fz_irect canvas_area, fz_irect area, fz_rect *rect)
+{
+ static fz_point start_pt;
+ static float w, h;
+ static int moving = 0;
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ start_pt.x = rect->x0;
+ start_pt.y = rect->y0;
+ w = rect->x1 - rect->x0;
+ h = rect->y1 - rect->y0;
+ moving = 1;
+ }
+ }
+
+ if (ui.active == selected_annot && moving)
+ {
+ rect->x0 = start_pt.x + (ui.x - ui.down_x);
+ rect->y0 = start_pt.y + (ui.y - ui.down_y);
+
+ /* Clamp to fit on page */
+ rect->x0 = fz_clamp(rect->x0, view_page_area.x0, view_page_area.x1-w);
+ rect->y0 = fz_clamp(rect->y0, view_page_area.y0, view_page_area.y1-h);
+ rect->x1 = rect->x0 + w;
+ rect->y1 = rect->y0 + h;
+
+ /* cancel on right click */
+ if (ui.right)
+ moving = 0;
+
+ /* Commit movement on mouse-up */
+ if (!ui.down)
+ {
+ moving = 0;
+ if (fz_abs(start_pt.x - rect->x0) > 0.1f || fz_abs(start_pt.x - rect->y0) > 0.1f)
+ {
+ fz_rect trect = *rect;
+ fz_transform_rect(&trect, &view_page_inv_ctm);
+ pdf_set_annot_rect(ctx, selected_annot, &trect);
+ }
+ }
+ }
+}
+
+static void do_edit_rect(fz_irect canvas_area, fz_irect area, fz_rect *rect)
+{
+ enum {
+ ER_N=1, ER_E=2, ER_S=4, ER_W=8,
+ ER_NONE = 0,
+ ER_NW = ER_N|ER_W,
+ ER_NE = ER_N|ER_E,
+ ER_SW = ER_S|ER_W,
+ ER_SE = ER_S|ER_E,
+ ER_MOVE = ER_N|ER_E|ER_S|ER_W,
+ };
+ static fz_rect start_rect;
+ static int state = ER_NONE;
+
+ fz_expand_irect(&area, 5);
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ start_rect = *rect;
+ state = ER_NONE;
+ if (ui.x <= area.x0 + 10) state |= ER_W;
+ if (ui.x >= area.x1 - 10) state |= ER_E;
+ if (ui.y <= area.y0 + 10) state |= ER_N;
+ if (ui.y >= area.y1 - 10) state |= ER_S;
+ if (!state) state = ER_MOVE;
+ }
+ }
+
+ if (ui.active == selected_annot && state != ER_NONE)
+ {
+ *rect = start_rect;
+ if (state & ER_W) rect->x0 += (ui.x - ui.down_x);
+ if (state & ER_E) rect->x1 += (ui.x - ui.down_x);
+ if (state & ER_N) rect->y0 += (ui.y - ui.down_y);
+ if (state & ER_S) rect->y1 += (ui.y - ui.down_y);
+ if (rect->x1 < rect->x0) { float t = rect->x1; rect->x1 = rect->x0; rect->x0 = t; }
+ if (rect->y1 < rect->y0) { float t = rect->y1; rect->y1 = rect->y0; rect->y0 = t; }
+ if (rect->x1 < rect->x0 + 10) rect->x1 = rect->x0 + 10;
+ if (rect->y1 < rect->y0 + 10) rect->y1 = rect->y0 + 10;
+
+ /* cancel on right click */
+ if (ui.right)
+ state = ER_NONE;
+
+ /* commit on mouse-up */
+ if (!ui.down)
+ {
+ state = ER_NONE;
+ if (rects_differ(&start_rect, rect, 1))
+ {
+ fz_rect trect = *rect;
+ fz_transform_rect(&trect, &view_page_inv_ctm);
+ pdf_set_annot_rect(ctx, selected_annot, &trect);
+ }
+ }
+ }
+}
+
+static void do_edit_line(fz_irect canvas_area, fz_irect area, fz_rect *rect)
+{
+ enum { EL_NONE, EL_A=1, EL_B=2, EL_MOVE=EL_A|EL_B };
+ static fz_point start_a, start_b;
+ static int state = EL_NONE;
+ fz_irect a_grab, b_grab;
+ fz_point a, b;
+ float lw;
+
+ fz_expand_irect(&area, 5);
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ pdf_annot_line(ctx, selected_annot, &start_a, &start_b);
+ fz_transform_point(&start_a, &view_page_ctm);
+ fz_transform_point(&start_b, &view_page_ctm);
+ a_grab = fz_make_irect(start_a.x, start_a.y, start_a.x, start_a.y);
+ b_grab = fz_make_irect(start_b.x, start_b.y, start_b.x, start_b.y);
+ fz_expand_irect(&a_grab, 10);
+ fz_expand_irect(&b_grab, 10);
+ state = EL_NONE;
+ if (ui_mouse_inside(&a_grab)) state |= EL_A;
+ if (ui_mouse_inside(&b_grab)) state |= EL_B;
+ if (!state) state = EL_MOVE;
+ }
+ }
+
+ if (ui.active == selected_annot && state != 0)
+ {
+ a = start_a;
+ b = start_b;
+ if (state & EL_A) { a.x += (ui.x - ui.down_x); a.y += (ui.y - ui.down_y); }
+ if (state & EL_B) { b.x += (ui.x - ui.down_x); b.y += (ui.y - ui.down_y); }
+
+ glBegin(GL_LINES);
+ glColor4f(1, 0, 0, 1);
+ glVertex2f(a.x, a.y);
+ glVertex2f(b.x, b.y);
+ glEnd();
+
+ rect->x0 = fz_min(a.x, b.x);
+ rect->y0 = fz_min(a.y, b.y);
+ rect->x1 = fz_max(a.x, b.x);
+ rect->y1 = fz_max(a.y, b.y);
+ lw = pdf_annot_border(ctx, selected_annot);
+ fz_expand_rect(rect, fz_matrix_expansion(&view_page_ctm) * lw);
+
+ /* cancel on right click */
+ if (ui.right)
+ state = EL_NONE;
+
+ /* commit on mouse-up */
+ if (!ui.down)
+ {
+ state = EL_NONE;
+ if (points_differ(&start_a, &a, 1) || points_differ(&start_b, &b, 1))
+ {
+ fz_transform_point(&a, &view_page_inv_ctm);
+ fz_transform_point(&b, &view_page_inv_ctm);
+ pdf_set_annot_line(ctx, selected_annot, a, b);
+ }
+ }
+ }
+}
+
+static void do_edit_polygon(fz_irect canvas_area, int close)
+{
+ static int drawing = 0;
+ fz_point a, p;
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&view_page_area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active || ui.active == selected_annot)
+ ui.cursor = GLUT_CURSOR_CROSSHAIR;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ drawing = 1;
+ }
+ }
+
+ if (ui.active == selected_annot && drawing)
+ {
+ int n = pdf_annot_vertex_count(ctx, selected_annot);
+ if (n > 0)
+ {
+ p = pdf_annot_vertex(ctx, selected_annot, n-1);
+ fz_transform_point(&p, &view_page_ctm);
+ if (close)
+ {
+ a = pdf_annot_vertex(ctx, selected_annot, 0);
+ fz_transform_point(&a, &view_page_ctm);
+ }
+ glBegin(GL_LINE_STRIP);
+ glColor4f(1, 0, 0, 1);
+ glVertex2f(p.x, p.y);
+ glVertex2f(ui.x, ui.y);
+ if (close)
+ glVertex2f(a.x, a.y);
+ glEnd();
+ }
+
+ glColor4f(1, 0, 0, 1);
+ glPointSize(4);
+ glBegin(GL_POINTS);
+ glVertex2f(ui.x, ui.y);
+ glEnd();
+
+ /* cancel on right click */
+ if (ui.right)
+ drawing = 0;
+
+ /* commit point on mouse-up */
+ if (!ui.down)
+ {
+ fz_point p = { ui.x, ui.y };
+ fz_transform_point(&p, &view_page_inv_ctm);
+ pdf_add_annot_vertex(ctx, selected_annot, p);
+ drawing = 0;
+ }
+ }
+}
+
+static void do_edit_ink(fz_irect canvas_area)
+{
+ static int drawing = 0;
+ static fz_point p[1000];
+ static int n, last_x, last_y;
+ int i;
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&view_page_area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active || ui.active == selected_annot)
+ ui.cursor = GLUT_CURSOR_CROSSHAIR;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ drawing = 1;
+ n = 0;
+ last_x = INT_MIN;
+ last_y = INT_MIN;
+ }
+ }
+
+ if (ui.active == selected_annot && drawing)
+ {
+ if (n < nelem(p) && (ui.x != last_x || ui.y != last_y))
+ {
+ p[n].x = fz_clamp(ui.x, view_page_area.x0, view_page_area.x1);
+ p[n].y = fz_clamp(ui.y, view_page_area.y0, view_page_area.y1);
+ ++n;
+ }
+ last_x = ui.x;
+ last_y = ui.y;
+
+ if (n > 1)
+ {
+ glBegin(GL_LINE_STRIP);
+ glColor4f(1, 0, 0, 1);
+ for (i = 0; i < n; ++i)
+ glVertex2f(p[i].x, p[i].y);
+ glEnd();
+ }
+
+ /* cancel on right click */
+ if (ui.right)
+ {
+ drawing = 0;
+ n = 0;
+ }
+
+ /* commit stroke on mouse-up */
+ if (!ui.down)
+ {
+ if (n > 1)
+ {
+ for (i = 0; i < n; ++i)
+ fz_transform_point(&p[i], &view_page_inv_ctm);
+ pdf_add_annot_ink_list(ctx, selected_annot, n, p);
+ }
+ drawing = 0;
+ n = 0;
+ }
+ }
+}
+
+static void do_edit_quad_points(void)
+{
+ static fz_point pt = { 0, 0 };
+ static int marking = 0;
+ fz_rect hits[1000];
+ int i, n;
+
+ if (ui_mouse_inside(&view_page_area))
+ {
+ ui.hot = selected_annot;
+ if (!ui.active || ui.active == selected_annot)
+ ui.cursor = GLUT_CURSOR_TEXT;
+ if (!ui.active && ui.down)
+ {
+ ui.active = selected_annot;
+ marking = 1;
+ pt.x = ui.x;
+ pt.y = ui.y;
+ }
+ }
+
+ if (ui.active == selected_annot && marking)
+ {
+ fz_point page_a = { pt.x, pt.y };
+ fz_point page_b = { ui.x, ui.y };
+
+ fz_transform_point(&page_a, &view_page_inv_ctm);
+ fz_transform_point(&page_b, &view_page_inv_ctm);
+
+ n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
+
+ glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); /* invert destination color */
+ glEnable(GL_BLEND);
+
+ glColor4f(1, 1, 1, 1);
+ for (i = 0; i < n; ++i)
+ {
+ fz_rect thit = hits[i];
+ fz_transform_rect(&thit, &view_page_ctm);
+ glRectf(thit.x0, thit.y0, thit.x1 + 1, thit.y1 + 1);
+ }
+
+ glDisable(GL_BLEND);
+
+ /* cancel on right click */
+ if (ui.right)
+ marking = 0;
+
+ if (!ui.down)
+ {
+ if (n > 0)
+ {
+ pdf_clear_annot_quad_points(ctx, selected_annot);
+ for (i = 0; i < n; ++i)
+ pdf_add_annot_quad_point(ctx, selected_annot, hits[i]);
+ }
+ marking = 0;
+ }
+ }
+}
+
+void do_annotate_canvas(fz_irect canvas_area)
+{
+ fz_rect bounds;
+ fz_irect area;
+ pdf_annot *annot;
+
+ int was_dirty = pdf->dirty;
+
+ for (annot = pdf_first_annot(ctx, page); annot; annot = pdf_next_annot(ctx, annot))
+ {
+ pdf_bound_annot(ctx, annot, &bounds);
+ fz_transform_rect(&bounds, &view_page_ctm);
+ fz_irect_from_rect(&area, &bounds);
+
+ if (ui_mouse_inside(&canvas_area) && ui_mouse_inside(&area))
+ {
+ ui.hot = annot;
+ if (!ui.active && ui.right)
+ {
+ ui.active = annot;
+ selected_annot = annot;
+ }
+ }
+
+ if (annot == selected_annot)
+ {
+ switch (pdf_annot_type(ctx, selected_annot))
+ {
+ default:
+ break;
+
+ /* Popup window */
+ case PDF_ANNOT_POPUP:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+
+ /* Icons */
+ case PDF_ANNOT_TEXT:
+ case PDF_ANNOT_CARET:
+ case PDF_ANNOT_FILE_ATTACHMENT:
+ case PDF_ANNOT_SOUND:
+ do_edit_icon(canvas_area, area, &bounds);
+ break;
+
+ case PDF_ANNOT_STAMP:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+
+ case PDF_ANNOT_FREE_TEXT:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+
+ /* Drawings */
+ case PDF_ANNOT_LINE:
+ do_edit_line(canvas_area, area, &bounds);
+ break;
+ case PDF_ANNOT_CIRCLE:
+ case PDF_ANNOT_SQUARE:
+ do_edit_rect(canvas_area, area, &bounds);
+ break;
+ case PDF_ANNOT_POLYGON:
+ do_edit_polygon(canvas_area, 1);
+ break;
+ case PDF_ANNOT_POLY_LINE:
+ do_edit_polygon(canvas_area, 0);
+ break;
+
+ case PDF_ANNOT_INK:
+ do_edit_ink(canvas_area);
+ break;
+
+ case PDF_ANNOT_HIGHLIGHT:
+ case PDF_ANNOT_UNDERLINE:
+ case PDF_ANNOT_STRIKE_OUT:
+ case PDF_ANNOT_SQUIGGLY:
+ do_edit_quad_points();
+ break;
+ }
+
+ glLineStipple(1, 0xAAAA);
+ glEnable(GL_LINE_STIPPLE);
+ glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO);
+ glEnable(GL_BLEND);
+ glColor4f(1, 1, 1, 1);
+ glBegin(GL_LINE_LOOP);
+ fz_irect_from_rect(&area, &bounds);
+ glVertex2f(area.x0-0.5f, area.y0-0.5f);
+ glVertex2f(area.x1+0.5f, area.y0-0.5f);
+ glVertex2f(area.x1+0.5f, area.y1+0.5f);
+ glVertex2f(area.x0-0.5f, area.y1+0.5f);
+ glEnd();
+ glDisable(GL_BLEND);
+ glDisable(GL_LINE_STIPPLE);
+
+ if (annot->needs_new_ap)
+ {
+ pdf_update_appearance(ctx, annot);
+ render_page();
+ }
+ }
+ }
+
+ if (ui_mouse_inside(&canvas_area) && ui.right)
+ {
+ if (!ui.active)
+ selected_annot = NULL;
+ }
+
+ if (was_dirty != pdf->dirty)
+ update_title();
+}
diff --git a/platform/gl/gl-app.h b/platform/gl/gl-app.h
index 6efb1ff7..a966641a 100644
--- a/platform/gl/gl-app.h
+++ b/platform/gl/gl-app.h
@@ -1,7 +1,6 @@
#ifdef _WIN32
#include <windows.h>
void win_install(void);
-int win_open_file(char *buf, int len);
#endif
#include "mupdf/fitz.h"
@@ -82,15 +81,23 @@ struct ui
int grab_down, grab_middle, grab_right;
const void *hot, *active, *focus;
+ int last_cursor, cursor;
int fontsize;
int baseline;
int lineheight;
+ int gridsize;
struct layout *layout;
fz_irect *cavity;
struct layout layout_stack[32];
fz_irect cavity_stack[32];
+
+ struct { fz_irect area; const char *title; } popup[32];
+ fz_irect popup_area;
+ int popup_count;
+
+ void (*dialog)(void);
};
extern struct ui ui;
@@ -125,9 +132,9 @@ void ui_draw_image(struct texture *tex, float x, float y);
enum
{
- UI_INPUT_CANCEL = -1,
- UI_INPUT_ACCEPT = 1,
- UI_INPUT_CONTINUE = 0,
+ UI_INPUT_NONE = 0,
+ UI_INPUT_EDIT = 1,
+ UI_INPUT_ACCEPT = 2,
};
struct input
@@ -154,29 +161,79 @@ fz_irect ui_pack(int slave_w, int slave_h);
void ui_pack_push(fz_irect cavity);
void ui_pack_pop(void);
-void ui_panel_begin(int w, int h, int opaque);
+void ui_dialog_begin(int w, int h);
+void ui_dialog_end(void);
+void ui_panel_begin(int w, int h, int padx, int pady, int opaque);
void ui_panel_end(void);
void ui_spacer(void);
+void ui_splitter(int *x, int min, int max, enum side side);
void ui_label(const char *fmt, ...);
int ui_button(const char *label);
-void ui_checkbox(const char *label, int *value);
-void ui_slider(float *value, float min, float max, int width);
+int ui_checkbox(const char *label, int *value);
+int ui_slider(int *value, int min, int max, int width);
+int ui_select(const void *id, const char *current, const char *options[], int n);
void ui_input_init(struct input *input, const char *text);
int ui_input(struct input *input, int width);
void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max);
void ui_list_begin(struct list *list, int count, int req_w, int req_h);
-int ui_list_item(struct list *list, void *id, int indent, const char *label, int selected);
+int ui_list_item(struct list *list, const void *id, const char *label, int selected);
+int ui_list_item_x(struct list *list, const void *id, int indent, const char *label, int selected);
void ui_list_end(struct list *list);
+int ui_popup(const void *id, const char *title, int is_button, int count);
+int ui_popup_item(const char *title);
+void ui_popup_end(void);
+void ui_draw_popup(void);
+
+void ui_init_open_file(const char *dir, int (*filter)(const char *fn));
+int ui_open_file(char filename[]);
+void ui_init_save_file(const char *path, int (*filter)(const char *fn));
+int ui_save_file(char filename[], void (*extra_panel)(void));
+
+void ui_show_warning_dialog(const char *message);
+void ui_show_error_dialog(const char *message);
+
+/* Theming */
+
+enum
+{
+ UI_COLOR_PANEL = 0xc0c0c0,
+ UI_COLOR_BUTTON = 0xc0c0c0,
+ UI_COLOR_SCROLLBAR = 0xdfdfdf,
+ UI_COLOR_TEXT_BG = 0xffffff,
+ UI_COLOR_TEXT_FG = 0x000000,
+ UI_COLOR_TEXT_SEL_BG = 0x000080,
+ UI_COLOR_TEXT_SEL_FG = 0xffffff,
+ UI_COLOR_BEVEL_1 = 0x000000,
+ UI_COLOR_BEVEL_2 = 0x808080,
+ UI_COLOR_BEVEL_3 = 0xdfdfdf,
+ UI_COLOR_BEVEL_4 = 0xffffff,
+};
+
+void glColorHex(unsigned int hex);
+void ui_draw_bevel(fz_irect area, int depressed);
+void ui_draw_ibevel(fz_irect area, int depressed);
+void ui_draw_bevel_rect(fz_irect area, unsigned int fill, int depressed);
+void ui_draw_ibevel_rect(fz_irect area, unsigned int fill, int depressed);
+
/* App */
extern fz_context *ctx;
extern pdf_document *pdf;
extern pdf_page *page;
-extern fz_matrix page_ctm, page_inv_ctm;
-extern int page_x_ofs, page_y_ofs;
+extern fz_stext_page *page_text;
+extern pdf_annot *selected_annot;
+extern fz_matrix draw_page_ctm, view_page_ctm, view_page_inv_ctm;
+extern fz_rect page_bounds, draw_page_bounds, view_page_bounds;
+extern fz_irect view_page_area;
+extern char filename[];
void run_main_loop(void);
+void do_annotate_panel(void);
+void do_annotate_canvas(fz_irect canvas_area);
+void render_page(void);
+void update_title(void);
+void reload(void);
diff --git a/platform/gl/gl-file.c b/platform/gl/gl-file.c
new file mode 100644
index 00000000..ec74f5fc
--- /dev/null
+++ b/platform/gl/gl-file.c
@@ -0,0 +1,506 @@
+#include "gl-app.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+
+#define ICON_PC 0x1f4bb
+#define ICON_HOME 0x1f3e0
+#define ICON_FOLDER 0x1f4c1
+#define ICON_DOCUMENT 0x1f4c4
+#define ICON_DISK 0x1f4be
+#define ICON_PIN 0x1f4cc
+
+#ifndef PATH_MAX
+#define PATH_MAX 2048
+#endif
+
+struct entry
+{
+ int is_dir;
+ char name[FILENAME_MAX];
+};
+
+static struct
+{
+ int (*filter)(const char *fn);
+ struct input input_dir;
+ struct input input_file;
+ struct list list_dir;
+ char curdir[PATH_MAX];
+ int count;
+ struct entry files[512];
+ int selected;
+} fc;
+
+static int cmp_entry(const void *av, const void *bv)
+{
+ const struct entry *a = av;
+ const struct entry *b = bv;
+ /* "." first */
+ if (a->name[0] == '.' && a->name[1] == 0) return -1;
+ if (b->name[0] == '.' && b->name[1] == 0) return 1;
+ /* ".." second */
+ if (a->name[0] == '.' && a->name[1] == '.' && a->name[2] == 0) return -1;
+ if (b->name[0] == '.' && b->name[1] == '.' && b->name[2] == 0) return 1;
+ /* directories before files */
+ if (a->is_dir && !b->is_dir) return -1;
+ if (b->is_dir && !a->is_dir) return 1;
+ /* then alphabetically */
+ return strcmp(a->name, b->name);
+}
+
+#ifdef _WIN32
+
+#include <strsafe.h>
+#include <shlobj.h>
+
+const char *realpath(const char *path, char buf[PATH_MAX])
+{
+ wchar_t wpath[PATH_MAX];
+ wchar_t wbuf[PATH_MAX];
+ int i;
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX);
+ GetFullPathNameW(wpath, PATH_MAX, wbuf, NULL);
+ WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, PATH_MAX, NULL, NULL);
+ for (i=0; buf[i]; ++i)
+ if (buf[i] == '\\')
+ buf[i] = '/';
+ return buf;
+}
+
+static void load_dir(const char *path)
+{
+ WIN32_FIND_DATA ffd;
+ HANDLE dir;
+ wchar_t wpath[PATH_MAX];
+ char buf[PATH_MAX];
+ int i;
+
+ realpath(path, fc.curdir);
+ if (!fz_is_directory(ctx, path))
+ return;
+
+ ui_input_init(&fc.input_dir, fc.curdir);
+
+ fc.selected = -1;
+ fc.count = 0;
+
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, PATH_MAX);
+ for (i=0; wpath[i]; ++i)
+ if (wpath[i] == '/')
+ wpath[i] = '\\';
+ StringCchCat(wpath, PATH_MAX, TEXT("/*"));
+ dir = FindFirstFileW(wpath, &ffd);
+ if (dir)
+ {
+ do
+ {
+ WideCharToMultiByte(CP_UTF8, 0, ffd.cFileName, -1, buf, PATH_MAX, NULL, NULL);
+ if (ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
+ continue;
+ fc.files[fc.count].is_dir = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
+ if (fc.files[fc.count].is_dir || !fc.filter || fc.filter(buf))
+ {
+ fz_strlcpy(fc.files[fc.count].name, buf, FILENAME_MAX);
+ ++fc.count;
+ }
+ }
+ while (FindNextFile(dir, &ffd));
+ FindClose(dir);
+ }
+
+ qsort(fc.files, fc.count, sizeof fc.files[0], cmp_entry);
+}
+
+static void list_drives(void)
+{
+ static struct list drive_list;
+ DWORD drives;
+ char dir[PATH_MAX], vis[PATH_MAX], buf[100];
+ const char *user, *home;
+ char personal[MAX_PATH], desktop[MAX_PATH];
+ int i, n;
+
+ drives = GetLogicalDrives();
+ n = 5; /* curdir + home + desktop + documents + downloads */
+ for (i=0; i < 26; ++i)
+ if (drives & (1<<i))
+ ++n;
+
+ ui_list_begin(&drive_list, n, 0, 10 * ui.lineheight + 4);
+
+ user = getenv("USERNAME");
+ home = getenv("USERPROFILE");
+ if (user && home)
+ {
+ fz_snprintf(vis, sizeof vis, "%C %s", ICON_HOME, user);
+ if (ui_list_item(&drive_list, "~", vis, 0))
+ load_dir(home);
+ }
+
+ if (SHGetFolderPathA(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, desktop) == S_OK)
+ {
+ fz_snprintf(vis, sizeof vis, "%C Desktop", ICON_PC);
+ if (ui_list_item(&drive_list, "~/Desktop", vis, 0))
+ load_dir(desktop);
+ }
+
+ if (SHGetFolderPathA(NULL, CSIDL_PERSONAL, NULL, 0, personal) == S_OK)
+ {
+ fz_snprintf(vis, sizeof vis, "%C Documents", ICON_FOLDER);
+ if (ui_list_item(&drive_list, "~/Documents", vis, 0))
+ load_dir(personal);
+ }
+
+ if (home)
+ {
+ fz_snprintf(vis, sizeof vis, "%C Downloads", ICON_FOLDER);
+ fz_snprintf(dir, sizeof dir, "%s/Downloads", home);
+ if (ui_list_item(&drive_list, "~/Downloads", vis, 0))
+ load_dir(dir);
+ }
+
+ for (i = 0; i < 26; ++i)
+ {
+ if (drives & (1<<i))
+ {
+ fz_snprintf(dir, sizeof dir, "%c:\\", i+'A');
+ if (!GetVolumeInformationA(dir, buf, sizeof buf, NULL, NULL, NULL, NULL, 0))
+ buf[0] = 0;
+ fz_snprintf(vis, sizeof vis, "%C %c: %s", ICON_DISK, i+'A', buf);
+ if (ui_list_item(&drive_list, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"+i, vis, 0))
+ {
+ load_dir(dir);
+ }
+ }
+ }
+
+ fz_snprintf(vis, sizeof vis, "%C .", ICON_PIN);
+ if (ui_list_item(&drive_list, ".", vis, 0))
+ load_dir(".");
+
+ ui_list_end(&drive_list);
+}
+
+#else
+
+#include <dirent.h>
+
+static void load_dir(const char *path)
+{
+ char buf[PATH_MAX];
+ DIR *dir;
+ struct dirent *dp;
+
+ realpath(path, fc.curdir);
+ if (!fz_is_directory(ctx, fc.curdir))
+ return;
+
+ ui_input_init(&fc.input_dir, fc.curdir);
+
+ fc.selected = -1;
+ fc.count = 0;
+ dir = opendir(fc.curdir);
+ if (!dir)
+ {
+ fc.files[fc.count].is_dir = 1;
+ fz_strlcpy(fc.files[fc.count].name, "..", FILENAME_MAX);
+ ++fc.count;
+ }
+ else
+ {
+ while ((dp = readdir(dir)) && fc.count < nelem(fc.files))
+ {
+ /* skip hidden files */
+ if (dp->d_name[0] == '.' && strcmp(dp->d_name, ".") && strcmp(dp->d_name, ".."))
+ continue;
+ fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, dp->d_name);
+ fc.files[fc.count].is_dir = fz_is_directory(ctx, buf);
+ if (fc.files[fc.count].is_dir || !fc.filter || fc.filter(buf))
+ {
+ fz_strlcpy(fc.files[fc.count].name, dp->d_name, FILENAME_MAX);
+ ++fc.count;
+ }
+ }
+ closedir(dir);
+ }
+
+ qsort(fc.files, fc.count, sizeof fc.files[0], cmp_entry);
+}
+
+static const struct {
+ int icon;
+ const char *name;
+} common_dirs[] = {
+ { ICON_HOME, "~" },
+ { ICON_PC, "~/Desktop" },
+ { ICON_FOLDER, "~/Documents" },
+ { ICON_FOLDER, "~/Downloads" },
+ { ICON_FOLDER, "/" },
+ { ICON_DISK, "/Volumes" },
+ { ICON_DISK, "/media" },
+ { ICON_DISK, "/mnt" },
+ { ICON_PIN, "." },
+};
+
+static int has_dir(const char *home, const char *user, int i, char dir[PATH_MAX], char vis[PATH_MAX])
+{
+ const char *subdir = common_dirs[i].name;
+ int icon = common_dirs[i].icon;
+ if (subdir[0] == '~')
+ {
+ if (!home)
+ return 0;
+ if (subdir[1] == '/')
+ {
+ fz_snprintf(dir, PATH_MAX, "%s/%s", home, subdir+2);
+ fz_snprintf(vis, PATH_MAX, "%C %s", icon, subdir+2);
+ }
+ else
+ {
+ fz_snprintf(dir, PATH_MAX, "%s", home);
+ fz_snprintf(vis, PATH_MAX, "%C %s", icon, user ? user : "~");
+ }
+ }
+ else
+ {
+ fz_strlcpy(dir, subdir, PATH_MAX);
+ fz_snprintf(vis, PATH_MAX, "%C %s", icon, subdir);
+ }
+ return fz_is_directory(ctx, dir);
+}
+
+static void list_drives(void)
+{
+ static struct list drive_list;
+ char dir[PATH_MAX], vis[PATH_MAX];
+ const char *home = getenv("HOME");
+ const char *user = getenv("USER");
+ int i;
+
+ ui_list_begin(&drive_list, nelem(common_dirs), 0, nelem(common_dirs) * ui.lineheight + 4);
+
+ for (i = 0; i < nelem(common_dirs); ++i)
+ if (has_dir(home, user, i, dir, vis))
+ if (ui_list_item(&drive_list, common_dirs[i].name, vis, 0))
+ load_dir(dir);
+
+ ui_list_end(&drive_list);
+}
+
+#endif
+
+void ui_init_open_file(const char *dir, int (*filter)(const char *fn))
+{
+ fc.filter = filter;
+ load_dir(".");
+}
+
+int ui_open_file(char filename[PATH_MAX])
+{
+ static int last_click_time = 0;
+ static int last_click_sel = -1;
+ int i, rv = 0;
+
+ ui_panel_begin(0, 0, 4, 4, 1);
+ {
+ ui_layout(L, Y, NW, 0, 0);
+ ui_panel_begin(150, 0, 0, 0, 0);
+ {
+ ui_layout(T, X, NW, 2, 2);
+ list_drives();
+ ui_layout(B, X, NW, 2, 2);
+ if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
+ {
+ filename[0] = 0;
+ rv = 1;
+ }
+ }
+ ui_panel_end();
+
+ ui_layout(T, X, NW, 2, 2);
+ ui_panel_begin(0, ui.gridsize, 0, 0, 0);
+ {
+ if (fc.selected >= 0)
+ {
+ ui_layout(R, NONE, CENTER, 0, 0);
+ if (ui_button("Open") || (!ui.focus && ui.key == KEY_ENTER))
+ {
+ fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, fc.files[fc.selected].name);
+ rv = 1;
+ }
+ ui_spacer();
+ }
+ ui_layout(ALL, X, CENTER, 0, 0);
+ if (ui_input(&fc.input_dir, 0) == UI_INPUT_ACCEPT)
+ load_dir(fc.input_dir.text);
+ }
+ ui_panel_end();
+
+ ui_layout(ALL, BOTH, NW, 2, 2);
+ ui_list_begin(&fc.list_dir, fc.count, 0, 0);
+ for (i = 0; i < fc.count; ++i)
+ {
+ const char *name = fc.files[i].name;
+ char buf[PATH_MAX];
+ if (fc.files[i].is_dir)
+ fz_snprintf(buf, sizeof buf, "%C %s", ICON_FOLDER, name);
+ else
+ fz_snprintf(buf, sizeof buf, "%C %s", ICON_DOCUMENT, name);
+ if (ui_list_item(&fc.list_dir, &fc.files[i], buf, i==fc.selected))
+ {
+ fc.selected = i;
+ if (fc.files[i].is_dir)
+ {
+ fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, name);
+ load_dir(buf);
+ ui.active = NULL;
+ last_click_sel = -1;
+ }
+ else
+ {
+ int click_time = glutGet(GLUT_ELAPSED_TIME);
+ if (i == last_click_sel && click_time < last_click_time + 250)
+ {
+ fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, name);
+ rv = 1;
+ }
+ last_click_time = click_time;
+ last_click_sel = i;
+ }
+ }
+ }
+ ui_list_end(&fc.list_dir);
+ }
+ ui_panel_end();
+
+ return rv;
+}
+
+void ui_init_save_file(const char *path, int (*filter)(const char *fn))
+{
+ char dir[PATH_MAX], *p;
+ fc.filter = filter;
+ fz_strlcpy(dir, path, sizeof dir);
+ for (p=dir; *p; ++p)
+ if (*p == '\\') *p = '/';
+ fz_cleanname(dir);
+ p = strrchr(dir, '/');
+ if (p)
+ {
+ *p = 0;
+ load_dir(dir);
+ ui_input_init(&fc.input_file, p+1);
+ }
+ else
+ {
+ load_dir(".");
+ ui_input_init(&fc.input_file, dir);
+ }
+}
+
+static void bump_file_version(int dir)
+{
+ char buf[PATH_MAX], *p, *n;
+ char base[PATH_MAX], out[PATH_MAX];
+ int x;
+ fz_strlcpy(buf, fc.input_file.text, sizeof buf);
+ p = strrchr(buf, '.');
+ if (p)
+ {
+ n = p;
+ while (n > buf && n[-1] >= '0' && n[-1] <= '9')
+ --n;
+ if (n != p)
+ x = atoi(n) + dir;
+ else
+ x = dir;
+ memcpy(base, buf, n-buf);
+ base[n-buf] = 0;
+ fz_snprintf(out, sizeof out, "%s%d%s", base, x, p);
+ ui_input_init(&fc.input_file, out);
+ }
+}
+
+int ui_save_file(char filename[PATH_MAX], void (*extra_panel)(void))
+{
+ int i, rv = 0;
+
+ ui_panel_begin(0, 0, 4, 4, 1);
+ {
+ ui_layout(L, Y, NW, 0, 0);
+ ui_panel_begin(150, 0, 0, 0, 0);
+ {
+ ui_layout(T, X, NW, 2, 2);
+ list_drives();
+ if (extra_panel)
+ {
+ ui_spacer();
+ extra_panel();
+ }
+ ui_layout(B, X, NW, 2, 2);
+ if (ui_button("Cancel") || (!ui.focus && ui.key == KEY_ESCAPE))
+ {
+ filename[0] = 0;
+ rv = 1;
+ }
+ }
+ ui_panel_end();
+
+ ui_layout(T, X, NW, 2, 2);
+ if (ui_input(&fc.input_dir, 0) == UI_INPUT_ACCEPT)
+ load_dir(fc.input_dir.text);
+
+ ui_layout(T, X, NW, 2, 2);
+ ui_panel_begin(0, ui.gridsize, 0, 0, 0);
+ {
+ ui_layout(R, NONE, CENTER, 0, 0);
+ if (ui_button("Save"))
+ {
+ fz_snprintf(filename, PATH_MAX, "%s/%s", fc.curdir, fc.input_file.text);
+ rv = 1;
+ }
+ ui_spacer();
+ if (ui_button("\xe2\x9e\x95")) /* U+2795 HEAVY PLUS */
+ bump_file_version(1);
+ if (ui_button("\xe2\x9e\x96")) /* U+2796 HEAVY MINUS */
+ bump_file_version(-1);
+ ui_spacer();
+ ui_layout(ALL, X, CENTER, 0, 0);
+ ui_input(&fc.input_file, 0);
+ }
+ ui_panel_end();
+
+ ui_layout(ALL, BOTH, NW, 2, 2);
+ ui_list_begin(&fc.list_dir, fc.count, 0, 0);
+ for (i = 0; i < fc.count; ++i)
+ {
+ const char *name = fc.files[i].name;
+ char buf[PATH_MAX];
+ if (fc.files[i].is_dir)
+ fz_snprintf(buf, sizeof buf, "\xf0\x9f\x93\x81 %s", name);
+ else
+ fz_snprintf(buf, sizeof buf, "\xf0\x9f\x93\x84 %s", name);
+ if (ui_list_item(&fc.list_dir, &fc.files[i], buf, i==fc.selected))
+ {
+ fc.selected = i;
+ if (fc.files[i].is_dir)
+ {
+ fz_snprintf(buf, sizeof buf, "%s/%s", fc.curdir, name);
+ load_dir(buf);
+ ui.active = NULL;
+ }
+ else
+ {
+ ui_input_init(&fc.input_file, name);
+ }
+ }
+ }
+ ui_list_end(&fc.list_dir);
+ }
+ ui_panel_end();
+
+ return rv;
+}
diff --git a/platform/gl/gl-input.c b/platform/gl/gl-input.c
index ad68a692..73243865 100644
--- a/platform/gl/gl-input.c
+++ b/platform/gl/gl-input.c
@@ -89,6 +89,7 @@ static void ui_input_delete_selection(struct input *input)
char *q = input->p > input->q ? input->p : input->q;
memmove(p, q, input->end - q);
input->end -= q - p;
+ *input->end = 0;
input->p = input->q = p;
}
@@ -111,6 +112,8 @@ static int ui_input_key(struct input *input)
{
switch (ui.key)
{
+ case 0:
+ return UI_INPUT_NONE;
case KEY_LEFT:
if (ui.mod == GLUT_ACTIVE_CTRL + GLUT_ACTIVE_SHIFT)
{
@@ -213,7 +216,7 @@ static int ui_input_key(struct input *input)
break;
case KEY_ESCAPE:
ui.focus = NULL;
- return UI_INPUT_CANCEL;
+ return UI_INPUT_NONE;
case KEY_ENTER:
ui.focus = NULL;
return UI_INPUT_ACCEPT;
@@ -281,66 +284,74 @@ static int ui_input_key(struct input *input)
}
break;
}
- return UI_INPUT_CONTINUE;
+ return UI_INPUT_EDIT;
}
void ui_input_init(struct input *input, const char *text)
{
fz_strlcpy(input->text, text, sizeof input->text);
input->end = input->text + strlen(input->text);
- input->p = input->q = input->text;
+ input->p = input->text;
+ input->q = input->end;
}
int ui_input(struct input *input, int width)
{
fz_irect area;
- float px, qx;
+ float ax, px, qx;
char *p, *q;
int state;
- area = ui_pack(width, ui.lineheight + 4);
+ area = ui_pack(width, ui.lineheight + 6);
if (ui_mouse_inside(&area))
{
ui.hot = input;
+ if (!ui.active || ui.active == input)
+ ui.cursor = GLUT_CURSOR_TEXT;
if (!ui.active && ui.down)
{
- input->p = find_string_location(input->text, input->end, area.x0 + 2, ui.x);
+ input->p = find_string_location(input->text, input->end, area.x0 + 3, ui.x);
ui.active = input;
}
}
if (ui.active == input)
{
- input->q = find_string_location(input->text, input->end, area.x0 + 2, ui.x);
+ input->q = find_string_location(input->text, input->end, area.x0 + 3, ui.x);
ui.focus = input;
}
if (ui.focus == input)
state = ui_input_key(input);
else
- state = 0;
+ state = UI_INPUT_NONE;
- glColor4f(0, 0, 0, 1);
- glRectf(area.x0, area.y0, area.x1, area.y1);
-
- glColor4f(1, 1, 1, 1);
- glRectf(area.x0+1, area.y0+1, area.x1-1, area.y1-1);
+ ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
p = input->p < input->q ? input->p : input->q;
q = input->p > input->q ? input->p : input->q;
- px = area.x0 + 2 + measure_string_part(input->text, p);
+ ax = area.x0 + 4;
+ px = ax + measure_string_part(input->text, p);
qx = px + measure_string_part(p, q);
if (ui.focus == input)
{
- glColor4f(0.6f, 0.6f, 1.0f, 1.0f);
- glRectf(px, area.y0 + 2, qx+1, area.y1 - 2);
+ glColorHex(UI_COLOR_TEXT_SEL_BG);
+ glRectf(px, area.y0 + 3, qx+1, area.y1 - 3);
+ glColorHex(UI_COLOR_TEXT_FG);
+ draw_string_part(ax, area.y0 + 3, input->text, p);
+ glColorHex(UI_COLOR_TEXT_SEL_FG);
+ draw_string_part(px, area.y0 + 3, p, q);
+ glColorHex(UI_COLOR_TEXT_FG);
+ draw_string_part(qx, area.y0 + 3, q, input->end);
+ }
+ else
+ {
+ glColorHex(UI_COLOR_TEXT_FG);
+ draw_string_part(ax, area.y0 + 3, input->text, input->end);
}
-
- glColor4f(0, 0, 0, 1);
- draw_string_part(area.x0 + 2, area.y0 + 2, input->text, input->end);
return state;
}
diff --git a/platform/gl/gl-main.c b/platform/gl/gl-main.c
index 67f69f16..c6aadc0b 100644
--- a/platform/gl/gl-main.c
+++ b/platform/gl/gl-main.c
@@ -1,9 +1,14 @@
#include "gl-app.h"
+#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
+#ifndef PATH_MAX
+#define PATH_MAX 2048
+#endif
+
#ifndef _WIN32
#include <unistd.h> /* for fork and exec */
#endif
@@ -11,9 +16,12 @@
fz_context *ctx = NULL;
pdf_document *pdf = NULL;
pdf_page *page = NULL;
-int page_x_ofs = 0;
-int page_y_ofs = 0;
-fz_matrix page_ctm, page_inv_ctm;
+pdf_annot *selected_annot = NULL;
+fz_stext_page *page_text = NULL;
+fz_matrix draw_page_ctm, view_page_ctm, view_page_inv_ctm;
+fz_rect page_bounds, draw_page_bounds, view_page_bounds;
+fz_irect view_page_area;
+char filename[PATH_MAX];
enum
{
@@ -74,7 +82,6 @@ static int zoom_out(int oldres)
#define MAXRES (zoom_list[nelem(zoom_list)-1])
#define DEFRES 96
-static char filename[2048];
static char *password = "";
static char *anchor = NULL;
static float layout_w = DEFAULT_LAYOUT_W;
@@ -83,10 +90,8 @@ static float layout_em = DEFAULT_LAYOUT_EM;
static char *layout_css = NULL;
static int layout_use_doc_css = 1;
-static const char *title = "MuPDF/GL";
static fz_document *doc = NULL;
static fz_page *fzpage = NULL;
-static fz_stext_page *text = NULL;
static fz_outline *outline = NULL;
static fz_link *links = NULL;
@@ -98,6 +103,7 @@ static int canvas_x = 0, canvas_w = 100;
static int canvas_y = 0, canvas_h = 100;
static int outline_w = 260;
+static int annotate_w = 220;
static int oldinvert = 0, currentinvert = 0;
static int oldpage = 0, currentpage = 0;
@@ -106,6 +112,7 @@ static float oldrotate = 0, currentrotate = 0;
static int isfullscreen = 0;
static int showoutline = 0;
+static int showannotate = 0;
static int showlinks = 0;
static int showsearch = 0;
static int showinfo = 0;
@@ -132,29 +139,79 @@ static int search_hit_page = -1;
static int search_hit_count = 0;
static fz_rect search_hit_bbox[5000];
-static void update_title(void)
+static char error_message[256];
+static void error_dialog(void)
+{
+ ui_dialog_begin(500, (ui.gridsize+4)*3);
+ ui_layout(T, NONE, NW, 2, 2);
+ ui_label("%C %s", 0x1f4a3, error_message); /* BOMB */
+ ui_layout(B, NONE, S, 2, 2);
+ if (ui_button("Quit") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE || ui.key == 'q')
+ exit(1);
+ ui_dialog_end();
+}
+void ui_show_error_dialog(const char *message)
+{
+ fz_strlcpy(error_message, message, sizeof error_message);
+ ui.dialog = error_dialog;
+}
+
+static char warning_message[256];
+static void warning_dialog(void)
{
- static char buf[256];
- size_t n = strlen(title);
+ ui_dialog_begin(500, (ui.gridsize+4)*3);
+ ui_layout(T, NONE, NW, 2, 2);
+ ui_label("%C %s", 0x26a0, warning_message); /* WARNING SIGN */
+ ui_layout(B, NONE, S, 2, 2);
+ if (ui_button("Okay") || ui.key == KEY_ENTER || ui.key == KEY_ESCAPE)
+ ui.dialog = NULL;
+ ui_dialog_end();
+}
+void ui_show_warning_dialog(const char *message)
+{
+ fz_strlcpy(warning_message, message, sizeof warning_message);
+ ui.dialog = warning_dialog;
+}
+
+void update_title(void)
+{
+ char buf[256];
+ char *title = "MuPDF/GL";
+ char *extra = "";
+ size_t n;
+
+ title = strrchr(filename, '/');
+ if (!title)
+ title = strrchr(filename, '\\');
+ if (title)
+ ++title;
+ else
+ title = filename;
+
+ if (pdf && pdf->dirty)
+ extra = "*";
+
+ n = strlen(title);
if (n > 50)
- sprintf(buf, "...%s - %d / %d", title + n - 50, currentpage + 1, fz_count_pages(ctx, doc));
+ sprintf(buf, "...%s%s - %d / %d", title + n - 50, extra, currentpage + 1, fz_count_pages(ctx, doc));
else
- sprintf(buf, "%s - %d / %d", title, currentpage + 1, fz_count_pages(ctx, doc));
+ sprintf(buf, "%s%s - %d / %d", title, extra, currentpage + 1, fz_count_pages(ctx, doc));
glutSetWindowTitle(buf);
glutSetIconTitle(buf);
}
void load_page(void)
{
- fz_rect rect;
- fz_irect irect;
+ fz_irect area;
- fz_scale(&page_ctm, currentzoom / 72, currentzoom / 72);
- fz_pre_rotate(&page_ctm, -currentrotate);
- fz_invert_matrix(&page_inv_ctm, &page_ctm);
+ fz_scale(&draw_page_ctm, currentzoom / 72, currentzoom / 72);
+ fz_pre_rotate(&draw_page_ctm, -currentrotate);
- fz_drop_stext_page(ctx, text);
- text = NULL;
+ /* clear all editor selections */
+ selected_annot = NULL;
+
+ fz_drop_stext_page(ctx, page_text);
+ page_text = NULL;
fz_drop_link(ctx, links);
links = NULL;
fz_drop_page(ctx, fzpage);
@@ -165,26 +222,25 @@ void load_page(void)
page = (pdf_page*)fzpage;
links = fz_load_links(ctx, fzpage);
- text = fz_new_stext_page_from_page(ctx, fzpage, NULL);
-
+ page_text = fz_new_stext_page_from_page(ctx, fzpage, NULL);
/* compute bounds here for initial window size */
- fz_bound_page(ctx, fzpage, &rect);
- fz_transform_rect(&rect, &page_ctm);
- fz_irect_from_rect(&irect, &rect);
- page_tex.w = irect.x1 - irect.x0;
- page_tex.h = irect.y1 - irect.y0;
+ fz_bound_page(ctx, fzpage, &page_bounds);
+ draw_page_bounds = page_bounds;
+ fz_transform_rect(&draw_page_bounds, &draw_page_ctm);
+ fz_irect_from_rect(&area, &draw_page_bounds);
+ page_tex.w = area.x1 - area.x0;
+ page_tex.h = area.y1 - area.y0;
}
void render_page(void)
{
fz_pixmap *pix;
- fz_scale(&page_ctm, currentzoom / 72, currentzoom / 72);
- fz_pre_rotate(&page_ctm, -currentrotate);
- fz_invert_matrix(&page_inv_ctm, &page_ctm);
+ fz_scale(&draw_page_ctm, currentzoom / 72, currentzoom / 72);
+ fz_pre_rotate(&draw_page_ctm, -currentrotate);
- pix = fz_new_pixmap_from_page(ctx, fzpage, &page_ctm, fz_device_rgb(ctx), 0);
+ pix = fz_new_pixmap_from_page(ctx, fzpage, &draw_page_ctm, fz_device_rgb(ctx), 0);
if (currentinvert)
{
fz_invert_pixmap(ctx, pix);
@@ -201,14 +257,14 @@ static struct mark save_mark()
mark.page = currentpage;
mark.scroll.x = scroll_x;
mark.scroll.y = scroll_y;
- fz_transform_point(&mark.scroll, &page_inv_ctm);
+ fz_transform_point(&mark.scroll, &view_page_inv_ctm);
return mark;
}
static void restore_mark(struct mark mark)
{
currentpage = mark.page;
- fz_transform_point(&mark.scroll, &page_ctm);
+ fz_transform_point(&mark.scroll, &draw_page_ctm);
scroll_x = mark.scroll.x;
scroll_y = mark.scroll.y;
}
@@ -257,7 +313,7 @@ static void jump_to_page_xy(int newpage, float x, float y)
{
fz_point p = { x, y };
newpage = fz_clampi(newpage, 0, fz_count_pages(ctx, doc) - 1);
- fz_transform_point(&p, &page_ctm);
+ fz_transform_point(&p, &draw_page_ctm);
clear_future();
push_history();
currentpage = newpage;
@@ -313,7 +369,7 @@ static void do_outline_imp(struct list *list, int end, fz_outline *node, int dep
n = node->next->page;
selected = (currentpage == p || (currentpage > p && currentpage < n));
- if (ui_list_item(list, node, depth * ui.lineheight, node->title, selected))
+ if (ui_list_item_x(list, node, depth * ui.lineheight, node->title, selected))
jump_to_page_xy(p, node->x, node->y);
if (node->down)
@@ -325,11 +381,12 @@ static void do_outline_imp(struct list *list, int end, fz_outline *node, int dep
static void do_outline(fz_outline *node)
{
- static struct list list = {};
+ static struct list list;
ui_layout(L, BOTH, NW, 0, 0);
ui_list_begin(&list, count_outline(node), outline_w, 0);
do_outline_imp(&list, fz_count_pages(ctx, doc), node, 1);
ui_list_end(&list);
+ ui_splitter(&outline_w, 150, 500, R);
}
static void do_links(fz_link *link)
@@ -344,9 +401,8 @@ static void do_links(fz_link *link)
while (link)
{
bounds = link->rect;
- fz_transform_rect(&bounds, &page_ctm);
+ fz_transform_rect(&bounds, &view_page_ctm);
fz_irect_from_rect(&area, &bounds);
- fz_translate_irect(&area, page_x_ofs, page_y_ofs);
if (ui_mouse_inside(&area))
{
@@ -389,13 +445,13 @@ static void do_links(fz_link *link)
glDisable(GL_BLEND);
}
-static void do_page_selection(int x0, int y0, int x1, int y1)
+static void do_page_selection(void)
{
static fz_point pt = { 0, 0 };
fz_rect hits[1000];
int i, n;
- if (ui.x >= x0 && ui.x < x1 && ui.y >= y0 && ui.y < y1)
+ if (ui_mouse_inside(&view_page_area))
{
ui.hot = &pt;
if (!ui.active && ui.right)
@@ -408,16 +464,13 @@ static void do_page_selection(int x0, int y0, int x1, int y1)
if (ui.active == &pt)
{
- int xofs = x0 - page_tex.x;
- int yofs = y0 - page_tex.y;
-
- fz_point page_a = { pt.x - xofs, pt.y - yofs };
- fz_point page_b = { ui.x - xofs, ui.y - yofs };
+ fz_point page_a = { pt.x, pt.y };
+ fz_point page_b = { ui.x, ui.y };
- fz_transform_point(&page_a, &page_inv_ctm);
- fz_transform_point(&page_b, &page_inv_ctm);
+ fz_transform_point(&page_a, &view_page_inv_ctm);
+ fz_transform_point(&page_b, &view_page_inv_ctm);
- n = fz_highlight_selection(ctx, text, page_a, page_b, hits, nelem(hits));
+ n = fz_highlight_selection(ctx, page_text, page_a, page_b, hits, nelem(hits));
glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ZERO); /* invert destination color */
glEnable(GL_BLEND);
@@ -425,8 +478,8 @@ static void do_page_selection(int x0, int y0, int x1, int y1)
glColor4f(1, 1, 1, 1);
for (i = 0; i < n; ++i)
{
- fz_transform_rect(&hits[i], &page_ctm);
- glRectf(hits[i].x0+xofs, hits[i].y0+yofs, hits[i].x1 + 1 + xofs, hits[i].y1 + 1 + yofs);
+ fz_transform_rect(&hits[i], &view_page_ctm);
+ glRectf(hits[i].x0, hits[i].y0, hits[i].x1 + 1, hits[i].y1 + 1);
}
glDisable(GL_BLEND);
@@ -435,9 +488,9 @@ static void do_page_selection(int x0, int y0, int x1, int y1)
{
char *s;
#ifdef _WIN32
- s = fz_copy_selection(ctx, text, page_a, page_b, 1);
+ s = fz_copy_selection(ctx, page_text, page_a, page_b, 1);
#else
- s = fz_copy_selection(ctx, text, page_a, page_b, 0);
+ s = fz_copy_selection(ctx, page_text, page_a, page_b, 0);
#endif
ui_set_clipboard(s);
fz_free(ctx, s);
@@ -457,9 +510,8 @@ static void do_search_hits(void)
for (i = 0; i < search_hit_count; ++i)
{
bounds = search_hit_bbox[i];
- fz_transform_rect(&bounds, &page_ctm);
+ fz_transform_rect(&bounds, &view_page_ctm);
fz_irect_from_rect(&area, &bounds);
- fz_translate_irect(&area, page_x_ofs, page_y_ofs);
glColor4f(1, 0, 0, 0.4f);
glRectf(area.x0, area.y0, area.x1, area.y1);
@@ -477,9 +529,9 @@ static void do_forms(void)
if (!pdf || search_active)
return;
- p.x = page_x_ofs + ui.x;
- p.y = page_y_ofs + ui.y;
- fz_transform_point(&p, &page_inv_ctm);
+ p.x = ui.x;
+ p.y = ui.y;
+ fz_transform_point(&p, &view_page_inv_ctm);
if (ui.down && !ui.active)
{
@@ -533,7 +585,7 @@ static void shrinkwrap(void)
{
int screen_w = glutGet(GLUT_SCREEN_WIDTH) - SCREEN_FURNITURE_W;
int screen_h = glutGet(GLUT_SCREEN_HEIGHT) - SCREEN_FURNITURE_H;
- int w = page_tex.w + (showoutline ? outline_w : 0);
+ int w = page_tex.w + (showoutline ? outline_w + 4 : 0) + (showannotate ? annotate_w : 0);
int h = page_tex.h;
if (screen_w > 0 && w > screen_w)
w = screen_w;
@@ -544,6 +596,36 @@ static void shrinkwrap(void)
glutReshapeWindow(w, h);
}
+static struct input input_password;
+static void password_dialog(void)
+{
+ int is;
+ ui_dialog_begin(400, (ui.gridsize+4)*3);
+ {
+ ui_layout(T, X, NW, 2, 2);
+ ui_label("Password:");
+ is = ui_input(&input_password, 200);
+
+ ui_layout(B, X, NW, 2, 2);
+ ui_panel_begin(0, ui.gridsize, 0, 0, 0);
+ {
+ ui_layout(R, NONE, S, 0, 0);
+ if (ui_button("Cancel"))
+ exit(1);
+ ui_spacer();
+ if (ui_button("Okay") || is == UI_INPUT_ACCEPT)
+ {
+ password = input_password.text;
+ ui.dialog = NULL;
+ reload();
+ shrinkwrap();
+ }
+ }
+ ui_panel_end();
+ }
+ ui_dialog_end();
+}
+
static void load_document(void)
{
fz_drop_outline(ctx, outline);
@@ -554,8 +636,13 @@ static void load_document(void)
{
if (!fz_authenticate_password(ctx, doc, password))
{
+ fz_drop_document(ctx, doc);
+ doc = NULL;
fprintf(stderr, "Invalid password.\n");
- exit(1);
+ ui_input_init(&input_password, "");
+ ui.focus = &input_password;
+ ui.dialog = password_dialog;
+ return;
}
}
@@ -583,12 +670,15 @@ static void load_document(void)
currentpage = fz_clampi(currentpage, 0, fz_count_pages(ctx, doc) - 1);
}
-static void reload(void)
+void reload(void)
{
load_document();
- load_page();
- render_page();
- update_title();
+ if (doc)
+ {
+ load_page();
+ render_page();
+ update_title();
+ }
}
static void toggle_outline(void)
@@ -601,6 +691,16 @@ static void toggle_outline(void)
}
}
+static void toggle_annotate(void)
+{
+ if (pdf)
+ {
+ showannotate = !showannotate;
+ if (canvas_w == page_tex.w && canvas_h == page_tex.h)
+ shrinkwrap();
+ }
+}
+
static void set_zoom(int z, int cx, int cy)
{
z = fz_clamp(z, MINRES, MAXRES);
@@ -681,6 +781,7 @@ static void smart_move_forward(void)
static void clear_search(void)
{
+ showsearch = 0;
search_hit_page = -1;
search_hit_count = 0;
}
@@ -697,8 +798,9 @@ static void do_app(void)
{
switch (ui.key)
{
- case KEY_ESCAPE: clear_search(); break;
+ case KEY_ESCAPE: clear_search(); selected_annot = NULL; break;
case KEY_F1: showhelp = !showhelp; break;
+ case 'a': toggle_annotate(); break;
case 'o': toggle_outline(); break;
case 'L': showlinks = !showlinks; break;
case 'i': showinfo = !showinfo; break;
@@ -817,51 +919,27 @@ static void do_app(void)
}
}
-static int do_info_line(int x, int y, char *label, char *text)
-{
- char buf[512];
- fz_snprintf(buf, sizeof buf, "%s: %s", label, text);
- ui_draw_string(x, y, buf);
- return y + ui.lineheight;
-}
-
static void do_info(void)
{
- char buf[256];
-
- int x = canvas_x + 4 * ui.lineheight;
- int y = canvas_y + 4 * ui.lineheight;
- int w = canvas_w - 8 * ui.lineheight;
- int h = 9 * ui.lineheight;
-
- glBegin(GL_TRIANGLE_STRIP);
- {
- glColor4f(0.9f, 0.9f, 0.9f, 1.0f);
- glVertex2f(x, y);
- glVertex2f(x, y + h);
- glVertex2f(x + w, y);
- glVertex2f(x + w, y + h);
- }
- glEnd();
+ char buf[100];
- x += ui.lineheight;
- y += ui.lineheight + ui.baseline;
+ ui_dialog_begin(500, 10 * ui.lineheight);
+ ui_layout(T, X, W, 0, 0);
- glColor4f(0, 0, 0, 1);
if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_TITLE, buf, sizeof buf) > 0)
- y = do_info_line(x, y, "Title", buf);
+ ui_label("Title: %s", buf);
if (fz_lookup_metadata(ctx, doc, FZ_META_INFO_AUTHOR, buf, sizeof buf) > 0)
- y = do_info_line(x, y, "Author", buf);
+ ui_label("Author: %s", buf);
if (fz_lookup_metadata(ctx, doc, FZ_META_FORMAT, buf, sizeof buf) > 0)
- y = do_info_line(x, y, "Format", buf);
+ ui_label("Format: %s", buf);
if (fz_lookup_metadata(ctx, doc, FZ_META_ENCRYPTION, buf, sizeof buf) > 0)
- y = do_info_line(x, y, "Encryption", buf);
+ ui_label("Encryption: %s", buf);
if (pdf_specifics(ctx, doc))
{
if (fz_lookup_metadata(ctx, doc, "info:Creator", buf, sizeof buf) > 0)
- y = do_info_line(x, y, "PDF Creator", buf);
+ ui_label("PDF Creator: %s", buf);
if (fz_lookup_metadata(ctx, doc, "info:Producer", buf, sizeof buf) > 0)
- y = do_info_line(x, y, "PDF Producer", buf);
+ ui_label("PDF Producer: %s", buf);
buf[0] = 0;
if (fz_has_permission(ctx, doc, FZ_PERMISSION_PRINT))
fz_strlcat(buf, "print, ", sizeof buf);
@@ -875,74 +953,74 @@ static void do_info(void)
buf[strlen(buf)-2] = 0;
else
fz_strlcat(buf, "none", sizeof buf);
- y = do_info_line(x, y, "Permissions", buf);
+ ui_label("Permissions: %s", buf);
}
+
+ ui_dialog_end();
}
-static int do_help_line(int x, int y, char *label, char *text)
+static void do_help_line(char *label, char *text)
{
- ui_draw_string(x, y, label);
- ui_draw_string(x+100, y, text);
- return y + ui.lineheight;
+ ui_panel_begin(0, ui.lineheight, 0, 0, 0);
+ {
+ ui_layout(L, NONE, W, 0, 0);
+ ui_panel_begin(100, ui.lineheight, 0, 0, 0);
+ ui_layout(R, NONE, W, 20, 0);
+ ui_label("%s", label);
+ ui_panel_end();
+
+ ui_layout(ALL, X, W, 0, 0);
+ ui_panel_begin(0, ui.lineheight, 0, 0, 0);
+ ui_label("%s", text);
+ ui_panel_end();
+ }
+ ui_panel_end();
}
static void do_help(void)
{
- int x = canvas_x + 4 * ui.lineheight;
- int y = canvas_y + 4 * ui.lineheight;
- int w = canvas_w - 8 * ui.lineheight;
- int h = 38 * ui.lineheight;
-
- glBegin(GL_TRIANGLE_STRIP);
- {
- glColor4f(0.9f, 0.9f, 0.9f, 1.0f);
- glVertex2f(x, y);
- glVertex2f(x, y + h);
- glVertex2f(x + w, y);
- glVertex2f(x + w, y + h);
- }
- glEnd();
-
- x += ui.lineheight;
- y += ui.lineheight + ui.baseline;
-
- glColor4f(0, 0, 0, 1);
- y = do_help_line(x, y, "MuPDF", FZ_VERSION);
- y += ui.lineheight;
- y = do_help_line(x, y, "F1", "show this message");
- y = do_help_line(x, y, "i", "show document information");
- y = do_help_line(x, y, "o", "show/hide outline");
- y = do_help_line(x, y, "L", "show/hide links");
- y = do_help_line(x, y, "r", "reload file");
- y = do_help_line(x, y, "q", "quit");
- y += ui.lineheight;
- y = do_help_line(x, y, "I", "toggle inverted color mode");
- y = do_help_line(x, y, "f", "fullscreen window");
- y = do_help_line(x, y, "w", "shrink wrap window");
- y = do_help_line(x, y, "W or H", "fit to width or height");
- y = do_help_line(x, y, "Z", "fit to page");
- y = do_help_line(x, y, "z", "reset zoom");
- y = do_help_line(x, y, "N z", "set zoom to N");
- y = do_help_line(x, y, "+ or -", "zoom in or out");
- y = do_help_line(x, y, "[ or ]", "rotate left or right");
- y = do_help_line(x, y, "arrow keys", "pan in small increments");
- y += ui.lineheight;
- y = do_help_line(x, y, "b", "smart move backward");
- y = do_help_line(x, y, "Space", "smart move forward");
- y = do_help_line(x, y, ", or PgUp", "go backward");
- y = do_help_line(x, y, ". or PgDn", "go forward");
- y = do_help_line(x, y, "<", "go backward 10 pages");
- y = do_help_line(x, y, ">", "go forward 10 pages");
- y = do_help_line(x, y, "N g", "go to page N");
- y = do_help_line(x, y, "G", "go to last page");
- y += ui.lineheight;
- y = do_help_line(x, y, "t", "go backward in history");
- y = do_help_line(x, y, "T", "go forward in history");
- y = do_help_line(x, y, "N m", "save location in bookmark N");
- y = do_help_line(x, y, "N t", "go to bookmark N");
- y += ui.lineheight;
- y = do_help_line(x, y, "/ or ?", "search for text");
- y = do_help_line(x, y, "n or N", "repeat search");
+ ui_dialog_begin(500, 35 * ui.lineheight);
+ ui_layout(T, X, W, 0, 0);
+
+ do_help_line("MuPDF", FZ_VERSION);
+ ui_spacer();
+ do_help_line("F1", "show this message");
+ do_help_line("i", "show document information");
+ do_help_line("o", "show/hide outline");
+ do_help_line("a", "show/hide annotation editor");
+ do_help_line("L", "show/hide links");
+ do_help_line("r", "reload file");
+ do_help_line("q", "quit");
+ ui_spacer();
+ do_help_line("I", "toggle inverted color mode");
+ do_help_line("f", "fullscreen window");
+ do_help_line("w", "shrink wrap window");
+ do_help_line("W or H", "fit to width or height");
+ do_help_line("Z", "fit to page");
+ do_help_line("z", "reset zoom");
+ do_help_line("N z", "set zoom to N");
+ do_help_line("+ or -", "zoom in or out");
+ do_help_line("[ or ]", "rotate left or right");
+ do_help_line("arrow keys", "pan in small increments");
+ ui_spacer();
+ do_help_line("b", "smart move backward");
+ do_help_line("Space", "smart move forward");
+ do_help_line(", or PgUp", "go backward");
+ do_help_line(". or PgDn", "go forward");
+ do_help_line("<", "go backward 10 pages");
+ do_help_line(">", "go forward 10 pages");
+ do_help_line("N g", "go to page N");
+ do_help_line("G", "go to last page");
+ ui_spacer();
+ do_help_line("t", "go backward in history");
+ do_help_line("T", "go forward in history");
+ do_help_line("N m", "save location in bookmark N");
+ do_help_line("N t", "go to bookmark N");
+ ui_spacer();
+ do_help_line("/ or ?", "search for text");
+ do_help_line("n or N", "repeat search");
+
+ ui_dialog_end();
}
static void do_canvas(void)
@@ -952,7 +1030,7 @@ static void do_canvas(void)
static int saved_ui_x = 0;
static int saved_ui_y = 0;
fz_irect area;
- int state;
+ int page_x, page_y;
ui_layout(ALL, BOTH, NW, 0, 0);
ui_pack_push(area = ui_pack(0, 0));
@@ -992,39 +1070,55 @@ static void do_canvas(void)
if (page_tex.w <= canvas_w)
{
scroll_x = 0;
- page_x_ofs = canvas_x + (canvas_w - page_tex.w) / 2;
+ page_x = canvas_x + (canvas_w - page_tex.w) / 2;
}
else
{
scroll_x = fz_clamp(scroll_x, 0, page_tex.w - canvas_w);
- page_x_ofs = canvas_x - scroll_x;
+ page_x = canvas_x - scroll_x;
}
if (page_tex.h <= canvas_h)
{
scroll_y = 0;
- page_y_ofs = canvas_y + (canvas_h - page_tex.h) / 2;
+ page_y = canvas_y + (canvas_h - page_tex.h) / 2;
}
else
{
scroll_y = fz_clamp(scroll_y, 0, page_tex.h - canvas_h);
- page_y_ofs = canvas_y - scroll_y;
+ page_y = canvas_y - scroll_y;
}
- page_x_ofs -= page_tex.x;
- page_y_ofs -= page_tex.y;
- ui_draw_image(&page_tex, page_x_ofs, page_y_ofs);
+ view_page_ctm = draw_page_ctm;
+ view_page_ctm.e += page_x;
+ view_page_ctm.f += page_y;
+ fz_invert_matrix(&view_page_inv_ctm, &view_page_ctm);
+ view_page_bounds = page_bounds;
+ fz_transform_rect(&view_page_bounds, &view_page_ctm);
+ fz_irect_from_rect(&view_page_area, &view_page_bounds);
+
+ ui_draw_image(&page_tex, page_x, page_y);
if (search_active)
{
- ui_layout(T, X, NW, 2, 2);
+ ui_layout(T, X, NW, 0, 0);
+ ui_panel_begin(0, ui.gridsize+8, 4, 4, 1);
+ ui_layout(L, NONE, W, 2, 0);
ui_label("Searching page %d of %d.", search_page + 1, fz_count_pages(ctx, doc));
+ ui_panel_end();
}
else
{
- do_forms();
- do_links(links);
- do_page_selection(page_x_ofs, page_y_ofs, page_x_ofs+page_tex.w, page_y_ofs+page_tex.h);
+ if (showannotate)
+ {
+ do_annotate_canvas(area);
+ }
+ else
+ {
+ do_forms();
+ do_links(links);
+ do_page_selection();
+ }
if (search_hit_page == currentpage && search_hit_count > 0)
do_search_hits();
@@ -1033,12 +1127,11 @@ static void do_canvas(void)
if (showsearch)
{
ui_layout(T, X, NW, 0, 0);
- state = ui_input(&search_input, 0);
- if (state == UI_INPUT_CANCEL)
- {
- showsearch = 0;
- }
- else if (state == UI_INPUT_ACCEPT)
+ ui_panel_begin(0, ui.gridsize+8, 4, 4, 1);
+ ui_layout(L, NONE, W, 2, 0);
+ ui_label("Search:");
+ ui_layout(ALL, X, E, 2, 0);
+ if (ui_input(&search_input, 0) == UI_INPUT_ACCEPT)
{
showsearch = 0;
search_page = -1;
@@ -1054,16 +1147,17 @@ static void do_canvas(void)
search_page = currentpage;
}
}
+ if (ui.focus != &search_input)
+ showsearch = 0;
+ ui_panel_end();
}
ui_pack_pop();
glDisable(GL_SCISSOR_TEST);
}
-void run_main_loop(void)
+void do_main(void)
{
- ui_begin();
-
if (search_active)
{
int start_time = glutGet(GLUT_ELAPSED_TIME);
@@ -1121,13 +1215,34 @@ void run_main_loop(void)
if (showoutline)
do_outline(outline);
+ if (showannotate)
+ {
+ ui_layout(R, BOTH, NW, 0, 0);
+ ui_panel_begin(annotate_w, 0, 4, 4, 1);
+ do_annotate_panel();
+ ui_panel_end();
+ }
+
do_canvas();
if (showinfo)
do_info();
else if (showhelp)
do_help();
+}
+void run_main_loop(void)
+{
+ ui_begin();
+ fz_try(ctx)
+ {
+ if (ui.dialog)
+ ui.dialog();
+ else
+ do_main();
+ }
+ fz_catch(ctx)
+ ui_show_error_dialog(fz_caught_message(ctx));
ui_end();
}
@@ -1146,6 +1261,29 @@ static void usage(const char *argv0)
exit(1);
}
+static int document_filter(const char *filename)
+{
+ return !!fz_recognize_document(ctx, filename);
+}
+
+static void do_open_document_dialog(void)
+{
+ if (ui_open_file(filename))
+ {
+ ui.dialog = NULL;
+ if (filename[0] == 0)
+ exit(0);
+ load_document();
+ if (doc)
+ {
+ load_page();
+ render_page();
+ shrinkwrap();
+ update_title();
+ }
+ }
+}
+
#ifdef _MSC_VER
int main_utf8(int argc, char **argv)
#else
@@ -1171,32 +1309,6 @@ int main(int argc, char **argv)
}
}
- if (fz_optind < argc)
- {
- fz_strlcpy(filename, argv[fz_optind++], sizeof filename);
- }
- else
- {
-#ifdef _WIN32
- win_install();
- if (!win_open_file(filename, sizeof filename))
- exit(0);
-#else
- usage(argv[0]);
-#endif
- }
-
- if (fz_optind < argc)
- anchor = argv[fz_optind++];
-
- title = strrchr(filename, '/');
- if (!title)
- title = strrchr(filename, '\\');
- if (title)
- ++title;
- else
- title = filename;
-
ctx = fz_new_context(NULL, NULL, 0);
fz_register_document_handlers(ctx);
if (layout_css)
@@ -1207,14 +1319,52 @@ int main(int argc, char **argv)
}
fz_set_use_document_css(ctx, layout_use_doc_css);
- load_document();
- load_page();
+ if (fz_optind < argc)
+ {
+ fz_strlcpy(filename, argv[fz_optind++], sizeof filename);
+ if (fz_optind < argc)
+ anchor = argv[fz_optind++];
- ui_init(page_tex.w, page_tex.h, title);
- ui_input_init(&search_input, "");
+ fz_try(ctx)
+ {
+ page_tex.w = 600;
+ page_tex.h = 700;
+ load_document();
+ if (doc) load_page();
+ }
+ fz_always(ctx)
+ {
+ ui_init(page_tex.w, page_tex.h, "MuPDF: Loading...");
+ ui_input_init(&search_input, "");
+ }
+ fz_catch(ctx)
+ {
+ ui_show_error_dialog(fz_caught_message(ctx));
+ }
- render_page();
- update_title();
+ fz_try(ctx)
+ {
+ if (doc)
+ {
+ render_page();
+ update_title();
+ }
+ }
+ fz_catch(ctx)
+ {
+ ui_show_error_dialog(fz_caught_message(ctx));
+ }
+ }
+ else
+ {
+#ifdef _WIN32
+ win_install();
+#endif
+ ui_init(640, 700, "MuPDF: Open document");
+ ui_input_init(&search_input, "");
+ ui_init_open_file(".", document_filter);
+ ui.dialog = do_open_document_dialog;
+ }
glutMainLoop();
@@ -1225,7 +1375,7 @@ int main(int argc, char **argv)
fz_debug_store(ctx);
#endif
- fz_drop_stext_page(ctx, text);
+ fz_drop_stext_page(ctx, page_text);
fz_drop_link(ctx, links);
fz_drop_page(ctx, fzpage);
fz_drop_outline(ctx, outline);
diff --git a/platform/gl/gl-ui.c b/platform/gl/gl-ui.c
index df65ab2a..606afed9 100644
--- a/platform/gl/gl-ui.c
+++ b/platform/gl/gl-ui.c
@@ -19,9 +19,10 @@ enum
DEFAULT_UI_FONTSIZE = 15,
DEFAULT_UI_BASELINE = 14,
DEFAULT_UI_LINEHEIGHT = 18,
+ DEFAULT_UI_GRIDSIZE = DEFAULT_UI_LINEHEIGHT + 6,
};
-struct ui ui = {};
+struct ui ui;
#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
@@ -148,6 +149,61 @@ void ui_draw_image(struct texture *tex, float x, float y)
glDisable(GL_BLEND);
}
+void glColorHex(unsigned int hex)
+{
+ float r = ((hex>>16)&0xff) / 255.0f;
+ float g = ((hex>>8)&0xff) / 255.0f;
+ float b = ((hex)&0xff) / 255.0f;
+ glColor3f(r, g, b);
+}
+
+void ui_draw_bevel_imp(fz_irect area, unsigned ot, unsigned it, unsigned ib, unsigned ob)
+{
+ glColorHex(ot);
+ glRectf(area.x0, area.y0, area.x1-1, area.y0+1);
+ glRectf(area.x0, area.y0+1, area.x0+1, area.y1-1);
+ glColorHex(ob);
+ glRectf(area.x1-1, area.y0, area.x1, area.y1);
+ glRectf(area.x0, area.y1-1, area.x1-1, area.y1);
+ glColorHex(it);
+ glRectf(area.x0+1, area.y0+1, area.x1-2, area.y0+2);
+ glRectf(area.x0+1, area.y0+2, area.x0+2, area.y1-2);
+ glColorHex(ib);
+ glRectf(area.x1-2, area.y0+1, area.x1-1, area.y1-1);
+ glRectf(area.x0+1, area.y1-2, area.x1-2, area.y1-1);
+}
+
+
+void ui_draw_bevel(fz_irect area, int depressed)
+{
+ if (depressed)
+ ui_draw_bevel_imp(area, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4);
+ else
+ ui_draw_bevel_imp(area, UI_COLOR_BEVEL_4, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1);
+}
+
+void ui_draw_ibevel(fz_irect area, int depressed)
+{
+ if (depressed)
+ ui_draw_bevel_imp(area, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4);
+ else
+ ui_draw_bevel_imp(area, UI_COLOR_BEVEL_3, UI_COLOR_BEVEL_4, UI_COLOR_BEVEL_2, UI_COLOR_BEVEL_1);
+}
+
+void ui_draw_bevel_rect(fz_irect area, unsigned int fill, int depressed)
+{
+ ui_draw_bevel(area, depressed);
+ glColorHex(fill);
+ glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2);
+}
+
+void ui_draw_ibevel_rect(fz_irect area, unsigned int fill, int depressed)
+{
+ ui_draw_ibevel(area, depressed);
+ glColorHex(fill);
+ glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2);
+}
+
#if defined(FREEGLUT) && (GLUT_API_VERSION >= 6)
static void on_keyboard(int key, int x, int y)
#else
@@ -273,6 +329,13 @@ static void on_motion(int x, int y)
ui_invalidate();
}
+static void on_passive_motion(int x, int y)
+{
+ ui.x = x;
+ ui.y = y;
+ ui_invalidate();
+}
+
static void on_reshape(int w, int h)
{
ui.window_w = w;
@@ -324,7 +387,7 @@ void ui_init(int w, int h, const char *title)
glutSpecialFunc(on_special);
glutMouseFunc(on_mouse);
glutMotionFunc(on_motion);
- glutPassiveMotionFunc(on_motion);
+ glutPassiveMotionFunc(on_passive_motion);
glutMouseWheelFunc(on_wheel);
has_ARB_texture_non_power_of_two = glutExtensionSupported("GL_ARB_texture_non_power_of_two");
@@ -333,11 +396,10 @@ void ui_init(int w, int h, const char *title)
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
- memset(&ui, 0, sizeof ui);
-
ui.fontsize = DEFAULT_UI_FONTSIZE;
ui.baseline = DEFAULT_UI_BASELINE;
ui.lineheight = DEFAULT_UI_LINEHEIGHT;
+ ui.gridsize = DEFAULT_UI_GRIDSIZE;
ui_init_fonts(ui.fontsize);
}
@@ -370,6 +432,10 @@ void ui_begin(void)
ui.layout->padx = 0;
ui.layout->pady = 0;
+ ui.popup_count = 0;
+
+ ui.cursor = GLUT_CURSOR_INHERIT;
+
glViewport(0, 0, ui.window_w, ui.window_h);
glClearColor(0.3f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
@@ -384,13 +450,30 @@ void ui_begin(void)
void ui_end(void)
{
- int code = glGetError();
+ int code;
+
+ if (ui.popup_count > 0)
+ ui_draw_popup();
+
+ if (ui.cursor != ui.last_cursor)
+ {
+ glutSetCursor(ui.cursor);
+ ui.last_cursor = ui.cursor;
+ }
+
+ code = glGetError();
if (code != GL_NO_ERROR)
fz_warn(ctx, "glGetError: %s", ogl_error_string(code));
if (!ui.active && (ui.down || ui.middle || ui.right))
ui.active = "dummy";
+ if ((ui.grab_down && !ui.down) || (ui.grab_middle && !ui.middle) || (ui.grab_right && !ui.right))
+ {
+ ui.grab_down = ui.grab_middle = ui.grab_right = 0;
+ ui.active = NULL;
+ }
+
if (ui.active)
{
if (ui.active != ui.focus)
@@ -403,12 +486,6 @@ void ui_end(void)
}
}
- if ((ui.grab_down && !ui.down) || (ui.grab_middle && !ui.middle) || (ui.grab_right && !ui.right))
- {
- ui.grab_down = ui.grab_middle = ui.grab_right = 0;
- ui.active = NULL;
- }
-
glutSwapBuffers();
}
@@ -557,20 +634,16 @@ void ui_layout(enum side side, enum fill fill, enum anchor anchor, int padx, int
ui.layout->pady = pady;
}
-void ui_panel_begin(int w, int h, int opaque)
+void ui_panel_begin(int w, int h, int padx, int pady, int opaque)
{
fz_irect area = ui_pack(w, h);
if (opaque)
{
- fz_irect total = {
- area.x0 - ui.layout->padx,
- area.y0 - ui.layout->pady,
- area.x1 + ui.layout->padx,
- area.y1 + ui.layout->pady
- };
- glColor4f(0.8f, 0.8f, 0.8f, 1);
- glRectf(total.x0, total.y0, total.x1, total.y1);
+ glColorHex(UI_COLOR_PANEL);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
}
+ area.x0 += padx; area.y0 += padx;
+ area.x1 -= pady; area.y1 -= pady;
ui_pack_push(area);
}
@@ -579,6 +652,27 @@ void ui_panel_end(void)
ui_pack_pop();
}
+void ui_dialog_begin(int w, int h)
+{
+ fz_irect area;
+ int x, y;
+ w += 24 + 4;
+ h += 24 + 4;
+ if (w > ui.window_w) w = ui.window_w - 20;
+ if (h > ui.window_h) h = ui.window_h - 20;
+ x = (ui.window_w-w)/2;
+ y = (ui.window_h-h)/3;
+ area = fz_make_irect(x, y, x+w, y+h);
+ ui_draw_bevel_rect(area, UI_COLOR_PANEL, 0);
+ fz_expand_irect(&area, -14);
+ ui_pack_push(area);
+}
+
+void ui_dialog_end(void)
+{
+ ui_pack_pop();
+}
+
void ui_spacer(void)
{
ui_pack(ui.lineheight / 2, ui.lineheight / 2);
@@ -597,15 +691,16 @@ void ui_label(const char *fmt, ...)
width = ui_measure_string(buf);
area = ui_pack(width, ui.lineheight);
- glColor4f(0, 0, 0, 1);
+ glColorHex(UI_COLOR_TEXT_FG);
ui_draw_string(area.x0, area.y0 + ui.baseline, buf);
}
int ui_button(const char *label)
{
int width = ui_measure_string(label);
- fz_irect area = ui_pack(width + 12, ui.lineheight + 6);
+ fz_irect area = ui_pack(width + 20, ui.gridsize);
int text_x = area.x0 + ((area.x1 - area.x0) - width) / 2;
+ int pressed;
if (ui_mouse_inside(&area))
{
@@ -614,36 +709,24 @@ int ui_button(const char *label)
ui.active = label;
}
- glColor4f(0, 0, 0, 1);
- glRectf(area.x0, area.y0, area.x1, area.y1);
-
- if (ui.hot == label && ui.active == label && ui.down)
- glColor4f(0, 0, 0, 1);
- else
- glColor4f(1, 1, 1, 1);
- glRectf(area.x0+2, area.y0+2, area.x1-2, area.y1-2);
-
- if (ui.hot == label && ui.active == label && ui.down)
- glColor4f(1, 1, 1, 1);
- else
- glColor4f(0, 0, 0, 1);
- ui_draw_string(text_x, area.y0 + 3 + ui.baseline, label);
+ pressed = (ui.hot == label && ui.active == label && ui.down);
+ ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed);
+ glColorHex(UI_COLOR_TEXT_FG);
+ ui_draw_string(text_x + pressed, area.y0+3 + ui.baseline + pressed, label);
return ui.hot == label && ui.active == label && !ui.down;
}
-void ui_checkbox(const char *label, int *value)
+int ui_checkbox(const char *label, int *value)
{
int width = ui_measure_string(label);
- fz_irect area = ui_pack(width + ui.baseline - 3 + 4, ui.lineheight);
- fz_irect mark = { area.x0, area.y0 + 3, area.x0 + ui.baseline - 3, area.y0 + ui.baseline };
+ fz_irect area = ui_pack(13 + 4 + width, ui.lineheight);
+ fz_irect mark = { area.x0, area.y0 + ui.baseline-12, area.x0 + 13, area.y0 + ui.baseline+1 };
+ int pressed;
- glColor4f(0, 0, 0, 1);
+ glColorHex(UI_COLOR_TEXT_FG);
ui_draw_string(mark.x1 + 4, area.y0 + ui.baseline, label);
- glColor4f(0, 0, 0, 1);
- glRectf(mark.x0, mark.y0, mark.x1, mark.y1);
-
if (ui_mouse_inside(&area))
{
ui.hot = label;
@@ -654,25 +737,34 @@ void ui_checkbox(const char *label, int *value)
if (ui.hot == label && ui.active == label && !ui.down)
*value = !*value;
- glColor4f(1, 1, 1, 1);
- if (ui.hot == label && ui.active == label && ui.down)
- glRectf(mark.x0+2, mark.y0+2, mark.x1-2, mark.y1-2);
- else
- glRectf(mark.x0+1, mark.y0+1, mark.x1-1, mark.y1-1);
-
+ pressed = (ui.hot == label && ui.active == label && ui.down);
+ ui_draw_bevel_rect(mark, pressed ? UI_COLOR_PANEL : UI_COLOR_TEXT_BG, 1);
if (*value)
{
- glColor4f(0, 0, 0, 1);
- glRectf(mark.x0+3, mark.y0+3, mark.x1-3, mark.y1-3);
+ float ax = mark.x0+2 + 1, ay = mark.y0+2 + 3;
+ float bx = mark.x0+2 + 4, by = mark.y0+2 + 5;
+ float cx = mark.x0+2 + 8, cy = mark.y0+2 + 1;
+ glColorHex(UI_COLOR_TEXT_FG);
+ glBegin(GL_TRIANGLE_STRIP);
+ glVertex2f(ax, ay); glVertex2f(ax, ay+3);
+ glVertex2f(bx, by); glVertex2f(bx, by+3);
+ glVertex2f(cx, cy); glVertex2f(cx, cy+3);
+ glEnd();
}
+
+ return ui.hot == label && ui.active == label && !ui.down;
}
-void ui_slider(float *value, float min, float max, int width)
+int ui_slider(int *value, int min, int max, int width)
{
+ static int start_value = 0;
fz_irect area = ui_pack(width, ui.lineheight);
- static float start_value = 0;
- float w = area.x1 - area.x0 - 4;
- char buf[50];
+ int m = 6;
+ int w = area.x1 - area.x0 - m * 2;
+ int h = area.y1 - area.y0;
+ fz_irect gutter = { area.x0, area.y0+h/2-2, area.x1, area.y0+h/2+2 };
+ fz_irect thumb;
+ int x;
if (ui_mouse_inside(&area))
{
@@ -690,20 +782,60 @@ void ui_slider(float *value, float min, float max, int width)
*value = start_value;
else
{
- float v = (float)(ui.x - (area.x0+2)) / (area.x1-area.x0-4);
+ float v = (float)(ui.x - (area.x0+m)) / w;
*value = fz_clamp(min + v * (max - min), min, max);
}
}
- glColor4f(0.4, 0.4, 0.4, 1);
- glRectf(area.x0, area.y0, area.x1, area.y1);
- glColor4f(0.7, 0.7, 0.7, 1);
- glRectf(area.x0+2, area.y0+2, area.x0+2 + (*value - min) / (max - min) * w, area.y1 - 2);
+ x = ((*value - min) * w) / (max - min);
+ thumb = fz_make_irect(area.x0+m + x-m, area.y0, area.x0+m + x+m, area.y1);
+
+ ui_draw_bevel(gutter, 1);
+ ui_draw_bevel_rect(thumb, UI_COLOR_BUTTON, 0);
+
+ return *value != start_value && ui.active == value && !ui.down;
+}
+
+void ui_splitter(int *x, int min, int max, enum side side)
+{
+ static int start_x = 0;
+ fz_irect area = ui_pack(4, 0);
+
+ if (ui_mouse_inside(&area))
+ {
+ ui.hot = x;
+ if (!ui.active && ui.down)
+ {
+ ui.active = x;
+ start_x = *x;
+ }
+ }
+
+ if (ui.active == x)
+ *x = fz_clampi(start_x + (ui.x - ui.down_x), min, max);
+
+ if (ui.hot == x || ui.active == x)
+ ui.cursor = GLUT_CURSOR_LEFT_RIGHT;
+
+ if (side == L)
+ {
+ glColorHex(UI_COLOR_BEVEL_4);
+ glRectf(area.x0+0, area.y0, area.x0+2, area.y1);
+ glColorHex(UI_COLOR_BEVEL_3);
+ glRectf(area.x0+2, area.y0, area.x0+3, area.y1);
+ glColorHex(UI_COLOR_PANEL);
+ glRectf(area.x0+3, area.y0, area.x0+4, area.y1);
+ }
+ if (side == R)
+ {
+ glColorHex(UI_COLOR_PANEL);
+ glRectf(area.x0, area.y0, area.x0+2, area.y1);
+ glColorHex(UI_COLOR_BEVEL_2);
+ glRectf(area.x0+2, area.y0, area.x0+3, area.y1);
+ glColorHex(UI_COLOR_BEVEL_1);
+ glRectf(area.x0+3, area.y0, area.x0+4, area.y1);
+ }
- glColor4f(1, 1, 1, 1);
- fz_snprintf(buf, sizeof buf, "%0.2f", *value);
- w = ui_measure_string(buf);
- ui_draw_string(area.x0 + ((area.x1-area.x0) - w) / 2, area.y0 + ui.baseline, buf);
}
void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int max)
@@ -711,7 +843,7 @@ void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int
static float start_top = 0; /* we can only drag in one scrollbar at a time, so static is safe */
float top;
- int total_h = y1 - y0 - 4;
+ int total_h = y1 - y0;
int thumb_h = fz_maxi(x1 - x0, total_h * page_size / max);
int avail_h = total_h - thumb_h;
@@ -720,7 +852,7 @@ void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int
if (max <= 0)
{
*value = 0;
- glColor4f(0.6f, 0.6f, 0.6f, 1.0f);
+ glColorHex(UI_COLOR_SCROLLBAR);
glRectf(x0, y0, x1, y1);
return;
}
@@ -762,22 +894,22 @@ void ui_scrollbar(int x0, int y0, int x1, int y1, int *value, int page_size, int
top = (float) *value * avail_h / max;
- glColor4f(0.6f, 0.6f, 0.6f, 1.0f);
+ glColorHex(UI_COLOR_SCROLLBAR);
glRectf(x0, y0, x1, y1);
- glColor4f(0.8f, 0.8f, 0.8f, 1.0f);
- glRectf(x0+2, y0+2 + top, x1-2, y0+2 + top + thumb_h);
+ ui_draw_ibevel_rect(fz_make_irect(x0, y0+top, x1, y0+top+thumb_h), UI_COLOR_BUTTON, 0);
}
void ui_list_begin(struct list *list, int count, int req_w, int req_h)
{
static int start_scroll_y = 0; /* we can only drag in one list at a time, so static is safe */
- fz_irect area = ui_pack(req_w, req_h);
+ fz_irect outer_area = ui_pack(req_w, req_h);
+ fz_irect area = { outer_area.x0+2, outer_area.y0+2, outer_area.x1-2, outer_area.y1-2 };
int max_scroll_y = count * ui.lineheight - (area.y1-area.y0);
if (max_scroll_y > 0)
- area.x1 -= ui.lineheight;
+ area.x1 -= 16;
if (ui_mouse_inside(&area))
{
@@ -803,23 +935,21 @@ void ui_list_begin(struct list *list, int count, int req_w, int req_h)
if (list->scroll_y < 0)
list->scroll_y = 0;
+ ui_draw_bevel_rect(outer_area, UI_COLOR_TEXT_BG, 1);
if (max_scroll_y > 0)
{
- ui_scrollbar(area.x1, area.y0, area.x1+ui.lineheight, area.y1,
+ ui_scrollbar(area.x1, area.y0, area.x1+16, area.y1,
&list->scroll_y, area.y1-area.y0, count * ui.lineheight);
}
list->area = area;
list->item_y = area.y0 - list->scroll_y;
- glColor4f(1, 1, 1, 1);
- glRectf(area.x0, area.y0, area.x1, area.y1);
-
- glScissor(area.x0, ui.window_h-area.y1, area.x1-area.x0, area.y1-area.y0);
+ glScissor(list->area.x0, ui.window_h-list->area.y1, list->area.x1-list->area.x0, list->area.y1-list->area.y0);
glEnable(GL_SCISSOR_TEST);
}
-int ui_list_item(struct list *list, void *id, int indent, const char *label, int selected)
+int ui_list_item_x(struct list *list, const void *id, int indent, const char *label, int selected)
{
fz_irect area = { list->area.x0, list->item_y, list->area.x1, list->item_y + ui.lineheight };
@@ -833,23 +963,157 @@ int ui_list_item(struct list *list, void *id, int indent, const char *label, int
ui.active = id;
}
- if (selected)
+ if (ui.active == id || selected)
{
- glColor4f(0.9f, 0.9f, 0.9f, 1);
+ glColorHex(UI_COLOR_TEXT_SEL_BG);
glRectf(area.x0, area.y0, area.x1, area.y1);
+ glColorHex(UI_COLOR_TEXT_SEL_FG);
+ ui_draw_string(area.x0 + indent, area.y0 + ui.baseline, label);
+ }
+ else
+ {
+ glColorHex(UI_COLOR_TEXT_FG);
+ ui_draw_string(area.x0 + indent, area.y0 + ui.baseline, label);
}
-
- glColor4f(0, 0, 0, 1);
- ui_draw_string(area.x0 + indent, area.y0 + ui.baseline, label);
}
list->item_y += ui.lineheight;
- /* trigger on first mouse down */
- return ui.active == id;
+ /* trigger on mouse up */
+ return ui.active == id && !ui.down;
+}
+
+int ui_list_item(struct list *list, const void *id, const char *label, int selected)
+{
+ return ui_list_item_x(list, id, 2, label, selected);
}
void ui_list_end(struct list *list)
{
glDisable(GL_SCISSOR_TEST);
}
+
+int ui_popup(const void *popup, const char *label, int is_button, int count)
+{
+ int width = ui_measure_string(label);
+ fz_irect area = ui_pack(width + 22 + 6, ui.gridsize);
+ int pressed;
+
+ if (ui_mouse_inside(&area))
+ {
+ ui.hot = popup;
+ if (!ui.active && ui.down)
+ ui.active = popup;
+ }
+
+ pressed = (ui.active == popup);
+
+ if (is_button)
+ {
+ ui_draw_bevel_rect(area, UI_COLOR_BUTTON, pressed);
+ glColorHex(UI_COLOR_TEXT_FG);
+ ui_draw_string(area.x0 + 6+pressed, area.y0+3 + ui.baseline+pressed, label);
+ glBegin(GL_TRIANGLES);
+ glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9);
+ glVertex2f(area.x1+pressed-8, area.y0+pressed+9);
+ glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14);
+ glEnd();
+ }
+ else
+ {
+ fz_irect arrow = { area.x1-22, area.y0+2, area.x1-2, area.y1-2 };
+ ui_draw_bevel_rect(area, UI_COLOR_TEXT_BG, 1);
+ glColorHex(UI_COLOR_TEXT_FG);
+ ui_draw_string(area.x0 + 6, area.y0+3 + ui.baseline, label);
+ ui_draw_ibevel_rect(arrow, UI_COLOR_BUTTON, pressed);
+
+ glColorHex(UI_COLOR_TEXT_FG);
+ glBegin(GL_TRIANGLES);
+ glVertex2f(area.x1+pressed-8-10, area.y0+pressed+9);
+ glVertex2f(area.x1+pressed-8, area.y0+pressed+9);
+ glVertex2f(area.x1+pressed-8-4, area.y0+pressed+14);
+ glEnd();
+ }
+
+ if (pressed)
+ {
+ /* Area inside the border line */
+ ui.popup_area.x0 = area.x0+1;
+ ui.popup_area.x1 = area.x1-1; // TODO: width of submenu
+ if (area.y1+2 + count * ui.lineheight < ui.window_h)
+ {
+ ui.popup_area.y0 = area.y1+2;
+ ui.popup_area.y1 = ui.popup_area.y0 + count * ui.lineheight;
+ }
+ else
+ {
+ ui.popup_area.y1 = area.y0-2;
+ ui.popup_area.y0 = ui.popup_area.y1 - count * ui.lineheight;
+ }
+ ui_pack_push(ui.popup_area);
+ ui_layout(T, X, NW, 0, 0);
+ return 1;
+ }
+
+ return 0;
+}
+
+int ui_popup_item(const char *title)
+{
+ fz_irect area = ui_pack(0, ui.lineheight);
+
+ if (ui_mouse_inside(&area))
+ ui.hot = title;
+
+ ui.popup[ui.popup_count].area = area;
+ ui.popup[ui.popup_count].title = title;
+ ++ui.popup_count;
+
+ return ui.hot == title && !ui.down;
+}
+
+void ui_popup_end(void)
+{
+ ui_pack_pop();
+}
+
+void ui_draw_popup(void)
+{
+ int i;
+
+ glColorHex(UI_COLOR_TEXT_FG);
+ glRectf(ui.popup_area.x0-1, ui.popup_area.y0-1, ui.popup_area.x1+1, ui.popup_area.y1+1);
+ glColorHex(UI_COLOR_TEXT_BG);
+ glRectf(ui.popup_area.x0, ui.popup_area.y0, ui.popup_area.x1, ui.popup_area.y1);
+
+ for (i = 0; i < ui.popup_count; ++i)
+ {
+ const char *title = ui.popup[i].title;
+ fz_irect area = ui.popup[i].area;
+ if (ui_mouse_inside(&area))
+ {
+ glColorHex(UI_COLOR_TEXT_SEL_BG);
+ glRectf(area.x0, area.y0, area.x1, area.y1);
+ glColorHex(UI_COLOR_TEXT_SEL_FG);
+ ui_draw_string(area.x0 + 4, area.y0 + ui.baseline, title);
+ }
+ else
+ {
+ glColorHex(UI_COLOR_TEXT_FG);
+ ui_draw_string(area.x0 + 4, area.y0 + ui.baseline, title);
+ }
+ }
+}
+
+int ui_select(const void *id, const char *current, const char *options[], int n)
+{
+ int i, choice = -1;
+ if (ui_popup(id, current, 0, n))
+ {
+ for (i = 0; i < n; ++i)
+ if (ui_popup_item(options[i]))
+ choice = i;
+ ui_popup_end();
+ }
+ return choice;
+}
diff --git a/platform/gl/gl-win32.c b/platform/gl/gl-win32.c
index ec5fea9a..79446ff8 100644
--- a/platform/gl/gl-win32.c
+++ b/platform/gl/gl-win32.c
@@ -83,23 +83,4 @@ void win_install(void)
RegCloseKey(software);
}
-int win_open_file(char *buf, int len)
-{
- wchar_t wbuf[2048];
- OPENFILENAME ofn;
- int code;
- wbuf[0] = 0;
- memset(&ofn, 0, sizeof(OPENFILENAME));
- ofn.lStructSize = sizeof(OPENFILENAME);
- ofn.lpstrFile = wbuf;
- ofn.nMaxFile = 2048;
- ofn.lpstrTitle = L"MuPDF: Open PDF file";
- ofn.lpstrFilter = L"Documents (*.pdf;*.xps;*.cbz;*.epub;*.fb2)\0*.pdf;*.xps;*.cbz;*.epub;*.fb2\0All Files\0*\0\0";
- ofn.Flags = OFN_FILEMUSTEXIST|OFN_HIDEREADONLY;
- code = GetOpenFileNameW(&ofn);
- if (code)
- WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, buf, len, NULL, NULL);
- return code;
-}
-
#endif