diff --git a/kopete/chatwindow/chattexteditpart.cpp b/kopete/chatwindow/chattexteditpart.cpp index 5d47ea5db..03511de05 100644 --- a/kopete/chatwindow/chattexteditpart.cpp +++ b/kopete/chatwindow/chattexteditpart.cpp @@ -1,547 +1,547 @@ /* chattexteditpart.cpp - Chat Text Edit Part Copyright (c) 2008 by Benson Tsai Copyright (c) 2004 by Richard Smith Kopete (c) 2002-2004 by the Kopete developers ************************************************************************* * * * 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 "chattexteditpart.h" #include "kopetecontact.h" #include "kopetechatsession.h" #include "kopeteonlinestatus.h" #include "kopeteprotocol.h" #include "kopeteglobal.h" #include "kopeteappearancesettings.h" #include "kopetechatwindowsettings.h" #include #include #include #include #include "kopetechatwindow_debug.h" #include #include #include #include #include #include #include // Qt includes #include #include #include #include #include #include #include #include #include typedef KParts::GenericFactory ChatTextEditPartFactory; K_EXPORT_COMPONENT_FACTORY(librichtexteditpart, ChatTextEditPartFactory) ChatTextEditPart::ChatTextEditPart(Kopete::ChatSession *session, QWidget *parent) : KParts::ReadOnlyPart(parent) , m_session(session) { init(session, parent); } ChatTextEditPart::ChatTextEditPart(QWidget *parent, QObject *, const QStringList &) : KParts::ReadOnlyPart(parent) , m_session() { init(m_session, parent); } void ChatTextEditPart::init(Kopete::ChatSession *session, QWidget *parent) { - setComponentName(QStringLiteral("kopete"), i18n("Kopete")); + setComponentName(QStringLiteral("kopeterichtexteditpart"), i18n("Kopete")); editor = new KopeteRichTextWidget(parent, m_session->protocol()->capabilities(), actionCollection()); setWidget(editor); // TODO: Rename rc file - setXMLFile(QStringLiteral("kopeterichtexteditpart/kopeterichtexteditpartfull.rc")); + setXMLFile(QStringLiteral("kopeterichtexteditpartfull.rc")); historyPos = -1; mComplete = new KCompletion(); mComplete->setIgnoreCase(true); mComplete->setOrder(KCompletion::Weighted); // set params on the edit widget textEdit()->setMinimumSize(QSize(75, 20)); // some signals and slots connections connect(textEdit(), SIGNAL(textChanged()), this, SLOT(slotTextChanged())); // timers for typing notifications m_typingRepeatTimer = new QTimer(this); m_typingRepeatTimer->setObjectName(QStringLiteral("m_typingRepeatTimer")); m_typingStopTimer = new QTimer(this); m_typingStopTimer->setObjectName(QStringLiteral("m_typingStopTimer")); connect(m_typingRepeatTimer, &QTimer::timeout, this, &ChatTextEditPart::slotRepeatTypingTimer); connect(m_typingStopTimer, SIGNAL(timeout()), this, SLOT(slotStoppedTypingTimer())); connect(session, SIGNAL(contactAdded(const Kopete::Contact *,bool)), this, SLOT(slotContactAdded(const Kopete::Contact *))); connect(session, SIGNAL(contactRemoved(const Kopete::Contact *,QString,Qt::TextFormat,bool)), this, SLOT(slotContactRemoved(const Kopete::Contact *))); connect(session, SIGNAL(onlineStatusChanged(Kopete::Contact *,Kopete::OnlineStatus,Kopete::OnlineStatus)), this, SLOT(slotContactStatusChanged(Kopete::Contact *,Kopete::OnlineStatus,Kopete::OnlineStatus))); connect(Kopete::AppearanceSettings::self(), SIGNAL(appearanceChanged()), this, SLOT(slotAppearanceChanged())); connect(KGlobalSettings::self(), SIGNAL(kdisplayFontChanged()), this, SLOT(slotAppearanceChanged())); connect(editor, &KopeteRichTextWidget::richTextSupportChanged, this, &ChatTextEditPart::slotRichTextSupportChanged); slotAppearanceChanged(); slotContactAdded(session->myself()); foreach (Kopete::Contact *contact, session->members()) { slotContactAdded(contact); } } ChatTextEditPart::~ChatTextEditPart() { delete mComplete; } void ChatTextEditPart::complete() { QTextCursor textCursor = textEdit()->textCursor(); QTextBlock block = textCursor.block(); QString txt = block.text(); const int blockLength = block.length() - 1; // block.length includes the '\n' const int blockPosition = block.position(); int cursorPos = textCursor.position() - blockPosition; // TODO replace with textCursor.movePosition(QTextCursor::PreviousWord)? const int startPos = txt.lastIndexOf(QRegExp(QLatin1String("\\s\\S+")), cursorPos - 1) + 1; int endPos = txt.indexOf(QRegExp(QLatin1String("[\\s\\:]")), startPos); if (endPos == -1) { endPos = blockLength; } const QString word = txt.mid(startPos, endPos - startPos); if (endPos < txt.length() && txt[endPos] == ':') { // Eat ':' and ' ' too, if they are there, so that after pressing Tab // we are on the right side of them again. ++endPos; if (endPos < txt.length() && txt[endPos] == ' ') { ++endPos; } } //qCDebug(KOPETE_CHATEWINDOW_LOG) << word << "from" << txt // << "cursor pos=" << cursorPos // << "start pos=" << startPos << "end pos=" << endPos; QString match; if (word != m_lastMatch) { match = mComplete->makeCompletion(word); m_lastMatch.clear(); } else { match = mComplete->nextMatch(); } if (!match.isEmpty()) { m_lastMatch = match; if (textCursor.blockNumber() == 0 && startPos == 0) { match += QLatin1String(": "); } //qCDebug(KOPETE_CHATEWINDOW_LOG) << "Selecting from position" << cursorPos << "to position" << endPos; // Select the text to remove textCursor.setPosition(startPos + blockPosition); textCursor.setPosition(endPos + blockPosition, QTextCursor::KeepAnchor); //qCDebug(KOPETE_CHATEWINDOW_LOG) << "replacing selection:" << textCursor.selectedText() << "with match:" << match; // Type the text to replace it textCursor.insertText(match); textEdit()->setTextCursor(textCursor); } else { //qCDebug(KOPETE_CHATEWINDOW_LOG) << "No completions! Tried" << mComplete->items(); } } void ChatTextEditPart::slotDisplayNameChanged(const QString &oldName, const QString &newName) { mComplete->removeItem(oldName); mComplete->addItem(newName); } void ChatTextEditPart::slotContactAdded(const Kopete::Contact *contact) { connect(contact, SIGNAL(displayNameChanged(QString,QString)), this, SLOT(slotDisplayNameChanged(QString,QString))); mComplete->addItem(contact->displayName()); } void ChatTextEditPart::slotContactRemoved(const Kopete::Contact *contact) { disconnect(contact, SIGNAL(displayNameChanged(QString,QString)), this, SLOT(slotDisplayNameChanged(QString,QString))); mComplete->removeItem(contact->displayName()); } bool ChatTextEditPart::canSend() { if (!m_session) { return false; } // can't send if there's nothing *to* send... if (text(Qt::PlainText).isEmpty()) { return false; } Kopete::ContactPtrList members = m_session->members(); // if we can't send offline, make sure we have a reachable contact... if (!(m_session->protocol()->capabilities() & Kopete::Protocol::CanSendOffline)) { bool reachableContactFound = false; //TODO: does this perform badly in large / busy IRC channels? - no, doesn't seem to for (int i = 0; i != members.size(); ++i) { if (members[i]->isReachable()) { reachableContactFound = true; break; } } // no online contact found and can't send offline? can't send. if (!reachableContactFound) { return false; } } return true; } void ChatTextEditPart::slotContactStatusChanged(Kopete::Contact *, const Kopete::OnlineStatus &newStatus, const Kopete::OnlineStatus &oldStatus) { //FIXME: should use signal contact->isReachableChanged, but it doesn't exist ;( if ((oldStatus.status() == Kopete::OnlineStatus::Offline) != (newStatus.status() == Kopete::OnlineStatus::Offline)) { emit canSendChanged(canSend()); } } void ChatTextEditPart::sendMessage() { QString txt = this->text(Qt::PlainText); // avoid sending emtpy messages or enter keys (see bug 100334) if (txt.isEmpty() || txt == QLatin1String("\n")) { return; } if (m_lastMatch.isNull() && (txt.indexOf(QRegExp(QLatin1String("^\\w+:\\s"))) > -1)) { //no last match and it finds something of the form of "word:" at the start of a line QString search = txt.left(txt.indexOf(':')); if (!search.isEmpty()) { QString match = mComplete->makeCompletion(search); if (!match.isNull()) { textEdit()->setText(txt.replace(0, search.length(), match)); } } } if (!m_lastMatch.isNull()) { //FIXME: what is the next line for? mComplete->addItem(m_lastMatch); m_lastMatch.clear(); } slotStoppedTypingTimer(); Kopete::Message sentMessage = contents(); emit messageSent(sentMessage); historyList.prepend(this->text(Qt::AutoText)); historyPos = -1; textEdit()->moveCursor(QTextCursor::End); textEdit()->clear(); emit canSendChanged(false); } bool ChatTextEditPart::isTyping() { QString txt = text(Qt::PlainText); //Make sure the message is empty. QString::isEmpty() //returns false if a message contains just whitespace //which is the reason why we strip the whitespace return !txt.trimmed().isEmpty(); } void ChatTextEditPart::slotTextChanged() { if (isTyping()) { // And they were previously typing if (!m_typingRepeatTimer->isActive()) { m_typingRepeatTimer->setSingleShot(false); m_typingRepeatTimer->start(4000); slotRepeatTypingTimer(); } // Reset the stop timer again, regardless of status m_typingStopTimer->setSingleShot(true); m_typingStopTimer->start(4500); } emit canSendChanged(canSend()); } void ChatTextEditPart::historyUp() { if (historyList.empty() || historyPos == historyList.count() - 1) { return; } QString text = this->text(Qt::PlainText); bool empty = text.trimmed().isEmpty(); // got text? save it if (!empty) { text = this->text(Qt::AutoText); if (historyPos == -1) { historyList.prepend(text); historyPos = 0; } else { historyList[historyPos] = text; } } historyPos++; QString newText = historyList[historyPos]; textEdit()->setTextOrHtml(newText); textEdit()->moveCursor(QTextCursor::End); } void ChatTextEditPart::historyDown() { if (historyList.empty() || historyPos == -1) { return; } QString text = this->text(Qt::PlainText); bool empty = text.trimmed().isEmpty(); // got text? save it if (!empty) { text = this->text(Qt::AutoText); historyList[historyPos] = text; } historyPos--; QString newText = (historyPos >= 0 ? historyList[historyPos] : QString()); textEdit()->setTextOrHtml(newText); textEdit()->moveCursor(QTextCursor::End); } void ChatTextEditPart::addText(const QString &text) { if (Qt::mightBeRichText(text)) { if (textEdit()->isRichTextEnabled()) { textEdit()->insertHtml(text); } else { QTextDocument doc; doc.setHtml(text); textEdit()->insertPlainText(doc.toPlainText()); } } else { textEdit()->insertPlainText(text); } } void ChatTextEditPart::setContents(const Kopete::Message &message) { if (isRichTextEnabled()) { textEdit()->setHtml(message.escapedBody()); } else { textEdit()->setPlainText(message.plainBody()); } textEdit()->moveCursor(QTextCursor::End); } Kopete::Message ChatTextEditPart::contents() { Kopete::Message currentMsg(m_session->myself(), m_session->members()); currentMsg.setDirection(Kopete::Message::Outbound); if (isRichTextEnabled()) { currentMsg.setHtmlBody(text()); Kopete::Protocol::Capabilities protocolCaps = m_session->protocol()->capabilities(); // I hate base *only* support, *waves fist at MSN* if (protocolCaps & Kopete::Protocol::BaseFormatting) { currentMsg.setFont(textEdit()->currentRichFormat().font()); } if (protocolCaps & Kopete::Protocol::BaseFgColor) { currentMsg.setForegroundColor(textEdit()->currentRichFormat().foreground().color()); } if (protocolCaps & Kopete::Protocol::BaseBgColor) { currentMsg.setBackgroundColor(textEdit()->currentRichFormat().background().color()); } } else { currentMsg.setPlainBody(text()); } return currentMsg; } void ChatTextEditPart::slotRepeatTypingTimer() { emit typing(true); } void ChatTextEditPart::slotStoppedTypingTimer() { m_typingRepeatTimer->stop(); m_typingStopTimer->stop(); emit typing(false); } void ChatTextEditPart::slotAppearanceChanged() { Kopete::AppearanceSettings *settings = Kopete::AppearanceSettings::self(); QFont font = (settings->chatFontSelection() == 1) ? settings->chatFont() : QFontDatabase::systemFont(QFontDatabase::GeneralFont); QTextCharFormat format; format.setFont(font); format.setBackground(settings->chatBackgroundColor()); format.setForeground(settings->chatTextColor()); editor->setDefaultPlainCharFormat(format); editor->setDefaultRichCharFormat(format); QString styleSheet = QStringLiteral("QTextEdit { color: %1; }"); editor->setStyleSheet(styleSheet.arg(settings->chatTextColor().name())); } void ChatTextEditPart::slotRichTextSupportChanged() { KXMLGUIFactory *f = factory(); if (f) { f->removeClient(this); f->addClient(this); } } KopeteRichTextWidget *ChatTextEditPart::textEdit() { return editor; } void ChatTextEditPart::setCheckSpellingEnabled(bool enabled) { editor->setCheckSpellingEnabled(enabled); } bool ChatTextEditPart::checkSpellingEnabled() const { return editor->checkSpellingEnabled(); } void ChatTextEditPart::checkToolbarEnabled() { emit toolbarToggled(isRichTextEnabled()); } K4AboutData *ChatTextEditPart::createAboutData() { K4AboutData *aboutData = new K4AboutData("krichtexteditpart", 0, ki18n("Chat Text Edit Part"), "0.1", ki18n("A simple rich text editor part"), K4AboutData::License_LGPL); aboutData->addAuthor(ki18n("Richard J. Moore"), KLocalizedString(), "rich@kde.org", "http://xmelegance.org/"); aboutData->addAuthor(ki18n("Jason Keirstead"), KLocalizedString(), "jason@keirstead.org", "http://www.keirstead.org/"); aboutData->addAuthor(ki18n("Michaël Larouche"), KLocalizedString(), "larouche@kde.org" "http://www.tehbisnatch.org/"); aboutData->addAuthor(ki18n("Benson Tsai"), KLocalizedString(), "btsai@vrwarp.com" "http://www.vrwarp.com/"); return aboutData; } void ChatTextEditPart::readConfig(KConfigGroup &config) { qDebug() << "Loading config"; QTextCharFormat format = editor->defaultRichFormat(); QFont font = config.readEntry("TextFont", format.font()); QColor fg = config.readEntry("TextFgColor", format.foreground().color()); QColor bg = config.readEntry("TextBgColor", format.background().color()); QTextCharFormat desiredFormat = editor->currentRichFormat(); desiredFormat.setFont(font); desiredFormat.setForeground(fg); desiredFormat.setBackground(bg); editor->setCurrentRichCharFormat(desiredFormat); textEdit()->setAlignment(static_cast(config.readEntry("EditAlignment", int(Qt::AlignLeft)))); } void ChatTextEditPart::writeConfig(KConfigGroup &config) { qDebug() << "Saving config"; config.writeEntry("TextFont", editor->currentRichFormat().font()); config.writeEntry("TextFgColor", editor->currentRichFormat().foreground().color()); config.writeEntry("TextBgColor", editor->currentRichFormat().background().color()); config.writeEntry("EditAlignment", int(editor->alignment())); } void ChatTextEditPart::resetConfig(KConfigGroup &config) { qDebug() << "Setting default font style"; editor->slotResetFontAndColor(); //action_align_left->trigger(); config.deleteEntry("TextFont"); config.deleteEntry("TextFg"); config.deleteEntry("TextBg"); config.deleteEntry("EditAlignment"); } QString ChatTextEditPart::text(Qt::TextFormat format) const { if ((format == Qt::RichText || format == Qt::AutoText) && isRichTextEnabled()) { return editor->toHtml(); } else { return editor->toPlainText(); } } bool ChatTextEditPart::isRichTextEnabled() const { return editor->isRichTextEnabled(); } // vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/chatwindow/kopetechatwindow.cpp b/kopete/chatwindow/kopetechatwindow.cpp index 6a28ab0b5..a70343f22 100644 --- a/kopete/chatwindow/kopetechatwindow.cpp +++ b/kopete/chatwindow/kopetechatwindow.cpp @@ -1,1397 +1,1397 @@ /* kopetechatwindow.cpp - Chat Window Copyright (c) 2008 by Benson Tsai Copyright (c) 2007 by Gustavo Pichorim Boiko Copyright (c) 2002-2006 by Olivier Goffart Copyright (c) 2003-2004 by Richard Smith Copyright (C) 2002 by James Grant Copyright (c) 2002 by Stefan Gehn Copyright (c) 2002-2004 by Martijn Klingens Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * 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 "kopetechatwindow.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef CHRONO #include #endif #include #include #include #include #include #include #include "kopetechatwindow_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "chatmessagepart.h" #include "chattexteditpart.h" #include "chatview.h" #include "kopeteapplication.h" #include "kopetebehaviorsettings.h" #include "kopeteemoticonaction.h" #include "kopetegroup.h" #include "kopetechatsession.h" #include "kopetemetacontact.h" #include "kopetepluginmanager.h" #include "kopeteprotocol.h" #include "kopetestdaction.h" #include "kopeteviewmanager.h" #include "chatmemberslistview.h" #include "chatsessionmemberslistmodel.h" #include #include #include #include typedef QMap AccountMap; typedef QMap GroupMap; typedef QMap MetaContactMap; typedef QList WindowList; using Kopete::ChatSessionMembersListModel; namespace { AccountMap accountMap; GroupMap groupMap; MetaContactMap mcMap; WindowList windows; } KopeteChatWindow *KopeteChatWindow::window(Kopete::ChatSession *manager) { bool windowCreated = false; KopeteChatWindow *myWindow = 0; //Take the first and the first? What else? Kopete::Group *group = nullptr; Kopete::ContactPtrList members = manager->members(); Kopete::MetaContact *metaContact = members.first()->metaContact(); if (metaContact) { Kopete::GroupList gList = metaContact->groups(); group = gList.first(); } switch (Kopete::BehaviorSettings::self()->chatWindowGroupPolicy()) { //Open chats from the same protocol in the same window case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByAccount: if (accountMap.contains(manager->account())) { myWindow = accountMap[ manager->account() ]; } else { windowCreated = true; } break; //Open chats from the same group in the same window case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByGroup: if (group && groupMap.contains(group)) { myWindow = groupMap[ group ]; } else { windowCreated = true; } break; //Open chats from the same metacontact in the same window case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupByMetaContact: if (mcMap.contains(metaContact)) { myWindow = mcMap[ metaContact ]; } else { windowCreated = true; } break; //Open all chats in the same window case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::GroupAll: if (windows.isEmpty()) { windowCreated = true; } else { //Here we are finding the window with the most tabs and //putting it there. Need this for the cases where config changes //midstream int viewCount = -1; WindowList::iterator it; for (it = windows.begin(); it != windows.end(); ++it) { if ((*it)->chatViewCount() > viewCount) { myWindow = (*it); viewCount = (*it)->chatViewCount(); } } } break; //Open every chat in a new window case Kopete::BehaviorSettings::EnumChatWindowGroupPolicy::OpenNewWindow: default: windowCreated = true; break; } if (windowCreated) { myWindow = new KopeteChatWindow(manager->form()); if (!accountMap.contains(manager->account())) { accountMap.insert(manager->account(), myWindow); } if (!mcMap.contains(metaContact)) { mcMap.insert(metaContact, myWindow); } if (group && !groupMap.contains(group)) { groupMap.insert(group, myWindow); } } // kDebug( 14010 ) << "Open Windows: " << windows.count(); return myWindow; } KopeteChatWindow::KopeteChatWindow(Kopete::ChatSession::Form form, QWidget *parent) : KXmlGuiWindow(parent) , initialForm(form) { #ifdef CHRONO QTime chrono; chrono.start(); #endif m_activeView = nullptr; m_popupView = nullptr; backgroundFile = nullptr; updateBg = true; m_tabBar = nullptr; m_participantsWidget = new QDockWidget(i18n("Participants"), this); m_participantsWidget->setAllowedAreas(Qt::RightDockWidgetArea | Qt::LeftDockWidgetArea); m_participantsWidget->setFeatures(QDockWidget::DockWidgetClosable); m_participantsWidget->setTitleBarWidget(nullptr); m_participantsWidget->setObjectName(QStringLiteral("Participants")); //object name is required for automatic position and settings save. ChatSessionMembersListModel *members_model = new ChatSessionMembersListModel(this); connect(this, SIGNAL(chatSessionChanged(Kopete::ChatSession *)), members_model, SLOT(setChatSession(Kopete::ChatSession *))); ChatMembersListView *chatmembers = new ChatMembersListView(m_participantsWidget); chatmembers->setModel(members_model); chatmembers->setWordWrap(true); m_participantsWidget->setWidget(chatmembers); initActions(); addDockWidget(Qt::RightDockWidgetArea, m_participantsWidget); KVBox *vBox = new KVBox(this); vBox->setLineWidth(0); vBox->setSpacing(0); vBox->setFrameStyle(QFrame::NoFrame); // set default window size. This could be removed by fixing the size hints of the contents if (initialForm == Kopete::ChatSession::Chatroom) { resize(650, 400); } else { m_participantsWidget->hide(); resize(400, 400); } setCentralWidget(vBox); mainArea = new QFrame(vBox); mainArea->setLineWidth(0); mainArea->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout = new QVBoxLayout(mainArea); mainLayout->setContentsMargins(0, 4, 0, 0); if (Kopete::BehaviorSettings::self()->chatWindowShowSendButton()) { //Send Button m_button_send = new QPushButton(i18nc("@action:button", "Send"), statusBar()); m_button_send->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); m_button_send->setEnabled(false); m_button_send->setFont(statusBar()->font()); m_button_send->setFixedHeight(statusBar()->sizeHint().height()); connect(m_button_send, SIGNAL(clicked()), this, SLOT(slotSendMessage())); statusBar()->addPermanentWidget(m_button_send, 0); } else { m_button_send = nullptr; } m_status_text = new KSqueezedTextLabel(i18nc("@info:status", "Ready."), statusBar()); m_status_text->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); m_status_text->setFont(statusBar()->font()); m_status_text->setFixedHeight(statusBar()->sizeHint().height()); statusBar()->addWidget(m_status_text, 1); windows.append(this); windowListChanged(); m_alwaysShowTabs = KSharedConfig::openConfig()->group("ChatWindowSettings"). readEntry(QStringLiteral("AlwaysShowTabs"), false); // kDebug( 14010 ) << "Open Windows: " << windows.count(); setupGUI(static_cast(ToolBar | Keys | StatusBar | Save | Create), QStringLiteral("kopetechatwindow.rc")); //has to be done after the setupGUI, in order to have the toolbar set up to restore window settings. readOptions(); #ifdef CHRONO qDebug()<<"TIME: "<checkDetachEnable(); } } void KopeteChatWindow::slotTabContextMenu(QWidget *tab, const QPoint &pos) { m_popupView = static_cast(tab); QMenu popup; popup.addSection(KStringHandler::rsqueeze(m_popupView->caption())); popup.addAction(actionContactMenu); popup.addSeparator(); popup.addAction(actionTabPlacementMenu); popup.addAction(tabDetach); popup.addAction(actionDetachMenu); popup.addAction(tabCloseAllOthers); popup.addAction(tabClose); popup.exec(pos); m_popupView = 0; } ChatView *KopeteChatWindow::activeView() { return m_activeView; } void KopeteChatWindow::updateSendKeySequence() { if (!sendMessage || !m_activeView) { return; } m_activeView->editPart()->textEdit()->setSendKeySequenceList(sendMessage->shortcuts()); } void KopeteChatWindow::initActions(void) { KActionCollection *coll = actionCollection(); createStandardStatusBarAction(); chatSend = new QAction(QIcon::fromTheme(QStringLiteral("mail-send")), i18n("&Send Message"), coll); //Recuperate the qAction for later sendMessage = coll->addAction(QStringLiteral("chat_send"), chatSend); //Set up change signal in case the user changer the shortcut later connect(sendMessage, SIGNAL(changed()), SLOT(updateSendKeySequence())); connect(chatSend, SIGNAL(triggered(bool)), SLOT(slotSendMessage())); //Default to 'Return' and 'Enter' for sending messages //'Return' is the key in the main part of the keyboard //'Enter' is on the Numpad QList chatSendShortcuts; chatSendShortcuts << QKeySequence(Qt::Key_Return) << QKeySequence(Qt::Key_Enter); coll->setDefaultShortcuts(chatSend, chatSendShortcuts); chatSend->setEnabled(false); chatSendFile = new QAction(QIcon::fromTheme(QStringLiteral("mail-attachment")), i18n("Send File..."), coll); coll->addAction(QStringLiteral("chat_send_file"), chatSendFile); connect(chatSendFile, SIGNAL(triggered(bool)), SLOT(slotSendFile())); chatSendFile->setEnabled(false); KStandardAction::save(this, SLOT(slotChatSave()), coll); KStandardAction::print(this, SLOT(slotChatPrint()), coll); QAction *quitAction = KStandardAction::quit(this, SLOT(close()), coll); quitAction->setText(i18n("Close All Chats")); tabClose = KStandardAction::close(this, SLOT(slotChatClosed()), coll); coll->addAction(QStringLiteral("tabs_close"), tabClose); tabActive = new QAction(i18n("&Activate Next Active Tab"), coll); coll->addAction(QStringLiteral("tabs_active"), tabActive); // tabActive->setShortcut( KStandardShortcut::tabNext() ); tabActive->setEnabled(false); connect(tabActive, SIGNAL(triggered(bool)), this, SLOT(slotNextActiveTab())); tabRight = new QAction(i18n("&Activate Next Tab"), coll); coll->addAction(QStringLiteral("tabs_right"), tabRight); coll->setDefaultShortcuts(tabRight, KStandardShortcut::tabNext()); tabRight->setEnabled(false); connect(tabRight, SIGNAL(triggered(bool)), this, SLOT(slotNextTab())); tabLeft = new QAction(i18n("&Activate Previous Tab"), coll); coll->addAction(QStringLiteral("tabs_left"), tabLeft); coll->setDefaultShortcuts(tabLeft, KStandardShortcut::tabPrev()); tabLeft->setEnabled(false); connect(tabLeft, SIGNAL(triggered(bool)), this, SLOT(slotPreviousTab())); // This action exists mostly so that the shortcut is configurable. // The actual "slot" is the eventFilter. nickComplete = new QAction(i18n("Nic&k Completion"), coll); coll->addAction(QStringLiteral("nick_complete"), nickComplete); coll->setDefaultShortcut(nickComplete, QKeySequence(Qt::Key_Tab)); tabDetach = new QAction(QIcon::fromTheme(QStringLiteral("tab-detach")), i18n("&Detach Chat"), coll); coll->addAction(QStringLiteral("tabs_detach"), tabDetach); tabDetach->setEnabled(false); connect(tabDetach, SIGNAL(triggered(bool)), this, SLOT(slotDetachChat())); tabCloseAllOthers = new QAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("Close &All But This Tab"), coll); coll->addAction(QStringLiteral("tabs_close_others"), tabCloseAllOthers); tabCloseAllOthers->setEnabled(true); connect(tabCloseAllOthers, SIGNAL(triggered(bool)), this, SLOT(slotCloseAllOtherTabs())); actionDetachMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("tab-detach")), i18n("&Move Tab to Window"), coll); coll->addAction(QStringLiteral("tabs_detachmove"), actionDetachMenu); actionDetachMenu->setDelayed(false); connect(actionDetachMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareDetachMenu())); connect(actionDetachMenu->menu(), SIGNAL(triggered(QAction *)), this, SLOT(slotDetachChat(QAction *))); actionTabPlacementMenu = new KActionMenu(i18n("&Tab Placement"), coll); coll->addAction(QStringLiteral("tabs_placement"), actionTabPlacementMenu); connect(actionTabPlacementMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPreparePlacementMenu())); connect(actionTabPlacementMenu->menu(), SIGNAL(triggered(QAction *)), this, SLOT(slotPlaceTabs(QAction *))); coll->setDefaultShortcut(tabDetach, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); KStandardAction::cut(this, SLOT(slotCut()), coll); KStandardAction::copy(this, SLOT(slotCopy()), coll); KStandardAction::paste(this, SLOT(slotPaste()), coll); QAction *action; historyUp = new QAction(i18n("Previous History"), coll); coll->addAction(QStringLiteral("history_up"), historyUp); coll->setDefaultShortcut(historyUp, QKeySequence(Qt::CTRL + Qt::Key_Up)); connect(historyUp, SIGNAL(triggered(bool)), this, SLOT(slotHistoryUp())); historyDown = new QAction(i18n("Next History"), coll); coll->addAction(QStringLiteral("history_down"), historyDown); coll->setDefaultShortcut(historyDown, QKeySequence(Qt::CTRL + Qt::Key_Down)); connect(historyDown, SIGNAL(triggered(bool)), this, SLOT(slotHistoryDown())); action = KStandardAction::prior(this, SLOT(slotPageUp()), coll); coll->addAction(QStringLiteral("scroll_up"), action); action = KStandardAction::next(this, SLOT(slotPageDown()), coll); coll->addAction(QStringLiteral("scroll_down"), action); KStandardAction::showMenubar(menuBar(), SLOT(setVisible(bool)), coll); toggleAutoSpellCheck = new KToggleAction(i18n("Automatic Spell Checking"), coll); coll->addAction(QStringLiteral("enable_auto_spell_check"), toggleAutoSpellCheck); toggleAutoSpellCheck->setChecked(true); connect(toggleAutoSpellCheck, SIGNAL(triggered(bool)), this, SLOT(toggleAutoSpellChecking())); QAction *toggleParticipantsAction = m_participantsWidget->toggleViewAction(); toggleParticipantsAction->setText(i18n("Show Participants")); toggleParticipantsAction->setIconText(i18n("Participants")); toggleParticipantsAction->setIcon(QIcon::fromTheme(QStringLiteral("system-users"))); coll->addAction(QStringLiteral("show_participants_widget"), toggleParticipantsAction); actionSmileyMenu = new KopeteEmoticonAction(coll); coll->addAction(QStringLiteral("format_smiley"), actionSmileyMenu); actionSmileyMenu->setDelayed(false); connect(actionSmileyMenu, SIGNAL(activated(QString)), this, SLOT(slotSmileyActivated(QString))); actionContactMenu = new KActionMenu(i18n("Co&ntacts"), coll); coll->addAction(QStringLiteral("contacts_menu"), actionContactMenu); actionContactMenu->setDelayed(false); connect(actionContactMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotPrepareContactMenu())); KopeteStdAction::preferences(coll, "settings_prefs"); KToolBarSpacerAction *spacer = new KToolBarSpacerAction(coll); coll->addAction(QStringLiteral("spacer"), spacer); //The Sending movie normalIcon = QPixmap(BarIcon(QStringLiteral("kopete"))); // we can't set the tool bar as parent, if we do, it will be deleted when we configure toolbars anim = new QLabel(QString(), 0L); anim->setObjectName(QStringLiteral("kde toolbar widget")); anim->setMargin(5); anim->setPixmap(normalIcon); animIcon = KIconLoader::global()->loadMovie(QStringLiteral("newmessage"), KIconLoader::Toolbar); if (animIcon) { animIcon->setPaused(true); } QWidgetAction *animAction = new QWidgetAction(coll); animAction->setText(i18n("Toolbar Animation")); animAction->setDefaultWidget(anim); coll->addAction(QStringLiteral("toolbar_animation"), animAction); //toolBar()->insertWidget( 99, anim->width(), anim ); //toolBar()->alignItemRight( 99 ); } /* const QString KopeteChatWindow::fileContents( const QString &path ) const { QString contents; QFile file( path ); if ( file.open( QIODevice::ReadOnly ) ) { QTextStream stream( &file ); contents = stream.readAll(); file.close(); } return contents; } */ void KopeteChatWindow::slotStopAnimation(ChatView *view) { if (view == m_activeView) { anim->setPixmap(normalIcon); if (animIcon && animIcon->state() == QMovie::Running) { animIcon->setPaused(true); } } } void KopeteChatWindow::slotUpdateSendEnabled() { if (!m_activeView) { return; } bool enabled = m_activeView->canSend(); chatSend->setEnabled(enabled); if (m_button_send) { m_button_send->setEnabled(enabled); } } void KopeteChatWindow::updateChatSendFileAction() { if (!m_activeView) { return; } chatSendFile->setEnabled(m_activeView->canSendFile()); } void KopeteChatWindow::toggleAutoSpellChecking() { if (!m_activeView) { return; } bool currentSetting = m_activeView->editPart()->checkSpellingEnabled(); m_activeView->editPart()->setCheckSpellingEnabled(!currentSetting); updateSpellCheckAction(); } void KopeteChatWindow::updateSpellCheckAction() { if (!m_activeView) { return; } bool currentSetting = m_activeView->editPart()->checkSpellingEnabled(); toggleAutoSpellCheck->setChecked(currentSetting); } void KopeteChatWindow::enableSpellCheckAction(bool enable) { toggleAutoSpellCheck->setChecked(enable); } void KopeteChatWindow::updateActions() { updateSpellCheckAction(); updateChatSendFileAction(); } void KopeteChatWindow::slotHistoryUp() { if (m_activeView) { m_activeView->editPart()->historyUp(); } } void KopeteChatWindow::slotHistoryDown() { if (m_activeView) { m_activeView->editPart()->historyDown(); } } void KopeteChatWindow::slotPageUp() { if (m_activeView) { m_activeView->messagePart()->pageUp(); } } void KopeteChatWindow::slotPageDown() { if (m_activeView) { m_activeView->messagePart()->pageDown(); } } void KopeteChatWindow::slotCut() { m_activeView->cut(); } void KopeteChatWindow::slotCopy() { m_activeView->copy(); } void KopeteChatWindow::slotPaste() { m_activeView->paste(); } void KopeteChatWindow::slotResetFontAndColor() { m_activeView->resetFontAndColor(); } void KopeteChatWindow::setStatus(const QString &text) { m_status_text->setText(text); } void KopeteChatWindow::testCanDecode(const QDragMoveEvent *event, bool &accept) { if (m_tabBar && qobject_cast(m_tabBar->childAt(event->pos())) && chatViewList[static_cast(m_tabBar->childAt(event->pos()))->selectTab(event->pos())]->isDragEventAccepted(event)) { accept = true; } else { accept = false; } } void KopeteChatWindow::receivedDropEvent(QWidget *w, QDropEvent *e) { m_tabBar->setCurrentWidget(w); activeView()->dropEvent(e); } void KopeteChatWindow::createTabBar() { if (!m_tabBar) { KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("ChatWindowSettings")); m_tabBar = new KTabWidget(mainArea); m_tabBar->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); m_tabBar->setTabsClosable(cg.readEntry(QStringLiteral("HoverClose"), true)); m_tabBar->setMovable(true); m_tabBar->setAutomaticResizeTabs(true); connect(m_tabBar, SIGNAL(closeRequest(QWidget *)), this, SLOT(slotCloseChat(QWidget *))); m_UpdateChatLabel = cg.readEntry(QStringLiteral("ShowContactName"), true); QToolButton *m_rightWidget = new QToolButton(m_tabBar); connect(m_rightWidget, SIGNAL(clicked()), this, SLOT(slotChatClosed())); m_rightWidget->setIcon(SmallIcon("tab-close")); m_rightWidget->adjustSize(); m_rightWidget->setToolTip(i18nc("@info:tooltip", "Close the current tab")); m_tabBar->setCornerWidget(m_rightWidget, Qt::TopRightCorner); mainLayout->addWidget(m_tabBar); m_tabBar->show(); for (ChatViewList::iterator it = chatViewList.begin(); it != chatViewList.end(); ++it) { addTab(*it); } connect(m_tabBar, SIGNAL(testCanDecode(const QDragMoveEvent *,bool&)), this, SLOT(testCanDecode(const QDragMoveEvent *,bool&))); connect(m_tabBar, SIGNAL(receivedDropEvent(QWidget *,QDropEvent *)), this, SLOT(receivedDropEvent(QWidget *,QDropEvent *))); connect(m_tabBar, SIGNAL(currentChanged(QWidget *)), this, SLOT(setActiveView(QWidget *))); connect(m_tabBar, SIGNAL(contextMenu(QWidget *,QPoint)), this, SLOT(slotTabContextMenu(QWidget *,QPoint))); if (m_activeView) { m_tabBar->setCurrentWidget(m_activeView); } else { setActiveView(chatViewList.first()); } int tabPosition = cg.readEntry(QStringLiteral("Tab Placement"), 0); QAction action(this); action.setData(tabPosition); slotPlaceTabs(&action); } } void KopeteChatWindow::slotCloseChat(QWidget *chatView) { static_cast(chatView)->closeView(); //FIXME: check if we need to remove from the chatViewList } void KopeteChatWindow::addTab(ChatView *view) { QList chatMembers = view->msgManager()->members(); Kopete::Contact *c = nullptr; foreach (Kopete::Contact *contact, chatMembers) { if (!c || c->onlineStatus() < contact->onlineStatus()) { c = contact; } } QIcon pluginIcon = c ? view->msgManager()->contactOnlineStatus(c).iconFor(c) : QIcon::fromTheme(view->msgManager()->protocol()->pluginIcon()); view->setParent(m_tabBar); view->setWindowFlags(0); view->move(QPoint()); - //view->show(); + view->show(); m_tabBar->addTab(view, pluginIcon, QLatin1String("")); view->setVisible(view == m_activeView); connect(view, SIGNAL(updateStatusIcon(ChatView *)), this, SLOT(slotUpdateCaptionIcons(ChatView *))); if (m_UpdateChatLabel) { connect(view, SIGNAL(captionChanged(bool)), this, SLOT(updateChatLabel())); view->setCaption(view->caption(), false); } } void KopeteChatWindow::setPrimaryChatView(ChatView *view) { //TODO figure out what else we have to save here besides the font //reparent clears a lot of stuff out view->setParent(mainArea); view->setWindowFlags(0); view->move(QPoint()); view->show(); mainLayout->addWidget(view); setActiveView(view); } void KopeteChatWindow::deleteTabBar() { if (m_tabBar) { disconnect(m_tabBar, SIGNAL(currentChanged(QWidget *)), this, SLOT(setActiveView(QWidget *))); disconnect(m_tabBar, SIGNAL(contextMenu(QWidget *,QPoint)), this, SLOT(slotTabContextMenu(QWidget *,QPoint))); if (!chatViewList.isEmpty()) { setPrimaryChatView(chatViewList.first()); } m_tabBar->deleteLater(); m_tabBar = nullptr; } } void KopeteChatWindow::attachChatView(ChatView *newView) { chatViewList.append(newView); if (!m_alwaysShowTabs && chatViewList.count() == 1) { setPrimaryChatView(newView); } else { if (!m_tabBar) { createTabBar(); } else { addTab(newView); } newView->setActive(false); } newView->setMainWindow(this); newView->editWidget()->installEventFilter(this); KCursor::setAutoHideCursor(newView->editWidget(), true, true); connect(newView, SIGNAL(captionChanged(bool)), this, SLOT(slotSetCaption(bool))); connect(newView, SIGNAL(messageSuccess(ChatView *)), this, SLOT(slotStopAnimation(ChatView *))); connect(newView, SIGNAL(updateStatusIcon(ChatView *)), this, SLOT(slotUpdateCaptionIcons(ChatView *))); if (m_UpdateChatLabel) { connect(newView, SIGNAL(updateChatState(ChatView *,int)), this, SLOT(updateChatState(ChatView *,int))); } updateActions(); checkDetachEnable(); connect(newView, SIGNAL(autoSpellCheckEnabled(ChatView *,bool)), this, SLOT(slotAutoSpellCheckEnabled(ChatView *,bool))); } void KopeteChatWindow::checkDetachEnable() { bool haveTabs = (chatViewList.count() > 1); tabCloseAllOthers->setEnabled(haveTabs); tabDetach->setEnabled(haveTabs); tabLeft->setEnabled(haveTabs); tabRight->setEnabled(haveTabs); tabActive->setEnabled(haveTabs); actionTabPlacementMenu->setEnabled(m_tabBar != 0); bool otherWindows = (windows.count() > 1); actionDetachMenu->setEnabled(otherWindows); } void KopeteChatWindow::detachChatView(ChatView *view) { chatViewList.removeAt(chatViewList.indexOf(view)); disconnect(view, SIGNAL(captionChanged(bool)), this, SLOT(slotSetCaption(bool))); disconnect(view, SIGNAL(updateStatusIcon(ChatView *)), this, SLOT(slotUpdateCaptionIcons(ChatView *))); disconnect(view, SIGNAL(updateChatState(ChatView *,int)), this, SLOT(updateChatState(ChatView *,int))); view->editWidget()->removeEventFilter(this); if (m_tabBar) { int curPage = m_tabBar->currentIndex(); QWidget *page = m_tabBar->currentWidget(); // if the current view is to be detached, switch to a different one if (page == view) { if (curPage > 0) { m_tabBar->setCurrentIndex(curPage - 1); } else { m_tabBar->setCurrentIndex(curPage + 1); } } m_tabBar->removePage(view); if (m_tabBar->currentWidget()) { setActiveView(static_cast(m_tabBar->currentWidget())); } } if (m_activeView == view) { m_activeView = 0; } if (chatViewList.isEmpty()) { close(); } else if (!m_alwaysShowTabs && chatViewList.count() == 1) { deleteTabBar(); } checkDetachEnable(); } void KopeteChatWindow::slotDetachChat(QAction *action) { KopeteChatWindow *newWindow = nullptr; ChatView *detachedView; if (m_popupView) { detachedView = m_popupView; } else { detachedView = m_activeView; } if (!detachedView) { return; } //if we don't do this, we might crash -// createGUI(nullptr); + createGUI(nullptr); guiFactory()->removeClient(detachedView->msgManager()); if (!action) { newWindow = new KopeteChatWindow(detachedView->msgManager()->form()); newWindow->setObjectName(QStringLiteral("KopeteChatWindow")); } else { newWindow = windows.at(action->data().toInt()); } newWindow->show(); newWindow->raise(); detachChatView(detachedView); newWindow->attachChatView(detachedView); } void KopeteChatWindow::slotCloseAllOtherTabs() { ChatView *detachedView; if (m_popupView) { detachedView = m_popupView; } else { detachedView = m_activeView; } foreach (ChatView *view, chatViewList) { if (view != detachedView) { view->closeView(); } } } void KopeteChatWindow::slotPreviousTab() { int curPage = m_tabBar->currentIndex(); if (curPage > 0) { m_tabBar->setCurrentIndex(curPage - 1); } else { m_tabBar->setCurrentIndex(m_tabBar->count() - 1); } } void KopeteChatWindow::slotNextTab() { int curPage = m_tabBar->currentIndex(); if (curPage == (m_tabBar->count() - 1)) { m_tabBar->setCurrentIndex(0); } else { m_tabBar->setCurrentIndex(curPage + 1); } } void KopeteChatWindow::slotNextActiveTab() { int curPage = m_tabBar->currentIndex(); for (int i = (curPage+1) % m_tabBar->count(); i != curPage; i = (i+1) % m_tabBar->count()) { ChatView *v = static_cast(m_tabBar->widget(i)); //We assume we only have ChatView's if (v->tabState() == ChatView::Highlighted || v->tabState() == ChatView::Message) { m_tabBar->setCurrentIndex(i); break; } } } void KopeteChatWindow::slotSetCaption(bool active) { if (active && m_activeView) { setCaption(m_activeView->caption(), false); } } void KopeteChatWindow::updateBackground(const QPixmap &pm) { if (updateBg) { updateBg = false; delete backgroundFile; backgroundFile = new KTemporaryFile(); backgroundFile->setSuffix(QStringLiteral(".bmp")); backgroundFile->open(); pm.save(backgroundFile, "BMP"); QTimer::singleShot(100, this, SLOT(slotEnableUpdateBg())); } } void KopeteChatWindow::setActiveView(QWidget *widget) { ChatView *view = static_cast(widget); if (m_activeView == view) { return; } if (m_activeView) { disconnect(m_activeView->editWidget(), SIGNAL(checkSpellingChanged(bool)), this, SLOT(enableSpellCheckAction(bool))); disconnect(m_activeView, SIGNAL(canSendChanged(bool)), this, SLOT(slotUpdateSendEnabled())); disconnect(m_activeView, SIGNAL(canAcceptFilesChanged()), this, SLOT(updateChatSendFileAction())); guiFactory()->removeClient(m_activeView->msgManager()); m_activeView->saveChatSettings(); } if (view != 0) { guiFactory()->addClient(view->msgManager()); } // createGUI( view->editPart() ); if (m_activeView) { m_activeView->setActive(false); } m_activeView = view; if (view == 0) { return; } if (chatViewList.indexOf(view) == -1) { attachChatView(view); } connect(m_activeView->editWidget(), SIGNAL(checkSpellingChanged(bool)), this, SLOT(enableSpellCheckAction(bool))); connect(m_activeView, SIGNAL(canSendChanged(bool)), this, SLOT(slotUpdateSendEnabled())); connect(m_activeView, SIGNAL(canAcceptFilesChanged()), this, SLOT(updateChatSendFileAction())); //Tell it it is active m_activeView->setActive(true); //Update icons to match slotUpdateCaptionIcons(m_activeView); if (m_activeView->sendInProgress() && animIcon) { anim->setMovie(animIcon); animIcon->setPaused(false); } else { anim->setPixmap(normalIcon); if (animIcon) { animIcon->setPaused(true); } } if (m_alwaysShowTabs || chatViewList.count() > 1) { if (!m_tabBar) { createTabBar(); } m_tabBar->setCurrentWidget(m_activeView); } setCaption(m_activeView->caption()); setStatus(m_activeView->statusText()); m_activeView->setFocus(); updateActions(); slotUpdateSendEnabled(); m_activeView->loadChatSettings(); updateSendKeySequence(); emit chatSessionChanged(m_activeView->msgManager()); } void KopeteChatWindow::slotUpdateCaptionIcons(ChatView *view) { if (!view) { return; //(pas de charité) } QList chatMembers = view->msgManager()->members(); Kopete::Contact *c = nullptr; foreach (Kopete::Contact *contact, chatMembers) { if (!c || c->onlineStatus() < contact->onlineStatus()) { c = contact; } } if (view == m_activeView) { setWindowIcon(c ? view->msgManager()->contactOnlineStatus(c).iconFor(c) : QIcon::fromTheme(view->msgManager()->protocol()->pluginIcon())); } if (m_tabBar) { m_tabBar->setTabIcon(m_tabBar->indexOf(view), c ? view->msgManager()->contactOnlineStatus(c).iconFor(c) : QIcon::fromTheme(view->msgManager()->protocol()->pluginIcon())); } } void KopeteChatWindow::slotChatClosed() { if (m_popupView) { m_popupView->closeView(); } else { m_activeView->closeView(); } } void KopeteChatWindow::slotPrepareDetachMenu(void) { QMenu *detachMenu = actionDetachMenu->menu(); detachMenu->clear(); QAction *action; for (int id = 0; id < windows.count(); id++) { KopeteChatWindow *win = windows.at(id); if (win != this) { action = detachMenu->addAction(win->windowIcon(), win->windowTitle()); action->setData(id); } } } void KopeteChatWindow::slotSendMessage() { if (m_activeView && m_activeView->canSend()) { if (animIcon) { anim->setMovie(animIcon); animIcon->setPaused(false); } m_activeView->sendMessage(); } } void KopeteChatWindow::slotSendFile() { if (m_activeView) { m_activeView->sendFile(); } } void KopeteChatWindow::slotPrepareContactMenu(void) { QMenu *contactsMenu = actionContactMenu->menu(); contactsMenu->clear(); Kopete::ContactPtrList m_them; if (m_popupView) { m_them = m_popupView->msgManager()->members(); } else { m_them = m_activeView->msgManager()->members(); } //TODO: don't display a menu with one contact in it, display that // contact's menu instead. Will require changing text and icon of // 'Contacts' action, or something cleverer. uint contactCount = 0; foreach (Kopete::Contact *contact, m_them) { QMenu *p = contact->popupMenu(); connect(actionContactMenu->menu(), SIGNAL(aboutToHide()), p, SLOT(deleteLater())); p->setIcon(contact->onlineStatus().iconFor(contact)); if (contact->metaContact()) { p->setTitle(contact->metaContact()->displayName()); } else { p->setTitle(contact->contactId()); } contactsMenu->addMenu(p); //FIXME: This number should be a config option if (++contactCount == 15 && contact != m_them.last()) { KActionMenu *moreMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("folder-open")), i18n("More..."), this); connect(actionContactMenu->menu(), SIGNAL(aboutToHide()), moreMenu, SLOT(deleteLater())); contactsMenu->addAction(moreMenu); contactsMenu = moreMenu->menu(); contactCount = 0; } } } void KopeteChatWindow::slotPreparePlacementMenu() { QMenu *placementMenu = actionTabPlacementMenu->menu(); placementMenu->clear(); QAction *action; action = placementMenu->addAction(i18n("Top")); action->setData(0); action = placementMenu->addAction(i18n("Bottom")); action->setData(1); action = placementMenu->addAction(i18n("Left")); action->setData(2); action = placementMenu->addAction(i18n("Right")); action->setData(3); } void KopeteChatWindow::slotPlaceTabs(QAction *action) { int placement = action->data().toInt(); if (m_tabBar) { switch (placement) { case 1: m_tabBar->setTabPosition(QTabWidget::South); break; case 2: m_tabBar->setTabPosition(QTabWidget::West); break; case 3: m_tabBar->setTabPosition(QTabWidget::East); break; default: m_tabBar->setTabPosition(QTabWidget::North); } saveOptions(); } } void KopeteChatWindow::readOptions() { // load and apply config file settings affecting the appearance of the UI // kDebug(14010) ; applyMainWindowSettings(KSharedConfig::openConfig()->group((initialForm == Kopete::ChatSession::Chatroom ? QStringLiteral("KopeteChatWindowGroupMode") : QStringLiteral("KopeteChatWindowIndividualMode")))); //config->setGroup( QLatin1String("ChatWindowSettings") ); } void KopeteChatWindow::saveOptions() { // kDebug(14010) ; KConfigGroup kopeteChatWindowMainWinSettings(KSharedConfig::openConfig(), (initialForm == Kopete::ChatSession::Chatroom ? QStringLiteral("KopeteChatWindowGroupMode") : QStringLiteral( "KopeteChatWindowIndividualMode"))); // saves menubar,toolbar and statusbar setting saveMainWindowSettings(kopeteChatWindowMainWinSettings); if (m_tabBar) { KConfigGroup chatWindowSettings(KSharedConfig::openConfig(), QStringLiteral("ChatWindowSettings")); chatWindowSettings.writeEntry(QStringLiteral("Tab Placement"), (int)m_tabBar->tabPosition()); chatWindowSettings.sync(); } kopeteChatWindowMainWinSettings.sync(); } void KopeteChatWindow::slotChatSave() { // kDebug(14010) << "KopeteChatWindow::slotChatSave()"; if (isActiveWindow() && m_activeView) { m_activeView->messagePart()->save(); } } void KopeteChatWindow::changeEvent(QEvent *e) { if (e->type() == QEvent::ActivationChange && isActiveWindow() && m_activeView) { m_activeView->setActive(true); } } void KopeteChatWindow::slotChatPrint() { m_activeView->messagePart()->print(); } void KopeteChatWindow::slotSmileyActivated(const QString &sm) { if (!sm.isNull()) { m_activeView->addText(' ' + sm + ' '); } //we are adding space around the emoticon becasue our parser only display emoticons not in a word. } void KopeteChatWindow::slotAutoSpellCheckEnabled(ChatView *view, bool isEnabled) { if (view != m_activeView) { return; } toggleAutoSpellCheck->setChecked(isEnabled); m_activeView->editPart()->setCheckSpellingEnabled(isEnabled); } bool KopeteChatWindow::queryClose() { #ifdef CHRONO QTime chrono; chrono.start(); #endif bool canClose = true; // kDebug( 14010 ) << " Windows left open:"; // for( QPtrListIterator it( chatViewList ); it; ++it) // kDebug( 14010 ) << " " << *it << " (" << (*it)->caption() << ")"; setUpdatesEnabled(false);//hide the crazyness from users while (!chatViewList.isEmpty()) { ChatView *view = chatViewList.takeFirst(); // FIXME: This should only check if it *can* close // and not start closing if the close can be aborted halfway, it would // leave us with half the chats open and half of them closed. - Martijn // if the view is closed, it is removed from chatViewList for us if (!view->closeView()) { qDebug() << "Closing view failed!"; canClose = false; } } setUpdatesEnabled(true); #ifdef CHRONO qDebug()<<"TIME: "<(qApp); if (app->isSavingSession() || app->isShuttingDown() /* only set if KopeteApplication::quitKopete() or KopeteApplication::commitData() called */ || !Kopete::BehaviorSettings::self()->showSystemTray() /* also close if our tray icon is hidden! */ || isHidden()) { Kopete::PluginManager::self()->shutdown(); return true; } else { return false; } } void KopeteChatWindow::closeEvent(QCloseEvent *e) { // if there's a system tray applet and we are not shutting down then just do what needs to be done if a // window is closed. KopeteApplication *app = static_cast(qApp); if (Kopete::BehaviorSettings::self()->showSystemTray() && !app->isShuttingDown() && !app->isSavingSession()) { // hide(); // BEGIN of code borrowed from KMainWindow::closeEvent // Save settings if auto-save is enabled, and settings have changed if (settingsDirty() && autoSaveSettings()) { saveAutoSaveSettings(); } if (queryClose()) { e->accept(); } else { e->ignore(); } // END of code borrowed from KMainWindow::closeEvent } else { KXmlGuiWindow::closeEvent(e); } } void KopeteChatWindow::updateChatState(ChatView *cv, int newState) { Q_UNUSED(cv); if (m_tabBar) { KColorScheme scheme(QPalette::Active, KColorScheme::Window); switch (newState) { case ChatView::Highlighted: m_tabBar->setTabTextColor(m_tabBar->indexOf(cv), scheme.foreground(KColorScheme::LinkText).color()); break; case ChatView::Message: m_tabBar->setTabTextColor(m_tabBar->indexOf(cv), scheme.foreground(KColorScheme::ActiveText).color()); break; case ChatView::Changed: m_tabBar->setTabTextColor(m_tabBar->indexOf(cv), scheme.foreground(KColorScheme::NeutralText).color()); break; case ChatView::Typing: m_tabBar->setTabTextColor(m_tabBar->indexOf(cv), scheme.foreground(KColorScheme::PositiveText).color()); break; case ChatView::Normal: default: m_tabBar->setTabTextColor(m_tabBar->indexOf(cv), scheme.foreground(KColorScheme::NormalText).color()); break; } } } void KopeteChatWindow::updateChatTooltip(ChatView *cv) { if (m_tabBar) { m_tabBar->setTabToolTip(m_tabBar->indexOf(cv), QStringLiteral("%1").arg(cv->caption())); } } void KopeteChatWindow::updateChatLabel() { ChatView *chat = dynamic_cast(sender()); if (!chat || !m_tabBar) { return; } if (m_tabBar) { m_tabBar->setTabText(m_tabBar->indexOf(chat), chat->caption()); if (m_tabBar->count() < 2 || m_tabBar->currentWidget() == chat) { setCaption(chat->caption()); } } } void KopeteChatWindow::resizeEvent(QResizeEvent *e) { KXmlGuiWindow::resizeEvent(e); if (m_activeView && m_activeView->messagePart()) { m_activeView->messagePart()->keepScrolledDown(); } } bool KopeteChatWindow::eventFilter(QObject *obj, QEvent *event) { if (m_activeView && obj == m_activeView->editWidget() && event->type() == QEvent::KeyPress) { KShortcut *eventFilterShortcut = new KShortcut(nickComplete->shortcut()); QKeyEvent *keyEvent = static_cast(event); if (eventFilterShortcut->primary() == QKeySequence(keyEvent->key())) { m_activeView->nickComplete(); return true; } } return KXmlGuiWindow::eventFilter(obj, event); } // vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopeteapplication.cpp b/kopete/kopeteapplication.cpp index a7b71c697..b2b15eec6 100644 --- a/kopete/kopeteapplication.cpp +++ b/kopete/kopeteapplication.cpp @@ -1,385 +1,390 @@ /* kopete.cpp Kopete Instant Messenger Main Class Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett Copyright (c) 2002-2003 by Martijn Klingens Kopete (c) 2001-2003 by the Kopete developers ************************************************************************* * * * 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 "kopeteapplication.h" #include #include -#include #include #include #include #include #include #include #include "addaccountwizard.h" #include "kabcpersistence.h" #include "kopeteaccount.h" #include "kopeteaccountmanager.h" #include "kopetestatusmanager.h" #include "kopetestatusitems.h" #include "kopetebehaviorsettings.h" #include "kopetecommandhandler.h" #include "kopetecontactlist.h" #include "kopeteglobal.h" //#include "kopetefileengine.h" #include "kopetemimetypehandler.h" #include "kopetepluginmanager.h" #include "kopeteprotocol.h" #include "kopetestdaction.h" #include "kopeteuiglobal.h" #include "kopetewindow.h" #include "kopeteviewmanager.h" #include "kopeteidentitymanager.h" #include "kopetedbusinterface.h" KopeteApplication::KopeteApplication(int &argc, char *argv[]) : QApplication(argc, argv) { m_isShuttingDown = false; //Create the identity manager Kopete::IdentityManager::self()->load(); m_mainWindow = new KopeteWindow(0); Kopete::PluginManager::self(); Kopete::UI::Global::setMainWidget(m_mainWindow); /* * FIXME: This is a workaround for a quite odd problem: * When starting up kopete and the msn plugin gets loaded it can bring up * a messagebox, in case the msg configuration is missing. This messagebox * will result in a QApplication::enter_loop() call, an event loop is * created. At this point however the loop_level is 0, because this is all * still inside the KopeteApplication constructor, before the exec() call from main. * When the messagebox is finished the loop_level will drop down to zero and * QApplication thinks the application shuts down (this is usually the case * when the loop_level goes down to zero) . So it emits aboutToQuit(), to * which KApplication is connected and re-emits shutdown() , to which again * KXmlGuiWindow (a KopeteWindow instance exists already) is connected. KXmlGuiWindow's * shuttingDown() slot calls queryExit() which results in KopeteWindow::queryExit() * calling unloadPlugins() . This of course is wrong and just shouldn't happen. * The workaround is to simply delay the initialization of all this to a point * where the loop_level is already > 0 . That is why I moved all the code from * the constructor to the initialize() method and added this single-shot-timer * setup. (Simon) * * Additionally, it makes the GUI appear less 'blocking' during startup, so * there is a secondary benefit as well here. (Martijn) */ QTimer::singleShot(0, this, SLOT(slotLoadPlugins())); //NOTE : QAbstractFileEngine and QAbstractFileEngineHandler deprecated in Qt5 //m_fileEngineHandler = new Kopete::FileEngineHandler(); //Create the emoticon installer m_emoticonHandler = new Kopete::EmoticonMimeTypeHandler; //Create the DBus interface for org.kde.kopete new KopeteDBusInterface(this); } KopeteApplication::~KopeteApplication() { kDebug(14000); if (!m_isShuttingDown) { // destruct was called without proper shutdown, dbus quit maybe? m_isShuttingDown = true; // close all windows QList members = KMainWindow::memberList(); QList::iterator it, itEnd = members.end(); for (it = members.begin(); it != itEnd; ++it) { (*it)->close(); } - // shutdown plugin manager - Kopete::PluginManager::self()->shutdown(); - // destroy all plugins until KopeteApplication is alive Kopete::PluginList list = Kopete::PluginManager::self()->loadedPlugins(); foreach (Kopete::Plugin *plugin, list) { delete plugin; } + + // shutdown plugin manager + Kopete::PluginManager::self()->shutdown(); } //delete m_fileEngineHandler; delete m_emoticonHandler; //kDebug( 14000 ) << "Done"; } void KopeteApplication::slotLoadPlugins() { // we have to load the address book early, because calling this enters the Qt event loop when there are remote resources. // The plugin manager is written with the assumption that Kopete will not reenter the event loop during plugin load, // otherwise lots of things break as plugins are loaded, then contacts are added to incompletely initialised MCLVIs //Kopete::KABCPersistence::self()->addressbook(); //Create the command handler (looks silly) Kopete::CommandHandler::commandHandler(); //Create the view manager KopeteViewManager::viewManager(); // the account manager should be created after the identity manager is created Kopete::AccountManager::self()->load(); Kopete::ContactList::self()->load(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); // Parse command-line arguments //FIXME KF5 KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); bool showConfigDialog = false; KConfigGroup pluginsGroup = config->group("Plugins"); /* FIXME: This is crap, if something purged that groups but your accounts * are still working kopete will load the necessary plugins but still show the * stupid accounts dialog (of course empty at that time because account data * gets loaded later on). [mETz - 29.05.2004] */ if (!pluginsGroup.exists()) { showConfigDialog = true; } // Listen to arguments /* // TODO: conflicts with emoticon installer and the general meaning // of %U in kopete.desktop if ( args->count() > 0 ) { showConfigDialog = false; for ( int i = 0; i < args->count(); i++ ) Kopete::PluginManager::self()->setPluginEnabled( args->arg( i ), true ); } */ // Prevent plugins from loading? (--disable=foo,bar) #if 0//FIXME KF5 foreach (const QString &disableArg, args->getOption("disable").split(',')) { showConfigDialog = false; Kopete::PluginManager::self()->setPluginEnabled(disableArg, false); } // Load some plugins exclusively? (--load-plugins=foo,bar) if (args->isSet("load-plugins")) { pluginsGroup.deleteGroup(KConfigBase::Global); showConfigDialog = false; foreach (const QString &plugin, args->getOption("load-plugins").split(',')) { Kopete::PluginManager::self()->setPluginEnabled(plugin, true); } } #endif config->sync(); // Disable plugins altogether? (--noplugins) - if (/*!args->isSet("plugins")*/1) { //KF5 FIXME + if (/*!args->isSet("plugins")*/0) { //KF5 FIXME // If anybody reenables this I'll get a sword and make a nice chop-suy out // of your body :P [mETz - 29.05.2004] // This screws up kopeterc because there is no way to get the Plugins group back! //config->deleteGroup( "Plugins", true ); showConfigDialog = false; // pretend all plugins were loaded :) QTimer::singleShot(0, this, SLOT(slotAllPluginsLoaded())); } else { Kopete::PluginManager::self()->loadAllPlugins(); } connect(Kopete::PluginManager::self(), SIGNAL(allPluginsLoaded()), this, SLOT(slotAllPluginsLoaded())); if (showConfigDialog) { // No plugins specified. Show the config dialog. // FIXME: Although it's a bit stupid it is theoretically possible that a user // explicitly configured Kopete to not load plugins on startup. In this // case we don't want this dialog. We need some other config setting // like a bool hasRunKopeteBefore or so to trigger the loading of the // wizard. Maybe using the last run version number is more useful even // as it also allows for other features. - Martijn // FIXME: Possibly we need to influence the showConfigDialog bool based on the // command line arguments processed below. But how exactly? - Martijn // NB: the command line args are completely broken atm. // I don't want to fix them for 3.5 as plugin loading will change for KDE4. - Will AddAccountWizard *m_addwizard = new AddAccountWizard(Kopete::UI::Global::mainWidget(), true); m_addwizard->exec(); Kopete::AccountManager::self()->save(); } } void KopeteApplication::slotAllPluginsLoaded() { //FIXME KF5 KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); //FIXME: this should probably ask for the identities to connect instead of all accounts // --noconnect not specified? Kopete::OnlineStatusManager::Category initStatus = Kopete::OnlineStatusManager::self()->initialStatus(); Kopete::OnlineStatusManager::Category setStatus = Kopete::OnlineStatusManager::Offline; #if 0 //FIXME if (args->isSet("connect") && initStatus != Kopete::OnlineStatusManager::Offline && (Solid::Networking::status() == Solid::Networking::Unknown || Solid::Networking::status() == Solid::Networking::Connected)) { setStatus = initStatus; } #endif QList statusList = Kopete::StatusManager::self()->getRootGroup()->childList(); QString message, title; bool found = false; //find first Status for OnlineStatus for (QList ::ConstIterator it = statusList.constBegin(); it != statusList.constEnd(); ++it) { if (!(*it)->isGroup() && (*it)->category() == setStatus) { title = (*it)->title(); message = (static_cast (*it))->message(); //if it is not group, it status found = true; break; } } if (found) { if (setStatus != Kopete::OnlineStatusManager::Offline) { Kopete::AccountManager::self()->setOnlineStatus(initStatus, Kopete::StatusMessage(title, message), Kopete::AccountManager::ConnectIfOffline); } Kopete::StatusManager::self()->setGlobalStatus(setStatus, Kopete::StatusMessage(title, message)); } else { if (setStatus != Kopete::OnlineStatusManager::Offline) { Kopete::AccountManager::self()->setOnlineStatus(initStatus, QString(), Kopete::AccountManager::ConnectIfOffline); } } kDebug(14000)<< "initial status set in config: " << initStatus; //FIXME KF5 #if 0 QStringList connectArgs = args->getOptionList("autoconnect"); // toConnect will contain all the protocols to connect to QStringList toConnect; for (QStringList::ConstIterator i = connectArgs.constBegin(); i != connectArgs.constEnd(); ++i) { foreach (const QString &connectArg, (*i).split(',')) { toConnect.append(connectArg); } } for (QStringList::ConstIterator i = toConnect.constBegin(); i != toConnect.constEnd(); ++i) { QRegExp rx(QLatin1String("([^\\|]*)\\|\\|(.*)")); rx.indexIn(*i); QString protocolId = rx.cap(1); QString accountId = rx.cap(2); if (accountId.isEmpty()) { if (protocolId.isEmpty()) { accountId = *i; } else { continue; } } QListIterator it(Kopete::AccountManager::self()->accounts()); Kopete::Account *account; while (it.hasNext()) { account = it.next(); if ((account->accountId() == accountId)) { if (protocolId.isEmpty() || account->protocol()->pluginId() == protocolId) { account->connect(); break; } } } } // Parse any passed URLs/files handleURLArgs(); #endif } #if 0 int KopeteApplication::newInstance() { // kDebug(14000) ; handleURLArgs(); return KUniqueApplication::newInstance(); } #endif void KopeteApplication::handleURLArgs() { #if 0 KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); // kDebug(14000) << "called with " << args->count() << " arguments to handle."; if (args->count() > 0) { for (int i = 0; i < args->count(); i++) { QUrl u(args->url(i)); if (!u.isValid()) { continue; } Kopete::MimeTypeHandler::dispatchURL(u); } // END for() } // END args->count() > 0 #endif } void KopeteApplication::quitKopete() { kDebug(14000); m_isShuttingDown = true; // close all windows QList members = KMainWindow::memberList(); QList::iterator it, itEnd = members.end(); for (it = members.begin(); it != itEnd; ++it) { if (!(*it)->close()) { m_isShuttingDown = false; return; } } + // destroy all plugins until KopeteApplication is alive + const Kopete::PluginList &list = Kopete::PluginManager::self()->loadedPlugins(); + foreach (Kopete::Plugin *plugin, list) { + delete plugin; + } + // shutdown plugin manager Kopete::PluginManager::self()->shutdown(); if (m_mainWindow) { m_mainWindow->deleteLater(); m_mainWindow = 0; } } void KopeteApplication::commitData(QSessionManager &sm) { m_isShuttingDown = true; - //FIXME KUniqueApplication::commitData(sm); + QGuiApplication::saveStateRequest(sm); } // vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/kopetewindow.cpp b/kopete/kopetewindow.cpp index da2e6bc3a..4bce39632 100644 --- a/kopete/kopetewindow.cpp +++ b/kopete/kopetewindow.cpp @@ -1,1526 +1,1528 @@ /* kopetewindow.cpp - Kopete Main Window Copyright (c) 2001-2002 by Duncan Mac-Vicar Prett Copyright (c) 2001-2002 by Stefan Gehn Copyright (c) 2002-2003 by Martijn Klingens Copyright (c) 2002-2009 by Olivier Goffart Copyright (c) 2005-2006 by Will Stephenson Copyright (c) 2008 by Roman Jarosz Kopete (c) 2002-2008 by the Kopete developers ************************************************************************* * * * 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 "kopetewindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "addcontactpage.h" #include "addressbooklinkwidget.h" #include "ui_groupkabcselectorwidget.h" #include "kabcexport.h" #include "kopeteappearancesettings.h" #include "kopeteapplication.h" #include "kopeteaccount.h" #include "kopeteaccountmanager.h" #include "kopeteaccountstatusbaricon.h" #include "kopeteidentitystatusbaricon.h" #include "kopetebehaviorsettings.h" #include "kopetecontact.h" #include "kopetecontactlist.h" #include "kopetegroup.h" #include "kopeteidentity.h" #include "kopeteidentitymanager.h" #include "kopetelistviewsearchline.h" #include "kopetechatsessionmanager.h" #include "kopetepluginmanager.h" #include "kopeteprotocol.h" #include "kopetestdaction.h" #include "kopeteuiglobal.h" #include "systemtray.h" #include "kopeteonlinestatusmanager.h" #include "identitystatuswidget.h" #include "kopetestatusmanager.h" #include "kopetestatusrootaction.h" #include "kopetestatuseditaction.h" #include "kopeteemoticons.h" #include "kopeteinfoeventmanager.h" #include "infoeventwidget.h" #include "contactlisttreemodel.h" #include "contactlistplainmodel.h" #include "contactlistproxymodel.h" #include "kopeteitemdelegate.h" #include "kopetemetacontact.h" #include "kopetecontactlistview.h" #include "kopetestatusitems.h" //BEGIN GlobalStatusMessageIconLabel GlobalStatusMessageIconLabel::GlobalStatusMessageIconLabel (QWidget *parent) : QLabel(parent) { setCursor(QCursor(Qt::PointingHandCursor)); setFixedSize(16, 16); setPixmap(SmallIcon(QStringLiteral("im-status-message-edit"))); setToolTip(i18n("Global status message")); } void GlobalStatusMessageIconLabel::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { emit iconClicked(event->globalPos()); event->accept(); } } //END GlobalStatusMessageIconLabel //BEGIN InfoEventIconLabel InfoEventIconLabel::InfoEventIconLabel(QWidget *parent) : QLabel(parent) { setCursor(QCursor(Qt::PointingHandCursor)); setFixedSize(16, 16); setPixmap(SmallIcon(QStringLiteral("flag-black"))); setToolTip(i18n("Service messages")); connect(Kopete::InfoEventManager::self(), SIGNAL(changed()), this, SLOT(updateIcon())); } void InfoEventIconLabel::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) { emit clicked(); event->accept(); } } void InfoEventIconLabel::updateIcon() { if (Kopete::InfoEventManager::self()->eventCount() > 0) { setPixmap(SmallIcon(QStringLiteral("flag-green"))); } else { setPixmap(SmallIcon(QStringLiteral("flag-black"))); emit clicked(); } } //END InfoEventIconLabel class KopeteWindow::Private { public: Private() : contactlist(0) , model(0) , proxyModel(0) , identitywidget(0) , infoEventWidget(0) , actionAddContact(0) , actionDisconnect(0) , actionExportContacts(0) , actionStatusMenu(0) , actionDockMenu(0) , actionSetAway(0) , actionSetBusy(0) , actionSetAvailable(0) , actionSetInvisible(0) , actionPrefs(0) , actionQuit(0) , actionSave(0) , menubarAction(0) , statusbarAction(0) , actionShowOfflineUsers(0) , actionShowEmptyGroups(0) , docked(0) , deskRight(0) , statusBarWidget(0) , tray(0) , hidden(false) , autoHide(false) , autoHideTimeout(0) , autoHideTimer(0) , addContactMapper(0) , showIdentityIcons(Kopete::AppearanceSettings::self()->showIdentityIcons()) , globalStatusMessage(0) { } ~Private() { } KopeteContactListView *contactlist; Kopete::UI::ContactListModel *model; Kopete::UI::ContactListProxyModel *proxyModel; IdentityStatusWidget *identitywidget; InfoEventWidget *infoEventWidget; // Some Actions KActionMenu *actionAddContact; QAction *actionDisconnect; QAction *actionExportContacts; KActionMenu *actionStatusMenu; KActionMenu *actionDockMenu; QAction *actionSetAway; QAction *actionSetBusy; QAction *actionSetAvailable; QAction *actionSetInvisible; QAction *actionPrefs; QAction *actionQuit; QAction *actionSave; KToggleAction *menubarAction; KToggleAction *statusbarAction; KToggleAction *actionShowAllOfflineEmpty; KToggleAction *actionShowOfflineUsers; KToggleAction *actionShowEmptyGroups; int docked; int deskRight; QPoint position; KHBox *statusBarWidget; KopeteSystemTray *tray; bool appDestroyed; bool hidden; bool autoHide; unsigned int autoHideTimeout; QTimer *autoHideTimer; QTimer *autoResizeTimer; QSignalMapper *addContactMapper; bool showIdentityIcons; QHash identityStatusBarIcons; QHash accountStatusBarIcons; KSqueezedTextLabel *globalStatusMessage; }; /* KMainWindow is very broken from our point of view - it deref()'s the app * when the last visible KMainWindow is destroyed. But when our main window is * hidden when it's in the tray,closing the last chatwindow would cause the app * to quit. - Richard * * Fortunately KMainWindow checks queryExit before deref()ing the Kapplication. * KopeteWindow reimplements queryExit() and only returns true if it is shutting down * (either because the user quit Kopete, or the session manager did). * * KopeteWindow and ChatWindows are closed by session management. * App shutdown is not performed by the KopeteWindow but by KopeteApplication: * 1) user quit - KopeteWindow::slotQuit() was called, calls KopeteApplication::quitKopete(), * which closes all chatwindows and the KopeteWindow. The last window to close * shuts down the PluginManager in queryExit(). When the PluginManager has completed its * shutdown, the app is finally deref()ed, and the contact list and accountmanager * are saved. * and calling KApplication::quit() * 2) session - KopeteWindow and all chatwindows are closed by KApplication session management. * quit Then the shutdown proceeds as above. * * queryClose() is honoured so groupchats and chats receiving recent messages can interrupt * (session) quit. */ KopeteWindow::KopeteWindow (QWidget *parent) : KXmlGuiWindow(parent) , d(new Private) { d->appDestroyed = false; connect(qApp, SIGNAL(destroyed()), this, SLOT(slotAppDestroyed())); setAttribute(Qt::WA_DeleteOnClose, false); setAttribute(Qt::WA_QuitOnClose, false); // Applications should ensure that their StatusBar exists before calling createGUI() // so that the StatusBar is always correctly positioned when KDE is configured to use // a MacOS-style MenuBar. // This fixes a "statusbar drawn over the top of the toolbar" bug // e.g. it can happen when you switch desktops on Kopete startup d->statusBarWidget = new KHBox(statusBar()); d->statusBarWidget->setMargin(2); d->statusBarWidget->setSpacing(1); window()->setAttribute(Qt::WA_AlwaysShowToolTips); statusBar()->addPermanentWidget(d->statusBarWidget, 0); QWidget *statusBarMessage = new QWidget(statusBar()); QHBoxLayout *statusBarMessageLayout = new QHBoxLayout(statusBarMessage); statusBarMessageLayout->setMargin(2); KStatusBarOfflineIndicator *indicator = new KStatusBarOfflineIndicator(this); statusBar()->addPermanentWidget(indicator, 0); GlobalStatusMessageIconLabel *label = new GlobalStatusMessageIconLabel(statusBarMessage); connect(label, SIGNAL(iconClicked(QPoint)), this, SLOT(slotGlobalStatusMessageIconClicked(QPoint))); statusBarMessageLayout->addWidget(label); statusBarMessageLayout->addSpacing(1); InfoEventIconLabel *infoLabel = new InfoEventIconLabel(statusBarMessage); connect(infoLabel, SIGNAL(clicked()), this, SLOT(slotInfoIconClicked())); statusBarMessageLayout->addWidget(infoLabel); statusBarMessageLayout->addSpacing(1); connect(Kopete::InfoEventManager::self(), SIGNAL(eventAdded(Kopete::InfoEvent *)), this, SLOT(slotNewInfoEvent())); d->globalStatusMessage = new KSqueezedTextLabel(statusBarMessage); connect(Kopete::StatusManager::self(), SIGNAL(globalStatusChanged()), this, SLOT(globalStatusChanged())); statusBarMessageLayout->addWidget(d->globalStatusMessage); statusBar()->addWidget(statusBarMessage, 1); d->autoHideTimer = new QTimer(this); d->autoResizeTimer = new QTimer(this); d->autoResizeTimer->setSingleShot(true); // -------------------------------------------------------------------------------- initView(); initActions(); d->contactlist->initActions(actionCollection()); initSystray(); // -------------------------------------------------------------------------------- // Trap all loaded plugins, so we can add their status bar icons accordingly , also used to add XMLGUIClient connect(Kopete::PluginManager::self(), SIGNAL(pluginLoaded(Kopete::Plugin *)), this, SLOT(slotPluginLoaded(Kopete::Plugin *))); connect(Kopete::PluginManager::self(), SIGNAL(allPluginsLoaded()), this, SLOT(slotAllPluginsLoaded())); // Connect all identity signals connect(Kopete::IdentityManager::self(), SIGNAL(identityRegistered(Kopete::Identity *)), this, SLOT(slotIdentityRegistered(Kopete::Identity *))); connect(Kopete::IdentityManager::self(), SIGNAL(identityUnregistered(const Kopete::Identity *)), this, SLOT(slotIdentityUnregistered(const Kopete::Identity *))); connect(d->autoHideTimer, SIGNAL(timeout()), this, SLOT(slotAutoHide())); connect(d->contactlist, SIGNAL(visibleContentHeightChanged()), this, SLOT(slotStartAutoResizeTimer())); connect(d->autoResizeTimer, SIGNAL(timeout()), this, SLOT(slotUpdateSize())); connect(Kopete::AppearanceSettings::self(), SIGNAL(contactListAppearanceChanged()), this, SLOT(slotContactListAppearanceChanged())); createGUI(QStringLiteral("kopeteui.rc")); // call this _after_ createGUI(), otherwise menubar is not set up correctly loadOptions(); // If some plugins are already loaded, merge the GUI Kopete::PluginList plugins = Kopete::PluginManager::self()->loadedPlugins(); foreach (Kopete::Plugin *plug, plugins) { slotPluginLoaded(plug); } // If some identity already registered, build the status icon Kopete::Identity::List identityList = Kopete::IdentityManager::self()->identities(); foreach (Kopete::Identity *i, identityList) { slotIdentityRegistered(i); } //install an event filter for the quick search toolbar so we can //catch the hide events toolBar(QStringLiteral("quickSearchBar"))->installEventFilter(this); } void KopeteWindow::slotAppDestroyed() { d->appDestroyed = true; } void KopeteWindow::initView() { QWidget *w = new QWidget(this); QVBoxLayout *l = new QVBoxLayout(w); d->contactlist = new KopeteContactListView(w); if (Kopete::AppearanceSettings::self()->groupContactByGroup()) { d->model = new Kopete::UI::ContactListTreeModel(this); } else { d->model = new Kopete::UI::ContactListPlainModel(this); } d->model->init(); d->proxyModel = new Kopete::UI::ContactListProxyModel(this); d->proxyModel->setSourceModel(d->model); d->contactlist->setModel(d->proxyModel); l->addWidget(d->contactlist); l->setSpacing(0); l->setContentsMargins(0, 0, 0, 0); d->identitywidget = new IdentityStatusWidget(0, w); d->identitywidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum)); d->identitywidget->setVisible(false); l->addWidget(d->identitywidget); d->infoEventWidget = new InfoEventWidget(w); d->infoEventWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum)); d->infoEventWidget->setVisible(false); connect(d->infoEventWidget, SIGNAL(showRequest()), this, SLOT(slotShowInfoEventWidget())); l->addWidget(d->infoEventWidget); setCentralWidget(w); d->contactlist->setFocus(); } void KopeteWindow::initActions() { // this action menu contains one action per account and is updated when accounts are registered/unregistered d->actionAddContact = new KActionMenu(QIcon::fromTheme(QStringLiteral("list-add-user")), i18n("&Add Contact"), this); d->actionAddContact->setIconText(i18n("Add")); actionCollection()->addAction(QStringLiteral("AddContact"), d->actionAddContact); d->actionAddContact->setDelayed(false); // this signal mapper is needed to call slotAddContact with the correct arguments d->addContactMapper = new QSignalMapper(this); connect(d->addContactMapper, SIGNAL(mapped(QString)), this, SLOT(slotAddContactDialogInternal(QString))); d->actionDisconnect = new QAction(QIcon::fromTheme(QStringLiteral("user-offline")), QString(i18n("Offline")), this); actionCollection()->addAction(QStringLiteral("DisconnectAll"), d->actionDisconnect); connect(d->actionDisconnect, SIGNAL(triggered(bool)), this, SLOT(slotDisconnectAll())); d->actionDisconnect->setEnabled(false); d->actionExportContacts = new QAction(i18n("&Export Contacts..."), this); d->actionExportContacts->setIcon(QIcon("document-export")); actionCollection()->addAction(QStringLiteral("ExportContacts"), d->actionExportContacts); connect(d->actionExportContacts, SIGNAL(triggered(bool)), this, SLOT(showExportDialog())); - d->actionSetAway = new KAction(KIcon(QStringLiteral("user-identity"), 0, QStringList() << QString() << QStringLiteral("user-away")), i18n("&Away"), this); + d->actionSetAway = new QAction(QIcon::fromTheme(QStringLiteral("user-away")), i18n("&Away"), this); actionCollection()->addAction(QStringLiteral("SetAwayAll"), d->actionSetAway); connect(d->actionSetAway, SIGNAL(triggered(bool)), this, SLOT(slotGlobalAway())); - d->actionSetBusy = new KAction(KIcon(QStringLiteral("user-identity"), 0, QStringList() << QString() << QStringLiteral("user-busy")), i18n("&Busy"), this); + d->actionSetBusy = new QAction(QIcon::fromTheme(QStringLiteral("user-busy")), i18n("&Busy"), this); actionCollection()->addAction(QStringLiteral("SetBusyAll"), d->actionSetBusy); connect(d->actionSetBusy, SIGNAL(triggered(bool)), this, SLOT(slotGlobalBusy())); - d->actionSetInvisible = new KAction(KIcon(QStringLiteral("user-identity"), 0, QStringList() << QString() << QStringLiteral("user-invisible")), i18n("&Invisible"), this); + d->actionSetInvisible = new QAction(QIcon::fromTheme(QStringLiteral("user-invisible")), i18n("&Invisible"), this); actionCollection()->addAction(QStringLiteral("SetInvisibleAll"), d->actionSetInvisible); connect(d->actionSetInvisible, SIGNAL(triggered(bool)), this, SLOT(slotSetInvisibleAll())); - d->actionSetAvailable = new KAction(KIcon(QStringLiteral("user-identity"), 0, QStringList() << QString() << QStringLiteral("user-online")), i18n("&Online"), this); + d->actionSetAvailable = new QAction(QIcon::fromTheme(QStringLiteral("user-online")), i18n("&Online"), this); actionCollection()->addAction(QStringLiteral("SetAvailableAll"), d->actionSetAvailable); connect(d->actionSetAvailable, SIGNAL(triggered(bool)), this, SLOT(slotGlobalAvailable())); - d->actionStatusMenu = new KActionMenu(KIcon(QStringLiteral("user-identity"), 0, QStringList() << QString() << QStringLiteral("user-online")), i18n("&Set Status"), this); + d->actionStatusMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("user-online")), i18n("&Set Status"), this); d->actionStatusMenu->setIconText(i18n("Status")); actionCollection()->addAction(QStringLiteral("Status"), d->actionStatusMenu); d->actionStatusMenu->setDelayed(false); // Will be automatically deleted when the actionStatusMenu is deleted. Kopete::StatusRootAction *statusAction = new Kopete::StatusRootAction(d->actionStatusMenu); connect(statusAction, SIGNAL(changeStatus(uint,Kopete::StatusMessage)), this, SLOT(setOnlineStatus(uint,Kopete::StatusMessage))); connect(statusAction, SIGNAL(updateMessage(Kopete::StatusRootAction *)), this, SLOT(updateStatusMenuMessage(Kopete::StatusRootAction *))); connect(statusAction, SIGNAL(changeMessage(Kopete::StatusMessage)), this, SLOT(setStatusMessage(Kopete::StatusMessage))); d->actionPrefs = KopeteStdAction::preferences(actionCollection(), "settings_prefs"); KStandardAction::quit(this, SLOT(slotQuit()), actionCollection()); setStandardToolBarMenuEnabled(true); d->menubarAction = KStandardAction::showMenubar(menuBar(), SLOT(setVisible(bool)), actionCollection()); actionCollection()->addAction(QStringLiteral("settings_showmenubar"), d->menubarAction); d->statusbarAction = KStandardAction::showStatusbar(statusBar(), SLOT(setVisible(bool)), actionCollection()); actionCollection()->addAction(QStringLiteral("settings_showstatusbar"), d->statusbarAction); QAction *act = KStandardAction::keyBindings(guiFactory(), SLOT(configureShortcuts()), actionCollection()); actionCollection()->addAction(QStringLiteral("settings_keys"), act); QAction *configureGlobalShortcutsAction = new QAction(QIcon::fromTheme(QStringLiteral("configure-shortcuts")), i18n("Configure &Global Shortcuts..."), this); configureGlobalShortcutsAction->setMenuRole(QAction::NoRole); //OS X: prevent Qt heuristics to move action to app menu->"Preferences" actionCollection()->addAction(QStringLiteral("settings_global"), configureGlobalShortcutsAction); connect(configureGlobalShortcutsAction, SIGNAL(triggered(bool)), this, SLOT(slotConfGlobalKeys())); KStandardAction::configureToolbars(this, SLOT(slotConfToolbar()), actionCollection()); act = KStandardAction::configureNotifications(this, SLOT(slotConfNotifications()), actionCollection()); actionCollection()->addAction(QStringLiteral("settings_notifications"), act); d->actionShowAllOfflineEmpty = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-user-offline-kopete")), i18n("Show &All"), this); actionCollection()->addAction(QStringLiteral("settings_show_all_offline_empty"), d->actionShowAllOfflineEmpty); actionCollection()->setDefaultShortcut(d->actionShowAllOfflineEmpty, QKeySequence(Qt::CTRL + Qt::Key_U)); connect(d->actionShowAllOfflineEmpty, SIGNAL(triggered(bool)), this, SLOT(slotToggleShowAllOfflineEmpty(bool))); d->actionShowOfflineUsers = new KToggleAction(QIcon::fromTheme(QStringLiteral("view-user-offline-kopete")), i18n("Show Offline &Users"), this); actionCollection()->addAction(QStringLiteral("settings_show_offliners"), d->actionShowOfflineUsers); connect(d->actionShowOfflineUsers, SIGNAL(triggered(bool)), this, SLOT(slotToggleShowOfflineUsers())); d->actionShowEmptyGroups = new KToggleAction(QIcon::fromTheme(QStringLiteral("folder-grey")), i18n("Show Empty &Groups"), this); actionCollection()->addAction(QStringLiteral("settings_show_empty_groups"), d->actionShowEmptyGroups); actionCollection()->setDefaultShortcut(d->actionShowEmptyGroups, QKeySequence(Qt::CTRL + Qt::Key_G)); connect(d->actionShowEmptyGroups, SIGNAL(triggered(bool)), this, SLOT(slotToggleShowEmptyGroups())); /* The following are highly misleading together with the checkbox, consider removing them - ahartmetz d->actionShowAllOfflineEmpty->setCheckedState ( KGuiItem ( i18n ( "Hide O&ffline" ) ) ); d->actionShowOfflineUsers->setCheckedState ( KGuiItem ( i18n ( "Hide Offline &Users" ) ) ); d->actionShowEmptyGroups->setCheckedState ( KGuiItem ( i18n ( "Hide Empty &Groups" ) ) ); */ KFilterProxySearchLine *searchLine = new KFilterProxySearchLine(this); searchLine->setProxy(d->proxyModel); - KAction *quickSearch = new KAction(i18n("Quick Search Bar"), this); + QWidgetAction *quickSearch = new QWidgetAction(this); actionCollection()->addAction(QStringLiteral("quicksearch_bar"), quickSearch); quickSearch->setDefaultWidget(searchLine); // sync actions, config and prefs-dialog connect(Kopete::AppearanceSettings::self(), SIGNAL(configChanged()), this, SLOT(slotConfigChanged())); slotConfigChanged(); // Global actions - KAction *globalReadMessage = new KAction(i18n("Read Message"), this); + QAction *globalReadMessage = new QAction(i18n("Read Message"), this); actionCollection()->addAction(QStringLiteral("ReadMessage"), globalReadMessage); connect(globalReadMessage, SIGNAL(triggered(bool)), Kopete::ChatSessionManager::self(), SLOT(slotReadMessage())); - globalReadMessage->setGlobalShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I))); + actionCollection()->setDefaultShortcut(globalReadMessage, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_I)); globalReadMessage->setWhatsThis(i18n("Read the next pending message")); - KAction *globalShowContactList = new KAction(i18n("Show/Hide Contact List"), this); + QAction *globalShowContactList = new QAction(i18n("Show/Hide Contact List"), this); actionCollection()->addAction(QStringLiteral("ShowContactList"), globalShowContactList); connect(globalShowContactList, SIGNAL(triggered(bool)), this, SLOT(slotShowHide())); - globalShowContactList->setGlobalShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_T))); + actionCollection()->setDefaultShortcut(globalShowContactList, QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_T)); globalShowContactList->setWhatsThis(i18n("Show or hide the contact list")); QAction *globalSetAway = new QAction(i18n("Set Away/Back"), this); actionCollection()->addAction(QStringLiteral("Set_Away_Back"), globalSetAway); connect(globalSetAway, SIGNAL(triggered(bool)), this, SLOT(slotToggleAway())); - //FIXME KF5 globalSetAway->setGlobalShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W))); + actionCollection()->setDefaultShortcut(globalSetAway, QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W)); + globalSetAway->setWhatsThis(i18n("Set Away/Back")); } void KopeteWindow::slotShowHide() { if (isActiveWindow()) { d->autoHideTimer->stop(); //no timeouts if active hide(); } else { show(); #ifdef Q_WS_X11 if (!KWindowSystem::windowInfo(winId(), NET::WMDesktop).onAllDesktops()) { KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); } #endif raise(); KWindowSystem::forceActiveWindow(winId()); } } void KopeteWindow::slotToggleAway() { kDebug(14000); Kopete::StatusManager *statusManager = Kopete::StatusManager::self(); const Kopete::Status::StatusItem *item = 0; bool away = Kopete::StatusManager::self()->globalAway(); foreach (const Kopete::Status::StatusItem *i, statusManager->getRootGroup()->childList()) { if (i->title() == QLatin1String("Online") && away) { item = i; break; } else if (i->title() == QLatin1String("Away") && !away) { item = i; break; } } const Kopete::Status::Status *status = qobject_cast(item); if (status) { statusManager->setGlobalStatusMessage(Kopete::StatusMessage(status->title(), status->message())); } if (away) { slotGlobalAvailable(); } else { slotGlobalAway(); } } void KopeteWindow::initSystray() { if (Kopete::BehaviorSettings::self()->showSystemTray()) { d->tray = KopeteSystemTray::systemTray(this); QObject::connect(d->tray, SIGNAL(aboutToShowMenu(QMenu *)), this, SLOT(slotTrayAboutToShowMenu(QMenu *))); - // :FIXME: The signal quitSelected does not exist on KopeteSystemTray - // QObject::connect ( d->tray, SIGNAL (quitSelected()), this, SLOT (slotQuit()) ); + d->tray->setStandardActionsEnabled(true); + QObject::connect(d->tray, SIGNAL(quit()), this, SLOT(slotQuit())); } } KopeteWindow::~KopeteWindow() { delete d; } bool KopeteWindow::eventFilter(QObject *target, QEvent *event) { KToolBar *toolBar = dynamic_cast(target); QAction *resetAction = actionCollection()->action(QStringLiteral("quicksearch_reset")); if (toolBar && resetAction && resetAction->associatedWidgets().contains(toolBar)) { if (event->type() == QEvent::Hide) { resetAction->trigger(); return true; } return KXmlGuiWindow::eventFilter(target, event); } return KXmlGuiWindow::eventFilter(target, event); } void KopeteWindow::loadOptions() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); toolBar(QStringLiteral("mainToolBar"))->applySettings(config->group("ToolBar Settings")); toolBar(QStringLiteral("quickSearchBar"))->applySettings(config->group("QuickSearchBar Settings")); applyMainWindowSettings(config->group("General Options")); KConfigGroup cg(config, "General Options"); QPoint pos = cg.readEntry("Position", QPoint(-1, -1)); if (pos.x() != -1 || pos.y() != -1) { move(pos); } QSize size = cg.readEntry("Geometry", QSize()); if (size.isEmpty()) { // Default size - resize(QSize(272, 400)); + resize(QSize(375, 425)); } else { resize(size); } d->autoHide = Kopete::AppearanceSettings::self()->contactListAutoHide(); d->autoHideTimeout = Kopete::AppearanceSettings::self()->contactListAutoHideTimeout(); QString tmp = cg.readEntry("WindowState", "Shown"); if (tmp == QLatin1String("Minimized") && Kopete::BehaviorSettings::self()->showSystemTray()) { showMinimized(); } else if (tmp == QLatin1String("Hidden") && Kopete::BehaviorSettings::self()->showSystemTray()) { hide(); } else if (!Kopete::BehaviorSettings::self()->startDocked() || !Kopete::BehaviorSettings::self()->showSystemTray()) { show(); } d->menubarAction->setChecked(!menuBar()->isHidden()); d->statusbarAction->setChecked(!statusBar()->isHidden()); } void KopeteWindow::saveOptions() { KConfigGroup mainToolbarGroup(KSharedConfig::openConfig(), "ToolBar Settings"); toolBar(QStringLiteral("mainToolBar"))->saveSettings(mainToolbarGroup); KConfigGroup qsbGroup(KSharedConfig::openConfig(), "QuickSearchBar Settings"); toolBar(QStringLiteral("quickSearchBar"))->saveSettings(qsbGroup); KConfigGroup cg(KSharedConfig::openConfig(), "General Options"); saveMainWindowSettings(cg); cg.writeEntry("Position", pos()); cg.writeEntry("Geometry", size()); if (isMinimized()) { cg.writeEntry("WindowState", "Minimized"); } else if (isHidden()) { cg.writeEntry("WindowState", "Hidden"); } else { cg.writeEntry("WindowState", "Shown"); } Kopete::Identity *identity = d->identitywidget->identity(); if (identity) { cg.writeEntry("ShownIdentityId", identity->id()); } else { cg.writeEntry("ShownIdentityId", QString()); } cg.sync(); } void KopeteWindow::slotToggleShowAllOfflineEmpty(bool toggled) { d->actionShowOfflineUsers->setChecked(toggled); d->actionShowEmptyGroups->setChecked(toggled); Kopete::AppearanceSettings::self()->setShowOfflineUsers(toggled); Kopete::AppearanceSettings::self()->setShowEmptyGroups(toggled); Kopete::AppearanceSettings::self()->save(); } void KopeteWindow::slotToggleShowOfflineUsers() { Kopete::AppearanceSettings::self()->setShowOfflineUsers(d->actionShowOfflineUsers->isChecked()); Kopete::AppearanceSettings::self()->save(); } void KopeteWindow::slotToggleShowEmptyGroups() { Kopete::AppearanceSettings::self()->setShowEmptyGroups(d->actionShowEmptyGroups->isChecked()); Kopete::AppearanceSettings::self()->save(); } static bool compareOnlineStatus(const Kopete::Account *a, const Kopete::Account *b); static bool invertedCompareOnlineStatus(const Kopete::Account *a, const Kopete::Account *b); void KopeteWindow::slotConfigChanged() { bool groupContactByGroupModel = qobject_cast(d->model); if (groupContactByGroupModel != Kopete::AppearanceSettings::self()->groupContactByGroup()) { Kopete::UI::ContactListModel *oldModel = d->model; if (Kopete::AppearanceSettings::self()->groupContactByGroup()) { d->model = new Kopete::UI::ContactListTreeModel(this); } else { d->model = new Kopete::UI::ContactListPlainModel(this); } d->model->init(); d->proxyModel->setSourceModel(d->model); oldModel->deleteLater(); } if (isHidden() && !Kopete::BehaviorSettings::self()->showSystemTray()) { // user disabled systray while kopete is hidden, show it! show(); } d->actionShowAllOfflineEmpty->setChecked(Kopete::AppearanceSettings::self()->showOfflineUsers() && Kopete::AppearanceSettings::self()->showEmptyGroups()); d->actionShowOfflineUsers->setChecked(Kopete::AppearanceSettings::self()->showOfflineUsers()); d->actionShowEmptyGroups->setChecked(Kopete::AppearanceSettings::self()->showEmptyGroups()); if (d->showIdentityIcons != Kopete::AppearanceSettings::self()->showIdentityIcons()) { // Delete status bar icons if (d->showIdentityIcons) { if (d->identitywidget->isVisible()) { d->identitywidget->setIdentity(0); d->identitywidget->setVisible(false); } qDeleteAll(d->identityStatusBarIcons); d->identityStatusBarIcons.clear(); } else { qDeleteAll(d->accountStatusBarIcons); d->accountStatusBarIcons.clear(); } // Add new status bar icons d->showIdentityIcons = Kopete::AppearanceSettings::self()->showIdentityIcons(); if (d->showIdentityIcons) { Kopete::Identity::List identityList = Kopete::IdentityManager::self()->identities(); foreach (Kopete::Identity *identity, identityList) { KopeteIdentityStatusBarIcon *sbIcon = new KopeteIdentityStatusBarIcon(identity, d->statusBarWidget); connect(sbIcon, SIGNAL(leftClicked(Kopete::Identity *,QPoint)), this, SLOT(slotIdentityStatusIconLeftClicked(Kopete::Identity *,QPoint))); d->identityStatusBarIcons.insert(identity, sbIcon); slotIdentityStatusIconChanged(identity); slotIdentityToolTipChanged(identity); } } else { QList accountList = Kopete::AccountManager::self()->accounts(); qSort(accountList.begin(), accountList.end(), invertedCompareOnlineStatus); foreach (Kopete::Account *account, accountList) { KopeteAccountStatusBarIcon *sbIcon = new KopeteAccountStatusBarIcon(account, d->statusBarWidget); d->accountStatusBarIcons.insert(account, sbIcon); } } } } void KopeteWindow::slotContactListAppearanceChanged() { d->autoHide = Kopete::AppearanceSettings::self()->contactListAutoHide(); d->autoHideTimeout = Kopete::AppearanceSettings::self()->contactListAutoHideTimeout(); startAutoHideTimer(); } void KopeteWindow::slotConfNotifications() { KNotifyConfigWidget::configure(this); } void KopeteWindow::slotConfGlobalKeys() { KShortcutsDialog::configure(actionCollection()); } void KopeteWindow::slotConfToolbar() { KConfigGroup cg(KSharedConfig::openConfig(), "General Options"); saveMainWindowSettings(cg); KEditToolBar *dlg = new KEditToolBar(factory()); connect(dlg, SIGNAL(newToolBarConfig()), this, SLOT(slotUpdateToolbar())); connect(dlg, SIGNAL(finished()), dlg, SLOT(deleteLater())); dlg->show(); } void KopeteWindow::slotUpdateToolbar() { applyMainWindowSettings(KSharedConfig::openConfig()->group("General Options")); } void KopeteWindow::slotGlobalAway() { Kopete::AccountManager::self()->setOnlineStatus(Kopete::OnlineStatusManager::Away, Kopete::StatusManager::self()->globalStatusMessage()); } void KopeteWindow::slotGlobalBusy() { Kopete::AccountManager::self()->setOnlineStatus(Kopete::OnlineStatusManager::Busy, Kopete::StatusManager::self()->globalStatusMessage()); } void KopeteWindow::slotGlobalAvailable() { Kopete::AccountManager::self()->setOnlineStatus(Kopete::OnlineStatusManager::Online, Kopete::StatusManager::self()->globalStatusMessage()); } void KopeteWindow::slotSetInvisibleAll() { Kopete::AccountManager::self()->setOnlineStatus(Kopete::OnlineStatusManager::Invisible, Kopete::StatusManager::self()->globalStatusMessage()); } void KopeteWindow::slotDisconnectAll() { Kopete::AccountManager::self()->setOnlineStatus(Kopete::OnlineStatusManager::Offline, Kopete::StatusManager::self()->globalStatusMessage()); } bool KopeteWindow::queryClose() { KopeteApplication *app = static_cast(qApp); if (app->isSavingSession() || app->isShuttingDown()) { // we are shutting down, don't show any message return true; } Kopete::PluginList list = Kopete::PluginManager::self()->loadedPlugins(); foreach (Kopete::Plugin *plugin, list) { bool shown = false; QMetaObject::invokeMethod(plugin, "showCloseWindowMessage", Qt::DirectConnection, Q_RETURN_ARG(bool, shown)); if (shown) { // A message box has just been shown. Stop now, we do not want // to spam the user with multiple message boxes. return true; } } if (Kopete::BehaviorSettings::self()->showSystemTray() && !isHidden()) { // I would make this a KMessageBox::queuedMessageBox but there doesn't seem to be don'tShowAgain support for those KMessageBox::information(this, i18n("Closing the main window will keep Kopete running in the " "system tray. Use 'Quit' from the 'File' menu to quit the application."), i18n("Docking in System Tray"), QStringLiteral("hideOnCloseInfo")); } -// else // we are shutting down either user initiated or session management -// Kopete::PluginManager::self()->shutdown(); - + else{ + // we are shutting down either user initiated or session management + Kopete::PluginManager::self()->shutdown(); + } return true; } bool KopeteWindow::shouldExitOnClose() const { - Kopete::PluginList list = Kopete::PluginManager::self()->loadedPlugins(); + const Kopete::PluginList &list = Kopete::PluginManager::self()->loadedPlugins(); foreach (Kopete::Plugin *plugin, list) { bool ok = true; - QMetaObject::invokeMethod(plugin, "shouldExitOnClose", Qt::DirectConnection, Q_RETURN_ARG(bool, ok)); + QMetaObject::invokeMethod(plugin, "shouldExitOnclose", Qt::DirectConnection, Q_RETURN_ARG(bool, ok)); if (!ok) { kDebug(14000) << "plugin" << plugin->displayName() << "does not want to exit"; return false; } } // If all plugins are OK, consider ourself OK only if there is no tray icon return !Kopete::BehaviorSettings::self()->showSystemTray(); } bool KopeteWindow::queryExit() { KopeteApplication *app = static_cast(qApp); if (app->isSavingSession() || app->isShuttingDown() /* only set if KopeteApplication::quitKopete() or KopeteApplication::commitData() called */ || shouldExitOnClose() || isHidden()) { saveOptions(); kDebug(14000) << " shutting down plugin manager"; Kopete::PluginList list = Kopete::PluginManager::self()->loadedPlugins(); foreach (Kopete::Plugin *plugin, list) { guiFactory()->removeClient(plugin); } Kopete::PluginManager::self()->shutdown(); return true; } else { return false; } } void KopeteWindow::closeEvent(QCloseEvent *e) { // if we are not ok to exit on close and we are not shutting down then just do what needs to be done if a // window is closed. KopeteApplication *app = static_cast(qApp); if (!shouldExitOnClose() && !app->isShuttingDown() && !app->isSavingSession()) { // BEGIN of code borrowed from KMainWindow::closeEvent // Save settings if auto-save is enabled, and settings have changed if (settingsDirty() && autoSaveSettings()) { saveAutoSaveSettings(); } if (queryClose()) { e->accept(); } // END of code borrowed from KMainWindow::closeEvent kDebug(14000) << "just closing because we have a system tray icon"; } else { kDebug(14000) << "delegating to KXmlGuiWindow::closeEvent()"; KXmlGuiWindow::closeEvent(e); } } void KopeteWindow::slotQuit() { KopeteApplication *app = static_cast(qApp); app->quitKopete(); if (d->tray && app->isShuttingDown()) { d->tray->deleteLater(); d->tray = 0; } } void KopeteWindow::slotPluginLoaded(Kopete::Plugin *p) { guiFactory()->addClient(p); } void KopeteWindow::slotAllPluginsLoaded() { // actionConnect->setEnabled(true); d->actionDisconnect->setEnabled(true); KConfigGroup cg(KSharedConfig::openConfig(), "General Options"); // If some account already loaded, build the status icon QList accountList = Kopete::AccountManager::self()->accounts(); qSort(accountList.begin(), accountList.end(), invertedCompareOnlineStatus); foreach (Kopete::Account *a, accountList) { slotAccountRegistered(a); } //Connect the appropriate account signals /* Please note that I tried to put this in the slotAllPluginsLoaded() function * but it seemed to break the account icons in the statusbar --Matt */ connect(Kopete::AccountManager::self(), SIGNAL(accountRegistered(Kopete::Account *)), this, SLOT(slotAccountRegistered(Kopete::Account *))); connect(Kopete::AccountManager::self(), SIGNAL(accountUnregistered(const Kopete::Account *)), this, SLOT(slotAccountUnregistered(const Kopete::Account *))); if (d->showIdentityIcons) { QString identityId = cg.readEntry("ShownIdentityId", Kopete::IdentityManager::self()->defaultIdentity()->id()); if (!identityId.isEmpty()) { Kopete::Identity *identity = Kopete::IdentityManager::self()->findIdentity(identityId); if (identity) { slotIdentityStatusIconLeftClicked(identity, QPoint()); } } } } void KopeteWindow::slotIdentityRegistered(Kopete::Identity *identity) { if (!identity) { return; } connect(identity, SIGNAL(onlineStatusChanged(Kopete::Identity *)), this, SLOT(slotIdentityStatusIconChanged(Kopete::Identity *))); connect(identity, SIGNAL(identityChanged(Kopete::Identity *)), this, SLOT(slotIdentityStatusIconChanged(Kopete::Identity *))); connect(identity, SIGNAL(toolTipChanged(Kopete::Identity *)), this, SLOT(slotIdentityToolTipChanged(Kopete::Identity *))); if (d->showIdentityIcons) { KopeteIdentityStatusBarIcon *sbIcon = new KopeteIdentityStatusBarIcon(identity, d->statusBarWidget); connect(sbIcon, SIGNAL(leftClicked(Kopete::Identity *,QPoint)), SLOT(slotIdentityStatusIconLeftClicked(Kopete::Identity *,QPoint))); d->identityStatusBarIcons.insert(identity, sbIcon); } slotIdentityStatusIconChanged(identity); slotIdentityToolTipChanged(identity); } void KopeteWindow::slotIdentityUnregistered(const Kopete::Identity *identity) { kDebug(14000); if (d->showIdentityIcons) { KopeteIdentityStatusBarIcon *sbIcon = d->identityStatusBarIcons.value(identity, 0); if (sbIcon) { d->identityStatusBarIcons.remove(identity); delete sbIcon; } } makeTrayToolTip(); } void KopeteWindow::slotIdentityToolTipChanged(Kopete::Identity *identity) { if (d->appDestroyed) { return; } KopeteApplication *app = static_cast(qApp); if (app->isSavingSession() || app->isShuttingDown()) { return; } // Adds tooltip for each status icon, useful in case you have many accounts // over one protocol KopeteIdentityStatusBarIcon *i = d->identityStatusBarIcons.value(identity, 0); if (i) { i->setToolTip(identity->toolTip()); } makeTrayToolTip(); } void KopeteWindow::slotIdentityStatusIconChanged(Kopete::Identity *identity) { kDebug(14000) << identity->property(Kopete::Global::Properties::self()->statusMessage()).value(); // update the global status label if the change doesn't // QString newAwayMessage = contact->property( Kopete::Global::Properties::self()->awayMessage() ).value().toString(); // if ( status.status() != Kopete::OnlineStatus::Connecting ) // { // QString globalMessage = m_globalStatusMessage->text(); // if ( newAwayMessage != globalMessage ) // m_globalStatusMessage->setText( ""i18n("status message to show when different accounts have different status messages", "(multiple)" ); // } // kDebug(14000) << "Icons: '" << // status.overlayIcons() << "'" << endl; if (d->appDestroyed) { return; } KopeteApplication *app = static_cast(qApp); if (app->isSavingSession() || app->isShuttingDown()) { return; } if (identity->onlineStatus() != Kopete::OnlineStatus::Connecting) { // FIXME: It's not global status so don't save it //Kopete::StatusManager::self()->setGlobalStatusMessage( identity->property( Kopete::Global::Properties::self()->statusMessage() ).value().toString() ); } KopeteIdentityStatusBarIcon *i = d->identityStatusBarIcons.value(identity, 0); if (!i) { return; } QPixmap pm; switch (identity->onlineStatus()) { case Kopete::OnlineStatus::Offline: case Kopete::OnlineStatus::Connecting: pm = SmallIcon(QStringLiteral("user-identity"), 0, KIconLoader::DefaultState, QStringList() << QString() << QStringLiteral("user-offline")); break; case Kopete::OnlineStatus::Invisible: pm = SmallIcon(QStringLiteral("user-identity"), 0, KIconLoader::DefaultState, QStringList() << QString() << QStringLiteral("user-invisible")); break; case Kopete::OnlineStatus::Away: pm = SmallIcon(QStringLiteral("user-identity"), 0, KIconLoader::DefaultState, QStringList() << QString() << QStringLiteral("user-away")); break; case Kopete::OnlineStatus::Busy: pm = SmallIcon(QStringLiteral("user-identity"), 0, KIconLoader::DefaultState, QStringList() << QString() << QStringLiteral("user-busy")); break; case Kopete::OnlineStatus::Online: pm = SmallIcon(QStringLiteral("user-identity"), 0, KIconLoader::DefaultState, QStringList() << QString() << QStringLiteral("user-online")); break; case Kopete::OnlineStatus::Unknown: pm = SmallIcon(QStringLiteral("user-identity")); break; } // No Pixmap found, fallback to Unknown if (pm.isNull()) { i->setPixmap(SmallIcon(QStringLiteral("user-identity"))); } else { i->setPixmap(pm); } } static bool compareOnlineStatus(const Kopete::Account *a, const Kopete::Account *b) { int c = 0; if (a->identity() && b->identity()) { c = QString::localeAwareCompare(a->identity()->label(), b->identity()->label()); } if (c == 0) { c = a->myself()->onlineStatus().status() - b->myself()->onlineStatus().status(); if (c == 0) { return QString::localeAwareCompare(a->protocol()->displayName(), b->protocol()->displayName()) < 0; } return c > 0; } return c < 0; } static bool invertedCompareOnlineStatus(const Kopete::Account *a, const Kopete::Account *b) { return !compareOnlineStatus(a, b); } void KopeteWindow::makeTrayToolTip() { //FIXME: maybe use identities here? //the tool-tip of the systemtray. if (d->tray) { QString tt = QStringLiteral(""); QList accountList = Kopete::AccountManager::self()->accounts(); qSort(accountList.begin(), accountList.end(), compareOnlineStatus); foreach (Kopete::Account *a, accountList) { Kopete::Contact *self = a->myself(); /*tt += i18nc ( "Account tooltip information: ICON PROTOCOL: NAME (STATUS)
", " %1: %2 (%5)
", a->protocol()->displayName(), a->accountLabel(), QString ( QUrl::toPercentEncoding ( a->protocol()->pluginId() ) ), QString ( QUrl::toPercentEncoding ( a->accountId() ) ), self->onlineStatus().description() );*/ tt += i18nc("Account tooltip information: ICON PROTOCOL: NAME (STATUS)
", " %1: %2 (%4)
", a->protocol()->displayName(), a->accountLabel(), a->accountIconPath(KIconLoader::Small), self->onlineStatus().description()); } tt += QLatin1String("
"); d->tray->setToolTip(QStringLiteral("kopete"), i18n("Kopete"), tt); } } void KopeteWindow::slotIdentityStatusIconLeftClicked(Kopete::Identity *identity, const QPoint &p) { Q_UNUSED(p) if (d->identitywidget->isVisible() && d->identitywidget->identity() == identity) { d->identitywidget->setIdentity(0); d->identitywidget->setVisible(false); return; } if (d->infoEventWidget->isVisible()) { d->infoEventWidget->setVisible(false); } d->identitywidget->setIdentity(identity); d->identitywidget->setVisible(true); } void KopeteWindow::slotShowInfoEventWidget() { if (d->identitywidget->isVisible()) { d->identitywidget->setIdentity(0); d->identitywidget->setVisible(false); } if (!d->infoEventWidget->isVisible()) { d->infoEventWidget->setVisible(true); } if (!isActiveWindow()) { slotShowHide(); } } void KopeteWindow::slotInfoIconClicked() { if (d->infoEventWidget->isVisible()) { d->infoEventWidget->setVisible(false); } else { if (d->identitywidget->isVisible()) { d->identitywidget->setIdentity(0); d->identitywidget->setVisible(false); } d->infoEventWidget->setVisible(true); } } void KopeteWindow::slotAccountRegistered(Kopete::Account *account) { //enable the connect all toolbar button // actionConnect->setEnabled(true); d->actionDisconnect->setEnabled(true); // add an item for this account to the add contact actionmenu QString s = QStringLiteral("actionAdd%1Contact").arg(account->accountId()); QAction *action = new QAction(QIcon(account->accountIcon()), account->accountLabel(), this); actionCollection()->addAction(s, action); connect(action, SIGNAL(triggered(bool)), d->addContactMapper, SLOT(map())); connect(account, SIGNAL(colorChanged(QColor)), this, SLOT(slotAccountColorChanged())); d->addContactMapper->setMapping(action, account->protocol()->pluginId() + QChar(0xE000) + account->accountId()); d->actionAddContact->addAction(action); if (!d->showIdentityIcons) { KopeteAccountStatusBarIcon *sbIcon = new KopeteAccountStatusBarIcon(account, d->statusBarWidget); d->accountStatusBarIcons.insert(account, sbIcon); } } void KopeteWindow::slotAccountColorChanged() { Kopete::Account *account = qobject_cast(sender()); Q_ASSERT(account); // update add contact actionmenu QString s = QStringLiteral("actionAdd%1Contact").arg(account->accountId()); QAction *action = actionCollection()->action(s); if (action) { action->setIcon(QIcon(account->accountIcon())); } } void KopeteWindow::slotAccountUnregistered(const Kopete::Account *account) { QList accounts = Kopete::AccountManager::self()->accounts(); if (accounts.isEmpty()) { // actionConnect->setEnabled(false); d->actionDisconnect->setEnabled(false); } disconnect(account, SIGNAL(colorChanged(QColor)), this, SLOT(slotAccountColorChanged())); // update add contact actionmenu QString s = QStringLiteral("actionAdd%1Contact").arg(account->accountId()); QAction *action = actionCollection()->action(s); if (action) { kDebug(14000) << " found QAction " << action << " with name: " << action->objectName(); d->addContactMapper->removeMappings(action); d->actionAddContact->removeAction(action); } if (!d->showIdentityIcons) { KopeteAccountStatusBarIcon *sbIcon = d->accountStatusBarIcons.value(account, 0); if (sbIcon) { d->accountStatusBarIcons.remove(account); delete sbIcon; } } } void KopeteWindow::slotTrayAboutToShowMenu(QMenu *popup) { popup->clear(); popup->addSection(qApp->windowIcon(), KGlobal::caption()); QList accountList = Kopete::AccountManager::self()->accounts(); qSort(accountList.begin(), accountList.end(), invertedCompareOnlineStatus); foreach (Kopete::Account *account, accountList) { KActionMenu *menu = new KActionMenu(account->accountId(), account); menu->setIcon(account->myself()->onlineStatus().iconFor(account)); if (!account->hasCustomStatusMenu()) { Kopete::StatusRootAction::createAccountStatusActions(account, menu); } account->fillActionMenu(menu); popup->addAction(menu); connect(popup, SIGNAL(aboutToHide()), menu, SLOT(deleteLater())); } popup->addSeparator(); popup->addAction(d->actionStatusMenu); popup->addSeparator(); popup->addAction(d->actionPrefs); popup->addAction(d->actionAddContact); popup->addSeparator(); popup->addAction(d->tray->action(QStringLiteral("minimizeRestore"))); popup->addAction(d->tray->action(KStandardAction::name(KStandardAction::Quit))); } void KopeteWindow::showExportDialog() { KabcExportWizard *wizard = new KabcExportWizard(this); wizard->setObjectName(QStringLiteral("export_contact_dialog")); wizard->show(); } void KopeteWindow::leaveEvent(QEvent *) { startAutoHideTimer(); } void KopeteWindow::showEvent(QShowEvent *) { startAutoHideTimer(); slotStartAutoResizeTimer(); } void KopeteWindow::hideEvent(QHideEvent *) { d->autoResizeTimer->stop(); } void KopeteWindow::slotAutoHide() { if (this->geometry().contains(QCursor::pos()) == false) { /* The autohide-timer doesn't need to emit * timeouts when the window is hidden already. */ d->autoHideTimer->stop(); hide(); } } void KopeteWindow::startAutoHideTimer() { if (d->autoHideTimeout > 0 && d->autoHide == true && isVisible() && Kopete::BehaviorSettings::self()->showSystemTray()) { d->autoHideTimer->start(d->autoHideTimeout * 1000); } } void KopeteWindow::slotStartAutoResizeTimer() { if (Kopete::AppearanceSettings::contactListAutoResize() == true) { if (!d->autoResizeTimer->isActive()) { d->autoResizeTimer->start(1000); } } } void KopeteWindow::setOnlineStatus(uint category, const Kopete::StatusMessage &statusMessage) { Kopete::AccountManager::self()->setOnlineStatus(category, statusMessage, 0, true); } void KopeteWindow::setStatusMessage(const Kopete::StatusMessage &statusMessage) { Kopete::StatusManager::self()->setGlobalStatusMessage(statusMessage); } void KopeteWindow::globalStatusChanged() { QString statusTitle = Kopete::StatusManager::self()->globalStatusMessage().title(); QString statusMessage = Kopete::StatusManager::self()->globalStatusMessage().message(); d->globalStatusMessage->setText(statusTitle); QString toolTip; toolTip += i18nc("@label:textbox formatted status title", "Status Title: %1", Kopete::Emoticons::parseEmoticons(Kopete::Message::escape(statusTitle))); toolTip += i18nc("@label:textbox formatted status message", "
Status Message: %1", Kopete::Emoticons::parseEmoticons(Kopete::Message::escape(statusMessage))); d->globalStatusMessage->setToolTip(toolTip); } void KopeteWindow::slotGlobalStatusMessageIconClicked(const QPoint &position) { QMenu *menu = new QMenu(this); menu->addSection(i18n("Status Message")); Kopete::UI::StatusEditAction *statusEditAction = new Kopete::UI::StatusEditAction(this); statusEditAction->setStatusMessage(Kopete::StatusManager::self()->globalStatusMessage()); connect(statusEditAction, SIGNAL(statusChanged(Kopete::StatusMessage)), this, SLOT(setStatusMessage(Kopete::StatusMessage))); menu->addAction(statusEditAction); menu->exec(position); statusEditAction->deleteLater(); delete menu; } void KopeteWindow::slotAddContactDialogInternal(const QString &accountIdentifier) { QString protocolId = accountIdentifier.section(QChar(0xE000), 0, 0); QString accountId = accountIdentifier.section(QChar(0xE000), 1, 1); Kopete::Account *account = Kopete::AccountManager::self()->findAccount(protocolId, accountId); showAddContactDialog(account); } void KopeteWindow::updateStatusMenuMessage(Kopete::StatusRootAction *statusRootAction) { statusRootAction->setCurrentMessage(Kopete::StatusManager::self()->globalStatusMessage()); } void KopeteWindow::showAddContactDialog(Kopete::Account *account) { if (!account) { kDebug(14000) << "no account given"; return; } KDialog *addDialog = new KDialog(this); addDialog->setCaption(i18n("Add Contact")); addDialog->setButtons(KDialog::Ok | KDialog::Cancel); addDialog->setDefaultButton(KDialog::Ok); addDialog->showButtonSeparator(true); KVBox *mainWid = new KVBox(addDialog); AddContactPage *addContactPage = account->protocol()->createAddContactWidget(mainWid, account); QWidget *groupKABC = new QWidget(mainWid); groupKABC->setObjectName(QStringLiteral("groupkabcwidget")); Ui::GroupKABCSelectorWidget ui_groupKABC; ui_groupKABC.setupUi(groupKABC); // Populate the groups list Kopete::GroupList groups = Kopete::ContactList::self()->groups(); QHash groupItems; // Add top level group groupItems.insert(Kopete::Group::topLevel()->displayName(), Kopete::Group::topLevel()); ui_groupKABC.groupCombo->addItem(Kopete::Group::topLevel()->displayName()); foreach (Kopete::Group *group, groups) { if (group->type() != Kopete::Group::Normal) { continue; } QString groupname = group->displayName(); if (!groupname.isEmpty()) { groupItems.insert(groupname, group); ui_groupKABC.groupCombo->addItem(groupname); } } if (!addContactPage) { kDebug(14000) <<"Error while creating addcontactpage" << endl; } else { addDialog->setMainWidget(mainWid); if (addDialog->exec() == QDialog::Accepted) { if (addContactPage->validateData()) { Kopete::MetaContact *metacontact = new Kopete::MetaContact(); metacontact->addToGroup(groupItems[ ui_groupKABC.groupCombo->currentText() ]); metacontact->setKabcId(ui_groupKABC.widAddresseeLink->uid()); if (addContactPage->apply(account, metacontact)) { Kopete::ContactList::self()->addMetaContact(metacontact); } else { delete metacontact; } } } } addDialog->deleteLater(); } void KopeteWindow::slotUpdateSize() { /* resize with rules: - never will be taller than maxTall - never shorter than minTall - never will resize if contactlist is empty - never will resize if cursor is in window */ if (this->geometry().contains(QCursor::pos()) == true) { return; // don't do anything if cursor is inside window } const int amountWindowBiggerThanContactList = 200; const QRect workArea = KWindowSystem::workArea(); const int minHeight = 400; QRect newGeometry = geometry(); const QRect oldGeometry = geometry(); const int topFrameWidth = -(frameGeometry().top() - oldGeometry.top()); const int bottomFrameWidth = frameGeometry().bottom() - oldGeometry.bottom(); // desired height is height of full contents of contact list tree, as well as // some buffer for other elements in the main window int height = d->contactlist->visibleContentHeight(); newGeometry.setHeight(height + amountWindowBiggerThanContactList); if (height) { // if new size is too big or too small, bring inside limits if (newGeometry.height() > workArea.height()) { newGeometry.setHeight(workArea.height() - topFrameWidth - bottomFrameWidth); } else if (newGeometry.height() < minHeight) { newGeometry.setHeight(minHeight); } // set position of new geometry rectangle to same position as the old one if (Kopete::AppearanceSettings::contactListResizeAnchor() == Kopete::AppearanceSettings::EnumContactListResizeAnchor::Top) { newGeometry.moveTop(oldGeometry.top()); } else { newGeometry.moveBottom(oldGeometry.bottom()); } // if the window + its frame is out of the work area, bring it just inside if ((newGeometry.top() - topFrameWidth) < workArea.top()) { newGeometry.moveTop(workArea.top() + topFrameWidth); } else if ((newGeometry.bottom() + bottomFrameWidth) > workArea.bottom()) { newGeometry.moveBottom(workArea.bottom() - bottomFrameWidth); } // do it! setGeometry(newGeometry); } } void KopeteWindow::slotNewInfoEvent() { if (!d->infoEventWidget->isVisible()) { if (d->identitywidget->isVisible()) { d->identitywidget->setIdentity(0); d->identitywidget->setVisible(false); } d->infoEventWidget->setVisible(true); } } // vim: set noet ts=4 sts=4 sw=4: diff --git a/kopete/systemtray.cpp b/kopete/systemtray.cpp index f7812efae..3ff65cde6 100644 --- a/kopete/systemtray.cpp +++ b/kopete/systemtray.cpp @@ -1,244 +1,244 @@ /* systemtray.cpp - Kopete Tray Dock Icon Copyright (c) 2002 by Nick Betcher Copyright (c) 2002-2003 by Martijn Klingens Copyright (c) 2003-2004 by Olivier Goffart Kopete (c) 2002-2007 by the Kopete developers ************************************************************************* * * * 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 "systemtray.h" #include #include #include #include #include #include #include #include #include #include #include "kopetechatsessionmanager.h" #include "kopetebehaviorsettings.h" #include "kopetemetacontact.h" #include "kopeteaccount.h" #include "kopeteaccountmanager.h" #include "kopetecontact.h" #include "kopetewindow.h" KopeteSystemTray *KopeteSystemTray::s_systemTray = 0; KopeteSystemTray *KopeteSystemTray::systemTray(QWidget *parent) { if (!s_systemTray) { s_systemTray = new KopeteSystemTray(parent); } return s_systemTray; } KopeteSystemTray::KopeteSystemTray(QWidget *parent) : KStatusNotifierItem(parent) { kDebug(14010); setCategory(Communications); setToolTip(QStringLiteral("kopete"), QStringLiteral("Kopete"), KGlobal::mainComponent().aboutData()->shortDescription()); setStatus(Passive); mIsBlinkIcon = false; mBlinkTimer = new QTimer(this); mBlinkTimer->setObjectName(QStringLiteral("mBlinkTimer")); mKopeteIcon = QStringLiteral("kopete"); connect(contextMenu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowMenu())); connect(mBlinkTimer, SIGNAL(timeout()), this, SLOT(slotBlink())); connect(Kopete::ChatSessionManager::self(), SIGNAL(newEvent(Kopete::MessageEvent *)), this, SLOT(slotNewEvent(Kopete::MessageEvent *))); connect(Kopete::BehaviorSettings::self(), SIGNAL(configChanged()), this, SLOT(slotConfigChanged())); connect(Kopete::AccountManager::self(), SIGNAL(accountOnlineStatusChanged(Kopete::Account *, const Kopete::OnlineStatus&,const Kopete::OnlineStatus&)), this, SLOT(slotReevaluateAccountStates())); // the slot called by default by the quit action, KSystemTray::maybeQuit(), // just closes the parent window, which is hard to distinguish in that window's closeEvent() // from a click on the window's close widget // in the quit case, we want to quit the application // in the close widget click case, we only want to hide the parent window // so instead, we make it call our general purpose quit slot on the window, which causes a window close and everything else we need // KDE4 - app will have to listen for quitSelected instead QList actionList = actionCollection(); KActionCollection *newActionList = new KActionCollection(parent); newActionList->addActions(actionList); QAction *quit = newActionList->action(QStringLiteral("file_quit")); quit->disconnect(); KopeteWindow *myParent = static_cast(parent); - connect(quit, SIGNAL(activated()), myParent, SLOT(slotQuit())); + connect(quit, SIGNAL(triggered()), myParent, SLOT(slotQuit())); setIconByName(mKopeteIcon); setAttentionMovieByName(QStringLiteral("newmessage")); slotReevaluateAccountStates(); slotConfigChanged(); } KopeteSystemTray::~KopeteSystemTray() { kDebug(14010); // delete mBlinkTimer; } void KopeteSystemTray::slotAboutToShowMenu() { emit aboutToShowMenu(qobject_cast(contextMenu())); } void KopeteSystemTray::activate(const QPoint &pos) { if (isBlinking() && Kopete::BehaviorSettings::self()->trayflashNotifyLeftClickOpensMessage()) { if (!mEventList.isEmpty()) { mEventList.first()->apply(); } } else { KStatusNotifierItem::activate(pos); } } // void KopeteSystemTray::contextMenuAboutToShow( QMenu *me ) // { // //kDebug(14010) << "Called."; // emit aboutToShowMenu( me ); // } void KopeteSystemTray::startBlink(const QString &icon) { mBlinkIcon = icon; if (mBlinkTimer->isActive()) { mBlinkTimer->stop(); } mIsBlinkIcon = true; mBlinkTimer->setSingleShot(false); mBlinkTimer->start(1000); } void KopeteSystemTray::startBlink() { setStatus(NeedsAttention); } void KopeteSystemTray::stopBlink() { setStatus(Passive); if (mBlinkTimer->isActive()) { mBlinkTimer->stop(); } mIsBlinkIcon = false; //setPixmap( mKopeteIcon ); slotReevaluateAccountStates(); } void KopeteSystemTray::slotBlink() { setIconByName(mIsBlinkIcon ? mKopeteIcon : mBlinkIcon); mIsBlinkIcon = !mIsBlinkIcon; } void KopeteSystemTray::slotNewEvent(Kopete::MessageEvent *event) { mEventList.append(event); connect(event, SIGNAL(done(Kopete::MessageEvent *)), this, SLOT(slotEventDone(Kopete::MessageEvent *))); // tray animation if (Kopete::BehaviorSettings::self()->trayflashNotify()) { startBlink(); } } void KopeteSystemTray::slotEventDone(Kopete::MessageEvent *event) { mEventList.removeAll(event); if (mEventList.isEmpty()) { stopBlink(); } } void KopeteSystemTray::slotConfigChanged() { #if 0 // kDebug(14010) << "called."; if (Kopete::BehaviorSettings::self()->showSystemTray()) { show(); } else { hide(); // for users without kicker or a similar docking app } #endif } void KopeteSystemTray::slotReevaluateAccountStates() { // If there is a pending message, we don't need to refresh the system tray now. // This function will even be called when the animation will stop. if (mBlinkTimer->isActive()) { return; } Kopete::OnlineStatus highestStatus; foreach (Kopete::Account *account, Kopete::AccountManager::self()->accounts()) { if (account->myself() && account->myself()->onlineStatus() > highestStatus) { highestStatus = account->myself()->onlineStatus(); } } switch (highestStatus.status()) { case Kopete::OnlineStatus::Unknown: case Kopete::OnlineStatus::Offline: case Kopete::OnlineStatus::Connecting: setIconByName(QStringLiteral("kopete-offline")); setOverlayIconByName(QStringLiteral("user-offline")); break; case Kopete::OnlineStatus::Invisible: setIconByName(mKopeteIcon); setOverlayIconByName(QStringLiteral("user-invisible")); break; case Kopete::OnlineStatus::Away: setIconByName(mKopeteIcon); setOverlayIconByName(QStringLiteral("user-away")); break; case Kopete::OnlineStatus::Busy: setIconByName(mKopeteIcon); setOverlayIconByName(QStringLiteral("user-busy")); break; case Kopete::OnlineStatus::Online: setIconByName(mKopeteIcon); setOverlayIconByName(QString()); break; } } bool KopeteSystemTray::isBlinking() const { return mBlinkTimer->isActive() || (status() == NeedsAttention); } // vim: set noet ts=4 sts=4 sw=4: diff --git a/libkopete/kopeteaccount.cpp b/libkopete/kopeteaccount.cpp index f07112b10..236853a90 100644 --- a/libkopete/kopeteaccount.cpp +++ b/libkopete/kopeteaccount.cpp @@ -1,730 +1,730 @@ /* kopeteaccount.cpp - Kopete Account Copyright (c) 2003-2005 by Olivier Goffart Copyright (c) 2003-2004 by Martijn Klingens Copyright (c) 2004 by Richard Smith Copyright (c) 2007 Will Stephenson Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * ************************************************************************* */ #include "kopeteaccount.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kopeteidentity.h" #include "kopeteidentitymanager.h" #include "kabcpersistence.h" #include "kopetecontactlist.h" #include "kopeteaccountmanager.h" #include "kopetecontact.h" #include "kopetemetacontact.h" #include "kopeteprotocol.h" #include "kopetepluginmanager.h" #include "kopetegroup.h" #include "kopetebehaviorsettings.h" #include "kopeteutils.h" #include "kopeteuiglobal.h" #include "kopeteblacklister.h" #include "kopeteonlinestatusmanager.h" #include "editaccountwidget.h" namespace Kopete { class Account::Private { public: Private(Protocol *protocol, const QString &accountId) : protocol(protocol) , id(accountId) , excludeconnect(true) , priority(0) , connectionTry(0) , identity(0) , myself(0) , suppressStatusTimer(0) , suppressStatusNotification(false) , blackList(new Kopete::BlackLister(protocol->pluginId(), accountId)) { } ~Private() { delete blackList; } QPointer protocol; QString id; QString accountLabel; bool excludeconnect; uint priority; QHash contacts; QColor color; uint connectionTry; QPointer identity; QPointer myself; QTimer suppressStatusTimer; QTimer reconnectTimer; bool reconnectOnNetworkIsOnline; bool suppressStatusNotification; Kopete::BlackLister *blackList; KConfigGroup *configGroup; QString customIcon; Kopete::OnlineStatus restoreStatus; Kopete::StatusMessage restoreMessage; QDateTime lastLoginTime; bool suspended; Kopete::OnlineStatus suspendStatus; }; Account::Account(Protocol *parent, const QString &accountId) : QObject(parent) , d(new Private(parent, accountId)) { d->configGroup = new KConfigGroup(KSharedConfig::openConfig(), QStringLiteral("Account_%1_%2").arg(d->protocol->pluginId(), d->id)); d->excludeconnect = d->configGroup->readEntry("ExcludeConnect", false); d->color = d->configGroup->readEntry("Color", QColor()); d->customIcon = d->configGroup->readEntry("Icon", QString()); d->priority = d->configGroup->readEntry("Priority", 0); d->restoreStatus = Kopete::OnlineStatus::Online; d->restoreMessage = Kopete::StatusMessage(); d->reconnectOnNetworkIsOnline = false; d->reconnectTimer.setSingleShot(true); QObject::connect(&d->reconnectTimer, SIGNAL(timeout()), this, SLOT(reconnect())); QObject::connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)), this, SLOT(networkingStatusChanged(Solid::Networking::Status))); QObject::connect(&d->suppressStatusTimer, SIGNAL(timeout()), this, SLOT(slotStopSuppression())); d->suspended = false; d->suspendStatus = Kopete::OnlineStatus::Offline; } Account::~Account() { // Delete all registered child contacts first foreach (Contact *c, d->contacts) { QObject::disconnect(c, SIGNAL(contactDestroyed(Kopete::Contact *)), this, 0); } qDeleteAll(d->contacts); d->contacts.clear(); qCDebug(LIBKOPETE_LOG) << " account '" << d->id << "' about to emit accountDestroyed "; emit accountDestroyed(this); delete d->myself; delete d->configGroup; delete d; } void Account::reconnect() { if (isConnected()) { return; // Already connected } qCDebug(LIBKOPETE_LOG) << "account " << d->id << " restoreStatus " << d->restoreStatus.status() << " restoreTitle " << d->restoreMessage.title() << " restoreMessage " << d->restoreMessage.message(); setOnlineStatus(d->restoreStatus, d->restoreMessage); } void Account::networkingStatusChanged(const Solid::Networking::Status status) { switch (status) { case Solid::Networking::Connected: if (d->reconnectOnNetworkIsOnline && !excludeConnect()) { reconnect(); } break; case Solid::Networking::Unconnected: case Solid::Networking::Disconnecting: setOnlineStatus(OnlineStatus::Offline); if (Kopete::BehaviorSettings::self()->reconnectOnDisconnect()) { d->reconnectOnNetworkIsOnline = true; } break; case Solid::Networking::Unknown: case Solid::Networking::Connecting: break; } } void Account::disconnected(DisconnectReason reason) { qCDebug(LIBKOPETE_LOG) << reason; //reconnect if needed if (reason == BadPassword) { d->reconnectTimer.start(0); } else if (Kopete::BehaviorSettings::self()->reconnectOnDisconnect() == true && reason > Manual) { bool networkAvailable = (Solid::Networking::status() == Solid::Networking::Unknown || Solid::Networking::status() == Solid::Networking::Connected); if (reason == ConnectionReset && !networkAvailable) { d->reconnectOnNetworkIsOnline = true; d->reconnectTimer.stop(); } else { if (d->reconnectTimer.isActive()) { return; // In case protocol calls disconnected more than one time on disconnect. } d->connectionTry++; //use a timer to allow the plugins to clean up after return if (d->connectionTry < 3) { d->reconnectTimer.start(10000); // wait 10 seconds before reconnect } else if (d->connectionTry <= 10) { d->reconnectTimer.start(((2 * (d->connectionTry - 2)) - 1) * 60000); // wait 1,3,5...15 minutes => stops after 64 min } } } else { d->reconnectOnNetworkIsOnline = false; d->reconnectTimer.stop(); } if (reason == OtherClient) { Kopete::Utils::notifyConnectionLost(this, i18n("You have been disconnected"), i18n("You have connected from another client or computer to the account '%1'", d->id), i18n( "Most proprietary Instant Messaging services do not allow you to connect from more than one location. Check that nobody is using your account without your permission. If you need a service that supports connection from various locations at the same time, use the Jabber protocol.")); } } Protocol *Account::protocol() const { return d->protocol; } QString Account::accountId() const { return d->id; } const QColor Account::color() const { return d->color; } void Account::setColor(const QColor &color) { d->color = color; if (d->color.isValid()) { d->configGroup->writeEntry("Color", d->color); } else { d->configGroup->deleteEntry("Color"); } emit colorChanged(color); } void Account::setPriority(uint priority) { d->priority = priority; d->configGroup->writeEntry("Priority", d->priority); } uint Account::priority() const { return d->priority; } QPixmap Account::accountIcon(const int size) const { QString icon = d->customIcon.isEmpty() ? d->protocol->pluginIcon() : d->customIcon; // FIXME: this code is duplicated with OnlineStatus, can we merge it somehow? QPixmap base = KIconLoader::global()->loadIcon( icon, KIconLoader::Small, size); if (d->color.isValid()) { KIconEffect effect; base = effect.apply(base, KIconEffect::Colorize, 1, d->color, 0); } if (size > 0 && base.width() != size) { base.scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } return base; } QString Account::accountIconPath(const KIconLoader::Group size) const { return KIconLoader::global()->iconPath(d->customIcon.isEmpty() ? d->protocol->pluginIcon() : d->customIcon, size); } KConfigGroup *Kopete::Account::configGroup() const { return d->configGroup; } void Account::setAccountLabel(const QString &label) { d->accountLabel = label; } QString Account::accountLabel() const { if (d->accountLabel.isNull()) { return d->id; } return d->accountLabel; } void Account::setExcludeConnect(bool b) { d->excludeconnect = b; d->configGroup->writeEntry("ExcludeConnect", d->excludeconnect); } bool Account::excludeConnect() const { return d->excludeconnect; } bool Account::registerContact(Contact *c) { Q_ASSERT(c->metaContact() != Kopete::ContactList::self()->myself()); if (d->contacts.value(c->contactId())) { qCWarning(LIBKOPETE_LOG) << "Contact already exists!!! accountId: " << c->account() << " contactId: " << c->contactId(); return false; } d->contacts.insert(c->contactId(), c); QObject::connect(c, SIGNAL(contactDestroyed(Kopete::Contact *)), SLOT(contactDestroyed(Kopete::Contact *))); return true; } void Account::contactDestroyed(Contact *c) { d->contacts.remove(c->contactId()); } const QHash &Account::contacts() { return d->contacts; } Kopete::MetaContact *Account::addContact(const QString &contactId, const QString &displayName, Group *group, AddMode mode) { if (!protocol()->canAddMyself() && contactId == d->myself->contactId()) { if (isConnected() && d->lastLoginTime.secsTo(QDateTime::currentDateTime()) > 30) { KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), KMessageBox::Error, i18n("You are not allowed to add yourself to the contact list. The addition of \"%1\" to account \"%2\" will not take place.", contactId, accountId()), i18n("Error Creating Contact") ); } else { qCWarning(LIBKOPETE_LOG) << "You are not allowed to add yourself to the contact list. The addition of" << contactId << "to account" << accountId() << "will not take place."; } return 0; } bool isTemporary = (mode == Temporary); Contact *c = d->contacts.value(contactId); if (!group) { group = Group::topLevel(); } if (c && c->metaContact()) { if (c->metaContact()->isTemporary() && !isTemporary) { qCDebug(LIBKOPETE_LOG) << " You are trying to add an existing temporary contact. Just add it on the list"; c->metaContact()->setTemporary(false, group); ContactList::self()->addMetaContact(c->metaContact()); } else { // should we here add the contact to the parentContact if any? qCDebug(LIBKOPETE_LOG) << "Contact already exists"; } return c->metaContact(); } MetaContact *parentContact = new MetaContact(); if (!displayName.isEmpty()) { parentContact->setDisplayName(displayName); } //Set it as a temporary contact if requested if (isTemporary) { parentContact->setTemporary(true); } else { parentContact->addToGroup(group); } if (c) { c->setMetaContact(parentContact); if (mode == ChangeKABC) { qCDebug(LIBKOPETE_LOG) << " changing KABC"; KABCPersistence::self()->write(parentContact); } } else { if (!createContact(contactId, parentContact)) { delete parentContact; return nullptr; } } ContactList::self()->addMetaContact(parentContact); return parentContact; } bool Account::addContact(const QString &contactId, MetaContact *parent, AddMode mode) { if (!protocol()->canAddMyself() && contactId == myself()->contactId()) { if (isConnected() && d->lastLoginTime.secsTo(QDateTime::currentDateTime()) > 30) { KMessageBox::queuedMessageBox(Kopete::UI::Global::mainWidget(), KMessageBox::Error, i18n("You are not allowed to add yourself to the contact list. The addition of \"%1\" to account \"%2\" will not take place.", contactId, accountId()), i18n("Error Creating Contact") ); } else { qCWarning(LIBKOPETE_LOG) << "You are not allowed to add yourself to the contact list. The addition of" << contactId << "to account" << accountId() << "will not take place."; } return false; } const bool isTemporary = parent->isTemporary(); Contact *c = d->contacts.value(contactId); if (c && c->metaContact()) { if (c->metaContact()->isTemporary() && !isTemporary) { qCDebug(LIBKOPETE_LOG) <<"Account::addContact: You are trying to add an existing temporary contact. Just add it on the list" << endl; //setMetaContact ill take care about the deletion of the old contact c->setMetaContact(parent); return true; } else { // should we here add the contact to the parentContact if any? qCDebug(LIBKOPETE_LOG) << "Account::addContact: Contact already exists"; } return false; //(the contact is not in the correct metacontact, so false) } const bool success = createContact(contactId, parent); if (success && mode == ChangeKABC) { qCDebug(LIBKOPETE_LOG) << " changing KABC"; KABCPersistence::self()->write(parent); } return success; } void Account::fillActionMenu(KActionMenu *actionMenu) { //default implementation -// KActionMenu *menu = new KActionMenu( QIcon(myself()->onlineStatus().iconFor( this )), accountId(), 0, 0); + KActionMenu *menu = new KActionMenu( QIcon(myself()->onlineStatus().iconFor( this )), accountId(), this); #ifdef __GNUC__ #warning No icon shown, we should go away from QPixmap genered icons with overlays. #endif QString nick; if (identity()->hasProperty(Kopete::Global::Properties::self()->nickName().key())) { nick = identity()->property(Kopete::Global::Properties::self()->nickName()).value().toString(); } else { nick = myself()->displayName(); } // Always add title at the beginning of actionMenu QAction *before = actionMenu->menu()->actions().value(0, 0); actionMenu->menu()->setTitle(nick.isNull() ? accountLabel() : i18n("%2 <%1>", accountLabel(), nick)); actionMenu->menu()->setIcon(myself()->onlineStatus().iconFor(myself())); actionMenu->menu()->setDefaultAction(before); actionMenu->menu()->addSeparator(); QAction *propertiesAction = new QAction(i18n("Properties"), actionMenu); QObject::connect(propertiesAction, SIGNAL(triggered(bool)), this, SLOT(editAccount())); actionMenu->addAction(propertiesAction); } bool Account::hasCustomStatusMenu() const { return false; } bool Account::isConnected() const { return myself() && myself()->isOnline(); } bool Account::isAway() const { //We might want to change the method name here. return d->myself && (d->myself->onlineStatus().status() == Kopete::OnlineStatus::Away || d->myself->onlineStatus().status() == Kopete::OnlineStatus::Busy); } bool Account::isBusy() const { return d->myself && (d->myself->onlineStatus().status() == Kopete::OnlineStatus::Busy); } Identity *Account::identity() const { return d->identity; } bool Account::setIdentity(Identity *ident) { if (d->identity == ident) { return false; } if (d->identity) { d->identity->removeAccount(this); } ident->addAccount(this); d->identity = ident; d->configGroup->writeEntry("Identity", ident->id()); return true; } Contact *Account::myself() const { return d->myself; } void Account::setMyself(Contact *myself) { Q_ASSERT(!d->myself); d->myself = myself; QObject::connect(d->myself, SIGNAL(onlineStatusChanged(Kopete::Contact *,Kopete::OnlineStatus,Kopete::OnlineStatus)), this, SLOT(slotOnlineStatusChanged(Kopete::Contact *,Kopete::OnlineStatus,Kopete::OnlineStatus))); QObject::connect(d->myself, SIGNAL(propertyChanged(Kopete::PropertyContainer *,QString,QVariant,QVariant)), this, SLOT(slotContactPropertyChanged(Kopete::PropertyContainer *,QString,QVariant,QVariant))); if (isConnected()) { emit isConnectedChanged(); } } void Account::slotOnlineStatusChanged(Contact * /* contact */, const OnlineStatus &newStatus, const OnlineStatus &oldStatus) { const bool wasOffline = !oldStatus.isDefinitelyOnline(); const bool isOffline = !newStatus.isDefinitelyOnline(); d->suspended = false; if (wasOffline && !isOffline) { d->lastLoginTime = QDateTime::currentDateTime(); } // If we went offline we have to ensure that all of our contacts // are online too. Otherwise the "Last Seen" tooltip won't work // properly. See bug 266580. if (!wasOffline && isOffline) { setAllContactsStatus(Kopete::OnlineStatus::Offline); } if (wasOffline || newStatus.status() == OnlineStatus::Offline) { // Wait for twenty seconds until we treat status notifications for contacts // as unrelated to our own status change. // Twenty seconds may seem like a long time, but just after your own // connection it's basically neglectible, and depending on your own // contact list's size, the protocol you are using, your internet // connection's speed and your computer's speed you *will* need it. d->suppressStatusNotification = true; d->suppressStatusTimer.setSingleShot(true); d->suppressStatusTimer.start(20000); //the timer is also used to reset the d->connectionTry } if (!isOffline) { d->restoreStatus = newStatus; d->restoreMessage.setTitle(myself()->property(Kopete::Global::Properties::self()->statusTitle()).value().toString()); d->restoreMessage.setMessage(myself()->property(Kopete::Global::Properties::self()->statusMessage()).value().toString()); } /* qCDebug(LIBKOPETE_LOG) << "account " << d->id << " changed status. was " << Kopete::OnlineStatus::statusTypeToString(oldStatus.status()) << ", is " << Kopete::OnlineStatus::statusTypeToString(newStatus.status()) << endl;*/ if (wasOffline != isOffline) { emit isConnectedChanged(); } } bool Account::suspend(const Kopete::StatusMessage &reason) { if (d->suspended) { return false; } d->suspendStatus = myself()->onlineStatus(); if (myself()->onlineStatus().status() == OnlineStatus::Connecting) { d->suspendStatus = d->restoreStatus; } setOnlineStatus(OnlineStatus::Offline, reason); d->suspended = true; return true; } bool Account::resume() { if (!d->suspended) { return false; } if (d->suspendStatus != Kopete::OnlineStatus::Offline) { setOnlineStatus(d->suspendStatus, d->restoreMessage, Kopete::Account::None); } return true; } void Account::setAllContactsStatus(const Kopete::OnlineStatus &status) { d->suppressStatusNotification = true; d->suppressStatusTimer.setSingleShot(true); d->suppressStatusTimer.start(20000); QHashIterator it(d->contacts); for (; it.hasNext();) { it.next(); Contact *c = it.value(); if (c) { c->setOnlineStatus(status); } } } void Account::slotContactPropertyChanged(PropertyContainer * /* contact */, const QString &key, const QVariant &old, const QVariant &newVal) { if (key == Kopete::Global::Properties::self()->statusTitle().key() && old != newVal && isConnected()) { d->restoreMessage.setTitle(newVal.toString()); } else if (key == Kopete::Global::Properties::self()->statusMessage().key() && old != newVal && isConnected()) { d->restoreMessage.setMessage(newVal.toString()); } } void Account::slotStopSuppression() { d->suppressStatusNotification = false; if (isConnected()) { d->connectionTry = 0; } } bool Account::suppressStatusNotification() const { return d->suppressStatusNotification; } bool Account::removeAccount() { //default implementation return true; } BlackLister *Account::blackLister() { return d->blackList; } void Account::block(const QString &contactId) { d->blackList->addContact(contactId); } void Account::unblock(const QString &contactId) { d->blackList->removeContact(contactId); } bool Account::isBlocked(const QString &contactId) { return d->blackList->isBlocked(contactId); } void Account::editAccount(QWidget *parent) { QPointer editDialog = new QDialog(parent); editDialog->setWindowTitle(i18n("Edit Account")); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QVBoxLayout *mainLayout = new QVBoxLayout; editDialog->setLayout(mainLayout); KopeteEditAccountWidget *m_accountWidget = protocol()->createEditAccountWidget(this, editDialog); // FIXME: Why the #### is EditAccountWidget not a QWidget?!? This sideways casting // is braindead and error-prone. Looking at MSN the only reason I can see is // because it allows direct subclassing of designer widgets. But what is // wrong with embedding the designer widget in an empty QWidget instead? // Also, if this REALLY has to be a pure class and not a widget, then the // class should at least be renamed to EditAccountIface instead - Martijn QWidget *w = dynamic_cast(m_accountWidget); if (!w) { delete editDialog; return; } mainLayout->addWidget(w); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); QObject::connect(buttonBox, SIGNAL(accepted()), editDialog, SLOT(accept())); QObject::connect(buttonBox, SIGNAL(rejected()), editDialog, SLOT(reject())); mainLayout->addWidget(buttonBox); if (editDialog->exec() == QDialog::Accepted) { if (m_accountWidget->validateData()) { m_accountWidget->apply(); } } delete editDialog; } void Account::setCustomIcon(const QString &i) { d->customIcon = i; if (!i.isEmpty()) { d->configGroup->writeEntry("Icon", i); } else { d->configGroup->deleteEntry("Icon"); } emit colorChanged(color()); } QString Account::customIcon() const { return d->customIcon; } } // END namespace Kopete diff --git a/libkopete/kopetepluginmanager.cpp b/libkopete/kopetepluginmanager.cpp index 921449601..249d48390 100644 --- a/libkopete/kopetepluginmanager.cpp +++ b/libkopete/kopetepluginmanager.cpp @@ -1,479 +1,479 @@ /* kopetepluginmanager.cpp - Kopete Plugin Loader Copyright (c) 2002-2003 by Duncan Mac-Vicar Prett Copyright (c) 2002-2003 by Martijn Klingens Copyright (c) 2002-2004 by Olivier Goffart Kopete (c) 2002-2003 by the Kopete developers ************************************************************************* * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * ************************************************************************* */ #include "kopetepluginmanager.h" #if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) // We don't want the per-skin includes, so pretend we have a skin header already #define __VALGRIND_SOMESKIN_H #include #endif #include #include #include #include #include #include #include #include #include #include #include "kopeteplugin.h" #include "kopeteprotocol.h" #include "kopetecontactlist.h" #include "kopeteaccountmanager.h" namespace Kopete { class PluginManagerPrivate { public: PluginManagerPrivate() : shutdownMode(StartingUp) , isAllPluginsLoaded(false) { plugins = KPluginInfo::fromServices(KServiceTypeTrader::self()->query(QStringLiteral("Kopete/Plugin"), QStringLiteral("[X-Kopete-Version] == 1000900"))); } ~PluginManagerPrivate() { if (shutdownMode != DoneShutdown && !loadedPlugins.empty()) { qCWarning(LIBKOPETE_LOG) << "Destructing plugin manager without going through the shutdown process! Backtrace is: " << endl << kBacktrace(); } // Clean up loadedPlugins manually, because PluginManager can't access our global // static once this destructor has started. while (!loadedPlugins.empty()) { InfoToPluginMap::ConstIterator it = loadedPlugins.constBegin(); qCWarning(LIBKOPETE_LOG) << "Deleting stale plugin '" << it.value()->objectName() << "'"; KPluginInfo info = it.key(); Plugin *plugin = it.value(); loadedPlugins.remove(info); plugin->disconnect(&instance, SLOT(slotPluginDestroyed(QObject *))); delete plugin; } } // All available plugins, regardless of category, and loaded or not QList plugins; // Dict of all currently loaded plugins, mapping the KPluginInfo to // a plugin typedef QMap InfoToPluginMap; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during Kopete shutdown by the // async unloading of plugins. enum ShutdownMode { StartingUp, Running, ShuttingDown, DoneShutdown }; ShutdownMode shutdownMode; // Plugins pending for loading QStack pluginsToLoad; bool isAllPluginsLoaded; PluginManager instance; }; -K_GLOBAL_STATIC(PluginManagerPrivate, _kpmp) +Q_GLOBAL_STATIC(PluginManagerPrivate, _kpmp) PluginManager* PluginManager::self() { return &_kpmp->instance; } PluginManager::PluginManager() : QObject(0) { // We want to add a reference to the application's event loop so we // can remain in control when all windows are removed. // This way we can unload plugins asynchronously, which is more // robust if they are still doing processing. KGlobal::ref(); } PluginManager::~PluginManager() { } QList PluginManager::availablePlugins(const QString &category) const { if (category.isEmpty()) { return _kpmp->plugins; } QList result; QList::ConstIterator it; for (it = _kpmp->plugins.constBegin(); it != _kpmp->plugins.constEnd(); ++it) { if (it->category() == category && !(*it).service()->noDisplay()) { result.append(*it); } } return result; } PluginList PluginManager::loadedPlugins(const QString &category) const { PluginList result; for (PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin(); it != _kpmp->loadedPlugins.constEnd(); ++it) { if (category.isEmpty() || it.key().category() == category) { result.append(it.value()); } } return result; } KPluginInfo PluginManager::pluginInfo(const Plugin *plugin) const { for (PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin(); it != _kpmp->loadedPlugins.constEnd(); ++it) { if (it.value() == plugin) { return it.key(); } } return KPluginInfo(); } void PluginManager::shutdown() { if (_kpmp->shutdownMode != PluginManagerPrivate::Running) { qCDebug(LIBKOPETE_LOG) << "called when not running. / state = " << _kpmp->shutdownMode; return; } _kpmp->shutdownMode = PluginManagerPrivate::ShuttingDown; // Remove any pending plugins to load, we're shutting down now :) _kpmp->pluginsToLoad.clear(); // Ask all plugins to unload for (PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin(); it != _kpmp->loadedPlugins.constEnd(); /* EMPTY */) { // Plugins could emit their ready for unload signal directly in response to this, // which would invalidate the current iterator. Therefore, we copy the iterator // and increment it beforehand. PluginManagerPrivate::InfoToPluginMap::ConstIterator current(it); ++it; // FIXME: a much cleaner approach would be to just delete the plugin now. if it needs // to do some async processing, it can grab a reference to the app itself and create // another object to do it. current.value()->aboutToUnload(); } // save the contact list now, just in case a change was made very recently // and it hasn't autosaved yet // from a OO point of view, theses lines should not be there, but i don't // see better place -Olivier Kopete::ContactList::self()->shutdown(); // Save and shutdown contact list Kopete::AccountManager::self()->save(); // When running under valgrind, don't enable the timer because it will almost // certainly fire due to valgrind's much slower processing #if defined(HAVE_VALGRIND_H) && !defined(NDEBUG) && defined(__i386__) if (RUNNING_ON_VALGRIND) { qCDebug(LIBKOPETE_LOG) << "Running under valgrind, disabling plugin unload timeout guard"; } else #endif QTimer::singleShot(3000, this, SLOT(slotShutdownTimeout())); } void PluginManager::slotPluginReadyForUnload() { // Using QObject::sender() is on purpose here, because otherwise all // plugins would have to pass 'this' as parameter, which makes the API // less clean for plugin authors // FIXME: I don't buy the above argument. Add a Kopete::Plugin::emitReadyForUnload(void), // and make readyForUnload be passed a plugin. - Richard Plugin *plugin = dynamic_cast(const_cast(sender())); if (!plugin) { qCWarning(LIBKOPETE_LOG) << "Calling object is not a plugin!"; return; } qCDebug(LIBKOPETE_LOG) << plugin->pluginId() << "ready for unload"; plugin->deleteLater(); } void PluginManager::slotShutdownTimeout() { // When we were already done the timer might still fire. // Do nothing in that case. if (_kpmp->shutdownMode == PluginManagerPrivate::DoneShutdown) { return; } QStringList remaining; for (PluginManagerPrivate::InfoToPluginMap::ConstIterator it = _kpmp->loadedPlugins.constBegin(); it != _kpmp->loadedPlugins.constEnd(); ++it) { remaining.append(it.value()->pluginId()); } qCWarning(LIBKOPETE_LOG) << "Some plugins didn't shutdown in time!" << endl << "Remaining plugins: " << remaining.join(QLatin1String(", ")) << endl << "Forcing Kopete shutdown now." << endl; slotShutdownDone(); } void PluginManager::slotShutdownDone() { qCDebug(LIBKOPETE_LOG); _kpmp->shutdownMode = PluginManagerPrivate::DoneShutdown; KGlobal::deref(); } void PluginManager::loadAllPlugins() { // FIXME: We need session management here - Martijn KSharedConfig::Ptr config = KSharedConfig::openConfig(); if (config->hasGroup(QStringLiteral("Plugins"))) { QMap pluginsMap; QMap entries = config->entryMap(QStringLiteral("Plugins")); QMap::Iterator it; for (it = entries.begin(); it != entries.end(); ++it) { QString key = it.key(); if (key.endsWith(QLatin1String("Enabled"))) { pluginsMap.insert(key.left(key.length() - 7), (it.value() == QLatin1String("true"))); } } QList plugins = availablePlugins(QString()); QList::ConstIterator it2 = plugins.constBegin(); QList::ConstIterator end = plugins.constEnd(); for (; it2 != end; ++it2) { // Protocols are loaded automatically so they aren't always in Plugins group. (fixes bug 167113) if (it2->category() == QLatin1String("Protocols")) { continue; } QString pluginName = it2->pluginName(); if (pluginsMap.value(pluginName, it2->isPluginEnabledByDefault())) { if (!plugin(pluginName)) { _kpmp->pluginsToLoad.push(pluginName); } } else { //This happens if the user unloaded plugins with the config plugin page. // No real need to be assync because the user usually unload few plugins // compared tto the number of plugin to load in a cold start. - Olivier if (plugin(pluginName)) { unloadPlugin(pluginName); } } } } else { // we had no config, so we load any plugins that should be loaded by default. QList plugins = availablePlugins(QString()); QList::ConstIterator it = plugins.constBegin(); QList::ConstIterator end = plugins.constEnd(); for (; it != end; ++it) { if (it->isPluginEnabledByDefault()) { _kpmp->pluginsToLoad.push(it->pluginName()); } } } // Schedule the plugins to load QTimer::singleShot(0, this, SLOT(slotLoadNextPlugin())); } void PluginManager::slotLoadNextPlugin() { if (_kpmp->pluginsToLoad.isEmpty()) { if (_kpmp->shutdownMode == PluginManagerPrivate::StartingUp) { _kpmp->shutdownMode = PluginManagerPrivate::Running; _kpmp->isAllPluginsLoaded = true; emit allPluginsLoaded(); } return; } QString key = _kpmp->pluginsToLoad.pop(); loadPluginInternal(key); // Schedule the next run unconditionally to avoid code duplication on the // allPluginsLoaded() signal's handling. This has the added benefit that // the signal is delayed one event loop, so the accounts are more likely // to be instantiated. QTimer::singleShot(0, this, SLOT(slotLoadNextPlugin())); } Plugin *PluginManager::loadPlugin(const QString &_pluginId, PluginLoadMode mode /* = LoadSync */) { QString pluginId = _pluginId; // Try to find legacy code // FIXME: Find any cases causing this, remove them, and remove this too - Richard if (pluginId.endsWith(QLatin1String(".desktop"))) { qCWarning(LIBKOPETE_LOG) << "Trying to use old-style API!" << endl << kBacktrace(); pluginId = pluginId.remove(QRegExp(QLatin1String(".desktop$"))); } if (mode == LoadSync) { return loadPluginInternal(pluginId); } else { _kpmp->pluginsToLoad.push(pluginId); QTimer::singleShot(0, this, SLOT(slotLoadNextPlugin())); return nullptr; } } Plugin *PluginManager::loadPluginInternal(const QString &pluginId) { //qCDebug(LIBKOPETE_LOG) << pluginId; KPluginInfo info = infoForPluginId(pluginId); if (!info.isValid()) { qCWarning(LIBKOPETE_LOG) << "Unable to find a plugin named '" << pluginId << "'!"; return nullptr; } if (_kpmp->loadedPlugins.contains(info)) { return _kpmp->loadedPlugins[ info ]; } QString error; Plugin *plugin = KServiceTypeTrader::createInstanceFromQuery(QStringLiteral("Kopete/Plugin"), QStringLiteral("[X-KDE-PluginInfo-Name]=='%1'").arg(pluginId), this, QVariantList(), &error); if (plugin) { _kpmp->loadedPlugins.insert(info, plugin); info.setPluginEnabled(true); connect(plugin, SIGNAL(destroyed(QObject *)), this, SLOT(slotPluginDestroyed(QObject *))); connect(plugin, SIGNAL(readyForUnload()), this, SLOT(slotPluginReadyForUnload())); qCDebug(LIBKOPETE_LOG) << "Successfully loaded plugin '" << pluginId << "'"; emit pluginLoaded(plugin); Protocol *protocol = dynamic_cast(plugin); if (protocol) { emit protocolLoaded(protocol); } } else { qCDebug(LIBKOPETE_LOG) << "Loading plugin " << pluginId << " failed, KServiceTypeTrader reported error: " << error; } return plugin; } bool PluginManager::unloadPlugin(const QString &spec) { //qCDebug(LIBKOPETE_LOG) << spec; if (Plugin *thePlugin = plugin(spec)) { thePlugin->aboutToUnload(); return true; } else { return false; } } void PluginManager::slotPluginDestroyed(QObject *plugin) { for (PluginManagerPrivate::InfoToPluginMap::Iterator it = _kpmp->loadedPlugins.begin(); it != _kpmp->loadedPlugins.end(); ++it) { if (it.value() == plugin) { QString pluginName = it.key().pluginName(); _kpmp->loadedPlugins.erase(it); emit pluginUnloaded(pluginName); break; } } if (_kpmp->shutdownMode == PluginManagerPrivate::ShuttingDown && _kpmp->loadedPlugins.isEmpty()) { // Use a timer to make sure any pending deleteLater() calls have // been handled first QTimer::singleShot(0, this, SLOT(slotShutdownDone())); } } Plugin *PluginManager::plugin(const QString &_pluginId) const { // Hack for compatibility with Plugin::pluginId(), which returns // classname() instead of the internal name. Changing that is not easy // as it invalidates the config file, the contact list, and most likely // other code as well. // For now, just transform FooProtocol to kopete_foo. // FIXME: In the future we'll need to change this nevertheless to unify // the handling - Martijn QString pluginId = _pluginId; if (pluginId.endsWith(QLatin1String("Protocol"))) { pluginId = QLatin1String("kopete_") + _pluginId.toLower().remove(QStringLiteral("protocol")); } // End hack KPluginInfo info = infoForPluginId(pluginId); if (!info.isValid()) { return nullptr; } if (_kpmp->loadedPlugins.contains(info)) { return _kpmp->loadedPlugins[ info ]; } else { return nullptr; } } KPluginInfo PluginManager::infoForPluginId(const QString &pluginId) const { QList::ConstIterator it; for (it = _kpmp->plugins.constBegin(); it != _kpmp->plugins.constEnd(); ++it) { if (it->pluginName() == pluginId) { return *it; } } return KPluginInfo(); } bool PluginManager::setPluginEnabled(const QString &_pluginId, bool enabled /* = true */) { QString pluginId = _pluginId; KConfigGroup config(KSharedConfig::openConfig(), "Plugins"); // FIXME: What is this for? This sort of thing is kconf_update's job - Richard if (!pluginId.startsWith(QLatin1String("kopete_"))) { pluginId.prepend(QLatin1String("kopete_")); } if (!infoForPluginId(pluginId).isValid()) { return false; } config.writeEntry(pluginId + QLatin1String("Enabled"), enabled); config.sync(); return true; } bool PluginManager::isAllPluginsLoaded() const { return _kpmp->isAllPluginsLoaded; } } //END namespace Kopete diff --git a/plugins/latex/latexguiclient.cpp b/plugins/latex/latexguiclient.cpp index 8dc1ae2e3..3f3a42c31 100644 --- a/plugins/latex/latexguiclient.cpp +++ b/plugins/latex/latexguiclient.cpp @@ -1,80 +1,80 @@ /* latexguiclient.cpp - Kopete LaTeX plugin Copyright (c) 2003-2005 by Olivier Goffart Kopete (c) 2003-2005 by the Kopete developers ************************************************************************* * * * 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 "latexguiclient.h" #include #include #include #include #include #include #include #include #include "kopetechatsession.h" #include "kopeteview.h" #include "kopetemessage.h" #include "latexplugin.h" #include LatexGUIClient::LatexGUIClient(Kopete::ChatSession *parent) : QObject(parent) , KXMLGUIClient(parent) { - setComponentName(QStringLiteral("kopete"), i18n("Kopete")); + setComponentName(QStringLiteral("kopete_latex"), i18n("Kopete")); connect(LatexPlugin::plugin(), &QObject::destroyed, this, &QObject::deleteLater); m_manager = parent; QAction *previewAction = new QAction(QIcon::fromTheme(QStringLiteral("latex")), i18n("Preview Latex Images"), this); actionCollection()->addAction(QStringLiteral("latexPreview"), previewAction); actionCollection()->setDefaultShortcut(previewAction, QKeySequence(Qt::CTRL + Qt::Key_L)); connect(previewAction, &QAction::triggered, this, &LatexGUIClient::slotPreview); setXMLFile(QStringLiteral("latexchatui.rc")); } LatexGUIClient::~LatexGUIClient() { } void LatexGUIClient::slotPreview() { if (!m_manager->view()) { return; } Kopete::Message msg = m_manager->view()->currentMessage(); const QString messageText = msg.plainBody(); if (!messageText.contains(QLatin1String("$$"))) { //we haven't found any latex strings KMessageBox::sorry(m_manager->view()->mainWidget(), i18n("The message you are typing does not contain any LaTeX. A LaTeX formula must be enclosed within two pairs of dollar signs: $$formula$$ "), i18n("No LaTeX Formula")); return; } const QString oldBody = msg.escapedBody(); msg = Kopete::Message(msg.from(), msg.to()); msg.setHtmlBody(i18n("Preview of the LaTeX message :
%1", oldBody)); msg.setDirection(Kopete::Message::Internal); m_manager->appendMessage(msg); } // vim: set noet ts=4 sts=4 sw=4: