//$Id: XDirComp.cpp,v 1.65 2007/03/22 19:26:13 markus Rel $

//PROJECT     : XDirComp
//SUBSYSTEM   : XDirComp
//REFERENCES  :
//TODO        :
//BUGS        :
//REVISION    : $Revision: 1.65 $
//AUTHOR      : Markus Schwab
//CREATED     : 27.8.1999
//COPYRIGHT   : Copyright (C) 1999 - 2007

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.


// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#include <cerrno>
#include <unistd.h>
#include <limits.h>

#include <sys/stat.h>

#include <fstream>
#include <iomanip>

#define CONVERT_TO_UTF8
#include <dircomp-cfg.h>

#ifdef ENABLE_THREADS
#  include <glib.h>
#endif

#include <gtkmm/stock.h>
#include <gtkmm/toggleaction.h>
#include <gtkmm/messagedialog.h>

#include <YGP/Check.h>
#include <YGP/Trace.h>

#include <YGP/File.h>
#include <YGP/SmartPtr.h>
#include <YGP/ANumeric.h>
#include <YGP/PathSrch.h>
#include <YGP/RDirSrch.h>
#include <YGP/PathDirSrch.h>

#include <XGP/XDate.h>
#include <XGP/XAbout.h>
#include <XGP/XFileDlg.h>
#include <XGP/XPrintDlg.h>

#include "XFileSel.h"
#include "XDirComp.h"


static const int DEFAULTPORT = (31336U);


