 /***************************************************************************
                          kstarsdata.cpp  -  Trinity Desktop Planetarium
                             -------------------
    begin                : Sun Jul 29 2001
    copyright            : (C) 2001 by Heiko Evermann
    email                : heiko@evermann.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include <tqregexp.h>
#include <tqdir.h>
#include <tqfile.h>

#include <tdeapplication.h>
#include <tdemessagebox.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kstandarddirs.h>

#include "kstarsdata.h"

#include "Options.h"
#include "dms.h"
#include "skymap.h"
#include "ksutils.h"
#include "ksnumbers.h"
#include "skypoint.h"
#include "skyobject.h"
#include "starobject.h"
#include "deepskyobject.h"
#include "skyobjectname.h"
#include "ksplanet.h"
#include "ksasteroid.h"
#include "kscomet.h"
#include "ksmoon.h"
#include "jupitermoons.h"

#include "simclock.h"
#include "timezonerule.h"
#include "filesource.h"
#include "stardatasink.h"
#include "ksfilereader.h"
#include "indidriver.h"
#include "indi/lilxml.h"
#include "indistd.h"
#include "detaildialog.h"
#include "csegment.h"
#include "customcatalog.h"

TQPtrList<GeoLocation> KStarsData::geoList = TQPtrList<GeoLocation>();
TQMap<TQString, TimeZoneRule> KStarsData::Rulebook = TQMap<TQString, TimeZoneRule>();
TQStringList KStarsData::CustomColumns = TQStringList::split( " ", "ID RA Dc Tp Nm Mg Mj Mn PA Ig" );
int KStarsData::objects = 0;

KStarsData::KStarsData() : stdDirs(0), locale(0), 
		LST(0), HourAngle(0),
		PCat(0), Moon(0), jmoons(0),
		starFileReader(0), initTimer(0),
		source(0), loader(0), pump(0)
{
	startupComplete = false;
	objects++;

	//standard directories and locale objects
	stdDirs = new TDEStandardDirs();
	locale = new TDELocale( "kstars" );

	//Check to see if config file already exists.  If not, set 
	//useDefaultOptions = true
	TQString fname = locateLocal( "config", "kstarsrc" );
	useDefaultOptions = ! ( TQFile(fname).exists() );

	//Instantiate LST and HourAngle
	LST = new dms();
	HourAngle = new dms();
	
	//Instantiate planet catalog
	PCat = new PlanetCatalog(this);

	//initialize FOV symbol
	fovSymbol = FOV();

	//set AutoDelete property for TQPtrLists.  Most are set true,
	//but some 'meta-lists' need to be false.
	starList.setAutoDelete( true );
	ADVtreeList.setAutoDelete( true );
	geoList.setAutoDelete( true );
	deepSkyList.setAutoDelete( true );        // list of all deep space objects

	//separate lists for each deep-sky catalog.  The objects are duplicates of
	//deepSkyList, so do not delete them twice!
	deepSkyListMessier.setAutoDelete( false );
	deepSkyListNGC.setAutoDelete( false );
	deepSkyListIC.setAutoDelete( false );
	deepSkyListOther.setAutoDelete( false );

	//ObjLabelList does not construct new objects, so no autoDelete needed
	ObjLabelList.setAutoDelete( false );

	cometList.setAutoDelete( true );
	asteroidList.setAutoDelete( true );

	//Constellation lines are now pointers to existing StarObjects;
	//these are already auto-deleted by starList.
	clineList.setAutoDelete( false );
	clineModeList.setAutoDelete( true );
	cnameList.setAutoDelete( true );
	csegmentList.setAutoDelete( true );

	Equator.setAutoDelete( true );
	Ecliptic.setAutoDelete( true );
	Horizon.setAutoDelete( true );
	for ( unsigned int i=0; i<11; ++i ) MilkyWay[i].setAutoDelete( true );

	VariableStarsList.setAutoDelete(true);
	INDIHostsList.setAutoDelete(true);
	INDITelescopeList.setAutoDelete(true);

	//Initialize object type strings
	TypeName[0] = i18n( "star" );
	TypeName[1] = i18n( "multiple star" );
	TypeName[2] = i18n( "planet" );
	TypeName[3] = i18n( "open cluster" );
	TypeName[4] = i18n( "globular cluster" );
	TypeName[5] = i18n( "gaseous nebula" );
	TypeName[6] = i18n( "planetary nebula" );
	TypeName[7] = i18n( "supernova remnant" );
	TypeName[8] = i18n( "galaxy" );
	TypeName[9] = i18n( "comet" );
	TypeName[10] = i18n( "asteroid" );
	TypeName[11] = i18n( "constellation" );

	// at startup times run forward
	setTimeDirection( 0.0 );

	//The StoredDate is used when saving user settings in a script; initialize to invalid date
	StoredDate.setDJD( (long double)INVALID_DAY );

	temporaryTrail = false;
}

KStarsData::~KStarsData() {
	objects--;
	checkDataPumpAction();

	// the list items do not need to be removed by hand.
	// all lists are set to AutoDelete = true

	delete stdDirs;
	delete Moon;
	delete locale;
	delete PCat;
	delete jmoons;
	delete initTimer;
}

bool KStarsData::readMWData( void ) {
	TQFile file;

	for ( unsigned int i=0; i<11; ++i ) {
		TQString snum, fname, szero;
		snum = snum.setNum( i+1 );
		if ( i+1 < 10 ) szero = "0"; else szero = "";
		fname = "mw" + szero + snum + ".dat";

		if ( KSUtils::openDataFile( file, fname ) ) {
			KSFileReader fileReader( file ); // close file is included
			while ( fileReader.hasMoreLines() ) {
				TQString line;
				double ra, dec;

				line = fileReader.readLine();

				ra = line.mid( 0, 8 ).toDouble();
				dec = line.mid( 9, 8 ).toDouble();

				SkyPoint *o = new SkyPoint( ra, dec );
				MilkyWay[i].append( o );
			}
		} else {
			return false;
		}
	}
	return true;
}

bool KStarsData::readADVTreeData(void)
{

  TQFile file;
  TQString Interface;

  if (!KSUtils::openDataFile(file, "advinterface.dat"))
   return false;

   TQTextStream stream(&file);
   TQString Line;

  while  (!stream.atEnd())
  {
    TQString Name, Link, subName;
    int Type, interfaceIndex;

    Line = stream.readLine();

    if (Line.startsWith("[KSLABEL]"))
    {
       Name = Line.mid(9);
       Type = 0;
    }
    else if (Line.startsWith("[END]"))
       Type = 1;
    else if (Line.startsWith("[KSINTERFACE]"))
    {
      Interface = Line.mid(13);
      continue;
    }

    else
    {
      Name = Line.mid(0, Line.find(":"));
      Link = Line.mid(Line.find(":") + 1);

      // Link is empty, using Interface instead
      if (Link.isEmpty())
      {
         Link = Interface;
         subName = Name;
         interfaceIndex = Link.find("KSINTERFACE");
         Link.remove(interfaceIndex, 11);
         Link = Link.insert(interfaceIndex, subName.replace( TQRegExp(" "), "+"));

       }

      Type = 2;
    }

    ADVTreeData *ADVData = new ADVTreeData;

    ADVData->Name = Name;
    ADVData->Link = Link;
    ADVData->Type = Type;

    ADVtreeList.append(ADVData);
   }

   return true;



}

bool KStarsData::readVARData(void)
{
  TQString varFile("valaav.txt");
  TQFile localeFile;
  TQFile file;

  file.setName( locateLocal( "appdata", varFile ) );
		if ( !file.open( IO_ReadOnly ) )
		{
			// Open default variable stars file
			if ( KSUtils::openDataFile( file, varFile ) )
			{
				// we found urlfile, we need to copy it to locale
				localeFile.setName( locateLocal( "appdata", varFile ) );

				if (localeFile.open(IO_WriteOnly))
				{
				    TQTextStream readStream(&file);
				    TQTextStream writeStream(&localeFile);
				    writeStream <<  readStream.read();
				    localeFile.close();
				    file.reset();
				}
			}
			else
			 return false;
		}


   TQTextStream stream(&file);

  stream.readLine();

  while  (!stream.atEnd())
  {
    TQString Name;
    TQString Designation;
    TQString Line;

    Line = stream.readLine();

    if (Line[0] == TQChar('*'))
     break;

    Designation = Line.mid(0,8).stripWhiteSpace();
    Name          = Line.mid(10,20).simplifyWhiteSpace();

    VariableStarInfo *VInfo = new VariableStarInfo;

    VInfo->Designation = Designation;
    VInfo->Name          = Name;
    VariableStarsList.append(VInfo);
   }

   return true;

}


bool KStarsData::readINDIHosts(void)
{
  TQString indiFile("indihosts.xml");
  TQFile localeFile;
  TQFile file;
  char errmsg[1024];
  signed char c;
  LilXML *xmlParser = newLilXML();
  XMLEle *root = NULL;
  XMLAtt *ap;

  file.setName( locate( "appdata", indiFile ) );
	if ( file.name().isEmpty() || !file.open( IO_ReadOnly ) )
		 return false;

 while ( (c = (signed char) file.getch()) != -1)
 {
    root = readXMLEle(xmlParser, c, errmsg);

    if (root)
    {

      // Get host name
      ap = findXMLAtt(root, "name");
      if (!ap) {
	delLilXML(xmlParser);
	return false;
      }

       INDIHostsInfo *VInfo = new INDIHostsInfo;

       VInfo->name = TQString(valuXMLAtt(ap));

      // Get host name
      ap = findXMLAtt(root, "hostname");

      if (!ap) {
	delLilXML(xmlParser);
	return false;
      }

    VInfo->hostname = TQString(valuXMLAtt(ap));

    ap = findXMLAtt(root, "port");

    if (!ap) {
      delLilXML(xmlParser);
      return false;
    }

    VInfo->portnumber = TQString(valuXMLAtt(ap));

    VInfo->isConnected = false;
    VInfo->mgrID = -1;

    INDIHostsList.append(VInfo);

    delXMLEle(root);
    }

    else if (errmsg[0])
     kdDebug() << errmsg << endl;

  }

  delLilXML(xmlParser);

  return true;


}

bool KStarsData::readCLineData( void ) {
	//The constellation lines data file (clines.dat) contains lists
	//of abbreviated genetive star names in the same format as they 
	//appear in the star data files (hipNNN.dat).  
	//
	//Each constellation consists of a TQPtrList of SkyPoints, 
	//corresponding to the stars at each "node" of the constellation.
	//These are pointers to the starobjects themselves, so the nodes 
	//will automatically be fixed to the stars even as the star 
	//positions change due to proper motions.  In addition, each node 
	//has a corresponding flag that determines whether a line should 
	//connect this node and the previous one.
	
	TQFile file;
	if ( KSUtils::openDataFile( file, "clines.dat" ) ) {
	  TQTextStream stream( &file );

		while ( !stream.eof() ) {
			TQString line, name;
			TQChar *mode;

			line = stream.readLine();

			//ignore lines beginning with "#":
			if ( line.at( 0 ) != '#' ) {
				name = line.mid( 2 ).stripWhiteSpace();
				
				//Find the star with the same abbreviated genitive name ( name2() )
				//increase efficiency by searching the list of named objects, rather than the 
				//full list of all stars.  
				bool starFound( false );
				for ( SkyObjectName *oname = ObjNames.first(); oname; oname = ObjNames.next() ) {
					if ( oname->skyObject()->type() == SkyObject::STAR && 
							 oname->skyObject()->name2() == name ) {
						starFound = true;
						clineList.append( (SkyPoint *)( oname->skyObject() ) );
						
						mode = new TQChar( line.at( 0 ) );
						clineModeList.append( mode );
						break;
					}
				}
				
				if ( ! starFound ) 
					kdWarning() << i18n( "No star named %1 found." ).arg(name) << endl;
			}
		}
		file.close();

		return true;
	} else {
		return false;
	}
}

bool KStarsData::readCNameData( void ) {
	TQFile file;
	cnameFile = "cnames.dat";

	if ( KSUtils::openDataFile( file, cnameFile ) ) {
		TQTextStream stream( &file );

		while ( !stream.eof() ) {
			TQString line, name, abbrev;
			int rah, ram, ras, dd, dm, ds;
			TQChar sgn;

			line = stream.readLine();

			rah = line.mid( 0, 2 ).toInt();
			ram = line.mid( 2, 2 ).toInt();
			ras = line.mid( 4, 2 ).toInt();

			sgn = line.at( 6 );
			dd = line.mid( 7, 2 ).toInt();
			dm = line.mid( 9, 2 ).toInt();
			ds = line.mid( 11, 2 ).toInt();

			abbrev = line.mid( 13, 3 );
			name  = line.mid( 17 ).stripWhiteSpace();

			dms r; r.setH( rah, ram, ras );
			dms d( dd, dm,  ds );

			if ( sgn == "-" ) { d.setD( -1.0*d.Degrees() ); }

			SkyObject *o = new SkyObject( SkyObject::CONSTELLATION, r, d, 0.0, name, abbrev );
			cnameList.append( o );
			ObjNames.append( o );
		}
		file.close();

		return true;
	} else {
		return false;
	}
}

bool KStarsData::readCBoundData( void ) {
	TQFile file;

	if ( KSUtils::openDataFile( file, "cbound.dat" ) ) {
		TQTextStream stream( &file );

		unsigned int nn(0);
		double ra(0.0), dec(0.0);
		TQString d1, d2;
		bool ok(false), comment(false);
		
		//read the stream one field at a time.  Individual segments can span
		//multiple lines, so our normal readLine() is less convenient here.
		//Read fields into strings and then attempt to recast them as ints 
		//or doubles..this lets us do error checking on the stream.
		while ( !stream.eof() ) {
			stream >> d1;
			if ( d1.at(0) == '#' ) { 
				comment = true; 
				ok = true; 
			} else {
				comment = false;
				nn = d1.toInt( &ok );
			}
			
			if ( !ok || comment ) {
				d1 = stream.readLine();
				
				if ( !ok ) 
					kdWarning() << i18n( "Unable to parse boundary segment." ) << endl;
				
			} else { 
				CSegment *seg = new CSegment();
				
				for ( unsigned int i=0; i<nn; ++i ) {
					stream >> d1 >> d2;
					ra = d1.toDouble( &ok );
					if ( ok ) dec = d2.toDouble( &ok );
					if ( !ok ) break;
					
					seg->addPoint( ra, dec );
				}
				
				if ( !ok ) {
					//uh oh, this entry was not parsed.  Skip to the next line.
					kdWarning() << i18n( "Unable to parse boundary segment." ) << endl;
					delete seg;
					d1 = stream.readLine();
					
				} else {
					stream >> d1; //this should always equal 2
					
					nn = d1.toInt( &ok );
					//error check
					if ( !ok || nn != 2 ) {
						kdWarning() << i18n( "Bad Constellation Boundary data." ) << endl;
						delete seg;
						d1 = stream.readLine();
					}
				}

				if ( ok ) {
					stream >> d1 >> d2;
					ok = seg->setNames( d1, d2 );
					if ( ok ) csegmentList.append( seg );
				}
			}
		}
		
		return true;
	} else {
		return false;
	}
}

bool KStarsData::openStarFile( int i ) {
	if (starFileReader != 0) delete starFileReader;
	TQFile file;
	TQString snum, fname;
	snum = TQString().sprintf("%03d", i);
	fname = "hip" + snum + ".dat";
	if (KSUtils::openDataFile(file, fname)) {
		starFileReader = new KSFileReader(file); // close file is included
	} else {
		starFileReader = 0;
		return false;
	}
	return true;
}

bool KStarsData::readStarData( void ) {
	bool ready = false;

	float loadUntilMag = MINDRAWSTARMAG;
	if (Options::magLimitDrawStar() > loadUntilMag) loadUntilMag = Options::magLimitDrawStar();
	
	for (unsigned int i=1; i<NHIPFILES+1; ++i) {
		emit progressText( i18n( "Loading Star Data (%1%)" ).arg( int(100.*float(i)/float(NHIPFILES)) ) );
		
		if (openStarFile(i) == true) {
			while (starFileReader->hasMoreLines()) {
				TQString line;
				float mag;

				line = starFileReader->readLine();
				if ( line.left(1) != "#" ) {  //ignore comments
					// check star magnitude
					mag = line.mid( 46,5 ).toFloat();
					if ( mag > loadUntilMag ) {
						ready = true;
						break;
					}

					processStar(&line);
				}
			}  // end of while

		} else { //one of the star files could not be read.
			if ( starList.count() ) return true;
			else return false;
		}
		// magnitude level is reached
		if (ready == true) break;
	}

//Store the max set magnitude of current session. Will increase in KStarsData::appendNewData()
	maxSetMagnitude = Options::magLimitDrawStar();
	delete starFileReader;
	starFileReader = 0;
	return true;
}

void KStarsData::processStar( TQString *line, bool reloadMode ) {
	TQString name, gname, SpType;
	int rah, ram, ras, ras2, dd, dm, ds, ds2;
	bool mult, var;
	TQChar sgn;
	double mag, bv, dmag, vper;
	double pmra, pmdec, plx;

	name = ""; gname = "";

	//parse coordinates
	rah = line->mid( 0, 2 ).toInt();
	ram = line->mid( 2, 2 ).toInt();
	ras = int(line->mid( 4, 5 ).toDouble());
	ras2 = int(60.0*(line->mid( 4, 5 ).toDouble()-ras) + 0.5); //add 0.5 to get nearest integer with int()

	sgn = line->at(10);
	dd = line->mid(11, 2).toInt();
	dm = line->mid(13, 2).toInt();
	ds = int(line->mid(15, 4).toDouble());
	ds2 = int(60.0*(line->mid( 15, 5 ).toDouble()-ds) + 0.5); //add 0.5 to get nearest integer with int()

	//parse proper motion and parallax
	pmra = line->mid( 20, 9 ).toDouble();
	pmdec = line->mid( 29, 9 ).toDouble();
	plx = line->mid( 38, 7 ).toDouble();

	//parse magnitude, B-V color, and spectral type
	mag = line->mid( 46, 5 ).toDouble();
	bv  = line->mid( 51, 5 ).toDouble();
	SpType = line->mid(56, 2);

	//parse multiplicity
	mult = line->mid( 59, 1 ).toInt();

	//parse variablility...currently not using dmag or var
	var = false; dmag = 0.0; vper = 0.0;
	if ( line->at( 62 ) == '.' ) {
		var = true;
		dmag = line->mid( 61, 4 ).toDouble();
		vper = line->mid( 66, 6 ).toDouble();
	}

	//parse name(s)
	name = line->mid( 72 ).stripWhiteSpace(); //the rest of the line
	if (name.contains( ':' )) { //genetive form exists
		gname = name.mid( name.find(':')+1 ).stripWhiteSpace();
		name = name.mid( 0, name.find(':') ).stripWhiteSpace();
	}

	// HEV: look up star name in internationalization filesource
	name = i18n("star name", name.local8Bit().data());

	bool starIsUnnamed( false );
	if (name.isEmpty() && gname.isEmpty()) { //both names are empty
		starIsUnnamed = true;
	}
	
	dms r;
	r.setH(rah, ram, ras, ras2);
	dms d(dd, dm, ds, ds2);

	if (sgn == "-") { d.setD( -1.0*d.Degrees() ); }

	StarObject *o = new StarObject(r, d, mag, name, gname, SpType, pmra, pmdec, plx, mult, var );
	starList.append(o);
	
	// get horizontal coordinates when object will loaded while running the application
	// first run doesn't need this because updateTime() will called after loading all data
	if (reloadMode) {
		o->EquatorialToHorizontal( LST, geo()->lat() );
	}
	
	//STAR_SIZE
//	StarObject *p = new StarObject(r, d, mag, name, gname, SpType, pmra, pmdec, plx, mult, var );
//	starList.append(p);

	// add named stars to list
	if (starIsUnnamed == false) {
		ObjNames.append(o);
	}
}

bool KStarsData::readAsteroidData( void ) {
	TQFile file;

	if ( KSUtils::openDataFile( file, "asteroids.dat" ) ) {
		KSFileReader fileReader( file );

		while( fileReader.hasMoreLines() ) {
			TQString line, name;
			int mJD;
			double a, e, dble_i, dble_w, dble_N, dble_M, H;
			long double JD;
			KSAsteroid *ast = 0;

			line = fileReader.readLine();
			name = line.mid( 6, 17 ).stripWhiteSpace();
			mJD  = line.mid( 24, 5 ).toInt();
			a    = line.mid( 30, 9 ).toDouble();
			e    = line.mid( 41, 10 ).toDouble();
			dble_i = line.mid( 52, 9 ).toDouble();
			dble_w = line.mid( 62, 9 ).toDouble();
			dble_N = line.mid( 72, 9 ).toDouble();
			dble_M = line.mid( 82, 11 ).toDouble();
			H = line.mid( 94, 5 ).toDouble();

			JD = double( mJD ) + 2400000.5;

			ast = new KSAsteroid( this, name, "", JD, a, e, dms(dble_i), dms(dble_w), dms(dble_N), dms(dble_M), H );
			ast->setAngularSize( 0.005 );
			asteroidList.append( ast );
			ObjNames.append( ast );
		}

		if ( asteroidList.count() ) return true;
	}

	return false;
}

bool KStarsData::readCometData( void ) {
	TQFile file;

	if ( KSUtils::openDataFile( file, "comets.dat" ) ) {
		KSFileReader fileReader( file );

		while( fileReader.hasMoreLines() ) {
			TQString line, name;
			int mJD;
			double q, e, dble_i, dble_w, dble_N, Tp;
			long double JD;
			KSComet *com = 0;

			line = fileReader.readLine();
			name = line.mid( 3, 35 ).stripWhiteSpace();
			mJD  = line.mid( 38, 5 ).toInt();
			q    = line.mid( 44, 10 ).toDouble();
			e    = line.mid( 55, 10 ).toDouble();
			dble_i = line.mid( 66, 9 ).toDouble();
			dble_w = line.mid( 76, 9 ).toDouble();
			dble_N = line.mid( 86, 9 ).toDouble();
			Tp = line.mid( 96, 14 ).toDouble();

			JD = double( mJD ) + 2400000.5;

			com = new KSComet( this, name, "", JD, q, e, dms(dble_i), dms(dble_w), dms(dble_N), Tp );
			com->setAngularSize( 0.005 );

			cometList.append( com );
			ObjNames.append( com );
		}

		if ( cometList.count() ) return true;
	}

	return false;
}


//02/2003: NEW: split data files, using Heiko's new KSFileReader.
bool KStarsData::readDeepSkyData( void ) {
	TQFile file;

	for ( unsigned int i=0; i<NNGCFILES; ++i ) {
		TQString snum, fname;
		snum = TQString().sprintf( "%02d", i+1 );
		fname = "ngcic" + snum + ".dat";

		emit progressText( i18n( "Loading NGC/IC Data (%1%)" ).arg( int(100.*float(i)/float(NNGCFILES)) ) );

		if ( KSUtils::openDataFile( file, fname ) ) {
			KSFileReader fileReader( file ); // close file is included
			while ( fileReader.hasMoreLines() ) {
				TQString line, con, ss, name, name2, longname;
				TQString cat, cat2;
				float mag(1000.0), ras, a, b;
				int type, ingc, imess(-1), rah, ram, dd, dm, ds, pa;
				int pgc, ugc;
				TQChar sgn, iflag;

				line = fileReader.readLine();
				//Ignore comment lines
				while ( line.at(0) == '#' && fileReader.hasMoreLines() ) line = fileReader.readLine();
				//Ignore lines with no coordinate values
				while ( line.mid(6,8).stripWhiteSpace().isEmpty() ) line = fileReader.readLine();
				
				iflag = line.at( 0 ); //check for NGC/IC catalog flag
				if ( iflag == 'I' ) cat = "IC";
				else if ( iflag == 'N' ) cat = "NGC";

				ingc = line.mid( 1, 4 ).toInt();  // NGC/IC catalog number
				if ( ingc==0 ) cat = ""; //object is not in NGC or IC catalogs

				//coordinates
				rah = line.mid( 6, 2 ).toInt();
				ram = line.mid( 8, 2 ).toInt();
				ras = line.mid( 10, 4 ).toFloat();
				sgn = line.at( 15 );
				dd = line.mid( 16, 2 ).toInt();
				dm = line.mid( 18, 2 ).toInt();
				ds = line.mid( 20, 2 ).toInt();

				//B magnitude
				ss = line.mid( 23, 4 );
			  if (ss == "    " ) { mag = 99.9; } else { mag = ss.toFloat(); }

				//object type
				type = line.mid( 29, 1 ).toInt();

				//major and minor axes
				ss = line.mid( 31, 5 );
				if (ss == "      " ) { a = 0.0; } else { a = ss.toFloat(); }
				ss = line.mid( 37, 5 );
				if (ss == "     " ) { b = 0.0; } else { b = ss.toFloat(); }
				//position angle.  The catalog PA is zero when the Major axis 
				//is horizontal.  But we want the angle measured from North, so 
				//we set PA = 90 - pa.
				ss = line.mid( 43, 3 );
				if (ss == "   " ) { pa = 90; } else { pa = 90 - ss.toInt(); }

				//PGC number
				ss = line.mid( 47, 6 );
				if (ss == "      " ) { pgc = 0; } else { pgc = ss.toInt(); }

				//UGC number
				if ( line.mid( 54, 3 ) == "UGC" ) {
					ugc = line.mid( 58, 5 ).toInt();
				} else {
					ugc = 0;
				}

				//Messier number
				if ( line.at( 70 ) == 'M' ) {
					cat2 = cat;
					if ( ingc==0 ) cat2 = "";
					cat = "M";
					imess = line.mid( 72, 3 ).toInt();
				}

				longname = line.mid( 76, line.length() ).stripWhiteSpace();

				dms r;
				r.setH( rah, ram, int(ras) );
				dms d( dd, dm, ds );

				if ( sgn == "-" ) { d.setD( -1.0*d.Degrees() ); }

//				TQString snum;
				if ( cat=="IC" || cat=="NGC" ) {
					snum.setNum( ingc );
					name = cat + " " + snum;
				} else if ( cat=="M" ) {
					snum.setNum( imess );
					name = cat + " " + snum;
					if ( cat2=="NGC" ) {
						snum.setNum( ingc );
						name2 = cat2 + " " + snum;
					} else if ( cat2=="IC" ) {
						snum.setNum( ingc );
						name2 = cat2 + " " + snum;
					} else {
						name2 = "";
					}
				} else {
					if ( longname.isEmpty() ) name = i18n( "Unnamed Object" );
					else name = longname;
				}

				// create new deepskyobject
				DeepSkyObject *o = 0;
				if ( type==0 ) type = 1; //Make sure we use CATALOG_STAR, not STAR
				o = new DeepSkyObject( type, r, d, mag, name, name2, longname, cat, a, b, pa, pgc, ugc );

				// keep object in deep sky objects' list
				deepSkyList.append( o );
				// plus: keep objects separated for performance reasons. Switching the colors during
				// paint is too expensive.
				if ( o->isCatalogM()) {
					deepSkyListMessier.append( o );
				} else if (o->isCatalogNGC() ) {
					deepSkyListNGC.append( o );
				} else if ( o->isCatalogIC() ) {
					deepSkyListIC.append( o );
				} else {
					deepSkyListOther.append( o );
				}

				// list of object names
				ObjNames.append( (SkyObject*)o );

				//Add longname to objList, unless longname is the same as name
				if ( !o->longname().isEmpty() && o->name() != o->longname() && o->hasName() ) {
					ObjNames.append( o, true );  // append with longname
				}
			} //end while-filereader
		} else { //one of the files could not be opened
			return false;
		}
	} //end for-loop through files

	return true;
}

bool KStarsData::openURLFile(TQString urlfile, TQFile & file) {
	//TQFile file;
	TQString localFile;
	bool fileFound = false;
	TQFile localeFile;

	if ( locale->language() != "en_US" )
		localFile = locale->language() + "/" + urlfile;

	if ( ! localFile.isEmpty() && KSUtils::openDataFile( file, localFile ) ) {
		fileFound = true;
	} else {
   // Try to load locale file, if not successful, load regular urlfile and then copy it to locale.
		file.setName( locateLocal( "appdata", urlfile ) );
		if ( file.open( IO_ReadOnly ) ) {
			//local file found.  Now, if global file has newer timestamp, then merge the two files.
			//First load local file into TQStringList
			bool newDataFound( false );
			TQStringList urlData;
			TQTextStream lStream( &file );
			while ( ! lStream.eof() ) urlData.append( lStream.readLine() );

			//Find global file(s) in findAllResources() list.
			TQFileInfo fi_local( file.name() );
			TQStringList flist = TDEGlobal::instance()->dirs()->findAllResources( "appdata", urlfile );
			for ( unsigned int i=0; i< flist.count(); i++ ) {
				if ( flist[i] != file.name() ) {
					TQFileInfo fi_global( flist[i] );

					//Is this global file newer than the local file?
					if ( fi_global.lastModified() > fi_local.lastModified() ) {
						//Global file has newer timestamp than local.  Add lines in global file that don't already exist in local file.
						//be smart about this; in some cases the URL is updated but the image is probably the same if its
						//label string is the same.  So only check strings up to last ":"
						TQFile globalFile( flist[i] );
						if ( globalFile.open( IO_ReadOnly ) ) {
							TQTextStream gStream( &globalFile );
							while ( ! gStream.eof() ) {
								TQString line = gStream.readLine();

								//If global-file line begins with "XXX:" then this line should be removed from the local file.
								if ( line.left( 4 ) == "XXX:"  && urlData.contains( line.mid( 4 ) ) ) {
									urlData.remove( urlData.find( line.mid( 4 ) ) );
								} else {
									//does local file contain the current global file line, up to second ':' ?

									bool linefound( false );
									for ( unsigned int j=0; j< urlData.count(); ++j ) {
										if ( urlData[j].contains( line.left( line.find( ':', line.find( ':' ) + 1 ) ) ) ) {
											//replace line in urlData with its equivalent in the newer global file.
											urlData.remove( urlData.at(j) );
											urlData.insert( urlData.at(j), line );
											if ( !newDataFound ) newDataFound = true;
											linefound = true;
											break;
										}
									}
									if ( ! linefound ) {
										urlData.append( line );
										if ( !newDataFound ) newDataFound = true;
									}
								}
							}
						}
					}
				}
			}

			file.close();

			//(possibly) write appended local file
			if ( newDataFound ) {
				if ( file.open( IO_WriteOnly ) ) {
					TQTextStream outStream( &file );
					for ( unsigned int i=0; i<urlData.count(); i++ ) {
						outStream << urlData[i] << endl;
					}
					file.close();
				}
			}

			if ( file.open( IO_ReadOnly ) ) fileFound = true;

		} else {
			if ( KSUtils::openDataFile( file, urlfile ) ) {
				if ( locale->language() != "en_US" ) kdDebug() << i18n( "No localized URL file; using default English file." ) << endl;
				// we found urlfile, we need to copy it to locale
				localeFile.setName( locateLocal( "appdata", urlfile ) );
				if (localeFile.open(IO_WriteOnly)) {
					TQTextStream readStream(&file);
					TQTextStream writeStream(&localeFile);
					while ( ! readStream.eof() ) {
						TQString line = readStream.readLine();
						if ( line.left( 4 ) != "XXX:" ) //do not write "deleted" lines
							writeStream << line << endl;
					}

					localeFile.close();
					file.reset();
				} else {
					kdDebug() << i18n( "Failed to copy default URL file to locale folder, modifying default object links is not possible" ) << endl;
				}
				fileFound = true;
			}
		}
	}
	return fileFound;
}

bool KStarsData::readUserLog(void)
{
	TQFile file;
	TQString buffer;
	TQString sub, name, data;

	if (!KSUtils::openDataFile( file, "userlog.dat" )) return false;

	TQTextStream stream(&file);

	if (!stream.eof()) buffer = stream.read();

	while (!buffer.isEmpty()) {
		int startIndex, endIndex;

		startIndex = buffer.find("[KSLABEL:");
		sub = buffer.mid(startIndex);
		endIndex = sub.find("[KSLogEnd]");

		// Read name after KSLABEL identifer
		uint uiFirstNewline = sub.find("\n", startIndex + 9);
		name = sub.mid(startIndex + 9, sub.findRev( "]", uiFirstNewline ) - (startIndex + 9) );
		// Read data and skip new line
		data   = sub.mid( uiFirstNewline + 1, endIndex - (uiFirstNewline + 1));
		buffer = buffer.mid(endIndex + 11);

		//Find the sky object named 'name'.
		//Note that ObjectNameList::find() looks for the ascii representation 
		//of star genetive names, so stars are identified that way in the user log.
		SkyObjectName *sonm = ObjNames.find(name);
		if (sonm == 0) {
			kdWarning() << k_funcinfo << name << " not found" << endl;
		} else {
			sonm->skyObject()->userLog = data;
		}

	} // end while
	file.close();
	return true;
}

bool KStarsData::readURLData( TQString urlfile, int type, bool deepOnly ) {
	TQFile file;
	if (!openURLFile(urlfile, file)) return false;

	TQTextStream stream(&file);

	while ( !stream.eof() ) {
		TQString line = stream.readLine();

		//ignore comment lines
		if ( line.left(1) != "#" ) {
			TQString name = line.mid( 0, line.find(':') );
			TQString sub = line.mid( line.find(':')+1 );
			TQString title = sub.mid( 0, sub.find(':') );
			TQString url = sub.mid( sub.find(':')+1 );
	
			SkyObjectName *sonm = ObjNames.find(name);
			
			if (sonm == 0) {
				kdWarning() << k_funcinfo << name << " not found" << endl;
			} else {
				if ( ! deepOnly || ( sonm->skyObject()->type() > 2 && sonm->skyObject()->type() < 9 ) ) {
					if ( type==0 ) { //image URL
						sonm->skyObject()->ImageList.append( url );
						sonm->skyObject()->ImageTitle.append( title );
					} else if ( type==1 ) { //info URL
						sonm->skyObject()->InfoList.append( url );
						sonm->skyObject()->InfoTitle.append( title );
					}
				}
			}
		}
	}
	file.close();
	return true;
}

bool KStarsData::readCustomCatalogs() {
	bool result = false;

	for ( uint i=0; i < Options::catalogFile().count(); i++ ) {
		bool thisresult = addCatalog( Options::catalogFile()[i] );
		//Make result = true if at least one catalog is added
		result = ( result || thisresult );
	}
	return result;
}

bool KStarsData::addCatalog( TQString filename ) {
	CustomCatalog *newCat = createCustomCatalog( filename, false );
	if ( newCat ) {
		CustomCatalogs.append( newCat );

		// add object names to ObjNames list
		for ( uint i=0; i < newCat->objList().count(); i++ ) {
//		for ( SkyObject *o=newCat->objList().first(); o; o = newCat->objList().next() ) {
			SkyObject *o = newCat->objList().at(i);
			ObjNames.append( o );
			if ( o->hasLongName() && o->longname() != o->name() ) 
				ObjNames.append( o, true ); //Add long name
			// PdV
			//if (reloadMode) {
		        //        o->EquatorialToHorizontal( LST, geo()->lat() );
			//}

		}

		return true;
	} else
		kdWarning() << k_funcinfo << i18n("Error adding catalog: %1").arg( filename ) << endl;

	return false;
}

bool KStarsData::removeCatalog( int i ) {
	if ( ! CustomCatalogs.at(i) ) return false;

	TQPtrList<SkyObject> cat = CustomCatalogs.at(i)->objList();

	for ( SkyObject *o=cat.first(); o; o=cat.next() ) {
		ObjNames.remove( o->name() );
		if ( o->hasLongName() && o->longname() != o->name() ) 
			ObjNames.remove( o->longname() );
	}

	CustomCatalogs.remove(i);

	return true;
}

CustomCatalog* KStarsData::createCustomCatalog( TQString filename, bool showerrs ) {
	TQDir::setCurrent( TQDir::homeDirPath() );  //for files with relative path
	TQPtrList<SkyObject> objList;
	TQString CatalogName, CatalogPrefix, CatalogColor;
	float CatalogEpoch;

	//If the filename begins with "~", replace the "~" with the user's home directory
	//(otherwise, the file will not successfully open)
	if ( filename.at(0)=='~' )
		filename = TQDir::homeDirPath() + filename.mid( 1, filename.length() );
	TQFile ccFile( filename );

	if ( ccFile.open( IO_ReadOnly ) ) {
		int iStart(0); //the line number of the first non-header line
		TQStringList errs; //list of error messages 
		TQStringList Columns; //list of data column descriptors in the header

		TQTextStream stream( &ccFile );
		TQStringList lines = TQStringList::split( "\n", stream.read() );

		if ( parseCustomDataHeader( lines, Columns, CatalogName, CatalogPrefix, 
				CatalogColor, CatalogEpoch, iStart, showerrs, errs ) ) {
	
			TQStringList::Iterator it = lines.begin();
			TQStringList::Iterator itEnd  = lines.end();
			it += iStart; //jump ahead past header
		
			for ( uint i=iStart; i < lines.count(); i++ ) {
				TQStringList d = TQStringList::split( " ", lines[i] );
	
				//Now, if one of the columns is the "Name" field, the name may contain spaces.
				//In this case, the name field will need to be surrounded by quotes.  
				//Check for this, and adjust the d list accordingly
				int iname = Columns.findIndex( "Nm" );
				if ( iname >= 0 && d[iname].left(1) == "\"" ) { //multi-word name in quotes
					d[iname] = d[iname].mid(1); //remove leading quote
					//It's possible that the name is one word, but still in quotes
					if ( d[iname].right(1) == "\"" ) {
						d[iname] = d[iname].left( d[iname].length() - 1 );
					} else {
						int iend = iname + 1;
						while ( d[iend].right(1) != "\"" ) {
							d[iname] += " " + d[iend];
							++iend;
						}
						d[iname] += " " + d[iend].left( d[iend].length() - 1 );
	
						//remove the entries from d list that were the multiple words in the name
						for ( int j=iname+1; j<=iend; j++ ) {
							d.remove( d.at(iname + 1) ); //index is *not* j, because next item becomes "iname+1" after remove
						}
					}
				}
	
				if ( d.count() == Columns.count() ) {
					processCustomDataLine( i, d, Columns, CatalogPrefix, objList, showerrs, errs );
				} else {
					if ( showerrs ) errs.append( i18n( "Line %1 does not contain %2 fields.  Skipping it." ).arg( i ).arg( Columns.count() ) );
				}
			}
		}

		if ( objList.count() ) {
			if ( errs.count() > 0 ) { //some data parsed, but there are errs to report
				TQString message( i18n( "Some lines in the custom catalog could not be parsed; see error messages below." ) + "\n" +
												i18n( "To reject the file, press Cancel. " ) +
												i18n( "To accept the file (ignoring unparsed lines), press Accept." ) );
				if ( KMessageBox::warningContinueCancelList( 0, message, errs,
						i18n( "Some Lines in File Were Invalid" ), i18n( "Accept" ) ) != KMessageBox::Continue ) {
					return 0; //User pressed Cancel, return NULL pointer
				}
			}
		} else { //objList.count() == 0
			if ( showerrs ) {
				TQString message( i18n( "No lines could be parsed from the specified file, see error messages below." ) );
				KMessageBox::informationList( 0, message, errs,
						i18n( "No Valid Data Found in File" ) );
			}
			return 0; //no valid catalog data parsed, return NULL pointer
		}

	} else { //Error opening catalog file
		if ( showerrs )
			KMessageBox::sorry( 0, i18n( "Could not open custom data file: %1" ).arg( filename ), 
					i18n( "Error opening file" ) );
		else 
			kdDebug() << i18n( "Could not open custom data file: %1" ).arg( filename ) << endl;
	}

	//Return the catalog
	if ( objList.count() )
		return new CustomCatalog( CatalogName, CatalogPrefix, CatalogColor, CatalogEpoch, objList );
	else
		return 0;
}


bool KStarsData::processCustomDataLine( int lnum, TQStringList d, TQStringList Columns, 
		TQString Prefix, TQPtrList<SkyObject> &objList, bool showerrs, TQStringList &errs ) {

	//object data
	unsigned char iType(0);
	dms RA, Dec;
	float mag(0.0), a(0.0), b(0.0), PA(0.0);
	TQString name(""); TQString lname("");

	for ( uint i=0; i<Columns.count(); i++ ) {
		if ( Columns[i] == "ID" ) 
			name = Prefix + " " + d[i];

		if ( Columns[i] == "Nm" )
			lname = d[i];

		if ( Columns[i] == "RA" ) {
			if ( ! RA.setFromString( d[i], false ) ) {
				if ( showerrs ) 
					errs.append( i18n( "Line %1, field %2: Unable to parse RA value: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}

		if ( Columns[i] == "Dc" ) {
			if ( ! Dec.setFromString( d[i], true ) ) {
				if ( showerrs )
					errs.append( i18n( "Line %1, field %2: Unable to parse Dec value: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}

		if ( Columns[i] == "Tp" ) {
			bool ok(false);
			iType = d[i].toUInt( &ok );
			if ( ok ) {
				if ( iType == 2 || iType > 8 ) {
					if ( showerrs )
						errs.append( i18n( "Line %1, field %2: Invalid object type: %3" )
								.arg(lnum).arg(i).arg(d[i]) +
								i18n( "Must be one of 0, 1, 3, 4, 5, 6, 7, 8." ) );
					return false;
				}
			} else {
				if ( showerrs )
					errs.append( i18n( "Line %1, field %2: Unable to parse Object type: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}

		if ( Columns[i] == "Mg" ) {
			bool ok(false);
			mag = d[i].toFloat( &ok );
			if ( ! ok ) {
				if ( showerrs )
					errs.append( i18n( "Line %1, field %2: Unable to parse Magnitude: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}

		if ( Columns[i] == "Mj" ) {
			bool ok(false);
			a = d[i].toFloat( &ok );
			if ( ! ok ) {
				if ( showerrs )
					errs.append( i18n( "Line %1, field %2: Unable to parse Major Axis: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}

		if ( Columns[i] == "Mn" ) {
			bool ok(false);
			b = d[i].toFloat( &ok );
			if ( ! ok ) {
				if ( showerrs )
					errs.append( i18n( "Line %1, field %2: Unable to parse Minor Axis: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}

		if ( Columns[i] == "PA" ) {
			bool ok(false);
			PA = d[i].toFloat( &ok );
			if ( ! ok ) {
				if ( showerrs )
					errs.append( i18n( "Line %1, field %2: Unable to parse Position Angle: %3" )
							.arg(lnum).arg(i).arg(d[i]) );
				return false;
			}
		}
	}

	if ( iType == 0 ) { //Add a star
		StarObject *o = new StarObject( RA, Dec, mag, lname );
		objList.append( o );
	} else { //Add a deep-sky object
		DeepSkyObject *o = new DeepSkyObject( iType, RA, Dec, mag, 
					name, "", lname, Prefix, a, b, PA );
		objList.append( o );
	}

	return true;
}

bool KStarsData::parseCustomDataHeader( TQStringList lines, TQStringList &Columns, 
			TQString &CatalogName, TQString &CatalogPrefix, TQString &CatalogColor, float &CatalogEpoch,
			int &iStart, bool showerrs, TQStringList &errs ) {

	bool foundDataColumns( false ); //set to true if description of data columns found
	int ncol( 0 );

	TQStringList::Iterator it = lines.begin();
	TQStringList::Iterator itEnd  = lines.end();

	CatalogName = "";
	CatalogPrefix = "";
	CatalogColor = "";
	CatalogEpoch = 0.;
	for ( ; it != itEnd; it++ ) {
		TQString d( *it ); //current data line
		if ( d.left(1) != "#" ) break;  //no longer in header!

		int iname   = d.find( "# Name: " );
		int iprefix = d.find( "# Prefix: " );
		int icolor  = d.find( "# Color: " );
		int iepoch  = d.find( "# Epoch: " );

		if ( iname == 0 ) { //line contains catalog name
			iname = d.find(":")+2;
			if ( CatalogName.isEmpty() ) { 
				CatalogName = d.mid( iname );
			} else { //duplicate name in header
				if ( showerrs )
					errs.append( i18n( "Parsing header: " ) + 
							i18n( "Extra Name field in header: %1.  Will be ignored" ).arg( d.mid(iname) ) );
			}
		} else if ( iprefix == 0 ) { //line contains catalog prefix
			iprefix = d.find(":")+2;
			if ( CatalogPrefix.isEmpty() ) { 
				CatalogPrefix = d.mid( iprefix );
			} else { //duplicate prefix in header
				if ( showerrs )
					errs.append( i18n( "Parsing header: " ) + 
							i18n( "Extra Prefix field in header: %1.  Will be ignored" ).arg( d.mid(iprefix) ) );
			}
		} else if ( icolor == 0 ) { //line contains catalog prefix
			icolor = d.find(":")+2;
			if ( CatalogColor.isEmpty() ) { 
				CatalogColor = d.mid( icolor );
			} else { //duplicate prefix in header
				if ( showerrs )
					errs.append( i18n( "Parsing header: " ) + 
							i18n( "Extra Color field in header: %1.  Will be ignored" ).arg( d.mid(icolor) ) );
			}
		} else if ( iepoch == 0 ) { //line contains catalog epoch
			iepoch = d.find(":")+2;
			if ( CatalogEpoch == 0. ) {
				bool ok( false );
				CatalogEpoch = d.mid( iepoch ).toFloat( &ok );
				if ( !ok ) {
					if ( showerrs )
						errs.append( i18n( "Parsing header: " ) + 
								i18n( "Could not convert Epoch to float: %1.  Using 2000. instead" ).arg( d.mid(iepoch) ) );
					CatalogEpoch = 2000.; //adopt default value
				}
			} else { //duplicate epoch in header
				if ( showerrs )
					errs.append( i18n( "Parsing header: " ) + 
							i18n( "Extra Epoch field in header: %1.  Will be ignored" ).arg( d.mid(iepoch) ) );
			}
		} else if ( ! foundDataColumns ) { //don't try to parse data column descriptors if we already found them
			//Chomp off leading "#" character
			d = d.replace( TQRegExp( "#" ), "" );

			TQStringList fields = TQStringList::split( " ", d ); //split on whitespace

			//we need a copy of the master list of data fields, so we can 
			//remove fields from it as they are encountered in the "fields" list.
			//this allows us to detect duplicate entries
			TQStringList master( KStarsData::CustomColumns ); 

			TQStringList::Iterator itf    = fields.begin();
			TQStringList::Iterator itfEnd = fields.end();

			for ( ; itf != itfEnd; itf++ ) {
				TQString s( *itf );
				if ( master.contains( s ) ) {
					//add the data field
					Columns.append( s );

					// remove the field from the master list and inc the 
					// count of "good" columns (unless field is "Ignore")
					if ( s != "Ig" ) {
						master.remove( master.find( s ) );
						ncol++;
					}
				} else if ( fields.contains( s ) ) { //duplicate field
					fields.append( "Ig" ); //skip this column
					if ( showerrs )
						errs.append( i18n( "Parsing header: " ) + 
								i18n( "Duplicate data field descriptor \"%1\" will be ignored" ).arg( s ) );
				} else { //Invalid field
					fields.append( "Ig" ); //skip this column
					if ( showerrs )
						errs.append( i18n( "Parsing header: " ) + 
								i18n( "Invalid data field descriptor \"%1\" will be ignored" ).arg( s ) );
				}
			}

			if ( ncol ) foundDataColumns = true;
		}
	}

	if ( ! foundDataColumns ) {
		if ( showerrs )
			errs.append( i18n( "Parsing header: " ) + 
					i18n( "No valid column descriptors found.  Exiting" ) );
		return false;
	}

	if ( it == itEnd ) {
		if ( showerrs ) errs.append( i18n( "Parsing header: " ) +
				i18n( "No data lines found after header.  Exiting." ) );
		return false; //fatal error
	} else {
		//Make sure Name, Prefix, Color and Epoch were set
		if ( CatalogName.isEmpty() ) { 
			if ( showerrs ) errs.append( i18n( "Parsing header: " ) +
				i18n( "No Catalog Name specified; setting to \"Custom\"" ) );
			CatalogName = i18n("Custom");
		}
		if ( CatalogPrefix.isEmpty() ) { 
			if ( showerrs ) errs.append( i18n( "Parsing header: " ) +
				i18n( "No Catalog Prefix specified; setting to \"CC\"" ) );
			CatalogPrefix = "CC";
		}
		if ( CatalogColor.isEmpty() ) { 
			if ( showerrs ) errs.append( i18n( "Parsing header: " ) +
				i18n( "No Catalog Color specified; setting to Red" ) );
			CatalogColor = "#CC0000";
		}
		if ( CatalogEpoch == 0. ) { 
			if ( showerrs ) errs.append( i18n( "Parsing header: " ) +
				i18n( "No Catalog Epoch specified; assuming 2000." ) );
			CatalogEpoch = 2000.;
		}

		//the it iterator now points to the first line past the header
		iStart = lines.findIndex( TQString( *it ) );
		return true;
	}
}

bool KStarsData::processCity( TQString& line ) {
	TQString totalLine;
	TQString name, province, country;
	TQStringList fields;
	TimeZoneRule *TZrule;
	bool intCheck = true;
	char latsgn, lngsgn;
	int lngD, lngM, lngS;
	int latD, latM, latS;
	double TZ;
	float lng, lat;

	totalLine = line;

	// separate fields
	fields = TQStringList::split( ":", line );

	for ( unsigned int i=0; i< fields.count(); ++i )
		fields[i] = fields[i].stripWhiteSpace();

	if ( fields.count() < 11 ) {
		kdDebug()<< i18n( "Cities.dat: Ran out of fields.  Line was:" ) <<endl;
		kdDebug()<< totalLine.local8Bit() <<endl;
		return false;
	} else if ( fields.count() < 12 ) {
		// allow old format (without TZ) for mycities.dat
		fields.append("");
		fields.append("--");
	} else if ( fields.count() < 13 ) {
		// Set TZrule to "--"
		fields.append("--");
	}

	name = fields[0];
	province = fields[1];
	country = fields[2];

	latD = fields[3].toInt( &intCheck );
	if ( !intCheck ) {
		kdDebug() << fields[3] << i18n( "\nCities.dat: Bad integer.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	latM = fields[4].toInt( &intCheck );
	if ( !intCheck ) {
		kdDebug() << fields[4] << i18n( "\nCities.dat: Bad integer.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	latS = fields[5].toInt( &intCheck );
	if ( !intCheck ) {
		kdDebug() << fields[5] << i18n( "\nCities.dat: Bad integer.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	TQChar ctemp = fields[6].at(0);
	latsgn = ctemp;
	if (latsgn != 'N' && latsgn != 'S') {
		kdDebug() << latsgn << i18n( "\nCities.dat: Invalid latitude sign.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	lngD = fields[7].toInt( &intCheck );
	if ( !intCheck ) {
		kdDebug() << fields[7] << i18n( "\nCities.dat: Bad integer.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	lngM = fields[8].toInt( &intCheck );
	if ( !intCheck ) {
		kdDebug() << fields[8] << i18n( "\nCities.dat: Bad integer.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	lngS = fields[9].toInt( &intCheck );
	if ( !intCheck ) {
		kdDebug() << fields[9] << i18n( "\nCities.dat: Bad integer.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	ctemp = fields[10].at(0);
	lngsgn = ctemp;
	if (lngsgn != 'E' && lngsgn != 'W') {
		kdDebug() << latsgn << i18n( "\nCities.dat: Invalid longitude sign.  Line was:\n" ) << totalLine << endl;
		return false;
	}

	lat = (float)latD + ((float)latM + (float)latS/60.0)/60.0;
	lng = (float)lngD + ((float)lngM + (float)lngS/60.0)/60.0;

	if ( latsgn == 'S' ) lat *= -1.0;
	if ( lngsgn == 'W' ) lng *= -1.0;

	// find time zone. Use value from Cities.dat if available.
	// otherwise use the old approximation: int(lng/15.0);
	if ( fields[11].isEmpty() || ('x' == fields[11].at(0)) ) {
		TZ = int(lng/15.0);
	} else {
		bool doubleCheck = true;
		TZ = fields[11].toDouble( &doubleCheck);
		if ( !doubleCheck ) {
			kdDebug() << fields[11] << i18n( "\nCities.dat: Bad time zone.  Line was:\n" ) << totalLine << endl;
			return false;
		}
	}

	//last field is the TimeZone Rule ID.
	TZrule = &( Rulebook[ fields[12] ] );

//	if ( fields[12]=="--" )
//		kdDebug() << "Empty rule start month: " << TZrule->StartMonth << endl;
	geoList.append ( new GeoLocation( lng, lat, name, province, country, TZ, TZrule ));  // appends city names to list
	return true;
}

bool KStarsData::readTimeZoneRulebook( void ) {
	TQFile file;
	TQString id;

	if ( KSUtils::openDataFile( file, "TZrules.dat" ) ) {
		TQTextStream stream( &file );

		while ( !stream.eof() ) {
			TQString line = stream.readLine().stripWhiteSpace();
			if ( line.left(1) != "#" && line.length() ) { //ignore commented and blank lines
				TQStringList fields = TQStringList::split( " ", line );
				id = fields[0];
				TQTime stime = TQTime( fields[3].left( fields[3].find(':')).toInt() ,
								fields[3].mid( fields[3].find(':')+1, fields[3].length()).toInt() );
				TQTime rtime = TQTime( fields[6].left( fields[6].find(':')).toInt(),
								fields[6].mid( fields[6].find(':')+1, fields[6].length()).toInt() );

				Rulebook[ id ] = TimeZoneRule( fields[1], fields[2], stime, fields[4], fields[5], rtime );
			}
		}
		return true;
	} else {
		return false;
	}
}

bool KStarsData::readCityData( void ) {
	TQFile file;
	bool citiesFound = false;

// begin new code
	if ( KSUtils::openDataFile( file, "Cities.dat" ) ) {
    KSFileReader fileReader( file ); // close file is included
    while ( fileReader.hasMoreLines() ) {
			citiesFound |= processCity( fileReader.readLine() );
    }
  }
// end new code

	//check for local cities database, but don't require it.
	file.setName( locate( "appdata", "mycities.dat" ) ); //determine filename in local user KDE directory tree.
	if ( file.exists() && file.open( IO_ReadOnly ) ) {
		TQTextStream stream( &file );

  	while ( !stream.eof() ) {
			TQString line = stream.readLine();
			citiesFound |= processCity( line );
		}	// while ( !stream.eof() )
		file.close();
	}	// if ( fileopen() )

	return citiesFound;
}

void KStarsData::setMagnitude( float newMagnitude, bool forceReload ) {
// only reload data if not loaded yet
// if checkDataPumpAction() detects that new magnitude is higher than the
// loaded, it can force a reload
	if ( newMagnitude > maxSetMagnitude || forceReload ) {
		maxSetMagnitude = newMagnitude;  // store new highest magnitude level

		if (reloadingData() == false) {  // if not already reloading data
			source = new FileSource(this, newMagnitude);
			loader = new StarDataSink(this);
			connect(loader, TQ_SIGNAL(done()), this, TQ_SLOT(checkDataPumpAction()));
			connect(loader, TQ_SIGNAL(updateSkymap()), this, TQ_SLOT(updateSkymap()));
			connect(loader, TQ_SIGNAL(clearCache()), this, TQ_SLOT(sendClearCache()));
			// start reloading
			pump = new TQDataPump (source, (TQDataSink*) loader);
		}
	} /*else if ( newMagnitude < maxSetMagnitude ) {
		StarObject *lastStar = starList.last();
		while ( lastStar->mag() > newMagnitude  && starList.count() ) {
			//check if star is named.  If so, remove it from ObjNames
			if ( ! lastStar->name().isEmpty() ) {
				ObjNames.remove( lastStar->name() );
			}
			starList.removeLast();
			lastStar = starList.last();

			//Need to recompute names of objects
			sendClearCache();
		}
	}*/

	// change current magnitude level in KStarsOptions
	Options::setMagLimitDrawStar( newMagnitude );
}

