void ModuleBasicsPageEvtHandler::OnListTreeSelChanged(wxTreeEvent& event)
{
   TDEBUG_ENTER("ModuleBasicsPageEvtHandler::OnListTreeSelChanged");

   if (myIgnoreChange)
   {
      myIgnoreChange = false;
      return;
   }

   wxArrayTreeItemIds sel;
   int selCount = static_cast<int>(myParent->GetTreeCtrl()->GetSelections(sel));
   if (selCount == 1)
   {
      wxTreeItemData* thisdata = myParent->GetTreeCtrl()->GetItemData(sel[0]);
      if (thisdata)
      {
         CRepoNode* thisnode = ((ModuleBasicsPageTreeItemData*) thisdata)->m_Node;
        // If it's a module then just the name goes in the module box.
        // If it's a directory or file then we need to traverse the entire 
        // tree back up to the module collecting all the names on the way...
        // unless the CRepoNode class can do that for us when it creates
        // the node in the first place.... that'd be good...
        switch (thisnode->GetType())
        {
        case kNodeModule:
        {
           CRepoNodeModule* modulenode = (CRepoNodeModule*) (thisnode);

           myParent->GetModuleCombo()->SetValue(wxText(modulenode->c_str()));
           myParent->GetModule() = wxAscii(myParent->GetModuleCombo()->GetValue().c_str());
           myParent->UpdateModule();
        }
        break;
        case kNodeDirectory:
        {
           CRepoNodeDirectory* directorynode = (CRepoNodeDirectory*) (thisnode);

           myParent->GetModuleCombo()->SetValue(wxText(directorynode->c_str()));
           myParent->GetModule() = wxAscii(myParent->GetModuleCombo()->GetValue().c_str());
           myParent->UpdateModule();
        }
        break;
        case kNodeFile:
        {
           CRepoNodeFile* filenode = (CRepoNodeFile*) (thisnode);
           
           myParent->GetModuleCombo()->SetValue(wxText(filenode->c_str()));
           myParent->GetModule() = wxAscii(myParent->GetModuleCombo()->GetValue().c_str());
           myParent->UpdateModule();
        }
        break;
        default:
           break;
        }
      }
   }
}
// Size event handler
void ExtSplitterWindow::OnSize(wxSizeEvent& e)
{
   TDEBUG_ENTER("ExtSplitterWindow::OnSize");
   if (m_RightSashPos >= 0)
   {
      SetSashPosition(e.GetSize().GetHeight() - m_RightSashPos);
   }
   e.Skip();
}
void ModuleBasicsPageEvtHandler::OnListTreeRightClick(wxTreeEvent& event)
{
   TDEBUG_ENTER("ModuleBasicsPageEvtHandler::OnListTreeRightClick");
   if (!myParent->GetTreeCtrl()->IsSelected(event.GetItem())
       && GetAsyncKeyState(VK_APPS) >= 0)
   {
      myParent->GetTreeCtrl()->SelectItem(event.GetItem());
   }
}
Exemple #4
0
void BuildIgnoredList(std::vector<std::string> & ignlist, const std::string& path,
                      DWORD *timeStamp, FileChangeParams *fcp)
{
   TDEBUG_ENTER("BuildIgnoredList");
   bool doUpdate = false;

   std::string ignPath(path);
   ignPath = EnsureTrailingDelimiter(ignPath);
   ignPath += ".cvsignore";
   FileChangeParams myFcp;
   if (fcp)
      myFcp = GetFileChangeParams(ignPath);

   // Update default ignored list
   BuildDefaultIgnoredList();
   if (timeStamp)
   {
      if ((*timeStamp != defIgnoreListTimeStamp) || (*timeStamp == 0))
         doUpdate = true;
   }
   else
   {
      doUpdate = true;
   }

   // check directory ignored file
   if (fcp)
   {
      if ((fcp->IsNull()) || (*fcp != myFcp))
      {
         doUpdate = true;
      }
   }
   else
   {
      doUpdate = true;
   }

   // Do we have to update
   if (doUpdate)
   {
      ignlist.clear();
      ignlist = defIgnoredList;

      // TODO: read $CVSROOT/CVSROOT/cvsignore
      
      // Read .cvsignore from current directory
      ReadIgnoredFile(ignPath, ignlist);
      if (fcp)
         *fcp = myFcp;
      if (timeStamp)
         *timeStamp = defIgnoreListTimeStamp;
   }
}
bool RunExternalMerge(std::string fileMine,
                      std::string fileYours,
                      DirectoryGroups& dirGroups,
                      std::string fileOlder)
{
   TDEBUG_ENTER("RunExternalMerge");
   TDEBUG_TRACE("File mine: " << fileMine);
   TDEBUG_TRACE("File yours: " << fileYours);
   bool bResult = true;
   bool again = false;
   std::string command;
   std::string externalApp;
   std::string externalParams;


   // Perform merge, waiting for it to finish 
   if (fileOlder.empty())
   {
       externalParams = dirGroups.GetStringPreference("External Merge2 Params");
       std::map<std::string, std::string> params;
       params["mine"] = fileMine;
       params["yours"] = fileYours;
       externalParams = ReplaceParams(externalParams, params);
   }
   else
       externalParams = dirGroups.GetStringPreference("External Merge3 Params");

   do
   {
      externalApp = GetExternalApplication("Merge",
                                           dirGroups,
                                           again);

      again = false;
      if (externalApp.empty())
      {
         goto Cleanup;
      }
      command = "\"" + externalApp + "\" " + externalParams;
      if (!LaunchCommand(command, true))
      {
         DoMessageDialog(0, wxString(_("Failed to launch external merge application")) 
                         + wxString(wxT("\n"))
                         + wxString(wxText(command)));
         again = true;
      }
   } while (again);

   bResult = true;

Cleanup:
   return bResult;
}
// Split horizontally
bool ExtSplitterWindow::SplitHorizontally(wxWindow *window1,
                                          wxWindow *window2,
                                          int sashPosition)
{
   TDEBUG_ENTER("ExtSplitterWindow::SplitHorizontally");
   bool b = wxSplitterWindow::SplitHorizontally(window1, window2, sashPosition);
   if (sashPosition < 0)
      m_RightSashPos = -sashPosition;
   else
      m_RightSashPos = -1;
   return b;
}
std::string GetExternalApplication(const char* appType,
                                   const DirectoryGroups& dirGroups,
                                   bool forceQuery)
{
   TDEBUG_ENTER("GetExternalApplication");
   std::string regName;
   wxString dialogTitle;
   if (stricmp(appType, "Diff") == 0)
   {
      regName = "External Diff Application";
      dialogTitle = _("Choose external diff application");
   }
   else if (stricmp(appType, "Merge") == 0)
   {
      regName = "External Merge Application";
      dialogTitle = _("Choose external merge application");
   }
   else
   {
      return "";
   }
      
   TDEBUG_TRACE("RegKey: " << regName);
   std::string externalApp = dirGroups.GetStringPreference(regName);
   if (externalApp.empty() || (externalApp == "?"))
       // Module preference not set, use global
       externalApp = GetStringPreference(regName);
   
   TDEBUG_TRACE("App: " << externalApp);

   // Only ask the user once for external diff application
   if (externalApp.empty() && !forceQuery)
      return externalApp;

   // A question mark signals to ask the user
   if (externalApp == "?" || forceQuery)
   {
      do
      {
         if (externalApp == "?")
            externalApp = "";

         externalApp = DoOpenFileDialog(0, dialogTitle, externalApp, 
                                        wxString(_("Executables")) + wxString(wxT(" (*.exe)|*.exe")));
      }
      while (!(externalApp.empty() || FileExists(externalApp.c_str())));
      // Always set global preference
      SetStringPreferenceRootModule(regName, externalApp, "");
   }
   return externalApp;
}
std::string CVSStatus::CVSRepositoryForPath(std::string path)   
{   
    TDEBUG_ENTER("CVSStatus::CVSRepositoryForPath");   
    TDEBUG_TRACE("  path: '" << path << "')");   
    
    // We must be a directory with a CVS dir     
    path = GetDirectoryPart(path);   
    path = EnsureTrailingDelimiter(path);     
    if (!CVSDirectoryHere(path))     
        return "";    
    
    std::string rootFile = path + "CVS/Repository";    
    std::ifstream in(rootFile.c_str(), std::ios::in);     
    if (!in.good())   
        return "";    
    std::string repository;    
    std::getline(in, repository);    
    
    // Apparently, the path in CVS/Repository may be either absolute or relative.    
    // So if it is absolute, we hack it to be relative.   
    TDEBUG_TRACE("  Repository is '" << repository << "')");    
    if (repository[0] == '/' || (repository.length() > 2 && repository[1] == ':'))   
    {     
        CVSRoot cvsroot(CVSRootForPath(path));   
        std::string root = cvsroot.GetDirectory();     
        TDEBUG_TRACE("  Root is '" << root << "')");   
        unsigned int i = 0;    
        // Find out how many leading chars match    
        while (i < root.length() && i < repository.length())    
        {    
            if (root[i] == repository[i]   
                || (root[i] == '\\' && repository[i] == '/')    
                || (root[i] == '/' && repository[i] == '\\'))   
            {   
                i++;     
                continue;   
            }   
            else   
            {   
                break;   
            }   
        }    
        // Also kill final slash     
        if (repository[i] == '/' || repository[i] == '\\')   
            ++i;   
        repository = repository.substr(i);    
    }     
    return repository;   
}   
bool RunExternalDiff(std::string filename1,
                     std::string filename2,
                     DirectoryGroups& dirGroups,
                     std::string filename3)
{
   TDEBUG_ENTER("RunExternalDiff");
   TDEBUG_TRACE("File 1: " << filename1);
   TDEBUG_TRACE("File 2: " << filename2);
   bool bResult = true;
   bool again = false;
   std::string command;
   std::string externalApp;
   std::string externalParams;

   // Perform diff, waiting for it to finish 
   if (filename3.empty())
   {
      externalParams = dirGroups.GetStringPreference("External Diff2 Params");
      std::map<std::string, std::string> params;
      params["1"] = filename1;
      params["2"] = filename2;
      externalParams = ReplaceParams(externalParams, params);
   }
   else
      externalParams = dirGroups.GetStringPreference("External Diff3 Params");

   do
   {
      externalApp = GetExternalApplication("Diff",
                                           dirGroups,
                                           again);
      again = false;
      if (externalApp.empty())
          return false;

      command = "\"" + externalApp + "\" " + externalParams;
      if (!LaunchCommand(command, true))
      {
         DoMessageDialog(0, wxString(_("Failed to launch external diff application"))
                         + wxString(wxT("\n"))
                         + wxString(wxText(command)));
         again = true;
      }
   } while (again);

   return true;
}
Exemple #10
0
// mostly the cvs one...
static bool unmodified(const struct stat & sb, const char* ts)
{
   TDEBUG_ENTER("unmodified");
   struct tm tmbuf;
   char timebuf[30];
   char* cp;
   struct tm *tm_p;
   struct tm local_tm;
   /* We want to use the same timestamp format as is stored in the
   st_mtime.  For unix (and NT I think) this *must* be universal
   time (UT), so that files don't appear to be modified merely
   because the timezone has changed.  For VMS, or hopefully other
   systems where gmtime returns NULL, the modification time is
   stored in local time, and therefore it is not possible tcause
   st_mtime to be out of sync by changing the timezone.  */
   tm_p = gmtime_r(&sb.st_mtime, &tmbuf);
   if (tm_p)
   {
      memcpy (&local_tm, tm_p, sizeof (local_tm));
      cp = asctime_r(&local_tm, timebuf);  /* copy in the modify time */
   }
   else
      cp = ctime_r(&sb.st_mtime, timebuf);

   if (!cp)
      return true;

   cp[24] = 0;

#if defined(_MSC_VER) || defined(__GNUWIN32__)
   /* Work around non-standard asctime() and ctime() in MS VC++ and mingw
   These return '01' instead of ' 1' for the day of the month. */
   if (
      (strlen(ts) > 8) &&
      ((cp[8] == '0' && ts[8] == ' ') ||
      (cp[8] == ' ' && ts[8] == '0'))
   ) {
      cp[8] = ts[8];
   }
#endif
   
   TDEBUG_TRACE("Timestamp: " << ts);
   TDEBUG_TRACE("Filetime : " << cp);
   return strcmp(cp, ts) == 0;
}
Exemple #11
0
unsigned int CAnnotationList::AddAnnotation(const std::string& line)
{
    TDEBUG_ENTER("AddAnnotation");
    TDEBUG_TRACE("Line: " << line);
    annotationList.push_back(new CAnnotation(this, static_cast<unsigned int>(annotationList.size() + 1), line));
    tm& date = annotationList[annotationList.size() - 1]->Date();
    TDEBUG_TRACE("Date: " << date.tm_mday << "-" << date.tm_mon << "-" << date.tm_year << " "
                 << date.tm_hour << ":" << date.tm_min);

   time_t itemDate = mktime(&date);

   if (minDate == 0 || itemDate < minDate)
      minDate = itemDate;
   if (maxDate == 0 || itemDate > maxDate)
      maxDate = itemDate;

   dateMap[itemDate] = 1;
   return static_cast<unsigned int>(annotationList.size());
}
void AnnotateDialog::Populate(CAnnotationList* annotationList) 
{
    TDEBUG_ENTER("AnnotateDialog::Populate");
    myAnnotations = annotationList;
    unsigned int annotateCount = annotationList->AnnotationCount();
    myListCtrl->SetItemCount(annotateCount);

    // Create the line color array
    delete[] myLineColors;
    myLineColors = new wxColour[annotateCount];
    for (unsigned int i = 0; i < annotateCount; i++)
    {
        CAnnotation* item = (*annotationList)[i];
        TDEBUG_TRACE("Item " << i << ": " << asctime(&(item->Date())) << item->Text());
        item->SetData(&myLineColors[i]);
    }

    ApplySort();
}
void ModuleBasicsPageEvtHandler::OnListTreeExpand(wxTreeEvent& event)
{
   TDEBUG_ENTER("ModuleBasicsPageEvtHandler::OnListTreeExpand");
   wxTreeItemId openingitem = event.GetItem();

   long c = 0; // Cookie
   wxTreeItemData* thisdata = myParent->GetTreeCtrl()->GetItemData(openingitem);
   if (thisdata)
   {
      wxTreeItemId childitem = myParent->GetTreeCtrl()->GetFirstChild(openingitem, c);
      wxTreeItemData* childdata = myParent->GetTreeCtrl()->GetItemData(childitem);
      CRepoNode* childnode = ((ModuleBasicsPageTreeItemData*) childdata)->m_Node;
      if (childnode->GetType() == kNodeRubbish)
      {
         myParent->GetTreeCtrl()->DeleteChildren(openingitem);
         myParent->GetTreeCtrl()->SelectItem(openingitem);
         myParent->GoBrowse(openingitem);
      }
   }
}
Exemple #14
0
// Build default ignored list
void BuildDefaultIgnoredList()
{
   TDEBUG_ENTER("BuildDefaultIgnoredList");
   CSHelper csHelper(myCriticalSection, true);
   std::string userCvsIgnoreFile;
   GetHomeDirectory(userCvsIgnoreFile);
   userCvsIgnoreFile = EnsureTrailingDelimiter(userCvsIgnoreFile) + ".cvsignore";
   FileChangeParams myFcp = GetFileChangeParams(userCvsIgnoreFile);

   // Update every dwUpdateIgnoredListInterval seconds
   if (GetTickCount() > defIgnoreListTimeStamp + 1000 * dwUpdateIgnoredListInterval)
   {
      DoUpdateIgnoredList();
      fcpCvsignore = myFcp;
   }
   // Update if .cvsignore in home dir has changed
   else if (fcpCvsignore != myFcp)
   {
      DoUpdateIgnoredList();
      fcpCvsignore = myFcp;
   }
}
bool GetHomeDirectory(std::string& HomeDirectory)
{
   TDEBUG_ENTER("GetHomeDirectory");
   std::string home("");
   bool SuccessValue;

   // We usually recalculate home every time - so ignore the
   // value in the registry.  The user can turn that off though!
   if (!GetBooleanPreference("Always Recalculate Home"))
      home = GetStringPreference("HOME");

   if (home.empty() || !IsDirectory(home.c_str()))
      SuccessValue = GetCalculatedHomeDirectory(home);
   else
      SuccessValue = true;

   TDEBUG_TRACE("Home directory is " << home);
 
   SuccessValue = (SuccessValue && !home.empty() && IsDirectory(home.c_str()));
   if (SuccessValue)
      HomeDirectory = home;

   return SuccessValue;
}
Exemple #16
0
EntnodeData *Entries_SetVisited(const char* path, EntnodeMap& entries, const char* name,
                                const struct stat& finfo, bool isDir, bool isReadOnly,
                                bool isMissing, const std::vector<std::string>* ignlist)
{
   TDEBUG_ENTER("Entries_SetVisited");
   bool isCvs = false;
   std::string lookupName;
   if (isDir)
   {
      TDEBUG_TRACE("Is dir");
      EntnodeDir *adata = new EntnodeDir(path, name);
      ENTNODE anode(adata);
      adata->UnRef();

      lookupName = anode.Data()->GetName();
      EntnodeMap::iterator it = entries.find(lookupName);
      isCvs = it != entries.end();
      if (!isCvs)
         entries[lookupName] = anode;
   }
   else
   {
      TDEBUG_TRACE("Is no dir");
      EntnodeFile *adata = new EntnodeFile(path, name);
      ENTNODE anode(adata);
      adata->UnRef();

      lookupName = anode.Data()->GetName();
      EntnodeMap::iterator it = entries.find(lookupName);
      isCvs = it != entries.end();
      if (!isCvs)
         entries[lookupName] = anode;
   }

   const ENTNODE & theNode = entries[lookupName];
   EntnodeData *data = ((ENTNODE *)&theNode)->Data();
   data->SetVisited(true);
   if (!isCvs)
   {
      data->SetUnknown(true);
      if (ignlist && MatchIgnoredList(name, *ignlist) || 
         finfo.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)
         data->SetIgnored(true);

      // the dir may have some cvs informations in it, despite the fact
      // that it is not referenced by the parent directory, so try
      // to figure it.

      if (!data->IsIgnored())
      {
         std::string cvsFile = path;
         cvsFile = EnsureTrailingDelimiter(cvsFile);
         cvsFile += name;
         cvsFile = EnsureTrailingDelimiter(cvsFile);
         cvsFile += "CVS";
         struct stat sb;
         TDEBUG_TRACE("Before stat");
         if (stat(cvsFile.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode))
         {
            data->SetUnknown(false);
         }
         TDEBUG_TRACE("After stat");
      }
   }

   data->SetReadOnly(isReadOnly);
   data->SetMissing(isMissing);

   if (isDir)
   {
      if (data->IsIgnored())
         data->SetDesc(_("Ignored Folder"));
      else if (data->IsUnknown())
         data->SetDesc(_("Non-CVS Folder"));
      else
         data->SetDesc(_("Folder"));
   }
   else if (!isMissing)
   {
      const char* ts = data->GetTS();

     TDEBUG_TRACE("Timestamp: " << (ts == 0 ? "NULL" : ts));
      // Revision "0" means "added"
      if (ts == 0)
      {
         data->SetUnmodified(true);
      }
      else if (strcmp(data->GetVN(), "0") == 0)
      {
         data->SetAdded(true);
         // Added files are always modified
         data->SetUnmodified(false);
      }
      else
      {
         data->SetUnmodified(unmodified(finfo, ts));
      }

      const char* ts_conflict = data->GetConflict();
      if (ts_conflict == 0)
         data->SetNeedsMerge(false);
      else
         data->SetNeedsMerge(unmodified(finfo,ts_conflict));

      data->SetLocked((finfo.st_mode & S_IWRITE) == 0);
      
      const char* info = 0;
      if (data->IsIgnored())
      {
         data->SetDesc(_("Ignored"));
      }
      else if (data->IsUnknown())
      {
         data->SetDesc(_("Non-CVS File"));
      }
      else if (data->NeedsMerge())
      {
         data->SetDesc(_("Conflict"));
      }
      else if ((info = data->GetOption()) != 0 && strcmp(info, "-kb") == 0)
      {
         data->SetDesc(data->IsUnmodified() ? _("Binary") : _("Mod. Binary"));
      }
      else
      {
         data->SetDesc(data->IsUnmodified() ? _("File") : _("Mod. File"));
      }
   }

   return data;
}
Exemple #17
0
/* Read the entries file into a list, hashing on the file name.

   UPDATE_DIR is the name of the current directory, for use in error
   messages, or NULL if not known (that is, noone has gotten around
   to updating the caller to pass in the information).  */
