* ImageList.cs: When the image stream is set pull all the images
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms / TreeView.cs
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.
11 //
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.
19 //
20 // Copyright (c) 2004-2005 Novell, Inc.
21 //
22 // Authors:
23 //      Jackson Harper (jackson@ximian.com)
24 //      Kazuki Oikawa (kazuki@panicode.com)
25
26 using System;
27 using System.Collections;
28 using System.ComponentModel;
29 using System.ComponentModel.Design;
30 using System.Drawing;
31 using System.Drawing.Drawing2D;
32 using System.Runtime.InteropServices;
33
34 namespace System.Windows.Forms {
35         [DefaultProperty("Nodes")]
36         [DefaultEvent("AfterSelect")]
37         [Designer("System.Windows.Forms.Design.TreeViewDesigner, " + Consts.AssemblySystem_Design)]
38         public class TreeView : Control {
39                 #region Fields
40                 private string path_separator = "\\";
41                 private int item_height = -1;
42                 private bool sorted;
43                 private TreeNode top_node;
44                 internal TreeNode root_node;
45                 private TreeNodeCollection nodes;
46                 private int total_node_count;
47
48                 private TreeNode selected_node = null;
49                 private TreeNode focused_node = null;
50                 private bool select_mmove = false;
51
52                 private ImageList image_list;
53                 private int image_index = -1;
54                 private int selected_image_index = -1;
55
56                 private bool full_row_select;
57                 private bool hot_tracking;
58                 private int indent = 19;
59
60                 private TextBox edit_text_box;
61                 private TreeNode edit_node;
62                 
63                 private bool checkboxes;
64                 private bool label_edit;
65                 private bool scrollable;
66                 private bool show_lines = true;
67                 private bool show_root_lines = true;
68                 private bool show_plus_minus = true;
69                 private bool hide_selection = true;
70
71                 private bool add_hscroll;
72                 private bool add_vscroll;
73                 private int max_node_width;
74                 private VScrollBar vbar;
75                 private bool vbar_added;
76                 private int skipped_nodes;
77                 private HScrollBar hbar;
78                 private bool hbar_added;
79                 private int hbar_offset;
80                 
81                 private int update_stack;
82
83                 private TreeViewEventHandler on_after_check;
84                 private TreeViewEventHandler on_after_collapse;
85                 private TreeViewEventHandler on_after_expand;
86                 private NodeLabelEditEventHandler on_after_label_edit;
87                 private TreeViewEventHandler on_after_select;
88                 private TreeViewCancelEventHandler on_before_check;
89                 private TreeViewCancelEventHandler on_before_collapse;
90                 private TreeViewCancelEventHandler on_before_expand;
91                 private NodeLabelEditEventHandler on_before_label_edit;
92                 private TreeViewCancelEventHandler on_before_select;
93
94                 private Pen dash;
95                 private int open_node_count = -1;
96                 #endregion      // Fields
97
98                 #region Public Constructors     
99                 public TreeView ()
100                 {
101                         base.background_color = ThemeEngine.Current.ColorWindow;
102                         base.foreground_color = ThemeEngine.Current.ColorWindowText;
103
104                         root_node = new TreeNode (this);
105                         root_node.Text = "ROOT NODE";
106                         nodes = new TreeNodeCollection (root_node);
107                         root_node.SetNodes (nodes);
108
109                         MouseDown += new MouseEventHandler (MouseDownHandler);
110                         MouseUp += new MouseEventHandler(MouseUpHandler);
111                         MouseMove += new MouseEventHandler(MouseMoveHandler);
112                         SizeChanged += new EventHandler (SizeChangedHandler);
113
114                         SetStyle (ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw, true);
115                         SetStyle (ControlStyles.UserPaint | ControlStyles.Selectable, true);
116
117                         dash = new Pen (SystemColors.ControlLight, 1);
118                 }
119
120                 #endregion      // Public Constructors
121
122                 #region Public Instance Properties
123                 public override Color BackColor {
124                         get { return base.BackColor;}
125                         set { base.BackColor = value; }
126                 }
127
128
129                 [Browsable(false)]
130                 [EditorBrowsable(EditorBrowsableState.Never)]
131                 public override Image BackgroundImage {
132                         get { return base.BackgroundImage; }
133                         set { base.BackgroundImage = value; }
134                 }
135
136                 [DefaultValue(BorderStyle.Fixed3D)]
137                 [DispId(-504)]
138                 public BorderStyle BorderStyle {
139                         get { return InternalBorderStyle; }
140                         set { InternalBorderStyle  = value; }
141                 }
142
143                 [DefaultValue(false)]
144                 public bool CheckBoxes {
145                         get { return checkboxes; }
146                         set {
147                                 if (value == checkboxes)
148                                         return;
149                                 checkboxes = value;
150
151                                 // Match a "bug" in the MS implementation where disabling checkboxes
152                                 // collapses the entire tree, but enabling them does not affect the
153                                 // state of the tree.
154                                 if (!checkboxes)
155                                         root_node.CollapseAllUncheck ();
156
157                                 Refresh ();
158                         }
159                 }
160
161                 public override Color ForeColor {
162                         get { return base.ForeColor; }
163                         set { base.ForeColor = value; }
164                 }
165                 [DefaultValue(false)]
166                 public bool FullRowSelect {
167                         get { return full_row_select; }
168                         set {
169                                 if (value == full_row_select)
170                                         return;
171                                 full_row_select = value;
172                                 Refresh ();
173                         }
174                 }
175                 [DefaultValue(true)]
176                 public bool HideSelection {
177                         get { return hide_selection; }
178                         set {
179                                 if (hide_selection == value)
180                                         return;
181                                 hide_selection = value;
182                                 this.Refresh ();
183                         }
184                 }
185
186                 [DefaultValue(false)]
187                 public bool HotTracking {
188                         get { return hot_tracking; }
189                         set { hot_tracking = value; }
190                 }
191
192                 [DefaultValue(0)]
193                 [Editor("System.Windows.Forms.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
194                 [Localizable(true)]
195                 [TypeConverter(typeof(TreeViewImageIndexConverter))]
196                 public int ImageIndex {
197                         get { return image_index; }
198                         set {
199                                 if (value < -1) {
200                                         throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
201                                                 "'value' must be greater than or equal to 0.");
202                                 }
203                                 image_index = value;
204                         }
205                 }
206
207                 [MonoTODO ("Anything special need to be done here?")]
208                 [DefaultValue(null)]
209                 public ImageList ImageList {
210                         get { return image_list; }
211                         set { image_list = value; }
212                 }
213
214                 [Localizable(true)]
215                 public int Indent {
216                         get { return indent; }
217                         set {
218                                 if (indent == value)
219                                         return;
220                                 if (value > 32000) {
221                                         throw new ArgumentException ("'" + value + "' is not a valid value for 'Indent'. " +
222                                                 "'Indent' must be less than or equal to 32000");
223                                 }       
224                                 if (value < 0) {
225                                         throw new ArgumentException ("'" + value + "' is not a valid value for 'Indent'. " +
226                                                 "'Indent' must be greater than or equal to 0.");
227                                 }
228                                 indent = value;
229                                 Refresh ();
230                         }
231                 }
232
233                 [Localizable(true)]
234                 public int ItemHeight {
235                         get {
236                                 if (item_height == -1)
237                                         return FontHeight + 3;
238                                 return item_height;
239                         }
240                         set {
241                                 if (value == item_height)
242                                         return;
243                                 item_height = value;
244                                 Refresh ();
245                         }
246                 }
247
248                 [DefaultValue(false)]
249                 public bool LabelEdit {
250                         get { return label_edit; }
251                         set { label_edit = value; }
252                 }
253
254                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
255                 [MergableProperty(false)]
256                 [Localizable(true)]
257                 public TreeNodeCollection Nodes {
258                         get { return nodes; }
259                 }
260
261                 [DefaultValue("\\")]
262                 public string PathSeparator {
263                         get { return path_separator; }
264                         set { path_separator = value; }
265                 }
266
267                 [DefaultValue(true)]
268                 public bool Scrollable {
269                         get { return scrollable; }
270                         set {
271                                 if (scrollable == value)
272                                         return;
273                                 scrollable = value;
274                         }
275                 }
276
277                 [Editor("System.Windows.Forms.Design.ImageIndexEditor, " + Consts.AssemblySystem_Design, typeof(System.Drawing.Design.UITypeEditor))]
278                 [TypeConverter(typeof(TreeViewImageIndexConverter))]
279                 [Localizable(true)]
280                 [DefaultValue(0)]
281                 public int SelectedImageIndex {
282                         get { return selected_image_index; }
283                         set {
284                                 if (value < -1) {
285                                         throw new ArgumentException ("'" + value + "' is not a valid value for 'value'. " +
286                                                 "'value' must be greater than or equal to 0.");
287                                 }
288                         }
289                 }
290
291                 [Browsable(false)]
292                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
293                 public TreeNode SelectedNode {
294                         get { return selected_node; }
295                         set {
296                                 if (selected_node == value)
297                                         return;
298
299                                 TreeViewCancelEventArgs e = new TreeViewCancelEventArgs (value, false, TreeViewAction.Unknown);
300                                 OnBeforeSelect (e);
301
302                                 if (e.Cancel)
303                                         return;
304
305                                 Rectangle invalid = Rectangle.Empty;
306
307                                 if (selected_node != null)
308                                         invalid = selected_node.Bounds;
309                                 if (focused_node != null)
310                                         invalid = Rectangle.Union (focused_node.Bounds, invalid);
311                                 invalid = Rectangle.Union (invalid, value.Bounds);
312
313                                 selected_node = value;
314                                 focused_node = value;
315
316                                 Invalidate (invalid);
317                                 
318                                 OnAfterSelect (new TreeViewEventArgs (value, TreeViewAction.Unknown));
319                         }
320                 }
321
322                 [DefaultValue(true)]
323                 public bool ShowLines {
324                         get { return show_lines; }
325                         set {
326                                 if (show_lines == value)
327                                         return;
328                                 show_lines = value;
329                                 Refresh ();
330                         }
331                 }
332
333                 [DefaultValue(true)]
334                 public bool ShowPlusMinus {
335                         get { return show_plus_minus; }
336                         set {
337                                 if (show_plus_minus == value)
338                                         return;
339                                 show_plus_minus = value;
340                                 Refresh ();
341                         }
342                 }
343
344                 [DefaultValue(true)]
345                 public bool ShowRootLines {
346                         get { return show_root_lines; }
347                         set {
348                                 if (show_root_lines == value)
349                                         return;
350                                 show_root_lines = value;
351                                 Refresh ();
352                         }
353                 }
354
355                 [DefaultValue(false)]
356                 public bool Sorted {
357                         get { return sorted; }
358                         set {
359                                 if (sorted != value)
360                                         sorted = value;
361                                 if (sorted) {
362                                         Nodes.Sort ();
363                                         Refresh ();
364                                 }
365                         }
366                 }
367
368                 [Browsable(false)]
369                 [EditorBrowsable(EditorBrowsableState.Never)]
370                 [Bindable(false)]
371                 public override string Text {
372                         get { return base.Text; }
373                         set { base.Text = value; }
374                 }
375
376                 [Browsable(false)]
377                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
378                 public TreeNode TopNode {
379                         get { return top_node; }
380                 }
381
382                 [Browsable(false)]
383                 [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
384                 public int VisibleCount {
385                         get {
386                                 return ClientRectangle.Height / ItemHeight;
387                         }
388                 }
389
390                 #endregion      // Public Instance Properties
391
392                 #region Protected Instance Properties
393                 [MonoTODO ("Anything extra needed here?")]
394                 protected override CreateParams CreateParams {
395                         get {
396                                 CreateParams cp = base.CreateParams;
397                                 return cp;
398                         }
399                 }
400
401                 protected override Size DefaultSize {
402                         get { return new Size (121, 97); }
403                 }
404
405                 #endregion      // Protected Instance Properties
406
407                 #region Public Instance Methods
408                 public void BeginUpdate () {
409                         if (!IsHandleCreated)
410                                 return;
411                         update_stack++;
412                 }
413
414                 public void CollapseAll () {
415                         root_node.CollapseAll ();
416                 }
417
418                 public void EndUpdate () {
419                         if (!IsHandleCreated)
420                                 return;
421
422                         if (update_stack > 1) {
423                                 update_stack--;
424                         } else {
425                                 update_stack = 0;
426                                 Refresh ();
427                         }
428                 }
429
430                 public void ExpandAll () {
431                         root_node.ExpandAll ();
432                 }
433
434                 public TreeNode GetNodeAt (Point pt) {
435                         return GetNodeAt (pt.X, pt.Y);
436                 }
437
438                 public TreeNode GetNodeAt (int x, int y) {
439                         TreeNode node = GetNodeAt (y);
440                         if (node == null || !IsTextArea (node, x))
441                                 return null;
442                         return node;
443                                         
444                 }
445
446                 public int GetNodeCount (bool include_subtrees) {
447                         return root_node.GetNodeCount (include_subtrees);
448                 }
449
450                 public override string ToString () {
451                         int count = Nodes.Count;
452                         if (count < 0)
453                                 return String.Concat (base.ToString (), "Node Count: 0");
454                         return String.Concat (base.ToString (), "Node Count: ", count, " Nodes[0]: ", Nodes [0]);
455                                                 
456                 }
457
458                 #endregion      // Public Instance Methods
459
460                 #region Protected Instance Methods
461                 protected override void CreateHandle () {
462                         base.CreateHandle ();
463                 }
464
465                 protected override void Dispose (bool disposing) {
466                         if (disposing) {
467                                 if (image_list != null)
468                                         image_list.Dispose ();
469                         }
470                         base.Dispose (disposing);
471                 }
472
473                 [MonoTODO ("What does the state effect?")]
474                 protected OwnerDrawPropertyBag GetItemRenderStyles (TreeNode node, int state) {
475                         return node.prop_bag;
476                 }
477
478                 protected override bool IsInputKey (Keys key_data) {
479                         if (label_edit && (key_data & Keys.Alt) == 0) {
480                                 switch (key_data & Keys.KeyCode) {
481                                         case Keys.Enter:
482                                         case Keys.Escape:
483                                         case Keys.Prior:
484                                         case Keys.Next:
485                                         case Keys.End:
486                                         case Keys.Home:
487                                         case Keys.Left:
488                                         case Keys.Up:
489                                         case Keys.Right:
490                                         case Keys.Down:
491                                                 return true;
492                                 }
493                         }
494                         return base.IsInputKey (key_data);
495                 }
496
497                 protected virtual void OnAfterCheck (TreeViewEventArgs e) {
498                         if (on_after_check != null)
499                                 on_after_check (this, e);
500                 }
501
502                 protected internal virtual void OnAfterCollapse (TreeViewEventArgs e) {
503                         if (on_after_collapse != null)
504                                 on_after_collapse (this, e);
505                 }
506
507                 protected internal virtual void OnAfterExpand (TreeViewEventArgs e) {
508                         if (on_after_expand != null)
509                                 on_after_expand (this, e);
510                 }
511
512                 protected virtual void OnAfterLabelEdit (NodeLabelEditEventArgs e) {
513                         if (on_after_label_edit != null)
514                                 on_after_label_edit (this, e);
515                 }
516
517                 protected virtual void OnAfterSelect (TreeViewEventArgs e) {
518                         if (on_after_select != null)
519                                 on_after_select (this, e);
520                 }
521
522                 protected virtual void OnBeforeCheck (TreeViewCancelEventArgs e) {
523                         if (on_before_check != null)
524                                 on_before_check (this, e);
525                 }
526
527                 protected internal virtual void OnBeforeCollapse (TreeViewCancelEventArgs e) {
528                         if (on_before_collapse != null)
529                                 on_before_collapse (this, e);
530                 }
531
532                 protected internal virtual void OnBeforeExpand (TreeViewCancelEventArgs e) {
533                         if (on_before_expand != null)
534                                 on_before_expand (this, e);
535                 }
536
537                 protected virtual void OnBeforeLabelEdit (NodeLabelEditEventArgs e) {
538                         if (on_before_label_edit != null)
539                                 on_before_label_edit (this, e);
540                 }
541
542                 protected virtual void OnBeforeSelect (TreeViewCancelEventArgs e) {
543                         if (on_before_select != null)
544                                 on_before_select (this, e);
545                 }
546
547                 protected override void OnHandleCreated (EventArgs e) {
548                         base.OnHandleCreated (e);
549                 }
550
551                 protected override void OnHandleDestroyed (EventArgs e) {
552                         base.OnHandleDestroyed (e);
553                 }
554
555                 protected override void WndProc(ref Message m) {
556                         switch ((Msg) m.Msg) {
557                                 case Msg.WM_PAINT: {                            
558                                         PaintEventArgs  paint_event;
559
560                                         paint_event = XplatUI.PaintEventStart (Handle);
561                                         DoPaint (paint_event);
562                                         XplatUI.PaintEventEnd (Handle);
563                                         return;
564                                 }
565                                 case Msg.WM_LBUTTONDBLCLK:
566                                         int val = m.LParam.ToInt32();
567                                         DoubleClickHandler (null, new MouseEventArgs (MouseButtons.Left, 2, val & 0xffff, (val>>16) & 0xffff, 0));
568                                         break;
569                         }
570                         base.WndProc (ref m);
571                 }
572
573                 #endregion      // Protected Instance Methods
574
575                 #region Internal & Private Methods and Properties
576                 internal string LabelEditText {
577                         get {
578                                 if (edit_text_box == null)
579                                         return String.Empty;
580                                 return edit_text_box.Text;
581                         }
582                 }
583
584                 internal int TotalNodeCount {
585                         get { return total_node_count; }
586                         set { total_node_count = value; }
587                 }
588
589                 // TODO: we shouldn't have to compute this on the fly
590                 private Rectangle ViewportRectangle {
591                         get {
592                                 Rectangle res = ClientRectangle;
593
594                                 if (vbar != null && vbar.Visible)
595                                         res.Width -= vbar.Width;
596                                 if (hbar != null && hbar.Visible)
597                                         res.Height -= hbar.Height;
598                                 return res;
599                         }
600                 }
601
602                 [MonoTODO ("Need to know if we are editing, not if editing is enabled")]
603                 private TreeNode GetNodeAt (int y) {
604
605                         if (top_node == null)
606                                 top_node = nodes [0];
607
608                         OpenTreeNodeEnumerator o = new OpenTreeNodeEnumerator (TopNode);
609                         int move = y / ItemHeight + skipped_nodes;
610
611                         for (int i = -1; i < move; i++) {
612                                 if (!o.MoveNext ())
613                                         return null;
614                         }
615
616                         return o.CurrentNode;
617                 }
618
619                 private bool IsTextArea (TreeNode node, int x) {
620                         return node != null && node.Bounds.Left <= x && node.Bounds.Right >= x;
621                 }
622
623
624                 // TODO: Update from supplied node down
625                 internal void UpdateBelow (TreeNode node)
626                 {
627                         // We need to update the current node so the plus/minus block gets update too
628                         Rectangle invalid = new Rectangle (0, node.Bounds.Top, Width, Height - node.Bounds.Top);
629                         Invalidate (invalid);
630                 }
631
632                 internal void UpdateNode (TreeNode node)
633                 {
634                         Rectangle invalid = new Rectangle (0, node.Bounds.Top, Width, node.Bounds.Height);
635                         Invalidate (invalid);
636                 }
637
638                 private void DoPaint (PaintEventArgs pe)
639                 {
640                         if (Width <= 0 || Height <=  0 || Visible == false)
641                                 return;
642
643                         Draw (pe.ClipRectangle);
644                         
645                         pe.Graphics.DrawImage (ImageBuffer, pe.ClipRectangle, pe.ClipRectangle, GraphicsUnit.Pixel);
646                 }
647
648                 private void Draw (Rectangle clip)
649                 {
650                         if (top_node == null && Nodes.Count > 0)
651                                 top_node = nodes [0];
652                         // Decide if we need a scrollbar
653                         int old_open_node_count = open_node_count;
654
655                         Rectangle fill = ClientRectangle;
656                         add_vscroll = false;
657                         add_hscroll = false;
658                         
659                         DeviceContext.FillRectangle (new SolidBrush (BackColor), fill);
660
661                         int depth = 0;
662                         int item_height = ItemHeight;
663                         Font font = Font;
664                         int height = ClientRectangle.Height;
665
666                         open_node_count = 0;
667                         foreach (TreeNode node in nodes) {
668                                 DrawNode (node, clip, ref depth, item_height, font, height);
669                                 depth = 0;
670                         }
671
672                         add_vscroll = (open_node_count * ItemHeight) > ClientRectangle.Height;
673
674                         if (max_node_width > ClientRectangle.Width)
675                                 add_hscroll = true;
676
677                         if (add_vscroll)
678                                 add_hscroll = max_node_width > ClientRectangle.Width - ThemeEngine.Current.VScrollBarDefaultSize.Width;
679                         if (add_hscroll)
680                                 add_vscroll = (open_node_count * ItemHeight) > ClientRectangle.Height - ThemeEngine.Current.HScrollBarDefaultSize.Width;
681
682                         if (add_hscroll) {
683                                 AddHorizontalScrollBar ();
684                         } else if (hbar != null) {
685                                 hbar_offset = 0;
686                                 hbar.Visible = false;
687                         }
688
689                         if (add_vscroll) {
690                                 AddVerticalScrollBar (open_node_count, old_open_node_count != open_node_count);
691                         } else if (vbar != null) {
692                                 vbar.Visible = false;
693                                 skipped_nodes = 0;
694                         }
695
696                         if (add_hscroll && add_vscroll) {
697                                 Rectangle corner = new Rectangle (hbar.Right, vbar.Bottom, vbar.Width, hbar.Height);
698                                 if (clip.IntersectsWith (corner))
699                                         DeviceContext.FillRectangle (new SolidBrush (ThemeEngine.Current.ColorButtonFace), corner);
700                         }
701                 }
702
703                 private void DrawNodePlusMinus (TreeNode node, Rectangle clip, int x, int y, int middle)
704                 {
705                         node.UpdatePlusMinusBounds (x, middle - 4, 8, 8);
706
707                         if (!clip.IntersectsWith (node.PlusMinusBounds))
708                                 return;
709
710                         DeviceContext.DrawRectangle (SystemPens.ControlDark, node.PlusMinusBounds);
711
712                         if (node.IsExpanded) {
713                                 DeviceContext.DrawLine (SystemPens.ControlDarkDark, x + 2, middle, x + 6, middle); 
714                         } else {
715                                 DeviceContext.DrawLine (SystemPens.ControlDarkDark, x + 2, middle, x + 6, middle);
716                                 DeviceContext.DrawLine (SystemPens.ControlDarkDark, x + 4, middle - 2, x + 4, middle + 2);
717                         }
718                 }
719
720                 private void DrawNodeCheckBox (TreeNode node, Rectangle clip, int x, int y)
721                 {
722                         int offset = (ItemHeight - 13);
723
724                         node.UpdateCheckBoxBounds (x + 3, y + offset, 10, 10);
725
726                         // new rectangle that factors in line width
727                         if (!RectsIntersect (clip, x + 3, y + offset, 12, 12))
728                                 return;
729
730                         DeviceContext.DrawRectangle (new Pen (Color.Black, 2), x + 0.5F + 3, y + 0.5F + offset, 11, 11);
731
732                         if (node.Checked) {
733                                 Pen check_pen = new Pen (Color.Black, 1);
734
735                                 DeviceContext.DrawLine (check_pen, x + 6, y + offset + 5, x + 8, y + offset + 8);
736                                 DeviceContext.DrawLine (check_pen, x + 6, y + offset + 6, x + 8, y + offset + 9);
737
738                                 DeviceContext.DrawLine (check_pen, x + 7, y + offset + 8, x + 13, y + offset + 3);
739                                 DeviceContext.DrawLine (check_pen, x + 7, y + offset + 9, x + 13, y + offset + 4);
740                         }
741                 }
742
743                 private void DrawNodeLines (TreeNode node, bool visible, Pen dash, int x, int y, int middle, int item_height, int node_count)
744                 {
745                         int ladjust = 9; // left adjust
746                         int radjust = 0; // right adjust
747
748                         if (node_count > 0 && show_plus_minus)
749                                 ladjust = 13;
750                         if (checkboxes)
751                                 radjust = 3;
752
753                         DeviceContext.DrawLine (dash, x - indent + ladjust, middle, x + radjust, middle);
754
755                         //if (!visible)
756                         //      return;
757
758                         int ly = 0;
759                         if (node.PrevNode != null) {
760                                 int prevadjust = (node.Nodes.Count > 0 && show_plus_minus ? (node.PrevNode.Nodes.Count == 0 ? 0 : 4) :
761                                                 (node.PrevNode.Nodes.Count == 0 ? 0 : 4));
762                                 int myadjust = (node.Nodes.Count > 0 && show_plus_minus ? 4 : 0);
763                                 ly = node.PrevNode.Bounds.Bottom - (item_height / 2) + prevadjust;
764                                 DeviceContext.DrawLine (dash, x - indent + 9, middle - myadjust, x - indent + 9, ly);
765                         } else if (node.Parent != null) {
766                                 int myadjust = (node.Nodes.Count > 0 && show_plus_minus ? 4 : 0);
767                                 ly = node.Parent.Bounds.Bottom - 1;
768                                 DeviceContext.DrawLine (dash, x - indent + 9, middle - myadjust, x - indent + 9, ly);
769                         }
770                 }
771
772                 private void DrawNodeImage (TreeNode node, Rectangle clip, int x, int y)
773                 {
774                         Rectangle r = new Rectangle (x, y + 2, ImageList.ImageSize.Width, 
775                                         ImageList.ImageSize.Height);
776                         if (!RectsIntersect (r, x, y + 2, ImageList.ImageSize.Width, ImageList.ImageSize.Height))
777                                 return;
778
779                         if (node.ImageIndex > -1 && ImageList != null && node.ImageIndex < ImageList.Images.Count) {
780                                 ImageList.Draw (DeviceContext, x, y + 2, ImageList.ImageSize.Width, 
781                                                 ImageList.ImageSize.Height, node.ImageIndex);
782                         } else if (ImageIndex > -1 && ImageList != null && ImageIndex < ImageList.Images.Count) {
783                                 ImageList.Draw (DeviceContext, x, y + 2, ImageList.ImageSize.Width, 
784                                                 ImageList.ImageSize.Height, ImageIndex);
785                         }
786                 }
787
788                 private void DrawEditNode (TreeNode node)
789                 {
790                         SuspendLayout ();
791
792                         if (edit_text_box == null) {
793                                 edit_text_box = new TextBox ();
794                                 edit_text_box.BorderStyle = BorderStyle.FixedSingle;
795                                 edit_text_box.KeyUp += new KeyEventHandler (EditTextBoxKeyDown);
796                                 edit_text_box.Leave += new EventHandler (EditTextBoxLeave);
797                                 Controls.Add (edit_text_box);
798                         }
799
800                         edit_text_box.Bounds = node.Bounds;
801                         edit_text_box.Width += 4;
802
803                         edit_text_box.Text = node.Text;
804                         edit_text_box.Visible = true;
805                         edit_text_box.Focus ();
806                         edit_text_box.SelectAll ();
807
808                         ResumeLayout ();
809                 }
810
811                 private void EditTextBoxKeyDown (object sender, KeyEventArgs e)
812                 {
813                         if (e.KeyCode == Keys.Return)
814                                 EndEdit ();
815                 }
816
817                 private void EditTextBoxLeave (object sender, EventArgs e)
818                 {
819                         EndEdit ();
820                 }
821
822                 private void EndEdit ()
823                 {
824                         edit_text_box.Visible = false;
825                         edit_node.EndEdit (false);
826                         Invalidate (edit_node.Bounds);
827                 }
828
829                 private void UpdateNodeBounds (TreeNode node, int x, int y, int item_height)
830                 {
831                         int width = (int) (node.Text.Length * Font.Size);
832                         node.UpdateBounds (x, y, width, item_height);
833                 }
834
835                 private void DrawNode (TreeNode node, Rectangle clip, ref int depth, int item_height,
836                                 Font font, int max_height)
837                 {
838                         open_node_count++;
839                         int x = (!show_root_lines && node.Parent != null ? depth  - 1 : depth) * indent - hbar_offset;
840                         int y = item_height * (open_node_count - skipped_nodes - 1);
841                         bool visible = (y >= 0 && y < max_height);
842                         int _n_count = node.nodes.Count;
843                         int middle = y + (item_height / 2);
844
845                         // The thing is totally out of the clipping rectangle
846                         if (clip.Top > y + ItemHeight || clip.Bottom < y)
847                                 visible = false;
848
849                         if (show_root_lines || node.Parent != null) {
850                                 x += 5;
851                                 if (_n_count > 0) {
852                                         if (show_plus_minus && visible) {
853                                                 DrawNodePlusMinus (node, clip, x, y, middle);
854                                         }
855                                 }
856                                 x += indent - 5; 
857                         }
858
859                         int ox = x;
860
861                         if (visible && checkboxes) {
862                                 DrawNodeCheckBox (node, clip, ox, y);
863                                 ox += 19;
864                         }
865
866                         if (show_lines)
867                                 DrawNodeLines (node, visible, dash, x, y, middle, item_height, _n_count);
868
869                         if (visible && ImageList != null) {
870                                 if (visible)
871                                         DrawNodeImage (node, clip, ox, y);
872                                 // MS leaves the space for the image if the ImageList is
873                                 // non null regardless of whether or not an image is drawn
874                                 ox += ImageList.ImageSize.Width + 3; // leave a little space so the text isn't against the image
875                         }
876
877                         UpdateNodeBounds (node, ox, y, item_height);
878
879                         bool bounds_in_clip = clip.IntersectsWith (node.Bounds);
880                         if (visible &&  bounds_in_clip && !node.IsEditing) {
881                                 Rectangle r = node.Bounds;
882                                 StringFormat format = new StringFormat ();
883                                 format.LineAlignment = StringAlignment.Center;
884
885                                 r.Y += 2; // we have to adjust this to get nice middle alignment
886                                 
887                                 Color text_color = (Focused && SelectedNode == node ? ThemeEngine.Current.ColorHilightText : node.ForeColor);
888                                 if (Focused) {
889                                         if (SelectedNode == node)
890                                                 DeviceContext.FillRectangle (new SolidBrush (ThemeEngine.Current.ColorHilight), r);
891                                         if (focused_node == node) {
892                                                 Pen dot_pen = new Pen (ThemeEngine.Current.ColorButtonHilight, 1);
893                                                 dot_pen.DashStyle = DashStyle.Dot;
894                                                 DeviceContext.DrawRectangle (new Pen (ThemeEngine.Current.ColorButtonDkShadow),
895                                                                 node.Bounds.X, node.Bounds.Y, node.Bounds.Width - 1, node.Bounds.Height - 1);
896                                                 DeviceContext.DrawRectangle (dot_pen, node.Bounds.X, node.Bounds.Y, node.Bounds.Width - 1, node.Bounds.Height - 1);
897                                         }
898                                 } else {
899                                         if (!HideSelection && SelectedNode == node)
900                                                 DeviceContext.FillRectangle (new SolidBrush (ThemeEngine.Current.ColorButtonFace), node.Bounds);
901                                 }
902                                 DeviceContext.DrawString (node.Text, font, new SolidBrush (text_color), r, format);
903                                 y += item_height + 1;
904                         } else if (visible && bounds_in_clip) {
905                                 DrawEditNode (node);
906                         }
907
908                         if (node.Bounds.Right > max_node_width) {
909                                 max_node_width = node.Bounds.Right;
910                                 if (max_node_width > ClientRectangle.Width && !add_hscroll) {
911                                         max_height -= ItemHeight;
912                                         add_hscroll = true;
913                                 }
914                         }
915
916                         depth++;
917                         if (node.IsExpanded) {
918                                 for (int i = 0; i < _n_count; i++) {
919                                         int tdepth = depth;
920                                         DrawNode (node.nodes [i], clip, ref tdepth, item_height, font, max_height);
921                                 }
922                         }
923
924                 }
925
926                 private void AddVerticalScrollBar (int total_nodes, bool count_changed)
927                 {
928                         if (vbar == null) {
929                                 vbar = new VScrollBar ();
930                                 count_changed = true;
931                         }
932
933                         vbar.Bounds = new Rectangle (ClientRectangle.Width - vbar.Width,
934                                 0, vbar.Width, (add_hscroll ? Height - ThemeEngine.Current.HScrollBarDefaultSize.Height : Height));
935
936                         if (count_changed) {
937                                 vbar.Maximum = total_nodes;
938                                 int height = ClientRectangle.Height;
939                                 vbar.LargeChange = height / ItemHeight;
940                         }
941
942                         if (!vbar_added) {
943                                 Controls.Add (vbar);
944                                 vbar.ValueChanged += new EventHandler (VScrollBarValueChanged);
945                                 vbar_added = true;
946                         }
947
948                         vbar.Visible = true;
949                 }
950
951                 private void AddHorizontalScrollBar ()
952                 {
953                         if (hbar == null)
954                                 hbar = new HScrollBar ();
955
956                         hbar.Bounds = new Rectangle (ClientRectangle.Left, ClientRectangle.Bottom - hbar.Height,
957                                         (add_vscroll ? Width - ThemeEngine.Current.VScrollBarDefaultSize.Width : Width), hbar.Height);
958
959                         if (!hbar_added) {
960                                 Controls.Add (hbar);
961                                 hbar.ValueChanged += new EventHandler (HScrollBarValueChanged);
962                                 hbar_added = true;
963                         }
964
965                         hbar.Visible = true;
966                 }
967
968                 private void SizeChangedHandler (object sender, EventArgs e)
969                 {
970                         SuspendLayout ();
971
972                         if (max_node_width > ClientRectangle.Width) {
973                                 add_hscroll = true;
974                                 AddHorizontalScrollBar ();
975                         }
976
977                         if (vbar != null) {
978                                 int height = (hbar != null && hbar.Visible ? Height - hbar.Height : Height);
979                                 vbar.SetBounds (Right - vbar.Width, 0, 0, height, BoundsSpecified.X | BoundsSpecified.Height);
980                         }
981
982                         if (hbar != null) {
983                                 int width = (vbar != null && vbar.Visible ? Width - vbar.Width : Width);
984                                 hbar.SetBounds (0, Bottom - hbar.Height, width, 0, BoundsSpecified.Y | BoundsSpecified.Width);
985                         }
986
987                         ResumeLayout ();
988                 }
989
990                 private void VScrollBarValueChanged (object sender, EventArgs e)
991                 {
992                         int old_skip = skipped_nodes;
993                         skipped_nodes = vbar.Value;
994
995                         int y_move = (old_skip - skipped_nodes) * ItemHeight;
996                         XplatUI.ScrollWindow (Handle, ViewportRectangle, 0, y_move, false);
997                 }
998
999                 private void HScrollBarValueChanged(object sender, EventArgs e)
1000                 {
1001                         int old_offset = hbar_offset;
1002                         hbar_offset = hbar.Value;
1003
1004                         XplatUI.ScrollWindow (Handle, ViewportRectangle, old_offset - hbar_offset, 0, false);
1005                 }
1006
1007                 private int GetOpenNodeCount ()
1008                 {
1009
1010                         if (Nodes.Count < 1)
1011                                 return 0;
1012
1013                         OpenTreeNodeEnumerator e = new OpenTreeNodeEnumerator (root_node.Nodes [0]);
1014
1015                         int count = 0;
1016                         while (e.MoveNext ()) {
1017                                 count++;
1018                         }
1019
1020                         return count;
1021                 }
1022
1023                 private void MouseDownHandler (object sender, MouseEventArgs e)
1024                 {
1025                         if (!show_plus_minus)
1026                                 return;
1027
1028                         TreeNode node = GetNodeAt (e.Y);
1029                         if (node == null)
1030                                 return;
1031                         if (IsTextArea (node, e.X)) {
1032                                 TreeNode old_selected = selected_node;
1033                                 selected_node = node;
1034                                 if (label_edit && e.Clicks == 1 && selected_node == old_selected) {
1035                                         Rectangle invalid = node.Bounds;
1036                                         node.BeginEdit ();
1037                                         if (edit_node != null) {
1038                                                 invalid = Rectangle.Union (invalid, edit_node.Bounds);
1039                                                 edit_node.EndEdit (false);
1040                                         }
1041                                         edit_node = node;
1042                                         Invalidate (selected_node.Bounds);
1043                                 } else if (selected_node != focused_node) {
1044                                         select_mmove = true;
1045                                         Rectangle invalid = (old_selected == null ? Rectangle.Empty : old_selected.Bounds);
1046                                         invalid = Rectangle.Union (invalid, selected_node.Bounds);
1047                                         Invalidate (invalid);
1048                                 }
1049                         } else if (node.PlusMinusBounds.Contains (e.X, e.Y)) {
1050                                 node.Toggle ();
1051                                 return;
1052                         } else if (node.CheckBoxBounds.Contains (e.X, e.Y)) {
1053                                 node.Checked = !node.Checked;
1054                                 return;
1055                         }
1056                 }
1057
1058                 private void MouseUpHandler (object sender, MouseEventArgs e) {
1059                         if (!select_mmove)
1060                                 return;
1061                                 
1062                         select_mmove = false;
1063
1064                         TreeViewCancelEventArgs ce = new TreeViewCancelEventArgs (selected_node, false, TreeViewAction.ByMouse);
1065                         OnBeforeSelect (ce);
1066
1067                         Rectangle invalid;
1068                         if (!ce.Cancel) {
1069                                 if (focused_node != null)
1070                                         invalid = Rectangle.Union (focused_node.Bounds, selected_node.Bounds);
1071                                 else
1072                                         invalid = selected_node.Bounds;
1073                                 focused_node = selected_node;
1074                                 OnAfterSelect (new TreeViewEventArgs (selected_node, TreeViewAction.ByMouse));
1075                                 Invalidate (invalid);
1076                         } else {
1077                                 selected_node = focused_node;
1078                         }
1079
1080                         
1081                 }
1082
1083                 private void MouseMoveHandler (object sender, MouseEventArgs e) {
1084                         if(!select_mmove)
1085                                 return;
1086                         TreeNode node = GetNodeAt(e.X,e.Y);
1087                         if(node == selected_node)
1088                                 return;
1089                         
1090                         selected_node = focused_node;
1091                         select_mmove = false;
1092                         Refresh();
1093                 }
1094
1095                 private void DoubleClickHandler (object sender, MouseEventArgs e) {
1096                         TreeNode node = GetNodeAt(e.X,e.Y);
1097                         if(node != null) {
1098                                 node.Toggle();
1099                         }
1100                 }
1101
1102                 
1103                 private bool RectsIntersect (Rectangle r, int left, int top, int width, int height)
1104                 {
1105                         return !((r.Left > left + width) || (r.Right < left) ||
1106                                         (r.Top > top + height) || (r.Bottom < top));
1107                 }
1108
1109                 #endregion      // Internal & Private Methods and Properties
1110
1111                 #region Events
1112                 public event TreeViewEventHandler AfterCheck {
1113                         add { on_after_check += value; }
1114                         remove { on_after_check -= value; }
1115                 }
1116
1117                 public event TreeViewEventHandler AfterCollapse {
1118                         add { on_after_collapse += value; }
1119                         remove { on_after_collapse -= value; }
1120                 }
1121
1122                 public event TreeViewEventHandler AfterExpand {
1123                         add { on_after_expand += value; }
1124                         remove { on_after_expand -= value; }
1125                 }
1126
1127                 public event NodeLabelEditEventHandler AfterLabelEdit {
1128                         add { on_after_label_edit += value; }
1129                         remove { on_after_label_edit -= value; }
1130                 }
1131
1132                 public event TreeViewEventHandler AfterSelect {
1133                         add { on_after_select += value; }
1134                         remove { on_after_select -= value; }
1135                 }
1136
1137                 public event TreeViewCancelEventHandler BeforeCheck {
1138                         add { on_before_check += value; }
1139                         remove { on_before_check -= value; }
1140                 }
1141
1142                 public event TreeViewCancelEventHandler BeforeCollapse {
1143                         add { on_before_collapse += value; }
1144                         remove { on_before_collapse -= value; }
1145                 }
1146
1147                 public event TreeViewCancelEventHandler BeforeExpand {
1148                         add { on_before_expand += value; }
1149                         remove { on_before_expand -= value; }
1150                 }
1151
1152                 public event NodeLabelEditEventHandler BeforeLabelEdit {
1153                         add { on_before_label_edit += value; }
1154                         remove { on_before_label_edit -= value; }
1155                 }
1156
1157                 public event TreeViewCancelEventHandler BeforeSelect {
1158                         add { on_before_select += value; }
1159                         remove { on_before_select -= value; }
1160                 }
1161
1162                 [EditorBrowsable (EditorBrowsableState.Never)]  
1163                 [Browsable (false)]
1164                 public new event PaintEventHandler Paint {
1165                         add { base.Paint += value; }
1166                         remove { base.Paint -= value; }
1167                 }
1168
1169                 [EditorBrowsable (EditorBrowsableState.Never)]  
1170                 [Browsable (false)]
1171                 public new event EventHandler TextChanged {
1172                         add { base.TextChanged += value; }
1173                         remove { base.TextChanged -= value; }
1174                 }
1175                 #endregion      // Events
1176         }
1177 }
1178