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