//===================================================================================
//                                                                  FFCalMOD_v20d.mq4
//
// **********************************************************************************
// LICENSING:  This is free, open source software, licensed under
//             Version 2 of the GNU General Public License (GPL).
//
// In particular, this means that distribution of this software in a binary
// format, e.g. as compiled in as part of a .ex4 format, must be accompanied
// by the non-obfuscated source code of both this file, AND the .mq4 source
// files which it is compiled with, or you must make such files available at
// no charge to binary recipients. If you do not agree with such terms you
// must not use this code.  Detailed terms of the GPL are widely available
// on the Internet.  The Library GPL (LGPL) was intentionally not used,
// therefore the source code of files which link to this are subject to
// terms of the GPL if binaries made from them are publicly distributed or
// sold.
//
// ANY USE OF THIS CODE NOT CONFORMING TO THIS LICENSE MUST FIRST RECEIVE
// PRIOR AUTHORIZATION FROM THE AUTHOR(S).  ANY COMMERCIAL USE MUST FIRST
// OBTAIN A COMMERCIAL LICENSE FROM THE AUTHOR(S).
// **********************************************************************************
//	
//	This is a FORK of the original work done by:
//  * Derk Wehler (derkwehler@gmail.com)
//  * Paul Hampton-Smith
//  * Mike Nguyen
//  * Fai Hatena
// And many others who have contributed to this code
//
// Original code can be found at:
// http://www.forexfactory.com/showthread.php?t=19293
//
// **********************************************************************************
//
// This "indicator" calls DLLs to fetch a special XML file from the 
// ForexFactory web site.  It then parses it and writes it out as a .CSV 
// file, which it places in the folder: experts/files so that IsNewsTime() 
// can use that file to tell if it is near announcement time.
//
//=============================================================================
//
// Changes from the original version (FFCal v20):
//
//	* Related Calencar functions externalized to library. This library is easily usable from an EA
// * The indicator / funtions have been abstracted from the source used (ForexFactory or DailyFX)
// * Code has been reduced. Redundant / similar functions have been unified.
// * Additional graphic capabilities added (future events lines, coloring of vertical lines, ...)
// * Backtesting capabilities added.  The indicator can instruct the library to use an External Time 
//	  reference. This reference can be easily provided by the EA undertest adding the following line
//	  of code at the beginning of the start() function:
//   		if(IsTesting()) GlobalVariableSet("_TimeReference", TimeCurrent());
//	* For ForexFactory XML files are kept on a permanent basis with different name.
// * Many other changes ...

#property copyright "Copyright � 2006, Derk Wehler"
#property link      "http://www.forexfactory.com"

#property indicator_chart_window
#property indicator_buffers 3

#include "../include/LibFFCal.mqh"

#define EA_NAME	"FFCalv20d"

//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================

#define			MAX_SIMULTANEOUS_EVENTS		4

extern string	__events_selection__			= "--- EVENTS SELECTION ---";
extern bool 	SelectHighImpactEvents 		= true;
extern bool 	SelectMediumImpactEvents 	= true;
extern bool 	SelectLowImpactEvents 		= false;   //true;
extern bool 	SelectSpeaksEvents 			= true; 		// news items with "Speaks" in them have different characteristics

extern string	__symbols_selection__		= "--- SYMBOL SELECTION ---";
extern bool		ReportAllForAll				= true;		// By default is only shows news related with the chart's pair
extern bool		ReportAllForUSD				= false;
extern bool    ReportAllForEUR   			= false;
extern bool    ReportAllForGBP   			= false;
extern bool    ReportAllForNZD   			= false;		// added and tested by a1ra, seems working OK.
extern bool    ReportAllForJPY   			= false;
extern bool    ReportAllForAUD   			= false;
extern bool    ReportAllForCHF   			= false;
extern bool    ReportAllForCAD   			= false;
extern bool    ReportAllForCNY   			= false;

