a4983df641d0923de3d06ba1c9705e8192b1ac99
[sdk] / ecere / src / gui / controls / Button.ec
1 namespace gui::controls;
2
3 import "Window"
4
5 class ButtonBits { bool bevel:1, scale:1, offset:1, stayDown:1, checkBox:1, toggle:1, radio:1, keyRepeat:1, bevelOver:1, ellipsis:1, stayUp:1; };
6
7 #if defined(HIGH_DPI)
8 #define CAPTION_DISTANCE   36
9 #else
10 #define CAPTION_DISTANCE   18
11 #endif
12
13 public enum ButtonState /*: byte*/ { up, down, over, disabled, upChecked, downChecked, overChecked, disabledChecked };
14 //use and extend preexisting enum? -> public enum Alignment { left, right, center };
15 public enum BoxAlignment { center, left, right, top, bottom }; // topLeftCorner, topRightCorner, bottomLeftCorner, bottomRightCorner
16
17 public class Button : CommonControl
18 {
19    /*
20    private(opacity);
21    private(drawBehind);
22    */
23    public property Percentage opacity
24    {
25       isset { return (buttonStyle.checkBox || buttonStyle.radio) ? (Window::opacity != 0) : Window::opacity != 1; }
26       get { return Window::opacity; }
27       set { Window::opacity = value; }
28    }
29    public property bool drawBehind
30    {
31       isset { return (buttonStyle.checkBox || buttonStyle.radio) ? (Window::drawBehind != drawBehind) : Window::drawBehind != false; }
32       get { return Window::drawBehind; }
33       set { Window::drawBehind = value; }
34    }
35    class_property(icon) = "<:ecere>controls/button.png";
36
37    watch(background)
38    {
39       Color background = this.background;
40
41       if(colors[down] == colors[up])      colors[down] = background;
42       if(colors[over] == colors[up])      colors[over] = background;
43       if(colors[disabled] == colors[up])  colors[disabled] = background;
44       colors[0] = background;
45    };
46
47    watch(foreground)
48    {
49       Color foreground = this.foreground;
50
51       if(textColor[down] == textColor[up])      textColor[down] = foreground;
52       if(textColor[over] == textColor[up])      textColor[over] = foreground;
53       if(textColor[disabled] == textColor[up])  textColor[disabled] = foreground;
54       textColor[0] = foreground;
55    };
56
57    watch(font)
58    {
59       OnLoadGraphics();
60       SetInitSize(initSize);
61    };
62
63    watch(caption)
64    {
65       OnLoadGraphics();
66       SetInitSize(initSize);
67    };
68
69    ButtonBits buttonStyle;
70    ButtonState state;
71    BitmapResource bitmaps[ButtonState];
72         int captionHeight, captionWidth;
73    unichar symbol;
74    Color textColor[ButtonState];
75    Font font;
76    Color colors[ButtonState];
77    Alignment alignment;
78    BoxAlignment bitmapAlignment;
79    struct { bool hint:1, key:1, captured:1, over:1, checked:1; };
80
81    Button()
82    {
83       background = formColor;
84       foreground = black;
85       //opacity = 0;
86       //drawBehind = true;
87       borderStyle = 0;
88       bevel = true;
89       buttonStyle.offset = true;
90
91       // SetCursor(eInterface_GetCursor(__ecereModule->app, CUR_ARROW));
92
93       alignment = center;
94       state = up;
95       hint = false;
96       textColor[up] = textColor[down] =
97       textColor[over] = black;
98       textColor[disabled] = Color { 85, 85, 85 };
99
100       colors[up] = formColor;
101       colors[down] = formColor;
102       colors[over] = formColor;
103       colors[disabled] = white;
104       colors[upChecked] = Color { 233,229,221 };
105       colors[overChecked] = formColor;
106       colors[downChecked] = Color { 233,229,221 };
107       colors[disabledChecked] = formColor;
108    }
109
110    Bitmap GetButtonBitmap()
111    {
112       ButtonState state = this.state;
113       Bitmap buttonBitmap = bitmaps[(int)checked*4 + state].bitmap;
114       if(!buttonBitmap) buttonBitmap = bitmaps[(int)checked * 4+up].bitmap;
115       if(!buttonBitmap) buttonBitmap = bitmaps[up].bitmap;
116       if(!isEnabled && bitmaps[disabled]) buttonBitmap = bitmaps[disabled].bitmap;
117       return buttonBitmap;
118    }
119
120    void OnRedraw(Surface surface)
121    {
122       ButtonState state = this.state;
123       Bitmap buttonBitmap = GetButtonBitmap();
124       const char * text = this.text;
125       int offset = (state == down && buttonStyle.offset) ? 1 : 0;
126       Color backColor = colors[(int)checked*4 + state];
127       int isDefault = this.isDefault;
128       int tw = 0, th = 0;
129       int bw = 0, bh = 0;
130
131       if(!backColor) backColor = colors[up];
132
133       font = fontObject;
134       surface.TextFont(font);
135       if(text)
136          surface.TextExtent(text, strlen(text),&tw, &th);
137
138       if(buttonStyle.bevelOver && checked)
139          offset = 1;
140
141       if(!isEnabled)
142       {
143          if(bitmaps[disabled]) buttonBitmap = bitmaps[disabled].bitmap;
144          state = disabled;
145       }
146
147       if(buttonBitmap && buttonStyle.bevelOver && !buttonStyle.checkBox && !buttonStyle.radio && text)
148          isDefault = 0;
149
150       // Background
151       if((buttonStyle.bevel || buttonStyle.bevelOver) /* && opacity && backColor*/)
152       {
153          if(!buttonStyle.scale || !buttonBitmap)
154          {
155             surface.SetBackground({(byte)(opacity * 255), backColor});
156             //surface.Area(1, 1,clientSize.w-3+offset,clientSize.h-3+offset);
157             surface.Area(0, 0,clientSize.w-1,clientSize.h-1);
158          }
159       }
160
161       // Checkbox
162       if(buttonStyle.checkBox && !buttonBitmap)
163       {
164 #if defined(HIGH_DPI)
165          int height = 32;
166 #else
167          int height = 16;
168 #endif
169          int start = (clientSize.h - height) / 2;
170
171          if(!isEnabled)
172             // surface.SetBackground(formColor);
173             surface.SetBackground(gainsboro);
174          else if(active && !text)
175             surface.SetBackground((offset ? formColor : Color { 0,0,170 }));
176          else
177             surface.SetBackground((offset ? formColor : background /*white*/));
178          surface.Area(2, start+2,height-3,start+height-3);
179
180          surface.SetForeground(Color { 85, 85, 85 });
181          surface.HLine(0, height - 2, start + 0);
182          surface.VLine(start+1, start+height - 2, 0);
183
184          surface.SetForeground(Color { 64,64,64 });
185          surface.HLine(1, height - 3, start + 1);
186          surface.VLine(start+2, start+height - 3, 1);
187
188          surface.SetForeground(Color { 212,208,200 });
189          surface.HLine(1, height - 2, start + height-2);
190          surface.VLine(start+1, start+height - 3, height-2);
191
192          surface.SetForeground(white);
193          surface.HLine(0, height - 1, start + height-1);
194          surface.VLine(start+0, start+height - 2, height-1);
195
196          if(checked)
197          {
198             if(active && !text)
199                surface.SetForeground(white);
200             else if(!isEnabled)
201                surface.SetForeground(Color { 85, 85, 85 });
202             else
203                surface.SetForeground(foreground /*black*/);
204 #ifdef HIGH_DPI
205                surface.DrawLine(8, start+16, 14,start+22);
206                surface.DrawLine(8, start+18, 14,start+24);
207                surface.DrawLine(14, start+22, 19,start+6);
208                surface.DrawLine(14, start+24, 19,start+8);
209 #else
210             surface.DrawLine(4, start+8, 7,start+11);
211             surface.DrawLine(4, start+9, 7,start+12);
212             surface.DrawLine(7, start+11, 11,start+3);
213             surface.DrawLine(7, start+12, 11,start+4);
214 #endif
215          }
216       }
217
218       // Bitmaps
219       if(buttonBitmap)
220       {
221          surface.SetForeground(white);
222          if(buttonStyle.radio || buttonStyle.checkBox)
223          {
224             int x = 0, y = (clientSize.h-buttonBitmap.height)/2;
225             if(buttonStyle.bevelOver && text)
226             {
227                x = (CAPTION_DISTANCE-buttonBitmap.width)/2 + offset;
228                y = (clientSize.h-buttonBitmap.height)/2 + offset;
229             }
230
231             // Radio Buttons and Checkboxes
232             surface.Blit(buttonBitmap,
233                x, y,
234                0,0,buttonBitmap.width,buttonBitmap.height);
235          }
236          else
237          {
238             // Push Buttons
239             if(buttonStyle.scale)
240             {
241                if(buttonStyle.bevel || buttonStyle.offset)
242                   surface.Stretch(buttonBitmap,
243                      1 + offset, 1 + offset,0,0,
244                      clientSize.w-3,clientSize.h-3,buttonBitmap.width,buttonBitmap.height);
245                else
246                   surface.Stretch(buttonBitmap, 0,0, 0,0,
247                      clientSize.w,clientSize.h,buttonBitmap.width,buttonBitmap.height);
248             }
249             else
250             {
251                int x, y;
252                bw = buttonBitmap.width;
253                bh = buttonBitmap.height;
254
255                if(bitmapAlignment == left || bitmapAlignment == right)
256                {
257                   if(bitmapAlignment == left)
258                      x = 2;
259                   else
260                      x = clientSize.w-bw-2;
261                   y = (clientSize.h-bh)/2;
262                }
263                else if(bitmapAlignment == top || bitmapAlignment == bottom)
264                {
265                   x = (clientSize.w-bw)/2;
266                   if(bitmapAlignment == top)
267                      y = 2;
268                   else
269                      y = clientSize.h-bh-2;
270                }
271                else
272                {
273                   x = (clientSize.w-bw)/2;
274                   y = (clientSize.h-bh - (int)(buttonStyle.bevelOver && text) * th)/2;
275                }
276                if(buttonStyle.bevel || buttonStyle.offset)
277                {
278                   x += offset;
279                   y += offset;
280                }
281                surface.Blit(buttonBitmap, x,y, 0,0, bw,bh);
282             }
283          }
284       }
285
286       // Shadows
287       if(buttonStyle.bevel ||
288          (buttonStyle.bevelOver &&
289             (state == down || state == over || checked)))
290       {
291          if(state == down || checked)
292          {
293             surface.SetForeground(Color { 85, 85, 85 });
294             surface.HLine(isDefault + 0, clientSize.w-2-isDefault, 0);
295             surface.VLine(isDefault + 1, clientSize.h-2-isDefault, 0);
296             surface.SetForeground(white);
297             surface.HLine(isDefault + 0, clientSize.w-1-isDefault, clientSize.h-1-isDefault);
298             surface.VLine(isDefault + 0, clientSize.h-2-isDefault, clientSize.w-1-isDefault);
299          }
300          else
301          {
302             surface.SetForeground(white);
303             surface.HLine(0 + isDefault, clientSize.w-2 - isDefault,  isDefault);
304             surface.VLine(1 + isDefault, clientSize.h-2 - isDefault,  isDefault);
305             surface.SetForeground(Color { 85, 85, 85 });
306             surface.HLine(1 + isDefault, clientSize.w-2 - isDefault, clientSize.h-2 - isDefault);
307             surface.VLine(1 + isDefault, clientSize.h-3 - isDefault, clientSize.w-2 - isDefault);
308
309             if(buttonStyle.bevel)
310             {
311                surface.SetForeground(black);
312                surface.HLine( isDefault, clientSize.w-1 - isDefault, clientSize.h-1 - isDefault);
313                surface.VLine( isDefault, clientSize.h-2 - isDefault, clientSize.w-1 - isDefault);
314             }
315          }
316       }
317
318       // Text
319       surface.TextOpacity(false);
320       surface.SetForeground((textColor[state] ? textColor[state] : textColor[up]));
321       if(text)
322       {
323          if((buttonStyle.radio || buttonStyle.checkBox) && !(buttonStyle.bevelOver))
324             WriteCaption(surface, /*clientSize.h +*/ CAPTION_DISTANCE + 3,
325                (clientSize.h - th - 4)/2);
326          else
327          {
328             int x, y;
329
330             if(buttonStyle.bevelOver && buttonBitmap && !buttonStyle.checkBox && !buttonStyle.radio)
331             {
332                if(bitmapAlignment == top)
333                   y = (clientSize.h - bh - 4 - th - 5)/2 + offset + bh + 4;
334                else if(bitmapAlignment == bottom)
335                   y = (clientSize.h - bh - 4 - th - 5)/2 + offset;
336                else//if(bitmapAlignment == left || bitmapAlignment == right)
337                   y = clientSize.h - th - 5 + offset;
338             }
339             else
340                y = (clientSize.h - th - 1)/2 + offset;
341
342             if(buttonStyle.ellipsis)
343             {
344                int width = clientSize.w - 2*6;
345                int x = 6 + offset;
346
347                surface.WriteTextDots(alignment, x, y, width, text, strlen(text));
348             }
349             else
350             {
351                int width;
352                x = 6 + offset;
353                width = clientSize.w - 2 * 6;
354                if(bitmapAlignment == left || bitmapAlignment == right)
355                {
356                   if(bitmapAlignment == left)
357                      x += bw + 4;
358                   width -= bw + 4;
359                }
360                if(buttonStyle.checkBox || ((buttonStyle.radio /*|| buttonStyle.bevelOver*/) && buttonBitmap))
361                {
362                   x += CAPTION_DISTANCE + 3;
363                }
364
365                if(tw < width)
366                {
367                   if(alignment == right)
368                      x += width - tw - 1;
369                   else if(alignment == center)
370                      x += (width - tw) / 2;
371                }
372                WriteCaption(surface, x, y);
373             }
374          }
375       }
376
377       // Activation Highlight
378       if(isDefault)
379       {
380          surface.SetForeground(black);
381          surface.Rectangle(0,0,clientSize.w-1,clientSize.h-1);
382       }
383       if(!(buttonStyle.bevelOver) && !isRemote)
384       {
385          if(active/* && (text || !(buttonStyle.radio || buttonStyle.checkBox))*/)
386          {
387             int x1,y1,x2,y2;
388             surface.SetForeground(black);
389             surface.LineStipple(0x5555);
390
391             if((buttonStyle.radio || buttonStyle.checkBox) && text)
392             {
393                x1 = /*clientSize.h + */CAPTION_DISTANCE;
394                y1 = 0;
395                x2 = clientSize.w-4;
396                y2 = clientSize.h-4;
397             }
398             else
399             {
400                x1 = 3+offset;
401                y1 = 3+offset;
402                x2 = clientSize.w - 5+offset;
403                y2 = clientSize.h - 5+offset;
404
405                if(buttonStyle.radio || buttonStyle.checkBox)
406                {
407                   x1-=3;
408                   y1-=3;
409                   x2+=1;
410                   y2+=1;
411                }
412             }
413             if((x2 - x1) & 1) x2++;
414             if((y2 - y1) & 1) y2++;
415
416             surface.Rectangle(x1, y1, x2, y2);
417             surface.LineStipple(0);
418          }
419       }
420    }
421
422    /*
423    void TVisionOnRedraw(Window window,Surface surface)
424    {
425       Button * button = (Button *)window->data;
426       int state = this.state;
427       int tw, th;
428
429       int offset = (buttonStyle.bevel && state == down) ? textCellW : 0;
430
431       if(clientSize.h <= textCellH || clientSize.w <= 2*textCellW) offset = 0;
432
433       if(!isEnabled)
434          state = disabled;
435
436       surface.TextOpacity(false);
437       surface.SetForeground((active ? white : black) );
438
439       surface.SetBackground(GREEN);
440       // Background
441       if(buttonStyle.bevel)
442       {
443          if(state == down)
444          {
445             if(clientSize.h > textCellH && clientSize.w > 2*textCellW)
446                surface.Area(textCellW,0,clientSize.w-textCellW,clientSize.h-textCellH-1);
447             else if(!window->background)
448                surface.Area(0,0,clientSize.w-1,clientSize.h-1);
449          }
450          else
451          {
452             if(clientSize.h > textCellH && clientSize.w > 2*textCellW)
453             {
454                surface.Area(0,0,clientSize.w-2*textCellW,clientSize.h-textCellH-1);
455                surface.SetForeground(black);
456                eSurface_DrawingChar(surface, 223);
457                surface.HLine(textCellW,clientSize.w-textCellW,window->h-textCellH);
458                if(clientSize.h>32)
459                {
460                   eSurface_DrawingChar(surface, 219);
461                   surface.VLine(1,clientSize.h-textCellH,clientSize.w-textCellW);
462                }
463                eSurface_DrawingChar(surface, 220);
464                surface.HLine(clientSize.w - textCellW, clientSize.w - textCellW, 0);
465             }
466             else if(!window->background)
467                surface.Area(0,0,clientSize.w-1,clientSize.h-1);
468          }
469       }
470
471       surface.SetForeground(((active || state == down) ? ((BITwindow->buttonStyle, ES_DEFAULT)) ? LT_RED : white) : ((BITwindow->buttonStyle, ES_DEFAULT)) ? LT_CYAN : textColor[state])) );
472
473       if(buttonStyle.checkBox)
474          surface.WriteText(0,0, checked ? "[\373]" : "[ ] = 3);
475       if(buttonStyle.radio)
476          surface.WriteText(0,0, checked ? "(\007)" : "( ) = 3);
477
478       if(text)
479          surface.TextExtent(text,strlen(text),&tw, &th);
480
481       if(buttonStyle.radio || buttonStyle.checkBox)
482          surface.SetForeground(((state == down) ? ((BITwindow->buttonStyle, ES_DEFAULT)) ? LT_RED : white) : ((BITwindow->buttonStyle, ES_DEFAULT)) ? LT_CYAN : textColor[state])));
483
484       if(buttonStyle.radio || buttonStyle.checkBox)
485          WriteCaption(surface, 4*textCellW, (clientSize.h - th - 2)/2);
486       else if(!text)
487       {
488          if(symbol)
489          {
490             tw = textCellW; th = textCellH;
491             surface.WriteTextf((clientSize.w - tw)/2 + offset, (clientSize.h - th - 2)/2 + offset.%c = symbol);
492          }
493       }
494       else
495          WriteCaption(surface,(clientSize.w - tw)/2 + offset,(clientSize.h - th - 2)/2 + offset);
496    }
497    */
498
499    bool OnMouseMove(int x, int y, Modifiers mods)
500    {
501       if(!mods.isSideEffect)
502          return NotifyMouseMove(master, this, x,y, mods);
503       return true;
504    }
505
506    bool OnActivate(bool active, Window swap, bool * goOnWithActivation, bool direct)
507    {
508       //eSystem_Logf("%s %s\n = text, active ? "Activated" : "Deactivated;
509       if(!active)
510       {
511          if(!buttonStyle.toggle)
512          {
513             if(captured)
514             {
515                captured = false;
516                ReleaseCapture();
517             }
518             if(hint && NotifyReleased(master, this, 0, 0, 0))
519             {
520                state = up;
521                hint = false;
522                key = false;
523             }
524          }
525       }
526       Update(null);
527       return true;
528    }
529
530    bool OnLeftButtonDown(int x, int y, Modifiers mods)
531    {
532       if(x >= 0 && y >= 0 && x < size.w && y < size.h)
533       {
534          if(buttonStyle.toggle)
535          {
536             state = (state == down) ? up : down;
537             if(NotifyPushed(master, this, x,y, mods) &&
538                NotifyClicked(master, this, x,y, mods))
539                Update(null);
540          }
541          else
542          {
543             if(key || Capture())
544             {
545                Window master = this.master;
546                if(!key)
547                   captured = true;
548                if(NotifyPushed(master, this, x,y, mods))
549                {
550                   hint = true;
551                   if(!buttonStyle.stayUp)
552                      state = down;
553                   Update(null);
554                }
555             }
556          }
557       }
558       return true;
559    }
560
561    bool OnLeftButtonUp(int x, int y, Modifiers mods)
562    {
563       if(!(buttonStyle.toggle))
564       {
565          bool releasedResult;
566          if(captured)
567          {
568             captured = false;
569             ReleaseCapture();
570          }
571
572          releasedResult = NotifyReleased(master, this, x,y, mods);
573          if(hint)
574          {
575             if(x >= 0 && y >= 0 && x < size.w && y < size.h)
576             {
577                if(buttonStyle.checkBox)
578                   property::checked = !checked;
579                else if(buttonStyle.radio)
580                   property::checked = true;
581
582                // state = mods ? up : over;
583                // TESTING THIS OUT:
584                state = over ? over : up;
585                hint = false;
586                Update(null);
587                if(releasedResult)
588                {
589                   if(NotifyClicked(master, this, x,y, mods))
590                      return true;
591                   else
592                      return false;
593                }
594             }
595             else
596             {
597                state = up;
598                hint = false;
599                Update(null);
600             }
601          }
602       }
603       return true;
604    }
605
606    bool OnLeftDoubleClick(int x, int y, Modifiers mods)
607    {
608       return NotifyDoubleClick(master, this, x,y, mods);
609    }
610
611    bool OnMouseOver(int x, int y, Modifiers mods)
612    {
613       over = true;
614       if(!key)
615       {
616          if(!(buttonStyle.toggle || buttonStyle.stayUp))
617          {
618             if(!hint)
619                state = over;
620             else
621             {
622                state = down;
623             }
624          }
625          else
626          {
627             if(state != down)
628                state = over;
629          }
630
631          if(NotifyMouseOver(master, this, x,y, mods))
632             Update(null);
633       }
634       return true;
635    }
636
637    void OnMouseCaptureLost()
638    {
639       if(captured)
640       {
641          captured = false;
642          if(hint)
643          {
644             state = up;
645             hint = false;
646             Update(null);
647          }
648       }
649    }
650
651    bool OnMouseLeave(Modifiers mods)
652    {
653       if(!key)
654       {
655          if( (!buttonStyle.stayDown || !hint) /*&&  !buttonStyle.toggle */)
656          {
657             if(!(buttonStyle.toggle) || state == over)
658                state = up;
659          }
660
661          if(NotifyMouseLeave(master, this, mods))
662             Update(null);
663       }
664       over = false;
665       return true;
666    }
667
668    bool OnLoadGraphics()
669    {
670       font = fontObject;
671       if(text)
672          display.FontExtent(font,text,strlen(text), &captionWidth, &captionHeight);
673       else
674       {
675          display.FontExtent(font," ", 1, &captionWidth, &captionHeight);
676          captionWidth = 80;
677       }
678       if(bitmapAlignment != center && !buttonStyle.checkBox && !buttonStyle.radio && !buttonStyle.scale)
679       {
680          Bitmap buttonBitmap = GetButtonBitmap();
681          if(buttonBitmap)
682          {
683             if(bitmapAlignment == left || bitmapAlignment == right)
684                captionWidth += buttonBitmap.width + 4;
685             else
686                captionHeight += buttonBitmap.height + 4;
687          }
688       }
689       return true;
690    }
691
692    bool OnResizing(int *width, int *height)
693    {
694       // Checking *width/*height for zero does not work with minClientSize being set
695       Size initSize = this.initSize;
696       if(this.anchor.left.type != none && this.anchor.right.type != none) initSize.w = 1;
697       if(this.anchor.top.type != none && this.anchor.bottom.type != none) initSize.h = 1;
698
699       if(buttonStyle.checkBox || buttonStyle.radio || buttonStyle.bevelOver)
700       {
701          Bitmap bitmap0 = bitmaps[0] ? bitmaps[0].bitmap : null;
702          if(!initSize.w /**width*/ && bitmap0) *width = Max(*width, bitmap0.width);
703          if(!initSize.h /**height*/ && bitmap0) *height = Max(*height, bitmap0.height);
704
705          *height = Max(*height, captionHeight + 2);
706          if(text)
707          {
708             if(guiApp.textMode)
709                *width = Max(*width, /**height + */captionWidth + CAPTION_DISTANCE + 16);
710             else
711                *width = Max(*width, /**height + */captionWidth + CAPTION_DISTANCE + 8);
712          }
713          else
714          {
715             *width = Max(*width, *height);
716             if(guiApp.textMode)
717                *width = Max(*width, 32);
718          }
719       }
720       else
721       {
722          if(!caption && bitmap && bitmap.bitmap)
723          {
724             if(!initSize.w /**width*/)
725                *width = Max(*width, bitmap.bitmap.width);
726             if(!initSize.h /**height*/)
727                *height = Max(*height, bitmap.bitmap.height);
728          }
729          else if(guiApp.textMode && buttonStyle.bevel)
730          {
731             if(!initSize.w /**width*/)
732                *width = Max(*width, captionWidth + 8);
733             if(!initSize.h /**height*/)
734                *height = Max(*height, captionHeight + 16);
735          }
736          else
737          {
738             if(!initSize.w /**width*/)
739                *width = Max(*width, captionWidth + /*4*/12);
740             if(!initSize.h /**height*/)
741                *height = Max(*height, captionHeight + /*6*/ 8);
742          }
743       }
744       return true;
745    }
746
747
748    bool OnKeyUp(Key key, unichar character)
749    {
750       if(key == space || key.code == enter || key.code == keyPadEnter || (key == hotKey && !buttonStyle.keyRepeat))
751       {
752          if(this.key)
753          {
754             this.key = false;
755             /*return */OnLeftButtonUp(0,0, key.modifiers);
756             if(key == hotKey)
757                return false;
758          }
759       }
760       return true;
761    }
762
763    bool OnKeyDown(Key key, unichar character)
764    {
765       if(!key.alt && !key.ctrl)
766       {
767          switch(key.code)
768          {
769             case hotKey:
770                if(buttonStyle.keyRepeat) break;
771             case space:
772             case defaultKey:
773                this.key = true;
774                OnLeftButtonDown(0,0, key.modifiers);
775                if(key.code == hotKey)
776                   return true;
777                else
778                   return false;
779             case escape:
780                if(this.key)
781                {
782                   OnActivate(false, null, null, true);
783                   return false;
784                }
785                break;
786          }
787       }
788       return true;
789    }
790
791    bool OnKeyHit(Key key, unichar character)
792    {
793       switch(key)
794       {
795          case left: case up:
796             if(parent.CycleChildren(false, false, false, !(buttonStyle.bevelOver && buttonStyle.radio)))
797             {
798                if(buttonStyle.bevelOver && buttonStyle.radio)
799                {
800                   Button b = (Button)parent.activeChild;
801                   if(b && b != this && eClass_IsDerived(b._class, class(Button)) && b.buttonStyle.radio && b.buttonStyle.bevelOver)
802                   {
803                      b.OnLeftButtonDown(0,0,(Modifiers)key);   // Casting Key to Modifiers is bad
804                      b.OnLeftButtonUp(0,0,(Modifiers)key);
805                   }
806                }
807                return false;
808             }
809             break;
810          case right: case down:
811             if(parent.CycleChildren(true, false, false, !(buttonStyle.bevelOver && buttonStyle.radio)))
812             {
813                if(buttonStyle.bevelOver && buttonStyle.radio)
814                {
815                   Button b = (Button)parent.activeChild;
816                   if(b && b != this && eClass_IsDerived(b._class, class(Button)) && b.buttonStyle.radio && b.buttonStyle.bevelOver)
817                   {
818                      b.OnLeftButtonDown(0,0,(Modifiers)key);
819                      b.OnLeftButtonUp(0,0,(Modifiers)key);
820                   }
821                }
822                return false;
823             }
824             break;
825          case hotKey:
826             if(buttonStyle.keyRepeat)
827             {
828                this.key = true;
829                OnLeftButtonDown(0,0, key.modifiers);
830                this.key = false;
831                /*return */OnLeftButtonUp(0,0, key.modifiers);
832                return false;
833             }
834             break;
835       }
836       return true;
837    }
838 public:
839    void SetColor(ButtonState state, Color value)
840    {
841       colors[state] = value;
842    }
843
844    void SetTextColor(ButtonState state, Color value)
845    {
846       textColor[state] = value;
847    }
848
849    void RemoveRadio()
850    {
851       if(buttonStyle.radio)
852       {
853          RemoveResource(bitmaps[up]);
854          RemoveResource(bitmaps[down]);
855          RemoveResource(bitmaps[disabled]);
856          RemoveResource(bitmaps[upChecked]);
857          RemoveResource(bitmaps[downChecked]);
858          RemoveResource(bitmaps[disabledChecked]);
859
860          bitmaps[up] = null;
861          bitmaps[down] = null;
862          bitmaps[disabled] = null;
863          bitmaps[upChecked] = null;
864          bitmaps[downChecked] = null;
865          bitmaps[disabledChecked] = null;
866       }
867       buttonStyle.radio = false;
868    }
869
870    // Notifications
871    virtual bool Window::NotifyClicked(Button button, int x, int y, Modifiers mods);
872    virtual bool Window::NotifyDoubleClick(Button button, int x, int y, Modifiers mods);
873    virtual bool Window::NotifyPushed(Button button, int x, int y, Modifiers mods);
874    virtual bool Window::NotifyReleased(Button button, int x, int y, Modifiers mods);
875    virtual bool Window::NotifyMouseMove(Button button, int x, int y, Modifiers mods);
876    virtual bool Window::NotifyMouseOver(Button button, int x, int y, Modifiers mods);
877    virtual bool Window::NotifyMouseLeave(Button button, Modifiers mods);
878
879    // Properties
880    property bool isRadio
881    {
882       property_category $"Behavior"
883       set
884       {
885          if(value)
886          {
887             if(!bitmaps[up])
888             {
889                RemoveResource(bitmaps[up]);
890                RemoveResource(bitmaps[down]);
891                RemoveResource(bitmaps[disabled]);
892                RemoveResource(bitmaps[upChecked]);
893                RemoveResource(bitmaps[downChecked]);
894                RemoveResource(bitmaps[disabledChecked]);
895
896                bitmaps[up] = BitmapResource { };
897                bitmaps[down] = BitmapResource { };
898                bitmaps[disabled] = BitmapResource { };
899                bitmaps[upChecked] = BitmapResource { };
900                bitmaps[downChecked] = BitmapResource { };
901                bitmaps[disabledChecked] = BitmapResource { };
902                bitmaps[up].fileName = "<:ecere>elements/optionBoxUp.png";
903                bitmaps[down].fileName = "<:ecere>elements/optionBoxDown.png";
904                bitmaps[disabled].fileName = "<:ecere>elements/optionBoxDisabled.png";
905                bitmaps[upChecked].fileName = "<:ecere>elements/optionBoxSelectedUp.png";
906                bitmaps[downChecked].fileName = "<:ecere>elements/optionBoxSelectedDown.png";
907                bitmaps[disabledChecked].fileName = "<:ecere>elements/optionBoxDisabledSelected.png";
908
909                AddResource(bitmaps[up]);
910                AddResource(bitmaps[down]);
911                AddResource(bitmaps[disabled]);
912                AddResource(bitmaps[upChecked]);
913                AddResource(bitmaps[downChecked]);
914                AddResource(bitmaps[disabledChecked]);
915             }
916
917             buttonStyle.radio = true;
918             buttonStyle.checkBox = false;
919             buttonStyle.bevel = false;
920             opacity = 0;
921             drawBehind = true;
922          }
923          else
924          {
925             RemoveRadio();
926          }
927          OnLoadGraphics();
928
929          SetInitSize(initSize);
930       }
931       get { return buttonStyle.radio; }
932    };
933    property bool isCheckbox
934    {
935       property_category $"Behavior"
936       set
937       {
938          if(value)
939          {
940             buttonStyle.checkBox = true;
941             background = white;
942             RemoveRadio();
943             buttonStyle.radio = false;
944             buttonStyle.bevel = false;
945             opacity = 0;
946             drawBehind = true;
947          }
948          else
949             buttonStyle.checkBox = false;
950          OnLoadGraphics();
951
952          SetInitSize(initSize);
953       }
954       get { return buttonStyle.checkBox; }
955    };
956    property bool bevel
957    {
958       property_category $"Behavior"
959       set
960       {
961          if(value)
962          {
963             buttonStyle.bevel = true;
964             RemoveRadio();
965             buttonStyle.checkBox = false;
966             buttonStyle.radio = false;
967             buttonStyle.bevelOver = false;
968             opacity = 1;
969             drawBehind = false;
970          }
971          else
972          {
973             buttonStyle.bevel = false;
974             opacity = 0;
975             drawBehind = true;
976          }
977       }
978       get { return buttonStyle.bevel; }
979    };
980    property bool bevelOver
981    {
982       property_category $"Behavior"
983       set
984       {
985          if(value)
986          {
987             buttonStyle.bevelOver = true;
988             buttonStyle.offset = true;
989             buttonStyle.bevel = false;
990          }
991          else
992          {
993             buttonStyle.bevelOver = false;
994             buttonStyle.offset = false;
995          }
996       }
997       get { return buttonStyle.bevelOver; }
998    };
999    property bool toggle { property_category $"Behavior" set { buttonStyle.toggle = value; } get { return buttonStyle.toggle; } };
1000    property bool checked
1001    {
1002       property_category $"Appearance"
1003       set
1004       {
1005          if(buttonStyle.toggle)
1006          {
1007             state = value ? down : up;
1008          }
1009          else
1010          {
1011             if(buttonStyle.radio && value)
1012             {
1013                Window parent = this.parent;
1014                if(parent)
1015                {
1016                   Window otherWindow;
1017                   for(otherWindow = parent.firstChild; otherWindow; otherWindow = otherWindow.next)
1018                   {
1019                      if(otherWindow != this && eClass_IsDerived(otherWindow._class, _class))
1020                      {
1021                         Button otherButton = (Button)otherWindow;
1022                         if(otherButton.buttonStyle.radio)
1023                            otherButton.checked = false;
1024                      }
1025                   }
1026                }
1027             }
1028             checked = value;
1029          }
1030          Update(null);
1031       }
1032       get { return buttonStyle.toggle ? (state == down) : checked; }
1033    };
1034    property Alignment alignment { property_category $"Appearance" set { alignment = value; } get { return alignment; } };
1035    property BitmapResource bitmap
1036    {
1037       property_category $"Appearance"
1038       set
1039       {
1040          AddResource(value);
1041          RemoveResource(bitmaps[up]);
1042          bitmaps[up] = value;
1043
1044          RemoveResource(bitmaps[over]);
1045          RemoveResource(bitmaps[down]);
1046          RemoveResource(bitmaps[disabled]);
1047          RemoveResource(bitmaps[upChecked]);
1048          RemoveResource(bitmaps[overChecked]);
1049          RemoveResource(bitmaps[downChecked]);
1050          RemoveResource(bitmaps[disabledChecked]);
1051
1052          bitmaps[over] = null;
1053          bitmaps[down] = null;
1054          bitmaps[disabled] = null;
1055          bitmaps[upChecked] = null;
1056          bitmaps[overChecked] = null;
1057          bitmaps[downChecked] = null;
1058          bitmaps[disabledChecked] = null;
1059
1060          /*
1061          if(buttonStyle.radio && !value)
1062          {
1063             AddResource((bitmaps[up] = BitmapResource { fileName = "<:ecere>elements/optionBox.png" }));
1064             AddResource((bitmaps[down] = BitmapResource { fileName = "<:ecere>elements/optionBox.png" }));
1065             AddResource((bitmaps[disabled] = BitmapResource { fileName = "<:ecere>elements/optionBoxDisabled.png" }));
1066             AddResource((bitmaps[upChecked] = BitmapResource { fileName = "<:ecere>elements/optionBoxSelected.png" }));
1067             AddResource((bitmaps[downChecked] = BitmapResource { fileName = "<:ecere>elements/optionBoxSelected.png" }));
1068             AddResource((bitmaps[disabledChecked] = BitmapResource { fileName = "<:ecere>elements/optionBoxDisabledSelected.png" }));
1069          }
1070          else */if(!bitmaps[disabled])
1071          {
1072             bitmaps[disabled] = BitmapResource { fileName = bitmap.fileName, grayed = true };
1073             AddResource(bitmaps[disabled]);
1074          }
1075       }
1076       get { return bitmaps[up]; }
1077    };
1078    property bool stayUp { property_category $"Behavior" set { buttonStyle.stayUp = value; } get { return buttonStyle.stayUp; } };
1079    property bool scaleBitmap { property_category $"Appearance" set { buttonStyle.scale = value; } get { return buttonStyle.scale; } };
1080    property bool keyRepeat { property_category $"Behavior" set { buttonStyle.keyRepeat = value; } get { return buttonStyle.keyRepeat; } };
1081    property unichar symbol { property_category $"Appearance" set { this.symbol = value; } get { return symbol; } };
1082    property bool ellipsis { property_category $"Appearance" set { buttonStyle.ellipsis = value; } get { return buttonStyle.ellipsis; } };
1083    property bool stayDown { property_category $"Behavior" set { buttonStyle.stayDown = value; } get { return buttonStyle.stayDown; } };
1084    property bool offset { property_category $"Behavior" set { buttonStyle.offset = value; } get { return buttonStyle.offset; } };
1085
1086    property ButtonState buttonState { get { return state; } set { state = value; } };
1087    property BoxAlignment bitmapAlignment { property_category $"Appearance" set { bitmapAlignment = value; } get { return bitmapAlignment; } };
1088 };