void KStarsData::checkDataPumpAction() {
	// it will set to true if new data should be reloaded
	bool reloadMoreData = false;
	if (source != 0) {
		// check if a new reload must be started
		if (source->magnitude() < maxSetMagnitude) reloadMoreData = true;
		delete source;
		source = 0;
	}
	if (pump != 0) {  // if pump exists
		delete pump;
		pump = 0;
	}
	if (loader != 0) {  // if loader exists
		delete loader;
		loader = 0;
	}
	// If magnitude was changed while reloading data start a new reload of data.
	if (reloadMoreData == true) {
		setMagnitude(maxSetMagnitude, true);
	}
}

bool KStarsData::reloadingData() {
	return ( pump || loader || source );  // true if variables != 0
}

void KStarsData::updateSkymap() {
	emit update();
}

void KStarsData::sendClearCache() {
	emit clearCache();
}

void KStarsData::initialize() {
	if (startupComplete) return;

	initTimer = new TQTimer;
	TQObject::connect(initTimer, TQ_SIGNAL(timeout()), this, TQ_SLOT( slotInitialize() ) );
	initCounter = 0;
	initTimer->start(1);
}

void KStarsData::initError(TQString s, bool required = false) {
	TQString message, caption;
	initTimer->stop();
	if (required) {
		message = i18n( "The file %1 could not be found. "
				"KStars cannot run properly without this file. "
				"To continue loading, place the file in one of the "
				"following locations, then press Retry:\n\n" ).arg( s )
				+ TQString( "\t$(TDEDIR)/share/apps/kstars/%1\n" ).arg( s )
				+ TQString( "\t~/.trinity/share/apps/kstars/%1\n\n" ).arg( s ) 
				+ i18n( "Otherwise, press Cancel to shutdown." );
		caption = i18n( "Critical File Not Found: %1" ).arg( s );
	} else {
		message = i18n( "The file %1 could not be found. "
				"KStars can still run without this file. "
				"However, to avoid seeing this message in the future, you can "
				"place the file in one of the following locations, then press Retry:\n\n" ).arg( s )
				+ TQString( "\t$(TDEDIR)/share/apps/kstars/%1\n" ).arg( s )
				+ TQString( "\t~/.trinity/share/apps/kstars/%1\n\n" ).arg( s )
				+ i18n( "Otherwise, press Cancel to continue loading without this file." ).arg( s );
		caption = i18n( "Non-Critical File Not Found: %1" ).arg( s );
	}

	if ( KMessageBox::warningContinueCancel( 0, message, caption, i18n( "Retry" ) ) == KMessageBox::Continue ) {
		initCounter--;
		initTimer->start(1);
	} else {
		if (required) {
			delete initTimer;
			initTimer = 0L;
			emit initFinished(false);
		} else {
			initTimer->start(1);
		}
	}
}

