Click here to Skip to main content
15,885,767 members
Articles / Desktop Programming / WTL

RSS Reader Plug-in for Internet Explorer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (12 votes)
16 Jul 2007CPOL4 min read 317.3K   1.3K   32  
This is a toolbar for the Internet Explorer which shows information from RSS taken from the Internet.
/*
	Project		: RSS Reader plugin for Internet Explorer
	File name	: RRPNewsButton.h
	Date		: 
	Pupose		: It encapsulates News Button which has the responsibility of showing
				  News in cool owner-drawn drop down menu.
	Note		: It handle some Window Messaged chainded to it by RRPToolbar class
				  This messages are of course related with Button
	Send comments 
	or Bugs to	: prafulla_t@users.sourceforge.net
*/



#include "stdafx.h"
#include "RRPNewsButton.h"
#include "Resource.h"
#include "RRPConfigurationManager.h"


int RRPNewsButton::buttonsCount = 1;

RRPNewsButton::RRPNewsButton(void) {
	rssFileName = NULL;
	debug = RRPDebugInfoLogger::getInstance();
	buttonClicked = false;
	dummyNewsItem = "This is absoulutely bakwas news item!!!!";
	startIndex = 0;
	endIndex = 0;
	rssParser = NULL;

}

void RRPNewsButton::setToolbar(RRPToolbar *rrpToolbar) {
	this->rrpToolbar = rrpToolbar;
	//I dont know why this message need to be sent
	::SendMessage(*rrpToolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0L);
}

void RRPNewsButton::setRssFetcher(RRPRssFetcher *fetcher) {
	this->rssFetcher = fetcher;
	return;
}


RRPNewsButton::~RRPNewsButton(void) {
	if(rssFileName)
		delete rssFileName;
	if(rssParser)
		delete rssParser;
}

BOOL RRPNewsButton::createButton(char *nameOfButton,int commandID,CString newsHeader,BYTE buttonStyle) {
	this->commandID = commandID;
	this->newsHeader = newsHeader;
	rrpToolbar->InsertButton(buttonsCount,commandID,buttonStyle | BTNS_BUTTON | BTNS_AUTOSIZE ,TBSTATE_ENABLED,0,-1,0);
	changeButtonCaption(nameOfButton);
	ZeroMemory(&buttonRect,sizeof(buttonRect));
	buttonsCount++;
	rrpToolbar->GetItemRect(rrpToolbar->CommandToIndex(commandID),&buttonRect);
	rrpToolbar->MapWindowPoints(HWND_DESKTOP, (POINT *)&buttonRect, 2);
	refreshNews();
	return true;
}

