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 HexMergeFrm.cpp
10 * @brief Implementation file for CHexMergeFrame
15 #include "HexMergeFrm.h"
17 #include "HexMergeDoc.h"
18 #include "HexMergeView.h"
19 #include "OptionsDef.h"
20 #include "OptionsMgr.h"
26 #define SWAPPARAMS_IF(c, a, b) (c ? a : b), (c ? b : a)
28 /////////////////////////////////////////////////////////////////////////////
31 IMPLEMENT_DYNCREATE(CHexMergeFrame, CMergeFrameCommon)
33 BEGIN_MESSAGE_MAP(CHexMergeFrame, CMergeFrameCommon)
34 //{{AFX_MSG_MAP(CHexMergeFrame)
38 ON_MESSAGE_VOID(WM_IDLEUPDATECMDUI, OnIdleUpdateCmdUI)
39 ON_MESSAGE(MSG_STORE_PANESIZES, OnStorePaneSizes)
41 ON_COMMAND(ID_VIEW_SPLITVERTICALLY, OnViewSplitVertically)
42 ON_UPDATE_COMMAND_UI(ID_VIEW_SPLITVERTICALLY, OnUpdateViewSplitVertically)
43 //ON_UPDATE_COMMAND_UI(ID_VIEW_DETAIL_BAR, OnUpdateControlBarMenu)
44 //ON_COMMAND_EX(ID_VIEW_DETAIL_BAR, OnBarCheck)
45 //ON_UPDATE_COMMAND_UI(ID_VIEW_LOCATION_BAR, OnUpdateControlBarMenu)
46 //ON_COMMAND_EX(ID_VIEW_LOCATION_BAR, OnBarCheck)
51 * @brief Statusbar pane indexes
63 /////////////////////////////////////////////////////////////////////////////
64 // CHexMergeFrame construction/destruction
66 CHexMergeFrame::CHexMergeFrame()
67 : CMergeFrameCommon(IDI_EQUALBINARY, IDI_BINARYDIFF)
68 , m_HScrollInfo{}, m_VScrollInfo{}
73 CHexMergeFrame::~CHexMergeFrame()
78 * @brief Create a status bar to be associated with a heksedit control
80 void CHexMergeFrame::CreateHexWndStatusBar(CStatusBar &wndStatusBar, CWnd *pwndPane)
82 const int lpx = CClientDC(this).GetDeviceCaps(LOGPIXELSX);
83 auto pointToPixel = [lpx](int point) { return MulDiv(point, lpx, 72); };
84 wndStatusBar.Create(pwndPane, WS_CHILD|WS_VISIBLE);
85 wndStatusBar.SetIndicators(0, 3);
86 wndStatusBar.SetPaneInfo(0, 0, SBPS_STRETCH, 0);
87 wndStatusBar.SetPaneInfo(1, 0, 0, pointToPixel(60));
88 wndStatusBar.SetPaneInfo(2, 0, 0, pointToPixel(60));
89 wndStatusBar.SetParent(this);
90 wndStatusBar.SetWindowPos(&wndBottom, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
94 * @brief Create the splitter, the filename bar, the status bar, and the two views
96 BOOL CHexMergeFrame::OnCreateClient( LPCREATESTRUCT /*lpcs*/,
97 CCreateContext* pContext)
99 m_pMergeDoc = dynamic_cast<CHexMergeDoc *>(pContext->m_pCurrentDoc);
100 bool bSplitVert = !GetOptionsMgr()->GetBool(OPT_SPLIT_HORIZONTALLY);
102 // create a splitter with 1 row, 2 columns
103 if (!m_wndSplitter.CreateStatic(this, SWAPPARAMS_IF(bSplitVert, 1, m_pMergeDoc->m_nBuffers), WS_CHILD | WS_VISIBLE | WS_HSCROLL | WS_VSCROLL) )
105 TRACE0("Failed to CreateStaticSplitter\n");
110 for (nPane = 0; nPane < m_pMergeDoc->m_nBuffers; nPane++)
112 if (!m_wndSplitter.CreateView(SWAPPARAMS_IF(bSplitVert, 0, nPane),
113 RUNTIME_CLASS(CHexMergeView), CSize(-1, 200), pContext))
115 TRACE0("Failed to create first pane\n");
120 m_wndSplitter.ResizablePanes(true);
121 m_wndSplitter.AutoResizePanes(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
123 // Merge frame has a header bar at top
124 if (!m_wndFilePathBar.Create(this))
126 TRACE0("Failed to create dialog bar\n");
127 return FALSE; // fail to create
130 m_wndFilePathBar.SetPaneCount(m_pMergeDoc->m_nBuffers);
131 m_wndFilePathBar.SetOnSetFocusCallback([&](int pane) {
134 m_wndFilePathBar.SetOnCaptionChangedCallback([&](int pane, const String& sText) {
135 m_pMergeDoc->SetDescription(pane ,sText);
136 m_pMergeDoc->UpdateHeaderPath(pane);
138 m_wndFilePathBar.SetOnFileSelectedCallback([&](int pane, const String& sFilepath) {
139 m_pMergeDoc->ChangeFile(pane, sFilepath);
142 // Set filename bars inactive so colors get initialized
143 for (nPane = 0; nPane < m_pMergeDoc->m_nBuffers; nPane++)
144 m_wndFilePathBar.SetActive(nPane, false);
146 CHexMergeView *pView[3];
147 for (nPane = 0; nPane < m_pMergeDoc->m_nBuffers; nPane++)
148 pView[nPane] = static_cast<CHexMergeView *>(m_wndSplitter.GetPane(SWAPPARAMS_IF(bSplitVert, 0, nPane)));
150 for (nPane = 0; nPane < m_pMergeDoc->m_nBuffers; nPane++)
151 CreateHexWndStatusBar(m_wndStatusBar[nPane], pView[nPane]);
152 CSize size = m_wndStatusBar[0].CalcFixedLayout(TRUE, TRUE);
153 m_rectBorder.bottom = size.cy;
155 // get the IHexEditorWindow interfaces
156 IHexEditorWindow *pif[3];
157 for (nPane = 0; nPane < m_pMergeDoc->m_nBuffers; nPane++)
159 pif[nPane] = reinterpret_cast<IHexEditorWindow *>(
160 ::GetWindowLongPtr(pView[nPane]->m_hWnd, GWLP_USERDATA));
163 // tell the heksedit controls about each other
164 if (m_pMergeDoc->m_nBuffers == 2)
166 pif[0]->set_sibling(pif[1]);
167 pif[1]->set_sibling(pif[0]);
168 pif[1]->share_undorecords(pif[0]->share_undorecords(nullptr));
170 else if (m_pMergeDoc->m_nBuffers > 2)
172 pif[0]->set_sibling(pif[1]);
173 pif[1]->set_sibling2(pif[0], pif[2]);
174 pif[2]->set_sibling(pif[1]);
175 pif[2]->share_undorecords(
176 pif[1]->share_undorecords(
177 pif[0]->share_undorecords(nullptr)));
180 // tell merge doc about these views
181 m_pMergeDoc = dynamic_cast<CHexMergeDoc *>(pContext->m_pCurrentDoc);
182 m_pMergeDoc->SetMergeViews(pView);
183 m_pMergeDoc->RefreshOptions();
188 /////////////////////////////////////////////////////////////////////////////
189 // CHexMergeFrame message handlers
191 void CHexMergeFrame::ActivateFrame(int nCmdShow)
193 CMergeFrameCommon::ActivateFrame(nCmdShow);
197 * @brief Save the window's position, free related resources, and destroy the window
199 BOOL CHexMergeFrame::DestroyWindow()
204 CFrameWnd* pParentFrame = GetParentFrame();
205 BOOL result = CMergeFrameCommon::DestroyWindow();
207 pParentFrame->OnUpdateFrameTitle(FALSE);
212 * @brief Save coordinates of the frame, splitters, and bars
214 * @note Do not save the maximized/restored state here. We are interested
215 * in the state of the active frame, and maybe this frame is not active
217 void CHexMergeFrame::SavePosition()
221 void CHexMergeFrame::SaveActivePane()
223 if (CWnd* pLeft = m_wndSplitter.GetPane(0, 0))
225 GetOptionsMgr()->SaveOption(OPT_ACTIVE_PANE, GetActivePane());
229 void CHexMergeFrame::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* pDeactivateWnd)
231 CMDIChildWnd::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
233 CHexMergeDoc *pDoc = GetMergeDoc();
234 if (bActivate && pDoc != nullptr)
235 this->GetParentFrame()->PostMessage(WM_USER+1);
239 void CHexMergeFrame::OnSize(UINT nType, int cx, int cy)
241 CMDIChildWnd::OnSize(nType, cx, cy);
245 /// update splitting position for panels 1/2 and headerbar and statusbar
246 void CHexMergeFrame::UpdateHeaderSizes()
248 if (!m_wndSplitter.m_hWnd || !m_wndFilePathBar.m_hWnd)
251 GetClientRect(&rcFrame);
253 int nPaneCount = m_wndSplitter.GetColumnCount();
256 for (int pane = 0; pane < nPaneCount; pane++)
258 m_wndSplitter.GetColumnInfo(pane, w[pane], wmin);
259 if (w[pane]<1) w[pane]=1; // Perry 2003-01-22 (I don't know why this happens)
264 nPaneCount = m_wndSplitter.GetRowCount();
265 for (int pane = 0; pane < nPaneCount; pane++)
267 w[pane] = rcFrame.Width() / nPaneCount;
268 if (w[pane]<1) w[pane]=1; // Perry 2003-01-22 (I don't know why this happens)
272 if (!std::equal(m_nLastSplitPos, m_nLastSplitPos + nPaneCount - 1, w))
274 std::copy_n(w, nPaneCount - 1, m_nLastSplitPos);
276 // resize controls in header dialog bar
277 m_wndFilePathBar.Resize(w);
279 rc.top = rc.bottom - m_rectBorder.bottom;
281 for (int pane = 0; pane < nPaneCount; pane++)
283 if (pane < nPaneCount - 1)
284 rc.right += w[pane] + 6;
286 rc.right = rcFrame.right;
287 m_wndStatusBar[pane].MoveWindow(&rc);
293 void CHexMergeFrame::UpdateAutoPaneResize()
295 m_wndSplitter.AutoResizePanes(GetOptionsMgr()->GetBool(OPT_RESIZE_PANES));
298 void CHexMergeFrame::UpdateSplitter()
300 m_wndSplitter.RecalcLayout();
303 int CHexMergeFrame::GetActivePane()
306 if (m_wndSplitter.GetColumnCount() > 1)
307 m_wndSplitter.GetActivePane(nullptr, &nPane);
309 m_wndSplitter.GetActivePane(&nPane, nullptr);
313 void CHexMergeFrame::SetActivePane(int nPane)
315 if (m_wndSplitter.GetColumnCount() > 1)
316 m_wndSplitter.SetActivePane(0, nPane);
318 m_wndSplitter.SetActivePane(nPane, 0);
322 * @brief Synchronize control and status bar placements with splitter position,
323 * update mod indicators, synchronize scrollbars
325 void CHexMergeFrame::OnIdleUpdateCmdUI()
327 if (IsWindowVisible())
332 int nColumns = m_wndSplitter.GetColumnCount();
333 CHexMergeView *pView[3] = {0};
334 for (pane = 0; pane < nColumns; ++pane)
335 pView[pane] = static_cast<CHexMergeView *>(m_wndSplitter.GetPane(0, pane));
337 // Update mod indicators
340 for (pane = 0; pane < nColumns; ++pane)
342 if (m_wndFilePathBar.GetDlgItemText(IDC_STATIC_TITLE_PANE0 + pane, ind, 2))
343 if (pView[pane]->GetModified() ? ind[0] != _T('*') : ind[0] == _T('*'))
344 m_pMergeDoc->UpdateHeaderPath(pane);
347 // Synchronize scrollbars
348 SCROLLINFO si, siView[3];
349 // Synchronize horizontal scrollbars
350 pView[0]->GetScrollInfo(SB_HORZ, &si, SIF_ALL | SIF_DISABLENOSCROLL);
351 m_HScrollInfo[0] = si;
352 for (pane = 1; pane < nColumns; ++pane)
355 pView[pane]->GetScrollInfo(SB_HORZ, &siCur, SIF_ALL | SIF_DISABLENOSCROLL);
356 siView[pane] = siCur;
357 if (si.nMin > siCur.nMin)
358 si.nMin = siCur.nMin;
359 if (si.nPage < siCur.nPage)
360 si.nPage = siCur.nPage;
361 if (si.nMax < siCur.nMax)
362 si.nMax = siCur.nMax;
363 if (memcmp(&siCur, &m_HScrollInfo[pane], sizeof si))
365 si.nPos = siCur.nPos;
366 si.nTrackPos = siCur.nTrackPos;
368 m_HScrollInfo[pane] = siCur;
370 for (pane = 0; pane < nColumns; ++pane)
372 if (memcmp(&si, &siView[pane], sizeof si))
374 pView[pane]->SetScrollInfo(SB_HORZ, &si);
375 pView[pane]->SendMessage(WM_HSCROLL, MAKEWPARAM(SB_THUMBTRACK, si.nTrackPos));
378 for (pane = 0; pane < nColumns; ++pane)
379 m_wndSplitter.GetScrollBarCtrl(pView[pane], SB_HORZ)->SetScrollInfo(&si);
381 // Synchronize vertical scrollbars
382 pView[0]->GetScrollInfo(SB_VERT, &si, SIF_ALL | SIF_DISABLENOSCROLL);
383 m_VScrollInfo[0] = si;
384 for (pane = 1; pane < nColumns; ++pane)
387 pView[pane]->GetScrollInfo(SB_VERT, &siCur, SIF_ALL | SIF_DISABLENOSCROLL);
388 siView[pane] = siCur;
389 if (si.nMin > siCur.nMin)
390 si.nMin = siCur.nMin;
391 if (si.nMax < siCur.nMax)
392 si.nMax = siCur.nMax;
393 if (memcmp(&siCur, &m_VScrollInfo[pane], sizeof si))
395 si.nPos = siCur.nPos;
396 si.nTrackPos = siCur.nTrackPos;
398 m_VScrollInfo[pane] = siCur;
400 for (pane = 0; pane < nColumns; ++pane)
402 if (memcmp(&si, &siView[pane], sizeof si))
404 pView[pane]->SetScrollInfo(SB_VERT, &si);
405 pView[pane]->SendMessage(WM_VSCROLL, MAKEWPARAM(SB_THUMBTRACK, si.nTrackPos));
409 m_wndSplitter.GetScrollBarCtrl(pView[nColumns - 1], SB_VERT)->SetScrollInfo(&si);
411 CMDIChildWnd::OnIdleUpdateCmdUI();
415 * @brief Update any resources necessary after a GUI language change
417 void CHexMergeFrame::UpdateResources()
422 * @brief Save pane sizes and positions when one of panes requests it.
424 LRESULT CHexMergeFrame::OnStorePaneSizes(WPARAM wParam, LPARAM lParam)
431 * @brief Split panes vertically
433 void CHexMergeFrame::OnViewSplitVertically()
435 bool bSplitVertically = (m_wndSplitter.GetColumnCount() != 1);
436 bSplitVertically = !bSplitVertically; // toggle
437 GetOptionsMgr()->SaveOption(OPT_SPLIT_HORIZONTALLY, !bSplitVertically);
438 m_wndSplitter.FlipSplit();
442 * @brief Update "Split Vertically" UI items
444 void CHexMergeFrame::OnUpdateViewSplitVertically(CCmdUI* pCmdUI)
446 pCmdUI->Enable(TRUE);
447 pCmdUI->SetCheck((m_wndSplitter.GetColumnCount() != 1));