2fc4fce2058d64cc842d22202eac00d55a204825
[mono.git] / mcs / class / referencesource / System.Web / UI / WebControls / Adapters / MenuAdapter.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="MenuAdapter.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 namespace System.Web.UI.WebControls.Adapters {
8
9     using System;
10     using System.Diagnostics;
11     using System.Globalization;
12     using System.Text;
13     using System.Web;
14     using System.Web.UI;
15     using System.Web.UI.HtmlControls;
16     using System.Web.UI.WebControls;
17
18     public class MenuAdapter : WebControlAdapter, IPostBackEventHandler {
19         
20         private string _path;
21         private Panel _menuPanel;
22         private int _currentAccessKey = 0;
23         private MenuItem _titleItem;
24
25         protected new Menu Control  {
26             get  {
27                 return (Menu) base.Control;
28             }
29         }
30
31         protected internal override void LoadAdapterControlState(Object state) {
32             if (state != null) {
33                 Pair pairState = state as Pair;
34                 if (pairState != null) {
35                     base.LoadAdapterViewState(pairState.First);
36                     _path = (string)pairState.Second;
37                 }
38                 else {
39                     base.LoadAdapterViewState(null);
40                     _path = state as string;
41                 }
42             }
43         }
44
45         private string Escape(string path) {
46             // This function escapes \\ so that they don't get replaced because of
47             // a Netscape 4 
48
49             StringBuilder b = null;
50
51             if (String.IsNullOrEmpty(path)) {
52                 return String.Empty;
53             }
54
55             int startIndex = 0;
56             int count = 0;
57             for (int i = 0; i < path.Length; i++) {
58                 switch (path[i]) {
59                     case '\\':
60
61                         if (i + 1 < path.Length && path[i + 1] == '\\') {
62                             if (b == null) {
63                                 b = new StringBuilder(path.Length + 5);
64                             }
65                             if (count > 0) {
66                                 b.Append(path, startIndex, count);
67                             }
68                             b.Append(@"\_\");
69                             i++;
70                             startIndex = i + 1;
71                             count = 0;
72                         }
73                         else {
74                             count++;
75                         }
76                         break;
77                     case '_':
78                         if (b == null) {
79                             b = new StringBuilder(path.Length + 5);
80                         }
81
82                         if (count > 0) {
83                             b.Append(path, startIndex, count);
84                         }
85
86                         b.Append("__");
87
88                         startIndex = i + 1;
89                         count = 0;
90                         break;
91                     default:
92                         count++;
93                         break;
94                 }
95             }
96
97             if (b == null) {
98                 return path;
99             }
100
101             if (count > 0) {
102                 b.Append(path, startIndex, count);
103             }
104
105             return b.ToString();
106         }
107
108         private string UnEscape(string path) {
109             return path.Replace(@"\\", @"\").Replace(@"\_\", @"\\").Replace("__", "_");
110         }
111
112         protected internal override void OnInit(EventArgs e) {
113             base.OnInit(e);
114             Control.Page.RegisterRequiresControlState(Control);
115         }
116
117         protected internal override void OnPreRender(EventArgs e) {
118             Control.OnPreRender(e, false);
119         }
120
121         protected internal override object SaveAdapterControlState() {
122             object baseState = base.SaveAdapterViewState();
123
124             if (baseState == null) {
125                 if (_path != null) {
126                     return _path;
127                 }
128                 else {
129                     return null;
130                 }
131             }
132             else {
133                 return new Pair(baseState, _path);
134             }
135         }
136
137         private void RenderBreak(HtmlTextWriter writer) {
138             if (Control.Orientation == Orientation.Vertical) {
139                 writer.WriteBreak();
140             }
141             else {
142                 writer.Write(' ');
143             }
144         }
145
146         protected override void RenderBeginTag(HtmlTextWriter writer) {
147             Menu owner = Control;
148             // skip link
149             if (owner.SkipLinkText.Length != 0) {
150                 HyperLink skipLink = new HyperLink();
151                 skipLink.NavigateUrl = '#' + owner.ClientID + "_SkipLink";
152                 skipLink.ImageUrl = owner.SpacerImageUrl;
153                 skipLink.Text = owner.SkipLinkText;
154                 skipLink.Height = Unit.Pixel(1);
155                 skipLink.Width = Unit.Pixel(1);
156                 skipLink.Page = Page;
157                 skipLink.RenderControl(writer);
158             }
159             _menuPanel = new Panel();
160             _menuPanel.ID = owner.UniqueID;
161             _menuPanel.Page = Page;
162             // Determine root menu style
163             MenuItem titleItem;
164             if (_path != null) {
165                 titleItem = owner.Items.FindItem(_path.Split(TreeView.InternalPathSeparator), 0);
166                 _titleItem = titleItem;
167             }
168             else {
169                 titleItem = owner.RootItem;
170             }
171             SubMenuStyle rootMenuStyle = owner.GetSubMenuStyle(titleItem);
172             if (!rootMenuStyle.IsEmpty) {
173                 if (Page != null && Page.SupportsStyleSheets) {
174                     string styleClass = owner.GetSubMenuCssClassName(titleItem);
175                     if (styleClass.Trim().Length > 0) {
176                         _menuPanel.CssClass = styleClass;
177                     }
178                 }
179                 else {
180                     _menuPanel.ApplyStyle(rootMenuStyle);
181                 }
182             }
183             _menuPanel.Width = owner.Width;
184             _menuPanel.Height = owner.Height;
185             _menuPanel.Enabled = owner.IsEnabled;
186             _menuPanel.RenderBeginTag(writer);
187         }
188
189         protected override void RenderContents(HtmlTextWriter writer) {
190             Menu owner = Control;
191             int position = 0;
192             if (_titleItem != null) {
193                 if (_titleItem.Depth + 1 >= owner.MaximumDepth) {
194                     throw new InvalidOperationException(SR.GetString(SR.Menu_InvalidDepth));
195                 }
196                 if (!_titleItem.IsEnabled) {
197                     throw new InvalidOperationException(SR.GetString(SR.Menu_InvalidNavigation));
198                 }
199                 RenderItem(writer, _titleItem, position++);
200                 foreach (MenuItem child in _titleItem.ChildItems) {
201                     RenderItem(writer, child, position++);
202                 }
203                 if (PageAdapter != null) {
204                     PageAdapter.RenderPostBackEvent(writer,
205                                                     owner.UniqueID,
206                                                     "u",
207                                                     SR.GetString(SR.MenuAdapter_Up),
208                                                     SR.GetString(SR.MenuAdapter_UpOneLevel));
209                 }
210                 else {
211                     HyperLink link = new HyperLink();
212                     link.NavigateUrl = Page.ClientScript.GetPostBackClientHyperlink(owner, "u");
213                     link.Text = SR.GetString(SR.MenuAdapter_UpOneLevel);
214                     link.Page = Page;
215                     link.RenderControl(writer);
216                 }
217                 return;
218             }
219             else {
220                 position = 1;
221             }
222             _path = null;
223             foreach(MenuItem child in owner.Items) {
224                 RenderItem(writer, child, position++);
225                 if (owner.StaticDisplayLevels > 1 && child.ChildItems.Count > 0) {
226                     RenderContentsRecursive(writer, child, 1, owner.StaticDisplayLevels);
227                 }
228             }
229         }
230
231         private void RenderContentsRecursive(HtmlTextWriter writer, MenuItem parentItem, int depth, int maxDepth) {
232             int position = 1;
233             foreach(MenuItem child in parentItem.ChildItems) {
234                 RenderItem(writer, child, position++);
235                 if (depth + 1 < maxDepth && child.ChildItems.Count > 0) {
236                     RenderContentsRecursive(writer, child, depth + 1, maxDepth);
237                 }
238             }
239         }
240
241         protected override void RenderEndTag(HtmlTextWriter writer) {
242             _menuPanel.RenderEndTag(writer);
243             // skip link
244             if (Control.SkipLinkText.Length != 0) {
245                 HtmlAnchor skipAnchor = new HtmlAnchor();
246                 skipAnchor.Name = Control.ClientID + "_SkipLink";
247                 skipAnchor.Page = Page;
248                 skipAnchor.RenderControl(writer);
249             }
250         }
251
252         private void RenderExpand(HtmlTextWriter writer, MenuItem item, Menu owner) {
253             string expandImageUrl = item.GetExpandImageUrl();
254             if (expandImageUrl.Length > 0) {
255                 Image expandImage = new Image();
256                 expandImage.ImageUrl = expandImageUrl;
257                 expandImage.GenerateEmptyAlternateText = true;
258                 if (item.Depth < owner.StaticDisplayLevels) {
259                     
260                     expandImage.AlternateText = String.Format(
261                         CultureInfo.CurrentCulture,
262                         owner.StaticPopOutImageTextFormatString,
263                         item.Text);
264                 }
265                 else {
266                     
267                     expandImage.AlternateText = String.Format(
268                         CultureInfo.CurrentCulture,
269                         owner.DynamicPopOutImageTextFormatString,
270                         item.Text);
271                 }
272                 // expandImage.ImageAlign = ImageAlign.Right;
273                 expandImage.ImageAlign = ImageAlign.AbsMiddle;
274                 expandImage.Page = Page;
275                 expandImage.RenderControl(writer);
276             }
277             else {
278                 writer.Write(' ');
279                 if (item.Depth < owner.StaticDisplayLevels &&
280                     owner.StaticPopOutImageTextFormatString.Length != 0) {
281                     
282                     writer.Write(HttpUtility.HtmlEncode(String.Format(
283                         CultureInfo.CurrentCulture,
284                         owner.StaticPopOutImageTextFormatString,
285                         item.Text)));
286                 }
287                 else if (item.Depth >= owner.StaticDisplayLevels &&
288                     owner.DynamicPopOutImageTextFormatString.Length != 0) {
289                     
290                     writer.Write(HttpUtility.HtmlEncode(String.Format(
291                         CultureInfo.CurrentCulture,
292                         owner.DynamicPopOutImageTextFormatString,
293                         item.Text)));
294                 }
295                 else {
296                     writer.Write(HttpUtility.HtmlEncode(SR.GetString(SR.MenuAdapter_Expand, item.Text)));
297                 }
298             }
299         }
300
301         protected virtual internal void RenderItem(HtmlTextWriter writer, MenuItem item, int position) {
302             Menu owner = Control;
303             MenuItemStyle mergedStyle = owner.GetMenuItemStyle(item);
304
305             string imageUrl = item.ImageUrl;
306             int depth = item.Depth;
307             int depthPlusOne = depth + 1;
308             string toolTip = item.ToolTip;
309             string navigateUrl = item.NavigateUrl;
310             string text = item.Text;
311             bool enabled = item.IsEnabled;
312             bool selectable = item.Selectable;
313             MenuItemCollection childItems = item.ChildItems;
314
315             // Top separator
316             string topSeparatorUrl = null;
317             if (depth < owner.StaticDisplayLevels && owner.StaticTopSeparatorImageUrl.Length != 0) {
318                 topSeparatorUrl = owner.StaticTopSeparatorImageUrl;
319             }
320             else if (depth >= owner.StaticDisplayLevels && owner.DynamicTopSeparatorImageUrl.Length != 0) {
321                 topSeparatorUrl = owner.DynamicTopSeparatorImageUrl;
322             }
323             if (topSeparatorUrl != null) {
324                 Image separatorImage = new Image();
325                 separatorImage.ImageUrl = topSeparatorUrl;
326                 separatorImage.GenerateEmptyAlternateText = true; // XHtml compliance
327                 separatorImage.Page = Page;
328                 separatorImage.RenderControl(writer);
329                 RenderBreak(writer);
330             }
331
332             // Don't render the top spacing if this is the first root item
333             if ((mergedStyle != null) && !mergedStyle.ItemSpacing.IsEmpty &&
334                 ((_titleItem != null) || (position != 0))) {
335                 RenderSpace(writer, mergedStyle.ItemSpacing, owner.Orientation);
336             }
337
338             // Item span
339             Panel itemPanel = new SpanPanel();
340             itemPanel.Enabled = enabled;
341             itemPanel.Page = Page;
342
343             // Apply styles
344             if (Page != null && Page.SupportsStyleSheets) {
345                 string styleClass = owner.GetCssClassName(item, false);
346                 if (styleClass.Trim().Length > 0) {
347                     itemPanel.CssClass = styleClass;
348                 }
349             }
350             else if (mergedStyle != null) {
351                 itemPanel.ApplyStyle(mergedStyle);
352             }
353
354             // Tooltip
355             if (item.ToolTip.Length != 0) {
356                 itemPanel.ToolTip = item.ToolTip;
357             }
358
359             // Render item begin tag
360             itemPanel.RenderBeginTag(writer);
361
362             // If there is a navigation url on this item, set up the navigation stuff if:
363             // - the item is the current title item for this level
364             // - the item has no children
365             // - the item is a static item of depth + 1 < StaticDisplayLevels
366             bool clickOpensThisNode = !((position == 0) || 
367                                         (childItems.Count == 0) ||
368                                         (depthPlusOne < owner.StaticDisplayLevels) || 
369                                         (depthPlusOne >= owner.MaximumDepth));
370            
371             // Indent
372             if (position != 0 &&
373                 depth > 0 &&
374                 owner.StaticSubMenuIndent != Unit.Pixel(0) &&
375                 depth < owner.StaticDisplayLevels) {
376                 Image spacerImage = new Image();
377                 spacerImage.ImageUrl = owner.SpacerImageUrl;
378                 spacerImage.GenerateEmptyAlternateText = true; // XHtml compliance
379                 double indent = owner.StaticSubMenuIndent.Value * depth;
380                 if (indent < Unit.MaxValue) {
381                     spacerImage.Width = new Unit(indent, owner.StaticSubMenuIndent.Type);
382                 }
383                 else {
384                     spacerImage.Width = new Unit(Unit.MaxValue, owner.StaticSubMenuIndent.Type);;
385                 }
386                 spacerImage.Height = Unit.Pixel(1);
387                 spacerImage.Page = Page;
388                 spacerImage.RenderControl(writer);
389             }
390
391             // Render out the item icon, if it is set and if the item is not templated (VSWhidbey 501618)
392             if (imageUrl.Length > 0 && item.NotTemplated()) {
393                 Image newImage = new Image();
394                 newImage.ImageUrl = imageUrl;
395                 if (toolTip.Length != 0) {
396                     newImage.AlternateText = toolTip;
397                 }
398                 else {
399                     newImage.GenerateEmptyAlternateText = true; // XHtml compliance
400                 }
401                 newImage.Page = Page;
402                 newImage.RenderControl(writer);
403                 writer.Write(' ');
404             }
405
406             bool applyInlineBorder;
407             string linkClass;
408             if (Page != null && Page.SupportsStyleSheets) {
409                 linkClass = owner.GetCssClassName(item, true, out applyInlineBorder);
410             }
411             else {
412                 linkClass = String.Empty;
413                 applyInlineBorder = false;
414             }
415             if (enabled && (clickOpensThisNode || selectable)) {
416                 string accessKey = owner.AccessKey;
417                 string itemAccessKey = ((position == 0 || (position == 1 && depth == 0)) && accessKey.Length != 0) ?
418                     accessKey :
419                     null;
420                 if (navigateUrl.Length > 0 && !clickOpensThisNode) {
421                     if (PageAdapter != null) {
422                         PageAdapter.RenderBeginHyperlink(writer,
423                                                          owner.ResolveClientUrl(navigateUrl),
424                                                          true,
425                                                          SR.GetString(SR.Adapter_GoLabel),
426                                                          itemAccessKey != null ?
427                                                           itemAccessKey :
428                                                           (_currentAccessKey < 10 ?
429                                                             (_currentAccessKey++).ToString(CultureInfo.InvariantCulture) :
430                                                             null));
431                         writer.Write(HttpUtility.HtmlEncode(item.FormattedText));
432                         PageAdapter.RenderEndHyperlink(writer);
433                     }
434                     else {
435                         HyperLink link = new HyperLink();
436                         link.NavigateUrl = owner.ResolveClientUrl(navigateUrl);
437                         string target = item.Target;
438                         if (String.IsNullOrEmpty(target)) {
439                             target = owner.Target;
440                         }
441                         if (!String.IsNullOrEmpty(target)) {
442                             link.Target = target;
443                         }
444                         link.AccessKey = itemAccessKey;
445                         link.Page = Page;
446                         if (writer is Html32TextWriter) {
447                             link.RenderBeginTag(writer);
448                             SpanPanel lbl = new SpanPanel();
449                             lbl.Page = Page;
450                             RenderStyle(writer, lbl, linkClass, mergedStyle, applyInlineBorder);
451                             lbl.RenderBeginTag(writer);
452                             item.RenderText(writer);
453                             lbl.RenderEndTag(writer);
454                             link.RenderEndTag(writer);
455                         }
456                         else {
457                             RenderStyle(writer, link, linkClass, mergedStyle, applyInlineBorder);
458                             link.RenderBeginTag(writer);
459                             item.RenderText(writer);
460                             link.RenderEndTag(writer);
461                         }
462                     }
463                 }
464                 // Otherwise, write out a postback that will open or select the item
465                 else {
466                     if (PageAdapter != null) {
467                         PageAdapter.RenderPostBackEvent(writer,
468                                                         owner.UniqueID,
469                                                         (clickOpensThisNode ? 'o' : 'b') +
470                                                             Escape(item.InternalValuePath),
471                                                         SR.GetString(SR.Adapter_OKLabel),
472                                                         item.FormattedText,
473                                                         null,
474                                                         itemAccessKey != null ?
475                                                           itemAccessKey :
476                                                           (_currentAccessKey < 10 ?
477                                                          (_currentAccessKey++).ToString(CultureInfo.InvariantCulture) :
478                                                          null));
479
480                         // Expand image
481                         if (clickOpensThisNode) {
482                             RenderExpand(writer, item, owner);
483                         }
484                     }
485                     else {
486                         HyperLink link = new HyperLink();
487                         link.NavigateUrl = Page.ClientScript.GetPostBackClientHyperlink(owner,
488                             (clickOpensThisNode ? 'o' : 'b') + Escape(item.InternalValuePath), true);
489                         link.AccessKey = itemAccessKey;
490                         link.Page = Page;
491                         if (writer is Html32TextWriter) {
492                             link.RenderBeginTag(writer);
493                             SpanPanel lbl = new SpanPanel();
494                             lbl.Page = Page;
495                             RenderStyle(writer, lbl, linkClass, mergedStyle, applyInlineBorder);
496                             lbl.RenderBeginTag(writer);
497                             item.RenderText(writer);
498                             if (clickOpensThisNode) {
499                                 RenderExpand(writer, item, owner);
500                             }
501                             lbl.RenderEndTag(writer);
502                             link.RenderEndTag(writer);
503                         }
504                         else {
505                             RenderStyle(writer, link, linkClass, mergedStyle, applyInlineBorder);
506                             link.RenderBeginTag(writer);
507                             item.RenderText(writer);
508                             if (clickOpensThisNode) {
509                                 RenderExpand(writer, item, owner);
510                             }
511                             link.RenderEndTag(writer);
512                         }
513                     }
514                 }
515             }
516             else {
517                 item.RenderText(writer);
518             }
519             itemPanel.RenderEndTag(writer);
520
521             // Bottom (or right) item spacing
522             RenderBreak(writer);
523             if ((mergedStyle != null) && !mergedStyle.ItemSpacing.IsEmpty) {
524                 RenderSpace(writer, mergedStyle.ItemSpacing, owner.Orientation);
525             }
526
527             // Bottom separator
528             string bottomSeparatorUrl = null;
529             if (item.SeparatorImageUrl.Length != 0) {
530                 bottomSeparatorUrl = item.SeparatorImageUrl;
531             }
532             else if ((depth < owner.StaticDisplayLevels) && (owner.StaticBottomSeparatorImageUrl.Length != 0)) {
533                 bottomSeparatorUrl = owner.StaticBottomSeparatorImageUrl;
534             }
535             else if ((depth >= owner.StaticDisplayLevels) && (owner.DynamicBottomSeparatorImageUrl.Length != 0)) {
536                 bottomSeparatorUrl = owner.DynamicBottomSeparatorImageUrl;
537             }
538             if (bottomSeparatorUrl != null) {
539                 Image separatorImage = new Image();
540                 separatorImage.ImageUrl = bottomSeparatorUrl;
541                 separatorImage.GenerateEmptyAlternateText = true; // XHtml compliance
542                 separatorImage.Page = Page;
543                 separatorImage.RenderControl(writer);
544                 RenderBreak(writer);
545             }
546         }
547         
548         private void RenderSpace(HtmlTextWriter writer, Unit space, Orientation orientation) {
549             Image spacerImage = new Image();
550             spacerImage.ImageUrl = Control.SpacerImageUrl;
551             spacerImage.GenerateEmptyAlternateText = true; // XHtml compliance
552             spacerImage.Page = Page;
553             if (orientation == Orientation.Vertical) {
554                 spacerImage.Height = space;
555                 spacerImage.Width = Unit.Pixel(1);
556                 spacerImage.RenderControl(writer);
557                 writer.WriteBreak();
558             }
559             else {
560                 spacerImage.Width = space;
561                 spacerImage.Height = Unit.Pixel(1);
562                 spacerImage.RenderControl(writer);
563             }
564         }
565
566         private void RenderStyle(HtmlTextWriter writer, WebControl control, string className, MenuItemStyle style, bool applyInlineBorder) {
567             if (!String.IsNullOrEmpty(className)) {
568                 control.CssClass = className;
569                 if (applyInlineBorder) {
570                     // Add inline style to force the border to none to override any CssClass (VSWhidbey 336610)
571                     writer.AddStyleAttribute(HtmlTextWriterStyle.BorderStyle, "none");
572                     // And an inline font-size of 1em to avoid squaring relative font sizes by applying them twice
573                     writer.AddStyleAttribute(HtmlTextWriterStyle.FontSize, "1em");
574                 }
575             }
576             else if (style != null) {
577                 control.ApplyStyle(style);
578             }
579         }
580
581         void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) {
582             RaisePostBackEvent(eventArgument);
583         }
584
585         protected virtual void RaisePostBackEvent(string eventArgument) {
586             if (eventArgument.Length == 0) {
587                 return;
588             }
589
590             // On postback, see what kind of event we received by checking the first character
591             char eventType = eventArgument[0];
592
593             switch (eventType) {
594                 case 'o': {
595                     // 'o' means that we're opening the secondary navigation for this node
596                     // Get the path of the item specified in the eventArgument
597                     // The replace is to correct a Netscape 4 bug
598                     string newPath = UnEscape(HttpUtility.UrlDecode(eventArgument.Substring(1)));
599                     // Check the number of separator characters in the argument (should not be more than the max depth)
600                     int matches = 0;
601                     for (int i = 0; i < newPath.Length; i++) {
602                         if (newPath[i] == TreeView.InternalPathSeparator) {
603                             if (++matches >= Control.MaximumDepth) {
604                                 throw new InvalidOperationException(SR.GetString(SR.Menu_InvalidDepth));
605                             }
606                         }
607                     }
608                     // Check this item does have subitem
609                     // (otherwise, it should just be selected, not opened)
610                     MenuItem item = Control.Items.FindItem(newPath.Split(TreeView.InternalPathSeparator), 0);
611                     if (item != null) {
612                         if (item.ChildItems.Count > 0) {
613                             _path = newPath;
614                         }
615                         else {
616                             Control.InternalRaisePostBackEvent(newPath);
617                         }
618                     }
619                     break;
620                 }
621                 case 'u' : 
622                     // 'u' means go up a level
623                     if (_path != null) {
624                         // Find that item in the tree
625                         MenuItem item = Control.Items.FindItem(_path.Split(TreeView.InternalPathSeparator), 0);
626                         if (item != null) {
627                             MenuItem parentItem = item.Parent;
628                             if (parentItem != null && item.Depth + 1 > Control.StaticDisplayLevels) {
629                                 _path = parentItem.InternalValuePath;
630                             }
631                             else {
632                                 _path = null;
633                             }
634                         }
635                     }
636                     break;
637
638                 case 'b' : 
639                     // 'b' means bubble for to the control to handle
640                     // The replace is to correct a Netscape 4 bug
641                     Control.InternalRaisePostBackEvent(
642                         UnEscape(HttpUtility.UrlDecode(eventArgument.Substring(1))));
643                     break;
644             }
645         }
646
647         internal void SetPath(string path) {
648             _path = path;
649         }
650
651         private class SpanPanel : Panel {
652             protected override HtmlTextWriterTag TagKey {
653                 get {
654                     return HtmlTextWriterTag.Span;
655                 }
656             }
657         }
658     }
659 }