Fix typos
[TortoiseGit.git] / src / TortoiseProc / RevisionGraph / RevisionGraphDlg.cpp
blob2c909ec6763914e439a710d17377c77765f3b738
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2012, 2018, 2021 - TortoiseSVN
4 // Copyright (C) 2012-2016, 2018-2020, 2023-2024 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
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.
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 Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "RevisionGraphDlg.h"
23 #include "Git.h"
24 #include "AppUtils.h"
25 #include "StringUtils.h"
26 #include "TempFile.h"
27 #include "UnicodeUtils.h"
28 #include "TGitPath.h"
29 #include "RevGraphFilterDlg.h"
30 #include "DPIAware.h"
31 #include "LogDlgFilter.h"
32 #include "GitLogList.h"
34 #ifdef _DEBUG
35 #define new DEBUG_NEW
36 #undef THIS_FILE
37 static char THIS_FILE[] = __FILE__;
38 #endif
40 using namespace Gdiplus;
42 const UINT CRevisionGraphDlg::m_FindDialogMessage = RegisterWindowMessage(FINDMSGSTRING);
44 struct CToolBarData
46 WORD wVersion;
47 WORD wWidth;
48 WORD wHeight;
49 WORD wItemCount;
50 //WORD aItems[wItemCount]
52 WORD* items()
54 return reinterpret_cast<WORD*>(this + 1);
58 IMPLEMENT_DYNAMIC(CRevisionGraphDlg, CResizableStandAloneDialog)
59 CRevisionGraphDlg::CRevisionGraphDlg(CWnd* pParent /*=nullptr*/)
60 : CResizableStandAloneDialog(CRevisionGraphDlg::IDD, pParent)
61 , m_bFetchLogs(true)
62 , m_fZoomFactor(DEFAULT_ZOOM)
63 , m_bVisible(true)
65 // GDI+ initialization
67 GdiplusStartupInput input;
68 GdiplusStartup(&m_gdiPlusToken, &input, nullptr);
70 m_szTip[0] = '\0';
71 m_wszTip[0] = L'\0';
74 CRevisionGraphDlg::~CRevisionGraphDlg()
76 // GDI+ cleanup
77 GdiplusShutdown (m_gdiPlusToken);
80 void CRevisionGraphDlg::DoDataExchange(CDataExchange* pDX)
82 CResizableStandAloneDialog::DoDataExchange(pDX);
86 BEGIN_MESSAGE_MAP(CRevisionGraphDlg, CResizableStandAloneDialog)
87 ON_WM_SIZE()
88 ON_WM_LBUTTONDOWN()
90 ON_COMMAND(ID_VIEW_ZOOMIN, OnViewZoomin)
91 ON_COMMAND(ID_VIEW_ZOOMOUT, OnViewZoomout)
92 ON_COMMAND(ID_VIEW_ZOOM100, OnViewZoom100)
93 ON_COMMAND(ID_VIEW_ZOOMHEIGHT, OnViewZoomHeight)
94 ON_COMMAND(ID_VIEW_ZOOMWIDTH, OnViewZoomWidth)
95 ON_COMMAND(ID_VIEW_ZOOMALL, OnViewZoomAll)
96 ON_CBN_SELCHANGE(ID_REVGRAPH_ZOOMCOMBO, OnChangeZoom)
97 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipNotify)
98 ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipNotify)
99 ON_COMMAND(ID_MENUEXIT, OnMenuexit)
100 ON_COMMAND(ID_MENUHELP, OnMenuhelp)
101 ON_COMMAND(ID_FILE_SAVEGRAPHAS, OnFileSavegraphas)
102 ON_COMMAND(ID_VIEW_SHOWOVERVIEW, OnViewShowoverview)
103 ON_COMMAND(ID_VIEW_FILTER, OnViewFilter)
104 ON_COMMAND(ID_VIEW_COMPAREHEADREVISIONS, OnViewCompareheadrevisions)
105 ON_COMMAND(ID_VIEW_COMPAREREVISIONS, OnViewComparerevisions)
106 ON_COMMAND(ID_VIEW_UNIFIEDDIFF, OnViewUnifieddiff)
107 ON_COMMAND(ID_VIEW_UNIFIEDDIFFOFHEADREVISIONS, OnViewUnifieddiffofheadrevisions)
108 ON_WM_WINDOWPOSCHANGING()
109 ON_COMMAND(ID_VIEW_SHOWBRANCHINGSANDMERGES, OnViewShowBranchingsMerges)
110 ON_COMMAND(ID_VIEW_SHOWALLTAGS, OnViewShowAllTags)
111 ON_COMMAND(ID_VIEW_ARROW_POINT_TO_MERGES, OnViewArrowPointToMerges)
112 ON_COMMAND(ID_FIND, OnFind)
113 ON_REGISTERED_MESSAGE(m_FindDialogMessage, OnFindDialogMessage)
114 ON_MESSAGE(WM_DPICHANGED, OnDPIChanged)
115 END_MESSAGE_MAP()
117 BOOL CRevisionGraphDlg::InitializeToolbar()
119 // set up the toolbar
120 // add the tool bar to the dialog
121 m_ToolBar.CreateEx(this, TBSTYLE_FLAT | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT | CBRS_SIZE_DYNAMIC);
123 // LoadToolBar() asserts in debug mode because the bitmap
124 // fails to load. That's not a problem because we load the bitmap
125 // further down manually.
126 // but the assertion is ugly, so we load the button resource here
127 // manually as well and call SetButtons().
128 HINSTANCE hInst = AfxFindResourceHandle(MAKEINTRESOURCE(IDR_REVGRAPHBAR), RT_TOOLBAR);
129 HRSRC hRsrc = ::FindResource(hInst, MAKEINTRESOURCE(IDR_REVGRAPHBAR), RT_TOOLBAR);
130 if (!hRsrc)
131 return FALSE;
133 HGLOBAL hGlobal = LoadResource(hInst, hRsrc);
134 if (!hGlobal)
135 return FALSE;
137 auto pData = reinterpret_cast<CToolBarData*>(LockResource(hGlobal));
138 if (!pData)
139 return FALSE;
140 ASSERT(pData->wVersion == 1);
142 auto pItems = std::make_unique<UINT[]>(pData->wItemCount);
143 for (int i = 0; i < pData->wItemCount; ++i)
144 pItems[i] = pData->items()[i];
145 m_ToolBar.SetButtons(pItems.get(), pData->wItemCount);
147 UnlockResource(hGlobal);
148 FreeResource(hGlobal);
150 m_ToolBar.ShowWindow(SW_SHOW);
151 m_ToolBar.SetBarStyle(CBRS_ALIGN_TOP | CBRS_TOOLTIPS | CBRS_FLYBY);
153 // toolbars aren't true-color without some tweaking:
155 CImageList cImageList;
156 CBitmap cBitmap;
157 BITMAP bmBitmap;
159 // load the toolbar with the dimensions of the bitmap itself
160 cBitmap.Attach(LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_REVGRAPHBAR),
161 IMAGE_BITMAP, 0, 0,
162 LR_DEFAULTSIZE | LR_CREATEDIBSECTION));
163 cBitmap.GetBitmap(&bmBitmap);
164 cBitmap.DeleteObject();
165 // now load the toolbar again, but this time with the dpi-scaled dimensions
166 // note: we could just load it once and then resize the bitmap, but
167 // that's not faster. So loading it again is what we do.
168 cBitmap.Attach(LoadImage(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_REVGRAPHBAR),
169 IMAGE_BITMAP,
170 CDPIAware::Instance().ScaleX(GetSafeHwnd(), bmBitmap.bmWidth),
171 CDPIAware::Instance().ScaleY(GetSafeHwnd(), bmBitmap.bmHeight),
172 LR_CREATEDIBSECTION));
173 cBitmap.GetBitmap(&bmBitmap);
176 CSize cSize(bmBitmap.bmWidth, bmBitmap.bmHeight);
177 int nNbBtn = cSize.cx / CDPIAware::Instance().ScaleX(GetSafeHwnd(), 20);
178 auto rgb = static_cast<RGBTRIPLE*>(bmBitmap.bmBits);
179 COLORREF rgbMask = RGB(rgb[0].rgbtRed, rgb[0].rgbtGreen, rgb[0].rgbtBlue);
181 cImageList.Create(CDPIAware::Instance().ScaleX(GetSafeHwnd(), 20), cSize.cy, ILC_COLOR32 | ILC_MASK | ILC_HIGHQUALITYSCALE, nNbBtn, 0);
182 cImageList.Add(&cBitmap, rgbMask);
183 // set the sizes of the button and images:
184 // note: buttonX must be 7 pixels more than imageX, and buttonY must be 6 pixels more than imageY.
185 // See the source of SetSizes().
186 m_ToolBar.SetSizes(CSize(CDPIAware::Instance().ScaleX(GetSafeHwnd(), 27), CDPIAware::Instance().ScaleY(GetSafeHwnd(), 26)),
187 CSize(CDPIAware::Instance().ScaleX(GetSafeHwnd(), 20), CDPIAware::Instance().ScaleY(GetSafeHwnd(), 20)));
188 m_ToolBar.SendMessage(TB_SETIMAGELIST, 0, reinterpret_cast<LPARAM>(cImageList.m_hImageList));
189 cImageList.Detach();
190 cBitmap.Detach();
193 RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
195 #define SNAP_WIDTH CDPIAware::Instance().ScaleX(GetSafeHwnd(), 60) // the width of the combo box
196 // set up the ComboBox control as a snap mode select box
197 // First get the index of the placeholders position in the toolbar
198 int zoomComboIndex = 0;
199 while (m_ToolBar.GetItemID(zoomComboIndex) != ID_REVGRAPH_ZOOMCOMBO) ++zoomComboIndex;
201 // next convert that button to a separator and get its position
202 m_ToolBar.SetButtonInfo(zoomComboIndex, ID_REVGRAPH_ZOOMCOMBO, TBBS_SEPARATOR,
203 SNAP_WIDTH);
204 RECT rect;
205 m_ToolBar.GetItemRect(zoomComboIndex, &rect);
207 // expand the rectangle to allow the combo box room to drop down
208 rect.top += CDPIAware::Instance().ScaleY(GetSafeHwnd(), 3);
209 rect.bottom += CDPIAware::Instance().ScaleY(GetSafeHwnd(), 200);
211 // then create the combo box and show it
212 if (!m_ToolBar.m_ZoomCombo.CreateEx(WS_EX_RIGHT, WS_CHILD|WS_VISIBLE|CBS_AUTOHSCROLL|CBS_DROPDOWN,
213 rect, &m_ToolBar, ID_REVGRAPH_ZOOMCOMBO))
215 CTraceToOutputDebugString::Instance()(__FUNCTION__ ": Failed to create combo-box\n");
216 return FALSE;
218 m_ToolBar.m_ZoomCombo.ShowWindow(SW_SHOW);
220 // fill the combo box
222 const wchar_t* texts[] = { L"5%"
223 , L"10%"
224 , L"20%"
225 , L"40%"
226 , L"50%"
227 , L"75%"
228 , L"100%"
229 , L"200%"
230 , nullptr};
232 COMBOBOXEXITEM cbei = { 0 };
233 cbei.mask = CBEIF_TEXT;
235 for (const wchar_t** text = texts; *text; ++text)
237 cbei.pszText = const_cast<wchar_t*>(*text);
238 m_ToolBar.m_ZoomCombo.InsertItem(&cbei);
241 m_ToolBar.m_ZoomCombo.SetCurSel(1);
243 return TRUE;
246 BOOL CRevisionGraphDlg::OnInitDialog()
248 CResizableStandAloneDialog::OnInitDialog();
249 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
250 EnableToolTips();
252 CAppUtils::SetWindowTitle(*this, g_Git.m_CurrentDir);
255 // set up the status bar
256 m_StatusBar.Create(WS_CHILD|WS_VISIBLE|SBT_OWNERDRAW,
257 CRect(0,0,0,0), this, 1);
258 int strPartDim[2] = { CDPIAware::Instance().ScaleX(GetSafeHwnd(), 120), -1 };
259 m_StatusBar.SetParts(2, strPartDim);
261 if (InitializeToolbar() != TRUE)
262 return FALSE;
264 m_pTaskbarList.Release();
265 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
266 m_pTaskbarList = nullptr;
268 m_Graph.SetShowOverview(InitialSetMenu(L"ShowRevGraphOverview", false, ID_VIEW_SHOWOVERVIEW));
269 m_Graph.m_bShowBranchingsMerges = InitialSetMenu(L"ShowRevGraphBranchesMerges", false, ID_VIEW_SHOWBRANCHINGSANDMERGES);
270 m_Graph.m_bShowAllTags = InitialSetMenu(L"ShowRevGraphAllTags", true, ID_VIEW_SHOWALLTAGS);
271 m_Graph.m_bArrowPointToMerges = InitialSetMenu(L"ArrowPointToMerges", false, ID_VIEW_ARROW_POINT_TO_MERGES);
273 // m_hAccel = LoadAccelerators(AfxGetResourceHandle(),MAKEINTRESOURCE(IDR_ACC_REVISIONGRAPH));
276 CRect graphrect = GetGraphRect();
277 m_Graph.Init(this, &graphrect);
278 m_Graph.SetOwner(this);
279 m_Graph.UpdateWindow();
280 DoZoom (DEFAULT_ZOOM);
282 // begin background operation
283 StartWorkerThread();
285 EnableSaveRestore(L"RevisionGraphDlg");
286 // if (GetExplorerHWND())
287 // CenterWindow(CWnd::FromHandle(GetExplorerHWND()));
289 SetTheme(CTheme::Instance().IsDarkTheme());
291 return TRUE; // return TRUE unless you set the focus to a control
294 bool CRevisionGraphDlg::InitialSetMenu(const CString& settingName, bool defaultValue, int nId)
296 CRegDWORD reg = CRegDWORD(L"Software\\TortoiseGit\\" + settingName, defaultValue ? TRUE : FALSE);
297 CMenu* pMenu = GetMenu();
298 if (!pMenu)
299 return static_cast<DWORD>(reg) != FALSE;
300 pMenu->CheckMenuItem(nId, MF_BYCOMMAND | (DWORD(reg) ? MF_CHECKED : 0));
301 int tbstate = m_ToolBar.GetToolBarCtrl().GetState(nId);
302 m_ToolBar.GetToolBarCtrl().SetState(nId, tbstate | (DWORD(reg) ? TBSTATE_CHECKED : 0));
303 return static_cast<DWORD>(reg) != FALSE;
306 bool CRevisionGraphDlg::ToggleSetMenu(const CString& settingName, int nId)
308 CMenu* pMenu = GetMenu();
309 if (!pMenu)
310 return false;
311 int tbstate = m_ToolBar.GetToolBarCtrl().GetState(nId);
312 UINT state = pMenu->GetMenuState(nId, MF_BYCOMMAND);
313 bool ret = false;
314 if (state & MF_CHECKED)
316 pMenu->CheckMenuItem(nId, MF_BYCOMMAND | MF_UNCHECKED);
317 m_ToolBar.GetToolBarCtrl().SetState(nId, tbstate & (~TBSTATE_CHECKED));
318 ret = false;
320 else
322 pMenu->CheckMenuItem(nId, MF_BYCOMMAND | MF_CHECKED);
323 m_ToolBar.GetToolBarCtrl().SetState(nId, tbstate | TBSTATE_CHECKED);
324 ret = true;
327 CRegDWORD reg = CRegDWORD(L"Software\\TortoiseGit\\" + settingName, FALSE);
328 reg = ret;
330 return ret;
333 bool CRevisionGraphDlg::UpdateData()
335 CoInitialize(nullptr);
337 if (!m_Graph.FetchRevisionData (m_Graph.m_sPath, nullptr, m_pTaskbarList, m_hWnd))
339 // only show the error dialog if we're not in hidden mode
340 //if (m_bVisible)
342 // TGitMessageBox( m_hWnd
343 // , // m_Graph.m_state.GetLastErrorMessage()
344 // , L"TortoiseGit"
345 // , MB_ICONERROR);
349 CoUninitialize();
350 m_Graph.PostMessage (CRevisionGraphWnd::WM_WORKERTHREADDONE, 0, 0);
352 return true;
355 void CRevisionGraphDlg::SetTheme(bool bDark)
357 __super::SetTheme(bDark);
358 DarkModeHelper::Instance().AllowDarkModeForWindow(m_Graph.GetSafeHwnd(), bDark);
359 DarkModeHelper::Instance().AllowDarkModeForWindow(m_StatusBar.GetSafeHwnd(), bDark);
360 DarkModeHelper::Instance().AllowDarkModeForWindow(m_ToolBar.GetSafeHwnd(), bDark);
362 SetWindowTheme(m_Graph.GetSafeHwnd(), L"Explorer", nullptr);
363 SetWindowTheme(m_StatusBar.GetSafeHwnd(), L"Explorer", nullptr);
364 SetWindowTheme(m_ToolBar.GetSafeHwnd(), L"Explorer", nullptr);
367 void CRevisionGraphDlg::OnSize(UINT nType, int cx, int cy)
369 __super::OnSize(nType, cx, cy);
370 CRect rect;
371 GetClientRect(&rect);
372 if (IsWindow(m_ToolBar))
374 RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
376 if (IsWindow(m_StatusBar))
378 CRect statusbarrect;
379 m_StatusBar.GetClientRect(&statusbarrect);
380 statusbarrect.top = rect.bottom - statusbarrect.top + statusbarrect.bottom;
381 m_StatusBar.MoveWindow(&statusbarrect);
383 if (IsWindow(m_Graph))
385 m_Graph.MoveWindow (GetGraphRect());
389 BOOL CRevisionGraphDlg::PreTranslateMessage(MSG* pMsg)
391 #define SCROLL_STEP 20
392 if (pMsg->message == WM_KEYDOWN)
394 int pos = 0;
395 switch (pMsg->wParam)
397 case VK_UP:
398 pos = m_Graph.GetScrollPos(SB_VERT);
399 m_Graph.SetScrollPos(SB_VERT, pos - SCROLL_STEP);
400 m_Graph.Invalidate();
401 break;
402 case VK_DOWN:
403 pos = m_Graph.GetScrollPos(SB_VERT);
404 m_Graph.SetScrollPos(SB_VERT, pos + SCROLL_STEP);
405 m_Graph.Invalidate();
406 break;
407 case VK_LEFT:
408 pos = m_Graph.GetScrollPos(SB_HORZ);
409 m_Graph.SetScrollPos(SB_HORZ, pos - SCROLL_STEP);
410 m_Graph.Invalidate();
411 break;
412 case VK_RIGHT:
413 pos = m_Graph.GetScrollPos(SB_HORZ);
414 m_Graph.SetScrollPos(SB_HORZ, pos + SCROLL_STEP);
415 m_Graph.Invalidate();
416 break;
417 case VK_PRIOR:
418 pos = m_Graph.GetScrollPos(SB_VERT);
419 m_Graph.SetScrollPos(SB_VERT, pos - GetGraphRect().Height() / 2);
420 m_Graph.Invalidate();
421 break;
422 case VK_NEXT:
423 pos = m_Graph.GetScrollPos(SB_VERT);
424 m_Graph.SetScrollPos(SB_VERT, pos + GetGraphRect().Height() / 2);
425 m_Graph.Invalidate();
426 break;
427 case VK_F5:
428 UpdateFullHistory();
429 break;
430 case 'F':
431 if (GetKeyState(VK_CONTROL) < 0)
432 OnFind();
433 return TRUE;
436 if ((m_hAccel)&&(pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST))
438 if (pMsg->wParam == VK_ESCAPE)
439 if (m_Graph.CancelMouseZoom())
440 return TRUE;
441 return TranslateAccelerator(m_hWnd,m_hAccel,pMsg);
443 return __super::PreTranslateMessage(pMsg);
446 void CRevisionGraphDlg::OnViewShowBranchingsMerges()
448 m_Graph.m_bShowBranchingsMerges = ToggleSetMenu(L"ShowRevGraphBranchesMerges", ID_VIEW_SHOWBRANCHINGSANDMERGES);
450 UpdateFullHistory();
453 void CRevisionGraphDlg::OnViewShowAllTags()
455 m_Graph.m_bShowAllTags = ToggleSetMenu(L"ShowRevGraphAllTags", ID_VIEW_SHOWALLTAGS);
457 UpdateFullHistory();
460 void CRevisionGraphDlg::OnViewArrowPointToMerges()
462 m_Graph.m_bArrowPointToMerges = ToggleSetMenu(L"ArrowPointToMerges", ID_VIEW_ARROW_POINT_TO_MERGES);
463 UpdateFullHistory();
466 void CRevisionGraphDlg::DoZoom (float zoom)
468 m_fZoomFactor = zoom;
469 m_Graph.DoZoom (zoom);
470 UpdateZoomBox();
473 void CRevisionGraphDlg::OnViewZoomin()
475 DoZoom (min (MAX_ZOOM, m_fZoomFactor / ZOOM_STEP));
478 void CRevisionGraphDlg::OnViewZoomout()
480 DoZoom (max (MIN_ZOOM, m_fZoomFactor * ZOOM_STEP));
483 void CRevisionGraphDlg::OnViewZoom100()
485 DoZoom (DEFAULT_ZOOM);
488 void CRevisionGraphDlg::OnViewZoomHeight()
490 CRect graphRect = m_Graph.GetGraphRect();
491 CRect windowRect = m_Graph.GetWindowRect();
493 float horzfact = (windowRect.Width() - 4.0f)/(4.0f + graphRect.Width());
494 float vertfact = (windowRect.Height() - 4.0f)/(4.0f + graphRect.Height());
495 if ((horzfact < vertfact) && (horzfact < MAX_ZOOM))
496 vertfact = (windowRect.Height() - CDPIAware::Instance().ScaleY(GetSafeHwnd(), 20)) / (4.0f + graphRect.Height());
498 DoZoom (min (MAX_ZOOM, vertfact));
501 void CRevisionGraphDlg::OnViewZoomWidth()
503 // zoom the graph so that it is completely visible in the window
504 CRect graphRect = m_Graph.GetGraphRect();
505 CRect windowRect = m_Graph.GetWindowRect();
507 float horzfact = (windowRect.Width() - 4.0f)/(4.0f + graphRect.Width());
508 float vertfact = (windowRect.Height() - 4.0f)/(4.0f + graphRect.Height());
509 if ((vertfact < horzfact) && (vertfact < MAX_ZOOM))
510 horzfact = (windowRect.Width() - CDPIAware::Instance().ScaleX(GetSafeHwnd(), 20)) / (4.0f + graphRect.Width());
512 DoZoom (min (MAX_ZOOM, horzfact));
515 void CRevisionGraphDlg::OnViewZoomAll()
517 // zoom the graph so that it is completely visible in the window
518 CRect graphRect = m_Graph.GetGraphRect();
519 CRect windowRect = m_Graph.GetWindowRect();
521 float horzfact = (windowRect.Width() - 4.0f)/(4.0f + graphRect.Width());
522 float vertfact = (windowRect.Height() - 4.0f)/(4.0f + graphRect.Height());
524 DoZoom (min (MAX_ZOOM, min(horzfact, vertfact)));
527 void CRevisionGraphDlg::OnFind()
529 if (!m_pFindDialog)
531 m_pFindDialog = new CFindDlg(this);
532 m_pFindDialog->Create(this);
534 else
536 m_pFindDialog->SetFocus();
537 return;
541 LRESULT CRevisionGraphDlg::OnFindDialogMessage(WPARAM /*wParam*/, LPARAM /*lParam*/)
543 ASSERT(m_pFindDialog);
544 bool bFound = false;
545 int i = 0;
547 if (m_pFindDialog->IsTerminating())
549 // invalidate the handle identifying the dialog box.
550 m_pFindDialog = nullptr;
551 return 0;
554 bool bShift = (GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0;
555 int cnt = static_cast<int>(m_Graph.m_logEntries.size());
556 if (m_pFindDialog->IsRef())
558 CString str;
559 str = m_pFindDialog->GetFindString();
561 CGitHash hash;
563 if (!str.IsEmpty())
565 if (g_Git.GetHash(hash, str + L"^{}")) // add ^{} in order to get the correct SHA-1 (especially for signed tags)
566 MessageBox(g_Git.GetGitLastErr(L"Could not get hash of ref \"" + str + L"^{}\"."), L"TortoiseGit", MB_ICONERROR);
569 if (!hash.IsEmpty())
571 for (i = 0; i < cnt; ++i)
573 if (m_Graph.m_logEntries.at(i) == hash)
575 bFound = true;
576 break;
580 if (!bFound)
582 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
583 return 0;
587 if (m_pFindDialog->FindNext() && !bFound)
589 //read data from dialog
590 CLogDlgFilter filter{ m_pFindDialog->GetFindString(), m_pFindDialog->Regex(), LOGFILTER_SUBJECT | LOGFILTER_MESSAGES | LOGFILTER_AUTHORS | LOGFILTER_EMAILS | LOGFILTER_REVS | LOGFILTER_REFNAME, m_pFindDialog->MatchCase() == TRUE };
592 for (i = m_nSearchIndex + 1;; ++i)
594 if (i >= cnt)
596 i = 0;
597 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 2, 100);
599 if (m_nSearchIndex >= 0)
601 if (i == m_nSearchIndex)
603 ::MessageBeep(0xFFFFFFFF);
604 m_pFindDialog->FlashWindowEx(FLASHW_ALL, 3, 100);
605 break;
609 if (filter(m_Graph.m_LogCache.GetCacheData(m_Graph.m_logEntries.at(i)), nullptr, m_Graph.m_HashMap))
611 bFound = true;
612 break;
615 } // if(m_pFindDialog->FindNext())
617 if (bFound)
619 m_nSearchIndex = i;
620 m_Graph.ScrollTo(i, !bShift);
621 Invalidate(FALSE);
624 return 0;
627 void CRevisionGraphDlg::OnMenuexit()
629 if (!m_Graph.IsUpdateJobRunning())
630 EndDialog(IDOK);
633 void CRevisionGraphDlg::OnMenuhelp()
635 OnHelp();
638 void CRevisionGraphDlg::OnViewCompareheadrevisions()
640 m_Graph.CompareRevs(L"HEAD");
643 void CRevisionGraphDlg::OnViewComparerevisions()
645 m_Graph.CompareRevs(L"");
648 void CRevisionGraphDlg::OnViewUnifieddiff()
650 m_Graph.UnifiedDiffRevs(false);
653 void CRevisionGraphDlg::OnViewUnifieddiffofheadrevisions()
655 m_Graph.UnifiedDiffRevs(true);
658 void CRevisionGraphDlg::UpdateFullHistory()
660 m_bFetchLogs = true;
661 Invalidate();
662 StartWorkerThread();
665 void CRevisionGraphDlg::StartWorkerThread()
667 if (!m_Graph.IsUpdateJobRunning())
668 m_Graph.updateJob = std::make_unique<CFuture<bool>>(this, &CRevisionGraphDlg::UpdateData);
671 void CRevisionGraphDlg::OnCancel()
673 if (!m_Graph.IsUpdateJobRunning())
674 __super::OnCancel();
677 void CRevisionGraphDlg::OnOK()
679 OnChangeZoom();
682 void CRevisionGraphDlg::OnFileSavegraphas()
684 CString tempfile;
685 int filterindex = 0;
686 if (CAppUtils::FileOpenSave(tempfile, &filterindex, IDS_REVGRAPH_SAVEPIC, IDS_PICTUREFILEFILTER, false, m_hWnd))
688 // if the user doesn't specify a file extension, default to
689 // svg and add that extension to the filename. But only if the
690 // user chose the 'pictures' filter. The filename isn't changed
691 // if the 'All files' filter was chosen.
692 CString extension;
693 int dotPos = tempfile.ReverseFind('.');
694 int slashPos = tempfile.ReverseFind('\\');
695 if (dotPos > slashPos)
696 extension = tempfile.Mid(dotPos);
697 if ((filterindex == 1)&&(extension.IsEmpty()))
699 extension = L".svg";
700 tempfile += extension;
702 if ((filterindex == 2)&&(extension.IsEmpty()))
704 extension = L".gv";
705 tempfile += extension;
707 m_Graph.SaveGraphAs(tempfile);
711 CRect CRevisionGraphDlg::GetGraphRect()
713 CRect rect;
714 GetClientRect(&rect);
716 CRect statusbarrect;
717 m_StatusBar.GetClientRect(&statusbarrect);
718 rect.bottom -= statusbarrect.Height();
720 CRect toolbarrect;
721 m_ToolBar.GetClientRect(&toolbarrect);
722 rect.top += toolbarrect.Height();
724 return rect;
727 void CRevisionGraphDlg::UpdateStatusBar()
729 // CString sFormat;
730 // sFormat.Format(IDS_REVGRAPH_STATUSBARURL, static_cast<LPCWSTR>(m_Graph.m_sPath));
731 // m_StatusBar.SetText(sFormat,1,0);
732 // sFormat.Format(IDS_REVGRAPH_STATUSBARNUMNODES, m_Graph.m_state.GetNodeCount());
733 // m_StatusBar.SetText(sFormat,0,0);
736 void CRevisionGraphDlg::OnChangeZoom()
738 if (!IsWindow(m_Graph.GetSafeHwnd()))
739 return;
740 CString strItem;
741 auto pCBox = static_cast<CComboBoxEx*>(m_ToolBar.GetDlgItem(ID_REVGRAPH_ZOOMCOMBO));
742 pCBox->GetWindowText(strItem);
743 if (strItem.IsEmpty())
744 return;
746 DoZoom(static_cast<float>(_wtof(strItem) / 100.0));
749 void CRevisionGraphDlg::UpdateZoomBox()
751 CString strText;
752 CString strItem;
753 auto pCBox = static_cast<CComboBoxEx*>(m_ToolBar.GetDlgItem(ID_REVGRAPH_ZOOMCOMBO));
754 pCBox->GetWindowText(strItem);
755 strText.Format(L"%.0f%%", (m_fZoomFactor * 100.0));
756 if (strText.Compare(strItem) != 0)
757 pCBox->SetWindowText(strText);
760 BOOL CRevisionGraphDlg::OnToolTipNotify(UINT /*id*/, NMHDR *pNMHDR, LRESULT *pResult)
762 // need to handle both ANSI and UNICODE versions of the message
763 auto pTTTA = reinterpret_cast<TOOLTIPTEXTA*>(pNMHDR);
764 auto pTTTW = reinterpret_cast<TOOLTIPTEXTW*>(pNMHDR);
765 CString strTipText;
767 UINT_PTR nID = pNMHDR->idFrom;
769 if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
770 pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
772 // idFrom is actually the HWND of the tool
773 nID = ::GetDlgCtrlID(reinterpret_cast<HWND>(nID));
776 if (nID != 0) // will be zero on a separator
777 strTipText.LoadString (static_cast<UINT>(nID));
779 *pResult = 0;
780 if (strTipText.IsEmpty())
781 return TRUE;
783 if (strTipText.GetLength() >= MAX_TT_LENGTH)
784 strTipText = strTipText.Left(MAX_TT_LENGTH);
786 if (pNMHDR->code == TTN_NEEDTEXTA)
788 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 600);
789 pTTTA->lpszText = m_szTip;
790 WideCharToMultiByte(CP_ACP, 0, strTipText, -1, m_szTip, strTipText.GetLength()+1, 0, 0);
792 else
794 ::SendMessage(pNMHDR->hwndFrom, TTM_SETMAXTIPWIDTH, 0, 600);
795 lstrcpyn(m_wszTip, strTipText, strTipText.GetLength()+1);
796 pTTTW->lpszText = m_wszTip;
798 // bring the tooltip window above other pop up windows
799 ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,
800 SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
801 return TRUE; // message was handled
804 void CRevisionGraphDlg::OnViewFilter()
806 CRevGraphFilterDlg dlg;
808 dlg.m_bCurrentBranch = this->m_Graph.m_bCurrentBranch;
809 dlg.m_bLocalBranches = m_Graph.m_bLocalBranches;
810 dlg.SetRevisionRange(m_Graph.m_FromRev, m_Graph.m_ToRev);
812 if (dlg.DoModal()==IDOK)
814 // user pressed OK to dismiss the dialog, which means
815 // we have to accept the new filter settings and apply them
817 dlg.GetRevisionRange(m_Graph.m_FromRev, m_Graph.m_ToRev);
818 // update menu & toolbar
820 this->m_Graph.m_bCurrentBranch = dlg.m_bCurrentBranch;
821 m_Graph.m_bLocalBranches = dlg.m_bLocalBranches;
823 CMenu * pMenu = GetMenu();
824 int tbstate = m_ToolBar.GetToolBarCtrl().GetState(ID_VIEW_FILTER);
825 if (m_Graph.m_bCurrentBranch || m_Graph.m_bLocalBranches || !m_Graph.m_FromRev.IsEmpty() || !m_Graph.m_ToRev.IsEmpty())
827 if (pMenu)
828 pMenu->CheckMenuItem(ID_VIEW_FILTER, MF_BYCOMMAND | MF_CHECKED);
829 m_ToolBar.GetToolBarCtrl().SetState(ID_VIEW_FILTER, tbstate | TBSTATE_CHECKED);
831 else
833 if (pMenu)
834 pMenu->CheckMenuItem(ID_VIEW_FILTER, MF_BYCOMMAND | MF_UNCHECKED);
835 m_ToolBar.GetToolBarCtrl().SetState(ID_VIEW_FILTER, tbstate & (~TBSTATE_CHECKED));
838 // re-run query
840 StartWorkerThread();
844 void CRevisionGraphDlg::OnViewShowoverview()
846 m_Graph.SetShowOverview(ToggleSetMenu(L"ShowRevGraphOverview", ID_VIEW_SHOWOVERVIEW));
848 m_Graph.Invalidate(FALSE);
851 void CRevisionGraphDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
853 if (!m_bVisible)
854 lpwndpos->flags &= ~SWP_SHOWWINDOW;
855 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos);
858 LRESULT CRevisionGraphDlg::OnDPIChanged(WPARAM wParam, LPARAM lParam)
860 CDPIAware::Instance().Invalidate();
861 m_ToolBar.CloseWindow();
862 m_ToolBar.DestroyWindow();
863 InitializeToolbar();
864 return __super::OnDPIChanged(wParam, lParam);