1 /*
2  * This file is part of gtkD.
3  *
4  * gtkD is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation; either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * gtkD is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with gtkD; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
17  */
18 
19 module TestDrawingArea;
20 
21 //debug = trace;
22 
23 private import cairo.Context;
24 private import cairo.ImageSurface;
25 
26 private import gtk.VBox;
27 private import pango.PgContext;
28 private import pango.PgLayout;
29 
30 private import std.stdio;
31 private import std.math;
32 
33 private import gtk.Widget;
34 private import gtk.MenuItem;
35 private import gtk.ComboBox;
36 private import gtk.ComboBoxText;
37 private import gtk.Menu;
38 private import gtk.Adjustment;
39 private import gtk.HBox;
40 private import gdk.Pixbuf;
41 private import gdk.Cairo;
42 private import gdk.Color;
43 private import gdk.Event;
44 
45 private import pango.PgCairo;
46 private import pango.PgFontDescription;
47 
48 private import gtk.DrawingArea;
49 private import gtk.Image;
50 private import gtk.SpinButton;
51 
52 
53 /**
54  * This tests the gtkD drawing area widget
55  */
56 class TestDrawingArea : VBox
57 {
58 
59 	this()
60 	{
61 		super(false,4);
62 
63 		TestDrawing drawingArea = new TestDrawing();
64 
65 		ComboBoxText operators = new ComboBoxText();
66 		operators.appendText("CLEAR");
67 		operators.appendText("SOURCE");
68 		operators.appendText("OVER");
69 		operators.appendText("IN");
70 		operators.appendText("OUT");
71 		operators.appendText("ATOP");
72 		operators.appendText("DEST");
73 		operators.appendText("DEST_OVER");
74 		operators.appendText("DEST_IN");
75 		operators.appendText("DEST_OUT");
76 		operators.appendText("DEST_ATOP");
77 		operators.appendText("XOR");
78 		operators.appendText("ADD");
79 		operators.appendText("SATURATE");
80 		operators.appendText("MULTIPLY");
81 		operators.appendText("SCREEN");
82 		operators.appendText("OVERLAY");
83 		operators.appendText("DARKEN");
84 		operators.appendText("LIGHTEN");
85 		operators.appendText("COLOR_DODGE");
86 		operators.appendText("COLOR_BURN");
87 		operators.appendText("HARD_LIGHT");
88 		operators.appendText("SOFT_LIGHT");
89 		operators.appendText("DIFFERENCE");
90 		operators.appendText("EXCLUSION");
91 		operators.appendText("HSL_HUE");
92 		operators.appendText("HSL_SATURATION");
93 		operators.appendText("HSL_COLOR");
94 		operators.appendText("HSL_LUMINOSITY");
95 		operators.setActive(1);
96 		operators.addOnChanged(&drawingArea.onOperatorsChanged);
97 
98 		ComboBoxText primOption = new ComboBoxText();
99 		primOption.appendText("Filled Arc");
100 		primOption.appendText("Arc");
101 		primOption.appendText("Line");
102 		primOption.appendText("Point");
103 		primOption.appendText("Rectangle");
104 		primOption.appendText("Filled Rectangle");
105 		primOption.appendText("Text");
106 		primOption.appendText("Pango text");
107 		primOption.appendText("Image");
108 		primOption.appendText("Polygon");
109 		primOption.setActive(0);
110 		primOption.addOnChanged(&drawingArea.onPrimOptionChanged);
111 
112 		packStart(drawingArea,true,true,0);
113 
114 		HBox hbox = new HBox(false,4);
115 		hbox.packStart(operators,false,false,2);
116 		hbox.packStart(primOption,false,false,2);
117 		hbox.packStart(drawingArea.spin,false,false,2);
118 		hbox.packStart(drawingArea.backSpin,false,false,2);
119 
120 		packStart(hbox, false, false, 0);
121 	}
122 
123 	class TestDrawing : DrawingArea
124 	{
125 		CairoOperator operator = CairoOperator.OVER;
126 		ImageSurface surface;
127 		Color paintColor;
128 		Color black;
129 
130 		int width;
131 		int height;
132 
133 		bool buttonIsDown;
134 
135 		string primitiveType;
136 		PgFontDescription font;
137 		Image image;
138 		Pixbuf scaledPixbuf;
139 
140 		SpinButton spin;
141 		SpinButton backSpin;
142 		static GdkPoint[] polygonStar = [
143 			{0,4},
144 			{1,1},
145 			{4,0},
146 			{1,-1},
147 			{0,-4},
148 			{-1,-1},
149 			{-4,0},
150 			{-1,1}
151 			];
152 
153 		this()
154 		{
155 			setSizeRequest(333,333);
156 			width = getWidth();
157 			height = getHeight();
158 
159 			primitiveType = "Filled Arc";
160 			font = PgFontDescription.fromString("Courier 48");
161 
162 			image = new Image("images/gtkDlogo_a_small.png");
163 			scaledPixbuf = image.getPixbuf();
164 			if (scaledPixbuf is null)
165 			{
166 				writeln("\nFailed to load image file gtkDlogo_a_small.png");
167 			}
168 
169 			paintColor = new Color(cast(ubyte)0,cast(ubyte)0,cast(ubyte)0);
170 			black = new Color(cast(ubyte)0,cast(ubyte)0,cast(ubyte)0);
171 
172 			spin = new SpinButton(new Adjustment(30, 1, 400, 1, 10, 0),1,0);
173 			sizeSpinChanged(spin);
174 			spin.addOnValueChanged(&sizeSpinChanged);
175 			backSpin = new SpinButton(new Adjustment(5, 4, 100, 1, 10, 0),1,0);
176 			backSpin.addOnValueChanged(&backSpinChanged);
177 
178 			addOnDraw(&drawCallback);
179 			addOnMotionNotify(&onMotionNotify);
180 			addOnSizeAllocate(&onSizeAllocate);
181 			addOnButtonPress(&onButtonPress);
182 			addOnButtonRelease(&onButtonRelease);
183 		}
184 
185 		void onSizeAllocate(GtkAllocation* allocation, Widget widget)
186 		{
187 			width = allocation.width;
188 			height = allocation.height;
189 
190 			surface = ImageSurface.create(CairoFormat.ARGB32, width, height);
191 			drawPoints(Context.create(surface));
192 		}
193 
194 		public bool onButtonPress(Event event, Widget widget)
195 		{
196 			debug(trace) writeln("button DOWN");
197 			if ( event.type == EventType.BUTTON_PRESS && event.button.button == 1 )
198 			{
199 				debug(trace) writeln("Button 1 down");
200 				buttonIsDown = true;
201 
202 				drawPrimitive(cast(int)event.button.x, cast(int)event.button.y);
203 			}
204 			return false;
205 		}
206 
207 		public bool onButtonRelease(Event event, Widget widget)
208 		{
209 			debug(trace) writeln("button UP");
210 			if ( event.type == EventType.BUTTON_RELEASE && event.button.button == 1 )
211 			{
212 				debug(trace) writeln("Button 1 UP");
213 				buttonIsDown = false;
214 			}
215 			return false;
216 		}
217 
218 		/**
219 		 * This will be called from the expose event call back.
220 		 * \bug this is called on get or loose focus - review
221 		 */
222 		public bool drawCallback(Scoped!Context context, Widget widget)
223 		{
224 			//Fill the Widget with the surface we are drawing on.
225 			context.setSourceSurface(surface, 0, 0);
226 			context.paint();
227 
228 			return true;
229 		}
230 
231 		public bool onMotionNotify(Event event, Widget widget)
232 		{
233 			//writeln("testWindow.mouseMoved -----------------------------");
234 			if ( buttonIsDown && event.type == EventType.MOTION_NOTIFY )
235 			{
236 				drawPrimitive(cast(int)event.motion.x, cast(int)event.motion.y);
237 			}
238 
239 			return true;
240 		}
241 
242 		static int backSpinCount = 0;
243 
244 		public void backSpinChanged(SpinButton spinButton)
245 		{
246 
247 			debug(trace) writefln("backSpinChanged - entry %s", ++backSpinCount);
248 
249 			drawPoints(Context.create(surface));
250 			this.queueDraw();
251 
252 			debug(trace) writeln("backSpinChanged - exit");
253 		}
254 
255 		public void sizeSpinChanged(SpinButton spinButton)
256 		{
257 			if ( !(scaledPixbuf is null))
258 			{
259 				int width = spinButton.getValueAsInt();
260 				scaledPixbuf = image.getPixbuf();
261 
262 				float ww = width * scaledPixbuf.getWidth() / 30;
263 				float hh = width * scaledPixbuf.getHeight() / 30;
264 
265 				scaledPixbuf = scaledPixbuf.scaleSimple(cast(int)ww, cast(int)hh, GdkInterpType.HYPER);
266 			}
267 		}
268 
269 		public void drawPrimitive(int x, int y)
270 		{
271 			int width = spin.getValueAsInt();
272 			int height = width * 3 / 4;
273 
274 			Context context = Context.create(surface);
275 			context.setOperator(operator);
276 
277 			debug(trace) writefln("primitiveType = %s", primitiveType);
278 
279 			switch ( primitiveType )
280 			{
281 				case "Arc":
282 					context.arc(x-width/2,y-width/2,width,0,2*PI);
283 					context.stroke();
284 					break;
285 
286 				case "Filled Arc":
287 					context.arc(x-width/4,y-width/4,width/2,0,2*PI);
288 					context.fill();
289 					break;
290 
291 				case "Line":
292 					context.moveTo(x, y);
293 					context.lineTo(x+width, y);
294 					context.stroke();
295 					break;
296 
297 				case "Point":
298 					context.rectangle(x, y, 1, 1);
299 					context.fill();
300 					break;
301 
302 				case "Rectangle":
303 					context.rectangle(x-width/2, y-width/4, width, height);
304 					context.stroke();
305 					break;
306 
307 				case "Filled Rectangle":
308 					context.rectangle(x-width/2, y-width/4, width, height);
309 					context.fill();
310 					break;
311 
312 				case "Text":
313 					context.selectFontFace("FreeMono", CairoFontSlant.NORMAL, CairoFontWeight.NORMAL);
314 					context.setFontSize(12);
315 					context.moveTo(x, y);
316 					context.showText("gtkD toolkit");
317 					break;
318 
319 				case "Pango text":
320 					PgLayout l = PgCairo.createLayout(context);
321 					PgFontDescription fd = new PgFontDescription("Sans", width);
322 
323 					l.setText("Gtk+ with D");
324 					l.setFontDescription(fd);
325 
326 					context.moveTo(x, y);
327 					PgCairo.showLayout(context, l);
328 					break;
329 
330 				case "Image":
331 					if ( !(scaledPixbuf is null))
332 					{
333 						context.setSourcePixbuf(scaledPixbuf, x, y);
334 						context.paint();
335 					}
336 					break;
337 
338 				case "Polygon":
339 					//TODO: Use Context.scale and transform ?
340 					for ( int scale = 10 ; scale<= 300; scale+=15)
341 					{
342 						context.save();
343 						context.moveTo(polygonStar[0].x*scale/2+x, polygonStar[0].y*scale/2+y);
344 
345 						foreach(p; polygonStar[1 .. $])
346 						{
347 							context.lineTo(p.x*scale/2+x, p.y*scale/2+y);
348 						}
349 						context.closePath();
350 						context.stroke();
351 						context.restore();
352 					}
353 					break;
354 
355 				default:
356 					context.arcNegative(x-2,y-2,4,0,6);
357 					context.fill();
358 					break;
359 			}
360 
361 			//Redraw the Widget.
362 			this.queueDraw();
363 		}
364 
365 		private void drawPoints(Context context)
366 		{
367 			int square = backSpin.getValueAsInt();
368 			int totalcount = 0;
369 			int count = 0;
370 			Color color = new Color();
371 			int width = this.width;
372 			int height = this.height;
373 			int x = 0;
374 			int y = 0;
375 
376 			debug(trace) writefln("w,h = %s %s",width ,height);
377 
378 			float dx = 256.0 / width;
379 			float dy = 256.0 / height ;
380 			float xx;
381 			float yy;
382 			while ( x < width || y <height )
383 			{
384 				context.save();
385 
386 				xx = x * dx;
387 				yy = y * dy;
388 				context.setSourceRgba( xx / 255,
389 				                       yy / 255,
390 				                       sqrt((xx*xx)+(yy*yy)) / 255,
391 				                       1 );
392 
393 				if ( square > 1 )
394 				{
395 					context.rectangle(x, y, square, square);
396 					context.fill();
397 				}
398 				else
399 				{
400 					context.moveTo(x, y);
401 					context.stroke();
402 				}
403 				x +=square;
404 				if  ( x > width)
405 				{
406 					if ( y>height)
407 					{
408 						//y=0;
409 					}
410 					else
411 					{
412 						x = 0;
413 						y+=square;
414 					}
415 				}
416 				++totalcount;
417 
418 				context.restore();
419 			}
420 			color.destroy();
421 		}
422 
423 		void onOperatorsChanged(ComboBoxText comboBoxText)
424 		{
425 			debug(trace) writefln("CairoOperator = %s", comboBoxText.getActiveText());
426 			switch ( comboBoxText.getActiveText() )
427 			{
428 				case "CLEAR":          operator = CairoOperator.CLEAR;          break;
429 				case "SOURCE":         operator = CairoOperator.SOURCE;         break;
430 				case "OVER":           operator = CairoOperator.OVER;           break;
431 				case "IN":             operator = CairoOperator.IN;             break;
432 				case "OUT":            operator = CairoOperator.OUT;            break;
433 				case "ATOP":           operator = CairoOperator.ATOP;           break;
434 				case "DEST":           operator = CairoOperator.DEST;           break;
435 				case "DEST_OVER":      operator = CairoOperator.DEST_OVER;      break;
436 				case "DEST_IN":        operator = CairoOperator.DEST_IN;        break;
437 				case "DEST_OUT":       operator = CairoOperator.DEST_OUT;       break;
438 				case "DEST_ATOP":      operator = CairoOperator.DEST_ATOP;      break;
439 				case "XOR":            operator = CairoOperator.XOR;            break;
440 				case "ADD":            operator = CairoOperator.ADD;            break;
441 				case "SATURATE":       operator = CairoOperator.SATURATE;       break;
442 				case "MULTIPLY":       operator = CairoOperator.MULTIPLY;       break;
443 				case "SCREEN":         operator = CairoOperator.SCREEN;         break;
444 				case "OVERLAY":        operator = CairoOperator.OVERLAY;        break;
445 				case "DARKEN":         operator = CairoOperator.DARKEN;         break;
446 				case "LIGHTEN":        operator = CairoOperator.LIGHTEN;        break;
447 				case "COLOR_DODGE":    operator = CairoOperator.COLOR_DODGE;    break;
448 				case "COLOR_BURN":     operator = CairoOperator.COLOR_BURN;     break;
449 				case "HARD_LIGHT":     operator = CairoOperator.HARD_LIGHT;     break;
450 				case "SOFT_LIGHT":     operator = CairoOperator.SOFT_LIGHT;     break;
451 				case "DIFFERENCE":     operator = CairoOperator.DIFFERENCE;     break;
452 				case "EXCLUSION":      operator = CairoOperator.EXCLUSION;      break;
453 				case "HSL_HUE":        operator = CairoOperator.HSL_HUE;        break;
454 				case "HSL_SATURATION": operator = CairoOperator.HSL_SATURATION; break;
455 				case "HSL_COLOR":      operator = CairoOperator.HSL_COLOR;      break;
456 				case "HSL_LUMINOSITY": operator = CairoOperator.HSL_LUMINOSITY; break;
457 				default:               operator = CairoOperator.OVER;           break;
458 			}
459 		}
460 
461 		void onPrimOptionChanged(ComboBoxText comboBoxText)
462 		{
463 			primitiveType = comboBoxText.getActiveText();
464 		}
465 	}
466 }
467