LRESULT RRPNewsButton::OnButtonDropDown(int idCtrl, LPNMHDR pnmh, BOOL& bHandled) {
	NMTOOLBAR* ptb = reinterpret_cast<NMTOOLBAR *>(pnmh);
	//Creating Menu
	if(ptb->iItem == commandID)  {
		buttonClicked = true;
		CMenu menuPopup;
		CMenuHandle sub;
		rrpToolbar->GetItemRect(rrpToolbar->CommandToIndex(commandID),&buttonRect);
		rrpToolbar->MapWindowPoints(HWND_DESKTOP, (POINT *)&buttonRect, 2);
		menuPosition.x = buttonRect.left;
		menuPosition.y = buttonRect.bottom;
		menuPopup.CreatePopupMenu();
		MenuItem *menuItem = new MenuItem();
		::ZeroMemory(menuItem,sizeof(MenuItem));
		menuItem->name = new char[newsHeader.GetLength()+1];
		strcpy_s(menuItem->name,newsHeader.GetLength()+1,newsHeader);
		menuItem->commandID = commandID;
		menuPopup.AppendMenu(MF_STRING | MF_OWNERDRAW,ID_NEWS_HEADER,(LPTSTR)menuItem);
		menuPopup.AppendMenu(MF_STRING | MF_OWNERDRAW,ID_MENU_SEPARATOR,(LPTSTR)menuItem);
		//Inserting actual news item now
		/*
		RRPRssItemIterator *it = rssParser->getItemIterator();
		int i = 0;
		while(it->hasNext()) {
			RRPRssItem *item = it->getItem();
			//menuItem->name = new char[strlen(item->getTitle()) + 1];
			//strcpy_s(menuItem->name,strlen(item->getTitle()) + 1,item->getTitle());
			menuPopup.AppendMenu(MF_STRING | MF_OWNERDRAW,ID_NEWS_ITEM_START + i,reinterpret_cast<LPTSTR>(menuItem));
			i++;
		}
		*/
		int n = rssParser->getVectorSize();
		int i = 0;
		if(startIndex < n) 
			for(i = startIndex;i <  (startIndex + 5);i++) {
				if(i == n) {
					endIndex = i;
					break;
				}
				RRPRssItem *item = rssParser->getRssItem(i);
				menuPopup.AppendMenu(MF_STRING | MF_OWNERDRAW,ID_NEWS_ITEM_START + i,reinterpret_cast<LPTSTR>(menuItem));
			}
		endIndex = i;
		//No need to have a menu item here but passing it just like that
		menuPopup.AppendMenu(MF_STRING | MF_OWNERDRAW,ID_MENU_SEPARATOR,reinterpret_cast<LPTSTR>(menuItem));
		
		char *indexString = new char[25];
		if(startIndex == endIndex)
			sprintf(indexString,"%d-%d",startIndex,endIndex);
		else
			sprintf(indexString,"%d-%d",startIndex + 1,endIndex);
		menuItem->name = new char[25];
		strcpy(menuItem->name,indexString);
		menuPopup.AppendMenu(MF_STRING | MF_OWNERDRAW,ID_NEWS_FOOTER,reinterpret_cast<LPTSTR>(menuItem));
	
		//menuPopup.AppendMenu(MF_STRING ,1001,message);
		//menuPopup.AppendMenu(MF_SEPARATOR,25000);		
		::TrackPopupMenu(menuPopup.Detach(), TPM_LEFTALIGN | TPM_RIGHTBUTTON, menuPosition.x, menuPosition.y, 0, (*rrpToolbar), NULL);		
		menuTooltip->ShowToolTip(FALSE);
	}
	return 0;
}

void RRPNewsButton::changeButtonCaption(char *str,DWORD dwMask) {
    TBBUTTONINFO tbi;	
	ZeroMemory(&tbi,sizeof(tbi));
	tbi.cbSize = sizeof(TBBUTTONINFO);
	tbi.dwMask = TBIF_TEXT;
	tbi.pszText = _T(str);
	nameOfButton = str;
	newsHeader = str;
    rrpToolbar->SetButtonInfo(commandID, &tbi);
}	   

LRESULT RRPNewsButton::OnClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) {
	debug->log(newsHeader);
	return 0;
}

LRESULT RRPNewsButton::OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	return 0;
}

