1 /////////////////////////////////////////////////////////////////////////////
2 // WinMerge: an interactive diff/merge utility
3 // Copyright (C) 1997-2000 Thingamahoochie Software
5 // SPDX-License-Identifier: GPL-2.0-or-later
6 /////////////////////////////////////////////////////////////////////////////
8 * @file MergeEditFrm.cpp
10 * @brief Implementation file for CMergeEditFrame
15 #include "MergeEditFrm.h"
19 #include "MergeEditView.h"
20 #include "LocationView.h"
21 #include "DiffViewBar.h"
22 #include "OptionsDef.h"
23 #include "OptionsMgr.h"
29 #define SWAPPARAMS_IF(c, a, b) (c ? a : b), (c ? b : a)
31 /////////////////////////////////////////////////////////////////////////////
34 IMPLEMENT_DYNCREATE(CMergeEditFrame, CMergeFrameCommon)
36 BEGIN_MESSAGE_MAP(CMergeEditFrame, CMergeFrameCommon)
37 //{{AFX_MSG_MAP(CMergeEditFrame)
43 ON_MESSAGE_VOID(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
44 ON_MESSAGE(MSG_STORE_PANESIZES, OnStorePaneSizes)
46 ON_UPDATE_COMMAND_UI(ID_VIEW_DETAIL_BAR, OnUpdateControlBarMenu)
47 ON_COMMAND_EX(ID_VIEW_DETAIL_BAR, OnBarCheck)
48 ON_UPDATE_COMMAND_UI(ID_VIEW_LOCATION_BAR, OnUpdateControlBarMenu)
49 ON_COMMAND_EX(ID_VIEW_LOCATION_BAR, OnBarCheck)
50 ON_COMMAND(ID_VIEW_SPLITVERTICALLY, OnViewSplitVertically)
51 ON_UPDATE_COMMAND_UI(ID_VIEW_SPLITVERTICALLY, OnUpdateViewSplitVertically)
55 constexpr UINT_PTR IDT_SAVEPOSITION = 2;
57 /////////////////////////////////////////////////////////////////////////////
58 // CMergeEditFrame construction/destruction
63 CMergeEditFrame::CMergeEditFrame()
64 : CMergeFrameCommon(IDI_EQUALTEXTFILE, IDI_NOTEQUALTEXTFILE)
65 , m_pwndDetailMergeEditSplitterView(nullptr)
73 CMergeEditFrame::~CMergeEditFrame()
77 BOOL CMergeEditFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,
78 CCreateContext* pContext)
80 m_wndSplitter.HideBorders(true);
81 m_wndSplitter.Create(this, 2, 1, CSize(1, 1), pContext, WS_CHILD | WS_VISIBLE | 1/*SPLS_DYNAMIC_SPLIT*/);
83 // Merge frame has also a dockable bar at the very left
84 // This is not the client area, but we create it now because we want
85 // to use the CCreateContext
86 String sCaption = _("Location Pane");
87 if (!m_wndLocationBar.Create(this, sCaption.c_str(), WS_CHILD | WS_VISIBLE, ID_VIEW_LOCATION_BAR))
89 TRACE0("Failed to create LocationBar\n");
93 CLocationView *pLocationView = new CLocationView;
94 DWORD dwStyle = AFX_WS_DEFAULT_VIEW & ~WS_BORDER;
95 pLocationView->Create(nullptr, nullptr, dwStyle, CRect(0,0,40,100), &m_wndLocationBar, 152, pContext);
97 // Merge frame has also a dockable bar at the very bottom
98 // This is not the client area, but we create it now because we want
99 // to use the CCreateContext
100 sCaption = _("Diff Pane");
101 if (!m_wndDetailBar.Create(this, sCaption.c_str(), WS_CHILD | WS_VISIBLE, ID_VIEW_DETAIL_BAR))
103 TRACE0("Failed to create DiffViewBar\n");
107 m_pwndDetailMergeEditSplitterView = new CMergeEditSplitterView();
108 m_pwndDetailMergeEditSplitterView->m_bDetailView = true;
109 m_pwndDetailMergeEditSplitterView->Create(nullptr, nullptr, dwStyle, CRect(0,0,1,1), &m_wndDetailBar, ID_VIEW_DETAIL_BAR+1, pContext);
111 // tell merge doc about these views
112 m_pMergeDoc = dynamic_cast<CMergeDoc *>(pContext->m_pCurrentDoc);
113 m_pMergeDoc->ForEachView([&](auto& pView) {
114 pView->SetStatusInterface(m_wndStatusBar.GetIMergeEditStatus(pView->m_nThisPane));
116 m_pMergeDoc->SetLocationView(pLocationView);
118 m_wndFilePathBar.SetPaneCount(m_pMergeDoc->m_nBuffers);
119 m_wndFilePathBar.SetOnSetFocusCallback([&](int pane) {
120 m_pMergeDoc->GetView(0, pane)->SetActivePane();
122 m_wndFilePathBar.SetOnCaptionChangedCallback([&](int pane, const String& sText) {
123 m_pMergeDoc->SetDescription(pane, sText);
124 m_pMergeDoc->UpdateHeaderPath(pane);
126 m_wndFilePathBar.SetOnFileSelectedCallback([&](int pane, const String& sFilepath) {
127 m_pMergeDoc->ChangeFile(pane, sFilepath);
129 m_wndStatusBar.SetPaneCount(m_pMergeDoc->m_nBuffers);
131 // Set frame window handles so we can post stage changes back
132 pLocationView->SetFrameHwnd(GetSafeHwnd());
133 m_wndLocationBar.SetFrameHwnd(GetSafeHwnd());
134 m_wndDetailBar.SetFrameHwnd(GetSafeHwnd());
139 /////////////////////////////////////////////////////////////////////////////
140 // CMergeEditFrame message handlers
143 * @brief Create the child frame, the splitter, the filename bar, the status bar,
144 * the diff dockable bar, and the four views
146 * @note the panels layout is
167 int CMergeEditFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
169 if (__super::OnCreate(lpCreateStruct) == -1)
172 EnableDocking(CBRS_ALIGN_TOP|CBRS_ALIGN_BOTTOM|CBRS_ALIGN_LEFT|CBRS_ALIGN_RIGHT);
176 // Merge frame has a header bar at top
177 if (!m_wndFilePathBar.Create(this))
179 TRACE0("Failed to create dialog bar\n");
180 return -1; // fail to create
183 // Set filename bars inactive so colors get initialized
184 m_wndFilePathBar.SetActive(0, false);
185 m_wndFilePathBar.SetActive(1, false);
186 m_wndFilePathBar.SetActive(2, false);
188 // Merge frame also has a dockable bar at the very left
189 // created in OnCreateClient
190 m_wndLocationBar.SetBarStyle(m_wndLocationBar.GetBarStyle() |
191 CBRS_SIZE_DYNAMIC | CBRS_ALIGN_LEFT);
192 m_wndLocationBar.EnableDocking(CBRS_ALIGN_LEFT | CBRS_ALIGN_RIGHT);
193 CRect rc{ 0, 0, 0, 0 };
194 DockControlBar(&m_wndLocationBar, AFX_IDW_DOCKBAR_LEFT, &rc);
196 // Merge frame also has a dockable bar at the very bottom
197 // created in OnCreateClient
198 m_wndDetailBar.SetBarStyle(m_wndDetailBar.GetBarStyle() |
199 CBRS_SIZE_DYNAMIC | CBRS_ALIGN_TOP);
200 m_wndDetailBar.EnableDocking(CBRS_ALIGN_TOP | CBRS_ALIGN_BOTTOM);
201 DockControlBar(&m_wndDetailBar, AFX_IDW_DOCKBAR_BOTTOM, &rc);
203 // Merge frame also has a status bar at bottom,
204 // m_wndDetailBar is below, so we create this bar after m_wndDetailBar
205 if (!m_wndStatusBar.Create(this))
207 TRACE0("Failed to create status bar\n");
208 return -1; // fail to create
211 // load docking positions and sizes
212 CDockState pDockState;
213 pDockState.LoadState(_T("Settings"));
214 if (EnsureValidDockState(pDockState)) // checks for valid so won't ASSERT
215 SetDockState(pDockState);
216 // for the dimensions of the diff and location pane, use the CSizingControlBar loader
217 m_wndLocationBar.LoadState(_T("Settings"));
218 m_wndDetailBar.LoadState(_T("Settings"));
223 BOOL CMergeEditFrame::OnBarCheck(UINT nID)
225 BOOL result = __super::OnBarCheck(nID);
226 // Fix for osdn.net #42862
227 if (nID == ID_VIEW_DETAIL_BAR && m_wndDetailBar.IsWindowVisible())
229 int nDiff = m_pMergeDoc->GetCurrentDiff();
230 m_pMergeDoc->ForEachView ([nDiff](auto& pView) { if (pView->m_bDetailView) pView->OnDisplayDiff(nDiff); });
236 * @brief We must use this function before a call to SetDockState
238 * @note Without this, SetDockState will assert or crash if a bar from the
239 * CDockState is missing in the current CMergeEditFrame.
240 * The bars are identified with their ID. This means the missing bar bug is triggered
241 * when we run WinMerge after changing the ID of a bar.
243 bool CMergeEditFrame::EnsureValidDockState(CDockState& state)
245 for (int i = (int) state.m_arrBarInfo.GetSize()-1 ; i >= 0; i--)
247 bool barIsCorrect = true;
248 CControlBarInfo* pInfo = (CControlBarInfo*)state.m_arrBarInfo[i];
249 if (pInfo == nullptr)
250 barIsCorrect = false;
253 if (! pInfo->m_bFloating)
255 pInfo->m_pBar = GetControlBar(pInfo->m_nBarID);
256 if (pInfo->m_pBar == nullptr)
257 barIsCorrect = false; //toolbar id's probably changed
262 state.m_arrBarInfo.RemoveAt(i);
267 void CMergeEditFrame::ActivateFrame(int nCmdShow)
269 CMergeFrameCommon::ActivateFrame(nCmdShow);
272 BOOL CMergeEditFrame::DestroyWindow()
277 CFrameWnd* pParentFrame = GetParentFrame();
278 BOOL result = CMergeFrameCommon::DestroyWindow();
280 pParentFrame->OnUpdateFrameTitle(FALSE);
285 * @brief Save coordinates of the frame, splitters, and bars
287 * @note Do not save the maximized/restored state here. We are interested
288 * in the state of the active frame, and maybe this frame is not active
290 void CMergeEditFrame::SavePosition()
292 // save the bars layout
293 // save docking positions and sizes
294 CDockState m_pDockState;
295 GetDockState(m_pDockState);
296 m_pDockState.SaveState(_T("Settings"));
297 // for the dimensions of the diff pane, use the CSizingControlBar save
298 m_wndLocationBar.SaveState(_T("Settings"));
299 m_wndDetailBar.SaveState(_T("Settings"));
302 void CMergeEditFrame::SaveActivePane()
304 for (int iRowParent = 0; iRowParent < m_wndSplitter.GetRowCount(); ++iRowParent)
307 auto& splitterWnd = static_cast<CMergeEditSplitterView*>(m_wndSplitter.GetPane(iRowParent, 0))->m_wndSplitter;
308 splitterWnd.GetActivePane(&iRow, &iCol);
309 if (iRow >= 0 || iCol >= 0)
310 GetOptionsMgr()->SaveOption(OPT_ACTIVE_PANE, max(iRow, iCol));
314 void CMergeEditFrame::OnClose()
316 // clean up pointers.
317 CMergeFrameCommon::OnClose();
320 /// update splitting position for panels 1/2 and headerbar and statusbar
321 void CMergeEditFrame::UpdateHeaderSizes()
323 if(!::IsWindow(m_wndFilePathBar.m_hWnd) || !::IsWindow(m_wndSplitter.m_hWnd))
328 CMergeDoc * pDoc = GetMergeDoc();
329 auto& wndSplitter = GetMergeEditSplitterWnd(0);
330 if (wndSplitter.GetColumnCount() > 1)
332 for (pane = 0; pane < wndSplitter.GetColumnCount(); pane++)
335 wndSplitter.GetColumnInfo(pane, w[pane], wmin);
336 if (w[pane]<1) w[pane]=1; // Perry 2003-01-22 (I don't know why this happens)
342 wndSplitter.GetColumnInfo(0, w2, wmin);
343 for (pane = 0; pane < pDoc->m_nBuffers; pane++)
344 w[pane] = (w2 - 4 * pDoc->m_nBuffers) / pDoc->m_nBuffers;
347 if (!std::equal(m_nLastSplitPos, m_nLastSplitPos + pDoc->m_nBuffers - 1, w))
349 std::copy_n(w, pDoc->m_nBuffers - 1, m_nLastSplitPos);
351 // resize controls in header dialog bar
352 m_wndFilePathBar.Resize(w);
354 m_wndStatusBar.Resize(w);
358 IHeaderBar * CMergeEditFrame::GetHeaderInterface()
360 return &m_wndFilePathBar;
363 void CMergeEditFrame::UpdateAutoPaneResize()
365 auto& wndSplitter = GetMergeEditSplitterWnd(0);
366 wndSplitter.AutoResizePanes(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
369 void CMergeEditFrame::UpdateSplitter()
371 for (int iRow = 0; iRow < m_wndSplitter.GetRowCount(); ++iRow)
372 GetMergeEditSplitterWnd(iRow).RecalcLayout();
373 m_wndSplitter.RecalcLayout();
374 m_pwndDetailMergeEditSplitterView->m_wndSplitter.RecalcLayout();
378 * @brief Synchronize control with splitter position,
380 void CMergeEditFrame::OnIdleUpdateCmdUI()
383 CMergeFrameCommon::OnIdleUpdateCmdUI();
386 void CMergeEditFrame::OnTimer(UINT_PTR nIDEvent)
388 if (nIDEvent == IDT_SAVEPOSITION)
391 KillTimer(IDT_SAVEPOSITION);
397 CMergeFrameCommon::OnTimer(nIDEvent);
400 void CMergeEditFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
402 CMergeFrameCommon::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
404 CMergeDoc *pDoc = GetMergeDoc();
405 if (bActivate && pDoc != nullptr)
406 this->GetParentFrame()->PostMessage(WM_USER+1);
411 * @brief Split panes vertically
413 void CMergeEditFrame::OnViewSplitVertically()
415 auto& wndSplitter = GetMergeEditSplitterWnd(0);
416 bool bSplitVertically = (wndSplitter.GetColumnCount() != 1);
417 bSplitVertically = !bSplitVertically; // toggle
418 GetOptionsMgr()->SaveOption(OPT_SPLIT_HORIZONTALLY, !bSplitVertically);
419 for (int iRow = 0; iRow < m_wndSplitter.GetRowCount(); ++iRow)
420 GetMergeEditSplitterWnd(iRow).FlipSplit();
421 m_pwndDetailMergeEditSplitterView->m_wndSplitter.FlipSplit();
425 * @brief Update "Split Vertically" UI items
427 void CMergeEditFrame::OnUpdateViewSplitVertically(CCmdUI* pCmdUI)
429 auto& wndSplitter = GetMergeEditSplitterWnd(0);
430 pCmdUI->Enable(TRUE);
431 pCmdUI->SetCheck((wndSplitter.GetColumnCount() != 1));
435 * @brief Update any resources necessary after a GUI language change
437 void CMergeEditFrame::UpdateResources()
439 m_wndStatusBar.UpdateResources();
440 m_wndLocationBar.UpdateResources();
441 m_wndDetailBar.UpdateResources();
445 * @brief Save pane sizes and positions when one of panes requests it.
447 LRESULT CMergeEditFrame::OnStorePaneSizes(WPARAM wParam, LPARAM lParam)
449 KillTimer(IDT_SAVEPOSITION);
450 SetTimer(IDT_SAVEPOSITION, 300, nullptr);
454 void CMergeEditFrame::OnSize(UINT nType, int cx, int cy)
456 __super::OnSize(nType, cx, cy);