void KStarsData::slotInitialize() {
	TQFile imFile;
	TQString ImageName;

	kapp->flush(); // flush all paint events before loading data

	switch ( initCounter )
	{
		case 0: //Load Time Zone Rules//
			emit progressText( i18n("Reading Time Zone Rules") );

			if (objects==1) {
				// timezone rules
				if ( !readTimeZoneRulebook( ) )
					initError( "TZrules.dat", true );
			}

			// read INDI hosts file, not required
			readINDIHosts();
			break;

		case 1: //Load Cities//
		{
			if (objects>1) break;

			emit progressText( i18n("Loading City Data") );

			if ( !readCityData( ) )
				initError( "Cities.dat", true );
			
			break;
		}

		case 2: //Load stellar database//

			emit progressText(i18n("Loading Star Data (%1%)" ).arg(0) );
			if ( !readStarData( ) )
				initError( "hipNNN.dat", true );
			if (!readVARData())
				initError( "valaav.dat", false);
			if (!readADVTreeData())
				initError( "advinterface.dat", false);
			break;

		case 3: //Load NGC/IC database and custom catalogs//

			emit progressText( i18n("Loading NGC/IC Data (%1%)" ).arg(0) );
			if ( !readDeepSkyData( ) )
				initError( "ngcicN.dat", true );

			emit progressText( i18n("Loading Custom catalogs" ) );
			readCustomCatalogs( );
//			if ( !readCustomCatalogs( ) )
//				initError( "<custom catalog>", true );
			break;

		case 4: //Load Constellation lines//

			emit progressText( i18n("Loading Constellations" ) );
			if ( !readCLineData( ) )
				initError( "clines.dat", true );
			break;

		case 5: //Load Constellation names//

			emit progressText( i18n("Loading Constellation Names" ) );
			if ( !readCNameData( ) )
				initError( cnameFile, true );
			break;

		case 6: //Load Constellation boundaries//

			emit progressText( i18n("Loading Constellation Boundaries" ) );
			if ( !readCBoundData( ) )
				initError( "cbound.dat", true );
			break;

		case 7: //Load Milky Way//

			emit progressText( i18n("Loading Milky Way" ) );
			if ( !readMWData( ) )
				initError( "mw*.dat", true );
			break;

		case 8: //Initialize the Planets//

			emit progressText( i18n("Creating Planets" ) );
			if (PCat->initialize())
				PCat->addObject( ObjNames );

			jmoons = new JupiterMoons();
			break;

		case 9: //Initialize Asteroids & Comets//

			emit progressText( i18n( "Creating Asteroids and Comets" ) );
			if ( !readAsteroidData() )
				initError( "asteroids.dat", false );
			if ( !readCometData() )
				initError( "comets.dat", false );

			break;

		case 10: //Initialize the Moon//

			emit progressText( i18n("Creating Moon" ) );
			Moon = new KSMoon(this);
			ObjNames.append( Moon );
			Moon->loadData();
			break;

		case 11: //Load Image URLs//

			emit progressText( i18n("Loading Image URLs" ) );
			if ( !readURLData( "image_url.dat", 0 ) ) {
				initError( "image_url.dat", false );
			}
			//if ( !readURLData( "myimage_url.dat", 0 ) ) {
			//Don't do anything if the local file is not found.
			//}
          // doesn't belong here, only temp
             readUserLog();

			break;

		case 12: //Load Information URLs//

			emit progressText( i18n("Loading Information URLs" ) );
			if ( !readURLData( "info_url.dat", 1 ) ) {
				initError( "info_url.dat", false );
			}
			//if ( !readURLData( "myinfo_url.dat", 1 ) ) {
			//Don't do anything if the local file is not found.
			//}
			break;

		default:
			initTimer->stop();
			delete initTimer;
			initTimer = 0L;
			startupComplete = true;
			emit initFinished(true);
			break;
	} // switch ( initCounter )

	initCounter++;
}

