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