extern string  __dashboard_configuration_	= "--- DASHBOARD CONFIGURATION ---";
extern bool		NewsDashBoardShow				= true;			// Choose if we show the Dashboard.
extern int		NewsDashboardCorner 			= 2;				// Choose which corner to place the Dashboard (0=Upper Left, 1=Upper Right, 2=lower left , 3=lower right)
extern int		NewsDashboardEvents			= 4;				// Number of events that will be showed at the Dashboard
extern bool		NewsDashboardSummary			= true;			// If TRUE it will only draw one line per selected event.
extern color 	NewsDashboardTitleColor 	= Gold;       
extern color 	NewsDashboardTextColor 		= White;
extern int 		NewsDashboardTextSize 		= 10;

extern string  __events_color_				= "--- EVENTS COLORS ---";
extern color 	ColorImpactHigh      		= Red;			// Color of description / vertical lines if IMPACT is HIGH
extern color 	ColorImpactMedium       	= Orange;		// Color of description / vertical lines if IMPACT is MEDIUM
extern color 	ColorImpactLow       		= Khaki;			// Color of description / vertical lines if IMPACT is LOW
extern color	ColorImpactUndefined 		= Aqua;			// Color of our vertical news line, if no IMPACT has been defined
extern color	ColorImpactOld					= Gray;			// Color or description is event is OLD.

extern string  __vertical_lines__			= "--- CHART VERTICAL LINES ---";
extern bool		LineShowOldEvents				= true;			// Draw vertical lines of past events
extern bool		LineShowFutureEvents			= true;			// Pending!!!! Draw vertical lines of future events.
extern color	LineTextColor 					= LightGray;	// Color of our vertical text color 
extern int		LineTextSize 					= 8;				// Color of our vertical text
extern int		LineTextLeftShift 			= 900;			// How far away to the left of the line we want to place our vertical news text
extern int		LineTextRightShift 			= 900;			// How far away to the left of the line we want to place our vertical news text
extern int		LineTextVerticalShift 		= 21;				// How far away below the ask line we want to place our vertical news text

extern string  __alerts__						= "--- ALERTS ---";
extern int		AlertXMinutesBefore				= 0;				// Set to -1 for no Alert
extern int		AlertYMinutesBefore				= 240;			// Set to -1 for no Alert

extern string  __configuration__				= "--- ADDITIONAL CONFIGURATION ---";
extern bool		AutoGMTZones					= false;
extern int		LocalGMTZone			   	= 1;      // GTM+X
extern int		BrokerGMTZone					= 2;
// extern bool		AllowWebUpdates			= true;			// Set this to false when using in another EA or Chart, so that the multiple instances of the indicator dont fight with each other
extern bool		IsEA_Call						= false;
extern bool		UseDailyFXAsSource			= true;
extern bool 	UseGlobalTimeReference 		= false;

double 		ExtMapBuffer0[];	// Contains (minutes until) each news event
double 		ExtMapBuffer1[];	// Contains only most recent and next news event ([0] & [1])
double 		ExtMapBuffer2[];	// Contains impact value for most recent and next news event

int			EventsArray[1000];
int 	   	minsTillNews; 
int			minutesTillNextEvent;
int			indexOfNextEvent;
static int	previousMinute = -1;
datetime 	LastTimeAlert1 = 0;	// Used to make sure we only draw something once per annoucement. Added by MN
int 		  	globalNewsSelectedTotal = 0;
int		  	globalNewsAllTotal = 0;

string 		objectPrefix = "CA*";

int init() {
	// Buffers definitions
	SetIndexStyle(0, DRAW_NONE);
	SetIndexBuffer(0, ExtMapBuffer0);
	SetIndexStyle(1, DRAW_NONE);
	SetIndexBuffer(1, ExtMapBuffer1);
	SetIndexStyle(1, DRAW_NONE);
	SetIndexBuffer(2, ExtMapBuffer2);

	IndicatorShortName("FFCalv20d");
	
	// for(int i = 0; i < ObjectsTotal(); i++) {
	//	string objectName	= ObjectName(i);
	//	if (StringFind(objectName, objectPrefix, 0) == 0)
	//		ObjectDelete(objectName);
	// }

	FFCalSetTimeReference (UseGlobalTimeReference); 
	FFCalSetTimeOffset (AutoGMTZones, LocalGMTZone, BrokerGMTZone);
   FFCalInitLibrary ();
	
	return(0);
}