// Pixmap for program
const char* XDirComp::xpmXDirComp[] = {
   "56 46 38 1",
   " 	c None",
   ".	c #FFFFFF",
   "+	c #D7D3D7",
   "@	c #000000",
   "#	c #E7E7EF",
   "$	c #515549",
   "%	c #969696",
   "&	c #AEAAAE",
   "*	c #AEA6AE",
   "=	c #AEAEAE",
   "-	c #A69EA6",
   ";	c #B6AEB6",
   ">	c #616161",
   ",	c #A6A2A6",
   "'	c #AEAEB6",
   ")	c #717571",
   "!	c #BEB6BE",
   "~	c #CFC7CF",
   "{	c #303430",
   "]	c #000808",
   "^	c #696971",
   "/	c #EFF3F7",
   "(	c #202020",
   "_	c #182020",
   ":	c #080408",
   "<	c #8E8A8E",
   "[	c #BEC3BE",
   "}	c #9E9A9E",
   "|	c #D7DFE7",
   "1	c #79869E",
   "2	c #AE9E59",
   "3	c #282C30",
   "4	c #BEBABE",
   "5	c #AE6161",
   "6	c #51A249",
   "7	c #E7D79E",
   "8	c #BEC3CF",
   "9	c #8E8679",
   "......................................................+@",
   "..+++++++++++++++++++++++++++++++++++++++++++++++++++#$@",
   "..%&*=%**%**%**%**%**%**%**%**%**%**%**%**%**%**%**=-;>@",
   "..,**&&=&&=&'*=&=&'*=&=&'*=&=&'*=&=&'*=&=&'*=&=&'=*==')@",
   "..-==&&&&&&&&=&&&&&=&&&&&=&&&&&=&&&&&=&&&&&=&&&&&&=&=!)@",
   "..-&&&&&&&&&&&&&&&&&&----------&=!~!~~~!;&&&&&&&&&&&=')@",
   "..,&&&&&&&&&&&&&&&&~............#)@{@]@$=&&&&&&&&&&&&!^@",
   "..&&&&&&&&&&&&&&=!'/............#,(@@@@('!&&&&&&&&&&=!)@",
   "..-&&&;&;&;&&;&!~=$)^$>$$........~{({]@@)&&&&;&&;&&&&;)@",
   "..-&&&&&&&&&&'!=@@@{{{{{{_@@.....+@@@@@@$!'!&&&&&&&&==)@",
   "..,&&&!*=&&..+_@$$$$))_@@($$@{+...~(]@:@{$!=&&;&&&&&&!)@",
   "..&&&=*=&&=.<@@$$==+#[<$=~=&=@@=..!@@@@@@@~;&&&&&;&==;)@",
   "..-&&&=&&;!$_$$#/...'$$$&=...+}@).;]@:@:@@)!==&&&&&&=!^@",
   "..-&&&;&=~@{$>......|)$$}}.....+@1/$@@@@@@$=;;&&&&&==;)@",
   "..,&&&&&!)_${.......#=$_(@!;....~@(@@:@:@@@$};==&&&&=!)@",
   "..&&&&=;~_{$~.....=<)@({@@$$<<}..)@@@@:@]@@_>1!&&&&==;)@",
   "..-&&&&!)@{{...#='}$@@{$@@$$$$$$}&@@:@@:@@@@@(;!=&&&&;)@",
   "..-&&&=;{{$)..#,=;)@@@@@@@$$$$$$$$(:@@:@@:]@@]>;&&&===)@",
   "..,&&&=~{{$..}===!^:@:((}&!~{{${$${@@:@@:@@@@@_};=&&=!)@",
   "..&&&='!{{$/^{;=-;}$(@{$#...#.#+$$(@:@@:@@:]@@@$!!&==;)@",
   "..-&&&&[{{$$$$[=+.~$$>]]+.......+$_:@$:@@:@@]@@]1====!^@",
   "..-&&&'!{{$((@<!~++<>}{@$)^)*,+~[)(@@{@@:@@:@]@@)!!==!)@",
   "..,=&&&;)@$@@@$&=*=*=~$@@@@@$$'!')@@:@:@@:@@:@@@@);==;$@",
   "..&&&&==;{{>$);..,*&,)$$$$$$@@$^/(@@@:@@:@$@@)#%@$!===)@",
   "..-&&&&=!)_$$~+.#=!+<^}=&=)<^)==}@@:@@:@(~$$)!..$$;==!)@",
   "..-=&&=!!;$@1...#$2~+~....~=.../@{@@:@@:@);....#$$!==;)@",
   "..-=&&====[@{$$...=]@@@@@@}}.#+_@((@@:@@&.#....+$$===!^@",
   "..-&&=;===';@@3+..+${@{{$$~+.@@{@)):@(&~..!~.+~,$$!==!)@",
   "..,=&&&&&;&.#{@@'...~{%!....$@@,<3{@@:@,....#=.~@$;==;)@",
   "..&&;&&&&&,..$$]@@@$=..}$@@@@$@@^}(:@{}.;.=...+(]$;==;)@",
   "..-&&&&&&&&+#.$$($@]@@@@@@$]{{(@:$;$).......~#$@@>'==!^]",
   "..-&&&&&&&=&}+!$$$$${${(($@$<});@@,#&<#+...+.=@(@$[===)@",
   "..,=&&;&&&-#;')#$$$$<;)$+.....>)$((,#[)#!.;.,@{@@>'==!)@",
   "..-&&&&=;}.+42%/~{$$3)<%<1<<({{$$>{]^/~)1=.)_@$@@$!&==)@",
   "..-=&&&!}<1<)@@@@@@@@@]@@@@@@@]@({$$@$+#)^$@@@(@@>'=&!)@",
   "..=*=!!-((@@@@@@@@]@]@]@@@@@$@:@@@@@@]$~.=@@@]@@])!==;)@",
   "..-=!%{@@@@@@]@:@:@@@@@]@:@@$$@@@@@@@@@_}.~$:@:@@$!==!)@",
   "..-;}(@@@@@@]@@]@@:@:@:@@@:@:@:@:]@]@@@@@).+)@@@@$;==!^@",
   "..};}^))51))^)6))))1)^51))))6))51)^))${{{@)~~+$<)}!==;)@",
   "..==;;![;!;;!!;!4;!=;!!!;;;;!'4!=!;!;!)$$$@)#{{=!;====)@",
   "..-===&===============================!)$$$@]@{!=====!)@",
   "..-*=&&&&&====&&*==&==========&========;}$$${)=;=&===;)@",
   "..-=&=&&&&=!=*&&;===&=*==!=*=;&&=&&&&===!=$$$$)!===&=!^@",
   "..=!;!;;;!;;;;!;;;!!;!;!;;!;!!;!!;!;!;!!'!7'887=;!;!=+)@",
   "..)<1<<1<191<191<191<<<19191<191<191<191<1112111<191<1^]",
   "#{@@@@@]@@]@]@]@@@@@@@@@@@@@@@@@@@@@@@@@@@]@]@]]@@@@@::@" };

