1 /////////////////////////////////////////////////////////////////////////////
2 // Name: samples/fswatcher/fswatcher.cpp
3 // Purpose: wxFileSystemWatcher sample
4 // Author: Bartosz Bekier
7 // Copyright: (c) Bartosz Bekier
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 #include "wx/wxprec.h"
20 #ifndef wxHAS_IMAGES_IN_RESOURCES
21 #include "../sample.xpm"
24 #include "wx/fswatcher.h"
25 #include "wx/listctrl.h"
26 #include "wx/cmdline.h"
28 // Define a new frame type: this is going to be our main frame
29 class MyFrame
: public wxFrame
32 MyFrame(const wxString
& title
);
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());
39 bool CreateWatcherIfNecessary();
42 // file system watcher creation
46 void OnClear(wxCommandEvent
& WXUNUSED(event
)) { m_evtConsole
->Clear(); }
47 void OnQuit(wxCommandEvent
& WXUNUSED(event
)) { Close(true); }
48 void OnWatch(wxCommandEvent
& event
);
49 void OnFollowLinks(wxCommandEvent
& event
);
50 void OnAbout(wxCommandEvent
& event
);
52 void OnAdd(wxCommandEvent
& event
);
53 void OnAddTree(wxCommandEvent
& event
);
54 void OnRemove(wxCommandEvent
& event
);
55 void OnRemoveUpdateUI(wxUpdateUIEvent
& event
);
57 void OnFileSystemEvent(wxFileSystemWatcherEvent
& event
);
58 void LogEvent(const wxFileSystemWatcherEvent
& event
);
60 wxTextCtrl
*m_evtConsole
; // events console
61 wxListView
*m_filesList
; // list of watched paths
62 wxFileSystemWatcher
* m_watcher
; // file system watcher
63 bool m_followLinks
; // should symlinks be dereferenced
65 const static wxString LOG_FORMAT
; // how to format events
68 const wxString
MyFrame::LOG_FORMAT
= " %-12s %-36s %-36s";
70 // Define a new application type, each program should derive a class from wxApp
71 class MyApp
: public wxApp
74 // 'Main program' equivalent: the program execution "starts" here
77 if ( !wxApp::OnInit() )
80 wxLog::AddTraceMask("EventSource");
81 wxLog::AddTraceMask(wxTRACE_FSWATCHER
);
83 // create the main application window
84 m_frame
= new MyFrame("File System Watcher wxWidgets App");
86 // If we returned false here, the application would exit immediately.
90 // create the file system watcher here, because it needs an active loop
91 virtual void OnEventLoopEnter(wxEventLoopBase
* WXUNUSED(loop
))
93 if ( m_frame
->CreateWatcherIfNecessary() )
95 if ( !m_dirToWatch
.empty() )
96 m_frame
->AddEntry(wxFSWPath_Dir
, m_dirToWatch
);
100 virtual void OnInitCmdLine(wxCmdLineParser
& parser
)
102 wxApp::OnInitCmdLine(parser
);
103 parser
.AddParam("directory to watch",
104 wxCMD_LINE_VAL_STRING
,
105 wxCMD_LINE_PARAM_OPTIONAL
);
108 virtual bool OnCmdLineParsed(wxCmdLineParser
& parser
)
110 if ( !wxApp::OnCmdLineParsed(parser
) )
113 if ( parser
.GetParamCount() )
114 m_dirToWatch
= parser
.GetParam();
122 // The directory to watch if specified on the command line.
123 wxString m_dirToWatch
;
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
134 // ============================================================================
136 // ============================================================================
139 MyFrame::MyFrame(const wxString
& title
)
140 : wxFrame(NULL
, wxID_ANY
, title
),
141 m_watcher(NULL
), m_followLinks(false)
143 SetIcon(wxICON(sample
));
145 // IDs for menu and buttons
148 MENU_ID_QUIT
= wxID_EXIT
,
149 MENU_ID_CLEAR
= wxID_CLEAR
,
158 // ================================================================
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");
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
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")
181 Connect(MENU_ID_DEREFERENCE
, wxEVT_COMMAND_MENU_SELECTED
,
182 wxCommandEventHandler(MyFrame::OnFollowLinks
));
185 // the "About" item should be in the help menu
186 wxMenu
*menuHelp
= new wxMenu
;
187 menuHelp
->Append(wxID_ABOUT
, "&About\tF1", "Show about dialog");
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");
195 // ... and attach this menu bar to the frame
198 // ================================================================
202 wxPanel
*panel
= new wxPanel(this);
203 wxSizer
*panelSizer
= new wxGridSizer(2);
204 wxBoxSizer
*leftSizer
= new wxBoxSizer(wxVERTICAL
);
207 wxStaticText
* label
= new wxStaticText(panel
, wxID_ANY
, "Watched paths");
208 leftSizer
->Add(label
, wxSizerFlags().Center().Border(wxALL
));
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());
216 wxButton
* buttonAdd
= new wxButton(panel
, BTN_ID_ADD
, "&Add");
217 wxButton
* buttonAddTree
= new wxButton(panel
, BTN_ID_ADD_TREE
, "Add &tree");
218 wxButton
* buttonRemove
= new wxButton(panel
, BTN_ID_REMOVE
, "&Remove");
219 wxSizer
*btnSizer
= new wxGridSizer(2);
220 btnSizer
->Add(buttonAdd
, wxSizerFlags().Center().Border(wxALL
));
221 btnSizer
->Add(buttonAddTree
, wxSizerFlags().Center().Border(wxALL
));
222 btnSizer
->Add(buttonRemove
, wxSizerFlags().Center().Border(wxALL
));
224 // and put it all together
225 leftSizer
->Add(btnSizer
, wxSizerFlags(0).Expand());
226 panelSizer
->Add(leftSizer
, wxSizerFlags(1).Expand());
227 panel
->SetSizerAndFit(panelSizer
);
229 // ================================================================
232 wxTextCtrl
*headerText
= new wxTextCtrl(this, wxID_ANY
, "",
233 wxDefaultPosition
, wxDefaultSize
,
235 wxString h
= wxString::Format(LOG_FORMAT
, "event", "path", "new path");
236 headerText
->SetValue(h
);
239 m_evtConsole
= new wxTextCtrl(this, wxID_ANY
, "",
240 wxDefaultPosition
, wxSize(200,200),
241 wxTE_MULTILINE
|wxTE_READONLY
|wxHSCROLL
);
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
);
249 // ================================================================
250 // laying out whole frame
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
);
258 // set size and position on screen
262 // ================================================================
263 // event handlers & show
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
));
276 Connect(BTN_ID_ADD
, wxEVT_COMMAND_BUTTON_CLICKED
,
277 wxCommandEventHandler(MyFrame::OnAdd
));
278 Connect(BTN_ID_ADD_TREE
, wxEVT_COMMAND_BUTTON_CLICKED
,
279 wxCommandEventHandler(MyFrame::OnAddTree
));
280 Connect(BTN_ID_REMOVE
, wxEVT_COMMAND_BUTTON_CLICKED
,
281 wxCommandEventHandler(MyFrame::OnRemove
));
282 Connect(BTN_ID_REMOVE
, wxEVT_UPDATE_UI
,
283 wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI
));
285 // and show itself (the frames, unlike simple controls, are not shown when
286 // created initially)
295 bool MyFrame::CreateWatcherIfNecessary()
301 Connect(wxEVT_FSWATCHER
,
302 wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent
));
307 void MyFrame::CreateWatcher()
309 wxCHECK_RET(!m_watcher
, "Watcher already initialized");
310 m_watcher
= new wxFileSystemWatcher();
311 m_watcher
->SetOwner(this);
314 // ============================================================================
316 // ============================================================================
318 void MyFrame::OnAbout(wxCommandEvent
& WXUNUSED(event
))
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);
328 void MyFrame::OnWatch(wxCommandEvent
& event
)
330 wxLogDebug("%s start=%d", __WXFUNCTION__
, event
.IsChecked());
332 if (event
.IsChecked())
334 wxCHECK_RET(!m_watcher
, "Watcher already initialized");
339 wxCHECK_RET(m_watcher
, "Watcher not initialized");
340 m_filesList
->DeleteAllItems();
345 void MyFrame::OnFollowLinks(wxCommandEvent
& event
)
347 m_followLinks
= event
.IsChecked();
350 void MyFrame::OnAdd(wxCommandEvent
& WXUNUSED(event
))
352 AddEntry(wxFSWPath_Dir
);
355 void MyFrame::OnAddTree(wxCommandEvent
& WXUNUSED(event
))
357 AddEntry(wxFSWPath_Tree
);
360 void MyFrame::AddEntry(wxFSWPathType type
, wxString filename
)
362 if ( filename
.empty() )
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() )
371 wxCHECK_RET(m_watcher
, "Watcher not initialized");
373 wxLogDebug("Adding %s: '%s'",
375 type
== wxFSWPath_Dir
? "directory" : "directory tree");
380 // This will tell wxFileSystemWatcher whether to dereference symlinks
381 wxFileName fn
= wxFileName::DirName(filename
);
390 ok
= m_watcher
->Add(fn
);
395 ok
= m_watcher
->AddTree(fn
);
401 wxFAIL_MSG( "Unexpected path type." );
406 wxLogError("Error adding '%s' to watched paths", filename
);
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());
416 void MyFrame::OnRemove(wxCommandEvent
& WXUNUSED(event
))
418 wxCHECK_RET(m_watcher
, "Watcher not initialized");
419 long idx
= m_filesList
->GetFirstSelected();
424 wxString path
= m_filesList
->GetItemText(idx
).Mid(6);
426 // This will tell wxFileSystemWatcher whether to dereference symlinks
427 wxFileName fn
= wxFileName::DirName(path
);
433 // TODO we know it is a dir, but it doesn't have to be
434 if (m_filesList
->GetItemText(idx
).StartsWith("Dir: "))
436 ret
= m_watcher
->Remove(fn
);
438 else if (m_filesList
->GetItemText(idx
).StartsWith("Tree: "))
440 ret
= m_watcher
->RemoveTree(fn
);
444 wxFAIL_MSG("Unexpected item in wxListView.");
449 wxLogError("Error removing '%s' from watched paths", path
);
453 m_filesList
->DeleteItem(idx
);
457 void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent
& event
)
459 event
.Enable(m_filesList
->GetFirstSelected() != wxNOT_FOUND
);
462 void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent
& event
)
464 // TODO remove when code is rock-solid
465 wxLogTrace(wxTRACE_FSWATCHER
, "*** %s ***", event
.ToString());
468 int type
= event
.GetChangeType();
469 if ((type
== wxFSW_EVENT_DELETE
) || (type
== wxFSW_EVENT_RENAME
))
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();
475 for (size_t n
= m_filesList
->GetItemCount(); n
> 0; --n
)
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
)))
481 wxFAIL_MSG("Unexpected item in wxListView.");
483 if (path
== eventpath
)
485 if (type
== wxFSW_EVENT_DELETE
)
487 m_filesList
->DeleteItem(n
-1);
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())
498 // Just in case either of these are possible...
499 wxLogTrace(wxTRACE_FSWATCHER
,
500 "Invalid attempt to rename to %s", newname
);
504 m_filesList
->GetItemText(n
-1).StartsWith("Dir: ") ?
506 m_filesList
->SetItemText(n
-1, prefix
+ newname
);
509 // Don't break: a filepath may have been added more than once
515 wxString msg
= wxString::Format(
516 "Your watched path %s has been deleted or renamed\n",
518 m_evtConsole
->AppendText(msg
);
524 static wxString
GetFSWEventChangeTypeName(int changeType
)
528 case wxFSW_EVENT_CREATE
:
530 case wxFSW_EVENT_DELETE
:
532 case wxFSW_EVENT_RENAME
:
534 case wxFSW_EVENT_MODIFY
:
536 case wxFSW_EVENT_ACCESS
:
538 case wxFSW_EVENT_ATTRIB
: // Currently this is wxGTK-only
541 case wxFSW_EVENT_UNMOUNT
: // Currently this is wxGTK-only
544 case wxFSW_EVENT_WARNING
:
546 case wxFSW_EVENT_ERROR
:
550 return "INVALID_TYPE";
553 void MyFrame::LogEvent(const wxFileSystemWatcherEvent
& event
)
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
);