1 /**
2  * clock.d
3  *
4  * A gtkD widget that implements a clock face
5  *
6  * Based on the Gtkmm example by:
7  * Jonathon Jongsma
8  *
9  * and the original GTK+ example by:
10  * (c) 2005-2006, Davyd Madeley
11  *
12  * Authors:
13  *   Jonas Kivi (D version)
14  *   Jonathon Jongsma (C++ version)
15  *   Davyd Madeley (C version)
16  */
17 
18 module clock;
19 
20 version(Tango) import tango.io.Stdout;
21 else import std.stdio;
22 
23 version(Tango) import tango.math.Math;
24 else import std.math;
25 
26 version(Tango)
27 {
28 	import tango.time.Time;
29 	import tangoClock = tango.time.WallClock;
30 }
31 else version(D_Version2)
32 {
33 	import std.datetime;
34 }
35 else
36 {
37 	import std.date;
38 }
39 
40 import gtk.Timeout;
41 
42 import cairo.Context;
43 import cairo.Surface;
44 
45 import gtk.Widget;
46 import gdk.Drawable;
47 import gdk.Window;
48 import gdk.Rectangle;
49 
50 import gtk.DrawingArea;
51 
52 class Clock : DrawingArea
53 {
54 public:
55 	this()
56 	{
57 		//Attach our expose callback, which will draw the window.
58 		addOnExpose(&exposeCallback);
59 	}
60 
61 protected:
62 	//Override default signal handler:
63 	bool exposeCallback(GdkEventExpose* event, Widget widget)
64 	{
65 		if ( m_timeout is null )
66 		{
67 			//Create a new timeout that will ask the window to be drawn once every second.
68 			m_timeout = new Timeout( 1000, &onSecondElapsed, false );
69 		}
70 
71 		// This is where we draw on the window
72 
73 		Drawable dr = getWindow();
74 
75 		int width;
76 		int height;
77 
78 		dr.getSize(width, height);
79 
80 		auto cr = new Context (dr);
81 
82 		if (event)
83 		{
84 			// clip to the area indicated by the expose event so that we only redraw
85 			// the portion of the window that needs to be redrawn
86 			cr.rectangle(event.area.x, event.area.y,
87 				event.area.width, event.area.height);
88 			cr.clip();
89 		}
90 
91 		// scale to unit square and translate (0, 0) to be (0.5, 0.5), i.e. the
92 		// center of the window
93 		cr.scale(width, height);
94 		cr.translate(0.5, 0.5);
95 		cr.setLineWidth(m_lineWidth);
96 
97 		cr.save();
98 			cr.setSourceRgba(0.3, 0.6, 0.2, 0.9);   // brownish green
99 			cr.paint();
100 		cr.restore();
101 
102 		cr.arc(0, 0, m_radius, 0, 2 * PI);
103 
104 		cr.save();
105 			cr.setSourceRgba(0.0, 0.0, 0.0, 0.8);
106 			cr.fillPreserve();
107 		cr.restore();
108 
109 		cr.save();
110 			cr.setSourceRgba(1.0, 1.0, 1.0, 1.0);
111 			cr.setLineWidth( m_lineWidth * 1.7);
112 			cr.strokePreserve();
113 			cr.clip();
114 		cr.restore();
115 
116 
117 		//clock ticks
118 
119 		for (int i = 0; i < 12; i++)
120 		{
121 			double inset = 0.07;
122 
123 			cr.save();
124 				cr.setSourceRgba(1.0, 1.0, 1.0, 1.0);
125 				cr.setLineWidth( m_lineWidth * 0.25);
126 				cr.setLineCap(cairo_line_cap_t.ROUND);
127 
128 				if (i % 3 != 0)
129 				{
130 					inset *= 1.2;
131 					cr.setLineWidth( m_lineWidth * 0.5 );
132 				}
133 
134 				cr.moveTo(
135 					(m_radius - inset) * cos (i * PI / 6),
136 					(m_radius - inset) * sin (i * PI / 6));
137 				cr.lineTo (
138 					m_radius * cos (i * PI / 6),
139 					m_radius * sin (i * PI / 6));
140 				cr.stroke();
141 			cr.restore(); // stack-pen-size
142 		}
143 
144 		version(Tango)
145 		{
146 			auto time = tangoClock.WallClock.now.time;
147 
148 			double minutes = time.minutes * PI / 30;
149 			double hours = time.hours * PI / 6;
150 			double seconds = time.seconds * PI / 30;
151 		}
152 		else version(D_Version2)
153 		{
154 			SysTime lNow = std.datetime.Clock.currTime();
155 
156 			// compute the angles of the indicators of our clock
157 			double minutes = lNow.minute * PI / 30; 
158 			double hours = lNow.hour * PI / 6; 
159 			double seconds= lNow.second * PI / 30; 
160 		}
161 		else
162 		{
163 			d_time lNow;
164 			string lNowString;
165 
166 			// Grab the date and time relative to UTC
167 			lNow = std.date.getUTCtime();
168 			// Convert this into the local date and time for display.
169 			lNowString = std.date.toString(lNow);
170 
171 			Date timeinfo;
172 			timeinfo.parse(lNowString);
173 
174 			// compute the angles of the indicators of our clock
175 			double minutes = timeinfo.minute * PI / 30;
176 			double hours = timeinfo.hour * PI / 6;
177 			double seconds= timeinfo.second * PI / 30;
178 		}
179 
180 		cr.save();
181 			cr.setLineCap(cairo_line_cap_t.ROUND);
182 
183 			// draw the seconds hand
184 			cr.save();
185 				cr.setLineWidth(m_lineWidth / 3);
186 				cr.setSourceRgba(0.7, 0.7, 0.85, 0.8); // blueish gray
187 				cr.moveTo(0, 0);
188 				cr.lineTo(sin(seconds) * (m_radius * 0.8),
189 					-cos(seconds) * (m_radius * 0.8));
190 				cr.stroke();
191 			cr.restore();
192 
193 			// draw the minutes hand
194 			//cr.setSourceRgba(0.117, 0.337, 0.612, 0.9);   // blue
195 			cr.setSourceRgba(0.712, 0.337, 0.117, 0.9);   // red
196 			cr.moveTo(0, 0);
197 			cr.lineTo(sin(minutes + seconds / 60) * (m_radius * 0.7),
198 				-cos(minutes + seconds / 60) * (m_radius * 0.7));
199 			cr.stroke();
200 
201 			// draw the hours hand
202 			cr.setSourceRgba(0.337, 0.612, 0.117, 0.9);   // green
203 			cr.moveTo(0, 0);
204 			cr.lineTo(sin(hours + minutes / 12.0) * (m_radius * 0.4),
205 				-cos(hours + minutes / 12.0) * (m_radius * 0.4));
206 			cr.stroke();
207 		cr.restore();
208 
209 		// draw a little dot in the middle
210 		cr.arc(0, 0, m_lineWidth / 3.0, 0, 2 * PI);
211 		cr.fill();
212 
213 		delete cr;
214 
215 		return true;
216 	}
217 
218 	bool onSecondElapsed()
219 	{
220 		//force our program to redraw the entire clock once per every second.
221 
222 		Window win = getWindow();
223 
224 		if(win)
225 		{
226 
227 			int width;
228 			int height;
229 
230 			win.getSize(width, height);
231 
232 			//I think this should be also made possible:
233 			//width = win.getWidth();
234 			//height = win.getHeight();
235 
236 			//And there should be a constructor like: Rectangle( int x, int y, int width, int height );
237 			//because at the moment we have to do this to use a Rectangle, and that it needed for
238 			//invalidateRect. The easiest way would be a new invalidate( int x, int y, int width, int height)
239 			//that would do everything that we're doing here. And maybe also invalidateAll();
240 			GdkRectangle* grect = new GdkRectangle();
241 
242 			grect.x = 0;
243 			grect.y = 0;
244 			grect.width = width;
245 			grect.height = height;
246 
247 			//Rectangle r = new Rectangle(0, 0, width, height);
248 			Rectangle r = new Rectangle(grect);
249 
250 			win.invalidateRect(r, false);
251 		}
252 		//else writefln("The Gdk.Window doesn't exist. Something went wrong in clock.d onSecondsElapsed()");
253 
254 		return true;
255 
256 	}
257 
258 	double m_radius = 0.40;
259 	double m_lineWidth = 0.065;
260 
261 	Timeout m_timeout;
262 }
263