// Pixmap for author
const char* XDirComp::xpmAuthor[] = {
   "56 43 5 1",
   " 	c None",
   "!   c #0000FF",
   "@	c #000000",
   "-	c #AEAAAE",
   ".	c #FFFFFF",
   ".......................................................@",
   "..-----------------------------------------------------@",
   "..-----------------------------------------------------@",
   "..----------------------!!!!!!-------------------------@",
   "..--------------------!!!!@@!!!!-----------------------@",
   "..------------------!!!!@@---@@!!!---------------------@",
   "..-----------------!!!@@--------!!!--------------------@",
   "..-----------------!!!@---!!----!!!--------------------@",
   "..----------------!!!@---!!!!@---!!!-------------------@",
   "..----------------!!@---!!!!!!@---!!@------------------@",
   "..----------------!!@----!!!!@----!!@------------------@",
   "..----------------!!!-----!!@----!!!@------------------@",
   "..-----------------!!!-----@-----!!!@------------------@",
   "..-----------------!!!!---------!!!@-------------------@",
   "..------------------!!!!-------!!!@--------------------@",
   "..--------------------!!!!--!!!!@@---------------------@",
   "..----------------------!!!!!!@@-----------------------@",
   "..-----------------------!!!!@-------------------------@",
   "..------------------------!!@--------------------------@",
   "..------------------------!!@--------------------------@",
   "..------------------!!!!!!!!!!!!!!---------------------@",
   "..-----------------!!!!!!!!!!!!!!!!--------------------@",
   "..----------------!!!@@@@@!!@@@@@!!!-------------------@",
   "..----------------!!@-----!!@-----!!@------------------@",
   "..----------------!!@-----!!@-----!!@------------------@",
   "..----------------!!@-----!!@-----!!@------------------@",
   "..----------------!!@-----!!@-----!!@------------------@",
   "..-----------------@@-----!!@------@@------------------@",
   "..------------------------!!@--------------------------@",
   "..--------------------!!!!!!!!!!-----------------------@",
   "..------------------!!!!!!!!!!!!!!---------------------@",
   "..-----------------!!!@@@@@@@@@!!!!--------------------@",
   "..-----------------!!@----------!!!!-------------------@",
   "..----------------!!!@-----------!!!@------------------@",
   "..----------------!!@------------!!!@------------------@",
   "..----------------!!@-------------!!@------------------@",
   "..----------------!!@-------------!!@------------------@",
   "..----------------!@--------------!!@------------------@",
   "..----------------!!@--------------@@------------------@",
   "..-----------------!@----------------------------------@",
   "..------------------@----------------------------------@",
   "..-----------------------------------------------------@",
   "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" };


//-----------------------------------------------------------------------------
/// Defaultconstructor; all widget are created
//-----------------------------------------------------------------------------
XDirComp::XDirComp ()
   : XApplication ("X" PACKAGE " V" PRG_RELEASE)
     , tblInput (2, 2), inDirComp (YGP::DirectorySearch::FILE_DIRECTORY)
     , inDirOrig (YGP::DirectorySearch::FILE_DIRECTORY)
     , lblDirOrig (Glib::locale_to_utf8 (_("Original directory:")))
     , lblDirComp (Glib::locale_to_utf8 (_("Comparative directory:")))
     , listFiles () , status (), scroll ()
#ifdef ENABLE_THREADS
     , dc (listFiles, status, (YGP::Thread*)(childSearch))
     , childSearch (NULL)
#else
     , dc (listFiles, status)
