**** Merged from MCS ****
[mono-project.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TabControl.cs
blobbc353081867e1dc56159aa720197f4ba09fdb288
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 //
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 // Copyright (c) 2004 Novell, Inc.
22 // Authors:
23 // Jackson Harper (jackson@ximian.com)
26 using System;
27 using System.Drawing;
28 using System.Collections;
31 namespace System.Windows.Forms {
33 public class TabControl : Control {
35 private int selected_index = -1;
36 private TabAlignment alignment;
37 private TabAppearance appearance;
38 private TabDrawMode draw_mode;
39 private bool multiline;
40 private ImageList image_list;
41 private Size item_size = Size.Empty;
42 private Point padding;
43 private int row_count = 1;
44 private bool hottrack;
45 private TabPageCollection tab_pages;
46 private bool show_tool_tips;
47 private TabSizeMode size_mode;
48 private bool redraw;
49 private Rectangle display_rect;
50 private bool show_slider = false;
51 private ButtonState right_slider_state;
52 private ButtonState left_slider_state;
53 private int slider_pos = 0;
55 public TabControl ()
57 tab_pages = new TabPageCollection (this);
58 SetStyle (ControlStyles.UserPaint, true);
59 padding = ThemeEngine.Current.TabControlDefaultPadding;
60 item_size = ThemeEngine.Current.TabControlDefaultItemSize;
62 MouseDown += new MouseEventHandler (MouseDownHandler);
63 MouseUp += new MouseEventHandler (MouseUpHandler);
64 SizeChanged += new EventHandler (SizeChangedHandler);
67 public TabAlignment Alignment {
68 get { return alignment; }
69 set {
70 if (alignment == value)
71 return;
72 alignment = value;
73 if (alignment == TabAlignment.Left || alignment == TabAlignment.Right)
74 multiline = true;
75 Refresh ();
79 public TabAppearance Appearance {
80 get { return appearance; }
81 set {
82 if (appearance == value)
83 return;
84 appearance = value;
85 Refresh ();
89 public override Color BackColor {
90 get { return base.BackColor; }
91 set { /* nothing happens on set on MS */ }
94 public override Image BackgroundImage {
95 get { return base.BackgroundImage; }
96 set { base.BackgroundImage = value; }
99 public override Rectangle DisplayRectangle {
100 get {
101 return ThemeEngine.Current.GetTabControlDisplayRectangle (this);
105 public TabDrawMode DrawMode {
106 get { return draw_mode; }
107 set {
108 if (draw_mode == value)
109 return;
110 draw_mode = value;
111 Refresh ();
115 public override Color ForeColor {
116 get { return base.ForeColor; }
117 set { base.ForeColor = value; }
120 public bool HotTrack {
121 get { return hottrack; }
122 set {
123 if (hottrack == value)
124 return;
125 hottrack = value;
126 Refresh ();
130 public ImageList ImageList {
131 get { return image_list; }
132 set { image_list = value; }
135 public Size ItemSize {
136 get {
137 return item_size;
139 set {
140 if (value.Height < 0 || value.Width < 0)
141 throw new ArgumentException ("'" + value + "' is not a valid value for 'ItemSize'.");
142 item_size = value;
143 Refresh ();
147 public bool Multiline {
148 get { return multiline; }
149 set {
150 if (multiline == value)
151 return;
152 multiline = value;
153 if (!multiline && alignment == TabAlignment.Left || alignment == TabAlignment.Right)
154 alignment = TabAlignment.Top;
155 Refresh ();
159 public Point Padding {
160 get { return padding; }
161 set {
162 if (value.X < 0 || value.Y < 0)
163 throw new ArgumentException ("'" + value + "' is not a valid value for 'Padding'.");
164 if (padding == value)
165 return;
166 padding = value;
167 Refresh ();
172 public int RowCount {
173 get { return row_count; }
176 public int SelectedIndex {
177 get { return selected_index; }
178 set {
179 if (selected_index == value)
180 return;
181 if (selected_index < -1) {
182 throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
183 "'value' must be greater than or equal to -1.");
186 SuspendLayout ();
187 if (selected_index != -1)
188 Controls [selected_index].Visible = false;
189 selected_index = value;
190 if (selected_index != -1)
191 Controls [selected_index].Visible = true;
192 ResumeLayout ();
194 if (SelectedIndex != -1 && TabPages [SelectedIndex].Row != BottomRow)
195 DropRow (TabPages [selected_index].Row);
196 SizeTabs ();
198 Refresh ();
202 public TabPage SelectedTab {
203 get {
204 if (selected_index == -1)
205 return null;
206 return tab_pages [selected_index];
208 set {
209 int index = IndexForTabPage (value);
210 if (index == selected_index)
211 return;
212 selected_index = index;
213 Refresh ();
217 public bool ShowToolTips {
218 get { return show_tool_tips; }
219 set {
220 if (show_tool_tips == value)
221 return;
222 show_tool_tips = value;
223 Refresh ();
227 public TabSizeMode SizeMode {
228 get { return size_mode; }
229 set {
230 if (size_mode == value)
231 return;
232 size_mode = value;
233 Refresh ();
237 public int TabCount {
238 get {
239 return tab_pages.Count;
243 public TabPageCollection TabPages {
244 get { return tab_pages; }
247 public override string Text {
248 get { return base.Text; }
249 set { base.Text = value; }
252 internal bool ShowSlider {
253 get { return show_slider; }
254 set { show_slider = value; }
257 internal ButtonState RightSliderState {
258 get { return right_slider_state; }
261 internal ButtonState LeftSliderState {
262 get { return left_slider_state; }
265 [MonoTODO ("Anything special need to be done?")]
266 protected override CreateParams CreateParams {
267 get {
268 CreateParams c = base.CreateParams;
269 // Do we need to do anything here?
270 return c;
274 protected override Size DefaultSize {
275 get { return new Size (200, 100); }
278 private Size DefaultItemSize {
279 get {
280 return ThemeEngine.Current.TabControlDefaultItemSize;
284 public new event EventHandler BackColorChanged {
285 add { base.BackColorChanged += value; }
286 remove { base.BackColorChanged -= value; }
289 public new event EventHandler BackgroundImageChanged {
290 add { base.BackgroundImageChanged += value; }
291 remove { base.BackgroundImageChanged -= value; }
294 public new event EventHandler ForeColorChanged {
295 add { base.ForeColorChanged += value; }
296 remove { base.ForeColorChanged -= value; }
299 public new event PaintEventHandler Paint {
300 add { base.Paint += value; }
301 remove { base.Paint -= value; }
304 public new event EventHandler TextChanged {
305 add { base.TextChanged += value; }
306 remove { base.TextChanged -= value; }
309 public event DrawItemEventHandler DrawItem;
310 public event EventHandler SelectedIndexChanged;
312 public Rectangle GetTabRect (int index)
314 TabPage page = GetTab (index);
315 return page.TabBounds;
318 public Control GetControl (int index)
320 return GetTab (index);
323 protected override Control.ControlCollection CreateControlsInstance ()
325 return new TabControl.ControlCollection (this);
328 protected override void CreateHandle ()
330 ResizeTabPages ();
331 base.CreateHandle ();
334 protected override void Dispose (bool disposing)
336 base.Dispose (disposing);
339 protected virtual object [] GetItems ()
341 TabPage [] pages = new TabPage [Controls.Count];
342 Controls.CopyTo (pages, 0);
343 return pages;
346 protected virtual object [] GetItems (Type type)
348 object [] pages = (object []) Array.CreateInstance (type, Controls.Count);
349 Controls.CopyTo (pages, 0);
350 return pages;
353 protected string GetToolTipText (object item)
355 TabPage page = (TabPage) item;
356 return page.ToolTipText;
359 protected override void WndProc (ref Message m)
361 switch ((Msg) m.Msg) {
362 case Msg.WM_PAINT:
363 PaintEventArgs paint_event;
364 paint_event = XplatUI.PaintEventStart (Handle);
365 PaintInternal (paint_event);
366 XplatUI.PaintEventEnd (Handle);
367 break;
368 default:
369 base.WndProc (ref m);
370 break;
374 private bool CanScrollRight {
375 get { return slider_pos != 0; }
378 private bool CanScrollLeft {
379 get {
380 if (TabPages [TabCount - 1].TabBounds.Right > ClientRectangle.Right - 40)
381 return true;
382 return false;
386 private void MouseDownHandler (object sender, MouseEventArgs e)
388 if (ShowSlider) {
389 Rectangle right = ThemeEngine.Current.GetTabControlRightScrollRect (this);
390 Rectangle left = ThemeEngine.Current.GetTabControlLeftScrollRect (this);
391 if (right.Contains (e.X, e.Y)) {
392 right_slider_state = ButtonState.Pushed;
393 if (CanScrollRight) {
394 slider_pos++;
395 SizeTabs ();
397 Refresh ();
398 return;
399 } else if (left.Contains (e.X, e.Y)) {
400 left_slider_state = ButtonState.Pushed;
401 if (CanScrollLeft) {
402 slider_pos--;
403 SizeTabs ();
405 Refresh ();
406 return;
411 int count = Controls.Count;
412 for (int i = 0; i<count; i++) {
413 if (!GetTabRect (i).Contains (e.X, e.Y))
414 continue;
415 SelectedIndex = i;
416 break;
420 private void MouseUpHandler (object sender, MouseEventArgs e)
422 left_slider_state = ButtonState.Normal;
423 right_slider_state = ButtonState.Normal;
424 Refresh ();
427 private void SizeChangedHandler (object sender, EventArgs e)
429 ResizeTabPages ();
432 internal void UpdateTabpage (TabPage page)
437 internal int IndexForTabPage (TabPage page)
439 for (int i = 0; i < tab_pages.Count; i++) {
440 if (page == tab_pages [i])
441 return i;
443 return -1;
446 private void ResizeTabPages ()
448 CalcTabRows ();
449 SizeTabs ();
450 Rectangle r = DisplayRectangle;
451 foreach (TabPage page in Controls) {
452 page.Bounds = r;
456 private int MinimumTabWidth {
457 get {
458 return ThemeEngine.Current.TabControlMinimumTabWidth;
462 private Size TabSpacing {
463 get {
464 return ThemeEngine.Current.TabControlGetSpacing (this);
468 private void CalcTabRows ()
470 switch (Alignment) {
471 case TabAlignment.Right:
472 case TabAlignment.Left:
473 CalcTabRows (Height);
474 break;
475 default:
476 CalcTabRows (Width);
477 break;
481 private void CalcTabRows (int row_width)
483 int xpos = 4;
484 Size spacing = TabSpacing;
486 row_count = 1;
487 show_slider = false;
489 for (int i = 0; i < TabPages.Count; i++) {
490 TabPage page = TabPages [i];
491 int width;
493 page.Row = 1;
495 if (SizeMode == TabSizeMode.Fixed) {
496 width = item_size.Width;
497 } else {
498 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
501 if (i == SelectedIndex)
502 width += 8;
503 if (width < MinimumTabWidth)
504 width = MinimumTabWidth;
506 if (xpos + width > row_width && multiline) {
507 xpos = 4;
508 for (int j = 0; j < i; j++) {
509 TabPages [j].Row++;
511 row_count++;
512 } else if (xpos + width > row_width) {
513 show_slider = true;
516 xpos += width + 1 + spacing.Width;
519 if (SelectedIndex != -1 && TabPages [SelectedIndex].Row != BottomRow)
520 DropRow (TabPages [SelectedIndex].Row);
523 private int BottomRow {
524 get {
525 switch (Alignment) {
526 case TabAlignment.Right:
527 case TabAlignment.Bottom:
528 return row_count;
529 default:
530 return 1;
535 private int Direction
537 get {
538 switch (Alignment) {
539 case TabAlignment.Right:
540 case TabAlignment.Bottom:
541 return -1;
542 default:
543 return 1;
548 private void DropRow (int row)
550 int bottom = BottomRow;
551 int direction = Direction;
553 foreach (TabPage page in TabPages) {
554 if (page.Row == row) {
555 page.Row = bottom;
556 } else if (direction == 1 && page.Row < row) {
557 page.Row += direction;
558 } else if (direction == -1 && page.Row > row) {
559 page.Row += direction;
564 private int CalcYPos ()
566 if (Alignment == TabAlignment.Bottom) {
567 Rectangle r = ThemeEngine.Current.GetTabControlDisplayRectangle (this);
568 return r.Bottom + 3;
570 return 1;
573 private int CalcXPos ()
575 if (Alignment == TabAlignment.Right) {
576 Rectangle r = ThemeEngine.Current.GetTabControlDisplayRectangle (this);
577 return r.Right + 4;
579 return 4;
583 private void SizeTabs ()
585 switch (Alignment) {
586 case TabAlignment.Right:
587 case TabAlignment.Left:
588 SizeTabsV (Height);
589 break;
590 default:
591 SizeTabs (Width);
592 break;
596 private void SizeTabsV (int row_width)
598 int ypos = 1;
599 int prev_row = 1;
600 Size spacing = TabSpacing;
601 int size = item_size.Height + 2 + spacing.Width;
602 int xpos = CalcXPos ();
604 if (TabPages.Count == 0)
605 return;
607 prev_row = TabPages [0].Row;
609 for (int i = 0; i < TabPages.Count; i++) {
610 TabPage page = TabPages [i];
611 int width;
613 if (SizeMode == TabSizeMode.Fixed) {
614 width = item_size.Width;
615 } else {
616 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
619 if (width < MinimumTabWidth)
620 width = MinimumTabWidth;
621 if (page.Row != prev_row)
622 ypos = 1;
624 page.TabBounds = new Rectangle (xpos + (row_count - page.Row) * ((item_size.Height - 2) + spacing.Width),
625 ypos, item_size.Height - 2, width);
627 ypos += width + spacing.Width;
628 prev_row = page.Row;
631 if (SelectedIndex != -1) {
632 TabPage page = TabPages [SelectedIndex];
633 ExpandSelected (TabPages [SelectedIndex], 1, row_width - 1);
637 private void SizeTabs (int row_width)
639 int ypos = CalcYPos ();
640 int prev_row = 1;
641 Size spacing = TabSpacing;
642 int size = item_size.Width + 2 + (spacing.Width * 2);
643 int xpos = 4 + (slider_pos * size);
644 int begin_prev = 0;
646 if (TabPages.Count == 0)
647 return;
649 prev_row = TabPages [0].Row;
651 for (int i = 0; i < TabPages.Count; i++) {
652 TabPage page = TabPages [i];
653 int width;
655 if (SizeMode == TabSizeMode.Fixed) {
656 width = item_size.Width;
657 } else {
658 width = (int) DeviceContext.MeasureString (page.Text, Font).Width + (Padding.X * 2);
661 if (width < MinimumTabWidth)
662 width = MinimumTabWidth;
663 if (page.Row != prev_row)
664 xpos = 4 + (slider_pos * size);
666 page.TabBounds = new Rectangle (xpos,
667 ypos + (row_count - page.Row) * (item_size.Height + spacing.Height),
668 width, item_size.Height);
671 if (page.Row != prev_row) {
672 if (SizeMode == TabSizeMode.FillToRight) {
673 FillRow (begin_prev, i - 1, ((row_width - TabPages [i - 1].TabBounds.Right) / (i - begin_prev)), spacing);
675 begin_prev = i;
678 xpos += width + 1 + spacing.Width;
679 prev_row = page.Row;
682 if (SizeMode == TabSizeMode.FillToRight) {
683 FillRow (begin_prev, TabPages.Count - 1,
684 ((row_width - TabPages [TabPages.Count - 1].TabBounds.Right) / (TabPages.Count - begin_prev)), spacing);
687 if (SelectedIndex != -1) {
688 TabPage page = TabPages [SelectedIndex];
689 ExpandSelected (TabPages [SelectedIndex], 2, row_width - 1);
693 private void FillRow (int start, int end, int amount, Size spacing)
695 int xpos = TabPages [start].TabBounds.Left;
696 for (int i = start; i <= end; i++) {
697 TabPage page = TabPages [i];
698 int left = xpos;
699 int width = (i == end ? Width - left - 3 : page.TabBounds.Width + amount);
701 page.TabBounds = new Rectangle (left, page.TabBounds.Top,
702 width, page.TabBounds.Height);
703 xpos = page.TabBounds.Right + 1 + spacing.Width;
707 private void ExpandSelected (TabPage page, int left_edge, int right_edge)
709 if (Appearance != TabAppearance.Normal)
710 return;
712 if (Alignment == TabAlignment.Top || Alignment == TabAlignment.Bottom) {
713 int l = page.TabBounds.Left - 4;
714 int r = page.TabBounds.Right + 4;
715 int y = page.TabBounds.Y;
716 int h = page.TabBounds.Height + 2;
718 if (l < left_edge)
719 l = left_edge;
720 if (r > right_edge && SizeMode != TabSizeMode.Normal)
721 r = right_edge;
722 if (Alignment == TabAlignment.Top)
723 y -= 1;
724 if (Alignment == TabAlignment.Bottom)
725 y -= 2;
727 page.TabBounds = new Rectangle (l, y, r - l, h);
728 } else {
729 int l = page.TabBounds.Left - 3;
730 int r = page.TabBounds.Right + 3;
731 int t = page.TabBounds.Top - 3;
732 int b = page.TabBounds.Bottom + 3;
734 if (t < left_edge)
735 t = left_edge;
736 if (b > right_edge)
737 b = right_edge;
739 page.TabBounds = new Rectangle (l, t, r - l, b - t);
743 private void PaintInternal (PaintEventArgs pe)
745 if (this.Width <= 0 || this.Height <= 0 || this.Visible == false)
746 return;
748 Draw ();
749 pe.Graphics.DrawImageUnscaled (ImageBuffer, 0, 0);
750 ImageBuffer.Save ("ImageBuffer.bmp");
751 // On MS the Paint event never seems to be raised
754 private void Redraw (bool recalculate)
756 if (recalculate) {
759 redraw = true;
760 Refresh ();
763 private void Draw ()
765 ThemeEngine.Current.DrawTabControl (DeviceContext, ClientRectangle, this);
766 redraw = false;
769 private TabPage GetTab (int index)
771 return Controls [index] as TabPage;
774 private void SetTab (int index, TabPage value)
776 ((IList) Controls).Insert (index, value);
777 Refresh ();
780 public class ControlCollection : System.Windows.Forms.Control.ControlCollection {
782 private TabControl owner;
783 private ArrayList list = new ArrayList ();
785 public ControlCollection (TabControl owner) : base (owner)
787 this.owner = owner;
790 public override void Add (Control value)
792 if (!(value is TabPage))
793 throw new ArgumentException ("Cannot add " +
794 value.GetType ().Name + " to TabControl. " +
795 "Only TabPages can be directly added to TabControls.");
797 value.Visible = false;
798 base.Add (value);
799 if (Count == 1) {
800 owner.SelectedIndex = 0;
801 } else {
802 // Setting the selected index will calc the tab rows so
803 // we don't need to do it again
804 owner.CalcTabRows ();
809 public class TabPageCollection : IList, ICollection, IEnumerable {
811 private TabControl owner;
812 private IList controls;
814 public TabPageCollection (TabControl owner)
816 if (owner == null)
817 throw new ArgumentNullException ("Value cannot be null.");
818 this.owner = owner;
819 controls = owner.Controls;
822 public virtual int Count {
823 get { return owner.Controls.Count; }
826 public virtual bool IsReadOnly {
827 get { return false; }
830 public virtual TabPage this [int index] {
831 get {
832 return owner.GetTab (index);
834 set {
835 owner.SetTab (index, value);
839 bool ICollection.IsSynchronized {
840 get { return false; }
843 object ICollection.SyncRoot {
844 get { return this; }
847 bool IList.IsFixedSize {
848 get { return false; }
851 object IList.this [int index] {
852 get {
853 return owner.GetTab (index);
855 set {
856 owner.SetTab (index, (TabPage) value);
860 public void Add (TabPage page)
862 if (page == null)
863 throw new ArgumentNullException ("Value cannot be null.");
864 owner.Controls.Add (page);
867 public void AddRange (TabPage [] pages)
869 if (pages == null)
870 throw new ArgumentNullException ("Value cannot be null.");
871 owner.Controls.AddRange (pages);
874 public virtual void Clear ()
876 owner.Controls.Clear ();
879 public bool Contains (TabPage page)
881 if (page == null)
882 throw new ArgumentNullException ("Value cannot be null.");
883 return owner.Controls.Contains (page);
886 public virtual IEnumerator GetEnumerator ()
888 return owner.Controls.GetEnumerator ();
891 public int IndexOf (TabPage page)
893 return owner.Controls.IndexOf (page);
896 public void Remove (TabPage page)
898 owner.Controls.Remove (page);
901 public virtual void RemoveAt (int index)
903 owner.Controls.RemoveAt (index);
906 void ICollection.CopyTo (Array dest, int index)
908 owner.Controls.CopyTo (dest, index);
911 int IList.Add (object value)
913 // return owner.Controls.Add ((TabPage) value);
914 return -1;
917 bool IList.Contains (object page)
919 return Contains ((TabPage) page);
922 int IList.IndexOf (object page)
924 return IndexOf ((TabPage) page);
927 void IList.Insert (int index, object value)
929 controls.Insert (index, (TabPage) value);
932 void IList.Remove (object value)
934 Remove ((TabPage) value);