diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2018-02-21 22:00:10 +0100 |
---|---|---|
committer | Robin Watts <robin.watts@artifex.com> | 2018-06-22 16:48:46 +0100 |
commit | 41050d282409f86f425ac4a157fc07d889e4b54d (patch) | |
tree | ff0611853ff17214a3c6e3e472b7ee2dfb425107 /platform | |
parent | bde703f83b83232f830426b45957b10e4e44043d (diff) | |
download | mupdf-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')
-rw-r--r-- | platform/gl/gl-annotate.c | 1123 | ||||
-rw-r--r-- | platform/gl/gl-app.h | 77 | ||||
-rw-r--r-- | platform/gl/gl-file.c | 506 | ||||
-rw-r--r-- | platform/gl/gl-input.c | 49 | ||||
-rw-r--r-- | platform/gl/gl-main.c | 568 | ||||
-rw-r--r-- | platform/gl/gl-ui.c | 432 | ||||
-rw-r--r-- | platform/gl/gl-win32.c | 19 | ||||
-rw-r--r-- | platform/win32/mupdf-gl.vcproj | 12 |
8 files changed, 2445 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 diff --git a/platform/win32/mupdf-gl.vcproj b/platform/win32/mupdf-gl.vcproj index c5fe2722..3ed7f42a 100644 --- a/platform/win32/mupdf-gl.vcproj +++ b/platform/win32/mupdf-gl.vcproj @@ -931,10 +931,18 @@ </References> <Files> <File + RelativePath="..\gl\gl-annotate.c" + > + </File> + <File RelativePath="..\gl\gl-app.h" > </File> <File + RelativePath="..\gl\gl-file.c" + > + </File> + <File RelativePath="..\gl\gl-font.c" > </File> @@ -947,6 +955,10 @@ > </File> <File + RelativePath="..\gl\gl-ui.c" + > + </File> + <File RelativePath="..\gl\gl-win32.c" > </File> |