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.Drawing.Drawing2D;
26 using System.Drawing.Imaging;
27 using System.IO;
28 using System.Text;
29 using System.Windows.Forms;
30 
31 using KeePass.App;
32 using KeePass.Native;
33 using KeePass.Resources;
34 using KeePass.UI;
35 using KeePass.Util;
36 
37 using KeePassLib;
38 using KeePassLib.Utility;
39 
40 using NativeLib = KeePassLib.Native.NativeLib;
41 
42 namespace KeePass.Forms
43 {
44 	public partial class DataViewerForm : Form
45 	{
46 		private string m_strDataDesc = string.Empty;
47 		private byte[] m_pbData = null;
48 
49 		private bool m_bInitializing = true;
50 
51 		private uint m_uStartOffset = 0;
52 		private BinaryDataClass m_bdc = BinaryDataClass.Unknown;
53 
54 		private readonly string m_strViewerHex = KPRes.HexViewer;
55 		private readonly string m_strViewerText = KPRes.TextViewer;
56 		private readonly string m_strViewerImage = KPRes.ImageViewer;
57 		private readonly string m_strViewerWeb = KPRes.WebBrowser;
58 
59 		private readonly string m_strZoomAuto = KPRes.Auto;
60 
61 		// Link on Windows, hint on Linux (linkifying only works on Windows)
62 		private readonly string m_strDataExpand = (NativeLib.IsUnix() ?
63 			("--- " + KPRes.More + " ---") :
64 			("--- " + KPRes.ShowMore + " (" + KPRes.TimeReq + ") ---"));
65 		private bool m_bDataExpanded = false;
66 
67 		private string m_strInitialFormRect = string.Empty;
68 		private RichTextBoxContextMenu m_ctxText = new RichTextBoxContextMenu();
69 
70 		private Image m_img = null;
71 		private Image m_imgResized = null;
72 
73 		public event EventHandler<DvfContextEventArgs> DvfInit;
74 		public event EventHandler<DvfContextEventArgs> DvfUpdating;
75 		public event EventHandler<DvfContextEventArgs> DvfRelease;
76 
SupportsDataType(BinaryDataClass bdc)77 		public static bool SupportsDataType(BinaryDataClass bdc)
78 		{
79 			return ((bdc == BinaryDataClass.Text) || (bdc == BinaryDataClass.RichText) ||
80 				(bdc == BinaryDataClass.Image) || (bdc == BinaryDataClass.WebDocument));
81 		}
82 
InitEx(string strDataDesc, byte[] pbData)83 		public void InitEx(string strDataDesc, byte[] pbData)
84 		{
85 			if(strDataDesc != null) m_strDataDesc = strDataDesc;
86 
87 			m_pbData = pbData;
88 		}
89 
DataViewerForm()90 		public DataViewerForm()
91 		{
92 			InitializeComponent();
93 
94 			// GlobalWindowManager.InitializeForm checks docked controls
95 			m_rtbText.Dock = DockStyle.Fill;
96 			m_pnlImageViewer.Dock = DockStyle.Fill;
97 			m_picBox.Dock = DockStyle.Fill;
98 			m_webBrowser.Dock = DockStyle.Fill;
99 
100 			GlobalWindowManager.InitializeForm(this);
101 		}
102 
OnFormLoad(object sender, EventArgs e)103 		private void OnFormLoad(object sender, EventArgs e)
104 		{
105 			Debug.Assert(m_pbData != null);
106 			if(m_pbData == null) throw new InvalidOperationException();
107 
108 			m_bInitializing = true;
109 
110 			GlobalWindowManager.AddWindow(this);
111 
112 			this.Icon = AppIcons.Default;
113 			this.DoubleBuffered = true;
114 
115 			string strTitle = KPRes.DataViewerKP;
116 			if(m_strDataDesc.Length > 0)
117 				strTitle = m_strDataDesc + " - " + strTitle;
118 			this.Text = strTitle;
119 
120 			m_strInitialFormRect = UIUtil.SetWindowScreenRectEx(this,
121 				Program.Config.UI.DataViewerRect);
122 
123 			m_tssStatusMain.Text = KPRes.Ready;
124 			m_ctxText.Attach(m_rtbText, this);
125 
126 			m_tslEncoding.Text = KPRes.Encoding + ":";
127 
128 			foreach(StrEncodingInfo seiEnum in StrUtil.Encodings)
129 			{
130 				m_tscEncoding.Items.Add(seiEnum.Name);
131 			}
132 
133 			StrEncodingInfo seiGuess = BinaryDataClassifier.GetStringEncoding(
134 				m_pbData, out m_uStartOffset);
135 
136 			int iSel = 0;
137 			if(seiGuess != null)
138 				iSel = Math.Max(m_tscEncoding.FindStringExact(seiGuess.Name), 0);
139 			m_tscEncoding.SelectedIndex = iSel;
140 
141 			m_tslZoom.Text = KPRes.Zoom + ":";
142 
143 			// Required for mouse wheel handling
144 			Debug.Assert(m_tscZoom.DropDownStyle == ComboBoxStyle.DropDownList);
145 
146 			m_tscZoom.Items.Add(m_strZoomAuto);
147 			int[] vZooms = new int[] { 10, 25, 50, 75, 100, 125, 150, 200, 400 };
148 			foreach(int iZoom in vZooms)
149 				m_tscZoom.Items.Add(iZoom.ToString() + "%");
150 			m_tscZoom.SelectedIndex = 0;
151 
152 			m_tsbZoomOut.ToolTipText = KPRes.Zoom + " - (" + UIUtil.GetKeysName(
153 				Keys.Control | Keys.Subtract) + ")";
154 			m_tsbZoomIn.ToolTipText = KPRes.Zoom + " + (" + UIUtil.GetKeysName(
155 				Keys.Control | Keys.Add) + ")";
156 
157 			m_tslViewer.Text = KPRes.ShowIn + ":";
158 
159 			m_tscViewers.Items.Add(m_strViewerHex);
160 			m_tscViewers.Items.Add(m_strViewerText);
161 			m_tscViewers.Items.Add(m_strViewerImage);
162 			m_tscViewers.Items.Add(m_strViewerWeb);
163 
164 			m_bdc = BinaryDataClassifier.Classify(m_strDataDesc, m_pbData);
165 
166 			if((m_bdc == BinaryDataClass.Text) || (m_bdc == BinaryDataClass.RichText))
167 				m_tscViewers.SelectedIndex = 1;
168 			else if(m_bdc == BinaryDataClass.Image) m_tscViewers.SelectedIndex = 2;
169 			else if(m_bdc == BinaryDataClass.WebDocument) m_tscViewers.SelectedIndex = 3;
170 			else m_tscViewers.SelectedIndex = 0;
171 
172 			if(this.DvfInit != null)
173 				this.DvfInit(this, new DvfContextEventArgs(this, m_pbData,
174 					m_strDataDesc, m_tscViewers));
175 
176 			m_picBox.MouseWheel += this.OnPicBoxMouseWheel;
177 
178 			m_bInitializing = false;
179 			UpdateDataView();
180 		}
181 
OnRichTextBoxLinkClicked(object sender, LinkClickedEventArgs e)182 		private void OnRichTextBoxLinkClicked(object sender, LinkClickedEventArgs e)
183 		{
184 			try
185 			{
186 				string strLink = e.LinkText;
187 				if(string.IsNullOrEmpty(strLink)) { Debug.Assert(false); return; }
188 
189 				string strViewer = m_tscViewers.Text;
190 				bool bTextViewer = ((strViewer == m_strViewerHex) ||
191 					(strViewer == m_strViewerText));
192 
193 				if((strLink == m_strDataExpand) && bTextViewer)
194 				{
195 					m_bDataExpanded = true;
196 
197 					UpdateDataView();
198 
199 					m_rtbText.Select(m_rtbText.TextLength, 0);
200 					m_rtbText.ScrollToCaret();
201 				}
202 				else WinUtil.OpenUrl(strLink, null);
203 			}
204 			catch(Exception) { } // ScrollToCaret might throw (but still works)
205 		}
206 
BinaryDataToString(bool bReplaceNulls, out bool bDecodedStringValid)207 		private string BinaryDataToString(bool bReplaceNulls, out bool bDecodedStringValid)
208 		{
209 			string strEnc = m_tscEncoding.Text;
210 			StrEncodingInfo sei = StrUtil.GetEncoding(strEnc);
211 
212 			try
213 			{
214 				string str = (sei.Encoding.GetString(m_pbData, (int)m_uStartOffset,
215 					m_pbData.Length - (int)m_uStartOffset) ?? string.Empty);
216 
217 				bDecodedStringValid = StrUtil.IsValid(str);
218 
219 				if(bReplaceNulls) str = StrUtil.ReplaceNulls(str);
220 				return str;
221 			}
222 			catch(Exception) { bDecodedStringValid = false; }
223 
224 			return string.Empty;
225 		}
226 
SetRtbData(string strData, bool bRtf, bool bFixedFont, bool bLinkify)227 		private void SetRtbData(string strData, bool bRtf, bool bFixedFont,
228 			bool bLinkify)
229 		{
230 			if(strData == null) { Debug.Assert(false); strData = string.Empty; }
231 
232 			m_rtbText.Clear(); // Clear formatting (esp. induced by Unicode)
233 
234 			if(bFixedFont) FontUtil.AssignDefaultMono(m_rtbText, false);
235 			else FontUtil.AssignDefault(m_rtbText);
236 
237 			if(bRtf) m_rtbText.Rtf = StrUtil.RtfFix(strData);
238 			else m_rtbText.Text = strData;
239 
240 			if(bLinkify) UIUtil.RtfLinkifyUrls(m_rtbText);
241 
242 			if(!bRtf)
243 			{
244 				Font f = (bFixedFont ? FontUtil.MonoFont : FontUtil.DefaultFont);
245 				if(f != null)
246 				{
247 					m_rtbText.SelectAll();
248 					m_rtbText.SelectionFont = f;
249 				}
250 				else { Debug.Assert(false); }
251 			}
252 
253 			m_rtbText.Select(0, 0);
254 		}
255 
UpdateHexView()256 		private void UpdateHexView()
257 		{
258 			int cbData = (m_bDataExpanded ? m_pbData.Length :
259 				Math.Min(m_pbData.Length, 16 * 256));
260 
261 			const int cbGrp = 4;
262 			const int cbLine = 16;
263 
264 			int iMaxAddrWidth = Convert.ToString(Math.Max(cbData - 1, 0), 16).Length;
265 
266 			int cbDataUp = cbData;
267 			if((cbDataUp % cbLine) != 0)
268 				cbDataUp = cbDataUp + cbLine - (cbDataUp % cbLine);
269 			Debug.Assert(((cbDataUp % cbLine) == 0) && (cbDataUp >= cbData));
270 
271 			StringBuilder sb = new StringBuilder();
272 			for(int i = 0; i < cbDataUp; ++i)
273 			{
274 				if((i % cbLine) == 0)
275 				{
276 					sb.Append(Convert.ToString(i, 16).ToUpper().PadLeft(
277 						iMaxAddrWidth, '0'));
278 					sb.Append(": ");
279 				}
280 
281 				if(i < cbData)
282 				{
283 					byte bt = m_pbData[i];
284 					byte btHigh = (byte)(bt >> 4);
285 					byte btLow = (byte)(bt & 0x0F);
286 					if(btHigh >= 10) sb.Append((char)('A' + btHigh - 10));
287 					else sb.Append((char)('0' + btHigh));
288 					if(btLow >= 10) sb.Append((char)('A' + btLow - 10));
289 					else sb.Append((char)('0' + btLow));
290 				}
291 				else sb.Append("  ");
292 
293 				if(((i + 1) % cbGrp) == 0)
294 					sb.Append(' ');
295 
296 				if(((i + 1) % cbLine) == 0)
297 				{
298 					sb.Append(' ');
299 					int iStart = i - cbLine + 1;
300 					int iEndExcl = Math.Min(iStart + cbLine, cbData);
301 					for(int j = iStart; j < iEndExcl; ++j)
302 						sb.Append(StrUtil.ByteToSafeChar(m_pbData[j]));
303 					sb.AppendLine();
304 				}
305 			}
306 
307 			if(cbData < m_pbData.Length) sb.AppendLine(m_strDataExpand);
308 
309 			SetRtbData(sb.ToString(), false, true, false);
310 
311 			if(cbData < m_pbData.Length) LinkifyExpandLink();
312 		}
313 
UpdateTextView()314 		private void UpdateTextView()
315 		{
316 			bool bValid;
317 			string strData = BinaryDataToString(true, out bValid);
318 
319 			bool bRtf = (m_bdc == BinaryDataClass.RichText);
320 
321 			const int ccInvMax = 1024;
322 			bool bShorten = (!bValid && !bRtf && !m_bDataExpanded &&
323 				(strData.Length > ccInvMax));
324 
325 			if(bShorten)
326 				strData = strData.Substring(0, ccInvMax) + MessageService.NewLine +
327 					m_strDataExpand + MessageService.NewLine;
328 
329 			SetRtbData(strData, bRtf, false, true);
330 
331 			if(bShorten) LinkifyExpandLink();
332 		}
333 
LinkifyExpandLink()334 		private void LinkifyExpandLink()
335 		{
336 			int i = m_rtbText.Text.LastIndexOf(m_strDataExpand);
337 			if(i < 0) { Debug.Assert(false); return; }
338 
339 			m_rtbText.Select(i, m_strDataExpand.Length);
340 			UIUtil.RtfSetSelectionLink(m_rtbText);
341 			m_rtbText.Select(0, 0);
342 		}
343 
UpdateVisibility(string strViewer, bool bMakeVisible)344 		private void UpdateVisibility(string strViewer, bool bMakeVisible)
345 		{
346 			if(string.IsNullOrEmpty(strViewer)) { Debug.Assert(false); return; }
347 
348 			if(!bMakeVisible) // Hide all except strViewer
349 			{
350 				if((strViewer != m_strViewerHex) && (strViewer != m_strViewerText))
351 					m_rtbText.Visible = false;
352 				if(strViewer != m_strViewerImage)
353 				{
354 					m_picBox.Visible = false;
355 					m_pnlImageViewer.Visible = false;
356 				}
357 				if(strViewer != m_strViewerWeb)
358 					m_webBrowser.Visible = false;
359 			}
360 			else // Show strViewer
361 			{
362 				if((strViewer == m_strViewerHex) || (strViewer == m_strViewerText))
363 					m_rtbText.Visible = true;
364 				else if(strViewer == m_strViewerImage)
365 				{
366 					m_pnlImageViewer.Visible = true;
367 					m_picBox.Visible = true;
368 				}
369 				else if(strViewer == m_strViewerWeb)
370 					m_webBrowser.Visible = true;
371 			}
372 		}
373 
UpdateDataView()374 		private void UpdateDataView()
375 		{
376 			if(m_bInitializing) return;
377 
378 			string strViewer = m_tscViewers.Text;
379 			bool bText = ((strViewer == m_strViewerText) ||
380 				(strViewer == m_strViewerWeb));
381 			bool bImage = (strViewer == m_strViewerImage);
382 
383 			UpdateVisibility(strViewer, false);
384 			m_tssSeparator0.Visible = (bText || bImage);
385 			m_tslEncoding.Visible = m_tscEncoding.Visible = bText;
386 			m_tslZoom.Visible = m_tscZoom.Visible = m_tsbZoomOut.Visible =
387 				m_tsbZoomIn.Visible = bImage;
388 
389 			try
390 			{
391 				if(this.DvfUpdating != null)
392 				{
393 					DvfContextEventArgs args = new DvfContextEventArgs(this,
394 						m_pbData, m_strDataDesc, m_tscViewers);
395 					this.DvfUpdating(this, args);
396 					if(args.Cancel) return;
397 				}
398 
399 				if(strViewer == m_strViewerHex)
400 					UpdateHexView();
401 				else if(strViewer == m_strViewerText)
402 					UpdateTextView();
403 				else if(strViewer == m_strViewerImage)
404 				{
405 					if(m_img == null) m_img = GfxUtil.LoadImage(m_pbData);
406 					UpdateImageView();
407 				}
408 				else if(strViewer == m_strViewerWeb)
409 				{
410 					string strData = string.Empty;
411 					if(m_bdc == BinaryDataClass.WebDocument)
412 					{
413 						bool bValid;
414 						strData = BinaryDataToString(false, out bValid);
415 					}
416 					UIUtil.SetWebBrowserDocument(m_webBrowser, strData);
417 				}
418 			}
419 			catch(Exception) { Debug.Assert(strViewer == m_strViewerImage); }
420 
421 			UpdateVisibility(strViewer, true);
422 		}
423 
OnViewersSelectedIndexChanged(object sender, EventArgs e)424 		private void OnViewersSelectedIndexChanged(object sender, EventArgs e)
425 		{
426 			UpdateDataView();
427 		}
428 
OnEncodingSelectedIndexChanged(object sender, EventArgs e)429 		private void OnEncodingSelectedIndexChanged(object sender, EventArgs e)
430 		{
431 			UpdateDataView();
432 		}
433 
OnFormSizeChanged(object sender, EventArgs e)434 		private void OnFormSizeChanged(object sender, EventArgs e)
435 		{
436 			UpdateImageView();
437 		}
438 
UpdateImageView()439 		private void UpdateImageView()
440 		{
441 			if(m_img == null) return;
442 
443 			string strZoom = m_tscZoom.Text;
444 			if(string.IsNullOrEmpty(strZoom) || (strZoom == m_strZoomAuto))
445 			{
446 				m_pnlImageViewer.AutoScroll = false;
447 				m_picBox.Dock = DockStyle.Fill;
448 				m_picBox.Image = m_img;
449 
450 				if((m_img.Width > m_picBox.ClientSize.Width) ||
451 					(m_img.Height > m_picBox.ClientSize.Height))
452 				{
453 					m_picBox.SizeMode = PictureBoxSizeMode.Zoom;
454 				}
455 				else m_picBox.SizeMode = PictureBoxSizeMode.CenterImage;
456 
457 				return;
458 			}
459 
460 			if(!strZoom.EndsWith("%")) { Debug.Assert(false); return; }
461 
462 			int iZoom;
463 			if(!int.TryParse(strZoom.Substring(0, strZoom.Length - 1), out iZoom))
464 			{
465 				Debug.Assert(false);
466 				return;
467 			}
468 
469 			int cliW = m_pnlImageViewer.ClientRectangle.Width;
470 			int cliH = m_pnlImageViewer.ClientRectangle.Height;
471 
472 			int dx = (m_img.Width * iZoom) / 100;
473 			int dy = (m_img.Height * iZoom) / 100;
474 
475 			float fScrollX = 0.5f, fScrollY = 0.5f;
476 			if(m_pnlImageViewer.AutoScroll)
477 			{
478 				Point ptOffset = m_pnlImageViewer.AutoScrollPosition;
479 				Size sz = m_picBox.ClientSize;
480 
481 				if(sz.Width > cliW)
482 				{
483 					fScrollX = Math.Abs((float)ptOffset.X / (float)(sz.Width - cliW));
484 					if(fScrollX < 0.0f) { Debug.Assert(false); fScrollX = 0.0f; }
485 					if(fScrollX > 1.0f) { Debug.Assert(false); fScrollX = 1.0f; }
486 				}
487 
488 				if(sz.Height > cliH)
489 				{
490 					fScrollY = Math.Abs((float)ptOffset.Y / (float)(sz.Height - cliH));
491 					if(fScrollY < 0.0f) { Debug.Assert(false); fScrollY = 0.0f; }
492 					if(fScrollY > 1.0f) { Debug.Assert(false); fScrollY = 1.0f; }
493 				}
494 			}
495 			m_pnlImageViewer.AutoScroll = false;
496 
497 			m_picBox.Dock = DockStyle.None;
498 			m_picBox.SizeMode = PictureBoxSizeMode.AutoSize;
499 
500 			int x = 0, y = 0;
501 			if(dx < cliW) x = (cliW - dx) / 2;
502 			if(dy < cliH) y = (cliH - dy) / 2;
503 
504 			m_picBox.Location = new Point(x, y);
505 
506 			if((dx == m_img.Width) && (dy == m_img.Height))
507 				m_picBox.Image = m_img;
508 			else if((m_imgResized != null) && (m_imgResized.Width == dx) &&
509 				(m_imgResized.Height == dy))
510 				m_picBox.Image = m_imgResized;
511 			else
512 			{
513 				Image imgToDispose = m_imgResized;
514 
515 				m_imgResized = GfxUtil.ScaleImage(m_img, dx, dy);
516 				m_picBox.Image = m_imgResized;
517 
518 				if(imgToDispose != null) imgToDispose.Dispose();
519 			}
520 
521 			m_pnlImageViewer.AutoScroll = true;
522 
523 			int sx = 0, sy = 0;
524 			if(dx > cliW) sx = (int)(fScrollX * (float)(dx - cliW));
525 			if(dy > cliH) sy = (int)(fScrollY * (float)(dy - cliH));
526 			try { m_pnlImageViewer.AutoScrollPosition = new Point(sx, sy); }
527 			catch(Exception) { Debug.Assert(false); }
528 		}
529 
OnFormClosing(object sender, FormClosingEventArgs e)530 		private void OnFormClosing(object sender, FormClosingEventArgs e)
531 		{
532 			if(this.DvfRelease != null)
533 			{
534 				DvfContextEventArgs args = new DvfContextEventArgs(this,
535 					m_pbData, m_strDataDesc, m_tscViewers);
536 				this.DvfRelease(sender, args);
537 				if(args.Cancel)
538 				{
539 					e.Cancel = true;
540 					return;
541 				}
542 			}
543 
544 			string strRect = UIUtil.GetWindowScreenRect(this);
545 			if(strRect != m_strInitialFormRect) // Don't overwrite ""
546 				Program.Config.UI.DataViewerRect = strRect;
547 		}
548 
OnFormClosed(object sender, FormClosedEventArgs e)549 		private void OnFormClosed(object sender, FormClosedEventArgs e)
550 		{
551 			m_picBox.MouseWheel -= this.OnPicBoxMouseWheel;
552 
553 			m_picBox.Image = null;
554 			if(m_img != null) { m_img.Dispose(); m_img = null; }
555 			if(m_imgResized != null) { m_imgResized.Dispose(); m_imgResized = null; }
556 
557 			m_ctxText.Detach();
558 			GlobalWindowManager.RemoveWindow(this);
559 		}
560 
ProcessCmdKey(ref Message msg, Keys keyData)561 		protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
562 		{
563 			bool bDown;
564 			if(!NativeMethods.GetKeyMessageState(ref msg, out bDown))
565 				return base.ProcessCmdKey(ref msg, keyData);
566 
567 			if(keyData == Keys.Escape) // No modifiers
568 			{
569 				if(bDown) Close();
570 				return true;
571 			}
572 
573 			Keys kc = (keyData & Keys.KeyCode);
574 			bool bCtrl = ((keyData & Keys.Control) != Keys.None);
575 
576 			if(bCtrl && m_tscZoom.Visible)
577 			{
578 				if((kc == Keys.Add) || (kc == Keys.Subtract))
579 				{
580 					if(bDown) PerformZoom((kc == Keys.Add) ? 1 : -1);
581 					return true;
582 				}
583 				if((kc == Keys.Oemplus) || (kc == Keys.OemMinus))
584 				{
585 					if(bDown) PerformZoom((kc == Keys.Oemplus) ? 1 : -1);
586 					return true;
587 				}
588 			}
589 
590 			return base.ProcessCmdKey(ref msg, keyData);
591 		}
592 
OnZoomSelectedIndexChanged(object sender, EventArgs e)593 		private void OnZoomSelectedIndexChanged(object sender, EventArgs e)
594 		{
595 			UpdateImageView();
596 		}
597 
PerformZoom(int d)598 		private void PerformZoom(int d)
599 		{
600 			if(!m_tscZoom.Visible) { Debug.Assert(false); return; }
601 
602 			int iCur = m_tscZoom.SelectedIndex, cMax = m_tscZoom.Items.Count;
603 			if((iCur < 0) || (iCur >= cMax)) { Debug.Assert(false); return; }
604 
605 			int iAuto = m_tscZoom.Items.IndexOf(m_strZoomAuto);
606 			if((iAuto < 0) || (iAuto >= cMax)) { Debug.Assert(false); return; }
607 
608 			if(iCur == iAuto)
609 			{
610 				iCur = GetNearestFixedZoomItemIndex();
611 				if((iCur < 0) || (iCur >= cMax)) { Debug.Assert(false); return; }
612 			}
613 
614 			int iNew = Math.Min(Math.Max(iCur + d, 0), cMax - 1);
615 			if((iNew != iCur) && (iNew != iAuto)) m_tscZoom.SelectedIndex = iNew;
616 		}
617 
OnPicBoxMouseWheel(object sender, MouseEventArgs e)618 		private void OnPicBoxMouseWheel(object sender, MouseEventArgs e)
619 		{
620 			if(e == null) { Debug.Assert(false); return; }
621 			if((Control.ModifierKeys & Keys.Control) == Keys.None) return;
622 
623 			int d = e.Delta / 120; // See Control.MouseWheel event
624 			PerformZoom(d);
625 		}
626 
OnViewerZoomOut(object sender, EventArgs e)627 		private void OnViewerZoomOut(object sender, EventArgs e)
628 		{
629 			PerformZoom(-1);
630 		}
631 
OnViewerZoomIn(object sender, EventArgs e)632 		private void OnViewerZoomIn(object sender, EventArgs e)
633 		{
634 			PerformZoom(1);
635 		}
636 
GetNearestFixedZoomItemIndex()637 		private int GetNearestFixedZoomItemIndex()
638 		{
639 			if(m_img == null) { Debug.Assert(false); return -1; }
640 
641 			int iW = m_img.Width, iH = m_img.Height;
642 			if((iW <= 0) || (iH <= 0)) { Debug.Assert(false); return -1; }
643 
644 			int cW = m_picBox.ClientSize.Width, cH = m_picBox.ClientSize.Height;
645 			if((cW <= 0) || (cH <= 0)) { Debug.Assert(false); return -1; }
646 
647 			int z = 100;
648 			if((iW > cW) || (iH > cH))
649 			{
650 				int zW = (int)Math.Round(100.0 * (double)cW / (double)iW);
651 				int zH = (int)Math.Round(100.0 * (double)cH / (double)iH);
652 				z = Math.Min(zW, zH);
653 			}
654 
655 			int dMin = int.MaxValue;
656 			int iBest = -1;
657 			for(int i = 0; i < m_tscZoom.Items.Count; ++i)
658 			{
659 				string str = (m_tscZoom.Items[i] as string);
660 				if(string.IsNullOrEmpty(str)) { Debug.Assert(false); continue; }
661 
662 				if(!str.EndsWith("%")) continue;
663 				str = str.Substring(0, str.Length - 1);
664 
665 				int zItem = 0;
666 				if(!int.TryParse(str, out zItem)) { Debug.Assert(false); continue; }
667 
668 				int d = Math.Abs(z - zItem);
669 				if(d < dMin) { iBest = i; dMin = d; }
670 			}
671 
672 			return iBest;
673 		}
674 	}
675 
676 	public sealed class DvfContextEventArgs : CancellableOperationEventArgs
677 	{
678 		private DataViewerForm m_form;
679 		public DataViewerForm Form { get { return m_form; } }
680 
681 		private byte[] m_pbData;
682 		public byte[] Data { get { return m_pbData; } }
683 
684 		private string m_strDataDesc;
685 		public string DataDescription { get { return m_strDataDesc; } }
686 
687 		private ToolStripComboBox m_tscViewers;
688 		public ToolStripComboBox ViewersComboBox { get { return m_tscViewers; } }
689 
DvfContextEventArgs(DataViewerForm form, byte[] pbData, string strDataDesc, ToolStripComboBox cbViewers)690 		public DvfContextEventArgs(DataViewerForm form, byte[] pbData,
691 			string strDataDesc, ToolStripComboBox cbViewers)
692 		{
693 			m_form = form;
694 			m_pbData = pbData;
695 			m_strDataDesc = strDataDesc;
696 			m_tscViewers = cbViewers;
697 		}
698 	}
699 }
700