int deinit() {
	int i;

	// Cycle through all the Objects looking for the Object Prefix
	for(i = ObjectsTotal() - 1; i >= 0; i--) {
		string objectName	= ObjectName(i);
		if (StringFind(objectName, objectPrefix) == 0)
			ObjectDelete(objectName);
	}
	
	FFCalDeinitLibrary();
	
	return(0);
}

int start() {
	int i;
	bool skipThisEvent;
	datetime newsTimeGMT;
      
	// check to make sure we are connected, otherwise exit. Added by MN
	if (!IsConnected()) {
		Print("News Indicator is disabled because NO CONNECTION to Broker!");
		return(0);
	}
					
	// Perform remaining checks once per minute
	if (!IsEA_Call && TimeMinute(AltTimeLocal()) == previousMinute)
		return (true);
		
	previousMinute = TimeMinute(AltTimeLocal());
	//	Print("FFCal NEW MINUTE...Refreshing News from XML file...");

	// Init the buffer array to zero just in case
	ArrayInitialize(ExtMapBuffer0, 0);
	ArrayInitialize(ExtMapBuffer1, 0);
	
	FFCalRefreshEvents (UseDailyFXAsSource);

	// Get the currency pair, and split it into the two countries
	string pair = Symbol();
	string country1 = StringSubstr(pair, 0, 3);
	string country2 = StringSubstr(pair, 3, 3);

	// Parse the loaded events looking for an event to report
	minutesTillNextEvent = 10080;	// (a week)
	globalNewsSelectedTotal = 0;

	for (int index = 0; index < FFCalGetTotalEvents(); index++)	{
		skipThisEvent = false;
		
		string eventData[8], nexteventData[8];
		FFCalGetEvent (index, eventData);
		
		// Test against filters that define whether we want to skip the selected announcement 
		if (country1 != eventData[FFCAL_COUNTRY] && country2 != eventData[FFCAL_COUNTRY] &&
				((!ReportAllForUSD || eventData[FFCAL_COUNTRY] != "USD") && 
				(!ReportAllForEUR || eventData[FFCAL_COUNTRY] != "EUR") && 
				(!ReportAllForGBP || eventData[FFCAL_COUNTRY] != "GBP") && 
				(!ReportAllForNZD || eventData[FFCAL_COUNTRY] != "NZD") && 
				(!ReportAllForJPY || eventData[FFCAL_COUNTRY] != "JPY") && 
				(!ReportAllForAUD || eventData[FFCAL_COUNTRY] != "AUD") && 
				(!ReportAllForCAD || eventData[FFCAL_COUNTRY] != "CAD") && 
				(!ReportAllForCHF || eventData[FFCAL_COUNTRY] != "CHF") && 
				(!ReportAllForCNY || eventData[FFCAL_COUNTRY] != "CNY")))
			skipThisEvent = true;
		if (ReportAllForAll)
			skipThisEvent = false;
		if (!SelectHighImpactEvents && eventData[FFCAL_IMPACT] == "High") 
			skipThisEvent = true;
		if (!SelectMediumImpactEvents && eventData[FFCAL_IMPACT] == "Medium") 
			skipThisEvent = true;
		if (!SelectLowImpactEvents && eventData[FFCAL_IMPACT] == "Low") 
			skipThisEvent = true;
		if (!SelectSpeaksEvents && (StringFind(eventData[FFCAL_TITLE], "speaks") != -1 || 
								StringFind(eventData[FFCAL_TITLE], "Speaks") != -1) ) 
			skipThisEvent = true;
		if (eventData[FFCAL_TIME] == "All Day" || 
			 eventData[FFCAL_TIME] == "Tentative" ||
			 eventData[FFCAL_TIME] == "") 
			skipThisEvent = true;
	   
		// If this event is not skipped, then log it into the draw buffers
		if (!skipThisEvent) {   
			EventsArray[globalNewsSelectedTotal] = index;
			
			// Print(EA_NAME, "EVENT : Title: ", eventData[FFCAL_TITLE],
			//					" Country: ", eventData[FFCAL_COUNTRY], 
			//					" Date: ", eventData[FFCAL_DATE], 
			//					" Time: ", eventData[FFCAL_TIME],
			//					" Impact: ", eventData[FFCAL_IMPACT],
			//					" Forecast: ", eventData[FFCAL_FORECAST], 
			//					" Previous: ", eventData[FFCAL_PREVIOUS], 
			//					" GMT: ", eventData[FFCAL_GMT],
			//					" NOW: ", TimeToStr(AltTimeCurrent() - TimeGMT(), TIME_MINUTES));	
			
			globalNewsSelectedTotal++;
		}
	}


	for (index = 0; index < globalNewsSelectedTotal; index++)	{
		FFCalGetEvent (EventsArray[index], eventData);
		
		// First, convert the announcement time to seconds (in GMT)
		newsTimeGMT = StrToTime(eventData[FFCAL_GMT]);
						
		// Now calculate the minutes until this announcement (may be negative).
		// minsTillNews = (newsTimeGMT - LocalTimeGMT()) / 60;
		minsTillNews = (newsTimeGMT - TimeLocalToGMT(AltTimeLocal())) / 60;
								
		// This "if" section added by MN
		// If minsTillNews is zero, then it's the time our news is hit
		if (	((minsTillNews == 0) && (LastTimeAlert1 != newsTimeGMT)) ||
				(!IsEA_Call && LineShowOldEvents && minsTillNews < 0)    ||
				(!IsEA_Call && LineShowFutureEvents && minsTillNews > 0) ){
			DisplayVerticalNews(EventsArray[index], 0); 
			for (int shift = 1; shift < MAX_SIMULTANEOUS_EVENTS; shift++) {
				FFCalGetEvent (EventsArray[index + shift], nexteventData);
				if (eventData[FFCAL_TIME] == nexteventData[FFCAL_TIME])
					DisplayVerticalNews(EventsArray[index + shift], shift);
			}
			// only draw once per announcement
			if ((minsTillNews == 0) && (LastTimeAlert1 != newsTimeGMT))
				LastTimeAlert1 = newsTimeGMT;
		}
			
		// Keep track of the most recent news announcement.
		// Do that by saving each one until we get to the 
		// first annoucement that isn't in the past; i.e.
		// minsTillNews > 0.  Then, keep this one instead for
		// display, but only once the minutes until the next 
		// news is SMALLER than the minutes since the last.
		if (minsTillNews < 0 || MathAbs(minutesTillNextEvent) > minsTillNews) {
				indexOfNextEvent = index;
				minutesTillNextEvent	= minsTillNews;
		}
						
		// Do alert if user has enabled
		if (AlertXMinutesBefore != -1 && minsTillNews == AlertXMinutesBefore)
				Alert(AlertXMinutesBefore, " minutes until news for ", pair, ": ", eventData[FFCAL_TITLE]);
		if (AlertYMinutesBefore != -1 && minsTillNews == AlertYMinutesBefore)
				Alert(AlertYMinutesBefore, " minutes until news for ", pair, ": ", eventData[FFCAL_TITLE]);
				
		// Buffers are set up as so: 
		// ExtMapBuffer0 contains the time UNTIL each announcement (can be negative)
		// e.g. [0] = -372; [1] = 25; [2] = 450; [3] = 1768 (etc.)
		// ExtMapBuffer1[0] has the mintutes since the last annoucement.
		// ExtMapBuffer1[1] has the mintutes until the next annoucement.
		ExtMapBuffer0[index] = minsTillNews;
	}



	// Cycle through the events array and pick out the most recent 
	// past and the next coming event to put into ExtMapBuffer1. 
	// Put the corresponding impact for these two into ExtMapBuffer2.
	bool first = true;
	ExtMapBuffer1[0] = 99999;
	ExtMapBuffer1[1] = 99999;
	ExtMapBuffer2[0] = 0;
	ExtMapBuffer2[1] = 0;
	for (i = 0; i < globalNewsSelectedTotal; i++)	
	{
		FFCalGetEvent (EventsArray[i], eventData);		
		if (ExtMapBuffer0[i] >= 0 && first)
		{
			first = false;
			
			// Put the relevant info into the indicator buffers...

			// Minutes SINCE - - - - - - - - - - - - - - - - - - - - - - - - -
			// (does not apply if the first event of the week has not passed)
			if (i > 0) {
				string previouseventData[8];
				FFCalGetEvent (EventsArray[i - 1], previouseventData);
				ExtMapBuffer1[0] = MathAbs(ExtMapBuffer0[i-1]);
				ExtMapBuffer2[0] = FFCalImpactToNumber(previouseventData[FFCAL_IMPACT]);
			}
			
			// Minutes UNTIL - - - - - - - - - - - - - - - - - - - - - - - - -
			// Check if past the last event.  
			if (ExtMapBuffer0[i] > 0 || (ExtMapBuffer0[i] == 0 && ExtMapBuffer0[i+1] > 0))		
				ExtMapBuffer1[1] = ExtMapBuffer0[i];

			ExtMapBuffer2[1] = FFCalImpactToNumber(eventData[FFCAL_IMPACT]);
		}
	}	
	
	// If we are past all news events, then neither one will have been 
	// set, so set the past event to the last (negative) minutes
	if (ExtMapBuffer1[0] == 0 && ExtMapBuffer1[1] == 0) {
		ExtMapBuffer1[0] = ExtMapBuffer0[i-1];
		ExtMapBuffer1[1] = 999999;
	}      
	
	if (!IsEA_Call && NewsDashBoardShow) {
			for( i = 0; i < NewsDashboardEvents; i++ ) 
				OutputToChart( i, indexOfNextEvent );
	}
	
	return (0);
}