void KStarsData::initGuides(KSNumbers *num)
{
	// Define the Celestial Equator
	for ( unsigned int i=0; i<NCIRCLE; ++i ) {
		SkyPoint *o = new SkyPoint( i*24./NCIRCLE, 0.0 );
		o->EquatorialToHorizontal( LST, geo()->lat() );
		Equator.append( o );
	}

  // Define the horizon.
  // Use the celestial Equator as a convenient starting point, but instead of RA and Dec,
  // interpret the coordinates as azimuth and altitude, and then convert to RA, dec.
  // The horizon will be redefined whenever the positions of sky objects are updated.
	dms temp( 0.0 );
	for (SkyPoint *point = Equator.first(); point; point = Equator.next()) {
		double sinlat, coslat, sindec, cosdec, sinAz, cosAz;
		double HARad;
		dms dec, HA, RA, Az;
		Az = dms(*(point->ra()));

		Az.SinCos( sinAz, cosAz );
		geo()->lat()->SinCos( sinlat, coslat );

		dec.setRadians( asin( coslat*cosAz ) );
		dec.SinCos( sindec, cosdec );

		HARad = acos( -1.0*(sinlat*sindec)/(coslat*cosdec) );
		if ( sinAz > 0.0 ) { HARad = 2.0*dms::PI - HARad; }
		HA.setRadians( HARad );
		RA = LST->Degrees() - HA.Degrees();

		SkyPoint *o = new SkyPoint( RA, dec );
		o->setAlt( 0.0 );
		o->setAz( Az );

		Horizon.append( o );

		//Define the Ecliptic (use the same ListIteration; interpret coordinates as Ecliptic long/lat)
		o = new SkyPoint( 0.0, 0.0 );
		o->setFromEcliptic( num->obliquity(), point->ra(), &temp );
		o->EquatorialToHorizontal( LST, geo()->lat() );
		Ecliptic.append( o );
	}
}

