#include "wx/wx.h"
#endif
-#ifndef __WXMSW__
+#ifndef wxHAS_IMAGES_IN_RESOURCES
#include "../sample.xpm"
#endif
#include "wx/fswatcher.h"
#include "wx/listctrl.h"
+#include "wx/cmdline.h"
// Define a new frame type: this is going to be our main frame
class MyFrame : public wxFrame
MyFrame(const wxString& title);
virtual ~MyFrame();
+ // Add an entry of the specified type asking the user for the filename if
+ // the one passed to this function is empty.
+ void AddEntry(wxFSWPathType type, wxString filename = wxString());
+
+ bool CreateWatcherIfNecessary();
+
private:
// file system watcher creation
- void OnEventLoopEnter();
void CreateWatcher();
// event handlers
void OnClear(wxCommandEvent& WXUNUSED(event)) { m_evtConsole->Clear(); }
void OnQuit(wxCommandEvent& WXUNUSED(event)) { Close(true); }
void OnWatch(wxCommandEvent& event);
+ void OnFollowLinks(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void OnAdd(wxCommandEvent& event);
+ void OnAddTree(wxCommandEvent& event);
void OnRemove(wxCommandEvent& event);
+ void OnRemoveUpdateUI(wxUpdateUIEvent& event);
void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
void LogEvent(const wxFileSystemWatcherEvent& event);
wxTextCtrl *m_evtConsole; // events console
wxListView *m_filesList; // list of watched paths
wxFileSystemWatcher* m_watcher; // file system watcher
-
- friend class MyApp;
+ bool m_followLinks; // should symlinks be dereferenced
const static wxString LOG_FORMAT; // how to format events
};
// 'Main program' equivalent: the program execution "starts" here
virtual bool OnInit()
{
+ if ( !wxApp::OnInit() )
+ return false;
+
wxLog::AddTraceMask("EventSource");
wxLog::AddTraceMask(wxTRACE_FSWATCHER);
// create the file system watcher here, because it needs an active loop
virtual void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop))
{
- m_frame->OnEventLoopEnter();
+ if ( m_frame->CreateWatcherIfNecessary() )
+ {
+ if ( !m_dirToWatch.empty() )
+ m_frame->AddEntry(wxFSWPath_Dir, m_dirToWatch);
+ }
+ }
+
+ virtual void OnInitCmdLine(wxCmdLineParser& parser)
+ {
+ wxApp::OnInitCmdLine(parser);
+ parser.AddParam("directory to watch",
+ wxCMD_LINE_VAL_STRING,
+ wxCMD_LINE_PARAM_OPTIONAL);
+ }
+
+ virtual bool OnCmdLineParsed(wxCmdLineParser& parser)
+ {
+ if ( !wxApp::OnCmdLineParsed(parser) )
+ return false;
+
+ if ( parser.GetParamCount() )
+ m_dirToWatch = parser.GetParam();
+
+ return true;
}
private:
MyFrame *m_frame;
+
+ // The directory to watch if specified on the command line.
+ wxString m_dirToWatch;
};
// Create a new application object: this macro will allow wxWidgets to create
// frame constructor
MyFrame::MyFrame(const wxString& title)
: wxFrame(NULL, wxID_ANY, title),
- m_watcher(NULL)
+ m_watcher(NULL), m_followLinks(false)
{
SetIcon(wxICON(sample));
MENU_ID_QUIT = wxID_EXIT,
MENU_ID_CLEAR = wxID_CLEAR,
MENU_ID_WATCH = 101,
+ MENU_ID_DEREFERENCE,
BTN_ID_ADD = 200,
- BTN_ID_REMOVE = 201,
+ BTN_ID_ADD_TREE,
+ BTN_ID_REMOVE
};
// ================================================================
// started by default, because file system watcher is started by default
it->Check(true);
+#if defined(__UNIX__)
+ // Let the user decide whether to dereference symlinks. If he makes the
+ // wrong choice, asserts will occur if the symlink target is also watched
+ it = menuMon->AppendCheckItem(MENU_ID_DEREFERENCE,
+ "&Follow symlinks\tCtrl-F",
+ _("If checked, dereference symlinks")
+ );
+ it->Check(false);
+ Connect(MENU_ID_DEREFERENCE, wxEVT_COMMAND_MENU_SELECTED,
+ wxCommandEventHandler(MyFrame::OnFollowLinks));
+#endif // __UNIX__
+
// the "About" item should be in the help menu
wxMenu *menuHelp = new wxMenu;
- menuHelp->Append(wxID_ABOUT, "&About...\tF1", "Show about dialog");
+ menuHelp->Append(wxID_ABOUT, "&About\tF1", "Show about dialog");
// now append the freshly created menu to the menu bar...
wxMenuBar *menuBar = new wxMenuBar();
// buttons
wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add");
+ wxButton* buttonAddTree = new wxButton(panel, BTN_ID_ADD_TREE, "Add &tree");
wxButton* buttonRemove = new wxButton(panel, BTN_ID_REMOVE, "&Remove");
wxSizer *btnSizer = new wxGridSizer(2);
btnSizer->Add(buttonAdd, wxSizerFlags().Center().Border(wxALL));
+ btnSizer->Add(buttonAddTree, wxSizerFlags().Center().Border(wxALL));
btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL));
// and put it all together
// buttons
Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyFrame::OnAdd));
+ Connect(BTN_ID_ADD_TREE, wxEVT_COMMAND_BUTTON_CLICKED,
+ wxCommandEventHandler(MyFrame::OnAddTree));
Connect(BTN_ID_REMOVE, wxEVT_COMMAND_BUTTON_CLICKED,
wxCommandEventHandler(MyFrame::OnRemove));
+ Connect(BTN_ID_REMOVE, wxEVT_UPDATE_UI,
+ wxUpdateUIEventHandler(MyFrame::OnRemoveUpdateUI));
// and show itself (the frames, unlike simple controls, are not shown when
// created initially)
delete m_watcher;
}
-void MyFrame::OnEventLoopEnter()
+bool MyFrame::CreateWatcherIfNecessary()
{
if (m_watcher)
- return;
+ return false;
CreateWatcher();
Connect(wxEVT_FSWATCHER,
wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent));
+
+ return true;
}
void MyFrame::CreateWatcher()
}
}
+void MyFrame::OnFollowLinks(wxCommandEvent& event)
+{
+ m_followLinks = event.IsChecked();
+}
+
void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
{
+ AddEntry(wxFSWPath_Dir);
+}
+
+void MyFrame::OnAddTree(wxCommandEvent& WXUNUSED(event))
+{
+ AddEntry(wxFSWPath_Tree);
+}
+
+void MyFrame::AddEntry(wxFSWPathType type, wxString filename)
+{
+ if ( filename.empty() )
+ {
+ // TODO account for adding the files as well
+ filename = wxDirSelector("Choose a folder to watch", "",
+ wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
+ if ( filename.empty() )
+ return;
+ }
+
wxCHECK_RET(m_watcher, "Watcher not initialized");
- // TODO account for adding the files as well
- const wxString& dir = wxDirSelector("Choose a folder to watch", "",
- wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
- if ( dir.empty() )
- return;
+ wxLogDebug("Adding %s: '%s'",
+ filename,
+ type == wxFSWPath_Dir ? "directory" : "directory tree");
- wxLogDebug("Adding directory: '%s'", dir);
+ wxString prefix;
+ bool ok = false;
- if (!m_watcher->Add(wxFileName::DirName(dir), wxFSW_EVENT_ALL))
+ // This will tell wxFileSystemWatcher whether to dereference symlinks
+ wxFileName fn = wxFileName::DirName(filename);
+ if (!m_followLinks)
{
- wxLogError("Error adding '%s' to watched paths", dir);
+ fn.DontFollowLink();
}
- else
+
+ switch ( type )
+ {
+ case wxFSWPath_Dir:
+ ok = m_watcher->Add(fn);
+ prefix = "Dir: ";
+ break;
+
+ case wxFSWPath_Tree:
+ ok = m_watcher->AddTree(fn);
+ prefix = "Tree: ";
+ break;
+
+ case wxFSWPath_File:
+ case wxFSWPath_None:
+ wxFAIL_MSG( "Unexpected path type." );
+ }
+
+ if (!ok)
{
- m_filesList->InsertItem(m_filesList->GetItemCount(), dir);
+ wxLogError("Error adding '%s' to watched paths", filename);
+ return;
}
+
+ // Prepend 'prefix' to the filepath, partly for display
+ // but mostly so that OnRemove() can work out the correct way to remove it
+ m_filesList->InsertItem(m_filesList->GetItemCount(),
+ prefix + wxFileName::DirName(filename).GetFullPath());
}
void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
if (idx == -1)
return;
- wxString path = m_filesList->GetItemText(idx);
+ bool ret = false;
+ wxString path = m_filesList->GetItemText(idx).Mid(6);
+
+ // This will tell wxFileSystemWatcher whether to dereference symlinks
+ wxFileName fn = wxFileName::DirName(path);
+ if (!m_followLinks)
+ {
+ fn.DontFollowLink();
+ }
// TODO we know it is a dir, but it doesn't have to be
- if (!m_watcher->Remove(wxFileName::DirName(path)))
+ if (m_filesList->GetItemText(idx).StartsWith("Dir: "))
+ {
+ ret = m_watcher->Remove(fn);
+ }
+ else if (m_filesList->GetItemText(idx).StartsWith("Tree: "))
+ {
+ ret = m_watcher->RemoveTree(fn);
+ }
+ else
+ {
+ wxFAIL_MSG("Unexpected item in wxListView.");
+ }
+
+ if (!ret)
{
wxLogError("Error removing '%s' from watched paths", path);
}
}
}
+void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event)
+{
+ event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND);
+}
+
void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
{
// TODO remove when code is rock-solid
- wxLogDebug(wxTRACE_FSWATCHER, "*** %s ***", event.ToString());
+ wxLogTrace(wxTRACE_FSWATCHER, "*** %s ***", event.ToString());
LogEvent(event);
+
+ int type = event.GetChangeType();
+ if ((type == wxFSW_EVENT_DELETE) || (type == wxFSW_EVENT_RENAME))
+ {
+ // If path is one of our watched dirs, we need to react to this
+ // otherwise there'll be asserts if later we try to remove it
+ wxString eventpath = event.GetPath().GetFullPath();
+ bool found(false);
+ for (size_t n = m_filesList->GetItemCount(); n > 0; --n)
+ {
+ wxString path, foo = m_filesList->GetItemText(n-1);
+ if ((!m_filesList->GetItemText(n-1).StartsWith("Dir: ", &path)) &&
+ (!m_filesList->GetItemText(n-1).StartsWith("Tree: ", &path)))
+ {
+ wxFAIL_MSG("Unexpected item in wxListView.");
+ }
+ if (path == eventpath)
+ {
+ if (type == wxFSW_EVENT_DELETE)
+ {
+ m_filesList->DeleteItem(n-1);
+ }
+ else
+ {
+ // At least in wxGTK, we'll never get here: renaming the top
+ // watched dir gives IN_MOVE_SELF and no new-name info.
+ // However I'll leave the code in case other platforms do
+ wxString newname = event.GetNewPath().GetFullPath();
+ if (newname.empty() ||
+ newname == event.GetPath().GetFullPath())
+ {
+ // Just in case either of these are possible...
+ wxLogTrace(wxTRACE_FSWATCHER,
+ "Invalid attempt to rename to %s", newname);
+ return;
+ }
+ wxString prefix =
+ m_filesList->GetItemText(n-1).StartsWith("Dir: ") ?
+ "Dir: " : "Tree: ";
+ m_filesList->SetItemText(n-1, prefix + newname);
+ }
+ found = true;
+ // Don't break: a filepath may have been added more than once
+ }
+ }
+
+ if (found)
+ {
+ wxString msg = wxString::Format(
+ "Your watched path %s has been deleted or renamed\n",
+ eventpath);
+ m_evtConsole->AppendText(msg);
+ }
+ }
}
return "MODIFY";
case wxFSW_EVENT_ACCESS:
return "ACCESS";
+ case wxFSW_EVENT_ATTRIB: // Currently this is wxGTK-only
+ return "ATTRIBUTE";
+#ifdef wxHAS_INOTIFY
+ case wxFSW_EVENT_UNMOUNT: // Currently this is wxGTK-only
+ return "UNMOUNT";
+#endif
+ case wxFSW_EVENT_WARNING:
+ return "WARNING";
+ case wxFSW_EVENT_ERROR:
+ return "ERROR";
}
return "INVALID_TYPE";