]>
Commit | Line | Data |
---|---|---|
6b8ef0b3 VZ |
1 | ///////////////////////////////////////////////////////////////////////////// |
2 | // Name: samples/fswatcher/fswatcher.cpp | |
3 | // Purpose: wxFileSystemWatcher sample | |
4 | // Author: Bartosz Bekier | |
5 | // Created: 2009-06-27 | |
6 | // RCS-ID: $Id$ | |
7 | // Copyright: (c) Bartosz Bekier | |
8 | // Licence: wxWindows licence | |
9 | ///////////////////////////////////////////////////////////////////////////// | |
10 | ||
11 | #include "wx/wxprec.h" | |
12 | ||
13 | #ifdef __BORLANDC__ | |
14 | #pragma hdrstop | |
15 | #endif | |
16 | #ifndef WX_PRECOMP | |
17 | #include "wx/wx.h" | |
18 | #endif | |
19 | ||
e7092398 | 20 | #ifndef wxHAS_IMAGES_IN_RESOURCES |
6b8ef0b3 VZ |
21 | #include "../sample.xpm" |
22 | #endif | |
23 | ||
24 | #include "wx/fswatcher.h" | |
25 | #include "wx/listctrl.h" | |
5fc3e6d4 | 26 | #include "wx/cmdline.h" |
6b8ef0b3 VZ |
27 | |
28 | // Define a new frame type: this is going to be our main frame | |
29 | class MyFrame : public wxFrame | |
30 | { | |
31 | public: | |
32 | MyFrame(const wxString& title); | |
33 | virtual ~MyFrame(); | |
34 | ||
f8d37148 VZ |
35 | // Add an entry of the specified type asking the user for the filename if |
36 | // the one passed to this function is empty. | |
37 | void AddEntry(wxFSWPathType type, wxString filename = wxString()); | |
5fc3e6d4 VZ |
38 | |
39 | bool CreateWatcherIfNecessary(); | |
40 | ||
6b8ef0b3 VZ |
41 | private: |
42 | // file system watcher creation | |
6b8ef0b3 VZ |
43 | void CreateWatcher(); |
44 | ||
45 | // event handlers | |
46 | void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); } | |
47 | void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); } | |
48 | void OnWatch(wxCommandEvent& event); | |
49 | void OnAbout(wxCommandEvent& event); | |
50 | ||
51 | void OnAdd(wxCommandEvent& event); | |
f8d37148 | 52 | void OnAddTree(wxCommandEvent& event); |
6b8ef0b3 VZ |
53 | void OnRemove(wxCommandEvent& event); |
54 | ||
55 | void OnFileSystemEvent(wxFileSystemWatcherEvent& event); | |
56 | void LogEvent(const wxFileSystemWatcherEvent& event); | |
57 | ||
58 | wxTextCtrl *m_evtConsole; // events console | |
59 | wxListView *m_filesList; // list of watched paths | |
60 | wxFileSystemWatcher* m_watcher; // file system watcher | |
61 | ||
6b8ef0b3 VZ |
62 | const static wxString LOG_FORMAT; // how to format events |
63 | }; | |
64 | ||
65 | const wxString MyFrame::LOG_FORMAT = " %-12s %-36s %-36s"; | |
66 | ||
67 | // Define a new application type, each program should derive a class from wxApp | |
68 | class MyApp : public wxApp | |
69 | { | |
70 | public: | |
71 | // 'Main program' equivalent: the program execution "starts" here | |
72 | virtual bool OnInit() | |
73 | { | |
5fc3e6d4 VZ |
74 | if ( !wxApp::OnInit() ) |
75 | return false; | |
76 | ||
5cd99866 | 77 | wxLog::AddTraceMask("EventSource"); |
6b8ef0b3 VZ |
78 | wxLog::AddTraceMask(wxTRACE_FSWATCHER); |
79 | ||
80 | // create the main application window | |
81 | m_frame = new MyFrame("File System Watcher wxWidgets App"); | |
82 | ||
83 | // If we returned false here, the application would exit immediately. | |
84 | return true; | |
85 | } | |
86 | ||
87 | // create the file system watcher here, because it needs an active loop | |
88 | virtual void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop)) | |
89 | { | |
5fc3e6d4 VZ |
90 | if ( m_frame->CreateWatcherIfNecessary() ) |
91 | { | |
92 | if ( !m_dirToWatch.empty() ) | |
f8d37148 | 93 | m_frame->AddEntry(wxFSWPath_Dir, m_dirToWatch); |
5fc3e6d4 VZ |
94 | } |
95 | } | |
96 | ||
97 | virtual void OnInitCmdLine(wxCmdLineParser& parser) | |
98 | { | |
99 | wxApp::OnInitCmdLine(parser); | |
100 | parser.AddParam("directory to watch", | |
101 | wxCMD_LINE_VAL_STRING, | |
102 | wxCMD_LINE_PARAM_OPTIONAL); | |
103 | } | |
104 | ||
105 | virtual bool OnCmdLineParsed(wxCmdLineParser& parser) | |
106 | { | |
107 | if ( !wxApp::OnCmdLineParsed(parser) ) | |
108 | return false; | |
109 | ||
110 | if ( parser.GetParamCount() ) | |
111 | m_dirToWatch = parser.GetParam(); | |
112 | ||
113 | return true; | |
6b8ef0b3 VZ |
114 | } |
115 | ||
116 | private: | |
117 | MyFrame *m_frame; | |
5fc3e6d4 VZ |
118 | |
119 | // The directory to watch if specified on the command line. | |
120 | wxString m_dirToWatch; | |
6b8ef0b3 VZ |
121 | }; |
122 | ||
123 | // Create a new application object: this macro will allow wxWidgets to create | |
124 | // the application object during program execution (it's better than using a | |
125 | // static object for many reasons) and also declares the accessor function | |
126 | // wxGetApp() which will return the reference of the right type (i.e. MyApp and | |
127 | // not wxApp) | |
128 | IMPLEMENT_APP(MyApp) | |
129 | ||
130 | ||
131 | // ============================================================================ | |
132 | // implementation | |
133 | // ============================================================================ | |
134 | ||
135 | // frame constructor | |
136 | MyFrame::MyFrame(const wxString& title) | |
137 | : wxFrame(NULL, wxID_ANY, title), | |
138 | m_watcher(NULL) | |
139 | { | |
140 | SetIcon(wxICON(sample)); | |
141 | ||
142 | // IDs for menu and buttons | |
143 | enum | |
144 | { | |
145 | MENU_ID_QUIT = wxID_EXIT, | |
146 | MENU_ID_CLEAR = wxID_CLEAR, | |
147 | MENU_ID_WATCH = 101, | |
148 | ||
149 | BTN_ID_ADD = 200, | |
f8d37148 VZ |
150 | BTN_ID_ADD_TREE, |
151 | BTN_ID_REMOVE | |
6b8ef0b3 VZ |
152 | }; |
153 | ||
154 | // ================================================================ | |
155 | // menu | |
156 | ||
157 | // create a menu bar | |
158 | wxMenu *menuFile = new wxMenu; | |
159 | menuFile->Append(MENU_ID_CLEAR, "&Clear log\tCtrl-L"); | |
160 | menuFile->AppendSeparator(); | |
161 | menuFile->Append(MENU_ID_QUIT, "E&xit\tAlt-X", "Quit this program"); | |
162 | ||
163 | // "Watch" menu | |
164 | wxMenu *menuMon = new wxMenu; | |
165 | wxMenuItem* it = menuMon->AppendCheckItem(MENU_ID_WATCH, "&Watch\tCtrl-W"); | |
166 | // started by default, because file system watcher is started by default | |
167 | it->Check(true); | |
168 | ||
169 | // the "About" item should be in the help menu | |
170 | wxMenu *menuHelp = new wxMenu; | |
2d143b66 | 171 | menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog"); |
6b8ef0b3 VZ |
172 | |
173 | // now append the freshly created menu to the menu bar... | |
174 | wxMenuBar *menuBar = new wxMenuBar(); | |
175 | menuBar->Append(menuFile, "&File"); | |
176 | menuBar->Append(menuMon, "&Watch"); | |
177 | menuBar->Append(menuHelp, "&Help"); | |
178 | ||
179 | // ... and attach this menu bar to the frame | |
180 | SetMenuBar(menuBar); | |
181 | ||
182 | // ================================================================ | |
183 | // upper panel | |
184 | ||
185 | // panel | |
186 | wxPanel *panel = new wxPanel(this); | |
187 | wxSizer *panelSizer = new wxGridSizer(2); | |
188 | wxBoxSizer *leftSizer = new wxBoxSizer(wxVERTICAL); | |
189 | ||
190 | // label | |
191 | wxStaticText* label = new wxStaticText(panel, wxID_ANY, "Watched paths"); | |
192 | leftSizer->Add(label, wxSizerFlags().Center().Border(wxALL)); | |
193 | ||
194 | // list of files | |
195 | m_filesList = new wxListView(panel, wxID_ANY, wxPoint(-1,-1), | |
196 | wxSize(300,200), wxLC_LIST | wxLC_SINGLE_SEL); | |
197 | leftSizer->Add(m_filesList, wxSizerFlags(1).Expand()); | |
198 | ||
199 | // buttons | |
200 | wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add"); | |
f8d37148 | 201 | wxButton* buttonAddTree = new wxButton(panel, BTN_ID_ADD_TREE, "Add &tree"); |
6b8ef0b3 VZ |
202 | wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove"); |
203 | wxSizer *btnSizer = new wxGridSizer(2); | |
204 | btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL)); | |
f8d37148 | 205 | btnSizer->Add(buttonAddTree, wxSizerFlags().Center().Border(wxALL)); |
6b8ef0b3 VZ |
206 | btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL)); |
207 | ||
208 | // and put it all together | |
209 | leftSizer->Add(btnSizer, wxSizerFlags(0).Expand()); | |
210 | panelSizer->Add(leftSizer, wxSizerFlags(1).Expand()); | |
211 | panel->SetSizerAndFit(panelSizer); | |
212 | ||
213 | // ================================================================ | |
214 | // lower panel | |
215 | ||
216 | wxTextCtrl *headerText = new wxTextCtrl(this, wxID_ANY, "", | |
217 | wxDefaultPosition, wxDefaultSize, | |
218 | wxTE_READONLY); | |
219 | wxString h = wxString::Format(LOG_FORMAT, "event", "path", "new path"); | |
220 | headerText->SetValue(h); | |
221 | ||
222 | // event console | |
223 | m_evtConsole = new wxTextCtrl(this, wxID_ANY, "", | |
224 | wxDefaultPosition, wxSize(200,200), | |
225 | wxTE_MULTILINE|wxTE_READONLY|wxHSCROLL); | |
226 | ||
227 | // set monospace font to have output in nice columns | |
228 | wxFont font(9, wxFONTFAMILY_TELETYPE, | |
229 | wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); | |
230 | headerText->SetFont(font); | |
231 | m_evtConsole->SetFont(font); | |
232 | ||
233 | // ================================================================ | |
234 | // laying out whole frame | |
235 | ||
236 | wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); | |
237 | sizer->Add(panel, wxSizerFlags(1).Expand()); | |
238 | sizer->Add(headerText, wxSizerFlags().Expand()); | |
239 | sizer->Add(m_evtConsole, wxSizerFlags(1).Expand()); | |
240 | SetSizerAndFit(sizer); | |
241 | ||
242 | // set size and position on screen | |
243 | SetSize(800, 600); | |
244 | CentreOnScreen(); | |
245 | ||
246 | // ================================================================ | |
247 | // event handlers & show | |
248 | ||
249 | // menu | |
250 | Connect(MENU_ID_CLEAR, wxEVT_COMMAND_MENU_SELECTED, | |
251 | wxCommandEventHandler(MyFrame::OnClear)); | |
252 | Connect(MENU_ID_QUIT, wxEVT_COMMAND_MENU_SELECTED, | |
253 | wxCommandEventHandler(MyFrame::OnQuit)); | |
254 | Connect(MENU_ID_WATCH, wxEVT_COMMAND_MENU_SELECTED, | |
255 | wxCommandEventHandler(MyFrame::OnWatch)); | |
256 | Connect(wxID_ABOUT, wxEVT_COMMAND_MENU_SELECTED, | |
257 | wxCommandEventHandler(MyFrame::OnAbout)); | |
258 | ||
259 | // buttons | |
260 | Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED, | |
261 | wxCommandEventHandler(MyFrame::OnAdd)); | |
f8d37148 VZ |
262 | Connect(BTN_ID_ADD_TREE, wxEVT_COMMAND_BUTTON_CLICKED, |
263 | wxCommandEventHandler(MyFrame::OnAddTree)); | |
6b8ef0b3 VZ |
264 | Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED, |
265 | wxCommandEventHandler(MyFrame::OnRemove)); | |
266 | ||
267 | // and show itself (the frames, unlike simple controls, are not shown when | |
268 | // created initially) | |
269 | Show(true); | |
270 | } | |
271 | ||
272 | MyFrame::~MyFrame() | |
273 | { | |
274 | delete m_watcher; | |
275 | } | |
276 | ||
5fc3e6d4 | 277 | bool MyFrame::CreateWatcherIfNecessary() |
6b8ef0b3 VZ |
278 | { |
279 | if (m_watcher) | |
5fc3e6d4 | 280 | return false; |
6b8ef0b3 VZ |
281 | |
282 | CreateWatcher(); | |
283 | Connect(wxEVT_FSWATCHER, | |
284 | wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent)); | |
5fc3e6d4 VZ |
285 | |
286 | return true; | |
6b8ef0b3 VZ |
287 | } |
288 | ||
289 | void MyFrame::CreateWatcher() | |
290 | { | |
291 | wxCHECK_RET(!m_watcher, "Watcher already initialized"); | |
292 | m_watcher = new wxFileSystemWatcher(); | |
293 | m_watcher->SetOwner(this); | |
294 | } | |
295 | ||
296 | // ============================================================================ | |
297 | // event handlers | |
298 | // ============================================================================ | |
299 | ||
300 | void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event)) | |
301 | { | |
302 | wxMessageBox("Demonstrates the usage of file system watcher, " | |
303 | "the wxWidgets monitoring system notifying you of " | |
304 | "changes done to your files.\n" | |
305 | "(c) 2009 Bartosz Bekier\n", | |
306 | "About wxWidgets File System Watcher Sample", | |
307 | wxOK | wxICON_INFORMATION, this); | |
308 | } | |
309 | ||
310 | void MyFrame::OnWatch(wxCommandEvent& event) | |
311 | { | |
312 | wxLogDebug("%s start=%d", __WXFUNCTION__, event.IsChecked()); | |
313 | ||
314 | if (event.IsChecked()) | |
315 | { | |
316 | wxCHECK_RET(!m_watcher, "Watcher already initialized"); | |
317 | CreateWatcher(); | |
318 | } | |
319 | else | |
320 | { | |
321 | wxCHECK_RET(m_watcher, "Watcher not initialized"); | |
322 | m_filesList->DeleteAllItems(); | |
5276b0a5 | 323 | wxDELETE(m_watcher); |
6b8ef0b3 VZ |
324 | } |
325 | } | |
326 | ||
327 | void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event)) | |
328 | { | |
f8d37148 VZ |
329 | AddEntry(wxFSWPath_Dir); |
330 | } | |
6b8ef0b3 | 331 | |
f8d37148 VZ |
332 | void MyFrame::OnAddTree(wxCommandEvent& WXUNUSED(event)) |
333 | { | |
334 | AddEntry(wxFSWPath_Tree); | |
5fc3e6d4 VZ |
335 | } |
336 | ||
f8d37148 | 337 | void MyFrame::AddEntry(wxFSWPathType type, wxString filename) |
5fc3e6d4 | 338 | { |
f8d37148 VZ |
339 | if ( filename.empty() ) |
340 | { | |
341 | // TODO account for adding the files as well | |
342 | filename = wxDirSelector("Choose a folder to watch", "", | |
343 | wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); | |
344 | if ( filename.empty() ) | |
345 | return; | |
346 | } | |
347 | ||
348 | wxCHECK_RET(m_watcher, "Watcher not initialized"); | |
349 | ||
350 | wxLogDebug("Adding %s: '%s'", | |
351 | filename, | |
352 | type == wxFSWPath_Dir ? "directory" : "directory tree"); | |
6b8ef0b3 | 353 | |
ce2532fb | 354 | wxString prefix; |
f8d37148 VZ |
355 | bool ok = false; |
356 | switch ( type ) | |
6b8ef0b3 | 357 | { |
f8d37148 VZ |
358 | case wxFSWPath_Dir: |
359 | ok = m_watcher->Add(wxFileName::DirName(filename)); | |
ce2532fb | 360 | prefix = "Dir: "; |
f8d37148 VZ |
361 | break; |
362 | ||
363 | case wxFSWPath_Tree: | |
364 | ok = m_watcher->AddTree(wxFileName::DirName(filename)); | |
ce2532fb | 365 | prefix = "Tree: "; |
f8d37148 VZ |
366 | break; |
367 | ||
368 | case wxFSWPath_File: | |
369 | case wxFSWPath_None: | |
370 | wxFAIL_MSG( "Unexpected path type." ); | |
6b8ef0b3 | 371 | } |
f8d37148 VZ |
372 | |
373 | if (!ok) | |
6b8ef0b3 | 374 | { |
f8d37148 VZ |
375 | wxLogError("Error adding '%s' to watched paths", filename); |
376 | return; | |
6b8ef0b3 | 377 | } |
f8d37148 | 378 | |
ce2532fb VZ |
379 | // Prepend 'prefix' to the filepath, partly for display |
380 | // but mostly so that OnRemove() can work out the correct way to remove it | |
381 | m_filesList->InsertItem(m_filesList->GetItemCount(), | |
382 | prefix + wxFileName::DirName(filename).GetFullPath()); | |
6b8ef0b3 VZ |
383 | } |
384 | ||
385 | void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event)) | |
386 | { | |
387 | wxCHECK_RET(m_watcher, "Watcher not initialized"); | |
388 | long idx = m_filesList->GetFirstSelected(); | |
389 | if (idx == -1) | |
390 | return; | |
391 | ||
ce2532fb VZ |
392 | bool ret; |
393 | wxString path; | |
6b8ef0b3 | 394 | // TODO we know it is a dir, but it doesn't have to be |
ce2532fb VZ |
395 | if (m_filesList->GetItemText(idx).StartsWith("Dir: ", &path)) |
396 | { | |
397 | ret = m_watcher->Remove(wxFileName::DirName(path)); | |
398 | } | |
399 | else if (m_filesList->GetItemText(idx).StartsWith("Tree: ", &path)) | |
400 | { | |
401 | ret = m_watcher->RemoveTree(wxFileName::DirName(path)); | |
402 | } | |
403 | else | |
404 | { | |
405 | wxFAIL_MSG("Unexpected item in wxListView."); | |
406 | } | |
407 | ||
408 | if (!ret) | |
6b8ef0b3 VZ |
409 | { |
410 | wxLogError("Error removing '%s' from watched paths", path); | |
411 | } | |
412 | else | |
413 | { | |
414 | m_filesList->DeleteItem(idx); | |
415 | } | |
416 | } | |
417 | ||
418 | void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event) | |
419 | { | |
420 | // TODO remove when code is rock-solid | |
6969b18c | 421 | wxLogTrace(wxTRACE_FSWATCHER, "*** %s ***", event.ToString()); |
6b8ef0b3 VZ |
422 | LogEvent(event); |
423 | } | |
424 | ||
425 | ||
426 | static wxString GetFSWEventChangeTypeName(int changeType) | |
427 | { | |
428 | switch (changeType) | |
429 | { | |
430 | case wxFSW_EVENT_CREATE: | |
431 | return "CREATE"; | |
432 | case wxFSW_EVENT_DELETE: | |
433 | return "DELETE"; | |
434 | case wxFSW_EVENT_RENAME: | |
435 | return "RENAME"; | |
436 | case wxFSW_EVENT_MODIFY: | |
437 | return "MODIFY"; | |
438 | case wxFSW_EVENT_ACCESS: | |
439 | return "ACCESS"; | |
440 | } | |
441 | ||
442 | return "INVALID_TYPE"; | |
443 | } | |
444 | ||
445 | void MyFrame::LogEvent(const wxFileSystemWatcherEvent& event) | |
446 | { | |
447 | wxString entry = wxString::Format(LOG_FORMAT + "\n", | |
448 | GetFSWEventChangeTypeName(event.GetChangeType()), | |
449 | event.GetPath().GetFullPath(), | |
450 | event.GetNewPath().GetFullPath()); | |
451 | m_evtConsole->AppendText(entry); | |
452 | } |