diff --git a/data/konversationui.rc b/data/konversationui.rc index 171d32ae..1f33a21d 100644 --- a/data/konversationui.rc +++ b/data/konversationui.rc @@ -1,79 +1,83 @@ - + + + + + &Insert &Window diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0f88743c..40423d29 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,903 +1,930 @@ /* 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. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005 Ismail Donmez Copyright (C) 2005 Peter Simonsson Copyright (C) 2005 John Tapsell Copyright (C) 2005-2008 Eike Hein */ #include "mainwindow.h" #include "application.h" #include "settingsdialog.h" #include "viewcontainer.h" #include "statusbar.h" #include "bookmarkhandler.h" #include "trayicon.h" #include "serverlistdialog.h" #include "identitydialog.h" #include "notificationhandler.h" #include "irccharsets.h" #include "connectionmanager.h" #include "awaymanager.h" #include "transfermanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include MainWindow::MainWindow() : KXmlGuiWindow(0) { m_hasDirtySettings = false; m_closeApp = false; m_serverListDialog = 0; m_trayIcon = 0; m_settingsDialog = NULL; m_viewContainer = new ViewContainer(this); setCentralWidget(m_viewContainer->getWidget()); //used for event compression. See header file for resetHasDirtySettings() connect(Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(resetHasDirtySettings())); connect(Application::instance(), SIGNAL(appearanceChanged()), this, SLOT(updateTrayIcon())); // Set up view container connect(Application::instance(), SIGNAL(appearanceChanged()), m_viewContainer, SLOT(updateAppearance())); connect(Application::instance(), SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), m_viewContainer, SLOT(updateViews(Konversation::ServerGroupSettingsPtr))); connect(m_viewContainer, SIGNAL(autoJoinToggled(Konversation::ServerGroupSettingsPtr)), Application::instance(), SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr))); connect(m_viewContainer, SIGNAL(autoConnectOnStartupToggled(Konversation::ServerGroupSettingsPtr)), Application::instance(), SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr))); connect(m_viewContainer, SIGNAL(setWindowCaption(QString)), this, SLOT(setCaption(QString))); connect(Application::instance()->getConnectionManager(), SIGNAL(connectionChangedState(Server*,Konversation::ConnectionState)), m_viewContainer, SLOT(connectionStateChanged(Server*,Konversation::ConnectionState))); connect(this, SIGNAL(triggerRememberLine()), m_viewContainer, SLOT(insertRememberLine())); connect(this, SIGNAL(triggerRememberLines(Server*)), m_viewContainer, SLOT(insertRememberLines(Server*))); connect(this, SIGNAL(cancelRememberLine()), m_viewContainer, SLOT(cancelRememberLine())); connect(this, SIGNAL(insertMarkerLine()), m_viewContainer, SLOT(insertMarkerLine())); // Set up status bar m_statusBar = new Konversation::StatusBar(this); connect(Application::instance(), SIGNAL(appearanceChanged()), m_statusBar, SLOT(updateAppearance())); createStandardStatusBarAction(); connect(m_viewContainer, SIGNAL(resetStatusBar()), m_statusBar, SLOT(resetStatusBar())); connect(m_viewContainer, SIGNAL(setStatusBarTempText(QString)), m_statusBar, SLOT(setMainLabelTempText(QString))); connect(m_viewContainer, SIGNAL(clearStatusBarTempText()), m_statusBar, SLOT(clearMainLabelTempText())); connect(m_viewContainer, SIGNAL(setStatusBarInfoLabel(QString)), m_statusBar, SLOT(updateInfoLabel(QString))); connect(m_viewContainer, SIGNAL(clearStatusBarInfoLabel()), m_statusBar, SLOT(clearInfoLabel())); connect(m_viewContainer, SIGNAL(setStatusBarLagLabelShown(bool)), m_statusBar, SLOT(setLagLabelShown(bool))); connect(m_viewContainer, SIGNAL(updateStatusBarLagLabel(Server*,int)), m_statusBar, SLOT(updateLagLabel(Server*,int))); connect(m_viewContainer, SIGNAL(resetStatusBarLagLabel(Server*)), m_statusBar, SLOT(resetLagLabel(Server*))); connect(m_viewContainer, SIGNAL(setStatusBarLagLabelTooLongLag(Server*,int)), m_statusBar, SLOT(setTooLongLag(Server*,int))); connect(m_viewContainer, SIGNAL(updateStatusBarSSLLabel(Server*)), m_statusBar, SLOT(updateSSLLabel(Server*))); connect(m_viewContainer, SIGNAL(removeStatusBarSSLLabel()), m_statusBar, SLOT(removeSSLLabel())); // Actions KStandardAction::quit(this,SLOT(quitProgram()),actionCollection()); m_showMenuBarAction = KStandardAction::showMenubar(this, SLOT(toggleMenubar()), actionCollection()); setStandardToolBarMenuEnabled(true); KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection()); KStandardAction::keyBindings(this, SLOT(openKeyBindings()), actionCollection()); KStandardAction::preferences(this, SLOT(openPrefsDialog()), actionCollection()); KStandardAction::configureNotifications(this, SLOT(openNotifications()), actionCollection()); QAction* action; action=new QAction(this); action->setText(i18n("Restart")); action->setIcon(QIcon::fromTheme(QStringLiteral("system-reboot"))); action->setStatusTip(i18n("Quit and restart the application")); connect(action, SIGNAL(triggered()), Application::instance(), SLOT(restart())); actionCollection()->addAction(QStringLiteral("restart"), action); action=new QAction(this); action->setText(i18n("&Server List...")); action->setIcon(QIcon::fromTheme(QStringLiteral("network-server"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("F2"))); action->setStatusTip(i18n("Manage networks and servers")); connect(action, SIGNAL(triggered()), SLOT(openServerList())); actionCollection()->addAction(QStringLiteral("open_server_list"), action); action=new QAction(this); action->setText(i18n("Quick &Connect...")); action->setIcon(QIcon::fromTheme(QStringLiteral("network-connect"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F7"))); action->setStatusTip(i18n("Type in the address of a new IRC server to connect to")); connect(action, SIGNAL(triggered()), SLOT(openQuickConnectDialog())); actionCollection()->addAction(QStringLiteral("quick_connect_dialog"), action); action=new QAction(this); action->setText(i18n("&Reconnect")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh"))); action->setEnabled(false); action->setStatusTip(i18n("Reconnect to the current server.")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(reconnectFrontServer())); actionCollection()->addAction(QStringLiteral("reconnect_server"), action); action=new QAction(this); action->setText(i18n("&Disconnect")); action->setIcon(QIcon::fromTheme(QStringLiteral("network-disconnect"))); action->setEnabled(false); action->setStatusTip(i18n("Disconnect from the current server.")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(disconnectFrontServer())); actionCollection()->addAction(QStringLiteral("disconnect_server"), action); action=new QAction(this); action->setText(i18n("&Identities...")); action->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F8"))); action->setStatusTip(i18n("Manage your nick, away and other identity settings")); connect(action, SIGNAL(triggered()), SLOT(openIdentitiesDialog())); actionCollection()->addAction(QStringLiteral("identities_dialog"), action); action=new KToggleAction(this); action->setText(i18n("&Watched Nicks")); action->setIcon(QIcon::fromTheme(QStringLiteral("im-user"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F4"))); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openNicksOnlinePanel())); actionCollection()->addAction(QStringLiteral("open_nicksonline_window"), action); action=new KToggleAction(this); action->setText(i18n("&DCC Status")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right-double"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F9"))); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleDccPanel())); actionCollection()->addAction(QStringLiteral("open_dccstatus_window"), action); action=new QAction(this); action->setText(i18n("&Open Logfile")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+O"))); action->setEnabled(false); action->setStatusTip(i18n("Open the known history for this channel in a new tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openLogFile())); actionCollection()->addAction(QStringLiteral("open_logfile"), action); action=new QAction(this); action->setText(i18n("&Channel Settings...")); action->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); action->setEnabled(false); action->setStatusTip(i18n("Open the channel settings dialog for this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openChannelSettings())); actionCollection()->addAction(QStringLiteral("channel_settings"), action); action=new KToggleAction(this); action->setText(i18n("Channel &List")); action->setIcon(QIcon::fromTheme(QStringLiteral("view-list-text"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F5"))); action->setEnabled(false); action->setStatusTip(i18n("Show a list of all the known channels on this server")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(openChannelList())); actionCollection()->addAction(QStringLiteral("open_channel_list"), action); action=new KToggleAction(this); action->setText(i18n("&URL Catcher")); action->setIcon(QIcon::fromTheme(QStringLiteral("text-html"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("F6"))); action->setStatusTip(i18n("List all URLs that have been mentioned recently in a new tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(addUrlCatcher())); actionCollection()->addAction(QStringLiteral("open_url_catcher"), action); if (KAuthorized::authorize(QStringLiteral("shell_access"))) { action=new QAction(this); action->setText(i18n("New &Konsole")); action->setIcon(QIcon::fromTheme(QStringLiteral("utilities-terminal"))); action->setStatusTip(i18n("Open a terminal in a new tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(addKonsolePanel())); actionCollection()->addAction(QStringLiteral("open_konsole"), action); } // Actions to navigate through the different pages QList nextShortcut = KStandardShortcut::tabNext(); QList prevShortcut = KStandardShortcut::tabPrev(); QString nextIcon, prevIcon; if (QApplication::isRightToLeft()) { prevShortcut.append(QKeySequence(QStringLiteral("Alt+Right"))); nextShortcut.append(QKeySequence(QStringLiteral("Alt+Left"))); nextIcon=QStringLiteral("go-previous-view"); prevIcon=QStringLiteral("go-next-view"); } else { nextShortcut.append(QKeySequence(QStringLiteral("Alt+Right"))); prevShortcut.append(QKeySequence(QStringLiteral("Alt+Left"))); nextIcon=QStringLiteral("go-next-view"); prevIcon=QStringLiteral("go-previous-view"); } action=new QAction(this); action->setText(i18n("&Next Tab")); action->setIcon(QIcon::fromTheme(nextIcon)); actionCollection()->setDefaultShortcuts(action,nextShortcut); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showNextView())); actionCollection()->addAction(QStringLiteral("next_tab"), action); action=new QAction(this); action->setText(i18n("&Previous Tab")); action->setIcon(QIcon::fromTheme(prevIcon)); actionCollection()->setDefaultShortcuts(action, prevShortcut); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showPreviousView())); actionCollection()->addAction(QStringLiteral("previous_tab"), action); action=new QAction(this); action->setText(i18n("Close &Tab")); action->setIcon(QIcon::fromTheme(QStringLiteral("tab-close-other"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+w"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(closeCurrentView())); actionCollection()->addAction(QStringLiteral("close_tab"), action); action=new QAction(this); action->setText(i18n("Last Focused Tab")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Alt+Space"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showLastFocusedView())); actionCollection()->addAction(QStringLiteral("last_focused_tab"), action); action=new QAction(this); action->setText(i18n("Next Active Tab")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+Alt+Space"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showNextActiveView())); actionCollection()->addAction(QStringLiteral("next_active_tab"), action); KGlobalAccel::setGlobalShortcut(action, QList()); if (Preferences::self()->tabPlacement()==Preferences::Left) { action=new QAction(this); action->setText(i18n("Move Tab Up")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Alt+Shift+Left"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewLeft())); actionCollection()->addAction(QStringLiteral("move_tab_left"), action); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); action=new QAction(this); action->setText(i18n("Move Tab Down")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Right"))); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewRight())); actionCollection()->addAction(QStringLiteral("move_tab_right"), action); } else { if (QApplication::isRightToLeft()) { action=new QAction(this); action->setText(i18n("Move Tab Right")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Right"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewLeft())); actionCollection()->addAction(QStringLiteral("move_tab_left"), action); action=new QAction(this); action->setText(i18n("Move Tab Left")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Left"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewRight())); actionCollection()->addAction(QStringLiteral("move_tab_right"), action); } else { action=new QAction(this); action->setText(i18n("Move Tab Left")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-left"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Left"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewLeft())); actionCollection()->addAction(QStringLiteral("move_tab_left"), action); action=new QAction(this); action->setText(i18n("Move Tab Right")); action->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Alt+Shift+Right"))); action->setEnabled(false); action->setStatusTip(i18n("Move this tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(moveViewRight())); actionCollection()->addAction(QStringLiteral("move_tab_right"), action); } } action->setEnabled(false); action=new QAction(this); action->setText(i18n("Rejoin Channel")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(rejoinChannel())); actionCollection()->addAction(QStringLiteral("rejoin_channel"), action); action->setEnabled(false); action=new KToggleAction(this); action->setText(i18n("Enable Notifications")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleViewNotifications())); actionCollection()->addAction(QStringLiteral("tab_notifications"), action); action->setEnabled(false); action=new KToggleAction(this); action->setText(i18n("Join on Connect")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleAutoJoin())); actionCollection()->addAction(QStringLiteral("tab_autojoin"), action); action=new KToggleAction(this); action->setText(i18n("Connect at Startup")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(toggleConnectOnStartup())); actionCollection()->addAction(QStringLiteral("tab_autoconnect"), action); QStringList encodingDescs = Konversation::IRCCharsets::self()->availableEncodingDescriptiveNames(); encodingDescs.prepend(i18n("Default")); KSelectAction* selectAction = new KSelectAction(this); selectAction->setEditable(false); selectAction->setItems(encodingDescs); selectAction->setEnabled(false); selectAction->setText(i18n("Set Encoding")); selectAction->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); connect(selectAction, SIGNAL(triggered(int)), m_viewContainer, SLOT(changeViewCharset(int))); actionCollection()->addAction(QStringLiteral("tab_encoding"), selectAction); QSignalMapper* tabSelectionMapper = new QSignalMapper(this); connect(tabSelectionMapper, SIGNAL(mapped(int)), m_viewContainer, SLOT(goToView(int))); for (uint i = 1; i <= 10; ++i) { action=new QAction(this); action->setText(i18n("Go to Tab %1",i)); actionCollection()->setDefaultShortcut(action,QKeySequence(QString(QStringLiteral("Alt+%1")).arg(i%10))); connect(action, SIGNAL(triggered()), tabSelectionMapper, SLOT(map())); actionCollection()->addAction(QString(QStringLiteral("go_to_tab_%1")).arg(i), action); tabSelectionMapper->setMapping(action, i-1); } action=new QAction(this); action->setText(i18n("Clear &Marker Lines")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+Shift+R"))); action->setEnabled(false); action->setStatusTip(i18n("Clear marker lines in the current tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(clearViewLines())); actionCollection()->addAction(QStringLiteral("clear_lines"), action); + action=new QAction(this); + action->setText(i18n("Enlarge Font Size")); + actionCollection()->setDefaultShortcuts(action, KStandardShortcut::zoomIn()); + action->setEnabled(false); + action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-in"))); + action->setStatusTip(i18n("Increase the current font size")); + connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(zoomIn())); + actionCollection()->addAction(QStringLiteral("increase_font"), action); + + action=new QAction(this); + action->setText(i18n("Reset Font Size")); + actionCollection()->setDefaultShortcut(action, QKeySequence(QStringLiteral("Ctrl+0"))); + action->setEnabled(false); + action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-original"))); + action->setStatusTip(i18n("Reset the current font size to settings values")); + connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(resetFont())); + actionCollection()->addAction(QStringLiteral("reset_font"), action); + + action=new QAction(this); + action->setText(i18n("Decrease Font Size")); + actionCollection()->setDefaultShortcuts(action, KStandardShortcut::zoomOut()); + action->setEnabled(false); + action->setIcon(QIcon::fromTheme(QStringLiteral("zoom-out"))); + action->setStatusTip(i18n("Decrease the current font size")); + connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(zoomOut())); + actionCollection()->addAction(QStringLiteral("shrink_font"), action); + action=new QAction(this); action->setText(i18n("&Clear Window")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+L"))); action->setEnabled(false); action->setStatusTip(i18n("Clear the contents of the current tab")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(clearView())); actionCollection()->addAction(QStringLiteral("clear_window"), action); action=new QAction(this); action->setText(i18n("Clear &All Windows")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+Shift+L"))); action->setEnabled(false); action->setStatusTip(i18n("Clear the contents of all open tabs")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(clearAllViews())); actionCollection()->addAction(QStringLiteral("clear_tabs"), action); KToggleAction* awayAction = new KToggleAction(this); awayAction->setText(i18n("Global Away")); actionCollection()->setDefaultShortcut(awayAction,QKeySequence(QStringLiteral("Ctrl+Shift+A"))); awayAction->setEnabled(false); awayAction->setIcon(QIcon::fromTheme(QStringLiteral("im-user-away"))); connect(awayAction, SIGNAL(triggered(bool)), Application::instance()->getAwayManager(), SLOT(setGlobalAway(bool))); actionCollection()->addAction(QStringLiteral("toggle_away"), awayAction); action=new QAction(this); action->setText(i18n("&Join Channel...")); action->setIcon(QIcon::fromTheme(QStringLiteral("irc-join-channel"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+J"))); action->setEnabled(false); action->setStatusTip(i18n("Join a new channel on this server")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(showJoinChannelDialog())); actionCollection()->addAction(QStringLiteral("join_channel"), action); action = KStandardAction::find(m_viewContainer, SLOT(findText()), actionCollection()); action->setEnabled(false); action = KStandardAction::findNext(m_viewContainer, SLOT(findNextText()), actionCollection()); action->setEnabled(false); action = KStandardAction::findPrev(m_viewContainer, SLOT(findPrevText()), actionCollection()); action->setEnabled(false); action=new QAction(this); action->setText(i18n("&IRC Color...")); action->setIcon(QIcon::fromTheme(QStringLiteral("format-text-color"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+K"))); action->setEnabled(false); action->setStatusTip(i18n("Set the color of your current IRC message")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(insertIRCColor())); actionCollection()->addAction(QStringLiteral("irc_colors"), action); action=new QAction(this); action->setText(i18n("&Marker Line")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Ctrl+R"))); action->setEnabled(false); action->setStatusTip(i18n("Insert a horizontal line into the current tab that only you can see")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(insertMarkerLine())); actionCollection()->addAction(QStringLiteral("insert_marker_line"), action); action=new QAction(this); action->setText(i18n("Special &Character...")); action->setIcon(QIcon::fromTheme(QStringLiteral("character-set"))); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("Alt+Shift+C"))); action->setEnabled(false); action->setStatusTip(i18n("Insert any character into your current IRC message")); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(insertCharacter())); actionCollection()->addAction(QStringLiteral("insert_character"), action); action=new QAction(this); action->setText(i18n("Auto Replace")); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(doAutoReplace())); actionCollection()->addAction(QStringLiteral("auto_replace"), action); action=new QAction(this); action->setText(i18n("Focus Input Box")); actionCollection()->setDefaultShortcut(action,QKeySequence(Qt::Key_Escape)); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(focusInputBox())); actionCollection()->addAction(QStringLiteral("focus_input_box"), action); action=new QAction(this); action->setText(i18n("Close &All Open Queries")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F11"))); action->setEnabled(false); connect(action, SIGNAL(triggered()), m_viewContainer, SLOT(closeQueries())); actionCollection()->addAction(QStringLiteral("close_queries"), action); KToggleAction* toggleChannelNickListsAction = new KToggleAction(this); if (Preferences::self()->showNickList()) toggleChannelNickListsAction->setChecked(true); toggleChannelNickListsAction->setText(i18n("Show Nicklist")); actionCollection()->setDefaultShortcut(toggleChannelNickListsAction, QKeySequence(QStringLiteral("Ctrl+H"))); connect(toggleChannelNickListsAction, SIGNAL(triggered()), m_viewContainer, SLOT(toggleChannelNicklists())); actionCollection()->addAction(QStringLiteral("hide_nicknamelist"), toggleChannelNickListsAction); action=new QAction(this); action->setText(i18n("Show/Hide Konversation")); connect(action, SIGNAL(triggered()), this, SLOT(toggleVisibility())); actionCollection()->addAction(QStringLiteral("toggle_mainwindow_visibility"), action); KGlobalAccel::setGlobalShortcut(action, QList()); action=new KToggleAction(this); action->setEnabled(true); action->setChecked(Preferences::self()->useOSD()); action->setText(i18n("Enable On Screen Display")); action->setIcon(QIcon::fromTheme(QStringLiteral("video-display"))); connect(action, SIGNAL(triggered(bool)), Preferences::self(), SLOT(slotSetUseOSD(bool))); actionCollection()->addAction(QStringLiteral("toggle_osd"), action); // Bookmarks action=new QAction(this); action->setText(i18n("Bookmarks")); QMenu *menu = new QMenu(this); action->setMenu(menu); new KonviBookmarkHandler(menu, this); actionCollection()->addAction(QStringLiteral("bookmarks") , action); // decide whether to show the tray icon or not updateTrayIcon(); createGUI(); setAutoSaveSettings(); // Apply menubar show/hide pref m_showMenuBarAction->setChecked(Preferences::self()->showMenuBar()); toggleMenubar(true); if (Preferences::self()->useNotify() && Preferences::self()->openWatchedNicksAtStartup()) m_viewContainer->openNicksOnlinePanel(); } MainWindow::~MainWindow() { } QSize MainWindow::sizeHint() const { return QSize(700, 500); // Give the app a sane default size } int MainWindow::confirmQuit() { Application* konvApp = Application::instance(); if (konvApp->getConnectionManager()->connectionCount() == 0) return KMessageBox::Continue; int result = KMessageBox::Cancel; if (!KMessageBox::shouldBeShownContinue(QStringLiteral("systemtrayquitKonversation")) && konvApp->getDccTransferManager()->hasActiveTransfers()) { result = KMessageBox::warningContinueCancel( this, i18n("You have active DCC file transfers. Are you sure you want to quit Konversation?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), QStringLiteral("QuitWithActiveDccTransfers")); } else { result = KMessageBox::warningContinueCancel( this, i18n("Are you sure you want to quit Konversation?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel(), QStringLiteral("systemtrayquitKonversation")); } if (result != KMessageBox::Continue) konvApp->abortScheduledRestart(); return result; } void MainWindow::activateAndRaiseWindow() { if (isMinimized()) KWindowSystem::unminimizeWindow(winId()); else if (Preferences::self()->showTrayIcon() && !isVisible()) m_trayIcon->restore(); KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop()); KWindowSystem::activateWindow(winId()); } void MainWindow::quitProgram() { if (Preferences::self()->showTrayIcon() && sender() != m_trayIcon && confirmQuit() == KMessageBox::Cancel) return; // will call queryClose() m_closeApp = true; close(); } bool MainWindow::queryClose() { Application* konvApp = Application::instance(); if (!konvApp->isSavingSession()) { if (sender() == m_trayIcon) m_closeApp = true; if (Preferences::self()->showTrayIcon() && !m_closeApp) { bool doit = KMessageBox::warningContinueCancel(this, i18n("

