OSDN Git Service

Reduce the size of the executable file (3)
[winmerge-jp/winmerge-jp.git] / Src / FilepathEdit.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 //    WinMerge:  an interactive diff/merge utility
3 //    Copyright (C) 1997-2000  Thingamahoochie Software
4 //    Author: Dean Grimm
5 //
6 //    This program is free software; you can redistribute it and/or modify
7 //    it under the terms of the GNU General Public License as published by
8 //    the Free Software Foundation; either version 2 of the License, or
9 //    (at your option) any later version.
10 //
11 //    This program is distributed in the hope that it will be useful,
12 //    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //    GNU General Public License for more details.
15 //
16 //    You should have received a copy of the GNU General Public License
17 //    along with this program; if not, write to the Free Software
18 //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 //
20 /////////////////////////////////////////////////////////////////////////////
21 /** 
22  * @file  FilePathEdit.cpp
23  *
24  * @brief Implementation of the CFilepathEdit class.
25  */
26
27 #include "stdafx.h"
28 #include "FilepathEdit.h"
29 #include "Merge.h"
30 #include "BCMenu.h"
31 #include "ClipBoard.h"
32 #include "Shlwapi.h"
33 #include "paths.h"
34
35 #ifdef _DEBUG
36 #define new DEBUG_NEW
37 #endif
38
39 static int FormatFilePathForDisplayWidth(CDC * pDC, int maxWidth, String & sFilepath);
40
41 BEGIN_MESSAGE_MAP(CFilepathEdit, CEdit)
42         ON_WM_CONTEXTMENU()
43         ON_WM_CTLCOLOR_REFLECT()
44         ON_WM_NCPAINT()
45         ON_COMMAND(ID_EDIT_COPY, OnEditCopy)
46 END_MESSAGE_MAP()
47
48
49 /** 
50  * @brief Format the path for display in header control. 
51  *
52  * Formats path so it fits to given length, tries to end lines after
53  * slash characters.
54  *
55  * @param [in] pDC Pointer to draw context.
56  * @param [in] maxWidth Maximum width of the string in the GUI.
57  * @param [in,out] sFilepath:
58  * - in: string to format
59  * - out: formatted string
60  * @return Number of lines path is splitted to.
61  */
62 static int FormatFilePathForDisplayWidth(CDC * pDC, int maxWidth, String & sFilepath)
63 {
64         size_t iBegin = 0;
65         int nLines = 1;
66         
67         while (true)
68         {
69                 String line;
70
71                 // find the next truncation point
72                 size_t iEndMin = 0;
73                 size_t iEndMax = sFilepath.length() - iBegin + 1;
74                 while(1)
75                 {
76                         size_t iEnd = (iEndMin + iEndMax) / 2;
77                         if (iEnd == iEndMin)
78                                 break;
79                         line = sFilepath.substr(iBegin, iEnd);
80                         int width = (pDC->GetTextExtent(line.c_str())).cx;
81                         if (width > maxWidth)
82                                 iEndMax = iEnd;
83                         else
84                                 iEndMin = iEnd;
85                 };
86                 ASSERT(iEndMax == iEndMin+1);
87
88                 // here iEndMin is the last character displayed in maxWidth
89
90                 // exit the loop if we can display the remaining characters with no truncation
91                 if (iBegin + iEndMin == sFilepath.length())
92                         break;
93
94                 // truncate the text to the previous "\" if possible
95                 line = sFilepath.substr(iBegin, iEndMin);
96                 size_t lastSlash = line.rfind('\\');
97                 if (lastSlash != String::npos)
98                         iEndMin = lastSlash + 1;
99
100                 sFilepath.insert(iBegin + iEndMin, _T("\n"));
101                 iBegin += iEndMin + 2;
102                 nLines ++;
103         }
104
105         return nLines;
106 }
107
108 /**
109  * @brief Constructor.
110  * Set text color to black and background white by default.
111  */
112 CFilepathEdit::CFilepathEdit()
113  : m_crBackGnd(RGB(255, 255, 255))
114  , m_crText(RGB(0,0,0))
115  , m_bActive(false)
116 {
117 }
118
119 /**
120  * @brief Subclass the control.
121  * @param [in] nID ID of the control to subclass.
122  * @param [in] pParent Parent control of the control to subclass.
123  * @return `true` if succeeded, `false` otherwise.
124  */
125 bool CFilepathEdit::SubClassEdit(UINT nID, CWnd* pParent)
126 {
127         m_bActive = false;
128         return SubclassDlgItem(nID, pParent);
129 };
130
131 /**
132  * @brief Set the text to show in the control.
133  * This function sets the text (original text) to show in the control.
134  * The control may modify the text for displaying in the GUI.
135  */
136 void CFilepathEdit::SetOriginalText(const String& sString)
137 {
138         if (m_sOriginalText.compare(sString) == 0)
139                 return;
140
141         m_sOriginalText = sString;
142
143         RefreshDisplayText();
144 }
145
146 /**
147  * @brief Re-format the displayed text and update GUI.
148  * This method formats the visible text from original text.
149  */
150 void CFilepathEdit::RefreshDisplayText()
151 {
152         String line = m_sOriginalText;
153
154         // we want to keep the first and the last path component, and in between,
155         // as much characters as possible from the right
156         // PathCompactPath keeps, in between, as much characters as possible from the left
157         // so we reverse everything between the first and the last component before calling PathCompactPath
158         size_t iBeginLast = line.rfind('\\');
159         size_t iEndIntro = line.find('\\');
160         if (iBeginLast != String::npos && iEndIntro != iBeginLast)
161         {
162                 String textToReverse = line.substr(iEndIntro + 1, iBeginLast -
163                                 (iEndIntro + 1));
164                 std::reverse(textToReverse.begin(), textToReverse.end());
165                 line = line.substr(0, iEndIntro + 1) + textToReverse + line.substr(iBeginLast);
166         }
167
168         // get a device context object
169         CClientDC lDC(this);
170         // and use the correct font
171         CFont *pFontOld = lDC.SelectObject(GetFont());  
172
173         // compact the path
174         CRect rect;
175         GetRect(rect);
176         // take GetBuffer (lenght +3) to count for ellipsis
177         std::vector<TCHAR> tmp(line.length() + 4);
178         std::copy(line.begin(), line.end(), tmp.begin());
179         PathCompactPath(lDC.GetSafeHdc(), &tmp[0],      rect.Width());
180         line = &tmp[0];
181         
182         // set old font back
183         lDC.SelectObject(pFontOld);
184
185         // we reverse back everything between the first and the last component
186         // it works OK as "..." reversed = "..." again
187         iBeginLast = line.rfind('\\');
188         iEndIntro = line.find('\\');
189         if (iBeginLast != String::npos && iEndIntro != iBeginLast)
190         {
191                 String textToReverse = line.substr(iEndIntro + 1, iBeginLast -
192                                 (iEndIntro+1));
193                 std::reverse(textToReverse.begin(), textToReverse.end());
194                 line = line.substr(0, iEndIntro + 1) + textToReverse + line.substr(iBeginLast);
195         }
196
197         SetWindowText(line.c_str());
198 }
199
200 /**
201  * @brief Updates and returns the tooltip for this edit box
202  */
203 const String& CFilepathEdit::GetUpdatedTipText(CDC * pDC, int maxWidth)
204 {
205         GetOriginalText(m_sToolTipString);
206         FormatFilePathForDisplayWidth(pDC, maxWidth, m_sToolTipString);
207         return m_sToolTipString;
208 }
209
210 /**
211  * @brief retrieve text from the OriginalText
212  *
213  * @note The standard Copy function works with the (compacted) windowText 
214  */
215 void CFilepathEdit::CustomCopy(size_t iBegin, size_t iEnd /*=-1*/)
216 {
217         if (iEnd == String::npos)
218                 iEnd = m_sOriginalText.length();
219
220         PutToClipboard(m_sOriginalText.substr(iBegin, iEnd - iBegin), m_hWnd);
221 }
222
223 /**
224  * @brief Format the context menu.
225  */
226 void CFilepathEdit::OnContextMenu(CWnd*, CPoint point)
227 {
228         {
229                 if (point.x == -1 && point.y == -1){
230                         //keystroke invocation
231                         CRect rect;
232                         GetClientRect(rect);
233                         ClientToScreen(rect);
234
235                         point = rect.TopLeft();
236                         point.Offset(5, 5);
237                 }
238
239                 BCMenu menu;
240                 VERIFY(menu.LoadMenu(IDR_POPUP_EDITOR_HEADERBAR));
241                 theApp.TranslateMenu(menu.m_hMenu);
242
243                 BCMenu* pPopup = static_cast<BCMenu *>(menu.GetSubMenu(0));
244                 ASSERT(pPopup != nullptr);
245
246                 DWORD sel = GetSel();
247                 if (HIWORD(sel) == LOWORD(sel))
248                         pPopup->EnableMenuItem(ID_EDITOR_COPY, MF_GRAYED);
249                 if (paths::EndsWithSlash(m_sOriginalText))
250                         // no filename, we have to disable the unwanted menu entry
251                         pPopup->EnableMenuItem(ID_EDITOR_COPY_FILENAME, MF_GRAYED);
252
253                 // invoke context menu
254                 // we don't want to use the main application handlers, so we
255                 // use flags TPM_NONOTIFY | TPM_RETURNCMD
256                 // and handle the command after TrackPopupMenu
257                 int command = pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON |
258                         TPM_NONOTIFY  | TPM_RETURNCMD, point.x, point.y, AfxGetMainWnd());
259
260                 // compute the beginning of the text to copy (in OriginalText)
261                 size_t iBegin = 0;
262                 switch (command)
263                 {
264                 case ID_EDITOR_COPY:
265                         Copy();
266                         return;
267                 case ID_EDITOR_COPY_FILENAME:
268                         {
269                         size_t lastSlash = m_sOriginalText.rfind('\\');
270                         if (lastSlash == String::npos)
271                                 lastSlash = m_sOriginalText.rfind('/');
272                         if (lastSlash != String::npos)
273                                 iBegin = lastSlash+1;
274                         else
275                                 iBegin = 0;
276                         }
277                         break;
278                 case ID_EDITOR_COPY_PATH:
279                         // pass the heading "*" for modified files
280                         if (m_sOriginalText.at(0) == '*')
281                                 iBegin = 2;
282                         else
283                                 iBegin = 0;
284                         break;
285                 default:
286                         return;
287                 }
288                 
289                 CustomCopy(iBegin);
290         }
291 }
292
293 static COLORREF GetDarkenColor(COLORREF a, double r)
294 {
295         const int R = static_cast<int>(GetRValue(a) * r);
296         const int G = static_cast<int>(GetGValue(a) * r);
297         const int B = static_cast<int>(GetBValue(a) * r);
298         return RGB(R, G, B);
299 }
300
301 void CFilepathEdit::OnNcPaint()
302 {
303         CWindowDC dc(this);
304         CRect rect;
305         const int margin = 4;
306         GetWindowRect(rect);
307         rect.OffsetRect(-rect.TopLeft());
308         dc.FillSolidRect(CRect(rect.left, rect.top, rect.left + margin, rect.bottom), GetDarkenColor(m_crBackGnd, 0.98));
309         dc.FillSolidRect(CRect(rect.left, rect.top, rect.left + 1, rect.bottom), GetDarkenColor(m_crBackGnd, 0.90));
310         dc.FillSolidRect(CRect(rect.right - margin, rect.top, rect.right, rect.bottom), m_crBackGnd);
311         dc.FillSolidRect(CRect(rect.left + 1, rect.top, rect.right, rect.top + margin), GetDarkenColor(m_crBackGnd, 0.98));
312         dc.FillSolidRect(CRect(rect.left, rect.top, rect.right, rect.top + 1), GetDarkenColor(m_crBackGnd, 0.90));
313         dc.FillSolidRect(CRect(rect.left + margin, rect.bottom - margin, rect.right, rect.bottom), m_crBackGnd);
314 }
315
316 void CFilepathEdit::OnEditCopy()
317 {
318         int nStartChar, nEndChar;
319         GetSel(nStartChar, nEndChar);
320         if (nStartChar == nEndChar)
321                 SetSel(0, -1);
322         Copy();
323         if (nStartChar == nEndChar)
324                 SetSel(nStartChar, nEndChar);
325 }
326
327 BOOL CFilepathEdit::PreTranslateMessage(MSG *pMsg)
328 {
329         if (pMsg->message >= WM_KEYFIRST && pMsg->message <= WM_KEYLAST)
330         {
331                 if (::TranslateAccelerator (m_hWnd, static_cast<CFrameWnd *>(AfxGetMainWnd())->GetDefaultAccelerator(), pMsg))
332                         return TRUE;
333         }
334         return CEdit::PreTranslateMessage(pMsg);
335 }
336
337 /**
338  * @brief Set the control to look active/inactive.
339  * This function sets control to look like an active control. We don't
340  * have real focus on this control, but editor pane below it. However
341  * for user this active look informs which editor pane is active.
342  * @param [in] bActive If `true` set control look like active control.
343  */
344 void CFilepathEdit::SetActive(bool bActive)
345 {
346         m_bActive = bActive;
347
348         if (m_hWnd == nullptr)
349                 return;
350
351         CRect rcWnd;
352         GetWindowRect(&rcWnd);
353
354         if (bActive)
355         {
356                 SetTextColor(::GetSysColor(COLOR_CAPTIONTEXT));
357                 SetBackColor(::GetSysColor(COLOR_ACTIVECAPTION));
358         }
359         else
360         {
361                 SetTextColor(::GetSysColor(COLOR_INACTIVECAPTIONTEXT));
362                 SetBackColor(::GetSysColor(COLOR_INACTIVECAPTION));
363         }
364         RedrawWindow(nullptr, nullptr, RDW_FRAME | RDW_INVALIDATE);
365 }
366
367 /**
368  * @brief Set control's colors.
369  * @param [in] pDC pointer to device context.
370  * @param [in] nCtlColor Control color to set.
371  * @note Parameter @p nCtlColor is not used but must be present as this method
372  * is called by framework.
373  * @return Brush for background.
374  */
375 HBRUSH CFilepathEdit::CtlColor(CDC* pDC, UINT nCtlColor) 
376 {
377         UNUSED_ALWAYS(nCtlColor);
378         // Return a non-`nullptr` brush if the parent's 
379         //handler should not be called
380
381         //set text color
382         pDC->SetTextColor(m_crText);
383
384         //set the text's background color
385         pDC->SetBkColor(m_crBackGnd);
386
387         //return the brush used for background this sets control background
388         return m_brBackGnd;
389 }
390
391 /**
392  * @brief Set control's bacground color.
393  * @param [in] rgb Color to set as background color.
394  */
395 void CFilepathEdit::SetBackColor(COLORREF rgb)
396 {
397         //set background color ref (used for text's background)
398         m_crBackGnd = rgb;
399         
400         //free brush
401         if (m_brBackGnd.GetSafeHandle())
402                 m_brBackGnd.DeleteObject();
403         //set brush to new color
404         m_brBackGnd.CreateSolidBrush(rgb);
405         
406         //redraw
407         Invalidate(TRUE);
408 }
409
410 /**
411  * @brief Set control's text color.
412  * @param [in] Color to set as text color.
413  */
414 void CFilepathEdit::SetTextColor(COLORREF rgb)
415 {
416         //set text color ref
417         m_crText = rgb;
418
419         //redraw
420         Invalidate(TRUE);
421 }