// Draws the news veritically on the Chart so that we 
// have a visual reference of when it occurs. Added by MN 
void DisplayVerticalNews(int index, int shift) {    
	string eventData[8];
	FFCalGetEvent (index, eventData);	

	// datetime TheTime = StrToTime(eventData[FFCAL_GMT]);
	string dispCountry = eventData[FFCAL_COUNTRY];
	string dispTitle = eventData[FFCAL_TITLE];
	int eventImpact = FFCalImpactToNumber(eventData[FFCAL_IMPACT]);
	datetime theTime = TimeGMTToCurrentTime(StrToTime(eventData[FFCAL_GMT]));

	// if (theTime > AltTimeCurrent())
	// 	return;
	
	int BarShift = iBarShift(NULL, 0, theTime);
	double Pivot, Height = 0.0;
	// Calculate our pivot point to determine where to place the news text
	Pivot = (iHigh(NULL, 1440, BarShift + 1) + iLow(NULL, 1440, BarShift + 1) + iClose(NULL, 1440, BarShift + 1)) / 3;  
   
	// If Open price is above the Pivot determine our height
	if (Open[BarShift] > Pivot)
		Height = Low[iLowest(NULL, 0, MODE_LOW, 5, BarShift)] - LineTextVerticalShift * Point;  
	else 
		// Otherwise Open is below Pivot
		Height = High[iHighest(NULL, 0, MODE_HIGH, 5, BarShift)] + LineTextVerticalShift * Point;          

		// Draw a vertical line at the time of the news if it hasnt already been drawn
	color actualcolor;
	string vLineObject = StringConcatenate(objectPrefix, theTime);
	if (ObjectFind(vLineObject) == -1) {
		ObjectCreate(vLineObject, OBJ_TREND, 0, theTime, 0, theTime, iHigh(NULL, 0, BarShift)); 	// experimental
		ObjectSet(vLineObject, OBJPROP_STYLE, STYLE_DOT);
		// Put object in the background behind any other object on the chart
		ObjectSet(vLineObject, OBJPROP_BACK, true);	
		actualcolor = ColorImpactUndefined;
	} else 
		actualcolor = ObjectGet(vLineObject, OBJPROP_COLOR);

	if (eventImpact == FFCAL_IMPACT_HIGH && (actualcolor != ColorImpactHigh))
		ObjectSet(vLineObject, OBJPROP_COLOR, ColorImpactHigh);
	else if (eventImpact == FFCAL_IMPACT_MEDIUM && 
						(actualcolor != ColorImpactHigh && actualcolor != ColorImpactMedium)) 
		ObjectSet(vLineObject, OBJPROP_COLOR, ColorImpactMedium);
	else if (eventImpact == FFCAL_IMPACT_LOW && 
						(actualcolor != ColorImpactHigh && actualcolor != ColorImpactMedium && actualcolor != ColorImpactLow)) 
		ObjectSet(vLineObject, OBJPROP_COLOR, ColorImpactLow);

	
	string headlineObject = StringConcatenate(objectPrefix, theTime, shift);
	if (ObjectFind(headlineObject) == -1) {  
			ObjectCreate(headlineObject, OBJ_TEXT, 0, theTime - LineTextLeftShift + LineTextRightShift * shift, Height); 
			// Rotate the text 90 degrees
			ObjectSet(headlineObject, OBJPROP_ANGLE, 90);
			ObjectSetText(headlineObject, StringConcatenate(dispCountry, " ", dispTitle), LineTextSize, "Arial", LineTextColor);
	}

	//force a redraw of our chart
	WindowRedraw();

	return(0);
}


