1 /*
2 * Copyright (C) 1998-2000 by Marco G"otze.
3 *
4 * This code is part of the wmpinboard source package, which is
5 * distributed under the terms of the GNU GPL2.
6 */
7
8 #include <stdlib.h>
9 #include <stdio.h>
10
11 #include <X11/X.h>
12 #include <X11/Xlib.h>
13 #include <X11/Xutil.h>
14
15 #include "wmpinboard.h"
16 #include "misc.h"
17 #include "xmisc.h"
18 #include "notes.h"
19
20 #ifdef HAVE_STRING_H
21 #include <string.h>
22 #endif
23 #ifndef HAVE_MEMCMP
24 #include "memcmp.h"
25 #endif
26
27 /* a list of the upper left corners of the separate areas of the alarm panel,
28 numbered from left to right and from top to bottom (double digits followed
29 by switches) */
30 typedef struct { int x, y; } coords_t;
31 const coords_t abar_areas[6] = {
32 /* double digits, 12x9 each */
33 { 3, 4 }, { 19, 4 },
34 { 3, 15 }, { 19, 15 },
35 /* switches, 8x8 each */
36 { 47, 4 }, { 47, 16 }
37 };
38
39 /*
40 * adds a new note and returns its number (or -1 if notes limit exceeded)
41 */
42 int
add_note()43 add_note()
44 {
45 int cols[C_NUM];
46 int rep = 20;
47 int i, j, n;
48
49 if (notes_count >= MAX_NOTES)
50 return -1;
51 n = notes_count++;
52
53 /* find a reasonable color */
54 for (i = 0; i < C_NUM; i++) cols[i] = 1;
55 for (i = 0; i < n; i++) cols[ndata[i].col] = 0;
56 for (i = j = 0; i < C_NUM; i++)
57 if (cols[i]) cols[j++] = i;
58 ndata[n].col = j ? cols[rand() % j] : rand() % C_NUM;
59
60 /* choose a random place to pin it, some pixels off any other note's origin
61 (possible if MAX_NOTES is sufficiently small) */
62 do {
63 ndata[n].x = 6 + rand() % 36;
64 ndata[n].y = 2 + rand() % 44;
65 j = 1;
66 for (i = 0; i < n; i++)
67 if (ndata[i].x - ndata[n].x < 5 && ndata[i].x - ndata[n].x > -5 &&
68 ndata[i].y - ndata[n].y < 5 && ndata[i].y - ndata[n].y > -5)
69 {
70 j = 0;
71 break;
72 }
73 } while (!j && --rep);
74
75 memset(ndata[n].text, 32, 59);
76 ndata[n].text[59] = '\0';
77 ndata[n].cursor = 0;
78 memset(ndata[n].sketch, 0, 512);
79 memset(ndata[n].creases, 0, 32);
80 ndata[n].a_time = -1;
81 ndata[n].a_flags = ALARM_DATE;
82
83 state.counter++;
84
85 return n;
86 }
87
88 /*
89 * removes a note
90 */
91 void
remove_note(int number)92 remove_note(int number)
93 {
94 int i;
95
96 if (number >= notes_count) return;
97 /* in case memcpy() can't do overlapping copies... */
98 for (i = number; i < notes_count-1; i++)
99 memcpy(&ndata[i], &ndata[i+1], sizeof(data_t));
100 notes_count--;
101 }
102
103 /*
104 * moves a note on top of all others and returns the quote's new ID
105 */
106 int
raise_note(int number)107 raise_note(int number)
108 {
109 data_t tmp;
110 int i;
111
112 if (number >= notes_count-1 || number < 0) return number;
113 memcpy(&tmp, &ndata[number], sizeof(data_t));
114 /* in case memcpy() can't do overlapping copies... */
115 for (i = number; i < notes_count-1; i++)
116 memcpy(&ndata[i], &ndata[i+1], sizeof(data_t));
117 memcpy(&ndata[notes_count-1], &tmp, sizeof(data_t));
118 return notes_count-1;
119 }
120
121 /*
122 * returns a value corresponding to how tilted a note is to be displayed
123 */
124 inline int
note_tilt(int note)125 note_tilt(int note)
126 {
127 return ndata[note].x < 14 ? 0 : (ndata[note].x < 36 ? 1 : 2);
128 }
129
130 /*
131 * returns true if the note in question is to be considered empty
132 */
133 int
note_empty(int note)134 note_empty(int note)
135 {
136 char *ptr = ndata[note].sketch;
137 int i;
138
139 if (!string_empty(ndata[note].text, 0)) return 0;
140 for (i = 0; i < 511; i++, ptr++) /* last byte ignored */
141 if (*ptr) return 0;
142 return 1;
143 }
144
145 /*
146 * creates templates for the icons suiting the specified note
147 */
148 void
color_notes(int note)149 color_notes(int note)
150 {
151 XGetSubImage(display, app, 64, 0, 16, 48, ~0, ZPixmap, img, 64, 0);
152 if (ndata[note].sketch[511] & 1)
153 replace_color(img, 64, 0, 16, 48, C_EXTRA, palette[ndata[note].col].fg);
154 else
155 replace_color(img, 64, 0, 16, 48, C_EXTRA, palette[ndata[note].col].bg);
156 replace_color(img, 64, 0, 16, 48, C_INNER, palette[ndata[note].col].bg);
157 #ifdef CREASES
158 render_wear(note);
159 #endif
160 }
161
162 /*
163 * draws a note's miniature representation on the pinboard
164 */
165 void
pin_note(int note)166 pin_note(int note)
167 {
168 merge_masked(img, 64, 16*note_tilt(note), ndata[note].x,
169 ndata[note].y, 16, 16, C_OUTER);
170 }
171
172 /*
173 * renders the pinboard with all of its notes excluding <exclude> if >= 0
174 */
175 void
render_pinboard(int exclude)176 render_pinboard(int exclude)
177 {
178 int i;
179
180 XPutImage(display, app, normalGC, img, 0, 0, 0, 0, 64, 64);
181 for (i = 0; i < notes_count; i++)
182 if (i != exclude) {
183 color_notes(i);
184 pin_note(i);
185 }
186 }
187
188 /*
189 * if (<x>, <y>) is within a quote's area, returns the quote's ID, otherwise -1
190 */
191 int
selected_note(int x,int y)192 selected_note(int x, int y)
193 {
194 int i;
195
196 for (i = notes_count-1; i >= 0; i--)
197 if (x >= ndata[i].x && x < ndata[i].x+16 &&
198 y >= ndata[i].y && y < ndata[i].y+16 &&
199 XGetPixel(img, 128+x-ndata[i].x, note_tilt(i)*16+y-ndata[i].y) !=
200 C_OUTER)
201 {
202 return i;
203 }
204 return -1;
205 }
206
207 /*
208 * prints a letter identified by a note and a position in its text string, and
209 * overlays it by a possible sketch if sketch has a true value
210 */
211 void
print_letter(int note,int letter,int sketch)212 print_letter(int note, int letter, int sketch)
213 {
214 int a = 2+6*(letter%10), b = 2+10*(letter/10);
215
216
217 if (sketch) {
218 XFillRectangle(display, app, fillGC, a, b, 6, 10);
219 #ifdef CREASES
220 render_edit_wear_area(note, a, b, 6, 10);
221 #endif
222 draw_sketch_area(note, a, b, 6, 10);
223 }
224 XDrawString(display, app, fontGC, a, b + font->ascent,
225 &ndata[note].text[letter], 1);
226 }
227
228 /*
229 * prints a note's entire text *without* drawing a possibly existing sketch
230 */
231 void
print_text(int note)232 print_text(int note)
233 {
234 int i;
235
236 for (i = 0; i < 59; i++) print_letter(note, i, 0);
237 }
238
239 /*
240 * shifts part of a note's text to the right, inserting <ch> (step >= 1)
241 */
242 void
shift_string(int note,int at,int margin,int step,int ch)243 shift_string(int note, int at, int margin, int step, int ch)
244 {
245 int i;
246
247 if (step < 1) return;
248
249 for (i = margin; i >= at+step; i--) {
250 ndata[note].text[i] = ndata[note].text[i-step];
251 print_letter(note, i, 1);
252 }
253 for (i = at; i < at+step; i++) {
254 ndata[note].text[i] = ch;
255 print_letter(note, i, 1);
256 }
257 }
258
259 /*
260 * draws a rectangular section of a sketch, according to the data saved along
261 * with a note
262 */
263 void
draw_sketch_area(int note,int x,int y,int w,int h)264 draw_sketch_area(int note, int x, int y, int w, int h)
265 {
266 int i, j;
267
268 for (i = x; i < x+w; i++)
269 for (j = y; j < y+h; j++)
270 if (ndata[note].sketch[8*j + i/8] & (1<<(i%8)))
271 XDrawPoint(display, app, fontGC, i, j);
272 }
273
274 /*
275 * returns the number of the button bar's button corresponding to the given
276 :* coordinates, or -1
277 */
278 int
bbar_button(int x,int y)279 bbar_button(int x, int y)
280 {
281 int i;
282
283 for (i = 0; i < 8; i++)
284 if (x >= 4 + (i%4)*14 && x < 18 + (i%4)*14 && y >= 32 + (i/4)*14 &&
285 y < 46 + (i/4)*14)
286 {
287 return i;
288 }
289 return -1;
290 }
291
292 /*
293 * returns the number of that area on the alarm panel that corresponds to the
294 * given coordinates, or -1
295 */
296 int
abar_area(int x,int y)297 abar_area(int x, int y)
298 {
299 int i;
300
301 /* relativate positions */
302 x -= 3;
303 y -= 4;
304 for (i = 0; i < 6; i++) {
305 coords_t c = abar_areas[i];
306 if (x >= c.x && x < (c.x + (i < 4 ? 12 : 8)) &&
307 y >= c.y && y < (c.y + (i < 4 ? 9 : 8)))
308 {
309 return i;
310 }
311 }
312 return -1;
313 }
314
315 /*
316 * displays note <note> in edit mode view
317 */
318 void
render_note(int note)319 render_note(int note)
320 {
321 int i;
322
323 XSetForeground(display, fillGC, palette[ndata[note].col].bg);
324 XSetForeground(display, fontGC, BlackPixel(display, DefaultScreen(display)));
325 XSetBackground(display, fontGC, palette[ndata[note].col].bg);
326 set_mask(0);
327 XFillRectangle(display, app, fillGC, 0, 0, 64, 64);
328 XDrawRectangle(display, app, fontGC, 0, 0, 63, 63);
329 XSetForeground(display, fontGC, palette[ndata[note].col].fg);
330 for (i = 55; i < 62; i += 2) /* draw triangle in the bottom right corner */
331 XDrawLine(display, app, fontGC, i, 62, 62, i);
332 }
333
334 /*
335 * prepares edit mode for note <note> (does NOT update the display)
336 */
337 void
init_edit_mode(int note)338 init_edit_mode(int note)
339 {
340 #ifdef CREASES
341 XGCValues gcv;
342
343 gcv.foreground = palette[ndata[note].col].cr;
344 XChangeGC(display, creaseGC, GCForeground, &gcv);
345 #endif
346 render_note(note);
347 #ifdef CREASES
348 render_edit_wear(note);
349 #endif
350 print_text(note);
351 draw_sketch(note);
352 }
353
354 /*
355 * updates a double digit area on the alarm panel
356 */
357 inline void
render_abar_number(int field)358 render_abar_number(int field)
359 {
360 int a, b;
361
362 /* split two-digit number into separate digits */
363 b = state.a_edit[field] % 10;
364 a = (state.a_edit[field] / 10) % 10;
365 /* copy appropriate digits */
366 XCopyArea(display, digits, abar, normalGC, 6*a, 0, 6, 9,
367 abar_areas[field].x, abar_areas[field].y);
368 XCopyArea(display, digits, abar, normalGC, 6*b, 0, 6, 9,
369 abar_areas[field].x+7, abar_areas[field].y);
370 }
371
372 /*
373 * updates the alarm panel's switches for a given note
374 */
375 inline void
render_abar_switches(int note)376 render_abar_switches(int note)
377 {
378 XPutImage(display, abar, normalGC, img,
379 (ndata[note].a_flags & ALARM_DATE) ? 78 : 70, 55, 47, 4, 8, 8);
380 XPutImage(display, abar, normalGC, img,
381 (ndata[note].a_flags & ALARM_DATE) ? 70 : 78, 55, 47, 16, 8, 8);
382 }
383
384 /*
385 * renders the alarm panel for a given note
386 */
387 void
render_abar(int note)388 render_abar(int note)
389 {
390 int i;
391
392 for (i = 0; i < 4; i++) render_abar_number(i);
393 render_abar_switches(note);
394 }
395
396 /*
397 * converts <note>'s contents to what's referred to as "cooked" format (as
398 * opposed to raw data); returns a malloc()ed string that has to be freed
399 * later on!
400 */
401 char*
cook(int note,int pos,int len)402 cook(int note, int pos, int len)
403 {
404 char *s, *t;
405 int i;
406
407 s = smalloc(70);
408 for (t = s, i = pos; i < (pos+len > 59 ? 59 : pos+len); i++) {
409 if (!string_empty(&ndata[note].text[i], (i >= 50 ? 59 : 10*(i/10+1))-i))
410 {
411 if (t > s && !(i%10) && t[-1] != ' ' && t[-1] != '-')
412 *t++ = ' '; /* replace virtual newlines by spaces */
413 if ((t > s && (ndata[note].text[i] != ' ' || t[-1] != ' ') &&
414 !(t[-1] == '-'
415 && string_empty(&ndata[note].text[10*(i/10)], i%10+1))) ||
416 (t == s && ndata[note].text[i] != ' '))
417 {
418 *t++ = ndata[note].text[i];
419 }
420 }
421 }
422 *t = '\0';
423 return s;
424 }
425
426 /*
427 * dumps all notes' contents on STDOUT (raw dump unless <cooked> is true)
428 */
429 void
dump_notes(int cooked)430 dump_notes(int cooked)
431 {
432 char *s;
433 int c = 0, i, alarms;
434
435 /* determine if any notes have alarms configured */
436 time_next_alarm();
437 alarms = state.alarm.note >= 0;
438
439 /* print existing notes */
440 for (; c < 4; c++)
441 for (i = 0; i < notes_count; i++)
442 if (c_group[ndata[i].col] == c) {
443 s = cooked ? cook(i, 0, 59) : ndata[i].text;
444 if (alarms) { /* any alarms set at all? */
445 if (ndata[i].a_flags & ALARM_ON) {
446 explode_time(i);
447 if (ndata[i].a_flags & ALARM_DATE) { /* date-specific alarm */
448 printf("%02d-%02d-%02d %02d:%02d #%02d: %s\n",
449 state.a_edit[4]%100, state.a_edit[2], state.a_edit[3],
450 state.a_edit[0], state.a_edit[1], i, s);
451 } else { /* daily alarm */
452 printf("daily at %02d:%02d #%02d: %s\n", state.a_edit[0],
453 state.a_edit[1], i, s);
454 }
455 } else /* no alarm set for this note */
456 printf("%14s #%02d: %s\n", "", i, s);
457 } else /* no alarms set at all */
458 printf("#%02d: %s\n", i, s);
459 if (cooked) free(s);
460 }
461 }
462
463 /*
464 * returns the (serial) number of the character (<x>, <y>) belongs to on a
465 * note; prohibiting char #59 if <strict>
466 */
467 int
char_at(int x,int y,int strict)468 char_at(int x, int y, int strict)
469 {
470 int i;
471
472 if (x < 2) x = 2;
473 if (x > 62) x = 62; /* intentional! */
474 if (y > 61) y = 61;
475 if (y < 2) y = 2;
476 i = (x-2)/6 + ((y-2)/10)*10;
477 if (i > 59) i = 59;
478 return i;
479 }
480
481 #ifdef CREASES
482 /*
483 * applies random wear & tear to a note (the default probability is multiplied
484 * by <factor>)
485 */
486 void
wear_note(int note)487 wear_note(int note)
488 {
489 /* prefabricated crease patterns */
490 static const int pats = 8;
491 static const unsigned char pat[8][5][5] = {
492 { { 1,0,0,0,0 },
493 { 0,1,0,0,0 },
494 { 0,0,1,0,0 },
495 { 0,0,0,1,0 },
496 { 0,0,0,0,1 } },
497 { { 0,0,0,0,1 },
498 { 0,0,0,1,0 },
499 { 0,0,1,0,0 },
500 { 0,1,0,0,0 },
501 { 1,0,0,0,0 } },
502 { { 0,0,0,1,0 },
503 { 0,0,0,1,0 },
504 { 0,0,1,0,0 },
505 { 1,1,0,0,0 },
506 { 0,0,0,0,0 } },
507 { { 0,1,0,0,0 },
508 { 0,1,0,0,0 },
509 { 0,0,1,0,0 },
510 { 0,0,0,1,1 },
511 { 0,0,0,0,0 } },
512 { { 0,0,0,0,0 },
513 { 0,0,0,1,1 },
514 { 0,0,1,0,0 },
515 { 0,1,0,0,0 },
516 { 0,1,0,0,0 } },
517 { { 0,0,0,0,0 },
518 { 1,1,0,0,0 },
519 { 0,0,1,0,0 },
520 { 0,0,0,1,0 },
521 { 0,0,0,1,0 } },
522 { { 0,0,0,0,0 },
523 { 0,1,0,0,0 },
524 { 0,0,1,0,0 },
525 { 0,0,0,1,0 },
526 { 0,0,0,0,0 } },
527 { { 0,0,0,0,0 },
528 { 0,0,0,1,0 },
529 { 0,0,1,0,0 },
530 { 0,1,0,0,0 },
531 { 0,0,0,0,0 } }
532 };
533 int i, j, k = 0;
534
535 /* determine number of crease bits */
536 for (i = 0; i < 32; i++)
537 for (j = 0; j < 8; j++)
538 k += ndata[note].creases[i]>>(j) & 1;
539 /* don't cover more than 35% of a note's area with creases */
540 if (100*k/256 < 35 && !(rand() % (4+k/64))) {
541 int x, y;
542 int t = rand() % pats; /* choose a prefab crease pattern to apply... */
543 int a = rand() % 11; /* ...as well as its location */
544 int b = rand() % 11;
545
546 for (y = 0; y < 5; y++)
547 for (x = 0; x < 5; x++)
548 if (pat[t][y][x])
549 ndata[note].creases[2*(b+y) + (a+x<8)] |= 1<<(7-(a+x)%8);
550 }
551 }
552
553 /*
554 * makes an otherwise pre-rendered note look worn out
555 */
556 void
render_wear(int note)557 render_wear(int note)
558 {
559 int x, y, z;
560
561 for (z = 0; z <= 48; z += 16)
562 for (y = 0; y < 16; y++)
563 for (x = 0; x < 16; x++)
564 if (ndata[note].creases[2*y+x/8]>>(7-x%8) & 1 &&
565 XGetPixel(img, 64+x, z+y) == palette[ndata[note].col].bg)
566 {
567 XPutPixel(img, 64+x, z+y, palette[ndata[note].col].cr);
568 }
569 }
570
571 /*
572 * renders wear & tear in edit mode in a specific window
573 */
574 void
render_edit_wear_area_win(Window win,int note,int a,int b,int w,int h)575 render_edit_wear_area_win(Window win, int note, int a, int b, int w, int h)
576 {
577 #define m(x, y) (m[2*(y) + (x)/8]>>(7-(x)%8) & 1)
578 #define l(x, y) (l[8*(y) + (x)/8]>>(7-(x)%8) & 1)
579 static unsigned char l[(64/8)*64]; /* high res crease map */
580 static unsigned char m[32]; /* low res crease map */
581 int x, y, z;
582
583 /* lazily render a high-res crease pattern */
584 if (memcmp(m, ndata[note].creases, sizeof(m))) {
585 memcpy(m, ndata[note].creases, sizeof(m));
586 memset(l, 0, sizeof(l));
587 /* now we have to somehow extrapolate a 64x64 crease pattern from a 16x16
588 sketch... */
589 for (z = 0; z < 3; z++) /* three passes */
590 for (y = 2; y < 62; y++)
591 for (x = 2; x < 62; x++) {
592 int sx = x/4, sy = y/4;
593 int xx = x%4, yy = y%4;
594 int xi = (xx<<1)%3, yi = (yy<<1)%3;
595 int xd = xx<2 ? -1 : 1, yd = yy<2 ? -1 : 1;
596
597 if (z < 2 && ( /* during passes #0 and #1 */
598 (!z && xi && yi && m(sx, sy) &&
599 (m(sx+xd, sy+yd) || m(sx, sy+yd) || m(sx+xd, sy) ) &&
600 ((xd<0 && sx<2) || (xd>0 && sx>14) || (yd<0 && sy<2) ||
601 (yd>0 && sy>14) || m(sx+2*xd, sy+2*yd))
602 ) || (z && (
603 (!xi && !yi && l(x-xd, y-yd) && l(x+2*xd, y+2*yd)) ||
604 (!xi && yi && l(x-xd, y) && l(x+2*xd, y)) ||
605 (xi && !yi && l(x, y-yd) && l(x, y+2*yd))
606 ))))
607 {
608 l[8*y + x/8] |= 1<<(7-x%8);
609 }
610 /* during pass #2, remove isolated dots */
611 if (z == 2 && l(x, y) && !(l(x-1, y-1) || l(x, y-1) ||
612 l(x+1, y-1) || l(x-1, y) || l(x+1, y) || l(x-1, y+1) ||
613 l(x, y+1) || l(x+1, y+1)))
614 {
615 l[8*y + x/8] &= 0xff - (1<<(7-x%8));
616 }
617 }
618 }
619 for (y = b; y < b+h; y++)
620 for (x = a; x < a+w; x++)
621 if (x > 1 && y > 1 && x < 62 && y < 62 && x+y <= 116 && l(x, y))
622 XDrawPoint(display, win, creaseGC, x, y);
623 #undef m
624 #undef l
625 }
626 #endif
627
628 #ifdef FUNSTUFF
629
630 /*
631 * atek sparppoirta ecaitnoo npsceai lcoacisno,sr terusnt ur efit ah tahppnede
632 */
633 void
check_occasion(int d,int m,int y)634 check_occasion(int d, int m, int y)
635 {
636 static const unsigned char sketch0[256] = {
637 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
638 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
639 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
640 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
641 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
642 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
643 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
644 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
645 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
646 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
647 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00,
648 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x00,
649 0x00, 0x00, 0x00, 0x00, 0x00, 0x87, 0x3d, 0x00,
650 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe7, 0x00,
651 0x00, 0x00, 0x00, 0x00, 0x00, 0xaf, 0x4d, 0x01,
652 0x00, 0x00, 0x00, 0x00, 0x00, 0xb3, 0x5d, 0x01,
653 0x00, 0x00, 0x00, 0x00, 0x00, 0xbe, 0x0d, 0x01,
654 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x1f, 0x01,
655 0x00, 0x00, 0x00, 0x00, 0x18, 0x78, 0x9f, 0x01,
656 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0xfe, 0x00,
657 0x00, 0x00, 0x00, 0x00, 0x10, 0x60, 0x00, 0x00,
658 0x00, 0x00, 0x00, 0x00, 0x30, 0xf0, 0x00, 0x00,
659 0x00, 0x00, 0x00, 0x00, 0x60, 0xf0, 0x00, 0x00,
660 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00,
661 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00,
662 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x01,
663 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x3e, 0x00,
664 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x03, 0x00,
665 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00,
666 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
667 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
668 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
669 };
670 static const unsigned char sketch1[512] = {
671 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
672 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
673 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
674 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
675 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
676 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
677 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
678 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
679 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
680 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
681 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
682 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
683 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
684 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
685 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
686 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
687 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
688 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
689 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
690 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
691 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
692 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
693 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
694 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
695 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
696 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
697 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
698 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00,
699 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x01, 0x00,
700 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x03, 0x00,
701 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x03, 0x00,
702 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x03, 0x00,
703 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00,
704 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00,
705 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x04, 0x00,
706 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x04, 0x00,
707 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x0a, 0x00,
708 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0a, 0x00,
709 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0a, 0x00,
710 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x18, 0x00,
711 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x1b, 0x00,
712 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x30, 0x00,
713 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x66, 0x00,
714 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x08, 0x00,
715 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x48, 0x00,
716 0x00, 0x00, 0x00, 0x00, 0x00, 0x6f, 0x61, 0x00,
717 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x97, 0x00,
718 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x90, 0x00,
719 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00,
720 0x00, 0x00, 0x00, 0x00, 0xc0, 0x0d, 0xc0, 0x00,
721 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x80, 0x03,
722 0x00, 0x00, 0x00, 0x00, 0x60, 0x40, 0x80, 0x02,
723 0x00, 0x00, 0x00, 0x00, 0x20, 0x80, 0x03, 0x02,
724 0x00, 0x00, 0x00, 0x00, 0x20, 0x08, 0x56, 0x06,
725 0x00, 0x00, 0x00, 0x00, 0xe0, 0xf8, 0x1f, 0x06,
726 0x00, 0x00, 0x00, 0x00, 0xf8, 0x6f, 0xfd, 0x07,
727 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x03,
728 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00,
729 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x07, 0x00,
730 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
731 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
732 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
733 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
734 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01
735 };
736 char *s;
737 int note;
738
739 if (!notes_count && !(state.state_bits & 1)) { /* ewclmo een wsures */
740 if ((note = add_note()) >= 0) {
741 ndata[note].col = 15;
742 memcpy(&ndata[note].sketch[256], sketch0, 256);
743 s = csarbmel(
744 "EWCLMO EotmwipbnaodrhTnaskf rortiygni tuo!t ");
745 strcpy(ndata[note].text, s);
746 free(s);
747 ndata[note].cursor = 50;
748 state.state_bits |= 1;
749 }
750 } else if (notes_count)
751 state.state_bits |= 1; /* purgdadeu essrw not'g tet ihs */
752
753 if (m == 12 && d >= 25 && d <= 26) { /* mXsa */
754 if (!(state.state_bits & 2) && (note = add_note()) >= 0) {
755 ndata[note].col = 14;
756 memcpy(ndata[note].sketch, sketch1, 512);
757 s = csarbmel(
758 " A M REYR MXSA T O Y UO ! ");
759 strcpy(ndata[note].text, s);
760 free(s);
761 state.state_bits |= 2;
762 }
763 } else state.state_bits &= ~2;
764
765 if (m == 1 && d <= 3) { /* eN weYra */
766 if (!(state.state_bits & 4) && (note = add_note()) >= 0) {
767 ndata[note].col = 11;
768 ndata[note].sketch[511] = 0x01;
769 s = csarbmel(
770 "nOeca agni aeyrah saapssde.. .A ynaw,y A H PAYP EN WEYRA!");
771 strcpy(ndata[note].text, s);
772 free(s);
773 state.state_bits |= 4;
774 }
775 } else state.state_bits &= ~4;
776 }
777 #endif
778
779 /*
780 * pastes <text> into note #<note>, starting at <offset>; returns the serial
781 * number of the character right after the pasted string's end; overwrites
782 * previous contents; tries to word-wrap unless <raw> has a true value
783 */
784 int
paste_string(int note,int offset,const char * s,int raw)785 paste_string(int note, int offset, const char *s, int raw)
786 {
787 const char *t;
788 int i, j;
789
790 if (!raw) { /* paste "cooked" */
791 for (i = offset; *s && i < 59; ) {
792 for (; *s && (*s == ' ' || *s == '\t' || *s == '\n'); s++);
793 for (t = s; *t && (t == s || *t == '-' || *(t-1) != '-') &&
794 *t != ' ' && *t != '\t' && *t != '\n'; t++);
795 j = t-s;
796 if (i < 50 && i%10 && i/10 != (i+j-1)/10) i = (i/10+1)*10; /* next l.? */
797 if (j && i <= 58) {
798 if (i+j >= 58) { /* word too long for note? */
799 strncpy(&ndata[note].text[i], s, 59-i);
800 i = 59;
801 } else {
802 strncpy(&ndata[note].text[i], s, j);
803 i += j;
804 }
805 if (*(t-1) != '-' && i%10) i++; /* insert blank? */
806 s = t;
807 }
808 }
809 if (ndata[note].text[i] == ' ') /* place cursor right after the text */
810 for (; i > offset && ndata[note].text[i-1] == ' '; i--);
811 return i;
812 } else { /* paste raw */
813 i = strlen(s);
814 if (offset+i > 59) i = 59-offset;
815 strncpy(&ndata[note].text[offset], s, i);
816 return offset+i;
817 }
818 }
819
820 /*
821 * pastes <data> into note #<note> at offset <pos>, inserting if <ins>,
822 * word-wrapping unless <raw>
823 */
824 void
paste(int note,int pos,const char * data,int ins,int raw)825 paste(int note, int pos, const char *data, int ins, int raw)
826 {
827 char *s = 0;
828 int i;
829
830 if (ins) {
831 if (raw) {
832 s = smalloc(60);
833 strcpy(s, ndata[note].text);
834 } else {
835 /* want to wrap subsequent text later, so we have to save it cooked */
836 s = cook(note, 0, 59);
837 }
838 memset(&ndata[note].text[pos], ' ', 59-pos);
839 } else
840 memset(&ndata[note].text[pos], ' ', pos+strlen(data) > 59 ? 59-pos :
841 strlen(data));
842 i = paste_string(note, pos, data, raw);
843 if (ins) {
844 if (i <= 58) paste_string(note, i, &s[pos], raw);
845 free(s);
846 }
847 ndata[note].cursor = i > 58 ? 58 : i;
848 }
849
850 /*
851 * checks whether state.a_edit is valid (mday range w/respect to month...),
852 * sets the hidden year field to a sensible value; the return value is boolean
853 */
854 int
check_time()855 check_time()
856 {
857 struct tm data, *ptr;
858 time_t tt;
859
860 tt = time(0);
861 ptr = localtime(&tt);
862 memcpy(&data, ptr, sizeof(struct tm));
863 data.tm_isdst = -1; /* let mktime() decide */
864 data.tm_year = state.a_edit[2]-1 > ptr->tm_mon ||
865 (state.a_edit[2]-1 == ptr->tm_mon && (state.a_edit[3] > ptr->tm_mday ||
866 (state.a_edit[3] == ptr->tm_mday && (state.a_edit[0] > ptr->tm_hour ||
867 (state.a_edit[0] == ptr->tm_hour && (state.a_edit[1] > ptr->tm_min ||
868 (state.a_edit[1] == ptr->tm_min && !ptr->tm_sec))))))) ? ptr->tm_year :
869 ptr->tm_year+1;
870 data.tm_mon = state.a_edit[2]-1;
871 data.tm_mday = state.a_edit[3];
872 data.tm_hour = state.a_edit[0];
873 data.tm_min = state.a_edit[1];
874 /* check if date is convertible, i.e., valid */
875 tt = mktime(&data);
876 if (tt == -1) return 0;
877 /* check if date has been adapted -> return failure */
878 if (data.tm_mon != state.a_edit[2]-1 || data.tm_mday != state.a_edit[3])
879 return 0;
880 /* adapt date and return */
881 state.a_edit[4] = (unsigned char) data.tm_year; /* char suffices */
882 return 1;
883 }
884
885 /*
886 * transforms ndata[note].a_time to state.a_edit
887 */
888 void
explode_time(int note)889 explode_time(int note)
890 {
891 struct tm *ptr;
892
893 if (ndata[note].a_time != -1) {
894 ptr = localtime(&ndata[note].a_time);
895 state.a_edit[0] = ptr->tm_hour;
896 state.a_edit[1] = ptr->tm_min;
897 state.a_edit[4] = ptr->tm_year;
898 } else { /* initialize on first call */
899 time_t tt;
900 time(&tt);
901 tt += 60*60;
902 ptr = localtime(&tt);
903 state.a_edit[0] = ptr->tm_hour;
904 state.a_edit[1] = 0; /* minute */
905 state.a_edit[4] = (unsigned char) ptr->tm_year;
906 }
907 state.a_edit[2] = ptr->tm_mon+1;
908 state.a_edit[3] = ptr->tm_mday;
909 }
910
911 /*
912 * transforms state.a_edit to ndata[note].a_time
913 */
914 void
implode_time(int note)915 implode_time(int note)
916 {
917 struct tm data, *ptr;
918 time_t tt;
919
920 time(&tt);
921 ptr = localtime(&tt);
922 memcpy(&data, ptr, sizeof(struct tm));
923 data.tm_isdst = -1; /* let mktime determine */
924 data.tm_hour = state.a_edit[0];
925 data.tm_min = state.a_edit[1];
926 data.tm_sec = 0;
927 /* the following 3 ain't required for daily notes, but this way, the date
928 will be saved even in this case, so we set them */
929 data.tm_year = state.a_edit[4];
930 data.tm_mon = state.a_edit[2]-1;
931 data.tm_mday = state.a_edit[3];
932 ndata[note].a_time = mktime(&data); /* shouldn't fail */
933 }
934
935 /*
936 * evaluating its a_flags, returns an updated value of a note's a_time field,
937 * considering <now> the current time if != -1
938 */
939 time_t
adapt_time(int note,time_t now)940 adapt_time(int note, time_t now)
941 {
942 time_t res = ndata[note].a_time;
943
944 /* adaption is only necessary in the case of a daily alarm */
945 if (!(ndata[note].a_flags & ALARM_DATE)) {
946 struct tm atm, *ptr;
947
948 if (now == -1) time(&now);
949 ptr = localtime(&res); /* break down alarm time */
950 memcpy(&atm, ptr, sizeof(struct tm));
951 ptr = localtime(&now); /* break down current time */
952 atm.tm_year = ptr->tm_year;
953 atm.tm_mon = ptr->tm_mon;
954 atm.tm_mday = ptr->tm_mday;
955 atm.tm_sec = 0; /* just making sure */
956 res = mktime(&atm);
957 if (res < now) res += 24*60*60; /* NOT `<='! */
958 }
959
960 return res;
961 }
962
963 /*
964 * sets the state variable's alarm sub structure's values for the next alarm
965 */
966 /*
967 Together with appropriate calls from wmpinboard.c, this function results
968 in the following strategy for scheduling alarms:
969
970 - firstly, alarms with ALARM_DATE set are executed only once and get
971 deactivated afterwards; daily alarms remain active even after an alarm
972 - if the program is started after some alarm time was reach, the alarm
973 is displayed if (and only if) it's a date-specific alarm
974 - if an alarm is running while another one becomes due, this subsequent
975 alarm will be run after the user has closed the other note
976 - to achieve the latter, time_next_alarm() considers all notes with an
977 alarm time equal to or greater than the previous one; if they're equal,
978 the new alarm's note's ID must be greater than that of the previous
979 alarm (this realizes a determined order for concurrent alarms and
980 allows the program to avoid running one daily alarm several times in
981 a row)
982 */
983 void
time_next_alarm()984 time_next_alarm()
985 {
986 time_t prev_time = state.alarm.time;
987 int i, prev_note = state.alarm.note;
988
989 if (state.alarm.note >= 0 && !state.alarm.run) prev_note = -1;
990 if (prev_time == -1 || prev_time > time(0)) prev_time = time(0);
991 state.alarm.time = -1;
992 state.alarm.note = -1;
993 state.alarm.run = 0;
994 for (i = 0; i < notes_count; i++)
995 if (ndata[i].a_flags & ALARM_ON) {
996 time_t tt = adapt_time(i, prev_time);
997 if (((ndata[i].a_flags & ALARM_DATE) || (tt == prev_time &&
998 i > prev_note) || (tt > prev_time)) &&
999 (state.alarm.time == -1 || tt < state.alarm.time))
1000 {
1001 state.alarm.time = tt;
1002 state.alarm.note = i;
1003 }
1004 }
1005 if (state.alarm.note < 0 && prev_note >= 0 &&
1006 (ndata[prev_note].a_flags & ALARM_ON))
1007 { /* if there is just one daily alarm, it hasn't been set again above, so
1008 let's do it here explicitly */
1009 state.alarm.note = prev_note;
1010 state.alarm.time = adapt_time(prev_note, prev_time+1);
1011 }
1012 }
1013
1014