377
378
ids.add(email.id);
380
// Request a list of search terms.
381
Gee.Collection<string>? search_keywords = yield search_folder.get_search_matches_async(
381
Gee.Collection<string>? search_matches = yield search_folder.get_search_matches_async(
382
382
ids, cancellable_fetch);
384
// Highlight the search terms.
385
if (search_keywords != null) {
386
foreach(string keyword in search_keywords)
387
web_view.mark_text_matches(keyword, false, 0);
384
// Webkit's highlighting is ... weird. In order to actually see
385
// all the highlighting you're applying, it seems necessary to
386
// start with the shortest string and work up. If you don't, it
387
// seems that shorter strings will overwrite longer ones, and
388
// you're left with incomplete highlighting.
389
Gee.ArrayList<string> ordered_matches = new Gee.ArrayList<string>();
390
if (search_matches != null)
391
ordered_matches.add_all(search_matches);
392
ordered_matches.sort((a, b) => a.length - b.length);
394
foreach(string match in ordered_matches)
395
web_view.mark_text_matches(match, false, 0);
389
396
} catch (Error e) {
390
397
debug("Error highlighting search results: %s", e.message);
688
695
if (!(mimetype in INLINE_MIME_TYPES))
698
// Even if the image doesn't need to be rotated, there's a win here: by reducing the size
699
// of the image at load time, it reduces the amount of work that has to be done to insert
700
// it into the HTML and then decoded and displayed for the user ... note that we currently
701
// have the doucment set up to reduce the size of the image to fit in the viewport, and a
702
// scaled load-and-deode is always faster than load followed by scale.
703
Geary.Memory.Buffer rotated_image = buffer;
705
Gdk.PixbufLoader loader = new Gdk.PixbufLoader();
706
loader.size_prepared.connect(on_inline_image_size_prepared);
708
Geary.Memory.UnownedBytesBuffer? unowned_buffer = buffer as Geary.Memory.UnownedBytesBuffer;
709
if (unowned_buffer != null)
710
loader.write(unowned_buffer.to_unowned_uint8_array());
712
loader.write(buffer.get_uint8_array());
715
Gdk.Pixbuf? pixbuf = loader.get_pixbuf();
716
if (pixbuf != null) {
717
pixbuf = pixbuf.apply_embedded_orientation();
719
// trade-off here between how long it takes to compress the data and how long it
720
// takes to turn it into Base-64 (coupled with how long it takes WebKit to then
721
// Base-64 decode and uncompress it)
723
pixbuf.save_to_buffer(out image_data, "png", "compression", "5");
725
rotated_image = new Geary.Memory.ByteBuffer.take((owned) image_data, image_data.length);
727
} catch (Error err) {
728
debug("Unable to load and rotate image %s for display: %s", filename, err.message);
691
731
return "<img alt=\"%s\" class=\"%s\" src=\"%s\" />".printf(
692
filename, DATA_IMAGE_CLASS, assemble_data_uri(mimetype, buffer));
732
filename, DATA_IMAGE_CLASS, assemble_data_uri(mimetype, rotated_image));
735
// Called by Gdk.PixbufLoader when the image's size has been determined but not loaded yet ...
736
// this allows us to load the image scaled down, for better performance when manipulating and
737
// writing the data URI for WebKit
738
private static void on_inline_image_size_prepared(Gdk.PixbufLoader loader, int width, int height) {
739
// easier to use as local variable than have the const listed everywhere in the code
740
// IN ALL SCREAMING CAPS
741
int scale = MAX_INLINE_IMAGE_MAJOR_DIM;
743
// Borrowed liberally from Shotwell's Dimensions.get_scaled() method
745
// check for existing fit
746
if (width <= scale && height <= scale)
749
int adj_width, adj_height;
750
if ((width - scale) > (height - scale)) {
751
double aspect = (double) scale / (double) width;
754
adj_height = (int) Math.round((double) height * aspect);
756
double aspect = (double) scale / (double) height;
758
adj_width = (int) Math.round((double) width * aspect);
762
loader.set_size(adj_width, adj_height);
695
765
private void unhide_last_email() {
1823
1893
message_overlay_label.ellipsize = Pango.EllipsizeMode.MIDDLE;
1824
1894
message_overlay_label.halign = Gtk.Align.START;
1825
1895
message_overlay_label.valign = Gtk.Align.END;
1896
message_overlay_label.realize.connect(on_message_overlay_label_realize);
1826
1897
message_overlay.add_overlay(message_overlay_label);
1900
// This ensures the overlay's background color matches the background color of the
1902
private void update_message_overlay_label_style() {
1903
Gtk.Window main_window = GearyApplication.instance.controller.main_window;
1904
Gdk.RGBA window_background = main_window.get_style_context()
1905
.get_background_color(Gtk.StateFlags.NORMAL);
1906
Gdk.RGBA label_background = message_overlay_label.get_style_context()
1907
.get_background_color(Gtk.StateFlags.NORMAL);
1909
// To prevent an event loop situation, only update the background if it actually changed.
1910
if (label_background == window_background)
1913
message_overlay_label.get_style_context().changed.disconnect(
1914
on_message_overlay_label_style_changed);
1916
message_overlay_label.override_background_color(Gtk.StateFlags.NORMAL, window_background);
1918
message_overlay_label.get_style_context().changed.connect(
1919
on_message_overlay_label_style_changed);
1922
private void on_message_overlay_label_realize() {
1923
update_message_overlay_label_style();
1926
private void on_message_overlay_label_style_changed() {
1927
// The Gtk theme has probably changed - update the label background.
1928
update_message_overlay_label_style();
1829
1931
private void on_hovering_over_link(string? title, string? url) {
1830
1932
// Copy the link the user is hovering over. Note that when the user mouses-out,
1831
1933
// this signal is called again with null for both parameters.