3018 lines
92 KiB
C++
3018 lines
92 KiB
C++
/***************************************************************************
|
|
chatwindow.cpp - description
|
|
-------------------
|
|
begin : Tue Apr 23 2002
|
|
copyright : (C) 2002 by Mike K. Bennett
|
|
email : mkb137b@hotmail.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* 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 "chatwindow.h"
|
|
|
|
#include "../contact/contact.h"
|
|
#include "../contact/msnobject.h"
|
|
#include "../contact/invitedcontact.h"
|
|
#include "../dialogs/chathistorydialog.h"
|
|
#include "../dialogs/invitedialog.h"
|
|
#include "../model/contactlist.h"
|
|
#include "../network/msnswitchboardconnection.h"
|
|
#include "../utils/richtextparser.h"
|
|
#include "../utils/kmessconfig.h"
|
|
#include "../utils/kmessshared.h"
|
|
#include "../currentaccount.h"
|
|
#include "../kmess.h"
|
|
#include "../kmessapplication.h"
|
|
#include "../kmessdebug.h"
|
|
#include "chat.h"
|
|
#include "config-kmess.h"
|
|
#include "chathistorymanager.h"
|
|
#include "contactswidget.h"
|
|
#include "emoticonswidget.h"
|
|
#include "winkswidget.h"
|
|
|
|
#include <IsfQt>
|
|
#include <IsfQtDrawing>
|
|
|
|
#include <QBuffer>
|
|
#include <QDir>
|
|
#include <QDockWidget>
|
|
#include <QDragEnterEvent>
|
|
#include <QDropEvent>
|
|
#include <QKeyEvent>
|
|
#include <QLocale>
|
|
#include <QMimeData>
|
|
#include <QMouseEvent>
|
|
#include <QRegularExpression>
|
|
#include <QShortcut>
|
|
#include <QTabBar>
|
|
#include <QTabWidget>
|
|
#include <QTextDocumentFragment>
|
|
|
|
#include <QAction>
|
|
#include <KActionMenu>
|
|
#include <KConfigGroup>
|
|
#include <KHelpMenu>
|
|
#include <KLocalizedString>
|
|
#include <QMenu>
|
|
#include <QMenuBar>
|
|
#include <KMessageBox>
|
|
#include <KStandardAction>
|
|
#include <KStandardGuiItem>
|
|
#include <QStatusBar>
|
|
#include <KToolBar>
|
|
#include <kwindowsystem.h>
|
|
#include <KStandardShortcut>
|
|
#include <KIconLoader>
|
|
#include <QColorDialog>
|
|
#include <QFontDialog>
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
#define KMESSDEBUG_CHATWINDOW_TYPING_MESSAGES
|
|
#endif
|
|
|
|
#define TYPING_NOTIFICATION_TIME 4000
|
|
|
|
|
|
|
|
// The constructor
|
|
ChatWindow::ChatWindow( QWidget *parent )
|
|
: KXmlGuiWindow( parent )
|
|
, Ui::ChatWindow()
|
|
, actionCollection_( actionCollection() )
|
|
, blinkToUpper_( true )
|
|
, currentAccount_( 0 )
|
|
, doSendTypingMessages_( true )
|
|
, indexSentences_( 0 )
|
|
, initialized_( false )
|
|
, zoomLevel_( 100 )
|
|
{
|
|
setObjectName( "ChatWindow#" );
|
|
|
|
// Create the interface
|
|
QWidget *mainWidget = new QWidget( this );
|
|
setupUi( mainWidget );
|
|
setCentralWidget( mainWidget );
|
|
|
|
#if KMESS_ENABLE_INK == 0
|
|
// Only enable the Ink button if Ink sending support is available
|
|
inkButton_->hide();
|
|
inkCanvas_->setEnabled( false );
|
|
#endif
|
|
|
|
// Connect the UI signals
|
|
connect( sendButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotSendButtonClicked() ) );
|
|
connect( newLineButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotNewLineButtonClicked() ) );
|
|
connect( messageEdit_, SIGNAL( textChanged() ),
|
|
this, SLOT ( slotMessageChanged() ) );
|
|
connect( inkCanvas_, SIGNAL( inkChanged() ),
|
|
this, SLOT ( slotMessageChanged() ) );
|
|
connect( textButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotSwitchEditor() ) );
|
|
connect( inkButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotSwitchEditor() ) );
|
|
connect( winksButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotSwitchEditor() ) );
|
|
connect( standardEmoticonButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotSwitchEditor() ) );
|
|
connect( customEmoticonButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotSwitchEditor() ) );
|
|
connect( fontButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( editFont() ) );
|
|
connect( fontColorButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( editFontColor() ) );
|
|
|
|
// Create the contacts dock
|
|
contactsDock_ = new QDockWidget( i18n( "Contacts" ), this );
|
|
contactsDock_->setObjectName( "ContactsDockWidget" );
|
|
contactsDock_->setAllowedAreas( Qt::AllDockWidgetAreas );
|
|
contactsDock_->setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
|
|
|
|
// Create the standard emoticons dock
|
|
standardEmoticonsDock_ = new QDockWidget( i18n( "Emoticons" ), this );
|
|
standardEmoticonsDock_->setObjectName( "StandardEmoticonsDockWidget" );
|
|
standardEmoticonsDock_->setAllowedAreas( Qt::AllDockWidgetAreas );
|
|
standardEmoticonsDock_->setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
|
|
standardEmoticonsDock_->hide(); // Hide it by default
|
|
|
|
// Create the custom emoticons dock
|
|
customEmoticonsDock_ = new QDockWidget( i18n( "My Emoticons" ), this );
|
|
customEmoticonsDock_->setObjectName( "CustomEmoticonsDockWidget" );
|
|
customEmoticonsDock_->setAllowedAreas( Qt::AllDockWidgetAreas );
|
|
customEmoticonsDock_->setFeatures( QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetClosable );
|
|
customEmoticonsDock_->hide(); // Hide it by default
|
|
|
|
// Add them to the window
|
|
setDockOptions( QMainWindow::AnimatedDocks | QMainWindow::AllowTabbedDocks );
|
|
Qt::DockWidgetArea position = QApplication::isLeftToRight()
|
|
? Qt::RightDockWidgetArea
|
|
: Qt::LeftDockWidgetArea;
|
|
addDockWidget( position, contactsDock_ );
|
|
addDockWidget( position, standardEmoticonsDock_ );
|
|
addDockWidget( position, customEmoticonsDock_ );
|
|
|
|
//Install the event filters to catch events
|
|
installEventFilter( this );
|
|
inkCanvas_ ->installEventFilter( this );
|
|
messageEdit_->installEventFilter( this );
|
|
winksWidget_->installEventFilter( this );
|
|
messageEdit_->viewport()->installEventFilter( this );
|
|
|
|
// Set the button icons
|
|
standardEmoticonButton_->setIcon( QIcon::fromTheme( "face-smile" ) );
|
|
customEmoticonButton_ ->setIcon( QIcon::fromTheme( "face-smile-gearhead-female" ) );
|
|
inkButton_ ->setIcon( QIcon::fromTheme( "draw-brush" ) );
|
|
winksButton_ ->setIcon( QIcon::fromTheme( "applications-toys" ) );
|
|
textButton_ ->setIcon( QIcon::fromTheme( "draw-text" ) );
|
|
fontButton_ ->setIcon( QIcon::fromTheme( "preferences-desktop-font" ) );
|
|
fontColorButton_ ->setIcon( QIcon::fromTheme( "format-stroke-color" ) );
|
|
|
|
// Set up the buttons for setting ink colour/stroke size/etc.
|
|
inkCanvas_->setPenSize( inkPenSize_->value() );
|
|
|
|
inkColorButton_->setIcon( QIcon::fromTheme( "format-stroke-color" ) );
|
|
inkEraseButton_->setIcon( QIcon::fromTheme( "draw-eraser" ) );
|
|
inkClearButton_->setIcon( QIcon::fromTheme( "edit-clear" ) );
|
|
|
|
connect( inkColorButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotChangeInkColor() ) );
|
|
connect( inkEraseButton_, SIGNAL( clicked() ),
|
|
this, SLOT ( slotChangeInkBrush() ) );
|
|
connect( inkClearButton_, SIGNAL( clicked() ),
|
|
inkCanvas_, SLOT ( clear() ) );
|
|
connect( inkPenSize_, SIGNAL( valueChanged(int) ),
|
|
inkCanvas_, SLOT ( setPenSize(int) ) );
|
|
|
|
// Connect the message edit so that if its displayed color changes,
|
|
// it's checked to match the user's chosen color.
|
|
connect( messageEdit_, SIGNAL( currentColorChanged(const QColor&) ),
|
|
this, SLOT ( editorColorChanged(const QColor&) ) );
|
|
// Connect the docks signals
|
|
connect( standardEmoticonsDock_, SIGNAL( visibilityChanged(bool) ),
|
|
this, SLOT ( slotEmoticonDocksToggled() ) );
|
|
connect( customEmoticonsDock_, SIGNAL( visibilityChanged(bool) ),
|
|
this, SLOT ( slotEmoticonDocksToggled() ) );
|
|
// And the tab widget signals
|
|
chatTabs_->tabBar()->installEventFilter( this );
|
|
connect( chatTabs_, &QTabWidget::tabCloseRequested, this, [this]( int index )
|
|
{
|
|
slotTabRemoved( chatTabs_->widget( index ) );
|
|
} );
|
|
connect( chatTabs_, SIGNAL( currentChanged(int) ),
|
|
this, SLOT ( slotTabChanged(int) ) );
|
|
|
|
chatTabs_->setTabsClosable( true );
|
|
|
|
// Create the status bar
|
|
statusLabel_ = new ChatStatusBar( this );
|
|
statusBar()->addWidget( statusLabel_, 1 );
|
|
|
|
// Create the menus and actions
|
|
createMenus();
|
|
|
|
// Build the gui from xml
|
|
setupGUI( Default, "chatwindowui.rc" );
|
|
|
|
|
|
// Lastly, initialize the "Typing notification" timer
|
|
userTypingTimer_.setInterval( TYPING_NOTIFICATION_TIME );
|
|
userTypingTimer_.setSingleShot( true );
|
|
|
|
|
|
// Forward reconnect signal
|
|
connect( statusLabel_, SIGNAL( reconnect() ),
|
|
this, SIGNAL( reconnect() ) );
|
|
// Setup the window title blinking timer
|
|
connect( &blinkTimer_, SIGNAL( timeout() ),
|
|
this, SLOT ( slotBlinkCaption() ) );
|
|
// Setup the status bar text timer
|
|
statusTimer_.setSingleShot( true );
|
|
connect( &statusTimer_, SIGNAL( timeout() ),
|
|
this, SLOT ( showStatusMessage() ) );
|
|
|
|
inkCtlFrame_->setVisible( false );
|
|
}
|
|
|
|
|
|
|
|
// The destructor
|
|
ChatWindow::~ChatWindow()
|
|
{
|
|
// Save the window properties
|
|
saveProperties();
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "DESTROYED.";
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
// Add a new chat tab
|
|
Chat *ChatWindow::addChatTab( Chat *newChat, bool foreground )
|
|
{
|
|
// Connect its main signals
|
|
connect( newChat, SIGNAL( chatInfoChanged() ),
|
|
this, SLOT ( slotUpdateChatInfo() ) );
|
|
connect( newChat, SIGNAL( gotNudge() ),
|
|
this, SLOT ( slotGotNudge() ) );
|
|
connect( newChat, SIGNAL( gotChatMessage(const ChatMessage&,Chat*) ),
|
|
this, SLOT ( slotGotChatMessage(const ChatMessage&,Chat*) ) );
|
|
connect( newChat, SIGNAL( gotTypingMessage(Chat*) ),
|
|
this, SLOT ( slotGotTypingMessage(Chat*) ) );
|
|
|
|
// Also create a tab for the new chat
|
|
int newTabIndex = chatTabs_->addTab( newChat, QString() );
|
|
slotUpdateChatInfo( newChat );
|
|
|
|
// The chat is the new active one: raise it and update the chat window
|
|
if( foreground )
|
|
{
|
|
chatTabs_->setCurrentIndex( newTabIndex );
|
|
}
|
|
|
|
// Set the appropriate parent of the contactswidget.
|
|
// This is needed when a chat is moved to another window.
|
|
newChat->getContactsWidget()->setParent( contactsDock_ );
|
|
|
|
// Apply the window's settings to the tab
|
|
newChat->getContactsWidget()->setDockWidget( contactsDock_, dockWidgetArea( contactsDock_ ) );
|
|
newChat->setZoomFactor( zoomLevel_ );
|
|
|
|
// Enable the spell checker for the new tab.
|
|
// FIXME KDE4.x bug this needs to be unset then set
|
|
messageEdit_->setCheckSpellingEnabled( ! spellCheckAction_->isChecked() );
|
|
messageEdit_->setCheckSpellingEnabled( spellCheckAction_->isChecked() );
|
|
|
|
// If there's more than one tab now, enable the Close All option
|
|
bool hasMultipleTabs = ( chatTabs_->count() > 1 );
|
|
closeAllAction_->setEnabled( hasMultipleTabs );
|
|
prevTabAction_->setEnabled( hasMultipleTabs );
|
|
nextTabAction_->setEnabled( hasMultipleTabs );
|
|
|
|
// Tab grouping is disabled, hide the bar
|
|
chatTabs_->tabBar()->setVisible( currentAccount_->getTabbedChatMode() != 2 );
|
|
|
|
return newChat;
|
|
}
|
|
|
|
|
|
|
|
// Set the zoom factor of the text
|
|
void ChatWindow::changeZoomFactor( bool increase )
|
|
{
|
|
int newZoom = zoomLevel_;
|
|
|
|
if( increase )
|
|
{
|
|
newZoom += 25;
|
|
}
|
|
else
|
|
{
|
|
newZoom -= 25;
|
|
}
|
|
|
|
if( newZoom < 20 || newZoom > 300 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Zoom level out of bounds, not updating.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// Apply the zoom level to all chats
|
|
zoomLevel_ = newZoom;
|
|
|
|
for( int i = 0; i < chatTabs_->count(); i++ )
|
|
{
|
|
getChat( i )->setZoomFactor( zoomLevel_ );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Check if the window must be closed
|
|
bool ChatWindow::checkAndCloseWindow()
|
|
{
|
|
// Check how many chat are still opened
|
|
int chatsNumber = chatTabs_->count();
|
|
if( chatsNumber > 0 )
|
|
{
|
|
// If there's more than one tab, show the Close All option and close button
|
|
bool hasMultipleTabs = ( chatTabs_->count() > 1 );
|
|
closeAllAction_->setEnabled( hasMultipleTabs );
|
|
prevTabAction_->setEnabled( hasMultipleTabs );
|
|
nextTabAction_->setEnabled( hasMultipleTabs );
|
|
return false;
|
|
}
|
|
|
|
// Save GUI settings if needed
|
|
if ( settingsDirty() && autoSaveSettings() )
|
|
{
|
|
saveAutoSaveSettings();
|
|
}
|
|
|
|
// Kill this window
|
|
hide();
|
|
deleteLater();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// The chat window is closing
|
|
bool ChatWindow::closeAllTabs()
|
|
{
|
|
// Ask the user for confirmation
|
|
if( chatTabs_->count() > 1 // Do not ask if there only is one tab
|
|
&& sender() != closeAllAction_ // Do not ask if the user chose the "close all tabs" action
|
|
&& ! static_cast<KMessApplication *>(QApplication::instance())->sessionSaving() ) // Do not ask if the KDE session is closing
|
|
{
|
|
int res = KMessageBox::questionTwoActionsCancel( this,
|
|
i18n( "<html>There are multiple tabs open in this chat window. "
|
|
"Do you want to close the current tab only, or all tabs?<br /><br />"
|
|
"<i>Note: You can close all tabs at once by pressing Alt+F4.</i></html>" ),
|
|
i18nc( "Dialog box caption: closing a chatwindow with a single tab",
|
|
"Closing a Chat Tab" ),
|
|
KGuiItem( i18n( "Close All Tabs" ), "window-close" ), // Yes option
|
|
KGuiItem( i18n( "Close Current Tab" ), "tab-close" ), // No option
|
|
KStandardGuiItem::cancel(),
|
|
"closeOneChatTabInfo" );
|
|
switch( res )
|
|
{
|
|
case KMessageBox::PrimaryAction : break;
|
|
case KMessageBox::SecondaryAction : return closeTab();
|
|
case KMessageBox::Cancel:
|
|
default : return false;
|
|
}
|
|
}
|
|
|
|
// Just close all tabs one by one, until the last one closes the window too
|
|
while( chatTabs_->count() > 0 )
|
|
{
|
|
closeTab();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// A chat tab is closing
|
|
bool ChatWindow::closeTab()
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Close request detected.";
|
|
#endif
|
|
|
|
Chat *currentChat = getCurrentChat();
|
|
if( currentChat != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Closing current chat tab " << currentChat;
|
|
#endif
|
|
|
|
if( ! slotTabRemoved( currentChat ) )
|
|
{
|
|
kmWarning() << "Unable to close the current chat!";
|
|
}
|
|
}
|
|
|
|
// If there still are open tabs, don't close
|
|
return checkAndCloseWindow();
|
|
}
|
|
|
|
|
|
|
|
// Close any open widget or close the tab
|
|
void ChatWindow::closeWidgetOrTab()
|
|
{
|
|
// if the Esc button was pressend on emoticon/ink editor then
|
|
// set the editor to Message Editor
|
|
if( ! messageEdit_->isVisible() )
|
|
{
|
|
slotSwitchEditor();
|
|
}
|
|
else
|
|
{
|
|
// Else close the current tab
|
|
closeTab();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Create the menus.
|
|
void ChatWindow::createMenus()
|
|
{
|
|
QAction *closeAction, *saveAction, *clearChatAction;
|
|
QAction *contactsDockAction, *standardEmoticonsDockAction, *customEmoticonsDockAction;
|
|
|
|
|
|
// Create the actions for "Chat" menu
|
|
inviteButton_ = new QAction ( QIcon::fromTheme( "list-add-user" ), i18n("&Invite..."), this );
|
|
sendAction_ = new QAction ( QIcon::fromTheme( "folder-remote" ), i18n("Send a &File..."), this );
|
|
nudgeAction_ = new QAction ( QIcon::fromTheme( "preferences-desktop-notification-bell" ), i18n("Send a &Nudge!"), this );
|
|
historyMenuAction_ = new KActionMenu ( QIcon::fromTheme( "chronometer" ), i18n("Chat &History..."), this );
|
|
saveAction = new QAction ( QIcon::fromTheme( "document-save" ), i18n("Save Chat..."), this );
|
|
closeAllAction_ = new QAction ( QIcon::fromTheme( "dialog-close" ), i18n("Close &All Tabs"), this );
|
|
closeAction = KStandardAction::close ( this, SLOT( closeWidgetOrTab() ) , actionCollection_ );
|
|
KStandardAction::quit ( QApplication::instance(), SLOT( quit() ), actionCollection_ );
|
|
|
|
// Create the actions for "Edit" menu
|
|
changeFontAction_ = new QAction ( QIcon::fromTheme( "preferences-desktop-font" ), i18n("Change &Font"), this );
|
|
changeFontColorAction_ = new QAction ( QIcon::fromTheme( "format-stroke-color" ), i18n("Change Font &Color"), this );
|
|
cutAction_ = KStandardAction::cut ( messageEdit_, SLOT( cut() ), actionCollection_ );
|
|
KStandardAction::copy ( messageEdit_, SLOT( copy() ), actionCollection_ );
|
|
pasteAction_ = KStandardAction::paste ( messageEdit_, SLOT( paste() ), actionCollection_ );
|
|
KStandardAction::find ( this, SLOT( editFind() ), actionCollection_ );
|
|
|
|
// Create the actions for "View" menu
|
|
emoticonAction_ = new KToggleAction( QIcon::fromTheme( "face-smile-big" ), i18n("Show &Emoticons"), this );
|
|
sessionInfoAction_ = new KToggleAction( QIcon::fromTheme( "flag-green" ), i18n("Show S&tatus Messages"), this );
|
|
KStandardAction::zoomIn ( this, SLOT( viewZoomIn() ), actionCollection_ );
|
|
KStandardAction::zoomOut( this, SLOT( viewZoomOut() ), actionCollection_ );
|
|
prevTabAction_ = KStandardAction::back ( this, SLOT( slotSwitchToLeftTab() ), actionCollection_ );
|
|
nextTabAction_ = KStandardAction::forward( this, SLOT( slotSwitchToRightTab() ), actionCollection_ );
|
|
clearChatAction = KStandardAction::clear ( this, SLOT( viewClearChat() ), actionCollection_ );
|
|
panelsMenuAction_ = new KActionMenu ( QIcon::fromTheme( "view-choose" ), i18n("&Panels"), this );
|
|
|
|
// Create the actions for "Settings" menu
|
|
spellCheckAction_ = new KToggleAction( QIcon::fromTheme( "tools-check-spelling" ), i18n("Use &Spell Checking"), this );
|
|
showMenuBar_ = KStandardAction::showMenubar( this, SLOT( showMenuBar() ), actionCollection_ );
|
|
|
|
// Add shorter texts for the toolbar items
|
|
nudgeAction_ ->setIconText( i18n("Nudge") );
|
|
sendAction_ ->setIconText( i18n("Send a &File") );
|
|
|
|
// Rename the tab navigation actions for clarity
|
|
prevTabAction_->setText ( i18n("P&revious Tab") );
|
|
prevTabAction_->setIconText( i18n("P&revious Tab") );
|
|
nextTabAction_->setText ( i18n("Ne&xt Tab") );
|
|
nextTabAction_->setIconText( i18n("Ne&xt Tab") );
|
|
|
|
// Enable some default action shortcuts (preserve the original ones where needed)
|
|
nudgeAction_->setShortcut( QKeySequence( "Alt+Z" ) );
|
|
closeAction->setShortcuts( QList<QKeySequence>() << QKeySequence( "Ctrl+W" ) << closeAction->shortcut() );
|
|
closeAllAction_->setShortcuts( QList<QKeySequence>() << QKeySequence( "Ctrl+Q" ) << closeAllAction_->shortcut() );
|
|
prevTabAction_->setShortcuts( QList<QKeySequence>() << QKeySequence( QKeySequence::PreviousChild ) << prevTabAction_->shortcut() );
|
|
nextTabAction_->setShortcuts( QList<QKeySequence>() << QKeySequence( QKeySequence::NextChild ) << nextTabAction_->shortcut() );
|
|
|
|
// Set up the dock actions
|
|
contactsDockAction = contactsDock_ ->toggleViewAction();
|
|
standardEmoticonsDockAction = standardEmoticonsDock_->toggleViewAction();
|
|
customEmoticonsDockAction = customEmoticonsDock_ ->toggleViewAction();
|
|
|
|
contactsDockAction->setIcon( QIcon::fromTheme( "meeting-attending" ) );
|
|
contactsDockAction->setToolTip( i18n("Enable or disable the contacts panel") );
|
|
contactsDockAction->setText( i18nc("Toolbar button","Contacts") );
|
|
contactsDockAction->setIconText( i18nc("Toolbar button","Contacts") );
|
|
contactsDockAction->setShortcut( QKeySequence( "Ctrl+D" ) );
|
|
|
|
standardEmoticonsDockAction->setIcon( QIcon::fromTheme( "face-smile" ) );
|
|
standardEmoticonsDockAction->setToolTip( i18n("Enable or disable the standard emoticons panel") );
|
|
standardEmoticonsDockAction->setText( i18nc("Toolbar button","Emoticons") );
|
|
standardEmoticonsDockAction->setIconText( i18nc("Toolbar button","Emoticons") );
|
|
standardEmoticonsDockAction->setShortcut( QKeySequence( "Ctrl+E" ) );
|
|
|
|
customEmoticonsDockAction->setIcon( QIcon::fromTheme( "face-smile-gearhead-female" ) );
|
|
customEmoticonsDockAction->setToolTip( i18n("Enable or disable the custom emoticons panel") );
|
|
customEmoticonsDockAction->setText( i18nc("Toolbar button","My Emoticons") );
|
|
customEmoticonsDockAction->setIconText( i18nc("Toolbar button","My Emoticons") );
|
|
customEmoticonsDockAction->setShortcut( QKeySequence( "Ctrl+Y" ) );
|
|
|
|
// Add the dock toggling actions to the Panels menu
|
|
panelsMenuAction_->addAction( contactsDockAction );
|
|
panelsMenuAction_->addAction( standardEmoticonsDockAction );
|
|
panelsMenuAction_->addAction( customEmoticonsDockAction );
|
|
|
|
// Initially the chat has one tab only, so the Close All option is not useful
|
|
closeAllAction_->setEnabled( false );
|
|
|
|
// Add shorter texts for the toolbar
|
|
changeFontAction_ ->setIconText( i18n("&Font") );
|
|
changeFontColorAction_->setIconText( i18n("Font &Color") );
|
|
clearChatAction->setText( i18n("C&lear Chat Window") );
|
|
clearChatAction->setIconText( i18n("C&lear Chat") );
|
|
|
|
// Connect slots to signals for "Chat" menu
|
|
connect( inviteButton_, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( slotInviteContact() ) );
|
|
connect( sendAction_, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( startFileTransfer() ) );
|
|
connect( nudgeAction_, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( sendNudge() ) );
|
|
connect( historyMenuAction_, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( slotShowChatHistory() ) );
|
|
connect( saveAction, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( saveChat() ) );
|
|
connect( closeAllAction_, SIGNAL( triggered() ),
|
|
this, SLOT ( closeAllTabs() ) );
|
|
|
|
// Connect slots to signals for "Edit" menu
|
|
connect( changeFontAction_, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( editFont() ) );
|
|
connect( changeFontColorAction_,SIGNAL( triggered(bool) ),
|
|
this, SLOT ( editFontColor() ) );
|
|
connect( clearChatAction, SIGNAL( triggered() ),
|
|
this, SLOT ( viewClearChat() ) );
|
|
|
|
// Connect slots to signals for "Settings" menu
|
|
connect( emoticonAction_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( toggleEmoticons(bool) ) );
|
|
connect( spellCheckAction_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( toggleSpellCheck(bool) ) );
|
|
connect( sessionInfoAction_, SIGNAL( toggled(bool) ),
|
|
this, SLOT ( toggleSessionInfo(bool) ) );
|
|
|
|
|
|
// Add actions to actionCollection for "Chat" menu
|
|
actionCollection_->addAction( "send", sendAction_ );
|
|
actionCollection_->addAction( "nudge", nudgeAction_ );
|
|
actionCollection_->addAction( "save", saveAction );
|
|
actionCollection_->addAction( "historyMenu", historyMenuAction_ );
|
|
actionCollection_->addAction( "closeAll", closeAllAction_ );
|
|
actionCollection_->addAction( "invite", inviteButton_ );
|
|
|
|
// Add actions to actionCollection for "Edit" menu
|
|
actionCollection_->addAction( "font", changeFontAction_ );
|
|
actionCollection_->addAction( "fontColor", changeFontColorAction_ );
|
|
|
|
// Add actions to actionCollection for "View" menu
|
|
actionCollection_->addAction( "prevTab", prevTabAction_ );
|
|
actionCollection_->addAction( "nextTab", nextTabAction_ );
|
|
actionCollection_->addAction( "clearChat", clearChatAction );
|
|
actionCollection_->addAction( "emoticons", emoticonAction_ );
|
|
actionCollection_->addAction( "panelsMenu", panelsMenuAction_ );
|
|
actionCollection_->addAction( "contactsDock", contactsDockAction );
|
|
actionCollection_->addAction( "sessionInfo", sessionInfoAction_ );
|
|
actionCollection_->addAction( "standardEmoticonsDock", standardEmoticonsDockAction );
|
|
actionCollection_->addAction( "customEmoticonsDock", customEmoticonsDockAction );
|
|
|
|
// Add actions to actionCollection for "Settings" menu
|
|
actionCollection_->addAction( "spellCheck", spellCheckAction_ );
|
|
}
|
|
|
|
|
|
|
|
// Copy the currently selected text
|
|
void ChatWindow::editCopy()
|
|
{
|
|
// Get the cursor reference
|
|
QTextCursor cursor = messageEdit_->textCursor();
|
|
|
|
if( cursor.hasSelection() )
|
|
{
|
|
messageEdit_->copy();
|
|
}
|
|
else
|
|
|
|
getCurrentChat()->editCopy();
|
|
}
|
|
|
|
|
|
|
|
// put the marked text/object into the clipboard and remove
|
|
// it from the document
|
|
void ChatWindow::editCut()
|
|
{
|
|
// If another editor has focus, don't actually cut anything
|
|
if( ! messageEdit_->hasFocus() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
messageEdit_->cut();
|
|
}
|
|
|
|
|
|
|
|
// Bring up a find dialog to search for text in the chat.
|
|
void ChatWindow::editFind()
|
|
{
|
|
getCurrentChat()->slotFindChatText();
|
|
}
|
|
|
|
|
|
|
|
// Bring up a dialog to change the message font color.
|
|
void ChatWindow::editFont()
|
|
{
|
|
QFont selection( currentAccount_->getFont() );
|
|
|
|
bool accepted = false;
|
|
selection = QFontDialog::getFont( &accepted, selection, this );
|
|
|
|
if( ! accepted )
|
|
{
|
|
return;
|
|
}
|
|
|
|
currentAccount_->setFont( selection );
|
|
|
|
// Commit changes to disk, now
|
|
currentAccount_->saveProperties();
|
|
}
|
|
|
|
|
|
|
|
// Bring up a dialog to change the message font color.
|
|
void ChatWindow::editFontColor()
|
|
{
|
|
QColor color( currentAccount_->getFontColor() );
|
|
|
|
// Show the color dialog
|
|
color = QColorDialog::getColor( color, this );
|
|
if ( color.isValid() )
|
|
{
|
|
// Get the string version of the selected color
|
|
const QString fontColor( color.name() );
|
|
// Set it to the stored account
|
|
currentAccount_->setFontColor(fontColor);
|
|
|
|
// Commit changes to disk, now
|
|
currentAccount_->saveProperties();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The color in the text box changed.
|
|
void ChatWindow::editorColorChanged(const QColor &color)
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( currentAccount_ != 0 );
|
|
#endif
|
|
|
|
// Whenever the QTextEdit's contents is entirely deleted, the font color spontaneously resets to black;
|
|
// if this happens, set the color back to the user's color.
|
|
// HACK: This possibly is a Qt <= 4.4 bug exposed.
|
|
// UPDATE: Happens with Qt 4.5 too. Also resets the font weight.
|
|
if ( color.name() != currentAccount_->getFontColor() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Color was reset to" << color.name() << ". Restoring it to the account setting.";
|
|
#endif
|
|
messageEdit_->setTextColor( QColor( currentAccount_->getFontColor() ) );
|
|
messageEdit_->setFont( QFont ( currentAccount_->getFont() ) );
|
|
}
|
|
|
|
// Adjust the message editor's minimum height to match the current font size
|
|
QFontMetrics metrics( messageEdit_->currentFont() );
|
|
messageEdit_->setMinimumSize( 100, metrics.height() + metrics.lineSpacing() );
|
|
}
|
|
|
|
|
|
|
|
// paste the clipboard into the document
|
|
void ChatWindow::editPaste()
|
|
{
|
|
// Regardless of focus, always paste in message edit.
|
|
messageEdit_->paste();
|
|
}
|
|
|
|
|
|
|
|
// Filter to catch window activation events
|
|
bool ChatWindow::eventFilter( QObject *obj, QEvent *event )
|
|
{
|
|
if( obj == this && event->type() == QEvent::WindowActivate )
|
|
{
|
|
// Stop blinking immediately when the window is activated
|
|
if( blinkTimer_.isActive() )
|
|
{
|
|
blinkTimer_.stop();
|
|
blinkToUpper_ = true;
|
|
|
|
setWindowTitle();
|
|
}
|
|
|
|
// Give focus to the current message editor
|
|
if( messageEdit_->isVisible() )
|
|
{
|
|
messageEdit_->setFocus();
|
|
}
|
|
else if( inkCanvas_->isVisible() )
|
|
{
|
|
inkCanvas_->setFocus();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
if( obj == chatTabs_->tabBar() && event->type() == QEvent::MouseButtonRelease )
|
|
{
|
|
QMouseEvent *mouseEvent = static_cast<QMouseEvent*>( event );
|
|
if( mouseEvent->button() == Qt::MiddleButton )
|
|
{
|
|
const int tabIndex = chatTabs_->tabBar()->tabAt( mouseEvent->position().toPoint() );
|
|
if( tabIndex >= 0 )
|
|
{
|
|
return slotTabRemoved( chatTabs_->widget( tabIndex ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( event->type() == QEvent::KeyPress )
|
|
{
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
|
|
if ( keyEvent->key() == Qt::Key_Escape )
|
|
{
|
|
closeWidgetOrTab();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Only manage events for the editors from now on (the event filter code
|
|
// is complex enough as it is now...)
|
|
if( obj != messageEdit_
|
|
&& obj != messageEdit_->viewport()
|
|
&& obj != inkCanvas_
|
|
&& obj != winksWidget_
|
|
&& obj != standardEmoticonsWidget_
|
|
&& obj != customEmoticonsWidget_ )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch( event->type() )
|
|
{
|
|
// If a key was pressed, check if it's return and if so, send the drawing
|
|
case QEvent::KeyPress:
|
|
{
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
|
|
bool ctrl = keyEvent->modifiers() & Qt::ControlModifier;
|
|
bool shift = keyEvent->modifiers() & Qt::ShiftModifier;
|
|
bool alt = keyEvent->modifiers() & Qt::AltModifier;
|
|
int key = keyEvent->key();
|
|
|
|
if( key == Qt::Key_Return || key == Qt::Key_Enter )
|
|
{
|
|
if( shift || ctrl )
|
|
{
|
|
// ctrl+enter or shift+enter, insert a new line instead.
|
|
messageEdit_->insertPlainText( "\n" );
|
|
}
|
|
else
|
|
{
|
|
slotSendButtonClicked();
|
|
}
|
|
|
|
// Blocks further handling of this event.
|
|
keyEvent->ignore();
|
|
return true;
|
|
}
|
|
// Detect scrolling commands. Alt scrolls faster (shift is used for text selection in the editor)
|
|
else if( ( key == Qt::Key_PageUp || key == Qt::Key_PageDown ) && ( ! shift ) )
|
|
{
|
|
getCurrentChat()->scrollTo( ( key == Qt::Key_PageDown ), alt );
|
|
return true;
|
|
}
|
|
else if( alt )
|
|
{
|
|
bool ok = false;
|
|
int chatIndex = keyEvent->text().toInt( &ok );
|
|
|
|
// Technically the index can't be more than 9, of course, but keep things safe & simple
|
|
if( ok && chatIndex > 0 && chatIndex <= chatTabs_->count() )
|
|
{
|
|
chatTabs_->setCurrentIndex( chatIndex - 1 );
|
|
keyEvent->ignore();
|
|
return true;
|
|
}
|
|
}
|
|
else if( ctrl )
|
|
{
|
|
QStringList& quickRetypeList = getCurrentChat()->getQuickRetypeList();
|
|
switch( key )
|
|
{
|
|
// Detect text copying from the message editor, or the Message View
|
|
case Qt::Key_C:
|
|
editCopy();
|
|
break;
|
|
|
|
// Detect 'save chat' command
|
|
case Qt::Key_S:
|
|
getCurrentChat()->showSaveChatDialog();
|
|
break;
|
|
|
|
// Detect fast-retype (ctrl+UpArrow)
|
|
case Qt::Key_Up:
|
|
if( quickRetypeList.isEmpty() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( indexSentences_ == quickRetypeList.size() )
|
|
{
|
|
// store the last sentence in writing
|
|
lastSentence_ = messageEdit_->toPlainText();
|
|
}
|
|
|
|
if( indexSentences_ > 0 )
|
|
{
|
|
indexSentences_--;
|
|
}
|
|
|
|
if( indexSentences_ >= 0 )
|
|
{
|
|
messageEdit_->setPlainText( quickRetypeList[ indexSentences_ ] );
|
|
}
|
|
break;
|
|
|
|
// Detect fast-retype (ctrl+DownArrow)
|
|
case Qt::Key_Down:
|
|
if( quickRetypeList.isEmpty() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
if( indexSentences_ == quickRetypeList.size() - 1 )
|
|
{
|
|
messageEdit_->setPlainText( lastSentence_ );
|
|
indexSentences_ = quickRetypeList.size();
|
|
}
|
|
|
|
if( indexSentences_ < quickRetypeList.size() - 1 )
|
|
{
|
|
indexSentences_++;
|
|
}
|
|
|
|
if( indexSentences_ < quickRetypeList.size() )
|
|
{
|
|
messageEdit_->setPlainText( quickRetypeList[ indexSentences_ ] );
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check if this is a Find key sequence. if so, stop it before it gets to the message edit
|
|
// this prevents the Ctrl+F key sequence being consumed by KTextEdit.
|
|
// KTextEdit does not stop consuming the find/replace key sequences if we disable find/replace
|
|
// (bug has been filed with KDE) so this hack is necessary.
|
|
if ( KStandardShortcut::find().contains(key | keyEvent->modifiers() ) )
|
|
{
|
|
editFind();
|
|
return true;
|
|
}
|
|
|
|
// Another key was pressed: switch to the message editor if needed
|
|
if( editorChooser_->currentWidget() != textPage_ )
|
|
{
|
|
slotSwitchEditor();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
case QEvent::WindowActivate:
|
|
if( messageEdit_->isVisible() )
|
|
{
|
|
messageEdit_->setFocus();
|
|
}
|
|
else if( inkCanvas_->isVisible() )
|
|
{
|
|
inkCanvas_->setFocus();
|
|
}
|
|
break;
|
|
|
|
case QEvent::FocusOut:
|
|
case QEvent::Show:
|
|
{
|
|
QWidget *focusedWidget = QApplication::focusWidget();
|
|
|
|
// Allow this chat's search bar to take focus away from the message editor
|
|
if( focusedWidget && QString( focusedWidget->metaObject()->className() ) == "KHistoryComboBox" )
|
|
{
|
|
break;
|
|
}
|
|
|
|
messageEdit_->setFocus( Qt::OtherFocusReason );
|
|
break;
|
|
}
|
|
|
|
case QEvent::DragEnter:
|
|
case QEvent::DragMove:
|
|
{
|
|
QDragEnterEvent *dragEvent = static_cast<QDragEnterEvent*>( event );
|
|
|
|
if( ! dragEvent )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Ignoring invalid drag event!";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
const QMimeData *mimeData = dragEvent->mimeData();
|
|
if( mimeData->hasUrls()
|
|
|| mimeData->hasImage() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Accepting drag event of mimetypes:" << dragEvent->mimeData()->formats();
|
|
#endif
|
|
dragEvent->acceptProposedAction();
|
|
return true;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Ignoring invalid drop having mimetypes:" << dragEvent->mimeData()->formats();
|
|
#endif
|
|
|
|
break;
|
|
}
|
|
|
|
case QEvent::Drop:
|
|
{
|
|
QDropEvent *dropEvent = static_cast<QDropEvent*>( event );
|
|
|
|
if( ! dropEvent )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Ignoring invalid drop!";
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
const QMimeData *mimeData = dropEvent->mimeData();
|
|
|
|
// Check for drops of ISF drawings
|
|
if( mimeData->hasImage() && ! mimeData->urls().isEmpty() )
|
|
{
|
|
const QString marker( "data:image/png;base64," );
|
|
const QString url( mimeData->urls().first().toString() );
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Checking if the dropped inline image is a fortified-PNG...";
|
|
#endif
|
|
if( url.startsWith( marker ) )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Setting drawing to the Ink Canvas.";
|
|
#endif
|
|
QByteArray isfData( url.mid( marker.length() ).toLatin1() );
|
|
Isf::Drawing* drawing = &( Isf::Stream::readerPng( isfData, true ) );
|
|
|
|
// We need to both set it to the canvas and change the object stored by the chat tab,
|
|
// so the old drawing will be destroyed
|
|
getCurrentChat()->setInkDrawing( drawing );
|
|
inkCanvas_->setDrawing( drawing );
|
|
|
|
// Update the canvas and switch to it
|
|
inkButton_->setChecked( true );
|
|
slotSwitchEditor( inkButton_ );
|
|
sendButton_->setDisabled( drawing->isNull() );
|
|
|
|
dropEvent->acceptProposedAction();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
// Process the drop only if it comes from out of our application.
|
|
if( dropEvent->source() != 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Ignoring internal drop!";
|
|
#endif
|
|
dropEvent->ignore();
|
|
return true;
|
|
}
|
|
|
|
// Handle drag and drop of files
|
|
if( mimeData->hasUrls() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Drag'n'dropped files:" << mimeData->urls();
|
|
#endif
|
|
|
|
// Send the files to the contact
|
|
getCurrentChat()->startFileTransfer( mimeData->urls() );
|
|
|
|
dropEvent->acceptProposedAction();
|
|
return true;
|
|
}
|
|
|
|
if( mimeData->hasImage() )
|
|
{
|
|
QImage image( qvariant_cast<QImage>( mimeData->imageData() ) );
|
|
|
|
if( image.isNull() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATVIEW
|
|
kmDebug() << "Drag'n'dropped an invalid image!";
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Drag'n'dropped a" << image.size() << "image";
|
|
#endif
|
|
|
|
// Create a temporary file where the image will be stored.
|
|
// TODO: The file is not destroyed, as it needs to be kept
|
|
// available until the transfer is completed. Find a way
|
|
// to remove it when KMess quits or as soon as the transfer
|
|
// is done.
|
|
QTemporaryFile file;
|
|
file.setAutoRemove( false );
|
|
file.setFileTemplate( QDir::tempPath() + "/kmess.XXXXXX.jpg" );
|
|
|
|
if( ! file.open() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Cannot create temporary image file!";
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
dropEvent->acceptProposedAction();
|
|
|
|
// Save the image and get its local URL
|
|
image.save( &file, "JPG" );
|
|
file.close();
|
|
QUrl imageUrl( "file://" + file.fileName() );
|
|
|
|
// Send the image to the contact
|
|
QList<QUrl> urlList;
|
|
urlList.append( imageUrl );
|
|
getCurrentChat()->startFileTransfer( urlList );
|
|
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @brief Return a reference to a chat
|
|
*
|
|
* When called without parameters, this method returns the active chat.
|
|
* If the given index is not valid, the current index is returned, too.
|
|
*
|
|
* @param index Number of the chat to retrieve, or nothing to get the currently active one.
|
|
* @return Chat *
|
|
*/
|
|
Chat *ChatWindow::getChat( int index )
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( chatTabs_->count() > 0 );
|
|
#endif
|
|
|
|
if( index < 0 || index >= chatTabs_->count() )
|
|
{
|
|
return getCurrentChat();
|
|
}
|
|
|
|
return qobject_cast<Chat*>( chatTabs_->widget( index ) );
|
|
}
|
|
|
|
|
|
|
|
// Return a reference to the active chat
|
|
Chat *ChatWindow::getCurrentChat()
|
|
{
|
|
return qobject_cast<Chat*>( chatTabs_->currentWidget() );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle a text command (e.g. '/online')
|
|
bool ChatWindow::handleCommand( QString command )
|
|
{
|
|
if( command.isEmpty() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Parse arguments
|
|
QStringList arguments( command.split( " ", Qt::SkipEmptyParts ) );
|
|
if( arguments.isEmpty() )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
command = arguments.takeFirst();
|
|
|
|
// Make it lower-case
|
|
command = command.toLower();
|
|
|
|
// Get us a kmess handle to call setStatus upon
|
|
KMessApplication *kmessApp = static_cast<KMessApplication *>(QApplication::instance());
|
|
KMess *kmess = kmessApp->getContactListWindow();
|
|
|
|
bool isStatusCommand = ( command == "status" );
|
|
|
|
// Check syntax errors
|
|
if( isStatusCommand && arguments.isEmpty() )
|
|
{
|
|
KMessageBox::error( this,
|
|
i18n( "<html>You used an incorrect syntax for the /status command. The correct syntax "
|
|
"is: <b>/status online|away|idle|brb|busy|lunch|phone|invisible</b>.<br/>"
|
|
"You can also use shortcuts like <b>/online</b> or <b>/phone</b>.</html>" ),
|
|
i18nc( "Dialog box caption for wrong command syntax warning", "Incorrect /status Syntax" )
|
|
);
|
|
return true;
|
|
}
|
|
|
|
QString toStatus;
|
|
const QStringList participants( getCurrentChat()->getParticipants() );
|
|
|
|
if( isStatusCommand )
|
|
{
|
|
toStatus = arguments.takeFirst(); // we take the first, so we can get to the other arguments.
|
|
}
|
|
|
|
// Switch status
|
|
if( command == "online" || ( isStatusCommand && toStatus == "online" ) ||
|
|
command == "back" || ( isStatusCommand && toStatus == "back" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_ONLINE );
|
|
}
|
|
else if( command == "away" || ( isStatusCommand && toStatus == "away" ) )
|
|
{
|
|
// Away message?
|
|
QString awayMessage;
|
|
if( ! arguments.isEmpty() )
|
|
{
|
|
awayMessage = arguments.join(" ");
|
|
kmess->changeStatus( STATUS_AWAY_AUTOREPLY, awayMessage );
|
|
}
|
|
else
|
|
{
|
|
kmess->changeStatus( STATUS_AWAY );
|
|
}
|
|
}
|
|
else if( command == "idle" || ( isStatusCommand && toStatus == "idle" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_IDLE );
|
|
}
|
|
else if( command == "brb" || ( isStatusCommand && toStatus == "brb" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_BE_RIGHT_BACK );
|
|
}
|
|
else if( command == "busy" || ( isStatusCommand && toStatus == "busy" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_BUSY );
|
|
}
|
|
else if( command == "lunch" || ( isStatusCommand && toStatus == "lunch" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_OUT_TO_LUNCH );
|
|
}
|
|
else if( command == "phone" || ( isStatusCommand && toStatus == "phone" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_ON_THE_PHONE );
|
|
}
|
|
else if( command == "invisible" || ( isStatusCommand && toStatus == "invisible" ) )
|
|
{
|
|
kmess->changeStatus( STATUS_INVISIBLE );
|
|
}
|
|
// Block and unblock user
|
|
else if( command == "block" )
|
|
{
|
|
if( participants.count() != 1 )
|
|
{
|
|
KMessageBox::error( this,
|
|
i18n( "<html>You cannot use the /block command in a group chat.</html>" ),
|
|
i18nc( "Caption when trying to block someone in a group chat",
|
|
"Cannot use /block command" )
|
|
);
|
|
return true;
|
|
}
|
|
getCurrentChat()->setContactBlocked( participants.first(), true );
|
|
}
|
|
else if( command == "unblock" )
|
|
{
|
|
if( participants.count() != 1 )
|
|
{
|
|
KMessageBox::error( this,
|
|
i18n( "<html>You cannot use the /unblock command in a group chat.</html>" ),
|
|
i18nc( "Caption when trying to unblock someone in a group chat",
|
|
"Cannot use /unblock command!" )
|
|
);
|
|
return true;
|
|
}
|
|
getCurrentChat()->setContactBlocked( participants.first(), false );
|
|
}
|
|
// Send special messages
|
|
else if( command == "me" )
|
|
{
|
|
const QString message( "* " + currentAccount_->getFriendlyName() + " " + arguments.join( " " ) );
|
|
getCurrentChat()->sendChatMessage( message );
|
|
ChatMessage chatmessage( ChatMessage::TYPE_OUTGOING, ChatMessage::CONTENT_MESSAGE, false,
|
|
message, currentAccount_->getHandle(),
|
|
currentAccount_->getFriendlyName( STRING_ORIGINAL ),
|
|
currentAccount_->getPicturePath(),
|
|
currentAccount_->getFont(), currentAccount_->getFontColor() );
|
|
getCurrentChat()->showMessage( chatmessage );
|
|
}
|
|
// Unknown command
|
|
else
|
|
{
|
|
// If an unknown command contains more than one '/', it's very likely to be a path.
|
|
// Don't show the error and return with 'false' to send the command/path.
|
|
|
|
if( command.contains( '/' ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
KMessageBox::error( this,
|
|
i18n( "<html>Unknown command <b>%1</b>. If you did not want this "
|
|
"message to be a command, prepend your message with another"
|
|
" /.</html>", command ),
|
|
i18nc( "Caption when an unknown command was requested", "Unknown Command" ) );
|
|
return true;
|
|
}
|
|
|
|
// Clear the message edit field, because the command was executed succesfully.
|
|
messageEdit_->clear();
|
|
messageEdit_->setFocus();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Initialize the user interface
|
|
bool ChatWindow::initialize()
|
|
{
|
|
if( initialized_ )
|
|
{
|
|
kmDebug() << "already initialized.";
|
|
return false;
|
|
}
|
|
|
|
if( ! initializeCurrentAccount() )
|
|
{
|
|
kmDebug() << "Couldn't setup the current account.";
|
|
return false;
|
|
}
|
|
|
|
// Autosave all GUI settings
|
|
setAutoSaveSettings( KMessConfig::instance()->getAccountConfig( currentAccount_->getHandle(), "ChatWindow" ),
|
|
true /* save WindowSize */ );
|
|
|
|
// Read the window properties
|
|
readProperties();
|
|
|
|
// Enable the emoticon widgets
|
|
standardEmoticonsWidget_->initialize( false );
|
|
customEmoticonsWidget_ ->initialize( true );
|
|
connect( standardEmoticonsWidget_, SIGNAL( insertEmoticon(QString) ),
|
|
this, SLOT ( insertEmoticon(QString) ) );
|
|
connect( customEmoticonsWidget_, SIGNAL( insertEmoticon(QString) ),
|
|
this, SLOT ( insertEmoticon(QString) ) );
|
|
|
|
// Create the emoticon widgets for the docks (they're not the same ones used for the chat tabs).
|
|
// Before we create the widgets, the docks have to be in place already, to be able to tell the
|
|
// widgets how to initially layout themselves.
|
|
EmoticonsWidget *standardEmoticonsDockWidget = new EmoticonsWidget( standardEmoticonsDock_ );
|
|
EmoticonsWidget *customEmoticonsDockWidget = new EmoticonsWidget( customEmoticonsDock_ );
|
|
standardEmoticonsDockWidget->initialize( false );
|
|
customEmoticonsDockWidget ->initialize( true );
|
|
standardEmoticonsDockWidget->setDockWidget( standardEmoticonsDock_, dockWidgetArea( standardEmoticonsDock_ ) );
|
|
customEmoticonsDockWidget ->setDockWidget( customEmoticonsDock_, dockWidgetArea( customEmoticonsDock_ ) );
|
|
standardEmoticonsDock_ ->setWidget( standardEmoticonsDockWidget );
|
|
customEmoticonsDock_ ->setWidget( customEmoticonsDockWidget );
|
|
connect( standardEmoticonsDockWidget, SIGNAL( insertEmoticon(const QString&) ),
|
|
this, SLOT ( insertEmoticon(const QString&) ) );
|
|
connect( customEmoticonsDockWidget, SIGNAL( insertEmoticon(const QString&) ),
|
|
this, SLOT ( insertEmoticon(const QString&) ) );
|
|
|
|
initialized_ = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Set up the current account
|
|
bool ChatWindow::initializeCurrentAccount()
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( currentAccount_ == 0 );
|
|
#endif
|
|
|
|
currentAccount_ = CurrentAccount::instance();
|
|
if ( currentAccount_ == 0 )
|
|
{
|
|
kmWarning() << "Could not get an instance of the current account.";
|
|
return false;
|
|
}
|
|
|
|
// Set up some widgets based on account settings
|
|
emoticonAction_ ->setChecked( currentAccount_->getUseEmoticons() );
|
|
sessionInfoAction_->setChecked( currentAccount_->getShowSessionInfo() );
|
|
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( currentAccount_ != 0 );
|
|
#endif
|
|
|
|
// Connect needed signals
|
|
connect( currentAccount_, SIGNAL( changedFontSettings() ),
|
|
this, SLOT ( updateEditorFont() ) );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Insert an emoticon into the message editor
|
|
void ChatWindow::insertEmoticon( const QString &shortcut )
|
|
{
|
|
// Insert the text at the cursor
|
|
messageEdit_->insertPlainText( shortcut );
|
|
|
|
// Switch to the message editor then
|
|
slotSwitchEditor();
|
|
}
|
|
|
|
|
|
|
|
// The window is closing, called by KMainWindow
|
|
bool ChatWindow::queryClose()
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Closing all open chats";
|
|
#endif
|
|
|
|
return closeAllTabs();
|
|
}
|
|
|
|
|
|
|
|
// The application is exiting, called by KMainWindow
|
|
bool ChatWindow::queryExit()
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Closing all open chats";
|
|
#endif
|
|
|
|
// Allow performing last operations to all open chats and delete them
|
|
for( int i = 0; i < chatTabs_->count(); i++ )
|
|
{
|
|
Chat *chat = getChat( i );
|
|
chat->queryExit();
|
|
}
|
|
|
|
// Clear the list of chat tabs
|
|
chatTabs_->clear();
|
|
|
|
// Force a call to the destructor, to ensure correct cleanup by ChatMaster
|
|
deleteLater();
|
|
|
|
// Always accept this one, otherwise the chat window won't close.
|
|
// The quit is presented in KMessApplication::slotLastWindowClosed() too.
|
|
#ifdef KMESSDEBUG_KMESSINTERFACE
|
|
kmDebug() << "Accepting exit request.";
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Restore the window properties (called by KMainWindow)
|
|
void ChatWindow::readProperties()
|
|
{
|
|
const KConfigGroup config( KMessConfig::instance()->getAccountConfig( currentAccount_->getHandle(), "ChatWindow" ) );
|
|
readProperties( config );
|
|
}
|
|
|
|
|
|
|
|
// Restore the window properties (called by KMainWindow)
|
|
void ChatWindow::readProperties(const KConfigGroup &config )
|
|
{
|
|
// The parameter is kept to mantain the same arguments as KMainWindow::readProperties().
|
|
Q_UNUSED( config );
|
|
const KConfigGroup group( KMessConfig::instance()->getAccountConfig( currentAccount_->getHandle(), "ChatWindow" ) );
|
|
|
|
// Read the window position
|
|
const QPoint pos( group.readEntry( "Position", QPoint() ) );
|
|
if( ! pos.isNull() )
|
|
{
|
|
move( pos );
|
|
}
|
|
|
|
// Resize the window to decent dimensions if there's no saved state.
|
|
resize( 550, 450 );
|
|
|
|
QByteArray state( group.readEntry( "ToolbarsAndDocksState", QByteArray() ) );
|
|
|
|
// adam (16Feb10): restoreWindowSize has to come before restoreState.
|
|
//
|
|
// when testing with KDE 4.4 I found, for some completely unknown reason,
|
|
// that calling restoreState *first* causes the configuration file in KConfigGroup above (which has *nothing* to do with restoreState)
|
|
// to reset its Width and Height settings to what seem to be default values.
|
|
//
|
|
// I have no idea why this happens - restoreState doesn't trigger a saveProperties call so it shouldn't happen at all!
|
|
// It's dark magic, I tell you.
|
|
//
|
|
// Swapping the calls around fixes the problem. But again, I'll be damned if I can figure out
|
|
// what's going on.
|
|
|
|
restoreGeometry( group.readEntry( "WindowGeometry", QByteArray() ) );
|
|
|
|
restoreState( state );
|
|
|
|
// Read if the spelling check feature is enabled
|
|
toggleSpellCheck( group.readEntry( "UseSpellCheck", false ) );
|
|
|
|
// Set the default widget sizes. The message editor has a default read size of 1 so it'll be
|
|
// as small as possible, without becoming invisible
|
|
int minChatViewHeight = chatTabs_->minimumSize().height();
|
|
splitter_->setSizes( group.readEntry( "MessageSplitterSizes", QList<int>() << minChatViewHeight << 1 ) );
|
|
|
|
// Load the level of zoom
|
|
zoomLevel_ = group.readEntry( "CurrentZoom", 100 );
|
|
}
|
|
|
|
|
|
|
|
// Close a chat tab without user intervention
|
|
void ChatWindow::removeChatTab( Chat *chat )
|
|
{
|
|
chatTabs_->removeTab( chatTabs_->indexOf( chat ) );
|
|
}
|
|
|
|
|
|
|
|
// Save the chat according to the user's request
|
|
void ChatWindow::saveChat()
|
|
{
|
|
getCurrentChat()->showSaveChatDialog();
|
|
}
|
|
|
|
|
|
|
|
// Save the window properties (called by KMainWindow)
|
|
void ChatWindow::saveProperties()
|
|
{
|
|
KConfigGroup config( KMessConfig::instance()->getAccountConfig( currentAccount_->getHandle(), "ChatWindow" ) );
|
|
saveProperties( config );
|
|
}
|
|
|
|
|
|
|
|
// Save the window properties (called by KMainWindow)
|
|
void ChatWindow::saveProperties( KConfigGroup &config )
|
|
{
|
|
// The parameter is kept to mantain the same arguments as KMainWindow::saveProperties().
|
|
Q_UNUSED( config );
|
|
KConfigGroup group( KMessConfig::instance()->getAccountConfig( currentAccount_->getHandle(), "ChatWindow" ) );
|
|
|
|
// Save the window position
|
|
group.writeEntry( "Position", pos() );
|
|
|
|
// Save the docks and toolbars state
|
|
group.writeEntry( "ToolbarsAndDocksState", saveState() );
|
|
group.writeEntry( "UseSpellCheck", spellCheckAction_->isChecked() );
|
|
|
|
group.writeEntry( "MessageSplitterSizes", splitter_->sizes() );
|
|
group.writeEntry( "CurrentZoom", zoomLevel_ );
|
|
|
|
// Save window size
|
|
group.writeEntry( "WindowGeometry", saveGeometry() );
|
|
|
|
group.config()->sync();
|
|
}
|
|
|
|
|
|
|
|
// Send a ink via server
|
|
void ChatWindow::sendInk()
|
|
{
|
|
Chat* currentChat = getCurrentChat();
|
|
|
|
// Do not send empty drawings
|
|
if( currentChat->getInkDrawing()->isNull() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool supportGifInk = true;
|
|
bool supportIsfInk = true;
|
|
|
|
// Find out which ink formats are supported by all participants
|
|
const QStringList participants( currentChat->getParticipants() );
|
|
foreach( const QString &handle, participants )
|
|
{
|
|
const ContactBase *contact = currentAccount_->getContactByHandle( handle );
|
|
if( contact == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
supportGifInk &= contact->hasCapability( ContactBase::MSN_CAP_INK_GIF );
|
|
supportIsfInk &= contact->hasCapability( ContactBase::MSN_CAP_INK_ISF );
|
|
}
|
|
|
|
// The contacts don't support either formats! Something is wrong, since the ink editor
|
|
// was enabled...
|
|
if( ! supportGifInk && ! supportIsfInk )
|
|
{
|
|
kmWarning() << "Neither Ink formats are supported by the participants!";
|
|
|
|
currentChat->showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
|
|
ChatMessage::CONTENT_SYSTEM_ERROR,
|
|
true,
|
|
i18nc( "Error message shown in chat",
|
|
"Failed to send the handwritten message: "
|
|
"the contacts do not support it." ),
|
|
QString(),
|
|
QString() ) );
|
|
return;
|
|
}
|
|
|
|
InkFormat format = FORMAT_ISF;
|
|
QByteArray ink;
|
|
|
|
if( supportIsfInk )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Retrieving ISF ink data";
|
|
#endif
|
|
|
|
format = FORMAT_ISF;
|
|
ink = Isf::Stream::writer( *( inkCanvas_->drawing() ) );
|
|
}
|
|
else if( supportGifInk && Isf::Stream::supportsGif() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Retrieving GIF ink data";
|
|
#endif
|
|
|
|
format = FORMAT_GIF;
|
|
ink = Isf::Stream::writerGif( *( inkCanvas_->drawing() ) );
|
|
}
|
|
// Any other possibility has been excluded above
|
|
|
|
if( ink.isEmpty() )
|
|
{
|
|
kmWarning() << "An error occured while creating the" << ( supportIsfInk ? "ISF" : "GIF" ) << "Ink data!";
|
|
|
|
currentChat->showMessage( ChatMessage( ChatMessage::TYPE_SYSTEM,
|
|
ChatMessage::CONTENT_SYSTEM_ERROR,
|
|
true,
|
|
i18nc( "Error message shown in chat",
|
|
"Failed to send the handwritten message: "
|
|
"an error has occurred while creating it." ),
|
|
QString(),
|
|
QString() ) );
|
|
return;
|
|
}
|
|
|
|
currentChat->sendInkMessage( format, ink );
|
|
|
|
// Show the message in the browser window
|
|
const QString text( "<img src='data:image/png;base64," + Isf::Stream::writerPng( *( inkCanvas_->drawing() ), true ) + "'>" );
|
|
|
|
ChatMessage message( ChatMessage::TYPE_OUTGOING,
|
|
ChatMessage::CONTENT_MESSAGE_INK,
|
|
false,
|
|
text,
|
|
currentAccount_->getHandle(),
|
|
currentAccount_->getFriendlyName( STRING_ORIGINAL ),
|
|
currentAccount_->getPicturePath() );
|
|
|
|
currentChat->showMessage( message );
|
|
|
|
// Clear and reset the state of the send button
|
|
inkCanvas_->clear();
|
|
sendButton_->setEnabled( false );
|
|
}
|
|
|
|
|
|
// Send a message via the server
|
|
void ChatWindow::sendMessage()
|
|
{
|
|
// Don't send any typing messages while preparing to send the message in the text box...
|
|
doSendTypingMessages_ = false;
|
|
|
|
// Get the message field's contents
|
|
QString text( messageEdit_->toPlainText() );
|
|
|
|
// Don't send empty messages
|
|
if( text.trimmed().isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Max length is fixed to 10 chunks ( multi-packet )
|
|
int maxSendableMessageLength = 14000;
|
|
|
|
// Messages like "//hello" should be sent as "/hello"
|
|
// Messages like "/hello" are commands.
|
|
// Check if this is a command, and if so, emit handleCommand signal
|
|
if( text.size() > 2 && text.mid( 2, 1 ) != "/" )
|
|
{
|
|
if( text.left( 2 ) == "//" )
|
|
{
|
|
text = text.mid( 1 );
|
|
}
|
|
else if( text.left( 1 ) == "/" )
|
|
{
|
|
const QString command( text.mid( 1 ) );
|
|
|
|
// Handle command. If 'false' is returned, send it anyway.
|
|
if ( handleCommand( command ) )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the text is longer than the sendable amount, put the remainder back in the text edit.
|
|
// Since the message will be sent as UTF8, it's the UTF8 length we have to consider.
|
|
const QByteArray utf8Text( text.toUtf8() );
|
|
if( utf8Text.length() > maxSendableMessageLength )
|
|
{
|
|
// If so, then divide the text into the first part and a remainder.
|
|
const QByteArray remainder( utf8Text.right( utf8Text.length() - maxSendableMessageLength ) );
|
|
text = QString::fromUtf8( utf8Text.left( maxSendableMessageLength ) );
|
|
|
|
// Return the remainder to the message edit.
|
|
const QString remainingText( QString::fromUtf8( remainder ) );
|
|
messageEdit_->setText( remainingText );
|
|
lastSentence_ = remainingText;
|
|
}
|
|
else
|
|
{
|
|
// Remove the message from the message field (allowing undo)
|
|
lastSentence_.clear();
|
|
|
|
QTextCursor cursor( messageEdit_->textCursor() );
|
|
messageEdit_->setTextCursor( cursor );
|
|
|
|
cursor.beginEditBlock();
|
|
cursor.select( QTextCursor::Document );
|
|
cursor.removeSelectedText();
|
|
cursor.endEditBlock();
|
|
}
|
|
|
|
// Replace "\n" with "\r\n" (to follow the Windows linebreak style followed by the MSN servers)
|
|
// @TODO Move this server stuff to the network/protocol code
|
|
text = text.replace( QRegularExpression( "\r?\n" ), "\r\n" );
|
|
|
|
// Send the message to the contact(s)
|
|
getCurrentChat()->sendChatMessage( text );
|
|
|
|
// Reset the typing timer, so it restarts correctly
|
|
userTypingTimer_.stop();
|
|
|
|
// Now messages can again be sent
|
|
doSendTypingMessages_ = true;
|
|
|
|
|
|
// Show the message in the browser window
|
|
ChatMessage message( ChatMessage::TYPE_OUTGOING,
|
|
ChatMessage::CONTENT_MESSAGE,
|
|
false,
|
|
text,
|
|
currentAccount_->getHandle(),
|
|
currentAccount_->getFriendlyName( STRING_ORIGINAL ),
|
|
currentAccount_->getPicturePath(),
|
|
currentAccount_->getFont(),
|
|
currentAccount_->getFontColor() );
|
|
|
|
getCurrentChat()->showMessage( message );
|
|
|
|
// Add it to the list of sent messages, to allow fast retyping
|
|
QStringList &quickRetypeList = getCurrentChat()->getQuickRetypeList();
|
|
quickRetypeList.append( message.getBody() );
|
|
lastSentence_ = QString();
|
|
|
|
// Reset the last sentence counter.
|
|
// showMessage() updates chatMessages_
|
|
indexSentences_ = quickRetypeList.size();
|
|
}
|
|
|
|
|
|
|
|
// The 'Send nudge' button has been clicked
|
|
void ChatWindow::sendNudge()
|
|
{
|
|
getCurrentChat()->slotSendNudge();
|
|
}
|
|
|
|
|
|
|
|
// Sets the window icon for this chat window
|
|
void ChatWindow::setChatWindowIcon()
|
|
{
|
|
Chat *chat = getCurrentChat();
|
|
if( chat == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
QPixmap kmessIcon = KIconLoader::global()->loadIcon( "kmess-shadow", KIconLoader::Panel );
|
|
QPixmap userIcon;
|
|
|
|
if ( chat->getParticipants().size() == 1 )
|
|
{
|
|
userIcon = QIcon::fromTheme( "user-identity" ).pixmap( 60, 60 );
|
|
}
|
|
else
|
|
{
|
|
userIcon = QIcon::fromTheme( "system-users" ).pixmap( 60, 60 );
|
|
}
|
|
|
|
setWindowIcon( KMessShared::drawIconOverlay( userIcon, kmessIcon, kmessIcon.size().width(), kmessIcon.size().height(), .60, .90 ) );
|
|
}
|
|
|
|
|
|
|
|
// Change the active chat
|
|
void ChatWindow::setCurrentChat( Chat *chat )
|
|
{
|
|
chatTabs_->setCurrentIndex( chatTabs_->indexOf( chat ) );
|
|
}
|
|
|
|
|
|
|
|
// Enable or disable the parts of the window which allow user interaction
|
|
void ChatWindow::setEnabled( bool isEnabled )
|
|
{
|
|
// Set the state of the main UI elements
|
|
customEmoticonsWidget_ ->setEnabled( isEnabled );
|
|
standardEmoticonsWidget_->setEnabled( isEnabled );
|
|
customEmoticonsDock_ ->setEnabled( isEnabled );
|
|
standardEmoticonsDock_ ->setEnabled( isEnabled );
|
|
|
|
inkCanvas_ ->setEnabled( isEnabled );
|
|
messageEdit_ ->setEnabled( isEnabled );
|
|
newLineButton_ ->setEnabled( isEnabled );
|
|
sendButton_ ->setEnabled( isEnabled );
|
|
standardEmoticonButton_ ->setEnabled( isEnabled );
|
|
customEmoticonButton_ ->setEnabled( isEnabled );
|
|
fontButton_ ->setEnabled( isEnabled );
|
|
fontColorButton_ ->setEnabled( isEnabled );
|
|
inviteButton_ ->setEnabled( isEnabled );
|
|
textButton_ ->setEnabled( isEnabled );
|
|
|
|
|
|
// Set the state of the extra contact actions
|
|
emoticonAction_ ->setEnabled( isEnabled );
|
|
nudgeAction_ ->setEnabled( isEnabled );
|
|
sendAction_ ->setEnabled( isEnabled );
|
|
changeFontAction_ ->setEnabled( isEnabled );
|
|
changeFontColorAction_ ->setEnabled( isEnabled );
|
|
spellCheckAction_ ->setEnabled( isEnabled );
|
|
|
|
// Set the state of cut and paste
|
|
cutAction_ ->setEnabled( isEnabled );
|
|
pasteAction_ ->setEnabled( isEnabled );
|
|
|
|
// Update the winks and handwriting buttons
|
|
if( isEnabled )
|
|
{
|
|
updateModeButtons();
|
|
}
|
|
else
|
|
{
|
|
inkButton_ ->setEnabled( false );
|
|
winksButton_->setEnabled( false );
|
|
}
|
|
// The window is being enabled back, replace some stuff which changes
|
|
// on every connection
|
|
if( isEnabled )
|
|
{
|
|
// Re-read the current account pointer
|
|
// because it likely is a different one
|
|
currentAccount_ = CurrentAccount::instance();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Enable or disable the parts of the window which allow user interaction
|
|
void ChatWindow::setUIState( UIState state )
|
|
{
|
|
uiState_ = state;
|
|
|
|
switch( state )
|
|
{
|
|
case Disconnecting:
|
|
case Disconnected:
|
|
statusLabel_->setButtonEnabled( ChatStatusBar::Disconnected, true );
|
|
setEnabled( false );
|
|
break;
|
|
|
|
case Connecting:
|
|
statusLabel_->setButtonEnabled( ChatStatusBar::Disconnected, false );
|
|
setEnabled( false );
|
|
break;
|
|
|
|
case Connected:
|
|
setEnabled( true );
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Change the caption of the chat window
|
|
void ChatWindow::setWindowTitle( const QString &caption )
|
|
{
|
|
// Remember the window caption when it is fetched from the current chat
|
|
if( caption.isEmpty() )
|
|
{
|
|
Chat *currentChat = getCurrentChat();
|
|
if( currentChat != 0 )
|
|
{
|
|
// Avoid newlines which would cause problems to the UI layout
|
|
caption_ = currentChat->getCaption();
|
|
}
|
|
else
|
|
{
|
|
caption_ = QString();
|
|
}
|
|
}
|
|
|
|
// Always append the "Chat" prefix to the chat
|
|
if( caption.isEmpty() && caption_.isEmpty() )
|
|
{
|
|
setCaption( i18nc( "Chat window caption, without contact name", "Chat" ) );
|
|
}
|
|
else
|
|
{
|
|
setCaption( i18nc( "Chat window caption, with contact name", "%1 - Chat", ( caption.isEmpty() ? caption_ : caption ) ) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// "Show menu bar" was toggled.
|
|
void ChatWindow::showMenuBar()
|
|
{
|
|
// Just show the menubar if it was hidden
|
|
if( ! menuBar()->isVisible() )
|
|
{
|
|
menuBar()->setVisible( true );
|
|
return;
|
|
}
|
|
|
|
// Ask the user if he/she really wants to hide the menubar, to avoid mistakes
|
|
// TODO: If the user disables the shortcut for the menu action, no shortcuts will be shown
|
|
int res = KMessageBox::questionTwoActions( this,
|
|
i18nc( "Question dialog box message",
|
|
"<html>Are you sure you want to hide the menu bar? "
|
|
"You will be able to show it again by using this "
|
|
"keyboard shortcut: <b>%1</b></html>",
|
|
showMenuBar_->shortcut().toString( QKeySequence::NativeText ) ),
|
|
i18nc( "Dialog box caption: hiding the menu bar", "Hiding the Menu" ),
|
|
KGuiItem( i18n( "Yes" ) ),
|
|
KGuiItem( i18n( "No" ) ),
|
|
"hideMenuBarQuestion" );
|
|
|
|
if( res == KMessageBox::PrimaryAction )
|
|
{
|
|
menuBar()->setVisible( showMenuBar_->isChecked() );
|
|
}
|
|
else
|
|
{
|
|
showMenuBar_->setChecked( true );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Show a status message in the statusbar
|
|
void ChatWindow::showStatusMessage( ChatStatusBar::MessageType type, QString message )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Showing status message" << message << "(" << type << ")";
|
|
#endif
|
|
statusLabel_->setMessage( type, message );
|
|
}
|
|
|
|
|
|
|
|
// Show a status message for some seconds in the status dialog
|
|
void ChatWindow::showStatusMessage( QString message, int duration )
|
|
{
|
|
// Avoid newlines which cause problems to the UI layout.
|
|
message = message.replace( "\n", " " );
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Showing status bar text" << message << "with duration" << duration;
|
|
#endif
|
|
|
|
// HACK: Empty strings are not rendered, and any previously set text is not removed.
|
|
// So we change empty strings to contain a single space, which is enough to clean the label up.
|
|
if( message.isEmpty() )
|
|
{
|
|
statusLabel_->setMessage( ChatStatusBar::DefaultType, " " );
|
|
}
|
|
else
|
|
{
|
|
statusLabel_->setMessage( ChatStatusBar::ContactTyping, message );
|
|
}
|
|
|
|
// Each message will have an expiration time of its own or none at all
|
|
statusTimer_.stop();
|
|
|
|
// Non-expiring messages do not trigger the timer
|
|
if( duration <= 0 || message.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Setting status bar update timer to" << duration << "sec.";
|
|
#endif
|
|
statusTimer_.start( duration * 1000 );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Change color for current pen
|
|
void ChatWindow::slotChangeInkColor()
|
|
{
|
|
QColor color;
|
|
color = QColorDialog::getColor( color, this );
|
|
if ( color.isValid() )
|
|
{
|
|
inkCanvas_->setPenColor( color );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Erase brush was selected
|
|
void ChatWindow::slotChangeInkBrush()
|
|
{
|
|
if( inkCanvas_->penType() == Isf::InkCanvas::DrawingPen )
|
|
{
|
|
inkCanvas_->setPenType( Isf::InkCanvas::EraserPen );
|
|
inkEraseButton_->setIcon( QIcon::fromTheme( "draw-freehand" ) );
|
|
inkEraseButton_->setToolTip( i18n( "Drawing brush" ) );
|
|
}
|
|
else
|
|
{
|
|
inkCanvas_->setPenType( Isf::InkCanvas::DrawingPen );
|
|
inkEraseButton_->setIcon( QIcon::fromTheme( "draw-eraser" ) );
|
|
inkEraseButton_->setToolTip( i18n( "Erase brush" ) );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Make the caption blink if the window still doesn't have focus
|
|
void ChatWindow::slotBlinkCaption()
|
|
{
|
|
// Make the caption alternate case
|
|
if ( blinkToUpper_ )
|
|
{
|
|
const QString caption( QTextDocumentFragment::fromHtml( caption_.toUpper() ).toPlainText() );
|
|
setWindowTitle( QString( "* " + caption ) );
|
|
}
|
|
else
|
|
{
|
|
setWindowTitle( QString( "* " + caption_.toLower() ) );
|
|
}
|
|
|
|
blinkToUpper_ = ! blinkToUpper_;
|
|
}
|
|
|
|
|
|
|
|
// Called when an emoticon dock is toggled
|
|
void ChatWindow::slotEmoticonDocksToggled()
|
|
{
|
|
bool showStandardButton = ( ! standardEmoticonsDock_->toggleViewAction()->isChecked() );
|
|
bool showCustomButton = ( ! customEmoticonsDock_ ->toggleViewAction()->isChecked() );
|
|
|
|
// If an emoticon widget is shown and the button needs to be hidden,
|
|
// switch away from it
|
|
if( ( ! showStandardButton && editorChooser_->currentWidget() == stdEmoPage_ )
|
|
|| ( ! showCustomButton && editorChooser_->currentWidget() == cusEmoPage_ ) )
|
|
{
|
|
slotSwitchEditor();
|
|
}
|
|
|
|
standardEmoticonButton_->setVisible( showStandardButton );
|
|
customEmoticonButton_ ->setVisible( showCustomButton );
|
|
}
|
|
|
|
|
|
|
|
// A message has been received
|
|
void ChatWindow::slotGotChatMessage( const ChatMessage &message, Chat *chat )
|
|
{
|
|
Q_UNUSED( message );
|
|
|
|
// Display an highlighted tab title
|
|
if( chat != getCurrentChat() )
|
|
{
|
|
// Paint the text red
|
|
chatTabs_->tabBar()->setTabTextColor( chatTabs_->indexOf( chat ), QColor( "red" ) );
|
|
|
|
// Highlight the window title with the chat with new messages
|
|
caption_ = chat->getCaption();
|
|
}
|
|
|
|
// If the window does not have focus, blink the caption as visual cue of an event
|
|
if( isActiveWindow() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Start blinking the caption
|
|
blinkTimer_.start( 1500 );
|
|
}
|
|
|
|
|
|
|
|
// A nudge has been received: shake the window.
|
|
void ChatWindow::slotGotNudge()
|
|
{
|
|
if( ! currentAccount_->getShakeNudge() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// avoid shaking again in a short period.
|
|
if( lastShake_.isValid() && lastShake_.elapsed() < 15000 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Not shaking the window until"
|
|
<< ( 15000 - lastShake_.elapsed() ) / 1000 << "more seconds.";
|
|
#endif
|
|
return;
|
|
}
|
|
lastShake_.restart();
|
|
|
|
QElapsedTimer t;
|
|
int xp = x();
|
|
int yp = y();
|
|
|
|
t.start();
|
|
|
|
// Code example from: http://ariya.blogspot.com/2005/12/buzz-or-shake-my-window.html
|
|
|
|
// Shake the window.
|
|
for ( int i = 16; i > 0; )
|
|
{
|
|
QApplication::processEvents();
|
|
|
|
if ( t.elapsed() >= 50 )
|
|
{
|
|
int delta = i >> 2;
|
|
int dir = i & 3;
|
|
int dx = (dir == 1 || dir == 2) ? delta : -delta;
|
|
int dy = (dir < 2) ? delta : -delta;
|
|
|
|
move( xp+dx, yp+dy );
|
|
t.restart();
|
|
i--;
|
|
}
|
|
}
|
|
|
|
// Move to start again
|
|
move( xp, yp );
|
|
}
|
|
|
|
|
|
|
|
// Update the contact typing messages in the status bar
|
|
void ChatWindow::slotGotTypingMessage( Chat *chat )
|
|
{
|
|
// Get the active chat if called with no or invalid parameters
|
|
if( chat == 0 )
|
|
{
|
|
chat = getCurrentChat();
|
|
#ifdef KMESSDEBUG_CHATWINDOW_TYPING_MESSAGES
|
|
kmDebug() << "Using current chat" << chat;
|
|
#endif
|
|
}
|
|
#ifdef KMESSDEBUG_CHATWINDOW_TYPING_MESSAGES
|
|
else
|
|
{
|
|
kmDebug() << "Using chat:" << chat;
|
|
}
|
|
#endif
|
|
|
|
const QPalette palette;
|
|
int tabIndex = chatTabs_->indexOf( chat );
|
|
const QStringList typingContacts( chat->getTypingContacts() );
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW_TYPING_MESSAGES
|
|
kmDebug() << "These contacts are typing:" << typingContacts;
|
|
#endif
|
|
|
|
// Update the color of this chat's tab
|
|
// If the color is red, leave it
|
|
if( chatTabs_->tabBar()->tabTextColor( tabIndex ) != QColor( "red" ) )
|
|
{
|
|
QColor color;
|
|
if( typingContacts.isEmpty() )
|
|
{
|
|
color = palette.color( QPalette::WindowText );
|
|
}
|
|
else
|
|
{
|
|
color = palette.color( QPalette::Highlight );
|
|
}
|
|
|
|
chatTabs_->tabBar()->setTabTextColor( tabIndex, color );
|
|
}
|
|
|
|
// If the typing message was not intended for the currently active tab, skip
|
|
// the status bar processing altogether
|
|
if( chat != getCurrentChat() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW_TYPING_MESSAGES
|
|
kmDebug() << "Typing message was not for the active chat, skipping status bar update.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// If no one is typing anymore, clean up the status bar
|
|
if( typingContacts.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW_TYPING_MESSAGES
|
|
kmDebug() << "No contacts are typing anymore, cleaning up the status bar.";
|
|
#endif
|
|
showStatusMessage();
|
|
return;
|
|
}
|
|
|
|
QString statusString;
|
|
|
|
// Decide what string will be used depending on how many contacts are typing
|
|
if( typingContacts.count() == 1 )
|
|
{
|
|
const QString& first = typingContacts.first();
|
|
statusString = i18n( "%1 is typing...", first );
|
|
}
|
|
else
|
|
{
|
|
int count = typingContacts.count() - 2;
|
|
const QString& first = typingContacts[0];
|
|
const QString& second = typingContacts[1];
|
|
|
|
if( count == 0 )
|
|
{
|
|
statusString = i18n( "%1 and %2 are typing...", first, second );
|
|
}
|
|
else
|
|
{
|
|
statusString = i18n( "%1, %2 and %3 others are typing...", first, second, count );
|
|
}
|
|
}
|
|
|
|
// Set the message to the status bar: the chat will send another signal when the current typing message expires
|
|
showStatusMessage(statusString, CHAT_TYPING_EXPIRATION_TIME );
|
|
}
|
|
|
|
|
|
|
|
// Invite button was pressed
|
|
void ChatWindow::slotInviteContact()
|
|
{
|
|
// Show invite dialog
|
|
QStringList usersToInvite;
|
|
InviteDialog inviteDialog( getCurrentChat()->getParticipants(), usersToInvite );
|
|
|
|
// If the user press OK and the users to invite list isn't empty then invite contacts
|
|
if( ! usersToInvite.isEmpty() )
|
|
{
|
|
getCurrentChat()->inviteContacts( usersToInvite );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// The message text changed, the user is typing or drawing
|
|
void ChatWindow::slotMessageChanged()
|
|
{
|
|
// The window is closing, no chats are active
|
|
if( chatTabs_->count() == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool disable = true;
|
|
|
|
// The message was changed
|
|
if( ! inkButton_->isChecked() )
|
|
{
|
|
disable = ( messageEdit_->document()->isEmpty() );
|
|
}
|
|
// The ink drawing was changed
|
|
else
|
|
{
|
|
disable = ( inkCanvas_->isEmpty() );
|
|
}
|
|
|
|
// Something has been typed, enable the button so the user can send the message
|
|
sendButton_->setDisabled( disable );
|
|
|
|
|
|
// There's some text, but the user doesn't want us to send typing notifications to the contacts
|
|
if( ! doSendTypingMessages_ )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the typing timer is already going, do nothing
|
|
if( userTypingTimer_.isActive() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if( ! inkButton_->isChecked() )
|
|
{
|
|
// If the last character of the message is a newline, sending the typing signal would
|
|
// cause the actual text message to be received twice, so check the last character
|
|
if( messageEdit_->toPlainText().right(1) == "\n" )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If a command is being typed, don't send a typing message
|
|
if( messageEdit_->toPlainText().left( 1 ) == "/" && messageEdit_->toPlainText().left( 2 ) != "//" )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// TODO Create a method to send an "user is drawing" message. WLM is able to do that.
|
|
getCurrentChat()->sendTypingMessage();
|
|
|
|
userTypingTimer_.start();
|
|
}
|
|
|
|
|
|
|
|
// The user clicked the new line button so insert a new line in the editor
|
|
void ChatWindow::slotNewLineButtonClicked()
|
|
{
|
|
messageEdit_->insertPlainText( "\n" );
|
|
}
|
|
|
|
|
|
|
|
// The user pressed return in the message editor, so send the message
|
|
void ChatWindow::slotSendButtonClicked()
|
|
{
|
|
if( inkButton_->isChecked() )
|
|
{
|
|
sendInk();
|
|
inkCanvas_->setFocus();
|
|
}
|
|
else if( winksButton_->isChecked() )
|
|
{
|
|
const MsnObject &msnObject = winksWidget_->getMsnObjectWinkSelected();
|
|
if( msnObject.isValid() )
|
|
{
|
|
getCurrentChat()->sendWink( msnObject );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sendMessage();
|
|
messageEdit_->setFocus();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show the chat history for a contact
|
|
void ChatWindow::slotShowChatHistory()
|
|
{
|
|
const QAction* action = qobject_cast< const QAction* >( sender() );
|
|
|
|
if( ! action )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmWarning() << "Called directly, action is not valid!";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
// The click came from the menu item itself, not from a sub item.
|
|
// Select the first contact of the list
|
|
if( action == historyMenuAction_ )
|
|
{
|
|
if( historyMenuAction_->children().isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmWarning() << "No contacts available in the menu!";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
action = qobject_cast< const QAction* >( historyMenuAction_->children().first() );
|
|
KMESS_ASSERT( action );
|
|
}
|
|
|
|
const QString handle( action->data().toString() );
|
|
|
|
if( handle.isEmpty() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmWarning() << "Action did not contain a valid handle! (contained a" << action->data().typeName() << " instead)";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
ChatHistoryDialog *dialog = new ChatHistoryDialog( this );
|
|
dialog->setInitialContact( currentAccount_->getHandle(), handle );
|
|
dialog->show();
|
|
}
|
|
|
|
|
|
|
|
// Switch to the message editor, or another one
|
|
void ChatWindow::slotSwitchEditor( QObject* widget )
|
|
{
|
|
// If a specific widget was specified, use it instead
|
|
const QObject *caller = widget ? widget : sender();
|
|
bool disableSendButton = true;
|
|
bool disableNewLineButton = true;
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Switching editors: caller:" << caller;
|
|
#endif
|
|
|
|
// Some widgets are pretty high, hide them unless they're needed
|
|
standardEmoticonsWidget_->hide();
|
|
customEmoticonsWidget_->hide();
|
|
winksWidget_->hide();
|
|
inkCanvas_->hide();
|
|
|
|
// Show the text control frame by default
|
|
inkCtlFrame_->setVisible( false );
|
|
textCtlFrame_->setVisible( true );
|
|
|
|
// Called by the winks button, show the available winks
|
|
if( caller == winksButton_ && winksButton_->isChecked() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Switching to winks list.";
|
|
#endif
|
|
winksWidget_->refresh();
|
|
winksWidget_->show();
|
|
|
|
editorChooser_->setCurrentWidget( winksPage_ );
|
|
|
|
winksButton_->setChecked( true );
|
|
inkButton_->setChecked( false );
|
|
standardEmoticonButton_->setChecked( false );
|
|
customEmoticonButton_->setChecked( false );
|
|
textButton_->setChecked( false );
|
|
|
|
disableSendButton = false;
|
|
}
|
|
// Called by the ink button, switch to the ink editor if it has been checked
|
|
else if( caller == inkButton_ && inkButton_->isChecked() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Switching to ink editor.";
|
|
#endif
|
|
inkCanvas_->show();
|
|
|
|
editorChooser_->setCurrentWidget( inkPage_ );
|
|
winksButton_->setChecked( false );
|
|
inkButton_->setChecked( true );
|
|
standardEmoticonButton_->setChecked( false );
|
|
customEmoticonButton_->setChecked( false );
|
|
textButton_->setChecked( false );
|
|
|
|
inkCtlFrame_->setVisible( true );
|
|
textCtlFrame_->setVisible( false );
|
|
|
|
disableSendButton = ( inkCanvas_->isEmpty() );
|
|
}
|
|
// Called by the standard emoticon button
|
|
else if( caller == standardEmoticonButton_
|
|
&& standardEmoticonButton_->isChecked() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Switching to standard emoticons widget.";
|
|
#endif
|
|
standardEmoticonsWidget_->show();
|
|
|
|
editorChooser_->setCurrentWidget( stdEmoPage_ );
|
|
|
|
winksButton_->setChecked( false );
|
|
inkButton_->setChecked( false );
|
|
standardEmoticonButton_->setChecked( true );
|
|
customEmoticonButton_->setChecked( false );
|
|
textButton_->setChecked( false );
|
|
}
|
|
// Called by the custom emoticon button
|
|
else if( caller == customEmoticonButton_
|
|
&& customEmoticonButton_->isChecked() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Switching to custom emoticons widget.";
|
|
#endif
|
|
customEmoticonsWidget_->show();
|
|
|
|
editorChooser_->setCurrentWidget( cusEmoPage_ );
|
|
|
|
winksButton_->setChecked( false );
|
|
inkButton_->setChecked( false );
|
|
standardEmoticonButton_->setChecked( false );
|
|
customEmoticonButton_->setChecked( true );
|
|
textButton_->setChecked( false );
|
|
}
|
|
else // Otherwise, show the message editor
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Switching to the message editor.";
|
|
#endif
|
|
editorChooser_->setCurrentWidget( textPage_ );
|
|
winksButton_->setChecked( false );
|
|
inkButton_->setChecked( false );
|
|
standardEmoticonButton_->setChecked( false );
|
|
customEmoticonButton_->setChecked( false );
|
|
textButton_->setChecked( true );
|
|
|
|
disableNewLineButton = false;
|
|
disableSendButton = ( messageEdit_->document()->isEmpty() );
|
|
}
|
|
|
|
// Update the send button status
|
|
sendButton_->setDisabled( disableSendButton );
|
|
newLineButton_->setDisabled( disableNewLineButton );
|
|
|
|
// Also scroll down the chat, in all cases
|
|
getCurrentChat()->scrollToBottom( false /* no forced scrolling */ );
|
|
}
|
|
|
|
|
|
|
|
// Switch the window to the tab at the left of the current one
|
|
void ChatWindow::slotSwitchToLeftTab()
|
|
{
|
|
// Find out at what tab are we and how many are there
|
|
int current = chatTabs_->currentIndex();
|
|
int count = chatTabs_->count();
|
|
|
|
// Do nothing if there's only one tab
|
|
if( count <= 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the first tab was selected, then restart from the last tab
|
|
if( current == 0 )
|
|
{
|
|
chatTabs_->setCurrentIndex( count - 1 );
|
|
return;
|
|
}
|
|
|
|
chatTabs_->setCurrentIndex( current - 1 );
|
|
}
|
|
|
|
|
|
|
|
// Switch the window to the tab at the right of the current one
|
|
void ChatWindow::slotSwitchToRightTab()
|
|
{
|
|
// Find out at what tab are we and how many are there
|
|
int current = chatTabs_->currentIndex();
|
|
int count = chatTabs_->count();
|
|
|
|
// Do nothing if there's only one tab
|
|
if( count <= 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the last tab was selected, then restart from the first tab
|
|
if( current >= ( count - 1 ) )
|
|
{
|
|
chatTabs_->setCurrentIndex( 0 );
|
|
return;
|
|
}
|
|
|
|
chatTabs_->setCurrentIndex( current + 1 );
|
|
}
|
|
|
|
|
|
|
|
// The active tab was changed
|
|
void ChatWindow::slotTabChanged( int currentIndex )
|
|
{
|
|
// Avoid switching to uninitialized widgets
|
|
if( ! initialized_ )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Not switching tab while not initialized.";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
if( currentIndex < 0 || currentIndex >= chatTabs_->count() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
if( currentIndex == -1 )
|
|
{
|
|
kmDebug() << "The window is closing.";
|
|
}
|
|
else
|
|
{
|
|
kmDebug() << "Chat tab index" << currentIndex << "doesn't exist in the tabs list!";
|
|
}
|
|
#endif
|
|
|
|
messageEdit_->setDocument( new QTextDocument( messageEdit_ ) );
|
|
updateModeButtons();
|
|
updateEditorFont();
|
|
return;
|
|
}
|
|
|
|
Chat *chat = getChat( currentIndex );
|
|
|
|
if( chat == 0 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Chat tab" << currentIndex << "was destroyed";
|
|
#endif
|
|
return;
|
|
}
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
else
|
|
{
|
|
kmDebug() << "Activating chat tab" << currentIndex;
|
|
}
|
|
#endif
|
|
|
|
// update the icon for this window appropriately.
|
|
setChatWindowIcon();
|
|
|
|
// Reset the color
|
|
const QPalette palette;
|
|
chatTabs_->tabBar()->setTabTextColor( currentIndex, palette.color( QPalette::WindowText ) );
|
|
|
|
// Display the new chat contents in the UI. Block the signals to avoid the editors
|
|
// from firing the "message changed" signal, which sends a typing message.
|
|
messageEdit_ ->blockSignals( true );
|
|
inkCanvas_ ->blockSignals( true );
|
|
contactsDock_->setWidget( chat->getContactsWidget() );
|
|
messageEdit_ ->setDocument( chat->getMessageEditContents() );
|
|
inkCanvas_ ->setDrawing( chat->getInkDrawing() );
|
|
messageEdit_ ->blockSignals( false );
|
|
inkCanvas_ ->blockSignals( false );
|
|
|
|
// Reset quick re-type index
|
|
indexSentences_ = chat->getQuickRetypeList().size();
|
|
|
|
// Set the font style/color
|
|
updateEditorFont();
|
|
|
|
// Update the window UI with this chat's details
|
|
slotSwitchEditor();
|
|
setWindowTitle();
|
|
updateModeButtons();
|
|
slotGotTypingMessage( chat );
|
|
|
|
QAction* action;
|
|
qDeleteAll( historyMenuAction_->children() );
|
|
|
|
const QStringList participantsList( chat->getParticipants() );
|
|
const QStringList contactsWithHistory( ChatHistoryManager::contactsList() );
|
|
foreach( const QString &handle, participantsList )
|
|
{
|
|
const ContactBase *contact = currentAccount_->getContactByHandle( handle );
|
|
if( contact == 0 )
|
|
{
|
|
contact = currentAccount_->addInvitedContact( handle );
|
|
}
|
|
|
|
action = new QAction( QIcon::fromTheme( MsnStatus::getIconName( contact->getStatus() ) ),
|
|
contact->getFriendlyName( STRING_CLEANED ),
|
|
historyMenuAction_ );
|
|
action->setData( handle );
|
|
|
|
// Only connect the items whose contact has recorded history
|
|
if( contactsWithHistory.contains( handle ) )
|
|
{
|
|
connect( action, SIGNAL( triggered(bool) ),
|
|
this, SLOT ( slotShowChatHistory() ) );
|
|
}
|
|
else
|
|
{
|
|
action->setEnabled( false );
|
|
}
|
|
|
|
historyMenuAction_->addAction( action );
|
|
}
|
|
|
|
// Re-enable the spell checker for this tab.
|
|
// FIXME KDE4.x bug this needs to be unset then set
|
|
messageEdit_->setCheckSpellingEnabled( ! spellCheckAction_->isChecked() );
|
|
messageEdit_->setCheckSpellingEnabled( spellCheckAction_->isChecked() );
|
|
}
|
|
|
|
|
|
|
|
// Remove a chat
|
|
bool ChatWindow::slotTabRemoved( Chat *chat )
|
|
{
|
|
// When the function is called the pointer for chat is already checked by
|
|
// queryClose() or slotTabRemoved function, so grep the index of it and remove
|
|
// it from tab widget
|
|
int closedTabIndex = chatTabs_->indexOf( chat );
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Removing chat with index" << closedTabIndex;
|
|
#endif
|
|
|
|
// Remove the tab of the closed chat
|
|
chatTabs_->removeTab( closedTabIndex );
|
|
|
|
// Request query close to chat ( so we can close correctly the SB and save the log )
|
|
chat->queryClose();
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Removed tab index" << closedTabIndex;
|
|
#endif
|
|
|
|
// Free the memory
|
|
delete chat;
|
|
|
|
// If there are no more open chats, close the window too
|
|
checkAndCloseWindow();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
// Delete an existing chat tab by widget
|
|
bool ChatWindow::slotTabRemoved( QWidget *chatWidget )
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( chatTabs_->count() > 0 );
|
|
#endif
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Forwarding removal command for a chat widget.";
|
|
kmDebug() << "Currently open chats:" << chatTabs_->count();
|
|
#endif
|
|
|
|
// Cast the Widget to Chat pointer
|
|
Chat *chat = qobject_cast<Chat*>( chatWidget );
|
|
|
|
if( chat == 0 )
|
|
{
|
|
kmWarning() << "Unable to find chat for widget:" << chatWidget;
|
|
return false;
|
|
}
|
|
|
|
// Relay the deletion to the main method
|
|
return slotTabRemoved( chat );
|
|
}
|
|
|
|
|
|
|
|
// Update a chat's tab title when the participants names change
|
|
void ChatWindow::slotUpdateChatInfo( Chat *chat )
|
|
{
|
|
// If called without a parameter, we've been called by a chat's signal
|
|
if( chat == 0 )
|
|
{
|
|
// Find the chat which has emitted the signal
|
|
chat = qobject_cast<Chat*>( sender() );
|
|
}
|
|
|
|
// Search it into the list
|
|
int index = chatTabs_->indexOf( chat );
|
|
if( index == -1 )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Cannot find source chat!";
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
const QStringList participantsList( chat->getParticipants() );
|
|
|
|
if ( chat == getCurrentChat() )
|
|
{
|
|
// update the icon for this window appropriately.
|
|
setChatWindowIcon();
|
|
}
|
|
|
|
// getCaption() should return a properly html-escaped string
|
|
QString chatCaption( chat->getCaption() );
|
|
|
|
// Change the tab caption
|
|
chatTabs_->setTabText( index, chatCaption.replace( '&', "&&" ) );
|
|
chatTabs_->setTabIcon( index, chat->getParticipantsTabIcon() );
|
|
|
|
// Create a tool tip with details on the chat
|
|
QStringList participants;
|
|
foreach( const QString &handle, participantsList )
|
|
{
|
|
const ContactBase *contact = currentAccount_->getContactByHandle( handle );
|
|
if( contact == 0 )
|
|
{
|
|
contact = currentAccount_->addInvitedContact( handle );
|
|
}
|
|
|
|
participants += QString( "%1 (%2)<br /><i>%3</i>" )
|
|
.arg( contact->getFriendlyName( STRING_CLEANED ) )
|
|
.arg( MsnStatus::getName( contact->getStatus() ) )
|
|
.arg( handle );
|
|
}
|
|
|
|
QString toolTip( i18nc( "Tool tip for chat tabs",
|
|
"<html><h2>Chat Info</h2>"
|
|
"<dl>"
|
|
"<dt><b>Contacts:</b></dt><dd><ul><li>%1</li></ul></dd>"
|
|
"<dt><b>Chat started on:</b></dt><dd>%2</dd>"
|
|
"<dt><b>Connected with account:</b></dt><dd>%3</dd>"
|
|
"</dl></html>",
|
|
participants.join( "</li><li>" ),
|
|
QLocale().toString( chat->getStartTime(), QLocale::LongFormat ),
|
|
currentAccount_->getHandle() ) );
|
|
|
|
chatTabs_->setTabToolTip( index, toolTip );
|
|
|
|
// If it is the current one, the UI must be updated
|
|
if ( chat == getCurrentChat() )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Source chat is the current one, updating UI.";
|
|
#endif
|
|
|
|
// Update the window title
|
|
setWindowTitle();
|
|
// Update the status of the writing mode buttons (winks, handwriting,...)
|
|
// This is needed in case the contact has changed status
|
|
updateModeButtons();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Start a file transfer
|
|
void ChatWindow::startFileTransfer()
|
|
{
|
|
getCurrentChat()->startFileTransfer();
|
|
}
|
|
|
|
|
|
|
|
// Called when the "use emoticons" action is called.
|
|
void ChatWindow::toggleEmoticons( bool useEmoticons )
|
|
{
|
|
// Set the new emoticon use value to the account
|
|
currentAccount_->setUseEmoticons( useEmoticons );
|
|
}
|
|
|
|
|
|
|
|
// Called when the "use emoticons" action is called.
|
|
void ChatWindow::toggleSessionInfo( bool showSessionMessages )
|
|
{
|
|
// Set the new emoticon use value to the account
|
|
currentAccount_->setShowSessionInfo( showSessionMessages );
|
|
}
|
|
|
|
|
|
|
|
// Called when the "use spell checking" action is called.
|
|
void ChatWindow::toggleSpellCheck( bool useSpellCheck )
|
|
{
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Spell checking is now" << useSpellCheck;
|
|
#endif
|
|
|
|
// Sync the action with a manually set flag (ie in readProperties())
|
|
if( spellCheckAction_->isChecked() != useSpellCheck )
|
|
{
|
|
spellCheckAction_->setChecked( useSpellCheck );
|
|
}
|
|
|
|
// Change the spell checking feature status
|
|
// FIXME KDE4.x bug this needs to be unset then set
|
|
messageEdit_->setCheckSpellingEnabled( ! spellCheckAction_->isChecked() );
|
|
messageEdit_->setCheckSpellingEnabled( spellCheckAction_->isChecked() );
|
|
}
|
|
|
|
|
|
|
|
// Update the status of the tiny writing mode buttons
|
|
void ChatWindow::updateModeButtons()
|
|
{
|
|
// don't do anything when the window
|
|
// chat was just closed or user is not connected
|
|
if( getCurrentChat() == NULL || uiState_ != Connected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool enableInkButton = true;
|
|
bool enableWinksButton = true;
|
|
|
|
#if KMESS_ENABLE_INK == 0
|
|
enableInkButton = false;
|
|
#endif
|
|
|
|
const QStringList participants( getCurrentChat()->getParticipants() );
|
|
foreach( const QString &handle, participants )
|
|
{
|
|
const ContactBase *contact = currentAccount_->getContactByHandle( handle );
|
|
if( contact == 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
#if KMESS_ENABLE_INK == 1
|
|
if( enableInkButton )
|
|
{
|
|
// Disable the Ink button if the contact doesn't support Ink
|
|
if( ! contact->hasCapability( ContactBase::MSN_CAP_INK_GIF )
|
|
&& ! contact->hasCapability( ContactBase::MSN_CAP_INK_ISF ) )
|
|
{
|
|
enableInkButton = false;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Disable the Winks button if the contact doesn't support Winks
|
|
if( enableWinksButton
|
|
&& ! contact->hasCapability( (ContactBase::MsnClientCapabilities) ( ContactBase::MSN_CAP_WINKS | ContactBase::MSN_CAP_MULTI_PACKET ) ) )
|
|
{
|
|
enableWinksButton = false;
|
|
}
|
|
}
|
|
|
|
#ifdef KMESSDEBUG_CHATWINDOW
|
|
kmDebug() << "Winks/Ink button disabling check: all contacts support winks?" << enableWinksButton
|
|
<< "and ink?" << enableInkButton;
|
|
#endif
|
|
|
|
inkButton_ ->setEnabled( enableInkButton );
|
|
winksButton_->setEnabled( enableWinksButton );
|
|
|
|
// Set a tooltip or a status bar message to either buttons.
|
|
// The tooltip covers the status message otherwise.
|
|
|
|
#if KMESS_ENABLE_INK == 1 // else, the ink button isn't shown anyway
|
|
if( enableInkButton )
|
|
{
|
|
inkButton_->setToolTip ( i18n( "Handwriting mode" ) );
|
|
inkButton_->setStatusTip( QString() );
|
|
}
|
|
else
|
|
{
|
|
inkButton_->setToolTip ( QString() );
|
|
if( getCurrentChat()->getParticipants().count() > 1 )
|
|
{
|
|
inkButton_->setStatusTip( i18nc( "Label text",
|
|
"Handwriting is disabled: some of the contacts do not "
|
|
"support receiving handwritten messages." ) );
|
|
}
|
|
else
|
|
{
|
|
inkButton_->setStatusTip( i18nc( "Label text",
|
|
"Handwriting is disabled: this contact does not "
|
|
"support receiving handwritten messages." ) );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if( enableWinksButton )
|
|
{
|
|
winksButton_->setToolTip ( i18n( "Winks" ) );
|
|
winksButton_->setStatusTip( QString() );
|
|
}
|
|
else
|
|
{
|
|
winksButton_->setToolTip ( QString() );
|
|
if( getCurrentChat()->getParticipants().count() > 1 )
|
|
{
|
|
winksButton_->setStatusTip( i18nc( "Label text",
|
|
"Winks are disabled: some of the contacts do not "
|
|
"support receiving winks." ) );
|
|
}
|
|
else
|
|
{
|
|
winksButton_->setStatusTip( i18nc( "Label text",
|
|
"Winks are disabled: this contact does not "
|
|
"support receiving winks." ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Update the editor's font to match the account's font
|
|
void ChatWindow::updateEditorFont()
|
|
{
|
|
#ifdef KMESSTEST
|
|
KMESS_ASSERT( currentAccount_ != 0 );
|
|
#endif
|
|
|
|
// Set the font settings the user has chosen
|
|
messageEdit_->setTextColor( QColor( currentAccount_->getFontColor() ) );
|
|
messageEdit_->setFont( currentAccount_->getFont() );
|
|
messageEdit_->setCurrentFont( currentAccount_->getFont() );
|
|
messageEdit_->setFontPointSize( currentAccount_->getFont().pointSize() );
|
|
}
|
|
|
|
|
|
|
|
// Clean up the view of the current chat tab
|
|
void ChatWindow::viewClearChat()
|
|
{
|
|
getCurrentChat()->slotClearChat();
|
|
}
|
|
|
|
|
|
|
|
// enlarge the font size
|
|
void ChatWindow::viewZoomIn()
|
|
{
|
|
changeZoomFactor( true );
|
|
}
|
|
|
|
|
|
|
|
// decrease the font size
|
|
void ChatWindow::viewZoomOut()
|
|
{
|
|
changeZoomFactor( false );
|
|
}
|