1 /********************************************************************************
2 *                                                                               *
3 *                   B a s e   C a l e n d a r   W i d g e t                     *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 2006,2020 by Sander Jansen.   All Rights Reserved.              *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxascii.h"
26 #include "fxkeys.h"
27 #include "FXArray.h"
28 #include "FXHash.h"
29 #include "FXMutex.h"
30 #include "FXStream.h"
31 #include "FXString.h"
32 #include "FXColors.h"
33 #include "FXSize.h"
34 #include "FXPoint.h"
35 #include "FXObjectList.h"
36 #include "FXRectangle.h"
37 #include "FXStringDictionary.h"
38 #include "FXSettings.h"
39 #include "FXRegistry.h"
40 #include "FXEvent.h"
41 #include "FXWindow.h"
42 #include "FXDCWindow.h"
43 #include "FXApp.h"
44 #include "FXFont.h"
45 #include "FXDate.h"
46 #include "FXCalendarView.h"
47 
48 /*
49   Notes:
50 
51   - ISO8601 weeknumbers are implemented for weeks starting at monday and ending at sunday
52 */
53 
54 #define SELECT_MASK   (CALENDAR_SINGLESELECT|CALENDAR_BROWSESELECT)
55 #define CALENDAR_MASK (CALENDAR_SINGLESELECT|CALENDAR_BROWSESELECT|CALENDAR_WEEKNUMBERS|CALENDAR_STATIC|CALENDAR_HIDEOTHER)
56 
57 using namespace FX;
58 
59 /*******************************************************************************/
60 
61 namespace FX {
62 
63 
64 // Map
65 FXDEFMAP(FXCalendarView) FXCalendarViewMap[]={
66   FXMAPFUNC(SEL_PAINT,0,FXCalendarView::onPaint),
67   FXMAPFUNC(SEL_LEFTBUTTONPRESS,0,FXCalendarView::onLeftBtnPress),
68   FXMAPFUNC(SEL_LEFTBUTTONRELEASE,0,FXCalendarView::onLeftBtnRelease),
69   FXMAPFUNC(SEL_KEYPRESS,0,FXCalendarView::onKeyPress),
70   FXMAPFUNC(SEL_FOCUSIN,0,FXCalendarView::onFocusIn),
71   FXMAPFUNC(SEL_FOCUSOUT,0,FXCalendarView::onFocusOut),
72   FXMAPFUNC(SEL_CLICKED,0,FXCalendarView::onClicked),
73   FXMAPFUNC(SEL_DOUBLECLICKED,0,FXCalendarView::onDoubleClicked),
74   FXMAPFUNC(SEL_TRIPLECLICKED,0,FXCalendarView::onTripleClicked),
75   FXMAPFUNC(SEL_COMMAND,0,FXCalendarView::onCommand),
76   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_SETVALUE,FXCalendarView::onCmdSetValue),
77   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_SETINTVALUE,FXCalendarView::onCmdSetIntValue),
78   FXMAPFUNC(SEL_COMMAND,FXWindow::ID_GETINTVALUE,FXCalendarView::onCmdGetIntValue),
79   };
80 
81 
82 // Implementation
FXIMPLEMENT(FXCalendarView,FXWindow,FXCalendarViewMap,ARRAYNUMBER (FXCalendarViewMap))83 FXIMPLEMENT(FXCalendarView,FXWindow,FXCalendarViewMap,ARRAYNUMBER(FXCalendarViewMap))
84 
85 
86 // For serialization
87 FXCalendarView::FXCalendarView(){
88   flags|=FLAG_SHOWN|FLAG_ENABLED;
89   font=(FXFont*)-1L;
90   }
91 
92 
93 // Construct and initialize
FXCalendarView(FXComposite * p,FXObject * tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h)94 FXCalendarView::FXCalendarView(FXComposite *p,FXObject* tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h):FXWindow(p,opts,x,y,w,h){
95   flags|=FLAG_SHOWN|FLAG_ENABLED;
96   font=getApp()->getNormalFont();
97   target=tgt;
98   message=sel;
99   has_selection=false;
100   selected=current=FXDate::localDate();
101   firstday=FXDate::Sun; // Sunday
102 
103   // Setup Default Colors
104   todayColor=FXRGB(0,180,0);
105   titleColor=getApp()->getBackColor();
106   titleBackColor=getApp()->getShadowColor();
107   dayColor=getApp()->getForeColor();
108   otherDayColor=getApp()->getShadowColor();
109   weekendColor=FXRGB(180,0,0);
110   otherWeekendColor=makeHiliteColor(FXRGB(200,100,100));
111   backColor=getApp()->getBackColor();
112 
113   // Update for date
114   updateview(false);
115   }
116 
117 
118 // Create X window
create()119 void FXCalendarView::create(){
120   FXWindow::create();
121   font->create();
122   }
123 
124 
125 // Detach window
detach()126 void FXCalendarView::detach(){
127   FXWindow::detach();
128   font->detach();
129   }
130 
131 
132 // Enable the window
enable()133 void FXCalendarView::enable(){
134   if(!(flags&FLAG_ENABLED)){
135     FXWindow::enable();
136     update();
137     }
138   }
139 
140 
141 // Disable the window
disable()142 void FXCalendarView::disable(){
143   if(flags&FLAG_ENABLED){
144     FXWindow::disable();
145     update();
146     }
147   }
148 
149 
150 // Set the Calendar Style
setCalendarStyle(FXuint style)151 void FXCalendarView::setCalendarStyle(FXuint style){
152   FXuint opts=((style^options)&CALENDAR_MASK)^options;
153   if(options!=opts){
154     options=opts;
155     recalc();
156     layout();
157     update();
158     }
159   }
160 
161 
162 // Get the Calendar Style
getCalendarStyle() const163 FXuint FXCalendarView::getCalendarStyle() const{
164   return (options&CALENDAR_MASK);
165   }
166 
167 
168 // Set the display color of titles
setTitleColor(FXColor clr)169 void FXCalendarView::setTitleColor(FXColor clr){
170   if(titleColor!=clr){
171     titleColor=clr;
172     update();
173     }
174   }
175 
176 
177 // Set the display background color of titles
setTitleBackColor(FXColor clr)178 void FXCalendarView::setTitleBackColor(FXColor clr){
179   if(titleBackColor!=clr){
180     titleBackColor=clr;
181     update();
182     }
183   }
184 
185 
186 // Set the display color of non-weekend days
setDayColor(FXColor clr)187 void FXCalendarView::setDayColor(FXColor clr){
188   if(dayColor!=clr){
189     dayColor=clr;
190     update();
191     }
192   }
193 
194 
195 // Set the display color of non-weekend days not in the current month
setOtherDayColor(FXColor clr)196 void FXCalendarView::setOtherDayColor(FXColor clr){
197   if(otherDayColor!=clr){
198     otherDayColor=clr;
199     update();
200     }
201   }
202 
203 
204 // Set the display color of today
setTodayColor(FXColor clr)205 void FXCalendarView::setTodayColor(FXColor clr){
206   if(todayColor!=clr){
207     todayColor=clr;
208     update();
209     }
210   }
211 
212 
213 // Set the display color of days in the weekend
setWeekendColor(FXColor clr)214 void FXCalendarView::setWeekendColor(FXColor clr){
215   if(weekendColor!=clr){
216     weekendColor=clr;
217     update();
218     }
219   }
220 
221 
222 // Set the display color of days in the weekend not in the current month
setOtherWeekendColor(FXColor clr)223 void FXCalendarView::setOtherWeekendColor(FXColor clr){
224   if(otherWeekendColor!=clr){
225     otherWeekendColor=clr;
226     update();
227     }
228   }
229 
230 
231 // Compute default width
getDefaultWidth()232 FXint FXCalendarView::getDefaultWidth(){
233   FXint cw=0,tw,i;
234   FXString text;
235   for(i=0; i<7; i++){
236     text=tr(FXDate::dayNameShort(i));
237     tw=font->getTextWidth(text);
238     if(tw>cw) cw=tw;
239     }
240   if(options&CALENDAR_WEEKNUMBERS){
241     text=tr("Wk");
242     tw=font->getTextWidth(text);
243     if(tw>cw) cw=tw;
244     return (cw+4)*8;
245     }
246   return (cw+4)*7;
247   }
248 
249 
250 // Compute default height
getDefaultHeight()251 FXint FXCalendarView::getDefaultHeight(){
252   return (font->getFontHeight()+4)*7;
253   }
254 
255 
256 // Into focus chain
setFocus()257 void FXCalendarView::setFocus(){
258   FXWindow::setFocus();
259   setDefault(true);
260   flags&=~FLAG_UPDATE;
261   }
262 
263 
264 // Out of focus chain
killFocus()265 void FXCalendarView::killFocus(){
266   FXWindow::killFocus();
267   setDefault(maybe);
268   flags|=FLAG_UPDATE;
269   }
270 
271 
272 // Set focus on given date
moveFocus(FXDate f)273 void FXCalendarView::moveFocus(FXDate f){
274   if((options&CALENDAR_STATIC) && f.month()!=month) return;
275   setCurrentDate(f,true);
276   }
277 
278 
279 // Get date at x,y if any
getDateAt(FXint x,FXint y,FXDate & date) const280 FXbool FXCalendarView::getDateAt(FXint x,FXint y,FXDate& date) const {
281   FXint ncols=(options&CALENDAR_WEEKNUMBERS)?8:7;
282   FXint nrows=7;
283   FXint cw=width/ncols;
284   FXint ch=height/nrows;
285   FXint cwr=width%ncols;
286   FXint chr=height%nrows;
287   FXint col=0,row=0,xx=0,yy=0;
288   FXint columnwidth[8];
289   FXint rowheight[7];
290   FXint i,e;
291 
292   // Calculate column widths
293   for(i=e=0; i<ncols; i++){
294     columnwidth[i]=cw;
295     e+=cwr;
296     if(e>=ncols){ columnwidth[i]++; e-=ncols; }
297     }
298 
299   // Calculate row heights
300   for(i=e=0; i<nrows; i++){
301     rowheight[i]=ch;
302     e+=chr;
303     if(e>=nrows){ rowheight[i]++; e-=nrows; }
304     }
305 
306   // Filter out obvious cases (clicked on headers)
307   if(y<rowheight[0]) return false;
308   if(options&CALENDAR_WEEKNUMBERS && x<columnwidth[0]) return false;
309 
310   // Determine where we clicked
311   while(xx<x && col<ncols) xx+=columnwidth[col++];
312   while(yy<y && row<nrows) yy+=rowheight[row++];
313   if(options&CALENDAR_WEEKNUMBERS) col-=1;
314   col-=1;
315   row-=2;
316   date=ds+(row*7)+col;
317   return true;
318   }
319 
320 
321 // Redraw widget
onPaint(FXObject *,FXSelector,void * ptr)322 long FXCalendarView::onPaint(FXObject*,FXSelector,void* ptr){
323   FXDate now=FXDate::localDate();
324   FXint coloffset=(options&CALENDAR_WEEKNUMBERS)?1:0;
325   FXbool displayselected=(has_selection || ((options&SELECT_MASK)==CALENDAR_BROWSESELECT));
326   FXint ncols=(options&CALENDAR_WEEKNUMBERS)?8:7;
327   FXint nrows=7;
328   FXint cw=width/ncols;
329   FXint ch=height/nrows;
330   FXint cwr=width%ncols;
331   FXint chr=height%nrows;
332   FXint textwidth=font->getTextWidth("88",2);
333   FXint fontheight=font->getFontHeight();
334   FXint i=0,c=0,r=0,e=0;
335   FXint xx=0,yy=0;
336   FXint columnwidth[8];      // Width of each column
337   FXint columnoffset[8];     // Text Offset in each column
338   FXint rowheight[7];        // Height of each column
339   FXString text;
340   FXDate pd=ds;
341 
342   // The acual painting
343   FXDCWindow dc(this,(FXEvent*)ptr);
344 
345   // Calculate column widths
346   for(i=0,e=0; i<ncols; i++){
347     columnwidth[i]=cw;
348     e+=cwr;
349     if(e>=ncols){ columnwidth[i]++; e-=ncols; }
350     columnoffset[i]=(columnwidth[i]-textwidth)/2;
351     }
352 
353   // Calculate row heights
354   for(i=0,e=0; i<nrows; i++){
355     rowheight[i]=ch;
356     e+=chr;
357     if(e>=nrows){ rowheight[i]++; e-=nrows; }
358     }
359 
360   // Paint backgrounds
361   if(isEnabled()){
362     dc.setForeground(backColor);
363     }
364   else{
365     dc.setForeground(getApp()->getBaseColor());
366     }
367 
368   dc.fillRectangle(0,rowheight[0],width,height-rowheight[0]);
369   dc.setForeground(titleBackColor);
370   dc.fillRectangle(0,0,width,rowheight[0]);
371 
372   // Paint separator between week numbers and days
373   if(options&CALENDAR_WEEKNUMBERS) {
374     dc.setForeground(getApp()->getForeColor());
375     dc.fillRectangle(columnwidth[0]-1,0,1,height);
376     }
377 
378   // Paint header
379   dc.setForeground(titleColor);
380   dc.setFont(font);
381 
382   // Week number caption
383   if(options&CALENDAR_WEEKNUMBERS){
384     text=tr("Wk");
385     dc.drawText((columnwidth[0]-font->getTextWidth(text))/2,(rowheight[0]-fontheight)/2+font->getFontAscent(),text);
386     xx+=columnwidth[0];
387     }
388 
389   // Day of week captions
390   for(c=firstday,i=coloffset; c<firstday+7; c++,i++){
391     text=tr(FXDate::dayNameShort(c%7));
392     if(c%7==0 || c%7==6){
393       dc.setForeground(weekendColor);
394       }
395     else{
396       dc.setForeground(titleColor);
397       }
398     dc.drawText(xx+(columnwidth[i]-font->getTextWidth(text))/2,(rowheight[0]-fontheight)/2+font->getFontAscent(),text);
399     xx+=columnwidth[i];
400     }
401 
402   // Next row
403   yy+=rowheight[0];
404 
405   // Paint days
406   dc.setForeground(getApp()->getForeColor());
407   for(r=1; r<nrows; r++){
408     xx=0;
409 
410     if(options&CALENDAR_WEEKNUMBERS){
411       text.fromInt(pd.weekOfYear());
412       dc.setForeground(dayColor);
413 
414       // Draw Text
415       dc.drawText(xx+columnwidth[0]-font->getTextWidth(text)-columnoffset[0],yy+(rowheight[r]-fontheight)/2+font->getFontAscent(),text);
416       xx+=columnwidth[0];
417       }
418 
419 
420     for(c=coloffset; c<ncols; c++){
421       if((options&CALENDAR_HIDEOTHER) && pd.month()!=month){
422         ++pd; // Next day
423         xx+=columnwidth[c]; // Next column
424         continue;
425         }
426 
427       text.fromInt(pd.day());
428 
429       // Determine drawing colors
430       if(displayselected && selected==pd){
431         if(isEnabled()){
432           dc.setForeground(getApp()->getSelbackColor());
433           }
434         else{
435           dc.setForeground(getApp()->getShadowColor());
436           }
437         dc.fillRectangle(xx,yy,columnwidth[c],rowheight[r]);
438         dc.setForeground(getApp()->getSelforeColor());
439         }
440       else{
441         if(pd==now){
442           dc.setForeground(todayColor);
443           }
444         else{
445           if(pd.dayOfWeek()==0 || pd.dayOfWeek()==6){
446             if(pd.month()==month)
447               dc.setForeground(weekendColor);
448             else
449               dc.setForeground(otherWeekendColor);
450             }
451           else{
452             if(pd.month()==month)
453               dc.setForeground(dayColor);
454             else
455               dc.setForeground(otherDayColor);
456             }
457           }
458         }
459 
460       // Draw Text
461       dc.drawText(xx+columnwidth[c]-font->getTextWidth(text)-columnoffset[c],yy+(rowheight[r]-fontheight)/2+font->getFontAscent(),text);
462 
463       if(hasFocus() && current==pd){
464         dc.drawFocusRectangle(xx,yy,columnwidth[c],rowheight[r]);
465         }
466 
467       ++pd; // Next day
468       xx+=columnwidth[c]; // Next column
469       }
470 
471     // Next Row
472     yy+=rowheight[r];
473     }
474   return 1;
475   }
476 
477 
478 
479 // Pressed left button
onLeftBtnPress(FXObject *,FXSelector,void * ptr)480 long FXCalendarView::onLeftBtnPress(FXObject*,FXSelector,void* ptr){
481   FXEvent* ev=(FXEvent*)ptr;
482   flags&=~FLAG_TIP;
483   handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
484   if(isEnabled()){
485     grab();
486     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONPRESS,message),ptr)) return 1;
487     flags&=~FLAG_UPDATE;
488     FXDate cd;
489     if(getDateAt(ev->click_x,ev->click_y,cd)){
490       if(((options&CALENDAR_STATIC) || (options&CALENDAR_HIDEOTHER)) && cd.month()!=month){
491         flags|=FLAG_PRESSED;
492         return 1;
493         }
494       setCurrentDate(cd,true);
495       if((options&SELECT_MASK)==CALENDAR_SINGLESELECT){
496         if(has_selection && current==selected)
497           state=true;
498         else
499           state=false;
500         selectDate(cd,true);
501         }
502       }
503     flags|=FLAG_PRESSED;
504     return 1;
505     }
506   return 0;
507   }
508 
509 
510 // If window can have focus
canFocus() const511 FXbool FXCalendarView::canFocus() const {
512   return true;
513   }
514 
515 
516 // Gained focus
onFocusIn(FXObject * sender,FXSelector sel,void * ptr)517 long FXCalendarView::onFocusIn(FXObject* sender,FXSelector sel,void* ptr){
518   FXWindow::onFocusIn(sender,sel,ptr);
519   markdirty(current);
520   return 1;
521   }
522 
523 
524 // Lost focus
onFocusOut(FXObject * sender,FXSelector sel,void * ptr)525 long FXCalendarView::onFocusOut(FXObject* sender,FXSelector sel,void* ptr){
526   FXWindow::onFocusOut(sender,sel,ptr);
527   markdirty(current);
528   return 1;
529   }
530 
531 
532 // Released left button
onLeftBtnRelease(FXObject *,FXSelector,void * ptr)533 long FXCalendarView::onLeftBtnRelease(FXObject*,FXSelector,void* ptr){
534   FXEvent* event=(FXEvent*)ptr;
535   if(isEnabled()){
536     ungrab();
537     flags|=FLAG_UPDATE;
538     flags&=~FLAG_PRESSED;
539     if(target && target->tryHandle(this,FXSEL(SEL_LEFTBUTTONRELEASE,message),ptr)) return 1;
540 
541     if((options&SELECT_MASK)==CALENDAR_SINGLESELECT){
542       if(state){
543         killSelection(true);
544         }
545       }
546 
547     // Generate clicked callbacks
548     if(event->click_count==1){
549       handle(this,FXSEL(SEL_CLICKED,0),(void*)(FXuval)current.getJulian());
550       }
551     else if(event->click_count==2){
552       handle(this,FXSEL(SEL_DOUBLECLICKED,0),(void*)(FXuval)current.getJulian());
553       }
554     else if(event->click_count==3){
555       handle(this,FXSEL(SEL_TRIPLECLICKED,0),(void*)(FXuval)current.getJulian());
556       }
557 
558     handle(this,FXSEL(SEL_COMMAND,0),(void*)(FXuval)current.getJulian());
559     return 1;
560     }
561   return 0;
562   }
563 
564 
565 // Command message
onCommand(FXObject *,FXSelector,void * ptr)566 long FXCalendarView::onCommand(FXObject*,FXSelector,void* ptr){
567   return target && target->tryHandle(this,FXSEL(SEL_COMMAND,message),ptr);
568   }
569 
570 
571 // Clicked in list
onClicked(FXObject *,FXSelector,void * ptr)572 long FXCalendarView::onClicked(FXObject*,FXSelector,void* ptr){
573   return target && target->tryHandle(this,FXSEL(SEL_CLICKED,message),ptr);
574   }
575 
576 
577 // Double clicked in list; ptr may or may not point to an item
onDoubleClicked(FXObject *,FXSelector,void * ptr)578 long FXCalendarView::onDoubleClicked(FXObject*,FXSelector,void* ptr){
579   return target && target->tryHandle(this,FXSEL(SEL_DOUBLECLICKED,message),ptr);
580   }
581 
582 
583 // Triple clicked in list; ptr may or may not point to an item
onTripleClicked(FXObject *,FXSelector,void * ptr)584 long FXCalendarView::onTripleClicked(FXObject*,FXSelector,void* ptr){
585   return target && target->tryHandle(this,FXSEL(SEL_TRIPLECLICKED,message),ptr);
586   }
587 
588 
589 // Update value from a message
onCmdSetValue(FXObject *,FXSelector,void * ptr)590 long FXCalendarView::onCmdSetValue(FXObject*,FXSelector,void* ptr){
591   setCurrentDate(FXDate((FXuint)(FXuval)ptr));
592   return 1;
593   }
594 
595 
596 // Obtain value from list
onCmdGetIntValue(FXObject *,FXSelector,void * ptr)597 long FXCalendarView::onCmdGetIntValue(FXObject*,FXSelector,void* ptr){
598   *((FXuint*)ptr)=getCurrentDate().getJulian();
599   return 1;
600   }
601 
602 
603 // Update value from a message
onCmdSetIntValue(FXObject *,FXSelector,void * ptr)604 long FXCalendarView::onCmdSetIntValue(FXObject*,FXSelector,void* ptr){
605   setCurrentDate(FXDate(*((FXuint*)ptr)));
606   return 1;
607   }
608 
609 
610 // Key pressed
onKeyPress(FXObject * sender,FXSelector sel,void * ptr)611 long FXCalendarView::onKeyPress(FXObject*sender,FXSelector sel,void* ptr){
612   FXEvent* event=(FXEvent*)ptr;
613   FXDate date=current;
614   flags&=~FLAG_TIP;
615 
616   // Bounce to focus widget
617   if(getFocus() && getFocus()->handle(sender,sel,ptr)) return 1;
618 
619   if(!isEnabled()) return 0;
620 
621   // Try target first
622   if(target && target->tryHandle(this,FXSEL(SEL_KEYPRESS,message),ptr)) return 1;
623 
624   // Eat keystroke
625   switch(event->code){
626     case KEY_Up:
627     case KEY_KP_Up:
628       moveFocus(current-7);
629       return 1;
630       break;
631     case KEY_Down:
632     case KEY_KP_Down:
633       moveFocus(current+7);
634       return 1;
635       break;
636     case KEY_Right:
637     case KEY_KP_Right:
638       moveFocus(current+1);
639       return 1;
640       break;
641     case KEY_Left:
642     case KEY_KP_Left:
643       moveFocus(current-1);
644       return 1;
645       break;
646     case KEY_space:
647     case KEY_Return:
648     case KEY_KP_Enter:
649       if(has_selection && current==selected)
650         killSelection(true);
651       else
652         selectDate(current);
653       return 1;
654       break;
655     case KEY_Page_Down:
656     case KEY_KP_Page_Down:
657       date.addMonths(1);
658       setCurrentDate(date,true);
659       return 1;
660       break;
661     case KEY_Page_Up:
662     case KEY_KP_Page_Up:
663       date.addMonths(-1);
664       setCurrentDate(date,true);
665       return 1;
666       break;
667     case KEY_Home:
668     case KEY_KP_Home:
669       setCurrentDate(FXDate::localDate(),true);
670       return 1;
671       break;
672     case KEY_End:
673     case KEY_KP_End:
674       if(has_selection)
675         setCurrentDate(selected,true);
676       else
677         getApp()->beep();
678       return 1;
679       break;
680      }
681   return 0;
682   }
683 
684 
685 // Mark as dirty
markdirty(FXDate d)686 void FXCalendarView::markdirty(FXDate d){
687   if(xid){
688     FXint ncols = ((options&CALENDAR_WEEKNUMBERS) ? 8 : 7);
689     FXint nrows = 7;
690     FXint cw    = width / ncols;
691     FXint ch    = height / nrows;
692     FXint cwr   = width % ncols;
693     FXint chr   = height % nrows;
694     FXint days  = d-ds;
695     FXint col   = (days%7) + ((options&CALENDAR_WEEKNUMBERS) ? 1 : 0);
696     FXint row   = (days/7) + 1;
697     FXint xx=0,yy=0,ww=0,hh=0,i=0,e=0;
698 
699     // Calculate xx
700     for(i=0,e=0;i<col;i++){
701       xx+=cw;
702       e+=cwr;
703       if(e>=ncols){ xx++; e-=ncols; }
704       }
705 
706     // Calculate ww
707     ww=cw;
708     e+=cwr;
709     if(e>=ncols){ ww++; e-=ncols; }
710 
711     // Calculate yy
712     for(i=0,e=0;i<row;i++){
713       yy+=ch;
714       e+=chr;
715       if(e>=nrows){ yy++; e-=nrows; }
716       }
717 
718     // Calculate hh
719     hh=ch;
720     e+=chr;
721     if(e>=nrows){ hh++; e-=nrows; }
722 
723     update(xx,yy,ww,hh);
724     }
725   }
726 
727 
728 // Update view
updateview(FXbool notify)729 void FXCalendarView::updateview(FXbool notify){
730   FXDate ms=(current-(current.day()-1));
731   month=current.month();
732   ds=ms-(7-(firstday-ms.dayOfWeek()))%7;
733   if(target && notify) target->tryHandle(this,FXSEL(SEL_REPLACED,message),(void*)(FXival)current.getJulian());
734   }
735 
736 
737 // Set the current month; current day will be properly updated for the choosen month
setCurrentMonth(FXint m,FXbool notify)738 void FXCalendarView::setCurrentMonth(FXint m,FXbool notify){
739   if(m<1 || m>12){ fxerror("%s::setCurrentMonth: Invalid month argument.\n",getClassName()); }
740   if(month!=m){
741 
742     // Set a new current date
743     FXint day=current.day();
744     current.setDate(current.year(),m,1);
745     if(current.daysInMonth()>day)
746       current.setDate(current.year(),m,day);
747     else
748       current.setDate(current.year(),m,current.daysInMonth());
749 
750     // Update the GUI and do the same as setCurrentDate would do.
751     // Don't call setCurrentDate since it wouldn't update the complete view.
752     updateview();
753     update(0,0,width,height);
754 
755     // Notify item change
756     if(notify && target){ target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)current.getJulian()); }
757 
758     // In browse select mode, select this item
759     if((options&SELECT_MASK)==CALENDAR_BROWSESELECT){
760       selectDate(current,notify);
761       }
762     }
763   }
764 
765 
766 // Get the selected date, if any
getSelectedDate(FXDate & date) const767 FXbool FXCalendarView::getSelectedDate(FXDate& date) const {
768   if(((options&SELECT_MASK)==CALENDAR_SINGLESELECT)){
769     if(has_selection){
770       date=selected;
771       return true;
772       }
773     return false;
774     }
775   else{
776     date=selected;
777     return true;
778     }
779   }
780 
781 
782 // Select Date
selectDate(FXDate d,FXbool notify)783 void FXCalendarView::selectDate(FXDate d,FXbool notify){
784   if(d!=selected || !has_selection){
785 
786     has_selection=true;
787 
788     // Within the current month view
789     if((selected>=ds) && (selected<=(ds+41))){
790       markdirty(selected);
791       }
792 
793     // Within the current month view
794     if((d.month()==month) && (d.year()==selected.year())){
795       selected=d;
796       markdirty(selected);
797       }
798     else{
799       selected=d;
800       updateview();
801       update(0,0,width,height);
802       }
803 
804     if(notify && target){ target->tryHandle(this,FXSEL(SEL_SELECTED,message),(void*)(FXival)selected.getJulian()); }
805     }
806   }
807 
808 
809 // Deselect Date
killSelection(FXbool notify)810 void FXCalendarView::killSelection(FXbool notify){
811   if(((options&SELECT_MASK)==CALENDAR_SINGLESELECT) && has_selection){
812     has_selection=false;
813     if(notify && target){ target->tryHandle(this,FXSEL(SEL_DESELECTED,message),(void*)(FXival)selected.getJulian()); }
814     markdirty(selected);
815     }
816   }
817 
818 
819 // Set Date
setCurrentDate(FXDate d,FXbool notify)820 void FXCalendarView::setCurrentDate(FXDate d,FXbool notify){
821   if(d!=current){
822 
823     // Update View and repaint everyting
824     if(d<ds || d>(ds+41) || (((options&CALENDAR_HIDEOTHER) || ((options&SELECT_MASK)==CALENDAR_BROWSESELECT)) && d.month()!=month)){
825       current=d;
826       updateview();
827       update(0,0,width,height);
828       }
829 
830     // Within the current month view. just mark fields used dirty
831     else{
832       markdirty(current);
833       markdirty(d);
834       current=d;
835       }
836 
837     // Notify item change
838     if(notify && target){ target->tryHandle(this,FXSEL(SEL_CHANGED,message),(void*)(FXival)current.getJulian()); }
839     }
840 
841   // In browse select mode, select this item
842   if((options&SELECT_MASK)==CALENDAR_BROWSESELECT){
843     selectDate(current,notify);
844     }
845   }
846 
847 
848 // Set the first day of the week [0...6]
setFirstDay(FXint d)849 void FXCalendarView::setFirstDay(FXint d){
850   if(d<0 || d>6){ fxerror("%s::setFirstDay: invalid day argument.\n",getClassName()); }
851   if(firstday!=d){
852     firstday=d;
853     updateview();
854     update();
855     }
856   }
857 
858 
859 // Change the font
setFont(FXFont * fnt)860 void FXCalendarView::setFont(FXFont *fnt){
861   if(!fnt){ fxerror("%s::setFont: NULL font specified.\n",getClassName()); }
862   if(font!=fnt){
863     font=fnt;
864     recalc();
865     update();
866     }
867   }
868 
869 
870 // Destroy it
~FXCalendarView()871 FXCalendarView::~FXCalendarView(){
872   font=(FXFont*)-1L;
873   }
874 
875 }
876 
877