1/* 2 * This file is part of gitg 3 * 4 * Copyright (C) 2016 - Jesse van den Kieboom 5 * 6 * gitg is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * gitg is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with gitg. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20class Gitg.DiffImageSideBySide : Gtk.DrawingArea 21{ 22 private Pango.Layout d_old_size_layout; 23 private Pango.Layout d_new_size_layout; 24 25 private const int TEXT_SPACING = 6; 26 27 private Pango.Layout? old_size_layout 28 { 29 get 30 { 31 if (d_old_size_layout == null && cache.old_pixbuf != null) 32 { 33 string message = @"$(cache.old_pixbuf.get_width()) × $(cache.old_pixbuf.get_height())"; 34 35 if (cache.new_pixbuf != null) 36 { 37 // Translators: this label is displayed below the image diff, %s 38 // is substituted with the size of the image 39 d_old_size_layout = create_pango_layout(_("before (%s)").printf(message)); 40 } 41 else 42 { 43 // Translators: this label is displayed below the image diff, %s 44 // is substituted with the size of the image 45 d_old_size_layout = create_pango_layout(_("removed (%s)").printf(message)); 46 } 47 } 48 49 return d_old_size_layout; 50 } 51 } 52 53 private Pango.Layout? new_size_layout 54 { 55 get 56 { 57 if (d_new_size_layout == null && cache.new_pixbuf != null) 58 { 59 string message = @"$(cache.new_pixbuf.get_width()) × $(cache.new_pixbuf.get_height())"; 60 61 if (cache.old_pixbuf != null) 62 { 63 // Translators: this label is displayed below the image diff, %s 64 // is substituted with the size of the image 65 d_new_size_layout = create_pango_layout(_("after (%s)").printf(message)); 66 } 67 else 68 { 69 // Translators: this label is displayed below the image diff, %s 70 // is substituted with the size of the image 71 d_new_size_layout = create_pango_layout(_("added (%s)").printf(message)); 72 } 73 } 74 75 return d_new_size_layout; 76 } 77 } 78 79 public Gitg.DiffImageSurfaceCache cache { get; set; } 80 public int spacing { get; set; } 81 82 private struct Size 83 { 84 public int width; 85 86 public int image_width; 87 public int image_height; 88 } 89 90 private struct Sizing 91 { 92 public Size old_size; 93 public Size new_size; 94 } 95 96 private Sizing get_sizing(int width) 97 { 98 double ow = 0, oh = 0, nw = 0, nh = 0; 99 100 var old_pixbuf = cache.old_pixbuf; 101 var new_pixbuf = cache.new_pixbuf; 102 103 var window = get_window(); 104 105 if (old_pixbuf != null) 106 { 107 double xscale = 1, yscale = 1; 108 109 if (window != null) 110 { 111 cache.get_old_surface(get_window()).get_device_scale(out xscale, out yscale); 112 } 113 114 ow = (double)old_pixbuf.get_width() / xscale; 115 oh = (double)old_pixbuf.get_height() / yscale; 116 } 117 118 if (new_pixbuf != null) 119 { 120 double xscale = 1, yscale = 1; 121 122 if (window != null) 123 { 124 cache.get_new_surface(get_window()).get_device_scale(out xscale, out yscale); 125 } 126 127 nw = (double)new_pixbuf.get_width() / xscale; 128 nh = (double)new_pixbuf.get_height() / yscale; 129 } 130 131 var tw = ow + nw; 132 133 width -= spacing; 134 135 double osw = 0, nsw = 0; 136 137 if (tw != 0) 138 { 139 if (ow != 0) 140 { 141 osw = width * (ow / tw); 142 } 143 144 if (nw != 0) 145 { 146 nsw = width * (nw / tw); 147 } 148 } 149 150 var oswi = double.min(osw, ow); 151 var nswi = double.min(nsw, nw); 152 153 double oshi = 0, nshi = 0; 154 155 if (ow != 0) 156 { 157 oshi = oswi / ow * oh; 158 } 159 160 if (nw != 0) 161 { 162 nshi = nswi / nw * nh; 163 } 164 165 return Sizing() { 166 old_size = Size() { 167 width = (int)osw, 168 169 image_width = (int)oswi, 170 image_height = (int)oshi 171 }, 172 173 new_size = Size() { 174 width = (int)nsw, 175 176 image_width = (int)nswi, 177 image_height = (int)nshi 178 } 179 }; 180 } 181 182 protected override void style_updated() 183 { 184 d_old_size_layout = null; 185 d_new_size_layout = null; 186 } 187 188 protected override void get_preferred_height_for_width(int width, out int minimum_height, out int natural_height) 189 { 190 var sizing = get_sizing(width); 191 var h = double.max(sizing.old_size.image_height, sizing.new_size.image_height); 192 193 var ol = old_size_layout; 194 var nl = new_size_layout; 195 196 int osw = 0, osh = 0, nsw = 0, nsh = 0; 197 198 if (ol != null) 199 { 200 ol.get_pixel_size(out osw, out osh); 201 } 202 203 if (nl != null) 204 { 205 nl.get_pixel_size(out nsw, out nsh); 206 } 207 208 h += TEXT_SPACING + int.max(osh, nsh); 209 210 minimum_height = (int)h; 211 natural_height = (int)h; 212 } 213 214 protected override Gtk.SizeRequestMode get_request_mode() 215 { 216 return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH; 217 } 218 219 protected override bool draw(Cairo.Context cr) 220 { 221 var window = get_window(); 222 223 Gtk.Allocation alloc; 224 get_allocation(out alloc); 225 226 var sizing = get_sizing(alloc.width); 227 228 var old_surface = cache.get_old_surface(window); 229 var new_surface = cache.get_new_surface(window); 230 231 var ctx = get_style_context(); 232 233 ctx.render_background(cr, alloc.x, alloc.y, alloc.width, alloc.height); 234 235 double max_height = double.max(sizing.old_size.image_height, sizing.new_size.image_height); 236 double spread_factor = 0.5; 237 238 if (old_surface != null && new_surface != null) 239 { 240 spread_factor = 2.0 / 3.0; 241 } 242 243 if (old_surface != null) 244 { 245 var x = (sizing.old_size.width - sizing.old_size.image_width) * spread_factor; 246 var y = (max_height - sizing.old_size.image_height) / 2; 247 248 cr.set_source_surface(old_surface, x, y); 249 cr.paint(); 250 251 Pango.Rectangle rect; 252 253 old_size_layout.get_pixel_extents(null, out rect); 254 255 ctx.render_layout(cr, 256 x + rect.x + (sizing.old_size.image_width - rect.width) / 2, 257 rect.y + max_height + TEXT_SPACING, 258 old_size_layout); 259 } 260 261 if (new_surface != null) 262 { 263 var x = (sizing.new_size.width - sizing.new_size.image_width) * (1.0 - spread_factor); 264 var y = (max_height - sizing.new_size.image_height) / 2; 265 266 if (cache.old_pixbuf != null) 267 { 268 x += sizing.old_size.width + spacing; 269 } 270 271 cr.set_source_surface(new_surface, x, y); 272 cr.paint(); 273 274 Pango.Rectangle rect; 275 276 new_size_layout.get_pixel_extents(null, out rect); 277 278 ctx.render_layout(cr, 279 x + rect.x + (sizing.new_size.image_width - rect.width) / 2, 280 rect.y + max_height + TEXT_SPACING, 281 new_size_layout); 282 } 283 284 return true; 285 } 286 287 protected override void realize() 288 { 289 base.realize(); 290 queue_resize(); 291 } 292} 293