void KStarsData::resetToNewDST(const GeoLocation *geo, const bool automaticDSTchange) {
	// reset tzrules data with local time, timezone offset and time direction (forward or backward)
	// force a DST change with option true for 3. parameter
	geo->tzrule()->reset_with_ltime( LTime, geo->TZ0(), TimeRunsForward, automaticDSTchange );
	// reset next DST change time
	setNextDSTChange( geo->tzrule()->nextDSTChange() );
	//reset LTime, because TZoffset has changed
	LTime = geo->UTtoLT( ut() );
}

void KStarsData::updateTime( GeoLocation *geo, SkyMap *skymap, const bool automaticDSTchange ) {
	// sync LTime with the simulation clock
	LTime = geo->UTtoLT( ut() );
	syncLST();
	
	//Only check DST if (1) TZrule is not the empty rule, and (2) if we have crossed
	//the DST change date/time.
	if ( !geo->tzrule()->isEmptyRule() ) {
		if ( TimeRunsForward ) {
			// timedirection is forward
			// DST change happens if current date is bigger than next calculated dst change
			if ( ut() > NextDSTChange ) resetToNewDST(geo, automaticDSTchange);
		} else {
			// timedirection is backward
			// DST change happens if current date is smaller than next calculated dst change
			if ( ut() < NextDSTChange ) resetToNewDST(geo, automaticDSTchange);
		}
	}

	KSNumbers num( ut().djd() );

	bool needNewCoords = false;
	if ( fabs( ut().djd() - LastNumUpdate.djd() ) > 1.0 ) {
		//update time-dependent variables once per day
		needNewCoords = true;
		LastNumUpdate = ut().djd();
	}

	// Update positions of objects, if necessary
	if ( fabs( ut().djd() - LastPlanetUpdate.djd() ) > 0.01 ) {
		LastPlanetUpdate = ut().djd();

		if ( Options::showPlanets() ) PCat->findPosition( &num, geo->lat(), LST );

		//Asteroids
		if ( Options::showPlanets() && Options::showAsteroids() )
			for ( KSAsteroid *ast = asteroidList.first(); ast; ast = asteroidList.next() )
				ast->findPosition( &num, geo->lat(), LST, earth() );

		//Comets
		if ( Options::showPlanets() && Options::showComets() )
			for ( KSComet *com = cometList.first(); com; com = cometList.next() )
				com->findPosition( &num, geo->lat(), LST, earth() );

		//Recompute the Ecliptic
		if ( Options::showEcliptic() ) {
			Ecliptic.clear();

			dms temp(0.0);
			for ( unsigned int i=0; i<Equator.count(); ++i ) {
				SkyPoint *o = new SkyPoint( 0.0, 0.0 );
				o->setFromEcliptic( num.obliquity(), Equator.at(i)->ra(), &temp );
				Ecliptic.append( o );
			}
		}
	}

	// Moon moves ~30 arcmin/hr, so update its position every minute.
	if ( fabs( ut().djd() - LastMoonUpdate.djd() ) > 0.00069444 ) {
		LastMoonUpdate = ut();
		if ( Options::showMoon() ) {
			Moon->findPosition( &num, geo->lat(), LST );
			Moon->findPhase( PCat->planetSun() );
		}

		//for now, update positions of Jupiter's moons here also
		if ( Options::showPlanets() && Options::showJupiter() )
			jmoons->findPosition( &num, (const KSPlanet*)PCat->findByName("Jupiter"), PCat->planetSun() );
	}

	//Update Alt/Az coordinates.  Timescale varies with zoom level
	//If Clock is in Manual Mode, always update. (?)
	if ( fabs( ut().djd() - LastSkyUpdate.djd() ) > 0.25/Options::zoomFactor() || clock()->isManualMode() ) {
		LastSkyUpdate = ut();

		//Recompute Alt, Az coords for all objects
		//Planets
		//This updates trails as well
		PCat->EquatorialToHorizontal( LST, geo->lat() );

		jmoons->EquatorialToHorizontal( LST, geo->lat() );
		if ( Options::showMoon() ) {
			Moon->EquatorialToHorizontal( LST, geo->lat() );
			if ( Moon->hasTrail() ) Moon->updateTrail( LST, geo->lat() );
		}

//		//Planet Trails
//		for( SkyPoint *p = PlanetTrail.first(); p; p = PlanetTrail.next() )
//			p->EquatorialToHorizontal( LST, geo->lat() );

		//Asteroids
		if ( Options::showAsteroids() ) {
			for ( KSAsteroid *ast = asteroidList.first(); ast; ast = asteroidList.next() ) {
				ast->EquatorialToHorizontal( LST, geo->lat() );
				if ( ast->hasTrail() ) ast->updateTrail( LST, geo->lat() );
			}
		}

		//Comets
		if ( Options::showComets() ) {
			for ( KSComet *com = cometList.first(); com; com = cometList.next() ) {
				com->EquatorialToHorizontal( LST, geo->lat() );
				if ( com->hasTrail() ) com->updateTrail( LST, geo->lat() );
			}
		}

		//Stars (need to update if constell. lines or stars are shown)
		if ( Options::showStars() || Options::showCLines() ) {
			// use MINDRAWSTARMAG for calculating constellation lines right
			float mag = Options::magLimitDrawStar();
			if (mag < MINDRAWSTARMAG) mag = MINDRAWSTARMAG;
			for ( StarObject *star = starList.first(); star; star = starList.next() ) {
				if ( star->mag() > mag ) break;
				if (needNewCoords) star->updateCoords( &num );
				star->EquatorialToHorizontal( LST, geo->lat() );
			}
		}

		//Deep-sky objects. Keep lists separated for performance reasons
		if ( Options::showMessier() || Options::showMessierImages() ) {
			for ( SkyObject *o = deepSkyListMessier.first(); o; o = deepSkyListMessier.next() ) {
				if (needNewCoords) o->updateCoords( &num );
				o->EquatorialToHorizontal( LST, geo->lat() );
			}
		}
		if ( Options::showNGC() ) {
			for ( SkyObject *o = deepSkyListNGC.first(); o; o = deepSkyListNGC.next() ) {
				if (needNewCoords) o->updateCoords( &num );
				o->EquatorialToHorizontal( LST, geo->lat() );
			}
		}
		if ( Options::showIC() ) {
			for ( SkyObject *o = deepSkyListIC.first(); o; o = deepSkyListIC.next() ) {
				if (needNewCoords) o->updateCoords( &num );
				o->EquatorialToHorizontal( LST, geo->lat() );
			}
		}
		if ( Options::showOther() ) {
			for ( SkyObject *o = deepSkyListOther.first(); o; o = deepSkyListOther.next() ) {
				if (needNewCoords) o->updateCoords( &num );
				o->EquatorialToHorizontal( LST, geo->lat() );
			}
		}


		//Custom Catalogs
		for ( unsigned int j=0; j< CustomCatalogs.count(); ++j ) {
			TQPtrList<SkyObject> cat = CustomCatalogs.at(j)->objList();
			if ( Options::showCatalog()[j] ) {
                                 for ( SkyObject *o = cat.first(); o; o = cat.next() ) {
					if (needNewCoords) o->updateCoords( &num );
					o->EquatorialToHorizontal( LST, geo->lat() );
				}
			}
		}

		//Milky Way
		if ( Options::showMilkyWay() ) {
			for ( unsigned int j=0; j<11; ++j ) {
				for ( SkyPoint *p = MilkyWay[j].first(); p; p = MilkyWay[j].next() ) {
					if (needNewCoords) p->updateCoords( &num );
					p->EquatorialToHorizontal( LST, geo->lat() );
				}
			}
		}

		//CNames
		if ( Options::showCNames() ) {
			for ( SkyPoint *p = cnameList.first(); p; p = cnameList.next() ) {
				if (needNewCoords) p->updateCoords( &num );
				p->EquatorialToHorizontal( LST, geo->lat() );
			}
		}

		//Constellation Boundaries
		if ( Options::showCBounds() ) {  
			for ( CSegment *seg = csegmentList.first(); seg; seg = csegmentList.next() ) {
				for ( SkyPoint *p = seg->firstNode(); p; p = seg->nextNode() ) {
					if ( needNewCoords ) p->updateCoords( &num );
					p->EquatorialToHorizontal( LST, geo->lat() );
				}
			}
		}
		
		//Celestial Equator
		if ( Options::showEquator() ) {
			for ( SkyPoint *p = Equator.first(); p; p = Equator.next() ) {
				p->EquatorialToHorizontal( LST, geo->lat() );
			}
		}

		//Ecliptic
		if ( Options::showEcliptic() ) {
			for ( SkyPoint *p = Ecliptic.first(); p; p = Ecliptic.next() ) {
				p->EquatorialToHorizontal( LST, geo->lat() );
			}
		}

		//Horizon: different than the others; Alt & Az remain constant, RA, Dec must keep up
		if ( Options::showHorizon() || Options::showGround() ) {
			for ( SkyPoint *p = Horizon.first(); p; p = Horizon.next() ) {
				p->HorizontalToEquatorial( LST, geo->lat() );
			}
		}

		for (SkyObject *o = INDITelescopeList.first(); o; o = INDITelescopeList.next())
			o->EquatorialToHorizontal(LST, geo->lat());

		//Update focus
		skymap->updateFocus();

		if ( clock()->isManualMode() )
			TQTimer::singleShot( 0, skymap, TQ_SLOT( forceUpdateNow() ) );
		else skymap->forceUpdate();
	}
}