int OutputToChart( int id, int idxOfNext ) {
	static int pos_Y = 0;
  
	if( id == 0 ) {
		string ObjectSource = objectPrefix + "Source";
		pos_Y = 4;
		if (ObjectFind(ObjectSource) == -1) {
			ObjectCreate(ObjectSource, OBJ_LABEL, 0, 0, 0);
			ObjectSet(ObjectSource, OBJPROP_CORNER, NewsDashboardCorner);
			ObjectSet(ObjectSource, OBJPROP_XDISTANCE, 10);
			ObjectSet(ObjectSource, OBJPROP_YDISTANCE, pos_Y);       
		}
		if (!UseDailyFXAsSource && !UseGlobalTimeReference)
			ObjectSetText(ObjectSource, "Source: ForexFactory (date " +
										TimeToStr(AltTimeCurrent(), TIME_DATE|TIME_MINUTES) + " / GMT " +
										TimeToStr(TimeLocalToGMT(AltTimeLocal()), TIME_DATE|TIME_MINUTES) + 
										")", NewsDashboardTextSize - 2, "Arial Bold", NewsDashboardTitleColor);
		else
			ObjectSetText(ObjectSource, "Source: DailyFX (date " + 
										TimeToStr(AltTimeCurrent(), TIME_DATE|TIME_MINUTES) + " / GMT " +
										TimeToStr(TimeLocalToGMT(AltTimeLocal()), TIME_DATE|TIME_MINUTES) + 
										")", NewsDashboardTextSize - 2, "Arial Bold", NewsDashboardTitleColor);		
	} 
	
	pos_Y += NewsDashboardTextSize + 6;

	color NewsDashboardTextColor_  = NewsDashboardTextColor;
	color TxtColorImpact_ = ColorImpactHigh;

	string eventData[8];
	FFCalGetEvent (EventsArray[idxOfNext + id], eventData);

	string title   = eventData[FFCAL_TITLE];
	string country = eventData[FFCAL_COUNTRY];
	string impact  = eventData[FFCAL_IMPACT];
	string forecast = eventData[FFCAL_FORECAST];
	string previous = eventData[FFCAL_PREVIOUS];
	int    minutesTillEvent = (StrToTime(eventData[FFCAL_GMT]) - TimeLocalToGMT(AltTimeLocal())) / 60;
  	
	string TimeStr = GenerateTimeString (TimeGMTToLocalTime (StrToTime(eventData[FFCAL_GMT])));
		
	if (impact == "Low") TxtColorImpact_ = ColorImpactLow;
	if (impact == "Medium") TxtColorImpact_ = ColorImpactMedium;

	string Line1Object = objectPrefix + "L1N" + id;
	string Line2Object = objectPrefix + "L2N" + id;

	if( id != 0 && StringLen (title) == 0 ) return( 0 );

	if (ObjectFind(Line2Object) == -1) {
		ObjectCreate(Line2Object, OBJ_LABEL, 0, 0, 0 );
		ObjectSet(Line2Object, OBJPROP_CORNER, NewsDashboardCorner );
		ObjectSet(Line2Object, OBJPROP_XDISTANCE, 10 );
	} 
	ObjectSet( Line2Object, OBJPROP_YDISTANCE, pos_Y );
	
	if ( minutesTillEvent == 999999 ) {
		ObjectSetText( Line2Object, " (No more events this week)", NewsDashboardTextSize, "Arial Bold", ColorImpactOld );
	} else {
		if (NewsDashboardSummary) {
			if ( minutesTillEvent < 0 ) 
				TxtColorImpact_ = ColorImpactOld;
			ObjectSetText( Line2Object, country + ": " + TimeStr + title + makeKPIsummary( forecast, previous ), NewsDashboardTextSize, "Arial Bold", TxtColorImpact_ );
			if (ObjectFind(Line1Object) != -1) 
				ObjectDelete(Line1Object);
		} else {
			if ( minutesTillEvent < 0 ) 
				NewsDashboardTextColor_ = ColorImpactOld;

			ObjectSetText( Line2Object, country + ": " + title + makeKPIsummary( forecast, previous ), NewsDashboardTextSize, "Arial Bold", NewsDashboardTextColor_ );

			pos_Y += NewsDashboardTextSize + 4;
			if (ObjectFind(Line1Object) == -1) {
				ObjectCreate( Line1Object, OBJ_LABEL, 0, 0, 0 );
				ObjectSet( Line1Object, OBJPROP_CORNER, NewsDashboardCorner );
				ObjectSet( Line1Object, OBJPROP_XDISTANCE, 10 );
				ObjectSet (Line1Object, OBJPROP_YDISTANCE, pos_Y);
			}
			ObjectSetText( Line1Object, TimeStr + impact + " impact event [GMT " + eventData[FFCAL_TIME] + "]", NewsDashboardTextSize, "Arial Bold", TxtColorImpact_ );
		}
	}

  return( 0 );
}

