context: #region Copyright & License Information
  /*
   * Copyright 2007-2015 The OpenRA Developers (see AUTHORS)
   * This file is part of OpenRA, which is free software. It is made
   * available to you under the terms of the GNU General Public License
   * as published by the Free Software Foundation. For more information,
   * see COPYING.
   */
  #endregion
  
  using System;
  using System.Collections.Generic;
  using System.Drawing;
  using System.Linq;
  using OpenRA.Graphics;
  using OpenRA.Mods.Common.Orders;
  using OpenRA.Mods.Common.Traits;
  using OpenRA.Network;
  using OpenRA.Widgets;
  
  namespace OpenRA.Mods.Common.Widgets
  {
  	public class ProductionIcon
  	{
  		public ActorInfo Actor;
  		public string Name;
  		public Hotkey Hotkey;
  		public Sprite Sprite;
  		public PaletteReference Palette;
  		public PaletteReference IconClockPalette;
  		public PaletteReference IconDarkenPalette;
  		public float2 Pos;
  		public List<ProductionItem> Queued;
  	}
  
  	public class ProductionPaletteWidget : Widget
  	{
  		public enum ReadyTextStyleOptions { Solid, AlternatingColor, Blinking }
  		public readonly ReadyTextStyleOptions ReadyTextStyle = ReadyTextStyleOptions.AlternatingColor;
  		public readonly Color ReadyTextAltColor = Color.Gold;
  		public readonly int Columns = 3;
  		public readonly int2 IconSize = new int2(64, 48);
  		public readonly int2 IconMargin = int2.Zero;
  		public readonly int2 IconSpriteOffset = int2.Zero;
  
  		public readonly string TabClick = null;
  		public readonly string DisabledTabClick = null;
  		public readonly string TooltipContainer;
  		public readonly string TooltipTemplate = "PRODUCTION_TOOLTIP";
  
  		public readonly string ClockAnimation = "clock";
  		public readonly string ClockSequence = "idle";
  		public readonly string ClockPalette = "chrome";
  
  		public readonly string NotBuildableAnimation = "clock";
  		public readonly string NotBuildableSequence = "idle";
  		public readonly string NotBuildablePalette = "chrome";
  
  		[Translate] public readonly string ReadyText = "";
  		[Translate] public readonly string HoldText = "";
  
  		public int DisplayedIconCount { get; private set; }
  		public int TotalIconCount { get; private set; }
  		public event Action<int, int> OnIconCountChanged = (a, b) => { };
  
  		public ProductionIcon TooltipIcon { get; private set; }
  		public readonly World World;
  		readonly OrderManager orderManager;
  
  		public int MinimumRows = 4;
  		public int MaximumRows = int.MaxValue;
  
  		public int IconRowOffset = 0;
  		public int MaxIconRowOffset = int.MaxValue;
  
  		Lazy<TooltipContainerWidget> tooltipContainer;
  		ProductionQueue currentQueue;
  
  		public ProductionQueue CurrentQueue
  		{
  			get { return currentQueue; }
  			set { currentQueue = value; RefreshIcons(); }
  		}
  
  		public override Rectangle EventBounds { get { return eventBounds; } }
  		Dictionary<Rectangle, ProductionIcon> icons = new Dictionary<Rectangle, ProductionIcon>();
  		Animation cantBuild, clock;
  		Rectangle eventBounds = Rectangle.Empty;
  		readonly WorldRenderer worldRenderer;
  		SpriteFont overlayFont;
  		float2 holdOffset, readyOffset, timeOffset, queuedOffset;
  
  		[ObjectCreator.UseCtor]
  		public ProductionPaletteWidget(OrderManager orderManager, World world, WorldRenderer worldRenderer)
  		{
  			this.orderManager = orderManager;
  			this.World = world;
  			this.worldRenderer = worldRenderer;
  			tooltipContainer = Exts.Lazy(() =>
  				Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
  
  			cantBuild = new Animation(world, NotBuildableAnimation);
  			cantBuild.PlayFetchIndex(NotBuildableSequence, () => 0);
  			clock = new Animation(world, ClockAnimation);
  		}
  
  		public void ScrollDown()
  		{
  			if (CanScrollDown)
  				IconRowOffset++;
  		}
  
  		public bool CanScrollDown
  		{
  			get
  			{
  				var totalRows = (TotalIconCount + Columns - 1) / Columns;
  
  				return IconRowOffset < totalRows - MaxIconRowOffset;
  			}
  		}
  
  		public void ScrollUp()
  		{
  			if (CanScrollUp)
  				IconRowOffset--;
  		}
  
  		public bool CanScrollUp
  		{
  			get { return IconRowOffset > 0; }
  		}
  
  		public void ScrollToTop()
  		{
  			IconRowOffset = 0;
  		}
  
  		public IEnumerable<ActorInfo> AllBuildables
  		{
  			get
  			{
  				if (CurrentQueue == null)
  					return Enumerable.Empty<ActorInfo>();
  
  				return CurrentQueue.AllItems().OrderBy(a => a.TraitInfo<BuildableInfo>().BuildPaletteOrder);
  			}
  		}
  
  		public override void Tick()
  		{
  			TotalIconCount = AllBuildables.Count();
  
  			if (CurrentQueue != null && !CurrentQueue.Actor.IsInWorld)
  				CurrentQueue = null;
  
  			if (CurrentQueue != null)
  				RefreshIcons();
  		}
  
  		public override void MouseEntered()
  		{
  			if (TooltipContainer != null)
  				tooltipContainer.Value.SetTooltip(TooltipTemplate,
  					new WidgetArgs() { { "world", World }, { "palette", this } });
  		}
  
  		public override void MouseExited()
  		{
  			if (TooltipContainer != null)
  				tooltipContainer.Value.RemoveTooltip();
  		}
  
  		public override bool HandleMouseInput(MouseInput mi)
  		{
  			var icon = icons.Where(i => i.Key.Contains(mi.Location))
  				.Select(i => i.Value).FirstOrDefault();
  
  			if (mi.Event == MouseInputEvent.Move)
  				TooltipIcon = icon;
  
  			if (icon == null)
  				return false;
  
  			// Eat mouse-up events
  			if (mi.Event != MouseInputEvent.Down)
  				return true;
  
  			return HandleEvent(icon, mi.Button, mi.Modifiers);
  		}
  
  		protected bool PickUpCompletedBuildingIcon(ProductionIcon icon, ProductionItem item)
  		{
  			var actor = World.Map.Rules.Actors[icon.Name];
  
  			if (item != null && item.Done && actor.HasTraitInfo<BuildingInfo>())
  			{
  				World.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue, icon.Name);
  				return true;
  			}
  
  			return false;
  		}
  
  		public void PickUpCompletedBuilding()
  		{
  			foreach (var icon in icons.Values)
  			{
  				var item = icon.Queued.FirstOrDefault();
  				if (PickUpCompletedBuildingIcon(icon, item))
  					break;
  			}
  		}
  
  		bool HandleLeftClick(ProductionItem item, ProductionIcon icon, int handleCount)
  		{
  			if (PickUpCompletedBuildingIcon(icon, item))
  			{
  				Game.Sound.Play(TabClick);
  				return true;
  			}
  
  			if (item != null && item.Paused)
  			{
  				// Resume a paused item
  				Game.Sound.Play(TabClick);
  				World.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, icon.Name, false));
  				return true;
  			}
  
  			if (CurrentQueue.BuildableItems().Any(a => a.Name == icon.Name))
  			{
  				// Queue a new item
  				Game.Sound.Play(TabClick);
  				Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.QueuedAudio, World.LocalPlayer.Faction.InternalName);
  				World.IssueOrder(Order.StartProduction(CurrentQueue.Actor, icon.Name, handleCount));
  				return true;
  			}
  
  			return false;
  		}
  
  		bool HandleRightClick(ProductionItem item, ProductionIcon icon, int handleCount)
  		{
  			if (item == null)
  				return false;
  
  			Game.Sound.Play(TabClick);
  
  			if (item.Paused || item.Done || item.TotalCost == item.RemainingCost)
  			{
  				// Instant cancel of things we have not started yet and things that are finished
  				Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Faction.InternalName);
  				World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name, handleCount));
  			}
  			else
  			{
  				// Pause an existing item
  				Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.OnHoldAudio, World.LocalPlayer.Faction.InternalName);
  				World.IssueOrder(Order.PauseProduction(CurrentQueue.Actor, icon.Name, true));
  			}
  
  			return true;
  		}
  
  		bool HandleMiddleClick(ProductionItem item, ProductionIcon icon, int handleCount)
  		{
  			if (item == null)
  				return false;
  
  			// Directly cancel, skipping "on-hold"
  			Game.Sound.Play(TabClick);
  			Game.Sound.PlayNotification(World.Map.Rules, World.LocalPlayer, "Speech", CurrentQueue.Info.CancelledAudio, World.LocalPlayer.Faction.InternalName);
  			World.IssueOrder(Order.CancelProduction(CurrentQueue.Actor, icon.Name, handleCount));
  
  			return true;
  		}
  
  		bool HandleEvent(ProductionIcon icon, MouseButton btn, Modifiers modifiers)
  		{
  			var startCount = modifiers.HasModifier(Modifiers.Shift) ? 5 : 1;
  			var cancelCount = modifiers.HasModifier(Modifiers.Ctrl) ? CurrentQueue.QueueLength : startCount;
  			var item = icon.Queued.FirstOrDefault();
  			var handled = btn == MouseButton.Left ? HandleLeftClick(item, icon, startCount)
  				: btn == MouseButton.Right ? HandleRightClick(item, icon, cancelCount)
  				: btn == MouseButton.Middle ? HandleMiddleClick(item, icon, cancelCount)
  				: false;
  
  			if (!handled)
  				Game.Sound.Play(DisabledTabClick);
  
  			return true;
  		}
  
  		public override bool HandleKeyPress(KeyInput e)
  		{
  			if (e.Event == KeyInputEvent.Up || CurrentQueue == null)
  				return false;
  
  			var hotkey = Hotkey.FromKeyInput(e);
  			var toBuild = icons.Values.FirstOrDefault(i => i.Hotkey == hotkey);
  			return toBuild != null ? HandleEvent(toBuild, MouseButton.Left, Modifiers.None) : false;
  		}
  
  		public void RefreshIcons()
  		{
  			icons = new Dictionary<Rectangle, ProductionIcon>();
  			var producer = CurrentQueue != null ? CurrentQueue.MostLikelyProducer() : default(TraitPair<Production>);
  			if (CurrentQueue == null || producer.Trait == null)
  			{
  				if (DisplayedIconCount != 0)
  				{
  					OnIconCountChanged(DisplayedIconCount, 0);
  					DisplayedIconCount = 0;
  				}
  
  				return;
  			}
  
  			var oldIconCount = DisplayedIconCount;
  			DisplayedIconCount = 0;
  
  			var ks = Game.Settings.Keys;
  			var rb = RenderBounds;
  			var faction = producer.Trait.Faction;
  
  			foreach (var item in AllBuildables.Skip(IconRowOffset * Columns).Take(MaxIconRowOffset * Columns))
  			{
  				var x = DisplayedIconCount % Columns;
  				var y = DisplayedIconCount / Columns;
  				var rect = new Rectangle(rb.X + x * (IconSize.X + IconMargin.X), rb.Y + y * (IconSize.Y + IconMargin.Y), IconSize.X, IconSize.Y);
  
  				var rsi = item.TraitInfo<RenderSpritesInfo>();
  				var icon = new Animation(World, rsi.GetImage(item, World.Map.SequenceProvider, faction));
  				icon.Play(item.TraitInfo<TooltipInfo>().Icon);
  
  				var bi = item.TraitInfo<BuildableInfo>();
  
  				var pi = new ProductionIcon()
  				{
  					Actor = item,
  					Name = item.Name,
  					Hotkey = ks.GetProductionHotkey(DisplayedIconCount),
  					Sprite = icon.Image,
  					Palette = worldRenderer.Palette(bi.IconPalette),
  					IconClockPalette = worldRenderer.Palette(ClockPalette),
  					IconDarkenPalette = worldRenderer.Palette(NotBuildablePalette),
  					Pos = new float2(rect.Location),
  					Queued = CurrentQueue.AllQueued().Where(a => a.Item == item.Name).ToList()
  				};
  
  				icons.Add(rect, pi);
  				DisplayedIconCount++;
  			}
  
  			eventBounds = icons.Any() ? icons.Keys.Aggregate(Rectangle.Union) : Rectangle.Empty;
  
  			if (oldIconCount != DisplayedIconCount)
  				OnIconCountChanged(oldIconCount, DisplayedIconCount);
  		}
  
  		public override void Draw()
  		{
  			var iconOffset = 0.5f * IconSize.ToFloat2() + IconSpriteOffset;
  
  			overlayFont = Game.Renderer.Fonts["TinyBold"];
  			timeOffset = iconOffset - overlayFont.Measure(WidgetUtils.FormatTime(0, World.Timestep)) / 2;
  			queuedOffset = new float2(4, 2);
  			holdOffset = iconOffset - overlayFont.Measure(HoldText) / 2;
  			readyOffset = iconOffset - overlayFont.Measure(ReadyText) / 2;
  
  
gt: 			if (CurrentQueue == null)