LRESULT RRPNewsButton::OnDrawItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	DRAWITEMSTRUCT* lpDIS = reinterpret_cast<DRAWITEMSTRUCT *>(lParam);
	MenuItem *menuItem = reinterpret_cast<MenuItem *>(lpDIS->itemData);
	//Drawing Menu
	//First_News_Button_Clicked_...
	if(menuItem->commandID == commandID) {
			//debug->log("Drawing news header");
			CDCHandle dc = lpDIS->hDC;		
			RECT rc = lpDIS->rcItem;
			if (lpDIS->itemState & ODS_FOCUS)
				dc.DrawFocusRect(&rc);
			dc.FillSolidRect(&lpDIS->rcItem, RGB(255,255,255));
			int nIndexDC = dc.SaveDC();
			
			CBrush br;
			dc.SetBkMode(TRANSPARENT);
			int nLen = 6;
			//Font for Heading
			CFont cf;
			cf.CreateFont(20,0,0,0,FW_NORMAL,FALSE, FALSE, FALSE,DEFAULT_CHARSET ,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,DEFAULT_PITCH,_T("Times New Roman"));
			HFONT hf = dc.SelectFont(cf);
			const COLORREF clrBlue = RGB(0, 0, 255);
			const COLORREF clrRed = RGB(255, 0, 0);
			if(lpDIS->itemID == ID_MENU_SEPARATOR) {
				rc.top += (rc.bottom - rc.top) / 2;			// vertical center
				dc.DrawEdge(&rc, EDGE_ETCHED, BF_TOP);		// draw separator line
				return 0;
			}
			rc.left += 15 + 2;
			//Next  Prev And that "Trying to load" news String
			
			if(lpDIS->itemID==ID_NEWS_FOOTER) {
					CFont nextPrevStringFont;
					CFont bottomLineFont;
					LOGFONT lf = { 0 };
					cf.GetLogFont(lf);
					dc.SetTextColor(clrBlue);
					lf.lfHeight = 16;
					nextPrevStringFont.CreateFontIndirect(&lf);
					dc.SelectFont(nextPrevStringFont);
					rc.right -= 25;
					dc.DrawText("Next",-1,&rc,DT_RIGHT);
					rc.left += 10;
					dc.DrawText("Prev",-1,&rc,DT_LEFT);
					dc.SelectFont(nextPrevStringFont);
					CFont msg;
					cf.GetLogFont(lf);
					dc.SetTextColor(clrBlue);
					lf.lfHeight = 13;
					msg.CreateFontIndirect(&lf);
					dc.SelectFont(msg);
					
					//Drawing Index String
					char indexString[25];
					if(startIndex == endIndex)
						sprintf(indexString,"%d-%d",startIndex,endIndex);
					else {
						rc.top += 1;
						sprintf(indexString,"%d-%d",startIndex+1,endIndex);
					}
					dc.DrawText(menuItem->name,-1,&rc,DT_CENTER);
					rc.top=rc.top+10;
					if(startIndex == endIndex)
						dc.DrawText("Trying to Load news",-1,&rc,DT_CENTER);
					dc.RestoreDC(nIndexDC);
					return 0;
				}
			
			if(lpDIS->itemID == ID_NEWS_HEADER)	{

					CFont other_line;
					CFont bottom_line;
					//Length = 26
					
					CString newsHeaderTruncated;
					if(newsHeader.GetLength() >= 26) {
						int i;
						for(i = 0;i < newsHeader.GetLength();i++)
							newsHeaderTruncated += newsHeader[i];
						CString dotDotDot("...");
						newsHeaderTruncated += dotDotDot;
						dc.DrawText(newsHeaderTruncated,-1,&rc,DT_CENTER);
					}
					else 
						dc.DrawText(newsHeader,-1,&rc,DT_CENTER);
					LOGFONT lf = { 0 };
					cf.GetLogFont(lf);
					lf.lfHeight = 12;
					other_line.CreateFontIndirect(&lf);
					dc.SelectFont(other_line);
					rc.top=rc.top+20;
					dc.DrawText(getDate(),-1,&rc,DT_CENTER);
			
			}
			else {	
				//Get the Rss item to draw
				int rssItemIndex = lpDIS->itemID - ID_NEWS_ITEM_START;
				RRPRssItem *rssItem = rssParser->getRssItem(rssItemIndex);

				CFont newsItems;
				LOGFONT lf = { 0 };
				cf.GetLogFont(lf);
				lf.lfHeight = 16;
				
				if (lpDIS->itemState & ODS_SELECTED)
					dc.SetTextColor(clrRed);
				else
					dc.SetTextColor(clrBlue);

				newsItems.CreateFontIndirect(&lf);
				dc.SelectFont(newsItems);
		
				rc.right=300;
			
				dc.DrawText(rssItem->getTitle(),-1,&rc,DT_WORDBREAK|DT_NOCLIP|DT_MODIFYSTRING|DT_END_ELLIPSIS);
				CPoint ArrowTip=CPoint(rc.left-10,rc.top+5);
		
				CPoint ptDest;
				CPoint ptOrig = ArrowTip;

				COLORREF cr =RGB(0,0,255);
				CPen penArrow, penOld;
				if (lpDIS->itemState & ODS_SELECTED)
					penArrow.CreatePen(PS_SOLID,1,clrRed);
				else
					penArrow.CreatePen(PS_SOLID,1,clrBlue);
				penOld = dc.SelectPen(penArrow);
				if (lpDIS->itemState & ODS_SELECTED)
					dc.SetPixel(ArrowTip,clrRed);
				else
					dc.SetPixel(ArrowTip,clrBlue);
	
				ArrowTip -= CPoint(3,0);
				dc.MoveTo(ArrowTip);

				ptDest = ArrowTip;
				ptDest += CPoint(0,7);
				dc.LineTo(ptDest);

				for(int i=2;i>=-4;i--) {
					CPoint temp=ArrowTip-CPoint(i,0);
					temp += CPoint(1,0);
					dc.MoveTo(temp);
					temp +=  CPoint(0,7);
					dc.LineTo(temp);
				}
				dc.SelectPen(penOld);
			}
			dc.RestoreDC(nIndexDC);
			return 0;
		
	}
	return 0;
}


