OSDN Git Service

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