/*
 * knewsticker.cpp
 *
 * Copyright (c) 2000 Frerich Raabe <raabe@kde.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include "knewsticker.h"
#include "newssource.h"

#include <kaboutapplication.h>
#include <kaboutdata.h>
#include <kapp.h>
#include <kbugreport.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kpopupmenu.h>
#include <kprocess.h>
#include <kstddirs.h>
#include <knotifyclient.h>
#include <kstyle.h>

#include <qiconset.h>
#include <qtooltip.h>
#include <qlayout.h>
#include <qregexp.h>

#include <dcopclient.h>

KNewsTicker::KNewsTicker(const QString &cfgFile, Type t, int actions, QWidget *parent, const char *name)
	: KPanelApplet(cfgFile, t, actions, parent, name), DCOPObject("KNewsTicker"),
	_instance(new KInstance("knewsticker")),
	_updateId(0),
	_notifyId(0),
	_offlineMode(false),
	_contextMenu(0)
{
	_config = config();

	_dcopClient = new DCOPClient();
	_dcopClient->registerAs("knewsticker", false);

	_newsSource.setAutoDelete(true);

	_arrowButton = new ArrowButton(this);
	_arrowButton->setFocusPolicy(NoFocus);
	QToolTip::add(_arrowButton, i18n("Show menu"));
	connect(_arrowButton, SIGNAL(clicked()), SLOT(slotArrowButtonPressed()));

	_newsScroller = new NewsScroller(this);
	QToolTip::add(_newsScroller, QString::null);
	connect(_newsScroller, SIGNAL(contextMenu()), SLOT(slotOpenContextMenu()));

	_newsTimer = new QTimer(this);
	connect(_newsTimer, SIGNAL(timeout()), SLOT(slotUpdateNews()));

	_aboutData = new KAboutData("knewsticker", I18N_NOOP("KNewsTicker"), "v0.1", I18N_NOOP("A newsticker applet."), KAboutData::License_BSD, "(c) 2000, Frerich Raabe");
	_aboutData->addAuthor("Frerich Raabe", I18N_NOOP("Original author"), "raabe@kde.org");
	_aboutData->addAuthor("Malte Starostik", I18N_NOOP("Hypertext headlines and much more"), "malte.starostik@t-online.de");
	_aboutData->addAuthor("Wilco Greven", I18N_NOOP("Mouse wheel support"), "greven@kde.org");

	reparseConfig();
}

KNewsTicker::~KNewsTicker()
{
	delete _dcopClient;
}

int KNewsTicker::heightForWidth(int /*width*/) const
{
	return _newsScroller->height();
}

int KNewsTicker::widthForHeight(int /*height*/) const
{
	return QFontMetrics(_font).width("X") * 20;
}

void KNewsTicker::preferences()
{
	KProcess p;
	p << KGlobal::dirs()->findResource("exe", "kcmshell");
	p << "Personalization/kcmnewsticker";
	p.start(KProcess::DontCare);
}

void KNewsTicker::about()
{
	KAboutApplication aboutDlg(_aboutData);
	aboutDlg.show();
}

void KNewsTicker::help()
{
	kapp->invokeHTMLHelp("knewsticker", "");
}

void KNewsTicker::action(Action a)
{
	if ((a & KPanelApplet::ReportBug) != 0) {
		KBugReport bugReport(this, true, _aboutData);
		bugReport.show();
	}

	KPanelApplet::action(a);
}