LRESULT RRPNewsButton::OnMeasureItem(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	MEASUREITEMSTRUCT* pmis = reinterpret_cast<MEASUREITEMSTRUCT*>(lParam);
	MenuItem *menuItem = (MenuItem *)pmis->itemData;
	if(menuItem->commandID == commandID) {
		CClientDC dc((*rrpToolbar));
		HFONT hFont = ((HFONT)GetStockObject( DEFAULT_GUI_FONT ));
		int nLen = 6;
		CFont newsItems;
		CFont cf;
		cf.CreateFont(20,0,0,0,FW_NORMAL,FALSE, FALSE, FALSE,DEFAULT_CHARSET ,OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY,DEFAULT_PITCH,"Times New Roman");
		HFONT hf = dc.SelectFont(cf);
		dc.SelectFont( cf ); //GetFont()
		LOGFONT lf = { 0 };
		cf.GetLogFont(lf);
		lf.lfHeight = 16;
		lf.lfUnderline=1;

		newsItems.CreateFontIndirect(&lf);
		dc.SelectFont(newsItems);
		dc.SetTextColor(RGB(0,255,0));
		TEXTMETRIC tm;
		dc.GetTextMetrics(&tm); 
		pmis->itemWidth = 290;

		if(pmis->itemID == ID_NEWS_HEADER) {
			pmis->itemHeight = 2*(tm.tmHeight + tm.tmInternalLeading);
			return 0;
		}
		if(pmis->itemID == ID_MENU_SEPARATOR) {
			pmis->itemHeight = 10;
			return 0;
		}
		if(pmis->itemID == ID_NEWS_FOOTER) {
			pmis->itemHeight = 24;
			return 0;
		}
		else {
			//Get the Rss item to draw
			int rssItemIndex = pmis->itemID - ID_NEWS_ITEM_START;
			RRPRssItem *rssItem = rssParser->getRssItem(rssItemIndex);
			
			RECT rcText = { 0,0,250, 0 };
			pmis->itemHeight=dc.DrawText(rssItem->getTitle(),-1,&rcText,DT_WORDBREAK| DT_CALCRECT|DT_MODIFYSTRING|DT_END_ELLIPSIS);
			pmis->itemHeight += 7;
			return 0;
		}

		//Default Size -- Never gets set
		pmis->itemWidth = 290;
		pmis->itemHeight = 290;
	}
	return 0;
}

LRESULT RRPNewsButton::OnMenuSelect(WPARAM additionalParam, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	WORD nID = LOWORD(wParam);
	if(nID >= ID_NEWS_ITEM_START) {
			int rssItemIndex = nID - ID_NEWS_ITEM_START;
			RRPRssItem *rssItem = rssParser->getRssItem(rssItemIndex);
			CString desc = CString(rssItem->getDescription(),strlen(rssItem->getDescription()));
			BOOL hasImage,hasTable;
			desc = rssItem->removeTags(desc,hasImage,hasTable);
			if(hasImage)
				desc = desc+ "\r\n"+"Click on the Link to view the Image associated with this News";
			else
				desc = desc + "\r\n"+"Click on the Link to get more details";
			TCHAR *buf = new TCHAR[desc.GetLength()+1];
			int i ;
			for(i = 0;i<desc.GetLength();i++)
				buf[i] = desc[i];
			buf[i] = '\0';
			POINT *pt = reinterpret_cast<POINT *>(additionalParam);
			menuTooltip->OnMenuSelect(buf,LOWORD(wParam), 
				HIWORD(wParam), (HMENU)lParam,pt->x,pt->y);
	}
	return 0;
}

