Fix typos
[TortoiseGit.git] / src / TortoiseUDiff / MainWindow.cpp
blobaf93a4967b2b83087d853095756ee0bb00485079
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2012-2021, 2023 - TortoiseGit
4 // Copyright (C) 2003-2014, 2020 - TortoiseSVN
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 "TortoiseUDiff.h"
22 #include "MainWindow.h"
23 #include "UnicodeUtils.h"
24 #include "StringUtils.h"
25 #include "TaskbarUUID.h"
26 #include "CreateProcessHelper.h"
27 #include "UDiffColors.h"
28 #include "registry.h"
29 #include "DPIAware.h"
30 #include "LoadIconEx.h"
31 #include "Theme.h"
32 #include "DarkModeHelper.h"
33 #include "Lexilla.h"
35 const UINT TaskBarButtonCreated = RegisterWindowMessage(L"TaskbarButtonCreated");
37 #define SEARCHBARHEIGHT 30
39 CMainWindow::CMainWindow(HINSTANCE hInst, const WNDCLASSEX* wcx /* = nullptr*/)
40 : CWindow(hInst, wcx)
42 SetWindowTitle(L"TortoiseGitUDiff");
45 CMainWindow::~CMainWindow()
49 bool CMainWindow::RegisterAndCreateWindow()
51 WNDCLASSEX wcx;
53 // Fill in the window class structure with default parameters
54 wcx.cbSize = sizeof(WNDCLASSEX);
55 wcx.style = CS_HREDRAW | CS_VREDRAW;
56 wcx.lpfnWndProc = CWindow::stWinMsgHandler;
57 wcx.cbClsExtra = 0;
58 wcx.cbWndExtra = 0;
59 wcx.hInstance = hResource;
60 wcx.hCursor = nullptr;
61 ResString clsname(hResource, IDS_APP_TITLE);
62 wcx.lpszClassName = clsname;
63 wcx.hIcon = LoadIconEx(hResource, MAKEINTRESOURCE(IDI_TORTOISEUDIFF), GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON));
64 wcx.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_3DFACE + 1);
65 wcx.lpszMenuName = MAKEINTRESOURCE(IDC_TORTOISEUDIFF);
66 wcx.hIconSm = LoadIconEx(wcx.hInstance, MAKEINTRESOURCE(IDI_TORTOISEUDIFF));
67 if (RegisterWindow(&wcx))
69 if (Create(WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX | WS_SYSMENU | WS_CLIPCHILDREN, nullptr))
71 m_FindBar.SetParent(*this);
72 m_FindBar.Create(::hResource, IDD_FINDBAR, *this);
73 UpdateWindow(*this);
74 return true;
77 return false;
80 void CMainWindow::UpdateLineCount()
82 auto numberOfLines = static_cast<intptr_t>(SendEditor(SCI_GETLINECOUNT));
83 int numDigits = 2;
84 while (numberOfLines)
86 numberOfLines /= 10;
87 ++numDigits;
89 SendEditor(SCI_SETMARGINWIDTHN, 0, numDigits * static_cast<int>(SendEditor(SCI_TEXTWIDTH, STYLE_LINENUMBER, reinterpret_cast<LPARAM>("8"))));
92 LRESULT CALLBACK CMainWindow::WinMsgHandler(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
94 if (uMsg == TaskBarButtonCreated)
96 SetUUIDOverlayIcon(hwnd);
98 auto optRet = CTheme::HandleMenuBar(hwnd, uMsg, wParam, lParam);
99 if (optRet.has_value())
100 return optRet.value();
101 switch (uMsg)
103 case WM_CREATE:
105 m_hwnd = hwnd;
106 Initialize();
108 break;
109 case WM_COMMAND:
111 return DoCommand(LOWORD(wParam));
113 break;
114 case WM_MOUSEWHEEL:
116 if (GET_KEYSTATE_WPARAM(wParam) == MK_SHIFT)
118 // scroll sideways
119 SendEditor(SCI_LINESCROLL, -GET_WHEEL_DELTA_WPARAM(wParam)/40, 0);
121 else
122 return DefWindowProc(hwnd, uMsg, wParam, lParam);
124 break;
125 case WM_SIZE:
127 RECT rect;
128 GetClientRect(*this, &rect);
129 if (m_bShowFindBar)
131 ::SetWindowPos(m_hWndEdit, HWND_TOP,
132 rect.left, rect.top,
133 rect.right - rect.left, rect.bottom - rect.top - int(SEARCHBARHEIGHT * CDPIAware::Instance().ScaleFactorY(hwnd)),
134 SWP_SHOWWINDOW);
135 ::SetWindowPos(m_FindBar, HWND_TOP,
136 rect.left, rect.bottom - int((SEARCHBARHEIGHT + 2) * CDPIAware::Instance().ScaleFactorY(hwnd)),
137 rect.right - rect.left, int(SEARCHBARHEIGHT * CDPIAware::Instance().ScaleFactorY(hwnd)),
138 SWP_SHOWWINDOW);
140 else
142 ::SetWindowPos(m_hWndEdit, HWND_TOP,
143 rect.left, rect.top,
144 rect.right-rect.left, rect.bottom-rect.top,
145 SWP_SHOWWINDOW);
146 ::ShowWindow(m_FindBar, SW_HIDE);
149 break;
150 case WM_GETMINMAXINFO:
152 auto mmi = reinterpret_cast<MINMAXINFO*>(lParam);
153 mmi->ptMinTrackSize.x = 100;
154 mmi->ptMinTrackSize.y = 100;
155 return 0;
157 break;
158 case WM_DESTROY:
159 PostQuitMessage(0);
160 break;
161 case WM_CLOSE:
162 if (!canCloseWhenModified())
163 break;
164 ::DestroyWindow(m_hwnd);
165 break;
166 case WM_SETFOCUS:
167 SetFocus(m_hWndEdit);
168 break;
169 case WM_SYSCOLORCHANGE:
170 CTheme::Instance().OnSysColorChanged();
171 CTheme::Instance().SetDarkTheme(CTheme::Instance().IsDarkTheme(), true);
172 break;
173 case WM_DPICHANGED:
175 CDPIAware::Instance().Invalidate();
176 SendMessage(m_hWndEdit, WM_DPICHANGED, wParam, lParam);
177 const RECT* rect = reinterpret_cast<RECT*>(lParam);
178 SetWindowPos(*this, NULL, rect->left, rect->top, rect->right - rect->left, rect->bottom - rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
179 ::RedrawWindow(*this, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
181 break;
182 case COMMITMONITOR_FINDMSGNEXT:
183 m_bMatchCase = !!wParam;
184 m_findtext = reinterpret_cast<LPCWSTR>(lParam);
185 DoSearch(false);
186 break;
187 case COMMITMONITOR_FINDMSGPREV:
188 m_bMatchCase = !!wParam;
189 m_findtext = reinterpret_cast<LPCWSTR>(lParam);
190 DoSearch(true);
191 break;
192 case COMMITMONITOR_FINDEXIT:
194 RECT rect;
195 GetClientRect(*this, &rect);
196 m_bShowFindBar = false;
197 ::ShowWindow(m_FindBar, SW_HIDE);
198 ::SetWindowPos(m_hWndEdit, HWND_TOP,
199 rect.left, rect.top,
200 rect.right-rect.left, rect.bottom-rect.top,
201 SWP_SHOWWINDOW);
203 break;
204 case COMMITMONITOR_FINDRESET:
205 SendEditor(SCI_SETSELECTIONSTART, 0);
206 SendEditor(SCI_SETSELECTIONEND, 0);
207 break;
208 case WM_NOTIFY:
209 if (reinterpret_cast<LPNMHDR>(lParam)->code == SCN_PAINTED)
210 UpdateLineCount();
211 break;
212 default:
213 return DefWindowProc(hwnd, uMsg, wParam, lParam);
216 return 0;
219 LRESULT CMainWindow::DoCommand(int id)
221 switch (id)
223 case ID_FILE_OPEN:
224 if (!canCloseWhenModified())
225 break;
226 loadOrSaveFile(true);
227 break;
228 case ID_FILE_SAVEAS:
229 loadOrSaveFile(false);
230 break;
231 case ID_FILE_SAVE:
232 loadOrSaveFile(false, m_filename);
233 break;
234 case ID_FILE_EXIT:
235 if (!canCloseWhenModified())
236 break;
237 ::PostQuitMessage(0);
238 return 0;
239 case IDM_SHOWFINDBAR:
241 m_bShowFindBar = true;
242 ::ShowWindow(m_FindBar, SW_SHOW);
243 RECT rect;
244 GetClientRect(*this, &rect);
245 ::SetWindowPos(m_hWndEdit, HWND_TOP,
246 rect.left, rect.top,
247 rect.right - rect.left, rect.bottom - rect.top - int(SEARCHBARHEIGHT * CDPIAware::Instance().ScaleFactorY(*this)),
248 SWP_SHOWWINDOW);
249 ::SetWindowPos(m_FindBar, HWND_TOP,
250 rect.left, rect.bottom - int((SEARCHBARHEIGHT + 2) * CDPIAware::Instance().ScaleFactorY(*this)),
251 rect.right - rect.left, int(SEARCHBARHEIGHT * CDPIAware::Instance().ScaleFactorY(*this)),
252 SWP_SHOWWINDOW);
253 if (auto selstart = SendEditor(SCI_GETSELECTIONSTART), selend = SendEditor(SCI_GETSELECTIONEND); selstart != selend)
255 auto linebuf = std::make_unique<char[]>(selend - selstart + 1);
256 Sci_TextRange range = { static_cast<Sci_PositionCR>(selstart), static_cast<Sci_PositionCR>(selend), linebuf.get() };
257 if (SendEditor(SCI_GETTEXTRANGE, 0, reinterpret_cast<LPARAM>(&range)) > 0)
258 m_FindBar.SetSearchString(CUnicodeUtils::StdGetUnicode(linebuf.get()).c_str());
260 m_FindBar.SelectSearchString();
261 ::SetFocus(m_FindBar);
263 break;
264 case IDM_FINDNEXT:
265 if (m_findtext.empty())
267 DoCommand(IDM_SHOWFINDBAR);
268 break;
270 DoSearch(false);
271 break;
272 case IDM_FINDPREV:
273 if (m_findtext.empty())
275 DoCommand(IDM_SHOWFINDBAR);
276 break;
278 DoSearch(true);
279 break;
280 case IDM_FINDEXIT:
281 if (IsWindowVisible(m_FindBar))
283 RECT rect;
284 GetClientRect(*this, &rect);
285 m_bShowFindBar = false;
286 ::ShowWindow(m_FindBar, SW_HIDE);
287 ::SetWindowPos(m_hWndEdit, HWND_TOP,
288 rect.left, rect.top,
289 rect.right-rect.left, rect.bottom-rect.top,
290 SWP_SHOWWINDOW);
292 else if (canCloseWhenModified())
293 PostQuitMessage(0);
294 break;
295 case ID_FILE_SETTINGS:
297 std::wstring gitCmd = L" /command:settings /page:udiff";
298 RunCommand(gitCmd);
300 break;
301 case ID_FILE_APPLYPATCH:
303 std::wstring command = L" /diff:\"";
304 command += m_filename;
305 command += L'"';
306 std::wstring tortoiseMergePath = GetAppDirectory() + L"TortoiseGitMerge.exe";
307 CCreateProcessHelper::CreateProcessDetached(tortoiseMergePath.c_str(), command.c_str());
309 break;
310 case ID_FILE_PAGESETUP:
312 wchar_t localeInfo[3] = { 0 };
313 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, localeInfo, 3);
314 // Metric system. '1' is US System
315 int defaultMargin = localeInfo[0] == '0' ? 2540 : 1000;
317 PAGESETUPDLG pdlg = {0};
318 pdlg.lStructSize = sizeof(PAGESETUPDLG);
319 pdlg.hwndOwner = *this;
320 pdlg.hInstance = nullptr;
321 pdlg.Flags = PSD_DEFAULTMINMARGINS|PSD_MARGINS|PSD_DISABLEPAPER|PSD_DISABLEORIENTATION;
322 if (localeInfo[0] == '0')
323 pdlg.Flags |= PSD_INHUNDREDTHSOFMILLIMETERS;
325 CRegStdDWORD m_regMargLeft = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmarginleft", defaultMargin);
326 CRegStdDWORD m_regMargTop = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmargintop", defaultMargin);
327 CRegStdDWORD m_regMargRight = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmarginright", defaultMargin);
328 CRegStdDWORD m_regMargBottom = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmarginbottom", defaultMargin);
330 pdlg.rtMargin.left = static_cast<long>(m_regMargLeft);
331 pdlg.rtMargin.top = static_cast<long>(m_regMargTop);
332 pdlg.rtMargin.right = static_cast<long>(m_regMargRight);
333 pdlg.rtMargin.bottom = static_cast<long>(m_regMargBottom);
335 if (!PageSetupDlg(&pdlg))
336 return false;
338 m_regMargLeft = pdlg.rtMargin.left;
339 m_regMargTop = pdlg.rtMargin.top;
340 m_regMargRight = pdlg.rtMargin.right;
341 m_regMargBottom = pdlg.rtMargin.bottom;
343 break;
344 case ID_FILE_PRINT:
346 PRINTDLGEX pdlg = {0};
347 pdlg.lStructSize = sizeof(PRINTDLGEX);
348 pdlg.hwndOwner = *this;
349 pdlg.hInstance = nullptr;
350 pdlg.Flags = PD_USEDEVMODECOPIESANDCOLLATE | PD_ALLPAGES | PD_RETURNDC | PD_NOCURRENTPAGE | PD_NOPAGENUMS;
351 pdlg.nMinPage = 1;
352 pdlg.nMaxPage = 0xffffU; // We do not know how many pages in the document
353 pdlg.nCopies = 1;
354 pdlg.hDC = 0;
355 pdlg.nStartPage = START_PAGE_GENERAL;
357 // See if a range has been selected
358 auto startPos = static_cast<Sci_Position>(SendEditor(SCI_GETSELECTIONSTART));
359 auto endPos = static_cast<Sci_Position>(SendEditor(SCI_GETSELECTIONEND));
361 if (startPos == endPos)
362 pdlg.Flags |= PD_NOSELECTION;
363 else
364 pdlg.Flags |= PD_SELECTION;
366 HRESULT hResult = PrintDlgEx(&pdlg);
367 if ((hResult != S_OK) || (pdlg.dwResultAction != PD_RESULT_PRINT))
368 return 0;
370 // reset all indicators
371 auto endpos = static_cast<int>(SendEditor(SCI_GETLENGTH));
372 for (int i = INDIC_CONTAINER; i <= INDIC_MAX; ++i)
374 SendEditor(SCI_SETINDICATORCURRENT, i);
375 SendEditor(SCI_INDICATORCLEARRANGE, 0, endpos);
377 // store and reset UI settings
378 auto viewws = static_cast<int>(SendEditor(SCI_GETVIEWWS));
379 SendEditor(SCI_SETVIEWWS, 0);
380 auto edgemode = static_cast<int>(SendEditor(SCI_GETEDGEMODE));
381 SendEditor(SCI_SETEDGEMODE, EDGE_NONE);
382 SendEditor(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_END);
384 HDC hdc = pdlg.hDC;
386 RECT rectMargins, rectPhysMargins;
387 POINT ptPage;
388 POINT ptDpi;
390 // Get printer resolution
391 ptDpi.x = GetDeviceCaps(hdc, LOGPIXELSX); // dpi in X direction
392 ptDpi.y = GetDeviceCaps(hdc, LOGPIXELSY); // dpi in Y direction
394 // Start by getting the physical page size (in device units).
395 ptPage.x = GetDeviceCaps(hdc, PHYSICALWIDTH); // device units
396 ptPage.y = GetDeviceCaps(hdc, PHYSICALHEIGHT); // device units
398 // Get the dimensions of the unprintable
399 // part of the page (in device units).
400 rectPhysMargins.left = GetDeviceCaps(hdc, PHYSICALOFFSETX);
401 rectPhysMargins.top = GetDeviceCaps(hdc, PHYSICALOFFSETY);
403 // To get the right and lower unprintable area,
404 // we take the entire width and height of the paper and
405 // subtract everything else.
406 rectPhysMargins.right = ptPage.x // total paper width
407 - GetDeviceCaps(hdc, HORZRES) // printable width
408 - rectPhysMargins.left; // left unprintable margin
410 rectPhysMargins.bottom = ptPage.y // total paper height
411 - GetDeviceCaps(hdc, VERTRES) // printable height
412 - rectPhysMargins.top; // right unprintable margin
414 wchar_t localeInfo[3] = { 0 };
415 GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IMEASURE, localeInfo, 3);
416 // Metric system. '1' is US System
417 int defaultMargin = localeInfo[0] == '0' ? 2540 : 1000;
418 RECT pagesetupMargin;
419 CRegStdDWORD m_regMargLeft = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmarginleft", defaultMargin);
420 CRegStdDWORD m_regMargTop = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmargintop", defaultMargin);
421 CRegStdDWORD m_regMargRight = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmarginright", defaultMargin);
422 CRegStdDWORD m_regMargBottom = CRegStdDWORD(L"Software\\TortoiseGit\\UDiffpagesetupmarginbottom", defaultMargin);
424 pagesetupMargin.left = static_cast<long>(m_regMargLeft);
425 pagesetupMargin.top = static_cast<long>(m_regMargTop);
426 pagesetupMargin.right = static_cast<long>(m_regMargRight);
427 pagesetupMargin.bottom = static_cast<long>(m_regMargBottom);
429 if (pagesetupMargin.left != 0 || pagesetupMargin.right != 0 ||
430 pagesetupMargin.top != 0 || pagesetupMargin.bottom != 0)
432 RECT rectSetup;
434 // Convert the hundredths of millimeters (HiMetric) or
435 // thousandths of inches (HiEnglish) margin values
436 // from the Page Setup dialog to device units.
437 // (There are 2540 hundredths of a mm in an inch.)
438 if (localeInfo[0] == '0')
440 // Metric system. '1' is US System
441 rectSetup.left = MulDiv (pagesetupMargin.left, ptDpi.x, 2540);
442 rectSetup.top = MulDiv (pagesetupMargin.top, ptDpi.y, 2540);
443 rectSetup.right = MulDiv(pagesetupMargin.right, ptDpi.x, 2540);
444 rectSetup.bottom = MulDiv(pagesetupMargin.bottom, ptDpi.y, 2540);
446 else
448 rectSetup.left = MulDiv(pagesetupMargin.left, ptDpi.x, 1000);
449 rectSetup.top = MulDiv(pagesetupMargin.top, ptDpi.y, 1000);
450 rectSetup.right = MulDiv(pagesetupMargin.right, ptDpi.x, 1000);
451 rectSetup.bottom = MulDiv(pagesetupMargin.bottom, ptDpi.y, 1000);
454 // Don't reduce margins below the minimum printable area
455 rectMargins.left = max(rectPhysMargins.left, rectSetup.left);
456 rectMargins.top = max(rectPhysMargins.top, rectSetup.top);
457 rectMargins.right = max(rectPhysMargins.right, rectSetup.right);
458 rectMargins.bottom = max(rectPhysMargins.bottom, rectSetup.bottom);
460 else
462 rectMargins.left = rectPhysMargins.left;
463 rectMargins.top = rectPhysMargins.top;
464 rectMargins.right = rectPhysMargins.right;
465 rectMargins.bottom = rectPhysMargins.bottom;
468 // rectMargins now contains the values used to shrink the printable
469 // area of the page.
471 // Convert device coordinates into logical coordinates
472 DPtoLP(hdc, reinterpret_cast<LPPOINT>(&rectMargins), 2);
473 DPtoLP(hdc, reinterpret_cast<LPPOINT>(&rectPhysMargins), 2);
475 // Convert page size to logical units and we're done!
476 DPtoLP(hdc, reinterpret_cast<LPPOINT>(&ptPage), 1);
479 DOCINFO di = {sizeof(DOCINFO), 0, 0, 0, 0};
480 di.lpszDocName = m_filename.c_str();
481 di.lpszOutput = 0;
482 di.lpszDatatype = 0;
483 di.fwType = 0;
484 if (::StartDoc(hdc, &di) < 0)
486 ::DeleteDC(hdc);
487 return 0;
490 size_t lengthDoc = static_cast<int>(SendEditor(SCI_GETLENGTH));
491 size_t lengthDocMax = lengthDoc;
492 size_t lengthPrinted = 0;
494 // Requested to print selection
495 if (pdlg.Flags & PD_SELECTION)
497 if (startPos > endPos)
499 lengthPrinted = endPos;
500 lengthDoc = startPos;
502 else
504 lengthPrinted = startPos;
505 lengthDoc = endPos;
508 if (lengthDoc > lengthDocMax)
509 lengthDoc = lengthDocMax;
512 // We must subtract the physical margins from the printable area
513 Sci_RangeToFormat frPrint;
514 frPrint.hdc = hdc;
515 frPrint.hdcTarget = hdc;
516 frPrint.rc.left = rectMargins.left - rectPhysMargins.left;
517 frPrint.rc.top = rectMargins.top - rectPhysMargins.top;
518 frPrint.rc.right = ptPage.x - rectMargins.right - rectPhysMargins.left;
519 frPrint.rc.bottom = ptPage.y - rectMargins.bottom - rectPhysMargins.top;
520 frPrint.rcPage.left = 0;
521 frPrint.rcPage.top = 0;
522 frPrint.rcPage.right = ptPage.x - rectPhysMargins.left - rectPhysMargins.right - 1;
523 frPrint.rcPage.bottom = ptPage.y - rectPhysMargins.top - rectPhysMargins.bottom - 1;
525 // Print each page
526 while (lengthPrinted < lengthDoc)
528 ::StartPage(hdc);
530 frPrint.chrg.cpMin = static_cast<long>(lengthPrinted);
531 frPrint.chrg.cpMax = static_cast<long>(lengthDoc);
533 lengthPrinted = SendEditor(SCI_FORMATRANGE, true, reinterpret_cast<LPARAM>(&frPrint));
535 ::EndPage(hdc);
538 SendEditor(SCI_FORMATRANGE, FALSE, 0);
540 ::EndDoc(hdc);
541 ::DeleteDC(hdc);
543 if (pdlg.hDevMode)
544 GlobalFree(pdlg.hDevMode);
545 if (pdlg.hDevNames)
546 GlobalFree(pdlg.hDevNames);
547 if (pdlg.lpPageRanges)
548 GlobalFree(pdlg.lpPageRanges);
550 // reset the UI
551 SendEditor(SCI_SETVIEWWS, viewws);
552 SendEditor(SCI_SETEDGEMODE, edgemode);
553 SendEditor(SCI_SETWRAPVISUALFLAGS, SC_WRAPVISUALFLAG_NONE);
555 break;
556 case ID_VIEW_DARKMODE:
557 CTheme::Instance().SetDarkTheme(!CTheme::Instance().IsDarkTheme());
558 break;
559 default:
560 break;
562 return 1;
565 std::wstring CMainWindow::GetAppDirectory()
567 std::wstring path;
568 DWORD len = 0;
569 DWORD bufferlen = MAX_PATH; // MAX_PATH is not the limit here!
572 bufferlen += MAX_PATH; // MAX_PATH is not the limit here!
573 auto pBuf = std::make_unique<wchar_t[]>(bufferlen);
574 len = GetModuleFileName(nullptr, pBuf.get(), bufferlen);
575 path = std::wstring(pBuf.get(), len);
576 } while(len == bufferlen);
577 path = path.substr(0, path.rfind('\\') + 1);
579 return path;
582 void CMainWindow::RunCommand(const std::wstring& command)
584 std::wstring tortoiseProcPath = GetAppDirectory() + L"TortoiseGitProc.exe";
585 CCreateProcessHelper::CreateProcessDetached(tortoiseProcPath.c_str(), command.c_str());
588 LRESULT CMainWindow::SendEditor(UINT Msg, WPARAM wParam, LPARAM lParam)
590 if (m_directFunction)
592 return reinterpret_cast<SciFnDirect>(m_directFunction)(m_directPointer, Msg, wParam, lParam);
594 return ::SendMessage(m_hWndEdit, Msg, wParam, lParam);
597 bool CMainWindow::Initialize()
599 m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback([this]() { SetTheme(CTheme::Instance().IsDarkTheme()); });
600 m_hWndEdit = ::CreateWindow(
601 L"Scintilla",
602 L"Source",
603 WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_CLIPCHILDREN,
604 CW_USEDEFAULT, CW_USEDEFAULT,
605 CW_USEDEFAULT, CW_USEDEFAULT,
606 *this,
607 nullptr,
608 hResource,
609 nullptr);
610 if (!m_hWndEdit)
611 return false;
613 RECT rect;
614 GetClientRect(*this, &rect);
615 ::SetWindowPos(m_hWndEdit, HWND_TOP,
616 rect.left, rect.top,
617 rect.right-rect.left, rect.bottom-rect.top,
618 SWP_SHOWWINDOW);
620 m_directFunction = SendMessage(m_hWndEdit, SCI_GETDIRECTFUNCTION, 0, 0);
621 m_directPointer = SendMessage(m_hWndEdit, SCI_GETDIRECTPOINTER, 0, 0);
623 // Set up the global default style. These attributes are used wherever no explicit choices are made.
624 SendEditor(SCI_SETTABWIDTH, CRegStdDWORD(L"Software\\TortoiseGit\\UDiffTabSize", 4));
625 SendEditor(SCI_SETREADONLY, TRUE);
626 UpdateLineCount();
627 SendEditor(SCI_SETMARGINWIDTHN, 1);
628 SendEditor(SCI_SETMARGINWIDTHN, 2);
630 if (CRegStdDWORD(L"Software\\TortoiseGit\\ScintillaDirect2D", FALSE) != FALSE)
632 SendEditor(SCI_SETTECHNOLOGY, SC_TECHNOLOGY_DIRECTWRITERETAIN);
633 SendEditor(SCI_SETBUFFEREDDRAW, 0);
635 SendEditor(SCI_SETVIEWWS, 1);
636 SendEditor(SCI_SETWHITESPACESIZE, 2);
637 SendEditor(SCI_STYLESETVISIBLE, STYLE_CONTROLCHAR, TRUE);
639 SetTheme(CTheme::Instance().IsDarkTheme());
641 return true;
644 void CMainWindow::SetTheme(bool bDark)
646 auto fontNameW = CRegStdString(L"Software\\TortoiseGit\\UDiffFontName", L"Consolas");
647 auto fontName = CUnicodeUtils::StdGetUTF8(fontNameW);
649 if (bDark)
651 DarkModeHelper::Instance().AllowDarkModeForApp(TRUE);
653 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, TRUE);
654 SetClassLongPtr(*this, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetStockObject(BLACK_BRUSH)));
655 if (FAILED(SetWindowTheme(*this, L"DarkMode_Explorer", nullptr)))
656 SetWindowTheme(*this, L"Explorer", nullptr);
657 DarkModeHelper::Instance().AllowDarkModeForWindow(m_hWndEdit, TRUE);
658 if (FAILED(SetWindowTheme(m_hWndEdit, L"DarkMode_Explorer", nullptr)))
659 SetWindowTheme(m_hWndEdit, L"Explorer", nullptr);
660 BOOL darkFlag = TRUE;
661 DarkModeHelper::WINDOWCOMPOSITIONATTRIBDATA data = { DarkModeHelper::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, &darkFlag, sizeof(darkFlag) };
662 DarkModeHelper::Instance().SetWindowCompositionAttribute(*this, &data);
663 DarkModeHelper::Instance().FlushMenuThemes();
664 DarkModeHelper::Instance().RefreshImmersiveColorPolicyState();
666 else
668 DarkModeHelper::Instance().AllowDarkModeForWindow(*this, FALSE);
669 DarkModeHelper::Instance().AllowDarkModeForWindow(m_hWndEdit, FALSE);
670 BOOL darkFlag = FALSE;
671 DarkModeHelper::WINDOWCOMPOSITIONATTRIBDATA data = { DarkModeHelper::WINDOWCOMPOSITIONATTRIB::WCA_USEDARKMODECOLORS, &darkFlag, sizeof(darkFlag) };
672 DarkModeHelper::Instance().SetWindowCompositionAttribute(*this, &data);
673 DarkModeHelper::Instance().FlushMenuThemes();
674 DarkModeHelper::Instance().RefreshImmersiveColorPolicyState();
675 DarkModeHelper::Instance().AllowDarkModeForApp(FALSE);
676 SetClassLongPtr(*this, GCLP_HBRBACKGROUND, reinterpret_cast<LONG_PTR>(GetSysColorBrush(COLOR_3DFACE)));
677 SetWindowTheme(*this, L"Explorer", nullptr);
678 SetWindowTheme(m_hWndEdit, L"Explorer", nullptr);
681 if (bDark)
683 SendEditor(SCI_STYLESETFORE, STYLE_DEFAULT, UDiffTextColorDark);
684 SendEditor(SCI_STYLESETBACK, STYLE_DEFAULT, UDiffBackColorDark);
685 SendEditor(SCI_SETSELFORE, TRUE, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHTTEXT)));
686 SendEditor(SCI_SETSELBACK, TRUE, CTheme::Instance().GetThemeColor(::GetSysColor(COLOR_HIGHLIGHT)));
687 SendEditor(SCI_SETCARETFORE, UDiffTextColorDark);
688 SendEditor(SCI_SETWHITESPACEFORE, true, RGB(180, 180, 180));
689 SetAStyle(STYLE_DEFAULT, UDiffTextColorDark, UDiffBackColorDark,
690 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10), fontName.c_str());
691 SetAStyle(SCE_DIFF_DEFAULT, UDiffTextColorDark, UDiffBackColorDark,
692 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10), fontName.c_str());
693 SetAStyle(SCE_DIFF_COMMAND,
694 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffForeCommandColor", UDIFF_COLORFORECOMMAND_DARK),
695 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffBackCommandColor", UDIFF_COLORBACKCOMMAND_DARK));
696 SetAStyle(SCE_DIFF_POSITION,
697 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffForePositionColor", UDIFF_COLORFOREPOSITION_DARK),
698 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffBackPositionColor", UDIFF_COLORBACKPOSITION_DARK));
699 SetAStyle(SCE_DIFF_HEADER,
700 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffForeHeaderColor", UDIFF_COLORFOREHEADER_DARK),
701 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffBackHeaderColor", UDIFF_COLORBACKHEADER_DARK));
702 SetAStyle(SCE_DIFF_COMMENT,
703 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffForeCommentColor", UDIFF_COLORFORECOMMENT_DARK),
704 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffBackCommentColor", UDIFF_COLORBACKCOMMENT_DARK));
705 for (int style : { SCE_DIFF_ADDED, SCE_DIFF_PATCH_ADD, SCE_DIFF_PATCH_DELETE })
707 SetAStyle(style,
708 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffForeAddedColor", UDIFF_COLORFOREADDED_DARK),
709 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffBackAddedColor", UDIFF_COLORBACKADDED_DARK));
711 for (int style : { SCE_DIFF_DELETED, SCE_DIFF_REMOVED_PATCH_ADD, SCE_DIFF_REMOVED_PATCH_DELETE })
713 SetAStyle(style,
714 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffForeRemovedColor", UDIFF_COLORFOREREMOVED_DARK),
715 CRegStdDWORD(L"Software\\TortoiseGit\\DarkUDiffBackRemovedColor", UDIFF_COLORBACKREMOVED_DARK));
718 else
720 SendEditor(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
721 SendEditor(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
722 SendEditor(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
723 SendEditor(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
724 SendEditor(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
725 SendEditor(SCI_SETWHITESPACEFORE, true, ::GetSysColor(COLOR_3DSHADOW));
726 SetAStyle(STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
727 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10), fontName.c_str());
728 SetAStyle(SCE_DIFF_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT), ::GetSysColor(COLOR_WINDOW),
729 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffFontSize", 10), fontName.c_str());
730 SetAStyle(SCE_DIFF_COMMAND,
731 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommandColor", UDIFF_COLORFORECOMMAND),
732 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommandColor", UDIFF_COLORBACKCOMMAND));
733 SetAStyle(SCE_DIFF_POSITION,
734 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForePositionColor", UDIFF_COLORFOREPOSITION),
735 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackPositionColor", UDIFF_COLORBACKPOSITION));
736 SetAStyle(SCE_DIFF_HEADER,
737 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeHeaderColor", UDIFF_COLORFOREHEADER),
738 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackHeaderColor", UDIFF_COLORBACKHEADER));
739 SetAStyle(SCE_DIFF_COMMENT,
740 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeCommentColor", UDIFF_COLORFORECOMMENT),
741 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackCommentColor", UDIFF_COLORBACKCOMMENT));
742 for (int style : { SCE_DIFF_ADDED, SCE_DIFF_PATCH_ADD, SCE_DIFF_PATCH_DELETE })
744 SetAStyle(style,
745 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeAddedColor", UDIFF_COLORFOREADDED),
746 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackAddedColor", UDIFF_COLORBACKADDED));
748 for (int style : { SCE_DIFF_DELETED, SCE_DIFF_REMOVED_PATCH_ADD, SCE_DIFF_REMOVED_PATCH_DELETE })
750 SetAStyle(style,
751 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffForeRemovedColor", UDIFF_COLORFOREREMOVED),
752 CRegStdDWORD(L"Software\\TortoiseGit\\UDiffBackRemovedColor", UDIFF_COLORBACKREMOVED));
755 SendEditor(SCI_SETFOLDMARGINCOLOUR, true, RGB(240, 240, 240));
756 SendEditor(SCI_SETFOLDMARGINHICOLOUR, true, RGB(255, 255, 255));
757 SendEditor(SCI_STYLESETFORE, STYLE_LINENUMBER, RGB(109, 109, 109));
758 SendEditor(SCI_STYLESETBACK, STYLE_LINENUMBER, RGB(230, 230, 230));
760 SendEditor(SCI_STYLESETFORE, STYLE_BRACELIGHT, RGB(0, 150, 0));
761 SendEditor(SCI_STYLESETBOLD, STYLE_BRACELIGHT, 1);
762 SendEditor(SCI_STYLESETFORE, STYLE_BRACEBAD, RGB(255, 0, 0));
763 SendEditor(SCI_STYLESETBOLD, STYLE_BRACEBAD, 1);
764 DarkModeHelper::Instance().RefreshTitleBarThemeColor(*this, bDark);
765 if (bDark || CTheme::Instance().IsHighContrastModeDark())
767 SendEditor(SCI_SETFOLDMARGINCOLOUR, true, UDiffTextColorDark);
768 SendEditor(SCI_SETFOLDMARGINHICOLOUR, true, CTheme::darkBkColor);
769 SendEditor(SCI_STYLESETFORE, STYLE_LINENUMBER, RGB(140, 140, 140));
770 SendEditor(SCI_STYLESETBACK, STYLE_LINENUMBER, UDiffBackColorDark);
773 auto curlexer = SendEditor(SCI_GETLEXER);
774 if (CTheme::Instance().IsHighContrastMode() && curlexer != SCLEX_NULL)
776 SendEditor(SCI_CLEARDOCUMENTSTYLE, 0, 0);
777 SendEditor(SCI_SETILEXER, 0, reinterpret_cast<sptr_t>(nullptr));
778 SendEditor(SCI_COLOURISE, 0, -1);
780 else if (!CTheme::Instance().IsHighContrastMode() && curlexer != SCLEX_DIFF)
782 SendEditor(SCI_CLEARDOCUMENTSTYLE, 0, 0);
783 SendEditor(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
784 SendEditor(SCI_SETILEXER, 0, reinterpret_cast<sptr_t>(CreateLexer("diff")));
785 SendEditor(SCI_STYLESETBOLD, SCE_DIFF_COMMENT, TRUE);
786 SendEditor(SCI_SETKEYWORDS, 0, reinterpret_cast<LPARAM>("revision"));
787 SendEditor(SCI_COLOURISE, 0, -1);
790 HMENU hMenu = GetMenu(*this);
791 UINT uCheck = MF_BYCOMMAND;
792 uCheck |= CTheme::Instance().IsDarkTheme() ? MF_CHECKED : MF_UNCHECKED;
793 CheckMenuItem(hMenu, ID_VIEW_DARKMODE, uCheck);
794 UINT uEnabled = MF_BYCOMMAND;
795 uEnabled |= CTheme::Instance().IsDarkModeAllowed() ? MF_ENABLED : MF_DISABLED;
796 EnableMenuItem(hMenu, ID_VIEW_DARKMODE, uEnabled);
798 ::RedrawWindow(*this, nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_INTERNALPAINT | RDW_ALLCHILDREN | RDW_UPDATENOW);
801 bool CMainWindow::LoadFile(HANDLE hFile, bool wantStdIn)
803 InitEditor();
804 char data[4096] = { 0 };
805 DWORD dwRead = 0;
807 BOOL bRet = ReadFile(hFile, data, sizeof(data), &dwRead, nullptr);
808 bool bUTF8 = IsUTF8(data, dwRead);
809 while ((dwRead > 0) && (bRet))
811 SendEditor(SCI_ADDTEXT, dwRead,
812 reinterpret_cast<LPARAM>(static_cast<char *>(data)));
813 if (auto sciStatus = static_cast<int>(SendEditor(SCI_GETSTATUS)); sciStatus > SC_STATUS_OK && sciStatus < SC_STATUS_WARN_START)
815 if (sciStatus == SC_STATUS_BADALLOC)
816 SetLastError(static_cast<DWORD>(E_OUTOFMEMORY));
817 else
818 SetLastError(static_cast<DWORD>(E_FAIL));
819 MessageBox(*this, static_cast<LPCWSTR>(CFormatMessageWrapper()), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
820 return false;
822 if (static_cast<Sci_Position>(SendEditor(SCI_GETTEXT)) >= 250 * 1024 * 1024) // styling gets really slow and Scintilla requires special initialization for large files
824 MessageBox(*this, static_cast<LPCWSTR>(ResString(hResource, IDS_ERR_FILE_TOOBIG)), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
825 return false;
827 bRet = ReadFile(hFile, data, sizeof(data), &dwRead, nullptr);
829 if (!bRet)
831 if (hFile || wantStdIn)
833 MessageBox(*this, static_cast<LPCWSTR>(CFormatMessageWrapper()), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
834 return false;
836 SetTitle(L"Unnamed");
837 InitEditor();
838 bUTF8 = true;
840 SetupWindow(bUTF8);
841 return true;
844 bool CMainWindow::LoadFile(LPCWSTR filename)
846 CAutoFile hfile = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
847 if (!hfile)
849 MessageBox(*this, static_cast<LPCWSTR>(CFormatMessageWrapper()), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
850 return false;
853 if (LARGE_INTEGER fileSize; !::GetFileSizeEx(hfile, &fileSize))
855 MessageBox(*this, static_cast<LPCWSTR>(CFormatMessageWrapper()), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
856 return false;
858 else if (fileSize.QuadPart >= 250 * 1024 * 1024) // styling gets really slow and Scintilla requires special initialization for large files
860 MessageBox(*this, static_cast<LPCWSTR>(ResString(hResource, IDS_ERR_FILE_TOOBIG)), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
861 return false;
864 char data[4096] = { 0 };
865 DWORD size = 0;
866 if (!ReadFile(hfile, data, sizeof(data), &size, nullptr))
868 MessageBox(*this, static_cast<LPCWSTR>(CFormatMessageWrapper()), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
869 return false;
871 bool bUTF8 = IsUTF8(data, size);
872 InitEditor();
873 bool error = false;
874 while (size > 0)
876 SendEditor(SCI_ADDTEXT, size,
877 reinterpret_cast<LPARAM>(static_cast<char *>(data)));
878 if (auto sciStatus = static_cast<int>(SendEditor(SCI_GETSTATUS)); sciStatus > SC_STATUS_OK && sciStatus < SC_STATUS_WARN_START)
880 if (sciStatus == SC_STATUS_BADALLOC)
881 SetLastError(static_cast<DWORD>(E_OUTOFMEMORY));
882 else
883 SetLastError(static_cast<DWORD>(E_FAIL));
884 error = true;
885 break;
887 if (!ReadFile(hfile, data, sizeof(data), &size, nullptr))
889 error = true;
890 break;
894 if (error)
896 MessageBox(*this, static_cast<LPCWSTR>(CFormatMessageWrapper()), L"TortoiseGitUDiff", MB_ICONEXCLAMATION);
897 InitEditor();
898 bUTF8 = true;
899 SetTitle(L"Unnamed");
901 else
903 m_filename = filename;
904 SetTitle(filename);
906 SetupWindow(bUTF8);
907 return !error;
910 void CMainWindow::InitEditor()
912 m_filename.clear();
913 SendEditor(SCI_SETREADONLY, FALSE);
914 SendEditor(SCI_CLEARALL);
915 SendEditor(SCI_EMPTYUNDOBUFFER);
916 SendEditor(SCI_SETSAVEPOINT);
917 SendEditor(SCI_CANCEL);
918 SendEditor(SCI_SETUNDOCOLLECTION, 0);
921 void CMainWindow::SetupWindow(bool bUTF8)
923 SendEditor(SCI_SETCODEPAGE, bUTF8 ? SC_CP_UTF8 : GetACP());
925 SendEditor(SCI_SETUNDOCOLLECTION, 1);
926 ::SetFocus(m_hWndEdit);
927 SendEditor(SCI_EMPTYUNDOBUFFER);
928 SendEditor(SCI_SETSAVEPOINT);
929 SendEditor(SCI_GOTOPOS, 0);
931 ::ShowWindow(m_hWndEdit, SW_SHOW);
934 bool CMainWindow::SaveFile(LPCWSTR filename)
936 FILE* fp = nullptr;
937 _wfopen_s(&fp, filename, L"w+b");
938 if (!fp)
940 wchar_t fmt[1024] = { 0 };
941 LoadString(::hResource, IDS_ERRORSAVE, fmt, _countof(fmt));
942 wchar_t error[1024] = { 0 };
943 _snwprintf_s(error, _countof(error), fmt, filename, static_cast<LPCWSTR>(CFormatMessageWrapper()));
944 MessageBox(*this, error, L"TortoiseGitUDiff", MB_OK);
945 return false;
948 auto len = static_cast<int>(SendEditor(SCI_GETTEXT, 0, 0));
949 auto data = std::make_unique<char[]>(len + 1);
950 SendEditor(SCI_GETTEXT, len, reinterpret_cast<LPARAM>(static_cast<char *>(data.get())));
951 fwrite(data.get(), sizeof(char), len, fp);
952 fclose(fp);
954 SendEditor(SCI_SETSAVEPOINT);
955 ::ShowWindow(m_hWndEdit, SW_SHOW);
956 return true;
959 void CMainWindow::SetTitle(LPCWSTR title)
961 size_t len = wcslen(title);
962 auto pBuf = std::make_unique<wchar_t[]>(len + 40);
963 swprintf_s(pBuf.get(), len + 40, L"%s - TortoiseGitUDiff", title);
964 SetWindowTitle(std::wstring(pBuf.get()));
967 void CMainWindow::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
969 SendEditor(SCI_STYLESETFORE, style, fore);
970 SendEditor(SCI_STYLESETBACK, style, back);
971 if (size >= 1)
972 SendEditor(SCI_STYLESETSIZE, style, size);
973 if (face)
974 SendEditor(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
977 bool CMainWindow::IsUTF8(LPVOID pBuffer, size_t cb)
979 if (cb < 2)
980 return true;
981 auto pVal16 = static_cast<UINT16*>(pBuffer);
982 auto pVal8 = reinterpret_cast<UINT8*>(pVal16 + 1);
983 // scan the whole buffer for a 0x0000 sequence
984 // if found, we assume a binary file
985 for (size_t i=0; i<(cb-2); i=i+2)
987 if (0x0000 == *pVal16++)
988 return false;
990 pVal16 = static_cast<UINT16*>(pBuffer);
991 if (*pVal16 == 0xFEFF)
992 return false;
993 if (cb < 3)
994 return false;
995 if (*pVal16 == 0xBBEF)
997 if (*pVal8 == 0xBF)
998 return true;
1000 // check for illegal UTF8 chars
1001 pVal8 = static_cast<UINT8*>(pBuffer);
1002 for (size_t i=0; i<cb; ++i)
1004 if ((*pVal8 == 0xC0)||(*pVal8 == 0xC1)||(*pVal8 >= 0xF5))
1005 return false;
1006 pVal8++;
1008 pVal8 = static_cast<UINT8*>(pBuffer);
1009 bool bUTF8 = false;
1010 for (size_t i=0; i<(cb-3); ++i)
1012 if ((*pVal8 & 0xE0)==0xC0)
1014 pVal8++;i++;
1015 if ((*pVal8 & 0xC0)!=0x80)
1016 return false;
1017 bUTF8 = true;
1019 else if ((*pVal8 & 0xF0)==0xE0)
1021 pVal8++;i++;
1022 if ((*pVal8 & 0xC0)!=0x80)
1023 return false;
1024 pVal8++;i++;
1025 if ((*pVal8 & 0xC0)!=0x80)
1026 return false;
1027 bUTF8 = true;
1029 else if ((*pVal8 & 0xF8)==0xF0)
1031 pVal8++;i++;
1032 if ((*pVal8 & 0xC0)!=0x80)
1033 return false;
1034 pVal8++;i++;
1035 if ((*pVal8 & 0xC0)!=0x80)
1036 return false;
1037 pVal8++;i++;
1038 if ((*pVal8 & 0xC0)!=0x80)
1039 return false;
1040 bUTF8 = true;
1042 else if (*pVal8 >= 0x80)
1043 return false;
1045 pVal8++;
1047 if (bUTF8)
1048 return true;
1049 return false;
1052 bool CMainWindow::canCloseWhenModified()
1054 if (SendEditor(SCI_GETMODIFY) != TRUE)
1055 return true;
1057 wchar_t question[1024] = { 0 };
1058 LoadString(::hResource, IDS_MODIFIEDASKSAVE, question, _countof(question));
1059 switch (MessageBox(m_hwnd, question, L"TortoiseGitUDiff", MB_YESNOCANCEL | MB_ICONQUESTION))
1061 case IDNO:
1062 return true;
1063 case IDYES:
1064 return loadOrSaveFile(false, m_filename);
1065 default:
1066 return false;
1070 bool CMainWindow::loadOrSaveFile(bool doLoad, const std::wstring& filename /* = L"" */)
1072 OPENFILENAME ofn = {0}; // common dialog box structure
1073 wchar_t szFile[MAX_PATH] = {0}; // buffer for file name
1074 // Initialize OPENFILENAME
1075 ofn.lStructSize = sizeof(OPENFILENAME);
1076 ofn.hwndOwner = *this;
1077 ofn.lpstrFile = szFile;
1078 ofn.nMaxFile = sizeof(szFile)/sizeof(wchar_t);
1079 wchar_t filter[1024] = { 0 };
1080 LoadString(::hResource, IDS_PATCHFILEFILTER, filter, sizeof(filter) / sizeof(wchar_t));
1081 CStringUtils::PipesToNulls(filter);
1082 ofn.lpstrFilter = filter;
1083 ofn.nFilterIndex = 1;
1084 ofn.lpstrFileTitle = nullptr;
1085 ofn.lpstrDefExt = L"diff";
1086 ofn.nMaxFileTitle = 0;
1087 ofn.lpstrInitialDir = nullptr;
1088 wchar_t fileTitle[1024] = { 0 };
1089 LoadString(::hResource, doLoad ? IDS_OPENPATCH : IDS_SAVEPATCH, fileTitle, sizeof(fileTitle) / sizeof(wchar_t));
1090 ofn.lpstrTitle = fileTitle;
1091 ofn.Flags = OFN_ENABLESIZING | OFN_EXPLORER;
1092 if(doLoad)
1093 ofn.Flags |= OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
1094 else
1095 ofn.Flags |= OFN_OVERWRITEPROMPT;
1096 // Display the Open dialog box.
1097 if( doLoad )
1099 if (GetOpenFileName(&ofn)==TRUE)
1101 return LoadFile(ofn.lpstrFile);
1103 return false;
1105 else
1107 if (filename.empty())
1109 if (GetSaveFileName(&ofn)==TRUE)
1111 return SaveFile(ofn.lpstrFile);
1113 return false;
1115 else
1116 return SaveFile(filename.c_str());
1120 void CMainWindow::DoSearch(bool reverse)
1122 Sci_Position lastcursor = SendEditor(SCI_GETSELECTIONEND);
1123 if (!reverse)
1124 SendEditor(SCI_SETTARGETRANGE, lastcursor, SendEditor(SCI_GETLENGTH));
1125 else
1127 lastcursor = SendEditor(SCI_GETSELECTIONSTART);
1128 SendEditor(SCI_SETTARGETRANGE, lastcursor, 0);
1131 auto searchText = CUnicodeUtils::StdGetUTF8(m_findtext);
1132 SendEditor(SCI_SETSEARCHFLAGS, m_bMatchCase ? SCFIND_MATCHCASE : SCFIND_NONE);
1133 if (auto pos = SendEditor(SCI_SEARCHINTARGET, searchText.length(), reinterpret_cast<LPARAM>(static_cast<LPCSTR>(searchText.c_str()))); pos >= 0)
1135 SendEditor(SCI_SETSELECTION, pos, pos + searchText.length());
1136 SendEditor(SCI_SCROLLCARET);
1138 else
1140 SendEditor(SCI_SETSELECTION, lastcursor, lastcursor);
1141 FLASHWINFO fwi;
1142 fwi.cbSize = sizeof(FLASHWINFO);
1143 fwi.uCount = 3;
1144 fwi.dwTimeout = 100;
1145 fwi.dwFlags = FLASHW_ALL;
1146 fwi.hwnd = m_hwnd;
1147 FlashWindowEx(&fwi);