OSDN Git Service

Merge with stable
[winmerge-jp/winmerge-jp.git] / Src / MainFrm.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
6 //    This program is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program; if not, write to the Free Software
18 //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /** 
22  * @file  MainFrm.cpp
23  *
24  * @brief Implementation of the CMainFrame class
25  */
26
27 #include "StdAfx.h"
28 #include "MainFrm.h"
29 #include <vector>
30 #include <Poco/Exception.h>
31 #include <shlwapi.h>
32 #include <Poco/Exception.h>
33 #include <afxinet.h>
34 #include "Constants.h"
35 #include "Merge.h"
36 #include "FileFilterHelper.h"
37 #include "UnicodeString.h"
38 #include "BCMenu.h"
39 #include "OpenFrm.h"
40 #include "DirFrame.h"           // Include type information
41 #include "ChildFrm.h"
42 #include "HexMergeFrm.h"
43 #include "DirView.h"
44 #include "DirDoc.h"
45 #include "OpenDoc.h"
46 #include "OpenView.h"
47 #include "MergeDoc.h"
48 #include "MergeEditView.h"
49 #include "HexMergeDoc.h"
50 #include "ImgMergeFrm.h"
51 #include "LineFiltersList.h"
52 #include "ConflictFileParser.h"
53 #include "LineFiltersDlg.h"
54 #include "paths.h"
55 #include "Environment.h"
56 #include "CustomStatusCursor.h"
57 #include "PatchTool.h"
58 #include "Plugins.h"
59 #include "SelectUnpackerDlg.h"
60 #include "ConfigLog.h"
61 #include "7zCommon.h"
62 #include "Merge7zFormatMergePluginImpl.h"
63 #include "FileFiltersDlg.h"
64 #include "OptionsMgr.h"
65 #include "OptionsDef.h"
66 #include "codepage_detect.h"
67 #include "unicoder.h"
68 #include "codepage.h"
69 #include "PreferencesDlg.h"
70 #include "ProjectFilePathsDlg.h"
71 #include "FileOrFolderSelect.h"
72 #include "unicoder.h"
73 #include "PluginsListDlg.h"
74 #include "stringdiffs.h"
75 #include "MergeCmdLineInfo.h"
76 #include "OptionsFont.h"
77 #include "TFile.h"
78 #include "JumpList.h"
79 #include "DropHandler.h"
80 #include "LanguageSelect.h"
81 #include "version.h"
82
83 using std::vector;
84
85 /*
86  One source file must compile the stubs for multimonitor
87  by defining the symbol COMPILE_MULTIMON_STUBS & including <multimon.h>
88 */
89 #ifdef COMPILE_MULTIMON_STUBS
90 #undef COMPILE_MULTIMON_STUBS
91 #endif
92 #define COMPILE_MULTIMON_STUBS
93 #include <multimon.h>
94
95 #ifdef _DEBUG
96 #define new DEBUG_NEW
97 #undef THIS_FILE
98 static char THIS_FILE[] = __FILE__;
99 #endif
100
101 static void LoadToolbarImageList(CMainFrame::TOOLBAR_SIZE size, UINT nIDResource, CImageList& ImgList);
102 static const CPtrList &GetDocList(const CMultiDocTemplate *pTemplate);
103 template<class DocClass>
104 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles);
105
106 /**
107  * @brief A table associating menuitem id, icon and menus to apply.
108  */
109 const CMainFrame::MENUITEM_ICON CMainFrame::m_MenuIcons[] = {
110         { ID_FILE_OPENCONFLICT,                 IDB_FILE_OPENCONFLICT,                  CMainFrame::MENU_ALL },
111         { ID_EDIT_COPY,                                 IDB_EDIT_COPY,                                  CMainFrame::MENU_ALL },
112         { ID_EDIT_CUT,                                  IDB_EDIT_CUT,                                   CMainFrame::MENU_ALL },
113         { ID_EDIT_PASTE,                                IDB_EDIT_PASTE,                                 CMainFrame::MENU_ALL },
114         { ID_EDIT_FIND,                                 IDB_EDIT_SEARCH,                                CMainFrame::MENU_ALL },
115         { ID_WINDOW_CASCADE,                    IDB_WINDOW_CASCADE,                             CMainFrame::MENU_ALL },
116         { ID_WINDOW_TILE_HORZ,                  IDB_WINDOW_HORIZONTAL,                  CMainFrame::MENU_ALL },
117         { ID_WINDOW_TILE_VERT,                  IDB_WINDOW_VERTICAL,                    CMainFrame::MENU_ALL },
118         { ID_FILE_CLOSE,                                IDB_WINDOW_CLOSE,                               CMainFrame::MENU_ALL },
119         { ID_WINDOW_CHANGE_PANE,                IDB_WINDOW_CHANGEPANE,                  CMainFrame::MENU_ALL },
120         { ID_EDIT_WMGOTO,                               IDB_EDIT_GOTO,                                  CMainFrame::MENU_ALL },
121         { ID_EDIT_REPLACE,                              IDB_EDIT_REPLACE,                               CMainFrame::MENU_ALL },
122         { ID_VIEW_SELECTFONT,                   IDB_VIEW_SELECTFONT,                    CMainFrame::MENU_ALL },
123         { ID_APP_EXIT,                                  IDB_FILE_EXIT,                                  CMainFrame::MENU_ALL },
124         { ID_HELP_CONTENTS,                             IDB_HELP_CONTENTS,                              CMainFrame::MENU_ALL },
125         { ID_EDIT_SELECT_ALL,                   IDB_EDIT_SELECTALL,                             CMainFrame::MENU_ALL },
126         { ID_TOOLS_FILTERS,                             IDB_TOOLS_FILTERS,                              CMainFrame::MENU_ALL },
127         { ID_TOOLS_CUSTOMIZECOLUMNS,    IDB_TOOLS_COLUMNS,                              CMainFrame::MENU_ALL },
128         { ID_TOOLS_GENERATEPATCH,               IDB_TOOLS_GENERATEPATCH,                CMainFrame::MENU_ALL },
129         { ID_PLUGINS_LIST,                              IDB_PLUGINS_LIST,                               CMainFrame::MENU_ALL },
130         { ID_COPY_FROM_LEFT,                    IDB_COPY_FROM_LEFT,                             CMainFrame::MENU_ALL },
131         { ID_COPY_FROM_RIGHT,                   IDB_COPY_FROM_RIGHT,                    CMainFrame::MENU_ALL },
132         { ID_FILE_PRINT,                                IDB_FILE_PRINT,                                 CMainFrame::MENU_FILECMP },
133         { ID_TOOLS_GENERATEREPORT,              IDB_TOOLS_GENERATEREPORT,               CMainFrame::MENU_FILECMP },
134         { ID_EDIT_TOGGLE_BOOKMARK,              IDB_EDIT_TOGGLE_BOOKMARK,               CMainFrame::MENU_FILECMP },
135         { ID_EDIT_GOTO_NEXT_BOOKMARK,   IDB_EDIT_GOTO_NEXT_BOOKMARK,    CMainFrame::MENU_FILECMP },
136         { ID_EDIT_GOTO_PREV_BOOKMARK,   IDB_EDIT_GOTO_PREV_BOOKMARK,    CMainFrame::MENU_FILECMP },
137         { ID_EDIT_CLEAR_ALL_BOOKMARKS,  IDB_EDIT_CLEAR_ALL_BOOKMARKS,   CMainFrame::MENU_FILECMP },
138         { ID_VIEW_ZOOMIN,                               IDB_VIEW_ZOOMIN,                                CMainFrame::MENU_FILECMP },
139         { ID_VIEW_ZOOMOUT,                              IDB_VIEW_ZOOMOUT,                               CMainFrame::MENU_FILECMP },
140         { ID_MERGE_COMPARE,                             IDB_MERGE_COMPARE,                              CMainFrame::MENU_FOLDERCMP },
141         { ID_MERGE_COMPARE_LEFT1_LEFT2,         IDB_MERGE_COMPARE_LEFT1_LEFT2,  CMainFrame::MENU_FOLDERCMP },
142         { ID_MERGE_COMPARE_RIGHT1_RIGHT2,       IDB_MERGE_COMPARE_RIGHT1_RIGHT2,CMainFrame::MENU_FOLDERCMP },
143         { ID_MERGE_COMPARE_LEFT1_RIGHT2,        IDB_MERGE_COMPARE_LEFT1_RIGHT2, CMainFrame::MENU_FOLDERCMP },
144         { ID_MERGE_COMPARE_LEFT2_RIGHT1,        IDB_MERGE_COMPARE_LEFT2_RIGHT1, CMainFrame::MENU_FOLDERCMP },
145         { ID_MERGE_DELETE,                              IDB_MERGE_DELETE,                               CMainFrame::MENU_FOLDERCMP },
146         { ID_TOOLS_GENERATEREPORT,              IDB_TOOLS_GENERATEREPORT,               CMainFrame::MENU_FOLDERCMP },
147         { ID_DIR_COPY_LEFT_TO_RIGHT,    IDB_LEFT_TO_RIGHT,                              CMainFrame::MENU_FOLDERCMP },
148         { ID_DIR_COPY_RIGHT_TO_LEFT,    IDB_RIGHT_TO_LEFT,                              CMainFrame::MENU_FOLDERCMP },
149         { ID_DIR_COPY_LEFT_TO_BROWSE,   IDB_LEFT_TO_BROWSE,                             CMainFrame::MENU_FOLDERCMP },
150         { ID_DIR_COPY_RIGHT_TO_BROWSE,  IDB_RIGHT_TO_BROWSE,                    CMainFrame::MENU_FOLDERCMP },
151         { ID_DIR_MOVE_LEFT_TO_BROWSE,   IDB_MOVE_LEFT_TO_BROWSE,                CMainFrame::MENU_FOLDERCMP },
152         { ID_DIR_MOVE_RIGHT_TO_BROWSE,  IDB_MOVE_RIGHT_TO_BROWSE,               CMainFrame::MENU_FOLDERCMP },
153         { ID_DIR_DEL_LEFT,                              IDB_LEFT,                                               CMainFrame::MENU_FOLDERCMP },
154         { ID_DIR_DEL_RIGHT,                             IDB_RIGHT,                                              CMainFrame::MENU_FOLDERCMP },
155         { ID_DIR_DEL_BOTH,                              IDB_BOTH,                                               CMainFrame::MENU_FOLDERCMP },
156         { ID_DIR_COPY_PATHNAMES_LEFT,   IDB_LEFT,                                               CMainFrame::MENU_FOLDERCMP },
157         { ID_DIR_COPY_PATHNAMES_RIGHT,  IDB_RIGHT,                                              CMainFrame::MENU_FOLDERCMP },
158         { ID_DIR_COPY_PATHNAMES_BOTH,   IDB_BOTH,                                               CMainFrame::MENU_FOLDERCMP },
159         { ID_DIR_COPY_LEFT_TO_CLIPBOARD, IDB_LEFT,                                              CMainFrame::MENU_FOLDERCMP },
160         { ID_DIR_COPY_RIGHT_TO_CLIPBOARD, IDB_RIGHT,                                    CMainFrame::MENU_FOLDERCMP },
161         { ID_DIR_COPY_BOTH_TO_CLIPBOARD, IDB_BOTH,                                              CMainFrame::MENU_FOLDERCMP },
162         { ID_DIR_ZIP_LEFT,                              IDB_LEFT,                                               CMainFrame::MENU_FOLDERCMP },
163         { ID_DIR_ZIP_RIGHT,                             IDB_RIGHT,                                              CMainFrame::MENU_FOLDERCMP },
164         { ID_DIR_ZIP_BOTH,                              IDB_BOTH,                                               CMainFrame::MENU_FOLDERCMP }
165 };
166
167
168 /////////////////////////////////////////////////////////////////////////////
169 // CMainFrame
170
171 IMPLEMENT_DYNAMIC(CMainFrame, CMDIFrameWnd)
172
173 BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
174         //{{AFX_MSG_MAP(CMainFrame)
175         ON_WM_MENUCHAR()
176         ON_WM_MEASUREITEM()
177         ON_WM_INITMENUPOPUP()
178         ON_WM_INITMENU()
179         ON_COMMAND(ID_OPTIONS_SHOWDIFFERENT, OnOptionsShowDifferent)
180         ON_COMMAND(ID_OPTIONS_SHOWIDENTICAL, OnOptionsShowIdentical)
181         ON_COMMAND(ID_OPTIONS_SHOWUNIQUELEFT, OnOptionsShowUniqueLeft)
182         ON_COMMAND(ID_OPTIONS_SHOWUNIQUERIGHT, OnOptionsShowUniqueRight)
183         ON_COMMAND(ID_OPTIONS_SHOWBINARIES, OnOptionsShowBinaries)
184         ON_COMMAND(ID_OPTIONS_SHOWSKIPPED, OnOptionsShowSkipped)
185         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWDIFFERENT, OnUpdateOptionsShowdifferent)
186         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWIDENTICAL, OnUpdateOptionsShowidentical)
187         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUELEFT, OnUpdateOptionsShowuniqueleft)
188         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWUNIQUERIGHT, OnUpdateOptionsShowuniqueright)
189         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWBINARIES, OnUpdateOptionsShowBinaries)
190         ON_UPDATE_COMMAND_UI(ID_OPTIONS_SHOWSKIPPED, OnUpdateOptionsShowSkipped)
191         ON_WM_CREATE()
192         ON_COMMAND(ID_FILE_OPEN, OnFileOpen)
193         ON_COMMAND(ID_HELP_GNULICENSE, OnHelpGnulicense)
194         ON_COMMAND(ID_OPTIONS, OnOptions)
195         ON_COMMAND(ID_VIEW_SELECTFONT, OnViewSelectfont)
196         ON_UPDATE_COMMAND_UI(ID_VIEW_SELECTFONT, OnUpdateViewSelectfont)
197         ON_COMMAND(ID_VIEW_USEDEFAULTFONT, OnViewUsedefaultfont)
198         ON_UPDATE_COMMAND_UI(ID_VIEW_USEDEFAULTFONT, OnUpdateViewUsedefaultfont)
199         ON_COMMAND(ID_HELP_CONTENTS, OnHelpContents)
200         ON_UPDATE_COMMAND_UI(ID_HELP_CONTENTS, OnUpdateHelpContents)
201         ON_WM_CLOSE()
202         ON_COMMAND(ID_VIEW_WHITESPACE, OnViewWhitespace)
203         ON_UPDATE_COMMAND_UI(ID_VIEW_WHITESPACE, OnUpdateViewWhitespace)
204         ON_COMMAND(ID_TOOLS_GENERATEPATCH, OnToolsGeneratePatch)
205         ON_WM_DESTROY()
206         ON_WM_SETCURSOR()
207         ON_COMMAND_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnPluginUnpackMode)
208         ON_UPDATE_COMMAND_UI_RANGE(ID_UNPACK_MANUAL, ID_UNPACK_AUTO, OnUpdatePluginUnpackMode)
209         ON_COMMAND_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnPluginPrediffMode)
210         ON_UPDATE_COMMAND_UI_RANGE(ID_PREDIFFER_MANUAL, ID_PREDIFFER_AUTO, OnUpdatePluginPrediffMode)
211         ON_UPDATE_COMMAND_UI(ID_RELOAD_PLUGINS, OnUpdateReloadPlugins)
212         ON_COMMAND(ID_RELOAD_PLUGINS, OnReloadPlugins)
213         ON_COMMAND(ID_HELP_GETCONFIG, OnSaveConfigData)
214         ON_COMMAND(ID_FILE_NEW, OnFileNew)
215         ON_COMMAND(ID_FILE_NEW3, OnFileNew3)
216         ON_COMMAND(ID_TOOLS_FILTERS, OnToolsFilters)
217         ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
218         ON_UPDATE_COMMAND_UI(ID_VIEW_TAB_BAR, OnUpdateViewTabBar)
219         ON_COMMAND(ID_VIEW_TAB_BAR, OnViewTabBar)
220         ON_UPDATE_COMMAND_UI(ID_VIEW_RESIZE_PANES, OnUpdateResizePanes)
221         ON_COMMAND(ID_VIEW_RESIZE_PANES, OnResizePanes)
222         ON_COMMAND(ID_FILE_OPENPROJECT, OnFileOpenproject)
223         ON_MESSAGE(WM_COPYDATA, OnCopyData)
224         ON_MESSAGE(WM_USER+1, OnUser1)
225         ON_COMMAND(ID_WINDOW_CLOSEALL, OnWindowCloseAll)
226         ON_UPDATE_COMMAND_UI(ID_WINDOW_CLOSEALL, OnUpdateWindowCloseAll)
227         ON_COMMAND(ID_FILE_SAVEPROJECT, OnSaveProject)
228         ON_WM_TIMER()
229         ON_WM_ACTIVATE()
230         ON_WM_ACTIVATEAPP()
231         ON_COMMAND(ID_TOOLBAR_NONE, OnToolbarNone)
232         ON_UPDATE_COMMAND_UI(ID_TOOLBAR_NONE, OnUpdateToolbarNone)
233         ON_COMMAND(ID_TOOLBAR_SMALL, OnToolbarSmall)
234         ON_UPDATE_COMMAND_UI(ID_TOOLBAR_SMALL, OnUpdateToolbarSmall)
235         ON_COMMAND(ID_TOOLBAR_BIG, OnToolbarBig)
236         ON_UPDATE_COMMAND_UI(ID_TOOLBAR_BIG, OnUpdateToolbarBig)
237         ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
238         ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
239         ON_COMMAND(ID_HELP_CHECKFORUPDATES, OnHelpCheckForUpdates)
240         ON_COMMAND(ID_FILE_OPENCONFLICT, OnFileOpenConflict)
241         ON_COMMAND(ID_PLUGINS_LIST, OnPluginsList)
242         ON_UPDATE_COMMAND_UI(ID_STATUS_PLUGIN, OnUpdatePluginName)
243         ON_NOTIFY(TBN_DROPDOWN, AFX_IDW_TOOLBAR, OnDiffOptionsDropDown)
244         ON_COMMAND_RANGE(IDC_DIFF_WHITESPACE_COMPARE, IDC_DIFF_WHITESPACE_IGNOREALL, OnDiffWhitespace)
245         ON_UPDATE_COMMAND_UI_RANGE(IDC_DIFF_WHITESPACE_COMPARE, IDC_DIFF_WHITESPACE_IGNOREALL, OnUpdateDiffWhitespace)
246         ON_COMMAND(IDC_DIFF_CASESENSITIVE, OnDiffCaseSensitive)
247         ON_UPDATE_COMMAND_UI(IDC_DIFF_CASESENSITIVE, OnUpdateDiffCaseSensitive)
248         ON_COMMAND(IDC_DIFF_IGNOREEOL, OnDiffIgnoreEOL)
249         ON_UPDATE_COMMAND_UI(IDC_DIFF_IGNOREEOL, OnUpdateDiffIgnoreEOL)
250         ON_COMMAND_RANGE(ID_COMPMETHOD_FULL_CONTENTS, ID_COMPMETHOD_SIZE, OnCompareMethod)
251         ON_UPDATE_COMMAND_UI_RANGE(ID_COMPMETHOD_FULL_CONTENTS, ID_COMPMETHOD_SIZE, OnUpdateCompareMethod)
252         ON_COMMAND_RANGE(ID_MRU_FIRST, ID_MRU_LAST, OnMRUs)
253         ON_UPDATE_COMMAND_UI(ID_MRU_FIRST, OnUpdateNoMRUs)
254         ON_UPDATE_COMMAND_UI(ID_NO_MRU, OnUpdateNoMRUs)
255         //}}AFX_MSG_MAP
256 END_MESSAGE_MAP()
257
258 /**
259  * @brief MainFrame statusbar panels/indicators
260  */
261 static UINT StatusbarIndicators[] =
262 {
263         ID_SEPARATOR,           // Plugin name
264         ID_SEPARATOR,           // status line indicator
265         ID_SEPARATOR,           // Merge mode
266         ID_SEPARATOR,           // Diff number
267         ID_INDICATOR_CAPS,      // Caps Lock
268         ID_INDICATOR_NUM,       // Num Lock
269         ID_INDICATOR_OVR,       // Insert
270 };
271
272 /** @brief Timer ID for window flashing timer. */
273 static const UINT ID_TIMER_FLASH = 1;
274
275 /** @brief Timeout for window flashing timer, in milliseconds. */
276 static const UINT WINDOW_FLASH_TIMEOUT = 500;
277
278 /**
279   * @brief Return a const reference to a CMultiDocTemplate's list of documents.
280   */
281 static const CPtrList &GetDocList(const CMultiDocTemplate *pTemplate)
282 {
283         struct Template : public CMultiDocTemplate
284         {
285         public:
286                 using CMultiDocTemplate::m_docList;
287         };
288         return static_cast<const struct Template *>(pTemplate)->m_docList;
289 }
290
291 /////////////////////////////////////////////////////////////////////////////
292 // CMainFrame construction/destruction
293
294 /**
295  * @brief MainFrame constructor. Loads settings from registry.
296  * @todo Preference for logging?
297  */
298 CMainFrame::CMainFrame()
299 : m_bFlashing(FALSE)
300 , m_bFirstTime(TRUE)
301 , m_pDropHandler(NULL)
302 {
303         ZeroMemory(&m_pMenus[0], sizeof(m_pMenus));
304 }
305
306 CMainFrame::~CMainFrame()
307 {
308         GetOptionsMgr()->SaveOption(OPT_TABBAR_AUTO_MAXWIDTH, m_wndTabBar.GetAutoMaxWidth());
309         sd_Close();
310 }
311
312 // This is a bridge to implement IStatusDisplay for WaitStatusCursor
313 // by forwarding all calls to the main frame
314 class StatusDisplay : public IStatusDisplay
315 {
316 public:
317         StatusDisplay() : m_pfrm(0) { }
318         void SetFrame(CMainFrame * frm) { m_pfrm = frm; }
319 // Implement IStatusDisplay
320         virtual CString BeginStatus(LPCTSTR str) { return m_pfrm->SetStatus(str); }
321         virtual void ChangeStatus(LPCTSTR str) { m_pfrm->SetStatus(str); }
322         virtual void EndStatus(LPCTSTR str, LPCTSTR oldstr) { m_pfrm->SetStatus(oldstr); }
323
324 protected:
325         CMainFrame * m_pfrm;
326 };
327
328 static StatusDisplay myStatusDisplay;
329
330 #ifdef _UNICODE
331 const TCHAR CMainFrame::szClassName[] = _T("WinMergeWindowClassW");
332 #else
333 const TCHAR CMainFrame::szClassName[] = _T("WinMergeWindowClassA");
334 #endif
335 /**
336  * @brief Change MainFrame window class name
337  *        see http://support.microsoft.com/kb/403825/ja
338  */
339 BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
340 {
341         WNDCLASS wndcls;
342         BOOL bRes = CMDIFrameWnd::PreCreateWindow(cs);
343         HINSTANCE hInst = AfxGetInstanceHandle();
344         // see if the class already exists
345         if (!::GetClassInfo(hInst, szClassName, &wndcls))
346         {
347                 // get default stuff
348                 ::GetClassInfo(hInst, cs.lpszClass, &wndcls);
349                 // register a new class
350                 wndcls.lpszClassName = szClassName;
351                 wndcls.hIcon = ::LoadIcon(hInst, MAKEINTRESOURCE(IDR_MAINFRAME));
352                 ::RegisterClass(&wndcls);
353         }
354         cs.lpszClass = szClassName;
355         return bRes;
356 }
357
358 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
359 {
360         if (CMDIFrameWnd::OnCreate(lpCreateStruct) == -1)
361                 return -1;
362
363         m_lfDiff = Options::Font::Load(OPT_FONT_FILECMP);
364         m_lfDir = Options::Font::Load(OPT_FONT_DIRCMP);
365         
366         if (!CreateToolbar())
367         {
368                 TRACE0("Failed to create toolbar\n");
369                 return -1;      // fail to create
370         }
371         
372         if (!m_wndTabBar.Create(this))
373         {
374                 TRACE0("Failed to create tab bar\n");
375                 return -1;      // fail to create
376         }
377         m_wndTabBar.SetAutoMaxWidth(GetOptionsMgr()->GetBool(OPT_TABBAR_AUTO_MAXWIDTH));
378
379         if (GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR) == false)
380                 CMDIFrameWnd::ShowControlBar(&m_wndTabBar, false, 0);
381
382         if (!m_wndStatusBar.Create(this))
383         {
384                 TRACE0("Failed to create status bar\n");
385                 return -1;      // fail to create
386         }
387         theApp.SetIndicators(m_wndStatusBar, StatusbarIndicators,
388                         countof(StatusbarIndicators));
389
390         m_wndStatusBar.SetPaneInfo(1, ID_STATUS_PLUGIN, 0, 300);
391         m_wndStatusBar.SetPaneInfo(2, ID_STATUS_MERGINGMODE, 0, 100); 
392         m_wndStatusBar.SetPaneInfo(3, ID_STATUS_DIFFNUM, 0, 150); 
393
394         if (GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR) == false)
395                 CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, false, 0);
396
397         // Start handling status messages from CustomStatusCursors
398         myStatusDisplay.SetFrame(this);
399         CustomStatusCursor::SetStatusDisplay(&myStatusDisplay);
400
401         m_pDropHandler = new DropHandler(std::bind(&CMainFrame::OnDropFiles, this, std::placeholders::_1));
402         RegisterDragDrop(m_hWnd, m_pDropHandler);
403
404         return 0;
405 }
406
407 void CMainFrame::OnDestroy(void)
408 {
409         if (m_pDropHandler)
410                 RevokeDragDrop(m_hWnd);
411 }
412
413 static HMENU GetSubmenu(HMENU mainMenu, UINT nIDFirstMenuItem, bool bFirstSubmenu)
414 {
415         int i;
416         for (i = 0 ; i < ::GetMenuItemCount(mainMenu) ; i++)
417                 if (::GetMenuItemID(::GetSubMenu(mainMenu, i), 0) == nIDFirstMenuItem)
418                         break;
419         HMENU menu = ::GetSubMenu(mainMenu, i);
420
421         if (!bFirstSubmenu)
422         {
423                 // look for last submenu
424                 for (i = ::GetMenuItemCount(menu) ; i >= 0  ; i--)
425                         if (::GetSubMenu(menu, i) != NULL)
426                                 return ::GetSubMenu(menu, i);
427         }
428         else
429         {
430                 // look for first submenu
431                 for (i = 0 ; i < ::GetMenuItemCount(menu) ; i++)
432                         if (::GetSubMenu(menu, i) != NULL)
433                                 return ::GetSubMenu(menu, i);
434         }
435
436         // error, submenu not found
437         return NULL;
438 }
439
440 /** 
441  * @brief Find the scripts submenu from the main menu
442  * As now this is the first submenu in "Edit" menu
443  * We find the "Edit" menu by looking for a menu 
444  *  starting with ID_EDIT_UNDO.
445  */
446 HMENU CMainFrame::GetScriptsSubmenu(HMENU mainMenu)
447 {
448         return GetSubmenu(mainMenu, ID_PLUGINS_LIST, false);
449 }
450
451 /**
452  * @brief Find the scripts submenu from the main menu
453  * As now this is the first submenu in "Plugins" menu
454  * We find the "Plugins" menu by looking for a menu 
455  *  starting with ID_UNPACK_MANUAL.
456  */
457 HMENU CMainFrame::GetPrediffersSubmenu(HMENU mainMenu)
458 {
459         return GetSubmenu(mainMenu, ID_PLUGINS_LIST, true);
460 }
461
462 /**
463  * @brief Create a new menu for the view..
464  * @param [in] view Menu view either MENU_DEFAULT, MENU_MERGEVIEW or MENU_DIRVIEW.
465  * @param [in] ID Menu's resource ID.
466  * @return Menu for the view.
467  */
468 HMENU CMainFrame::NewMenu(int view, int ID)
469 {
470         int menu_view, index;
471         if (m_pMenus[view] == NULL)
472         {
473                 m_pMenus[view].reset(new BCMenu());
474                 if (m_pMenus[view] == NULL)
475                         return NULL;
476         }
477
478         switch (view)
479         {
480         case MENU_MERGEVIEW:
481                 menu_view = MENU_FILECMP;
482                 break;
483         case MENU_DIRVIEW:
484                 menu_view = MENU_FOLDERCMP;
485                 break;
486         case MENU_DEFAULT:
487         default:
488                 menu_view = MENU_MAINFRM;
489                 break;
490         };
491
492         if (!m_pMenus[view]->LoadMenu(ID))
493         {
494                 ASSERT(FALSE);
495                 return NULL;
496         }
497
498         if (view == MENU_IMGMERGEVIEW)
499         {
500                 BCMenu *pMenu = new BCMenu;
501                 pMenu->LoadMenu(MAKEINTRESOURCE(IDR_POPUP_IMGMERGEVIEW));
502                 m_pMenus[view]->InsertMenu(4, MF_BYPOSITION | MF_POPUP, (UINT_PTR)pMenu->GetSubMenu(0)->m_hMenu, const_cast<TCHAR *>(LoadResString(IDS_IMAGE_MENU).c_str())); 
503         }
504
505         // Load bitmaps to menuitems
506         for (index = 0; index < countof(m_MenuIcons); index ++)
507         {
508                 if (menu_view == (m_MenuIcons[index].menusToApply & menu_view))
509                 {
510                         m_pMenus[view]->ModifyODMenu(NULL, m_MenuIcons[index].menuitemID, m_MenuIcons[index].iconResID);
511                 }
512         }
513
514         m_pMenus[view]->LoadToolbar(IDR_MAINFRAME);
515
516         theApp.TranslateMenu(m_pMenus[view]->m_hMenu);
517
518         return (m_pMenus[view]->Detach());
519
520 }
521 /** 
522 * @brief Create new default (CMainFrame) menu.
523 */
524 HMENU CMainFrame::NewDefaultMenu(int ID /*=0*/)
525 {
526         if (ID == 0)
527                 ID = IDR_MAINFRAME;
528         return NewMenu( MENU_DEFAULT, ID );
529 }
530
531 /**
532  * @brief Create new File compare (CMergeEditView) menu.
533  */
534 HMENU CMainFrame::NewMergeViewMenu()
535 {
536         return NewMenu( MENU_MERGEVIEW, IDR_MERGEDOCTYPE);
537 }
538
539 /**
540  * @brief Create new Dir compare (CDirView) menu
541  */
542 HMENU CMainFrame::NewDirViewMenu()
543 {
544         return NewMenu(MENU_DIRVIEW, IDR_DIRDOCTYPE );
545 }
546
547 /**
548  * @brief Create new File compare (CHexMergeView) menu.
549  */
550 HMENU CMainFrame::NewHexMergeViewMenu()
551 {
552         return NewMenu( MENU_HEXMERGEVIEW, IDR_MERGEDOCTYPE);
553 }
554
555 /**
556  * @brief Create new Image compare (CImgMergeView) menu.
557  */
558 HMENU CMainFrame::NewImgMergeViewMenu()
559 {
560         return NewMenu( MENU_IMGMERGEVIEW, IDR_MERGEDOCTYPE);
561 }
562
563 /**
564  * @brief Create new File compare (COpenView) menu.
565  */
566 HMENU CMainFrame::NewOpenViewMenu()
567 {
568         return NewMenu( MENU_OPENVIEW, IDR_MAINFRAME);
569 }
570
571 /**
572  * @brief This handler ensures that the popup menu items are drawn correctly.
573  */
574 void CMainFrame::OnMeasureItem(int nIDCtl,
575         LPMEASUREITEMSTRUCT lpMeasureItemStruct)
576 {
577         BOOL setflag = FALSE;
578         if (lpMeasureItemStruct->CtlType == ODT_MENU)
579         {
580                 if (IsMenu((HMENU)lpMeasureItemStruct->itemID))
581                 {
582                         CMenu* cmenu =
583                                 CMenu::FromHandle((HMENU)lpMeasureItemStruct->itemID);
584
585                         if (m_pMenus[MENU_DEFAULT]->IsMenu(cmenu))
586                         {
587                                 m_pMenus[MENU_DEFAULT]->MeasureItem(lpMeasureItemStruct);
588                                 setflag = TRUE;
589                         }
590                 }
591         }
592
593         if (!setflag)
594                 CMDIFrameWnd::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
595 }
596
597 /**
598  * @brief This handler ensures that keyboard shortcuts work.
599  */
600 LRESULT CMainFrame::OnMenuChar(UINT nChar, UINT nFlags, 
601         CMenu* pMenu) 
602 {
603         LRESULT lresult;
604         if(m_pMenus[MENU_DEFAULT]->IsMenu(pMenu))
605                 lresult=BCMenu::FindKeyboardShortcut(nChar, nFlags, pMenu);
606         else
607                 lresult=CMDIFrameWnd::OnMenuChar(nChar, nFlags, pMenu);
608         return(lresult);
609 }
610
611 /**
612  * @brief This handler updates the menus from time to time.
613  */
614 void CMainFrame::OnInitMenuPopup(CMenu* pPopupMenu, UINT nIndex, BOOL bSysMenu) 
615 {
616         CMDIFrameWnd::OnInitMenuPopup(pPopupMenu, nIndex, bSysMenu);
617         
618         if (!bSysMenu)
619         {
620                 if (BCMenu::IsMenu(pPopupMenu))
621                 {
622                         BCMenu::UpdateMenu(pPopupMenu);
623                 }
624         }
625 }
626
627 /////////////////////////////////////////////////////////////////////////////
628 // CMainFrame message handlers
629
630 void CMainFrame::OnFileOpen() 
631 {
632         DoFileOpen();
633 }
634
635 /**
636  * @brief Check for BOM, and also, if bGuessEncoding, try to deduce codepage
637  *
638  * Unpacks info from FileLocation & delegates all work to codepage_detect module
639  */
640 static void
641 FileLocationGuessEncodings(FileLocation & fileloc, int iGuessEncoding)
642 {
643         fileloc.encoding = GuessCodepageEncoding(fileloc.filepath, iGuessEncoding);
644 }
645
646 int CMainFrame::ShowAutoMergeDoc(CDirDoc * pDirDoc,
647         int nFiles, const FileLocation ifileloc[],
648         const DWORD dwFlags[] /*=0*/, const PackingInfo * infoUnpacker /*= NULL*/)
649 {
650         int pane;
651         FileFilterHelper filterImg, filterBin;
652         filterImg.UseMask(true);
653         filterImg.SetMask(GetOptionsMgr()->GetString(OPT_CMP_IMG_FILEPATTERNS));
654         filterBin.UseMask(true);
655         filterBin.SetMask(GetOptionsMgr()->GetString(OPT_CMP_BIN_FILEPATTERNS));
656         for (pane = 0; pane < nFiles; ++pane)
657         {
658                 if (filterImg.includeFile(ifileloc[pane].filepath))
659                         return ShowImgMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, infoUnpacker);
660                 else if (filterBin.includeFile(ifileloc[pane].filepath))
661                 {
662                         bool bRO[3];
663                         for (int pane = 0; pane < nFiles; pane++)
664                                 bRO[pane] = (dwFlags) ? ((dwFlags[pane] & FFILEOPEN_READONLY) > 0) : FALSE;
665                         ShowHexMergeDoc(pDirDoc, 
666                                 (nFiles == 2) ? 
667                                         PathContext(ifileloc[0].filepath, ifileloc[1].filepath) :
668                                         PathContext(ifileloc[0].filepath, ifileloc[1].filepath, ifileloc[2].filepath)
669                                 , bRO);
670                         return 0;
671                 }
672         }
673         return ShowMergeDoc(pDirDoc, nFiles, ifileloc, dwFlags, infoUnpacker);
674 }
675
676 /**
677  * @brief Creates new MergeDoc instance and shows documents.
678  * @param [in] pDirDoc Dir compare document to create a new Merge document for.
679  * @param [in] ifilelocLeft Left side file location info.
680  * @param [in] ifilelocRight Right side file location info.
681  * @param [in] dwLeftFlags Left side flags.
682  * @param [in] dwRightFlags Right side flags.
683  * @param [in] infoUnpacker Plugin info.
684  * @return OPENRESULTS_TYPE for success/failure code.
685  */
686 int CMainFrame::ShowMergeDoc(CDirDoc * pDirDoc,
687         int nFiles, const FileLocation ifileloc[],
688         const DWORD dwFlags[] /*=0*/, const PackingInfo * infoUnpacker /*= NULL*/)
689 {
690         if (!m_pMenus[MENU_MERGEVIEW])
691                 theApp.m_pDiffTemplate->m_hMenuShared = NewMergeViewMenu();
692         CMergeDoc * pMergeDoc = GetMergeDocForDiff<CMergeDoc>(theApp.m_pDiffTemplate, pDirDoc, nFiles);
693
694         // Make local copies, so we can change encoding if we guess it below
695         FileLocation fileloc[3];
696         bool bRO[3];
697         int pane;
698         for (pane = 0; pane < nFiles; pane++)
699         {
700                 fileloc[pane] = ifileloc[pane];
701                 if (dwFlags)
702                         bRO[pane] = (dwFlags[pane] & FFILEOPEN_READONLY) > 0;
703                 else
704                         bRO[pane] = FALSE;
705         }
706
707         ASSERT(pMergeDoc);              // must ASSERT to get an answer to the question below ;-)
708         if (!pMergeDoc)
709                 return OPENRESULTS_FAILED_MISC; // when does this happen ?
710
711         // if an unpacker is selected, it must be used during LoadFromFile
712         // MergeDoc must memorize it for SaveToFile
713         // Warning : this unpacker may differ from the pDirDoc one
714         // (through menu : "Plugins"->"Open with unpacker")
715         pMergeDoc->SetUnpacker(infoUnpacker);
716
717         // detect codepage
718         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
719         for (pane = 0; pane < nFiles; pane++)
720         {
721                 if (fileloc[pane].encoding.m_unicoding == -1)
722                         fileloc[pane].encoding.m_unicoding = ucr::NONE;
723                 if (fileloc[pane].encoding.m_unicoding == ucr::NONE && fileloc[pane].encoding.m_codepage == -1)
724                 {
725                         FileLocationGuessEncodings(fileloc[pane], iGuessEncodingType);
726                 }
727
728                 // TODO (Perry, 2005-12-04)
729                 // Should we do any unification if unicodings are different?
730
731
732 #ifndef _UNICODE
733                 // In ANSI (8-bit) build, character loss can occur in merging
734                 // if the two buffers use different encodings
735                 if (pane > 0 && fileloc[pane - 1].encoding.m_codepage != fileloc[pane].encoding.m_codepage)
736                 {
737                         CString msg;
738                         msg.Format(theApp.LoadString(IDS_SUGGEST_IGNORECODEPAGE).c_str(), fileloc[pane - 1].encoding.m_codepage,fileloc[pane].encoding.m_codepage);
739                         int msgflags = MB_YESNO | MB_ICONQUESTION | MB_DONT_ASK_AGAIN;
740                         // Two files with different codepages
741                         // Warn and propose to use the default codepage for both
742                         int userChoice = AfxMessageBox(msg, msgflags);
743                         if (userChoice == IDYES)
744                         {
745                                 fileloc[pane - 1].encoding.SetCodepage(ucr::getDefaultCodepage());
746                                 fileloc[pane - 1].encoding.m_bom = false;
747                                 fileloc[pane].encoding.SetCodepage(ucr::getDefaultCodepage());
748                                 fileloc[pane].encoding.m_bom = false;
749                         }
750                 }
751 #endif
752         }
753
754         int nActivePane = -1;
755         for (pane = 0; pane < nFiles; pane++)
756         {
757                 if (dwFlags && (dwFlags[pane] & FFILEOPEN_SETFOCUS))
758                         nActivePane = pane;
759         }
760
761         // Note that OpenDocs() takes care of closing compare window when needed.
762         OPENRESULTS_TYPE openResults = pMergeDoc->OpenDocs(fileloc, bRO, nActivePane);
763
764         if (openResults == OPENRESULTS_SUCCESS)
765         {
766                 for (pane = 0; pane < nFiles; pane++)
767                 {
768                         if (dwFlags)
769                         {
770                                 BOOL bModified = (dwFlags[pane] & FFILEOPEN_MODIFIED) > 0;
771                                 if (bModified)
772                                 {
773                                         pMergeDoc->m_ptBuf[pane]->SetModified(TRUE);
774                                         pMergeDoc->UpdateHeaderPath(pane);
775                                 }
776                                 if (dwFlags[pane] & FFILEOPEN_AUTOMERGE)
777                                 {
778                                         pMergeDoc->DoAutoMerge(pane);
779                                 }
780                         }
781                 }
782         }
783         return openResults;
784 }
785
786 void CMainFrame::ShowHexMergeDoc(CDirDoc * pDirDoc, 
787         const PathContext &paths, const bool bRO[])
788 {
789         if (!m_pMenus[MENU_HEXMERGEVIEW])
790                 theApp.m_pHexMergeTemplate->m_hMenuShared = NewHexMergeViewMenu();
791         if (CHexMergeDoc *pHexMergeDoc = GetMergeDocForDiff<CHexMergeDoc>(theApp.m_pHexMergeTemplate, pDirDoc, paths.GetSize()))
792                 pHexMergeDoc->OpenDocs(paths, bRO);
793 }
794
795 int CMainFrame::ShowImgMergeDoc(CDirDoc * pDirDoc, int nFiles, const FileLocation fileloc[],
796                 const DWORD dwFlags[], const PackingInfo * infoUnpacker/* = NULL*/)
797 {
798         CImgMergeFrame *pImgMergeFrame = new CImgMergeFrame();
799         if (!CImgMergeFrame::menu.m_hMenu)
800                 CImgMergeFrame::menu.m_hMenu = NewImgMergeViewMenu();
801         pImgMergeFrame->SetSharedMenu(CImgMergeFrame::menu.m_hMenu);
802         PathContext files;
803         int pane;
804         for (pane = 0; pane < nFiles; ++pane)
805                 files.SetPath(pane, fileloc[pane].filepath, false);
806
807         bool bRO[3];
808         for (pane = 0; pane < nFiles; pane++)
809         {
810                 if (dwFlags)
811                         bRO[pane] = (dwFlags[pane] & FFILEOPEN_READONLY) > 0;
812                 else
813                         bRO[pane] = FALSE;
814         }
815
816         int nActivePane = -1;
817         for (pane = 0; pane < nFiles; pane++)
818         {
819                 if (dwFlags && (dwFlags[pane] & FFILEOPEN_SETFOCUS))
820                         nActivePane = pane;
821         }
822
823         pImgMergeFrame->SetDirDoc(pDirDoc);
824         pDirDoc->AddMergeDoc(pImgMergeFrame);
825
826         if (!pImgMergeFrame->OpenImages(files, bRO, nActivePane, this))
827         {
828                 ShowMergeDoc(pDirDoc, nFiles, fileloc, dwFlags, infoUnpacker);
829         }
830         else
831         {
832                 for (pane = 0; pane < nFiles; pane++)
833                 {
834                         if (dwFlags[pane] & FFILEOPEN_AUTOMERGE)
835                                 pImgMergeFrame->DoAutoMerge(pane);
836                 }
837         }
838
839         return 0;
840 }
841
842 void CMainFrame::RedisplayAllDirDocs()
843 {
844         const DirDocList &dirdocs = GetAllDirDocs();
845         POSITION pos = dirdocs.GetHeadPosition();
846         while (pos)
847         {
848                 CDirDoc * pDirDoc = dirdocs.GetNext(pos);
849                 pDirDoc->Redisplay();
850         }
851 }
852
853 /**
854  * @brief Show/Hide different files/directories
855  */
856 void CMainFrame::OnOptionsShowDifferent() 
857 {
858         bool val = GetOptionsMgr()->GetBool(OPT_SHOW_DIFFERENT);
859         GetOptionsMgr()->SaveOption(OPT_SHOW_DIFFERENT, !val); // reverse
860         RedisplayAllDirDocs();
861 }
862
863 /**
864  * @brief Show/Hide identical files/directories
865  */
866 void CMainFrame::OnOptionsShowIdentical() 
867 {
868         bool val = GetOptionsMgr()->GetBool(OPT_SHOW_IDENTICAL);
869         GetOptionsMgr()->SaveOption(OPT_SHOW_IDENTICAL, !val); // reverse
870         RedisplayAllDirDocs();
871 }
872
873 /**
874  * @brief Show/Hide left-only files/directories
875  */
876 void CMainFrame::OnOptionsShowUniqueLeft() 
877 {
878         bool val = GetOptionsMgr()->GetBool(OPT_SHOW_UNIQUE_LEFT);
879         GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_LEFT, !val); // reverse
880         RedisplayAllDirDocs();
881 }
882
883 /**
884  * @brief Show/Hide right-only files/directories
885  */
886 void CMainFrame::OnOptionsShowUniqueRight() 
887 {
888         bool val = GetOptionsMgr()->GetBool(OPT_SHOW_UNIQUE_RIGHT);
889         GetOptionsMgr()->SaveOption(OPT_SHOW_UNIQUE_RIGHT, !val); // reverse
890         RedisplayAllDirDocs();
891 }
892
893 /**
894  * @brief Show/Hide binary files
895  */
896 void CMainFrame::OnOptionsShowBinaries()
897 {
898         bool val = GetOptionsMgr()->GetBool(OPT_SHOW_BINARIES);
899         GetOptionsMgr()->SaveOption(OPT_SHOW_BINARIES, !val); // reverse
900         RedisplayAllDirDocs();
901 }
902
903 /**
904  * @brief Show/Hide skipped files/directories
905  */
906 void CMainFrame::OnOptionsShowSkipped()
907 {
908         bool val = GetOptionsMgr()->GetBool(OPT_SHOW_SKIPPED);
909         GetOptionsMgr()->SaveOption(OPT_SHOW_SKIPPED, !val); // reverse
910         RedisplayAllDirDocs();
911 }
912
913 void CMainFrame::OnUpdateOptionsShowdifferent(CCmdUI* pCmdUI) 
914 {
915         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_DIFFERENT));
916 }
917
918 void CMainFrame::OnUpdateOptionsShowidentical(CCmdUI* pCmdUI) 
919 {
920         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_IDENTICAL));
921 }
922
923 void CMainFrame::OnUpdateOptionsShowuniqueleft(CCmdUI* pCmdUI) 
924 {
925         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_UNIQUE_LEFT));
926 }
927
928 void CMainFrame::OnUpdateOptionsShowuniqueright(CCmdUI* pCmdUI) 
929 {
930         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_UNIQUE_RIGHT));
931 }
932
933 void CMainFrame::OnUpdateOptionsShowBinaries(CCmdUI* pCmdUI) 
934 {
935         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_BINARIES));
936 }
937
938 void CMainFrame::OnUpdateOptionsShowSkipped(CCmdUI* pCmdUI)
939 {
940         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_SKIPPED));
941 }
942
943 /**
944  * @brief Show GNU licence information in notepad (local file) or in Web Browser
945  */
946 void CMainFrame::OnHelpGnulicense() 
947 {
948         const String spath = paths_ConcatPath(env_GetProgPath(), LicenseFile);
949         theApp.OpenFileOrUrl(spath.c_str(), LicenceUrl);
950 }
951
952 /// Wrapper to set the global option 'm_bAllowMixedEol'
953 void CMainFrame::SetEOLMixed(BOOL bAllow)
954 {
955         GetOptionsMgr()->SaveOption(OPT_ALLOW_MIXED_EOL, bAllow == TRUE);
956         ApplyViewWhitespace();
957 }
958
959 /**
960  * @brief Opens Options-dialog and saves changed options
961  */
962 void CMainFrame::OnOptions() 
963 {
964         // Using singleton shared syntax colors
965         CPreferencesDlg dlg(GetOptionsMgr(), theApp.GetMainSyntaxColors());
966         INT_PTR rv = dlg.DoModal();
967
968         if (rv == IDOK)
969         {
970                 LANGID lang = GetOptionsMgr()->GetInt(OPT_SELECTED_LANGUAGE);
971                 if (lang != theApp.m_pLangDlg->GetLangId())
972                 {
973                         theApp.m_pLangDlg->SetLanguage(lang, TRUE);
974         
975                         // Update status bar inicator texts
976                         theApp.SetIndicators(m_wndStatusBar, 0, 0);
977         
978                         // Update the current menu
979                         ReloadMenu();
980         
981                         // update the title text of the document
982                         UpdateDocTitle();
983
984                         UpdateResources();
985                 }
986
987                 // Set new filterpath
988                 String filterPath = GetOptionsMgr()->GetString(OPT_FILTER_USERPATH);
989                 theApp.m_pGlobalFileFilter->SetUserFilterPath(filterPath);
990
991                 theApp.UpdateCodepageModule();
992                 // Call the wrapper to set m_bAllowMixedEol (the wrapper updates the registry)
993                 SetEOLMixed(GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL));
994
995                 sd_SetBreakChars(GetOptionsMgr()->GetString(OPT_BREAK_SEPARATORS).c_str());
996
997                 // make an attempt at rescanning any open diff sessions
998                 ApplyDiffOptions();
999
1000                 // Update all dirdoc settings
1001                 const DirDocList &dirDocs = GetAllDirDocs();
1002                 POSITION pos = dirDocs.GetHeadPosition();
1003                 while (pos)
1004                 {
1005                         CDirDoc * pDirDoc = dirDocs.GetNext(pos);
1006                         pDirDoc->RefreshOptions();
1007                 }
1008
1009                 const HexMergeDocList &hexdocs = GetAllHexMergeDocs();
1010                 pos = hexdocs.GetHeadPosition();
1011                 while (pos)
1012                 {
1013                         CHexMergeDoc * pMergeDoc = hexdocs.GetNext(pos);
1014                         pMergeDoc->RefreshOptions();
1015                 }
1016         }
1017 }
1018
1019 static bool AddToRecentDocs(const PathContext& paths, const unsigned flags[], bool recurse, const String& filter)
1020 {
1021         String params, title;
1022         for (int nIndex = 0; nIndex < paths.GetSize(); ++nIndex)
1023         {
1024                 if (flags[nIndex] & FFILEOPEN_READONLY)
1025                 {
1026                         switch (nIndex)
1027                         {
1028                         case 0: params += _T("/wl "); break;
1029                         case 1: params += paths.GetSize() == 2 ? _T("/wr ") : _T("/wm "); break;
1030                         case 2: params += _T("/wr "); break;
1031                         }
1032                 }
1033                 params += _T("\"") + paths[nIndex] + _T("\" ");
1034
1035                 String path = paths[nIndex];
1036                 paths_normalize(path);
1037                 title += paths_FindFileName(path);
1038                 if (nIndex < paths.GetSize() - 1)
1039                         title += _T(" - ");
1040         }
1041         if (recurse)
1042                 params += _T("/r ");
1043         if (!filter.empty())
1044                 params += _T("/f \"") + filter + _T("\" ");
1045         return JumpList::AddToRecentDocs(_T(""), params, title, params, 0);
1046 }
1047
1048 /**
1049  * @brief Begin a diff: open dirdoc if it is directories, else open a mergedoc for editing.
1050  * @param [in] pszLeft Left-side path.
1051  * @param [in] pszRight Right-side path.
1052  * @param [in] dwLeftFlags Left-side flags.
1053  * @param [in] dwRightFlags Right-side flags.
1054  * @param [in] bRecurse Do we run recursive (folder) compare?
1055  * @param [in] pDirDoc Dir compare document to use.
1056  * @param [in] prediffer Prediffer plugin name.
1057  * @return TRUE if opening files and compare succeeded, FALSE otherwise.
1058  */
1059 BOOL CMainFrame::DoFileOpen(const PathContext * pFiles /*=NULL*/,
1060         const DWORD dwFlags[] /*=0*/, bool bRecurse /*=FALSE*/, CDirDoc *pDirDoc/*=NULL*/,
1061         String prediffer /*=_T("")*/, const PackingInfo *infoUnpacker/*=NULL*/)
1062 {
1063         if (pDirDoc && !pDirDoc->CloseMergeDocs())
1064                 return FALSE;
1065
1066         g_bUnpackerMode = theApp.GetProfileInt(_T("Settings"), _T("UnpackerMode"), PLUGIN_MANUAL);
1067         g_bPredifferMode = theApp.GetProfileInt(_T("Settings"), _T("PredifferMode"), PLUGIN_MANUAL);
1068
1069         Merge7zFormatMergePluginScope scope(infoUnpacker);
1070
1071         PathContext files;
1072         if (pFiles)
1073                 files = *pFiles;
1074         bool bRO[3] = {0};
1075         if (dwFlags)
1076         {
1077                 bRO[0] = (dwFlags[0] & FFILEOPEN_READONLY) != 0;
1078                 bRO[1] = (dwFlags[1] & FFILEOPEN_READONLY) != 0;
1079                 bRO[2] = (dwFlags[2] & FFILEOPEN_READONLY) != 0;
1080         };
1081
1082         // pop up dialog unless arguments exist (and are compatible)
1083         PATH_EXISTENCE pathsType = GetPairComparability(files, IsArchiveFile);
1084         if (pathsType == DOES_NOT_EXIST)
1085         {
1086                 if (!m_pMenus[MENU_OPENVIEW])
1087                         theApp.m_pOpenTemplate->m_hMenuShared = NewOpenViewMenu();
1088                 COpenDoc *pOpenDoc = (COpenDoc *)theApp.m_pOpenTemplate->CreateNewDocument();
1089                 if (dwFlags)
1090                 {
1091                         pOpenDoc->m_dwFlags[0] = dwFlags[0];
1092                         pOpenDoc->m_dwFlags[1] = dwFlags[1];
1093                         pOpenDoc->m_dwFlags[2] = dwFlags[2];
1094                 }
1095                 pOpenDoc->m_files = files;
1096                 if (infoUnpacker)
1097                         pOpenDoc->m_infoHandler = *infoUnpacker;
1098                 CFrameWnd *pFrame = theApp.m_pOpenTemplate->CreateNewFrame(pOpenDoc, NULL);
1099                 theApp.m_pOpenTemplate->InitialUpdateFrame(pFrame, pOpenDoc);
1100                 return TRUE;
1101         }
1102         else
1103         {
1104                 // Add trailing '\' for directories if its missing
1105                 if (pathsType == IS_EXISTING_DIR)
1106                 {
1107                         if (!paths_EndsWithSlash(files[0]) && !IsArchiveFile(files[0]))
1108                                 files[0] = paths_AddTrailingSlash(files[0]);
1109                         if (!paths_EndsWithSlash(files[1]) && !IsArchiveFile(files[1]))
1110                                 files[1] = paths_AddTrailingSlash(files[1]);
1111                         if (files.GetSize() == 3 && !paths_EndsWithSlash(files[2]) && !IsArchiveFile(files[1]))
1112                                 files[2] = paths_AddTrailingSlash(files[2]);
1113                 }
1114
1115                 //save the MRU left and right files.
1116                 if (dwFlags)
1117                 {
1118                         if (!(dwFlags[0] & FFILEOPEN_NOMRU))
1119                                 addToMru(files[0].c_str(), _T("Files\\Left"));
1120                         if (!(dwFlags[1] & FFILEOPEN_NOMRU))
1121                                 addToMru(files[1].c_str(), _T("Files\\Right"));
1122                         if (files.GetSize() == 3 && !(dwFlags[2] & FFILEOPEN_NOMRU))
1123                                 addToMru(files[2].c_str(), _T("Files\\Option"));
1124                 }
1125         }
1126
1127         DecompressResult res = DecompressArchive(m_hWnd, files);
1128         if (res.pTempPathContext)
1129         {
1130                 pathsType = res.pathsType;
1131                 files = res.files;
1132         }
1133
1134         // Determine if we want new a dirview open now that we know if it was
1135         // and archive. Don't open new dirview if we are comparing files.
1136         if (!pDirDoc)
1137         {
1138                 if (pathsType == IS_EXISTING_DIR)
1139                 {
1140                         CDirDoc::m_nDirsTemp = files.GetSize();
1141                         if (!m_pMenus[MENU_DIRVIEW])
1142                                 theApp.m_pDirTemplate->m_hMenuShared = NewDirViewMenu();
1143                         pDirDoc = (CDirDoc*)theApp.m_pDirTemplate->OpenDocumentFile(NULL);
1144                 }
1145                 else
1146                 {
1147                         pDirDoc = (CDirDoc*)theApp.m_pDirTemplate->CreateNewDocument();
1148                 }
1149         }
1150
1151         // open the diff
1152         if (pathsType == IS_EXISTING_DIR)
1153         {
1154                 if (pDirDoc)
1155                 {
1156                         if (files.GetSize() == 3)
1157                         {
1158                                 AfxMessageBox(_T("3-way folder compare feature is in progress"), MB_ICONWARNING | MB_DONT_ASK_AGAIN);
1159                         }
1160                         // Anything that can go wrong inside InitCompare() will yield an
1161                         // exception. There is no point in checking return value.
1162                         pDirDoc->InitCompare(files, bRecurse, res.pTempPathContext);
1163
1164                         pDirDoc->SetDescriptions(theApp.m_strDescriptions);
1165                         pDirDoc->SetTitle(NULL);
1166                         for (int nIndex = 0; nIndex < files.GetSize(); nIndex++)
1167                         {
1168                                 pDirDoc->SetReadOnly(nIndex, bRO[nIndex]);
1169                                 theApp.m_strDescriptions[nIndex].erase();
1170                         }
1171
1172                         pDirDoc->Rescan();
1173                 }
1174         }
1175         else
1176         {               
1177                 FileLocation fileloc[3];
1178
1179                 for (int nPane = 0; nPane < files.GetSize(); nPane++)
1180                         fileloc[nPane].setPath(files[nPane]);
1181
1182                 if (!prediffer.empty())
1183                 {
1184                         String strBothFilenames = string_join(files.begin(), files.end(), _T("|"));
1185                         pDirDoc->GetPluginManager().SetPrediffer(strBothFilenames, prediffer);
1186                 }
1187
1188                 ShowAutoMergeDoc(pDirDoc, files.GetSize(), fileloc, dwFlags,
1189                                 infoUnpacker);
1190         }
1191
1192         if (pFiles && !(dwFlags[0] & FFILEOPEN_NOMRU))
1193         {
1194                 String filter = GetOptionsMgr()->GetString(OPT_FILEFILTER_CURRENT);
1195                 AddToRecentDocs(*pFiles, (unsigned *)dwFlags, bRecurse, filter);
1196         }
1197
1198         return TRUE;
1199 }
1200
1201 void CMainFrame::UpdateFont(FRAMETYPE frame)
1202 {
1203         if (frame == FRAME_FOLDER)
1204         {
1205                 const DirDocList &dirdocs = GetAllDirDocs();
1206                 POSITION pos = dirdocs.GetHeadPosition();
1207                 while (pos)
1208                 {
1209                         CDirDoc * pDoc = dirdocs.GetNext(pos);
1210                         if (pDoc)
1211                                 pDoc->GetMainView()->SetFont(m_lfDir);
1212                 }
1213         }
1214         else
1215         {
1216                 const MergeDocList &mergedocs = GetAllMergeDocs();
1217                 POSITION pos = mergedocs.GetHeadPosition();
1218                 while (pos)
1219                 {
1220                         CMergeDoc * pDoc = mergedocs.GetNext(pos);
1221                         for (int pane = 0; pane < pDoc->m_nBuffers; pane++)
1222                         {
1223                                 CMergeEditView * pView = pDoc->GetView(pane);
1224                                 CMergeEditView * pDetailView = pDoc->GetDetailView(pane);
1225                                 if (pView)
1226                                         pView->SetFont(m_lfDiff);
1227                                 if (pDetailView)
1228                                         pDetailView->SetFont(m_lfDiff);
1229                         }
1230                 }
1231         }
1232 }
1233
1234 /**
1235  * @brief Select font for Merge/Dir view
1236  * 
1237  * Shows font selection dialog to user, sets current font and saves
1238  * selected font properties to registry. Selects fon type to active
1239  * view (Merge/Dir compare). If there is no open views, then font
1240  * is selected for Merge view (for example user may want to change to
1241  * unicode font before comparing files).
1242  */
1243 void CMainFrame::OnViewSelectfont() 
1244 {
1245         FRAMETYPE frame = GetFrameType(GetActiveFrame());
1246         CHOOSEFONT cf;
1247         LOGFONT *lf = NULL;
1248         ZeroMemory(&cf, sizeof(CHOOSEFONT));
1249         cf.lStructSize = sizeof(CHOOSEFONT);
1250         cf.Flags = CF_INITTOLOGFONTSTRUCT|CF_FORCEFONTEXIST|CF_SCREENFONTS;
1251         if (frame == FRAME_FILE)
1252                 cf.Flags |= CF_FIXEDPITCHONLY; // Only fixed-width fonts for merge view
1253
1254         // CF_FIXEDPITCHONLY = 0x00004000L
1255         // in case you are a developer and want to disable it to test with, eg, a Chinese capable font
1256         if (frame == FRAME_FOLDER)
1257                 lf = &m_lfDir;
1258         else
1259                 lf = &m_lfDiff;
1260
1261         cf.lpLogFont = lf;
1262
1263         if (ChooseFont(&cf))
1264         {
1265                 Options::Font::Save(frame == FRAME_FOLDER ? OPT_FONT_DIRCMP : OPT_FONT_FILECMP, lf, true);
1266                 UpdateFont(frame);
1267         }
1268 }
1269
1270 /**
1271  * @brief Enable 'Select font'.
1272  */
1273 void CMainFrame::OnUpdateViewSelectfont(CCmdUI* pCmdUI) 
1274 {
1275         pCmdUI->Enable(TRUE);
1276 }
1277
1278 /**
1279  * @brief Use default font for active view type
1280  *
1281  * Disable user-selected font for active view type (Merge/Dir compare).
1282  * If there is no open views, then Merge view font is changed.
1283  */
1284 void CMainFrame::OnViewUsedefaultfont() 
1285 {
1286         FRAMETYPE frame = GetFrameType(GetActiveFrame());
1287
1288         if (frame == FRAME_FOLDER)
1289         {
1290                 Options::Font::Reset(OPT_FONT_DIRCMP);
1291                 m_lfDir = Options::Font::Load(OPT_FONT_DIRCMP);
1292                 Options::Font::Save(OPT_FONT_DIRCMP, &m_lfDir, false);
1293         }
1294         else
1295         {
1296                 Options::Font::Reset(OPT_FONT_FILECMP);
1297                 m_lfDiff = Options::Font::Load(OPT_FONT_FILECMP);
1298                 Options::Font::Save(OPT_FONT_FILECMP, &m_lfDiff, false);
1299         }
1300
1301         UpdateFont(frame);
1302 }
1303
1304 /**
1305  * @brief Enable 'Use Default font'.
1306  */
1307 void CMainFrame::OnUpdateViewUsedefaultfont(CCmdUI* pCmdUI) 
1308 {
1309         pCmdUI->Enable(TRUE);
1310 }
1311
1312 /**
1313  * @brief Update any resources necessary after a GUI language change
1314  */
1315 void CMainFrame::UpdateResources()
1316 {
1317         m_wndStatusBar.SetPaneText(0, theApp.LoadString(AFX_IDS_IDLEMESSAGE).c_str());
1318
1319         const DirDocList &dirdocs = GetAllDirDocs();
1320         POSITION pos = dirdocs.GetHeadPosition();
1321         while (pos)
1322         {
1323                 CDirDoc * pDoc = dirdocs.GetNext(pos);
1324                 pDoc->UpdateResources();
1325         }
1326
1327         const MergeDocList &mergedocs = GetAllMergeDocs();
1328         pos = mergedocs.GetHeadPosition();
1329         while (pos)
1330         {
1331                 CMergeDoc * pDoc = mergedocs.GetNext(pos);
1332                 pDoc->UpdateResources();
1333         }
1334 }
1335
1336 /**
1337  * @brief Open WinMerge help.
1338  *
1339  * If local HTMLhelp file is found, open it, otherwise open HTML page from web.
1340  */
1341 void CMainFrame::OnHelpContents()
1342 {
1343         theApp.ShowHelp();
1344 }
1345
1346 /**
1347  * @brief Enable Open WinMerge help -menuitem.
1348  */
1349 void CMainFrame::OnUpdateHelpContents(CCmdUI* pCmdUI) 
1350 {
1351         pCmdUI->Enable(TRUE);
1352 }
1353
1354 /**
1355  * @brief Handle translation of default messages on the status bar
1356  */
1357 void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
1358 {
1359         // load appropriate string
1360         const String s = theApp.LoadString(nID);
1361         if (s.length() > 0)
1362                 AfxExtractSubString(rMessage, s.c_str(), 0);
1363 }
1364
1365 void CMainFrame::ActivateFrame(int nCmdShow) 
1366 {
1367         if (!m_bFirstTime)
1368         {
1369                 CMDIFrameWnd::ActivateFrame(nCmdShow);
1370                 return;
1371         }
1372
1373         m_bFirstTime = FALSE;
1374
1375         WINDOWPLACEMENT wp;
1376         wp.length = sizeof(WINDOWPLACEMENT);
1377         GetWindowPlacement(&wp);
1378         wp.rcNormalPosition.left=theApp.GetProfileInt(_T("Settings"), _T("MainLeft"),0);
1379         wp.rcNormalPosition.top=theApp.GetProfileInt(_T("Settings"), _T("MainTop"),0);
1380         wp.rcNormalPosition.right=theApp.GetProfileInt(_T("Settings"), _T("MainRight"),0);
1381         wp.rcNormalPosition.bottom=theApp.GetProfileInt(_T("Settings"), _T("MainBottom"),0);
1382         if (nCmdShow != SW_MINIMIZE && theApp.GetProfileInt(_T("Settings"), _T("MainMax"), FALSE))
1383                 wp.showCmd = SW_MAXIMIZE;
1384         else
1385                 wp.showCmd = nCmdShow;
1386
1387         CRect dsk_rc,rc(wp.rcNormalPosition);
1388
1389         dsk_rc.left = ::GetSystemMetrics(SM_XVIRTUALSCREEN);
1390         dsk_rc.top = ::GetSystemMetrics(SM_YVIRTUALSCREEN);
1391         dsk_rc.right = dsk_rc.left + ::GetSystemMetrics(SM_CXVIRTUALSCREEN);
1392         dsk_rc.bottom = dsk_rc.top + ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
1393         if (rc.Width() != 0 && rc.Height() != 0)
1394         {
1395                 // Ensure top-left corner is on visible area,
1396                 // 20 points margin is added to prevent "lost" window
1397                 CPoint ptTopLeft(rc.TopLeft());
1398                 ptTopLeft += CPoint(20, 20);
1399
1400                 if (dsk_rc.PtInRect(ptTopLeft))
1401                         SetWindowPlacement(&wp);
1402                 else
1403                         CMDIFrameWnd::ActivateFrame(nCmdShow);
1404         }
1405         else
1406                 CMDIFrameWnd::ActivateFrame(nCmdShow);
1407 }
1408
1409 /**
1410  * @brief Called when mainframe is about to be closed.
1411  * This function is called when mainframe is to be closed (not for
1412  * file/compare windows.
1413  */
1414 void CMainFrame::OnClose()
1415 {
1416         if (theApp.GetActiveOperations())
1417                 return;
1418
1419         // Check if there are multiple windows open and ask for closing them
1420         BOOL bAskClosing = GetOptionsMgr()->GetBool(OPT_ASK_MULTIWINDOW_CLOSE);
1421         if (bAskClosing)
1422         {
1423                 bool quit = AskCloseConfirmation();
1424                 if (!quit)
1425                         return;
1426         }
1427
1428         // Save last selected filter
1429         String filter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1430         GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, filter);
1431
1432         // save main window position
1433         WINDOWPLACEMENT wp;
1434         wp.length = sizeof(WINDOWPLACEMENT);
1435         GetWindowPlacement(&wp);
1436         theApp.WriteProfileInt(_T("Settings"), _T("MainLeft"),wp.rcNormalPosition.left);
1437         theApp.WriteProfileInt(_T("Settings"), _T("MainTop"),wp.rcNormalPosition.top);
1438         theApp.WriteProfileInt(_T("Settings"), _T("MainRight"),wp.rcNormalPosition.right);
1439         theApp.WriteProfileInt(_T("Settings"), _T("MainBottom"),wp.rcNormalPosition.bottom);
1440         theApp.WriteProfileInt(_T("Settings"), _T("MainMax"), (wp.showCmd == SW_MAXIMIZE));
1441
1442         // tell all merge docs to save position
1443         // don't call SavePosition, it is called when the child frame is destroyed
1444         /*
1445         while (!mergedocs.IsEmpty())
1446         {
1447                 CMergeDoc * pMergeDoc = mergedocs.RemoveHead();
1448                 CMergeEditView * pLeft = pMergeDoc->GetLeftView();
1449                 if (pLeft)
1450                         pMergeDoc->GetParentFrame()->SavePosition();
1451         }
1452         */
1453         
1454         // Stop handling status messages from CustomStatusCursors
1455         CustomStatusCursor::SetStatusDisplay(0);
1456         myStatusDisplay.SetFrame(0);
1457         
1458         // Close Non-Document/View frame with confirmation
1459         CMDIChildWnd *pChild = static_cast<CMDIChildWnd *>(CWnd::FromHandle(m_hWndMDIClient)->GetWindow(GW_CHILD));
1460         while (pChild)
1461         {
1462                 CMDIChildWnd *pNextChild = static_cast<CMDIChildWnd *>(pChild->GetWindow(GW_HWNDNEXT));
1463                 if (GetFrameType(pChild) == FRAME_IMGFILE)
1464                 {
1465                         if (!static_cast<CImgMergeFrame *>(pChild)->CloseNow())
1466                                 return;
1467                 }
1468                 pChild = pNextChild;
1469         }
1470
1471         CMDIFrameWnd::OnClose();
1472 }
1473
1474 /**
1475  * @brief Utility function to update CSuperComboBox format MRU
1476  */
1477 void CMainFrame::addToMru(LPCTSTR szItem, LPCTSTR szRegSubKey, UINT nMaxItems)
1478 {
1479         std::vector<CString> list;
1480         CString s;
1481         UINT cnt = AfxGetApp()->GetProfileInt(szRegSubKey, _T("Count"), 0);
1482         list.push_back(szItem);
1483         for (UINT i=0 ; i<cnt; ++i)
1484         {
1485                 s = AfxGetApp()->GetProfileString(szRegSubKey, string_format(_T("Item_%d"), i).c_str());
1486                 if (s != szItem)
1487                         list.push_back(s);
1488         }
1489         cnt = list.size() > nMaxItems ? nMaxItems : list.size();
1490         for (UINT i=0 ; i<cnt; ++i)
1491                 AfxGetApp()->WriteProfileString(szRegSubKey, string_format(_T("Item_%d"), i).c_str(), list[i]);
1492         // update count
1493         AfxGetApp()->WriteProfileInt(szRegSubKey, _T("Count"), cnt);
1494 }
1495
1496 void CMainFrame::ApplyDiffOptions() 
1497 {
1498         const MergeDocList &docs = GetAllMergeDocs();
1499         POSITION pos = docs.GetHeadPosition();
1500         while (pos)
1501         {
1502                 CMergeDoc * pMergeDoc = docs.GetNext(pos);
1503                 // Re-read MergeDoc settings (also updates view settings)
1504                 // and rescan using new options
1505                 pMergeDoc->RefreshOptions();
1506                 pMergeDoc->FlushAndRescan(TRUE);
1507         }
1508 }
1509
1510 /**
1511  * @brief Apply tabs and eols settings to all merge documents
1512  */
1513 void CMainFrame::ApplyViewWhitespace() 
1514 {
1515         const MergeDocList &mergedocs = GetAllMergeDocs();
1516         POSITION pos = mergedocs.GetHeadPosition();
1517         while (pos)
1518         {
1519                 CMergeDoc *pMergeDoc = mergedocs.GetNext(pos);
1520                 for (int pane = 0; pane < pMergeDoc->m_nBuffers; pane++)
1521                 {
1522                         CMergeEditView * pView = pMergeDoc->GetView(pane);
1523                         CMergeEditView * pDetailView = pMergeDoc->GetDetailView(pane);
1524                         if (pView)
1525                         {
1526                                 pView->SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
1527                                 pView->SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
1528                                         GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
1529                                         pView->GetDocument()->IsMixedEOL(pView->m_nThisPane));
1530                         }
1531                         if (pDetailView)
1532                         {
1533                                 pDetailView->SetViewTabs(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
1534                                 pDetailView->SetViewEols(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE),
1535                                         GetOptionsMgr()->GetBool(OPT_ALLOW_MIXED_EOL) ||
1536                                         pDetailView->GetDocument()->IsMixedEOL(pDetailView->m_nThisPane));
1537                         }
1538                 }
1539         }
1540 }
1541
1542 void CMainFrame::OnViewWhitespace() 
1543 {
1544         bool bViewWhitespace = GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE);
1545         GetOptionsMgr()->SaveOption(OPT_VIEW_WHITESPACE, !bViewWhitespace);
1546         ApplyViewWhitespace();
1547 }
1548
1549 /// Enables View/View Whitespace menuitem when merge view is active
1550 void CMainFrame::OnUpdateViewWhitespace(CCmdUI* pCmdUI) 
1551 {
1552         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_VIEW_WHITESPACE));
1553 }
1554
1555 /// Get list of OpenDocs (documents underlying edit sessions)
1556 const OpenDocList &CMainFrame::GetAllOpenDocs()
1557 {
1558         return static_cast<const OpenDocList &>(GetDocList(theApp.m_pOpenTemplate));
1559 }
1560
1561 /// Get list of MergeDocs (documents underlying edit sessions)
1562 const MergeDocList &CMainFrame::GetAllMergeDocs()
1563 {
1564         return static_cast<const MergeDocList &>(GetDocList(theApp.m_pDiffTemplate));
1565 }
1566
1567 /// Get list of DirDocs (documents underlying a scan)
1568 const DirDocList &CMainFrame::GetAllDirDocs()
1569 {
1570         return static_cast<const DirDocList &>(GetDocList(theApp.m_pDirTemplate));
1571 }
1572
1573 /// Get list of HexMergeDocs (documents underlying edit sessions)
1574 const HexMergeDocList &CMainFrame::GetAllHexMergeDocs()
1575 {
1576         return static_cast<const HexMergeDocList &>(GetDocList(theApp.m_pHexMergeTemplate));
1577 }
1578
1579 /**
1580  * @brief Obtain a merge doc to display a difference in files.
1581  * @return Pointer to CMergeDoc to use. 
1582  */
1583 template<class DocClass>
1584 DocClass * GetMergeDocForDiff(CMultiDocTemplate *pTemplate, CDirDoc *pDirDoc, int nFiles)
1585 {
1586         // Create a new merge doc
1587         DocClass::m_nBuffersTemp = nFiles;
1588         DocClass *pMergeDoc = (DocClass*)pTemplate->OpenDocumentFile(NULL);
1589         if (pMergeDoc)
1590         {
1591                 pDirDoc->AddMergeDoc(pMergeDoc);
1592                 pMergeDoc->SetDirDoc(pDirDoc);
1593         }
1594         return pMergeDoc;
1595 }
1596
1597 // Set status in the main status pane
1598 CString CMainFrame::SetStatus(LPCTSTR status)
1599 {
1600         CString old = m_wndStatusBar.GetPaneText(0);
1601         m_wndStatusBar.SetPaneText(0, status);
1602         return old;
1603 }
1604
1605 // Clear the item count in the main status pane
1606 void CMainFrame::ClearStatusbarItemCount()
1607 {
1608         m_wndStatusBar.SetPaneText(2, _T(""));
1609 }
1610
1611 /**
1612  * @brief Generate patch from files selected.
1613  *
1614  * Creates a patch from selected files in active directory compare, or
1615  * active file compare. Files in file compare must be saved before
1616  * creating a patch.
1617  */
1618 void CMainFrame::OnToolsGeneratePatch()
1619 {
1620         CPatchTool patcher;
1621         CFrameWnd * pFrame = GetActiveFrame();
1622         FRAMETYPE frame = GetFrameType(pFrame);
1623         BOOL bOpenDialog = TRUE;
1624
1625         // Mergedoc active?
1626         if (frame == FRAME_FILE)
1627         {
1628                 CMergeDoc * pMergeDoc = (CMergeDoc *) pFrame->GetActiveDocument();
1629                 // If there are changes in files, tell user to save them first
1630                 BOOL bModified = FALSE;
1631                 for (int pane = 0; pane < pMergeDoc->m_nBuffers; pane++)
1632                 {
1633                         if (pMergeDoc->m_ptBuf[pane]->IsModified())
1634                                 bModified = TRUE;
1635                 }
1636                 if (bModified)
1637                 {
1638                         bOpenDialog = FALSE;
1639                         LangMessageBox(IDS_SAVEFILES_FORPATCH, MB_ICONSTOP);
1640                 }
1641                 else
1642                 {
1643                         patcher.AddFiles(pMergeDoc->m_filePaths.GetLeft(),
1644                                         pMergeDoc->m_filePaths.GetRight());
1645                 }
1646         }
1647         // Dirview active
1648         else if (frame == FRAME_FOLDER)
1649         {
1650                 CDirDoc * pDoc = (CDirDoc*)pFrame->GetActiveDocument();
1651                 const CDiffContext& ctxt = pDoc->GetDiffContext();
1652                 CDirView *pView = pDoc->GetMainView();
1653
1654                 // Get selected items from folder compare
1655                 BOOL bValidFiles = TRUE;
1656                 for (DirItemIterator it = pView->SelBegin(); bValidFiles && it != pView->SelEnd(); ++it)
1657                 {
1658                         const DIFFITEM &item = *it;
1659                         if (item.diffcode.isBin())
1660                         {
1661                                 LangMessageBox(IDS_CANNOT_CREATE_BINARYPATCH, MB_ICONWARNING |
1662                                         MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_BINARYPATCH);
1663                                 bValidFiles = FALSE;
1664                         }
1665                         else if (item.diffcode.isDirectory())
1666                         {
1667                                 LangMessageBox(IDS_CANNOT_CREATE_DIRPATCH, MB_ICONWARNING |
1668                                         MB_DONT_DISPLAY_AGAIN, IDS_CANNOT_CREATE_DIRPATCH);
1669                                 bValidFiles = FALSE;
1670                         }
1671
1672                         if (bValidFiles)
1673                         {
1674                                 // Format full paths to files (leftFile/rightFile)
1675                                 String leftFile = item.getFilepath(0, ctxt.GetNormalizedPath(0));
1676                                 if (!leftFile.empty())
1677                                         leftFile = paths_ConcatPath(leftFile, item.diffFileInfo[0].filename);
1678                                 String rightFile = item.getFilepath(1, ctxt.GetNormalizedPath(1));
1679                                 if (!rightFile.empty())
1680                                         rightFile = paths_ConcatPath(rightFile, item.diffFileInfo[1].filename);
1681
1682                                 // Format relative paths to files in folder compare
1683                                 String leftpatch = item.diffFileInfo[0].path;
1684                                 if (!leftpatch.empty())
1685                                         leftpatch += _T("/");
1686                                 leftpatch += item.diffFileInfo[0].filename;
1687                                 String rightpatch = item.diffFileInfo[1].path;
1688                                 if (!rightpatch.empty())
1689                                         rightpatch += _T("/");
1690                                 rightpatch += item.diffFileInfo[1].filename;
1691                                 patcher.AddFiles(leftFile, leftpatch, rightFile, rightpatch);
1692                         }
1693                 }
1694         }
1695
1696         if (bOpenDialog)
1697         {
1698                 if (patcher.CreatePatch())
1699                 {
1700                         if (patcher.GetOpenToEditor())
1701                         {
1702                                 theApp.OpenFileToExternalEditor(patcher.GetPatchFile().c_str());
1703                         }
1704                 }
1705         }
1706 }
1707
1708 void CMainFrame::OnDropFiles(const std::vector<String>& dropped_files)
1709 {
1710         PathContext files(dropped_files);
1711         const size_t fileCount = files.GetSize();
1712
1713         // If Ctrl pressed, do recursive compare
1714         bool recurse = !!::GetAsyncKeyState(VK_CONTROL) || !!theApp.GetProfileInt(_T("Settings"), _T("Recurse"), 0);
1715
1716         // If user has <Shift> pressed with one file selected,
1717         // assume it is an archive and set filenames to same
1718         if (::GetAsyncKeyState(VK_SHIFT) < 0 && fileCount == 1)
1719         {
1720                 files.SetRight(files[0]);
1721         }
1722
1723         // Check if they dropped a project file
1724         DWORD dwFlags[3] = {FFILEOPEN_NONE, FFILEOPEN_NONE, FFILEOPEN_NONE};
1725         if (fileCount == 1)
1726         {
1727                 if (theApp.IsProjectFile(files[0].c_str()))
1728                 {
1729                         theApp.LoadAndOpenProjectFile(files[0].c_str());
1730                         return;
1731                 }
1732                 if (IsConflictFile(files[0]))
1733                 {
1734                         DoOpenConflict(files[0], true);
1735                         return;
1736                 }
1737         }
1738
1739         DoFileOpen(&files, dwFlags, recurse);
1740 }
1741
1742 BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) 
1743 {
1744         if (CustomStatusCursor::HasWaitCursor())
1745         {
1746                 CustomStatusCursor::RestoreWaitCursor();
1747                 return TRUE;
1748         }
1749         return CMDIFrameWnd::OnSetCursor(pWnd, nHitTest, message);
1750 }
1751
1752 void CMainFrame::OnPluginUnpackMode(UINT nID )
1753 {
1754         switch (nID)
1755         {
1756         case ID_UNPACK_MANUAL:
1757                 g_bUnpackerMode = PLUGIN_MANUAL;
1758                 break;
1759         case ID_UNPACK_AUTO:
1760                 g_bUnpackerMode = PLUGIN_AUTO;
1761                 break;
1762         }
1763         theApp.WriteProfileInt(_T("Settings"), _T("UnpackerMode"), g_bUnpackerMode);
1764 }
1765
1766 void CMainFrame::OnUpdatePluginUnpackMode(CCmdUI* pCmdUI) 
1767 {
1768         if (GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
1769                 pCmdUI->Enable(TRUE);
1770         else
1771                 pCmdUI->Enable(FALSE);
1772
1773         if (pCmdUI->m_nID == ID_UNPACK_MANUAL)
1774                 pCmdUI->SetRadio(PLUGIN_MANUAL == g_bUnpackerMode);
1775         if (pCmdUI->m_nID == ID_UNPACK_AUTO)
1776                 pCmdUI->SetRadio(PLUGIN_AUTO == g_bUnpackerMode);
1777 }
1778 void CMainFrame::OnPluginPrediffMode(UINT nID )
1779 {
1780         switch (nID)
1781         {
1782         case ID_PREDIFFER_MANUAL:
1783                 g_bPredifferMode = PLUGIN_MANUAL;
1784                 break;
1785         case ID_PREDIFFER_AUTO:
1786                 g_bPredifferMode = PLUGIN_AUTO;
1787                 break;
1788         }
1789         PrediffingInfo infoPrediffer;
1790         const MergeDocList &mergedocs = GetAllMergeDocs();
1791         POSITION pos = mergedocs.GetHeadPosition();
1792         while (pos)
1793                 mergedocs.GetNext(pos)->SetPrediffer(&infoPrediffer);
1794         const DirDocList &dirdocs = GetAllDirDocs();
1795         pos = dirdocs.GetHeadPosition();
1796         while (pos)
1797                 dirdocs.GetNext(pos)->GetPluginManager().SetPrediffSettingAll(g_bPredifferMode);
1798         theApp.WriteProfileInt(_T("Settings"), _T("PredifferMode"), g_bPredifferMode);
1799 }
1800
1801 void CMainFrame::OnUpdatePluginPrediffMode(CCmdUI* pCmdUI) 
1802 {
1803         if (GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
1804                 pCmdUI->Enable(TRUE);
1805         else
1806                 pCmdUI->Enable(FALSE);
1807
1808         if (pCmdUI->m_nID == ID_PREDIFFER_MANUAL)
1809                 pCmdUI->SetRadio(PLUGIN_MANUAL == g_bPredifferMode);
1810         if (pCmdUI->m_nID == ID_PREDIFFER_AUTO)
1811                 pCmdUI->SetRadio(PLUGIN_AUTO == g_bPredifferMode);
1812 }
1813 /**
1814  * @brief Called when "Reload Plugins" item is updated
1815  */
1816 void CMainFrame::OnUpdateReloadPlugins(CCmdUI* pCmdUI)
1817 {
1818         if (GetOptionsMgr()->GetBool(OPT_PLUGINS_ENABLED))
1819                 pCmdUI->Enable(TRUE);
1820         else
1821                 pCmdUI->Enable(FALSE);
1822 }
1823
1824 void CMainFrame::OnReloadPlugins()
1825 {
1826         // delete all script interfaces
1827         // (interfaces will be created again automatically when WinMerge needs them)
1828         CAllThreadsScripts::GetActiveSet()->FreeAllScripts();
1829
1830         // update the editor scripts submenu
1831         HMENU scriptsSubmenu = GetScriptsSubmenu(m_hMenuDefault);
1832         if (scriptsSubmenu != NULL)
1833                 CMergeEditView::createScriptsSubmenu(scriptsSubmenu);
1834         UpdatePrediffersMenu();
1835 }
1836
1837 /** @brief Return active merge edit view, if can figure it out/is available */
1838 CMergeEditView * CMainFrame::GetActiveMergeEditView()
1839 {
1840         // NB: GetActiveDocument does not return the Merge Doc 
1841         //     even when the merge edit view is in front
1842         // NB: CChildFrame::GetActiveView returns NULL when location view active
1843         // So we have this rather complicated logic to try to get a merge edit view
1844         // We look at the front child window, which should be a frame
1845         // and we can get a MergeEditView from it, if it is a CChildFrame
1846         // (DirViews use a different frame type)
1847         CChildFrame * pFrame = dynamic_cast<CChildFrame *>(GetActiveFrame());
1848         if (!pFrame) return 0;
1849         // Try to get the active MergeEditView (ie, left or right)
1850         if (pFrame->GetActiveView() && pFrame->GetActiveView()->IsKindOf(RUNTIME_CLASS(CMergeEditView)))
1851         {
1852                 return dynamic_cast<CMergeEditView *>(pFrame->GetActiveView());
1853         }
1854         return pFrame->GetMergeDoc()->GetActiveMergeView();
1855 }
1856
1857 void CMainFrame::UpdatePrediffersMenu()
1858 {
1859         CMenu* menu = GetMenu();
1860         if (menu == NULL)
1861         {
1862                 return;
1863         }
1864
1865         HMENU hMainMenu = menu->m_hMenu;
1866         HMENU prediffersSubmenu = GetPrediffersSubmenu(hMainMenu);
1867         if (prediffersSubmenu != NULL)
1868         {
1869                 CMergeEditView * pEditView = GetActiveMergeEditView();
1870                 if (pEditView)
1871                         pEditView->createPrediffersSubmenu(prediffersSubmenu);
1872                 else
1873                 {
1874                         // no view or dir view : display an empty submenu
1875                         int i = GetMenuItemCount(prediffersSubmenu);
1876                         while (i --)
1877                                 ::DeleteMenu(prediffersSubmenu, 0, MF_BYPOSITION);
1878                         ::AppendMenu(prediffersSubmenu, MF_SEPARATOR, 0, NULL);
1879                 }
1880         }
1881 }
1882
1883 /**
1884  * @brief Save WinMerge configuration and info to file
1885  */
1886 void CMainFrame::OnSaveConfigData()
1887 {
1888         CConfigLog configLog;
1889         String sError;
1890
1891         if (configLog.WriteLogFile(sError))
1892         {
1893                 String sFileName = configLog.GetFileName();
1894                 theApp.OpenFileToExternalEditor(sFileName);
1895         }
1896         else
1897         {
1898                 String sFileName = configLog.GetFileName();
1899                 String msg = string_format_string2(_("Cannot open file\n%1\n\n%2"), sFileName, sError);
1900                 AfxMessageBox(msg.c_str(), MB_OK | MB_ICONSTOP);
1901         }
1902 }
1903
1904 /**
1905  * @brief Open two new empty docs, 'Scratchpads'
1906  * 
1907  * Allows user to open two empty docs, to paste text to
1908  * compare from clipboard.
1909  * @note File filenames are set emptys and filedescriptors
1910  * are loaded from resource.
1911  * @sa CMergeDoc::OpenDocs()
1912  * @sa CMergeDoc::TrySaveAs()
1913  */
1914 void CMainFrame::FileNew(int nPanes) 
1915 {
1916         CDirDoc *pDirDoc = (CDirDoc*)theApp.m_pDirTemplate->CreateNewDocument();
1917         
1918         // Load emptyfile descriptors and open empty docs
1919         // Use default codepage
1920         DWORD dwFlags[3] = {0, 0, 0};
1921         FileLocation fileloc[3];
1922         if (nPanes == 2)
1923         {
1924                 theApp.m_strDescriptions[0] = _("Untitled left");
1925                 theApp.m_strDescriptions[1] = _("Untitled right");
1926                 fileloc[0].encoding.SetCodepage(ucr::getDefaultCodepage());
1927                 fileloc[1].encoding.SetCodepage(ucr::getDefaultCodepage());
1928                 ShowMergeDoc(pDirDoc, 2, fileloc, dwFlags);
1929         }
1930         else
1931         {
1932                 theApp.m_strDescriptions[0] = _("Untitled left");
1933                 theApp.m_strDescriptions[1] = _("Untitled middle");
1934                 theApp.m_strDescriptions[2] = _("Untitled right");
1935                 fileloc[0].encoding.SetCodepage(ucr::getDefaultCodepage());
1936                 fileloc[1].encoding.SetCodepage(ucr::getDefaultCodepage());
1937                 fileloc[2].encoding.SetCodepage(ucr::getDefaultCodepage());
1938                 ShowMergeDoc(pDirDoc, 3, fileloc, dwFlags);
1939         }
1940
1941         // Empty descriptors now that docs are open
1942         theApp.m_strDescriptions[0].erase();
1943         theApp.m_strDescriptions[1].erase();
1944         theApp.m_strDescriptions[2].erase();
1945 }
1946
1947 /**
1948  * @brief Open two new empty docs, 'Scratchpads'
1949  * 
1950  * Allows user to open two empty docs, to paste text to
1951  * compare from clipboard.
1952  * @note File filenames are set emptys and filedescriptors
1953  * are loaded from resource.
1954  * @sa CMergeDoc::OpenDocs()
1955  * @sa CMergeDoc::TrySaveAs()
1956  */
1957 void CMainFrame::OnFileNew() 
1958 {
1959         FileNew(2);
1960 }
1961
1962 void CMainFrame::OnFileNew3() 
1963 {
1964         FileNew(3);
1965 }
1966
1967 /**
1968  * @brief Open Filters dialog
1969  */
1970 void CMainFrame::OnToolsFilters()
1971 {
1972         String title = _("Filters");
1973         CPropertySheet sht(title.c_str());
1974         LineFiltersDlg lineFiltersDlg;
1975         FileFiltersDlg fileFiltersDlg;
1976         vector<FileFilterInfo> fileFilters;
1977         std::unique_ptr<LineFiltersList> lineFilters(new LineFiltersList());
1978         String selectedFilter;
1979         const String origFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
1980         sht.AddPage(&fileFiltersDlg);
1981         sht.AddPage(&lineFiltersDlg);
1982         sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
1983
1984         // Make sure all filters are up-to-date
1985         theApp.m_pGlobalFileFilter->ReloadUpdatedFilters();
1986
1987         theApp.m_pGlobalFileFilter->GetFileFilters(&fileFilters, selectedFilter);
1988         fileFiltersDlg.SetFilterArray(&fileFilters);
1989         fileFiltersDlg.SetSelected(selectedFilter);
1990         const bool lineFiltersEnabledOrig = GetOptionsMgr()->GetBool(OPT_LINEFILTER_ENABLED);
1991         lineFiltersDlg.m_bIgnoreRegExp = lineFiltersEnabledOrig;
1992
1993         lineFilters->CloneFrom(theApp.m_pLineFilters.get());
1994         lineFiltersDlg.SetList(lineFilters.get());
1995
1996         if (sht.DoModal() == IDOK)
1997         {
1998                 String strNone = _("<None>");
1999                 String path = fileFiltersDlg.GetSelected();
2000                 if (path.find(strNone) != String::npos)
2001                 {
2002                         // Don't overwrite mask we already have
2003                         if (!theApp.m_pGlobalFileFilter->IsUsingMask())
2004                         {
2005                                 String sFilter(_T("*.*"));
2006                                 theApp.m_pGlobalFileFilter->SetFilter(sFilter);
2007                                 GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
2008                         }
2009                 }
2010                 else
2011                 {
2012                         theApp.m_pGlobalFileFilter->SetFileFilterPath(path);
2013                         theApp.m_pGlobalFileFilter->UseMask(FALSE);
2014                         String sFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
2015                         GetOptionsMgr()->SaveOption(OPT_FILEFILTER_CURRENT, sFilter);
2016                 }
2017                 bool linefiltersEnabled = lineFiltersDlg.m_bIgnoreRegExp;
2018                 GetOptionsMgr()->SaveOption(OPT_LINEFILTER_ENABLED, linefiltersEnabled);
2019
2020                 // Check if compare documents need rescanning
2021                 BOOL bFileCompareRescan = FALSE;
2022                 BOOL bFolderCompareRescan = FALSE;
2023                 CFrameWnd * pFrame = GetActiveFrame();
2024                 FRAMETYPE frame = GetFrameType(pFrame);
2025                 if (frame == FRAME_FILE)
2026                 {
2027                         if (lineFiltersEnabledOrig != linefiltersEnabled ||
2028                                         !theApp.m_pLineFilters->Compare(lineFilters.get()))
2029                         {
2030                                 bFileCompareRescan = TRUE;
2031                         }
2032                 }
2033                 else if (frame == FRAME_FOLDER)
2034                 {
2035                         const String newFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
2036                         if (lineFiltersEnabledOrig != linefiltersEnabled || 
2037                                         !theApp.m_pLineFilters->Compare(lineFilters.get()) || origFilter != newFilter)
2038                         {
2039                                 int res = LangMessageBox(IDS_FILTERCHANGED, MB_ICONWARNING | MB_YESNO);
2040                                 if (res == IDYES)
2041                                         bFolderCompareRescan = TRUE;
2042                         }
2043                 }
2044
2045                 // Save new filters before (possibly) rescanning
2046                 theApp.m_pLineFilters->CloneFrom(lineFilters.get());
2047                 theApp.m_pLineFilters->SaveFilters();
2048
2049                 if (bFileCompareRescan)
2050                 {
2051                         const MergeDocList &docs = GetAllMergeDocs();
2052                         POSITION pos = docs.GetHeadPosition();
2053                         while (pos)
2054                         {
2055                                 CMergeDoc * pMergeDoc = docs.GetNext(pos);
2056                                 pMergeDoc->FlushAndRescan(TRUE);
2057                         }
2058                 }
2059                 else if (bFolderCompareRescan)
2060                 {
2061                         const DirDocList &dirDocs = GetAllDirDocs();
2062                         POSITION pos = dirDocs.GetHeadPosition();
2063                         while (pos)
2064                         {
2065                                 CDirDoc * pDirDoc = dirDocs.GetNext(pos);
2066                                 pDirDoc->Rescan();
2067                         }
2068                 }
2069         }
2070 }
2071
2072 /**
2073  * @brief Open Filters dialog.
2074  */
2075 void CMainFrame::SelectFilter()
2076 {
2077         OnToolsFilters();
2078 }
2079
2080 /**
2081  * @brief Closes application with ESC.
2082  *
2083  * Application is closed if:
2084  * - 'Close Windows with ESC' option is enabled and
2085  *    there is no open document windows
2086  * - '-e' commandline switch is given
2087  */
2088 BOOL CMainFrame::PreTranslateMessage(MSG* pMsg)
2089 {
2090         // Check if we got 'ESC pressed' -message
2091         if ((pMsg->message == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
2092         {
2093                 if (theApp.m_bEscShutdown && m_wndTabBar.GetItemCount() <= 1)
2094                 {
2095                         AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
2096                         return TRUE;
2097                 }
2098                 else if (GetOptionsMgr()->GetBool(OPT_CLOSE_WITH_ESC) && m_wndTabBar.GetItemCount() == 0)
2099                 {
2100                         AfxGetMainWnd()->PostMessage(WM_COMMAND, ID_APP_EXIT);
2101                         return FALSE;
2102                 }
2103         }
2104         return CMDIFrameWnd::PreTranslateMessage(pMsg);
2105 }
2106
2107 /**
2108  * @brief Show/hide statusbar.
2109  */
2110 void CMainFrame::OnViewStatusBar()
2111 {
2112         bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_STATUSBAR);
2113         GetOptionsMgr()->SaveOption(OPT_SHOW_STATUSBAR, bShow);
2114
2115         CMDIFrameWnd::ShowControlBar(&m_wndStatusBar, bShow, 0);
2116 }
2117
2118 /**
2119  * @brief Updates "Show Tabbar" menuitem.
2120  */
2121 void CMainFrame::OnUpdateViewTabBar(CCmdUI* pCmdUI) 
2122 {
2123         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR));
2124 }
2125
2126 /**
2127  * @brief Show/hide tabbar.
2128  */
2129 void CMainFrame::OnViewTabBar()
2130 {
2131         bool bShow = !GetOptionsMgr()->GetBool(OPT_SHOW_TABBAR);
2132         GetOptionsMgr()->SaveOption(OPT_SHOW_TABBAR, bShow);
2133
2134         CMDIFrameWnd::ShowControlBar(&m_wndTabBar, bShow, 0);
2135 }
2136
2137 /**
2138  * @brief Updates "Automatically Resize Panes" menuitem.
2139  */
2140 void CMainFrame::OnUpdateResizePanes(CCmdUI* pCmdUI)
2141 {
2142         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
2143 }
2144
2145
2146 /**
2147  * @brief Enable/disable automatic pane resizing.
2148  */
2149 void CMainFrame::OnResizePanes()
2150 {
2151         bool bResize = !GetOptionsMgr()->GetBool(OPT_RESIZE_PANES);
2152         GetOptionsMgr()->SaveOption(OPT_RESIZE_PANES, bResize);
2153         // TODO: Introduce a common merge frame superclass?
2154         CFrameWnd *pActiveFrame = GetActiveFrame();
2155         if (CChildFrame *pFrame = DYNAMIC_DOWNCAST(CChildFrame, pActiveFrame))
2156         {
2157                 pFrame->UpdateAutoPaneResize();
2158                 if (bResize)
2159                         pFrame->UpdateSplitter();
2160         }
2161         else if (CHexMergeFrame *pFrame = DYNAMIC_DOWNCAST(CHexMergeFrame, pActiveFrame))
2162         {
2163                 pFrame->UpdateAutoPaneResize();
2164                 if (bResize)
2165                         pFrame->UpdateSplitter();
2166         }
2167 }
2168
2169 /**
2170  * @brief Open project-file.
2171  */
2172 void CMainFrame::OnFileOpenproject()
2173 {
2174         String sFilepath;
2175         
2176         // get the default projects path
2177         String strProjectPath = GetOptionsMgr()->GetString(OPT_PROJECTS_PATH);
2178         if (!SelectFile(GetSafeHwnd(), sFilepath, strProjectPath.c_str(), _("Open"),
2179                         _("WinMerge Project Files (*.WinMerge)|*.WinMerge||"), TRUE))
2180                 return;
2181         
2182         strProjectPath = paths_GetParentPath(sFilepath);
2183         // store this as the new project path
2184         GetOptionsMgr()->SaveOption(OPT_PROJECTS_PATH, strProjectPath);
2185
2186         theApp.LoadAndOpenProjectFile(sFilepath.c_str());
2187 }
2188
2189 /**
2190  * @brief Receive command line from another instance.
2191  *
2192  * This function receives command line when only single-instance
2193  * is allowed. New instance tried to start sends its command line
2194  * to here so we can open paths it was meant to.
2195  */
2196 LRESULT CMainFrame::OnCopyData(WPARAM wParam, LPARAM lParam)
2197 {
2198         COPYDATASTRUCT *pCopyData = (COPYDATASTRUCT*)lParam;
2199         LPCTSTR pchData = (LPCTSTR)pCopyData->lpData;
2200         // Bail out if data isn't zero-terminated
2201         DWORD cchData = pCopyData->cbData / sizeof(TCHAR);
2202         if (cchData == 0 || pchData[cchData - 1] != _T('\0'))
2203                 return FALSE;
2204         ReplyMessage(TRUE);
2205         MergeCmdLineInfo cmdInfo = pchData;
2206         theApp.ParseArgsAndDoOpen(cmdInfo, this);
2207         return TRUE;
2208 }
2209
2210 LRESULT CMainFrame::OnUser1(WPARAM wParam, LPARAM lParam)
2211 {
2212         CFrameWnd * pFrame = GetActiveFrame();
2213         if (pFrame)
2214         {
2215                 IMergeDoc *pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
2216                 if (!pMergeDoc)
2217                         pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
2218                 if (pMergeDoc)
2219                         pMergeDoc->CheckFileChanged();
2220         }
2221         return 0;
2222 }
2223
2224 /**
2225  * @brief Handle timer events.
2226  * @param [in] nIDEvent Timer that timed out.
2227  */
2228 void CMainFrame::OnTimer(UINT_PTR nIDEvent)
2229 {
2230         switch (nIDEvent)
2231         {
2232         case WM_NONINTERACTIVE:
2233                 KillTimer(WM_NONINTERACTIVE);
2234                 PostMessage(WM_CLOSE);
2235                 break;
2236         
2237         case ID_TIMER_FLASH:
2238                 // This timer keeps running until window is activated
2239                 // See OnActivate()
2240                 FlashWindow(TRUE);
2241                 break;
2242         }
2243         CMDIFrameWnd::OnTimer(nIDEvent);
2244 }
2245
2246 /**
2247  * @brief Close all open windows.
2248  * 
2249  * Asks about saving unsaved files and then closes all open windows.
2250  */
2251 void CMainFrame::OnWindowCloseAll()
2252 {
2253         CMDIChildWnd *pChild = MDIGetActive();
2254         CDocument* pDoc;
2255         while (pChild)
2256         {
2257                 if ((pDoc = pChild->GetActiveDocument()) != NULL)
2258                 {
2259                         if (!pDoc->SaveModified())
2260                                 return;
2261                         pDoc->OnCloseDocument();
2262                 }
2263                 else if (GetFrameType(pChild) == FRAME_IMGFILE)
2264                 {
2265                         if (!static_cast<CImgMergeFrame *>(pChild)->CloseNow())
2266                                 return;
2267                 }
2268                 else
2269                 {
2270                         pChild->DestroyWindow();
2271                 }
2272                 pChild = MDIGetActive();
2273         }
2274         return;
2275 }
2276
2277 /**
2278  * @brief Enables Window/Close All item if there are open windows.
2279  */ 
2280 void CMainFrame::OnUpdateWindowCloseAll(CCmdUI* pCmdUI)
2281 {
2282         const MergeDocList &mergedocs = GetAllMergeDocs();
2283         if (!mergedocs.IsEmpty())
2284         {
2285                 pCmdUI->Enable(TRUE);
2286                 return;
2287         }
2288
2289         const DirDocList &dirdocs = GetAllDirDocs();
2290         if (!dirdocs.IsEmpty())
2291                 pCmdUI->Enable(TRUE);
2292         else
2293                 pCmdUI->Enable(FALSE);
2294 }
2295
2296 /**
2297  * @brief Access to the singleton main frame (where we have some globals)
2298  */
2299 CMainFrame * GetMainFrame()
2300 {
2301         CWnd * mainwnd = AfxGetMainWnd();
2302         ASSERT(mainwnd);
2303         CMainFrame *pMainframe = dynamic_cast<CMainFrame*>(mainwnd);
2304         ASSERT(pMainframe);
2305         return pMainframe;
2306 }
2307
2308 /**
2309  * @brief Opens dialog for user to Load, edit and save project files.
2310  * This dialog gets current compare paths and filter (+other properties
2311  * possible in project files) and initializes the dialog with them.
2312  */
2313 void CMainFrame::OnSaveProject()
2314 {
2315         String title = _("Project File");
2316         CPropertySheet sht(title.c_str());
2317         ProjectFilePathsDlg pathsDlg;
2318         sht.AddPage(&pathsDlg);
2319         sht.m_psh.dwFlags |= PSH_NOAPPLYNOW; // Hide 'Apply' button since we don't need it
2320
2321         String left;
2322         String right;
2323         CFrameWnd * pFrame = GetActiveFrame();
2324         FRAMETYPE frame = GetFrameType(pFrame);
2325
2326         if (frame == FRAME_FILE)
2327         {
2328                 CMergeDoc * pMergeDoc = (CMergeDoc *) pFrame->GetActiveDocument();
2329                 left = pMergeDoc->m_filePaths.GetLeft();
2330                 right = pMergeDoc->m_filePaths.GetRight();
2331                 pathsDlg.SetPaths(left, right);
2332                 pathsDlg.m_bLeftPathReadOnly = pMergeDoc->m_ptBuf[0]->GetReadOnly();
2333                 pathsDlg.m_bRightPathReadOnly = pMergeDoc->m_ptBuf[1]->GetReadOnly();
2334         }
2335         else if (frame == FRAME_FOLDER)
2336         {
2337                 // Get paths currently in compare
2338                 const CDirDoc * pDoc = (const CDirDoc*)pFrame->GetActiveDocument();
2339                 const CDiffContext& ctxt = pDoc->GetDiffContext();
2340                 left = paths_AddTrailingSlash(ctxt.GetNormalizedLeft());
2341                 right = paths_AddTrailingSlash(ctxt.GetNormalizedRight());
2342                 
2343                 // Set-up the dialog
2344                 pathsDlg.SetPaths(left, right);
2345                 pathsDlg.m_bIncludeSubfolders = ctxt.m_bRecursive;
2346                 pathsDlg.m_bLeftPathReadOnly = pDoc->GetReadOnly(0);
2347                 pathsDlg.m_bRightPathReadOnly = pDoc->GetReadOnly(pDoc->m_nDirs - 1);
2348         }
2349
2350         pathsDlg.m_sFilter = theApp.m_pGlobalFileFilter->GetFilterNameOrMask();
2351         sht.DoModal();
2352 }
2353
2354 /** 
2355  * @brief Start flashing window if window is inactive.
2356  */
2357 void CMainFrame::StartFlashing()
2358 {
2359         CWnd * activeWindow = GetActiveWindow();
2360         if (activeWindow != this)
2361         {
2362                 FlashWindow(TRUE);
2363                 m_bFlashing = TRUE;
2364                 SetTimer(ID_TIMER_FLASH, WINDOW_FLASH_TIMEOUT, NULL);
2365         }
2366 }
2367
2368 /** 
2369  * @brief Stop flashing window when window is activated.
2370  *
2371  * If WinMerge is inactive when compare finishes, we start flashing window
2372  * to alert user. When user activates WinMerge window we stop flashing.
2373  * @param [in] nState Tells if window is being activated or deactivated.
2374  * @param [in] pWndOther Pointer to window whose status is changing.
2375  * @param [in] Is window minimized?
2376  */
2377 void CMainFrame::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
2378 {
2379         CMDIFrameWnd::OnActivate(nState, pWndOther, bMinimized);
2380
2381         if (nState == WA_ACTIVE || nState == WA_CLICKACTIVE)
2382         {
2383                 if (m_bFlashing)
2384                 {
2385                         m_bFlashing = FALSE;
2386                         FlashWindow(FALSE);
2387                         KillTimer(ID_TIMER_FLASH);
2388                 }
2389         }
2390 }
2391
2392 #if _MFC_VER > 0x0600
2393 void CMainFrame::OnActivateApp(BOOL bActive, DWORD dwThreadID)
2394 #else
2395 void CMainFrame::OnActivateApp(BOOL bActive, HTASK hTask)
2396 #endif
2397 {
2398 #if _MFC_VER > 0x0600
2399         CMDIFrameWnd::OnActivateApp(bActive, dwThreadID);
2400 #else
2401         CMDIFrameWnd::OnActivateApp(bActive, hTask);
2402 #endif
2403
2404         CFrameWnd * pFrame = GetActiveFrame();
2405         if (pFrame)
2406         {
2407                 IMergeDoc *pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame->GetActiveDocument());
2408                 if (!pMergeDoc)
2409                         pMergeDoc = dynamic_cast<IMergeDoc *>(pFrame);
2410                 if (pMergeDoc)
2411                         PostMessage(WM_USER+1);
2412         }
2413 }
2414
2415 BOOL CMainFrame::CreateToolbar()
2416 {
2417         if (!m_wndToolBar.CreateEx(this) ||
2418                 !m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
2419         {
2420                 return FALSE;
2421         }
2422
2423         if (!m_wndReBar.Create(this))
2424         {
2425                 return FALSE;
2426         }
2427
2428         VERIFY(m_wndToolBar.ModifyStyle(0, TBSTYLE_FLAT|TBSTYLE_TRANSPARENT));
2429
2430         // Remove this if you don't want tool tips or a resizable toolbar
2431         m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
2432                 CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
2433         m_wndToolBar.GetToolBarCtrl().SetExtendedStyle(TBSTYLE_EX_DRAWDDARROWS);
2434
2435         m_wndReBar.AddBar(&m_wndToolBar);
2436
2437         LoadToolbarImages();
2438
2439         UINT nID, nStyle;
2440         int iImage;
2441         int index = m_wndToolBar.GetToolBarCtrl().CommandToIndex(ID_OPTIONS);
2442         m_wndToolBar.GetButtonInfo(index, nID, nStyle, iImage);
2443         nStyle |= TBSTYLE_DROPDOWN;
2444         m_wndToolBar.SetButtonInfo(index, nID, nStyle, iImage);
2445
2446         if (GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR) == false)
2447         {
2448                 CMDIFrameWnd::ShowControlBar(&m_wndToolBar, false, 0);
2449         }
2450
2451         return TRUE;
2452 }
2453
2454 /** @brief Load toolbar images from the resource. */
2455 void CMainFrame::LoadToolbarImages()
2456 {
2457         int toolbarSize = GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE);
2458         CToolBarCtrl& BarCtrl = m_wndToolBar.GetToolBarCtrl();
2459
2460         m_ToolbarImages[TOOLBAR_IMAGES_ENABLED].DeleteImageList();
2461         m_ToolbarImages[TOOLBAR_IMAGES_DISABLED].DeleteImageList();
2462         CSize sizeButton(0, 0);
2463
2464         if (toolbarSize == 0)
2465         {
2466                 LoadToolbarImageList(TOOLBAR_SIZE_16x16, IDB_TOOLBAR_ENABLED,
2467                         m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]);
2468                 LoadToolbarImageList(TOOLBAR_SIZE_16x16, IDB_TOOLBAR_DISABLED,
2469                         m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]);
2470                 sizeButton = CSize(24, 24);
2471         }
2472         else if (toolbarSize == 1)
2473         {
2474                 LoadToolbarImageList(TOOLBAR_SIZE_32x32, IDB_TOOLBAR_ENABLED32,
2475                         m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]);
2476                 LoadToolbarImageList(TOOLBAR_SIZE_32x32, IDB_TOOLBAR_DISABLED32,
2477                         m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]);
2478                 sizeButton = CSize(40, 40);
2479         }
2480
2481         BarCtrl.SetButtonSize(sizeButton);
2482         BarCtrl.SetImageList(&m_ToolbarImages[TOOLBAR_IMAGES_ENABLED]);
2483         BarCtrl.SetDisabledImageList(&m_ToolbarImages[TOOLBAR_IMAGES_DISABLED]);
2484         m_ToolbarImages[TOOLBAR_IMAGES_ENABLED].Detach();
2485         m_ToolbarImages[TOOLBAR_IMAGES_DISABLED].Detach();
2486
2487         // resize the rebar.
2488         REBARBANDINFO rbbi;
2489         rbbi.cbSize = sizeof(rbbi);
2490         rbbi.fMask = RBBIM_CHILDSIZE;
2491         rbbi.cyMinChild = sizeButton.cy;
2492         m_wndReBar.GetReBarCtrl().SetBandInfo(0, &rbbi);
2493 }
2494
2495
2496 /**
2497  * @brief Load a transparent 24-bit color image list.
2498  */
2499 static void LoadHiColImageList(UINT nIDResource, int nWidth, int nHeight, int nCount, CImageList& ImgList, COLORREF crMask = RGB(255,0,255))
2500 {
2501         CBitmap bm;
2502         VERIFY(bm.LoadBitmap(nIDResource));
2503         VERIFY(ImgList.Create(nWidth, nHeight, ILC_COLORDDB|ILC_MASK, nCount, 0));
2504         int nIndex = ImgList.Add(&bm, crMask);
2505         ASSERT(-1 != nIndex);
2506 }
2507
2508 /**
2509  * @brief Load toolbar image list.
2510  */
2511 static void LoadToolbarImageList(CMainFrame::TOOLBAR_SIZE size, UINT nIDResource,
2512                 CImageList& ImgList)
2513 {
2514         const int ImageCount = 22;
2515         int imageWidth = 0;
2516         int imageHeight = 0;
2517
2518         switch (size)
2519         {
2520                 case CMainFrame::TOOLBAR_SIZE_16x16:
2521                         imageWidth = 16;
2522                         imageHeight = 15;
2523                         break;
2524                 case CMainFrame::TOOLBAR_SIZE_32x32:
2525                         imageWidth = 32;
2526                         imageHeight = 31;
2527                         break;
2528         }
2529
2530         LoadHiColImageList(nIDResource, imageWidth, imageHeight, ImageCount, ImgList);
2531 }
2532
2533 /**
2534  * @brief Called when the document title is modified.
2535  */
2536 void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
2537 {
2538         CFrameWnd::OnUpdateFrameTitle(bAddToTitle);
2539         
2540         if (m_wndTabBar.m_hWnd)
2541                 m_wndTabBar.UpdateTabs();
2542 }
2543
2544 /** @brief Hide the toolbar. */
2545 void CMainFrame::OnToolbarNone()
2546 {
2547         GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, false);
2548         CMDIFrameWnd::ShowControlBar(&m_wndToolBar, FALSE, 0);
2549 }
2550
2551 /** @brief Update menuitem for hidden toolbar. */
2552 void CMainFrame::OnUpdateToolbarNone(CCmdUI* pCmdUI)
2553 {
2554         bool enabled = GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR);
2555         pCmdUI->SetRadio(!enabled);
2556 }
2557
2558 /** @brief Show small toolbar. */
2559 void CMainFrame::OnToolbarSmall()
2560 {
2561         GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, true);
2562         GetOptionsMgr()->SaveOption(OPT_TOOLBAR_SIZE, 0);
2563
2564         LoadToolbarImages();
2565
2566         CMDIFrameWnd::ShowControlBar(&m_wndToolBar, TRUE, 0);
2567 }
2568
2569 /** @brief Update menuitem for small toolbar. */
2570 void CMainFrame::OnUpdateToolbarSmall(CCmdUI* pCmdUI)
2571 {
2572         bool enabled = GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR);
2573         int toolbar = GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE);
2574         pCmdUI->SetRadio(enabled && toolbar == 0);
2575 }
2576
2577 /** @brief Show big toolbar. */
2578 void CMainFrame::OnToolbarBig()
2579 {
2580         GetOptionsMgr()->SaveOption(OPT_SHOW_TOOLBAR, true);
2581         GetOptionsMgr()->SaveOption(OPT_TOOLBAR_SIZE, 1);
2582
2583         LoadToolbarImages();
2584
2585         CMDIFrameWnd::ShowControlBar(&m_wndToolBar, TRUE, 0);
2586 }
2587
2588 /** @brief Update menuitem for big toolbar. */
2589 void CMainFrame::OnUpdateToolbarBig(CCmdUI* pCmdUI)
2590 {
2591         bool enabled = GetOptionsMgr()->GetBool(OPT_SHOW_TOOLBAR);
2592         int toolbar = GetOptionsMgr()->GetInt(OPT_TOOLBAR_SIZE);
2593         pCmdUI->SetRadio(enabled && toolbar == 1);
2594 }
2595
2596 /** @brief Lang aware version of CFrameWnd::OnToolTipText() */
2597 BOOL CMainFrame::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
2598 {
2599         ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
2600
2601         // need to handle both ANSI and UNICODE versions of the message
2602         TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
2603         TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
2604         String strFullText;
2605         CString strTipText;
2606         UINT_PTR nID = pNMHDR->idFrom;
2607         if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
2608                 pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
2609         {
2610                 // idFrom is actually the HWND of the tool
2611                 nID = ::GetDlgCtrlID((HWND)nID);
2612         }
2613
2614         if (nID != 0) // will be zero on a separator
2615         {
2616                 strFullText = theApp.LoadString(nID);
2617                 // don't handle the message if no string resource found
2618                 if (strFullText.empty())
2619                         return FALSE;
2620
2621                 // this is the command id, not the button index
2622                 AfxExtractSubString(strTipText, strFullText.c_str(), 1, '\n');
2623         }
2624 #ifndef _UNICODE
2625         if (pNMHDR->code == TTN_NEEDTEXTA)
2626                 lstrcpyn(pTTTA->szText, strTipText, countof(pTTTA->szText));
2627         else
2628                 _mbstowcsz(pTTTW->szText, strTipText, countof(pTTTW->szText));
2629 #else
2630         if (pNMHDR->code == TTN_NEEDTEXTA)
2631                 _wcstombsz(pTTTA->szText, strTipText, countof(pTTTA->szText));
2632         else
2633                 lstrcpyn(pTTTW->szText, strTipText, countof(pTTTW->szText));
2634 #endif
2635         *pResult = 0;
2636
2637         // bring the tooltip window above other popup windows
2638         ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
2639                 SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
2640
2641         return TRUE;    // message was handled
2642 }
2643
2644 /**
2645  * @brief Ask user for close confirmation when closing the mainframe.
2646  * This function asks if user wants to close multiple open windows when user
2647  * selects (perhaps accidentally) to close WinMerge (application).
2648  * @return true if user agreeds to close all windows.
2649  */
2650 bool CMainFrame::AskCloseConfirmation()
2651 {
2652         const DirDocList &dirdocs = GetAllDirDocs();
2653         const MergeDocList &mergedocs = GetAllMergeDocs();
2654
2655         int ret = IDYES;
2656         const int count = dirdocs.GetCount() + mergedocs.GetCount();
2657         if (count > 1)
2658         {
2659                 // Check that we don't have one empty dirdoc + mergedoc situation.
2660                 // That happens since we open "hidden" dirdoc for every file compare.
2661                 if (dirdocs.GetCount() == 1)
2662                 {
2663                         CDirDoc *pDoc = dirdocs.GetHead();
2664                         if (!pDoc->HasDiffs())
2665                                 return true;
2666                 }
2667                 ret = LangMessageBox(IDS_CLOSEALL_WINDOWS, MB_YESNO | MB_ICONWARNING);
2668         }
2669         return (ret == IDYES);
2670 }
2671
2672 void CMainFrame::OnHelpCheckForUpdates()
2673 {
2674         CVersionInfo version(AfxGetResourceHandle());
2675         CInternetSession session;
2676         try
2677         {
2678                 CHttpFile *file = (CHttpFile *)session.OpenURL(CurrentVersionURL);
2679                 if (!file)
2680                         return;
2681                 char buf[256] = { 0 };
2682                 file->Read(buf, sizeof(buf));
2683                 file->Close();
2684                 String current_version = ucr::toTString(buf);
2685                 string_replace(current_version, _T("\r\n"), _T(""));
2686                 delete file;
2687
2688                 int exe_vers[4] = { 0 }, cur_vers[4] = { 0 };
2689                 _stscanf(version.GetProductVersion().c_str(), _T("%d.%d.%d.%d"), &exe_vers[0], &exe_vers[1], &exe_vers[2], &exe_vers[3]);
2690                 _stscanf(current_version.c_str(),             _T("%d.%d.%d.%d"), &cur_vers[0], &cur_vers[1], &cur_vers[2], &cur_vers[3]);
2691                 String exe_version_hex = string_format(_T("%08x%08x%08x%08x"), exe_vers[0], exe_vers[1], exe_vers[2], exe_vers[3]);
2692                 String cur_version_hex = string_format(_T("%08x%08x%08x%08x"), cur_vers[0], cur_vers[1], cur_vers[2], cur_vers[3]);
2693
2694                 switch (exe_version_hex.compare(cur_version_hex))
2695                 {
2696                 case 1:
2697                         if (cur_version_hex == _T("00000000000000000000000000000000"))
2698                         {
2699                                 String msg = _("Failed to download latest version information");
2700                                 AfxMessageBox(msg.c_str(), MB_ICONERROR);
2701                                 break;
2702                         }
2703                         // pass through
2704                 case 0:
2705                 {
2706                         String msg = _("Your software is up to date");
2707                         AfxMessageBox(msg.c_str(), MB_ICONINFORMATION);
2708                         break;
2709                 }
2710                 case -1:
2711                 {
2712                         String msg = string_format_string2(_("A new version of WinMerge is available.\n%1 is now available (you have %2). Would you like to download it now?"), current_version, version.GetProductVersion());
2713                         if (AfxMessageBox(msg.c_str(), MB_ICONINFORMATION | MB_YESNO) == IDYES)
2714                                 ShellExecute(NULL, _T("open"), DownloadUrl, NULL, NULL, SW_SHOWNORMAL);
2715                         break;
2716                 }
2717                 }
2718         }
2719         catch (CException& e)
2720         {
2721                 TCHAR msg[512];
2722                 e.GetErrorMessage(msg, sizeof(msg)/sizeof(msg[0]));
2723                 AfxMessageBox(msg, MB_ICONERROR);
2724         }
2725 }
2726
2727 /**
2728  * @brief Called when user selects File/Open Conflict...
2729  */
2730 void CMainFrame::OnFileOpenConflict()
2731 {
2732         String conflictFile;
2733         if (SelectFile(GetSafeHwnd(), conflictFile))
2734         {
2735                 DoOpenConflict(conflictFile);
2736         }
2737 }
2738
2739 /**
2740  * @brief Select and open conflict file for resolving.
2741  * This function lets user to select conflict file to resolve.
2742  * Then we parse conflict file to two files to "merge" and
2743  * save resulting file over original file.
2744  *
2745  * Set left-side file read-only as it is the repository file which cannot
2746  * be modified anyway. Right-side file is user's file which is set as
2747  * modified by default so user can just save it and accept workspace
2748  * file as resolved file.
2749  * @param [in] conflictFile Full path to conflict file to open.
2750  * @param [in] checked If true, do not check if it really is project file.
2751  * @return TRUE if conflict file was opened for resolving.
2752  */
2753 BOOL CMainFrame::DoOpenConflict(const String& conflictFile, bool checked)
2754 {
2755         BOOL conflictCompared = FALSE;
2756
2757         if (!checked)
2758         {
2759                 bool confFile = IsConflictFile(conflictFile);
2760                 if (!confFile)
2761                 {
2762                         String message = string_format_string1(_("The file\n%1\nis not a conflict file."), conflictFile);
2763                         AfxMessageBox(message.c_str(), MB_ICONSTOP);
2764                         return FALSE;
2765                 }
2766         }
2767
2768         // Create temp files and put them into the list,
2769         // from where they get deleted when MainFrame is deleted.
2770         String ext = paths_FindExtension(conflictFile);
2771         TempFilePtr wTemp(new TempFile());
2772         String workFile = wTemp->Create(_T("confw_"), ext.c_str());
2773         m_tempFiles.push_back(wTemp);
2774         TempFilePtr vTemp(new TempFile());
2775         String revFile = vTemp->Create(_T("confv_"), ext.c_str());
2776         m_tempFiles.push_back(vTemp);
2777
2778         // Parse conflict file into two files.
2779         bool inners;
2780         int iGuessEncodingType = GetOptionsMgr()->GetInt(OPT_CP_DETECT);
2781         bool success = ParseConflictFile(conflictFile, workFile, revFile, iGuessEncodingType, inners);
2782
2783         if (success)
2784         {
2785                 // Open two parsed files to WinMerge, telling WinMerge to
2786                 // save over original file (given as third filename).
2787                 theApp.m_strSaveAsPath = conflictFile.c_str();
2788                 String theirs = _("Theirs File");
2789                 String my = _("Mine File");
2790                 theApp.m_strDescriptions[0] = theirs;
2791                 theApp.m_strDescriptions[1] = my;
2792
2793                 DWORD dwFlags[2] = {FFILEOPEN_READONLY | FFILEOPEN_NOMRU, FFILEOPEN_NOMRU | FFILEOPEN_MODIFIED};
2794                 conflictCompared = DoFileOpen(&PathContext(revFile, workFile), 
2795                                         dwFlags);
2796         }
2797         else
2798         {
2799                 LangMessageBox(IDS_ERROR_CONF_RESOLVE, MB_ICONSTOP);
2800         }
2801         return conflictCompared;
2802 }
2803
2804 /**
2805  * @brief Get type of frame (File/Folder compare).
2806  * @param [in] pFrame Pointer to frame to check.
2807  * @return FRAMETYPE of the given frame.
2808 */
2809 CMainFrame::FRAMETYPE CMainFrame::GetFrameType(const CFrameWnd * pFrame) const
2810 {
2811         BOOL bMergeFrame = pFrame->IsKindOf(RUNTIME_CLASS(CChildFrame));
2812         BOOL bHexMergeFrame = pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame));
2813         BOOL bImgMergeFrame = pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame));
2814         BOOL bDirFrame = pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame));
2815
2816         if (bMergeFrame)
2817                 return FRAME_FILE;
2818         else if (bHexMergeFrame)
2819                 return FRAME_HEXFILE;
2820         else if (bImgMergeFrame)
2821                 return FRAME_IMGFILE;
2822         else if (bDirFrame)
2823                 return FRAME_FOLDER;
2824         else
2825                 return FRAME_OTHER;
2826 }
2827
2828 /**
2829  * @brief Show the plugins list dialog.
2830  */
2831 void CMainFrame::OnPluginsList()
2832 {
2833         PluginsListDlg dlg;
2834         dlg.DoModal();
2835 }
2836
2837 void CMainFrame::OnDiffOptionsDropDown(NMHDR* pNMHDR, LRESULT* pResult)
2838 {
2839         LPNMTOOLBAR pToolBar = reinterpret_cast<LPNMTOOLBAR>(pNMHDR);
2840         ClientToScreen(&(pToolBar->rcButton));
2841         CMenu menu;
2842         VERIFY(menu.LoadMenu(IDR_POPUP_DIFF_OPTIONS));
2843         theApp.TranslateMenu(menu.m_hMenu);
2844         CMenu* pPopup = menu.GetSubMenu(0);
2845         if (NULL != pPopup)
2846         {
2847                 pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, 
2848                         pToolBar->rcButton.left, pToolBar->rcButton.bottom, this);
2849         }
2850         *pResult = 0;
2851 }
2852 void CMainFrame::OnDiffWhitespace(UINT nID)
2853 {
2854         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_WHITESPACE, nID - IDC_DIFF_WHITESPACE_COMPARE);
2855         ApplyDiffOptions();
2856 }
2857
2858 void CMainFrame::OnUpdateDiffWhitespace(CCmdUI* pCmdUI)
2859 {
2860         pCmdUI->SetRadio((pCmdUI->m_nID - IDC_DIFF_WHITESPACE_COMPARE) == GetOptionsMgr()->GetInt(OPT_CMP_IGNORE_WHITESPACE));
2861         pCmdUI->Enable();
2862 }
2863
2864 void CMainFrame::OnDiffCaseSensitive()
2865 {
2866         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_CASE, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2867         ApplyDiffOptions();
2868 }
2869
2870 void CMainFrame::OnUpdateDiffCaseSensitive(CCmdUI* pCmdUI)
2871 {
2872         pCmdUI->SetCheck(!GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_CASE));
2873         pCmdUI->Enable();
2874 }
2875
2876 void CMainFrame::OnDiffIgnoreEOL()
2877 {
2878         GetOptionsMgr()->SaveOption(OPT_CMP_IGNORE_EOL, !GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2879         ApplyDiffOptions();
2880 }
2881
2882 void CMainFrame::OnUpdateDiffIgnoreEOL(CCmdUI* pCmdUI)
2883 {
2884         pCmdUI->SetCheck(GetOptionsMgr()->GetBool(OPT_CMP_IGNORE_EOL));
2885         pCmdUI->Enable();
2886 }
2887
2888 void CMainFrame::OnCompareMethod(UINT nID)
2889
2890         GetOptionsMgr()->SaveOption(OPT_CMP_METHOD, nID - ID_COMPMETHOD_FULL_CONTENTS);
2891 }
2892
2893 void CMainFrame::OnUpdateCompareMethod(CCmdUI* pCmdUI)
2894 {
2895         pCmdUI->SetRadio((pCmdUI->m_nID - ID_COMPMETHOD_FULL_CONTENTS) == GetOptionsMgr()->GetInt(OPT_CMP_METHOD));
2896         pCmdUI->Enable();
2897 }
2898
2899 void CMainFrame::OnMRUs(UINT nID)
2900 {
2901         std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(9);
2902         const int idx = nID - ID_MRU_FIRST;
2903         if (idx < mrus.size())
2904         {
2905                 MergeCmdLineInfo cmdInfo((_T("\"") + mrus[idx].path + _T("\" ") + mrus[idx].params).c_str());
2906                 theApp.ParseArgsAndDoOpen(cmdInfo, this);
2907         }
2908 }
2909
2910 void CMainFrame::OnUpdateNoMRUs(CCmdUI* pCmdUI)
2911 {
2912         // append the MRU submenu
2913         HMENU hMenu = GetSubmenu(AfxGetMainWnd()->GetMenu()->m_hMenu, ID_FILE_NEW, false);
2914         if (hMenu == NULL)
2915                 return;
2916         
2917         // empty the menu
2918         int i = ::GetMenuItemCount(hMenu);
2919         while (i --)
2920                 ::DeleteMenu(hMenu, 0, MF_BYPOSITION);
2921
2922         std::vector<JumpList::Item> mrus = JumpList::GetRecentDocs(9);
2923
2924         if (mrus.size() == 0)
2925         {
2926                 // no script : create a <empty> entry
2927                 ::AppendMenu(hMenu, MF_STRING, ID_NO_EDIT_SCRIPTS, theApp.LoadString(IDS_NO_EDIT_SCRIPTS).c_str());
2928         }
2929         else
2930         {
2931                 // or fill in the submenu with the scripts names
2932                 int ID = ID_MRU_FIRST;  // first ID in menu
2933                 for (i = 0 ; i < mrus.size() ; i++, ID++)
2934                         ::AppendMenu(hMenu, MF_STRING, ID, (string_format(_T("&%d "), i+1) + mrus[i].title).c_str());
2935         }
2936
2937         pCmdUI->Enable(true);
2938 }
2939
2940 /**
2941  * @brief Update plugin name
2942  * @param [in] pCmdUI UI component to update.
2943  */
2944 void CMainFrame::OnUpdatePluginName(CCmdUI* pCmdUI)
2945 {
2946         pCmdUI->SetText(_T(""));
2947 }
2948
2949 void CMainFrame::ReloadMenu()
2950 {
2951         // set the menu of the main frame window
2952         UINT idMenu = IDR_MAINFRAME;
2953         CMergeApp *pApp = dynamic_cast<CMergeApp *> (AfxGetApp());
2954         CMainFrame * pMainFrame = dynamic_cast<CMainFrame *> ((CFrameWnd*)pApp->m_pMainWnd);
2955         HMENU hNewDefaultMenu = pMainFrame->NewDefaultMenu(idMenu);
2956         HMENU hNewMergeMenu = pMainFrame->NewMergeViewMenu();
2957         HMENU hNewImgMergeMenu = pMainFrame->NewImgMergeViewMenu();
2958         HMENU hNewDirMenu = pMainFrame->NewDirViewMenu();
2959         if (hNewDefaultMenu && hNewMergeMenu && hNewDirMenu)
2960         {
2961                 // Note : for Windows98 compatibility, use FromHandle and not Attach/Detach
2962                 CMenu * pNewDefaultMenu = CMenu::FromHandle(hNewDefaultMenu);
2963                 CMenu * pNewMergeMenu = CMenu::FromHandle(hNewMergeMenu);
2964                 CMenu * pNewImgMergeMenu = CMenu::FromHandle(hNewImgMergeMenu);
2965                 CMenu * pNewDirMenu = CMenu::FromHandle(hNewDirMenu);
2966
2967                 CWnd *pFrame = CWnd::FromHandle(::GetWindow(pMainFrame->m_hWndMDIClient, GW_CHILD));
2968                 while (pFrame)
2969                 {
2970                         if (pFrame->IsKindOf(RUNTIME_CLASS(CChildFrame)))
2971                                 static_cast<CChildFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2972                         if (pFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2973                                 static_cast<CHexMergeFrame *>(pFrame)->SetSharedMenu(hNewMergeMenu);
2974                         if (pFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2975                                 static_cast<CImgMergeFrame *>(pFrame)->SetSharedMenu(hNewImgMergeMenu);
2976                         else if (pFrame->IsKindOf(RUNTIME_CLASS(COpenFrame)))
2977                                 static_cast<COpenFrame *>(pFrame)->SetSharedMenu(hNewDefaultMenu);
2978                         else if (pFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2979                                 static_cast<CDirFrame *>(pFrame)->SetSharedMenu(hNewDirMenu);
2980                         pFrame = pFrame->GetNextWindow();
2981                 }
2982
2983                 CFrameWnd *pActiveFrame = pMainFrame->GetActiveFrame();
2984                 if (pActiveFrame)
2985                 {
2986                         if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CChildFrame)))
2987                                 pMainFrame->MDISetMenu(pNewMergeMenu, NULL);
2988                         else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CHexMergeFrame)))
2989                                 pMainFrame->MDISetMenu(pNewMergeMenu, NULL);
2990                         else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CImgMergeFrame)))
2991                                 pMainFrame->MDISetMenu(pNewImgMergeMenu, NULL);
2992                         else if (pActiveFrame->IsKindOf(RUNTIME_CLASS(CDirFrame)))
2993                                 pMainFrame->MDISetMenu(pNewDirMenu, NULL);
2994                         else
2995                                 pMainFrame->MDISetMenu(pNewDefaultMenu, NULL);
2996                 }
2997                 else
2998                         pMainFrame->MDISetMenu(pNewDefaultMenu, NULL);
2999
3000                 // Don't delete the old menu
3001                 // There is a bug in BCMenu or in Windows98 : the new menu does not
3002                 // appear correctly if we destroy the old one
3003                 //                      if (pOldDefaultMenu)
3004                 //                              pOldDefaultMenu->DestroyMenu();
3005                 //                      if (pOldMergeMenu)
3006                 //                              pOldMergeMenu->DestroyMenu();
3007                 //                      if (pOldDirMenu)
3008                 //                              pOldDirMenu->DestroyMenu();
3009
3010                 // m_hMenuDefault is used to redraw the main menu when we close a child frame
3011                 // if this child frame had a different menu
3012                 pMainFrame->m_hMenuDefault = hNewDefaultMenu;
3013                 pApp->m_pOpenTemplate->m_hMenuShared = hNewDefaultMenu;
3014                 pApp->m_pDiffTemplate->m_hMenuShared = hNewMergeMenu;
3015                 pApp->m_pDirTemplate->m_hMenuShared = hNewDirMenu;
3016
3017                 // force redrawing the menu bar
3018                 pMainFrame->DrawMenuBar();
3019         }
3020 }
3021
3022 void CMainFrame::UpdateDocTitle()
3023 {
3024         CDocManager* pDocManager = AfxGetApp()->m_pDocManager;
3025         POSITION posTemplate = pDocManager->GetFirstDocTemplatePosition();
3026         ASSERT(posTemplate != NULL);
3027
3028         while (posTemplate != NULL)
3029         {
3030                 CDocTemplate* pTemplate = pDocManager->GetNextDocTemplate(posTemplate);
3031
3032                 ASSERT(pTemplate != NULL);
3033
3034                 POSITION pos = pTemplate->GetFirstDocPosition();
3035                 CDocument* pDoc;
3036
3037                 while (pos != NULL)
3038                 {
3039                         pDoc = pTemplate->GetNextDoc(pos);
3040                         pDoc->SetTitle(NULL);
3041                         ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->OnUpdateFrameTitle(TRUE);
3042                 }
3043         }
3044 }