RECT RRPNewsButton::getButtonRect() {
	return buttonRect;
}

UINT RRPNewsButton::getCommandID() {
	return commandID;
}


BOOL RRPNewsButton::isButtonClicked() {
	return buttonClicked;
}

void RRPNewsButton::setButtonClicked(BOOL value) {
	buttonClicked = value;
}

void RRPNewsButton::OnNextHotSpotClicked() {
	try {
		int n = rssParser->getVectorSize();
		if(n > 0) {
		if((startIndex + 5) < n)
			startIndex += 5;
		else
			startIndex = 0;
		BOOL handled = 1;
		NMTOOLBAR *toolbarData = new NMTOOLBAR();
		//This is the field we need
		toolbarData->iItem = commandID;
		//Dropping menu manually!!
		OnButtonDropDown(0,reinterpret_cast<LPNMHDR>(toolbarData),handled);
		}
	}
	catch(...) {
	}
}

void RRPNewsButton::OnPrevHotSpotClicked() {
	try {
		int n = rssParser->getVectorSize();
		if(n > 0) {
		if((startIndex - 5) >= 0)
			startIndex -= 5;
		else
			startIndex = (n/5)*5;
		if(startIndex == n)
			startIndex -= 5;
		BOOL handled = 1;
		NMTOOLBAR *toolbarData = new NMTOOLBAR();
		toolbarData->iItem = commandID;
		//Dropping menu manually!!
		OnButtonDropDown(0,reinterpret_cast<LPNMHDR>(toolbarData),handled);
		}
	}
	catch(...) {
	}
}

void RRPNewsButton::removeRssFile(bool justRemoveRssFile) {
	if(justRemoveRssFile == false) {
		char *nameOfRssFile = new char[25];
		sprintf(nameOfRssFile,"%d",commandID);
		strcat(nameOfRssFile,".xml");
		rssFetcher->removeRssFromList(nameOfRssFile,NULL);
	}
	remove(rssFileName);
	if(rssFileName) {
		delete rssFileName;
		rssFileName = NULL;
	}
}

