ecere/gui: Fixed broken caption property watches (they were still referring to 'text'!)
[sdk] / ecere / src / gui / controls / TabControl.ec
1 namespace gui::controls;
2
3 import "Window"
4
5 public enum TabsPlacement
6 {
7    top, bottom, left, right
8 };
9
10 class TabButton : Button
11 {
12    Tab tab;
13
14    borderStyle = 0;
15    opacity = 0;
16    drawBehind = true;
17
18    bool OnKeyHit(Key key, unichar ch)
19    {
20       return true;
21    }
22    
23    bool OnLeftButtonDown(int x, int y, Modifiers mods)
24    {
25       Button::OnLeftButtonDown(x, y, mods);
26       Button::OnLeftButtonUp(x, y, mods);
27       return true;
28    }
29
30    void OnRedraw(Surface surface)
31    {
32       ColorKey keys[2] = { { silver, 0.0f }, { white, 1.0f } };
33       ColorKey rKeys[2] = { { white, 0.0f }, { silver, 1.0f } };
34       // Text
35       int tw = 0, th = 0;
36       int x, y;
37       int w = clientSize.w, h = clientSize.h;
38       bool checked = this.checked;
39       ButtonState state = buttonState;
40       if(state == down) state = over;
41
42       surface.SetForeground(gray);
43
44       x = 0;
45       y = 0;
46
47       switch(((TabControl)parent.parent).placement)
48       {
49          case bottom:
50             if(checked) w++;
51             surface.DrawLine(x,0,    x,h-3);
52             surface.DrawLine(w-2,0,  w-2,h-3);
53             break;
54          case top:
55             if(checked) w++;
56             surface.DrawLine(x,2,    x,h-1);
57             surface.DrawLine(w-2,2,  w-2,h-1);
58             break;
59          case right:
60             if(checked) h++;
61             surface.DrawLine(0,y,    w-3,y);
62             surface.DrawLine(0,h-2,  w-3,h-2);
63             break;
64          case left:
65             if(checked) h++;
66             surface.DrawLine(2,y,    w-1,y);
67             surface.DrawLine(2,h-2,  w-1,h-2);
68             break;
69       }
70       if(!checked)
71       {
72          if(state == over)
73          {
74             keys[0].color = lightGray;
75             rKeys[1].color = lightGray;
76          }
77          switch(((TabControl)parent.parent).placement)
78          {
79             case bottom: surface.Gradient(keys, sizeof(keys) / sizeof(ColorKey), 1, vertical, x+1, 0, w-3, h - 3); break;
80             case top:    surface.Gradient(rKeys, sizeof(rKeys) / sizeof(ColorKey), 1, vertical, x+1, 2, w-3, h - 1); break;
81
82             case right:  surface.Gradient(keys, sizeof(keys) / sizeof(ColorKey), 1, horizontal, 0, y+1, w-3, h - 3); break;
83             case left:   surface.Gradient(rKeys, sizeof(rKeys) / sizeof(ColorKey), 1, horizontal, 2, y+1, w-1, h - 3); break;
84          }
85       }
86       else
87       {
88          surface.SetBackground(background /*white*/);
89          switch(((TabControl)parent.parent).placement)
90          {
91             case bottom: surface.Area(x+1, 0, w-3, h - 3); break;
92             case top:    surface.Area(x+1, 2, w-3, h - 1); surface.foreground = white; surface.VLine(2, h - 1, x+1); break;
93             case right:  surface.Area(0, y+1, w-3, h - 3); break;
94             case left:   surface.Area(2, y+1, w-1, h - 3); break;
95          }
96       }
97
98       if(state == over || checked)
99          surface.SetForeground(lightBlue);
100       else
101          surface.SetForeground(white);
102
103       switch(((TabControl)parent.parent).placement)
104       {
105          case bottom:
106             surface.DrawLine(x+2,h-2, w-4, h-2);
107             surface.DrawLine(x+1,  h-3, w-3, h-3);
108             break;
109          case top:
110             surface.DrawLine(x+2,1, w-4, 1);
111             surface.DrawLine(x+1,2, w-3, 2);
112             break;
113          case right:
114             surface.DrawLine(w-2, y+2,  w-2, h-4);
115             surface.DrawLine(w-3, y+1,  w-3, h-3);
116             break;
117          case left:
118             surface.DrawLine(1, y+2,  1, h-4);
119             surface.DrawLine(2, y+1,  2, h-3);
120             break;
121       }
122       if(state == over || checked)
123          surface.SetForeground(slateBlue);
124       else
125          surface.SetForeground(gray);
126
127       switch(((TabControl)parent.parent).placement)
128       {
129          case bottom:
130             surface.DrawLine(x+2,h-1, w-4, h-1);
131             surface.PutPixel(x,h-3);
132             surface.PutPixel(x+1,h-2);
133             
134             surface.PutPixel(w-2, h - 3);
135             surface.PutPixel(w-3, h - 2);
136             break;
137          case top:
138             surface.DrawLine(x+2,0, w-4,0);
139             surface.PutPixel(x, 2);
140             surface.PutPixel(x+1, 1);
141             
142             surface.PutPixel(w-2, 2);
143             surface.PutPixel(w-3, 1);
144             break;
145          case right:
146             surface.DrawLine(w-1,y+2, w-1, h-4);
147             surface.PutPixel(w-3, y);
148             surface.PutPixel(w-2, y+1);
149             
150             surface.PutPixel(w - 3, h-2);
151             surface.PutPixel(w - 2, h-3);
152             break;
153          case left:
154             surface.DrawLine(0,y+2, 0, h-4);
155             surface.PutPixel(2, y);
156             surface.PutPixel(1, y+1);
157             
158             surface.PutPixel(2, h-2);
159             surface.PutPixel(1, h-3);
160             break;
161       }
162
163       surface.SetForeground(foreground);
164       if(text)
165          surface.TextExtent(text, strlen(text),&tw, &th);
166       y = (clientSize.h - th - 1)/2 + (checked ? 0 : -2);
167       
168       if(ellipsis)
169       {
170          int width = clientSize.w - 2*6;
171          int x = 6;
172
173          surface.WriteTextDots(alignment, x, y, width, text, strlen(text));
174       }
175       else
176       {
177          int width = clientSize.w - 2 * 6;
178          x = 6;
179
180          if(tw < width)
181          {
182             if(alignment == right)
183                x += width - tw - 1;
184             else if(alignment == center)
185                x += (width - tw) / 2;
186          }
187          WriteCaption(surface, x, y);
188       }
189
190       // Draw Active Stipple
191       if(active)
192       {
193          int x1 = 5;
194          int y1 = 4;
195          int x2 = clientSize.w - 6;
196          int y2 = clientSize.h - 5;
197          if((x2 - x1) & 1) x2++;
198          if((y2 - y1) & 1) y2++;
199
200          surface.LineStipple(0x5555);
201          surface.Rectangle(x1, y1, x2, y2);
202          surface.LineStipple(0);            
203       }
204    }
205 }
206
207 static void PlaceButton(TabButton button, TabsPlacement placement, bool selected, int buttonsOffset)
208 {
209    int of = (selected) ? 0 : 1;
210    switch(placement)
211    {
212       case top:
213          button.size = selected ? { 74, 25 } : { 70, 22 };
214          button.anchor = Anchor { left = buttonsOffset + button.tab.id * 70 + 2*of, bottom = 0 };
215          break;
216       case bottom:
217          button.size = selected ? { 74, 25 } : { 70, 22 };
218          button.anchor = Anchor { left = buttonsOffset + button.tab.id * 70 + 2*of, top = 0 };
219          break;
220       case left:
221          button.size = selected ? { 73, 26 } : { 70, 22 };
222          button.anchor = Anchor { top = buttonsOffset + button.tab.id * 22 + 2*of, right = 0 };
223          break;
224       case right:
225          button.size = selected ? { 73, 26 } : { 70, 22 };
226          button.anchor = Anchor { top = buttonsOffset + button.tab.id * 22 + 2*of, left = 0 };
227          break;
228    }
229 }
230
231 static define skinMainColor = Color { 0, 71, 128 };
232 static define skinBackground = Color { 255, 255, 255 };
233 static define skinTextColor = skinMainColor;
234 static define skinInactiveTextColor = Color { skinMainColor.r - 20, skinMainColor.g - 20, skinMainColor.b - 20 };
235
236 #define CAPTION      14
237 #define NAME_OFFSET   2
238 #define NAME_OFFSETX  4
239 #define SB_WIDTH  16
240 #define SB_HEIGHT 16
241 #define MENU_HEIGHT     25
242 #define STATUS_HEIGHT   18
243
244 public class TabControl : Window
245 {
246    TabsPlacement placement;
247
248    tabCycle = true;
249    int numTabs;
250
251    background = white;//formColor;
252
253    Window tabButtons { this, opacity = 0, drawBehind = true };
254
255    Tab curTab;
256    TabButton curButton;
257
258    int buttonsOffset;
259
260    public property TabsPlacement placement { set { placement = value; } get { return placement; } }
261    public property int buttonsOffset { set { buttonsOffset = value; } get { return buttonsOffset; } }
262    public property Tab curTab
263    {
264       set
265       {
266          if(value)
267          {
268             if(value.button)
269             {
270                // value.button.Activate();
271                value.button.MakeActive();
272                value.button.NotifyClicked(value.button.master, value.button, 0,0, 0);
273             }
274          }
275       }
276       get { return curTab; }
277    }
278
279    bool IsInside(int x, int y)
280    {
281       if(Window::IsInside(x, y))
282       {
283          Point tbAbsPos = tabButtons.absPosition;
284          x += absPosition.x - tbAbsPos.x;
285          y += absPosition.y - tbAbsPos.y;
286
287          if(tabButtons.IsInside(x, y))
288          {
289             Window button;
290             for(button = tabButtons.firstChild; button; button = button.next)
291             {
292                int bx = x + tbAbsPos.x - button.absPosition.x;
293                int by = y + tbAbsPos.y - button.absPosition.y;
294                if(button.IsInside(bx, by))
295                   return true;
296             }
297          }
298          else
299             return true; 
300       }
301       return false;
302    }
303
304    void ShowDecorations(Font captionFont, Surface surface, char * name, bool active, bool moving)
305    {
306       if(placement == bottom && ((BorderBits)borderStyle).fixed)
307       {
308          bool isNormal = (state == normal || state == maximized);
309          int top = 0, border = 0, bottom = 0;
310          if(state == minimized)
311             top = border = bottom = 0;
312          else if(((BorderBits)borderStyle).sizable)
313          {
314             top = 0;
315             border = 0;
316             bottom = 0;
317          }
318          else if(((BorderBits)borderStyle).fixed)
319          {
320             top = 0;
321             border = 0;
322             bottom = 0;
323          }
324
325          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
326          {
327             surface.SetForeground(gray);
328             surface.Rectangle(0,1, size.w-1, CAPTION+1);
329             
330             surface.SetForeground(white);
331             surface.Rectangle(1, 1, clientSize.w-2, CAPTION-1);
332
333             surface.SetBackground(skinBackground);         
334             surface.Area(2, 2, size.w-3, CAPTION+1);
335             
336             surface.SetForeground((active ? skinTextColor : skinInactiveTextColor));
337             surface.TextOpacity(false);
338             surface.TextFont(captionFont);
339             if(name)
340             {
341                int buttonsSize = border +
342                   ((hasMaximize || hasMinimize) ? 52 : 18);
343                surface.WriteTextDots(left, border + NAME_OFFSETX, top + NAME_OFFSET, 
344                   size.w - (buttonsSize + border + 4), name, strlen(name));
345             }
346          }
347
348          if(state != minimized && hasHorzScroll && hasVertScroll)
349          {
350             if(sbh && sbh.visible && sbv && sbv.visible)
351             {
352                surface.SetBackground(formColor);
353                surface.Area(
354                   clientStart.x + clientSize.w,
355                   clientStart.y + clientSize.h,
356                   clientStart.x + clientSize.w + SB_WIDTH - 1,
357                   clientStart.y + clientSize.h + SB_HEIGHT - 1);
358             }
359          }
360       }
361       else
362       {
363          Window::ShowDecorations(captionFont, surface, name, active, moving);
364       }
365
366       {
367          surface.SetForeground(white);
368          surface.Rectangle(
369             clientStart.x + 1 + (placement == left) * 80, 
370             clientStart.y + 1 + (placement == top) * 30, 
371             clientStart.x + clientSize.w - (placement == right) * 80 - 2, 
372             clientStart.y + clientSize.h - (placement == bottom) * 30 - 2);
373          surface.SetForeground(gray);
374          surface.Rectangle(
375             clientStart.x + (placement == left) * 80, 
376             clientStart.y + (placement == top) * 30, 
377             clientStart.x + clientSize.w - (placement == right) * 80 - 1, 
378             clientStart.y + clientSize.h - (placement == bottom) * 30 - 1);
379       }
380
381       if(curButton)
382       {
383          Box box;
384          int id = curTab ? curTab.id : 0;
385          Button button = curButton;
386          int x = button.position.x;
387          int y = button.position.y;
388          
389          switch(placement)
390          {
391             case TabsPlacement::bottom:
392                box = { /*((id == 0) ? 0 : */x/*)*/ + 1 + clientStart.x, clientSize.h-32 + clientStart.y + 1, x + clientStart.x + button.size.w - 2, clientSize.h-28 + clientStart.y + 1 };
393                break;
394             case TabsPlacement::top:
395                box = { /*((id == 0) ? 0 : */x/*)*/ + 1 + clientStart.x, clientStart.y + 30, x + clientStart.x + button.size.w - 2, clientStart.y + 31 };
396                break;
397             case TabsPlacement::left:
398                box = { 78 + clientStart.x, /*((id == 0) ? 0 : */y/*)*/ + 1 + clientStart.y, 81 + clientStart.x, y + button.size.h + clientStart.y - 2 };
399                break;
400             case TabsPlacement::right:
401                box = { clientSize.w - 80, /*((id == 0) ? 0 : */y/*)*/ + 1 + clientStart.y, clientSize.w - 78 + clientStart.x + 1, y + clientStart.y + button.size.h - 2 };
402                break;
403          }
404          surface.Clip(box);
405          surface.SetBackground(background /*white*/);
406          surface.Clear(colorBuffer);
407          surface.Clip(null);
408
409          surface.SetForeground(white);
410          switch(placement)
411          {
412             case TabsPlacement::bottom:   surface.VLine(clientSize.h-32 + clientStart.y + 1, clientSize.h-28 + clientStart.y + 1, x + 1 + clientStart.x); break; 
413             case TabsPlacement::top:      surface.VLine(clientStart.y + 30, clientStart.y + 31, x + 1 + clientStart.x); break;
414             case TabsPlacement::left:     surface.HLine(78 + clientStart.x, 81 + clientStart.x, y + 1 + clientStart.y); break;
415             case TabsPlacement::right:    surface.HLine(clientStart.y + 30, clientStart.y + 31, y + 1 + clientStart.y); break;
416          }
417       }
418    }
419
420    void GetDecorationsSize(MinMaxValue * w, MinMaxValue * h)
421    {
422       if(placement == bottom && ((BorderBits)borderStyle).fixed)
423       {
424          *w = *h = 0;
425          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
426             *h += CAPTION;
427          if(hasMenuBar && state != minimized)
428             *h += MENU_HEIGHT;
429          if(statusBar && state != minimized)
430             *h += STATUS_HEIGHT;
431       }
432       else
433          Window::GetDecorationsSize(w, h);
434    }
435
436    bool IsMouseResizing(int x, int y, int w, int h, bool *resizeX, bool *resizeY, bool *resizeEndX, bool *resizeEndY)
437    {
438       if(placement == bottom && ((BorderBits)borderStyle).fixed)
439       {
440          bool result = false;
441
442          *resizeX = *resizeY = *resizeEndX = *resizeEndY = false;
443
444          if(((BorderBits)borderStyle).sizable && (state == normal))
445          {
446             // TopLeft Corner
447             if(Box { 0, 0,2, 2 }.IsPointInside({x, y}))
448                result = *resizeX = *resizeY = true;
449             // TopRight Corner
450             if(Box { w-2, 0, w-1, 2 }.IsPointInside({x, y}))
451                result = *resizeEndX = *resizeY = true;
452             // BottomLeft Corner
453             if(Box { 0, h-2, 1, h-1 }.IsPointInside({x, y}))
454                result = *resizeX = *resizeEndY = true;
455             // BottomRight Corner
456             if(Box { w-2, h-32, w-1, h-25 }.IsPointInside({x, y}))
457                result = *resizeEndX = *resizeEndY = true;
458
459             // Left Border
460             if(Box { 0,0, 1, h-1 }.IsPointInside({x, y}))
461                result = *resizeX = true;
462             // Right Border
463             if(Box { w-1, 0, w-2, h-1 }.IsPointInside({x, y}))
464                result = *resizeEndX = true;
465             // Top Border
466             if(Box { 0, 0, w-1, 2 }.IsPointInside({x, y}))
467                result = *resizeY = true;
468             // Bottom Border
469             if(Box { numTabs * 70, h-32, w-2, h-25 }.IsPointInside({x, y}))
470                result = *resizeEndY = true;
471          }
472          return result;
473       }
474       else
475          return Window::IsMouseResizing(x, y, w, h, resizeX, resizeY, resizeEndX, resizeEndY);
476    }
477
478    void SetWindowArea(int * x, int * y, MinMaxValue * w, MinMaxValue * h, MinMaxValue * cw, MinMaxValue * ch)
479    {
480       if(placement == bottom && ((BorderBits)borderStyle).fixed)
481       {
482          bool isNormal = (state == normal || state == maximized);
483          MinMaxValue aw = 0, ah = 0;
484
485          *x = *y = 0;
486
487          GetDecorationsSize(&aw, &ah);
488
489          if(hasMenuBar)
490             *y += MENU_HEIGHT;
491
492          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
493             *y += CAPTION;
494
495          if(((BorderBits)borderStyle).contour && !((BorderBits)borderStyle).fixed)
496          {
497             *x += 1;
498             *y += 1;
499          }
500
501          // Reduce client area
502          *cw = *w - aw;
503          *ch = *h - ah;
504
505          *cw = Max(*cw, 0);
506          *ch = Max(*ch, 0);
507       }
508       else
509          Window::SetWindowArea(x, y, w, h, cw, ch);
510    }
511
512    bool OnCreate()
513    {
514       switch(placement)
515       {
516          case top:
517             tabButtons.anchor = { top = 5, left = 0, right = 5 };
518             tabButtons.size = { h = 25 };
519             break;
520          case bottom:
521             tabButtons.anchor = { bottom = 5, left = 0, right = 5 };
522             tabButtons.size = { h = 25 };
523             break;
524          case left:
525             tabButtons.anchor = { top = 0, left = 5, bottom = 5 };
526             tabButtons.size = { w = 75 };
527             break;
528          case right:
529             tabButtons.anchor = { top = 0, bottom = 5, right = 5 };
530             tabButtons.size = { w = 75 };
531             break;
532       }
533       return true;
534    }
535
536    bool OnPostCreate()
537    {
538       // curButton.Activate();
539       curButton.MakeActive();
540       return true;
541    }
542
543    bool NotifyClicked(Button button, int x, int y, Modifiers mods)
544    {
545       if(curTab == (Tab)button.id)
546          return true;
547       //curButton.Activate();
548       curButton.MakeActive();
549
550       if(curTab.Destroy(0))
551       {
552          curButton.checked = false;
553          button.checked = true;
554          curTab = (Tab)button.id;
555
556          if(curButton)
557             PlaceButton(curButton, placement, false, buttonsOffset);
558
559          curButton = (TabButton)button;
560          curButton.MakeActive();
561          // curButton.Activate();
562
563          PlaceButton(curButton, placement, true, buttonsOffset);
564          Update(null);
565
566          curTab.Create();
567       }
568       else
569       {
570          return false;
571       }
572       return true;
573    }
574
575    public void AddTab(Tab tab)
576    {
577       tab.parent = this;
578       tab.autoCreate = false;
579       tab.id = numTabs;
580       tab.button = TabButton
581       {
582          parent = tabButtons,
583          master = this, stayDown = true,
584          text = tab.text, id = (uint)tab, NotifyClicked = NotifyClicked,
585          tab = tab,
586          background = background;
587       };
588       incref tab.button;
589
590       if(created)
591       {
592          tab.button.Create();
593          incref tab;
594       }
595
596       numTabs++;
597       if(!curTab)
598       {
599          curTab = tab;
600          curButton = tab.button;
601          tab.button.checked = true;
602          tab.autoCreate = true;
603       }
604
605       PlaceButton(tab.button, placement, curTab == tab, buttonsOffset);
606       switch(placement)
607       {
608          case top: tab.anchor = { left = 2, bottom = 2, right = 2, top = 32 }; break;
609          case bottom: tab.anchor = { left = 2, bottom = 32, right = 2, top = 2 }; break;
610          case left: tab.anchor = { left = 82, bottom = 2, right = 2, top = 2 }; break;
611          case right: tab.anchor = { left = 2, bottom = 2, right = 82, top = 2 }; break;
612       }
613    }
614
615    public void RemoveTab(Tab tab)
616    {
617       Window child;
618       Tab fallbackTab = null;
619       tab.parent = null;
620       for(child = tabButtons.children.first; child; child = child.next)
621       {
622          if(child._class == class(TabButton))
623          {
624             TabButton button = (TabButton)child;
625             if(button.id == (uint)tab)
626             {
627                if(button.created)
628                   button.Destroy(0);
629                break;
630             }
631             else
632                fallbackTab = button.tab;
633          }
634       }
635       if(curTab == tab)
636       {
637          if(!fallbackTab)
638             fallbackTab = tabButtons.children.first ? ((TabButton)tabButtons.children.first).tab : null;
639          if(fallbackTab)
640             fallbackTab.SelectTab();
641          /*curTab = fallbackTab;
642          curButton = curTab.button;
643          curButton.checked = true;
644          curTab.autoCreate = true;*/
645       }
646       numTabs--;
647    }
648
649    ~TabControl()
650    {
651       Window child, next;
652       for(child = tabButtons.firstChild; child; child = next)
653       {
654          next = child.next;
655          delete child;
656       }
657
658       for(child = firstChild; child; child = next)
659       {
660          next = child.next;
661          if(eClass_IsDerived(child._class, class(Tab)))
662             child.parent = null;
663       }
664    }
665
666    bool OnKeyHit(Key key, unichar ch)
667    {
668       SmartKey smartKey = (SmartKey)key;
669       if(tabButtons.active && (smartKey == left || smartKey == right))
670       {
671          // BIG MESS... USE AN ARRAY INSTEAD?
672          TabButton newButton = null, button;
673          int cleft = curButton.anchor.left.distance, ctop = curButton.anchor.top.distance;
674
675          for(button = (TabButton)tabButtons.firstChild; button; button = (TabButton)button.next)
676          {
677             int pleft = button.anchor.left.distance, nleft = newButton ? newButton.anchor.left.distance : 0;
678             int ptop = button.anchor.top.distance, ntop = newButton ? newButton.anchor.top.distance : 0;
679             if(button == curButton) continue;
680             if((smartKey == left && (pleft < cleft || ptop < ctop) && (!newButton || pleft > nleft || ptop > ntop)) || 
681                (smartKey == right && (pleft > cleft || ptop > ctop) && (!newButton || pleft < nleft || pleft < ptop)))
682                newButton = button;
683          }
684          if(!newButton)
685          {
686             for(button = (TabButton)tabButtons.firstChild; button; button = (TabButton)button.next)
687             {
688                int pleft = button.anchor.left.distance, nleft = newButton ? newButton.anchor.left.distance : 0;
689                int ptop = button.anchor.top.distance, ntop = newButton ? newButton.anchor.top.distance : 0;
690                if(button == curButton) continue;
691                if(!newButton || (smartKey == right && (pleft < nleft || ptop < ntop)) || (smartKey == left && (pleft > nleft || ptop > ntop)))
692                   newButton = button;
693             }
694          }
695
696          if(newButton)
697          {
698             property::curTab = newButton.tab;
699             tabButtons.MakeActive();
700             // tabButtons.Activate();
701          }
702          return false;
703       }
704       return true;
705    }
706 }
707
708 public class Tab : Window
709 {
710    TabButton button;
711    TabControl tabControl;
712
713    //borderStyle = contour;
714    //background = lightBlue;
715    tabCycle = true;
716
717    watch(parent)
718    {
719       if(parent && eClass_IsDerived(parent._class, class(TabControl)))
720       {
721          tabControl = (TabControl)parent;
722          tabControl.AddTab(this);
723       }
724       else if(!parent && tabControl)
725       {
726          tabControl.RemoveTab(this);
727          tabControl = null;
728       }
729    };
730
731    public property TabControl tabControl
732    {
733       set { parent = value; }
734       get { return (TabControl)parent; }
735    }
736
737    public void SelectTab()
738    {
739       button.NotifyClicked(button.master, button, 0, 0, 0);
740    }
741
742    watch(caption)
743    {
744       if(button)
745          button.text = text;
746    };
747 }