1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/common/filehistorycmn.cpp
3 // Purpose:     wxFileHistory class
4 // Author:      Julian Smart, Vaclav Slavik, Vadim Zeitlin
5 // Created:     2010-05-03
6 // Copyright:   (c) Julian Smart
7 // Licence:     wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9 
10 // ============================================================================
11 // declarations
12 // ============================================================================
13 
14 // ----------------------------------------------------------------------------
15 // headers
16 // ----------------------------------------------------------------------------
17 
18 // For compilers that support precompilation, includes "wx.h".
19 #include "wx/wxprec.h"
20 
21 #ifdef __BORLANDC__
22     #pragma hdrstop
23 #endif
24 
25 #include "wx/filehistory.h"
26 
27 #if wxUSE_FILE_HISTORY
28 
29 #include "wx/menu.h"
30 #include "wx/confbase.h"
31 #include "wx/filename.h"
32 
33 // ============================================================================
34 // implementation
35 // ============================================================================
36 
37 // ----------------------------------------------------------------------------
38 // private helpers
39 // ----------------------------------------------------------------------------
40 
41 namespace
42 {
43 
44 // return the string used for the MRU list items in the menu
45 //
46 // NB: the index n is 0-based, as usual, but the strings start from 1
GetMRUEntryLabel(int n,const wxString & path)47 wxString GetMRUEntryLabel(int n, const wxString& path)
48 {
49     // we need to quote '&' characters which are used for mnemonics
50     wxString pathInMenu(path);
51     pathInMenu.Replace("&", "&&");
52 
53 #ifdef __WXMSW__
54     // absolute paths always start with Latin characters even in RTL
55     // environments and should therefore be rendered as LTR text (possibly with
56     // RTL chunks in it). Ensure this on Windows by prepending
57     // LEFT-TO-RIGHT EMBEDDING (other platforms detect this automatically)
58     pathInMenu.insert(0, wchar_t(0x202a));
59 #endif
60 
61     return wxString::Format("&%d %s", n + 1, pathInMenu);
62 }
63 
64 } // anonymous namespace
65 
66 // ----------------------------------------------------------------------------
67 // File history (a.k.a. MRU, most recently used, files list)
68 // ----------------------------------------------------------------------------
69 
IMPLEMENT_DYNAMIC_CLASS(wxFileHistory,wxObject)70 IMPLEMENT_DYNAMIC_CLASS(wxFileHistory, wxObject)
71 
72 wxFileHistoryBase::wxFileHistoryBase(size_t maxFiles, wxWindowID idBase)
73 {
74     m_fileMaxFiles = maxFiles;
75     m_idBase = idBase;
76 }
77 
78 /* static */
NormalizeFileName(const wxFileName & fn)79 wxString wxFileHistoryBase::NormalizeFileName(const wxFileName& fn)
80 {
81     // We specifically exclude wxPATH_NORM_LONG here as it can take a long time
82     // (several seconds) for network file paths under MSW, resulting in huge
83     // delays when opening a program using wxFileHistory. We also exclude
84     // wxPATH_NORM_ENV_VARS as the file names here are supposed to be "real"
85     // file names and not have any environment variables in them.
86     wxFileName fnNorm(fn);
87     fnNorm.Normalize(wxPATH_NORM_DOTS |
88                      wxPATH_NORM_TILDE |
89                      wxPATH_NORM_CASE |
90                      wxPATH_NORM_ABSOLUTE);
91     return fnNorm.GetFullPath();
92 }
93 
AddFileToHistory(const wxString & file)94 void wxFileHistoryBase::AddFileToHistory(const wxString& file)
95 {
96     // Check if we don't already have this file. Notice that we avoid
97     // wxFileName::operator==(wxString) here as it converts the string to
98     // wxFileName and then normalizes it using all normalizations which is too
99     // slow (see the comment above), so we use our own quick normalization
100     // functions and a string comparison.
101     const wxFileName fnNew(file);
102     const wxString newFile = NormalizeFileName(fnNew);
103     size_t i,
104            numFiles = m_fileHistory.size();
105     for ( i = 0; i < numFiles; i++ )
106     {
107         if ( newFile == NormalizeFileName(m_fileHistory[i]) )
108         {
109             // we do have it, move it to the top of the history
110             RemoveFileFromHistory(i);
111             numFiles--;
112             break;
113         }
114     }
115 
116     // if we already have a full history, delete the one at the end
117     if ( numFiles == m_fileMaxFiles )
118     {
119         RemoveFileFromHistory(--numFiles);
120     }
121 
122     // add a new menu item to all file menus (they will be updated below)
123     for ( wxList::compatibility_iterator node = m_fileMenus.GetFirst();
124         node;
125         node = node->GetNext() )
126     {
127         wxMenu * const menu = (wxMenu *)node->GetData();
128 
129         if ( !numFiles && menu->GetMenuItemCount() )
130             menu->AppendSeparator();
131 
132         // label doesn't matter, it will be set below anyhow, but it can't
133         // be empty (this is supposed to indicate a stock item)
134         menu->Append(m_idBase + numFiles, " ");
135     }
136 
137     // insert the new file in the beginning of the file history
138     m_fileHistory.insert(m_fileHistory.begin(), file);
139     numFiles++;
140 
141     // update the labels in all menus
142     for ( i = 0; i < numFiles; i++ )
143     {
144         // if in same directory just show the filename; otherwise the full path
145         const wxFileName fnOld(m_fileHistory[i]);
146 
147         wxString pathInMenu;
148         if ( fnOld.GetPath() == fnNew.GetPath() )
149         {
150             pathInMenu = fnOld.GetFullName();
151         }
152         else // file in different directory
153         {
154             // absolute path; could also set relative path
155             pathInMenu = m_fileHistory[i];
156         }
157 
158         for ( wxList::compatibility_iterator node = m_fileMenus.GetFirst();
159               node;
160               node = node->GetNext() )
161         {
162             wxMenu * const menu = (wxMenu *)node->GetData();
163 
164             menu->SetLabel(m_idBase + i, GetMRUEntryLabel(i, pathInMenu));
165         }
166     }
167 }
168 
RemoveFileFromHistory(size_t i)169 void wxFileHistoryBase::RemoveFileFromHistory(size_t i)
170 {
171     size_t numFiles = m_fileHistory.size();
172     wxCHECK_RET( i < numFiles,
173                  wxT("invalid index in wxFileHistoryBase::RemoveFileFromHistory") );
174 
175     // delete the element from the array
176     m_fileHistory.RemoveAt(i);
177     numFiles--;
178 
179     for ( wxList::compatibility_iterator node = m_fileMenus.GetFirst();
180           node;
181           node = node->GetNext() )
182     {
183         wxMenu * const menu = (wxMenu *) node->GetData();
184 
185         // shift filenames up
186         for ( size_t j = i; j < numFiles; j++ )
187         {
188             menu->SetLabel(m_idBase + j, GetMRUEntryLabel(j, m_fileHistory[j]));
189         }
190 
191         // delete the last menu item which is unused now
192         const wxWindowID lastItemId = m_idBase + numFiles;
193         if ( menu->FindItem(lastItemId) )
194             menu->Delete(lastItemId);
195 
196         // delete the last separator too if no more files are left
197         if ( m_fileHistory.empty() )
198         {
199             const wxMenuItemList::compatibility_iterator
200                 nodeLast = menu->GetMenuItems().GetLast();
201             if ( nodeLast )
202             {
203                 wxMenuItem * const lastMenuItem = nodeLast->GetData();
204                 if ( lastMenuItem->IsSeparator() )
205                     menu->Delete(lastMenuItem);
206             }
207             //else: menu is empty somehow
208         }
209     }
210 }
211 
UseMenu(wxMenu * menu)212 void wxFileHistoryBase::UseMenu(wxMenu *menu)
213 {
214     if ( !m_fileMenus.Member(menu) )
215         m_fileMenus.Append(menu);
216 }
217 
RemoveMenu(wxMenu * menu)218 void wxFileHistoryBase::RemoveMenu(wxMenu *menu)
219 {
220     m_fileMenus.DeleteObject(menu);
221 }
222 
223 #if wxUSE_CONFIG
Load(const wxConfigBase & config)224 void wxFileHistoryBase::Load(const wxConfigBase& config)
225 {
226     m_fileHistory.Clear();
227 
228     wxString buf;
229     buf.Printf(wxT("file%d"), 1);
230 
231     wxString historyFile;
232     while ((m_fileHistory.GetCount() < m_fileMaxFiles) &&
233            config.Read(buf, &historyFile) && !historyFile.empty())
234     {
235         m_fileHistory.Add(historyFile);
236 
237         buf.Printf(wxT("file%d"), (int)m_fileHistory.GetCount()+1);
238         historyFile = wxEmptyString;
239     }
240 
241     AddFilesToMenu();
242 }
243 
Save(wxConfigBase & config)244 void wxFileHistoryBase::Save(wxConfigBase& config)
245 {
246     size_t i;
247     for (i = 0; i < m_fileMaxFiles; i++)
248     {
249         wxString buf;
250         buf.Printf(wxT("file%d"), (int)i+1);
251         if (i < m_fileHistory.GetCount())
252             config.Write(buf, wxString(m_fileHistory[i]));
253         else
254             config.Write(buf, wxEmptyString);
255     }
256 }
257 #endif // wxUSE_CONFIG
258 
AddFilesToMenu()259 void wxFileHistoryBase::AddFilesToMenu()
260 {
261     if ( m_fileHistory.empty() )
262         return;
263 
264     for ( wxList::compatibility_iterator node = m_fileMenus.GetFirst();
265           node;
266           node = node->GetNext() )
267     {
268         AddFilesToMenu((wxMenu *) node->GetData());
269     }
270 }
271 
AddFilesToMenu(wxMenu * menu)272 void wxFileHistoryBase::AddFilesToMenu(wxMenu* menu)
273 {
274     if ( m_fileHistory.empty() )
275         return;
276 
277     if ( menu->GetMenuItemCount() )
278         menu->AppendSeparator();
279 
280     for ( size_t i = 0; i < m_fileHistory.GetCount(); i++ )
281     {
282         menu->Append(m_idBase + i, GetMRUEntryLabel(i, m_fileHistory[i]));
283     }
284 }
285 
286 #endif // wxUSE_FILE_HISTORY
287