void RRPNewsButton::refreshNews() {
	//Setting Rss for this button
//	RRPConfigurationManager *ptr = new RRPConfigurationManager();
	RRPConfigurationManager *ptr = rrpToolbar->getConfigManger();
//	char *rssDir = RRPConfigurationManager::getInstance()->getInstallationDirectory();
	char *rssDir = ptr->getInstallationDirectory();
	char *rssDirectory = new char[strlen(rssDir) + 30];
	strcpy(rssDirectory,rssDir);
	strcat(rssDirectory,"\\rss");
	char *nameOfRssFile = new char[25];
	sprintf(nameOfRssFile,"%d",commandID);
	strcat(nameOfRssFile,".xml");
	strcat(rssDirectory,"\\");
	strcat(rssDirectory,nameOfRssFile);

	//rss is stored in Rss subdir in the installation directory
	//The name of the rss is same as the command id of the button which 
	//is showing the corresponding news
	if(rssParser) {
		delete rssParser;
		rssParser = NULL;
	}
	rssParser = new RRPRssParser(rssDirectory);
	rssFileName = rssDirectory;

	OFSTRUCT fileInformation;
	HFILE fileHandle = OpenFile(rssFileName,&fileInformation,OF_READ);
	
	bool veryOld = false;
	
	if(fileHandle != HFILE_ERROR) {
		FILETIME lastModifiedTime;
		::GetFileTime(reinterpret_cast<HANDLE>(fileHandle),NULL,NULL,&lastModifiedTime);
//		DWORD d = ::CompareFileTime(&(rrpToolbar->getStartTIme()),&lastModifiedTime);
		SYSTEMTIME startTime,lastWriteTime;

		::FileTimeToSystemTime(&(rrpToolbar->getStartTIme()),&startTime);
		::FileTimeToSystemTime(&lastModifiedTime,&lastWriteTime);
		//Checking whether XML was written at least 30 mins 
		//before the toolbar started
		//It is so,tell fetcher process to load it again
		if(startTime.wYear > lastWriteTime.wYear)
			veryOld = true;
		else if(startTime.wMonth > lastWriteTime.wMonth)
				veryOld = true;
		else if(startTime.wDay > lastWriteTime.wDay)
				veryOld = true;
		else if(startTime.wHour > lastWriteTime.wHour)
				veryOld = true;
		else if((startTime.wMinute - lastWriteTime.wMinute) >= 30)
				veryOld = true;

/*		if(rrpToolbar->getStartTIme().dwHighDateTime > lastModifiedTime.dwHighDateTime)
			veryOld = true;
		else
			if(rrpToolbar->getStartTIme().dwHighDateTime < lastModifiedTime.dwHighDateTime)
				veryOld = false;
			else
				if(rrpToolbar->getStartTIme().dwHighDateTime == lastModifiedTime.dwHighDateTime)
					if((rrpToolbar->getStartTIme().dwLowDateTime - lastModifiedTime.dwLowDateTime) >= (DWORD)360000000) 
						veryOld = true;*/
		CloseHandle(reinterpret_cast<HANDLE>(fileHandle));
	}
	
	
	

	unsigned long flag = 0;
//	if(INTERNET_CONNECTION_MODEM == ret) {
	//If file does not exists, or parser error or link modified ,tell the fetcher process about it..
	if((fileHandle == HFILE_ERROR) || rssParser->isError() || ptr->isModified() || veryOld) {
		RRPConfigurationManager *configManager = ptr;
		int n = configManager->getSize();
		for(int i =0;i<n;i++)
			if(configManager->getItem(i)) {
				if(configManager->getItem(i)->title && configManager->getItem(i)->link)
					if(strcmp(configManager->getItem(i)->title,nameOfButton) == 0) {
						rssFetcher->sendFetchRssRequest(nameOfRssFile,configManager->getItem(i)->link);
						rrpToolbar->setBusyFlag(true);
					}
			}
		}


	rssParser->parse();
	if(nameOfRssFile)  {
		delete nameOfRssFile;
		nameOfRssFile = NULL;
	}

/*	//Creating menu tooltip
	menuTooltip = new RRPMenuTooltip();
	menuTooltip->Create((*rrpToolbar), _T(""),_Module.GetResourceInstance(),TTS_NOPREFIX | TTS_BALLOON
				, _T("Brief Description"));
*/
}

LRESULT RRPNewsButton::OnMenuClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled) {
	if(wID >= ID_NEWS_ITEM_START) {
		int rssItemIndex = wID - ID_NEWS_ITEM_START;
		RRPRssItem *rssItem = rssParser->getRssItem(rssItemIndex);
		if(rssItem) {
			CComBSTR bsSite(rssItem->getLink());

			webBrowser->Navigate(bsSite.Detach(),NULL,NULL,NULL,NULL);
		}

//		debug->log("Link is :: %s",rssItem->getLink());
	}
	return 0;
}

char* RRPNewsButton::getDate() {
	static char* days_of_week[] =
	{
	   { "Sunday" },
	   { "Monday" },
	   { "Tuesday" },
	   { "Wednesday" },
	   { "Thursday" },
	   { "Friday" },
	   { "Saturday" },
	} ;
	static char* months_of_year[] =
	{
	   { "" },
	   { "January" },
	   { "February" },
	   { "March" },
	   { "April" },
	   { "May" },
	   { "June" },
	   { "July" },
	   { "August" },
	   { "September" },
	   { "October" },
	   { "November" },
	   { "December" },
	} ;
	static char *temp = new char[255];
	SYSTEMTIME p;
	GetLocalTime(&p);
	sprintf(temp,"%s,%d %s %d %d:%d:%d",days_of_week[p.wDayOfWeek],p.wDay,months_of_year[p.wMonth],p.wYear,p.wHour,p.wMinute,p.wSecond);
	return temp;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
India India
Quote : "Life is all about solving problems and enjoying their solutions !! "

Comments and Discussions