ecere/gui/controls/TabControl: Tweaks to remove and add tabs
[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 + (int)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 + (int)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 + (int)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 + (int)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 { 0, 51, 108 };
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, const char * name, bool active, bool moving)
305    {
306       if(placement == bottom && ((BorderBits)borderStyle).fixed)
307       {
308          int top = 0, border = 0, bottom = 0;
309          if(state == minimized)
310             top = border = bottom = 0;
311          else if(((BorderBits)borderStyle).sizable)
312          {
313             top = 0;
314             border = 0;
315             bottom = 0;
316          }
317          else if(((BorderBits)borderStyle).fixed)
318          {
319             top = 0;
320             border = 0;
321             bottom = 0;
322          }
323
324          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
325          {
326             surface.SetForeground(gray);
327             surface.Rectangle(0,1, size.w-1, CAPTION+1);
328
329             surface.SetForeground(white);
330             surface.Rectangle(1, 1, clientSize.w-2, CAPTION-1);
331
332             surface.SetBackground(skinBackground);
333             surface.Area(2, 2, size.w-3, CAPTION+1);
334
335             surface.SetForeground((active ? skinTextColor : skinInactiveTextColor));
336             surface.TextOpacity(false);
337             surface.TextFont(captionFont);
338             if(name)
339             {
340                int buttonsSize = border +
341                   ((hasMaximize || hasMinimize) ? 52 : 18);
342                surface.WriteTextDots(left, border + NAME_OFFSETX, top + NAME_OFFSET,
343                   size.w - (buttonsSize + border + 4), name, strlen(name));
344             }
345          }
346
347          if(state != minimized && hasHorzScroll && hasVertScroll)
348          {
349             if(sbh && sbh.visible && sbv && sbv.visible)
350             {
351                surface.SetBackground(formColor);
352                surface.Area(
353                   clientStart.x + clientSize.w,
354                   clientStart.y + clientSize.h,
355                   clientStart.x + clientSize.w + SB_WIDTH - 1,
356                   clientStart.y + clientSize.h + SB_HEIGHT - 1);
357             }
358          }
359       }
360       else
361       {
362          Window::ShowDecorations(captionFont, surface, name, active, moving);
363       }
364
365       {
366          surface.SetForeground(white);
367          surface.Rectangle(
368             clientStart.x + 1 + (placement == left) * 80,
369             clientStart.y + 1 + (placement == top) * 30,
370             clientStart.x + clientSize.w - (placement == right) * 80 - 2,
371             clientStart.y + clientSize.h - (placement == bottom) * 30 - 2);
372          surface.SetForeground(gray);
373          surface.Rectangle(
374             clientStart.x + (placement == left) * 80,
375             clientStart.y + (placement == top) * 30,
376             clientStart.x + clientSize.w - (placement == right) * 80 - 1,
377             clientStart.y + clientSize.h - (placement == bottom) * 30 - 1);
378       }
379
380       if(curButton)
381       {
382          Box box;
383          //int id = curTab ? (int)curTab.id : 0;
384          Button button = curButton;
385          int x = button.position.x;
386          int y = button.position.y;
387
388          switch(placement)
389          {
390             case TabsPlacement::bottom:
391                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 };
392                break;
393             case TabsPlacement::top:
394                box = { /*((id == 0) ? 0 : */x/*)*/ + 1 + clientStart.x, clientStart.y + 30, x + clientStart.x + button.size.w - 2, clientStart.y + 31 };
395                break;
396             case TabsPlacement::left:
397                box = { 78 + clientStart.x, /*((id == 0) ? 0 : */y/*)*/ + 1 + clientStart.y, 81 + clientStart.x, y + button.size.h + clientStart.y - 2 };
398                break;
399             case TabsPlacement::right:
400                box = { clientSize.w - 80, /*((id == 0) ? 0 : */y/*)*/ + 1 + clientStart.y, clientSize.w - 78 + clientStart.x + 1, y + clientStart.y + button.size.h - 2 };
401                break;
402          }
403          surface.Clip(box);
404          surface.SetBackground(background /*white*/);
405          surface.Clear(colorBuffer);
406          surface.Clip(null);
407
408          surface.SetForeground(white);
409          switch(placement)
410          {
411             case TabsPlacement::bottom:   surface.VLine(clientSize.h-32 + clientStart.y + 1, clientSize.h-28 + clientStart.y + 1, x + 1 + clientStart.x); break;
412             case TabsPlacement::top:      surface.VLine(clientStart.y + 30, clientStart.y + 31, x + 1 + clientStart.x); break;
413             case TabsPlacement::left:     surface.HLine(78 + clientStart.x, 81 + clientStart.x, y + 1 + clientStart.y); break;
414             case TabsPlacement::right:    surface.HLine(clientStart.y + 30, clientStart.y + 31, y + 1 + clientStart.y); break;
415          }
416       }
417    }
418
419    void GetDecorationsSize(MinMaxValue * w, MinMaxValue * h)
420    {
421       if(placement == bottom && ((BorderBits)borderStyle).fixed)
422       {
423          *w = *h = 0;
424          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
425             *h += CAPTION;
426          if(hasMenuBar && state != minimized)
427             *h += MENU_HEIGHT;
428          if(statusBar && state != minimized)
429             *h += STATUS_HEIGHT;
430       }
431       else
432          Window::GetDecorationsSize(w, h);
433    }
434
435    bool IsMouseResizing(int x, int y, int w, int h, bool *resizeX, bool *resizeY, bool *resizeEndX, bool *resizeEndY)
436    {
437       if(placement == bottom && ((BorderBits)borderStyle).fixed)
438       {
439          bool result = false;
440
441          *resizeX = *resizeY = *resizeEndX = *resizeEndY = false;
442
443          if(((BorderBits)borderStyle).sizable && (state == normal))
444          {
445             // TopLeft Corner
446             if(Box { 0, 0,2, 2 }.IsPointInside({x, y}))
447                result = *resizeX = *resizeY = true;
448             // TopRight Corner
449             if(Box { w-2, 0, w-1, 2 }.IsPointInside({x, y}))
450                result = *resizeEndX = *resizeY = true;
451             // BottomLeft Corner
452             if(Box { 0, h-2, 1, h-1 }.IsPointInside({x, y}))
453                result = *resizeX = *resizeEndY = true;
454             // BottomRight Corner
455             if(Box { w-2, h-32, w-1, h-25 }.IsPointInside({x, y}))
456                result = *resizeEndX = *resizeEndY = true;
457
458             // Left Border
459             if(Box { 0,0, 1, h-1 }.IsPointInside({x, y}))
460                result = *resizeX = true;
461             // Right Border
462             if(Box { w-1, 0, w-2, h-1 }.IsPointInside({x, y}))
463                result = *resizeEndX = true;
464             // Top Border
465             if(Box { 0, 0, w-1, 2 }.IsPointInside({x, y}))
466                result = *resizeY = true;
467             // Bottom Border
468             if(Box { numTabs * 70, h-32, w-2, h-25 }.IsPointInside({x, y}))
469                result = *resizeEndY = true;
470          }
471          return result;
472       }
473       else
474          return Window::IsMouseResizing(x, y, w, h, resizeX, resizeY, resizeEndX, resizeEndY);
475    }
476
477    void SetWindowArea(int * x, int * y, MinMaxValue * w, MinMaxValue * h, MinMaxValue * cw, MinMaxValue * ch)
478    {
479       if(placement == bottom && ((BorderBits)borderStyle).fixed)
480       {
481          MinMaxValue aw = 0, ah = 0;
482
483          *x = *y = 0;
484
485          GetDecorationsSize(&aw, &ah);
486
487          if(hasMenuBar)
488             *y += MENU_HEIGHT;
489
490          if(((BorderBits)borderStyle).fixed && (state != maximized || !parent.menuBar))
491             *y += CAPTION;
492
493          if(((BorderBits)borderStyle).contour && !((BorderBits)borderStyle).fixed)
494          {
495             *x += 1;
496             *y += 1;
497          }
498
499          // Reduce client area
500          *cw = *w - aw;
501          *ch = *h - ah;
502
503          *cw = Max(*cw, 0);
504          *ch = Max(*ch, 0);
505       }
506       else
507          Window::SetWindowArea(x, y, w, h, cw, ch);
508    }
509
510    bool OnCreate()
511    {
512       switch(placement)
513       {
514          case top:
515             tabButtons.anchor = { top = 5, left = 0, right = 5 };
516             tabButtons.size = { h = 25 };
517             break;
518          case bottom:
519             tabButtons.anchor = { bottom = 5, left = 0, right = 5 };
520             tabButtons.size = { h = 25 };
521             break;
522          case left:
523             tabButtons.anchor = { top = 0, left = 5, bottom = 5 };
524             tabButtons.size = { w = 75 };
525             break;
526          case right:
527             tabButtons.anchor = { top = 0, bottom = 5, right = 5 };
528             tabButtons.size = { w = 75 };
529             break;
530       }
531       return true;
532    }
533
534    bool OnPostCreate()
535    {
536       // curButton.Activate();
537       curButton.MakeActive();
538       return true;
539    }
540
541    bool NotifyClicked(Button button, int x, int y, Modifiers mods)
542    {
543       if(curTab == (Tab)(intptr)button.id)
544          return true;
545       //curButton.Activate();
546       curButton.MakeActive();
547
548       if(curTab.Destroy(0))
549       {
550          curButton.checked = false;
551          button.checked = true;
552          curTab = (Tab)(intptr)button.id;
553
554          if(curButton)
555             PlaceButton(curButton, placement, false, buttonsOffset);
556
557          curButton = (TabButton)button;
558          curButton.MakeActive();
559          // curButton.Activate();
560
561          PlaceButton(curButton, placement, true, buttonsOffset);
562          Update(null);
563
564          curTab.Create();
565       }
566       else
567       {
568          return false;
569       }
570       return true;
571    }
572
573    public void AddTab(Tab tab)
574    {
575       if(tab.parent != this)
576       {
577          tab.parent = this;
578          return;
579       }
580       tab.autoCreate = false;
581       tab.id = numTabs;
582       if(!tab.button)
583       {
584          tab.button = TabButton
585          {
586             parent = tabButtons,
587             master = this, stayDown = true,
588             text = tab.text, id = (int64)(intptr)tab, NotifyClicked = NotifyClicked,
589             tab = tab,
590             background = background;
591          };
592          incref tab.button;
593       }
594
595       if(created)
596       {
597          tab.button.Create();
598          incref tab;
599       }
600
601       numTabs++;
602       if(!curTab)
603       {
604          curTab = tab;
605          curButton = tab.button;
606          tab.button.checked = true;
607          tab.autoCreate = true;
608       }
609
610       PlaceButton(tab.button, placement, curTab == tab, buttonsOffset);
611       switch(placement)
612       {
613          case top: tab.anchor = { left = 2, bottom = 2, right = 2, top = 32 }; break;
614          case bottom: tab.anchor = { left = 2, bottom = 32, right = 2, top = 2 }; break;
615          case left: tab.anchor = { left = 82, bottom = 2, right = 2, top = 2 }; break;
616          case right: tab.anchor = { left = 2, bottom = 2, right = 82, top = 2 }; break;
617       }
618    }
619
620    public void RemoveTab(Tab tab)
621    {
622       Window child;
623       Tab fallbackTab = null;
624       // tab.parent = null;
625       for(child = tabButtons.children.first; child; child = child.next)
626       {
627          if(child._class == class(TabButton))
628          {
629             TabButton button = (TabButton)child;
630             if(button.id == (int64)(intptr)tab)
631             {
632                if(button.created)
633                {
634                   button.Destroy(0);
635                   numTabs--;
636                }
637                break;
638             }
639             else
640                fallbackTab = button.tab;
641          }
642       }
643       if(curTab == tab)
644       {
645          if(!fallbackTab)
646             fallbackTab = tabButtons.children.first ? ((TabButton)tabButtons.children.first).tab : null;
647          if(fallbackTab)
648             fallbackTab.SelectTab();
649          else
650             curTab = null;
651          /*curTab = fallbackTab;
652          curButton = curTab.button;
653          curButton.checked = true;
654          curTab.autoCreate = true;*/
655       }
656    }
657
658    ~TabControl()
659    {
660       Window child, next;
661       for(child = tabButtons.firstChild; child; child = next)
662       {
663          next = child.next;
664          delete child;
665       }
666
667       for(child = firstChild; child; child = next)
668       {
669          next = child.next;
670          if(eClass_IsDerived(child._class, class(Tab)))
671             child.parent = null;
672       }
673    }
674
675    bool OnKeyHit(Key key, unichar ch)
676    {
677       SmartKey smartKey = (SmartKey)key;
678       if(tabButtons.active && (smartKey == left || smartKey == right))
679       {
680          // BIG MESS... USE AN ARRAY INSTEAD?
681          TabButton newButton = null, button;
682          int cleft = curButton.anchor.left.distance, ctop = curButton.anchor.top.distance;
683
684          for(button = (TabButton)tabButtons.firstChild; button; button = (TabButton)button.next)
685          {
686             int pleft = button.anchor.left.distance, nleft = newButton ? newButton.anchor.left.distance : 0;
687             int ptop = button.anchor.top.distance, ntop = newButton ? newButton.anchor.top.distance : 0;
688             if(button == curButton) continue;
689             if((smartKey == left && (pleft < cleft || ptop < ctop) && (!newButton || pleft > nleft || ptop > ntop)) ||
690                (smartKey == right && (pleft > cleft || ptop > ctop) && (!newButton || pleft < nleft || pleft < ptop)))
691                newButton = button;
692          }
693          if(!newButton)
694          {
695             for(button = (TabButton)tabButtons.firstChild; button; button = (TabButton)button.next)
696             {
697                int pleft = button.anchor.left.distance, nleft = newButton ? newButton.anchor.left.distance : 0;
698                int ptop = button.anchor.top.distance, ntop = newButton ? newButton.anchor.top.distance : 0;
699                if(button == curButton) continue;
700                if(!newButton || (smartKey == right && (pleft < nleft || ptop < ntop)) || (smartKey == left && (pleft > nleft || ptop > ntop)))
701                   newButton = button;
702             }
703          }
704
705          if(newButton)
706          {
707             property::curTab = newButton.tab;
708             tabButtons.MakeActive();
709             // tabButtons.Activate();
710          }
711          return false;
712       }
713       return true;
714    }
715 }
716
717 public class Tab : Window
718 {
719    TabButton button;
720    TabControl tabControl;
721
722    //borderStyle = contour;
723    //background = lightBlue;
724    tabCycle = true;
725
726    watch(parent)
727    {
728       if(parent && eClass_IsDerived(parent._class, class(TabControl)))
729       {
730          tabControl = (TabControl)parent;
731          tabControl.AddTab(this);
732       }
733       else if(!parent && tabControl)
734       {
735          tabControl.RemoveTab(this);
736          tabControl = null;
737       }
738    };
739
740    public property TabControl tabControl
741    {
742       set { parent = value; }
743       get { return (TabControl)parent; }
744    }
745
746    public void SelectTab()
747    {
748       button.NotifyClicked(button.master, button, 0, 0, 0);
749    }
750
751    watch(caption)
752    {
753       if(button)
754          button.text = text;
755    };
756 }