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