XRC: make wxStaticText's wrap property a dimension.
[wxWidgets.git] / samples / fswatcher / fswatcher.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: samples/fswatcher/fswatcher.cpp
3 // Purpose: wxFileSystemWatcher sample
4 // Author: Bartosz Bekier
5 // Created: 2009-06-27
6 // Copyright: (c) Bartosz Bekier
7 // Licence: wxWindows licence
8 /////////////////////////////////////////////////////////////////////////////
9
10 #include "wx/wxprec.h"
11
12 #ifdef __BORLANDC__
13 #pragma hdrstop
14 #endif
15 #ifndef WX_PRECOMP
16 #include "wx/wx.h"
17 #endif
18
19 #ifndef wxHAS_IMAGES_IN_RESOURCES
20 #include "../sample.xpm"
21 #endif
22
23 #include "wx/fswatcher.h"
24 #include "wx/listctrl.h"
25 #include "wx/cmdline.h"
26
27 // Define a new frame type: this is going to be our main frame
28 class MyFrame : public wxFrame
29 {
30 public:
31 MyFrame(const wxString& title);
32 virtual ~MyFrame();
33
34 // Add an entry of the specified type asking the user for the filename if
35 // the one passed to this function is empty.
36 void AddEntry(wxFSWPathType type, wxString filename = wxString());
37
38 bool CreateWatcherIfNecessary();
39
40 private:
41 // file system watcher creation
42 void CreateWatcher();
43
44 // event handlers
45 void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
46 void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
47 void OnWatch(wxCommandEvent& event);
48 void OnFollowLinks(wxCommandEvent& event);
49 void OnAbout(wxCommandEvent& event);
50
51 void OnAdd(wxCommandEvent& event);
52 void OnAddTree(wxCommandEvent& event);
53 void OnRemove(wxCommandEvent& event);
54 void OnRemoveUpdateUI(wxUpdateUIEvent& event);
55
56 void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
57 void LogEvent(const wxFileSystemWatcherEvent& event);
58
59 wxTextCtrl *m_evtConsole; // events console
60 wxListView *m_filesList; // list of watched paths
61 wxFileSystemWatcher* m_watcher; // file system watcher
62 bool m_followLinks; // should symlinks be dereferenced
63
64 const static wxString LOG_FORMAT; // how to format events
65 };
66
67 const wxString MyFrame::LOG_FORMAT = " %-12s %-36s %-36s";
68
69 // Define a new application type, each program should derive a class from wxApp
70 class MyApp : public wxApp
71 {
72 public:
73 // 'Main program' equivalent: the program execution "starts" here
74 virtual bool OnInit()
75 {
76 if ( !wxApp::OnInit() )
77 return false;
78
79 wxLog::AddTraceMask("EventSource");
80 wxLog::AddTraceMask(wxTRACE_FSWATCHER);
81
82 // create the main application window
83 m_frame = new MyFrame("File System Watcher wxWidgets App");
84
85 // If we returned false here, the application would exit immediately.
86 return true;
87 }
88
89 // create the file system watcher here, because it needs an active loop
90 virtual void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop))
91 {
92 if ( m_frame->CreateWatcherIfNecessary() )
93 {
94 if ( !m_dirToWatch.empty() )
95 m_frame->AddEntry(wxFSWPath_Dir, m_dirToWatch);
96 }
97 }
98
99 virtual void OnInitCmdLine(wxCmdLineParser& parser)
100 {
101 wxApp::OnInitCmdLine(parser);
102 parser.AddParam("directory to watch",
103 wxCMD_LINE_VAL_STRING,
104 wxCMD_LINE_PARAM_OPTIONAL);
105 }
106
107 virtual bool OnCmdLineParsed(wxCmdLineParser& parser)
108 {
109 if ( !wxApp::OnCmdLineParsed(parser) )
110 return false;
111
112 if ( parser.GetParamCount() )
113 m_dirToWatch = parser.GetParam();
114
115 return true;
116 }
117
118 private:
119 MyFrame *m_frame;
120
121 // The directory to watch if specified on the command line.
122 wxString m_dirToWatch;
123 };
124
125 // Create a new application object: this macro will allow wxWidgets to create
126 // the application object during program execution (it's better than using a
127 // static object for many reasons) and also declares the accessor function
128 // wxGetApp() which will return the reference of the right type (i.e. MyApp and
129 // not wxApp)
130 IMPLEMENT_APP(MyApp)
131
132
133 // ============================================================================
134 // implementation
135 // ============================================================================
136
137 // frame constructor
138 MyFrame::MyFrame(const wxString& title)
139 : wxFrame(NULL, wxID_ANY, title),
140 m_watcher(NULL), m_followLinks(false)
141 {
142 SetIcon(wxICON(sample));
143
144 // IDs for menu and buttons
145 enum
146 {
147 MENU_ID_QUIT = wxID_EXIT,
148 MENU_ID_CLEAR = wxID_CLEAR,
149 MENU_ID_WATCH = 101,
150 MENU_ID_DEREFERENCE,
151
152 BTN_ID_ADD = 200,
153 BTN_ID_ADD_TREE,
154 BTN_ID_REMOVE
155 };
156
157 // ================================================================
158 // menu
159
160 // create a menu bar
161 wxMenu *menuFile = new wxMenu;
162 menuFile->Append(MENU_ID_CLEAR, "&Clear log\tCtrl-L");
163 menuFile->AppendSeparator();
164 menuFile->Append(MENU_ID_QUIT, "E&xit\tAlt-X", "Quit this program");
165
166 // "Watch" menu
167 wxMenu *menuMon = new wxMenu;
168 wxMenuItem* it = menuMon->AppendCheckItem(MENU_ID_WATCH, "&Watch\tCtrl-W");
169 // started by default, because file system watcher is started by default
170 it->Check(true);
171
172 #if defined(__UNIX__)
173 // Let the user decide whether to dereference symlinks. If he makes the
174 // wrong choice, asserts will occur if the symlink target is also watched
175 it = menuMon->AppendCheckItem(MENU_ID_DEREFERENCE,
176 "&Follow symlinks\tCtrl-F",
177 _("If checked, dereference symlinks")
178 );
179 it->Check(false);
180 Connect(MENU_ID_DEREFERENCE, wxEVT_MENU,
181 wxCommandEventHandler(MyFrame::OnFollowLinks));
182 #endif // __UNIX__
183
184 // the "About" item should be in the help menu
185 wxMenu *menuHelp = new wxMenu;
186 menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
187
188 // now append the freshly created menu to the menu bar...
189 wxMenuBar *menuBar = new wxMenuBar();
190 menuBar->Append(menuFile, "&File");
191 menuBar->Append(menuMon, "&Watch");
192 menuBar->Append(menuHelp, "&Help");
193
194 // ... and attach this menu bar to the frame
195 SetMenuBar(menuBar);
196
197 // ================================================================
198 // upper panel
199
200 // panel
201 wxPanel *panel = new wxPanel(this);
202 wxSizer *panelSizer = new wxGridSizer(2);
203 wxBoxSizer *leftSizer = new wxBoxSizer(wxVERTICAL);
204
205 // label
206 wxStaticText* label = new wxStaticText(panel, wxID_ANY, "Watched paths");
207 leftSizer->Add(label, wxSizerFlags().Center().Border(wxALL));
208
209 // list of files
210 m_filesList = new wxListView(panel, wxID_ANY, wxPoint(-1,-1),
211 wxSize(300,200), wxLC_LIST | wxLC_SINGLE_SEL);
212 leftSizer->Add(m_filesList, wxSizerFlags(1).Expand());
213
214 // buttons
215 wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add");
216 wxButton* buttonAddTree = new wxButton(panel, BTN_ID_ADD_TREE, "Add &tree");
217 wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove");
218 wxSizer *btnSizer = new wxGridSizer(2);
219 btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL));
220 btnSizer->Add(buttonAddTree, wxSizerFlags().Center().Border(wxALL));
221 btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL));
222
223 // and put it all together
224 leftSizer->Add(btnSizer, wxSizerFlags(0).Expand());
225 panelSizer->Add(leftSizer, wxSizerFlags(1).Expand());
226 panel->SetSizerAndFit(panelSizer);
227
228 // ================================================================
229 // lower panel
230
231 wxTextCtrl *headerText = new wxTextCtrl(this, wxID_ANY, "",
232 wxDefaultPosition, wxDefaultSize,
233 wxTE_READONLY);
234 wxString h = wxString::Format(LOG_FORMAT, "event", "path", "new path");
235 headerText->SetValue(h);
236
237 // event console
238 m_evtConsole = new wxTextCtrl(this, wxID_ANY, "",
239 wxDefaultPosition, wxSize(200,200),
240 wxTE_MULTILINE|wxTE_READONLY|wxHSCROLL);
241
242 // set monospace font to have output in nice columns
243 wxFont font(9, wxFONTFAMILY_TELETYPE,
244 wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
245 headerText->SetFont(font);
246 m_evtConsole->SetFont(font);
247
248 // ================================================================
249 // laying out whole frame
250
251 wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
252 sizer->Add(panel, wxSizerFlags(1).Expand());
253 sizer->Add(headerText, wxSizerFlags().Expand());
254 sizer->Add(m_evtConsole, wxSizerFlags(1).Expand());
255 SetSizerAndFit(sizer);
256
257 // set size and position on screen
258 SetSize(800, 600);
259 CentreOnScreen();
260
261 // ================================================================
262 // event handlers & show
263
264 // menu
265 Connect(MENU_ID_CLEAR, wxEVT_MENU,
266 wxCommandEventHandler(MyFrame::OnClear));
267 Connect(MENU_ID_QUIT, wxEVT_MENU,
268 wxCommandEventHandler(MyFrame::OnQuit));
269 Connect(MENU_ID_WATCH, wxEVT_MENU,
270 wxCommandEventHandler(MyFrame::OnWatch));
271 Connect(wxID_ABOUT, wxEVT_MENU,
272 wxCommandEventHandler(MyFrame::OnAbout));
273
274 // buttons
275 Connect(BTN_ID_ADD, wxEVT_BUTTON,
276 wxCommandEventHandler(MyFrame::OnAdd));
277 Connect(BTN_ID_ADD_TREE, wxEVT_BUTTON,
278 wxCommandEventHandler(MyFrame::OnAddTree));
279 Connect(BTN_ID_REMOVE, wxEVT_BUTTON,
280 wxCommandEventHandler(MyFrame::OnRemove));
281 Connect(BTN_ID_REMOVE, wxEVT_UPDATE_UI,
282 wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI));
283
284 // and show itself (the frames, unlike simple controls, are not shown when
285 // created initially)
286 Show(true);
287 }
288
289 MyFrame::~MyFrame()
290 {
291 delete m_watcher;
292 }
293
294 bool MyFrame::CreateWatcherIfNecessary()
295 {
296 if (m_watcher)
297 return false;
298
299 CreateWatcher();
300 Connect(wxEVT_FSWATCHER,
301 wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent));
302
303 return true;
304 }
305
306 void MyFrame::CreateWatcher()
307 {
308 wxCHECK_RET(!m_watcher, "Watcher already initialized");
309 m_watcher = new wxFileSystemWatcher();
310 m_watcher->SetOwner(this);
311 }
312
313 // ============================================================================
314 // event handlers
315 // ============================================================================
316
317 void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
318 {
319 wxMessageBox("Demonstrates the usage of file system watcher, "
320 "the wxWidgets monitoring system notifying you of "
321 "changes done to your files.\n"
322 "(c) 2009 Bartosz Bekier\n",
323 "About wxWidgets File System Watcher Sample",
324 wxOK | wxICON_INFORMATION, this);
325 }
326
327 void MyFrame::OnWatch(wxCommandEvent& event)
328 {
329 wxLogDebug("%s start=%d", __WXFUNCTION__, event.IsChecked());
330
331 if (event.IsChecked())
332 {
333 wxCHECK_RET(!m_watcher, "Watcher already initialized");
334 CreateWatcher();
335 }
336 else
337 {
338 wxCHECK_RET(m_watcher, "Watcher not initialized");
339 m_filesList->DeleteAllItems();
340 wxDELETE(m_watcher);
341 }
342 }
343
344 void MyFrame::OnFollowLinks(wxCommandEvent& event)
345 {
346 m_followLinks = event.IsChecked();
347 }
348
349 void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
350 {
351 AddEntry(wxFSWPath_Dir);
352 }
353
354 void MyFrame::OnAddTree(wxCommandEvent& WXUNUSED(event))
355 {
356 AddEntry(wxFSWPath_Tree);
357 }
358
359 void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
360 {
361 if ( filename.empty() )
362 {
363 // TODO account for adding the files as well
364 filename = wxDirSelector("Choose a folder to watch", "",
365 wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
366 if ( filename.empty() )
367 return;
368 }
369
370 wxCHECK_RET(m_watcher, "Watcher not initialized");
371
372 wxLogDebug("Adding %s: '%s'",
373 filename,
374 type == wxFSWPath_Dir ? "directory" : "directory tree");
375
376 wxString prefix;
377 bool ok = false;
378
379 // This will tell wxFileSystemWatcher whether to dereference symlinks
380 wxFileName fn = wxFileName::DirName(filename);
381 if (!m_followLinks)
382 {
383 fn.DontFollowLink();
384 }
385
386 switch ( type )
387 {
388 case wxFSWPath_Dir:
389 ok = m_watcher->Add(fn);
390 prefix = "Dir: ";
391 break;
392
393 case wxFSWPath_Tree:
394 ok = m_watcher->AddTree(fn);
395 prefix = "Tree: ";
396 break;
397
398 case wxFSWPath_File:
399 case wxFSWPath_None:
400 wxFAIL_MSG( "Unexpected path type." );
401 }
402
403 if (!ok)
404 {
405 wxLogError("Error adding '%s' to watched paths", filename);
406 return;
407 }
408
409 // Prepend 'prefix' to the filepath, partly for display
410 // but mostly so that OnRemove() can work out the correct way to remove it
411 m_filesList->InsertItem(m_filesList->GetItemCount(),
412 prefix + wxFileName::DirName(filename).GetFullPath());
413 }
414
415 void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
416 {
417 wxCHECK_RET(m_watcher, "Watcher not initialized");
418 long idx = m_filesList->GetFirstSelected();
419 if (idx == -1)
420 return;
421
422 bool ret = false;
423 wxString path = m_filesList->GetItemText(idx).Mid(6);
424
425 // This will tell wxFileSystemWatcher whether to dereference symlinks
426 wxFileName fn = wxFileName::DirName(path);
427 if (!m_followLinks)
428 {
429 fn.DontFollowLink();
430 }
431
432 // TODO we know it is a dir, but it doesn't have to be
433 if (m_filesList->GetItemText(idx).StartsWith("Dir: "))
434 {
435 ret = m_watcher->Remove(fn);
436 }
437 else if (m_filesList->GetItemText(idx).StartsWith("Tree: "))
438 {
439 ret = m_watcher->RemoveTree(fn);
440 }
441 else
442 {
443 wxFAIL_MSG("Unexpected item in wxListView.");
444 }
445
446 if (!ret)
447 {
448 wxLogError("Error removing '%s' from watched paths", path);
449 }
450 else
451 {
452 m_filesList->DeleteItem(idx);
453 }
454 }
455
456 void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event)
457 {
458 event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND);
459 }
460
461 void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
462 {
463 // TODO remove when code is rock-solid
464 wxLogTrace(wxTRACE_FSWATCHER, "*** %s ***", event.ToString());
465 LogEvent(event);
466
467 int type = event.GetChangeType();
468 if ((type == wxFSW_EVENT_DELETE) || (type == wxFSW_EVENT_RENAME))
469 {
470 // If path is one of our watched dirs, we need to react to this
471 // otherwise there'll be asserts if later we try to remove it
472 wxString eventpath = event.GetPath().GetFullPath();
473 bool found(false);
474 for (size_t n = m_filesList->GetItemCount(); n > 0; --n)
475 {
476 wxString path, foo = m_filesList->GetItemText(n-1);
477 if ((!m_filesList->GetItemText(n-1).StartsWith("Dir: ", &path)) &&
478 (!m_filesList->GetItemText(n-1).StartsWith("Tree: ", &path)))
479 {
480 wxFAIL_MSG("Unexpected item in wxListView.");
481 }
482 if (path == eventpath)
483 {
484 if (type == wxFSW_EVENT_DELETE)
485 {
486 m_filesList->DeleteItem(n-1);
487 }
488 else
489 {
490 // At least in wxGTK, we'll never get here: renaming the top
491 // watched dir gives IN_MOVE_SELF and no new-name info.
492 // However I'll leave the code in case other platforms do
493 wxString newname = event.GetNewPath().GetFullPath();
494 if (newname.empty() ||
495 newname == event.GetPath().GetFullPath())
496 {
497 // Just in case either of these are possible...
498 wxLogTrace(wxTRACE_FSWATCHER,
499 "Invalid attempt to rename to %s", newname);
500 return;
501 }
502 wxString prefix =
503 m_filesList->GetItemText(n-1).StartsWith("Dir: ") ?
504 "Dir: " : "Tree: ";
505 m_filesList->SetItemText(n-1, prefix + newname);
506 }
507 found = true;
508 // Don't break: a filepath may have been added more than once
509 }
510 }
511
512 if (found)
513 {
514 wxString msg = wxString::Format(
515 "Your watched path %s has been deleted or renamed\n",
516 eventpath);
517 m_evtConsole->AppendText(msg);
518 }
519 }
520 }
521
522
523 static wxString GetFSWEventChangeTypeName(int changeType)
524 {
525 switch (changeType)
526 {
527 case wxFSW_EVENT_CREATE:
528 return "CREATE";
529 case wxFSW_EVENT_DELETE:
530 return "DELETE";
531 case wxFSW_EVENT_RENAME:
532 return "RENAME";
533 case wxFSW_EVENT_MODIFY:
534 return "MODIFY";
535 case wxFSW_EVENT_ACCESS:
536 return "ACCESS";
537 case wxFSW_EVENT_ATTRIB: // Currently this is wxGTK-only
538 return "ATTRIBUTE";
539 #ifdef wxHAS_INOTIFY
540 case wxFSW_EVENT_UNMOUNT: // Currently this is wxGTK-only
541 return "UNMOUNT";
542 #endif
543 case wxFSW_EVENT_WARNING:
544 return "WARNING";
545 case wxFSW_EVENT_ERROR:
546 return "ERROR";
547 }
548
549 return "INVALID_TYPE";
550 }
551
552 void MyFrame::LogEvent(const wxFileSystemWatcherEvent& event)
553 {
554 wxString entry = wxString::Format(LOG_FORMAT + "\n",
555 GetFSWEventChangeTypeName(event.GetChangeType()),
556 event.GetPath().GetFullPath(),
557 event.GetNewPath().GetFullPath());
558 m_evtConsole->AppendText(entry);
559 }