#endif
     , dateStart (time_t (0)) , dateEnd (time_t (INT_MAX)) {
    TRACE1 ("XDirComp::XDirComp ()");
   setIconProgram (xpmXDirComp);
   set_default_size (620, 400);

   Glib::ustring ui ("<ui><menubar name='Menu'>"
		     "  <menu action='Dir'>"
		     "    <menuitem action='Start'/>"
		     "    <menuitem action='EndComp'/>"
		     "    <separator/>"
		     "    <menuitem action='Save'/>"
		     "    <menuitem action='Print'/>"
		     "    <separator/>"
		     "    <menuitem action='Quit'/>"
		     "  </menu>"
		     "  <menu action='Options'>"
		     "    <menuitem action='ShowNew'/>"
		     "    <menuitem action='ShowChg'/>"
		     "    <menuitem action='ShowEqual'/>"
		     "    <menuitem action='ShowChgDirs'/>"
		     "    <menuitem action='ShowHidden'/>"
		     "    <separator/>"
		     "    <menuitem action='SrchDirs'/>"
		     "    <menuitem action='SrchEqualDirs'/>"
		     "    <separator/>"
		     "    <menuitem action='Comp'/>"
		     "    <menuitem action='CompAll'/>"
		     "    <separator/>"
		     "    <menuitem action='SelFiles'/>"
		     "    <menuitem action='SelDirs'/>"
		     "    <separator/>"
		     "    <menuitem action='StartDate'/>"
		     "    <menuitem action='EndDate'/>"
		     "  </menu>");

   grpAction->add (Gtk::Action::create ("Dir", _("_Directory")));
   grpAction->add (apMenus[START] = Gtk::Action::create ("Start", _("_Start compare")),
		   Gtk::AccelKey (_("<ctl>s")),
		   mem_fun (*this, &XDirComp::startCompare));
   grpAction->add (apMenus[STOP] = Gtk::Action::create ("EndComp", Gtk::Stock::CLOSE,
					_("S_top compare")),
		   Gtk::AccelKey (_("<ctl>Break")),
		   mem_fun (*this, &XDirComp::endCompare));
   grpAction->add (Gtk::Action::create ("Quit", Gtk::Stock::QUIT),
		   mem_fun (*this, &XDirComp::quit));

   grpAction->add (apMenus[SAVE] = Gtk::Action::create ("Save", Gtk::Stock::SAVE),
		   Gtk::AccelKey (_("F2")),
		   mem_fun (*this, &XDirComp::save));
   grpAction->add (apMenus[PRINT] = Gtk::Action::create ("Print", Gtk::Stock::PRINT),
		   mem_fun (*this, &XDirComp::print));

   grpAction->add (Gtk::Action::create ("Options", _("_Options")));
   grpAction->add (Gtk::ToggleAction::create ("ShowNew", _("Show _new"), "", true),
		   Gtk::AccelKey (_("<ctl>N")),
		   bind (mem_fun (*this, &XDirComp::toggleOption), CompareDirs::OPT_NONEWDEL));
   grpAction->add (Gtk::ToggleAction::create ("ShowChg", _("Show _changed files"),
					      "", true),
		   Gtk::AccelKey (_("<alt><ctl>N")),
		   bind (mem_fun (*this, &XDirComp::toggleOption), CompareDirs::OPT_NOCHANGED));
   grpAction->add (Gtk::ToggleAction::create ("ShowEqual", _("Show _equal files")),
		   Gtk::AccelKey (_("<ctl>E")),
		   bind (mem_fun (*this, &XDirComp::toggleOption), CompareDirs::OPT_EQUAL));
   grpAction->add (Gtk::ToggleAction::create ("ShowChgDirs", _("Show _changed dirs")),
		   Gtk::AccelKey (_("<alt><ctl>D")),
		   bind (mem_fun (*this, &XDirComp::toggleOption), CompareDirs::OPT_NODIRCOMP));
   grpAction->add (Gtk::ToggleAction::create ("ShowHidden", _("Show _hidden files")),
		   Gtk::AccelKey (_("<alt><ctl>H")),
		   bind (mem_fun (*this, &XDirComp::toggleOption), CompareDirs::OPT_CHECKHIDDEN));

   grpAction->add (apMenus[SUBDIRS] = Gtk::ToggleAction::create ("SrchDirs", Gtk::Stock::GOTO_LAST,
					      _("Search _subdirs")),
		   Gtk::AccelKey (_("<alt><ctl>S")),
		   mem_fun (*this, &XDirComp::searchSubdirs));
   grpAction->add (apMenus[EQUALSUBDIRS] = Gtk::ToggleAction::create ("SrchEqualDirs", _("Search _equal subdirs")),
		   Gtk::AccelKey (_("<alt><ctl>Q")),
		   mem_fun (*this, &XDirComp::searchEqualSubdirs));

   grpAction->add (apMenus[COMPARE] = Gtk::ToggleAction::create ("Comp", Gtk::Stock::FIND,
								 _("Compare c_ontents")),
		   Gtk::AccelKey (_("<ctl>C")),
		   bind (mem_fun (*this, &XDirComp::toggleOption), CompareDirs::OPT_CHECKCONTENTS));
   grpAction->add (Gtk::ToggleAction::create ("CompAll", _("Compare _always")),
		   Gtk::AccelKey (_("<alt><ctl>C")),
		   mem_fun (*this, &XDirComp::compareAlways));

   grpAction->add (Gtk::Action::create ("SelFiles", _("Select _files ...")),
		   Gtk::AccelKey (_("<ctl>F")),
		   mem_fun (*this, &XDirComp::selectFiles));
   grpAction->add (Gtk::Action::create ("SelDirs", _("Select d_irectories ...")),
		   Gtk::AccelKey (_("<alt><ctl>F")),
		   mem_fun (*this, &XDirComp::selectDirectories));

   grpAction->add (Gtk::Action::create ("StartDate", _("_Lower date-border  ...")),
		   Gtk::AccelKey (_("<ctl>T")),
		   bind (mem_fun (*this, &XDirComp::selectDate), false));
   grpAction->add (Gtk::Action::create ("EndDate", _("_Upper date-border  ...")),
		   Gtk::AccelKey (_("<alt><ctl>T")),
		   bind (mem_fun (*this, &XDirComp::selectDate), true));

   addHelpMenu (ui);
   ui += "</menubar></ui>";
   mgrUI->insert_action_group (grpAction);
   add_accel_group (mgrUI->get_accel_group ());
   mgrUI->add_ui_from_string (ui);

   ((Gtk::MenuItem*)(mgrUI->get_widget("/Menu/Help")))->set_right_justified ();

   getClient ()->pack_start (*mgrUI->get_widget("/Menu"), Gtk::PACK_SHRINK);

   // Disable menus according to state of program
   apMenus[START]->set_sensitive (false);
   apMenus[STOP]->set_sensitive (false);
   apMenus[SAVE]->set_sensitive (false);
   apMenus[PRINT]->set_sensitive (false);

   // Create controls
   tblInput.show ();
   vboxClient->pack_start (tblInput, false, true, 5);

   lblDirOrig.set_alignment (0.0, 0.5);
   lblDirOrig.show ();
   tblInput.attach (lblDirOrig, 0, 1, 0, 1, Gtk::FILL,
                    Gtk::EXPAND | Gtk::FILL, 5);

   lblDirComp.set_alignment (0.0, 0.5);
   lblDirComp.show ();
   tblInput.attach (lblDirComp, 0, 1, 1, 2, Gtk::FILL,
                    Gtk::EXPAND | Gtk::FILL, 5, 5);

   inDirComp.show ();
   inDirComp.signal_changed ().connect (mem_fun (*this, &XDirComp::dirChanged));
   tblInput.attach (inDirComp, 1, 2, 1, 2, Gtk::EXPAND | Gtk::FILL,
                    Gtk::EXPAND | Gtk::FILL, 5, 5);

   inDirOrig.show ();
   inDirOrig.signal_changed ().connect (mem_fun (*this, &XDirComp::dirChanged));
   tblInput.attach (inDirOrig, 1, 2, 0, 1, Gtk::EXPAND | Gtk::FILL,
                    Gtk::EXPAND | Gtk::FILL, 5);

   listFiles.get_selection ()->set_mode (Gtk::SELECTION_EXTENDED);
   listFiles.show ();

   scroll.add (listFiles);
   scroll.set_policy (Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
   vboxClient->pack_start (scroll);
   scroll.show ();

   status.push (Glib::locale_to_utf8 (_("Enter directories to compare")));
   status.show ();
   vboxClient->pack_start (status, Gtk::PACK_SHRINK);

   show ();
   inDirOrig.grab_focus ();
}

//-----------------------------------------------------------------------------
/// Callback to actually compare the two directories (incl. GUI-changes)
//-----------------------------------------------------------------------------
void* XDirComp::compare (YGP::Thread*) {
   TRACE5 ("XDirComp::compare (YGP::Thread*)");

   try {
      dc.compare ();
      listFiles.expand_all ();
   }
   catch (...) { }

#ifdef ENABLE_THREADS
   gdk_threads_enter ();
#endif
   listFiles.finish ();

   status.pop ();
   status.push (getDifferentFiles ());

   apMenus[SAVE]->set_sensitive (true);
   apMenus[PRINT]->set_sensitive (true);

#ifdef ENABLE_THREADS
   childSearch = NULL;
#endif
   menuInSearch (false);

#ifdef ENABLE_THREADS
   gdk_threads_leave ();
#endif

   delete dc.getOrigObject ();
   delete dc.getCompObject ();

   return NULL;
}

//-----------------------------------------------------------------------------
/// Builds a string with the number of changed files
//Returns   : Text with changed files
//-----------------------------------------------------------------------------
std::string XDirComp::getDifferentFiles () const {
   TRACE9 ("XDirComp::getDifferentFiles (): Files found: "
           << listFiles.rows () << "; Changed: "
           << dc.getChangedFiles () << "; New: " << dc.getNewFiles ());

   YGP::ANumeric numFiles (dc.getNumFiles ());
   YGP::ANumeric numChg (dc.getChangedFiles ());
   YGP::ANumeric numNew (dc.getNewFiles ());

   std::string stat (ngettext ("%1 file", "%1 files", numFiles));
   stat.replace (stat.find ("%1"), 2, numFiles.toString ());
   std::string chg (Glib::locale_to_utf8 (_("; %1 new, %2 changed")));
   chg.replace (chg.find ("%1"), 2, numNew.toString ());
   chg.replace (chg.find ("%2"), 2, numChg.toString ());
   return stat + chg;
}

//-----------------------------------------------------------------------------
/// Starts the comparison
//-----------------------------------------------------------------------------
void XDirComp::startCompare () {
   TRACE9 ("XDirComp::startCompare ()");
   YGP::IDirectorySearch* dirs[2];
   XGP::XFileEntry* dirNames[2] = { &inDirOrig, &inDirComp };

   Check3 ((sizeof (dirs) / sizeof (dirs[0]))
	   == (sizeof (dirNames) / sizeof (dirNames[0])));
   for (unsigned int i (0); i < (sizeof (dirs) / sizeof(dirs[0])); ++i) {
      try {
	 dirs[i] = CompareDirs::makeSearchobject (dirNames[i]->get_text ().c_str (),
						  DEFAULTPORT);
      }
      catch (std::exception& e) {
	 std::string error (Glib::locale_to_utf8 (_("Error accessing directory"
						    " '%1'\nReason: %2")));
	 error.replace (error.find ("%1"), 2, dirNames[i]->get_text ());
	 error.replace (error.find ("%2"), 2, e.what ());

	 Gtk::MessageDialog (error, false, Gtk::MESSAGE_ERROR,
			     Gtk::BUTTONS_OK).run ();
	 dirNames[i]->grab_focus ();
	 return;
      }
      Check3 (dirs[i]);
   }

   TRACE9 ("XDirComp::startCompare () - Cleaning list");
   dc.reset ();
   TRACE9 ("XDirComp::startCompare () - Setting directories to compare");
   dc.setOriginalDir (*dirs[0]);
   dc.setCompareDir (*dirs[1]);

   if (!dirInExcl.empty ()) {       // Set subdir-search if dirs to in/exclude
      Check3 (apMenus[SUBDIRS]);
      Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (apMenus[SUBDIRS])->set_active ();
   }

   TRACE8 ("XDirComp::startCompare () - Files to in/exclude: " << files);
   YGP::PathSearch ps (files);
   std::string node;

   // Handle all existing file-entries
   while (!(node = ps.getNextNode ()).empty ()) {
      bool include (node[0] == '+');
      node.replace (0, 1, 0, '\0');
      TRACE5 ((include ? "Including" : "Excluding") << " " << node);
      include ? dc.addIncludeFiles (node) : dc.addExcludeFiles (node);
   } // end-while

   TRACE8 ("XDirComp::command (int) - Dirs to in/exclude: " << dirInExcl);
   ps = dirInExcl;
   // Handle all existing directory-entries
   while (!(node = ps.getNextNode ()).empty ()) {
      bool include (node[0] == '+');
      node.replace (0, 1, 0, '\0');
      TRACE5 ((include ? "Incl. dir" : "Excl. dir") << " " << node);
      include ? dc.addIncludeDirs (node) : dc.addExcludeDirs (node);
   } // end-while

   dc.setBeginTime (dateStart.toSysTime ());
   dc.setEndTime (dateEnd.toSysTime ());

   menuInSearch (true);

#ifdef ENABLE_THREADS
   childSearch = (YGP::OThread<XDirComp>::create
		  (this, (YGP::OThread<XDirComp>::THREAD_OBJMEMBER)&XDirComp::compare, NULL));
   childSearch->allowCancelation ();
#else
   compare (NULL);
#endif
}

//-----------------------------------------------------------------------------
/// Ends the comparison
//-----------------------------------------------------------------------------
void XDirComp::endCompare () {
#ifdef ENABLE_THREADS
   Check2 (childSearch);
   dc.lock ();
   childSearch->cancel ();
   dc.unlock ();
   delete childSearch;
   childSearch = NULL;
#endif
   menuInSearch (false);

   status.pop ();
   status.push (Glib::locale_to_utf8 (_("User canceled; "))
		+ getDifferentFiles ());
}

//-----------------------------------------------------------------------------
/// Prints the result of the comparison
//-----------------------------------------------------------------------------
void XDirComp::print () {
   XGP::PrintDialog::create ()->sigPrint.connect (mem_fun (*this, &XDirComp::writeToStream));
}

//-----------------------------------------------------------------------------
/// Saves the result of the comparison
//-----------------------------------------------------------------------------
void XDirComp::save () {
   XGP::FileDialog::create (Glib::locale_to_utf8 (_("Save comparison to ...")),
			    Gtk::FILE_CHOOSER_ACTION_SAVE,
			    XGP::FileDialog::ASK_OVERWRITE)
      ->sigSelected.connect (mem_fun (*this, &XDirComp::saveToFile));
}

//-----------------------------------------------------------------------------
/// Toggles the search-subdirectory flag; If set, reset the equal subdirs
///  option
//-----------------------------------------------------------------------------
void XDirComp::searchSubdirs () {
   dc.toggleOption (CompareDirs::OPT_SEARCHSUBDIRS);
   if (dc.hasOption (CompareDirs::OPT_SEARCHSUBDIRS))
      Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (apMenus[EQUALSUBDIRS])->set_active (false);
}

//-----------------------------------------------------------------------------
/// Toggles the search-equal--subdirectory flag; If set, reset the subdirs
/// option
//-----------------------------------------------------------------------------
void XDirComp::searchEqualSubdirs () {
   dc.toggleOption (CompareDirs::OPT_SEARCHEQUALSUBDIRS);
   if (dc.hasOption (CompareDirs::OPT_SEARCHEQUALSUBDIRS))
      Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (apMenus[SUBDIRS])->set_active (false);
}

//-----------------------------------------------------------------------------
/// Checks the compare always option; If set, also set compare
//-----------------------------------------------------------------------------
void XDirComp::compareAlways () {
   dc.toggleOption (CompareDirs::OPT_IGNORETIMESTAMP);
   Check3 (apMenus[COMPARE]);
   Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (apMenus[COMPARE])->set_active ();
}

//-----------------------------------------------------------------------------
/// Terminates the program
//-----------------------------------------------------------------------------
void XDirComp::quit () {
#ifdef ENABLE_THREADS
   if (childSearch) {
      childSearch->cancel ();
      delete childSearch;
      childSearch = NULL;
   }
#endif
   hide ();
}

//-----------------------------------------------------------------------------
/// Enables selecting the files to compare
/// \param option: Option to toggle
//-----------------------------------------------------------------------------
void XDirComp::toggleOption (XCompareDirs::Options option) {
   dc.toggleOption (option);
}

//-----------------------------------------------------------------------------
/// Enables selecting the files to compare
//-----------------------------------------------------------------------------
void XDirComp::selectFiles () {
   XFileSelection::create (files, Glib::locale_to_utf8 (_("Select files to compare")));
}

//-----------------------------------------------------------------------------
/// Enables selecting the directories to compare
//-----------------------------------------------------------------------------
void XDirComp::selectDirectories () {
   XFileSelection::create (dirInExcl, Glib::locale_to_utf8 (_("Select directories to compare")));
}

//-----------------------------------------------------------------------------
/// Enables selecting the directories to compare
/// \param upper: Flag, if to change the upper or the lower date limit
//-----------------------------------------------------------------------------
void XDirComp::selectDate (bool upper) {
   XGP::XDate::create (Glib::locale_to_utf8 (upper ? _("Compare to") : _("Compare from")),
		       upper ? dateEnd : dateStart);
}

//-----------------------------------------------------------------------------
/// Shows the about box
//-----------------------------------------------------------------------------
void XDirComp::showAboutbox () {
   std::string ver (Glib::locale_to_utf8 (_("Copyright (C) 1999 - 2007 "
                                            "Markus Schwab\ne-mail: g17m0@"
                                            "lycos.com\n\nCompiled on %1 at %2")));
   ver.replace (ver.find ("%1"), 2, __DATE__);
   ver.replace (ver.find ("%2"), 2, __TIME__);
   XGP::XAbout* about (XGP::XAbout::create (ver, "X" PACKAGE " V" VERSION));
   about->setIconProgram (xpmXDirComp);
   about->setIconAuthor (xpmAuthor);
   about->get_window ()->set_transient_for (get_window ());
}

//-----------------------------------------------------------------------------
/// Entryfield for directory-path changed -> adapt menu-status
//-----------------------------------------------------------------------------
void XDirComp::dirChanged () {
   Check3 (apMenus[START]);
   TRACE9 ("XDirComp::dirChanged () - " << inDirOrig.get_text_length () << '/'
           << inDirComp.get_text_length ());

   bool inSearch (false);
#ifdef ENABLE_THREADS
   inSearch = childSearch;
#endif

   apMenus[START]->set_sensitive
      (inSearch
       ? false
       : inDirComp.get_text_length () && inDirOrig.get_text_length ());
}

//-----------------------------------------------------------------------------
/// Save result of comparison into a file
/// \param file: Name of file to create
//-----------------------------------------------------------------------------
void XDirComp::saveToFile (const std::string& file) {
   TRACE9 ("XDirComp::saveToFile (std::string&): " << file);

   FILE* output (fopen (file.c_str (), "w"));
   if (!output) {
      std::string error (Glib::locale_to_utf8 (_("Can not create file `%1'\n Reason: %2")));
      error.replace (error.find ("%1"), 2, file);
      error.replace (error.find ("%2"), 2, strerror (errno));

      Gtk::MessageDialog dlg (error, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK);
      dlg.run ();
      return;
   }
   writeToStream (output);
   fclose (output);
}

//-----------------------------------------------------------------------------
/// Save result of comparison into a file
/// \param file: Stream to fill
//-----------------------------------------------------------------------------
void XDirComp::writeToStream (FILE* file) {
   TRACE9 ("XDirComp::writeToStream (std::ostream&)");
   Check3 (file);

   Glib::ustring t1, t2;
   int lenTime (YGP::ATimestamp::now ().toString ().length ());
   Glib::ustring fill (lenTime++, ' ');
   lenTime <<= 1;

   for (unsigned int i (0); i < listFiles.rows (); ++i) {
      Gtk::TreeRow row (listFiles.row (i));
      std::string filename (row[listFiles.getColumns ().name]);
      TRACE8 ("XDirComp::writeToStream (std::ofstream&): " << filename);

      fprintf (file, "%-7s: %s", ((Glib::ustring)(row[listFiles.getColumns ().status])).c_str (),
	       filename.c_str ());
      for (unsigned int j (0); j < (69 - filename.size () - lenTime); ++j)
	 fputc (' ', file);

      t1 = row[listFiles.getColumns ().timeOrig];
      t2 = row[listFiles.getColumns ().timeComp];
      fprintf (file, " %s %s\n", (t1.size () ? t1 : fill).c_str (),
	       (t2.size () ? t2 : fill).c_str ());
   } // end-for all text-columns
}

//-----------------------------------------------------------------------------
/// Sets the menu according the status
/// \param posObject: Position of files in the listbox
//-----------------------------------------------------------------------------
void XDirComp::menuInSearch (bool search) {
   apMenus[STOP]->set_sensitive (search);
   if (search)
      apMenus[START]->set_sensitive (false);
   else
      dirChanged ();
}


//-----------------------------------------------------------------------------
/// Displays the different file
/// \param diff: Flag how the files differ
/// \param pFileOrig: Original file (NULL if new)
/// \param pFileComp: Compared file (NULL if deleted)
//-----------------------------------------------------------------------------
void XCompareDirs::showFile (const Status diff, const YGP::File* pFileOrig,
                             const YGP::File* pFileComp) {
   TRACE2 ("XCompareDirs::showFile (const Status, 2x const YGP::File*)");

   Check3 (diff <= DIR_NEW);
   Check3 (((diff == DIR_NEW) ? !pFileOrig : pFileOrig != NULL));
   Check3 (((diff == DIR_DELETED) ? !pFileComp : pFileComp != NULL));

   static const Glib::ustring apFileStati[] = {
       Glib::locale_to_utf8 (_("Younger")),
       Glib::locale_to_utf8 (_("Older")),
       Glib::locale_to_utf8 (_("Different")),
       Glib::locale_to_utf8 (_("Equal")),
       Glib::locale_to_utf8 (_("Deleted")),
       Glib::locale_to_utf8 (_("New")) };

#ifdef ENABLE_THREADS
   gdk_threads_enter ();
   lock ();
#endif
   TRACE2 ("XCompareDirs::showFile (const Status, 2x const YGP::File*) - Dir: " << getDirStartComp ());
   list.appendFile (getDirStartComp (), pFileOrig, pFileComp, apFileStati[diff]);

#ifdef ENABLE_THREADS
   unlock ();
   gdk_threads_leave ();
#endif

   // Adapt number of different files
   ++cAllFiles;
   if (diff < DIR_EQUAL)
       ++cChangedFiles;
   else if (diff > DIR_EQUAL)
      ++cNewFiles;

#ifdef ENABLE_THREADS
   if (thread)
      thread->isToCancel ();
#endif
}

//-----------------------------------------------------------------------------
/// Directory changed during compare
/// \param path: Entered directory
//-----------------------------------------------------------------------------
void XCompareDirs::enterDirectory (const std::string& path) {
   TRACE7 ("XCompareDirs::enterDirectory (const std::string&) - " << path);
#ifdef ENABLE_THREADS
   gdk_threads_enter ();
   lock ();
#endif

   static bool origEntry (true);
   if (origEntry) {
      TRACE9 ("XCompareDirs::enterDirectory (const std::string&) - " << getDirStartComp ());
      std::string dir (getDirStartComp ());
      if (dir.size ())
	 list.appendDirectory (dir);
   }
   origEntry = !origEntry;

   info.pop ();
   std::string str (Glib::locale_to_utf8 (_("Handling directory: %1")));
   str.replace (str.find ("%1"), 2, path);
   info.push (str);

#ifdef ENABLE_THREADS
   unlock ();
   gdk_threads_leave ();

   if (thread)
      thread->isToCancel ();
#endif
}

//-----------------------------------------------------------------------------
/// Clear object for compare (clear list; reset object-counters)
//-----------------------------------------------------------------------------
void XCompareDirs::reset () {
   clearFiles ();
   clearDirectories ();               // Reset in/exclude-info for new compare

   cAllFiles = cChangedFiles = cNewFiles = 0;
   list.freeze_child_notify ();
   list.clear ();
   list.thaw_child_notify ();
}


//-----------------------------------------------------------------------------
/// Entrypoint of application
/// \param argc: Number of parameters
/// \param argv: Array with pointer to parameter
//Returns   : int: Status
//-----------------------------------------------------------------------------
int main (int argc, char* argv[]) {
   TRACE1 ("main (int, char**) - Args: " << argc);
   XDirComp::initI18n (PACKAGE, LOCALEDIR);

#ifdef ENABLE_THREADS
   Glib::thread_init (NULL);
   gdk_threads_init ();
   gdk_threads_enter ();
#endif

   Gtk::Main appl (argc, argv);
   XDirComp win;
   appl.run (win);

#ifdef ENABLE_THREADS
   gdk_threads_leave ();
#endif
   return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1