bool Entries_Open(EntnodeMap& entries,
                  const char* fullpath,
                  FileChangeParams* fcp)
{
   TDEBUG_ENTER("Entries_Open");
   std::string cvsdir(fullpath);
   cvsdir = EnsureTrailingDelimiter(cvsdir);
   cvsdir += "CVS";
   cvsdir = EnsureTrailingDelimiter(cvsdir);

   if (fcp)
   {
      FileChangeParams myFcp = GetFileChangeParams(cvsdir + "Entries");
      if ((!(fcp->IsNull())) && (myFcp == *fcp))
      {
         return true;
      }
      *fcp = myFcp;
   }

   entries.clear();
   unsigned long sizeextra = 0;
   FILE* fpinx = fopen((cvsdir + "Entries.Extra").c_str(), "r");
   if (fpinx)
   {
      FileChangeParams myFcp = GetFileChangeParams(cvsdir + "Entries.Extra");
      sizeextra = myFcp.dwFileSizeLow;
   }
   FILE* fpin = fopen((cvsdir + "Entries").c_str(), "r");
   if (!fpin)
   {
      if (fpinx)
         fclose (fpinx);
      return false;
   }

   // Read contents of CVS/Rename into a set for easy lookup
   std::ifstream cvsRename((cvsdir + "Rename").c_str());
   std::set<std::string> renameEntries;
   while (cvsRename.good())
   {
      std::string file;
      // Each entry is
      //    newname
      //    (blank)
      //    newname
      //    oldname
      // We are only interested in the new name.
      std::getline(cvsRename, file);
      file = ExtractLastPart(file);
      renameEntries.insert(file);
      for (int i = 0; i < 3; ++i)
         std::getline(cvsRename, file);
   }
   EntnodeData* ent;
   char* extrabuf = 0;
   size_t lenreadx;
   if (fpinx && (sizeextra > 0))
   {
      extrabuf = (char*) malloc((sizeextra*2)+10);
      fseek(fpinx, 0, SEEK_SET);
      lenreadx = fread(extrabuf, sizeof(char), (sizeextra*2)+9, fpinx);
      *(extrabuf+lenreadx) = '\0';
      if (!feof(fpinx))
      {
         // could not read the whole file for some reason...
         free(extrabuf);
         extrabuf = 0;
      }
   }

   while ((ent = fgetentent(fpin, extrabuf, fullpath, 0, sizeextra)) != 0)
   {
      ENTNODE newnode(ent);
      ent->UnRef();

      std::string name = newnode.Data()->GetName();
      EntnodeMap::iterator it = entries.find(name);
      if (it != entries.end())
      {
         _ASSERT(false);
         TDEBUG_TRACE("Warning : duplicated entry in the 'CVS/Entries' file in folder " << fullpath);
      }
      std::set<std::string>::iterator renameIter = renameEntries.find(name);
      ent->SetRenamed(renameIter != renameEntries.end());
      entries[name] = newnode;
   }

   fclose (fpin);
   if (fpinx)
      fclose (fpinx);
   if (extrabuf)
      free (extrabuf);

   fpin = fopen((cvsdir + "Entries.log").c_str(), "r");
   if (fpin)
   {
      char cmd;

      while ((ent = fgetentent(fpin, extrabuf, fullpath, &cmd, sizeextra)) != 0)
      {
         ENTNODE newnode(ent);
         ent->UnRef();

         std::string name = newnode.Data()->GetName();

         switch (cmd)
         {
         case 'A':
            entries[name] = newnode;
            break;
         case 'R':
            entries.erase(std::string(name));
            break;
         default:
            /* Ignore unrecognized commands.  */
            TDEBUG_TRACE("Warning: Unrecognized command '" << cmd << "'");
            break;
         }
      }
      fclose (fpin);
   }
   return true;
}
void ModuleBasicsPageEvtHandler::OnListTreeContextMenu(wxContextMenuEvent& event)
{
   TDEBUG_ENTER("ModuleBasicsPageEvtHandler::OnListTreeContextMenu");
}
Exemple #19
0
// Parses a single line from a cvs annotate into its respective parts.  An example of one line is:
// 1.1          (frabcus  05-Jan-01): Building TortoiseCVS on Borland C++ Builder (version 4 or greater)
bool CAnnotation::Parse(const std::string& line)
{
   TDEBUG_ENTER("CAnnotation::Parse");
   TDEBUG_TRACE("line: " << line);
   Clear();

   if (line.empty())        
      return false;

   std::string token;
   std::string::size_type begin = 0;
   std::string::size_type end = 0;

   // revision number
   end = line.find(' ', begin);
   if (end == std::string::npos)
      return false;

   token = line.substr(begin, end);
   std::string::size_type tokenLength = token.length();
   std::string num;
   for (;;)
   {
      end = token.find('.', begin);
      num = token.substr(begin, end - begin);        
      revNum += atoi(num.c_str());        
      begin = end + 1;

      if (end == std::string::npos || end == tokenLength - 1)
         break;
   }

   // author
   begin = line.find('(', tokenLength);
   if (begin == std::string::npos)
      return false;

   ++begin;

   end = line.find(')', begin);
   if (end == std::string::npos)
      return false;

   end = line.rfind(' ', end);
   if (end == std::string::npos)
      return false;

   author = line.substr(begin, end - begin);

   // trim trailing whitespace from author
   author.erase(author.find_last_not_of(" ") + 1); 

   // date
   begin = line.find_first_not_of(' ', end);
   if (begin == std::string::npos)
      return false;

   end = line.find(')', begin);
   if (end == std::string::npos)
      return false;

   token = line.substr(begin, end - begin);
   TDEBUG_TRACE("Date string: " << token);
   if (!datestring_to_tm(token.c_str(), &date))
      return false;
   TDEBUG_TRACE("Datetime: " << asctime(&date));

   // bugnumber
   bugnumber.clear();

   // trim trailing whitespace from bugnumber
   bugnumber.erase(bugnumber.find_last_not_of(" ") + 1); 

   // text
   begin = line.find_first_not_of("):", end);
   if (begin == std::string::npos)
      return false;

   text = line.substr(begin + 1);

   return true;
}
EditorListDialog::EditorListDialog(wxWindow* parent, 
                                   EditorListDialog::EditedFileList& editors)
   : ExtDialog(parent, -1, _("TortoiseCVS - List Editors"),
               wxDefaultPosition, wxDefaultSize,
               wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER | 
               wxDEFAULT_DIALOG_STYLE | wxCLIP_CHILDREN),
     mySortCol(SORT_FILE),
     mySortAscending(true)   
{
   TDEBUG_ENTER("EditorListDialog::EditorListDialog");
   SetIcon(wxIcon(wxT("A_TORTOISE"), wxBITMAP_TYPE_ICO_RESOURCE));

   myEditedFiles = &editors;
   
   std::vector<std::string> tmp;
   EditedFileList::iterator it;

   // Add editors
   it = editors.begin();
   while (it != editors.end())
   {
      std::string s(it->myFilename);
      FindAndReplace<std::string>(s, "/", "\\");
      it->myFilename = s;
      tmp.push_back(s);
      it++;
   }

   // Trim paths to sensible length
   ShortenPaths(tmp, myStub);

   // Maybe use dedicated colour later
   myEditColour = ColorRefToWxColour(GetIntegerPreference("Colour Updated Files"));

   FilenameText* label1 = new FilenameText(this, -1, _("Folder: %s"), wxText(RemoveTrailingDelimiter(myStub)));
   label1->SetWindowStyle(label1->GetWindowStyle() | wxST_NO_AUTORESIZE);
   myEditors = new EditorListListCtrl(this, this, EDITORLISTDLG_ID_EDITORS, myEditColour);
   myEditors->SetSortIndicator(mySortCol, mySortAscending);
   AddEditors(tmp, editors);
   myEditors->SetBestColumnWidth(0);
   myEditors->SetBestColumnWidth(1);
   myEditors->SetBestSize(wxDLG_UNIT(this, wxSize(150, 150)), wxDefaultSize);

   // OK button
   wxBoxSizer* sizerConfirm = new wxBoxSizer(wxHORIZONTAL);
   myOK = new wxButton(this, wxID_OK, _("Close"));
   myOK->SetDefault();
   sizerConfirm->Add(myOK, 0, wxGROW | wxALL, 5);

   // Status bar
   myStatusBar = new wxStatusBar(this, -1);
   myStatusBar->SetStatusText(Printf(_("%d edited file(s)"), editors.size()).c_str());

   // Main box with everything in it
   wxBoxSizer* sizerTop = new wxBoxSizer(wxVERTICAL);
   sizerTop->Add(label1, 0, wxGROW | wxALL, 3);
   sizerTop->Add(myEditors, 2, wxGROW | wxALL, 3);
   sizerTop->Add(sizerConfirm, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 10);
   sizerTop->Add(myStatusBar, 0, wxGROW | wxALL, 0);

   // Overall dialog layout settings
   SetAutoLayout(TRUE);
   SetSizer(sizerTop);
   sizerTop->SetSizeHints(this);
   sizerTop->Fit(this);

   RestoreTortoiseDialogSize(this, "ListEditors");
   SetTortoiseDialogPos(this, GetRemoteHandle());
   RestoreTortoiseDialogState(this, "ListEditors");
}
// Sash pos changing event handler
void ExtSplitterWindow::OnSashPosChanging(wxSplitterEvent& WXUNUSED(event))
{
   TDEBUG_ENTER("ExtSplitterWindow::OnSashPosChanged");
   UpdateRightSashPosition();
}
ConflictListDialog::ConflictListDialog(wxWindow* parent, 
                                       const std::vector<std::string>& files)
   : ExtDialog(parent, -1, _("TortoiseCVS - Resolve Conflicts"),
               wxDefaultPosition, wxDefaultSize,
               wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxRESIZE_BORDER | 
               wxDEFAULT_DIALOG_STYLE | wxCLIP_CHILDREN)

