1 /*
2  Copyright (C) 2010-2014 Kristian Duske
3 
4  This file is part of TrenchBroom.
5 
6  TrenchBroom 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 3 of the License, or
9  (at your option) any later version.
10 
11  TrenchBroom 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 TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifndef TrenchBroom_RecentDocuments
21 #define TrenchBroom_RecentDocuments
22 
23 #include "CollectionUtils.h"
24 #include "Notifier.h"
25 #include "IO/Path.h"
26 
27 #include <vector>
28 
29 #include <wx/config.h>
30 #include <wx/confbase.h>
31 #include <wx/event.h>
32 #include <wx/menu.h>
33 
34 namespace TrenchBroom {
35     namespace View {
36         template <class EventHandler>
37         class RecentDocuments {
38         private:
39             typedef void (EventHandler::*Function)(wxCommandEvent&);
40             typedef std::vector<wxMenu*> MenuList;
41             MenuList m_menus;
42             EventHandler* m_handler;
43             Function m_function;
44             int m_baseId;
45             size_t m_maxSize;
46             IO::Path::List m_recentDocuments;
47         public:
48             Notifier0 didChangeNotifier;
49         public:
RecentDocuments(const int baseId,const size_t maxSize)50             RecentDocuments(const int baseId, const size_t maxSize) :
51             m_handler(NULL),
52             m_function(NULL),
53             m_baseId(baseId),
54             m_maxSize(maxSize) {
55                 assert(m_maxSize > 0);
56                 loadFromConfig();
57             }
58 
recentDocuments()59             const IO::Path::List& recentDocuments() const {
60                 return m_recentDocuments;
61             }
62 
addMenu(wxMenu * menu)63             void addMenu(wxMenu* menu) {
64                 assert(menu != NULL);
65                 clearMenu(menu);
66                 createMenuItems(menu);
67                 m_menus.push_back(menu);
68             }
69 
removeMenu(wxMenu * menu)70             void removeMenu(wxMenu* menu) {
71                 assert(menu != NULL);
72                 clearMenu(menu);
73                 VectorUtils::erase(m_menus, menu);
74             }
75 
setHandler(EventHandler * handler,Function function)76             void setHandler(EventHandler* handler, Function function) {
77                 if (m_handler != NULL && m_function != NULL)
78                     clearBindings();
79 
80                 m_handler = handler;
81                 m_function = function;
82 
83                 if (m_handler != NULL && m_function != NULL)
84                     createBindings();
85             }
86 
updatePath(const IO::Path & path)87             void updatePath(const IO::Path& path) {
88                 insertPath(path);
89                 updateMenus();
90                 updateBindings();
91                 saveToConfig();
92                 didChangeNotifier();
93             }
94 
removePath(const IO::Path & path)95             void removePath(const IO::Path& path) {
96                 const size_t oldSize = m_recentDocuments.size();
97 
98                 const IO::Path canonPath = path.makeCanonical();
99                 VectorUtils::erase(m_recentDocuments, canonPath);
100 
101                 if (oldSize > m_recentDocuments.size()) {
102                     updateMenus();
103                     updateBindings();
104                     saveToConfig();
105                     didChangeNotifier();
106                 }
107             }
108         private:
loadFromConfig()109             void loadFromConfig() {
110                 m_recentDocuments.clear();
111                 wxConfigBase* conf = wxConfig::Get();
112                 for (size_t i = 0; i < m_maxSize; ++i) {
113                     const wxString confName = wxString("RecentDocuments/") << i;
114                     wxString value;
115                     if (conf->Read(confName, &value))
116                         m_recentDocuments.push_back(IO::Path(value.ToStdString()));
117                     else
118                         break;
119                 }
120             }
121 
saveToConfig()122             void saveToConfig() {
123                 wxConfigBase* conf = wxConfig::Get();
124                 conf->DeleteGroup("RecentDocuments");
125                 for (size_t i = 0; i < m_recentDocuments.size(); ++i) {
126                     const wxString confName = wxString("RecentDocuments/") << i;
127                     const wxString value = m_recentDocuments[i].asString();
128                     conf->Write(confName, value);
129                 }
130             }
131 
updateBindings()132             void updateBindings() {
133                 if (m_handler != NULL && m_function != NULL) {
134                     clearBindings();
135                     createBindings();
136                 }
137             }
138 
createBindings()139             void createBindings() {
140                 for (size_t i = 0; i < m_recentDocuments.size(); ++i) {
141                     wxVariant* data = new wxVariant(wxString(m_recentDocuments[i].asString()));
142                     const int windowId = m_baseId + static_cast<int>(i);
143                     m_handler->Bind(wxEVT_MENU, m_function, m_handler, windowId, windowId, data);
144                 }
145             }
146 
clearBindings()147             void clearBindings() {
148                 for (int i = 0; i < static_cast<int>(m_maxSize); ++i)
149                     m_handler->Unbind(wxEVT_MENU, m_function, m_handler, m_baseId + i);
150             }
151 
insertPath(const IO::Path & path)152             void insertPath(const IO::Path& path) {
153                 const IO::Path canonPath = path.makeCanonical();
154                 IO::Path::List::iterator it = std::find(m_recentDocuments.begin(), m_recentDocuments.end(), canonPath);
155                 if (it != m_recentDocuments.end())
156                     m_recentDocuments.erase(it);
157                 m_recentDocuments.insert(m_recentDocuments.begin(), canonPath);
158                 if (m_recentDocuments.size() > m_maxSize)
159                     m_recentDocuments.pop_back();
160             }
161 
updateMenus()162             void updateMenus() {
163                 MenuList::iterator it, end;
164                 for (it = m_menus.begin(), end = m_menus.end(); it != end; ++it) {
165                     wxMenu* menu = *it;
166                     clearMenu(menu);
167                     createMenuItems(menu);
168                 }
169             }
170 
clearMenu(wxMenu * menu)171             void clearMenu(wxMenu* menu) {
172                 while (menu->GetMenuItemCount() > 0) {
173                     wxMenuItem* item = menu->FindItemByPosition(0);
174                     menu->Delete(item);
175                 }
176             }
177 
createMenuItems(wxMenu * menu)178             void createMenuItems(wxMenu* menu) {
179                 for (size_t i = 0; i < m_recentDocuments.size(); ++i) {
180                     const IO::Path& path = m_recentDocuments[i];
181                     const int windowId = m_baseId + static_cast<int>(i);
182                     menu->Append(windowId, path.lastComponent().asString());
183                 }
184             }
185         };
186     }
187 }
188 
189 #endif /* defined(TrenchBroom_RecentDocuments) */
190