void KStarsData::setTimeDirection( float scale ) {
	TimeRunsForward = ( scale < 0 ? false : true );
}

void KStarsData::setFullTimeUpdate() {
	LastSkyUpdate.setDJD(    (long double)INVALID_DAY );
	LastPlanetUpdate.setDJD( (long double)INVALID_DAY );
	LastMoonUpdate.setDJD(   (long double)INVALID_DAY );
	LastNumUpdate.setDJD(    (long double)INVALID_DAY );
}

void KStarsData::setLocationFromOptions() {
	TQMap<TQString, TimeZoneRule>::Iterator it = Rulebook.find( Options::dST() );
	setLocation( GeoLocation ( Options::longitude(), Options::latitude(), 
			Options::cityName(), Options::provinceName(), Options::countryName(), 
			Options::timeZone(), &(it.data()), 4, Options::elevation() ) );
}

void KStarsData::setLocation( const GeoLocation &l ) {
 	GeoLocation g( l );
	if ( g.lat()->Degrees() >= 90.0 ) g.setLat( 89.99 );
	if ( g.lat()->Degrees() <= -90.0 ) g.setLat( -89.99 );

	Geo = g;

	//store data in the Options objects
	Options::setCityName( g.name() );
	Options::setProvinceName( g.province() );
	Options::setCountryName( g.country() );
	Options::setTimeZone( g.TZ0() );
	Options::setElevation( g.height() );
	Options::setLongitude( g.lng()->Degrees() );
	Options::setLatitude( g.lat()->Degrees() );
}

