]> git.saurik.com Git - wxWidgets.git/blobdiff - samples/fswatcher/fswatcher.cpp
Remove wxRTTI macro for wxComboBox from Motif port.
[wxWidgets.git] / samples / fswatcher / fswatcher.cpp
index 907f46ceaf31ae7b8388a9e078e361d9958c6c42..890fd45ff5003b322ae5277e42eb9d83b4a0a6e8 100644 (file)
     #include "wx/wx.h"
 #endif
 
     #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 "../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
 
 // Define a new frame type: this is going to be our main frame
 class MyFrame : public wxFrame
@@ -31,19 +32,27 @@ public:
     MyFrame(const wxString& title);
     virtual ~MyFrame();
 
     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
 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 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 OnAbout(wxCommandEvent& event);
 
     void OnAdd(wxCommandEvent& event);
+    void OnAddTree(wxCommandEvent& event);
     void OnRemove(wxCommandEvent& event);
     void OnRemove(wxCommandEvent& event);
+    void OnRemoveUpdateUI(wxUpdateUIEvent& event);
 
     void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
     void LogEvent(const wxFileSystemWatcherEvent& event);
 
     void OnFileSystemEvent(wxFileSystemWatcherEvent& event);
     void LogEvent(const wxFileSystemWatcherEvent& event);
@@ -51,8 +60,7 @@ private:
     wxTextCtrl *m_evtConsole;         // events console
     wxListView *m_filesList;          // list of watched paths
     wxFileSystemWatcher* m_watcher;   // file system watcher
     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
 };
 
     const static wxString LOG_FORMAT; // how to format events
 };
@@ -66,6 +74,9 @@ public:
     // 'Main program' equivalent: the program execution "starts" here
     virtual bool OnInit()
     {
     // 'Main program' equivalent: the program execution "starts" here
     virtual bool OnInit()
     {
+        if ( !wxApp::OnInit() )
+            return false;
+
         wxLog::AddTraceMask("EventSource");
         wxLog::AddTraceMask(wxTRACE_FSWATCHER);
 
         wxLog::AddTraceMask("EventSource");
         wxLog::AddTraceMask(wxTRACE_FSWATCHER);
 
@@ -79,11 +90,37 @@ public:
     // create the file system watcher here, because it needs an active loop
     virtual void OnEventLoopEnter(wxEventLoopBase* WXUNUSED(loop))
     {
     // 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;
     }
 
 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
 };
 
 // Create a new application object: this macro will allow wxWidgets to create
@@ -101,7 +138,7 @@ IMPLEMENT_APP(MyApp)
 // frame constructor
 MyFrame::MyFrame(const wxString& title)
     : wxFrame(NULL, wxID_ANY, title),
 // frame constructor
 MyFrame::MyFrame(const wxString& title)
     : wxFrame(NULL, wxID_ANY, title),
-      m_watcher(NULL)
+      m_watcher(NULL), m_followLinks(false)
 {
     SetIcon(wxICON(sample));
 
 {
     SetIcon(wxICON(sample));
 
@@ -111,9 +148,11 @@ MyFrame::MyFrame(const wxString& title)
         MENU_ID_QUIT = wxID_EXIT,
         MENU_ID_CLEAR = wxID_CLEAR,
         MENU_ID_WATCH = 101,
         MENU_ID_QUIT = wxID_EXIT,
         MENU_ID_CLEAR = wxID_CLEAR,
         MENU_ID_WATCH = 101,
+        MENU_ID_DEREFERENCE,
 
         BTN_ID_ADD = 200,
 
         BTN_ID_ADD = 200,
-        BTN_ID_REMOVE = 201,
+        BTN_ID_ADD_TREE,
+        BTN_ID_REMOVE
     };
 
     // ================================================================
     };
 
     // ================================================================
@@ -131,9 +170,21 @@ MyFrame::MyFrame(const wxString& title)
     // started by default, because file system watcher is started by default
     it->Check(true);
 
     // 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;
     // 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();
 
     // now append the freshly created menu to the menu bar...
     wxMenuBar *menuBar = new wxMenuBar();
@@ -163,9 +214,11 @@ MyFrame::MyFrame(const wxString& title)
 
     // buttons
     wxButton* buttonAdd = new wxButton(panel, BTN_ID_ADD, "&Add");
 
     // 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));
     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
     btnSizer->Add(buttonRemove, wxSizerFlags().Center().Border(wxALL));
 
     // and put it all together
@@ -222,8 +275,12 @@ MyFrame::MyFrame(const wxString& title)
     // buttons
     Connect(BTN_ID_ADD, wxEVT_COMMAND_BUTTON_CLICKED,
             wxCommandEventHandler(MyFrame::OnAdd));
     // 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_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)
 
     // and show itself (the frames, unlike simple controls, are not shown when
     // created initially)
@@ -235,14 +292,16 @@ MyFrame::~MyFrame()
     delete m_watcher;
 }
 
     delete m_watcher;
 }
 
-void MyFrame::OnEventLoopEnter()
+bool MyFrame::CreateWatcherIfNecessary()
 {
     if (m_watcher)
 {
     if (m_watcher)
-        return;
+        return false;
 
     CreateWatcher();
     Connect(wxEVT_FSWATCHER,
             wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent));
 
     CreateWatcher();
     Connect(wxEVT_FSWATCHER,
             wxFileSystemWatcherEventHandler(MyFrame::OnFileSystemEvent));
+
+    return true;
 }
 
 void MyFrame::CreateWatcher()
 }
 
 void MyFrame::CreateWatcher()
@@ -283,26 +342,75 @@ void MyFrame::OnWatch(wxCommandEvent& event)
     }
 }
 
     }
 }
 
+void MyFrame::OnFollowLinks(wxCommandEvent& event)
+{
+    m_followLinks = event.IsChecked();
+}
+
 void MyFrame::OnAdd(wxCommandEvent& WXUNUSED(event))
 {
 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");
 
     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))
 }
 
 void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
@@ -312,10 +420,31 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
     if (idx == -1)
         return;
 
     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
 
     // 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);
     }
     {
         wxLogError("Error removing '%s' from watched paths", path);
     }
@@ -325,11 +454,70 @@ void MyFrame::OnRemove(wxCommandEvent& WXUNUSED(event))
     }
 }
 
     }
 }
 
+void MyFrame::OnRemoveUpdateUI(wxUpdateUIEvent& event)
+{
+    event.Enable(m_filesList->GetFirstSelected() != wxNOT_FOUND);
+}
+
 void MyFrame::OnFileSystemEvent(wxFileSystemWatcherEvent& event)
 {
     // TODO remove when code is rock-solid
 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);
     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);
+        }
+    }
 }
 
 
 }
 
 
@@ -347,6 +535,16 @@ static wxString GetFSWEventChangeTypeName(int changeType)
         return "MODIFY";
     case wxFSW_EVENT_ACCESS:
         return "ACCESS";
         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";
     }
 
     return "INVALID_TYPE";