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