void KStarsData::syncLST() {
	LST->set( geo()->GSTtoLST( ut().gst() ) );
}

void KStarsData::changeDateTime( const KStarsDateTime &newDate ) {
	//Turn off animated slews for the next time step.
	setSnapNextFocus();
	
	clock()->setUTC( newDate );
	LTime = geo()->UTtoLT( ut() );
	//set local sideral time
	syncLST();
	
	//Make sure Numbers, Moon, planets, and sky objects are updated immediately
	setFullTimeUpdate();

	// reset tzrules data with new local time and time direction (forward or backward)
	geo()->tzrule()->reset_with_ltime(LTime, geo()->TZ0(), isTimeRunningForward() );

	// reset next dst change time
	setNextDSTChange( geo()->tzrule()->nextDSTChange() );
}

SkyObject* KStarsData::objectNamed( const TQString &name ) {
	if ( (name== "star") || (name== "nothing") || name.isEmpty() ) return NULL;
	if ( name== Moon->name() ) return Moon;

	SkyObject *so = PCat->findByName(name);

	if (so != 0)
		return so;

	//Stars
	for ( unsigned int i=0; i<starList.count(); ++i ) {
		if ( name==starList.at(i)->name() ) return starList.at(i);
	}

	//Deep sky objects
	for ( unsigned int i=0; i<deepSkyListMessier.count(); ++i ) {
		if ( name==deepSkyListMessier.at(i)->name() ) return deepSkyListMessier.at(i);
	}
	for ( unsigned int i=0; i<deepSkyListNGC.count(); ++i ) {
		if ( name==deepSkyListNGC.at(i)->name() ) return deepSkyListNGC.at(i);
	}
	for ( unsigned int i=0; i<deepSkyListIC.count(); ++i ) {
		if ( name==deepSkyListIC.at(i)->name() ) return deepSkyListIC.at(i);
	}
	for ( unsigned int i=0; i<deepSkyListOther.count(); ++i ) {
		if ( name==deepSkyListOther.at(i)->name() ) return deepSkyListOther.at(i);
	}

	//Constellations
	for ( unsigned int i=0; i<cnameList.count(); ++i ) {
		if ( name==cnameList.at(i)->name() ) return cnameList.at(i);
	}

	//Still no match.  Try interpreting the string as a genetive star name 
	//(with ascii characters instead of a Greek letter)
	for ( unsigned int i=0; i<starList.count(); ++i ) {
		if ( name==starList.at(i)->gname( false ) ) return starList.at(i);
	}

       //Custom catalogs. 
        for ( unsigned int i=0; i<CustomCatalogs.count(); ++i ) {
                TQPtrList<SkyObject> custCatObjs = CustomCatalogs.at(i)->objList();
                for ( unsigned int j = 0; j < custCatObjs.count(); ++j ) {
                        if ( name==custCatObjs.at(j)->name() ) return custCatObjs.at(j);
                }
        }

	//reach here only if argument is not matched
	return NULL;
}