Closing the main window will keep Konversation running in the system tray. " "Use Quit from the Konversation menu to quit the application.

"), i18n("Docking in System Tray"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String("HideOnCloseInfo")) == KMessageBox::Continue; if (doit) hide(); return false; } if (!Preferences::self()->showTrayIcon() && confirmQuit() == KMessageBox::Cancel) return false; } konvApp->prepareShutdown(); return true; } void MainWindow::hideEvent(QHideEvent *e) { emit triggerRememberLine(); m_statusBar->clearMainLabelTempText(); KXmlGuiWindow::hideEvent(e); } void MainWindow::showEvent(QShowEvent *e) { emit cancelRememberLine(); KXmlGuiWindow::showEvent(e); } void MainWindow::leaveEvent(QEvent* e) { m_statusBar->clearMainLabelTempText(); KXmlGuiWindow::leaveEvent(e); } bool MainWindow::event(QEvent* e) { if (e->type() == QEvent::StyleChange) { QMetaObject::invokeMethod(Application::instance(), "appearanceChanged"); } else if (e->type() == QEvent::WindowActivate) { emit endNotification(); emit cancelRememberLine(); } else if(e->type() == QEvent::WindowDeactivate) { m_statusBar->clearMainLabelTempText(); if (qApp->activeModalWidget() == 0) emit triggerRememberLine(); } return KXmlGuiWindow::event(e); } void MainWindow::settingsChangedSlot() { // This is for compressing the events. m_hasDirtySettings is set to true // when the settings have changed, then set to false when the app reacts to it // via the appearanceChanged signal. This prevents a series of settingsChanged signals // causing the app expensively rereading its settings many times. // The appearanceChanged signal is connected to resetHasDirtySettings to reset this bool if (!m_hasDirtySettings) { QTimer::singleShot(0, Application::instance(), SIGNAL(appearanceChanged())); m_hasDirtySettings = true; } } void MainWindow::resetHasDirtySettings() { m_hasDirtySettings = false; } void MainWindow::updateTrayIcon() { if (Preferences::self()->showTrayIcon()) { if (!m_trayIcon) { // set up system tray m_trayIcon = new Konversation::TrayIcon(this); connect(this, SIGNAL(endNotification()), m_trayIcon, SLOT(endNotification())); connect(KIconLoader::global(), SIGNAL(iconChanged(int)), m_trayIcon, SLOT(updateAppearance())); QMenu *trayMenu = qobject_cast(m_trayIcon->contextMenu()); trayMenu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::Preferences)))); trayMenu->addAction(actionCollection()->action(QLatin1String(KStandardAction::name(KStandardAction::ConfigureNotifications)))); trayMenu->addAction(actionCollection()->action(QStringLiteral("toggle_away"))); } m_trayIcon->setNotificationEnabled(Preferences::self()->trayNotify()); } else { delete m_trayIcon; m_trayIcon = 0; } } void MainWindow::toggleMenubar(bool dontShowWarning) { if (m_showMenuBarAction->isChecked()) menuBar()->show(); else { bool doit = true; if (!dontShowWarning) { QString accel = m_showMenuBarAction->shortcut().toString(); doit = KMessageBox::warningContinueCancel(this, i18n("This will hide the menu bar completely. You can show it again by typing %1.", accel), i18n("Hide menu bar"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QLatin1String("HideMenuBarWarning")) == KMessageBox::Continue; } if (doit) menuBar()->hide(); else m_showMenuBarAction->setChecked (true); } Preferences::self()->setShowMenuBar(m_showMenuBarAction->isChecked()); } void MainWindow::focusAndShowErrorMessage(const QString &errorMsg) { show(); KWindowSystem::demandAttention(winId()); KWindowSystem::activateWindow(winId()); KMessageBox::error(this, errorMsg); } void MainWindow::openPrefsDialog() { //An instance of your dialog could be already created and could be cached, //in which case you want to display the cached dialog instead of creating //another one if (!m_settingsDialog) { m_settingsDialog = new KonviSettingsDialog(this); //User edited the configuration - update your local copies of the //configuration data connect(m_settingsDialog, SIGNAL(settingsChanged(QString)), this, SLOT(settingsChangedSlot())); } m_settingsDialog->show(); } void MainWindow::openKeyBindings() { // Change a number of action names to make them friendlier for the shortcut list. actionCollection()->action(QStringLiteral("tab_notifications"))->setText(i18n("Toggle Notifications")); actionCollection()->action(QStringLiteral("toggle_away"))->setText(i18n("Toggle Away Globally")); actionCollection()->action(QStringLiteral("irc_colors"))->setText(i18n("Insert &IRC Color...")); actionCollection()->action(QStringLiteral("insert_character"))->setText(i18n("Insert Special &Character...")); actionCollection()->action(QStringLiteral("insert_marker_line"))->setText(i18n("Insert &Marker Line")); QString openChannelListString = actionCollection()->action(QStringLiteral("open_channel_list"))->text(); actionCollection()->action(QStringLiteral("open_channel_list"))->setText(i18n("&Channel List")); QString openLogFileString = actionCollection()->action(QStringLiteral("open_logfile"))->text(); actionCollection()->action(QStringLiteral("open_logfile"))->setText(i18n("&Open Logfile")); // Open shortcut configuration dialog. KShortcutsDialog::configure(actionCollection()); // Reset action names. actionCollection()->action(QStringLiteral("tab_notifications"))->setText(i18n("Enable Notifications")); actionCollection()->action(QStringLiteral("toggle_away"))->setText(i18n("Set &Away Globally")); actionCollection()->action(QStringLiteral("irc_colors"))->setText(i18n("&IRC Color...")); actionCollection()->action(QStringLiteral("insert_character"))->setText(i18n("Special &Character...")); actionCollection()->action(QStringLiteral("insert_marker_line"))->setText(i18n("&Marker Line")); actionCollection()->action(QStringLiteral("open_channel_list"))->setText(openChannelListString); actionCollection()->action(QStringLiteral("open_logfile"))->setText(openLogFileString); } void MainWindow::openServerList() { if (!m_serverListDialog) { m_serverListDialog = new Konversation::ServerListDialog(i18n("Server List"), this); Application* konvApp = Application::instance(); connect(m_serverListDialog, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), konvApp, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr))); connect(konvApp, SIGNAL(serverGroupsChanged(Konversation::ServerGroupSettingsPtr)), m_serverListDialog, SLOT(updateServerList())); connect(m_serverListDialog, SIGNAL(connectTo(Konversation::ConnectionFlag,int)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,int))); connect(m_serverListDialog, SIGNAL(connectTo(Konversation::ConnectionFlag,ConnectionSettings)), konvApp->getConnectionManager(), SLOT(connectTo(Konversation::ConnectionFlag,ConnectionSettings))); connect(konvApp->getConnectionManager(), SIGNAL(closeServerList()), m_serverListDialog, SLOT(reject())); } m_serverListDialog->show(); } void MainWindow::openQuickConnectDialog() { emit showQuickConnectDialog(); } void MainWindow::openIdentitiesDialog() { QPointer dlg = new Konversation::IdentityDialog(this); if (dlg->exec() == QDialog::Accepted) { if (m_serverListDialog) m_serverListDialog->updateServerList(); m_viewContainer->updateViewEncoding(m_viewContainer->getFrontView()); } delete dlg; } IdentityPtr MainWindow::editIdentity(IdentityPtr identity) { IdentityPtr newIdentity; QPointer dlg = new Konversation::IdentityDialog(this); newIdentity = dlg->setCurrentIdentity(identity); if ((dlg->exec() == QDialog::Accepted) && m_serverListDialog) { m_serverListDialog->updateServerList(); delete dlg; return newIdentity; } else { delete dlg; return IdentityPtr(); } } void MainWindow::openNotifications() { (void) KNotifyConfigWidget::configure(this); } void MainWindow::notifyAction(int connectionId, const QString& nick) { Application* konvApp = Application::instance(); Server* server = konvApp->getConnectionManager()->getServerByConnectionId(connectionId); if (server) server->notifyAction(nick); } // TODO: Let an own class handle notify things void MainWindow::setOnlineList(Server* notifyServer,const QStringList& /*list*/, bool /*changed*/) { emit nicksNowOnline(notifyServer); // FIXME if (changed && nicksOnlinePanel) newText(nicksOnlinePanel, QString::null, true); } void MainWindow::toggleVisibility() { if (isActiveWindow()) { if (Preferences::self()->showTrayIcon()) hide(); else KWindowSystem::minimizeWindow(winId()); } else { activateAndRaiseWindow(); } } diff --git a/src/viewer/ircview.cpp b/src/viewer/ircview.cpp index fe9bcb7d..3e9d94ce 100644 --- a/src/viewer/ircview.cpp +++ b/src/viewer/ircview.cpp @@ -1,2243 +1,2273 @@ // -*- mode: c++; c-file-style: "bsd"; c-basic-offset: 4; tabs-width: 4; indent-tabs-mode: nil -*- /* 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. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005-2016 Peter Simonsson Copyright (C) 2006-2010 Eike Hein Copyright (C) 2004-2011 Eli Mackenzie */ #include "ircview.h" #include "channel.h" #include "dcc/chatcontainer.h" #include "application.h" #include "highlight.h" #include "sound.h" #include "emoticons.h" #include "notificationhandler.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Konversation; class ScrollBarPin { QPointer m_bar; public: ScrollBarPin(QScrollBar *scrollBar) : m_bar(scrollBar) { if (m_bar) m_bar = m_bar->value() == m_bar->maximum()? m_bar : 0; } ~ScrollBarPin() { if (m_bar) m_bar->setValue(m_bar->maximum()); } }; // Scribe bug - if the cursor position or anchor points to the last character in the document, // the cursor becomes glued to the end of the document instead of retaining the actual position. // This causes the selection to expand when something is appended to the document. class SelectionPin { int pos, anc; QPointer d; public: SelectionPin(IRCView *doc) : pos(0), anc(0), d(doc) { if (d->textCursor().hasSelection()) { int end = d->document()->rootFrame()->lastPosition(); //WARNING if selection pins don't work in some build environments, we need to keep the result d->document()->lastBlock(); pos = d->textCursor().position(); anc = d->textCursor().anchor(); if (pos != end && anc != end) anc = pos = 0; } } ~SelectionPin() { if (d && (pos || anc)) { QTextCursor mv(d->textCursor()); mv.setPosition(anc); mv.setPosition(pos, QTextCursor::KeepAnchor); d->setTextCursor(mv); } } }; IRCView::IRCView(QWidget* parent) : QTextBrowser(parent), m_rememberLine(0), m_lastMarkerLine(0), m_rememberLineDirtyBit(false), markerFormatObject(this) { m_mousePressedOnUrl = false; m_isOnNick = false; m_isOnChannel = false; m_chatWin = 0; m_server = 0; setAcceptDrops(false); // Marker lines connect(document(), SIGNAL(contentsChange(int,int,int)), SLOT(cullMarkedLine(int,int,int))); //This assert is here because a bad build environment can cause this to fail. There is a note // in the Qt source that indicates an error should be output, but there is no such output. QTextObjectInterface *iface = qobject_cast(&markerFormatObject); if (!iface) { Q_ASSERT(iface); } document()->documentLayout()->registerHandler(IRCView::MarkerLine, &markerFormatObject); document()->documentLayout()->registerHandler(IRCView::RememberLine, &markerFormatObject); connect(this, SIGNAL(anchorClicked(QUrl)), this, SLOT(anchorClicked(QUrl))); connect( this, SIGNAL(highlighted(QString)), this, SLOT(highlightedSlot(QString)) ); setOpenLinks(false); setUndoRedoEnabled(0); document()->setDefaultStyleSheet("a.nick:link {text-decoration: none}"); setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); setFocusPolicy(Qt::ClickFocus); setReadOnly(true); viewport()->setCursor(Qt::ArrowCursor); setTextInteractionFlags(Qt::TextBrowserInteraction); viewport()->setMouseTracking(true); //HACK to workaround an issue with the QTextDocument //doing a relayout/scrollbar over and over resulting in 100% //proc usage. See bug 215256 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setContextMenuOptions(IrcContextMenus::ShowTitle | IrcContextMenus::ShowFindAction, true); } IRCView::~IRCView() { } +void IRCView::increaseFontSize() +{ + QFont newFont; + newFont.setPointSize(font().pointSize() + 1); + setFont(newFont); +} + +void IRCView::decreaseFontSize() +{ + QFont newFont; + newFont.setPointSize(font().pointSize() - 1); + setFont(newFont); +} + +void IRCView::resetFontSize() +{ + setFont(Preferences::self()->textFont()); +} + void IRCView::setServer(Server* newServer) { if (m_server == newServer) return; m_server = newServer; } void IRCView::setChatWin(ChatWindow* chatWin) { m_chatWin = chatWin; } void IRCView::findText() { emit doSearch(); } void IRCView::findNextText() { emit doSearchNext(); } void IRCView::findPreviousText() { emit doSearchPrevious(); } bool IRCView::search(const QString& pattern, QTextDocument::FindFlags flags, bool fromCursor) { if (pattern.isEmpty()) return true; m_pattern = pattern; m_searchFlags = flags; if (!fromCursor) moveCursor(QTextCursor::End); else moveCursor(QTextCursor::StartOfWord); // Do this to that if possible the same position is kept when changing search options return searchNext(); } bool IRCView::searchNext(bool reversed) { QTextDocument::FindFlags flags = m_searchFlags; if(!reversed) flags |= QTextDocument::FindBackward; return find(m_pattern, flags); } class IrcViewMimeData : public QMimeData { public: IrcViewMimeData(const QTextDocumentFragment& _fragment): fragment(_fragment) {} virtual QStringList formats() const; protected: virtual QVariant retrieveData(const QString &mimeType, QVariant::Type type) const; private: mutable QTextDocumentFragment fragment; }; QStringList IrcViewMimeData::formats() const { if (!fragment.isEmpty()) return QStringList() << QString::fromLatin1("text/plain"); else return QMimeData::formats(); } QVariant IrcViewMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const { if (!fragment.isEmpty()) { IrcViewMimeData *that = const_cast(this); //Copy the text, skipping any QChar::ObjectReplacementCharacter QRegExp needle(QString("\\xFFFC\\n?")); that->setText(fragment.toPlainText().remove(needle)); fragment = QTextDocumentFragment(); } return QMimeData::retrieveData(mimeType, type); } QMimeData *IRCView::createMimeDataFromSelection() const { const QTextDocumentFragment fragment(textCursor()); return new IrcViewMimeData(fragment); } void IRCView::dragEnterEvent(QDragEnterEvent* e) { if (e->mimeData()->hasUrls()) e->acceptProposedAction(); else e->ignore(); } void IRCView::dragMoveEvent(QDragMoveEvent* e) { if (e->mimeData()->hasUrls()) e->accept(); else e->ignore(); } void IRCView::dropEvent(QDropEvent* e) { if (e->mimeData() && e->mimeData()->hasUrls()) emit urlsDropped(KUrlMimeData::urlsFromMimeData(e->mimeData(), KUrlMimeData::PreferLocalUrls)); } // Marker lines #define _S(x) #x << (x) QDebug operator<<(QDebug dbg, QTextBlockUserData *bd); QDebug operator<<(QDebug d, QTextFrame* feed); QDebug operator<<(QDebug d, QTextDocument* document); QDebug operator<<(QDebug d, QTextBlock b); // This object gets stuffed into the userData field of a text block. // Qt does not give us a way to track blocks, so we have to // rely on the destructor of this object to notify us that a // block we care about was removed from the document. This does not // prevent the first block bug from deleting the wrong block's data, // however that should not result in a crash. struct Burr: public QTextBlockUserData { Burr(IRCView* o, Burr* prev, QTextBlock b, int objFormat) : m_block(b), m_format(objFormat), m_prev(prev), m_next(0), m_owner(o) { if (m_prev) m_prev->m_next = this; } ~Burr() { m_owner->blockDeleted(this); unlink(); } void unlink() { if (m_prev) m_prev->m_next = m_next; if (m_next) m_next->m_prev = m_prev; } QTextBlock m_block; int m_format; Burr* m_prev, *m_next; IRCView* m_owner; }; void IrcViewMarkerLine::drawObject(QPainter *painter, const QRectF &r, QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(format); QTextBlock block=doc->findBlock(posInDocument); QPen pen; Burr* b = dynamic_cast(block.userData()); Q_ASSERT(b); // remember kids, only YOU can makes this document support two user data types switch (b->m_format) { case IRCView::BlockIsMarker: pen.setColor(Preferences::self()->color(Preferences::ActionMessage)); break; case IRCView::BlockIsRemember: pen.setColor(Preferences::self()->color(Preferences::CommandMessage)); // pen.setStyle(Qt::DashDotDotLine); break; default: //nice color, eh? pen.setColor(Qt::cyan); } pen.setWidth(2); // FIXME this is a hardcoded value... painter->setPen(pen); qreal y = (r.top() + r.height() / 2); QLineF line(r.left(), y, r.right(), y); painter->drawLine(line); } QSizeF IrcViewMarkerLine::intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format) { Q_UNUSED(posInDocument); Q_UNUSED(format); QTextFrameFormat f=doc->rootFrame()->frameFormat(); qreal width = doc->pageSize().width()-(f.leftMargin()+f.rightMargin()); return QSizeF(width, 6); // FIXME this is a hardcoded value... } QTextCharFormat IRCView::getFormat(ObjectFormats x) { QTextCharFormat f; f.setObjectType(x); return f; } void IRCView::blockDeleted(Burr* b) //slot { Q_ASSERT(b); // this method only to be called from a ~Burr(); //tracking only the tail if (b == m_lastMarkerLine) m_lastMarkerLine = b->m_prev; if (b == m_rememberLine) m_rememberLine = 0; } void IRCView::cullMarkedLine(int, int, int) //slot { QTextBlock prime = document()->firstBlock(); if (prime.length() == 1 && document()->blockCount() == 1) //the entire document was wiped. was a signal such a burden? apparently.. wipeLineParagraphs(); } void IRCView::insertMarkerLine() //slot { //if the last line is already a marker of any kind, skip out if (lastBlockIsLine(BlockIsMarker)) return; //the code used to preserve the dirty bit status, but that was never affected by appendLine... //maybe i missed something appendLine(IRCView::MarkerLine); } void IRCView::insertRememberLine() //slot { m_rememberLineDirtyBit = true; // means we're going to append a remember line if some text gets inserted if (!Preferences::self()->automaticRememberLineOnlyOnTextChange()) { appendRememberLine(); } } void IRCView::cancelRememberLine() //slot { m_rememberLineDirtyBit = false; } bool IRCView::lastBlockIsLine(int select) { Burr *b = dynamic_cast(document()->lastBlock().userData()); int state = -1; if (b) state = b->m_format; if (select == -1) return (state == BlockIsRemember || state == BlockIsMarker); return state == select; } void IRCView::appendRememberLine() { //clear this now, so that doAppend doesn't double insert m_rememberLineDirtyBit = false; //if the last line is already the remember line, do nothing if (lastBlockIsLine(BlockIsRemember)) return; if (m_rememberLine) { QTextBlock rem = m_rememberLine->m_block; voidLineBlock(rem); if (m_rememberLine != 0) { // this probably means we had a block containing only 0x2029, so Scribe merged the userData/userState into the next m_rememberLine = 0; } } m_rememberLine = appendLine(IRCView::RememberLine); } void IRCView::voidLineBlock(QTextBlock rem) { QTextCursor c(rem); c.select(QTextCursor::BlockUnderCursor); c.removeSelectedText(); } void IRCView::clearLines() { while (hasLines()) { //IRCView::blockDeleted takes care of the pointers voidLineBlock(m_lastMarkerLine->m_block); }; } void IRCView::wipeLineParagraphs() { m_rememberLine = m_lastMarkerLine = 0; } bool IRCView::hasLines() { return m_lastMarkerLine != 0; } Burr* IRCView::appendLine(IRCView::ObjectFormats type) { ScrollBarPin barpin(verticalScrollBar()); SelectionPin selpin(this); QTextCursor cursor(document()); cursor.movePosition(QTextCursor::End); if (cursor.block().length() > 1) // this will be a 0x2029 cursor.insertBlock(); cursor.insertText(QString(QChar::ObjectReplacementCharacter), getFormat(type)); QTextBlock block = cursor.block(); Burr *b = new Burr(this, m_lastMarkerLine, block, type == MarkerLine? BlockIsMarker : BlockIsRemember); block.setUserData(b); m_lastMarkerLine = b; //TODO figure out what this is for cursor.setPosition(block.position()); return b; } // Other stuff void IRCView::updateAppearance() { if (Preferences::self()->customTextFont()) setFont(Preferences::self()->textFont()); else setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); setVerticalScrollBarPolicy(Preferences::self()->showIRCViewScrollBar() ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); if (Preferences::self()->showBackgroundImage()) { QUrl url = Preferences::self()->backgroundImage(); if (url.isValid()) { viewport()->setStyleSheet("QWidget { background-image: url("+url.path()+"); background-attachment:fixed; }"); return; } } if (!viewport()->styleSheet().isEmpty()) viewport()->setStyleSheet(QString()); QPalette p; p.setColor(QPalette::Base, Preferences::self()->color(Preferences::TextViewBackground)); viewport()->setPalette(p); } // Data insertion void IRCView::append(const QString& nick, const QString& message, const QHash &messageTags, const QString& label) { QString channelColor = Preferences::self()->color(Preferences::ChannelMessage).name(); m_tabNotification = Konversation::tnfNormal; QString nickLine = createNickLine(nick, channelColor); QChar::Direction dir; QString text(filter(message, channelColor, nick, true, true, false, &dir)); QString line; bool rtl = (dir == QChar::DirR); QChar directionOfLine = rtl ? RLM : LRM; line = directionOfLine; if (!label.isEmpty()) { line += "[%4]"; } line += "%1" + directionOfLine + nickLine + directionOfLine + " %3"; line = line.arg(timeStamp(messageTags), nick, text); if (!label.isEmpty()) { line = line.arg(label); } emit textToLog(QString("<%1>\t%2").arg(nick, message)); doAppend(line, rtl); } void IRCView::appendRaw(const QString& message, bool self) { QColor color = self ? Preferences::self()->color(Preferences::ChannelMessage) : Preferences::self()->color(Preferences::ServerMessage); m_tabNotification = Konversation::tnfNone; QString line = QString(timeStamp(QHash()) + " " + message + ""); doAppend(line, false, self); } void IRCView::appendLog(const QString & message) { QColor channelColor = Preferences::self()->color(Preferences::ChannelMessage); m_tabNotification = Konversation::tnfNone; QString line("" + message + ""); doRawAppend(line, !QApplication::isLeftToRight()); } void IRCView::appendQuery(const QString& nick, const QString& message, const QHash &messageTags, bool inChannel) { QString queryColor=Preferences::self()->color(Preferences::QueryMessage).name(); m_tabNotification = Konversation::tnfPrivate; QString nickLine = createNickLine(nick, queryColor, true, inChannel); QString line; QChar::Direction dir; QString text(filter(message, queryColor, nick, true, true, false, &dir)); bool rtl = (dir == QChar::DirR); QChar directionOfLine = rtl ? RLM : LRM; line = directionOfLine + "%1" + directionOfLine + nickLine + directionOfLine + " %3"; line = line.arg(timeStamp(messageTags), nick, text); if (inChannel) { emit textToLog(QString("<-> %1>\t%2").arg(nick, message)); } else { emit textToLog(QString("<%1>\t%2").arg(nick, message)); } doAppend(line, rtl); } void IRCView::appendChannelAction(const QString& nick, const QString& message, const QHash &messageTags) { m_tabNotification = Konversation::tnfNormal; appendAction(nick, message, messageTags); } void IRCView::appendQueryAction(const QString& nick, const QString& message, const QHash &messageTags) { m_tabNotification = Konversation::tnfPrivate; appendAction(nick, message, messageTags); } void IRCView::appendAction(const QString& nick, const QString& message, const QHash &messageTags) { QString actionColor = Preferences::self()->color(Preferences::ActionMessage).name(); QString line; QString nickLine = createNickLine(nick, actionColor, false); if (message.isEmpty()) { line = LRM + "%1 * " + nickLine + ""; line = line.arg(timeStamp(messageTags), nick); emit textToLog(QString("\t * %1").arg(nick)); doAppend(line, false); } else { QChar::Direction dir; QString text(filter(message, actionColor, nick, true,true, false, &dir)); bool rtl = (dir == QChar::DirR); QChar directionOfLine = rtl ? RLM : LRM; line = directionOfLine + "%1 " + directionOfLine + "* " + nickLine + directionOfLine + " %3"; line = line.arg(timeStamp(messageTags), nick, text); emit textToLog(QString("\t * %1 %2").arg(nick, message)); doAppend(line, rtl); } } void IRCView::appendServerMessage(const QString& type, const QString& message, const QHash &messageTags, bool parseURL) { QString serverColor = Preferences::self()->color(Preferences::ServerMessage).name(); m_tabNotification = Konversation::tnfControl; // Fixed width font option for MOTD QString fixed; if(Preferences::self()->fixedMOTD() && !m_fontDataBase.isFixedPitch(font().family())) { if(type == i18n("MOTD")) fixed=" face=\"" + QFontDatabase::systemFont(QFontDatabase::FixedFont).family() + "\""; } QString line; QChar::Direction dir; QString text(filter(message, serverColor, 0 , true, parseURL, false, &dir)); bool rtl = (dir == QChar::DirR); QChar directionOfLine = rtl ? RLM : LRM; line = directionOfLine + "%1 " + directionOfLine + "[%2]" + directionOfLine + " %3"; line = line.arg(timeStamp(messageTags), type, text); emit textToLog(QString("%1\t%2").arg(type, message)); doAppend(line, rtl); } void IRCView::appendCommandMessage(const QString& type, const QString& message, const QHash &messageTags, bool parseURL, bool self) { QString commandColor = Preferences::self()->color(Preferences::CommandMessage).name(); QString prefix="***"; m_tabNotification = Konversation::tnfControl; if(type == i18nc("Message type", "Join")) { prefix="-->"; parseURL=false; } else if(type == i18nc("Message type", "Part") || type == i18nc("Message type", "Quit")) { prefix="<--"; } prefix=prefix.toHtmlEscaped(); QString line; QChar::Direction dir; QString text(filter(message, commandColor, 0, true, parseURL, self, &dir)); bool rtl = text.isRightToLeft(); QChar directionOfLine = rtl ? RLM : LRM; line = directionOfLine + "%1 %2 %3"; line = line.arg(timeStamp(messageTags), prefix, text); emit textToLog(QString("%1\t%2").arg(type, message)); doAppend(line, rtl, self); } void IRCView::appendBacklogMessage(const QString& firstColumn,const QString& rawMessage) { QString time; QString message = rawMessage; QString nick = firstColumn; QString backlogColor = Preferences::self()->color(Preferences::BacklogMessage).name(); m_tabNotification = Konversation::tnfNone; //The format in Chatwindow::logText is not configurable, so as long as nobody allows square brackets in a date/time format.... int eot = nick.lastIndexOf(' '); time = nick.left(eot); nick = nick.mid(eot+1); if(!nick.isEmpty() && !nick.startsWith('<') && !nick.startsWith('*')) { nick = '|' + nick + '|'; } else //It's a real nick { nick = LRM + nick + LRM; } // Nicks are in "" format so replace the "<>" nick.replace('<',"<"); nick.replace('>',">"); QString line; QChar::Direction dir; QString text(filter(message, backlogColor, NULL, false, false, false, &dir)); bool rtl = nick.startsWith('|') ? text.isRightToLeft() : (dir == QChar::DirR); QChar directionOfLine = rtl ? RLM : LRM; line = directionOfLine + "%1 " + directionOfLine + "%2" + directionOfLine + " %3"; line = line.arg(time, nick, text); doAppend(line, rtl); } void IRCView::doAppend(const QString& newLine, bool rtl, bool self) { if (m_rememberLineDirtyBit) appendRememberLine(); if (!self && m_chatWin) m_chatWin->activateTabNotification(m_tabNotification); int scrollMax = Preferences::self()->scrollbackMax(); if (scrollMax != 0) { //don't remove lines if the user has scrolled up to read old lines bool atBottom = (verticalScrollBar()->value() == verticalScrollBar()->maximum()); document()->setMaximumBlockCount(atBottom ? scrollMax : document()->maximumBlockCount() + 1); } doRawAppend(newLine, rtl); //FIXME: Disable auto-text for DCC Chats since we don't have a server to parse wildcards. if (!m_autoTextToSend.isEmpty() && m_server) { // replace placeholders in autoText QString sendText = m_server->parseWildcards(m_autoTextToSend,m_server->getNickname(), QString(), QString(), QString(), QString()); // avoid recursion due to signalling m_autoTextToSend.clear(); // send signal only now emit autoText(sendText); } else { m_autoTextToSend.clear(); } if (!m_lastStatusText.isEmpty()) emit clearStatusBarTempText(); } void IRCView::doRawAppend(const QString& newLine, bool rtl) { SelectionPin selpin(this); // HACK stop selection at end from growing QString line(newLine); line.remove('\n'); QTextBrowser::append(line); QTextCursor formatCursor(document()->lastBlock()); QTextBlockFormat format = formatCursor.blockFormat(); format.setAlignment(Qt::AlignAbsolute|(rtl ? Qt::AlignRight : Qt::AlignLeft)); formatCursor.setBlockFormat(format); } QString IRCView::timeStamp(QHash messageTags) { if(Preferences::self()->timestamping()) { QDateTime serverTime; if (messageTags.contains(QStringLiteral("time"))) // If it exists use the supplied server time. serverTime = QDateTime::fromString(messageTags[QStringLiteral("time")], Qt::ISODate).toLocalTime(); QTime time = serverTime.isValid() ? serverTime.time() : QTime::currentTime(); QString timeColor = Preferences::self()->color(Preferences::Time).name(); QString timeFormat = Preferences::self()->timestampFormat(); QString timeString; bool rtlLocale = (QLocale().zeroDigit() == QChar((ushort)0x0660)) or // ARABIC-INDIC DIGIT ZERO (QLocale().zeroDigit() == QChar((ushort)0x06F0)); // EXTENDED ARABIC-INDIC DIGIT ZERO if(!Preferences::self()->showDate()) { timeString = QString(QLatin1String("[%1] ")).arg(time.toString(timeFormat)); } else { QDate date = serverTime.isValid() ? serverTime.date() : QDate::currentDate(); timeString = QString("[%1%2 %3%4] ") .arg(rtlLocale ? RLM : LRM, QLocale().toString(date, QLocale::ShortFormat), time.toString(timeFormat), !rtlLocale ? RLM : LRM); } return timeString; } return QString(); } QString IRCView::createNickLine(const QString& nick, const QString& defaultColor, bool encapsulateNick, bool privMsg) { QString nickLine = LRM + "%2" + LRM; QString nickColor; if (Preferences::self()->useColoredNicks()) { if (m_server) { if (nick != m_server->getNickname()) nickColor = Preferences::self()->nickColor(m_server->obtainNickInfo(nick)->getNickColor()).name(); else nickColor = Preferences::self()->nickColor(8).name(); } else if (m_chatWin->getType() == ChatWindow::DccChat) { QString ownNick = static_cast(m_chatWin)->ownNick(); if (nick != ownNick) nickColor = Preferences::self()->nickColor(Konversation::colorForNick(ownNick)).name(); else nickColor = Preferences::self()->nickColor(8).name(); } } else nickColor = defaultColor; nickLine = QLatin1String("") + nickLine + QLatin1String(""); if (Preferences::self()->useClickableNicks()) nickLine = "" + nickLine + ""; if (privMsg) nickLine.prepend(QLatin1String("-> ")); if(encapsulateNick) nickLine = QLatin1String("<") + nickLine + QLatin1String(">"); if(Preferences::self()->useBoldNicks()) nickLine = QLatin1String("") + nickLine + QLatin1String(""); return nickLine; } void IRCView::replaceDecoration(QString& line, char decoration, char replacement) { int pos; bool decorated = false; while((pos=line.indexOf(decoration))!=-1) { line.replace(pos,1,(decorated) ? QString("").arg(replacement) : QString("<%1>").arg(replacement)); decorated = !decorated; } } QString IRCView::filter(const QString& line, const QString& defaultColor, const QString& whoSent, bool doHighlight, bool parseURL, bool self, QChar::Direction* direction) { QString filteredLine(line); Application* konvApp = Application::instance(); //Since we can't turn off whitespace simplification withouteliminating text wrapping, // if the line starts with a space turn it into a non-breaking space. // (which magically turns back into a space on copy) if (filteredLine[0] == ' ') { filteredLine[0] = '\xA0'; } // TODO: Use QStyleSheet::escape() here // Replace all < with < filteredLine.replace('<', "\x0blt;"); // Replace all > with > filteredLine.replace('>', "\x0bgt;"); if (filteredLine.contains('\x07')) { if (Preferences::self()->beep()) { qApp->beep(); } //remove char after beep filteredLine.remove('\x07'); } filteredLine = ircTextToHtml(filteredLine, parseURL, defaultColor, whoSent, true, direction); // Highlight QString ownNick; if (m_server) { ownNick = m_server->getNickname(); } else if (m_chatWin->getType() == ChatWindow::DccChat) { ownNick = static_cast(m_chatWin)->ownNick(); } if(doHighlight && (whoSent != ownNick) && !self) { QString highlightColor; if (Preferences::self()->highlightNick() && line.toLower().contains(QRegExp("(^|[^\\d\\w])" + QRegExp::escape(ownNick.toLower()) + "([^\\d\\w]|$)"))) { // highlight current nickname highlightColor = Preferences::self()->highlightNickColor().name(); m_tabNotification = Konversation::tnfNick; } else { QList highlightList = Preferences::highlightList(); QListIterator it(highlightList); Highlight* highlight; QStringList highlightChatWindowList; bool patternFound = false; QStringList captures; while (it.hasNext()) { highlight = it.next(); highlightChatWindowList = highlight->getChatWindowList(); if (highlightChatWindowList.isEmpty() || highlightChatWindowList.contains(m_chatWin->getName(), Qt::CaseInsensitive)) { if (highlight->getRegExp()) { QRegExp needleReg(highlight->getPattern()); needleReg.setCaseSensitivity(Qt::CaseInsensitive); // highlight regexp in text patternFound = ((line.contains(needleReg)) || // highlight regexp in nickname (whoSent.contains(needleReg))); // remember captured patterns for later captures = needleReg.capturedTexts(); } else { QString needle = highlight->getPattern(); // highlight patterns in text patternFound = ((line.contains(needle, Qt::CaseInsensitive)) || // highlight patterns in nickname (whoSent.contains(needle, Qt::CaseInsensitive))); } if (patternFound) { break; } } } if (patternFound) { highlightColor = highlight->getColor().name(); m_highlightColor = highlightColor; if (highlight->getNotify()) { m_tabNotification = Konversation::tnfHighlight; if (Preferences::self()->highlightSoundsEnabled() && m_chatWin->notificationsEnabled()) { konvApp->sound()->play(highlight->getSoundURL()); } konvApp->notificationHandler()->highlight(m_chatWin, whoSent, line); } m_autoTextToSend = highlight->getAutoText(); // replace %0 - %9 in regex groups for (int capture = 0; capture < captures.count(); capture++) { m_autoTextToSend.replace(QString("%%1").arg(capture), captures[capture]); } m_autoTextToSend.remove(QRegExp("%[0-9]")); } } // apply found highlight color to line if (!highlightColor.isEmpty()) { filteredLine = QLatin1String("") + filteredLine + QLatin1String(""); } } else if (doHighlight && (whoSent == ownNick) && Preferences::self()->highlightOwnLines()) { // highlight own lines filteredLine = QLatin1String("highlightOwnLinesColor().name() + QLatin1String("\">") + filteredLine + QLatin1String(""); } filteredLine = Konversation::Emoticons::parseEmoticons(filteredLine); return filteredLine; } QString IRCView::ircTextToHtml(const QString& text, bool parseURL, const QString& defaultColor, const QString& whoSent, bool closeAllTags, QChar::Direction* direction) { TextHtmlData data; data.defaultColor = defaultColor; QString htmlText(text); bool allowColors = Preferences::self()->allowColorCodes(); QString linkColor = Preferences::self()->color(Preferences::Hyperlink).name(); unsigned int rtl_chars = 0; unsigned int ltr_chars = 0; QString fromNick; TextUrlData urlData; TextChannelData channelData; if (parseURL) { QString strippedText(removeIrcMarkup(htmlText)); urlData = extractUrlData(strippedText); if (!urlData.urlRanges.isEmpty()) { // we detected the urls on a clean richtext-char-less text // to make 100% sure we get the correct urls, but as a result // we have to map them back to the original url adjustUrlRanges(urlData.urlRanges, urlData.fixedUrls, htmlText, strippedText); //Only set fromNick if we actually have a url, //yes this is a ultra-minor-optimization if (whoSent.isEmpty()) fromNick = m_chatWin->getName(); else fromNick = whoSent; } channelData = extractChannelData(strippedText); adjustUrlRanges(channelData.channelRanges, channelData.fixedChannels , htmlText, strippedText); } else { // Change & to & to prevent html entities to do strange things to the text htmlText.replace('&', "&"); htmlText.replace("\x0b", "&"); } int linkPos = -1; int linkOffset = 0; bool doChannel = false; if (parseURL) { //get next recent channel or link pos if (!urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { if (urlData.urlRanges.first() < channelData.channelRanges.first()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else { doChannel = true; linkPos = channelData.channelRanges.first().first; } } else if (!urlData.urlRanges.isEmpty() && channelData.channelRanges.isEmpty()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else if (urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { doChannel = true; linkPos = channelData.channelRanges.first().first; } else { linkPos = -1; } } // Remember last char for pair of spaces situation, see default in switch (htmlText.at(pos)... QChar lastChar; int offset; for (int pos = 0; pos < htmlText.length(); ++pos) { //check for next relevant url or channel link to insert if (parseURL && pos == linkPos+linkOffset) { if (doChannel) { QString fixedChannel = channelData.fixedChannels.takeFirst(); const QPair& range = channelData.channelRanges.takeFirst(); QString oldChannel = htmlText.mid(pos, range.second); QString strippedChannel = removeIrcMarkup(oldChannel); QString colorCodes = extractColorCodes(oldChannel); QString link("%1%3%4%5"); link = link.arg(closeTags(&data), fixedChannel, strippedChannel, openTags(&data, 0), colorCodes); htmlText.replace(pos, oldChannel.length(), link); pos += link.length() - colorCodes.length() - 1; linkOffset += link.length() - oldChannel.length(); } else { QString fixedUrl = urlData.fixedUrls.takeFirst(); const QPair& range = urlData.urlRanges.takeFirst(); QString oldUrl = htmlText.mid(pos, range.second); QString strippedUrl = removeIrcMarkup(oldUrl); QString closeTagsString(closeTags(&data)); QString colorCodes = extractColorCodes(oldUrl); colorCodes = removeDuplicateCodes(colorCodes, &data, allowColors); QString link("%1%3%4%5"); link = link.arg(closeTagsString, fixedUrl, strippedUrl, openTags(&data, 0), colorCodes); htmlText.replace(pos, oldUrl.length(), link); //url catcher QMetaObject::invokeMethod(Application::instance(), "storeUrl", Qt::QueuedConnection, Q_ARG(QString, fromNick), Q_ARG(QString, fixedUrl), Q_ARG(QDateTime, QDateTime::currentDateTime())); pos += link.length() - colorCodes.length() - 1; linkOffset += link.length() - oldUrl.length(); } bool invalidNextLink = false; do { if (!urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { if (urlData.urlRanges.first() < channelData.channelRanges.first()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else { doChannel = true; linkPos = channelData.channelRanges.first().first; } } else if (!urlData.urlRanges.isEmpty() && channelData.channelRanges.isEmpty()) { doChannel = false; linkPos = urlData.urlRanges.first().first; } else if (urlData.urlRanges.isEmpty() && !channelData.channelRanges.isEmpty()) { doChannel = true; linkPos = channelData.channelRanges.first().first; } else { linkPos = -1; } //for cases like "#www.some.url" we get first channel //and also url, the channel->clickable-channel replace we are //already after the url, so just forget it, as a clickable //channel is correct in this case if (linkPos > -1 && linkPos+linkOffset < pos) { invalidNextLink = true; if (doChannel) { channelData.channelRanges.removeFirst(); channelData.fixedChannels.removeFirst(); } else { urlData.urlRanges.removeFirst(); urlData.fixedUrls.removeFirst(); } } else { invalidNextLink = false; } } while (invalidNextLink); continue; } switch (htmlText.at(pos).toLatin1()) { case '\x02': //bold offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("b")); pos += offset -1; linkOffset += offset -1; break; case '\x1d': //italic offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("i")); pos += offset -1; linkOffset += offset -1; break; case '\x15': //mirc underline case '\x1f': //kvirc underline offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("u")); pos += offset -1; linkOffset += offset -1; break; case '\x13': //strikethru offset = defaultHtmlReplace(htmlText, &data, pos, QLatin1String("s")); pos += offset -1; linkOffset += offset -1; break; case '\x03': //color { QString fgColor, bgColor; bool fgOK = true, bgOK = true; QString colorMatch(getColors(htmlText, pos, fgColor, bgColor, &fgOK, &bgOK)); if (!allowColors) { htmlText.remove(pos, colorMatch.length()); pos -= 1; linkOffset -= colorMatch.length(); break; } QString colorString; // check for color reset conditions //TODO check if \x11 \017 is really valid here if (colorMatch == QLatin1String("\x03") || colorMatch == QLatin1String("\x11") || (fgColor.isEmpty() && bgColor.isEmpty()) || (!fgOK && !bgOK)) { //in reverse mode, just reset both colors //color tags are already closed before the reverse start if (data.reverse) { data.lastFgColor.clear(); data.lastBgColor.clear(); } else { if (data.openHtmlTags.contains(QLatin1String("font")) && data.openHtmlTags.contains(QLatin1String("span"))) { colorString += closeToTagString(&data, QLatin1String("span")); data.lastBgColor.clear(); colorString += closeToTagString(&data, QLatin1String("font")); data.lastFgColor.clear(); } else if (data.openHtmlTags.contains("font")) { colorString += closeToTagString(&data, QLatin1String("font")); data.lastFgColor.clear(); } } htmlText.replace(pos, colorMatch.length(), colorString); pos += colorString.length() - 1; linkOffset += colorString.length() -colorMatch.length(); break; } if (!fgOK) { fgColor = defaultColor; } if (!bgOK) { bgColor = fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); } // if we are in reverse mode, just remember the new colors if (data.reverse) { if (!fgColor.isEmpty()) { data.lastFgColor = fgColor; if (!bgColor.isEmpty()) { data.lastBgColor = bgColor; } } } // do we have a new fgColor? // NOTE: there is no new bgColor is there is no fgColor else if (!fgColor.isEmpty()) { if (data.openHtmlTags.contains(QLatin1String("font")) && data.openHtmlTags.contains(QLatin1String("span"))) { colorString += closeToTagString(&data, QLatin1String("span")); colorString += closeToTagString(&data, QLatin1String("font")); } else if (data.openHtmlTags.contains(QLatin1String("font"))) { colorString += closeToTagString(&data, QLatin1String("font")); } data.lastFgColor = fgColor; if (!bgColor.isEmpty()) data.lastBgColor = bgColor; if (!data.lastFgColor.isEmpty()) { colorString += fontColorOpenTag(data.lastFgColor); data.openHtmlTags.append(QLatin1String("font")); if (!data.lastBgColor.isEmpty()) { colorString += spanColorOpenTag(data.lastBgColor); data.openHtmlTags.append(QLatin1String("span")); } } } htmlText.replace(pos, colorMatch.length(), colorString); pos += colorString.length() - 1; linkOffset += colorString.length() -colorMatch.length(); break; } break; case '\x0f': //reset to default { QString closeText; while (!data.openHtmlTags.isEmpty()) { closeText += QLatin1String("'); } data.lastBgColor.clear(); data.lastFgColor.clear(); data.reverse = false; htmlText.replace(pos, 1, closeText); pos += closeText.length() - 1; linkOffset += closeText.length() - 1; } break; case '\x16': //reverse { // treat inverse as color and block it if colors are not allowed if (!allowColors) { htmlText.remove(pos, 1); pos -= 1; linkOffset -= 1; break; } QString colorString; // close current color strings and open reverse tags if (!data.reverse) { if (data.openHtmlTags.contains(QLatin1String("span"))) { colorString += closeToTagString(&data, QLatin1String("span")); } if (data.openHtmlTags.contains(QLatin1String("font"))) { colorString += closeToTagString(&data, QLatin1String("font")); } data.reverse = true; colorString += fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); data.openHtmlTags.append(QLatin1String("font")); colorString += spanColorOpenTag(defaultColor); data.openHtmlTags.append(QLatin1String("span")); } else { // if reset reverse, close reverse and set old fore- and // back-groundcolor if set in data colorString += closeToTagString(&data, QLatin1String("span")); colorString += closeToTagString(&data, QLatin1String("font")); data.reverse = false; if (!data.lastFgColor.isEmpty()) { colorString += fontColorOpenTag(data.lastFgColor); data.openHtmlTags.append(QLatin1String("font")); if (!data.lastBgColor.isEmpty()) { colorString += spanColorOpenTag(data.lastBgColor); data.openHtmlTags.append(QLatin1String("span")); } } } htmlText.replace(pos, 1, colorString); pos += colorString.length() -1; linkOffset += colorString.length() -1; } break; default: { const QChar& dirChar = htmlText.at(pos); // Replace pairs of spaces with " " to preserve some semblance of text wrapping //filteredLine.replace(" ", " \xA0"); // This used to work like above. But just for normal text like "test test" // It got replaced as "test \xA0 \xA0test" and QTextEdit showed 4 spaces. // In case of color/italic/bold codes we don't necessary get a real pair of spaces // just "test test" and QTextEdit shows it as 1 space. // Now if we remember the last char, to ignore html tags, and check if current and last ones are spaces // we replace the current one with \xA0 (a forced space) and get // "test \xA0 \xA0test", which QTextEdit correctly shows as 4 spaces. //NOTE: replacing all spaces with forced spaces will break text wrapping if (dirChar == ' ' && !lastChar.isNull() && lastChar == ' ') { htmlText[pos] = '\xA0'; lastChar = '\xA0'; } else { lastChar = dirChar; } if (!(dirChar.isNumber() || dirChar.isSymbol() || dirChar.isSpace() || dirChar.isPunct() || dirChar.isMark())) { switch(dirChar.direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: ltr_chars++; break; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: rtl_chars++; break; default: break; } } } } } if (direction) { // in case we found no right or left direction chars both // values are 0, but rtl_chars > ltr_chars is still false and QChar::DirL // is returned as default. if (rtl_chars > ltr_chars) *direction = QChar::DirR; else *direction = QChar::DirL; } if (parseURL) { // Change & to & to prevent html entities to do strange things to the text htmlText.replace('&', "&"); htmlText.replace("\x0b", "&"); } if (closeAllTags) { htmlText += closeTags(&data); } return htmlText; } int IRCView::defaultHtmlReplace(QString& htmlText, TextHtmlData* data, int pos, const QString& tag) { QString replace; if (data->openHtmlTags.contains(tag)) { replace = closeToTagString(data, tag); } else { data->openHtmlTags.append(tag); replace = QLatin1Char('<') + tag + QLatin1Char('>'); } htmlText.replace(pos, 1, replace); return replace.length(); } QString IRCView::closeToTagString(TextHtmlData* data, const QString& _tag) { QString ret; QString tag; int i = data->openHtmlTags.count() - 1; //close all tags to _tag for ( ; i >= 0 ; --i) { tag = data->openHtmlTags.at(i); ret += QLatin1String("'); if (tag == _tag) { data->openHtmlTags.removeAt(i); break; } } // reopen relevant tags ret += openTags(data, i); return ret; } QString IRCView::openTags(TextHtmlData* data, int from) { QString ret, tag; int i = from; for ( ; i < data->openHtmlTags.count(); ++i) { tag = data->openHtmlTags.at(i); if (tag == QLatin1String("font")) { if (data->reverse) { ret += fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); } else { ret += fontColorOpenTag(data->lastFgColor); } } else if (tag == QLatin1String("span")) { if (data->reverse) { ret += spanColorOpenTag(data->defaultColor); } else { ret += spanColorOpenTag(data->lastBgColor); } } else { ret += QLatin1Char('<') + tag + QLatin1Char('>'); } } return ret; } QString IRCView::closeTags(TextHtmlData* data) { QString ret; QListIterator< QString > i(data->openHtmlTags); i.toBack(); while (i.hasPrevious()) { ret += QLatin1String("'); } return ret; } QString IRCView::fontColorOpenTag(const QString& fgColor) { return QLatin1String(""); } QString IRCView::spanColorOpenTag(const QString& bgColor) { return QLatin1String(""); } QString IRCView::removeDuplicateCodes(const QString& codes, TextHtmlData* data, bool allowColors) { int pos = 0; QString ret; while (pos < codes.length()) { switch (codes.at(pos).toLatin1()) { case '\x02': //bold defaultRemoveDuplicateHandling(data, QLatin1String("b")); ++pos; break; case '\x1d': //italic defaultRemoveDuplicateHandling(data, QLatin1String("i")); ++pos; break; case '\x15': //mirc underline case '\x1f': //kvirc underline defaultRemoveDuplicateHandling(data, QLatin1String("u")); ++pos; break; case '\x13': //strikethru defaultRemoveDuplicateHandling(data, QLatin1String("s")); ++pos; break; case '\x0f': //reset to default data->openHtmlTags.clear(); data->lastBgColor.clear(); data->lastFgColor.clear(); data->reverse = false; ++pos; break; case '\x16': //reverse if (!allowColors) { pos += 1; continue; } if (data->reverse) { data->openHtmlTags.removeOne(QLatin1String("span")); data->openHtmlTags.removeOne(QLatin1String("font")); data->reverse = false; if (!data->lastFgColor.isEmpty()) { data->openHtmlTags.append(QLatin1String("font")); if (!data->lastBgColor.isEmpty()) { data->openHtmlTags.append(QLatin1String("span")); } } } else { data->openHtmlTags.removeOne(QLatin1String("span")); data->openHtmlTags.removeOne(QLatin1String("font")); data->reverse = true; data->openHtmlTags.append(QLatin1String("font")); data->openHtmlTags.append(QLatin1String("span")); } ++pos; break; case '\x03': //color { QString fgColor, bgColor; bool fgOK = true, bgOK = true; QString colorMatch(getColors(codes, pos, fgColor, bgColor, &fgOK, &bgOK)); if (!allowColors) { pos += colorMatch.length(); continue; } // check for color reset conditions //TODO check if \x11 \017 is really valid here if (colorMatch == QLatin1String("\x03") || colorMatch == QLatin1String("\x11") || (fgColor.isEmpty() && bgColor.isEmpty()) || (!fgOK && !bgOK)) { if (!data->lastBgColor.isEmpty()) { data->lastBgColor.clear(); data->openHtmlTags.removeOne(QLatin1String("span")); } if (!data->lastFgColor.isEmpty()) { data->lastFgColor.clear(); data->openHtmlTags.removeOne(QLatin1String("font")); } pos += colorMatch.length(); break; } if (!fgOK) { fgColor = data->defaultColor; } if (!bgOK) { bgColor = fontColorOpenTag(Preferences::self()->color(Preferences::TextViewBackground).name()); } if (!fgColor.isEmpty()) { data->lastFgColor = fgColor; data->openHtmlTags.append(QLatin1String("font")); if (!bgColor.isEmpty()) { data->lastBgColor = bgColor; data->openHtmlTags.append(QLatin1String("span")); } } pos += colorMatch.length(); } break; default: // qDebug() << "unsupported duplicate code:" << QString::number(codes.at(pos).toLatin1(), 16); ret += codes.at(pos); ++pos; } } return ret; } void IRCView::defaultRemoveDuplicateHandling(TextHtmlData* data, const QString& tag) { if (data->openHtmlTags.contains(tag)) { data->openHtmlTags.removeOne(tag); } else { data->openHtmlTags.append(tag); } } void IRCView::adjustUrlRanges(QList< QPair >& urlRanges, const QStringList& fixedUrls, QString& richtext, const QString& strippedText) { Q_UNUSED(fixedUrls); QRegExp ircRichtextRegExp(colorRegExp); int start = 0, j; int i = 0; QString url; int htmlTextLength = richtext.length(), urlCount = urlRanges.count(); for (int x = 0; x < urlCount; ++x) { if (x == 0) i = urlRanges.first().first; j = 0; const QPair& range = urlRanges.at(x); url = strippedText.mid(range.first, range.second); for ( ; i < htmlTextLength; ++i) { if (richtext.at(i) == url.at(j)) { if (j == 0) start = i; ++j; if (j == url.length()) { urlRanges[x].first = start; urlRanges[x].second = i - start + 1; break; } } else if (ircRichtextRegExp.exactMatch(richtext.at(i))) { ircRichtextRegExp.indexIn(richtext, i); i += ircRichtextRegExp.matchedLength() - 1; } else { j = 0; } } } } QString IRCView::getColors(const QString& text, int start, QString& _fgColor, QString& _bgColor, bool* fgValueOK, bool* bgValueOK) { QRegExp ircColorRegExp("(\003([0-9][0-9]|[0-9]|)(,([0-9][0-9]|[0-9]|)|,|)|\017)"); if (ircColorRegExp.indexIn(text,start) == -1) return QString(); QString ret(ircColorRegExp.cap(0)); QString fgColor(ircColorRegExp.cap(2)), bgColor(ircColorRegExp.cap(4)); if (!fgColor.isEmpty()) { int foregroundColor = fgColor.toInt(); if (foregroundColor > -1 && foregroundColor < 16) { _fgColor = Preferences::self()->ircColorCode(foregroundColor).name(); if (fgValueOK) *fgValueOK = true; } else { if (fgValueOK) *fgValueOK = false; } } else { if (fgValueOK) *fgValueOK = true; } if (!bgColor.isEmpty()) { int backgroundColor = bgColor.toInt(); if (backgroundColor > -1 && backgroundColor < 16) { _bgColor = Preferences::self()->ircColorCode(backgroundColor).name(); if (bgValueOK) *bgValueOK = true; } else { if (bgValueOK) *bgValueOK = false; } } else { if (bgValueOK) *bgValueOK = true; } return ret; } void IRCView::resizeEvent(QResizeEvent *event) { ScrollBarPin b(verticalScrollBar()); QTextBrowser::resizeEvent(event); } void IRCView::mouseMoveEvent(QMouseEvent* ev) { if (m_mousePressedOnUrl && (m_mousePressPosition - ev->pos()).manhattanLength() > QApplication::startDragDistance()) { m_mousePressedOnUrl = false; QTextCursor textCursor = this->textCursor(); textCursor.clearSelection(); setTextCursor(textCursor); QPointer drag = new QDrag(this); QMimeData* mimeData = new QMimeData; QUrl url(m_dragUrl); mimeData->setUrls(QList() << url); drag->setMimeData(mimeData); QPixmap pixmap = KIO::pixmapForUrl(url, 0, KIconLoader::Desktop, KIconLoader::SizeMedium); drag->setPixmap(pixmap); drag->exec(); return; } else { // Store the url here instead of in highlightedSlot as the link given there is decoded. m_urlToCopy = anchorAt(ev->pos()); } QTextBrowser::mouseMoveEvent(ev); } void IRCView::mousePressEvent(QMouseEvent* ev) { if (ev->button() == Qt::LeftButton) { m_dragUrl = anchorAt(ev->pos()); if (!m_dragUrl.isEmpty() && Konversation::isUrl(m_dragUrl)) { m_mousePressedOnUrl = true; m_mousePressPosition = ev->pos(); } } QTextBrowser::mousePressEvent(ev); } +void IRCView::wheelEvent(QWheelEvent *ev) +{ + if(ev->modifiers()==Qt::ControlModifier) + { + if(ev->delta() < 0) decreaseFontSize(); + if(ev->delta() > 0) increaseFontSize(); + } + + QTextBrowser::wheelEvent(ev); +} + void IRCView::mouseReleaseEvent(QMouseEvent *ev) { if (ev->button() == Qt::LeftButton) { m_mousePressedOnUrl = false; } else if (ev->button() == Qt::MidButton) { if (m_contextMenuOptions.testFlag(IrcContextMenus::ShowLinkActions)) { // The QUrl magic is what QTextBrowser's anchorClicked() does internally; // we copy it here for consistent behavior between left and middle clicks. openLink(QUrl::fromEncoded(m_urlToCopy.toUtf8())); // krazy:exclude=qclasses return; } else { emit textPasted(true); return; } } QTextBrowser::mouseReleaseEvent(ev); } void IRCView::keyPressEvent(QKeyEvent* ev) { const int key = ev->key() | ev->modifiers(); if (KStandardShortcut::paste().contains(key)) { emit textPasted(false); ev->accept(); return; } QTextBrowser::keyPressEvent(ev); } void IRCView::anchorClicked(const QUrl& url) { openLink(url); } void IRCView::openLink(const QUrl& url) { QString link(url.toString()); // HACK Replace " " with %20 for channelnames, NOTE there can't be 2 channelnames in one link link = link.replace (' ', "%20"); if (!link.isEmpty() && !link.startsWith('#')) Application::openUrl(url.toEncoded()); //FIXME: Don't do channel links in DCC Chats to begin with since they don't have a server. else if (link.startsWith(QLatin1String("##")) && m_server && m_server->isConnected()) { m_server->sendJoinCommand(link.mid(1)); } //FIXME: Don't do user links in DCC Chats to begin with since they don't have a server. else if (link.startsWith('#') && m_server && m_server->isConnected()) { QString recipient(link); recipient.remove('#'); NickInfoPtr nickInfo = m_server->obtainNickInfo(recipient); m_server->addQuery(nickInfo, true /*we initiated*/); } } void IRCView::highlightedSlot(const QString& /*_link*/) { QString link = m_urlToCopy; // HACK Replace " " with %20 for channelnames, NOTE there can't be 2 channelnames in one link link = link.replace (' ', "%20"); //we just saw this a second ago. no need to reemit. if (link == m_lastStatusText && !link.isEmpty()) return; if (link.isEmpty()) { if (!m_lastStatusText.isEmpty()) { emit clearStatusBarTempText(); m_lastStatusText.clear(); } } else { m_lastStatusText = link; } if (!link.startsWith(QLatin1Char('#'))) { m_isOnNick = false; m_isOnChannel = false; if (!link.isEmpty()) { //link therefore != m_lastStatusText so emit with this new text emit setStatusBarTempText(link); } if (link.isEmpty() && m_contextMenuOptions.testFlag(IrcContextMenus::ShowLinkActions)) setContextMenuOptions(IrcContextMenus::ShowLinkActions, false); else if (!link.isEmpty() && !m_contextMenuOptions.testFlag(IrcContextMenus::ShowLinkActions)) setContextMenuOptions(IrcContextMenus::ShowLinkActions, true); } else if (link.startsWith(QLatin1Char('#')) && !link.startsWith(QLatin1String("##"))) { m_currentNick = link.mid(1); m_isOnNick = true; emit setStatusBarTempText(i18n("Open a query with %1", m_currentNick)); } else { // link.startsWith("##") m_currentChannel = link.mid(1); m_isOnChannel = true; emit setStatusBarTempText(i18n("Join the channel %1", m_currentChannel)); } } void IRCView::setContextMenuOptions(IrcContextMenus::MenuOptions options, bool on) { if (on) m_contextMenuOptions |= options; else m_contextMenuOptions &= ~options; } void IRCView::contextMenuEvent(QContextMenuEvent* ev) { // Consider the following scenario: (1) context menu opened, (2) mouse // pointer moved, (3) mouse button clicked to dismiss menu, (4) mouse // button clicked to reopen context menu. In this scenario, if there is // no mouse movement between steps (3) and (4), highlighted() is never // emitted, and the data we use here to display the correct context menu // is outdated. Thus what we're going to do here is post a fake mouse // move event using the context menu event coordinate, forcing an update // just before we display the context menu. QMouseEvent fake(QEvent::MouseMove, ev->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); mouseMoveEvent(&fake); if (m_isOnChannel && m_server) { IrcContextMenus::channelMenu(ev->globalPos(), m_server, m_currentChannel); m_isOnChannel = false; return; } if (m_isOnNick && m_server) { IrcContextMenus::nickMenu(ev->globalPos(), m_contextMenuOptions, m_server, QStringList() << m_currentNick, m_chatWin->getName()); m_currentNick.clear(); m_isOnNick = false; return; } int contextMenuActionId = IrcContextMenus::textMenu(ev->globalPos(), m_contextMenuOptions, m_server, textCursor().selectedText(), m_urlToCopy, m_contextMenuOptions.testFlag(IrcContextMenus::ShowNickActions) ? m_chatWin->getName() : QString()); switch (contextMenuActionId) { case -1: break; case IrcContextMenus::TextCopy: copy(); break; case IrcContextMenus::TextSelectAll: selectAll(); break; default: if (m_contextMenuOptions.testFlag(IrcContextMenus::ShowNickActions)) { IrcContextMenus::processNickAction(contextMenuActionId, m_server, QStringList() << m_chatWin->getName(), m_contextMenuOptions.testFlag(IrcContextMenus::ShowChannelActions) ? m_chatWin->getName() : QString()); } break; } } // For more information about these RTFM // http://www.unicode.org/reports/tr9/ // http://www.w3.org/TR/unicode-xml/ QChar IRCView::LRM = (ushort)0x200e; // Right-to-Left Mark QChar IRCView::RLM = (ushort)0x200f; // Left-to-Right Mark QChar IRCView::LRE = (ushort)0x202a; // Left-to-Right Embedding QChar IRCView::RLE = (ushort)0x202b; // Right-to-Left Embedding QChar IRCView::RLO = (ushort)0x202e; // Right-to-Left Override QChar IRCView::LRO = (ushort)0x202d; // Left-to-Right Override QChar IRCView::PDF = (ushort)0x202c; // Previously Defined Format QChar::Direction IRCView::basicDirection(const QString& string) { // The following code decides between LTR or RTL direction for // a line based on the amount of each type of characters pre- // sent. It does so by counting, but stops when one of the two // counters becomes higher than half of the string length to // avoid unnecessary work. unsigned int pos = 0; unsigned int rtl_chars = 0; unsigned int ltr_chars = 0; unsigned int str_len = string.length(); unsigned int str_half_len = str_len/2; for(pos=0; pos < str_len; ++pos) { if (!(string[pos].isNumber() || string[pos].isSymbol() || string[pos].isSpace() || string[pos].isPunct() || string[pos].isMark())) { switch(string[pos].direction()) { case QChar::DirL: case QChar::DirLRO: case QChar::DirLRE: ltr_chars++; break; case QChar::DirR: case QChar::DirAL: case QChar::DirRLO: case QChar::DirRLE: rtl_chars++; break; default: break; } } if (ltr_chars > str_half_len) return QChar::DirL; else if (rtl_chars > str_half_len) return QChar::DirR; } if (rtl_chars > ltr_chars) return QChar::DirR; else return QChar::DirL; } #define dS d.space() #define dN d.nospace() QDebug operator<<(QDebug d, QTextBlockUserData *bd) { Burr* b = dynamic_cast(bd); if (b) { dN; d << "("; d << (void*)(b) << ", format=" << b->m_format << ", blockNumber=" << b->m_block.blockNumber() << " p,n=" << (void*)b->m_prev << ", " << (void*)b->m_next; d << ")"; } else if (bd) dN << "(UNKNOWN! " << (void*)bd << ")"; else d << "(none)"; return d.space(); } QDebug operator<<(QDebug d, QTextFrame* feed) { if (feed) { d << "\nDumping frame..."; dN << hex << (void*)feed << dec; QTextFrame::iterator it = feed->begin(); if (it.currentFrame() == feed) dS << "loop!" << endl; dS << "position" << feed->firstPosition() << feed->lastPosition(); dN << "parentFrame=" << (void*)feed->parentFrame(); dS; while (!it.atEnd()) { //d << "spin"; QTextFrame *frame = it.currentFrame(); if (!frame) // this is a block { //d<<"dumping blocks:"; QTextBlock b = it.currentBlock(); //d << "block" << b.position() << b.length(); d << endl << b; } else if (frame != feed) { d << frame; } ++it; }; d << "\n...done.\n"; } else d << "No frame to dump."; return d; } QDebug operator<<(QDebug d, QTextDocument* document) { d << "====================================================================================================================================================================="; if (document) d << document->rootFrame(); return d; } QDebug operator<<(QDebug d, QTextBlock b) { QTextBlock::Iterator it = b.begin(); int fragCount = 0; d << "blockNumber" << b.blockNumber(); d << "position" << b.position(); d << "length" << b.length(); dN << "firstChar 0x" << hex << b.document()->characterAt(b.position()).unicode() << dec; if (b.length() == 2) dN << " second 0x" << hex << b.document()->characterAt(b.position()+1).unicode() << dec; dS << "userState" << b.userState(); dN << "userData " << (void*)b.userData(); //dS << "text" << b.text(); dS << endl; if (b.userData()) d << b.userData(); for (it = b.begin(); !(it.atEnd()); ++it) { QTextFragment f = it.fragment(); if (f.isValid()) { fragCount++; //d << "frag" << fragCount << _S(f.position()) << _S(f.length()); } } d << _S(fragCount); return d; } diff --git a/src/viewer/ircview.h b/src/viewer/ircview.h index 7bc02ae1..03b545ef 100644 --- a/src/viewer/ircview.h +++ b/src/viewer/ircview.h @@ -1,336 +1,341 @@ /* 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. */ /* Copyright (C) 2002 Dario Abatianni Copyright (C) 2005-2016 Peter Simonsson Copyright (C) 2006-2008 Eike Hein */ #ifndef IRCVIEW_H #define IRCVIEW_H #include "common.h" #include "irccontextmenus.h" #include #include #include #include class Server; class ChatWindow; struct Burr; class IrcViewMarkerLine: public QObject, public QTextObjectInterface { Q_OBJECT Q_INTERFACES(QTextObjectInterface) public: IrcViewMarkerLine(QObject *p) : QObject(p), QTextObjectInterface() {} ~IrcViewMarkerLine() {} virtual void drawObject(QPainter *painter, const QRectF &rect, QTextDocument *doc, int posInDocument, const QTextFormat &format); virtual QSizeF intrinsicSize(QTextDocument *doc, int posInDocument, const QTextFormat &format); }; /// Helper struct which remembers the openHtmlTags, the fore and /// background color, if the reverse char is set and the defaultcolor (for reverse) /// while the ircrichtext -> html generation is in progress struct TextHtmlData { TextHtmlData() : reverse(false) { } QList openHtmlTags; QString lastFgColor; QString lastBgColor; bool reverse; QString defaultColor; }; class IRCView : public QTextBrowser { Q_OBJECT public: explicit IRCView(QWidget* parent); ~IRCView(); //! this function is proper given it is not nessary for the ircview to have a server for DCC. void setServer(Server* server); //! FIXME assumes the IRCView looks at a chatwin void setChatWin(ChatWindow* chatWin); bool search(const QString& pattern, QTextDocument::FindFlags flags, bool fromCursor); bool searchNext(bool reversed = false); //! FIXME maybe we should create some sort of palette of our own? QColor highlightColor() { return m_highlightColor; } void updateAppearance(); void setContextMenuOptions(IrcContextMenus::MenuOptions options, bool on); Q_SIGNALS: void gotFocus(); // So we can set focus to input line void textToLog(const QString& text); ///< send the to the log file void sendFile(); ///< a command for a target to which we can DCC send void autoText(const QString& text); ///< helper for autotext-on-highlight void textPasted(bool useSelection); ///< middle button with no m_copyUrlMenu void urlsDropped(const QList& urls); void doSearch(); /// Emitted when a search should be started void doSearchNext(); /// Emitted when there's a request to go to the next search result. void doSearchPrevious(); /// Emitted when there's a request to go to the previous search result. void setStatusBarTempText(const QString&); //! these two look like mixins to me void clearStatusBarTempText();//! these two look like mixins to me //// Marker lines public: /// Are there any markers or a remember lines in the view? ///Is used internally now. bool hasLines(); /// QTextBlockFormat states for setUserState. enum BlockStates { None = -1, BlockIsMarker = 1, BlockIsRemember = 2 }; /// QTextCharFormat object types. enum ObjectFormats { MarkerLine = QTextFormat::UserObject, RememberLine}; public Q_SLOTS: /// Inserts a marker line. /// Does not disturb m_rememberLineDirtyBit. void insertMarkerLine(); /// Insert a remember line now, or when text is appended. Sets the m_rememberLineDirtyBit /// unless configured to add a remember line at any time. void insertRememberLine(); /// Prevents the next append from inserting a remember line. ///Simply clears m_rememberLineDirtyBit. void cancelRememberLine(); /// Remove all of the marker lines, and the remember line. /// Does not effect m_rememberLineDirtyBit. void clearLines(); protected: virtual QMimeData* createMimeDataFromSelection() const; virtual void dragEnterEvent(QDragEnterEvent* e); virtual void dragMoveEvent(QDragMoveEvent* e); virtual void dropEvent(QDropEvent* e); private: /// The internal mechanics of inserting a line. /// Clears m_rememberLineDirtyBit. void appendRememberLine(); /// Create a remember line and insert it. /// @return - Pointer to the Burr that was inserted into the block Burr* appendLine(ObjectFormats=MarkerLine); /// Convenience method - forget the position of the remember line and markers. void wipeLineParagraphs(); /// Convenience method - is the last block any sort of line, or a specific line? /// @param select - default value is -1, meaning "is any kind of line" bool lastBlockIsLine(int select=-1); /// Causes a block to stop being a marker. void voidLineBlock(QTextBlock rem); /// Shortcut to get an object format of the desired type QTextCharFormat getFormat(ObjectFormats); public Q_SLOTS: // Doesn't have to be a slot, but what the hay. /// Called *only* from ~Burr(), by QTextBlockData::free void blockDeleted(Burr* b); private Q_SLOTS: /** Called every time a change occurs to the document. * * Used to infer the clearing of the entire document, * because Trolltech removed virtual from the method * that would indicate authoritatively. */ void cullMarkedLine(int, int, int); private: //marker/remember line data Burr *m_rememberLine, *m_lastMarkerLine; bool m_rememberLineDirtyBit; ///< the next append needs a remember line IrcViewMarkerLine markerFormatObject; ///< a QTextObjectInterface //// Other stuff public Q_SLOTS: //! FIXME enum { Raw, Query, Query+Action, Channel+Action, Server Message, Command Message, Backlog message } this looks more like a tuple void append(const QString& nick, const QString& message, const QHash &messageTags = QHash(), const QString& label = QString()); void appendRaw(const QString& message, bool self = false); void appendLog(const QString& message); void appendQuery(const QString& nick, const QString& message, const QHash &messageTags, bool inChannel = false); void appendQueryAction(const QString& nick, const QString& message, const QHash &messageTags); protected: //! FIXME why is this protected, and all alone down there? void appendAction(const QString& nick, const QString& message, const QHash &messageTags); /// Appends a new line without any scrollback or notification checks void doRawAppend(const QString& newLine, bool rtl); public Q_SLOTS: void appendChannelAction(const QString& nick, const QString& message, const QHash &messageTags); void appendServerMessage(const QString& type, const QString& message, const QHash &messageTags = QHash(), bool parseURL = true); void appendCommandMessage(const QString& command, const QString& message, const QHash &messageTags, bool parseURL=true, bool self=false); void appendBacklogMessage(const QString& firstColumn, const QString& message); protected: void doAppend(const QString& line, bool rtl, bool self=false); public Q_SLOTS: /// Emits the doSearch signal. void findText(); /// Emits the doSearchNext signal. void findNextText(); /// Emits the doSearchPrevious signal. void findPreviousText(); + void increaseFontSize(); + void decreaseFontSize(); + void resetFontSize(); + protected Q_SLOTS: void highlightedSlot(const QString& link); void anchorClicked(const QUrl& url); protected: void openLink(const QUrl &url); QString filter(const QString& line, const QString& defaultColor, const QString& who=QString(), bool doHighlight=true, bool parseURL=true, bool self=false, QChar::Direction* direction = 0); void replaceDecoration(QString& line, char decoration, char replacement); private: /// Returns a string where all irc-richtext chars are replaced with proper /// html tags and all urls are parsed if parseURL is true inline QString ircTextToHtml(const QString& text, bool parseURL, const QString& defaultColor, const QString& whoSent, bool closeAllTags = true, QChar::Direction* direction = 0); /// Returns a string that closes all open html tags to tag /// The closed tag is removed from opentagList in data inline QString closeToTagString(TextHtmlData* data, const QString& tag); /// Returns a html open span line with given backgroundcolor style inline QString spanColorOpenTag(const QString& bgColor); /// Returns a html open font line with given foregroundcolor inline QString fontColorOpenTag(const QString& fgColor); /// Insert a string that closes as open html tags to tag and reopen the remaining ones. /// For the next example I will use [b] as boldchar and [i] as italic char /// If are currently working on text like /// "aabbcc[b]dd[i]ee" /// it would generate for the next [b], "". /// is reopened as it is still relevant /// Returns the Length of the inserted String inline int defaultHtmlReplace(QString& htmlText, TextHtmlData* data, int pos, const QString& tag); /// Returns a string that opens all tags starting from index from inline QString openTags(TextHtmlData* data, int from = 0); /// Returns a string that closes all open tags /// but does not remove them from the opentaglist in data inline QString closeTags(TextHtmlData* data); /// This function looks in codes which tags it open/closes /// and appends/removes them from opentagList in data. /// This way we avoid pointless empty tags after the url like "" /// The returned string consists of all codes that this function could not deal with, /// which is the best case empty. QString removeDuplicateCodes(const QString& codes, TextHtmlData* data, bool allowColors); /// Helperfunction for removeDuplicateCodes, for dealing with simple irc richtext /// chars as bold, italic, underline and strikethrou. /// The default behaivor is to look if the tag is already in the /// opentagList in data and remove it if in case if is in, or /// append it in case it is not. inline void defaultRemoveDuplicateHandling(TextHtmlData* data, const QString& tag); /// Changes the ranges in urlRanges, that are found in /// strippedText, to match in richText. /// This is needed for cases were the url is tainted by ircrichtext chars inline void adjustUrlRanges(QList< QPair< int, int > >& urlRanges, const QStringList& fixedUrls, QString& richtext, const QString& strippedText); /// Parses the colors in text starting from start /// and returns them in the given fg and bg string, as well as information /// if the values are valid inline QString getColors(const QString& text, int start, QString& _fgColor, QString& _bgColor, bool* invalidFgVal, bool* invalidBgValue); protected: virtual void resizeEvent(QResizeEvent *event); virtual void mouseReleaseEvent(QMouseEvent* ev); virtual void mousePressEvent(QMouseEvent* ev); virtual void mouseMoveEvent(QMouseEvent* ev); virtual void keyPressEvent(QKeyEvent* ev); virtual void contextMenuEvent(QContextMenuEvent* ev); + virtual void wheelEvent(QWheelEvent* ev); QChar::Direction basicDirection(const QString &string); /// Returns a formated timestamp if timestamps are enabled else it returns QString::null QString timeStamp(QHash messageTags); /// Returns a formated nick string //! FIXME formatted in what way? QString createNickLine(const QString& nick, const QString& defaultColor, bool encapsulateNick = true, bool privMsg = false); //// Search QTextDocument::FindFlags m_searchFlags; bool m_forward; QString m_pattern; //used in ::filter QColor m_highlightColor; QString m_lastStatusText; //last sent status text to the statusbar. Is empty after clearStatusBarTempText() //used in ::filter QString m_autoTextToSend; //TODO FIXME light this on fire and send it sailing down an uncharted river riddled with arrows Konversation::TabNotifyType m_tabNotification; Server* m_server; //! FIXME assumes we have a server //// RTL hack static QChar LRM; static QChar RLM; static QChar LRE; static QChar RLE; static QChar RLO; static QChar LRO; static QChar PDF; IrcContextMenus::MenuOptions m_contextMenuOptions; QString m_currentNick; QString m_currentChannel; QString m_urlToCopy; ///< the URL we might be about to copy bool m_isOnNick; ///< context menu click hit a nickname bool m_isOnChannel; ///< context menu click hit a channel bool m_mousePressedOnUrl; ///< currently processing a mouse press QPoint m_mousePressPosition; ///< x,y of the click, relative to the GPS location of tip of Phantom's left ear QString m_dragUrl; ///< we took a stab at whatever was clicked on, may or may not actually be a URL //! TODO FIXME i'll bite. why do we have this in here? QFontDatabase m_fontDataBase; ChatWindow* m_chatWin; friend class IRCStyleSheet; }; #endif diff --git a/src/viewer/viewcontainer.cpp b/src/viewer/viewcontainer.cpp index 520c42c9..a2fe93dc 100644 --- a/src/viewer/viewcontainer.cpp +++ b/src/viewer/viewcontainer.cpp @@ -1,3041 +1,3065 @@ /* 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. */ /* Copyright (C) 2006-2008 Eike Hein */ #include "viewcontainer.h" #include "connectionmanager.h" #include "queuetuner.h" #include "application.h" #include "notificationhandler.h" #include "images.h" #include "irccharsets.h" #include "ircview.h" #include "ircinput.h" #include "logfilereader.h" #include "konsolepanel.h" #include "urlcatcher.h" #include "transferpanel.h" #include "transfermanager.h" #include "chatcontainer.h" #include "statuspanel.h" #include "channel.h" #include "query.h" #include "rawlog.h" #include "channellistpanel.h" #include "nicksonline.h" #include "insertchardialog.h" #include "irccolorchooser.h" #include "joinchanneldialog.h" #include "servergroupsettings.h" #include "irccontextmenus.h" #include "viewtree.h" #include "viewspringloader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Konversation; ViewMimeData::ViewMimeData(ChatWindow *view) : QMimeData() , m_view(view) { if (view) { setData("application/x-konversation-chatwindow", view->getName().toUtf8()); } } ViewMimeData::~ViewMimeData() { } ChatWindow* ViewMimeData::view() const { return m_view; } TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent) { } TabWidget::~TabWidget() { } void TabWidget::contextMenuEvent(QContextMenuEvent* event) { event->accept(); QPoint pos = event->globalPos(); int tabIndex = tabBar()->tabAt(tabBar()->mapFromGlobal(pos)); if (tabIndex != -1) { emit contextMenu(widget(tabIndex), pos); } } void TabWidget::mouseReleaseEvent(QMouseEvent* event) { if(event->button() == Qt::MiddleButton) { event->accept(); QPoint pos = event->globalPos(); int tabIndex = tabBar()->tabAt(tabBar()->mapFromGlobal(pos)); if(tabIndex != -1) { emit tabBarMiddleClicked(tabIndex); } } QTabWidget::mouseReleaseEvent(event); } ViewContainer::ViewContainer(MainWindow* window) : QAbstractItemModel(window) , m_window(window) , m_tabWidget(0) , m_viewTree(0) , m_vbox(0) , m_queueTuner(0) , m_urlCatcherPanel(0) , m_nicksOnlinePanel(0) , m_dccPanel(0) , m_insertCharDialog(0) , m_queryViewCount(0) { m_viewSpringLoader = new ViewSpringLoader(this); images = Application::instance()->images(); m_viewTreeSplitter = new QSplitter(m_window); m_viewTreeSplitter->setObjectName("view_tree_splitter"); m_saveSplitterSizesLock = true; // The tree needs to be initialized before the tab widget so that it // may assume a leading role in view selection management. if (Preferences::self()->tabPlacement()==Preferences::Left) setupViewTree(); setupTabWidget(); initializeSplitterSizes(); m_dccPanel = new DCC::TransferPanel(m_tabWidget); m_dccPanel->hide(); m_dccPanelOpen = false; connect(m_dccPanel, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); // Pre-construct context menus for better responsiveness when then // user opens them the first time. This is optional; the IrcContext- // Menus API would work fine without doing this here. // IrcContextMenus' setup code calls Application::instance(), and // ViewContainer is constructed in the scope of the Application // constructor, so to avoid a crash we need to queue. QMetaObject::invokeMethod(this, "setupIrcContextMenus", Qt::QueuedConnection); } ViewContainer::~ViewContainer() { } void ViewContainer::setupIrcContextMenus() { IrcContextMenus::self(); } void ViewContainer::showQueueTuner(bool p) { if (p) m_queueTuner->open(); else m_queueTuner->close(); } ///Use this instead of setting m_frontServer directly so we can emit the frontServerChanging signal easily. void ViewContainer::setFrontServer(Server* newserver) { if (m_frontServer == QPointer(newserver)) return; emit frontServerChanging(newserver); m_frontServer = newserver; } void ViewContainer::prepareShutdown() { if (!m_tabWidget) return; deleteDccPanel(); closeNicksOnlinePanel(); for (int i = 0; i < m_tabWidget->count(); ++i) m_tabWidget->widget(i)->blockSignals(true); m_tabWidget->blockSignals(true); m_tabWidget = 0; } void ViewContainer::initializeSplitterSizes() { if (m_viewTree && !m_viewTree->isHidden()) { QList sizes = Preferences::self()->treeSplitterSizes(); if (sizes.isEmpty()) sizes << 145 << (m_window->width() - 145); // FIXME: Make DPI-aware. m_viewTreeSplitter->setSizes(sizes); m_saveSplitterSizesLock = false; } } void ViewContainer::saveSplitterSizes() { if (!m_saveSplitterSizesLock) { Preferences::self()->setTreeSplitterSizes(m_viewTreeSplitter->sizes()); m_saveSplitterSizesLock = false; } } void ViewContainer::setupTabWidget() { m_popupViewIndex = -1; m_vbox = new QWidget(m_viewTreeSplitter); QVBoxLayout* vboxLayout = new QVBoxLayout(m_vbox); vboxLayout->setMargin(0); m_viewTreeSplitter->setStretchFactor(m_viewTreeSplitter->indexOf(m_vbox), 1); m_vbox->setObjectName("main_window_right_side"); m_tabWidget = new TabWidget(m_vbox); vboxLayout->addWidget(m_tabWidget); m_tabWidget->setObjectName("main_window_tab_widget"); m_viewSpringLoader->addWidget(m_tabWidget->tabBar()); m_queueTuner = new QueueTuner(m_vbox, this); vboxLayout->addWidget(m_queueTuner); m_queueTuner->hide(); m_tabWidget->setMovable(true); m_tabWidget->tabBar()->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); m_vbox->hide(); QToolButton* closeBtn = new QToolButton(m_tabWidget); closeBtn->setIcon(SmallIcon("tab-close")); closeBtn->adjustSize(); m_tabWidget->setCornerWidget(closeBtn, Qt::BottomRightCorner); connect(closeBtn, SIGNAL(clicked()), this, SLOT(closeCurrentView())); connect(m_tabWidget, SIGNAL(currentChanged(int)), this, SLOT (viewSwitched(int))); connect(m_tabWidget->tabBar(), SIGNAL(tabCloseRequested(int)), this, SLOT(closeView(int))); connect(m_tabWidget, SIGNAL(contextMenu(QWidget*,QPoint)), this, SLOT(showViewContextMenu(QWidget*,QPoint))); connect(m_tabWidget, SIGNAL(tabBarMiddleClicked(int)), this, SLOT(closeViewMiddleClick(int))); updateTabWidgetAppearance(); } +void ViewContainer::resetFont() +{ + m_frontView->getTextView()->resetFontSize(); +} + +void ViewContainer::zoomIn() +{ + if (m_frontView->getTextView()) + m_frontView->getTextView()->increaseFontSize(); +} + +void ViewContainer::zoomOut() +{ + if (m_frontView->getTextView()) + m_frontView->getTextView()->decreaseFontSize(); +} + void ViewContainer::setupViewTree() { unclutterTabs(); m_viewTree = new ViewTree(m_viewTreeSplitter); m_viewTree->setModel(this); m_viewTreeSplitter->insertWidget(0, m_viewTree); m_viewTreeSplitter->setStretchFactor(m_viewTreeSplitter->indexOf(m_viewTree), 0); m_viewSpringLoader->addWidget(m_viewTree->viewport()); if (m_tabWidget) { m_viewTree->selectView(indexForView(static_cast(m_tabWidget->currentWidget()))); setViewTreeShown(m_tabWidget->count()); } else { setViewTreeShown(false); } connect(m_viewTree, SIGNAL(sizeChanged()), this, SLOT(saveSplitterSizes())); connect(m_viewTree, SIGNAL(showView(ChatWindow*)), this, SLOT(showView(ChatWindow*))); connect(m_viewTree, SIGNAL(closeView(ChatWindow*)), this, SLOT(closeView(ChatWindow*))); connect(m_viewTree, SIGNAL(showViewContextMenu(QWidget*,QPoint)), this, SLOT(showViewContextMenu(QWidget*,QPoint))); connect(m_viewTree, SIGNAL(destroyed(QObject*)), this, SLOT(onViewTreeDestroyed(QObject*))); connect(this, SIGNAL(contextMenuClosed()), m_viewTree->viewport(), SLOT(update())); connect(Application::instance(), SIGNAL(appearanceChanged()), m_viewTree, SLOT(updateAppearance())); connect(this, SIGNAL(viewChanged(QModelIndex)), m_viewTree, SLOT(selectView(QModelIndex))); QAction* action; action = actionCollection()->action("move_tab_left"); if (action) { action->setText(i18n("Move Tab Up")); action->setIcon(QIcon::fromTheme("arrow-up")); } action = actionCollection()->action("move_tab_right"); if (action) { action->setText(i18n("Move Tab Down")); action->setIcon(QIcon::fromTheme("arrow-down")); } } void ViewContainer::onViewTreeDestroyed(QObject* object) { Q_UNUSED(object) setViewTreeShown(false); } void ViewContainer::setViewTreeShown(bool show) { if (m_viewTree) { if (!show) { m_saveSplitterSizesLock = true; m_viewTree->hide(); } else { m_viewTree->show(); initializeSplitterSizes(); m_saveSplitterSizesLock = false; } } } void ViewContainer::removeViewTree() { QAction* action; action = actionCollection()->action("move_tab_left"); if (action) { action->setText(i18n("Move Tab Left")); action->setIcon(QIcon::fromTheme("arrow-left")); } action = actionCollection()->action("move_tab_right"); if (action) { action->setText(i18n("Move Tab Right")); action->setIcon(QIcon::fromTheme("arrow-right")); } delete m_viewTree; m_viewTree = 0; } int ViewContainer::rowCount(const QModelIndex& parent) const { if (!m_tabWidget) { return 0; } if (parent.isValid()) { ChatWindow* statusView = static_cast(parent.internalPointer()); if (!statusView) { return 0; } int count = 0; for (int i = m_tabWidget->indexOf(statusView) + 1; i < m_tabWidget->count(); ++i) { const ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view != statusView && view->getServer() && view->getServer()->getStatusView() == statusView) { ++count; } if (view->isTopLevelView()) { break; } } return count; } else { int count = 0; for (int i = 0; i < m_tabWidget->count(); ++i) { const ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view->isTopLevelView()) { ++count; } } return count; } return 0; } int ViewContainer::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 1; } QModelIndex ViewContainer::index(int row, int column, const QModelIndex& parent) const { if (!m_tabWidget || column != 0) { return QModelIndex(); } int tabIndex = -1; if (parent.isValid()) { int parentTabIndex = m_tabWidget->indexOf(static_cast(parent.internalPointer())); if (parentTabIndex != -1) { tabIndex = parentTabIndex + row + 1; } else { return QModelIndex(); } } else { int count = -1; for (int i = 0; i < m_tabWidget->count(); ++i) { if (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { ++count; } if (count == row) { tabIndex = i; break; } } } if (tabIndex == -1) { return QModelIndex(); } return createIndex(row, column, m_tabWidget->widget(tabIndex)); } QModelIndex ViewContainer::indexForView(ChatWindow* view) const { if (!view || !m_tabWidget) { return QModelIndex(); } int index = m_tabWidget->indexOf(view); if (index == -1) { return QModelIndex(); } if (view->isTopLevelView()) { int count = -1; for (int i = 0; i <= index; ++i) { if (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { ++count; } } return createIndex(count, 0, view); } else { if (!view->getServer() || !view->getServer()->getStatusView()) { return QModelIndex(); } ChatWindow* statusView = view->getServer()->getStatusView(); int statusViewIndex = m_tabWidget->indexOf(statusView); return createIndex(index - statusViewIndex - 1, 0, view); } } QModelIndex ViewContainer::parent(const QModelIndex& index) const { if (!m_tabWidget) { return QModelIndex(); } const ChatWindow* view = static_cast(index.internalPointer()); if (!view || view->isTopLevelView() || !view->getServer() || !view->getServer()->getStatusView()) { return QModelIndex(); } return indexForView(view->getServer()->getStatusView()); } QVariant ViewContainer::data(const QModelIndex& index, int role) const { if (!index.isValid() || !index.internalPointer() || !m_tabWidget) { return QVariant(); } int row = m_tabWidget->indexOf(static_cast(index.internalPointer())); if (role == Qt::DisplayRole) { return static_cast(index.internalPointer())->getName(); } else if (role == Qt::DecorationRole) { // FIXME KF5 port: Don't show close buttons on the view tree for now. if (m_viewTree && Preferences::self()->closeButtons() && !Preferences::self()->tabNotificationsLeds()) { return QVariant(); } return m_tabWidget->tabIcon(row); } else if (role == ColorRole) { const ChatWindow* view = static_cast(index.internalPointer()); if (view->currentTabNotification() != Konversation::tnfNone) { if ((view->currentTabNotification() == Konversation::tnfNormal && Preferences::self()->tabNotificationsMsgs()) || (view->currentTabNotification() == Konversation::tnfPrivate && Preferences::self()->tabNotificationsPrivate()) || (view->currentTabNotification() == Konversation::tnfSystem && Preferences::self()->tabNotificationsSystem()) || (view->currentTabNotification() == Konversation::tnfControl && Preferences::self()->tabNotificationsEvents()) || (view->currentTabNotification() == Konversation::tnfNick && Preferences::self()->tabNotificationsNick()) || (view->currentTabNotification() == Konversation::tnfHighlight && Preferences::self()->tabNotificationsHighlights())) { return m_tabWidget->tabBar()->tabTextColor(row); } } } else if (role == DisabledRole) { const ChatWindow* view = static_cast(index.internalPointer()); if (view->getType() == ChatWindow::Channel) { return !static_cast(view)->joined(); } else if (view->getType() == ChatWindow::Query) { return !view->getServer()->isConnected(); } return false; } else if (role == HighlightRole) { return (row == m_popupViewIndex); } return QVariant(); } Qt::DropActions ViewContainer::supportedDragActions() const { return Qt::MoveAction; } Qt::DropActions ViewContainer::supportedDropActions() const { return Qt::MoveAction; } Qt::ItemFlags ViewContainer::flags(const QModelIndex &index) const { Qt::ItemFlags defaultFlags = QAbstractItemModel::flags(index); return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags; } QStringList ViewContainer::mimeTypes() const { return QStringList() << QLatin1String("application/x-konversation-chatwindow"); } QMimeData* ViewContainer::mimeData(const QModelIndexList &indexes) const { if (!indexes.length()) { return new ViewMimeData(0); } const QModelIndex &idx = indexes.at(0); if (!idx.isValid()) { return new ViewMimeData(0); } return new ViewMimeData(static_cast(idx.internalPointer())); } bool ViewContainer::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { if (action != Qt::MoveAction) { return false; } if (!data->hasFormat("application/x-konversation-chatwindow")) { return false; } if (row == -1 || column != 0) { return false; } ChatWindow *dragView = static_cast(data)->view(); if (!dragView->isTopLevelView() && (!parent.isValid() || (dragView->getServer() != static_cast(parent.internalPointer())->getServer()))) { return false; } if (dragView->isTopLevelView() && parent.isValid()) { return false; } if (m_viewTree && !m_viewTree->showDropIndicator()) { m_viewTree->setDropIndicatorShown(true); m_viewTree->viewport()->update(); } return true; } bool ViewContainer::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (action != Qt::MoveAction) { return false; } if (!data->hasFormat("application/x-konversation-chatwindow")) { return false; } if (row == -1 || column != 0) { return false; } ChatWindow *dragView = static_cast(data)->view(); QModelIndex dragIdx = indexForView(dragView); if (dragView->isTopLevelView()) { if (parent.isValid()) { return false; } for (int i = row < dragIdx.row() ? 0 : 1; i < qAbs(dragIdx.row() - row); ++i) { (row < dragIdx.row()) ? moveViewLeft() : moveViewRight(); } return true; } else { if (!parent.isValid()) { return false; } int from = m_tabWidget->indexOf(dragView); int to = m_tabWidget->indexOf(static_cast(parent.internalPointer())) + row; if (to < from) { ++to; } if (from == to) { return false; } beginMoveRows(parent, dragIdx.row(), dragIdx.row(), parent, row); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(from, to); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); return true; } return false; } bool ViewContainer::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(row) Q_UNUSED(count) Q_UNUSED(parent) return true; } void ViewContainer::updateAppearance() { if (Preferences::self()->tabPlacement()==Preferences::Left && m_viewTree == 0) { m_saveSplitterSizesLock = true; setupViewTree(); } if (!(Preferences::self()->tabPlacement()==Preferences::Left) && m_viewTree) { m_saveSplitterSizesLock = true; removeViewTree(); } updateViews(); updateTabWidgetAppearance(); KToggleAction* action = qobject_cast(actionCollection()->action("hide_nicknamelist")); Q_ASSERT(action); action->setChecked(Preferences::self()->showNickList()); if (m_insertCharDialog) { QFont font; if (Preferences::self()->customTextFont()) font = Preferences::self()->textFont(); else font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); m_insertCharDialog->setFont(font); } } void ViewContainer::updateTabWidgetAppearance() { bool noTabBar = (Preferences::self()->tabPlacement()==Preferences::Left); m_tabWidget->tabBar()->setHidden(noTabBar); m_tabWidget->setDocumentMode(true); if (Preferences::self()->customTabFont()) m_tabWidget->tabBar()->setFont(Preferences::self()->tabFont()); else m_tabWidget->tabBar()->setFont(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); m_tabWidget->setTabPosition((Preferences::self()->tabPlacement()==Preferences::Top) ? QTabWidget::North : QTabWidget::South); if (Preferences::self()->showTabBarCloseButton() && !noTabBar) m_tabWidget->cornerWidget()->show(); else m_tabWidget->cornerWidget()->hide(); m_tabWidget->tabBar()->setTabsClosable(Preferences::self()->closeButtons()); } void ViewContainer::updateViewActions(int index) { if (!m_tabWidget) return; QAction* action; ChatWindow* view = 0; if (index != -1) view = static_cast(m_tabWidget->widget(index)); if (m_tabWidget->count() > 0 && view) { ChatWindow::WindowType viewType = view->getType(); Server* server = view->getServer(); bool insertSupported = view->isInsertSupported(); IRCView* textView = view->getTextView(); // FIXME ViewTree port: Take hierarchy into account. action = actionCollection()->action("move_tab_left"); if (action) action->setEnabled(canMoveViewLeft()); action = actionCollection()->action("move_tab_right"); if (action) action->setEnabled(canMoveViewRight()); if (server && (viewType == ChatWindow::Status || server == m_frontServer)) { action = actionCollection()->action("reconnect_server"); if (action) action->setEnabled(true); - action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(server->isConnected() || server->isConnecting() || server->isScheduledToConnect()); - action = actionCollection()->action("join_channel"); if (action) action->setEnabled(server->isConnected()); } else { action = actionCollection()->action("reconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("join_channel"); if (action) action->setEnabled(false); } KToggleAction* notifyAction = static_cast(actionCollection()->action("tab_notifications")); if (notifyAction) { notifyAction->setEnabled(viewType == ChatWindow::Channel || viewType == ChatWindow::Query || viewType == ChatWindow::Status || viewType == ChatWindow::Konsole || viewType == ChatWindow::DccTransferPanel || viewType == ChatWindow::RawLog); notifyAction->setChecked(view->notificationsEnabled()); } KToggleAction* autoJoinAction = static_cast(actionCollection()->action("tab_autojoin")); Channel* channel = static_cast(view); if (autoJoinAction && viewType == ChatWindow::Channel && channel->getServer()->getServerGroup()) { autoJoinAction->setEnabled(true); autoJoinAction->setChecked(channel->autoJoin()); } else if (!(viewType != ChatWindow::Channel && index != m_tabWidget->currentIndex())) { autoJoinAction->setEnabled(false); autoJoinAction->setChecked(false); } KToggleAction* autoConnectAction = static_cast(actionCollection()->action("tab_autoconnect")); if (autoConnectAction && server && (viewType == ChatWindow::Status || server == m_frontServer) && server->getServerGroup()) { autoConnectAction->setEnabled(true); autoConnectAction->setChecked(server->getServerGroup()->autoConnectEnabled()); } else if (!(viewType != ChatWindow::Status && index != m_tabWidget->currentIndex())) { autoConnectAction->setEnabled(false); autoConnectAction->setChecked(false); } action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(viewType == ChatWindow::Channel && channel->rejoinable()); action = actionCollection()->action("close_queries"); if (action) action->setEnabled(m_queryViewCount > 0); action = actionCollection()->action("clear_tabs"); if (action) action->setEnabled(true); + action = actionCollection()->action("increase_font"); + if (action) action->setEnabled(true); + + action = actionCollection()->action("shrink_font"); + if (action) action->setEnabled(true); + + action = actionCollection()->action("reset_font"); + if (action) action->setEnabled(true); + action = actionCollection()->action("toggle_away"); if (action) action->setEnabled(true); action = actionCollection()->action("next_tab"); if (action) action->setEnabled(true); action = actionCollection()->action("previous_tab"); if (action) action->setEnabled(true); action = actionCollection()->action("next_active_tab"); if (action) action->setEnabled(true); action = actionCollection()->action("close_tab"); if (action) action->setEnabled(true); if (index == m_tabWidget->currentIndex()) { // The following only need to be updated when this run is related // to the active tab, e.g. when it was just changed. action = actionCollection()->action("insert_marker_line"); if (action) action->setEnabled(textView != 0); action = actionCollection()->action("insert_character"); if (action) action->setEnabled(insertSupported); action = actionCollection()->action("irc_colors"); if (action) action->setEnabled(insertSupported); action = actionCollection()->action("auto_replace"); if (action) action->setEnabled(view->getInputBar() != 0); action = actionCollection()->action("focus_input_box"); if (action) { action->setEnabled(view->getInputBar() != 0); if (view->getTextView() && view->getTextView()->parent()) { //HACK See notes in SearchBar::eventFilter QEvent e(static_cast(QEvent::User+414)); // Magic number to avoid QEvent::registerEventType Application::instance()->sendEvent(view->getTextView()->parent(), &e); } } action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(textView != 0 && view->getTextView()->hasLines()); action = actionCollection()->action("clear_window"); if (action) action->setEnabled(textView != 0); action = actionCollection()->action("edit_find"); if (action) { action->setText(i18n("Find Text...")); action->setEnabled(view->searchView()); action->setStatusTip(i18n("Search for text in the current tab")); } action = actionCollection()->action("edit_find_next"); if (action) action->setEnabled(view->searchView()); action = actionCollection()->action("edit_find_prev"); if (action) action->setEnabled(view->searchView()); KToggleAction* channelListAction = static_cast(actionCollection()->action("open_channel_list")); if (channelListAction) { if (m_frontServer) { QString name = m_frontServer->getDisplayName(); name = name.replace('&', "&&"); channelListAction->setEnabled(true); channelListAction->setChecked(m_frontServer->getChannelListPanel()); channelListAction->setText(i18n("Channel &List for %1",name)); } else { channelListAction->setEnabled(false); channelListAction->setChecked(false); channelListAction->setText(i18n("Channel &List")); } } action = actionCollection()->action("open_logfile"); if (action) { action->setEnabled(!view->logFileName().isEmpty()); if (view->logFileName().isEmpty()) action->setText(i18n("&Open Logfile")); else { QString name = view->getName(); name = name.replace('&', "&&"); action->setText(i18n("&Open Logfile for %1",name)); } } action = actionCollection()->action("hide_nicknamelist"); if (action) action->setEnabled(view->getType() == ChatWindow::Channel); action = actionCollection()->action("channel_settings"); if (action && view->getType() == ChatWindow::Channel) { action->setEnabled(true); action->setText(i18n("&Channel Settings for %1...",view->getName())); } else if (action) { action->setEnabled(false); action->setText(i18n("&Channel Settings...")); } } } else { action = actionCollection()->action("move_tab_left"); if (action) action->setEnabled(false); action = actionCollection()->action("move_tab_right"); if(action) action->setEnabled(false); action = actionCollection()->action("next_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("previous_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("close_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("next_active_tab"); if (action) action->setEnabled(false); action = actionCollection()->action("tab_notifications"); if (action) action->setEnabled(false); action = actionCollection()->action("tab_autojoin"); if (action) action->setEnabled(false); action = actionCollection()->action("tab_autoconnect"); if (action) action->setEnabled(false); action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(false); action = actionCollection()->action("insert_marker_line"); if (action) action->setEnabled(false); action = actionCollection()->action("insert_character"); if (action) action->setEnabled(false); action = actionCollection()->action("irc_colors"); if (action) action->setEnabled(false); action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(false); action = actionCollection()->action("clear_window"); if (action) action->setEnabled(false); action = actionCollection()->action("clear_tabs"); if (action) action->setEnabled(false); action = actionCollection()->action("edit_find"); if (action) action->setEnabled(false); action = actionCollection()->action("edit_find_next"); if (action) action->setEnabled(false); action = actionCollection()->action("edit_find_prev"); if (action) action->setEnabled(false); action = actionCollection()->action("open_channel_list"); if (action) action->setEnabled(false); action = actionCollection()->action("open_logfile"); if (action) action->setEnabled(false); action = actionCollection()->action("toggle_away"); if (action) action->setEnabled(false); action = actionCollection()->action("join_channel"); if (action) action->setEnabled(false); action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("reconnect_server"); if (action) action->setEnabled(false); action = actionCollection()->action("hide_nicknamelist"); if (action) action->setEnabled(false); action = actionCollection()->action("channel_settings"); if (action) action->setEnabled(false); action = actionCollection()->action("close_queries"); if (action) action->setEnabled(false); } action = actionCollection()->action("last_focused_tab"); if (action) action->setEnabled(m_lastFocusedView != 0); } void ViewContainer::updateFrontView() { if (!m_tabWidget) return; ChatWindow* view = static_cast(m_tabWidget->currentWidget()); if (!view) return; // Make sure that only views with info output get to be the m_frontView if (m_frontView) { disconnect(m_frontView, SIGNAL(updateInfo(QString)), this, SIGNAL(setStatusBarInfoLabel(QString))); } if (view->canBeFrontView()) { m_frontView = view; connect(view, SIGNAL(updateInfo(QString)), this, SIGNAL(setStatusBarInfoLabel(QString))); view->emitUpdateInfo(); } else { QString viewName = Konversation::removeIrcMarkup(view->getName()); if(viewName != "ChatWindowObject") emit setStatusBarInfoLabel(viewName); else emit clearStatusBarInfoLabel(); } switch (view->getType()) { case ChatWindow::Channel: case ChatWindow::Query: case ChatWindow::Status: case ChatWindow::ChannelList: case ChatWindow::RawLog: emit setStatusBarLagLabelShown(true); break; default: emit setStatusBarLagLabelShown(false); break; } // Make sure that only text views get to be the m_searchView if (view->searchView()) m_searchView = view; updateViewActions(m_tabWidget->currentIndex()); } void ViewContainer::updateViews(const Konversation::ServerGroupSettingsPtr serverGroup) { for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = static_cast(m_tabWidget->widget(i)); bool announce = false; if (serverGroup) { if (view->getType() == ChatWindow::Status && view->getServer()->getServerGroup() == serverGroup) { QString label = view->getServer()->getDisplayName(); if (!label.isEmpty() && m_tabWidget->tabText(i) != label) { m_tabWidget->setTabText(i, label); announce = true; if (view == m_frontView) { emit setStatusBarInfoLabel(label); emit setWindowCaption(label); } static_cast(view)->updateName(); } } if (i == m_tabWidget->currentIndex()) updateViewActions(i); } if (!Preferences::self()->tabNotificationsLeds()) { m_tabWidget->setTabIcon(i, QIcon()); announce = true; } if (!Preferences::self()->tabNotificationsText()) { m_tabWidget->tabBar()->setTabTextColor(i, m_window->palette().foreground().color()); announce = true; } if (Preferences::self()->tabNotificationsLeds() || Preferences::self()->tabNotificationsText()) { if (view->currentTabNotification()==Konversation::tnfNone) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfNormal && !Preferences::self()->tabNotificationsMsgs()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfPrivate && !Preferences::self()->tabNotificationsPrivate()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfSystem && !Preferences::self()->tabNotificationsSystem()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfControl && !Preferences::self()->tabNotificationsEvents()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfNick && !Preferences::self()->tabNotificationsNick()) unsetViewNotification(view); else if (view->currentTabNotification()==Konversation::tnfHighlight && !Preferences::self()->tabNotificationsHighlights()) unsetViewNotification(view); else if (view==m_tabWidget->currentWidget()) unsetViewNotification(view); else setViewNotification(view, view->currentTabNotification()); } if (announce) { const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx); } } } void ViewContainer::setViewNotification(ChatWindow* view, const Konversation::TabNotifyType& type) { if (!view || view == m_tabWidget->currentWidget()) return; if (type < Konversation::tnfControl && !m_activeViewOrderList.contains(view)) m_activeViewOrderList.append(view); if (!Preferences::self()->tabNotificationsLeds() && !Preferences::self()->self()->tabNotificationsText()) return; const int tabIndex = m_tabWidget->indexOf(view); switch (type) { case Konversation::tnfNormal: if (Preferences::self()->tabNotificationsMsgs()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getMsgsLed(true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsMsgsColor()); } break; case Konversation::tnfPrivate: if (Preferences::self()->tabNotificationsPrivate()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getPrivateLed(true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsPrivateColor()); } break; case Konversation::tnfSystem: if (Preferences::self()->tabNotificationsSystem()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getSystemLed(true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsSystemColor()); } break; case Konversation::tnfControl: if (Preferences::self()->tabNotificationsEvents()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getEventsLed()); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsEventsColor()); } break; case Konversation::tnfNick: if (Preferences::self()->tabNotificationsNick()) { if (Preferences::self()->tabNotificationsOverride() && Preferences::self()->highlightNick()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getLed(Preferences::self()->highlightNickColor(),true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->highlightNickColor()); } else { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getNickLed()); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsNickColor()); } } else { setViewNotification(view,Konversation::tnfNormal); } break; case Konversation::tnfHighlight: if (Preferences::self()->tabNotificationsHighlights()) { if (Preferences::self()->tabNotificationsOverride() && view->highlightColor().isValid()) { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getLed(view->highlightColor(),true)); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, view->highlightColor()); } else { if (Preferences::self()->tabNotificationsLeds()) m_tabWidget->setTabIcon(tabIndex, images->getHighlightsLed()); if (Preferences::self()->tabNotificationsText()) m_tabWidget->tabBar()->setTabTextColor(tabIndex, Preferences::self()->tabNotificationsHighlightsColor()); } } else { setViewNotification(view,Konversation::tnfNormal); } break; default: break; } const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << Qt::DecorationRole << ColorRole); } void ViewContainer::unsetViewNotification(ChatWindow* view) { if (!m_tabWidget) return; const int tabIndex = m_tabWidget->indexOf(view); if (Preferences::self()->tabNotificationsLeds()) { switch (view->getType()) { case ChatWindow::Channel: case ChatWindow::DccChat: m_tabWidget->setTabIcon(tabIndex, images->getMsgsLed(false)); break; case ChatWindow::Query: m_tabWidget->setTabIcon(tabIndex, images->getPrivateLed(false)); break; case ChatWindow::Status: m_tabWidget->setTabIcon(tabIndex, images->getServerLed(false)); break; default: m_tabWidget->setTabIcon(tabIndex, images->getSystemLed(false)); break; } } QColor textColor = m_window->palette().foreground().color(); if (view->getType() == ChatWindow::Channel) { Channel *channel = static_cast(view); if (!channel->joined()) textColor = m_tabWidget->palette().color(QPalette::Disabled, QPalette::Text); } else if (view->getType() == ChatWindow::Query) { if (!view->getServer()->isConnected()) textColor = m_tabWidget->palette().color(QPalette::Disabled, QPalette::Text); } m_tabWidget->tabBar()->setTabTextColor(tabIndex, textColor); const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << Qt::DecorationRole << ColorRole << DisabledRole); m_activeViewOrderList.removeAll(view); } void ViewContainer::toggleViewNotifications() { ChatWindow* view = 0; if (m_popupViewIndex == -1) view = static_cast(m_tabWidget->currentWidget()); else view = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (view) { if (!view->notificationsEnabled()) { view->setNotificationsEnabled(true); updateViews(); KToggleAction* action = static_cast(actionCollection()->action("tab_notifications")); if (action) action->setChecked(view->notificationsEnabled()); } else { view->setNotificationsEnabled(false); unsetViewNotification(view); KToggleAction* action = static_cast(actionCollection()->action("tab_notifications")); if (action) action->setChecked(view->notificationsEnabled()); } } m_popupViewIndex = -1; } void ViewContainer::toggleAutoJoin() { Channel* channel = 0; if (m_popupViewIndex == -1) channel = static_cast(m_tabWidget->currentWidget()); else channel = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (channel && channel->getType() == ChatWindow::Channel) { bool autoJoin = channel->autoJoin(); channel->setAutoJoin(!autoJoin); emit autoJoinToggled(channel->getServer()->getServerGroup()); } m_popupViewIndex = -1; } void ViewContainer::toggleConnectOnStartup() { ChatWindow* view = 0; if (m_popupViewIndex == -1) view = static_cast(m_tabWidget->currentWidget()); else view = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (view && view->getType() == ChatWindow::Status) { Server* server = view->getServer(); if (server) { Konversation::ServerGroupSettingsPtr settings = server->getConnectionSettings().serverGroup(); bool autoConnect = settings->autoConnectEnabled(); settings->setAutoConnectEnabled(!autoConnect); emit autoConnectOnStartupToggled(settings); } } m_popupViewIndex = -1; } void ViewContainer::addView(ChatWindow* view, const QString& label, bool weinitiated) { int placement = insertIndex(view); QIcon iconSet; if (Preferences::self()->closeButtons() && m_viewTree) iconSet = QIcon::fromTheme("dialog-close"); connect(Application::instance(), SIGNAL(appearanceChanged()), view, SLOT(updateAppearance())); connect(view, SIGNAL(setStatusBarTempText(QString)), this, SIGNAL(setStatusBarTempText(QString))); connect(view, SIGNAL(clearStatusBarTempText()), this, SIGNAL(clearStatusBarTempText())); connect(view, SIGNAL(closing(ChatWindow*)), this, SLOT(cleanupAfterClose(ChatWindow*))); switch (view->getType()) { case ChatWindow::Channel: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getMsgsLed(false); break; case ChatWindow::RawLog: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getSystemLed(false); break; case ChatWindow::Query: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getPrivateLed(false); break; case ChatWindow::DccChat: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getMsgsLed(false); break; case ChatWindow::Status: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getServerLed(false); break; case ChatWindow::ChannelList: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getSystemLed(false); break; default: if (Preferences::self()->tabNotificationsLeds()) iconSet = images->getSystemLed(false); break; } if (view->isTopLevelView()) { int diff = 0; for (int i = 0; i < placement; ++i) { if (!static_cast(m_tabWidget->widget(i))->isTopLevelView()) { ++diff; } } beginInsertRows(QModelIndex(), placement - diff, placement - diff); } else { int statusViewIndex = m_tabWidget->indexOf(view->getServer()->getStatusView()); const QModelIndex idx = indexForView(view->getServer()->getStatusView()); if (m_viewTree) { m_viewTree->setExpanded(idx, true); } beginInsertRows(idx, placement - statusViewIndex - 1, placement - statusViewIndex - 1); } m_tabWidget->insertTab(placement, view, iconSet, QString(label).replace('&', "&&")); endInsertRows(); m_vbox->show(); // Check, if user was typing in old input line bool doBringToFront=false; if (Preferences::self()->focusNewQueries() && view->getType()==ChatWindow::Query && !weinitiated) doBringToFront = true; if (Preferences::self()->bringToFront() && view->getType()!=ChatWindow::RawLog) doBringToFront = true; // make sure that bring to front only works when the user wasn't typing something if (m_frontView && view->getType() != ChatWindow::UrlCatcher && view->getType() != ChatWindow::Konsole) { if (!m_frontView->getTextInLine().isEmpty()) doBringToFront = false; } if (doBringToFront) showView(view); updateViewActions(m_tabWidget->currentIndex()); if (m_viewTree && m_tabWidget->count() == 1) { setViewTreeShown(true); } } int ViewContainer::insertIndex(ChatWindow* view) { int placement = m_tabWidget->count(); ChatWindow::WindowType wtype; ChatWindow *tmp_ChatWindow; // Please be careful about changing any of the grouping behavior in here, // because it needs to match up with the sorting behavior of the tree list, // otherwise they may become out of sync, wreaking havoc with the move // actions. Yes, this would do well with a more reliable approach in the // future. Then again, while this is ugly, it's also very fast. switch (view->getType()) { case ChatWindow::Channel: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() == ChatWindow::Status && tmp_ChatWindow->getServer() == view->getServer()) { for (int index = sindex + 1; index < m_tabWidget->count(); index++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(index)); wtype = tmp_ChatWindow->getType(); if (wtype != ChatWindow::Channel && wtype != ChatWindow::RawLog) { placement = index; break; } } break; } } break; case ChatWindow::RawLog: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() == ChatWindow::Status && tmp_ChatWindow->getServer() == view->getServer()) { placement = sindex + 1; break; } } break; case ChatWindow::Query: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() == ChatWindow::Status && tmp_ChatWindow->getServer() == view->getServer()) { for (int index = sindex + 1; index < m_tabWidget->count(); index++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(index)); wtype = tmp_ChatWindow->getType(); if (wtype != ChatWindow::Channel && wtype != ChatWindow::RawLog && wtype != ChatWindow::Query) { placement = index; break; } } break; } } break; case ChatWindow::DccChat: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); wtype = tmp_ChatWindow->getType(); if (wtype != ChatWindow::Status && wtype != ChatWindow::Channel && wtype != ChatWindow::RawLog && wtype != ChatWindow::Query && wtype != ChatWindow::DccChat && wtype != ChatWindow::ChannelList) { placement = sindex; break; } } break; case ChatWindow::Status: if (m_viewTree) { for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getType() != ChatWindow::Channel && tmp_ChatWindow->getType() != ChatWindow::Status && tmp_ChatWindow->getType() != ChatWindow::RawLog && tmp_ChatWindow->getType() != ChatWindow::Query && tmp_ChatWindow->getType() != ChatWindow::DccChat) { placement = sindex; break; } } } break; case ChatWindow::ChannelList: for (int sindex = 0; sindex < m_tabWidget->count(); sindex++) { tmp_ChatWindow = static_cast(m_tabWidget->widget(sindex)); if (tmp_ChatWindow->getServer() == view->getServer()) placement = sindex + 1; } break; default: break; } return placement; } void ViewContainer::unclutterTabs() { if (!m_tabWidget || !m_tabWidget->count()) { return; } emit beginResetModel(); m_tabWidget->blockSignals(true); QWidget* currentView = m_tabWidget->currentWidget(); QList views; while (m_tabWidget->count()) { views << static_cast(m_tabWidget->widget(0)); m_tabWidget->removeTab(0); } foreach(ChatWindow *view, views) { if (view->isTopLevelView()) { m_tabWidget->insertTab(insertIndex(view), view, QIcon(), view->getName().replace('&', "&&")); views.removeAll(view); } } foreach(ChatWindow *view, views) { if (!view->isTopLevelView()) { m_tabWidget->insertTab(insertIndex(view), view, QIcon(), view->getName().replace('&', "&&")); } } updateViews(); if (currentView) { m_tabWidget->setCurrentWidget(currentView); } m_tabWidget->blockSignals(false); emit endResetModel(); viewSwitched(m_tabWidget->currentIndex()); } void ViewContainer::viewSwitched(int newIndex) { ChatWindow* view = static_cast(m_tabWidget->widget(newIndex)); if (!view) return; m_lastFocusedView = m_currentView; m_currentView = view; const QModelIndex &idx = indexForView(view); emit viewChanged(idx); if (m_frontView) { m_frontView->resetTabNotification(); disconnect(m_frontView, SIGNAL(updateInfo(QString)), this, SIGNAL(setStatusBarInfoLabel(QString))); if (Preferences::self()->automaticRememberLine() && m_frontView->getTextView() != 0) m_frontView->getTextView()->insertRememberLine(); } m_frontView = 0; m_searchView = 0; setFrontServer(view->getServer()); // display this server's lag time if (m_frontServer) { updateStatusBarSSLLabel(m_frontServer); updateStatusBarLagLabel(m_frontServer, m_frontServer->getLag()); } emit clearStatusBarTempText(); updateFrontView(); unsetViewNotification(view); view->resetTabNotification(); if (!m_viewTree || !m_viewTree->hasFocus()) view->adjustFocus(); if (view->getTextView() != 0) view->getTextView()->cancelRememberLine(); updateViewEncoding(view); QString tabName = Konversation::removeIrcMarkup(view->getName()); if (tabName != "ChatWindowObject") emit setWindowCaption(tabName); else emit setWindowCaption(QString()); } void ViewContainer::showView(ChatWindow* view) { // Don't bring Tab to front if TabWidget is hidden. Otherwise QT gets confused // and shows the Tab as active but will display the wrong pane if (m_tabWidget->isVisible()) { m_tabWidget->setCurrentIndex(m_tabWidget->indexOf(view)); } } void ViewContainer::goToView(int page) { if (page == m_tabWidget->currentIndex()) return; if (page > m_tabWidget->count()) return; if (page >= m_tabWidget->count()) page = 0; else if (page < 0) page = m_tabWidget->count() - 1; if (page >= 0) m_tabWidget->setCurrentIndex(page); m_popupViewIndex = -1; } void ViewContainer::showNextView() { goToView(m_tabWidget->currentIndex()+1); } void ViewContainer::showPreviousView() { goToView(m_tabWidget->currentIndex()-1); } void ViewContainer::showNextActiveView() { if (m_window->isHidden()) m_window->show(); if (m_window->isMinimized()) KWindowSystem::unminimizeWindow(m_window->winId()); if (!m_window->isActiveWindow()) { m_window->raise(); KWindowSystem::activateWindow(m_window->winId()); } if (!m_activeViewOrderList.isEmpty()) { ChatWindow* prev = m_activeViewOrderList.first(); ChatWindow* view = prev; QList::ConstIterator it; for (it = m_activeViewOrderList.constBegin(); it != m_activeViewOrderList.constEnd(); ++it) { if ((*it)->currentTabNotification() < prev->currentTabNotification()) view = (*it); } m_tabWidget->setCurrentIndex(m_tabWidget->indexOf(view)); } } void ViewContainer::showLastFocusedView() { if (m_lastFocusedView) showView(m_lastFocusedView); } bool ViewContainer::canMoveViewLeft() const { if (!m_tabWidget || !m_tabWidget->count()) { return false; } int index = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(index)); if (view->isTopLevelView() && index > 0) { return true; } else if (!view->isTopLevelView()) { ChatWindow* statusView = view->getServer()->getStatusView(); int statusViewIndex = m_tabWidget->indexOf(statusView); index = index - statusViewIndex - 1; return (index > 0); } return false; } bool ViewContainer::canMoveViewRight() const { if (!m_tabWidget || !m_tabWidget->count()) { return false; } int index = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(index)); if (view->isTopLevelView()) { int lastTopLevelView = -1; for (int i = m_tabWidget->count() - 1; i >= index; --i) { if (static_cast(m_tabWidget->widget(i))->isTopLevelView()) { lastTopLevelView = i; break; } } return (index != lastTopLevelView); } else if (!view->isTopLevelView()) { view = static_cast(m_tabWidget->widget(index + 1)); return (view && !view->isTopLevelView()); } return false; } void ViewContainer::moveViewLeft() { if (!m_tabWidget || !m_tabWidget->count()) { return; } int tabIndex = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(tabIndex)); const QModelIndex idx = indexForView(view); if (view->isTopLevelView()) { const QModelIndex aboveIdx = index(idx.row() - 1, 0); int aboveTabIndex = m_tabWidget->indexOf(static_cast(aboveIdx.internalPointer())); beginMoveRows(QModelIndex(), idx.row(), idx.row(), QModelIndex(), aboveIdx.row()); m_tabWidget->blockSignals(true); if (view->getType() == ChatWindow::Status) { for (int i = m_tabWidget->count() - 1; i > tabIndex; --i) { ChatWindow* tab = static_cast(m_tabWidget->widget(i)); if (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->tabBar()->moveTab(i, aboveTabIndex); ++tabIndex; ++i; } } } m_tabWidget->tabBar()->moveTab(tabIndex, aboveTabIndex); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } else { beginMoveRows(idx.parent(), idx.row(), idx.row(), idx.parent(), idx.row() - 1); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(tabIndex, tabIndex - 1); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } } void ViewContainer::moveViewRight() { if (!m_tabWidget || !m_tabWidget->count()) { return; } int tabIndex = (m_popupViewIndex != -1) ? m_popupViewIndex : m_tabWidget->currentIndex(); ChatWindow* view = static_cast(m_tabWidget->widget(tabIndex)); const QModelIndex idx = indexForView(view); if (view->isTopLevelView()) { const QModelIndex belowIdx = index(idx.row() + 1, 0); int belowTabIndex = m_tabWidget->indexOf(static_cast(belowIdx.internalPointer())); int children = rowCount(belowIdx); if (children) { belowTabIndex = m_tabWidget->indexOf(static_cast(belowIdx.child(children - 1, 0).internalPointer())); } beginMoveRows(QModelIndex(), idx.row(), idx.row(), QModelIndex(), belowIdx.row() + 1); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(tabIndex, belowTabIndex); if (view->getType() == ChatWindow::Status) { ChatWindow* tab = static_cast(m_tabWidget->widget(tabIndex)); while (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->tabBar()->moveTab(tabIndex, belowTabIndex); tab = static_cast(m_tabWidget->widget(tabIndex)); } } m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } else { beginMoveRows(idx.parent(), idx.row(), idx.row(), idx.parent(), idx.row() + 2); m_tabWidget->blockSignals(true); m_tabWidget->tabBar()->moveTab(tabIndex, tabIndex + 1); m_tabWidget->blockSignals(false); endMoveRows(); viewSwitched(m_tabWidget->currentIndex()); } } void ViewContainer::closeView(int view) { ChatWindow* viewToClose = static_cast(m_tabWidget->widget(view)); closeView(viewToClose); } void ViewContainer::closeView(ChatWindow* view) { if (view) { ChatWindow::WindowType viewType = view->getType(); switch (viewType) { case ChatWindow::DccTransferPanel: closeDccPanel(); break; case ChatWindow::UrlCatcher: closeUrlCatcher(); break; case ChatWindow::NicksOnline: closeNicksOnlinePanel(); break; default: view->closeYourself(); break; } } } void ViewContainer::cleanupAfterClose(ChatWindow* view) { if (view == m_frontView) m_frontView = 0; if (view == m_lastFocusedView) { QAction* action = actionCollection()->action("last_focused_tab"); if (action) action->setEnabled(false); } if (m_tabWidget) { const int tabIndex = m_tabWidget->indexOf(view); if (tabIndex != -1) { m_tabWidget->blockSignals(true); const QModelIndex& idx = indexForView(view); beginRemoveRows(idx.parent(), idx.row(), idx.row()); if (view->getType() == ChatWindow::Status) { for (int i = m_tabWidget->count() - 1; i > tabIndex; --i) { const ChatWindow* tab = static_cast(m_tabWidget->widget(i)); if (!tab->isTopLevelView() && tab->getServer() && tab->getServer()->getStatusView() && tab->getServer()->getStatusView() == view) { m_tabWidget->removeTab(i); } } } m_tabWidget->removeTab(tabIndex); endRemoveRows(); m_tabWidget->blockSignals(false); viewSwitched(m_tabWidget->currentIndex()); } if (m_tabWidget->count() <= 0) { m_saveSplitterSizesLock = true; m_vbox->hide(); emit resetStatusBar(); emit setWindowCaption(QString()); updateViewActions(-1); } } // Remove the view from the active view list if it's still on it m_activeViewOrderList.removeAll(view); if (view->getType() == ChatWindow::Query) --m_queryViewCount; if (m_queryViewCount == 0 && actionCollection()) { QAction* action = actionCollection()->action("close_queries"); if (action) action->setEnabled(false); } if (!m_tabWidget->count() && m_viewTree) { setViewTreeShown(false); } } void ViewContainer::closeViewMiddleClick(int view) { if (Preferences::self()->middleClickClose()) closeView(view); } void ViewContainer::renameKonsole() { bool ok = false; if (!m_tabWidget) return; int popup = m_popupViewIndex ? m_popupViewIndex : m_tabWidget->currentIndex(); QString label = QInputDialog::getText(m_tabWidget->widget(popup), i18n("Rename Tab"), i18n("Enter new tab name:"), QLineEdit::Normal, m_tabWidget->tabText(popup), &ok); if (ok) { KonsolePanel* view = static_cast(m_tabWidget->widget(popup)); if (!view) return; view->setName(label); m_tabWidget->setTabText(popup, label); const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << Qt::DisplayRole); if (popup == m_tabWidget->currentIndex()) { emit setStatusBarInfoLabel(label); emit setWindowCaption(label); } } } void ViewContainer::closeCurrentView() { if (m_popupViewIndex == -1) closeView(m_tabWidget->currentIndex()); else closeView(m_popupViewIndex); m_popupViewIndex = -1; } void ViewContainer::changeViewCharset(int index) { ChatWindow* chatWin; if (m_popupViewIndex == -1) chatWin = static_cast(m_tabWidget->currentWidget()); else chatWin = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (chatWin) { if (index == 0) chatWin->setChannelEncoding(QString()); else chatWin->setChannelEncoding(Konversation::IRCCharsets::self()->availableEncodingShortNames()[index - 1]); } m_popupViewIndex = -1; } void ViewContainer::updateViewEncoding(ChatWindow* view) { if (view) { ChatWindow::WindowType viewType = view->getType(); KSelectAction* codecAction = qobject_cast(actionCollection()->action("tab_encoding")); if (codecAction) { if(viewType == ChatWindow::Channel || viewType == ChatWindow::Query || viewType == ChatWindow::Status) { codecAction->setEnabled(view->isChannelEncodingSupported()); QString encoding = view->getChannelEncoding(); if (view->getServer()) { codecAction->changeItem(0, i18nc("Default encoding", "Default ( %1 )", view->getServer()->getIdentity()->getCodecName())); } if (encoding.isEmpty()) { codecAction->setCurrentItem(0); } else { codecAction->setCurrentItem(Konversation::IRCCharsets::self()->shortNameToIndex(encoding) + 1); } } else { codecAction->setEnabled(false); } } } } void ViewContainer::showViewContextMenu(QWidget* tab, const QPoint& pos) { if (!tab) { return; } ChatWindow* view = static_cast(tab); m_popupViewIndex = m_tabWidget->indexOf(tab); updateViewActions(m_popupViewIndex); QMenu* menu = static_cast(m_window->guiFactory()->container("tabContextMenu", m_window)); if (!menu) return; KToggleAction* autoJoinAction = qobject_cast(actionCollection()->action("tab_autojoin")); KToggleAction* autoConnectAction = qobject_cast(actionCollection()->action("tab_autoconnect")); QAction* rejoinAction = actionCollection()->action("rejoin_channel"); QAction* closeAction = actionCollection()->action("close_tab"); QAction* renameAct = new QAction(this); renameAct->setText(i18n("&Rename Tab...")); connect(renameAct, SIGNAL(triggered()), this, SLOT(renameKonsole())); ChatWindow::WindowType viewType = view->getType(); updateViewEncoding(view); if (viewType == ChatWindow::Channel) { QAction* action = actionCollection()->action("tab_encoding"); menu->insertAction(action, autoJoinAction); Channel *channel = static_cast(view); if (channel->rejoinable() && rejoinAction) { menu->insertAction(closeAction, rejoinAction); rejoinAction->setEnabled(true); } } if (viewType == ChatWindow::Konsole) { QAction* action = actionCollection()->action("tab_encoding"); menu->insertAction(action, renameAct); } if (viewType == ChatWindow::Status) { QAction* action = actionCollection()->action("tab_encoding"); menu->insertAction(action, autoConnectAction); QList serverActions; action = actionCollection()->action("disconnect_server"); if (action) serverActions.append(action); action = actionCollection()->action("reconnect_server"); if (action) serverActions.append(action); action = actionCollection()->action("join_channel"); if (action) serverActions.append(action); // TODO FIXME who wants to own this action? action = new QAction(this); action->setSeparator(true); if (action) serverActions.append(action); m_window->plugActionList("server_actions", serverActions); m_contextServer = view->getServer(); } else { m_contextServer = 0; } const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << HighlightRole); const QAction* action = menu->exec(pos); m_popupViewIndex = -1; menu->removeAction(autoJoinAction); menu->removeAction(autoConnectAction); menu->removeAction(rejoinAction); menu->removeAction(renameAct); m_window->unplugActionList("server_actions"); emit contextMenuClosed(); emit dataChanged(idx, idx, QVector() << HighlightRole); if (action != actionCollection()->action("close_tab")) { updateViewEncoding(view); } updateViewActions(m_tabWidget->currentIndex()); } QString ViewContainer::currentViewTitle() { if (m_frontServer) { if (m_frontView && m_frontView->getType() == ChatWindow::Channel) return m_frontView->getTitle(); else return m_frontServer->getDisplayName(); } else { return QString(); } } QString ViewContainer::currentViewURL(bool passNetwork) { QString url; QString channel; QString port; QString server; if (m_frontServer && m_frontView) { updateFrontView(); url = m_frontView->getURI(passNetwork); } return url; } int ViewContainer::getViewIndex(QWidget* widget) { return m_tabWidget->indexOf(widget); } ChatWindow* ViewContainer::getViewAt(int index) { return static_cast(m_tabWidget->widget(index)); } QList > ViewContainer::getChannelsURI() { QList > URIList; for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view->getType() == ChatWindow::Channel) { QString uri = view->getURI(); QString name = QString("%1 (%2)") .arg(view->getName()) .arg(view->getServer()->getDisplayName()); URIList += QPair(name,uri); } } return URIList; } void ViewContainer::clearView() { if (m_frontView) m_frontView->getTextView()->clear(); } void ViewContainer::clearAllViews() { for (int i = 0; i < m_tabWidget->count(); i++) static_cast(m_tabWidget->widget(i))->clear(); } void ViewContainer::findText() { if (!m_searchView) { KMessageBox::sorry(m_window, i18n("You can only search in text fields."), i18n("Find Text Information")); } else { m_searchView->getTextView()->findText(); } } void ViewContainer::findNextText() { if (m_searchView) { m_searchView->getTextView()->findNextText(); } } void ViewContainer::findPrevText() { if (m_searchView) { m_searchView->getTextView()->findPreviousText(); } } void ViewContainer::appendToFrontmost(const QString& type, const QString& message, ChatWindow* serverView, const QHash &messageTags, bool parseURL) { if (!m_tabWidget) return; if (!serverView) // e.g. DCOP info call { if (m_frontView) // m_frontView == NULL if canBeFrontView() == false for active ChatWindow serverView = m_frontView->getServer()->getStatusView(); else if (m_frontServer) // m_frontView == NULL && m_frontServer != NULL if ChannelListPanel is active. serverView = m_frontServer->getStatusView(); } // This might happen if canBeFrontView() is false for active ChatWindow // and the view does not belong to any server (e.g. DCC Status View). // Discard message in this case. if (!serverView) return; updateFrontView(); if (!m_frontView || // Check if the m_frontView can actually display text or ... // if it does not belong to this server or... serverView->getServer()!=m_frontView->getServer() || // if the user decided to force it. Preferences::self()->redirectServerAndAppMsgToStatusPane()) { // if not, take server specified fallback view instead serverView->appendServerMessage(type, message, messageTags, parseURL); // FIXME: this signal should be sent from the status panel instead, so it // can be using the correct highlight color, would be more consistent // anyway! // FIXME newText(serverView,QString::null,true); } else m_frontView->appendServerMessage(type, message, messageTags, parseURL); } void ViewContainer::insertCharacter() { QFont font; if (Preferences::self()->customTextFont()) font = Preferences::self()->textFont(); else font = QFontDatabase::systemFont(QFontDatabase::GeneralFont); if (!m_insertCharDialog) { m_insertCharDialog = new Konversation::InsertCharDialog(font.family(), m_window); connect(m_insertCharDialog, SIGNAL(insertChar(QChar)), this, SLOT(insertChar(QChar))); } m_insertCharDialog->setFont(font); m_insertCharDialog->show(); } void ViewContainer::insertChar(const QChar& chr) { ChatWindow* view = static_cast(m_tabWidget->currentWidget()); if (view) view->appendInputText(chr, true/*fromCursor*/); } void ViewContainer::insertIRCColor() { // TODO FIXME QPointer dlg = new IRCColorChooser(m_window); if (dlg->exec() == QDialog::Accepted) { if(m_frontView) m_frontView->appendInputText(dlg->color(), true/*fromCursor*/); } delete dlg; } void ViewContainer::doAutoReplace() { if (!m_frontView) return; // Check for active window in case action was triggered from a modal dialog, like the Paste Editor if (!m_window->isActiveWindow()) return; if (m_frontView->getInputBar()) m_frontView->getInputBar()->doInlineAutoreplace(); } void ViewContainer::focusInputBox() { if (m_frontView && m_frontView->isInsertSupported()) m_frontView->adjustFocus(); } void ViewContainer::clearViewLines() { if (m_frontView && m_frontView->getTextView() != 0) { m_frontView->getTextView()->clearLines(); QAction* action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(false); } } void ViewContainer::insertRememberLine() { if (Preferences::self()->automaticRememberLine()) { if (m_frontView && m_frontView->getTextView() != 0) m_frontView->getTextView()->insertRememberLine(); } } void ViewContainer::insertRememberLines(Server* server) { for (int i = 0; i < m_tabWidget->count(); ++i) { ChatWindow* view = static_cast(m_tabWidget->widget(i)); if (view->getServer() == server && view->getTextView() != 0) view->getTextView()->insertRememberLine(); } } void ViewContainer::cancelRememberLine() { if (m_frontView && m_frontView->getTextView() != 0) { m_frontView->getTextView()->cancelRememberLine(); QAction* action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(m_frontView->getTextView()->hasLines()); } } void ViewContainer::insertMarkerLine() { if (Preferences::self()->markerLineInAllViews()) { int total = m_tabWidget->count()-1; ChatWindow* view; for (int i = 0; i <= total; ++i) { view = static_cast(m_tabWidget->widget(i)); if (view->getTextView() != 0) view->getTextView()->insertMarkerLine(); } } else { if (m_frontView && m_frontView->getTextView() != 0) m_frontView->getTextView()->insertMarkerLine(); } if (m_frontView && m_frontView->getTextView() != 0) { QAction* action = actionCollection()->action("clear_lines"); if (action) action->setEnabled(m_frontView->getTextView()->hasLines()); } } void ViewContainer::openLogFile() { if (m_frontView) { ChatWindow* view = static_cast(m_frontView); if (!view->logFileName().isEmpty()) openLogFile(view->getName(), view->logFileName()); } } void ViewContainer::openLogFile(const QString& caption, const QString& file) { if (!file.isEmpty()) { if(Preferences::self()->useExternalLogViewer()) { new KRun(QUrl::fromLocalFile(file), m_window, false); } else { LogfileReader* logReader = new LogfileReader(m_tabWidget, file, caption); addView(logReader, logReader->getName()); logReader->setServer(0); } } } void ViewContainer::addKonsolePanel() { KonsolePanel* panel=new KonsolePanel(m_tabWidget); panel->setName(i18n("Konsole")); addView(panel, i18n("Konsole")); connect(panel, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(panel, SIGNAL(closeView(ChatWindow*)), this, SLOT(closeView(ChatWindow*))); } void ViewContainer::addUrlCatcher() { if (m_urlCatcherPanel == 0) { m_urlCatcherPanel=new UrlCatcher(m_tabWidget); addView(m_urlCatcherPanel, i18n("URL Catcher")); (dynamic_cast(actionCollection()->action("open_url_catcher")))->setChecked(true); } else closeUrlCatcher(); } void ViewContainer::closeUrlCatcher() { if (m_urlCatcherPanel) { delete m_urlCatcherPanel; m_urlCatcherPanel = 0; (dynamic_cast(actionCollection()->action("open_url_catcher")))->setChecked(false); } } void ViewContainer::toggleDccPanel() { if (m_dccPanel==0 || !m_dccPanelOpen) addDccPanel(); else closeDccPanel(); } void ViewContainer::addDccPanel() { qDebug(); if (!m_dccPanelOpen) { addView(m_dccPanel, i18n("DCC Status")); m_dccPanelOpen=true; (dynamic_cast(actionCollection()->action("open_dccstatus_window")))->setChecked(true); } } void ViewContainer::closeDccPanel() { // if there actually is a dcc panel if (m_dccPanel && m_dccPanelOpen) { // hide it from view, does not delete it if (m_tabWidget) { if (m_popupViewIndex == m_tabWidget->indexOf(m_dccPanel)) { m_popupViewIndex = -1; } cleanupAfterClose(m_dccPanel); } m_dccPanelOpen=false; (dynamic_cast(actionCollection()->action("open_dccstatus_window")))->setChecked(false); } } void ViewContainer::deleteDccPanel() { if (m_dccPanel) { closeDccPanel(); delete m_dccPanel; m_dccPanel=0; } } ChatWindow* ViewContainer::getDccPanel() { return m_dccPanel; } void ViewContainer::addDccChat(DCC::Chat* chat) { if (!chat->selfOpened()) // Someone else initiated dcc chat { Application* konv_app=Application::instance(); konv_app->notificationHandler()->dccChat(m_frontView, chat->partnerNick()); } DCC::ChatContainer *chatcontainer = new DCC::ChatContainer(m_tabWidget,chat); connect(chatcontainer, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); addView(chatcontainer, chatcontainer->getName()); } StatusPanel* ViewContainer::addStatusView(Server* server) { StatusPanel* statusView = new StatusPanel(m_tabWidget); // Get group name for tab if available QString label = server->getDisplayName(); statusView->setName(label); statusView->setServer(server); if (server->getServerGroup()) statusView->setNotificationsEnabled(server->getServerGroup()->enableNotifications()); QObject::connect(server, SIGNAL(sslInitFailure()), this, SIGNAL(removeStatusBarSSLLabel())); QObject::connect(server, SIGNAL(sslConnected(Server*)), this, SIGNAL(updateStatusBarSSLLabel(Server*))); // ... then put it into the tab widget, otherwise we'd have a race with server member addView(statusView, label); connect(statusView, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(statusView, SIGNAL(sendFile()), server, SLOT(requestDccSend())); connect(server, SIGNAL(awayState(bool)), statusView, SLOT(indicateAway(bool)) ); // Make sure that m_frontServer gets set on adding the first status panel, too, // since there won't be a viewSwitched happening. if (!m_frontServer) setFrontServer(server); return statusView; } RawLog* ViewContainer::addRawLog(Server* server) { RawLog* rawLog = new RawLog(m_tabWidget); rawLog->setServer(server); if (server->getServerGroup()) rawLog->setNotificationsEnabled(server->getServerGroup()->enableNotifications()); addView(rawLog, i18n("Raw Log")); connect(rawLog, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); return rawLog; } void ViewContainer::reconnectFrontServer() { Server* server = 0; if (m_contextServer) server = m_contextServer; else server = m_frontServer; if (server) server->reconnectServer(); } void ViewContainer::disconnectFrontServer() { Server* server = 0; if (m_contextServer) server = m_contextServer; else server = m_frontServer; if (server && (server->isConnected() || server->isConnecting() || server->isScheduledToConnect())) server->disconnectServer(); } void ViewContainer::showJoinChannelDialog() { Server* server = 0; if (m_contextServer) server = m_contextServer; else server = m_frontServer; if (!server) return; QPointer dlg = new Konversation::JoinChannelDialog(server, m_window); if (dlg->exec() == QDialog::Accepted) { Server *server = Application::instance()->getConnectionManager()->getServerByConnectionId(dlg->connectionId()); if (!server) return; server->sendJoinCommand(dlg->channel(), dlg->password()); } delete dlg; } void ViewContainer::connectionStateChanged(Server* server, Konversation::ConnectionState state) { Server* updateServer = 0; if (m_contextServer) updateServer = m_contextServer; else updateServer = m_frontServer; if (updateServer && updateServer == server) { QAction* action = actionCollection()->action("disconnect_server"); if (action) action->setEnabled(state == Konversation::SSConnected || state == Konversation::SSConnecting || state == Konversation::SSScheduledToConnect); action = actionCollection()->action("join_channel"); if (action) action->setEnabled(state == Konversation::SSConnected); if (m_frontView && m_frontView->getServer() == server && m_frontView->getType() == ChatWindow::Channel) { ChatWindow* view = m_frontView; Channel* channel = static_cast(view); action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(state == Konversation::SSConnected && channel->rejoinable()); } } } void ViewContainer::channelJoined(Channel* channel) { ChatWindow* view = m_frontView; if (view == channel) { QAction* action = actionCollection()->action("rejoin_channel"); if (action) action->setEnabled(false); } } Channel* ViewContainer::addChannel(Server* server, const QString& name) { Channel* channel=new Channel(m_tabWidget, name); channel->setServer(server); channel->setName(name); //still have to do this for now addView(channel, name); connect(this, SIGNAL(updateChannelAppearance()), channel, SLOT(updateAppearance())); connect(channel, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(server, SIGNAL(awayState(bool)), channel, SLOT(indicateAway(bool)) ); connect(channel, SIGNAL(joined(Channel*)), this, SLOT(channelJoined(Channel*))); return channel; } void ViewContainer::rejoinChannel() { Channel* channel = 0; if (m_popupViewIndex == -1) channel = static_cast(m_tabWidget->currentWidget()); else channel = static_cast(m_tabWidget->widget(m_popupViewIndex)); if (channel && channel->getType() == ChatWindow::Channel) channel->rejoin(); } void ViewContainer::openChannelSettings() { if (m_frontView->getType() == ChatWindow::Channel) { Channel* channel = static_cast(m_tabWidget->currentWidget()); channel->showOptionsDialog(); } } void ViewContainer::toggleChannelNicklists() { KToggleAction* action = static_cast(actionCollection()->action("hide_nicknamelist")); if (action) { Preferences::self()->setShowNickList(action->isChecked()); Preferences::self()->save(); emit updateChannelAppearance(); } } Query* ViewContainer::addQuery(Server* server, const NickInfoPtr& nickInfo, bool weinitiated) { QString name = nickInfo->getNickname(); Query* query=new Query(m_tabWidget, name); query->setServer(server); query->setNickInfo(nickInfo); //still have to do this addView(query, name, weinitiated); // About to increase the number of queries, so enable the close action if (m_queryViewCount == 0) actionCollection()->action("close_queries")->setEnabled(true); ++m_queryViewCount; connect(query, SIGNAL(updateTabNotification(ChatWindow*,Konversation::TabNotifyType)), this, SLOT(setViewNotification(ChatWindow*,Konversation::TabNotifyType))); connect(query, SIGNAL(updateQueryChrome(ChatWindow*,QString)), this, SLOT(updateQueryChrome(ChatWindow*,QString))); connect(server, SIGNAL(awayState(bool)), query, SLOT(indicateAway(bool))); return query; } void ViewContainer::updateQueryChrome(ChatWindow* view, const QString& name) { //FIXME: updateQueryChrome is a last minute fix for 0.19 because // the updateInfo mess is indecipherable. Replace with a sane and // encompassing system. QString newName = Konversation::removeIrcMarkup(name); if (!newName.isEmpty() && m_tabWidget->tabText(m_tabWidget->indexOf(view)) != newName) { int tabIndex = m_tabWidget->indexOf(view); m_tabWidget->setTabText(tabIndex, newName); const QModelIndex& idx = indexForView(view); emit dataChanged(idx, idx, QVector() << Qt::DisplayRole); } if (!newName.isEmpty() && view==m_frontView) emit setWindowCaption(newName); } void ViewContainer::closeQueries() { int total=m_tabWidget->count()-1; int operations = 0; ChatWindow* nextPage; for (int i=0; i <=total; i++) { if (operations > total) break; nextPage = static_cast(m_tabWidget->widget(i)); if (nextPage && nextPage->getType()==ChatWindow::Query) { closeView(nextPage); if (m_tabWidget->indexOf(nextPage) == -1) --i; } ++operations; } actionCollection()->action("close_queries")->setEnabled(false); } ChannelListPanel* ViewContainer::addChannelListPanel(Server* server) { ChannelListPanel* channelListPanel=new ChannelListPanel(m_tabWidget); channelListPanel->setServer(server); addView(channelListPanel, i18n("Channel List")); KToggleAction* action = static_cast(actionCollection()->action("open_channel_list")); if ((server == m_frontServer) && action) action->setChecked(true); return channelListPanel; } void ViewContainer::openChannelList(Server* server, const QString& filter, bool getList) { if (!server) server = m_frontServer; if (!server) { KMessageBox::information(m_window, i18n( "To know which server to display the channel list " "for, the list can only be opened from a " "query, channel or status window." ), i18n("Channel List"), "ChannelListNoServerSelected"); return; } ChannelListPanel* panel = server->getChannelListPanel(); if (panel && filter.isEmpty()) { closeView(panel); if (server == m_frontServer) { KToggleAction* action = static_cast(actionCollection()->action("open_channel_list")); if (action) action->setChecked(false); } return; } if (!panel) { int ret = KMessageBox::Continue; if (filter.isEmpty()) { ret = KMessageBox::warningContinueCancel(m_window, i18n("Using this function may result in a lot " "of network traffic. If your connection is not fast " "enough, it is possible that your client will be " "disconnected by the server."), i18n("Channel List Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "ChannelListWarning"); } if (ret != KMessageBox::Continue) { if (server == m_frontServer) { KToggleAction* action = static_cast(actionCollection()->action("open_channel_list")); if (action) action->setChecked(false); } return; } panel = server->addChannelListPanel(); } panel->setFilter(filter); if (getList) panel->refreshList(); } void ViewContainer::openNicksOnlinePanel() { if (!m_nicksOnlinePanel) { m_nicksOnlinePanel=new NicksOnline(m_window); addView(m_nicksOnlinePanel, i18n("Watched Nicks")); connect(m_nicksOnlinePanel, SIGNAL(doubleClicked(int,QString)), m_window, SLOT(notifyAction(int,QString))); connect(m_nicksOnlinePanel, SIGNAL(showView(ChatWindow*)), this, SLOT(showView(ChatWindow*))); connect(m_window, SIGNAL(nicksNowOnline(Server*)), m_nicksOnlinePanel, SLOT(updateServerOnlineList(Server*))); (dynamic_cast(actionCollection()->action("open_nicksonline_window")))->setChecked(true); } else { closeNicksOnlinePanel(); } } void ViewContainer::closeNicksOnlinePanel() { delete m_nicksOnlinePanel; m_nicksOnlinePanel = 0; (dynamic_cast(actionCollection()->action("open_nicksonline_window")))->setChecked(false); } /*! \fn ViewContainer::frontServerChanging(Server *newServer) This signal is emitted immediately before the front server is changed. If the server is being removed this will fire with a null pointer. */ diff --git a/src/viewer/viewcontainer.h b/src/viewer/viewcontainer.h index 4c12f41e..113ed3fb 100644 --- a/src/viewer/viewcontainer.h +++ b/src/viewer/viewcontainer.h @@ -1,305 +1,309 @@ /* 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. */ /* Copyright (C) 2006-2008 Eike Hein */ #ifndef VIEWCONTAINER_H #define VIEWCONTAINER_H #include "mainwindow.h" #include "common.h" #include "server.h" #include #include #include class QSplitter; class KActionCollection; class MainWindow; class ViewTree; class ChatWindow; class Server; class Images; class UrlCatcher; class NicksOnline; class QueueTuner; class ViewSpringLoader; namespace Konversation { class InsertCharDialog; class ServerGroupSettings; namespace DCC { class Chat; } } class ViewMimeData : public QMimeData { public: explicit ViewMimeData(ChatWindow *view); ~ViewMimeData(); ChatWindow* view() const; private: ChatWindow *m_view; }; class TabWidget : public QTabWidget { Q_OBJECT public: explicit TabWidget(QWidget* parent = 0); ~TabWidget(); Q_SIGNALS: void contextMenu(QWidget* widget, const QPoint& pos); void tabBarMiddleClicked(int index); protected: virtual void contextMenuEvent(QContextMenuEvent* event); virtual void mouseReleaseEvent(QMouseEvent* event); }; class ViewContainer : public QAbstractItemModel { Q_OBJECT public: enum DataRoles { ColorRole = Qt::UserRole + 1, DisabledRole, HighlightRole }; explicit ViewContainer(MainWindow* window); ~ViewContainer(); QSplitter* getWidget() { return m_viewTreeSplitter; } MainWindow* getWindow() { return m_window; } KActionCollection* actionCollection() { return m_window->actionCollection(); } QPointer getFrontView() { return m_frontView; } Server* getFrontServer() { return m_frontServer; } void prepareShutdown(); int rowCount(const QModelIndex & parent = QModelIndex()) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const; QModelIndex indexForView(ChatWindow* view) const; QModelIndex parent(const QModelIndex& index) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; Qt::DropActions supportedDragActions() const; Qt::DropActions supportedDropActions() const; Qt::ItemFlags flags(const QModelIndex &index) const; QStringList mimeTypes() const; QMimeData* mimeData(const QModelIndexList &indexes) const; bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); QString currentViewTitle(); QString currentViewURL(bool passNetwork = true); void appendToFrontmost(const QString& type,const QString& message,ChatWindow* serverView, const QHash &messageTags = QHash(), bool parseURL = true); void showQueueTuner(bool); int getViewIndex(QWidget* widget); ChatWindow* getViewAt(int index); QList > getChannelsURI(); public Q_SLOTS: void updateAppearance(); void saveSplitterSizes(); void setViewTreeShown(bool show = false); void updateViews(const Konversation::ServerGroupSettingsPtr serverGroup = Konversation::ServerGroupSettingsPtr()); void setViewNotification(ChatWindow* widget, const Konversation::TabNotifyType& type); void unsetViewNotification(ChatWindow* view); void toggleViewNotifications(); void toggleAutoJoin(); void toggleConnectOnStartup(); void showView(ChatWindow* view); void goToView(int page); void showNextView(); void showPreviousView(); void showNextActiveView(); void showLastFocusedView(); bool canMoveViewLeft() const; bool canMoveViewRight() const; void moveViewLeft(); void moveViewRight(); void closeView(int view); void closeView(ChatWindow* view); void closeViewMiddleClick(int view); void closeCurrentView(); void renameKonsole(); void cleanupAfterClose(ChatWindow* view); void changeViewCharset(int index); void updateViewEncoding(ChatWindow* view); void showViewContextMenu(QWidget* tab, const QPoint& pos); void clearView(); void clearAllViews(); void findText(); void findNextText(); void findPrevText(); void insertCharacter(); void insertChar(const QChar& chr); void insertIRCColor(); void doAutoReplace(); void focusInputBox(); void clearViewLines(); void insertRememberLine(); void cancelRememberLine(); void insertMarkerLine(); void insertRememberLines(Server* server); void openLogFile(); void openLogFile(const QString& caption, const QString& file); void addKonsolePanel(); + void zoomIn(); + void zoomOut(); + void resetFont(); + void addUrlCatcher(); void closeUrlCatcher(); void toggleDccPanel(); void addDccPanel(); void closeDccPanel(); void deleteDccPanel(); ChatWindow* getDccPanel(); void addDccChat(Konversation::DCC::Chat* myNick); StatusPanel* addStatusView(Server* server); RawLog* addRawLog(Server* server); void disconnectFrontServer(); void reconnectFrontServer(); void showJoinChannelDialog(); void connectionStateChanged(Server* server, Konversation::ConnectionState state); void channelJoined(Channel* channel); Channel* addChannel(Server* server, const QString& name); void rejoinChannel(); void openChannelSettings(); void toggleChannelNicklists(); Query* addQuery(Server* server,const NickInfoPtr & name, bool weinitiated=true); void updateQueryChrome(ChatWindow* view, const QString& name); void closeQueries(); ChannelListPanel* addChannelListPanel(Server* server); void openChannelList(Server* server = 0, const QString& filter = QString(), bool getList = false); void openNicksOnlinePanel(); void closeNicksOnlinePanel(); Q_SIGNALS: void viewChanged(const QModelIndex& idx); void setWindowCaption(const QString& caption); void updateChannelAppearance(); void contextMenuClosed(); void resetStatusBar(); void setStatusBarTempText(const QString& text); void clearStatusBarTempText(); void setStatusBarInfoLabel(const QString& text); void clearStatusBarInfoLabel(); void setStatusBarLagLabelShown(bool shown); void updateStatusBarLagLabel(Server* server, int msec); void resetStatusBarLagLabel(Server* server); void setStatusBarLagLabelTooLongLag(Server* server, int msec); void updateStatusBarSSLLabel(Server* server); void removeStatusBarSSLLabel(); void autoJoinToggled(const Konversation::ServerGroupSettingsPtr); void autoConnectOnStartupToggled(const Konversation::ServerGroupSettingsPtr); void frontServerChanging(Server*); private Q_SLOTS: void setupIrcContextMenus(); void viewSwitched(int newIndex); void onViewTreeDestroyed(QObject *object); private: void setupTabWidget(); void setupViewTree(); void removeViewTree(); void updateTabWidgetAppearance(); void addView(ChatWindow* view, const QString& label, bool weinitiated=true); int insertIndex(ChatWindow* view); void unclutterTabs(); void updateViewActions(int index); void updateFrontView(); void setFrontServer(Server *); void initializeSplitterSizes(); bool m_saveSplitterSizesLock; MainWindow* m_window; QSplitter* m_viewTreeSplitter; TabWidget* m_tabWidget; ViewTree* m_viewTree; QWidget* m_vbox; QueueTuner* m_queueTuner; Images* images; QPointer m_frontServer; QPointer m_contextServer; QPointer m_frontView; QPointer m_searchView; QPointer m_currentView; QPointer m_lastFocusedView; UrlCatcher* m_urlCatcherPanel; NicksOnline* m_nicksOnlinePanel; ChatWindow* m_dccPanel; bool m_dccPanelOpen; Konversation::InsertCharDialog* m_insertCharDialog; int m_popupViewIndex; int m_queryViewCount; QList m_activeViewOrderList; ViewSpringLoader* m_viewSpringLoader; }; #endif