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.
20 #include "CmdLineParser.h"
21 #include "TortoiseBlame.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='*'\"")
30 #pragma warning(disable:4127) // conditional expression is constant
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()
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);
75 m_selectedorigrev
= -1;
80 m_lowestrev
= LONG_MAX
;
85 TortoiseBlame::~TortoiseBlame()
90 DeleteObject(m_italicfont
);
93 std::string
TortoiseBlame::GetAppDirectory()
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
);
105 } while(len
== bufferlen
);
106 path
= path
.substr(0, path
.rfind('\\') + 1);
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
)
120 // Limit Slider to 0..100% range
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;
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
)
146 tr
.chrg
.cpMin
= start
;
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];
165 fopen_s(&File
, fileName
, "rb");
175 wchar_t wbuf
[MAX_LOG_LENGTH
+6];
178 len
= fread(&rev
, sizeof(LONG
), 1, File
);
185 len
= fread(&slength
, sizeof(int), 1, File
);
192 if (slength
> MAX_LOG_LENGTH
)
194 reallength
= slength
;
195 slength
= MAX_LOG_LENGTH
;
199 len
= fread(logmsgbuf
, sizeof(char), slength
, File
);
200 if (len
< (size_t)slength
)
206 msg
= std::string(logmsgbuf
, slength
);
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);
214 len2
= ::WideCharToMultiByte(CP_ACP
, NULL
, wbuf
, len2
, logmsgbuf
, MAX_LOG_LENGTH
+5, NULL
, NULL
);
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
);
227 SendEditor(SCI_SETSAVEPOINT
);
228 SendEditor(SCI_CANCEL
);
229 SendEditor(SCI_SETUNDOCOLLECTION
, 0);
230 ::ShowWindow(wEditor
, SW_HIDE
);
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
;
248 File
.getline(line
, sizeof(line
)/sizeof(TCHAR
));
249 if (File
.gcount()>139)
251 mergelines
.push_back((line
[0] != ' '));
253 long rev
= _ttol(lineptr
);
255 m_lowestrev
= min(m_lowestrev
, rev
);
256 m_highestrev
= max(m_highestrev
, rev
);
258 rev
= _ttol(lineptr
);
259 origrevs
.push_back(rev
);
261 dates
.push_back(std::string(lineptr
, 30));
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
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
272 // The only way to deal with that would be to use a custom
273 // binary format for the blame file.
275 trimptr
= _tcschr(trimptr
, ' ');
276 } while ((trimptr
)&&(trimptr
+1 < lineptr
+61));
281 paths
.push_back(std::string(lineptr
));
282 if (trimptr
+1 < lineptr
+61)
285 lineptr
= (trimptr
+1);
286 trimptr
= lineptr
+30;
287 while ((*trimptr
== ' ')&&(trimptr
> lineptr
))
290 authors
.push_back(std::string(lineptr
));
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))
297 if (((unsigned char)lineptr
[0] == 0xBB)&&((unsigned char)lineptr
[1] == 0xEF)&&((unsigned char)lineptr
[2] == 0xBF))
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))
311 if ((*utf8CheckBuf
& 0xE0)==0xC0)
314 if (*utf8CheckBuf
== 0)
316 if ((*utf8CheckBuf
& 0xC0)!=0x80)
322 if ((*utf8CheckBuf
& 0xF0)==0xE0)
325 if (*utf8CheckBuf
== 0)
327 if ((*utf8CheckBuf
& 0xC0)!=0x80)
333 if (*utf8CheckBuf
== 0)
335 if ((*utf8CheckBuf
& 0xC0)!=0x80)
341 if ((*utf8CheckBuf
& 0xF8)==0xF0)
344 if (*utf8CheckBuf
== 0)
346 if ((*utf8CheckBuf
& 0xC0)!=0x80)
352 if (*utf8CheckBuf
== 0)
354 if ((*utf8CheckBuf
& 0xC0)!=0x80)
360 if (*utf8CheckBuf
== 0)
362 if ((*utf8CheckBuf
& 0xC0)!=0x80)
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);
377 SendEditor(SCI_SETCODEPAGE
, SC_CP_UTF8
);
379 SendEditor(SCI_SETUNDOCOLLECTION
, 1);
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
);
391 ::InvalidateRect(wMain
, NULL
, TRUE
);
393 GetWindowRect(wMain
, &rc
);
394 SetWindowPos(wMain
, 0, rc
.left
, rc
.top
, rc
.right
-rc
.left
-1, rc
.bottom
- rc
.top
, 0);
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
);
403 SendEditor(SCI_STYLESETSIZE
, style
, size
);
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"));
419 SendEditor(SCI_SETMARGINWIDTHN
, 0, pix
);
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()
439 // Initialize FINDREPLACE
440 if (fr
.Flags
& FR_MATCHCASE
)
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
)
456 int pos
= SendEditor(SCI_GETCURRENTPOS
);
457 int line
= SendEditor(SCI_LINEFROMPOSITION
, pos
);
459 bool bCaseSensitive
= !!(flags
& FR_MATCHCASE
);
461 strcpy_s(szWhat
, sizeof(szWhat
), what
);
466 size_t len
= strlen(szWhat
);
467 for (p
= szWhat
; p
< szWhat
+ len
; p
++)
469 if (isupper(*p
)&&__isascii(*p
))
474 std::string sWhat
= std::string(szWhat
);
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
);
487 for (p
= linebuf
; p
< linebuf
+ bufsize
; p
++)
489 if (isupper(*p
)&&__isascii(*p
))
493 _stprintf_s(buf
, 20, _T("%ld"), revs
[i
]);
494 if (authors
[i
].compare(sWhat
)==0)
496 else if ((!bCaseSensitive
)&&(_stricmp(authors
[i
].c_str(), szWhat
)==0))
498 else if (strcmp(buf
, szWhat
) == 0)
500 else if (strstr(linebuf
, szWhat
))
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
);
515 for (p
= linebuf
; p
< linebuf
+ bufsize
; p
++)
517 if (isupper(*p
)&&__isascii(*p
))
521 _stprintf_s(buf
, 20, _T("%ld"), revs
[i
]);
522 if (authors
[i
].compare(sWhat
)==0)
524 else if ((!bCaseSensitive
)&&(_stricmp(authors
[i
].c_str(), szWhat
)==0))
526 else if (strcmp(buf
, szWhat
) == 0)
528 else if (strstr(linebuf
, szWhat
))
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;
544 ::MessageBox(wMain
, searchstringnotfound
, "TortoiseBlame", MB_ICONINFORMATION
);
549 bool TortoiseBlame::GotoLine(long line
)
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
)
567 SendEditor(SCI_GOTOLINE
, line
);
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)));
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
);
590 bool TortoiseBlame::ScrollToLine(long line
)
595 int nCurrentLine
= SendEditor(SCI_GETFIRSTVISIBLELINE
);
597 int scrolldelta
= line
- nCurrentLine
;
598 SendEditor(SCI_LINESCROLL
, 0, scrolldelta
);
603 void TortoiseBlame::CopySelectedLogToClipboard()
605 if (m_selectedrev
<= 0)
607 std::map
<LONG
, std::string
>::iterator iter
;
608 if ((iter
= app
.logmessages
.find(m_selectedrev
)) != app
.logmessages
.end())
611 msg
+= m_selectedauthor
;
613 msg
+= app
.m_selecteddate
;
617 if (OpenClipboard(app
.wBlame
))
620 HGLOBAL hClipboardData
;
621 hClipboardData
= GlobalAlloc(GMEM_DDESHARE
, msg
.size()+1);
623 pchData
= (char*)GlobalLock(hClipboardData
);
624 strcpy_s(pchData
, msg
.size()+1, msg
.c_str());
625 GlobalUnlock(hClipboardData
);
626 SetClipboardData(CF_TEXT
,hClipboardData
);
632 void TortoiseBlame::BlamePreviousRevision()
634 LONG nRevisionTo
= m_selectedorigrev
- 1;
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
];
650 if ( nSmallestRevision
< 1 )
652 nSmallestRevision
= nRevision
;
656 nSmallestRevision
= min(nSmallestRevision
,nRevision
);
661 char bufStartRev
[20];
662 _stprintf_s(bufStartRev
, 20, _T("%d"), nSmallestRevision
);
665 _stprintf_s(bufEndRev
, 20, _T("%d"), nRevisionTo
);
668 _stprintf_s(bufLine
, 20, _T("%d"), m_SelectedLine
+1); //using the current line is a good guess.
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
;
680 svnCmd
+= _T(" /startrev:");
681 svnCmd
+= bufStartRev
;
682 svnCmd
+= _T(" /endrev:");
684 svnCmd
+= _T(" /line:");
687 svnCmd
+= _T(" /ignoreeol");
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
;
707 LONG nRevisionFrom
= nRevisionTo
-1;
709 char bufStartRev
[20];
710 _stprintf_s(bufStartRev
, 20, _T("%d"), nRevisionFrom
);
713 _stprintf_s(bufEndRev
, 20, _T("%d"), nRevisionTo
);
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
;
725 svnCmd
+= _T(" /startrev:");
726 svnCmd
+= bufStartRev
;
727 svnCmd
+= _T(" /endrev:");
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()
739 _stprintf_s(bufRev
, 20, _T("%d"), m_selectedorigrev
);
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
;
751 svnCmd
+= _T(" /startrev:");
753 svnCmd
+= _T(" /pegrev:");
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
:
769 case SCN_SAVEPOINTLEFT
:
772 InvalidateRect(wBlame
, NULL
, FALSE
);
773 InvalidateRect(wLocator
, NULL
, FALSE
);
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));
784 void TortoiseBlame::Command(int id
)
789 ::PostQuitMessage(0);
794 case ID_COPYTOCLIPBOARD
:
795 CopySelectedLogToClipboard();
797 case ID_BLAME_PREVIOUS_REVISION
:
798 BlamePreviousRevision();
800 case ID_DIFF_PREVIOUS_REVISION
:
801 DiffPreviousRevision();
806 case ID_EDIT_GOTOLINE
:
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
);
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
);
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*/)
849 switch (LOWORD(wParam
))
853 HWND hEditCtrl
= GetDlgItem(hwndDlg
, IDC_LINENUMBER
);
857 if (::GetWindowText(hEditCtrl
, buf
, MAX_PATH
))
859 m_gotoline
= _ttol(buf
);
866 EndDialog(hwndDlg
, wParam
);
875 LONG
TortoiseBlame::GetBlameWidth()
882 HDC hDC
= ::GetDC(wBlame
);
883 HFONT oldfont
= (HFONT
)::SelectObject(hDC
, m_font
);
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
;
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
;
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
)
905 m_authorwidth
= maxwidth
.cx
+ BLAMESPACE
;
906 blamewidth
+= m_authorwidth
;
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
)
917 m_pathwidth
= maxwidth
.cx
+ BLAMESPACE
;
918 blamewidth
+= m_pathwidth
;
920 ::SelectObject(hDC
, oldfont
);
921 POINT pt
= {blamewidth
, 0};
924 ReleaseDC(wBlame
, hDC
);
928 void TortoiseBlame::CreateFont()
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
);
942 m_italicfont
= ::CreateFontIndirect(&lf
);
944 ReleaseDC(wBlame
, hDC
);
947 void TortoiseBlame::DrawBlame(HDC hDC
)
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
);
962 GetClientRect(wBlame
, &rc
);
963 for (LRESULT i
=line
; i
<(line
+linesonscreen
); ++i
)
966 if (i
< (int)revs
.size())
969 oldfont
= (HFONT
)::SelectObject(hDC
, m_italicfont
);
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
);
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
;
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
;
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
;
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
))
1020 brush
.lbColor
= m_textcolor
;
1022 brush
.lbStyle
= BS_SOLID
;
1023 HPEN pen
= ExtCreatePen(PS_SOLID
| PS_GEOMETRIC
, 2, &brush
, 0, NULL
);
1024 HGDIOBJ hPenOld
= SelectObject(hDC
, pen
);
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
);
1037 ::SelectObject(hDC
, oldfont
);
1041 ::SetBkColor(hDC
, m_windowcolor
);
1042 for (int j
=0; j
< MAX_PATH
; ++j
)
1044 ::ExtTextOut(hDC
, 0, Y
, ETO_CLIPPED
, &rc
, buf
, MAX_PATH
-1, 0);
1050 void TortoiseBlame::DrawHeader(HDC hDC
)
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
;
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
;
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
;
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
)
1094 LONG_PTR line
= SendEditor(SCI_GETFIRSTVISIBLELINE
);
1095 LONG_PTR linesonscreen
= SendEditor(SCI_LINESONSCREEN
);
1097 COLORREF blackColor
= GetSysColor(COLOR_WINDOWTEXT
);
1100 GetClientRect(wLocator
, &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
)
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
);
1117 lineRect
.bottom
= (currentLine
* height
/ revs
.size());
1118 ::ExtTextOut(hDC
, 0, 0, ETO_OPAQUE
, &lineRect
, NULL
, 0, NULL
);
1119 Y
= lineRect
.bottom
;
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
)
1140 cPos
= strchr(cPos
, '\n');
1143 memmove(cPos
+1, cPos
, strlen(cPos
)*sizeof(char));
1148 } while (cPos
!= NULL
);
1150 void TortoiseBlame::StringExpand(LPWSTR str
)
1152 wchar_t * cPos
= str
;
1155 cPos
= wcschr(cPos
, '\n');
1158 memmove(cPos
+1, cPos
, wcslen(cPos
)*sizeof(wchar_t));
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*/,
1183 app
.hInstance
= hInstance
;
1187 if (::LoadLibrary("SciLexer.DLL") == NULL
)
1190 CRegStdWORD loc
= CRegStdWORD(_T("Software\\TortoiseGit\\LanguageID"), 1033);
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
))
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
);
1224 _tcscpy_s(blamefile
, MAX_PATH
, __argv
[1]);
1228 _tcscpy_s(logfile
, MAX_PATH
, __argv
[2]);
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
);
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
);
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
);
1289 if (TranslateAccelerator(app
.wMain
, hAccelTable
, &msg
) == 0)
1291 TranslateMessage(&msg
);
1292 DispatchMessage(&msg
);
1300 ATOM
MyRegisterClass(HINSTANCE hResource
)
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
)
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
)
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
)
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
);
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
))
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
);
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
);
1415 ShowWindow(app
.wMain
, nCmdShow
);
1416 UpdateWindow(app
.wMain
);
1418 //Create the tooltips
1420 INITCOMMONCONTROLSEX iccex
;
1421 app
.hwndTT
; // handle to the ToolTip control
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
,
1432 WS_POPUP
| TTS_NOPREFIX
| TTS_ALWAYSTIP
,
1443 SetWindowPos(app
.hwndTT
,
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
;
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
);
1478 void TortoiseBlame::InitSize()
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
;
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);
1505 ::SetWindowPos(wLocator
, 0, 0, blamerc
.top
, LOCATOR_WIDTH
, blamerc
.bottom
- blamerc
.top
, SWP_SHOWWINDOW
);
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
;
1523 if (lpfr
->Flags
& FR_FINDNEXT
)
1525 app
.DoSearch(lpfr
->lpstrFindWhat
, lpfr
->Flags
);
1532 app
.wEditor
= ::CreateWindow(
1535 WS_CHILD
| WS_VSCROLL
| WS_HSCROLL
| WS_CLIPCHILDREN
,
1542 app
.InitialiseEditor();
1543 ::ShowWindow(app
.wEditor
, SW_SHOW
);
1544 ::SetFocus(app
.wEditor
);
1545 app
.wBlame
= ::CreateWindow(
1546 _T("TortoiseBlameBlame"),
1548 WS_CHILD
| WS_CLIPCHILDREN
,
1555 ::ShowWindow(app
.wBlame
, SW_SHOW
);
1556 app
.wHeader
= ::CreateWindow(
1557 _T("TortoiseBlameHeader"),
1559 WS_CHILD
| WS_CLIPCHILDREN
| WS_BORDER
,
1566 ::ShowWindow(app
.wHeader
, SW_SHOW
);
1567 app
.wLocator
= ::CreateWindow(
1568 _T("TortoiseBlameLocator"),
1570 WS_CHILD
| WS_CLIPCHILDREN
| WS_BORDER
,
1577 ::ShowWindow(app
.wLocator
, SW_SHOW
);
1588 app
.Command(LOWORD(wParam
));
1591 app
.Notify(reinterpret_cast<SCNotification
*>(lParam
));
1598 CRegStdWORD
pos(_T("Software\\TortoiseGit\\TBlamePos"), 0);
1599 CRegStdWORD
width(_T("Software\\TortoiseGit\\TBlameSize"), 0);
1600 CRegStdWORD
state(_T("Software\\TortoiseGit\\TBlameState"), 0);
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
);
1612 ::DestroyWindow(app
.wEditor
);
1613 ::PostQuitMessage(0);
1617 ::SetFocus(app
.wBlame
);
1620 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
1625 LRESULT CALLBACK
WndBlameProc(HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
1628 TRACKMOUSEEVENT mevt
;
1635 hDC
= BeginPaint(app
.wBlame
, &ps
);
1637 EndPaint(app
.wBlame
, &ps
);
1640 app
.Command(LOWORD(wParam
));
1643 switch (((LPNMHDR
)lParam
)->code
)
1645 case TTN_GETDISPINFO
:
1647 LPNMHDR pNMHDR
= (LPNMHDR
)lParam
;
1648 NMTTDISPINFOA
* pTTTA
= (NMTTDISPINFOA
*)pNMHDR
;
1649 NMTTDISPINFOW
* pTTTW
= (NMTTDISPINFOW
*)pNMHDR
;
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())
1662 LONG rev
= app
.revs
[line
];
1663 if (line
>= (LONG
)app
.revs
.size())
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())
1674 msg
+= app
.authors
[line
];
1678 if (!ShowAuthor
) msg
+= " ";
1679 msg
+= app
.dates
[line
];
1681 if (!ShowAuthor
|| !ShowDate
)
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
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
;
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
);
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
);
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
);
1728 SendMessage(app
.hwndTT
, TTM_TRACKPOSITION
, 0, MAKELONG(pt
.x
, pt
.y
));
1732 ti
.cbSize
= sizeof(TOOLINFO
);
1733 ti
.hwnd
= app
.wBlame
;
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);
1757 case WM_RBUTTONDOWN
:
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
];
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
);
1786 app
.SetSelectedLine(-1);
1791 ::SetFocus(app
.wBlame
);
1792 app
.SendEditor(SCI_GRABFOCUS
);
1794 case WM_CONTEXTMENU
:
1796 if (app
.m_selectedrev
<= 0)
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
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
);
1824 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
1829 LRESULT CALLBACK
WndHeaderProc(HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
1838 hDC
= BeginPaint(app
.wHeader
, &ps
);
1839 app
.DrawHeader(hDC
);
1840 EndPaint(app
.wHeader
, &ps
);
1849 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
1854 LRESULT CALLBACK
WndLocatorProc(HWND hWnd
, UINT message
, WPARAM wParam
, LPARAM lParam
)
1861 hDC
= BeginPaint(app
.wLocator
, &ps
);
1862 app
.DrawLocatorBar(hDC
);
1863 EndPaint(app
.wLocator
, &ps
);
1865 case WM_LBUTTONDOWN
:
1867 if (wParam
& MK_LBUTTON
)
1870 ::GetClientRect(hWnd
, &rect
);
1871 int nLine
= HIWORD(lParam
)*app
.revs
.size()/(rect
.bottom
-rect
.top
);
1875 app
.ScrollToLine(nLine
);
1879 return DefWindowProc(hWnd
, message
, wParam
, lParam
);
1884 #pragma warning(pop)