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