1 /*
2 * IceWM
3 *
4 * Copyright (C) 1997-2001 Marko Macek
5 *
6 * Clock
7 */
8
9 #define NEED_TIME_H
10
11 #include "config.h"
12 #include "aclock.h"
13 #include "sysdep.h"
14 #include "applet.h"
15 #include "wpixmaps.h"
16 #include "wmapp.h"
17 #include "prefs.h"
18 #include "ymenuitem.h"
19 #include "intl.h"
20
21 static const char AppletClockTimeFmt[] = "%T";
22
strTimeFmt(const struct tm & t)23 const char* YClock::strTimeFmt(const struct tm& t) {
24 if (fTimeFormat && isEmpty(fAltFormat))
25 return fTimeFormat;
26 if (ledPixColon == null || ! prettyClock || strcmp(fmtTime, "%X"))
27 return (fAltFormat && (t.tm_sec & 1) ? fAltFormat : fTimeFormat);
28 return AppletClockTimeFmt;
29 }
30
YClock(YSMListener * sml,IAppletContainer * iapp,YWindow * parent,const char * envTZ,const char * myTZ,const char * format)31 YClock::YClock(YSMListener* sml, IAppletContainer* iapp, YWindow* parent,
32 const char* envTZ, const char* myTZ, const char* format):
33 IApplet(this, parent),
34 clockUTC(false),
35 toolTipUTC(false),
36 clockTicked(true),
37 paintCount(0),
38 transparent(-1),
39 smActionListener(sml),
40 iapp(iapp),
41 fMenu(nullptr),
42 fTimeFormat(format),
43 fAltFormat(fmtTimeAlt),
44 fPid(0),
45 negativePosition(INT_MAX),
46 clockBg(&clrClock),
47 clockFg(&clrClockText),
48 defaultTimezone(envTZ),
49 displayTimezone(myTZ),
50 currentTimezone(envTZ)
51 {
52 memset(positions, 0, sizeof positions);
53 memset(previous, 0, sizeof previous);
54 memset(lastTime, 0, sizeof lastTime);
55
56 if (prettyClock && ledPixSpace != null && ledPixSpace->width() == 1)
57 ledPixSpace = ledPixSpace->scale(5, ledPixSpace->height());
58
59 clockTimer->setFixed();
60 clockTimer->setTimer(1000, this, true);
61
62 autoSize();
63 setTitle("Clock");
64 show();
65 }
66
~YClock()67 YClock::~YClock() {
68 }
69
timezone(bool restore)70 bool YClock::timezone(bool restore) {
71 if (displayTimezone) {
72 const char* want = restore ? defaultTimezone : displayTimezone;
73 const char* have = currentTimezone;
74 if (want != have && (!want || !have || strcmp(want, have))) {
75 if (want) {
76 setenv("TZ", want, True);
77 } else {
78 unsetenv("TZ");
79 }
80 currentTimezone = want;
81 tzset();
82 return true;
83 }
84 }
85 return false;
86 }
87
autoSize()88 void YClock::autoSize() {
89 bool restore = timezone();
90 char str[TimeSize];
91 time_t newTime = seconds();
92 struct tm t = *localtime(&newTime);
93 int maxMonth = -1;
94 int maxWidth = -1;
95
96 t.tm_sec = 58;
97 t.tm_min = 48;
98 t.tm_hour = 23;
99 t.tm_mday = 25;
100 t.tm_wday = 0;
101
102 for (int m = 0; m < 12 ; m++) {
103 t.tm_mon = m;
104 int len = strftime(str, TimeSize, strTimeFmt(t), &t);
105 int w = calcWidth(str, len);
106 if (w > maxWidth) {
107 maxMonth = m;
108 maxWidth = w;
109 }
110 }
111 t.tm_mon = maxMonth;
112
113 for (int dw = 0; dw <= 6; dw++) {
114 t.tm_wday = dw;
115 int len = strftime(str, TimeSize, strTimeFmt(t), &t);
116 int w = calcWidth(str, len);
117 if (w > maxWidth) {
118 maxWidth = w;
119 }
120 }
121
122 if (!prettyClock)
123 maxWidth += 4;
124
125 setSize(maxWidth, unsigned(taskBarGraphHeight));
126 timezone(restore);
127 }
128
handleButton(const XButtonEvent & button)129 void YClock::handleButton(const XButtonEvent &button) {
130 if (button.type == ButtonPress) {
131 if (button.button == 1 && (button.state & ControlMask)) {
132 clockUTC = true;
133 repaint();
134 }
135 } else if (button.type == ButtonRelease) {
136 if (button.button == 1 && clockUTC) {
137 clockUTC = false;
138 repaint();
139 }
140 }
141 YWindow::handleButton(button);
142 }
143
updateToolTip()144 void YClock::updateToolTip() {
145 bool restore = timezone();
146 char str[DateSize];
147 time_t newTime = seconds();
148 struct tm *t = toolTipUTC ? gmtime(&newTime) : localtime(&newTime);
149 strftime(str, DateSize, fmtDate, t);
150 setToolTip(str);
151 timezone(restore);
152 }
153
handleCrossing(const XCrossingEvent & crossing)154 void YClock::handleCrossing(const XCrossingEvent &crossing) {
155 if (crossing.type == EnterNotify) {
156 toolTipUTC = hasbit(crossing.state, ControlMask);
157 clockTimer->startTimer();
158 }
159 clockTimer->runTimer();
160 YWindow::handleCrossing(crossing);
161 }
162
163 #ifdef DEBUG
164 static int countEvents = 0;
165 extern int xeventcount;
166 #endif
167
168 const YAction actionClockHM;
169 const YAction actionClockHMS;
170 const YAction actionClockDHM;
171 const YAction actionClockDate;
172 const YAction actionClockDefault;
173 const YAction actionClockUTC;
174
handleClick(const XButtonEvent & up,int count)175 void YClock::handleClick(const XButtonEvent &up, int count) {
176 if (up.button == 1) {
177 if (clockCommand && clockCommand[0] &&
178 (taskBarLaunchOnSingleClick ? count == 1 : !(count % 2))) {
179 bool restore = timezone();
180 smActionListener->runCommandOnce(clockClassHint, clockCommand, &fPid);
181 timezone(restore);
182 }
183 #ifdef DEBUG
184 } else if (up.button == 2) {
185 if ((count % 2) == 0)
186 countEvents = !countEvents;
187 #endif
188 }
189 else if (up.button == Button3) {
190 fMenu = new YMenu();
191 fMenu->setActionListener(this);
192 fMenu->addItem(_("CLOCK"), -2, null, actionNull)->setEnabled(false);
193 fMenu->addSeparator();
194 fMenu->addItem("%H:%M", -2, null, actionClockHM);
195 fMenu->addItem("%H:%M:%S", -2, null, actionClockHMS);
196 fMenu->addItem("%d %H:%M", -2, null, actionClockDHM);
197 if (!prettyClock)
198 fMenu->addItem(_("Date"), -2, null, actionClockDate);
199 fMenu->addItem(_("Default"), -2, null, actionClockDefault);
200 fMenu->addItem(_("_Disable"), -2, null, actionClose);
201 fMenu->addItem(_("_UTC"), -2, null, actionClockUTC)->setChecked(clockUTC);
202 fMenu->popup(nullptr, nullptr, nullptr, up.x_root, up.y_root,
203 YPopupWindow::pfCanFlipVertical |
204 YPopupWindow::pfCanFlipHorizontal |
205 YPopupWindow::pfPopupMenu);
206 }
207 }
208
changeTimeFormat(const char * format)209 void YClock::changeTimeFormat(const char* format) {
210 bool restore = timezone();
211 fTimeFormat = format;
212 fAltFormat = nullptr;
213 autoSize();
214 freePixmap();
215 memset(positions, 0, sizeof positions);
216 memset(previous, 0, sizeof previous);
217 memset(lastTime, 0, sizeof lastTime);
218 negativePosition = INT_MAX;
219 clockTicked = true;
220 repaint();
221 iapp->relayout();
222 timezone(restore);
223 }
224
actionPerformed(YAction action,unsigned int modifiers)225 void YClock::actionPerformed(YAction action, unsigned int modifiers) {
226 if (action == actionClose) {
227 hide();
228 iapp->relayout();
229 }
230 else if (action == actionClockUTC) {
231 clockUTC ^= true;
232 clockTicked = true;
233 bool restore = timezone();
234 repaint();
235 timezone(restore);
236 }
237 else if (action == actionClockHM) {
238 changeTimeFormat(" %H:%M ");
239 }
240 else if (action == actionClockHMS) {
241 changeTimeFormat(" %H:%M:%S ");
242 }
243 else if (action == actionClockDHM) {
244 changeTimeFormat(" %d %H:%M ");
245 }
246 else if (action == actionClockDate) {
247 changeTimeFormat(" %c ");
248 }
249 else if (action == actionClockDefault) {
250 changeTimeFormat(nullptr);
251 }
252 }
253
picture()254 bool YClock::picture() {
255 bool create = (hasPixmap() == false);
256
257 Graphics G(getPixmap(), width(), height(), depth());
258
259 if (create) {
260 memset(positions, 0, sizeof positions);
261 memset(previous, 0, sizeof previous);
262 memset(lastTime, 0, sizeof lastTime);
263 negativePosition = INT_MAX;
264 fill(G);
265 }
266
267 return clockTicked
268 ? clockTicked = false, draw(G), true
269 : create;
270 }
271
draw(Graphics & g)272 bool YClock::draw(Graphics& g) {
273 bool restore = timezone();
274 timeval walltm = walltime();
275 long nextChime = 1000L - walltm.tv_usec / 1000L;
276 time_t newTime = walltm.tv_sec;
277
278 clockTimer->setTimer(nextChime, this, true);
279
280 auto t = clockUTC ? gmtime(&newTime) : localtime(&newTime);
281
282 char str[TimeSize];
283 int len =
284 #ifdef DEBUG
285 countEvents ? snprintf(str, TimeSize, "%d", xeventcount) :
286 #endif
287 strftime(str, TimeSize, strTimeFmt(*t), t);
288
289 bool drawn = true;
290 if (toolTipVisible() || strcmp(str, lastTime)) {
291 memcpy(lastTime, str, TimeSize);
292 drawn = prettyClock
293 ? paintPretty(g, str, len)
294 : paintPlain(g, str, len);
295 }
296
297 timezone(restore);
298 return drawn;
299 }
300
fill(Graphics & g)301 void YClock::fill(Graphics& g)
302 {
303 if (!prettyClock && clockBg) {
304 g.setColor(clockBg);
305 g.fillRect(0, 0, width(), height());
306 return;
307 }
308
309 if (hasTransparency()) {
310 ref<YImage> gradient(getGradient());
311
312 if (gradient != null)
313 g.drawImage(gradient, this->x(), this->y(),
314 width(), height(), 0, 0);
315 else
316 if (taskbackPixmap != null) {
317 g.fillPixmap(taskbackPixmap, 0, 0,
318 width(), height(), this->x(), this->y());
319 }
320 else {
321 g.setColor(taskBarBg);
322 g.fillRect(0, 0, width(), height());
323 }
324 }
325 }
326
fill(Graphics & g,int x,int y,int w,int h)327 void YClock::fill(Graphics& g, int x, int y, int w, int h)
328 {
329 if (!prettyClock && clockBg) {
330 g.setColor(clockBg);
331 g.fillRect(x, y, w, h);
332 }
333 else {
334 ref<YImage> gradient(getGradient());
335 if (gradient != null) {
336 g.drawImage(gradient, this->x() + x, this->y() + y,
337 w, h, x, y);
338 }
339 else if (taskbackPixmap != null) {
340 XRectangle clip = YRect(x, y, w, h);
341 g.setClipRectangles(&clip, 1);
342 g.fillPixmap(taskbackPixmap, x, y,
343 w, h, this->x() + x, this->y() + y);
344 g.resetClip();
345 }
346 else if (clockBg) {
347 g.setColor(clockBg);
348 g.fillRect(x, y, w, h);
349 }
350 else {
351 g.setColor(taskBarBg);
352 g.fillRect(x, y, w, h);
353 }
354 }
355 }
356
paintPretty(Graphics & g,const char * str,int len)357 bool YClock::paintPretty(Graphics& g, const char* str, int len) {
358 bool paint = false;
359 if (prettyClock) {
360 bool const mustFill = hasTransparency();
361 int x = int(width());
362 int y = 0;
363
364 ++paintCount;
365 for (int i = len - 1; x >= 0; i--) {
366 ref<YPixmap> pix(i >= 0 ? getPixmap(str[i]) : ledPixSpace);
367 if (pix != null)
368 x -= pix->width();
369
370 if (paintCount <= 1) {
371 // evade bug
372 }
373 else if (i >= 0) {
374 if (positions[i] == x && previous[i] == str[i])
375 continue;
376 else positions[i] = x, previous[i] = str[i];
377 }
378 else if (i == -1) {
379 if (negativePosition == x)
380 break;
381 else negativePosition = x;
382 }
383
384 if (pix != null) {
385 if (mustFill)
386 fill(g, x, 0, pix->width(), height());
387
388 g.drawPixmap(pix, x, y);
389 }
390 else if (i < 0 && 0 < x) {
391 fill(g, 0, 0, x, height());
392 break;
393 }
394 paint = true;
395 }
396 }
397 return paint;
398 }
399
paintPlain(Graphics & g,const char * str,int len)400 bool YClock::paintPlain(Graphics& g, const char* str, int len) {
401 fill(g);
402 if (!prettyClock && (clockFont || (clockFont = clockFontName) != null)) {
403 int y = clockFont->ascent() + (height() - 1 - clockFont->height()) / 2;
404 g.setColor(clockFg);
405 g.setFont(clockFont);
406 g.drawChars(str, 0, len, 2, y);
407 }
408 return true;
409 }
410
handleTimer(YTimer * t)411 bool YClock::handleTimer(YTimer *t) {
412 if (t == clockTimer) {
413 if (toolTipVisible())
414 updateToolTip();
415 clockTicked = true;
416 repaint();
417 return true;
418 }
419 return false;
420 }
421
getPixmap(char c)422 ref<YPixmap> YClock::getPixmap(char c) {
423 ref<YPixmap> pix;
424 switch (c) {
425 case '0':
426 case '1':
427 case '2':
428 case '3':
429 case '4':
430 case '5':
431 case '6':
432 case '7':
433 case '8':
434 case '9':
435 pix = ledPixNum[c - '0'];
436 break;
437 case ' ':
438 pix = ledPixSpace;
439 break;
440 case ':':
441 pix = ledPixColon;
442 break;
443 case '/':
444 pix = ledPixSlash;
445 break;
446 case '.':
447 pix = ledPixDot;
448 break;
449 case 'p':
450 case 'P':
451 pix = ledPixP;
452 break;
453 case 'a':
454 case 'A':
455 pix = ledPixA;
456 break;
457 case 'm':
458 case 'M':
459 pix = ledPixM;
460 break;
461 }
462 return pix;
463 }
464
calcWidth(const char * str,int count)465 int YClock::calcWidth(const char* str, int count) {
466 int len = 0;
467 if (prettyClock) {
468 for (char c : YRange<const char>(str, count)) {
469 ref<YPixmap> pix = getPixmap(c);
470 if (pix != null)
471 len += pix->width();
472 }
473 }
474 else if (clockFont || (clockFont = clockFontName) != null) {
475 len = clockFont->textWidth(str, count);
476 }
477 return len;
478 }
479
hasTransparency()480 bool YClock::hasTransparency() {
481 if (transparent < 0) {
482 if (prettyClock == false) {
483 transparent = 1;
484 }
485 else if ((ledPixColon != null && ledPixColon->mask())
486 || (ledPixNum[0] != null && ledPixNum[0]->mask())) {
487 transparent = 1;
488 }
489 else {
490 transparent = 0;
491 }
492 }
493 return bool(transparent & 1);
494 }
495
ClockSet(YSMListener * sml,IAppletContainer * iapp,YWindow * parent)496 ClockSet::ClockSet(YSMListener* sml, IAppletContainer* iapp, YWindow* parent) {
497 const char* tz = strstr(fmtTime, "TZ=");
498 if (tz) {
499 if (tz - fmtTime > 1) {
500 specs += newstr(fmtTime, tz - fmtTime);
501 }
502 while (tz) {
503 const char* next = strstr(tz + 3, "TZ=");
504 int len = int(next ? next - tz : strlen(tz));
505 specs += newstr(tz, len);
506 tz = next;
507 }
508 char* envtz = getenv("TZ");
509 for (char* spec : specs) {
510 if (strncmp(spec, "TZ=", 3) == 0) {
511 char* space = strchr(spec, ' ');
512 if (space && strlen(space) > 2) {
513 *space = '\0';
514 clocks += new YClock(sml, iapp, parent,
515 envtz, spec + 3, space + 1);
516 }
517 } else {
518 clocks += new YClock(sml, iapp, parent,
519 nullptr, nullptr, spec);
520 }
521 }
522 }
523 else if (1 < strlen(fmtTime)) {
524 clocks += new YClock(sml, iapp, parent,
525 nullptr, nullptr, fmtTime);
526 }
527 }
528
~ClockSet()529 ClockSet::~ClockSet() {
530 clocks.clear();
531 for (char* spec : specs) {
532 delete[] spec;
533 }
534 }
535
536 // vim: set sw=4 ts=4 et:
537