35#include <glib/gstdio.h>
44#ifdef HAVE_HTTP_SERVER
45#include <libsoup/soup.h>
79#ifdef HAVE_HTTP_SERVER
80 GHashTable *download_inflight;
91 static const char *
v[] = {
"darkroom",
"lighttable", NULL };
135 GtkTextIter start, end;
136 gtk_text_buffer_get_bounds(buffer, &start, &end);
137 return gtk_text_buffer_get_text(buffer, &start, &end,
TRUE);
143 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
151 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
152 gtk_text_buffer_set_text(buffer, text ? text :
"", -1);
176 gchar *text = g_strndup((
const gchar *)params,
size);
187 static const char default_text[] =
190 "- [ ] Normalize illuminant & colors\n"
191 "- [ ] Normalize contrast & dynamic range\n"
192 "- [ ] Fix lens distortion and noise\n"
193 "- [ ] Enhance colors\n"
197 "- [Documentation](https://ansel.photos/en/doc)\n"
201 "- Shot: $(EXIF.YEAR)-$(EXIF.MONTH)-$(EXIF.DAY) $(EXIF.HOUR):$(EXIF.MINUTE)\n"
202 "- Imported: $(IMPORT.DATE)\n"
203 "- Last edited: $(CHANGE.DATE)\n"
204 "- Exported: $(EXPORT.DATE)\n"
206 "";
209 default_text,
sizeof(default_text),
TRUE);
216 GtkTextView *tv =
d->preview_view;
217 GdkWindow *tw = gtk_text_view_get_window(tv, GTK_TEXT_WINDOW_TEXT);
220 return gdk_window_get_width(tw);
245 if(g_strcmp0(gtk_stack_get_visible_child_name(GTK_STACK(
d->stack)),
"preview") != 0)
return;
248 d->preview_render_width = allocation->width;
255 if(
d->completion_popover)
256 gtk_widget_hide(
d->completion_popover);
257 if(
d->completion_mark &&
d->edit_view)
259 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
260 gtk_text_buffer_delete_mark(buffer,
d->completion_mark);
261 d->completion_mark = NULL;
270 gchar *norm_item = g_utf8_normalize(item, -1, G_NORMALIZE_ALL);
271 gchar *norm_prefix = g_utf8_normalize(prefix, -1, G_NORMALIZE_ALL);
272 if(!norm_item || !norm_prefix)
279 gchar *case_item = g_utf8_casefold(norm_item, -1);
280 gchar *case_prefix = g_utf8_casefold(norm_prefix, -1);
281 const gboolean match = case_item && case_prefix && g_str_has_prefix(case_item, case_prefix);
292 gtk_list_store_clear(
d->completion_model);
299 gtk_list_store_append(
d->completion_model, &iter);
300 gtk_list_store_set(
d->completion_model, &iter,
COMPL_VARNAME, l->varname,
306 GtkTextIter *start_iter, gchar **prefix_out)
310 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
311 GtkTextIter line_start = *cursor;
312 gtk_text_iter_set_line_offset(&line_start, 0);
314 gchar *line = gtk_text_buffer_get_text(buffer, &line_start, cursor,
FALSE);
317 gchar *match = g_strrstr(line,
"$(");
324 if(strchr(match,
')'))
330 gchar *prefix = match + 2;
331 for(
const gchar *
p = prefix; *
p;
p++)
333 if(g_ascii_isspace(*
p))
340 const int byte_offset = (int)(match - line);
341 const int char_offset = g_utf8_strlen(line, byte_offset);
342 *start_iter = line_start;
343 gtk_text_iter_set_line_offset(start_iter, char_offset + 2);
345 *prefix_out = g_strdup(prefix);
355 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(
d->completion_tree));
356 GtkTreeModel *
model = NULL;
358 if(!gtk_tree_selection_get_selected(sel, &
model, &iter))
return FALSE;
360 gchar *varname = NULL;
364 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
365 GtkTextIter start, end;
366 gtk_text_buffer_get_iter_at_mark(buffer, &start,
d->completion_mark);
367 gtk_text_buffer_get_iter_at_mark(buffer, &end, gtk_text_buffer_get_insert(buffer));
368 gtk_text_buffer_delete(buffer, &start, &end);
370 gchar *insert = g_strdup_printf(
"%s)", varname);
371 gtk_text_buffer_insert(buffer, &start, insert, -1);
383 if(!gtk_widget_get_visible(GTK_WIDGET(
d->edit_view)))
389 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
391 gtk_text_buffer_get_iter_at_mark(buffer, &cursor, gtk_text_buffer_get_insert(buffer));
393 GtkTextIter start_iter;
394 gchar *prefix = NULL;
404 GtkTreeModel *
model = gtk_tree_view_get_model(GTK_TREE_VIEW(
d->completion_tree));
405 if(!
model || gtk_tree_model_iter_n_children(
model, NULL) <= 0)
412 if(gtk_tree_model_get_iter_first(
model, &first))
414 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(
d->completion_tree));
415 gtk_tree_selection_select_iter(sel, &first);
418 if(
d->completion_mark)
419 gtk_text_buffer_move_mark(buffer,
d->completion_mark, &start_iter);
421 d->completion_mark = gtk_text_buffer_create_mark(buffer, NULL, &start_iter,
TRUE);
423 GdkRectangle
rect = { 0 };
424 gtk_text_view_get_iter_location(
d->edit_view, &cursor, &
rect);
425 gtk_text_view_buffer_to_window_coords(
d->edit_view, GTK_TEXT_WINDOW_WIDGET,
428 gtk_popover_set_relative_to(GTK_POPOVER(
d->completion_popover), anchor ? anchor : GTK_WIDGET(
d->edit_view));
429 if(anchor && anchor != GTK_WIDGET(
d->edit_view))
430 gtk_widget_translate_coordinates(GTK_WIDGET(
d->edit_view), anchor,
rect.
x,
rect.
y, &
rect.
x, &
rect.
y);
433 gtk_popover_set_pointing_to(GTK_POPOVER(
d->completion_popover), &
rect);
434 gtk_widget_show_all(
d->completion_popover);
435#if GTK_CHECK_VERSION(3, 22, 0)
436 gtk_popover_popup(GTK_POPOVER(
d->completion_popover));
446 if(
d->completion_popover && gtk_widget_get_visible(
d->completion_popover))
448 GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(
d->edit_view));
449 if(GTK_IS_WINDOW(toplevel))
451 GtkWidget *focus = gtk_window_get_focus(GTK_WINDOW(toplevel));
452 if(focus && gtk_widget_is_ancestor(focus,
d->completion_popover))
453 return G_SOURCE_REMOVE;
459 return G_SOURCE_REMOVE;
466 if(!gtk_widget_get_visible(
d->completion_popover))
return FALSE;
469 if(
key == GDK_KEY_Escape)
475 if(
key == GDK_KEY_Return ||
key == GDK_KEY_Tab)
515 d->completion_model = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
516 GtkWidget *completion_tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(
d->completion_model));
517 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(completion_tree),
FALSE);
518 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
519 GtkTreeViewColumn *col = gtk_tree_view_column_new_with_attributes(_(
"variable"), renderer,
521 gtk_tree_view_append_column(GTK_TREE_VIEW(completion_tree), col);
522 GtkTreeSelection *sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(completion_tree));
523 gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
526 GtkWidget *completion_sw = gtk_scrolled_window_new(NULL, NULL);
527 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(completion_sw), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
528 gtk_container_add(GTK_CONTAINER(completion_sw), completion_tree);
529 gtk_widget_set_size_request(completion_sw, 360, 200);
531 d->completion_popover = gtk_popover_new(NULL);
532 gtk_popover_set_position(GTK_POPOVER(
d->completion_popover), GTK_POS_BOTTOM);
534 gtk_popover_set_relative_to(GTK_POPOVER(
d->completion_popover), relative ? relative : textview);
535 gtk_container_add(GTK_CONTAINER(
d->completion_popover), completion_sw);
536 d->completion_tree = completion_tree;
541 *row_in = g_malloc((
size_t)
width * 4);
542 *row_out = g_malloc((
size_t)
width * 4);
543 if(!*row_in || !*row_out)
561 const int n_channels,
const gboolean has_alpha,
562 guchar *row_in, guchar *row_out)
566 const int s =
x * n_channels;
568 row_in[
d + 0] = src[s + 0];
569 row_in[
d + 1] = src[s + 1];
570 row_in[
d + 2] = src[s + 2];
571 row_in[
d + 3] = has_alpha ? src[s + 3] : 255;
579 const int d =
x * n_channels;
580 src[
d + 0] = row_out[s + 2];
581 src[
d + 1] = row_out[s + 1];
582 src[
d + 2] = row_out[s + 0];
584 src[
d + 3] = row_out[s + 3];
603 const int width = gdk_pixbuf_get_width(pixbuf);
604 const int height = gdk_pixbuf_get_height(pixbuf);
605 const int rowstride = gdk_pixbuf_get_rowstride(pixbuf);
606 const int n_channels = gdk_pixbuf_get_n_channels(pixbuf);
613 guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
620 const gboolean has_alpha = gdk_pixbuf_get_has_alpha(pixbuf);
624 guchar **rows_in = g_malloc0((
size_t)nthreads *
sizeof(*rows_in));
625 guchar **rows_out = g_malloc0((
size_t)nthreads *
sizeof(*rows_out));
627 for(
int i = 0;
i < nthreads;
i++)
637 for(
int i = 0;
i < nthreads;
i++)
645#pragma omp parallel default(firstprivate)
648 guchar *row_in = rows_in[tid];
649 guchar *row_out = rows_out[tid];
652 for(
int y = 0; y <
height; y++)
654 guchar *src = pixels + (size_t)y * rowstride;
659 for(
int i = 0;
i < nthreads;
i++)
664 guchar *row_in = NULL;
665 guchar *row_out = NULL;
671 for(
int y = 0; y <
height; y++)
673 guchar *src = pixels + (size_t)y * rowstride;
690 GtkWindow *win = NULL;
694 GError *
error = NULL;
695 const gboolean ok = gtk_show_uri_on_window(win, uri, GDK_CURRENT_TIME, &
error);
699 g_clear_error(&
error);
706 if(
IS_NULL_PTR(source_text) || !*source_text)
return NULL;
707 if(!strstr(source_text,
"$("))
return NULL;
721 gchar *tmp = g_strdup(source_text ? source_text :
"");
728typedef struct dt_textnotes_list_state_t
732} dt_textnotes_list_state_t;
734typedef struct dt_textnotes_image_state_t
736 gboolean suppress_text;
738} dt_textnotes_image_state_t;
740static void _buffer_append_newline(GtkTextBuffer *buffer)
743 gtk_text_buffer_get_end_iter(buffer, &end);
744 if(gtk_text_iter_is_start(&end))
return;
745 GtkTextIter it = end;
746 if(gtk_text_iter_backward_char(&it) && gtk_text_iter_get_char(&it) !=
'\n')
747 gtk_text_buffer_insert(buffer, &end,
"\n", 1);
750static void _buffer_append_blankline(GtkTextBuffer *buffer)
753 gtk_text_buffer_get_end_iter(buffer, &end);
754 if(gtk_text_iter_is_start(&end))
return;
756 GtkTextIter it = end;
757 if(gtk_text_iter_backward_char(&it))
759 if(gtk_text_iter_get_char(&it) ==
'\n')
761 GtkTextIter it2 = it;
762 if(gtk_text_iter_backward_char(&it2) && gtk_text_iter_get_char(&it2) ==
'\n')
return;
763 gtk_text_buffer_insert(buffer, &end,
"\n", 1);
768 gtk_text_buffer_insert(buffer, &end,
"\n\n", 2);
771static void _insert_with_tags(GtkTextBuffer *buffer,
const char *text, GPtrArray *tags)
774 GtkTextIter start, end;
775 gtk_text_buffer_get_end_iter(buffer, &start);
776 GtkTextMark *mark = gtk_text_buffer_create_mark(buffer, NULL, &start,
TRUE);
778 gtk_text_buffer_insert(buffer, &end, text, -1);
779 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
780 for(guint
i = 0;
i < tags->len;
i++)
781 gtk_text_buffer_apply_tag(buffer, g_ptr_array_index(tags,
i), &start, &end);
782 gtk_text_buffer_delete_mark(buffer, mark);
785static void _emit_list_prefix(GtkTextBuffer *buffer, GArray *list_stack,
const gboolean checkbox,
786 const gboolean checked,
const int checklist_line)
789 gtk_text_buffer_get_end_iter(buffer, &end);
791 const int depth = list_stack->len;
792 for(
int i = 1;
i < depth;
i++) gtk_text_buffer_insert(buffer, &end,
" ", 2);
794 dt_textnotes_list_state_t *st = NULL;
796 st = &g_array_index(list_stack, dt_textnotes_list_state_t, depth - 1);
800 GtkTextTag *checkbox_tag = gtk_text_buffer_create_tag(buffer, NULL,
"scale", 1.1, NULL);
801 if(checklist_line > 0)
802 g_object_set_data(G_OBJECT(checkbox_tag),
"checklist_line", GINT_TO_POINTER(checklist_line));
803 GtkTextIter start = end;
804 GtkTextMark *mark = gtk_text_buffer_create_mark(buffer, NULL, &start,
TRUE);
805 gtk_text_buffer_insert(buffer, &end, checked ?
"\u2611" :
"\u2610", -1);
806 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
807 gtk_text_buffer_apply_tag(buffer, checkbox_tag, &start, &end);
808 gtk_text_buffer_delete_mark(buffer, mark);
809 gtk_text_buffer_insert(buffer, &end,
" ", 1);
810 if(st && st->ordered) st->index++;
814 if(st && st->ordered)
816 gchar *num = g_strdup_printf(
"%d. ", st->index);
817 gtk_text_buffer_insert(buffer, &end, num, -1);
823 gtk_text_buffer_insert(buffer, &end,
"- ", 2);
827static void _collect_text_tag(GtkTextTag *tag, gpointer user_data)
829 GPtrArray *tags = (GPtrArray *)user_data;
830 g_ptr_array_add(tags, tag);
833static void _clear_tag_table(GtkTextBuffer *buffer)
835 GtkTextTagTable *table = gtk_text_buffer_get_tag_table(buffer);
836 GPtrArray *tags = g_ptr_array_new();
837 gtk_text_tag_table_foreach(table, _collect_text_tag, tags);
838 for(guint
i = 0;
i < tags->len;
i++)
839 gtk_text_tag_table_remove(table, g_ptr_array_index(tags,
i));
840 g_ptr_array_free(tags,
TRUE);
843typedef struct dt_textnotes_tags_t
851} dt_textnotes_tags_t;
853static dt_textnotes_tags_t _create_preview_tags(GtkTextBuffer *buffer)
855 dt_textnotes_tags_t tags = { 0 };
856 tags.bold = gtk_text_buffer_create_tag(buffer,
"tn_bold",
"weight", PANGO_WEIGHT_BOLD, NULL);
857 tags.italic = gtk_text_buffer_create_tag(buffer,
"tn_italic",
"style", PANGO_STYLE_ITALIC, NULL);
858 tags.mono = gtk_text_buffer_create_tag(buffer,
"tn_mono",
"family",
"monospace", NULL);
859 tags.h1 = gtk_text_buffer_create_tag(buffer,
"tn_h1",
"weight", PANGO_WEIGHT_BOLD,
"scale", 1.4, NULL);
860 tags.h2 = gtk_text_buffer_create_tag(buffer,
"tn_h2",
"weight", PANGO_WEIGHT_BOLD,
"scale", 1.25, NULL);
861 tags.h3 = gtk_text_buffer_create_tag(buffer,
"tn_h3",
"weight", PANGO_WEIGHT_BOLD,
"scale", 1.15, NULL);
865static void _pop_active_tag(GPtrArray *active_tags)
867 if(active_tags->len > 0)
868 g_ptr_array_remove_index(active_tags, active_tags->len - 1);
871static void _push_link_tag(GtkTextBuffer *buffer, GPtrArray *active_tags,
const char *url)
873 GtkTextTag *
tag = gtk_text_buffer_create_tag(buffer, NULL,
874 "underline", PANGO_UNDERLINE_SINGLE,
877 g_object_set_data_full(G_OBJECT(tag),
"href", g_strdup(url), g_free);
878 g_ptr_array_add(active_tags, tag);
881static void _insert_mono_text(GtkTextBuffer *buffer, GtkTextTag *tag_mono,
const char *lit)
884 GtkTextIter start, end;
885 gtk_text_buffer_get_end_iter(buffer, &start);
886 GtkTextMark *mark = gtk_text_buffer_create_mark(buffer, NULL, &start,
TRUE);
888 gtk_text_buffer_insert(buffer, &end, lit, -1);
889 gtk_text_buffer_get_iter_at_mark(buffer, &start, mark);
890 gtk_text_buffer_apply_tag(buffer, tag_mono, &start, &end);
891 gtk_text_buffer_delete_mark(buffer, mark);
894static void _emit_pending_list_prefix(GtkTextBuffer *buffer, GArray *list_stack,
895 gboolean *item_pending_prefix)
897 if(*item_pending_prefix)
899 _emit_list_prefix(buffer, list_stack,
FALSE,
FALSE, 0);
900 *item_pending_prefix =
FALSE;
904static const char *_handle_list_text_prefix(GtkTextBuffer *buffer, GArray *list_stack,
905 gboolean *item_pending_prefix,
906 const char *lit,
const int line_no)
910 if(*item_pending_prefix && lit[0] ==
'[' && lit[2] ==
']'
911 && (lit[1] ==
' ' || lit[1] ==
'x' || lit[1] ==
'X'))
913 const gboolean checked = (lit[1] ==
'x' || lit[1] ==
'X');
914 _emit_list_prefix(buffer, list_stack,
TRUE, checked, line_no);
915 *item_pending_prefix =
FALSE;
917 if(lit[3] ==
' ') offset = 4;
921 if(*item_pending_prefix)
923 _emit_list_prefix(buffer, list_stack,
FALSE,
FALSE, 0);
924 *item_pending_prefix =
FALSE;
930static void _list_push(GArray *list_stack, cmark_node *node)
932 dt_textnotes_list_state_t st = {
933 .ordered = (cmark_node_get_list_type(node) == CMARK_ORDERED_LIST),
934 .index = cmark_node_get_list_start(node)
936 g_array_append_val(list_stack, st);
939static void _list_pop(GtkTextBuffer *buffer, GArray *list_stack)
941 if(list_stack->len > 0) g_array_remove_index(list_stack, list_stack->len - 1);
942 _buffer_append_blankline(buffer);
945static void _list_item_enter(GtkTextBuffer *buffer, gboolean *in_list_item, gboolean *item_pending_prefix)
947 _buffer_append_newline(buffer);
948 *in_list_item =
TRUE;
949 *item_pending_prefix =
TRUE;
952static void _list_item_leave(GtkTextBuffer *buffer, gboolean *in_list_item, gboolean *item_pending_prefix)
954 _buffer_append_newline(buffer);
955 *in_list_item =
FALSE;
956 *item_pending_prefix =
FALSE;
959static gboolean _is_remote_url(
const char *url)
962 return g_str_has_prefix(url,
"http://") || g_str_has_prefix(url,
"https://");
965static gchar *_remote_cache_path(
const char *url)
969 gchar *hash = g_compute_checksum_for_string(G_CHECKSUM_SHA1, url, -1);
972 const char *end = strchr(url,
'?');
974 const char *slash = end;
975 while(slash > url && *slash !=
'/') slash--;
976 if(*slash ==
'/') slash++;
977 const char *dot = NULL;
978 for(
const char *
p = end - 1;
p > slash;
p--)
987 gchar *filename = NULL;
988 if(dot && (end - dot) <= 8)
989 filename = g_strconcat(hash, dot, NULL);
991 filename = g_strdup(hash);
995 gchar *cache_dir = g_build_filename(g_get_user_cache_dir(),
"ansel",
"downloads", NULL);
996 gchar *
path = g_build_filename(cache_dir, filename, NULL);
1002#ifdef HAVE_HTTP_SERVER
1003typedef struct dt_textnotes_fetch_t
1009} dt_textnotes_fetch_t;
1011static SoupSession *_textnotes_soup_session(
void)
1013 static SoupSession *session = NULL;
1014 if(session)
return session;
1015 session = soup_session_new();
1017 g_object_set(session,
"timeout", 10,
"user-agent",
"Ansel", NULL);
1021static void _finish_remote_download(dt_textnotes_fetch_t *fetch, gboolean ok)
1023 if(fetch->d && fetch->d->download_inflight && fetch->url)
1024 g_hash_table_remove(fetch->d->download_inflight, fetch->url);
1026 if(ok && fetch->self)
1034#if LIBSOUP_VERSION_MAJOR >= 3
1035static void _remote_download_cb(GObject *source, GAsyncResult *res, gpointer user_data)
1037 dt_textnotes_fetch_t *fetch = (dt_textnotes_fetch_t *)user_data;
1038 SoupSession *session = SOUP_SESSION(source);
1039 GError *
error = NULL;
1040 GBytes *bytes = soup_session_send_and_read_finish(session, res, &
error);
1045 if(bytes) g_bytes_unref(bytes);
1046 _finish_remote_download(fetch,
FALSE);
1050 const gsize len = g_bytes_get_size(bytes);
1051 const void *data = g_bytes_get_data(bytes, NULL);
1052 gboolean ok =
FALSE;
1053 if(fetch->path && data && len > 0)
1054 ok = g_file_set_contents(fetch->path, data, (gssize)len, NULL);
1056 g_bytes_unref(bytes);
1057 _finish_remote_download(fetch, ok);
1060static void _remote_download_cb(SoupSession *session, SoupMessage *msg, gpointer user_data)
1062 dt_textnotes_fetch_t *fetch = (dt_textnotes_fetch_t *)user_data;
1063 gboolean ok =
FALSE;
1065 if(msg->status_code == SOUP_STATUS_OK && msg->response_body && msg->response_body->data)
1067 ok = g_file_set_contents(fetch->path,
1068 msg->response_body->data,
1069 (gssize)msg->response_body->length,
1073 _finish_remote_download(fetch, ok);
1078 const char *url,
const char *path)
1082 d->download_inflight = g_hash_table_new_full(g_str_hash, g_str_equal,
dt_free_gpointer, NULL);
1083 if(g_hash_table_contains(
d->download_inflight, url))
return;
1086 g_mkdir_with_parents(cache_dir, 0700);
1089 SoupSession *session = _textnotes_soup_session();
1092 SoupMessage *msg = soup_message_new(
"GET", url);
1095 dt_textnotes_fetch_t *fetch = g_new0(dt_textnotes_fetch_t, 1);
1098 fetch->url = g_strdup(url);
1099 fetch->path = g_strdup(path);
1101 g_hash_table_add(
d->download_inflight, g_strdup(url));
1103#if LIBSOUP_VERSION_MAJOR >= 3
1104 soup_session_send_and_read_async(session, msg, G_PRIORITY_DEFAULT, NULL, _remote_download_cb, fetch);
1106 soup_session_queue_message(session, msg, _remote_download_cb, fetch);
1111static gchar *_resolve_image_path(
const char *url,
const char *base_dir)
1114 if(_is_remote_url(url) || g_str_has_prefix(url,
"ftp://"))
1117 if(g_str_has_prefix(url,
"file://"))
1118 return g_filename_from_uri(url, NULL, NULL);
1120 if(g_path_is_absolute(url))
1122 gchar *unescaped = g_uri_unescape_string(url, NULL);
1123 return unescaped ? unescaped : g_strdup(url);
1129 gchar *unescaped = g_uri_unescape_string(url, NULL);
1130 gchar *result = g_build_filename(base_dir, unescaped ? unescaped : url, NULL);
1141 if(
d->image_dir)
return g_strdup(
d->image_dir);
1142 return g_path_get_dirname(
d->image_path);
1148 if(
d &&
d->preview_view)
1149 scale = gtk_widget_get_scale_factor(GTK_WIDGET(
d->preview_view));
1150 if(scale <= 0) scale = 1;
1154static int _compute_max_image_width(
dt_lib_textnotes_t *
d,
const int scale, gboolean *have_device)
1157 if(have_device) *have_device = (device_w > 0);
1162 const int dpad = (scale > 1) ? 3 : 2;
1163 if(device_w > dpad) device_w -= dpad;
1164 max_w = device_w / scale;
1165 if(max_w < 1) max_w = 1;
1168 if(max_w <= 0 && d->preview_view)
1170 GdkRectangle
rect = { 0 };
1171 gtk_text_view_get_visible_rect(
d->preview_view, &
rect);
1174 if(max_w <= 0 && d->preview_view)
1175 max_w = gtk_widget_get_allocated_width(GTK_WIDGET(
d->preview_view));
1176 if(max_w <= 0 && d->preview_sw)
1177 max_w = gtk_widget_get_allocated_width(GTK_WIDGET(
d->preview_sw));
1178 if(max_w <= 0 && d->root)
1179 max_w = gtk_widget_get_allocated_width(
d->root);
1183 const int margin = gtk_text_view_get_left_margin(
d->preview_view)
1184 + gtk_text_view_get_right_margin(
d->preview_view);
1185 if(margin > 0 && max_w > margin) max_w -= margin;
1188 if((!have_device || !*have_device) &&
d->preview_view)
1190 GtkStyleContext *ctx = gtk_widget_get_style_context(GTK_WIDGET(
d->preview_view));
1191 GtkStateFlags
state = gtk_widget_get_state_flags(GTK_WIDGET(
d->preview_view));
1192 GtkBorder padding = { 0 }, border = { 0 };
1193 gtk_style_context_get_padding(ctx,
state, &padding);
1194 gtk_style_context_get_border(ctx,
state, &border);
1195 const int chrome = padding.left + padding.right + border.left + border.right;
1196 if(chrome > 0 && max_w > chrome) max_w -= chrome;
1204 const int sw_w = gtk_widget_get_allocated_width(
d->preview_sw);
1209 const int cap = (sw_w > chrome) ? sw_w - chrome : sw_w;
1210 if(max_w <= 0 || max_w > cap) max_w = cap;
1214 if(max_w > 2) max_w -= 2;
1218static GdkPixbuf *_load_scaled_pixbuf(
const char *path,
const int target_w, GError **
error)
1221 return gdk_pixbuf_new_from_file_at_scale(path, target_w, -1,
TRUE,
error);
1222 return gdk_pixbuf_new_from_file(path,
error);
1226 GdkPixbuf *pixbuf,
const int max_w)
1228 GtkWidget *image = gtk_image_new_from_pixbuf(pixbuf);
1230 gtk_widget_set_size_request(image, max_w, -1);
1231 gtk_widget_set_halign(image, GTK_ALIGN_START);
1232 gtk_widget_set_margin_top(image, 2);
1233 gtk_widget_set_margin_bottom(image, 6);
1236 gtk_text_buffer_get_end_iter(buffer, &iter);
1237 GtkTextChildAnchor *anchor = gtk_text_buffer_create_child_anchor(buffer, &iter);
1238 gtk_text_view_add_child_at_anchor(
d->preview_view, image, anchor);
1239 gtk_widget_show(image);
1243 const char *url,
const char *fallback_url,
1244 const char *base_dir)
1246 const char *remote_url = NULL;
1247 if(_is_remote_url(url)) remote_url = url;
1248 else if(_is_remote_url(fallback_url)) remote_url = fallback_url;
1253 path = _remote_cache_path(remote_url);
1254#ifdef HAVE_HTTP_SERVER
1255 if(path && !g_file_test(path, G_FILE_TEST_EXISTS))
1256 _queue_remote_download(
d->self,
d, remote_url, path);
1261 path = _resolve_image_path(url, base_dir);
1263 path = _resolve_image_path(fallback_url, base_dir);
1267 if(!g_file_test(path, G_FILE_TEST_EXISTS))
1273 const int scale = _get_preview_scale(
d);
1274 gboolean have_device =
FALSE;
1275 int max_w = _compute_max_image_width(
d, scale, &have_device);
1282 const int target_w = max_w * scale;
1283 GError *
error = NULL;
1284 GdkPixbuf *pixbuf = _load_scaled_pixbuf(path, target_w, &
error);
1293 _insert_pixbuf_widget(
d, buffer, pixbuf, max_w);
1294 g_object_unref(pixbuf);
1300static GArray *_build_line_offsets(
const char *text)
1302 GArray *offsets = g_array_new(
FALSE,
FALSE,
sizeof(gsize));
1304 g_array_append_val(offsets, off);
1306 for(
const char *
p = text; *
p;
p++, off++)
1310 gsize next = off + 1;
1311 g_array_append_val(offsets, next);
1317static gchar *_normalize_markdown_images(
const char *text)
1321 GString *
out = g_string_sized_new(strlen(text) + 16);
1322 const char *
p = text;
1325 if(
p[0] ==
'!' &&
p[1] ==
'[')
1327 const char *alt_end = strchr(
p + 2,
']');
1328 if(alt_end && alt_end[1] ==
'(')
1330 const char *dest_start = alt_end + 2;
1331 const char *line_end = strchr(dest_start,
'\n');
1332 if(
IS_NULL_PTR(line_end)) line_end = dest_start + strlen(dest_start);
1333 const char *close_paren = memchr(dest_start,
')', (
size_t)(line_end - dest_start));
1334 if(close_paren && close_paren > dest_start)
1336 const char *s = dest_start;
1337 const char *e = close_paren;
1338 while(s < e && g_ascii_isspace(*s)) s++;
1339 while(e > s && g_ascii_isspace(*(e - 1))) e--;
1341 gboolean has_space =
FALSE;
1342 gboolean has_quote =
FALSE;
1343 for(
const char *q = s; q < e; q++)
1345 if(g_ascii_isspace(*q)) has_space =
TRUE;
1346 if(*q ==
'"' || *q ==
'\'') has_quote =
TRUE;
1349 if(has_space && !has_quote && s < e && *s !=
'<')
1351 g_string_append_len(
out,
p, (gsize)(dest_start -
p));
1352 g_string_append_c(
out,
'<');
1353 g_string_append_len(
out, s, (gsize)(e - s));
1354 g_string_append(
out,
">)");
1355 p = close_paren + 1;
1362 g_string_append_c(
out, *
p);
1369static gchar *_extract_image_dest_from_source(
const char *text,
const GArray *offsets, cmark_node *node)
1371 if(
IS_NULL_PTR(text) || !offsets || offsets->len == 0)
return NULL;
1372 const int sl = cmark_node_get_start_line(node);
1373 const int sc = cmark_node_get_start_column(node);
1374 if(sl <= 0 || sc <= 0 || sl > (
int)offsets->len)
return NULL;
1376 const gsize line_start = g_array_index(offsets, gsize, sl - 1);
1377 const gsize line_end = (sl < (int)offsets->len)
1378 ? g_array_index(offsets, gsize, sl) - 1
1380 if(line_start >= line_end)
return NULL;
1382 gsize start = line_start + (gsize)(sc - 1);
1383 if(start >= line_end) start = line_start;
1385 const char *line = text + line_start;
1386 const gsize line_len = line_end - line_start;
1387 const char *
p = line + (start - line_start);
1388 const char *line_endp = line + line_len;
1390 const char *open_paren = NULL;
1391 for(
const char *q =
p; q < line_endp; q++)
1399 if(
IS_NULL_PTR(open_paren) || open_paren + 1 >= line_endp)
return NULL;
1401 const char *dest_start = open_paren + 1;
1402 const char *dest_end = NULL;
1404 if(*dest_start ==
'<')
1406 const char *close = strchr(dest_start + 1,
'>');
1407 if(close && close < line_endp) dest_end = close;
1411 for(
const char *q = line_endp - 1; q > dest_start; q--)
1421 if(
IS_NULL_PTR(dest_end) || dest_end <= dest_start)
return NULL;
1423 gchar *raw = g_strndup(dest_start, dest_end - dest_start);
1426 gchar *trimmed = g_strstrip(raw);
1427 if(trimmed[0] ==
'<' && trimmed[strlen(trimmed) - 1] ==
'>')
1429 trimmed[strlen(trimmed) - 1] =
'\0';
1433 GString *
out = g_string_new(NULL);
1434 for(
const char *q = trimmed; *q; q++)
1436 if(*q ==
'\\' && q[1] !=
'\0')
1439 g_string_append_c(
out, *q);
1443 g_string_append_c(
out, *q);
1447 gchar *result = g_string_free(
out,
FALSE);
1457 d->rendering =
TRUE;
1458 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->preview_view);
1459 gtk_text_buffer_set_text(buffer,
"", -1);
1462 _clear_tag_table(buffer);
1463 dt_textnotes_tags_t tags = _create_preview_tags(buffer);
1464 GPtrArray *active_tags = g_ptr_array_new();
1466 const char *source_text = text ? text :
"";
1468 const char *render_text = expanded ? expanded : source_text;
1470 gchar *normalized = _normalize_markdown_images(render_text);
1471 cmark_node *doc = cmark_parse_document(normalized,
1473 CMARK_OPT_DEFAULT | CMARK_OPT_SOURCEPOS);
1476 g_ptr_array_free(active_tags,
TRUE);
1483 cmark_iter *it = cmark_iter_new(doc);
1484 GArray *list_stack = g_array_new(
FALSE,
FALSE,
sizeof(dt_textnotes_list_state_t));
1485 GArray *image_stack = g_array_new(
FALSE,
FALSE,
sizeof(dt_textnotes_image_state_t));
1486 gboolean in_list_item =
FALSE;
1487 gboolean item_pending_prefix =
FALSE;
1489 GArray *line_offsets = _build_line_offsets(render_text);
1490 gchar *base_dir = _get_image_base_dir(
d);
1492 for(cmark_event_type ev = cmark_iter_next(it); ev != CMARK_EVENT_DONE; ev = cmark_iter_next(it))
1494 cmark_node *node = cmark_iter_get_node(it);
1495 const cmark_node_type
t = cmark_node_get_type(node);
1496 const gboolean entering = (ev == CMARK_EVENT_ENTER);
1500 case CMARK_NODE_PARAGRAPH:
1503 if(in_list_item) _buffer_append_newline(buffer);
1504 else _buffer_append_blankline(buffer);
1507 case CMARK_NODE_TEXT:
1510 const char *lit = cmark_node_get_literal(node);
1512 if(image_stack->len > 0)
1514 dt_textnotes_image_state_t *st =
1515 &g_array_index(image_stack, dt_textnotes_image_state_t, image_stack->len - 1);
1516 if(st->suppress_text)
break;
1518 lit = _handle_list_text_prefix(buffer, list_stack, &item_pending_prefix,
1519 lit, cmark_node_get_start_line(node));
1520 _insert_with_tags(buffer, lit, active_tags);
1523 case CMARK_NODE_SOFTBREAK:
1524 case CMARK_NODE_LINEBREAK:
1528 gtk_text_buffer_get_end_iter(buffer, &it_end);
1529 gtk_text_buffer_insert(buffer, &it_end,
"\n", 1);
1532 case CMARK_NODE_EMPH:
1534 g_ptr_array_add(active_tags, tags.italic);
1536 _pop_active_tag(active_tags);
1538 case CMARK_NODE_STRONG:
1540 g_ptr_array_add(active_tags, tags.bold);
1542 _pop_active_tag(active_tags);
1544 case CMARK_NODE_CODE:
1547 _emit_pending_list_prefix(buffer, list_stack, &item_pending_prefix);
1548 _insert_mono_text(buffer, tags.mono, cmark_node_get_literal(node));
1551 case CMARK_NODE_CODE_BLOCK:
1554 _buffer_append_blankline(buffer);
1555 _emit_pending_list_prefix(buffer, list_stack, &item_pending_prefix);
1556 _insert_mono_text(buffer, tags.mono, cmark_node_get_literal(node));
1557 _buffer_append_blankline(buffer);
1560 case CMARK_NODE_HEADING:
1563 GtkTextTag *tag = tags.h3;
1564 const int level = cmark_node_get_heading_level(node);
1565 if(level <= 1) tag = tags.h1;
1566 else if(level == 2) tag = tags.h2;
1567 g_ptr_array_add(active_tags, tag);
1571 _pop_active_tag(active_tags);
1572 _buffer_append_blankline(buffer);
1575 case CMARK_NODE_LINK:
1577 _push_link_tag(buffer, active_tags, cmark_node_get_url(node));
1579 _pop_active_tag(active_tags);
1581 case CMARK_NODE_IMAGE:
1584 _emit_pending_list_prefix(buffer, list_stack, &item_pending_prefix);
1585 const char *url = cmark_node_get_url(node);
1586 gchar *fallback = _extract_image_dest_from_source(render_text, line_offsets, node);
1587 const gboolean inlined = _insert_markdown_image(
d, buffer, url, fallback, base_dir);
1589 dt_textnotes_image_state_t st = { .suppress_text = inlined, .tag_added =
FALSE };
1592 _push_link_tag(buffer, active_tags, url);
1593 st.tag_added =
TRUE;
1595 g_array_append_val(image_stack, st);
1597 else if(image_stack->len > 0)
1599 dt_textnotes_image_state_t st =
1600 g_array_index(image_stack, dt_textnotes_image_state_t, image_stack->len - 1);
1601 if(st.tag_added) _pop_active_tag(active_tags);
1602 g_array_remove_index(image_stack, image_stack->len - 1);
1605 case CMARK_NODE_LIST:
1607 _list_push(list_stack, node);
1609 _list_pop(buffer, list_stack);
1611 case CMARK_NODE_ITEM:
1613 _list_item_enter(buffer, &in_list_item, &item_pending_prefix);
1615 _list_item_leave(buffer, &in_list_item, &item_pending_prefix);
1622 g_array_free(list_stack,
TRUE);
1623 g_array_free(image_stack,
TRUE);
1624 g_ptr_array_free(active_tags,
TRUE);
1625 cmark_iter_free(it);
1626 cmark_node_free(doc);
1627 g_array_free(line_offsets,
TRUE);
1632 const char *source_text = text ? text :
"";
1634 gtk_text_buffer_set_text(buffer, expanded ? expanded : source_text, -1);
1643 gtk_label_set_text(GTK_LABEL(
d->mtime_label),
"");
1644 gtk_widget_set_visible(
d->mtime_label,
FALSE);
1659 if(g_stat(
d->path, &statbuf) != 0)
1665 GDateTime *gdt = g_date_time_new_from_unix_local((gint64)statbuf.st_mtime);
1666 char local[128] = { 0 };
1669 gchar *text = g_strdup_printf(_(
"Last modified: %s"), local);
1670 gchar *markup = g_markup_printf_escaped(
"<i>%s</i>", text);
1671 gtk_label_set_markup(GTK_LABEL(
d->mtime_label), markup);
1672 gtk_widget_set_visible(
d->mtime_label,
TRUE);
1681 if(gdt) g_date_time_unref(gdt);
1686 if(line_no < 1)
return;
1689 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->edit_view);
1690 GtkTextIter line_start, line_end;
1691 gtk_text_buffer_get_iter_at_line(buffer, &line_start, line_no - 1);
1692 line_end = line_start;
1693 gtk_text_iter_forward_to_line_end(&line_end);
1695 GtkTextIter s_space, e_space, s_x, e_x, s_X, e_X;
1696 gboolean f_space = gtk_text_iter_forward_search(&line_start,
"[ ]", 0, &s_space, &e_space, &line_end);
1697 gboolean f_x = gtk_text_iter_forward_search(&line_start,
"[x]", 0, &s_x, &e_x, &line_end);
1698 gboolean f_X = gtk_text_iter_forward_search(&line_start,
"[X]", 0, &s_X, &e_X, &line_end);
1700 if(!f_space && !f_x && !f_X)
return;
1702 GtkTextIter *s = NULL;
1703 GtkTextIter *e = NULL;
1704 gboolean checked =
FALSE;
1708 s = &s_space; e = &e_space; checked =
FALSE;
1710 if(f_x && (!s || gtk_text_iter_get_offset(&s_x) < gtk_text_iter_get_offset(s)))
1712 s = &s_x; e = &e_x; checked =
TRUE;
1714 if(f_X && (!s || gtk_text_iter_get_offset(&s_X) < gtk_text_iter_get_offset(s)))
1716 s = &s_X; e = &e_X; checked =
TRUE;
1719 if(!s || !e)
return;
1721 gtk_text_buffer_begin_user_action(buffer);
1722 gtk_text_buffer_delete(buffer, s, e);
1723 gtk_text_buffer_insert(buffer, s, checked ?
"[ ]" :
"[x]", -1);
1724 gtk_text_buffer_end_user_action(buffer);
1726 GtkTextBuffer *edit_buffer = gtk_text_view_get_buffer(
d->edit_view);
1728 if(
d->mode_toggle && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
d->mode_toggle)))
1741 if(event->type != GDK_BUTTON_PRESS || event->button != 1)
return FALSE;
1743 GtkTextView *
view = GTK_TEXT_VIEW(widget);
1744 gint bx = 0, by = 0;
1745 gtk_text_view_window_to_buffer_coords(
view, GTK_TEXT_WINDOW_TEXT,
1746 (gint)event->x, (gint)event->y, &bx, &by);
1748 gtk_text_view_get_iter_at_location(
view, &iter, bx, by);
1750 GSList *tags = gtk_text_iter_get_tags(&iter);
1751 for(GSList *
t = tags;
t;
t = g_slist_next(
t))
1753 GtkTextTag *tag =
t->data;
1754 gpointer linep = g_object_get_data(G_OBJECT(tag),
"checklist_line");
1764 for(GSList *
t = tags;
t;
t = g_slist_next(
t))
1766 GtkTextTag *tag =
t->data;
1767 const char *href = g_object_get_data(G_OBJECT(tag),
"href");
1780 GtkTextIter line_start = iter;
1781 gtk_text_iter_set_line_offset(&line_start, 0);
1782 GtkTextIter line_end = line_start;
1783 gtk_text_iter_forward_to_line_end(&line_end);
1785 GtkTextIter scan = line_start;
1788 GSList *ltags = gtk_text_iter_get_tags(&scan);
1789 for(GSList *
t = ltags;
t;
t = g_slist_next(
t))
1791 GtkTextTag *tag =
t->data;
1792 gpointer linep = g_object_get_data(G_OBJECT(tag),
"checklist_line");
1796 g_slist_free(ltags);
1801 g_slist_free(ltags);
1803 if(gtk_text_iter_compare(&scan, &line_end) >= 0)
break;
1804 if(!gtk_text_iter_forward_char(&scan))
break;
1816 if(
d->mode_toggle && !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
d->mode_toggle)))
1817 return G_SOURCE_REMOVE;
1820 return G_SOURCE_REMOVE;
1827 if(!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
d->mode_toggle)))
return;
1843 if(
d->imgid > 0)
return G_SOURCE_REMOVE;
1845 return G_SOURCE_REMOVE;
1850 if(imgid <= 0)
return;
1863 if(imgid <= 0)
return FALSE;
1876 d->vars_params = NULL;
1883 if(imgid <= 0)
return FALSE;
1884 if(
d->image_path &&
d->image_dir)
return TRUE;
1888 d->image_path = NULL;
1889 d->image_dir = NULL;
1891 gboolean from_cache =
FALSE;
1893 dt_image_full_path(imgid, image_path,
sizeof(image_path), &from_cache, __FUNCTION__);
1894 if(image_path[0] ==
'\0' || !g_file_test(image_path, G_FILE_TEST_EXISTS))
1897 dt_image_full_path(imgid, image_path,
sizeof(image_path), &from_cache, __FUNCTION__);
1900 if(image_path[0] ==
'\0')
return FALSE;
1902 d->image_path = g_strdup(image_path);
1903 d->image_dir = g_path_get_dirname(image_path);
1904 return (
d->image_path &&
d->image_dir);
1920 if(params->path && g_file_get_contents(params->path, ¶ms->text, NULL, NULL))
1921 params->loaded =
TRUE;
1923 if(
IS_NULL_PTR(params->text)) params->text = g_strdup(
"");
1943 result->
self = params->self;
1944 result->
token = params->token;
1945 result->
text = params->text ? params->text : g_strdup(
"");
1946 result->
loaded = params->loaded;
1947 params->text = NULL;
1962 GError *
error = NULL;
1963 if(!g_file_set_contents(
d->path, text, -1, &
error))
1966 g_clear_error(&
error);
1982 d->save_timeout_id = 0;
1984 return G_SOURCE_REMOVE;
1990 if(
d->save_timeout_id)
1992 g_source_remove(
d->save_timeout_id);
1993 d->save_timeout_id = 0;
2001 if(
d->loading)
return;
2004 if(
d->save_timeout_id)
2006 g_source_remove(
d->save_timeout_id);
2007 d->save_timeout_id = 0;
2026 const gboolean preview = gtk_toggle_button_get_active(button);
2027 gtk_stack_set_visible_child_name(GTK_STACK(
d->stack), preview ?
"preview" :
"edit");
2028 gtk_button_set_label(GTK_BUTTON(
d->mode_toggle), preview ? _(
"Edit") : _(
"Preview"));
2055 if(
d->mode_toggle && gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(
d->mode_toggle)))
2060 gtk_widget_set_sensitive(GTK_WIDGET(
d->edit_view),
TRUE);
2061 gtk_widget_set_sensitive(
d->mode_toggle,
TRUE);
2070 return G_SOURCE_REMOVE;
2077 if(
d->save_timeout_id)
2079 g_source_remove(
d->save_timeout_id);
2080 d->save_timeout_id = 0;
2083 const int32_t old_imgid =
d->imgid;
2084 const gboolean changed = (old_imgid != imgid);
2089 g_clear_pointer(&
d->image_path, g_free);
2091 g_clear_pointer(&
d->image_dir, g_free);
2093 const gboolean has_img = (imgid > 0);
2094 gtk_widget_set_sensitive(GTK_WIDGET(
d->edit_view), has_img);
2095 gtk_widget_set_sensitive(
d->mode_toggle, has_img);
2103 GtkTextBuffer *buffer = gtk_text_view_get_buffer(
d->preview_view);
2104 gtk_text_buffer_set_text(buffer,
"", -1);
2108 if(!has_img)
return;
2113 gtk_widget_set_sensitive(GTK_WIDGET(
d->edit_view),
FALSE);
2114 gtk_widget_set_sensitive(
d->mode_toggle,
FALSE);
2120 gtk_widget_set_sensitive(GTK_WIDGET(
d->edit_view),
TRUE);
2121 gtk_widget_set_sensitive(
d->mode_toggle,
TRUE);
2126 params->self = self;
2127 params->token =
d->load_token;
2128 params->path = g_strdup(
d->path);
2157 if(img_id ==
d->imgid)
return;
2167 self->
data = (
void *)
d;
2171 d->height_setting = g_strdup(
"plugins/darkroom/textnotes/text_height");
2178 gtk_box_pack_start(GTK_BOX(vbox), toolbar,
FALSE,
FALSE, 0);
2180 d->mode_toggle = gtk_toggle_button_new_with_label(_(
"preview"));
2181 gtk_widget_set_tooltip_text(
d->mode_toggle, _(
"toggle Markdown preview"));
2182 gtk_box_pack_end(GTK_BOX(toolbar),
d->mode_toggle,
FALSE,
FALSE, 0);
2183 g_signal_connect(G_OBJECT(
d->mode_toggle),
"toggled", G_CALLBACK(
_toggle_mode), self);
2185 d->mtime_label = gtk_label_new(
"");
2186 gtk_label_set_xalign(GTK_LABEL(
d->mtime_label), 0.0f);
2187 gtk_widget_set_halign(
d->mtime_label, GTK_ALIGN_START);
2188 gtk_widget_set_visible(
d->mtime_label,
FALSE);
2189 gtk_box_pack_start(GTK_BOX(toolbar),
d->mtime_label,
TRUE,
TRUE, 0);
2191 d->stack = gtk_stack_new();
2192 gtk_stack_set_transition_type(GTK_STACK(
d->stack), GTK_STACK_TRANSITION_TYPE_CROSSFADE);
2193 gtk_box_pack_start(GTK_BOX(vbox),
d->stack,
TRUE,
TRUE, 0);
2195 GtkWidget *textview = gtk_text_view_new();
2198 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(textview), GTK_WRAP_WORD_CHAR);
2199 gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(textview),
FALSE);
2200 gtk_widget_set_hexpand(textview,
TRUE);
2202 GtkTextBuffer *buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));
2205 g_signal_connect(textview,
"key-press-event", G_CALLBACK(
_edit_key_press), self);
2206 g_signal_connect(textview,
"key-release-event", G_CALLBACK(
_edit_key_release), self);
2208 g_signal_connect(textview,
"map", G_CALLBACK(
_edit_map), self);
2210 d->edit_view = GTK_TEXT_VIEW(textview);
2215 gtk_widget_set_hexpand(edit_sw,
TRUE);
2216 gtk_widget_set_vexpand(edit_sw,
TRUE);
2217 gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(edit_inner),
FALSE);
2218 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(edit_inner), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2219 gtk_stack_add_named(GTK_STACK(
d->stack), edit_sw,
"edit");
2221 GtkWidget *preview_view = gtk_text_view_new();
2223 gtk_text_view_set_editable(GTK_TEXT_VIEW(preview_view),
FALSE);
2224 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(preview_view),
FALSE);
2225 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(preview_view), GTK_WRAP_WORD_CHAR);
2226 gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(preview_view),
FALSE);
2227 gtk_widget_set_hexpand(preview_view,
TRUE);
2228 gtk_widget_add_events(preview_view, GDK_BUTTON_PRESS_MASK);
2229 g_signal_connect(G_OBJECT(preview_view),
"button-press-event",
2231 g_signal_connect(G_OBJECT(preview_view),
"map", G_CALLBACK(
_preview_map), self);
2232 gtk_widget_set_hexpand(preview_view,
TRUE);
2233 gtk_widget_set_vexpand(preview_view,
TRUE);
2234 d->preview_view = GTK_TEXT_VIEW(preview_view);
2238 d->preview_sw = preview_sw;
2239 gtk_widget_set_hexpand(preview_sw,
TRUE);
2240 gtk_widget_set_vexpand(preview_sw,
TRUE);
2241 gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(preview_inner),
FALSE);
2242 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(preview_inner), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
2245 gtk_stack_add_named(GTK_STACK(
d->stack), preview_sw,
"preview");
2246 gtk_stack_set_visible_child_name(GTK_STACK(
d->stack),
"preview");
2247 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(
d->mode_toggle),
TRUE);
2256 gtk_widget_show_all(self->
widget);
2270 if(
d->save_timeout_id)
2272 g_source_remove(
d->save_timeout_id);
2273 d->save_timeout_id = 0;
2276#ifdef HAVE_HTTP_SERVER
2277 if(
d->download_inflight)
2279 g_hash_table_destroy(
d->download_inflight);
2280 d->download_inflight = NULL;
2284 if(
d->completion_popover)
2286 gtk_widget_destroy(
d->completion_popover);
2287 d->completion_popover = NULL;
2289 if(
d->completion_model)
2291 g_object_unref(
d->completion_model);
2292 d->completion_model = NULL;
int32_t dt_act_on_get_first_image()
static void error(char *msg)
void cleanup(dt_imageio_module_format_t *self)
static const dt_aligned_pixel_simd_t const dt_adaptation_t const float p
static void transform(float *x, float *o, const float *m, const float t_h, const float t_v)
const dt_colormatrix_t dt_aligned_pixel_t out
typedef void((*dt_cache_allocate_t)(void *userdata, dt_cache_entry_t *entry))
char * dt_image_build_text_path_from_path(const char *image_path)
void dt_image_full_path(const int32_t imgid, char *pathname, size_t pathname_len, gboolean *from_cache, const char *calling_func)
Get the full path of an image out of the database.
int32_t dt_control_get_mouse_over_id()
void dt_control_log(const char *msg,...)
uint32_t view(const dt_view_t *self)
#define omp_get_max_threads()
#define DT_MODULE(MODVER)
static void dt_free_gpointer(gpointer ptr)
#define omp_get_thread_num()
#define IS_NULL_PTR(p)
C is way too permissive with !=, == and if(var) checks, which can mean too many things depending on w...
gboolean dt_datetime_gdatetime_to_local(char *local, const size_t local_size, GDateTime *gdt, const gboolean msec, const gboolean tz)
static guint dt_keys_mainpad_alternatives(const guint key_val)
Remap keypad keys to usual mainpad ones.
void dt_gui_textview_set_padding(GtkTextView *textview)
Apply the standard recessed-input text padding to a GtkTextView.
GtkWidget * dt_ui_scroll_wrap_get_scrolled_window(GtkWidget *wrapper)
Return the inner scrolled window of a dt_ui_scroll_wrap() wrapper, or NULL.
GtkWidget * dt_gui_get_popup_relative_widget(GtkWidget *widget, GdkRectangle *rect)
Resolve the widget used as parent for nested popups on Wayland.
GtkWidget * dt_ui_scroll_wrap(GtkWidget *w, gint min_size, char *config_str, dt_ui_resize_mode_t mode)
Wrap a scrollable content widget in a recessed, vertically resizable scrolled window.
void dt_accels_disconnect_on_text_input(GtkWidget *widget)
Disconnects accels when a text or search entry gets the focus, and reconnects them when it looses it....
GtkWidget * dt_ui_main_window(dt_ui_t *ui)
get the main window widget
#define DT_GUI_BOX_SPACING
#define DT_PIXEL_APPLY_DPI(value)
const dt_gtkentry_completion_spec * dt_gtkentry_get_default_path_compl_list()
void dt_image_cache_read_release(dt_image_cache_t *cache, const dt_image_t *img)
dt_image_t * dt_image_cache_get(dt_image_cache_t *cache, const int32_t imgid, char mode)
void dt_image_cache_write_release(dt_image_cache_t *cache, dt_image_t *img, dt_image_cache_write_mode_t mode)
dt_job_t * dt_control_job_create(dt_job_execute_callback execute, const char *msg,...)
int dt_control_add_job(dt_control_t *control, dt_job_queue_t queue_id, _dt_job_t *job)
void * dt_control_job_get_params(const _dt_job_t *job)
void dt_control_job_set_state_callback(_dt_job_t *job, dt_job_state_change_callback cb)
void dt_control_job_set_params(_dt_job_t *job, void *params, dt_job_destroy_callback callback)
gboolean dt_lib_gui_get_expanded(dt_lib_module_t *module)
void dt_lib_presets_add(const char *name, const char *plugin_name, const int32_t version, const void *params, const int32_t params_size, gboolean readonly)
#define DT_DEBUG_CONTROL_SIGNAL_DISCONNECT(ctlsig, cb, user_data)
@ DT_SIGNAL_DEVELOP_INITIALIZE
This signal is raised when darktable.develop is initialized.
@ DT_SIGNAL_DEVELOP_IMAGE_CHANGED
This signal is raised when image is changed in darkroom.
@ DT_SIGNAL_MOUSE_OVER_IMAGE_CHANGE
This signal is raised when mouse hovers over image thumbs both on lighttable and in the filmstrip....
#define DT_DEBUG_CONTROL_SIGNAL_CONNECT(ctlsig, signal, cb, user_data)
struct _GtkWidget GtkWidget
char * dt_variables_expand(dt_variables_params_t *params, gchar *source, gboolean iterate)
void dt_variables_params_destroy(dt_variables_params_t *params)
void dt_variables_params_init(dt_variables_params_t **params)
const float uint32_t state[4]
unsigned __int64 uint64_t
struct dt_gui_gtk_t * gui
struct dt_colorspaces_t * color_profiles
struct dt_control_signal_t * signals
struct dt_image_cache_t * image_cache
struct dt_control_t * control
cmsHTRANSFORM transform_srgb_to_display
pthread_rwlock_t xprofile_lock
GtkTextMark * completion_mark
GtkWidget * completion_popover
GtkWidget * completion_tree
GtkListStore * completion_model
GtkTextView * preview_view
dt_variables_params_t * vars_params
static void _setup_completion(dt_lib_module_t *self, GtkWidget *textview)
static gboolean _textnotes_load_finish_idle(gpointer user_data)
int set_params(dt_lib_module_t *self, const void *params, int size)
static void _preview_map(GtkWidget *widget, dt_lib_module_t *self)
static gboolean _completion_apply_selected(dt_lib_module_t *self)
void * get_params(dt_lib_module_t *self, int *size)
static char * _text_sidecar_save_path(dt_lib_textnotes_t *d, const int32_t imgid)
static void _completion_fill(dt_lib_textnotes_t *d, const char *prefix)
static void _save_and_render(dt_lib_module_t *self)
static gboolean _edit_key_release(GtkWidget *widget, GdkEventKey *event, dt_lib_module_t *self)
static void _colorcorrect_pixbuf(GdkPixbuf *pixbuf)
static void _colorcorrect_row(cmsHTRANSFORM transform, guchar *src, const int width, const int n_channels, const gboolean has_alpha, guchar *row_in, guchar *row_out)
static gboolean _completion_focus_out_idle(gpointer user_data)
void gui_cleanup(dt_lib_module_t *self)
static void _render_preview(dt_lib_textnotes_t *d, const char *text)
static void _image_changed_callback(gpointer instance, gpointer user_data)
static void _update_mtime_label(dt_lib_module_t *self)
static void _render_preview_from_edit(dt_lib_textnotes_t *d)
static void _clear_variables_cache(dt_lib_textnotes_t *d)
static void _edit_map(GtkWidget *widget, dt_lib_module_t *self)
static gboolean _set_image_paths(dt_lib_textnotes_t *d, const int32_t imgid)
static int32_t _textnotes_load_job_run(dt_job_t *job)
static gchar * _get_buffer_text(GtkTextBuffer *buffer)
static gboolean _textview_focus_out(GtkWidget *widget, GdkEventFocus *event, dt_lib_module_t *self)
static void _toggle_checklist_at_line(dt_lib_module_t *self, const int line_no)
static gchar * _expand_text_for_preview(dt_lib_textnotes_t *d, const char *source_text)
static void _textbuffer_changed(GtkTextBuffer *buffer, dt_lib_module_t *self)
static void _completion_row_activated(GtkTreeView *tree, GtkTreePath *path, GtkTreeViewColumn *column, dt_lib_module_t *self)
static int _preview_text_window_width_px(dt_lib_textnotes_t *d)
static gboolean _completion_find_prefix(dt_lib_textnotes_t *d, GtkTextIter *cursor, GtkTextIter *start_iter, gchar **prefix_out)
void init_presets(dt_lib_module_t *self)
static void _completion_hide(dt_lib_textnotes_t *d)
static void _mouse_over_image_callback(gpointer instance, gpointer user_data)
static gboolean _alloc_row_buffers(const int width, guchar **row_in, guchar **row_out)
static void _save_now(dt_lib_module_t *self)
uint32_t container(dt_lib_module_t *self)
static void _set_edit_text(dt_lib_textnotes_t *d, const char *text)
static void _completion_update(dt_lib_module_t *self)
static gchar * _get_edit_text(dt_lib_textnotes_t *d)
static void _ensure_has_txt_flag(const int32_t imgid)
static void _load_for_image(dt_lib_module_t *self, const int32_t imgid)
static void _textnotes_load_job_cleanup(void *data)
static void _clear_mtime_label(dt_lib_textnotes_t *d)
void gui_init(dt_lib_module_t *self)
static gboolean _save_timeout_cb(gpointer user_data)
static void _textnotes_load_job_state(dt_job_t *job, dt_job_state_t state)
const char ** views(dt_lib_module_t *self)
static void _update_for_current_image(dt_lib_module_t *self)
static gboolean _preview_button_press(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
static gboolean _image_has_txt_flag(const int32_t imgid)
static void _open_uri(const char *uri)
static gboolean _refresh_preview_idle(gpointer user_data)
static gboolean _edit_key_press(GtkWidget *widget, GdkEventKey *event, dt_lib_module_t *self)
static void _toggle_mode(GtkToggleButton *button, dt_lib_module_t *self)
static gboolean _completion_match(const char *item, const char *prefix)
static void _preview_width_changed(GtkWidget *widget, GdkRectangle *allocation, gpointer user_data)
Re-render the preview when the panel-given width changes, so embedded images rescale to fit the avail...
static void _free_row_buffers(guchar *row_in, guchar *row_out)
static gboolean _edit_button_release(GtkWidget *widget, GdkEventButton *event, dt_lib_module_t *self)
static gboolean _initial_load_idle(gpointer user_data)
@ DT_UI_CONTAINER_PANEL_LEFT_CENTER