<commit_before>/*
    kopetechatwindow.cpp - Chat Window

    Copyright (c) 2008      by <NAME>           <<EMAIL>>
    Copyright (c) 2007      by <NAME> <<EMAIL>>
    Copyright (c) 2002-2006 by <NAME>       <<EMAIL>>
    Copyright (c) 2003-2004 by <NAME>         <<EMAIL>>
    Copyright (C) 2002      by <NAME>
    Copyright (c) 2002      by <NAME>           <<EMAIL>>
    Copyright (c) 2002-2004 by <NAME>      <<EMAIL>>

    Kopete    (c) 2002-2005 by the Kopete developers <<EMAIL>>

    *************************************************************************
    *                                                                       *
    * 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 <QtCore/QTextStream>
#include <QtCore/QTimer>
#include <QtGui/QDockWidget>
#include <QtGui/QFrame>
#include <QtGui/QLabel>
#include <QtGui/QLayout>
#include <QtGui/QMenu>
#include <QtGui/QPixmap>
#include <QtGui/QCloseEvent>
#include <QtGui/QVBoxLayout>

#ifdef CHRONO
#include <QTime>
#endif

#include <kactioncollection.h>
#include <kcursor.h>
#include <klocale.h>
#include <kmenubar.h>
#include <kconfig.h>
#include <kmenu.h>
#include <kicon.h>
#include <kiconloader.h>
#include <kdebug.h>
#include <kwindowsystem.h>
#include <ktemporaryfile.h>
#include <kedittoolbar.h>
#include <kstatusbar.h>
#include <kpushbutton.h>
#include <ktabwidget.h>
#include <kdialog.h>
#include <kstringhandler.h>
#include <ksqueezedtextlabel.h>
#include <kstandardshortcut.h>
#include <kglobalsettings.h>
#include <kcolorscheme.h>
#include <khbox.h>
#include <kvbox.h>
#include <ktoolbar.h>
#include <kstandardaction.h>
#include <ktoggleaction.h>
#include <kactionmenu.h>
#include <ktoolbarspaceraction.h>

#include "chatmessagepart.h"
#include "chattexteditpart.h"
#include "chatview.h"
#include "kopeteapplication.h"
#include "kopetebehaviorsettings.h"
#include "kopetechatwindow.h"
#include "kopeteemoticonaction.h"
#include "kopetegroup.h"
#include "kopetechatsession.h"
#include "kopetemetacontact.h"
#include "kopetepluginmanager.h"
#include "kopeteprotocol.h"
#include "kopetestdaction.h"
#include "kopeteviewmanager.h"
#include "chatmemberslistview.h"
#include "chatsessionmemberslistmodel.h"

#include <qtoolbutton.h>
#include <kxmlguifactory.h>

typedef QMap<Kopete::Account*,KopeteChatWindow*> AccountMap;
typedef QMap<Kopete::Group*,KopeteChatWindow*> GroupMap;
typedef QMap<Kopete::MetaContact*,KopeteChatWindow*> MetaContactMap;
typedef QList<KopeteChatWindow*> WindowList;

using Kopete::ChatSessionMembersListModel;

namespace
{
	AccountMap accountMap;
	GroupMap groupMap;
	MetaContactMap mcMap;
	WindowList windows;
}

KopeteChatWindow *KopeteChatWindow::window( Kopete::ChatSession *manager )
{
	bool windowCreated = false;
	KopeteChatWindow *myWindow = 0;

	//Take the first and the first? What else?
	Kopete::Group *group = 0L;
	Kopete::ContactPtrList members = manager->members();
	Kopete::MetaContact *metaContact = members.first()->metaContact();

	if ( metaContact )
	{
		Kopete::GroupList gList = metaContact->groups();
		group = gList.first();
	}

	switch( Kopete::BehaviorSettings::self()->chatWindowGroupPolicy() )
	{
		//Open chats from the same protocol in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByAccount:
			if( accountMap.contains( manager->account() ) )
				myWindow = accountMap[ manager->account() ];
			else
				windowCreated = true;
			break;

		//Open chats from the same group in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByGroup:
			if( group && groupMap.contains( group ) )
				myWindow = groupMap[ group ];
			else
				windowCreated = true;
			break;

		//Open chats from the same metacontact in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByMetaContact:
			if( mcMap.contains( metaContact ) )
				myWindow = mcMap[ metaContact ];
			else
				windowCreated = true;
			break;

		//Open all chats in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupAll:
			if( windows.isEmpty() )
				windowCreated = true;
			else
			{
				//Here we are finding the window with the most tabs and
				//putting it there. Need this for the cases where config changes
				//midstream

				int viewCount = -1;
				WindowList::iterator it;
				for ( it = windows.begin(); it != windows.end(); ++it )
				{
					if( (*it)->chatViewCount() > viewCount )
					{
						myWindow = (*it);
						viewCount = (*it)->chatViewCount();
					}
				}
			}
			break;

		//Open every chat in a new window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::OpenNewWindow:
		default:
			windowCreated = true;
			break;
	}

	if ( windowCreated )
	{
		myWindow = new KopeteChatWindow( manager->form() );

		if ( !accountMap.contains( manager->account() ) )
			accountMap.insert( manager->account(), myWindow );

		if ( !mcMap.contains( metaContact ) )
			mcMap.insert( metaContact, myWindow );

		if ( group && !groupMap.contains( group ) )
			groupMap.insert( group, myWindow );
	}

//	kDebug( 14010 ) << "Open Windows: " << windows.count();

	return myWindow;
}

KopeteChatWindow::KopeteChatWindow( Kopete::ChatSession::Form form, QWidget *parent  )
	: KXmlGuiWindow( parent ), initialForm( form )
{
#ifdef CHRONO
	QTime chrono;chrono.start();
#endif
	m_activeView = 0L;
	m_popupView = 0L;
	backgroundFile = 0L;
	updateBg = true;
	m_tabBar = 0L;

	m_participantsWidget = new QDockWidget(this);
	m_participantsWidget->setAllowedAreas(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea);
	m_participantsWidget->setFeatures(QDockWidget::DockWidgetClosable);
	m_participantsWidget->setTitleBarWidget(0L);
	m_participantsWidget->setObjectName("Participants"); //object name is required for automatic position and settings save.

	ChatSessionMembersListModel *members_model = new ChatSessionMembersListModel(this);

	connect(this, SIGNAL(chatSessionChanged(Kopete::ChatSession *)), members_model, SLOT(setChatSession(Kopete::ChatSession *)));

	ChatMembersListView *chatmembers = new ChatMembersListView(m_participantsWidget);
	chatmembers->setModel(members_model);
	chatmembers->setWordWrap(true);
	m_participantsWidget->setWidget(chatmembers);
	initActions();

	addDockWidget(Qt::RightDockWidgetArea, m_participantsWidget);

	KVBox *vBox = new KVBox( this );
	vBox->setLineWidth( 0 );
	vBox->setSpacing( 0 );
	vBox->setFrameStyle( QFrame::NoFrame );
	// set default window size.  This could be removed by fixing the size hints of the contents
	if ( initialForm == Kopete::ChatSession::Chatroom ) {
		resize( 650, 400 );
	} else {
		m_participantsWidget->hide();
		resize( 400, 400 );
	}
	setCentralWidget( vBox );

	mainArea = new QFrame( vBox );
	mainArea->setLineWidth( 0 );
	mainArea->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
	mainLayout = new QVBoxLayout( mainArea );
	mainLayout->setContentsMargins(0, 4, 0, 0);

	if ( Kopete::BehaviorSettings::self()->chatWindowShowSendButton() )
	{
		//Send Button
		m_button_send = new KPushButton( i18nc("@action:button", "Send"), statusBar() );
		m_button_send->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) );
		m_button_send->setEnabled( false );
		m_button_send->setFont( statusBar()->font() );
		m_button_send->setFixedHeight( statusBar()->sizeHint().height() );
		connect( m_button_send, SIGNAL( clicked() ), this, SLOT( slotSendMessage() ) );
		statusBar()->addPermanentWidget( m_button_send, 0 );
	}
	else
		m_button_send = 0L;

	m_status_text = new KSqueezedTextLabel( i18nc("@info:status","Ready."), statusBar() );
	m_status_text->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
	m_status_text->setFont( statusBar()->font() );
	m_status_text->setFixedHeight( statusBar()->sizeHint().height() );
	statusBar()->addWidget( m_status_text, 1 );

	windows.append( this );
	windowListChanged();

	m_alwaysShowTabs = KGlobal::config()->group( "ChatWindowSettings" ).
                           readEntry( QLatin1String("AlwaysShowTabs"), false );
//	kDebug( 14010 ) << "Open Windows: " << windows.count();

	setupGUI( static_cast<StandardWindowOptions>(ToolBar | Keys | StatusBar | Save | Create) , "kopetechatwindow.rc" );

	//has to be done after the setupGUI, in order to have the toolbar set up to restore window settings.
	readOptions();
#ifdef CHRONO
	kDebug()<<"TIME: "<<chrono.elapsed();
#endif
}

KopeteChatWindow::~KopeteChatWindow()
{
	kDebug( 14010 ) ;

	emit( closing( this ) );

	for( AccountMap::Iterator it = accountMap.begin(); it != accountMap.end(); )
	{
		AccountMap::Iterator mayDeleteIt = it;
		++it;
		if( mayDeleteIt.value() == this )
			accountMap.remove( mayDeleteIt.key() );
	}

	for( GroupMap::Iterator it = groupMap.begin(); it != groupMap.end(); )
	{
		GroupMap::Iterator mayDeleteIt = it;
		++it;
		if( mayDeleteIt.value() == this )
			groupMap.remove( mayDeleteIt.key() );
	}

	for( MetaContactMap::Iterator it = mcMap.begin(); it != mcMap.end(); )
	{
		MetaContactMap::Iterator mayDeleteIt = it;
		++it;
		if( mayDeleteIt.value() == this )
			mcMap.remove( mayDeleteIt.key() );
	}

	windows.removeAt( windows.indexOf( this ) );
	windowListChanged();

//	kDebug( 14010 ) << "Open Windows: " << windows.count();

	saveOptions();

	delete backgroundFile;
	delete anim;
	delete animIcon;
}

void KopeteChatWindow::windowListChanged()
{
	// update all windows' Move Tab to Window action
	for ( WindowList::iterator it = windows.begin(); it != windows.end(); ++it )
		(*it)->checkDetachEnable();
}

void KopeteChatWindow::slotTabContextMenu( QWidget *tab, const QPoint &pos )
{
	m_popupView = static_cast<ChatView*>( tab );

	KMenu *popup = new KMenu;
	popup->addTitle( KStringHandler::rsqueeze( m_popupView->caption() ) );
	popup->addAction( actionContactMenu );
	popup->addSeparator();
	popup->addAction( actionTabPlacementMenu );
	popup->addAction( tabDetach );
	popup->addAction( actionDetachMenu );
	popup->addAction( tabClose );
	popup->exec( pos );

	delete popup;
	m_popupView = 0;
}

ChatView *KopeteChatWindow::activeView()
{
	return m_activeView;
}

void KopeteChatWindow::initActions(void)
{
	KActionCollection *coll = actionCollection();

	createStandardStatusBarAction();

	chatSend = new KAction( KIcon("mail-send"), i18n( "&Send Message" ), coll );
	coll->addAction( "chat_send", chatSend );
	connect( chatSend, SIGNAL( triggered(bool) ), SLOT( slotSendMessage() ) );
	//Default to 'Return' for sending messages
	chatSend->setShortcut( QKeySequence(Qt::Key_Return) );
	chatSend->setEnabled( false );

	chatSendFile = new KAction( KIcon("mail-attachment"), i18n( "Send File" ), coll );
	coll->addAction( "chat_send_file", chatSendFile );
	connect( chatSendFile, SIGNAL( triggered(bool) ), SLOT( slotSendFile() ) );
	chatSendFile->setEnabled( false );

	KStandardAction::save ( this, SLOT(slotChatSave()), coll );
	KStandardAction::print ( this, SLOT(slotChatPrint()), coll );
	KAction* quitAction = KStandardAction::quit ( this, SLOT(close()), coll );
	quitAction->setText( i18n("Close All Chats") );

	tabClose = KStandardAction::close ( this, SLOT(slotChatClosed()), coll );
	coll->addAction( "tabs_close", tabClose );

	tabActive=new KAction( i18n( "&Activate Next Active Tab" ), coll );
	coll->addAction( "tabs_active", tabActive );
// 	tabActive->setShortcut( KStandardShortcut::tabNext() );
	tabActive->setEnabled( false );
	connect( tabActive, SIGNAL(triggered(bool)), this, SLOT(slotNextActiveTab()) );

	tabRight=new KAction( i18n( "&Activate Next Tab" ), coll );
	coll->addAction( "tabs_right", tabRight );
	tabRight->setShortcut( KStandardShortcut::tabNext() );
	tabRight->setEnabled( false );
	connect( tabRight, SIGNAL(triggered(bool)), this, SLOT(slotNextTab()) );

	tabLeft=new KAction( i18n( "&Activate Previous Tab" ), coll );
	coll->addAction( "tabs_left", tabLeft );
	tabLeft->setShortcut( KStandardShortcut::tabPrev() );
	tabLeft->setEnabled( false );
	connect( tabLeft, SIGNAL(triggered(bool)), this, SLOT(slotPreviousTab()) );

	// This action exists mostly so that the shortcut is configurable.
	// The actual "slot" is the eventFilter.
	nickComplete = new KAction( i18n( "Nic&k Completion" ), coll );
	coll->addAction( "nick_complete", nickComplete );
	nickComplete->setShortcut( QKeySequence( Qt::Key_Tab ) );

	tabDetach = new KAction( KIcon("tab-detach"), i18n( "&Detach Chat" ), coll );
	coll->addAction( "tabs_detach", tabDetach );
	tabDetach->setEnabled( false );
	connect( tabDetach, SIGNAL(triggered(bool)), this, SLOT( slotDetachChat() ));

	actionDetachMenu = new KActionMenu( KIcon("tab-detach"), i18n( "&Move Tab to Window" ), coll );
	coll->addAction( "tabs_detachmove", actionDetachMenu );
	actionDetachMenu->setDelayed( false );

	connect ( actionDetachMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareDetachMenu()) );
	connect ( actionDetachMenu->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotDetachChat(QAction*)) );

	actionTabPlacementMenu = new KActionMenu( i18n( "&Tab Placement" ), coll );
	coll->addAction( "tabs_placement", actionTabPlacementMenu );
	connect ( actionTabPlacementMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPreparePlacementMenu()) );
	connect ( actionTabPlacementMenu->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotPlaceTabs(QAction*)) );

	tabDetach->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B) );

	KStandardAction::cut( this, SLOT(slotCut()), coll);
	KStandardAction::copy( this, SLOT(slotCopy()), coll);
	KStandardAction::paste( this, SLOT(slotPaste()), coll);

	KAction* action;

	historyUp = new KAction( i18n( "Previous History" ), coll );
	coll->addAction( "history_up", historyUp );
	historyUp->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Up) );
	connect( historyUp, SIGNAL(triggered(bool)), this, SLOT(slotHistoryUp()) );

	historyDown = new KAction( i18n( "Next History" ), coll );
	coll->addAction( "history_down", historyDown );
	historyDown->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Down) );
	connect( historyDown, SIGNAL(triggered(bool)), this, SLOT(slotHistoryDown()) );

	action = KStandardAction::prior( this, SLOT( slotPageUp() ), coll );
	coll->addAction( "scroll_up", action );
	action = KStandardAction::next( this, SLOT( slotPageDown() ), coll );
	coll->addAction( "scroll_down", action );

	KStandardAction::showMenubar( menuBar(), SLOT(setVisible(bool)), coll );

	toggleAutoSpellCheck = new KToggleAction( i18n( "Automatic Spell Checking" ), coll );
	coll->addAction( "enable_auto_spell_check", toggleAutoSpellCheck );
	toggleAutoSpellCheck->setChecked( true );
	connect( toggleAutoSpellCheck, SIGNAL(triggered(bool)), this, SLOT(toggleAutoSpellChecking()) );

	QAction *toggleParticipantsAction = m_participantsWidget->toggleViewAction( );
	toggleParticipantsAction->setText( i18n( "Show Participants" ) );
	toggleParticipantsAction->setIconText(i18n( "Participants" ));
	toggleParticipantsAction->setIcon(KIcon( "system-users" ) );
	coll->addAction ( "show_participants_widget", toggleParticipantsAction );

	actionSmileyMenu = new KopeteEmoticonAction( coll );
	coll->addAction( "format_smiley", actionSmileyMenu );
	actionSmileyMenu->setDelayed( false );
	connect(actionSmileyMenu, SIGNAL(activated(const QString &)), this, SLOT(slotSmileyActivated(const QString &)));

	actionContactMenu = new KActionMenu(i18n("Co&ntacts"), coll );
	coll->addAction( "contacts_menu", actionContactMenu );
	actionContactMenu->setDelayed( false );
	connect ( actionContactMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareContactMenu()) );

	KopeteStdAction::preferences( coll , "settings_prefs" );

	KToolBarSpacerAction * spacer = new KToolBarSpacerAction( coll );
	coll->addAction( "spacer", spacer );

	//The Sending movie
	normalIcon = QPixmap( BarIcon( QLatin1String( "kopete" ) ) );

	// we can't set the tool bar as parent, if we do, it will be deleted when we configure toolbars
	anim = new QLabel( QString::null, 0L );	//krazy:exclude=nullstrassign for old broken gcc
	anim->setObjectName( QLatin1String("kde toolbar widget") );
	anim->setMargin(5);
	anim->setPixmap( normalIcon );

	animIcon = KIconLoader::global()->loadMovie( QLatin1String( "newmessage" ), KIconLoader::Toolbar);
	if ( animIcon )
		animIcon->setPaused(true);

	KAction *animAction = new KAction( i18n("Toolbar Animation"), coll );
        coll->addAction( "toolbar_animation", animAction );
	animAction->setDefaultWidget( anim );

	//toolBar()->insertWidget( 99, anim->width(), anim );
	//toolBar()->alignItemRight( 99 );
}

/*
const QString KopeteChatWindow::fileContents( const QString &path ) const
{
 	QString contents;
	QFile file( path );
	if ( file.open( QIODevice::ReadOnly ) )
	{
		QTextStream stream( &file );
		contents = stream.readAll();
		file.close();
	}

	return contents;
}
*/
void KopeteChatWindow::slotStopAnimation( ChatView* view )
{
	if( view == m_activeView )
	{
		anim->setPixmap( normalIcon );
		if( animIcon && animIcon->state() == QMovie::Running )
			animIcon->setPaused( true );
	}
}

