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.Globalization;
25 using System.Text;
26 using System.Windows.Forms;
27 
28 using KeePass.Resources;
29 
30 using KeePassLib;
31 using KeePassLib.Serialization;
32 using KeePassLib.Utility;
33 
34 namespace KeePass.UI
35 {
36 	/// <summary>
37 	/// MRU handler interface. An MRU handler must support executing an MRU
38 	/// item and clearing the MRU list.
39 	/// </summary>
40 	public interface IMruExecuteHandler
41 	{
42 		/// <summary>
43 		/// Function that is called when an MRU item is executed (i.e. when
44 		/// the user has clicked the menu item).
45 		/// </summary>
OnMruExecute(string strDisplayName, object oTag, ToolStripMenuItem tsmiParent)46 		void OnMruExecute(string strDisplayName, object oTag,
47 			ToolStripMenuItem tsmiParent);
48 
49 		/// <summary>
50 		/// Function to clear the MRU (for example all menu items must be
51 		/// removed from the menu).
52 		/// </summary>
OnMruClear()53 		void OnMruClear();
54 	}
55 
56 	public sealed class MruList
57 	{
58 		private List<KeyValuePair<string, object>> m_vItems =
59 			new List<KeyValuePair<string, object>>();
60 
61 		private IMruExecuteHandler m_handler = null;
62 		private List<ToolStripMenuItem> m_lContainers =
63 			new List<ToolStripMenuItem>();
64 
65 		private ToolStripMenuItem m_tsmiClear = null;
66 		private List<ToolStripMenuItem> m_lMruMenuItems =
67 			new List<ToolStripMenuItem>();
68 
69 		private enum MruMenuItemType
70 		{
71 			None = 0,
72 			Item,
73 			Clear
74 		}
75 
76 		// private Font m_fItalic = null;
77 
78 		private uint m_uMaxItemCount = 0;
79 		public uint MaxItemCount
80 		{
81 			get { return m_uMaxItemCount; }
82 			set { m_uMaxItemCount = value; }
83 		}
84 
85 		private bool m_bMarkOpened = false;
86 		public bool MarkOpened
87 		{
88 			get { return m_bMarkOpened; }
89 			set { m_bMarkOpened = value; }
90 		}
91 
92 		public uint ItemCount
93 		{
94 			get { return (uint)m_vItems.Count; }
95 		}
96 
97 		public bool IsValid
98 		{
99 			get { return (m_handler != null); }
100 		}
101 
MruList()102 		public MruList()
103 		{
104 		}
105 
Initialize(IMruExecuteHandler handler, params ToolStripMenuItem[] vContainers)106 		public void Initialize(IMruExecuteHandler handler,
107 			params ToolStripMenuItem[] vContainers)
108 		{
109 			Release();
110 
111 			Debug.Assert(handler != null); // No throw
112 			m_handler = handler;
113 
114 			if(vContainers != null)
115 			{
116 				foreach(ToolStripMenuItem tsmi in vContainers)
117 				{
118 					if(tsmi != null)
119 					{
120 						m_lContainers.Add(tsmi);
121 
122 						tsmi.DropDownOpening += this.OnDropDownOpening;
123 					}
124 				}
125 			}
126 		}
127 
Release()128 		public void Release()
129 		{
130 			ReleaseMenuItems();
131 
132 			foreach(ToolStripMenuItem tsmi in m_lContainers)
133 			{
134 				tsmi.DropDownOpening -= this.OnDropDownOpening;
135 			}
136 			m_lContainers.Clear();
137 
138 			m_handler = null;
139 		}
140 
ReleaseMenuItems()141 		private void ReleaseMenuItems()
142 		{
143 			if(m_tsmiClear != null)
144 			{
145 				m_tsmiClear.Click -= this.ClearHandler;
146 				m_tsmiClear = null;
147 			}
148 
149 			foreach(ToolStripMenuItem tsmi in m_lMruMenuItems)
150 			{
151 				tsmi.Click -= this.ClickHandler;
152 			}
153 			m_lMruMenuItems.Clear();
154 		}
155 
Clear()156 		public void Clear()
157 		{
158 			m_vItems.Clear();
159 		}
160 
161 		[Obsolete]
AddItem(string strDisplayName, object oTag, bool bUpdateMenu)162 		public void AddItem(string strDisplayName, object oTag, bool bUpdateMenu)
163 		{
164 			AddItem(strDisplayName, oTag);
165 		}
166 
AddItem(string strDisplayName, object oTag)167 		public void AddItem(string strDisplayName, object oTag)
168 		{
169 			Debug.Assert(strDisplayName != null);
170 			if(strDisplayName == null) throw new ArgumentNullException("strDisplayName");
171 			// oTag may be null
172 
173 			bool bExists = false;
174 			foreach(KeyValuePair<string, object> kvp in m_vItems)
175 			{
176 				Debug.Assert(kvp.Key != null);
177 				if(kvp.Key.Equals(strDisplayName, StrUtil.CaseIgnoreCmp))
178 				{
179 					bExists = true;
180 					break;
181 				}
182 			}
183 
184 			if(bExists) MoveItemToTop(strDisplayName, oTag);
185 			else
186 			{
187 				m_vItems.Insert(0, new KeyValuePair<string, object>(
188 					strDisplayName, oTag));
189 
190 				if(m_vItems.Count > m_uMaxItemCount)
191 					m_vItems.RemoveAt(m_vItems.Count - 1);
192 			}
193 
194 			// if(bUpdateMenu) UpdateMenu();
195 		}
196 
MoveItemToTop(string strName, object oNewTag)197 		private void MoveItemToTop(string strName, object oNewTag)
198 		{
199 			for(int i = 0; i < m_vItems.Count; ++i)
200 			{
201 				if(m_vItems[i].Key.Equals(strName, StrUtil.CaseIgnoreCmp))
202 				{
203 					KeyValuePair<string, object> t =
204 						new KeyValuePair<string, object>(strName, oNewTag);
205 
206 					m_vItems.RemoveAt(i);
207 					m_vItems.Insert(0, t);
208 					return;
209 				}
210 			}
211 
212 			Debug.Assert(false);
213 		}
214 
215 		[Obsolete]
UpdateMenu()216 		public void UpdateMenu()
217 		{
218 		}
219 
UpdateMenu(object oContainer)220 		private void UpdateMenu(object oContainer)
221 		{
222 			ToolStripMenuItem tsmiContainer = (oContainer as ToolStripMenuItem);
223 			if(tsmiContainer == null) { Debug.Assert(false); return; }
224 			if(!m_lContainers.Contains(tsmiContainer)) { Debug.Assert(false); return; }
225 
226 			tsmiContainer.DropDown.SuspendLayout();
227 
228 			// Verify that the popup arrow has been drawn (i.e. items existed)
229 			Debug.Assert(tsmiContainer.DropDownItems.Count > 0);
230 
231 			ReleaseMenuItems();
232 			tsmiContainer.DropDownItems.Clear();
233 
234 			uint uAccessKey = 1, uNull = 0;
235 			if(m_vItems.Count > 0)
236 			{
237 				foreach(KeyValuePair<string, object> kvp in m_vItems)
238 				{
239 					AddMenuItem(tsmiContainer, MruMenuItemType.Item,
240 						StrUtil.EncodeMenuText(kvp.Key), null, kvp.Value,
241 						true, ref uAccessKey);
242 				}
243 
244 				tsmiContainer.DropDownItems.Add(new ToolStripSeparator());
245 
246 				AddMenuItem(tsmiContainer, MruMenuItemType.Clear, KPRes.ClearMru,
247 					Properties.Resources.B16x16_EditDelete, null, true, ref uNull);
248 			}
249 			else
250 			{
251 				AddMenuItem(tsmiContainer, MruMenuItemType.None, "(" +
252 					KPRes.Empty + ")", null, null, false, ref uNull);
253 			}
254 
255 			tsmiContainer.DropDown.ResumeLayout(true);
256 		}
257 
AddMenuItem(ToolStripMenuItem tsmiParent, MruMenuItemType t, string strText, Image img, object oTag, bool bEnabled, ref uint uAccessKey)258 		private void AddMenuItem(ToolStripMenuItem tsmiParent, MruMenuItemType t,
259 			string strText, Image img, object oTag, bool bEnabled,
260 			ref uint uAccessKey)
261 		{
262 			ToolStripMenuItem tsmi = CreateMenuItem(t, strText, img, oTag,
263 				bEnabled, uAccessKey);
264 			tsmiParent.DropDownItems.Add(tsmi);
265 
266 			if(t == MruMenuItemType.Item)
267 				m_lMruMenuItems.Add(tsmi);
268 			else if(t == MruMenuItemType.Clear)
269 			{
270 				Debug.Assert(m_tsmiClear == null);
271 				m_tsmiClear = tsmi;
272 			}
273 
274 			if(uAccessKey != 0) ++uAccessKey;
275 		}
276 
CreateMenuItem(MruMenuItemType t, string strText, Image img, object oTag, bool bEnabled, uint uAccessKey)277 		private ToolStripMenuItem CreateMenuItem(MruMenuItemType t, string strText,
278 			Image img, object oTag, bool bEnabled, uint uAccessKey)
279 		{
280 			string strItem = strText;
281 			if(uAccessKey >= 1)
282 			{
283 				NumberFormatInfo nfi = NumberFormatInfo.InvariantInfo;
284 				if(uAccessKey < 10)
285 					strItem = @"&" + uAccessKey.ToString(nfi) + " " + strItem;
286 				else if(uAccessKey == 10)
287 					strItem = @"1&0 " + strItem;
288 				else strItem = uAccessKey.ToString(nfi) + " " + strItem;
289 			}
290 
291 			ToolStripMenuItem tsmi = new ToolStripMenuItem(strItem);
292 			if(img != null) tsmi.Image = img;
293 			if(oTag != null) tsmi.Tag = oTag;
294 
295 			IOConnectionInfo ioc = (oTag as IOConnectionInfo);
296 			if(m_bMarkOpened && (ioc != null) && (Program.MainForm != null))
297 			{
298 				foreach(PwDatabase pd in Program.MainForm.DocumentManager.GetOpenDatabases())
299 				{
300 					if(pd.IOConnectionInfo.GetDisplayName().Equals(
301 						ioc.GetDisplayName(), StrUtil.CaseIgnoreCmp))
302 					{
303 						// if(m_fItalic == null)
304 						// {
305 						//	Font f = tsi.Font;
306 						//	if(f != null)
307 						//		m_fItalic = FontUtil.CreateFont(f, FontStyle.Italic);
308 						//	else { Debug.Assert(false); }
309 						// }
310 
311 						// if(m_fItalic != null) tsmi.Font = m_fItalic;
312 						// 153, 51, 153
313 						tsmi.ForeColor = Color.FromArgb(64, 64, 255);
314 						tsmi.Text += " (" + KPRes.Opened + ")";
315 						break;
316 					}
317 				}
318 			}
319 
320 			if(t == MruMenuItemType.Item)
321 				tsmi.Click += this.ClickHandler;
322 			else if(t == MruMenuItemType.Clear)
323 				tsmi.Click += this.ClearHandler;
324 			// t == MruMenuItemType.None needs no handler
325 
326 			if(!bEnabled) tsmi.Enabled = false;
327 
328 			return tsmi;
329 		}
330 
GetItem(uint uIndex)331 		public KeyValuePair<string, object> GetItem(uint uIndex)
332 		{
333 			Debug.Assert(uIndex < (uint)m_vItems.Count);
334 			if(uIndex >= (uint)m_vItems.Count) throw new ArgumentException();
335 
336 			return m_vItems[(int)uIndex];
337 		}
338 
RemoveItem(string strDisplayName)339 		public bool RemoveItem(string strDisplayName)
340 		{
341 			Debug.Assert(strDisplayName != null);
342 			if(strDisplayName == null) throw new ArgumentNullException("strDisplayName");
343 
344 			for(int i = 0; i < m_vItems.Count; ++i)
345 			{
346 				KeyValuePair<string, object> kvp = m_vItems[i];
347 				if(kvp.Key.Equals(strDisplayName, StrUtil.CaseIgnoreCmp))
348 				{
349 					m_vItems.RemoveAt(i);
350 					return true;
351 				}
352 			}
353 
354 			return false;
355 		}
356 
ClickHandler(object sender, EventArgs args)357 		private void ClickHandler(object sender, EventArgs args)
358 		{
359 			ToolStripMenuItem tsmi = (sender as ToolStripMenuItem);
360 			if(tsmi == null) { Debug.Assert(false); return; }
361 			Debug.Assert(m_lMruMenuItems.Contains(tsmi));
362 
363 			ToolStripMenuItem tsmiParent = (tsmi.OwnerItem as ToolStripMenuItem);
364 			if(tsmiParent == null) { Debug.Assert(false); return; }
365 			if(!m_lContainers.Contains(tsmiParent)) { Debug.Assert(false); return; }
366 
367 			if(m_handler == null) { Debug.Assert(false); return; }
368 
369 			string strName = tsmi.Text;
370 			object oTag = tsmi.Tag;
371 
372 			m_handler.OnMruExecute(strName, oTag, tsmiParent);
373 
374 			// MoveItemToTop(strName);
375 		}
376 
ClearHandler(object sender, EventArgs e)377 		private void ClearHandler(object sender, EventArgs e)
378 		{
379 			if(m_handler != null) m_handler.OnMruClear();
380 			else { Debug.Assert(false); }
381 		}
382 
OnDropDownOpening(object sender, EventArgs e)383 		private void OnDropDownOpening(object sender, EventArgs e)
384 		{
385 			UpdateMenu(sender);
386 		}
387 	}
388 }
389