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