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