OSDN Git Service

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