string makeKPIsummary( string f, string p ) {
	if( StringLen( f ) == 0 && StringLen( p ) == 0 ) 
		return( "" );
	if( StringLen( f ) > 0 && StringLen( p ) == 0 ) 
		return( "  (" + f + "/-.-)" );
	if( StringLen( f ) == 0 && StringLen( p ) >  0 ) 
		return( "  (-.-/" + p + ")" );
	return( "  (" + f + "/" + p + ")" );
}


string GenerateTimeString (datetime time) {
	int    minutesTillEvent = (time - AltTimeLocal()) / 60;
	// string times = TimeToStr (TimeGMTToCurrentTime (StrToTime(eventData[FFCAL_GMT])), TIME_DATE | TIME_MINUTES);

	int Days, Hours, Mins; // to display time in days, hours, minutes
	string TimeStr;
	
	// If the time is 0 or negative, we want to say 
	// "xxx mins SINCE ... news event", else say "UNTIL ... news event"
	string 	sinceUntil = "until ";
	int 	dispMins = minutesTillEvent;
	if (minutesTillEvent <= 0) {
		sinceUntil = "since ";
		dispMins *= -1;
	}
		
	// added the following to show hours and days for longer durations
	// this could be enhanced to suppress 0 hours and 0 minutes
	if (dispMins == 999999) {
		TimeStr = " (No more events this week)";
	} else if (dispMins < 60) {
		TimeStr = dispMins + " mins ";
	} else { // time is 60 minutes or more
		Hours = MathRound(dispMins / 60);
		Mins = dispMins % 60;
		if (Hours < 24) { // less than a day: show hours and minutes
			TimeStr = Hours + " hrs " + Mins + " mins ";
		} else { // days, hours, and minutes
			Days = MathRound(Hours / 24);
			Hours = Hours % 24;
			TimeStr = Days + " days " + Hours + " hrs " + Mins + " mins ";
		}
	}
	
	return (TimeStr + sinceUntil);
}