void KNewsTicker::readConfig()
{
	_config->setGroup("General");
	_interval = _config->readNumEntry("Interval", 30);
	QFont stdFont = QFont("courier");
	_customNames = _config->readBoolEntry("Custom names", false);
	_endless = _config->readBoolEntry("Endless Scrolling", true);
	_scrollMostRecentOnly = _config->readBoolEntry("Scroll most recent only", false);
	setOfflineMode(_config->readBoolEntry("Offline mode", false));

	uint newsSources = _config->readNumEntry("News sources", 0);

	_config->setGroup("Scrolling");
	_scrollSpeed = _config->readNumEntry("Speed", 80);
	QString _dir = _config->readEntry("Direction", "Left");
	if (_dir == "Left")
		_direction = NewsScroller::Left;
	else if (_dir == "Right")
		_direction = NewsScroller::Right;
	else if (_dir == "Up")
		_direction = NewsScroller::Up;
	else
		_direction = NewsScroller::Down;
	_foreground = _config->readColorEntry("Foreground", &Qt::black);
	_background = _config->readColorEntry("Background", &Qt::white);
	_highlighted = _config->readColorEntry("Highlighted", &Qt::red);
	_font = _config->readFontEntry("Font", &stdFont);
	_underlineHighlighted = _config->readBoolEntry("Underline highlighted", true);

	if (newsSources == 0) {
		NewsSource *source = new NewsSource("http://www.kde.org/dotkdeorg.rdf", "dot.kde.org");
		connect(source, SIGNAL(newNewsAvailable(NewsSource *, uint, bool)), SLOT(slotNewsUpdated(NewsSource *, uint, bool)));
		connect(source, SIGNAL(invalidInput(const QString &)), SLOT(slotInvalidInput(const QString &)));
		_newsSource.insert(newsSources++, source);
		source = new NewsSource("http://slashdot.org/slashdot.rdf", "Slashdot");
		connect(source, SIGNAL(newNewsAvailable(NewsSource *, uint, bool)), SLOT(slotNewsUpdated(NewsSource *, uint, bool)));
		connect(source, SIGNAL(invalidInput(const QString &)), SLOT(slotInvalidInput(const QString &)));
		_newsSource.insert(newsSources++, source);
		source = new NewsSource("http://freshmeat.net/backend/fm.rdf", "Freshmeat");
		connect(source, SIGNAL(newNewsAvailable(NewsSource *, uint, bool)), SLOT(slotNewsUpdated(NewsSource *, uint, bool)));
		connect(source, SIGNAL(invalidInput(const QString &)), SLOT(slotInvalidInput(const QString &)));
		_newsSource.insert(newsSources++, source);
		source = new NewsSource("http://lwn.net/headlines/rss", "Linux Weekly News");
		connect(source, SIGNAL(newNewsAvailable(NewsSource *, uint, bool)), SLOT(slotNewsUpdated(NewsSource *, uint, bool)));
		connect(source, SIGNAL(invalidInput(const QString &)), SLOT(slotInvalidInput(const QString &)));
		_newsSource.insert(newsSources++, source);
	} else for (uint i = 0; i < newsSources; i++) {
		NewsSource *source = new NewsSource(_config, QString("News source #%1").arg(i));
		_newsSource.insert(i, source);
		connect(source, SIGNAL(newNewsAvailable(NewsSource *, uint, bool)), SLOT(slotNewsUpdated(NewsSource *, uint, bool)));
		connect(source, SIGNAL(invalidInput(const QString &)), SLOT(slotInvalidInput(const QString &)));
	}

	if (!_offlineMode)
		slotUpdateNews();
}

void KNewsTicker::reparseConfig()
{
	_config->reparseConfiguration();
	_newsSource.clear();

	readConfig();

	_newsScroller->setForegroundColor(_foreground);
	_newsScroller->setBackgroundColor(_background);
	_newsScroller->setHighlightedColor(_highlighted);
	_newsScroller->setFont(_font);
	_newsScroller->setDirection(_direction);
	_newsScroller->setSpeed(_scrollSpeed);
	_newsScroller->setEndless(_endless);
	_newsScroller->setUnderlineHighlighted(_underlineHighlighted);

	_newsTimer->stop();
	if (_interval > 0 && !_offlineMode)
		_newsTimer->start(_interval * 1000 * 60);
}

void KNewsTicker::slotUpdateNews()
{
	_updateId++;

	_newsScroller->clear();

	for (uint i = 0; i < _newsSource.count(); i++)
		_newsSource[i]->retrieveNews(_customNames, _updateId);
}

void KNewsTicker::slotNewsUpdated(NewsSource *source, uint _updateId, bool newNews)
{
	if (_contextMenu) {
		delete _contextMenu;
		_contextMenu = 0;
	}

	if (source->articles())
		if (_scrollMostRecentOnly)
			_newsScroller->addHeadline(source->article(0));
		else
			for (uint i = 0; i < source->articles(); i++)
				_newsScroller->addHeadline(source->article(i));

    // Notify only once for every update, not for every
    // NewsSource with new articles
	if (newNews && _notifyId < _updateId) {
		KNotifyClient::Instance instance(_instance);
		KNotifyClient::event("NewNews");
		_notifyId = _updateId;
	}
}

