1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Diagnostics;
23 using System.Drawing;
24 using System.Drawing.Drawing2D;
25 using System.Drawing.Imaging;
26 using System.Globalization;
27 using System.Windows.Forms;
28 
29 using KeePass.App;
30 using KeePass.Util;
31 
32 using KeePassLib.Utility;
33 
34 namespace KeePass.UI
35 {
36 	public enum BannerStyle
37 	{
38 		Default = 0,
39 
40 		WinXPLogin = 1,
41 		WinVistaBlack = 2,
42 		KeePassWin32 = 3,
43 		BlueCarbon = 4
44 	}
45 
46 	public sealed class BfBannerInfo
47 	{
48 		public int Width { get; private set; }
49 		public int Height { get; private set; }
50 		public BannerStyle Style { get; private set; }
51 		public Image Icon { get; private set; }
52 		public string TitleText { get; private set; }
53 		public string InfoText { get; private set; }
54 
BfBannerInfo(int nWidth, int nHeight, BannerStyle bs, Image imgIcon, string strTitle, string strLine)55 		public BfBannerInfo(int nWidth, int nHeight, BannerStyle bs,
56 			Image imgIcon, string strTitle, string strLine)
57 		{
58 			this.Width = nWidth;
59 			this.Height = nHeight;
60 			this.Style = bs;
61 			this.Icon = imgIcon;
62 			this.TitleText = strTitle;
63 			this.InfoText = strLine;
64 		}
65 	}
66 
BfBannerGenerator(BfBannerInfo bannerInfo)67 	public delegate Image BfBannerGenerator(BfBannerInfo bannerInfo);
68 
69 	public static class BannerFactory
70 	{
71 		private const int StdHeight = 60; // Standard height for 96 DPI
72 		private const int StdIconDim = 48;
73 
74 		private static Dictionary<string, Image> g_dCache =
75 			new Dictionary<string, Image>();
76 		private const int MaxCachedImages = 32;
77 
78 		private static BfBannerGenerator g_pCustomGen = null;
79 		public static BfBannerGenerator CustomGenerator
80 		{
81 			get { return g_pCustomGen; }
82 			set { g_pCustomGen = value; }
83 		}
84 
CreateBanner(int nWidth, int nHeight, BannerStyle bs, Image imgIcon, string strTitle, string strLine)85 		public static Image CreateBanner(int nWidth, int nHeight, BannerStyle bs,
86 			Image imgIcon, string strTitle, string strLine)
87 		{
88 			return CreateBanner(nWidth, nHeight, bs, imgIcon, strTitle, strLine, false);
89 		}
90 
CreateBanner(int nWidth, int nHeight, BannerStyle bs, Image imgIcon, string strTitle, string strLine, bool bNoCache)91 		public static Image CreateBanner(int nWidth, int nHeight, BannerStyle bs,
92 			Image imgIcon, string strTitle, string strLine, bool bNoCache)
93 		{
94 			// imgIcon may be null
95 			if(strTitle == null) { Debug.Assert(false); strTitle = string.Empty; }
96 			if(strLine == null) { Debug.Assert(false); strLine = string.Empty; }
97 
98 			Debug.Assert((nHeight == StdHeight) || DpiUtil.ScalingRequired ||
99 				UISystemFonts.OverrideUIFont);
100 			if(MonoWorkarounds.IsRequired(12525) && (nHeight > 0))
101 				--nHeight;
102 
103 			if(bs == BannerStyle.Default) bs = Program.Config.UI.BannerStyle;
104 			if(bs == BannerStyle.Default)
105 			{
106 				Debug.Assert(false);
107 				bs = BannerStyle.WinVistaBlack;
108 			}
109 
110 			NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo;
111 			ulong uIconHash = ((imgIcon != null) ? GfxUtil.HashImage64(imgIcon) : 0);
112 			string strID = nWidth.ToString(nfi) + "x" + nHeight.ToString(nfi) +
113 				":" + ((uint)bs).ToString(nfi) + ":" + strTitle + ":/:" + strLine +
114 				":" + uIconHash.ToString(nfi);
115 
116 			Image img = null;
117 			if(!bNoCache && g_dCache.TryGetValue(strID, out img))
118 				return img;
119 
120 			if(g_pCustomGen != null)
121 				img = g_pCustomGen(new BfBannerInfo(nWidth, nHeight, bs, imgIcon,
122 					strTitle, strLine));
123 
124 			const float fHorz = 0.90f;
125 			const float fVert = 90.0f;
126 
127 			if(img == null)
128 			{
129 				img = new Bitmap(nWidth, nHeight, PixelFormat.Format24bppRgb);
130 				Graphics g = Graphics.FromImage(img);
131 
132 				Color clrStart = Color.White;
133 				Color clrEnd = Color.LightBlue;
134 				float fAngle = fHorz;
135 
136 				if(bs == BannerStyle.BlueCarbon)
137 				{
138 					fAngle = fVert;
139 
140 					g.Clear(Color.Black); // Area from 3/8 to 1/2 height
141 
142 					clrStart = Color.LightGray;
143 					clrEnd = Color.Black;
144 
145 					Rectangle rect = new Rectangle(0, 0, nWidth, (nHeight * 3) / 8);
146 					using(LinearGradientBrush brCarbonT = new LinearGradientBrush(
147 						rect, clrStart, clrEnd, fAngle, true))
148 					{
149 						g.FillRectangle(brCarbonT, rect);
150 					}
151 
152 					// clrStart = Color.FromArgb(0, 0, 32);
153 					clrStart = Color.FromArgb(0, 0, 28);
154 					// clrEnd = Color.FromArgb(192, 192, 255);
155 					clrEnd = Color.FromArgb(155, 155, 214);
156 
157 					// rect = new Rectangle(0, nHeight / 2, nWidth, (nHeight * 5) / 8);
158 					int hMid = nHeight / 2;
159 					rect = new Rectangle(0, hMid - 1, nWidth, nHeight - hMid);
160 					using(LinearGradientBrush brCarbonB = new LinearGradientBrush(
161 						rect, clrStart, clrEnd, fAngle, true))
162 					{
163 						g.FillRectangle(brCarbonB, rect);
164 					}
165 
166 					// Workaround gradient drawing bug (e.g. occuring on
167 					// Windows 8.1 with 150% DPI)
168 					using(Pen pen = new Pen(Color.Black))
169 					{
170 						g.DrawLine(pen, 0, hMid - 1, nWidth - 1, hMid - 1);
171 					}
172 				}
173 				else
174 				{
175 					if(bs == BannerStyle.WinXPLogin)
176 					{
177 						clrStart = Color.FromArgb(200, 208, 248);
178 						clrEnd = Color.FromArgb(40, 64, 216);
179 					}
180 					else if(bs == BannerStyle.WinVistaBlack)
181 					{
182 						clrStart = Color.FromArgb(151, 154, 173);
183 						clrEnd = Color.FromArgb(27, 27, 37);
184 
185 						fAngle = fVert;
186 					}
187 					else if(bs == BannerStyle.KeePassWin32)
188 					{
189 						clrStart = Color.FromArgb(235, 235, 255);
190 						clrEnd = Color.FromArgb(192, 192, 255);
191 					}
192 
193 					Rectangle rect = new Rectangle(0, 0, nWidth, nHeight);
194 					using(LinearGradientBrush brBack = new LinearGradientBrush(
195 						rect, clrStart, clrEnd, fAngle, true))
196 					{
197 						g.FillRectangle(brBack, rect);
198 					}
199 				}
200 
201 				bool bRtl = Program.Translation.Properties.RightToLeft;
202 				// Matrix mxTrfOrg = g.Transform;
203 				// if(bRtl)
204 				// {
205 				//	g.TranslateTransform(nWidth, 0.0f);
206 				//	g.ScaleTransform(-1.0f, 1.0f);
207 				// }
208 
209 				int xIcon = DpiScaleInt(10, nHeight);
210 				int wIconScaled = StdIconDim;
211 				int hIconScaled = StdIconDim;
212 				if(imgIcon != null)
213 				{
214 					float fIconRel = (float)imgIcon.Width / (float)imgIcon.Height;
215 					wIconScaled = (int)Math.Round(DpiScaleFloat(fIconRel *
216 						(float)StdIconDim, nHeight));
217 					hIconScaled = DpiScaleInt(StdIconDim, nHeight);
218 
219 					int xIconR = (bRtl ? (nWidth - xIcon - wIconScaled) : xIcon);
220 					int yIconR = (nHeight - hIconScaled) / 2;
221 					if(hIconScaled == imgIcon.Height)
222 						g.DrawImageUnscaled(imgIcon, xIconR, yIconR);
223 					else
224 						g.DrawImage(imgIcon, xIconR, yIconR, wIconScaled, hIconScaled);
225 
226 					ColorMatrix cm = new ColorMatrix();
227 					cm.Matrix33 = 0.1f;
228 					ImageAttributes ia = new ImageAttributes();
229 					ia.SetColorMatrix(cm);
230 
231 					int w = wIconScaled * 3, h = hIconScaled * 3;
232 					int x = (bRtl ? xIcon : (nWidth - w - xIcon));
233 					int y = (nHeight - h) / 2;
234 					Rectangle rectDest = new Rectangle(x, y, w, h);
235 					g.DrawImage(imgIcon, rectDest, 0, 0, imgIcon.Width, imgIcon.Height,
236 						GraphicsUnit.Pixel, ia);
237 				}
238 
239 				if((bs == BannerStyle.WinXPLogin) || (bs == BannerStyle.WinVistaBlack) ||
240 					(bs == BannerStyle.BlueCarbon))
241 				{
242 					int sh = DpiUtil.ScaleIntY(20) / 10; // Force floor
243 
244 					Rectangle rect = new Rectangle(0, nHeight - sh, 0, sh);
245 
246 					rect.Width = nWidth / 2 + 1;
247 					rect.X = nWidth / 2;
248 					clrStart = Color.FromArgb(248, 136, 24);
249 					clrEnd = Color.White;
250 					using(LinearGradientBrush brushOrangeWhite = new LinearGradientBrush(
251 						rect, clrStart, clrEnd, fHorz, true))
252 					{
253 						g.FillRectangle(brushOrangeWhite, rect);
254 					}
255 
256 					rect.Width = nWidth / 2 + 1;
257 					rect.X = 0;
258 					clrStart = Color.White;
259 					clrEnd = Color.FromArgb(248, 136, 24);
260 					using(LinearGradientBrush brushWhiteOrange = new LinearGradientBrush(
261 						rect, clrStart, clrEnd, fHorz, true))
262 					{
263 						g.FillRectangle(brushWhiteOrange, rect);
264 					}
265 				}
266 				else if(bs == BannerStyle.KeePassWin32)
267 				{
268 					int sh = DpiUtil.ScaleIntY(10) / 10; // Force floor
269 
270 					// Black separator line
271 					using(Pen penBlack = new Pen(Color.Black))
272 					{
273 						for(int i = 0; i < sh; ++i)
274 							g.DrawLine(penBlack, 0, nHeight - i - 1,
275 								nWidth - 1, nHeight - i - 1);
276 					}
277 				}
278 
279 				// if(bRtl) g.Transform = mxTrfOrg;
280 
281 				// Brush brush;
282 				Color clrText;
283 				if(bs == BannerStyle.KeePassWin32)
284 				{
285 					// brush = Brushes.Black;
286 					clrText = Color.Black;
287 				}
288 				else
289 				{
290 					// brush = Brushes.White;
291 					clrText = Color.White;
292 				}
293 
294 				// float fx = 2 * xIcon, fy = 9.0f;
295 				int tx = 2 * xIcon, ty = DpiScaleInt(9, nHeight);
296 				if(imgIcon != null) tx += wIconScaled; // fx
297 
298 				// TextFormatFlags tff = (TextFormatFlags.PreserveGraphicsClipping |
299 				//	TextFormatFlags.NoPrefix);
300 				// if(bRtl) tff |= TextFormatFlags.RightToLeft;
301 
302 				float fFontSize = DpiScaleFloat((12.0f * 96.0f) / g.DpiY, nHeight);
303 				using(Font font = FontUtil.CreateFont(FontFamily.GenericSansSerif,
304 					fFontSize, FontStyle.Bold))
305 				{
306 					int txT = (!bRtl ? tx : (nWidth - tx));
307 						// - TextRenderer.MeasureText(g, strTitle, font).Width));
308 					// g.DrawString(strTitle, font, brush, fx, fy);
309 					BannerFactory.DrawText(g, strTitle, txT, ty, font,
310 						clrText, bRtl, nWidth);
311 				}
312 
313 				tx += xIcon; // fx
314 				ty += xIcon * 2 + 2; // fy
315 
316 				float fFontSizeSm = DpiScaleFloat((9.0f * 96.0f) / g.DpiY, nHeight);
317 				using(Font fontSmall = FontUtil.CreateFont(FontFamily.GenericSansSerif,
318 					fFontSizeSm, FontStyle.Regular))
319 				{
320 					int txL = (!bRtl ? tx : (nWidth - tx));
321 						// - TextRenderer.MeasureText(g, strLine, fontSmall).Width));
322 					// g.DrawString(strLine, fontSmall, brush, fx, fy);
323 					BannerFactory.DrawText(g, strLine, txL, ty, fontSmall,
324 						clrText, bRtl, nWidth);
325 				}
326 
327 				g.Dispose();
328 			}
329 
330 			if(!bNoCache)
331 			{
332 				if(g_dCache.Count >= MaxCachedImages)
333 				{
334 					List<string> lK = new List<string>(g_dCache.Keys);
335 					g_dCache.Remove(lK[Program.GlobalRandom.Next(lK.Count)]);
336 				}
337 
338 				g_dCache[strID] = img;
339 			}
340 
341 			return img;
342 		}
343 
DrawText(Graphics g, string strText, int x, int y, Font font, Color clrForeground, bool bRtl, int wImg)344 		private static void DrawText(Graphics g, string strText, int x,
345 			int y, Font font, Color clrForeground, bool bRtl, int wImg)
346 		{
347 			if(string.IsNullOrEmpty(strText)) return;
348 
349 			// With ClearType on, text drawn using Graphics.DrawString
350 			// looks better than TextRenderer.DrawText;
351 			// https://sourceforge.net/p/keepass/discussion/329220/thread/06ef4466/
352 
353 			/* // On Windows 2000 the DrawText method taking a Point doesn't
354 			// work by design, see MSDN:
355 			// https://msdn.microsoft.com/en-us/library/ms160657.aspx
356 			if(WinUtil.IsWindows2000)
357 				TextRenderer.DrawText(g, strText, font, new Rectangle(pt.X, pt.Y,
358 					nWidth - pt.X - 1, nHeight - pt.Y - 1), clrForeground, tff);
359 			else
360 				TextRenderer.DrawText(g, strText, font, pt, clrForeground, tff); */
361 
362 			using(SolidBrush br = new SolidBrush(clrForeground))
363 			{
364 				StringFormatFlags sff = (StringFormatFlags.FitBlackBox |
365 					StringFormatFlags.NoClip);
366 				if(bRtl) sff |= StringFormatFlags.DirectionRightToLeft;
367 
368 				using(StringFormat sf = new StringFormat(sff))
369 				{
370 					bool bDrawn = false;
371 
372 					try
373 					{
374 						if(bRtl) return; // Default draw (in 'finally')
375 
376 						GraphicsUnit gu = g.PageUnit; // For MeasureString
377 						if((gu != GraphicsUnit.Pixel) && (gu != GraphicsUnit.Display))
378 						{
379 							Debug.Assert(false); // The code below assumes pixels
380 							return;
381 						}
382 
383 						int wTextMax = wImg - x - 1; // 1 px free on right
384 						if(wTextMax <= 0) { Debug.Assert(false); return; }
385 
386 						for(int cch = strText.Length; cch > 0; --cch)
387 						{
388 							string str = StrUtil.CompactString3Dots(strText, cch);
389 							int wText = (int)g.MeasureString(str, font).Width;
390 
391 							if(wText <= 0)
392 							{
393 								Debug.Assert(false);
394 								break; // Default draw (in 'finally')
395 							}
396 							if(wText <= wTextMax)
397 							{
398 								g.DrawString(str, font, br, x, y, sf);
399 								bDrawn = true;
400 								break;
401 							}
402 						}
403 						Debug.Assert(bDrawn); // Even one char too wide?!
404 					}
405 					catch(Exception) { Debug.Assert(false); }
406 					finally
407 					{
408 						if(!bDrawn) g.DrawString(strText, font, br, x, y, sf);
409 					}
410 				}
411 			}
412 		}
413 
DpiScaleInt(int x, int nBaseHeight)414 		private static int DpiScaleInt(int x, int nBaseHeight)
415 		{
416 			return (int)Math.Round((double)(x * nBaseHeight) / (double)StdHeight);
417 		}
418 
DpiScaleFloat(float x, int nBaseHeight)419 		private static float DpiScaleFloat(float x, int nBaseHeight)
420 		{
421 			return ((x * (float)nBaseHeight) / (float)StdHeight);
422 		}
423 
CreateBannerEx(Form f, PictureBox picBox, Image imgIcon, string strTitle, string strLine)424 		public static void CreateBannerEx(Form f, PictureBox picBox, Image imgIcon,
425 			string strTitle, string strLine)
426 		{
427 			CreateBannerEx(f, picBox, imgIcon, strTitle, strLine, false);
428 		}
429 
CreateBannerEx(Form f, PictureBox picBox, Image imgIcon, string strTitle, string strLine, bool bNoCache)430 		public static void CreateBannerEx(Form f, PictureBox picBox, Image imgIcon,
431 			string strTitle, string strLine, bool bNoCache)
432 		{
433 			if(picBox == null) { Debug.Assert(false); return; }
434 
435 			try
436 			{
437 				picBox.Image = CreateBanner(picBox.Width, picBox.Height,
438 					BannerStyle.Default, imgIcon, strTitle, strLine, bNoCache);
439 
440 				UIUtil.AccSetName(picBox, strTitle, strLine);
441 			}
442 			catch(Exception) { Debug.Assert(false); }
443 		}
444 
445 		/// <summary>
446 		/// Update/create a dialog banner. This method is intended for
447 		/// updating banners in resizable dialogs. The created banner
448 		/// images bypass the cache of the factory and are disposed
449 		/// when the dialog is resized (i.e. the caller shouldn't do
450 		/// anything with the banner images).
451 		/// </summary>
UpdateBanner(Form f, PictureBox picBox, Image imgIcon, string strTitle, string strLine, ref int nOldWidth)452 		public static void UpdateBanner(Form f, PictureBox picBox, Image imgIcon,
453 			string strTitle, string strLine, ref int nOldWidth)
454 		{
455 			int nWidth = picBox.Width;
456 			if(nWidth != nOldWidth)
457 			{
458 				Image imgPrev = null;
459 				if(nOldWidth >= 0) imgPrev = picBox.Image;
460 
461 				BannerFactory.CreateBannerEx(f, picBox, imgIcon, strTitle,
462 					strLine, true);
463 
464 				if(imgPrev != null) imgPrev.Dispose(); // Release old banner
465 
466 				nOldWidth = nWidth;
467 			}
468 		}
469 	}
470 }
471