{
   TDEBUG_ENTER("ConflictListDialog::ConflictListDialog");
   SetIcon(wxIcon(wxT("A_TORTOISE"), wxBITMAP_TYPE_ICO_RESOURCE));

   std::vector<ItemData*> itemData;
   std::vector<std::string> tmp;
   std::vector<std::string>::const_iterator it;

   // Add files
   it = files.begin();
   while (it != files.end())
   {
      ItemData *data = new ItemData();
      data->m_Filename = *it;
      data->m_Status = CVSStatus::STATUS_CONFLICT;
      itemData.push_back(data);
      tmp.push_back(*it);
      it++;
   }


   // Trim paths to sensible length
   ShortenPaths(tmp, myStub);

   wxStaticText* label2 = new ExtStaticText(this, -1, _(
"CVS encountered conflicts when trying to merge your changes in the files below. Please merge your changes manually."),
                                            wxDefaultPosition, 
                                            wxDLG_UNIT(this, wxSize(60, 15)));
   
   FilenameText* label1 = new FilenameText(this, -1, _("Folder: %s"), wxText(RemoveTrailingDelimiter(myStub)));
   label1->SetWindowStyle(label1->GetWindowStyle() | wxST_NO_AUTORESIZE);
   myFiles = new ExtListCtrl(this, CONFLICTLISTDLG_ID_FILES, wxDefaultPosition, wxDefaultSize, 
                             wxLC_REPORT | wxLC_ALIGN_LEFT);
   myFiles->PushEventHandler(new ListCtrlEventHandler(this));
   myFiles->InsertColumn(0, _("Filename"), wxLIST_FORMAT_LEFT, 0);
   myFiles->InsertColumn(1, _("Format"), wxLIST_FORMAT_LEFT, 0);
   myFiles->InsertColumn(2, _("Status"), wxLIST_FORMAT_LEFT, 0);
   AddFiles(tmp, itemData);
   myFiles->SetBestColumnWidth(0);
   myFiles->SetBestColumnWidth(1);
   myFiles->SetBestColumnWidth(2);
   myFiles->SetBestSize(wxDLG_UNIT(this, wxSize(150, 150)), wxDefaultSize);

   wxStaticText* tip = new wxStaticText(this, -1,
                                        _("To resolve the conflicts, double or right click on the files above."));
   tip->SetForegroundColour(SetForegroundColour(ColorRefToWxColour(GetIntegerPreference("Colour Tip Text"))));

   // OK/Cancel button
   wxBoxSizer* sizerConfirm = new wxBoxSizer(wxHORIZONTAL);
   myOK = new wxButton(this, wxID_OK, _("Close"));
   myOK->SetDefault();
   sizerConfirm->Add(myOK, 0, wxGROW | wxALL, 5);

   // Status bar
   myStatusBar = new wxStatusBar(this, -1);
   myStatusBar->SetStatusText(Printf(_("%d file(s)"), files.size()).c_str());

   // Main box with everything in it
   wxBoxSizer* sizerTop = new wxBoxSizer(wxVERTICAL);
   sizerTop->Add(label2, 0, wxGROW | wxALL, 3);
   sizerTop->Add(label1, 0, wxGROW | wxALL, 3);
   sizerTop->Add(myFiles, 2, wxGROW | wxALL, 3);
   sizerTop->Add(tip, 0, wxALIGN_LEFT | wxALL, 3);
   sizerTop->Add(sizerConfirm, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 10);
   sizerTop->Add(myStatusBar, 0, wxGROW | wxALL, 0);

   // Overall dialog layout settings
   SetAutoLayout(TRUE);
   SetSizer(sizerTop);
   sizerTop->SetSizeHints(this);
   sizerTop->Fit(this);

   RestoreTortoiseDialogSize(this, "Conflict");
   SetTortoiseDialogPos(this, GetRemoteHandle());
   RestoreTortoiseDialogState(this, "Conflict");
}
// Get the annotation list
CAnnotationList* AnnotateDialog::GetAnnotationList(wxWindow* parent)
{
    TDEBUG_ENTER("GetAnnotationList");
    wxBusyCursor();

   // We do not want any progress dialog here
   CVSAction glue(parent);
   glue.SetCloseIfOK(true);
   glue.SetHideStdout();
   wxString title = Printf(_("Annotate %s"), wxText(myFilename).c_str());
   glue.SetProgressCaption(title);

   MakeArgs args;
   args.add_option("annotate");
   // Avoid truncating user names
   args.add_option("-w");
   args.add_option("30");       // Ought to be enough
   // If revision is given, specify it
   if (!myRevision.empty())
   {
      args.add_option("-r");
      args.add_option(myRevision);
   }
   // If we are working on a branch, specify the branch using -r
   else if (CVSStatus::HasStickyTag(myFilename))
   {
      args.add_option("-r");
      args.add_option(CVSStatus::GetStickyTag(myFilename));
   }
   args.add_arg(ExtractLastPart(myFilename));
   bool ok = glue.Command(StripLastPart(myFilename), args);
   if (!ok)
   {
      return 0;
   }
   
   std::string out = glue.GetOutputText();
#if 0
   // Debugging
   std::ifstream in("C:\\cvs-log-output.txt");
   out.clear();
   while (true)
   {
      std::string line;
      std::getline(in, line);
      if (line.empty() && in.eof())
         break;
      out += line;
      out += "\n";
   }
   in.close();
#endif
   FindAndReplace<std::string>(out, "\r\n", "\n");

   if (out.empty())
   {
      wxString s(_("This file is new and has never been committed to the server or is an empty file on the server."));
      s += wxT("\n\n");
      s += wxText(myFilename);
      DoMessageDialog(0, s);
      return 0;
   }

   CAnnotationList* annotationList = new CAnnotationList();

   std::stringstream ifs(out.c_str());

   // Parsing the annotations
   while (true)
   {
      std::string line;
      if (!std::getline(ifs, line) && line.empty())
         break;

      // We need to skip any text which comes before the actual annotations.  A valid
      // annotation starts with the revision number, so we do a simple check to see if
      // the first character is a digit.  This may need to be revised if this assumption
      // is not valid.
      if (!isdigit(line[0]))
         continue;

      annotationList->AddAnnotation(line);
   }
   return annotationList;
}
AnnotateDialog::AnnotateDialog(wxWindow* parent, const std::string& filename, const std::string& rev)
   : ExtDialog(parent, -1, wxString(_("TortoiseCVS - Annotate")),
               wxDefaultPosition, wxSize(500,400),
               wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX),
     myFilename(filename),
     mySortCol(ANNOTATE_COL_LINE),
     mySortAscending(true),
     myLineColors(0),
     myHighlightType(HIGHLIGHT_BY_NONE),
     myRevision(rev),
     myGotLogMessages(false),
     myShowLogMessages(false),
     myFindDialog(0),
     myFoundLineIndex(std::numeric_limits<unsigned int>::max())
{
    TDEBUG_ENTER("AnnotateDialog");
   SetIcon(wxIcon(wxT("A_TORTOISE"), wxBITMAP_TYPE_ICO_RESOURCE));

   wxString sTitle = GetTitle();
   sTitle += wxT(" ");
   sTitle += wxText(ExtractLastPart(filename));
   if (!rev.empty())
   {
      sTitle += wxT(" (");
      sTitle += wxText(rev);
      sTitle += wxT(")");
   }
   SetTitle(sTitle);

   myAnnotateCanvas = new AnnotateCanvas(this, -1, wxDefaultPosition, wxSize(10,10));
   // List control    
   myListCtrl = new AnnotateListCtrl(this, ANNOTATEDLG_ID_LISTCTRL, myAnnotateCanvas);

   int idx = 0;
   myListCtrl->InsertColumn(idx++, _("Line"),      wxLIST_FORMAT_LEFT);
   myListCtrl->InsertColumn(idx++, _("Revision"),  wxLIST_FORMAT_LEFT);
   myListCtrl->InsertColumn(idx++, _("Author"),    wxLIST_FORMAT_LEFT);
   myListCtrl->InsertColumn(idx++, _("Date"),      wxLIST_FORMAT_LEFT);
#if 0
   myListCtrl->InsertColumn(idx++, _("Bug Number"),wxLIST_FORMAT_LEFT);
#endif
   myListCtrl->InsertColumn(idx++, _("Text"),      wxLIST_FORMAT_LEFT);

   myFixedFont = wxSystemSettings::GetFont(wxSYS_ANSI_FIXED_FONT); 

   wxBoxSizer *sizerAnnotate = new wxBoxSizer(wxHORIZONTAL);
   sizerAnnotate->Add(myListCtrl, 3, wxGROW | wxALL, 3);

   wxBoxSizer* sizerCanvas = new wxBoxSizer(wxVERTICAL);
   sizerCanvas->Add(myAnnotateCanvas, 1, wxGROW | wxTOP | wxBOTTOM, 21);
   sizerCanvas->Add(10, 19);

   sizerAnnotate->Add(sizerCanvas, 0, wxGROW);

   wxBoxSizer *sizer1 = new wxBoxSizer(wxVERTICAL);
   sizer1->Add(sizerAnnotate, 3, wxGROW | wxLEFT | wxUP | wxDOWN, 3);

   // OK button
   wxBoxSizer *sizerConfirm = new wxBoxSizer(wxHORIZONTAL);
   wxButton* buttonOK = new wxButton(this, wxID_OK, _("OK"));
   buttonOK->SetDefault();
   sizerConfirm->Add(buttonOK, 0, wxALL, 5);
   wxButton* buttonSearch = new wxButton(this, wxID_FIND, _("&Find"));
   sizerConfirm->Add(buttonSearch, 0, wxALL, 5);
   SetDefaultItem(buttonOK);

   // Hidden Cancel button (only for making Esc close the dialog)
   wxButton* cancel = new wxButton(this, wxID_CANCEL, wxT(""));
   cancel->Hide();

   // Status bar
   myStatusBar = new wxStatusBar(this, -1);

   // Main box with everything in it
   wxBoxSizer *sizerTop = new wxBoxSizer(wxVERTICAL);
   sizerTop->Add(sizer1, 2, wxGROW | wxALL, 3);
   sizerTop->Add(sizerConfirm, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT, 10);
   sizerTop->Add(myStatusBar, 0, wxGROW | wxALL, 0);

   // Overall dialog layout settings
   SetAutoLayout(TRUE);
   SetSizer(sizerTop);
   sizerTop->SetSizeHints(this);
   sizerTop->Fit(this);

   RestoreTortoiseDialogSize(this, "Annotate", wxSize(500, 400));
   SetTortoiseDialogPos(this, GetRemoteHandle());
   RestoreTortoiseDialogState(this, "Annotate");

   GetRegistryData();
   UpdateStatusBar();

   // Get column widths from registry (if not outdated)
   unsigned int numSavedColumns = static_cast<unsigned int>(myColumnWidths.size());
   if (numSavedColumns == NUM_ANNOTATE_COL)
   {
       for (unsigned int i = 0; i < NUM_ANNOTATE_COL; ++i)
       {
           int colWidth = (i < numSavedColumns) ? myColumnWidths[i] : DEF_COLUMN_SIZE[i];
           if (colWidth <= 0 || colWidth > MAX_COLUMN_SIZE)
               colWidth = DEF_COLUMN_SIZE[i];
           
           myListCtrl->SetColumnWidth(i, wxDLG_UNIT_X(this, colWidth));
       }
   }
}
void ConflictListDialog::OnDblClick(wxListEvent& event)
{
   TDEBUG_ENTER("ConflictListDialog::OnDblClick");
   std::string file = ((ConflictListDialog::ItemData*) event.GetData())->m_Filename;
   LaunchTortoiseAct(false, CvsMergeConflictsVerb, file, "", GetHwnd()); 
}