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.ComponentModel; 23 using System.Diagnostics; 24 using System.Drawing; 25 using System.Text; 26 using System.Threading; 27 using System.Windows.Forms; 28 29 using KeePass.App; 30 using KeePass.App.Configuration; 31 using KeePass.Resources; 32 using KeePass.UI; 33 34 using KeePassLib; 35 using KeePassLib.Delegates; 36 using KeePassLib.Security; 37 using KeePassLib.Utility; 38 39 namespace KeePass.Forms 40 { AceColumnDelegate(AceColumn c)41 public delegate void AceColumnDelegate(AceColumn c); 42 // public delegate void UpdateUIDelegate(bool bGuiToInternal); 43 44 public partial class ColumnsForm : Form 45 { 46 private bool m_bIgnoreHideCheckEvent = false; 47 ColumnsForm()48 public ColumnsForm() 49 { 50 InitializeComponent(); 51 GlobalWindowManager.InitializeForm(this); 52 } 53 OnFormLoad(object sender, EventArgs e)54 private void OnFormLoad(object sender, EventArgs e) 55 { 56 GlobalWindowManager.AddWindow(this); 57 58 BannerFactory.CreateBannerEx(this, m_bannerImage, 59 Properties.Resources.B48x48_View_Detailed, 60 KPRes.ConfigureColumns, KPRes.ConfigureColumnsDesc); 61 this.Icon = AppIcons.Default; 62 this.Text = KPRes.ConfigureColumns; 63 64 float fWidth = (float)(m_lvColumns.ClientRectangle.Width - 65 UIUtil.GetVScrollBarWidth()) / 5.0f; 66 m_lvColumns.Columns.Add(KPRes.Column, (int)(fWidth * 3.0f)); 67 m_lvColumns.Columns.Add(KPRes.Asterisks + " ***", (int)fWidth); 68 m_lvColumns.Columns.Add(KPRes.Toggle + " ***", (int)fWidth); 69 70 UIUtil.SetExplorerTheme(m_lvColumns, false); 71 72 UpdateColumnPropInfo(); 73 74 ThreadPool.QueueUserWorkItem(new WaitCallback(FillColumnsList)); 75 } 76 AddAceColumnTh(AceColumn c)77 private void AddAceColumnTh(AceColumn c) 78 { 79 string strLvgName = KPRes.StandardFields; 80 if(c.Type == AceColumnType.CustomString) 81 strLvgName = KPRes.CustomFields; 82 else if(c.Type == AceColumnType.PluginExt) 83 strLvgName = KPRes.PluginProvided; 84 else if((c.Type == AceColumnType.Size) || (c.Type == AceColumnType.LastPasswordModTime)) 85 strLvgName = KPRes.More; 86 87 ListViewGroup lvgContainer = null; 88 foreach(ListViewGroup lvg in m_lvColumns.Groups) 89 { 90 if(lvg.Header == strLvgName) { lvgContainer = lvg; break; } 91 } 92 if(lvgContainer == null) 93 { 94 lvgContainer = new ListViewGroup(strLvgName); 95 m_lvColumns.Groups.Add(lvgContainer); 96 } 97 98 ListViewItem lvi = new ListViewItem(c.GetDisplayName()); 99 lvi.Tag = c; 100 101 lvi.SubItems.Add(c.HideWithAsterisks ? KPRes.Yes : KPRes.No); 102 103 if(c.Type == AceColumnType.Password) 104 lvi.SubItems.Add(UIUtil.GetKeysName(Keys.Control | Keys.H)); 105 else if(c.Type == AceColumnType.UserName) 106 lvi.SubItems.Add(UIUtil.GetKeysName(Keys.Control | Keys.J)); 107 else lvi.SubItems.Add(string.Empty); 108 109 bool bChecked = false; 110 List<AceColumn> lCur = Program.Config.MainWindow.EntryListColumns; 111 foreach(AceColumn cCur in lCur) 112 { 113 if(cCur.Type != c.Type) continue; 114 115 if((c.Type != AceColumnType.CustomString) && 116 (c.Type != AceColumnType.PluginExt)) 117 { 118 bChecked = true; 119 break; 120 } 121 else if((c.Type == AceColumnType.CustomString) && 122 (cCur.CustomName == c.CustomName)) 123 { 124 bChecked = true; 125 break; 126 } 127 else if((c.Type == AceColumnType.PluginExt) && 128 (cCur.CustomName == c.CustomName)) 129 { 130 bChecked = true; 131 break; 132 } 133 } 134 lvi.Checked = bChecked; 135 136 lvi.Group = lvgContainer; 137 m_lvColumns.Items.Add(lvi); 138 } 139 AddAceColumn(List<AceColumn> lContainer, AceColumn c)140 private void AddAceColumn(List<AceColumn> lContainer, AceColumn c) 141 { 142 m_lvColumns.Invoke(new AceColumnDelegate(AddAceColumnTh), c); 143 lContainer.Add(c); 144 } 145 AddStdAceColumn(List<AceColumn> lContainer, AceColumnType colType)146 private void AddStdAceColumn(List<AceColumn> lContainer, AceColumnType colType) 147 { 148 bool bHide = (colType == AceColumnType.Password); // Passwords hidden by default 149 int nWidth = -1; 150 List<AceColumn> lCur = Program.Config.MainWindow.EntryListColumns; 151 foreach(AceColumn cCur in lCur) 152 { 153 if(cCur.Type == colType) 154 { 155 bHide = cCur.HideWithAsterisks; 156 nWidth = cCur.Width; 157 break; 158 } 159 } 160 161 AddAceColumn(lContainer, new AceColumn(colType, string.Empty, bHide, nWidth)); 162 } 163 FillColumnsList(object state)164 private void FillColumnsList(object state) 165 { 166 List<AceColumn> l = new List<AceColumn>(); 167 168 AddStdAceColumn(l, AceColumnType.Title); 169 AddStdAceColumn(l, AceColumnType.UserName); 170 AddStdAceColumn(l, AceColumnType.Password); 171 AddStdAceColumn(l, AceColumnType.Url); 172 AddStdAceColumn(l, AceColumnType.Notes); 173 AddStdAceColumn(l, AceColumnType.ExpiryTime); 174 AddStdAceColumn(l, AceColumnType.ExpiryTimeDateOnly); 175 176 AddStdAceColumn(l, AceColumnType.Attachment); 177 AddStdAceColumn(l, AceColumnType.AttachmentCount); 178 179 AddStdAceColumn(l, AceColumnType.Tags); 180 AddStdAceColumn(l, AceColumnType.OverrideUrl); 181 AddStdAceColumn(l, AceColumnType.Uuid); 182 183 AddStdAceColumn(l, AceColumnType.AutoTypeEnabled); 184 AddStdAceColumn(l, AceColumnType.AutoTypeSequences); 185 186 AddStdAceColumn(l, AceColumnType.CreationTime); 187 AddStdAceColumn(l, AceColumnType.LastModificationTime); 188 if((Program.Config.UI.UIFlags & (ulong)AceUIFlags.ShowLastAccessTime) != 0) 189 AddStdAceColumn(l, AceColumnType.LastAccessTime); 190 AddStdAceColumn(l, AceColumnType.HistoryCount); 191 192 AddStdAceColumn(l, AceColumnType.Size); 193 AddStdAceColumn(l, AceColumnType.LastPasswordModTime); 194 195 SortedDictionary<string, AceColumn> d = 196 new SortedDictionary<string, AceColumn>(StrUtil.CaseIgnoreComparer); 197 List<AceColumn> lCur = Program.Config.MainWindow.EntryListColumns; 198 foreach(AceColumn cCur in lCur) 199 { 200 if((cCur.Type == AceColumnType.CustomString) && 201 !d.ContainsKey(cCur.CustomName)) 202 { 203 d[cCur.CustomName] = new AceColumn(AceColumnType.CustomString, 204 cCur.CustomName, cCur.HideWithAsterisks, cCur.Width); 205 } 206 } 207 208 foreach(PwDocument pwDoc in Program.MainForm.DocumentManager.Documents) 209 { 210 if(string.IsNullOrEmpty(pwDoc.LockedIoc.Path) && pwDoc.Database.IsOpen) 211 { 212 EntryHandler eh = delegate(PwEntry pe) 213 { 214 foreach(KeyValuePair<string, ProtectedString> kvp in pe.Strings) 215 { 216 if(PwDefs.IsStandardField(kvp.Key)) continue; 217 if(d.ContainsKey(kvp.Key)) continue; 218 219 d[kvp.Key] = new AceColumn(AceColumnType.CustomString, 220 kvp.Key, kvp.Value.IsProtected, -1); 221 } 222 223 return true; 224 }; 225 226 pwDoc.Database.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh); 227 } 228 } 229 230 foreach(KeyValuePair<string, AceColumn> kvpCustom in d) 231 { 232 AddAceColumn(l, kvpCustom.Value); 233 } 234 235 d.Clear(); 236 // Add active plugin columns (including those of uninstalled plugins) 237 foreach(AceColumn cCur in lCur) 238 { 239 if(cCur.Type != AceColumnType.PluginExt) continue; 240 if(d.ContainsKey(cCur.CustomName)) { Debug.Assert(false); continue; } 241 242 d[cCur.CustomName] = new AceColumn(AceColumnType.PluginExt, 243 cCur.CustomName, cCur.HideWithAsterisks, cCur.Width); 244 } 245 246 // Add unused plugin columns 247 string[] vPlgExtNames = Program.ColumnProviderPool.GetColumnNames(); 248 foreach(string strPlgName in vPlgExtNames) 249 { 250 if(d.ContainsKey(strPlgName)) continue; // Do not overwrite 251 252 d[strPlgName] = new AceColumn(AceColumnType.PluginExt, strPlgName, 253 false, -1); 254 } 255 256 foreach(KeyValuePair<string, AceColumn> kvpExt in d) 257 { 258 AddAceColumn(l, kvpExt.Value); 259 } 260 261 // m_lvColumns.Invoke(new UpdateUIDelegate(UpdateListEx), false); 262 } 263 UpdateListEx(bool bGuiToInternal)264 private void UpdateListEx(bool bGuiToInternal) 265 { 266 if(bGuiToInternal) 267 { 268 } 269 else // Internal to GUI 270 { 271 foreach(ListViewItem lvi in m_lvColumns.Items) 272 { 273 AceColumn c = (lvi.Tag as AceColumn); 274 if(c == null) { Debug.Assert(false); continue; } 275 276 string str = (c.HideWithAsterisks ? KPRes.Yes : KPRes.No); 277 lvi.SubItems[1].Text = str; 278 } 279 } 280 } 281 UpdateColumnPropInfo()282 private void UpdateColumnPropInfo() 283 { 284 ListView.SelectedListViewItemCollection lvsic = m_lvColumns.SelectedItems; 285 if((lvsic == null) || (lvsic.Count != 1) || (lvsic[0] == null)) 286 { 287 m_grpColumn.Text = KPRes.SelectedColumn + ": (" + KPRes.None + ")"; 288 m_cbHide.Checked = false; 289 m_cbHide.Enabled = false; 290 } 291 else 292 { 293 ListViewItem lvi = lvsic[0]; 294 AceColumn c = (lvi.Tag as AceColumn); 295 if(c == null) { Debug.Assert(false); return; } 296 297 m_grpColumn.Text = KPRes.SelectedColumn + ": " + c.GetDisplayName(); 298 m_cbHide.Enabled = true; 299 300 m_bIgnoreHideCheckEvent = true; 301 UIUtil.SetChecked(m_cbHide, c.HideWithAsterisks); 302 m_bIgnoreHideCheckEvent = false; 303 } 304 } 305 OnFormClosed(object sender, FormClosedEventArgs e)306 private void OnFormClosed(object sender, FormClosedEventArgs e) 307 { 308 GlobalWindowManager.RemoveWindow(this); 309 } 310 OnBtnOK(object sender, EventArgs e)311 private void OnBtnOK(object sender, EventArgs e) 312 { 313 AceMainWindow mw = Program.Config.MainWindow; 314 List<AceColumn> l = mw.EntryListColumns; 315 List<AceColumn> lOld = new List<AceColumn>(l); 316 317 l.Clear(); 318 foreach(ListViewItem lvi in m_lvColumns.Items) 319 { 320 if(!lvi.Checked) continue; 321 322 AceColumn c = (lvi.Tag as AceColumn); 323 if(c == null) { Debug.Assert(false); continue; } 324 325 l.Add(c); 326 } 327 328 mw.EntryListColumnDisplayOrder = ComputeNewDisplayOrder(l, 329 lOld, mw.EntryListColumnDisplayOrder); 330 } 331 OnBtnCancel(object sender, EventArgs e)332 private void OnBtnCancel(object sender, EventArgs e) 333 { 334 } 335 OnColumnsSelectedIndexChanged(object sender, EventArgs e)336 private void OnColumnsSelectedIndexChanged(object sender, EventArgs e) 337 { 338 UpdateColumnPropInfo(); 339 } 340 OnHideCheckedChanged(object sender, EventArgs e)341 private void OnHideCheckedChanged(object sender, EventArgs e) 342 { 343 if(m_bIgnoreHideCheckEvent) return; 344 345 bool bChecked = m_cbHide.Checked; 346 foreach(ListViewItem lvi in m_lvColumns.SelectedItems) 347 { 348 AceColumn c = (lvi.Tag as AceColumn); 349 if(c == null) { Debug.Assert(false); continue; } 350 351 if((c.Type == AceColumnType.Password) && c.HideWithAsterisks && 352 !AppPolicy.Try(AppPolicyId.UnhidePasswords)) 353 { 354 // Do not change c.HideWithAsterisks 355 } 356 else c.HideWithAsterisks = bChecked; 357 } 358 359 UpdateListEx(false); 360 } 361 362 private sealed class AceColumnWithTag 363 { 364 public readonly AceColumn Column; 365 public readonly string TypeNameEx; 366 367 public long Tag; 368 AceColumnWithTag(AceColumn c, long lTag)369 public AceColumnWithTag(AceColumn c, long lTag) 370 { 371 this.Column = c; 372 this.TypeNameEx = c.GetTypeNameEx(); 373 this.Tag = lTag; 374 } 375 376 #if DEBUG ToString()377 public override string ToString() 378 { 379 return (this.TypeNameEx + ", " + this.Tag.ToString()); 380 } 381 #endif 382 CompareByTags(AceColumnWithTag x, AceColumnWithTag y)383 public static int CompareByTags(AceColumnWithTag x, AceColumnWithTag y) 384 { 385 return x.Tag.CompareTo(y.Tag); 386 } 387 } 388 ComputeNewDisplayOrder(List<AceColumn> lNew, List<AceColumn> lOld, string strOldOrder)389 private static string ComputeNewDisplayOrder(List<AceColumn> lNew, 390 List<AceColumn> lOld, string strOldOrder) 391 { 392 if((lNew == null) || (lOld == null)) { Debug.Assert(false); return string.Empty; } 393 if(string.IsNullOrEmpty(strOldOrder)) return string.Empty; 394 395 List<AceColumnWithTag> lOldS = new List<AceColumnWithTag>(); 396 try 397 { 398 int[] vOld = StrUtil.DeserializeIntArray(strOldOrder); 399 if((vOld == null) || (vOld.Length != lOld.Count)) 400 { 401 Debug.Assert(false); 402 return string.Empty; 403 } 404 405 for(int i = 0; i < vOld.Length; ++i) 406 { 407 if(lOld[i] == null) { Debug.Assert(false); return string.Empty; } 408 lOldS.Add(new AceColumnWithTag(lOld[i], vOld[i])); 409 } 410 411 lOldS.Sort(AceColumnWithTag.CompareByTags); 412 } 413 catch(Exception) { Debug.Assert(false); return string.Empty; } 414 415 List<AceColumnWithTag> l = new List<AceColumnWithTag>(); 416 foreach(AceColumn c in lNew) 417 { 418 if(c != null) l.Add(new AceColumnWithTag(c, 0)); 419 else { Debug.Assert(false); return string.Empty; } 420 } 421 422 long m = Math.Max(lNew.Count, lOld.Count); 423 424 // Preserve order of previous columns 425 for(int i = 0; i < lOldS.Count; ++i) 426 { 427 string strOldName = lOldS[i].TypeNameEx; 428 429 foreach(AceColumnWithTag ct in l) 430 { 431 if(ct.TypeNameEx == strOldName) 432 { 433 ct.Tag = m * i; 434 break; 435 } 436 } 437 } 438 439 // Insert new columns based on their default position 440 for(int i = 1; i < l.Count; ++i) 441 { 442 if(l[i].Tag == 0) l[i].Tag = l[i - 1].Tag + 1; 443 } 444 445 l.Sort(AceColumnWithTag.CompareByTags); 446 447 int[] v = new int[lNew.Count]; 448 for(int i = 0; i < v.Length; ++i) 449 { 450 for(int j = 0; j < l.Count; ++j) 451 { 452 if(object.ReferenceEquals(l[j].Column, lNew[i])) 453 { 454 v[i] = j; 455 break; 456 } 457 } 458 } 459 Debug.Assert(Array.IndexOf(v, 0) == Array.LastIndexOf(v, 0)); 460 461 return StrUtil.SerializeIntArray(v); 462 } 463 } 464 } 465