1 //------------------------------------------------------------------------------
2 // <copyright file="MenuRendererStandards.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Web.UI.WebControls {
8     using System.Collections;
9     using System.Collections.Generic;
10     using System.Drawing;
11     using System.Globalization;
12     using System.Linq;
13     using System.Web.Util;
14 
15     public partial class Menu {
16         /// <devdoc>The standards-compliant Menu renderer</devdoc>
17         internal class MenuRendererStandards : MenuRenderer {
18             private string _dynamicPopOutUrl;
19             private string _staticPopOutUrl;
20 
MenuRendererStandards(Menu menu)21             public MenuRendererStandards(Menu menu) : base(menu) { }
22 
23             private string DynamicPopOutUrl {
24                 get {
25                     if (_dynamicPopOutUrl == null) {
26                         _dynamicPopOutUrl = GetDynamicPopOutImageUrl();
27                     }
28                     return _dynamicPopOutUrl;
29                 }
30             }
31 
32             protected virtual string SpacerImageUrl {
33                 get {
34                     return Menu.SpacerImageUrl;
35                 }
36             }
37 
38             private string StaticPopOutUrl {
39                 get {
40                     if (_staticPopOutUrl == null) {
41                         _staticPopOutUrl = GetStaticPopOutImageUrl();
42                     }
43                     return _staticPopOutUrl;
44                 }
45             }
46 
AddScriptReference()47             private void AddScriptReference() {
48                 string key = "_registerMenu_" + Menu.ClientID;
49                 string initScript = String.Format(CultureInfo.InvariantCulture,
50                     "<script type='text/javascript'>" +
51                     "new Sys.WebForms.Menu({{ element: '{0}', disappearAfter: {1}, orientation: '{2}', tabIndex: {3}, disabled: {4} }});" +
52                     "</script>",
53                     Menu.ClientID,
54                     Menu.DisappearAfter,
55                     Menu.Orientation.ToString().ToLowerInvariant(),
56                     Menu.TabIndex,
57                     (!Menu.IsEnabled).ToString().ToLowerInvariant());
58 
59                 if (Menu.Page.ScriptManager != null) {
60                     Menu.Page.ScriptManager.RegisterClientScriptResource(Menu.Page, typeof(Menu), "MenuStandards.js");
61                     Menu.Page.ScriptManager.RegisterStartupScript(Menu, typeof(MenuRendererStandards), key, initScript, false);
62                 }
63                 else {
64                     Menu.Page.ClientScript.RegisterClientScriptResource(Menu.Page, typeof(Menu), "MenuStandards.js");
65                     Menu.Page.ClientScript.RegisterStartupScript(typeof(MenuRendererStandards), key, initScript);
66                 }
67             }
68 
AddStyleBlock()69             private void AddStyleBlock() {
70                 if (Menu.IncludeStyleBlock) {
71                     Menu.Page.Header.Controls.Add(CreateStyleBlock());
72                 }
73             }
74 
CreateStyleBlock()75             private StyleBlock CreateStyleBlock() {
76                 StyleBlock styleBlock = new StyleBlock();
77                 Style rootMenuItemStyle = Menu.RootMenuItemStyle;
78 
79                 // drop the font and forecolor from the control style, those are applied
80                 // to the anchors directly with rootMenuItemStyle.
81                 Style menuStyle = null;
82                 if (!Menu.ControlStyle.IsEmpty) {
83                     menuStyle = new Style();
84                     menuStyle.CopyFrom(Menu.ControlStyle);
85                     // Relative sizes should not be multiplied (VSWhidbey 457610)
86                     menuStyle.Font.Reset();
87                     menuStyle.ForeColor = Color.Empty;
88                 }
89 
90                 // Menu surrounding DIV style -- without ForeColor or Font,
91                 // those are applied directly to the '#Menu a' selector.
92 
93                 styleBlock.AddStyleDefinition("#{0}", Menu.ClientID)
94                           .AddStyles(menuStyle);
95 
96                 // Image styles
97 
98                 styleBlock.AddStyleDefinition("#{0} img.icon", Menu.ClientID)
99                           .AddStyle(HtmlTextWriterStyle.BorderStyle, "none")
100                           .AddStyle(HtmlTextWriterStyle.VerticalAlign, "middle");
101 
102                 styleBlock.AddStyleDefinition("#{0} img.separator", Menu.ClientID)
103                           .AddStyle(HtmlTextWriterStyle.BorderStyle, "none")
104                           .AddStyle(HtmlTextWriterStyle.Display, "block");
105 
106                 if (Menu.Orientation == Orientation.Horizontal) {
107                     styleBlock.AddStyleDefinition("#{0} img.horizontal-separator", Menu.ClientID)
108                               .AddStyle(HtmlTextWriterStyle.BorderStyle, "none")
109                               .AddStyle(HtmlTextWriterStyle.VerticalAlign, "middle");
110                 }
111 
112                 // Menu styles
113 
114                 styleBlock.AddStyleDefinition("#{0} ul", Menu.ClientID)
115                           .AddStyle("list-style", "none")
116                           .AddStyle(HtmlTextWriterStyle.Margin, "0")
117                           .AddStyle(HtmlTextWriterStyle.Padding, "0")
118                           .AddStyle(HtmlTextWriterStyle.Width, "auto");
119 
120                 styleBlock.AddStyleDefinition("#{0} ul.static", Menu.ClientID)
121                           .AddStyles(Menu._staticMenuStyle);
122 
123                 var ulDynamic = styleBlock.AddStyleDefinition("#{0} ul.dynamic", Menu.ClientID)
124                                           .AddStyles(Menu._dynamicMenuStyle)
125                                           .AddStyle(HtmlTextWriterStyle.ZIndex, "1");
126 
127                 if (Menu.DynamicHorizontalOffset != 0) {
128                     ulDynamic.AddStyle(HtmlTextWriterStyle.MarginLeft, Menu.DynamicHorizontalOffset.ToString(CultureInfo.InvariantCulture) + "px");
129                 }
130 
131                 if (Menu.DynamicVerticalOffset != 0) {
132                     ulDynamic.AddStyle(HtmlTextWriterStyle.MarginTop, Menu.DynamicVerticalOffset.ToString(CultureInfo.InvariantCulture) + "px");
133                 }
134 
135                 if (Menu._levelStyles != null) {
136                     int index = 1;
137 
138                     foreach (SubMenuStyle style in Menu._levelStyles) {
139                         styleBlock.AddStyleDefinition("#{0} ul.level{1}", Menu.ClientID, index++)
140                                   .AddStyles(style);
141                     }
142                 }
143 
144                 // Menu item styles
145 
146                 // MenuItems have a 14px default right padding.
147                 // This is necessary to prevent the default (and the typical) popout image from going under
148                 // the menu item text when it is one of the longer items in the parent menu.
149                 // It is 'px' based since its based on the image size not font size.
150                 styleBlock.AddStyleDefinition("#{0} a", Menu.ClientID)
151                           .AddStyle(HtmlTextWriterStyle.WhiteSpace, "nowrap")
152                           .AddStyle(HtmlTextWriterStyle.Display, "block")
153                           .AddStyles(rootMenuItemStyle);
154 
155                 var menuItemStatic = styleBlock.AddStyleDefinition("#{0} a.static", Menu.ClientID);
156                 if ((Menu.Orientation == Orientation.Horizontal) &&
157                     ((Menu._staticItemStyle == null) || (Menu._staticItemStyle.HorizontalPadding.IsEmpty))) {
158                     menuItemStatic.AddStyle(HtmlTextWriterStyle.PaddingLeft, "0.15em")
159                                   .AddStyle(HtmlTextWriterStyle.PaddingRight, "0.15em");
160                 }
161                 menuItemStatic.AddStyles(Menu._staticItemStyle);
162 
163                 if (Menu._staticItemStyle != null) {
164                     menuItemStatic.AddStyles(Menu._staticItemStyle.HyperLinkStyle);
165                 }
166 
167                 if (!String.IsNullOrEmpty(StaticPopOutUrl)) {
168                     styleBlock.AddStyleDefinition("#{0} a.popout", Menu.ClientID)
169                         .AddStyle("background-image", "url(\"" + Menu.ResolveClientUrl(StaticPopOutUrl).Replace("\"", "\\\"") + "\")")
170                         .AddStyle("background-repeat", "no-repeat")
171                         .AddStyle("background-position", "right center")
172                         .AddStyle(HtmlTextWriterStyle.PaddingRight, "14px");
173                 }
174 
175                 if (!String.IsNullOrEmpty(DynamicPopOutUrl)) {
176                     // Check if dynamic popout is the same as the static one, so there's no need for a separate rule
177                     if (DynamicPopOutUrl != StaticPopOutUrl) {
178                         styleBlock.AddStyleDefinition("#{0} a.popout-dynamic", Menu.ClientID)
179                             .AddStyle("background", "url(\"" + Menu.ResolveClientUrl(DynamicPopOutUrl).Replace("\"", "\\\"") + "\") no-repeat right center")
180                             .AddStyle(HtmlTextWriterStyle.PaddingRight, "14px");
181                     }
182                 }
183 
184                 var styleBlockStyles = styleBlock.AddStyleDefinition("#{0} a.dynamic", Menu.ClientID)
185                     .AddStyles(Menu._dynamicItemStyle);
186                 if (Menu._dynamicItemStyle != null) {
187                     styleBlockStyles.AddStyles(Menu._dynamicItemStyle.HyperLinkStyle);
188                 }
189 
190                 if (Menu._levelMenuItemStyles != null || Menu.StaticDisplayLevels > 1) {
191                     int lastIndex = Menu.StaticDisplayLevels;
192                     if (Menu._levelMenuItemStyles != null) {
193                         lastIndex = Math.Max(lastIndex, Menu._levelMenuItemStyles.Count);
194                     }
195 
196                     for (int index = 0; index < lastIndex; ++index) {
197                         var style = styleBlock.AddStyleDefinition("#{0} a.level{1}", Menu.ClientID, index + 1);
198 
199                         if (index > 0 && index < Menu.StaticDisplayLevels) {
200                             Unit indent = Menu.StaticSubMenuIndent;
201 
202                             // The default value of Menu.StaticSubMenuIndent is Unit.Empty, and the effective default value
203                             // for list rendering is either 1em (vertical) or empty (horizontal).
204                             if (indent.IsEmpty && Menu.Orientation == Orientation.Vertical) {
205                                 indent = new Unit(1, UnitType.Em);
206                             }
207 
208                             if (!indent.IsEmpty && indent.Value != 0) {
209                                 double indentValue = indent.Value * index;
210                                 if (indentValue < Unit.MaxValue) {
211                                     indent = new Unit(indentValue, indent.Type);
212                                 }
213                                 else {
214                                     indent = new Unit(Unit.MaxValue, indent.Type);
215                                 }
216 
217                                 style.AddStyle(HtmlTextWriterStyle.PaddingLeft, indent.ToString(CultureInfo.InvariantCulture));
218                             }
219                         }
220 
221                         if (Menu._levelMenuItemStyles != null && index < Menu._levelMenuItemStyles.Count) {
222                             var levelItemStyle = Menu._levelMenuItemStyles[index];
223                             style.AddStyles(levelItemStyle).AddStyles(levelItemStyle.HyperLinkStyle);
224                         }
225                     }
226                 }
227 
228                 styleBlockStyles = styleBlock.AddStyleDefinition("#{0} a.static.selected", Menu.ClientID)
229                           .AddStyles(Menu._staticSelectedStyle);
230                 if (Menu._staticSelectedStyle != null) {
231                     styleBlockStyles.AddStyles(Menu._staticSelectedStyle.HyperLinkStyle);
232                 }
233 
234                 styleBlockStyles = styleBlock.AddStyleDefinition("#{0} a.dynamic.selected", Menu.ClientID)
235                           .AddStyles(Menu._dynamicSelectedStyle);
236                 if (Menu._dynamicSelectedStyle != null) {
237                     styleBlockStyles.AddStyles(Menu._dynamicSelectedStyle.HyperLinkStyle);
238                 }
239 
240                 styleBlock.AddStyleDefinition("#{0} a.static.highlighted", Menu.ClientID)
241                           .AddStyles(Menu._staticHoverStyle);
242 
243                 styleBlock.AddStyleDefinition("#{0} a.dynamic.highlighted", Menu.ClientID)
244                           .AddStyles(Menu._dynamicHoverStyle);
245 
246                 if (Menu._levelSelectedStyles != null) {
247                     int index = 1;
248 
249                     foreach (MenuItemStyle style in Menu._levelSelectedStyles) {
250                         styleBlock.AddStyleDefinition("#{0} a.selected.level{1}", Menu.ClientID, index++)
251                                   .AddStyles(style).AddStyles(style.HyperLinkStyle);
252                     }
253                 }
254 
255                 return styleBlock;
256             }
257 
GetCssClass(int level, Style staticStyle, Style dynamicStyle, IList levelStyles)258             private string GetCssClass(int level, Style staticStyle, Style dynamicStyle, IList levelStyles) {
259                 string result = "level" + level;
260                 Style style;
261 
262                 if (level > Menu.StaticDisplayLevels) {
263                     style = dynamicStyle;
264                 }
265                 else {
266                     if (Menu.DesignMode) {
267                         result += " static";
268                         if (Menu.Orientation == Orientation.Horizontal) {
269                             result += " horizontal";
270                         }
271                     }
272 
273                     style = staticStyle;
274                 }
275 
276                 if (style != null && !String.IsNullOrEmpty(style.CssClass)) {
277                     result += " " + style.CssClass;
278                 }
279 
280                 if (levelStyles != null && levelStyles.Count >= level) {
281                     Style levelStyle = (Style)levelStyles[level - 1];
282 
283                     if (levelStyle != null && !String.IsNullOrEmpty(levelStyle.CssClass)) {
284                         result += " " + levelStyle.CssClass;
285                     }
286                 }
287 
288                 return result;
289             }
290 
GetDynamicPopOutImageUrl()291             protected virtual string GetDynamicPopOutImageUrl() {
292                 string url = Menu.DynamicPopOutImageUrl;
293                 if (String.IsNullOrEmpty(url) && Menu.DynamicEnableDefaultPopOutImage) {
294                     url = Menu.GetImageUrl(Menu.PopOutImageIndex);
295                 }
296                 return url;
297             }
298 
GetStaticPopOutImageUrl()299             protected virtual string GetStaticPopOutImageUrl() {
300                 string url = Menu.StaticPopOutImageUrl;
301                 if (String.IsNullOrEmpty(url) && Menu.StaticEnableDefaultPopOutImage) {
302                     url = Menu.GetImageUrl(Menu.PopOutImageIndex);
303                 }
304                 return url;
305             }
306 
GetMenuCssClass(int level)307             private string GetMenuCssClass(int level) {
308                 return GetCssClass(level, Menu.StaticMenuStyle, Menu.DynamicMenuStyle, Menu._levelStyles);
309             }
310 
GetMenuItemCssClass(MenuItem item, int level)311             private string GetMenuItemCssClass(MenuItem item, int level) {
312                 // give the A the proper popout class
313                 string cssClass = null;
314                 if (ShouldHavePopOutImage(item)) {
315                     if (level > Menu.StaticDisplayLevels) {
316                         if (!String.IsNullOrEmpty(DynamicPopOutUrl)) {
317                             cssClass = (DynamicPopOutUrl == StaticPopOutUrl) ? "popout" : "popout-dynamic";
318                         }
319                     }
320                     else if (!String.IsNullOrEmpty(StaticPopOutUrl)) {
321                         cssClass = "popout";
322                     }
323                 }
324                 string levelCssClass = GetCssClass(level, Menu.StaticMenuItemStyle, Menu.DynamicMenuItemStyle, Menu._levelMenuItemStyles);
325                 if (!String.IsNullOrEmpty(cssClass)) {
326                     return cssClass + " " + levelCssClass;
327                 }
328                 else {
329                     return levelCssClass;
330                 }
331             }
332 
GetPostBackEventReference(MenuItem item)333             protected virtual string GetPostBackEventReference(MenuItem item) {
334                 return Menu.Page.ClientScript.GetPostBackEventReference(Menu, item.InternalValuePath, true);
335             }
336 
IsChildPastMaximumDepth(MenuItem item)337             private bool IsChildPastMaximumDepth(MenuItem item) {
338                 return (item.Depth + 1 >= Menu.MaximumDepth);
339             }
340 
IsChildDepthDynamic(MenuItem item)341             private bool IsChildDepthDynamic(MenuItem item) {
342                 return (item.Depth + 1 >= Menu.StaticDisplayLevels);
343             }
344 
IsDepthDynamic(MenuItem item)345             private bool IsDepthDynamic(MenuItem item) {
346                 // Depth is 0 based. StaticDisplayLevels is 1 based because it is a counter
347                 // (1 means show "one level" statically -- so, show Depth 0 statically but not Depth 1).
348                 // Therefore, it is dynamic if the item's depth is greater than or equal to the static levels.
349                 // Depth = 2 (3rd level), StaticDisplayLevels = 3 ==> Static
350                 // Depth = 2 (3rd level), StaticDisplayLevels = 2 ==> Dynamic
351                 return (item.Depth >= Menu.StaticDisplayLevels);
352             }
353 
IsDepthStatic(MenuItem item)354             private bool IsDepthStatic(MenuItem item) {
355                 return !IsDepthDynamic(item);
356             }
357 
PreRender(bool registerScript)358             public override void PreRender(bool registerScript) {
359                 if (Menu.DesignMode || Menu.Page == null) {
360                     return;
361                 }
362 
363                 if (Menu.IncludeStyleBlock && Menu.Page.Header == null) {
364                     throw new InvalidOperationException(SR.GetString(SR.NeedHeader, "Menu.IncludeStyleBlock"));
365                 }
366 
367                 AddScriptReference();  // We always need our script, even if we're disabled, because the script sets our styles
368                 AddStyleBlock();
369             }
370 
RenderBeginTag(HtmlTextWriter writer, bool staticOnly)371             public override void RenderBeginTag(HtmlTextWriter writer, bool staticOnly) {
372                 ControlRenderingHelper.WriteSkipLinkStart(writer, Menu.RenderingCompatibility, Menu.DesignMode, Menu.SkipLinkText, SpacerImageUrl, Menu.ClientID);
373 
374                 if (Menu.DesignMode && Menu.IncludeStyleBlock) {
375                     // Need to render style block in design mode, since it won't be present
376                     CreateStyleBlock().Render(writer);
377                 }
378 
379                 // Add expando attributes
380                 if (Menu.HasAttributes) {
381                     foreach (string key in Menu.Attributes.Keys) {
382                         writer.AddAttribute(key, Menu.Attributes[key]);
383                     }
384                 }
385 
386                 // CSS class, including disabled class if it's set
387                 string cssClass = Menu.CssClass ?? "";
388                 if (!Menu.Enabled) {
389                     cssClass = (cssClass + " " + DisabledCssClass).Trim();
390                 }
391                 if (!String.IsNullOrEmpty(cssClass)) {
392                     writer.AddAttribute(HtmlTextWriterAttribute.Class, cssClass);
393                 }
394 
395                 // Need to simulate the float done by Javascript when we're in design mode
396                 if (Menu.DesignMode) {
397                     writer.AddStyleAttribute("float", "left");
398                 }
399 
400                 writer.AddAttribute(HtmlTextWriterAttribute.Id, Menu.ClientID);
401                 writer.RenderBeginTag(HtmlTextWriterTag.Div);
402             }
403 
RenderContents(HtmlTextWriter writer, bool staticOnly)404             public override void RenderContents(HtmlTextWriter writer, bool staticOnly) {
405                 RenderItems(writer, staticOnly || Menu.DesignMode || !Menu.Enabled, Menu.Items, 1, !String.IsNullOrEmpty(Menu.AccessKey));
406             }
407 
RenderEndTag(HtmlTextWriter writer, bool staticOnly)408             public override void RenderEndTag(HtmlTextWriter writer, bool staticOnly) {
409                 writer.RenderEndTag();
410 
411                 // Need to simulate the clear done by Javascript when we're in design mode
412                 if (Menu.DesignMode) {
413                     writer.AddAttribute(HtmlTextWriterAttribute.Style, "clear: left");
414                     writer.RenderBeginTag(HtmlTextWriterTag.Div);
415                     writer.RenderEndTag();
416                 }
417 
418                 ControlRenderingHelper.WriteSkipLinkEnd(writer, Menu.DesignMode, Menu.SkipLinkText, Menu.ClientID);
419             }
420 
RenderItem(HtmlTextWriter writer, MenuItem item, int level, string cssClass, bool needsAccessKey)421             private bool RenderItem(HtmlTextWriter writer, MenuItem item, int level, string cssClass, bool needsAccessKey) {
422                 RenderItemPreSeparator(writer, item);
423 
424                 if (Menu.DesignMode && Menu.Orientation == Orientation.Horizontal) {
425                     writer.AddStyleAttribute(HtmlTextWriterStyle.WhiteSpace, "nowrap");
426                 }
427                 needsAccessKey = RenderItemLinkAttributes(writer, item, level, cssClass, needsAccessKey);
428                 writer.RenderBeginTag(HtmlTextWriterTag.A);
429                 RenderItemIcon(writer, item);
430                 item.RenderText(writer);
431                 // popout image is in the A's background css
432                 writer.RenderEndTag();  // </a>
433 
434                 RenderItemPostSeparator(writer, item);
435 
436                 return needsAccessKey;
437             }
438 
RenderItemIcon(HtmlTextWriter writer, MenuItem item)439             private void RenderItemIcon(HtmlTextWriter writer, MenuItem item) {
440                 if (String.IsNullOrEmpty(item.ImageUrl) || !item.NotTemplated()) {
441                     return;
442                 }
443 
444                 writer.AddAttribute(HtmlTextWriterAttribute.Src, Menu.ResolveClientUrl(item.ImageUrl));
445                 writer.AddAttribute(HtmlTextWriterAttribute.Alt, item.ToolTip);
446                 writer.AddAttribute(HtmlTextWriterAttribute.Title, item.ToolTip);
447                 writer.AddAttribute(HtmlTextWriterAttribute.Class, "icon");
448                 writer.RenderBeginTag(HtmlTextWriterTag.Img);
449                 writer.RenderEndTag();
450             }
451 
RenderItemLinkAttributes(HtmlTextWriter writer, MenuItem item, int level, string cssClass, bool needsAccessKey)452             private bool RenderItemLinkAttributes(HtmlTextWriter writer, MenuItem item, int level, string cssClass, bool needsAccessKey) {
453                 if (!String.IsNullOrEmpty(item.ToolTip)) {
454                     writer.AddAttribute(HtmlTextWriterAttribute.Title, item.ToolTip);
455                 }
456 
457                 // Bail out for for disabled or non-selectable menu items
458                 if (!item.Enabled || !Menu.Enabled) {
459                     writer.AddAttribute(HtmlTextWriterAttribute.Class, cssClass + " " + DisabledCssClass);
460                     return needsAccessKey;
461                 }
462                 if (!item.Selectable) {
463                     writer.AddAttribute(HtmlTextWriterAttribute.Class, cssClass);
464                     return needsAccessKey;
465                 }
466 
467                 // Selected
468                 if (item.Selected) {
469                     cssClass += " selected";
470                 }
471                 writer.AddAttribute(HtmlTextWriterAttribute.Class, cssClass);
472 
473                 // Attach the access key to the first link we render
474                 if (needsAccessKey) {
475                     writer.AddAttribute(HtmlTextWriterAttribute.Accesskey, Menu.AccessKey);
476                 }
477 
478                 // Postback...
479                 if (String.IsNullOrEmpty(item.NavigateUrl)) {
480                     writer.AddAttribute(HtmlTextWriterAttribute.Href, "#");
481                     writer.AddAttribute(HtmlTextWriterAttribute.Onclick, GetPostBackEventReference(item));
482                 }
483                 // ...or direct link
484                 else {
485                     writer.AddAttribute(HtmlTextWriterAttribute.Href, Menu.ResolveClientUrl(item.NavigateUrl));
486 
487                     string target = item.Target;
488                     if (String.IsNullOrEmpty(target)) {
489                         target = Menu.Target;
490                     }
491                     if (!String.IsNullOrEmpty(target)) {
492                         writer.AddAttribute(HtmlTextWriterAttribute.Target, target);
493                     }
494                 }
495 
496                 return false;
497             }
498 
RenderItemPostSeparator(HtmlTextWriter writer, MenuItem item)499             private void RenderItemPostSeparator(HtmlTextWriter writer, MenuItem item) {
500                 string separatorImageUrl = item.SeparatorImageUrl;
501                 if (String.IsNullOrEmpty(separatorImageUrl)) {
502                     separatorImageUrl = IsDepthStatic(item)
503                                        ? Menu.StaticBottomSeparatorImageUrl
504                                        : Menu.DynamicBottomSeparatorImageUrl;
505                 }
506 
507                 if (!String.IsNullOrEmpty(separatorImageUrl)) {
508                     RenderItemSeparatorImage(writer, item, separatorImageUrl);
509                 }
510             }
511 
RenderItemPreSeparator(HtmlTextWriter writer, MenuItem item)512             private void RenderItemPreSeparator(HtmlTextWriter writer, MenuItem item) {
513                 string separatorImageUrl = IsDepthStatic(item)
514                                    ? Menu.StaticTopSeparatorImageUrl
515                                    : Menu.DynamicTopSeparatorImageUrl;
516 
517                 if (!String.IsNullOrEmpty(separatorImageUrl)) {
518                     RenderItemSeparatorImage(writer, item, separatorImageUrl);
519                 }
520             }
521 
RenderItemSeparatorImage(HtmlTextWriter writer, MenuItem item, string separatorImageUrl)522             private void RenderItemSeparatorImage(HtmlTextWriter writer, MenuItem item, string separatorImageUrl) {
523                 if (Menu.RenderingCompatibility >= VersionUtil.Framework45) {
524                     // Dev10 #867750, Dev11 #436206: We need to be consistent with other controls by calling ResolveClientUrl,
525                     // but we need to hide this behavior behind a compat switch so that upgrading to 4.5 doesn't break
526                     // customers who have implemented their own manual workaround.
527                     separatorImageUrl = Menu.ResolveClientUrl(separatorImageUrl);
528                 }
529 
530                 writer.AddAttribute(HtmlTextWriterAttribute.Src, separatorImageUrl);
531                 writer.AddAttribute(HtmlTextWriterAttribute.Alt, String.Empty);
532                 writer.AddAttribute(HtmlTextWriterAttribute.Class,
533                                     IsDepthStatic(item) && Menu.Orientation == Orientation.Horizontal ? "horizontal-separator" : "separator");
534                 writer.RenderBeginTag(HtmlTextWriterTag.Img);
535                 writer.RenderEndTag();
536             }
537 
RenderItems(HtmlTextWriter writer, bool staticOnly, MenuItemCollection items, int level, bool needsAccessKey)538             private void RenderItems(HtmlTextWriter writer, bool staticOnly, MenuItemCollection items, int level, bool needsAccessKey) {
539                 if (level == 1 || level > Menu.StaticDisplayLevels) {  // Render a <UL> to start, and for all dynamic descendents
540                     if (Menu.DesignMode && Menu.Orientation == Orientation.Horizontal) {
541                         writer.AddStyleAttribute("float", "left");
542                     }
543                     writer.AddAttribute(HtmlTextWriterAttribute.Class, GetMenuCssClass(level));
544                     writer.RenderBeginTag(HtmlTextWriterTag.Ul);
545                 }
546 
547                 foreach (MenuItem item in items) {
548                     if (Menu.DesignMode && Menu.Orientation == Orientation.Horizontal) {
549                         writer.AddStyleAttribute("float", "left");
550                         writer.AddStyleAttribute(HtmlTextWriterStyle.WhiteSpace, "nowrap");
551                     }
552                     writer.RenderBeginTag(HtmlTextWriterTag.Li);
553 
554                     needsAccessKey = RenderItem(writer, item, level, GetMenuItemCssClass(item, level), needsAccessKey);
555 
556                     if (level < Menu.StaticDisplayLevels) {  // Close off <LI> if we (and our direct descendents) are static
557                         writer.RenderEndTag();
558                     }
559 
560                     if (item.ChildItems.Count > 0 && !IsChildPastMaximumDepth(item) && item.Enabled) {
561                         if (level < Menu.StaticDisplayLevels || !staticOnly) {
562                             RenderItems(writer, staticOnly, item.ChildItems, level + 1, needsAccessKey);
563                         }
564                     }
565 
566                     if (level >= Menu.StaticDisplayLevels) {  // Close off <LI> if we (or our direct descendents) are dynamic
567                         writer.RenderEndTag();
568                     }
569                 }
570 
571                 if (level == 1 || level > Menu.StaticDisplayLevels) {
572                     writer.RenderEndTag();
573                 }
574             }
575 
ShouldHavePopOutImage(MenuItem item)576             private bool ShouldHavePopOutImage(MenuItem item) {
577                 return (item.ChildItems.Count > 0) && IsChildDepthDynamic(item) && !IsChildPastMaximumDepth(item);
578             }
579         }
580     }
581 
582     internal class StyleBlock : Control {
583         List<StyleBlockStyles> _styles = new List<StyleBlockStyles>();
584 
AddStyleDefinition(string selector)585         public StyleBlockStyles AddStyleDefinition(string selector) {
586             StyleBlockStyles result = new StyleBlockStyles(selector, this);
587             _styles.Add(result);
588             return result;
589         }
590 
AddStyleDefinition(string selectorFormat, params object[] args)591         public StyleBlockStyles AddStyleDefinition(string selectorFormat, params object[] args) {
592             return AddStyleDefinition(String.Format(CultureInfo.InvariantCulture, selectorFormat, args));
593         }
594 
Render(HtmlTextWriter writer)595         protected internal override void Render(HtmlTextWriter writer) {
596             if (_styles.Any(s => !s.Empty)) {
597                 writer.AddAttribute(HtmlTextWriterAttribute.Type, "text/css");
598                 writer.RenderBeginTag(HtmlTextWriterTag.Style);
599                 writer.WriteLine("/* <![CDATA[ */");
600 
601                 foreach (var style in _styles.Where(s => !s.Empty)) {
602                     style.Render(writer);
603                 }
604 
605                 writer.Write("/* ]]> */");
606                 writer.RenderEndTag();
607             }
608         }
609     }
610 
611     internal class StyleBlockStyles {
612         private string _selector;
613         private StyleBlock _styleControl;
614         private CssStyleCollection _styles = new CssStyleCollection();
615 
StyleBlockStyles(string selector, StyleBlock styleControl)616         public StyleBlockStyles(string selector, StyleBlock styleControl) {
617             _selector = selector;
618             _styleControl = styleControl;
619         }
620 
621         public bool Empty {
622             get { return _styles.Count == 0; }
623         }
624 
AddStyle(HtmlTextWriterStyle styleName, string value)625         public StyleBlockStyles AddStyle(HtmlTextWriterStyle styleName, string value) {
626             _styles.Add(styleName, value);
627             return this;
628         }
629 
AddStyle(string styleName, string value)630         public StyleBlockStyles AddStyle(string styleName, string value) {
631             _styles.Add(styleName, value);
632             return this;
633         }
634 
AddStyles(Style style)635         public StyleBlockStyles AddStyles(Style style) {
636             if (style != null) {
637                 AddStyles(style.GetStyleAttributes(_styleControl));
638             }
639             return this;
640         }
641 
AddStyles(CssStyleCollection styles)642         public StyleBlockStyles AddStyles(CssStyleCollection styles) {
643             if (styles != null) {
644                 foreach (string key in styles.Keys) {
645                     _styles.Add(key, styles[key]);
646                 }
647             }
648             return this;
649         }
650 
Render(HtmlTextWriter writer)651         public void Render(HtmlTextWriter writer) {
652             writer.WriteLine("{0} {{ {1} }}", _selector, _styles.Value);
653         }
654     }
655 }
656 
657