void KNewsTicker::mousePressEvent(QMouseEvent *e)
{
	if (e->button() == QMouseEvent::RightButton)
		slotOpenContextMenu();
}

void KNewsTicker::slotOpenContextMenu()
{
	if (!_contextMenu)
		_contextMenu = new KNewsTickerMenu(this);

	_contextMenu->exec(QCursor::pos());

	delete _contextMenu;
	_contextMenu = 0;
}

void KNewsTicker::slotArrowButtonPressed()
{
	if (!_contextMenu) {
		_contextMenu = new KNewsTickerMenu(this);
		connect(_contextMenu, SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide()));
	}

	QPoint pos(_arrowButton->mapToGlobal(QPoint(0, 0)));
	QSize size(_arrowButton->size());

	if (orientation() == Horizontal) {
		if (popupDirection() == Down)
			pos.setY(pos.y() + size.height() + 2);
		else {
			int y = pos.y() - _contextMenu->sizeHint().height() - 2;
			pos.setY(y < 0 ? 0 : y);
		}
	} else {
		if (popupDirection() == Right)
			pos.setX(pos.x() + size.width() + 2);
		else
			pos.setX(pos.x() - _contextMenu->sizeHint().width() - 2);
	}

	_contextMenu->exec(pos);

	delete _contextMenu;
	_contextMenu = 0;
}

void KNewsTicker::orientationChange(Orientation orientation)
{
	delete layout();

	QBoxLayout *layout;

	if (orientation == Horizontal)
		layout = new QHBoxLayout(this);
	else
		layout = new QVBoxLayout(this);

	layout->addWidget(_arrowButton);
	layout->addWidget(_newsScroller);
}

void KNewsTicker::slotContextMenuAboutToHide()
{
	_arrowButton->setDown(false);
}

void KNewsTicker::slotInvalidInput(const QString &filename)
{
	KNotifyClient::Instance instance(_instance);
	KNotifyClient::event("InvalidRDF",
	                    i18n("Couldn't read news site '%1'.\n\nThe supplied"
	                    " resource file is probably invalid or broken.").arg(filename));
}

void KNewsTicker::setOfflineMode(bool offlineMode)
{
	_offlineMode = offlineMode;
	if (offlineMode)
		_newsTimer->stop();
	else
		_newsTimer->start(_interval * 1000 * 60);

	_config->setGroup("General");
	_config->writeEntry("Offline mode", _offlineMode);
	_config->sync();
}

Qt::ArrowType KNewsTicker::getArrowType()
{
	ArrowType at;

	if (orientation() == Horizontal)
		at = (popupDirection() == Down? DownArrow : UpArrow);
	else
		at = (popupDirection() == Right? RightArrow : LeftArrow);

	return at;
}

ArrowButton::ArrowButton(KNewsTicker *parent) : QToolButton(parent)
{
	_parent = parent;
	setAutoRaise(true);
}

void ArrowButton::drawButtonLabel(QPainter *p)
{
	ArrowType at = _parent->getArrowType();

	int nX = 0, nY = 0;

	if (at == DownArrow) {
		nY = height() - 6;
		nX = (width() - 4) / 2 + 1;
	} else if (at == UpArrow) {
		nY = 4;
		nX = (width() - 4) / 2 + 1;
	} else if (at == RightArrow) {
		nX = width() - 6;
		nY = (height() - 4) / 2 + 1;
	} else {
		nX = 4;
		nY = (height() - 4) / 2 + 1;
	}

	QColorGroup cg = QColorGroup(colorGroup().text(), backgroundColor(),
		white, black, black, black, white);

	// Use qDrawArrow() for now since some themes (i.e. Marble) don't
	// support drawArrow() yet (besides, we'd have to compute the width
	// and height of the arrow button dynamically as different styles have
	// different drawArrow()'s).
	// kapp->kstyle()->drawArrow(p, at, false, nX, nY, 0, 0, cg, false);
	qDrawArrow(p, at, Qt::WindowsStyle, false, nX, nY, 0, 0, cg, false);
}