//"pseudo-execute" a shell script, ignoring all interactive aspects.  Just use
//the portions of the script that change the state of the program.  This is only
//used for image-dump mode, where the GUI is not running.  So, some things (such as
//waitForKey()) don't make sense and should be ignored.
//also, even functions that do make sense in this context have aspects that should
//be modified or ignored.  For example, we don't need to call slotCenter() on recentering
//commands, just setDestination().  (sltoCenter() does additional things that we dont need).
bool KStarsData::executeScript( const TQString &scriptname, SkyMap *map ) {
	int cmdCount(0);

	TQFile f( scriptname );
	if ( !f.open( IO_ReadOnly) ) {
		kdDebug() << i18n( "Could not open file %1" ).arg( f.name() ) << endl;
		return false;
	}

	TQTextStream istream(&f);
	while ( ! istream.eof() ) {
		TQString line = istream.readLine();

		//found a dcop line
		if ( line.left(4) == "dcop" ) {
			line = line.mid( 20 );  //strip away leading characters
			TQStringList fn = TQStringList::split( " ", line );

			if ( fn[0] == "lookTowards" && fn.count() >= 2 ) {
				double az(-1.0);
				TQString arg = fn[1].lower();
				if ( arg == "n"  || arg == "north" )     az =   0.0;
				if ( arg == "ne" || arg == "northeast" ) az =  45.0;
				if ( arg == "e"  || arg == "east" )      az =  90.0;
				if ( arg == "se" || arg == "southeast" ) az = 135.0;
				if ( arg == "s"  || arg == "south" )     az = 180.0;
				if ( arg == "sw" || arg == "southwest" ) az = 225.0;
				if ( arg == "w"  || arg == "west" )      az = 270.0;
				if ( arg == "nw" || arg == "northwest" ) az = 335.0;
				if ( az >= 0.0 ) { 
					map->setFocusAltAz( 15.0, az ); 
					cmdCount++; 
					map->setDestination( map->focus() );
				}

				if ( arg == "z" || arg == "zenith" ) {
					map->setFocusAltAz( 90.0, map->focus()->az()->Degrees() );
					cmdCount++;
					map->setDestination( map->focus() );
				}

				//try a named object.  name is everything after the first word (which is 'lookTowards')
				fn.remove( fn.first() );
				SkyObject *target = objectNamed( fn.join( " " ) );
				if ( target ) { map->setFocus( target ); cmdCount++; }

			} else if ( fn[0] == "setRaDec" && fn.count() == 3 ) {
				bool ok( false );
				dms r(0.0), d(0.0);

				ok = r.setFromString( fn[1], false ); //assume angle in hours
				if ( ok ) ok = d.setFromString( fn[2], true );  //assume angle in degrees
				if ( ok ) {
					map->setFocus( r, d );
					cmdCount++;
				}

			} else if ( fn[0] == "setAltAz" && fn.count() == 3 ) {
				bool ok( false );
				dms az(0.0), alt(0.0);

				ok = alt.setFromString( fn[1] );
				if ( ok ) ok = az.setFromString( fn[2] );
				if ( ok ) {
					map->setFocusAltAz( alt, az );
					cmdCount++;
				}

			} else if ( fn[0] == "zoom" && fn.count() == 2 ) {
				bool ok(false);
				double z = fn[1].toDouble(&ok);
				if ( ok ) {
					if ( z > MAXZOOM ) z = MAXZOOM;
					if ( z < MINZOOM ) z = MINZOOM;
					Options::setZoomFactor( z );
					cmdCount++;
				}

			} else if ( fn[0] == "zoomIn" ) {
				if ( Options::zoomFactor() < MAXZOOM ) {
					Options::setZoomFactor( Options::zoomFactor() * DZOOM );
					cmdCount++;
				}
			} else if ( fn[0] == "zoomOut" ) {
				if ( Options::zoomFactor() > MINZOOM ) {
					Options::setZoomFactor( Options::zoomFactor() / DZOOM );
					cmdCount++;
				}
			} else if ( fn[0] == "defaultZoom" ) {
				Options::setZoomFactor( DEFAULTZOOM );
				cmdCount++;
			} else if ( fn[0] == "setLocalTime" && fn.count() == 7 ) {
				bool ok(false);
				int yr(0), mth(0), day(0) ,hr(0), min(0), sec(0);
				yr = fn[1].toInt(&ok);
				if ( ok ) mth = fn[2].toInt(&ok);
				if ( ok ) day = fn[3].toInt(&ok);
				if ( ok ) hr  = fn[4].toInt(&ok);
				if ( ok ) min = fn[5].toInt(&ok);
				if ( ok ) sec = fn[6].toInt(&ok);
				if ( ok ) {
					changeDateTime( geo()->LTtoUT( KStarsDateTime( ExtDate(yr, mth, day), TQTime(hr,min,sec) ) ) );
					cmdCount++;
				} else {
					kdWarning() << i18n( "Could not set time: %1 / %2 / %3 ; %4:%5:%6" )
						.arg(day).arg(mth).arg(yr).arg(hr).arg(min).arg(sec) << endl;
				}
			} else if ( fn[0] == "changeViewOption" && fn.count() == 3 ) {
				bool bOk(false), nOk(false), dOk(false);

				//parse bool value
				bool bVal(false);
				if ( fn[2].lower() == "true" ) { bOk = true; bVal = true; }
				if ( fn[2].lower() == "false" ) { bOk = true; bVal = false; }
				if ( fn[2] == "1" ) { bOk = true; bVal = true; }
				if ( fn[2] == "0" ) { bOk = true; bVal = false; }

				//parse int value
				int nVal = fn[2].toInt( &nOk );

				//parse double value
				double dVal = fn[2].toDouble( &dOk );

				if ( fn[1] == "FOVName"                ) { Options::setFOVName(       fn[2] ); cmdCount++; }
				if ( fn[1] == "FOVSize"         && dOk ) { Options::setFOVSize( (float)dVal ); cmdCount++; }
				if ( fn[1] == "FOVShape"        && nOk ) { Options::setFOVShape(       nVal ); cmdCount++; }
				if ( fn[1] == "FOVColor"               ) { Options::setFOVColor(      fn[2] ); cmdCount++; }
				if ( fn[1] == "ShowStars"         && bOk ) { Options::setShowStars(    bVal ); cmdCount++; }
				if ( fn[1] == "ShowMessier"        && bOk ) { Options::setShowMessier( bVal ); cmdCount++; }
				if ( fn[1] == "ShowMessierImages"  && bOk ) { Options::setShowMessierImages( bVal ); cmdCount++; }
				if ( fn[1] == "ShowCLines"      && bOk ) { Options::setShowCLines(   bVal ); cmdCount++; }
				if ( fn[1] == "ShowCNames"      && bOk ) { Options::setShowCNames(   bVal ); cmdCount++; }
				if ( fn[1] == "ShowNGC"         && bOk ) { Options::setShowNGC(      bVal ); cmdCount++; }
				if ( fn[1] == "ShowIC"          && bOk ) { Options::setShowIC(       bVal ); cmdCount++; }
				if ( fn[1] == "ShowMilkyWay"    && bOk ) { Options::setShowMilkyWay( bVal ); cmdCount++; }
				if ( fn[1] == "ShowGrid"        && bOk ) { Options::setShowGrid(     bVal ); cmdCount++; }
				if ( fn[1] == "ShowEquator"     && bOk ) { Options::setShowEquator(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowEcliptic"    && bOk ) { Options::setShowEcliptic( bVal ); cmdCount++; }
				if ( fn[1] == "ShowHorizon"     && bOk ) { Options::setShowHorizon(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowGround"      && bOk ) { Options::setShowGround(   bVal ); cmdCount++; }
				if ( fn[1] == "ShowSun"         && bOk ) { Options::setShowSun(      bVal ); cmdCount++; }
				if ( fn[1] == "ShowMoon"        && bOk ) { Options::setShowMoon(     bVal ); cmdCount++; }
				if ( fn[1] == "ShowMercury"     && bOk ) { Options::setShowMercury(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowVenus"       && bOk ) { Options::setShowVenus(    bVal ); cmdCount++; }
				if ( fn[1] == "ShowMars"        && bOk ) { Options::setShowMars(     bVal ); cmdCount++; }
				if ( fn[1] == "ShowJupiter"     && bOk ) { Options::setShowJupiter(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowSaturn"      && bOk ) { Options::setShowSaturn(   bVal ); cmdCount++; }
				if ( fn[1] == "ShowUranus"      && bOk ) { Options::setShowUranus(   bVal ); cmdCount++; }
				if ( fn[1] == "ShowNeptune"     && bOk ) { Options::setShowNeptune(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowPluto"       && bOk ) { Options::setShowPluto(    bVal ); cmdCount++; }
				if ( fn[1] == "ShowAsteroids"   && bOk ) { Options::setShowAsteroids( bVal ); cmdCount++; }
				if ( fn[1] == "ShowComets"      && bOk ) { Options::setShowComets(   bVal ); cmdCount++; }
				if ( fn[1] == "ShowPlanets"     && bOk ) { Options::setShowPlanets(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowDeepSky"     && bOk ) { Options::setShowDeepSky(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowStarNames"      && bOk ) { Options::setShowStarNames(      bVal ); cmdCount++; }
				if ( fn[1] == "ShowStarMagnitudes" && bOk ) { Options::setShowStarMagnitudes( bVal ); cmdCount++; }
				if ( fn[1] == "ShowAsteroidNames"  && bOk ) { Options::setShowAsteroidNames(  bVal ); cmdCount++; }
				if ( fn[1] == "ShowCometNames"     && bOk ) { Options::setShowCometNames(     bVal ); cmdCount++; }
				if ( fn[1] == "ShowPlanetNames"    && bOk ) { Options::setShowPlanetNames(    bVal ); cmdCount++; }
				if ( fn[1] == "ShowPlanetImages"   && bOk ) { Options::setShowPlanetImages(   bVal ); cmdCount++; }

				if ( fn[1] == "UseAltAz"         && bOk ) { Options::setUseAltAz(      bVal ); cmdCount++; }
				if ( fn[1] == "UseRefraction"    && bOk ) { Options::setUseRefraction( bVal ); cmdCount++; }
				if ( fn[1] == "UseAutoLabel"     && bOk ) { Options::setUseAutoLabel(  bVal ); cmdCount++; }
				if ( fn[1] == "UseAutoTrail"     && bOk ) { Options::setUseAutoTrail(  bVal ); cmdCount++; }
				if ( fn[1] == "UseAnimatedSlewing"   && bOk ) { Options::setUseAnimatedSlewing( bVal ); cmdCount++; }
				if ( fn[1] == "FadePlanetTrails" && bOk ) { Options::setFadePlanetTrails( bVal ); cmdCount++; }
				if ( fn[1] == "SlewTimeScale"    && dOk ) { Options::setSlewTimeScale(    dVal ); cmdCount++; }
				if ( fn[1] == "ZoomFactor"       && dOk ) { Options::setZoomFactor(       dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitDrawStar"     && dOk ) { Options::setMagLimitDrawStar( dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitDrawStarZoomOut" && dOk ) { Options::setMagLimitDrawStarZoomOut( dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitDrawDeepSky"     && dOk ) { Options::setMagLimitDrawDeepSky( dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitDrawDeepSkyZoomOut" && dOk ) { Options::setMagLimitDrawDeepSkyZoomOut( dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitDrawStarInfo" && dOk ) { Options::setMagLimitDrawStarInfo( dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitHideStar"     && dOk ) { Options::setMagLimitHideStar(     dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitAsteroid"     && dOk ) { Options::setMagLimitAsteroid(     dVal ); cmdCount++; }
				if ( fn[1] == "MagLimitAsteroidName" && dOk ) { Options::setMagLimitAsteroidName( dVal ); cmdCount++; }
				if ( fn[1] == "MaxRadCometName"      && dOk ) { Options::setMaxRadCometName(      dVal ); cmdCount++; }

				//these three are a "radio group"
				if ( fn[1] == "UseLatinConstellationNames" && bOk ) {
					Options::setUseLatinConstellNames( true );
					Options::setUseLocalConstellNames( false );
					Options::setUseAbbrevConstellNames( false );
					cmdCount++;
				}
				if ( fn[1] == "UseLocalConstellationNames" && bOk ) {
					Options::setUseLatinConstellNames( false );
					Options::setUseLocalConstellNames( true );
					Options::setUseAbbrevConstellNames( false );
					cmdCount++;
				}
				if ( fn[1] == "UseAbbrevConstellationNames" && bOk ) {
					Options::setUseLatinConstellNames( false );
					Options::setUseLocalConstellNames( false );
					Options::setUseAbbrevConstellNames( true );
					cmdCount++;
				}
			} else if ( fn[0] == "setGeoLocation" && ( fn.count() == 3 || fn.count() == 4 ) ) {
				TQString city( fn[1] ), province( "" ), country( fn[2] );
				if ( fn.count() == 4 ) {
					province = fn[2];
					country = fn[3];
				}

				bool cityFound( false );
				for (GeoLocation *loc = geoList.first(); loc; loc = geoList.next()) {
					if ( loc->translatedName() == city &&
								( province.isEmpty() || loc->translatedProvince() == province ) &&
								loc->translatedCountry() == country ) {

						cityFound = true;
						setLocation( *loc );
						cmdCount++;
						break;
					}
				}

				if ( !cityFound )
					kdWarning() << i18n( "Could not set location named %1, %2, %3" ).arg(city).arg(province).arg(country) << endl;
			}
		}
	}  //end while

	if ( cmdCount ) return true;
	return false;
}

void KStarsData::appendTelescopeObject(SkyObject * object)
{
  INDITelescopeList.append(object);
}

void KStarsData::saveTimeBoxShaded( bool b ) { Options::setShadeTimeBox( b ); }
void KStarsData::saveGeoBoxShaded( bool b ) { Options::setShadeGeoBox( b ); }
void KStarsData::saveFocusBoxShaded( bool b ) { Options::setShadeFocusBox( b ); }
void KStarsData::saveTimeBoxPos( TQPoint p ) { Options::setPositionTimeBox( p ); }
void KStarsData::saveGeoBoxPos( TQPoint p ) { Options::setPositionGeoBox( p ); }
void KStarsData::saveFocusBoxPos( TQPoint p ) { Options::setPositionFocusBox( p ); }

#include "kstarsdata.moc"