void KopeteChatWindow::slotUpdateSendEnabled()
{
	if ( !m_activeView ) return;

	bool enabled = m_activeView->canSend();
	chatSend->setEnabled( enabled );
	if(m_button_send)
		m_button_send->setEnabled( enabled );
}

void KopeteChatWindow::updateChatSendFileAction()
{
	if ( !m_activeView )
		return;
	
	chatSendFile->setEnabled( m_activeView->canSendFile() );
}

void KopeteChatWindow::toggleAutoSpellChecking()
{
	if ( !m_activeView )
		return;

	bool currentSetting = m_activeView->editPart()->checkSpellingEnabled();
	m_activeView->editPart()->setCheckSpellingEnabled( !currentSetting );
	updateSpellCheckAction();
}

void KopeteChatWindow::updateSpellCheckAction()
{
	if ( !m_activeView )
		return;

	bool currentSetting = m_activeView->editPart()->checkSpellingEnabled();
	toggleAutoSpellCheck->setChecked( currentSetting );
}

void KopeteChatWindow::enableSpellCheckAction(bool enable)
{
	toggleAutoSpellCheck->setChecked( enable );
}

void KopeteChatWindow::updateActions()
{
	updateSpellCheckAction();
	updateChatSendFileAction();
}

void KopeteChatWindow::slotHistoryUp()
{
	if( m_activeView )
		m_activeView->editPart()->historyUp();
}