QSize ArrowButton::sizeHint() const
{
	return QSize(12, 12);
}

KNewsTickerMenu::KNewsTickerMenu(QWidget *parent, const char *name)
  : KPopupMenu(parent, name)
{
	_parent = static_cast<KNewsTicker *>(parent);

	uint index = 0;

	QIconSet newsIcon = QIconSet(KGlobal::iconLoader()->loadIcon("news", KIcon::Small));
	QIconSet helpIcon = QIconSet(KGlobal::iconLoader()->loadIcon("help", KIcon::Small));
	QIconSet confIcon = QIconSet(KGlobal::iconLoader()->loadIcon("configure", KIcon::Small));
	QIconSet lookIcon = QIconSet(KGlobal::iconLoader()->loadIcon("viewmag", KIcon::Small));
	QIconSet newArticleIcon = QIconSet(KGlobal::iconLoader()->loadIcon("info", KIcon::Small));
	QIconSet oldArticleIcon = QIconSet(KGlobal::iconLoader()->loadIcon("mime_empty", KIcon::Small));
	QIconSet noArticlesIcon = QIconSet(KGlobal::iconLoader()->loadIcon("remove", KIcon::Small));

	insertTitle(KGlobal::iconLoader()->loadIcon("knewsticker", KIcon::Small), "KNewsTicker");

	KPopupMenu *submenu;

	for (uint i = 0; i < _parent->newsSource().count(); i++) {
		submenu = new KPopupMenu;
		insertItem(newsIcon, _parent->newsSource()[i]->name(), submenu, index++);
		submenu->insertItem(lookIcon, i18n("Check news"), index++);
		submenu->insertSeparator();
		if (_parent->newsSource()[i]->articles() > 0)
			for (uint x = 0; x < _parent->newsSource()[i]->articles(); x++) {
				Article *a = _parent->newsSource()[i]->article(x);
				if (a->read())
					submenu->insertItem(oldArticleIcon, a->headline().replace(QRegExp("&"), "&&"), index++);
				else
					submenu->insertItem(newArticleIcon, a->headline().replace(QRegExp("&"), "&&"), index++);
			}
		else
			submenu->insertItem(noArticlesIcon, i18n("No articles available"), index++);
	}

	if (_parent->newsSource().count() > 0)
		insertSeparator();

	insertItem(lookIcon, i18n("Check news"), index++);
	insertItem(i18n("Offline mode"), index++);
	setItemChecked(index - 1, _parent->offlineMode());
	insertSeparator();
	insertItem(helpIcon, i18n("Help"), index++);
	insertItem(helpIcon, i18n("About"), index++);
	insertSeparator();
	insertItem(confIcon, i18n("Preferences..."), index);

	_index = index;
}

int KNewsTickerMenu::exec(const QPoint &pos, int)
{
	uint result = KPopupMenu::exec(pos);

	uint res = result;
	for (uint i = 0; i < _parent->newsSource().count(); i++) {
		uint articles = _parent->newsSource()[i]->articles();
		uint entries = (articles ? articles : 1) + 1;
		if (res <= entries) {
			if (res == 1) {
				_parent->newsSource()[i]->retrieveNews(_parent->customNames(), _parent->updateId());
				_parent->setUpdateId(_parent->updateId() + 1);
			} else if (articles)
				_parent->newsSource()[i]->article(res - 2)->open();
			break;
		} else
			res -= entries + 1;
	}

	if (result == _index - 4)
		_parent->slotUpdateNews();
	else if (result == _index - 3)
		_parent->setOfflineMode(!_parent->offlineMode());
	else if (result == _index - 2)
		_parent->help();
	else if (result == _index - 1)
		_parent->about();
	else if (result == _index)
		_parent->preferences();

	return 0;
}

extern "C"
{
	KPanelApplet* init(QWidget *parent, const QString& configFile)
	{
		KGlobal::locale()->insertCatalogue("knewsticker");
		return new KNewsTicker(configFile, KPanelApplet::Stretch,
				KPanelApplet::Preferences | KPanelApplet::About | KPanelApplet::ReportBug,
			  parent, "knewsticker");
	}
}

#include "knewsticker.moc"
