Merge branch 'format-titles'
[TortoiseGit.git] / src / TortoiseBlame / TortoiseBlame.cpp
blob4c908fcef105ac5abf8a49c59310ab982703529e
1 // TortoiseBlame - a Viewer for Subversion Blames
3 // Copyright (C) 2003-2008 - TortoiseSVN
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software Foundation,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 #include "stdafx.h"
20 #include "CmdLineParser.h"
21 #include "TortoiseBlame.h"
22 #include "registry.h"
23 #include "LangDll.h"
25 #define MAX_LOADSTRING 1000
27 #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
29 #pragma warning(push)
30 #pragma warning(disable:4127) // conditional expression is constant
32 // Global Variables:
33 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
34 TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
35 TCHAR szViewtitle[MAX_PATH];
36 TCHAR szOrigPath[MAX_PATH];
37 TCHAR searchstringnotfound[MAX_LOADSTRING];
39 const bool ShowDate = false;
40 const bool ShowAuthor = true;
41 const bool ShowLine = true;
42 bool ShowPath = false;
44 static TortoiseBlame app;
45 long TortoiseBlame::m_gotoline = 0;
47 TortoiseBlame::TortoiseBlame()
49 hInstance = 0;
50 hResource = 0;
51 currentDialog = 0;
52 wMain = 0;
53 wEditor = 0;
54 wLocator = 0;
56 m_font = 0;
57 m_italicfont = 0;
58 m_blamewidth = 0;
59 m_revwidth = 0;
60 m_datewidth = 0;
61 m_authorwidth = 0;
62 m_pathwidth = 0;
63 m_linewidth = 0;
65 m_windowcolor = ::GetSysColor(COLOR_WINDOW);
66 m_textcolor = ::GetSysColor(COLOR_WINDOWTEXT);
67 m_texthighlightcolor = ::GetSysColor(COLOR_HIGHLIGHTTEXT);
68 m_mouserevcolor = InterColor(m_windowcolor, m_textcolor, 20);
69 m_mouseauthorcolor = InterColor(m_windowcolor, m_textcolor, 10);
70 m_selectedrevcolor = ::GetSysColor(COLOR_HIGHLIGHT);
71 m_selectedauthorcolor = InterColor(m_selectedrevcolor, m_texthighlightcolor, 35);
72 m_mouserev = -2;
74 m_selectedrev = -1;
75 m_selectedorigrev = -1;
76 m_SelectedLine = -1;
77 m_directPointer = 0;
78 m_directFunction = 0;
80 m_lowestrev = LONG_MAX;
81 m_highestrev = 0;
82 m_colorage = true;
85 TortoiseBlame::~TortoiseBlame()
87 if (m_font)
88 DeleteObject(m_font);
89 if (m_italicfont)
90 DeleteObject(m_italicfont);
93 std::string TortoiseBlame::GetAppDirectory()
95 std::string path;
96 DWORD len = 0;
97 DWORD bufferlen = MAX_PATH; // MAX_PATH is not the limit here!
100 bufferlen += MAX_PATH; // MAX_PATH is not the limit here!
101 TCHAR * pBuf = new TCHAR[bufferlen];
102 len = GetModuleFileName(NULL, pBuf, bufferlen);
103 path = std::string(pBuf, len);
104 delete [] pBuf;
105 } while(len == bufferlen);
106 path = path.substr(0, path.rfind('\\') + 1);
108 return path;
111 // Return a color which is interpolated between c1 and c2.
112 // Slider controls the relative proportions as a percentage:
113 // Slider = 0 represents pure c1
114 // Slider = 50 represents equal mixture
115 // Slider = 100 represents pure c2
116 COLORREF TortoiseBlame::InterColor(COLORREF c1, COLORREF c2, int Slider)
118 int r, g, b;
120 // Limit Slider to 0..100% range
121 if (Slider < 0)
122 Slider = 0;
123 if (Slider > 100)
124 Slider = 100;
126 // The color components have to be treated individually.
127 r = (GetRValue(c2) * Slider + GetRValue(c1) * (100 - Slider)) / 100;
128 g = (GetGValue(c2) * Slider + GetGValue(c1) * (100 - Slider)) / 100;
129 b = (GetBValue(c2) * Slider + GetBValue(c1) * (100 - Slider)) / 100;
131 return RGB(r, g, b);
134 LRESULT TortoiseBlame::SendEditor(UINT Msg, WPARAM wParam, LPARAM lParam)
136 if (m_directFunction)
138 return ((SciFnDirect) m_directFunction)(m_directPointer, Msg, wParam, lParam);
140 return ::SendMessage(wEditor, Msg, wParam, lParam);
143 void TortoiseBlame::GetRange(int start, int end, char *text)
145 TEXTRANGE tr;
146 tr.chrg.cpMin = start;
147 tr.chrg.cpMax = end;
148 tr.lpstrText = text;
149 SendMessage(wEditor, EM_GETTEXTRANGE, 0, reinterpret_cast<LPARAM>(&tr));
152 void TortoiseBlame::SetTitle()
154 char title[MAX_PATH + 100];
155 strcpy_s(title, MAX_PATH + 100, szTitle);
156 strcat_s(title, MAX_PATH + 100, " - ");
157 strcat_s(title, MAX_PATH + 100, szViewtitle);
158 ::SetWindowText(wMain, title);
161 BOOL TortoiseBlame::OpenLogFile(const char *fileName)
163 char logmsgbuf[10000+1];
164 FILE * File;
165 fopen_s(&File, fileName, "rb");
166 if (File == 0)
168 return FALSE;
170 LONG rev = 0;
171 std::string msg;
172 int slength = 0;
173 int reallength = 0;
174 size_t len = 0;
175 wchar_t wbuf[MAX_LOG_LENGTH+6];
176 for (;;)
178 len = fread(&rev, sizeof(LONG), 1, File);
179 if (len == 0)
181 fclose(File);
182 InitSize();
183 return TRUE;
185 len = fread(&slength, sizeof(int), 1, File);
186 if (len == 0)
188 fclose(File);
189 InitSize();
190 return FALSE;
192 if (slength > MAX_LOG_LENGTH)
194 reallength = slength;
195 slength = MAX_LOG_LENGTH;
197 else
198 reallength = 0;
199 len = fread(logmsgbuf, sizeof(char), slength, File);
200 if (len < (size_t)slength)
202 fclose(File);
203 InitSize();
204 return FALSE;
206 msg = std::string(logmsgbuf, slength);
207 if (reallength)
209 fseek(File, reallength-MAX_LOG_LENGTH, SEEK_CUR);
210 msg = msg + _T("\n...");
212 int len2 = ::MultiByteToWideChar(CP_UTF8, NULL, msg.c_str(), min(msg.size(), MAX_LOG_LENGTH+5), wbuf, MAX_LOG_LENGTH+5);
213 wbuf[len2] = 0;
214 len2 = ::WideCharToMultiByte(CP_ACP, NULL, wbuf, len2, logmsgbuf, MAX_LOG_LENGTH+5, NULL, NULL);
215 logmsgbuf[len2] = 0;
216 msg = std::string(logmsgbuf);
217 logmessages[rev] = msg;
221 BOOL TortoiseBlame::OpenFile(const char *fileName)
223 SendEditor(SCI_SETREADONLY, FALSE);
224 SendEditor(SCI_CLEARALL);
225 SendEditor(EM_EMPTYUNDOBUFFER);
226 SetTitle();
227 SendEditor(SCI_SETSAVEPOINT);
228 SendEditor(SCI_CANCEL);
229 SendEditor(SCI_SETUNDOCOLLECTION, 0);
230 ::ShowWindow(wEditor, SW_HIDE);
231 std::ifstream File;
232 File.open(fileName);
233 if (!File.good())
235 return FALSE;
237 char line[100*1024];
238 char * lineptr = NULL;
239 char * trimptr = NULL;
240 //ignore the first two lines, they're of no interest to us
241 File.getline(line, sizeof(line)/sizeof(char));
242 File.getline(line, sizeof(line)/sizeof(char));
243 m_lowestrev = LONG_MAX;
244 m_highestrev = 0;
245 bool bUTF8 = true;
248 File.getline(line, sizeof(line)/sizeof(TCHAR));
249 if (File.gcount()>139)
251 mergelines.push_back((line[0] != ' '));
252 lineptr = &line[9];
253 long rev = _ttol(lineptr);
254 revs.push_back(rev);
255 m_lowestrev = min(m_lowestrev, rev);
256 m_highestrev = max(m_highestrev, rev);
257 lineptr += 7;
258 rev = _ttol(lineptr);
259 origrevs.push_back(rev);
260 lineptr += 7;
261 dates.push_back(std::string(lineptr, 30));
262 lineptr += 31;
263 // unfortunately, the 'path' entry can be longer than the 60 chars
264 // we made the column. We therefore have to step through the path
265 // string until we find a space
266 trimptr = lineptr;
269 // TODO: how can we deal with the situation where the path has
270 // a space in it, but the space is after the 60 chars reserved
271 // for it?
272 // The only way to deal with that would be to use a custom
273 // binary format for the blame file.
274 trimptr++;
275 trimptr = _tcschr(trimptr, ' ');
276 } while ((trimptr)&&(trimptr+1 < lineptr+61));
277 if (trimptr)
278 *trimptr = 0;
279 else
280 trimptr = lineptr;
281 paths.push_back(std::string(lineptr));
282 if (trimptr+1 < lineptr+61)
283 lineptr +=61;
284 else
285 lineptr = (trimptr+1);
286 trimptr = lineptr+30;
287 while ((*trimptr == ' ')&&(trimptr > lineptr))
288 trimptr--;
289 *(trimptr+1) = 0;
290 authors.push_back(std::string(lineptr));
291 lineptr += 31;
292 // in case we find an UTF8 BOM at the beginning of the line, we remove it
293 if (((unsigned char)lineptr[0] == 0xEF)&&((unsigned char)lineptr[1] == 0xBB)&&((unsigned char)lineptr[2] == 0xBF))
295 lineptr += 3;
297 if (((unsigned char)lineptr[0] == 0xBB)&&((unsigned char)lineptr[1] == 0xEF)&&((unsigned char)lineptr[2] == 0xBF))
299 lineptr += 3;
301 // check each line for illegal utf8 sequences. If one is found, we treat
302 // the file as ASCII, otherwise we assume an UTF8 file.
303 char * utf8CheckBuf = lineptr;
304 while ((bUTF8)&&(*utf8CheckBuf))
306 if ((*utf8CheckBuf == 0xC0)||(*utf8CheckBuf == 0xC1)||(*utf8CheckBuf >= 0xF5))
308 bUTF8 = false;
309 break;
311 if ((*utf8CheckBuf & 0xE0)==0xC0)
313 utf8CheckBuf++;
314 if (*utf8CheckBuf == 0)
315 break;
316 if ((*utf8CheckBuf & 0xC0)!=0x80)
318 bUTF8 = false;
319 break;
322 if ((*utf8CheckBuf & 0xF0)==0xE0)
324 utf8CheckBuf++;
325 if (*utf8CheckBuf == 0)
326 break;
327 if ((*utf8CheckBuf & 0xC0)!=0x80)
329 bUTF8 = false;
330 break;
332 utf8CheckBuf++;
333 if (*utf8CheckBuf == 0)
334 break;
335 if ((*utf8CheckBuf & 0xC0)!=0x80)
337 bUTF8 = false;
338 break;
341 if ((*utf8CheckBuf & 0xF8)==0xF0)
343 utf8CheckBuf++;
344 if (*utf8CheckBuf == 0)
345 break;
346 if ((*utf8CheckBuf & 0xC0)!=0x80)
348 bUTF8 = false;
349 break;
351 utf8CheckBuf++;
352 if (*utf8CheckBuf == 0)
353 break;
354 if ((*utf8CheckBuf & 0xC0)!=0x80)
356 bUTF8 = false;
357 break;
359 utf8CheckBuf++;
360 if (*utf8CheckBuf == 0)
361 break;
362 if ((*utf8CheckBuf & 0xC0)!=0x80)
364 bUTF8 = false;
365 break;
369 utf8CheckBuf++;
371 SendEditor(SCI_ADDTEXT, _tcslen(lineptr), reinterpret_cast<LPARAM>(static_cast<char *>(lineptr)));
372 SendEditor(SCI_ADDTEXT, 2, (LPARAM)_T("\r\n"));
374 } while (File.gcount() > 0);
376 if (bUTF8)
377 SendEditor(SCI_SETCODEPAGE, SC_CP_UTF8);
379 SendEditor(SCI_SETUNDOCOLLECTION, 1);
380 ::SetFocus(wEditor);
381 SendEditor(EM_EMPTYUNDOBUFFER);
382 SendEditor(SCI_SETSAVEPOINT);
383 SendEditor(SCI_GOTOPOS, 0);
384 SendEditor(SCI_SETSCROLLWIDTHTRACKING, TRUE);
385 SendEditor(SCI_SETREADONLY, TRUE);
387 //check which lexer to use, depending on the filetype
388 SetupLexer(fileName);
389 ::ShowWindow(wEditor, SW_SHOW);
390 m_blamewidth = 0;
391 ::InvalidateRect(wMain, NULL, TRUE);
392 RECT rc;
393 GetWindowRect(wMain, &rc);
394 SetWindowPos(wMain, 0, rc.left, rc.top, rc.right-rc.left-1, rc.bottom - rc.top, 0);
395 return TRUE;
398 void TortoiseBlame::SetAStyle(int style, COLORREF fore, COLORREF back, int size, const char *face)
400 SendEditor(SCI_STYLESETFORE, style, fore);
401 SendEditor(SCI_STYLESETBACK, style, back);
402 if (size >= 1)
403 SendEditor(SCI_STYLESETSIZE, style, size);
404 if (face)
405 SendEditor(SCI_STYLESETFONT, style, reinterpret_cast<LPARAM>(face));
408 void TortoiseBlame::InitialiseEditor()
410 m_directFunction = SendMessage(wEditor, SCI_GETDIRECTFUNCTION, 0, 0);
411 m_directPointer = SendMessage(wEditor, SCI_GETDIRECTPOINTER, 0, 0);
412 // Set up the global default style. These attributes are used wherever no explicit choices are made.
413 SetAStyle(STYLE_DEFAULT, black, white, (DWORD)CRegStdWORD(_T("Software\\TortoiseGit\\BlameFontSize"), 10),
414 ((stdstring)(CRegStdString(_T("Software\\TortoiseGit\\BlameFontName"), _T("Courier New")))).c_str());
415 SendEditor(SCI_SETTABWIDTH, (DWORD)CRegStdWORD(_T("Software\\TortoiseGit\\BlameTabSize"), 4));
416 SendEditor(SCI_SETREADONLY, TRUE);
417 LRESULT pix = SendEditor(SCI_TEXTWIDTH, STYLE_LINENUMBER, (LPARAM)_T("_99999"));
418 if (ShowLine)
419 SendEditor(SCI_SETMARGINWIDTHN, 0, pix);
420 else
421 SendEditor(SCI_SETMARGINWIDTHN, 0);
422 SendEditor(SCI_SETMARGINWIDTHN, 1);
423 SendEditor(SCI_SETMARGINWIDTHN, 2);
424 //Set the default windows colors for edit controls
425 SendEditor(SCI_STYLESETFORE, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOWTEXT));
426 SendEditor(SCI_STYLESETBACK, STYLE_DEFAULT, ::GetSysColor(COLOR_WINDOW));
427 SendEditor(SCI_SETSELFORE, TRUE, ::GetSysColor(COLOR_HIGHLIGHTTEXT));
428 SendEditor(SCI_SETSELBACK, TRUE, ::GetSysColor(COLOR_HIGHLIGHT));
429 SendEditor(SCI_SETCARETFORE, ::GetSysColor(COLOR_WINDOWTEXT));
430 m_regOldLinesColor = CRegStdWORD(_T("Software\\TortoiseGit\\BlameOldColor"), RGB(230, 230, 255));
431 m_regNewLinesColor = CRegStdWORD(_T("Software\\TortoiseGit\\BlameNewColor"), RGB(255, 230, 230));
434 void TortoiseBlame::StartSearch()
436 if (currentDialog)
437 return;
438 bool bCase = false;
439 // Initialize FINDREPLACE
440 if (fr.Flags & FR_MATCHCASE)
441 bCase = true;
442 SecureZeroMemory(&fr, sizeof(fr));
443 fr.lStructSize = sizeof(fr);
444 fr.hwndOwner = wMain;
445 fr.lpstrFindWhat = szFindWhat;
446 fr.wFindWhatLen = 80;
447 fr.Flags = FR_HIDEUPDOWN | FR_HIDEWHOLEWORD;
448 fr.Flags |= bCase ? FR_MATCHCASE : 0;
450 currentDialog = FindText(&fr);
453 bool TortoiseBlame::DoSearch(LPSTR what, DWORD flags)
455 TCHAR szWhat[80];
456 int pos = SendEditor(SCI_GETCURRENTPOS);
457 int line = SendEditor(SCI_LINEFROMPOSITION, pos);
458 bool bFound = false;
459 bool bCaseSensitive = !!(flags & FR_MATCHCASE);
461 strcpy_s(szWhat, sizeof(szWhat), what);
463 if(!bCaseSensitive)
465 char *p;
466 size_t len = strlen(szWhat);
467 for (p = szWhat; p < szWhat + len; p++)
469 if (isupper(*p)&&__isascii(*p))
470 *p = _tolower(*p);
474 std::string sWhat = std::string(szWhat);
476 char buf[20];
477 int i=0;
478 for (i=line; (i<(int)authors.size())&&(!bFound); ++i)
480 int bufsize = SendEditor(SCI_GETLINE, i);
481 char * linebuf = new char[bufsize+1];
482 SecureZeroMemory(linebuf, bufsize+1);
483 SendEditor(SCI_GETLINE, i, (LPARAM)linebuf);
484 if (!bCaseSensitive)
486 char *p;
487 for (p = linebuf; p < linebuf + bufsize; p++)
489 if (isupper(*p)&&__isascii(*p))
490 *p = _tolower(*p);
493 _stprintf_s(buf, 20, _T("%ld"), revs[i]);
494 if (authors[i].compare(sWhat)==0)
495 bFound = true;
496 else if ((!bCaseSensitive)&&(_stricmp(authors[i].c_str(), szWhat)==0))
497 bFound = true;
498 else if (strcmp(buf, szWhat) == 0)
499 bFound = true;
500 else if (strstr(linebuf, szWhat))
501 bFound = true;
502 delete [] linebuf;
504 if (!bFound)
506 for (i=0; (i<line)&&(!bFound); ++i)
508 int bufsize = SendEditor(SCI_GETLINE, i);
509 char * linebuf = new char[bufsize+1];
510 SecureZeroMemory(linebuf, bufsize+1);
511 SendEditor(SCI_GETLINE, i, (LPARAM)linebuf);
512 if (!bCaseSensitive)
514 char *p;
515 for (p = linebuf; p < linebuf + bufsize; p++)
517 if (isupper(*p)&&__isascii(*p))
518 *p = _tolower(*p);
521 _stprintf_s(buf, 20, _T("%ld"), revs[i]);
522 if (authors[i].compare(sWhat)==0)
523 bFound = true;
524 else if ((!bCaseSensitive)&&(_stricmp(authors[i].c_str(), szWhat)==0))
525 bFound = true;
526 else if (strcmp(buf, szWhat) == 0)
527 bFound = true;
528 else if (strstr(linebuf, szWhat))
529 bFound = true;
530 delete [] linebuf;
533 if (bFound)
535 GotoLine(i);
536 int selstart = SendEditor(SCI_GETCURRENTPOS);
537 int selend = SendEditor(SCI_POSITIONFROMLINE, i);
538 SendEditor(SCI_SETSELECTIONSTART, selstart);
539 SendEditor(SCI_SETSELECTIONEND, selend);
540 m_SelectedLine = i-1;
542 else
544 ::MessageBox(wMain, searchstringnotfound, "TortoiseBlame", MB_ICONINFORMATION);
546 return true;
549 bool TortoiseBlame::GotoLine(long line)
551 --line;
552 if (line < 0)
553 return false;
554 if ((unsigned long)line >= authors.size())
556 line = authors.size()-1;
559 int nCurrentPos = SendEditor(SCI_GETCURRENTPOS);
560 int nCurrentLine = SendEditor(SCI_LINEFROMPOSITION,nCurrentPos);
561 int nFirstVisibleLine = SendEditor(SCI_GETFIRSTVISIBLELINE);
562 int nLinesOnScreen = SendEditor(SCI_LINESONSCREEN);
564 if ( line>=nFirstVisibleLine && line<=nFirstVisibleLine+nLinesOnScreen)
566 // no need to scroll
567 SendEditor(SCI_GOTOLINE, line);
569 else
571 // Place the requested line one third from the top
572 if ( line > nCurrentLine )
574 SendEditor(SCI_GOTOLINE, (WPARAM)(line+(int)nLinesOnScreen*(2/3.0)));
576 else
578 SendEditor(SCI_GOTOLINE, (WPARAM)(line-(int)nLinesOnScreen*(1/3.0)));
582 // Highlight the line
583 int nPosStart = SendEditor(SCI_POSITIONFROMLINE,line);
584 int nPosEnd = SendEditor(SCI_GETLINEENDPOSITION,line);
585 SendEditor(SCI_SETSEL,nPosEnd,nPosStart);
587 return true;
590 bool TortoiseBlame::ScrollToLine(long line)
592 if (line < 0)
593 return false;
595 int nCurrentLine = SendEditor(SCI_GETFIRSTVISIBLELINE);
597 int scrolldelta = line - nCurrentLine;
598 SendEditor(SCI_LINESCROLL, 0, scrolldelta);
600 return true;
603 void TortoiseBlame::CopySelectedLogToClipboard()
605 if (m_selectedrev <= 0)
606 return;
607 std::map<LONG, std::string>::iterator iter;
608 if ((iter = app.logmessages.find(m_selectedrev)) != app.logmessages.end())
610 std::string msg;
611 msg += m_selectedauthor;
612 msg += " ";
613 msg += app.m_selecteddate;
614 msg += '\n';
615 msg += iter->second;
616 msg += _T("\n");
617 if (OpenClipboard(app.wBlame))
619 EmptyClipboard();
620 HGLOBAL hClipboardData;
621 hClipboardData = GlobalAlloc(GMEM_DDESHARE, msg.size()+1);
622 char * pchData;
623 pchData = (char*)GlobalLock(hClipboardData);
624 strcpy_s(pchData, msg.size()+1, msg.c_str());
625 GlobalUnlock(hClipboardData);
626 SetClipboardData(CF_TEXT,hClipboardData);
627 CloseClipboard();
632 void TortoiseBlame::BlamePreviousRevision()
634 LONG nRevisionTo = m_selectedorigrev - 1;
635 if ( nRevisionTo<1 )
637 return;
640 // We now determine the smallest revision number in the blame file (but ignore "-1")
641 // We do this for two reasons:
642 // 1. we respect the "From revision" which the user entered
643 // 2. we speed up the call of "svn blame" because previous smaller revision numbers don't have any effect on the result
644 LONG nSmallestRevision = -1;
645 for (LONG line=0;line<(LONG)app.revs.size();line++)
647 const LONG nRevision = app.revs[line];
648 if ( nRevision > 0 )
650 if ( nSmallestRevision < 1 )
652 nSmallestRevision = nRevision;
654 else
656 nSmallestRevision = min(nSmallestRevision,nRevision);
661 char bufStartRev[20];
662 _stprintf_s(bufStartRev, 20, _T("%d"), nSmallestRevision);
664 char bufEndRev[20];
665 _stprintf_s(bufEndRev, 20, _T("%d"), nRevisionTo);
667 char bufLine[20];
668 _stprintf_s(bufLine, 20, _T("%d"), m_SelectedLine+1); //using the current line is a good guess.
670 STARTUPINFO startup;
671 PROCESS_INFORMATION process;
672 memset(&startup, 0, sizeof(startup));
673 startup.cb = sizeof(startup);
674 memset(&process, 0, sizeof(process));
675 stdstring tortoiseProcPath = GetAppDirectory() + _T("TortoiseProc.exe");
676 stdstring svnCmd = _T(" /command:blame ");
677 svnCmd += _T(" /path:\"");
678 svnCmd += szOrigPath;
679 svnCmd += _T("\"");
680 svnCmd += _T(" /startrev:");
681 svnCmd += bufStartRev;
682 svnCmd += _T(" /endrev:");
683 svnCmd += bufEndRev;
684 svnCmd += _T(" /line:");
685 svnCmd += bufLine;
686 if (bIgnoreEOL)
687 svnCmd += _T(" /ignoreeol");
688 if (bIgnoreSpaces)
689 svnCmd += _T(" /ignorespaces");
690 if (bIgnoreAllSpaces)
691 svnCmd += _T(" /ignoreallspaces");
692 if (CreateProcess(tortoiseProcPath.c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process))
694 CloseHandle(process.hThread);
695 CloseHandle(process.hProcess);
699 void TortoiseBlame::DiffPreviousRevision()
701 LONG nRevisionTo = m_selectedorigrev;
702 if ( nRevisionTo<1 )
704 return;
707 LONG nRevisionFrom = nRevisionTo-1;
709 char bufStartRev[20];
710 _stprintf_s(bufStartRev, 20, _T("%d"), nRevisionFrom);
712 char bufEndRev[20];
713 _stprintf_s(bufEndRev, 20, _T("%d"), nRevisionTo);
715 STARTUPINFO startup;
716 PROCESS_INFORMATION process;
717 memset(&startup, 0, sizeof(startup));
718 startup.cb = sizeof(startup);
719 memset(&process, 0, sizeof(process));
720 stdstring tortoiseProcPath = GetAppDirectory() + _T("TortoiseProc.exe");
721 stdstring svnCmd = _T(" /command:diff ");
722 svnCmd += _T(" /path:\"");
723 svnCmd += szOrigPath;
724 svnCmd += _T("\"");
725 svnCmd += _T(" /startrev:");
726 svnCmd += bufStartRev;
727 svnCmd += _T(" /endrev:");
728 svnCmd += bufEndRev;
729 if (CreateProcess(tortoiseProcPath.c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process))
731 CloseHandle(process.hThread);
732 CloseHandle(process.hProcess);
736 void TortoiseBlame::ShowLog()
738 char bufRev[20];
739 _stprintf_s(bufRev, 20, _T("%d"), m_selectedorigrev);
741 STARTUPINFO startup;
742 PROCESS_INFORMATION process;
743 memset(&startup, 0, sizeof(startup));
744 startup.cb = sizeof(startup);
745 memset(&process, 0, sizeof(process));
746 stdstring tortoiseProcPath = GetAppDirectory() + _T("TortoiseProc.exe");
747 stdstring svnCmd = _T(" /command:log ");
748 svnCmd += _T(" /path:\"");
749 svnCmd += szOrigPath;
750 svnCmd += _T("\"");
751 svnCmd += _T(" /startrev:");
752 svnCmd += bufRev;
753 svnCmd += _T(" /pegrev:");
754 svnCmd += bufRev;
755 if (CreateProcess(tortoiseProcPath.c_str(), const_cast<TCHAR*>(svnCmd.c_str()), NULL, NULL, FALSE, 0, 0, 0, &startup, &process))
757 CloseHandle(process.hThread);
758 CloseHandle(process.hProcess);
762 void TortoiseBlame::Notify(SCNotification *notification)
764 switch (notification->nmhdr.code)
766 case SCN_SAVEPOINTREACHED:
767 break;
769 case SCN_SAVEPOINTLEFT:
770 break;
771 case SCN_PAINTED:
772 InvalidateRect(wBlame, NULL, FALSE);
773 InvalidateRect(wLocator, NULL, FALSE);
774 break;
775 case SCN_GETBKCOLOR:
776 if ((m_colorage)&&(notification->line < (int)revs.size()))
778 notification->lParam = InterColor(DWORD(m_regOldLinesColor), DWORD(m_regNewLinesColor), (revs[notification->line]-m_lowestrev)*100/((m_highestrev-m_lowestrev)+1));
780 break;
784 void TortoiseBlame::Command(int id)
786 switch (id)
788 case IDM_EXIT:
789 ::PostQuitMessage(0);
790 break;
791 case ID_EDIT_FIND:
792 StartSearch();
793 break;
794 case ID_COPYTOCLIPBOARD:
795 CopySelectedLogToClipboard();
796 break;
797 case ID_BLAME_PREVIOUS_REVISION:
798 BlamePreviousRevision();
799 break;
800 case ID_DIFF_PREVIOUS_REVISION:
801 DiffPreviousRevision();
802 break;
803 case ID_SHOWLOG:
804 ShowLog();
805 break;
806 case ID_EDIT_GOTOLINE:
807 GotoLineDlg();
808 break;
809 case ID_VIEW_COLORAGEOFLINES:
811 m_colorage = !m_colorage;
812 HMENU hMenu = GetMenu(wMain);
813 UINT uCheck = MF_BYCOMMAND;
814 uCheck |= m_colorage ? MF_CHECKED : MF_UNCHECKED;
815 CheckMenuItem(hMenu, ID_VIEW_COLORAGEOFLINES, uCheck);
816 m_blamewidth = 0;
817 InitSize();
819 break;
820 case ID_VIEW_MERGEPATH:
822 ShowPath = !ShowPath;
823 HMENU hMenu = GetMenu(wMain);
824 UINT uCheck = MF_BYCOMMAND;
825 uCheck |= ShowPath ? MF_CHECKED : MF_UNCHECKED;
826 CheckMenuItem(hMenu, ID_VIEW_MERGEPATH, uCheck);
827 m_blamewidth = 0;
828 InitSize();
830 default:
831 break;
835 void TortoiseBlame::GotoLineDlg()
837 if (DialogBox(hResource, MAKEINTRESOURCE(IDD_GOTODLG), wMain, GotoDlgProc)==IDOK)
839 GotoLine(m_gotoline);
843 INT_PTR CALLBACK TortoiseBlame::GotoDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM /*lParam*/)
845 switch (uMsg)
847 case WM_COMMAND:
849 switch (LOWORD(wParam))
851 case IDOK:
853 HWND hEditCtrl = GetDlgItem(hwndDlg, IDC_LINENUMBER);
854 if (hEditCtrl)
856 TCHAR buf[MAX_PATH];
857 if (::GetWindowText(hEditCtrl, buf, MAX_PATH))
859 m_gotoline = _ttol(buf);
864 // fall through
865 case IDCANCEL:
866 EndDialog(hwndDlg, wParam);
867 break;
870 break;
872 return FALSE;
875 LONG TortoiseBlame::GetBlameWidth()
877 if (m_blamewidth)
878 return m_blamewidth;
879 LONG blamewidth = 0;
880 SIZE width;
881 CreateFont();
882 HDC hDC = ::GetDC(wBlame);
883 HFONT oldfont = (HFONT)::SelectObject(hDC, m_font);
884 TCHAR buf[MAX_PATH];
885 _stprintf_s(buf, MAX_PATH, _T("%8ld "), 88888888);
886 ::GetTextExtentPoint(hDC, buf, _tcslen(buf), &width);
887 m_revwidth = width.cx + BLAMESPACE;
888 blamewidth += m_revwidth;
889 if (ShowDate)
891 _stprintf_s(buf, MAX_PATH, _T("%30s"), _T("31.08.2001 06:24:14"));
892 ::GetTextExtentPoint32(hDC, buf, _tcslen(buf), &width);
893 m_datewidth = width.cx + BLAMESPACE;
894 blamewidth += m_datewidth;
896 if (ShowAuthor)
898 SIZE maxwidth = {0};
899 for (std::vector<std::string>::iterator I = authors.begin(); I != authors.end(); ++I)
901 ::GetTextExtentPoint32(hDC, I->c_str(), I->size(), &width);
902 if (width.cx > maxwidth.cx)
903 maxwidth = width;
905 m_authorwidth = maxwidth.cx + BLAMESPACE;
906 blamewidth += m_authorwidth;
908 if (ShowPath)
910 SIZE maxwidth = {0};
911 for (std::vector<std::string>::iterator I = paths.begin(); I != paths.end(); ++I)
913 ::GetTextExtentPoint32(hDC, I->c_str(), I->size(), &width);
914 if (width.cx > maxwidth.cx)
915 maxwidth = width;
917 m_pathwidth = maxwidth.cx + BLAMESPACE;
918 blamewidth += m_pathwidth;
920 ::SelectObject(hDC, oldfont);
921 POINT pt = {blamewidth, 0};
922 LPtoDP(hDC, &pt, 1);
923 m_blamewidth = pt.x;
924 ReleaseDC(wBlame, hDC);
925 return m_blamewidth;
928 void TortoiseBlame::CreateFont()
930 if (m_font)
931 return;
932 LOGFONT lf = {0};
933 lf.lfWeight = 400;
934 HDC hDC = ::GetDC(wBlame);
935 lf.lfHeight = -MulDiv((DWORD)CRegStdWORD(_T("Software\\TortoiseGit\\BlameFontSize"), 10), GetDeviceCaps(hDC, LOGPIXELSY), 72);
936 lf.lfCharSet = DEFAULT_CHARSET;
937 CRegStdString fontname = CRegStdString(_T("Software\\TortoiseGit\\BlameFontName"), _T("Courier New"));
938 _tcscpy_s(lf.lfFaceName, 32, ((stdstring)fontname).c_str());
939 m_font = ::CreateFontIndirect(&lf);
941 lf.lfItalic = TRUE;
942 m_italicfont = ::CreateFontIndirect(&lf);
944 ReleaseDC(wBlame, hDC);
947 void TortoiseBlame::DrawBlame(HDC hDC)
949 if (hDC == NULL)
950 return;
951 if (m_font == NULL)
952 return;
954 HFONT oldfont = NULL;
955 LONG_PTR line = SendEditor(SCI_GETFIRSTVISIBLELINE);
956 LONG_PTR linesonscreen = SendEditor(SCI_LINESONSCREEN);
957 LONG_PTR height = SendEditor(SCI_TEXTHEIGHT);
958 LONG_PTR Y = 0;
959 TCHAR buf[MAX_PATH];
960 RECT rc;
961 BOOL sel = FALSE;
962 GetClientRect(wBlame, &rc);
963 for (LRESULT i=line; i<(line+linesonscreen); ++i)
965 sel = FALSE;
966 if (i < (int)revs.size())
968 if (mergelines[i])
969 oldfont = (HFONT)::SelectObject(hDC, m_italicfont);
970 else
971 oldfont = (HFONT)::SelectObject(hDC, m_font);
972 ::SetBkColor(hDC, m_windowcolor);
973 ::SetTextColor(hDC, m_textcolor);
974 if (authors[i].size()>0)
976 if (authors[i].compare(m_mouseauthor)==0)
977 ::SetBkColor(hDC, m_mouseauthorcolor);
978 if (authors[i].compare(m_selectedauthor)==0)
980 ::SetBkColor(hDC, m_selectedauthorcolor);
981 ::SetTextColor(hDC, m_texthighlightcolor);
982 sel = TRUE;
985 if ((revs[i] == m_mouserev)&&(!sel))
986 ::SetBkColor(hDC, m_mouserevcolor);
987 if (revs[i] == m_selectedrev)
989 ::SetBkColor(hDC, m_selectedrevcolor);
990 ::SetTextColor(hDC, m_texthighlightcolor);
992 _stprintf_s(buf, MAX_PATH, _T("%8ld "), revs[i]);
993 rc.right = rc.left + m_revwidth;
994 ::ExtTextOut(hDC, 0, Y, ETO_CLIPPED, &rc, buf, _tcslen(buf), 0);
995 int Left = m_revwidth;
996 if (ShowDate)
998 rc.right = rc.left + Left + m_datewidth;
999 _stprintf_s(buf, MAX_PATH, _T("%30s "), dates[i].c_str());
1000 ::ExtTextOut(hDC, Left, Y, ETO_CLIPPED, &rc, buf, _tcslen(buf), 0);
1001 Left += m_datewidth;
1003 if (ShowAuthor)
1005 rc.right = rc.left + Left + m_authorwidth;
1006 _stprintf_s(buf, MAX_PATH, _T("%-30s "), authors[i].c_str());
1007 ::ExtTextOut(hDC, Left, Y, ETO_CLIPPED, &rc, buf, _tcslen(buf), 0);
1008 Left += m_authorwidth;
1010 if (ShowPath)
1012 rc.right = rc.left + Left + m_pathwidth;
1013 _stprintf_s(buf, MAX_PATH, _T("%-60s "), paths[i].c_str());
1014 ::ExtTextOut(hDC, Left, Y, ETO_CLIPPED, &rc, buf, _tcslen(buf), 0);
1015 Left += m_authorwidth;
1017 if ((i==m_SelectedLine)&&(currentDialog))
1019 LOGBRUSH brush;
1020 brush.lbColor = m_textcolor;
1021 brush.lbHatch = 0;
1022 brush.lbStyle = BS_SOLID;
1023 HPEN pen = ExtCreatePen(PS_SOLID | PS_GEOMETRIC, 2, &brush, 0, NULL);
1024 HGDIOBJ hPenOld = SelectObject(hDC, pen);
1025 RECT rc2 = rc;
1026 rc2.top = Y;
1027 rc2.bottom = Y + height;
1028 ::MoveToEx(hDC, rc2.left, rc2.top, NULL);
1029 ::LineTo(hDC, rc2.right, rc2.top);
1030 ::LineTo(hDC, rc2.right, rc2.bottom);
1031 ::LineTo(hDC, rc2.left, rc2.bottom);
1032 ::LineTo(hDC, rc2.left, rc2.top);
1033 SelectObject(hDC, hPenOld);
1034 DeleteObject(pen);
1036 Y += height;
1037 ::SelectObject(hDC, oldfont);
1039 else
1041 ::SetBkColor(hDC, m_windowcolor);
1042 for (int j=0; j< MAX_PATH; ++j)
1043 buf[j]=' ';
1044 ::ExtTextOut(hDC, 0, Y, ETO_CLIPPED, &rc, buf, MAX_PATH-1, 0);
1045 Y += height;
1050 void TortoiseBlame::DrawHeader(HDC hDC)
1052 if (hDC == NULL)
1053 return;
1055 RECT rc;
1056 HFONT oldfont = (HFONT)::SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
1057 GetClientRect(wHeader, &rc);
1059 ::SetBkColor(hDC, ::GetSysColor(COLOR_BTNFACE));
1061 TCHAR szText[MAX_LOADSTRING];
1062 LoadString(app.hResource, IDS_HEADER_REVISION, szText, MAX_LOADSTRING);
1063 ::ExtTextOut(hDC, LOCATOR_WIDTH, 0, ETO_CLIPPED, &rc, szText, _tcslen(szText), 0);
1064 int Left = m_revwidth+LOCATOR_WIDTH;
1065 if (ShowDate)
1067 LoadString(app.hResource, IDS_HEADER_DATE, szText, MAX_LOADSTRING);
1068 ::ExtTextOut(hDC, Left, 0, ETO_CLIPPED, &rc, szText, _tcslen(szText), 0);
1069 Left += m_datewidth;
1071 if (ShowAuthor)
1073 LoadString(app.hResource, IDS_HEADER_AUTHOR, szText, MAX_LOADSTRING);
1074 ::ExtTextOut(hDC, Left, 0, ETO_CLIPPED, &rc, szText, _tcslen(szText), 0);
1075 Left += m_authorwidth;
1077 if (ShowPath)
1079 LoadString(app.hResource, IDS_HEADER_PATH, szText, MAX_LOADSTRING);
1080 ::ExtTextOut(hDC, Left, 0, ETO_CLIPPED, &rc, szText, _tcslen(szText), 0);
1081 Left += m_pathwidth;
1083 LoadString(app.hResource, IDS_HEADER_LINE, szText, MAX_LOADSTRING);
1084 ::ExtTextOut(hDC, Left, 0, ETO_CLIPPED, &rc, szText, _tcslen(szText), 0);
1086 ::SelectObject(hDC, oldfont);
1089 void TortoiseBlame::DrawLocatorBar(HDC hDC)
1091 if (hDC == NULL)
1092 return;
1094 LONG_PTR line = SendEditor(SCI_GETFIRSTVISIBLELINE);
1095 LONG_PTR linesonscreen = SendEditor(SCI_LINESONSCREEN);
1096 LONG_PTR Y = 0;
1097 COLORREF blackColor = GetSysColor(COLOR_WINDOWTEXT);
1099 RECT rc;
1100 GetClientRect(wLocator, &rc);
1101 RECT lineRect = rc;
1102 LONG height = rc.bottom-rc.top;
1103 LONG currentLine = 0;
1105 // draw the colored bar
1106 for (std::vector<LONG>::const_iterator it = revs.begin(); it != revs.end(); ++it)
1108 currentLine++;
1109 // get the line color
1110 COLORREF cr = InterColor(DWORD(m_regOldLinesColor), DWORD(m_regNewLinesColor), (*it - m_lowestrev)*100/((m_highestrev-m_lowestrev)+1));
1111 if ((currentLine > line)&&(currentLine <= (line + linesonscreen)))
1113 cr = InterColor(cr, blackColor, 10);
1115 SetBkColor(hDC, cr);
1116 lineRect.top = Y;
1117 lineRect.bottom = (currentLine * height / revs.size());
1118 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &lineRect, NULL, 0, NULL);
1119 Y = lineRect.bottom;
1122 if (revs.size())
1124 // now draw two lines indicating the scroll position of the source view
1125 SetBkColor(hDC, blackColor);
1126 lineRect.top = line * height / revs.size();
1127 lineRect.bottom = lineRect.top+1;
1128 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &lineRect, NULL, 0, NULL);
1129 lineRect.top = (line + linesonscreen) * height / revs.size();
1130 lineRect.bottom = lineRect.top+1;
1131 ::ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &lineRect, NULL, 0, NULL);
1135 void TortoiseBlame::StringExpand(LPSTR str)
1137 char * cPos = str;
1140 cPos = strchr(cPos, '\n');
1141 if (cPos)
1143 memmove(cPos+1, cPos, strlen(cPos)*sizeof(char));
1144 *cPos = '\r';
1145 cPos++;
1146 cPos++;
1148 } while (cPos != NULL);
1150 void TortoiseBlame::StringExpand(LPWSTR str)
1152 wchar_t * cPos = str;
1155 cPos = wcschr(cPos, '\n');
1156 if (cPos)
1158 memmove(cPos+1, cPos, wcslen(cPos)*sizeof(wchar_t));
1159 *cPos = '\r';
1160 cPos++;
1161 cPos++;
1163 } while (cPos != NULL);
1166 // Forward declarations of functions included in this code module:
1167 ATOM MyRegisterClass(HINSTANCE hResource);
1168 ATOM MyRegisterBlameClass(HINSTANCE hResource);
1169 ATOM MyRegisterHeaderClass(HINSTANCE hResource);
1170 ATOM MyRegisterLocatorClass(HINSTANCE hResource);
1171 BOOL InitInstance(HINSTANCE, int);
1172 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
1173 LRESULT CALLBACK WndBlameProc(HWND, UINT, WPARAM, LPARAM);
1174 LRESULT CALLBACK WndHeaderProc(HWND, UINT, WPARAM, LPARAM);
1175 LRESULT CALLBACK WndLocatorProc(HWND, UINT, WPARAM, LPARAM);
1176 UINT uFindReplaceMsg;
1178 int APIENTRY _tWinMain(HINSTANCE hInstance,
1179 HINSTANCE /*hPrevInstance*/,
1180 LPTSTR lpCmdLine,
1181 int nCmdShow)
1183 app.hInstance = hInstance;
1184 MSG msg;
1185 HACCEL hAccelTable;
1187 if (::LoadLibrary("SciLexer.DLL") == NULL)
1188 return FALSE;
1190 CRegStdWORD loc = CRegStdWORD(_T("Software\\TortoiseGit\\LanguageID"), 1033);
1191 long langId = loc;
1193 CLangDll langDLL;
1194 app.hResource = langDLL.Init(_T("TortoiseBlame"), langId);
1195 if (app.hResource == NULL)
1196 app.hResource = app.hInstance;
1198 // Initialize global strings
1199 LoadString(app.hResource, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
1200 LoadString(app.hResource, IDC_TORTOISEBLAME, szWindowClass, MAX_LOADSTRING);
1201 LoadString(app.hResource, IDS_SEARCHNOTFOUND, searchstringnotfound, MAX_LOADSTRING);
1202 MyRegisterClass(app.hResource);
1203 MyRegisterBlameClass(app.hResource);
1204 MyRegisterHeaderClass(app.hResource);
1205 MyRegisterLocatorClass(app.hResource);
1207 // Perform application initialization:
1208 if (!InitInstance (app.hResource, nCmdShow))
1210 langDLL.Close();
1211 return FALSE;
1214 SecureZeroMemory(szViewtitle, MAX_PATH);
1215 SecureZeroMemory(szOrigPath, MAX_PATH);
1216 char blamefile[MAX_PATH] = {0};
1217 char logfile[MAX_PATH] = {0};
1219 CCmdLineParser parser(lpCmdLine);
1222 if (__argc > 1)
1224 _tcscpy_s(blamefile, MAX_PATH, __argv[1]);
1226 if (__argc > 2)
1228 _tcscpy_s(logfile, MAX_PATH, __argv[2]);
1230 if (__argc > 3)
1232 _tcscpy_s(szViewtitle, MAX_PATH, __argv[3]);
1233 if (parser.HasVal(_T("revrange")))
1235 _tcscat_s(szViewtitle, MAX_PATH, _T(" : "));
1236 _tcscat_s(szViewtitle, MAX_PATH, parser.GetVal(_T("revrange")));
1239 if ((_tcslen(blamefile)==0) || parser.HasKey(_T("?")) || parser.HasKey(_T("help")))
1241 TCHAR szInfo[MAX_LOADSTRING];
1242 LoadString(app.hResource, IDS_COMMANDLINE_INFO, szInfo, MAX_LOADSTRING);
1243 MessageBox(NULL, szInfo, _T("TortoiseBlame"), MB_ICONERROR);
1244 langDLL.Close();
1245 return 0;
1248 if ( parser.HasKey(_T("path")) )
1250 _tcscpy_s(szOrigPath, MAX_PATH, parser.GetVal(_T("path")));
1252 app.bIgnoreEOL = parser.HasKey(_T("ignoreeol"));
1253 app.bIgnoreSpaces = parser.HasKey(_T("ignorespaces"));
1254 app.bIgnoreAllSpaces = parser.HasKey(_T("ignoreallspaces"));
1256 app.SendEditor(SCI_SETCODEPAGE, GetACP());
1257 app.OpenFile(blamefile);
1258 if (_tcslen(logfile)>0)
1259 app.OpenLogFile(logfile);
1261 if (parser.HasKey(_T("line")))
1263 app.GotoLine(parser.GetLongVal(_T("line")));
1266 CheckMenuItem(GetMenu(app.wMain), ID_VIEW_COLORAGEOFLINES, MF_CHECKED|MF_BYCOMMAND);
1269 hAccelTable = LoadAccelerators(app.hResource, (LPCTSTR)IDC_TORTOISEBLAME);
1271 BOOL going = TRUE;
1272 msg.wParam = 0;
1273 while (going)
1275 going = GetMessage(&msg, NULL, 0, 0);
1276 if (app.currentDialog && going)
1278 if (!IsDialogMessage(app.currentDialog, &msg))
1280 if (TranslateAccelerator(msg.hwnd, hAccelTable, &msg) == 0)
1282 TranslateMessage(&msg);
1283 DispatchMessage(&msg);
1287 else if (going)
1289 if (TranslateAccelerator(app.wMain, hAccelTable, &msg) == 0)
1291 TranslateMessage(&msg);
1292 DispatchMessage(&msg);
1296 langDLL.Close();
1297 return msg.wParam;
1300 ATOM MyRegisterClass(HINSTANCE hResource)
1302 WNDCLASSEX wcex;
1304 wcex.cbSize = sizeof(WNDCLASSEX);
1306 wcex.style = CS_HREDRAW | CS_VREDRAW;
1307 wcex.lpfnWndProc = (WNDPROC)WndProc;
1308 wcex.cbClsExtra = 0;
1309 wcex.cbWndExtra = 0;
1310 wcex.hInstance = hResource;
1311 wcex.hIcon = LoadIcon(hResource, (LPCTSTR)IDI_TORTOISEBLAME);
1312 wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
1313 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
1314 wcex.lpszMenuName = (LPCTSTR)IDC_TORTOISEBLAME;
1315 wcex.lpszClassName = szWindowClass;
1316 wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
1318 return RegisterClassEx(&wcex);
1321 ATOM MyRegisterBlameClass(HINSTANCE hResource)
1323 WNDCLASSEX wcex;
1325 wcex.cbSize = sizeof(WNDCLASSEX);
1327 wcex.style = CS_HREDRAW | CS_VREDRAW;
1328 wcex.lpfnWndProc = (WNDPROC)WndBlameProc;
1329 wcex.cbClsExtra = 0;
1330 wcex.cbWndExtra = 0;
1331 wcex.hInstance = hResource;
1332 wcex.hIcon = LoadIcon(hResource, (LPCTSTR)IDI_TORTOISEBLAME);
1333 wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
1334 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
1335 wcex.lpszMenuName = 0;
1336 wcex.lpszClassName = _T("TortoiseBlameBlame");
1337 wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
1339 return RegisterClassEx(&wcex);
1342 ATOM MyRegisterHeaderClass(HINSTANCE hResource)
1344 WNDCLASSEX wcex;
1346 wcex.cbSize = sizeof(WNDCLASSEX);
1348 wcex.style = CS_HREDRAW | CS_VREDRAW;
1349 wcex.lpfnWndProc = (WNDPROC)WndHeaderProc;
1350 wcex.cbClsExtra = 0;
1351 wcex.cbWndExtra = 0;
1352 wcex.hInstance = hResource;
1353 wcex.hIcon = LoadIcon(hResource, (LPCTSTR)IDI_TORTOISEBLAME);
1354 wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
1355 wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
1356 wcex.lpszMenuName = 0;
1357 wcex.lpszClassName = _T("TortoiseBlameHeader");
1358 wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
1360 return RegisterClassEx(&wcex);
1363 ATOM MyRegisterLocatorClass(HINSTANCE hResource)
1365 WNDCLASSEX wcex;
1367 wcex.cbSize = sizeof(WNDCLASSEX);
1369 wcex.style = CS_HREDRAW | CS_VREDRAW;
1370 wcex.lpfnWndProc = (WNDPROC)WndLocatorProc;
1371 wcex.cbClsExtra = 0;
1372 wcex.cbWndExtra = 0;
1373 wcex.hInstance = hResource;
1374 wcex.hIcon = LoadIcon(hResource, (LPCTSTR)IDI_TORTOISEBLAME);
1375 wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
1376 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
1377 wcex.lpszMenuName = 0;
1378 wcex.lpszClassName = _T("TortoiseBlameLocator");
1379 wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);
1381 return RegisterClassEx(&wcex);
1384 BOOL InitInstance(HINSTANCE hResource, int nCmdShow)
1386 app.wMain = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
1387 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hResource, NULL);
1389 if (!app.wMain)
1391 return FALSE;
1394 CRegStdWORD pos(_T("Software\\TortoiseGit\\TBlamePos"), 0);
1395 CRegStdWORD width(_T("Software\\TortoiseGit\\TBlameSize"), 0);
1396 CRegStdWORD state(_T("Software\\TortoiseGit\\TBlameState"), 0);
1397 if (DWORD(pos) && DWORD(width))
1399 RECT rc;
1400 rc.left = LOWORD(DWORD(pos));
1401 rc.top = HIWORD(DWORD(pos));
1402 rc.right = rc.left + LOWORD(DWORD(width));
1403 rc.bottom = rc.top + HIWORD(DWORD(width));
1404 HMONITOR hMon = MonitorFromRect(&rc, MONITOR_DEFAULTTONULL);
1405 if (hMon)
1407 // only restore the window position if the monitor is valid
1408 MoveWindow(app.wMain, LOWORD(DWORD(pos)), HIWORD(DWORD(pos)),
1409 LOWORD(DWORD(width)), HIWORD(DWORD(width)), FALSE);
1412 if (DWORD(state) == SW_MAXIMIZE)
1413 ShowWindow(app.wMain, SW_MAXIMIZE);
1414 else
1415 ShowWindow(app.wMain, nCmdShow);
1416 UpdateWindow(app.wMain);
1418 //Create the tooltips
1420 INITCOMMONCONTROLSEX iccex;
1421 app.hwndTT; // handle to the ToolTip control
1422 TOOLINFO ti;
1423 RECT rect; // for client area coordinates
1424 iccex.dwICC = ICC_WIN95_CLASSES;
1425 iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
1426 InitCommonControlsEx(&iccex);
1428 /* CREATE A TOOLTIP WINDOW */
1429 app.hwndTT = CreateWindowEx(WS_EX_TOPMOST,
1430 TOOLTIPS_CLASS,
1431 NULL,
1432 WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
1433 CW_USEDEFAULT,
1434 CW_USEDEFAULT,
1435 CW_USEDEFAULT,
1436 CW_USEDEFAULT,
1437 app.wBlame,
1438 NULL,
1439 app.hResource,
1440 NULL
1443 SetWindowPos(app.hwndTT,
1444 HWND_TOPMOST,
1449 SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
1451 /* GET COORDINATES OF THE MAIN CLIENT AREA */
1452 GetClientRect (app.wBlame, &rect);
1454 /* INITIALIZE MEMBERS OF THE TOOLINFO STRUCTURE */
1455 ti.cbSize = sizeof(TOOLINFO);
1456 ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;//TTF_SUBCLASS | TTF_PARSELINKS;
1457 ti.hwnd = app.wBlame;
1458 ti.hinst = app.hResource;
1459 ti.uId = 0;
1460 ti.lpszText = LPSTR_TEXTCALLBACK;
1461 // ToolTip control will cover the whole window
1462 ti.rect.left = rect.left;
1463 ti.rect.top = rect.top;
1464 ti.rect.right = rect.right;
1465 ti.rect.bottom = rect.bottom;
1467 /* SEND AN ADDTOOL MESSAGE TO THE TOOLTIP CONTROL WINDOW */
1468 SendMessage(app.hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
1469 SendMessage(app.hwndTT, TTM_SETMAXTIPWIDTH, 0, 600);
1470 //SendMessage(app.hwndTT, TTM_SETDELAYTIME, TTDT_AUTOPOP, MAKELONG(50000, 0));
1471 //SendMessage(app.hwndTT, TTM_SETDELAYTIME, TTDT_RESHOW, MAKELONG(1000, 0));
1473 uFindReplaceMsg = RegisterWindowMessage(FINDMSGSTRING);
1475 return TRUE;
1478 void TortoiseBlame::InitSize()
1480 RECT rc;
1481 RECT blamerc;
1482 RECT sourcerc;
1483 ::GetClientRect(wMain, &rc);
1484 ::SetWindowPos(wHeader, 0, rc.left, rc.top, rc.right-rc.left, HEADER_HEIGHT, 0);
1485 rc.top += HEADER_HEIGHT;
1486 blamerc.left = rc.left;
1487 blamerc.top = rc.top;
1488 LONG w = GetBlameWidth();
1489 blamerc.right = w > abs(rc.right - rc.left) ? rc.right : w + rc.left;
1490 blamerc.bottom = rc.bottom;
1491 sourcerc.left = blamerc.right;
1492 sourcerc.top = rc.top;
1493 sourcerc.bottom = rc.bottom;
1494 sourcerc.right = rc.right;
1495 if (m_colorage)
1497 ::OffsetRect(&blamerc, LOCATOR_WIDTH, 0);
1498 ::OffsetRect(&sourcerc, LOCATOR_WIDTH, 0);
1499 sourcerc.right -= LOCATOR_WIDTH;
1501 InvalidateRect(wMain, NULL, FALSE);
1502 ::SetWindowPos(wEditor, 0, sourcerc.left, sourcerc.top, sourcerc.right - sourcerc.left, sourcerc.bottom - sourcerc.top, 0);
1503 ::SetWindowPos(wBlame, 0, blamerc.left, blamerc.top, blamerc.right - blamerc.left, blamerc.bottom - blamerc.top, 0);
1504 if (m_colorage)
1505 ::SetWindowPos(wLocator, 0, 0, blamerc.top, LOCATOR_WIDTH, blamerc.bottom - blamerc.top, SWP_SHOWWINDOW);
1506 else
1507 ::ShowWindow(wLocator, SW_HIDE);
1510 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1512 if (message == uFindReplaceMsg)
1514 LPFINDREPLACE lpfr = (LPFINDREPLACE)lParam;
1516 // If the FR_DIALOGTERM flag is set,
1517 // invalidate the handle identifying the dialog box.
1518 if (lpfr->Flags & FR_DIALOGTERM)
1520 app.currentDialog = NULL;
1521 return 0;
1523 if (lpfr->Flags & FR_FINDNEXT)
1525 app.DoSearch(lpfr->lpstrFindWhat, lpfr->Flags);
1527 return 0;
1529 switch (message)
1531 case WM_CREATE:
1532 app.wEditor = ::CreateWindow(
1533 "Scintilla",
1534 "Source",
1535 WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_CLIPCHILDREN,
1536 0, 0,
1537 100, 100,
1538 hWnd,
1540 app.hResource,
1542 app.InitialiseEditor();
1543 ::ShowWindow(app.wEditor, SW_SHOW);
1544 ::SetFocus(app.wEditor);
1545 app.wBlame = ::CreateWindow(
1546 _T("TortoiseBlameBlame"),
1547 _T("blame"),
1548 WS_CHILD | WS_CLIPCHILDREN,
1549 CW_USEDEFAULT, 0,
1550 CW_USEDEFAULT, 0,
1551 hWnd,
1552 NULL,
1553 app.hResource,
1554 NULL);
1555 ::ShowWindow(app.wBlame, SW_SHOW);
1556 app.wHeader = ::CreateWindow(
1557 _T("TortoiseBlameHeader"),
1558 _T("header"),
1559 WS_CHILD | WS_CLIPCHILDREN | WS_BORDER,
1560 CW_USEDEFAULT, 0,
1561 CW_USEDEFAULT, 0,
1562 hWnd,
1563 NULL,
1564 app.hResource,
1565 NULL);
1566 ::ShowWindow(app.wHeader, SW_SHOW);
1567 app.wLocator = ::CreateWindow(
1568 _T("TortoiseBlameLocator"),
1569 _T("locator"),
1570 WS_CHILD | WS_CLIPCHILDREN | WS_BORDER,
1571 CW_USEDEFAULT, 0,
1572 CW_USEDEFAULT, 0,
1573 hWnd,
1574 NULL,
1575 app.hResource,
1576 NULL);
1577 ::ShowWindow(app.wLocator, SW_SHOW);
1578 return 0;
1580 case WM_SIZE:
1581 if (wParam != 1)
1583 app.InitSize();
1585 return 0;
1587 case WM_COMMAND:
1588 app.Command(LOWORD(wParam));
1589 break;
1590 case WM_NOTIFY:
1591 app.Notify(reinterpret_cast<SCNotification *>(lParam));
1592 return 0;
1593 case WM_DESTROY:
1594 PostQuitMessage(0);
1595 break;
1596 case WM_CLOSE:
1598 CRegStdWORD pos(_T("Software\\TortoiseGit\\TBlamePos"), 0);
1599 CRegStdWORD width(_T("Software\\TortoiseGit\\TBlameSize"), 0);
1600 CRegStdWORD state(_T("Software\\TortoiseGit\\TBlameState"), 0);
1601 RECT rc;
1602 GetWindowRect(app.wMain, &rc);
1603 if ((rc.left >= 0)&&(rc.top >= 0))
1605 pos = MAKELONG(rc.left, rc.top);
1606 width = MAKELONG(rc.right-rc.left, rc.bottom-rc.top);
1608 WINDOWPLACEMENT wp = {0};
1609 wp.length = sizeof(WINDOWPLACEMENT);
1610 GetWindowPlacement(app.wMain, &wp);
1611 state = wp.showCmd;
1612 ::DestroyWindow(app.wEditor);
1613 ::PostQuitMessage(0);
1615 return 0;
1616 case WM_SETFOCUS:
1617 ::SetFocus(app.wBlame);
1618 break;
1619 default:
1620 return DefWindowProc(hWnd, message, wParam, lParam);
1622 return 0;
1625 LRESULT CALLBACK WndBlameProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1627 PAINTSTRUCT ps;
1628 TRACKMOUSEEVENT mevt;
1629 HDC hDC;
1630 switch (message)
1632 case WM_CREATE:
1633 return 0;
1634 case WM_PAINT:
1635 hDC = BeginPaint(app.wBlame, &ps);
1636 app.DrawBlame(hDC);
1637 EndPaint(app.wBlame, &ps);
1638 break;
1639 case WM_COMMAND:
1640 app.Command(LOWORD(wParam));
1641 break;
1642 case WM_NOTIFY:
1643 switch (((LPNMHDR)lParam)->code)
1645 case TTN_GETDISPINFO:
1647 LPNMHDR pNMHDR = (LPNMHDR)lParam;
1648 NMTTDISPINFOA* pTTTA = (NMTTDISPINFOA*)pNMHDR;
1649 NMTTDISPINFOW* pTTTW = (NMTTDISPINFOW*)pNMHDR;
1650 POINT point;
1651 DWORD ptW = GetMessagePos();
1652 point.x = GET_X_LPARAM(ptW);
1653 point.y = GET_Y_LPARAM(ptW);
1654 ::ScreenToClient(app.wBlame, &point);
1655 LONG_PTR line = app.SendEditor(SCI_GETFIRSTVISIBLELINE);
1656 LONG_PTR height = app.SendEditor(SCI_TEXTHEIGHT);
1657 line = line + (point.y/height);
1658 if (line >= (LONG)app.revs.size())
1659 break;
1660 if (line < 0)
1661 break;
1662 LONG rev = app.revs[line];
1663 if (line >= (LONG)app.revs.size())
1664 break;
1666 SecureZeroMemory(app.m_szTip, sizeof(app.m_szTip));
1667 SecureZeroMemory(app.m_wszTip, sizeof(app.m_wszTip));
1668 std::map<LONG, std::string>::iterator iter;
1669 if ((iter = app.logmessages.find(rev)) != app.logmessages.end())
1671 std::string msg;
1672 if (!ShowAuthor)
1674 msg += app.authors[line];
1676 if (!ShowDate)
1678 if (!ShowAuthor) msg += " ";
1679 msg += app.dates[line];
1681 if (!ShowAuthor || !ShowDate)
1682 msg += '\n';
1683 msg += iter->second;
1684 // an empty tooltip string will deactivate the tooltips,
1685 // which means we must make sure that the tooltip won't
1686 // be empty.
1687 if (msg.empty())
1688 msg = _T(" ");
1689 if (pNMHDR->code == TTN_NEEDTEXTA)
1691 lstrcpyn(app.m_szTip, msg.c_str(), MAX_LOG_LENGTH*2);
1692 app.StringExpand(app.m_szTip);
1693 pTTTA->lpszText = app.m_szTip;
1695 else
1697 pTTTW->lpszText = app.m_wszTip;
1698 ::MultiByteToWideChar( CP_ACP , 0, msg.c_str(), min(msg.size(), MAX_LOG_LENGTH*2), app.m_wszTip, MAX_LOG_LENGTH*2);
1699 app.StringExpand(app.m_wszTip);
1703 break;
1705 return 0;
1706 case WM_DESTROY:
1707 break;
1708 case WM_CLOSE:
1709 return 0;
1710 case WM_MOUSELEAVE:
1711 app.m_mouserev = -2;
1712 app.m_mouseauthor.clear();
1713 app.ttVisible = FALSE;
1714 SendMessage(app.hwndTT, TTM_TRACKACTIVATE, FALSE, 0);
1715 ::InvalidateRect(app.wBlame, NULL, FALSE);
1716 break;
1717 case WM_MOUSEMOVE:
1719 mevt.cbSize = sizeof(TRACKMOUSEEVENT);
1720 mevt.dwFlags = TME_LEAVE;
1721 mevt.dwHoverTime = HOVER_DEFAULT;
1722 mevt.hwndTrack = app.wBlame;
1723 ::TrackMouseEvent(&mevt);
1724 POINT pt = {((int)(short)LOWORD(lParam)), ((int)(short)HIWORD(lParam))};
1725 ClientToScreen(app.wBlame, &pt);
1726 pt.x += 15;
1727 pt.y += 15;
1728 SendMessage(app.hwndTT, TTM_TRACKPOSITION, 0, MAKELONG(pt.x, pt.y));
1729 if (!app.ttVisible)
1731 TOOLINFO ti;
1732 ti.cbSize = sizeof(TOOLINFO);
1733 ti.hwnd = app.wBlame;
1734 ti.uId = 0;
1735 SendMessage(app.hwndTT, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
1737 int y = ((int)(short)HIWORD(lParam));
1738 LONG_PTR line = app.SendEditor(SCI_GETFIRSTVISIBLELINE);
1739 LONG_PTR height = app.SendEditor(SCI_TEXTHEIGHT);
1740 line = line + (y/height);
1741 app.ttVisible = (line < (LONG)app.revs.size());
1742 if ( app.ttVisible )
1744 if (app.authors[line].compare(app.m_mouseauthor) != 0)
1746 app.m_mouseauthor = app.authors[line];
1748 if (app.revs[line] != app.m_mouserev)
1750 app.m_mouserev = app.revs[line];
1751 ::InvalidateRect(app.wBlame, NULL, FALSE);
1752 SendMessage(app.hwndTT, TTM_UPDATE, 0, 0);
1756 break;
1757 case WM_RBUTTONDOWN:
1758 // fall through
1759 case WM_LBUTTONDOWN:
1761 int y = ((int)(short)HIWORD(lParam));
1762 LONG_PTR line = app.SendEditor(SCI_GETFIRSTVISIBLELINE);
1763 LONG_PTR height = app.SendEditor(SCI_TEXTHEIGHT);
1764 line = line + (y/height);
1765 if (line < (LONG)app.revs.size())
1767 app.SetSelectedLine(line);
1768 if (app.revs[line] != app.m_selectedrev)
1770 app.m_selectedrev = app.revs[line];
1771 app.m_selectedorigrev = app.origrevs[line];
1772 app.m_selectedauthor = app.authors[line];
1773 app.m_selecteddate = app.dates[line];
1775 else
1777 app.m_selectedauthor.clear();
1778 app.m_selecteddate.clear();
1779 app.m_selectedrev = -2;
1780 app.m_selectedorigrev = -2;
1782 ::InvalidateRect(app.wBlame, NULL, FALSE);
1784 else
1786 app.SetSelectedLine(-1);
1789 break;
1790 case WM_SETFOCUS:
1791 ::SetFocus(app.wBlame);
1792 app.SendEditor(SCI_GRABFOCUS);
1793 break;
1794 case WM_CONTEXTMENU:
1796 if (app.m_selectedrev <= 0)
1797 break;;
1798 int xPos = GET_X_LPARAM(lParam);
1799 int yPos = GET_Y_LPARAM(lParam);
1800 if ((xPos < 0)||(yPos < 0))
1802 // requested from keyboard, not mouse pointer
1803 // use the center of the window
1804 RECT rect;
1805 GetClientRect(app.wBlame, &rect);
1806 xPos = rect.right-rect.left;
1807 yPos = rect.bottom-rect.top;
1809 HMENU hMenu = LoadMenu(app.hResource, MAKEINTRESOURCE(IDR_BLAMEPOPUP));
1810 HMENU hPopMenu = GetSubMenu(hMenu, 0);
1812 if ( _tcslen(szOrigPath)==0 )
1814 // Without knowing the original path we cannot blame the previous revision
1815 // because we don't know which filename to pass to tortoiseproc.
1816 EnableMenuItem(hPopMenu,ID_BLAME_PREVIOUS_REVISION, MF_DISABLED|MF_GRAYED);
1819 TrackPopupMenu(hPopMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, xPos, yPos, 0, app.wBlame, NULL);
1820 DestroyMenu(hMenu);
1822 break;
1823 default:
1824 return DefWindowProc(hWnd, message, wParam, lParam);
1826 return 0;
1829 LRESULT CALLBACK WndHeaderProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1831 PAINTSTRUCT ps;
1832 HDC hDC;
1833 switch (message)
1835 case WM_CREATE:
1836 return 0;
1837 case WM_PAINT:
1838 hDC = BeginPaint(app.wHeader, &ps);
1839 app.DrawHeader(hDC);
1840 EndPaint(app.wHeader, &ps);
1841 break;
1842 case WM_COMMAND:
1843 break;
1844 case WM_DESTROY:
1845 break;
1846 case WM_CLOSE:
1847 return 0;
1848 default:
1849 return DefWindowProc(hWnd, message, wParam, lParam);
1851 return 0;
1854 LRESULT CALLBACK WndLocatorProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
1856 PAINTSTRUCT ps;
1857 HDC hDC;
1858 switch (message)
1860 case WM_PAINT:
1861 hDC = BeginPaint(app.wLocator, &ps);
1862 app.DrawLocatorBar(hDC);
1863 EndPaint(app.wLocator, &ps);
1864 break;
1865 case WM_LBUTTONDOWN:
1866 case WM_MOUSEMOVE:
1867 if (wParam & MK_LBUTTON)
1869 RECT rect;
1870 ::GetClientRect(hWnd, &rect);
1871 int nLine = HIWORD(lParam)*app.revs.size()/(rect.bottom-rect.top);
1873 if (nLine < 0)
1874 nLine = 0;
1875 app.ScrollToLine(nLine);
1877 break;
1878 default:
1879 return DefWindowProc(hWnd, message, wParam, lParam);
1881 return 0;
1884 #pragma warning(pop)