void KopeteChatWindow::slotHistoryDown()
{
	if( m_activeView )
		m_activeView->editPart()->historyDown();
}

void KopeteChatWindow::slotPageUp()
{
	if( m_activeView )
		m_activeView->messagePart()->pageUp();
}

void KopeteChatWindow::slotPageDown()
{
	if( m_activeView )
		m_activeView->messagePart()->pageDown();
}

void KopeteChatWindow::slotCut()
{
	m_activeView->cut();
}

void KopeteChatWindow::slotCopy()
{
	m_activeView->copy();
}

void KopeteChatWindow::slotPaste()
{
	m_activeView->paste();
}


void KopeteChatWindow::slotSetFont()
{
	m_activeView->setFont();
}

void KopeteChatWindow::slotResetFontAndColor()
{
	m_activeView->resetFontAndColor();
}

void KopeteChatWindow::slotSetFgColor()
{
	m_activeView->setFgColor();
}

void KopeteChatWindow::slotSetBgColor()
{
	m_activeView->setBgColor();
}

void KopeteChatWindow::setStatus(const QString &text)
{
	m_status_text->setText(text);
}

void KopeteChatWindow::createTabBar()
{
	if( !m_tabBar )
	{
		KConfigGroup cg( KGlobal::config(), QLatin1String("ChatWindowSettings") );

		m_tabBar = new KTabWidget( mainArea );
		m_tabBar->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
		m_tabBar->setHoverCloseButton(cg.readEntry( QLatin1String("HoverClose"), false ));
		m_tabBar->setTabReorderingEnabled(true);
		m_tabBar->setAutomaticResizeTabs(true);
		connect( m_tabBar, SIGNAL( closeRequest( QWidget* )), this, SLOT( slotCloseChat( QWidget* ) ) );

		QToolButton* m_rightWidget = new QToolButton( m_tabBar );
		connect( m_rightWidget, SIGNAL( clicked() ), this, SLOT( slotChatClosed() ) );
		m_rightWidget->setIcon( SmallIcon( "tab-close" ) );
		m_rightWidget->adjustSize();
		m_rightWidget->setToolTip( i18nc("@info:tooltip","Close the current tab") );
		m_tabBar->setCornerWidget( m_rightWidget, Qt::TopRightCorner );

		mainLayout->addWidget( m_tabBar );
		m_tabBar->show();

		for( ChatViewList::iterator it = chatViewList.begin(); it != chatViewList.end(); ++it )<commit_msg>Changed deprecated uses of KTabWidget members:<commit_after>/*
    kopetechatwindow.cpp - Chat Window

    Copyright (c) 2008      by <NAME>           <<EMAIL>>
    Copyright (c) 2007      by <NAME> <<EMAIL>>
    Copyright (c) 2002-2006 by <NAME>       <<EMAIL>>
    Copyright (c) 2003-2004 by <NAME>         <<EMAIL>>
    Copyright (C) 2002      by <NAME>
    Copyright (c) 2002      by <NAME>           <<EMAIL>>
    Copyright (c) 2002-2004 by <NAME>      <<EMAIL>>

    Kopete    (c) 2002-2005 by the Kopete developers <<EMAIL>>

    *************************************************************************
    *                                                                       *
    * 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 <QtCore/QTextStream>
#include <QtCore/QTimer>
#include <QtGui/QDockWidget>
#include <QtGui/QFrame>
#include <QtGui/QLabel>
#include <QtGui/QLayout>
#include <QtGui/QMenu>
#include <QtGui/QPixmap>
#include <QtGui/QCloseEvent>
#include <QtGui/QVBoxLayout>

#ifdef CHRONO
#include <QTime>
#endif

#include <kactioncollection.h>
#include <kcursor.h>
#include <klocale.h>
#include <kmenubar.h>
#include <kconfig.h>
#include <kmenu.h>
#include <kicon.h>
#include <kiconloader.h>
#include <kdebug.h>
#include <kwindowsystem.h>
#include <ktemporaryfile.h>
#include <kedittoolbar.h>
#include <kstatusbar.h>
#include <kpushbutton.h>
#include <ktabwidget.h>
#include <kdialog.h>
#include <kstringhandler.h>
#include <ksqueezedtextlabel.h>
#include <kstandardshortcut.h>
#include <kglobalsettings.h>
#include <kcolorscheme.h>
#include <khbox.h>
#include <kvbox.h>
#include <ktoolbar.h>
#include <kstandardaction.h>
#include <ktoggleaction.h>
#include <kactionmenu.h>
#include <ktoolbarspaceraction.h>

#include "chatmessagepart.h"
#include "chattexteditpart.h"
#include "chatview.h"
#include "kopeteapplication.h"
#include "kopetebehaviorsettings.h"
#include "kopetechatwindow.h"
#include "kopeteemoticonaction.h"
#include "kopetegroup.h"
#include "kopetechatsession.h"
#include "kopetemetacontact.h"
#include "kopetepluginmanager.h"
#include "kopeteprotocol.h"
#include "kopetestdaction.h"
#include "kopeteviewmanager.h"
#include "chatmemberslistview.h"
#include "chatsessionmemberslistmodel.h"

#include <qtoolbutton.h>
#include <kxmlguifactory.h>

typedef QMap<Kopete::Account*,KopeteChatWindow*> AccountMap;
typedef QMap<Kopete::Group*,KopeteChatWindow*> GroupMap;
typedef QMap<Kopete::MetaContact*,KopeteChatWindow*> MetaContactMap;
typedef QList<KopeteChatWindow*> WindowList;

using Kopete::ChatSessionMembersListModel;

namespace
{
	AccountMap accountMap;
	GroupMap groupMap;
	MetaContactMap mcMap;
	WindowList windows;
}

KopeteChatWindow *KopeteChatWindow::window( Kopete::ChatSession *manager )
{
	bool windowCreated = false;
	KopeteChatWindow *myWindow = 0;

	//Take the first and the first? What else?
	Kopete::Group *group = 0L;
	Kopete::ContactPtrList members = manager->members();
	Kopete::MetaContact *metaContact = members.first()->metaContact();

	if ( metaContact )
	{
		Kopete::GroupList gList = metaContact->groups();
		group = gList.first();
	}

	switch( Kopete::BehaviorSettings::self()->chatWindowGroupPolicy() )
	{
		//Open chats from the same protocol in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByAccount:
			if( accountMap.contains( manager->account() ) )
				myWindow = accountMap[ manager->account() ];
			else
				windowCreated = true;
			break;

		//Open chats from the same group in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByGroup:
			if( group && groupMap.contains( group ) )
				myWindow = groupMap[ group ];
			else
				windowCreated = true;
			break;

		//Open chats from the same metacontact in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByMetaContact:
			if( mcMap.contains( metaContact ) )
				myWindow = mcMap[ metaContact ];
			else
				windowCreated = true;
			break;

		//Open all chats in the same window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupAll:
			if( windows.isEmpty() )
				windowCreated = true;
			else
			{
				//Here we are finding the window with the most tabs and
				//putting it there. Need this for the cases where config changes
				//midstream

				int viewCount = -1;
				WindowList::iterator it;
				for ( it = windows.begin(); it != windows.end(); ++it )
				{
					if( (*it)->chatViewCount() > viewCount )
					{
						myWindow = (*it);
						viewCount = (*it)->chatViewCount();
					}
				}
			}
			break;

		//Open every chat in a new window
		case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::OpenNewWindow:
		default:
			windowCreated = true;
			break;
	}

	if ( windowCreated )
	{
		myWindow = new KopeteChatWindow( manager->form() );

		if ( !accountMap.contains( manager->account() ) )
			accountMap.insert( manager->account(), myWindow );

		if ( !mcMap.contains( metaContact ) )
			mcMap.insert( metaContact, myWindow );

		if ( group && !groupMap.contains( group ) )
			groupMap.insert( group, myWindow );
	}

//	kDebug( 14010 ) << "Open Windows: " << windows.count();

	return myWindow;
}

KopeteChatWindow::KopeteChatWindow( Kopete::ChatSession::Form form, QWidget *parent  )
	: KXmlGuiWindow( parent ), initialForm( form )
{
#ifdef CHRONO
	QTime chrono;chrono.start();
#endif
	m_activeView = 0L;
	m_popupView = 0L;
	backgroundFile = 0L;
	updateBg = true;
	m_tabBar = 0L;

	m_participantsWidget = new QDockWidget(this);
	m_participantsWidget->setAllowedAreas(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea);
	m_participantsWidget->setFeatures(QDockWidget::DockWidgetClosable);
	m_participantsWidget->setTitleBarWidget(0L);
	m_participantsWidget->setObjectName("Participants"); //object name is required for automatic position and settings save.

	ChatSessionMembersListModel *members_model = new ChatSessionMembersListModel(this);

	connect(this, SIGNAL(chatSessionChanged(Kopete::ChatSession *)), members_model, SLOT(setChatSession(Kopete::ChatSession *)));

	ChatMembersListView *chatmembers = new ChatMembersListView(m_participantsWidget);
	chatmembers->setModel(members_model);
	chatmembers->setWordWrap(true);
	m_participantsWidget->setWidget(chatmembers);
	initActions();

	addDockWidget(Qt::RightDockWidgetArea, m_participantsWidget);

	KVBox *vBox = new KVBox( this );
	vBox->setLineWidth( 0 );
	vBox->setSpacing( 0 );
	vBox->setFrameStyle( QFrame::NoFrame );
	// set default window size.  This could be removed by fixing the size hints of the contents
	if ( initialForm == Kopete::ChatSession::Chatroom ) {
		resize( 650, 400 );
	} else {
		m_participantsWidget->hide();
		resize( 400, 400 );
	}
	setCentralWidget( vBox );

	mainArea = new QFrame( vBox );
	mainArea->setLineWidth( 0 );
	mainArea->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
	mainLayout = new QVBoxLayout( mainArea );
	mainLayout->setContentsMargins(0, 4, 0, 0);

	if ( Kopete::BehaviorSettings::self()->chatWindowShowSendButton() )
	{
		//Send Button
		m_button_send = new KPushButton( i18nc("@action:button", "Send"), statusBar() );
		m_button_send->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) );
		m_button_send->setEnabled( false );
		m_button_send->setFont( statusBar()->font() );
		m_button_send->setFixedHeight( statusBar()->sizeHint().height() );
		connect( m_button_send, SIGNAL( clicked() ), this, SLOT( slotSendMessage() ) );
		statusBar()->addPermanentWidget( m_button_send, 0 );
	}
	else
		m_button_send = 0L;

	m_status_text = new KSqueezedTextLabel( i18nc("@info:status","Ready."), statusBar() );
	m_status_text->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
	m_status_text->setFont( statusBar()->font() );
	m_status_text->setFixedHeight( statusBar()->sizeHint().height() );
	statusBar()->addWidget( m_status_text, 1 );

	windows.append( this );
	windowListChanged();

	m_alwaysShowTabs = KGlobal::config()->group( "ChatWindowSettings" ).
                           readEntry( QLatin1String("AlwaysShowTabs"), false );
//	kDebug( 14010 ) << "Open Windows: " << windows.count();

	setupGUI( static_cast<StandardWindowOptions>(ToolBar | Keys | StatusBar | Save | Create) , "kopetechatwindow.rc" );

	//has to be done after the setupGUI, in order to have the toolbar set up to restore window settings.
	readOptions();
#ifdef CHRONO
	kDebug()<<"TIME: "<<chrono.elapsed();
#endif
}

KopeteChatWindow::~KopeteChatWindow()
{
	kDebug( 14010 ) ;

	emit( closing( this ) );

	for( AccountMap::Iterator it = accountMap.begin(); it != accountMap.end(); )
	{
		AccountMap::Iterator mayDeleteIt = it;
		++it;
		if( mayDeleteIt.value() == this )
			accountMap.remove( mayDeleteIt.key() );
	}

	for( GroupMap::Iterator it = groupMap.begin(); it != groupMap.end(); )
	{
		GroupMap::Iterator mayDeleteIt = it;
		++it;
		if( mayDeleteIt.value() == this )
			groupMap.remove( mayDeleteIt.key() );
	}

	for( MetaContactMap::Iterator it = mcMap.begin(); it != mcMap.end(); )
	{
		MetaContactMap::Iterator mayDeleteIt = it;
		++it;
		if( mayDeleteIt.value() == this )
			mcMap.remove( mayDeleteIt.key() );
	}

	windows.removeAt( windows.indexOf( this ) );
	windowListChanged();

//	kDebug( 14010 ) << "Open Windows: " << windows.count();

	saveOptions();

	delete backgroundFile;
	delete anim;
	delete animIcon;
}

void KopeteChatWindow::windowListChanged()
{
	// update all windows' Move Tab to Window action
	for ( WindowList::iterator it = windows.begin(); it != windows.end(); ++it )
		(*it)->checkDetachEnable();
}

void KopeteChatWindow::slotTabContextMenu( QWidget *tab, const QPoint &pos )
{
	m_popupView = static_cast<ChatView*>( tab );

	KMenu *popup = new KMenu;
	popup->addTitle( KStringHandler::rsqueeze( m_popupView->caption() ) );
	popup->addAction( actionContactMenu );
	popup->addSeparator();
	popup->addAction( actionTabPlacementMenu );
	popup->addAction( tabDetach );
	popup->addAction( actionDetachMenu );
	popup->addAction( tabClose );
	popup->exec( pos );

	delete popup;
	m_popupView = 0;
}

ChatView *KopeteChatWindow::activeView()
{
	return m_activeView;
}

void KopeteChatWindow::initActions(void)
{
	KActionCollection *coll = actionCollection();

	createStandardStatusBarAction();

	chatSend = new KAction( KIcon("mail-send"), i18n( "&Send Message" ), coll );
	coll->addAction( "chat_send", chatSend );
	connect( chatSend, SIGNAL( triggered(bool) ), SLOT( slotSendMessage() ) );
	//Default to 'Return' for sending messages
	chatSend->setShortcut( QKeySequence(Qt::Key_Return) );
	chatSend->setEnabled( false );

	chatSendFile = new KAction( KIcon("mail-attachment"), i18n( "Send File" ), coll );
	coll->addAction( "chat_send_file", chatSendFile );
	connect( chatSendFile, SIGNAL( triggered(bool) ), SLOT( slotSendFile() ) );
	chatSendFile->setEnabled( false );

	KStandardAction::save ( this, SLOT(slotChatSave()), coll );
	KStandardAction::print ( this, SLOT(slotChatPrint()), coll );
	KAction* quitAction = KStandardAction::quit ( this, SLOT(close()), coll );
	quitAction->setText( i18n("Close All Chats") );

	tabClose = KStandardAction::close ( this, SLOT(slotChatClosed()), coll );
	coll->addAction( "tabs_close", tabClose );

	tabActive=new KAction( i18n( "&Activate Next Active Tab" ), coll );
	coll->addAction( "tabs_active", tabActive );
// 	tabActive->setShortcut( KStandardShortcut::tabNext() );
	tabActive->setEnabled( false );
	connect( tabActive, SIGNAL(triggered(bool)), this, SLOT(slotNextActiveTab()) );

	tabRight=new KAction( i18n( "&Activate Next Tab" ), coll );
	coll->addAction( "tabs_right", tabRight );
	tabRight->setShortcut( KStandardShortcut::tabNext() );
	tabRight->setEnabled( false );
	connect( tabRight, SIGNAL(triggered(bool)), this, SLOT(slotNextTab()) );

	tabLeft=new KAction( i18n( "&Activate Previous Tab" ), coll );
	coll->addAction( "tabs_left", tabLeft );
	tabLeft->setShortcut( KStandardShortcut::tabPrev() );
	tabLeft->setEnabled( false );
	connect( tabLeft, SIGNAL(triggered(bool)), this, SLOT(slotPreviousTab()) );

	// This action exists mostly so that the shortcut is configurable.
	// The actual "slot" is the eventFilter.
	nickComplete = new KAction( i18n( "Nic&k Completion" ), coll );
	coll->addAction( "nick_complete", nickComplete );
	nickComplete->setShortcut( QKeySequence( Qt::Key_Tab ) );

	tabDetach = new KAction( KIcon("tab-detach"), i18n( "&Detach Chat" ), coll );
	coll->addAction( "tabs_detach", tabDetach );
	tabDetach->setEnabled( false );
	connect( tabDetach, SIGNAL(triggered(bool)), this, SLOT( slotDetachChat() ));

	actionDetachMenu = new KActionMenu( KIcon("tab-detach"), i18n( "&Move Tab to Window" ), coll );
	coll->addAction( "tabs_detachmove", actionDetachMenu );
	actionDetachMenu->setDelayed( false );

	connect ( actionDetachMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareDetachMenu()) );
	connect ( actionDetachMenu->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotDetachChat(QAction*)) );

	actionTabPlacementMenu = new KActionMenu( i18n( "&Tab Placement" ), coll );
	coll->addAction( "tabs_placement", actionTabPlacementMenu );
	connect ( actionTabPlacementMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPreparePlacementMenu()) );
	connect ( actionTabPlacementMenu->menu(), SIGNAL(triggered(QAction*)), this, SLOT(slotPlaceTabs(QAction*)) );

	tabDetach->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B) );

	KStandardAction::cut( this, SLOT(slotCut()), coll);
	KStandardAction::copy( this, SLOT(slotCopy()), coll);
	KStandardAction::paste( this, SLOT(slotPaste()), coll);

	KAction* action;

	historyUp = new KAction( i18n( "Previous History" ), coll );
	coll->addAction( "history_up", historyUp );
	historyUp->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Up) );
	connect( historyUp, SIGNAL(triggered(bool)), this, SLOT(slotHistoryUp()) );

	historyDown = new KAction( i18n( "Next History" ), coll );
	coll->addAction( "history_down", historyDown );
	historyDown->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_Down) );
	connect( historyDown, SIGNAL(triggered(bool)), this, SLOT(slotHistoryDown()) );

	action = KStandardAction::prior( this, SLOT( slotPageUp() ), coll );
	coll->addAction( "scroll_up", action );
	action = KStandardAction::next( this, SLOT( slotPageDown() ), coll );
	coll->addAction( "scroll_down", action );

	KStandardAction::showMenubar( menuBar(), SLOT(setVisible(bool)), coll );

	toggleAutoSpellCheck = new KToggleAction( i18n( "Automatic Spell Checking" ), coll );
	coll->addAction( "enable_auto_spell_check", toggleAutoSpellCheck );
	toggleAutoSpellCheck->setChecked( true );
	connect( toggleAutoSpellCheck, SIGNAL(triggered(bool)), this, SLOT(toggleAutoSpellChecking()) );

	QAction *toggleParticipantsAction = m_participantsWidget->toggleViewAction( );
	toggleParticipantsAction->setText( i18n( "Show Participants" ) );
	toggleParticipantsAction->setIconText(i18n( "Participants" ));
	toggleParticipantsAction->setIcon(KIcon( "system-users" ) );
	coll->addAction ( "show_participants_widget", toggleParticipantsAction );

	actionSmileyMenu = new KopeteEmoticonAction( coll );
	coll->addAction( "format_smiley", actionSmileyMenu );
	actionSmileyMenu->setDelayed( false );
	connect(actionSmileyMenu, SIGNAL(activated(const QString &)), this, SLOT(slotSmileyActivated(const QString &)));

	actionContactMenu = new KActionMenu(i18n("Co&ntacts"), coll );
	coll->addAction( "contacts_menu", actionContactMenu );
	actionContactMenu->setDelayed( false );
	connect ( actionContactMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareContactMenu()) );

	KopeteStdAction::preferences( coll , "settings_prefs" );

	KToolBarSpacerAction * spacer = new KToolBarSpacerAction( coll );
	coll->addAction( "spacer", spacer );

	//The Sending movie
	normalIcon = QPixmap( BarIcon( QLatin1String( "kopete" ) ) );

	// we can't set the tool bar as parent, if we do, it will be deleted when we configure toolbars
	anim = new QLabel( QString::null, 0L );	//krazy:exclude=nullstrassign for old broken gcc
	anim->setObjectName( QLatin1String("kde toolbar widget") );
	anim->setMargin(5);
	anim->setPixmap( normalIcon );

	animIcon = KIconLoader::global()->loadMovie( QLatin1String( "newmessage" ), KIconLoader::Toolbar);
	if ( animIcon )
		animIcon->setPaused(true);

	KAction *animAction = new KAction( i18n("Toolbar Animation"), coll );
        coll->addAction( "toolbar_animation", animAction );
	animAction->setDefaultWidget( anim );

	//toolBar()->insertWidget( 99, anim->width(), anim );
	//toolBar()->alignItemRight( 99 );
}

/*
const QString KopeteChatWindow::fileContents( const QString &path ) const
{
 	QString contents;
	QFile file( path );
	if ( file.open( QIODevice::ReadOnly ) )
	{
		QTextStream stream( &file );
		contents = stream.readAll();
		file.close();
	}

	return contents;
}
*/
void KopeteChatWindow::slotStopAnimation( ChatView* view )
{
	if( view == m_activeView )
	{
		anim->setPixmap( normalIcon );
		if( animIcon && animIcon->state() == QMovie::Running )
			animIcon->setPaused( true );
	}
}

void KopeteChatWindow::slotUpdateSendEnabled()
{
	if ( !m_activeView ) return;

	bool enabled = m_activeView->canSend();
	chatSend->setEnabled( enabled );
	if(m_button_send)
		m_button_send->setEnabled( enabled );
}

void KopeteChatWindow::updateChatSendFileAction()
{
	if ( !m_activeView )
		return;
	
	chatSendFile->setEnabled( m_activeView->canSendFile() );
}

void KopeteChatWindow::toggleAutoSpellChecking()
{
	if ( !m_activeView )
		return;

	bool currentSetting = m_activeView->editPart()->checkSpellingEnabled();
	m_activeView->editPart()->setCheckSpellingEnabled( !currentSetting );
	updateSpellCheckAction();
}

void KopeteChatWindow::updateSpellCheckAction()
{
	if ( !m_activeView )
		return;

	bool currentSetting = m_activeView->editPart()->checkSpellingEnabled();
	toggleAutoSpellCheck->setChecked( currentSetting );
}

void KopeteChatWindow::enableSpellCheckAction(bool enable)
{
	toggleAutoSpellCheck->setChecked( enable );
}

void KopeteChatWindow::updateActions()
{
	updateSpellCheckAction();
	updateChatSendFileAction();
}

void KopeteChatWindow::slotHistoryUp()
{
	if( m_activeView )
		m_activeView->editPart()->historyUp();
}

void KopeteChatWindow::slotHistoryDown()
{
	if( m_activeView )
		m_activeView->editPart()->historyDown();
}

void KopeteChatWindow::slotPageUp()
{
	if( m_activeView )
		m_activeView->messagePart()->pageUp();
}

void KopeteChatWindow::slotPageDown()
{
	if( m_activeView )
		m_activeView->messagePart()->pageDown();
}

void KopeteChatWindow::slotCut()
{
	m_activeView->cut();
}

void KopeteChatWindow::slotCopy()
{
	m_activeView->copy();
}

void KopeteChatWindow::slotPaste()
{
	m_activeView->paste();
}


void KopeteChatWindow::slotSetFont()
{
	m_activeView->setFont();
}

void KopeteChatWindow::slotResetFontAndColor()
{
	m_activeView->resetFontAndColor();
}

void KopeteChatWindow::slotSetFgColor()
{
	m_activeView->setFgColor();
}

void KopeteChatWindow::slotSetBgColor()
{
	m_activeView->setBgColor();
}

void KopeteChatWindow::setStatus(const QString &text)
{
	m_status_text->setText(text);
}

void KopeteChatWindow::createTabBar()
{
	if( !m_tabBar )
	{
		KConfigGroup cg( KGlobal::config(), QLatin1String("ChatWindowSettings") );

		m_tabBar = new KTabWidget( mainArea );
		m_tabBar->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
		m_tabBar->setTabsClosable(cg.readEntry( QLatin1String("HoverClose"), false ));
		m_tabBar->setMovable(true);
		m_tabBar->setAutomaticResizeTabs(true);
		connect( m_tabBar, SIGNAL( closeRequest( QWidget* )), this, SLOT( slotCloseChat( QWidget* ) ) );

		QToolButton* m_rightWidget = new QToolButton( m_tabBar );
		connect( m_rightWidget, SIGNAL( clicked() ), this, SLOT( slotChatClosed() ) );
		m_rightWidget->setIcon( SmallIcon( "tab-close" ) );
		m_rightWidget->adjustSize();
		m_rightWidget->setToolTip( i18nc("@info:tooltip","Close the current tab") );
		m_tabBar->setCornerWidget( m_rightWidget, Qt::TopRightCorner );

		mainLayout->addWidget( m_tabBar );
		m_tabBar->show();

		for( ChatViewList::iterator it = chatViewList.begin(); it != chatViewList.end(); ++it )
{'fraction_non_alphanumeric': 0.1485885915217816, 'fraction_numerical': 0.007423324868138308, 'mean_word_length': 5.2869204789683755, 'pattern_counts': {'":': 0, '<': 171, '<?xml version=': 0, '>': 439, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2962949', 'n_tokens_mistral': 14391, 'n_tokens_neox': 13455, 'n_words': 2841}