~ubuntu-branches/ubuntu/saucy/geary/saucy-updates

« back to all changes in this revision

Viewing changes to src/client/views/conversation-viewer.vala

  • Committer: Package Import Robot
  • Author(s): Sebastien Bacher
  • Date: 2013-10-10 17:40:37 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20131010174037-5p5o4dlsoewek2kg
Tags: 0.4.0-0ubuntu1
New stable version

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
    private const string SELECTION_COUNTER_ID = "multiple_messages";
25
25
    private const string SPINNER_ID = "spinner";
26
26
    private const string DATA_IMAGE_CLASS = "data_inline_image";
 
27
    private const int MAX_INLINE_IMAGE_MAJOR_DIM = 1024;
27
28
    
28
29
    private enum SearchState {
29
30
        // Search/find states.
101
102
    
102
103
    // List of emails in this view.
103
104
    public Gee.TreeSet<Geary.Email> messages { get; private set; default = 
104
 
        new Geary.Collection.FixedTreeSet<Geary.Email>(Geary.Email.compare_date_ascending); }
 
105
        new Gee.TreeSet<Geary.Email>(Geary.Email.compare_date_ascending); }
105
106
    
106
107
    // The HTML viewer to view the emails.
107
108
    public ConversationWebView web_view { get; private set; }
377
378
            ids.add(email.id);
378
379
        
379
380
        try {
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);
383
383
            
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);
388
 
            }
 
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);
 
393
            
 
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);
391
398
        }
648
655
        string body_text = "";
649
656
        remote_images = false;
650
657
        try {
651
 
            body_text = message.get_body(true, inline_image_replacer);
 
658
            body_text = message.get_body(true, inline_image_replacer) ?? "";
652
659
            body_text = insert_html_markup(body_text, message, out remote_images);
653
660
        } catch (Error err) {
654
661
            debug("Could not get message text. %s", err.message);
688
695
        if (!(mimetype in INLINE_MIME_TYPES))
689
696
            return null;
690
697
        
 
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;
 
704
        try {
 
705
            Gdk.PixbufLoader loader = new Gdk.PixbufLoader();
 
706
            loader.size_prepared.connect(on_inline_image_size_prepared);
 
707
            
 
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());
 
711
            else
 
712
                loader.write(buffer.get_uint8_array());
 
713
            loader.close();
 
714
            
 
715
            Gdk.Pixbuf? pixbuf = loader.get_pixbuf();
 
716
            if (pixbuf != null) {
 
717
                pixbuf = pixbuf.apply_embedded_orientation();
 
718
                
 
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)
 
722
                uint8[] image_data;
 
723
                pixbuf.save_to_buffer(out image_data, "png", "compression", "5");
 
724
                
 
725
                rotated_image = new Geary.Memory.ByteBuffer.take((owned) image_data, image_data.length);
 
726
            }
 
727
        } catch (Error err) {
 
728
            debug("Unable to load and rotate image %s for display: %s", filename, err.message);
 
729
        }
 
730
        
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));
 
733
    }
 
734
    
 
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;
 
742
        
 
743
        // Borrowed liberally from Shotwell's Dimensions.get_scaled() method
 
744
        
 
745
        // check for existing fit
 
746
        if (width <= scale && height <= scale)
 
747
            return;
 
748
        
 
749
        int adj_width, adj_height;
 
750
        if ((width - scale) > (height - scale)) {
 
751
            double aspect = (double) scale / (double) width;
 
752
            
 
753
            adj_width = scale;
 
754
            adj_height = (int) Math.round((double) height * aspect);
 
755
        } else {
 
756
            double aspect = (double) scale / (double) height;
 
757
            
 
758
            adj_width = (int) Math.round((double) width * aspect);
 
759
            adj_height = scale;
 
760
        }
 
761
        
 
762
        loader.set_size(adj_width, adj_height);
693
763
    }
694
764
    
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);
1827
1898
    }
1828
1899
    
 
1900
    // This ensures the overlay's background color matches the background color of the
 
1901
    // main window
 
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);
 
1908
        
 
1909
        // To prevent an event loop situation, only update the background if it actually changed.
 
1910
        if (label_background == window_background)
 
1911
            return;
 
1912
        
 
1913
        message_overlay_label.get_style_context().changed.disconnect(
 
1914
            on_message_overlay_label_style_changed);
 
1915
        
 
1916
        message_overlay_label.override_background_color(Gtk.StateFlags.NORMAL, window_background);
 
1917
        
 
1918
        message_overlay_label.get_style_context().changed.connect(
 
1919
            on_message_overlay_label_style_changed);
 
1920
    }
 
1921
    
 
1922
    private void on_message_overlay_label_realize() {
 
1923
        update_message_overlay_label_style();
 
1924
    }
 
1925
    
 
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();
 
1929
    }
 
1930
    
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.