diff --git a/src/application.h b/src/application.h index 5cb7d6a5..8672244a 100644 --- a/src/application.h +++ b/src/application.h @@ -1,208 +1,208 @@ /* 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 */ #ifndef APPLICATION_H #define APPLICATION_H #include #include #include "preferences.h" #include "mainwindow.h" #include "server.h" #include "osd.h" #include "identity.h" #include "ircqueue.h" #include class ConnectionManager; class AwayManager; class ScriptLauncher; class Server; class QuickConnectDialog; class Images; class ServerGroupSettings; class QStandardItemModel; class QCommandLineParser; class KTextEdit; namespace Konversation { class DBus; class IdentDBus; class Sound; class NotificationHandler; namespace DCC { class TransferManager; } } namespace KWallet { class Wallet; } class Application : public QApplication { Q_OBJECT public: /** This function in general shouldn't be called, because in the future there * may be multiple windows. * However, in some situations we have messageboxes that aren't targeted for * any particular main window, such as general errors from dcop calls. * * Note to any MDI developer - get this to return any of the windows, or some * 'main' one. */ MainWindow* getMainWindow() { return mainWindow; } ConnectionManager* getConnectionManager() { return m_connectionManager; } AwayManager* getAwayManager() { return m_awayManager; } ScriptLauncher* getScriptLauncher() { return m_scriptLauncher; } Konversation::DCC::TransferManager* getDccTransferManager() { return m_dccTransferManager; } // HACK void showQueueTuner(bool); // URL-Catcher QStandardItemModel* getUrlModel() { return m_urlModel; } Application(int &argc, char **argv); ~Application(); static Application* instance(); /** For D-Bus, a user can be specified as user@irc.server.net * or user\@servergroup or using the unicode separator symbol 0xE120 instead * of the "@". This function takes a string like the above examples, and * modifies ircnick and serverOrGroup to contain the split up string. If * the string doesn't have an @ or 0xE120, ircnick is set to the * nick_server, and serverOrGroup is set to empty. * Behaviour is undefined for serverOrGroup if multiple @ or 0xE120 are found. * @param nick_server A string containting ircnick and possibly servername or server group * @param ircnick This is modified to contain the ircnick * @param serverOrGroup This is modified to contain the servername, servergroup or an empty string. */ static void splitNick_Server(const QString& nick_server, QString &ircnick, QString &serverOrGroup); /** Tries to find a nickinfo for a given ircnick on a given ircserver. * @param ircnick The case-insensitive ircnick of the person you want to find. e.g. "johnflux" * @param serverOrGroup The case-insensitive server name (e.g. "irc.kde.org") or server group name (e.g. "freenode"), or null to search all servers * @return A nickinfo for this user and server if one is found. */ NickInfoPtr getNickInfo(const QString &ircnick, const QString &serverOrGroup); OSDWidget* osd; Konversation::Sound* sound(); IRCQueue::EmptyingRate staticrates[Server::_QueueListSize]; Images* images() { return m_images; } Konversation::NotificationHandler* notificationHandler() const { return m_notificationHandler; } // auto replacement for input or output lines QPair doAutoreplace(const QString& text, bool output, int cursorPos = -1); // inline auto replacement for input lines void doInlineAutoreplace(KTextEdit* textEdit); void newInstance(QCommandLineParser *args); - static void openUrl(const QString& url); + Q_INVOKABLE static void openUrl(const QString& url); /// The wallet used to store passwords. Opens the wallet if it's closed. KWallet::Wallet* wallet(); void abortScheduledRestart() { m_restartScheduled = false; } /// The command line parser is needed for handling parsing arguments on new activations. void setCommandLineParser(QCommandLineParser *parser) { m_commandLineParser = parser; } QCommandLineParser *commandLineParser() const { return m_commandLineParser; } Q_SIGNALS: void serverGroupsChanged(const Konversation::ServerGroupSettingsPtr serverGroup); void appearanceChanged(); // FIXME TODO: Rather than relying on this catch-all, consumers should be rewritten to catch appropriate QEvents. public Q_SLOTS: void restart(); void readOptions(); void saveOptions(bool updateGUI=true); void fetchQueueRates(); ///< on Application::readOptions() void stashQueueRates(); ///< on application exit void resetQueueRates(); ///< when QueueTuner says to int countOfQueues() { return Server::_QueueListSize-1; } void prepareShutdown(); void storeUrl(const QString& origin, const QString& newUrl, const QDateTime& dateTime); void handleActivate(const QStringList& arguments); protected Q_SLOTS: void openQuickConnectDialog(); void dbusMultiServerRaw(const QString &command); void dbusRaw(const QString& connection, const QString &command); void dbusSay(const QString& connection, const QString& target, const QString& command); void dbusInfo(const QString& string); void sendMultiServerCommand(const QString& command, const QString& parameter); void updateProxySettings(); void closeWallet(); protected: bool event(QEvent* event) Q_DECL_OVERRIDE; private: void implementRestart(); ConnectionManager* m_connectionManager; AwayManager* m_awayManager; Konversation::DCC::TransferManager* m_dccTransferManager; ScriptLauncher* m_scriptLauncher; QStandardItemModel* m_urlModel; Konversation::DBus* dbusObject; Konversation::IdentDBus* identDBus; QPointer mainWindow; Konversation::Sound* m_sound; QuickConnectDialog* quickConnectDialog; Images* m_images; bool m_restartScheduled; Konversation::NotificationHandler* m_notificationHandler; KWallet::Wallet* m_wallet; QNetworkConfigurationManager* m_networkConfigurationManager; QCommandLineParser *m_commandLineParser; QStringList m_restartArguments; }; #endif // kate: space-indent on; tab-width 4; indent-width 4; mixed-indent off; replace-tabs on; // vim: set et sw=4 ts=4 cino=l1,cs,U1: diff --git a/src/irc/messagemodel.cpp b/src/irc/messagemodel.cpp index a35a18f1..07774952 100644 --- a/src/irc/messagemodel.cpp +++ b/src/irc/messagemodel.cpp @@ -1,173 +1,197 @@ /* 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) 2017 Eike Hein */ #include "messagemodel.h" #include +#define MAX_MESSAGES 500000 +#define MAX_MESSAGES_TOLERANCE 501000 +#define ALLOCATION_BATCH_SIZE 500 + FilteredMessageModel::FilteredMessageModel(QObject *parent) : QSortFilterProxyModel(parent) , m_filterView(nullptr) { } FilteredMessageModel::~FilteredMessageModel() { } QObject *FilteredMessageModel::filterView() const { return m_filterView; } void FilteredMessageModel::setFilterView(QObject *view) { if (m_filterView != view) { m_filterView = view; invalidateFilter(); emit filterViewChanged(); } } bool FilteredMessageModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const { Q_UNUSED(sourceParent) if (!m_filterView) { return false; } const QModelIndex &sourceIdx = sourceModel()->index(sourceRow, 0); const QObject *view = qvariant_cast(sourceIdx.data(MessageModel::View)); if (view && view == m_filterView) { return true; } return false; } QVariant FilteredMessageModel::data(const QModelIndex &index, int role) const { if (role == MessageModel::AuthorMatchesPrecedingMessage) { const int precedingMessageRow = index.row() + 1; if (precedingMessageRow < rowCount()) { const QModelIndex &precedingMessage = QSortFilterProxyModel::index(precedingMessageRow, 0); return (index.data(MessageModel::Author) == precedingMessage.data(MessageModel::Author)); } } if (role == MessageModel::TimeStampMatchesPrecedingMessage) { const int precedingMessageRow = index.row() + 1; if (precedingMessageRow < rowCount()) { const QModelIndex &precedingMessage = QSortFilterProxyModel::index(precedingMessageRow, 0); return (index.data(MessageModel::TimeStamp) == precedingMessage.data(MessageModel::TimeStamp)); } } return QSortFilterProxyModel::data(index, role); } MessageModel::MessageModel(QObject *parent) : QAbstractListModel(parent) + , m_allocCount(0) { + // Pre-allocate batch. + m_messages.reserve(ALLOCATION_BATCH_SIZE); } MessageModel::~MessageModel() { } QHash MessageModel::roleNames() const { QHash roles = QAbstractItemModel::roleNames(); QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); for (int i = 0; i < e.keyCount(); ++i) { roles.insert(e.value(i), e.key(i)); } return roles; } QVariant MessageModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_messages.count()) { return QVariant(); } const Message &msg = m_messages.at(index.row()); if (role == Qt::DisplayRole) { return msg.text; } else if (role == Type) { return (msg.action ? ActionMessage : NormalMessage); } else if (role == View) { return qVariantFromValue(msg.view); } else if (role == TimeStamp) { return msg.timeStamp; } else if (role == Author) { return msg.nick; } else if (role == NickColor) { return msg.nickColor; } return QVariant(); } int MessageModel::rowCount(const QModelIndex &parent) const { - return parent.isValid() ? 0 : m_messages.count(); + // Limit model to MAX_MESSAGES. + return parent.isValid() ? 0 : qMax(MAX_MESSAGES, m_messages.count()); } void MessageModel::appendMessage(QObject *view, const QString &timeStamp, const QString &nick, const QColor &nickColor, const QString &text, const MessageType type) { beginInsertRows(QModelIndex(), 0, 0); Message msg; msg.view = view; msg.timeStamp = timeStamp; msg.nick = nick; msg.nickColor = nickColor; msg.text = text; msg.action = (type == ActionMessage); m_messages.prepend(msg); endInsertRows(); + + ++m_allocCount; + + // Grow the list in batches to make the insertion a little + // faster. + if (m_allocCount == ALLOCATION_BATCH_SIZE) { + m_allocCount = 0; + m_messages.reserve(m_messages.count() + ALLOCATION_BATCH_SIZE); + } + + // Whenever we grow above MAX_MESSAGES_TOLERANCE, cull to + // MAX_MESSAGES. I.e. we cull in batches, not on every new + // message. + if (m_messages.count() > MAX_MESSAGES_TOLERANCE) { + m_messages = m_messages.mid(0, MAX_MESSAGES); + } } void MessageModel::cullMessages(const QObject *view) { int i = 0; while (i < m_messages.count()) { const Message &msg = m_messages.at(i); if (msg.view == view) { beginRemoveRows(QModelIndex(), i, i); m_messages.removeAt(i); endRemoveRows(); } else { ++i; } } } diff --git a/src/irc/messagemodel.h b/src/irc/messagemodel.h index 721b7102..488e8573 100644 --- a/src/irc/messagemodel.h +++ b/src/irc/messagemodel.h @@ -1,94 +1,95 @@ /* 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) 2017 Eike Hein */ #include "chatwindow.h" #include #include #include struct Message { QObject *view; QString timeStamp; QString nick; QColor nickColor; QString text; bool action; }; class FilteredMessageModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QObject* filterView READ filterView WRITE setFilterView NOTIFY filterViewChanged) public: explicit FilteredMessageModel(QObject *parent = 0); virtual ~FilteredMessageModel(); QObject *filterView() const; void setFilterView(QObject *view); virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; signals: void filterViewChanged() const; protected: bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; private: QObject *m_filterView; }; class MessageModel : public QAbstractListModel { Q_OBJECT public: enum AdditionalRoles { Type = Qt::UserRole + 1, View, TimeStamp, TimeStampMatchesPrecedingMessage, // Implemented in FilteredMessageModel for search efficiency. Author, AuthorMatchesPrecedingMessage, // Implemented in FilteredMessageModel for search efficiency. NickColor, }; Q_ENUM(AdditionalRoles) enum MessageType { NormalMessage = 0, ActionMessage }; Q_ENUM(MessageType) explicit MessageModel(QObject *parent = 0); virtual ~MessageModel(); QHash roleNames() const override; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; Q_INVOKABLE void appendMessage(QObject *view, const QString &timeStamp, const QString &nick, const QColor &nickColor, const QString &text, const MessageType type = NormalMessage); void cullMessages(const QObject *view); private: QList m_messages; + int m_allocCount; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 96743d69..750ca5d5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,1045 +1,1046 @@ /* 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 "messagemodel.h" // WIPQTQUICK #include #include #include #include #include #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include // WIPQTQUICK #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // WIPQTQUICK MainWindow::MainWindow(bool raiseQtQuickUi, const QString& uiPackage) : KXmlGuiWindow(0) // WIPQTQUICK { m_hasDirtySettings = false; m_closeApp = false; m_serverListDialog = 0; m_trayIcon = 0; m_settingsDialog = NULL; // BEGIN: WIPQTQUICK QStackedWidget *uiStack = new QStackedWidget(this); setCentralWidget(uiStack); m_viewContainer = new ViewContainer(this); uiStack->addWidget(m_viewContainer->getWidget()); m_messageModel = new MessageModel(this); m_filteredMessageModel = new FilteredMessageModel(this); m_filteredMessageModel->setSourceModel(m_messageModel); // Filter on the new view. connect(m_viewContainer, &ViewContainer::viewChanged, this, [this](const QModelIndex &idx) { m_filteredMessageModel->setFilterView(static_cast(idx.internalPointer())); } ); QObject::connect(m_viewContainer, &QAbstractItemModel::rowsAboutToBeRemoved, this, [this](const QModelIndex &parent, int first, int last) { Q_UNUSED(parent) for (int i = first; i <= last; ++i) { const QModelIndex &idx = m_viewContainer->index(i, 0); const QObject *view = static_cast(idx.internalPointer()); // Update filter if the filter view is closing. if (m_filteredMessageModel->filterView() == view) { m_filteredMessageModel->setFilterView(nullptr); } m_messageModel->cullMessages(view); } } ); // Update filter when Viewcontainer resets. QObject::connect(m_viewContainer, &QAbstractItemModel::modelAboutToBeReset, this, [this]() { m_filteredMessageModel->setFilterView(nullptr); } ); auto plist = KPackage::PackageLoader::self()->listPackages(QStringLiteral("Konversation/UiPackage")); qDebug() << "Available Qt Quick UI packages (name / id):"; for (const auto &pkg : plist) { qDebug() << " " << pkg.name() << "/" << pkg.pluginId(); } QLatin1Literal packageNamePrefix("org.kde.konversation.uipackages."); KPackage::Package p = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Konversation/UiPackage"), uiPackage.startsWith(packageNamePrefix) ? uiPackage : packageNamePrefix + uiPackage); qDebug() << "Package root:" << p.defaultPackageRoot(); qDebug() << "Required files:" << p.requiredFiles(); qDebug() << "Required directories:" << p.requiredDirectories(); if (p.isValid()) { qDebug() << "Package is valid."; qDebug() << "File path for 'window':" << p.filePath("window"); m_qmlEngine = new QQmlApplicationEngine(this); qmlRegisterUncreatableType("org.kde.konversation", 1, 0, "MessageModel", ""); + m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("konvApp"), Application::instance()); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("viewModel"), m_viewContainer); m_qmlEngine->rootContext()->setContextProperty(QStringLiteral("messageModel"), m_filteredMessageModel); m_qmlEngine->load(QUrl::fromLocalFile(p.filePath("window"))); uiStack->addWidget(QWidget::createWindowContainer(static_cast(m_qmlEngine->rootObjects().first()), this)); if (raiseQtQuickUi) { uiStack->setCurrentIndex(1); } } else { qDebug() << "Package is invalid."; m_qmlEngine = nullptr; delete uiStack->widget(1); } // END: WIPQTQUICK //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; // WIPQTQUICK action=new QAction(this); action->setText(i18n("Toggle UIs")); actionCollection()->setDefaultShortcut(action,QKeySequence(QStringLiteral("F10"))); connect(action, &QAction::triggered, this, [uiStack]() { uiStack->setCurrentIndex(uiStack->currentIndex() ? 0 : 1); } ); actionCollection()->addAction(QStringLiteral("toggle_ui"), 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(); // BEGIN WIPQTQUICK if (m_qmlEngine) { // Hide menubar by default. m_showMenuBarAction->setChecked(false); } else { // Apply menubar show/hide pref m_showMenuBarAction->setChecked(Preferences::self()->showMenuBar()); } toggleMenubar(true); QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(showMenuBar(bool)), this, SLOT(showMenubar(bool))); QObject::connect(m_qmlEngine->rootObjects().first(), SIGNAL(openLegacyConfigDialog()), this, SLOT(openPrefsDialog())); // END WIPQTQUICK 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::showMenubar(bool show) { Q_UNUSED(show) m_showMenuBarAction->setChecked(show); toggleMenubar(true /* dontShowWarning */); } 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/qtquick/uipackages/default/contents/Message.qml b/src/qtquick/uipackages/default/contents/Message.qml deleted file mode 100644 index 4bf29763..00000000 --- a/src/qtquick/uipackages/default/contents/Message.qml +++ /dev/null @@ -1,200 +0,0 @@ -/* - 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) 2017 Eike Hein -*/ - -import QtQuick 2.7 - -import org.kde.kirigami 2.1 as Kirigami - -import org.kde.konversation 1.0 as Konversation - -Item { - id: msg - - width: ListView.view.width - height: (metabitsLoader.active - ? (konvApp.largerFontSize + messageText.height + Kirigami.Units.gridUnit) - : messageText.height) - - readonly property int avatarSize: nick.height * 2 - - property bool selectable: false - property Item selectableText: null - - readonly property bool linkHovered: selectableText && selectableText.hoveredLink != "" - - onSelectableChanged: { - if (selectable) { - selectableText = selectableTextComponent.createObject(msg); - selectableText.forceActiveFocus(); - } else if (selectableText) { - selectableText.destroy(); - } - } - - Text { // WIPQTQUICK TODO Only outside loader to set avatar height. - id: nick - - visible: metabitsLoader.active - - y: Kirigami.Units.gridUnit / 2 - - anchors.left: parent.left - anchors.leftMargin: avatarSize + Kirigami.Units.gridUnit - - renderType: Text.NativeRendering - color: model.NickColor - - font.weight: Font.Bold - font.pixelSize: konvApp.largerFontSize - - text: model.Author - } - - Loader { - id: metabitsLoader - - active: !model.AuthorMatchesPrecedingMessage - - anchors.fill: parent - - sourceComponent: metabitsComponent - } - - Component { - id: metabitsComponent - - Item { - anchors.fill: parent - - Rectangle { - id: avatar - - x: Kirigami.Units.gridUnit / 2 - y: Kirigami.Units.gridUnit / 2 - - width: avatarSize - height: avatarSize - - color: model.NickColor - - radius: width * 0.5 - - Text { - anchors.fill: parent - anchors.margins: Kirigami.Units.smallSpacing - - renderType: Text.QtRendering - color: "white" - - font.weight: Font.Bold - font.pointSize: 100 - minimumPointSize: theme.defaultFont.pointSize - fontSizeMode: Text.Fit - - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - - text: { - // WIPQTQUICK HACK TODO Probably doesn't work with non-latin1. - var match = model.Author.match(/([a-zA-Z])([a-zA-Z])/); - var abbrev = match[1].toUpperCase(); - - if (match.length > 2) { - abbrev += match[2].toLowerCase(); - } - - return abbrev; - } - } - } - - Text { - id: timeStamp - - y: Kirigami.Units.gridUnit / 2 - - height: nick.height - - anchors.left: parent.left - anchors.leftMargin: (avatarSize - + Kirigami.Units.gridUnit - + nick.width - + (Kirigami.Units.gridUnit / 2)) - - renderType: Text.NativeRendering - color: "grey" - - text: model.TimeStamp - verticalAlignment: Text.AlignVCenter - } - } - } - - Text { - id: messageText - - visible: !selectable - - anchors.left: parent.left - anchors.leftMargin: avatarSize + Kirigami.Units.gridUnit - anchors.right: parent.right - anchors.bottom: parent.bottom - - renderType: Text.NativeRendering - textFormat: Text.RichText - - font.pixelSize: konvApp.largerFontSize - - wrapMode: Text.WordWrap - - text: { - var t = model.display; - - if (model.Type == Konversation.MessageModel.ActionMessage) { - t = "" + model.Nick + " " + t + ""; - } - - if (metabitsLoader.active) { - return t; - } else if (!model.TimeStampMatchesPrecedingMessage) { - return t + "  " + model.TimeStamp + ""; - } - - return t; - } - - onLinkActivated: Qt.openUrlExternally(link) - } - - Component { - id: selectableTextComponent - - TextEdit { - anchors.fill: messageText - - readOnly: true - selectByMouse: true - persistentSelection: true - - renderType: Text.NativeRendering - textFormat: Text.RichText - - font.pixelSize: konvApp.largerFontSize - - wrapMode: Text.WordWrap - - text: messageText.text - - onLinkActivated: Qt.openUrlExternally(link) - } - } -} - diff --git a/src/qtquick/uipackages/default/contents/SettingsModeButtons.qml b/src/qtquick/uipackages/default/contents/SettingsModeButtons.qml index 2f18b5be..6ebddcc2 100644 --- a/src/qtquick/uipackages/default/contents/SettingsModeButtons.qml +++ b/src/qtquick/uipackages/default/contents/SettingsModeButtons.qml @@ -1,67 +1,67 @@ /* 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) 2017 Eike Hein */ import QtQuick 2.7 import QtQuick.Controls 2.2 as QQC2 import org.kde.kirigami 2.1 as Kirigami Row { id: settingsModeButtons property Item okButton: null property Item applyButton: null property Item cancelButton: null spacing: Kirigami.Units.smallSpacing rightPadding: Kirigami.Units.largeSpacing / 2 layoutDirection: Qt.RightToLeft // WIPQTQUICK TODO Test RTL mode (layouts, animation directions) QQC2.Button { id: okButton anchors.verticalCenter: parent.verticalCenter enabled: parent.enabled text: "Ok" // WIPQTQUICK TODO i18n Component.onOnCompleted: settingsButtons.okButton = okButton } QQC2.Button { id: applyButton anchors.verticalCenter: parent.verticalCenter enabled: parent.enabled text: "Apply" // WIPQTQUICK TODO i18n Component.onOnCompleted: settingsButtons.applyButton = applyButton } QQC2.Button { id: cancelButton anchors.verticalCenter: parent.verticalCenter enabled: parent.enabled text: "Cancel" // WIPQTQUICK TODO i18n Component.onOnCompleted: settingsButtons.cancelButton = cancelButton } - Component.onCompleted: konvApp.settingsModeButtons = settingsModeButtons + Component.onCompleted: konvUi.settingsModeButtons = settingsModeButtons } diff --git a/src/qtquick/uipackages/default/contents/SettingsPage.qml b/src/qtquick/uipackages/default/contents/SettingsPage.qml index d138822a..c4988a58 100644 --- a/src/qtquick/uipackages/default/contents/SettingsPage.qml +++ b/src/qtquick/uipackages/default/contents/SettingsPage.qml @@ -1,52 +1,52 @@ /* 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) 2017 Eike Hein */ import QtQuick 2.7 import QtQuick.Controls 2.2 as QQC2 import org.kde.kirigami 2.1 as Kirigami Item { id: settingsPage property alias title: pageTitle.text Column { anchors.fill: parent anchors.leftMargin: Kirigami.Units.gridUnit spacing: Kirigami.Units.gridUnit Kirigami.Heading { id: pageTitle level: 1 } QQC2.Button { text: "Show Legacy Menu Bar" checkable: true - onCheckedChanged: konvApp.showMenuBar(checked) + onCheckedChanged: konvUi.showMenuBar(checked) } QQC2.Button { text: "Open Legacy Config Dialog" - onClicked: konvApp.openLegacyConfigDialog() + onClicked: konvUi.openLegacyConfigDialog() } QQC2.Label { text: "Note: You can press F10 at any time to switch between the old and new UI!" } } } diff --git a/src/qtquick/uipackages/default/contents/TextView.qml b/src/qtquick/uipackages/default/contents/TextView.qml new file mode 100644 index 00000000..3c387e95 --- /dev/null +++ b/src/qtquick/uipackages/default/contents/TextView.qml @@ -0,0 +1,196 @@ +/* + 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) 2017 Eike Hein +*/ + +import QtQuick 2.7 + +import QtQuick.Controls 2.2 as QQC2 + +import org.kde.kirigami 2.1 as Kirigami + +import org.kde.konversation 1.0 as Konversation +import org.kde.konversation.uicomponents 1.0 as KUIC + +KUIC.ListView { + id: textViewList + + visible: !konvUi.settingsMode + + QQC2.ScrollBar.vertical: QQC2.ScrollBar {} + + onHeightChanged: positionViewAtBeginning() + + readonly property int msgWidth: width - QQC2.ScrollBar.vertical.width + + verticalLayoutDirection: ListView.BottomToTop + + model: messageModel + delegate: msgComponent + + ListView.onAdd: { + currentIndex = 0; + positionViewAtIndex(0, ListView.Contain); + } + + Component { + id: msgComponent + + Loader { + id: msg + + width: ListView.view.msgWidth + height: (active ? (konvUi.largerFontSize + messageText.height + Kirigami.Units.gridUnit) + : messageText.height) + + readonly property int avatarSize: konvUi.largerFontSize * 3.6 + property var authorSize: Qt.point(0, 0) + + readonly property bool showTimeStamp: !model.TimeStampMatchesPrecedingMessage + property Item timeStamp: null + + active: !model.AuthorMatchesPrecedingMessage + sourceComponent: metabitsComponent + + onShowTimeStampChanged: { + if (!showTimeStamp) { + if (timeStamp) { + timeStamp.destroy(); + } + } else { + timeStamp = timeStampComponent.createObject(msg); + } + } + + Component { + id: timeStampComponent + + Text { + id: timeStamp + + readonly property bool collides: (messageText.x + + messageText.implicitWidth + + Kirigami.Units.smallSpacing + width > parent.width) + readonly property int margin: Kirigami.Units.gridUnit / 2 + + x: messageText.x + margin + (active ? authorSize.x : messageText.contentWidth) + + y: { + if (!active) { + return messageText.y + ((largerFontMetrics.height / 2) - (height / 2)); + } else { + return (Kirigami.Units.gridUnit / 2) + ((authorSize.y / 2) - (height / 2)); + } + } + + renderType: Text.NativeRendering + color: "grey" + + text: model.TimeStamp + } + } + + Component { + id: metabitsComponent + + Item { + anchors.fill: parent + + Rectangle { + id: avatar + + x: Kirigami.Units.gridUnit / 2 + y: Kirigami.Units.gridUnit / 2 + + width: avatarSize + height: avatarSize + + color: model.NickColor + + radius: width * 0.5 + + Text { + anchors.fill: parent + anchors.margins: Kirigami.Units.smallSpacing + + renderType: Text.QtRendering + color: "white" + + font.weight: Font.Bold + font.pointSize: 100 + minimumPointSize: theme.defaultFont.pointSize + fontSizeMode: Text.Fit + + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + + text: { + // WIPQTQUICK HACK TODO Probably doesn't work with non-latin1. + var match = model.Author.match(/([a-zA-Z])([a-zA-Z])/); + var abbrev = match[1].toUpperCase(); + + if (match.length > 2) { + abbrev += match[2].toLowerCase(); + } + + return abbrev; + } + } + } + + Text { + id: author + + y: Kirigami.Units.gridUnit / 2 + + anchors.left: parent.left + anchors.leftMargin: avatarSize + Kirigami.Units.gridUnit + + renderType: Text.NativeRendering + color: model.NickColor + + font.weight: Font.Bold + font.pixelSize: konvUi.largerFontSize + + text: model.Author + + onWidthChanged: msg.authorSize = Qt.point(width, height) + } + } + } + + Text { + id: messageText + + anchors.left: parent.left + anchors.leftMargin: avatarSize + Kirigami.Units.gridUnit + anchors.right: parent.right + anchors.rightMargin: (timeStamp && timeStamp.collides + ? timeStamp.margin + timeStamp.width : 0) + anchors.bottom: parent.bottom + + renderType: Text.NativeRendering + textFormat: Text.StyledText + + font.pixelSize: konvUi.largerFontSize + + wrapMode: Text.Wrap + + text: (model.Type == Konversation.MessageModel.ActionMessage + ? actionWrap(model.display) : model.display) + + function actionWrap(text) { + return "" + model.Author + " " + text + ""; + } + + onLinkActivated: konvApp.openUrl(link) + } + } + } +} diff --git a/src/qtquick/uipackages/default/contents/main.qml b/src/qtquick/uipackages/default/contents/main.qml index eeeae678..e65189c2 100644 --- a/src/qtquick/uipackages/default/contents/main.qml +++ b/src/qtquick/uipackages/default/contents/main.qml @@ -1,782 +1,718 @@ /* 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) 2017 Eike Hein */ import QtQuick 2.7 import QtQml.Models 2.2 import QtQuick.Controls 1.4 as QQC1 import QtQuick.Controls 2.2 as QQC2 -import QtQuick.Layouts 1.0 import org.kde.kirigami 2.1 as Kirigami import org.kde.konversation.uicomponents 1.0 as KUIC Kirigami.ApplicationWindow { - id: konvApp + id: konvUi property int defaultSidebarWidth: Kirigami.Units.gridUnit * 11 property int defaultContextDrawerWidth: Kirigami.Units.gridUnit * 17 property int sidebarWidth: defaultSidebarWidth property int largerFontSize: Kirigami.Theme.defaultFont.pixelSize * 1.1 property int footerHeight: largerFontSize + (Kirigami.Units.smallSpacing * 6) property Item sidebarStackView: null property Item contentStackView: null property Item contentFooterStackView: null property Item inputField: null property bool settingsMode: false property Item settingsModeButtons: null signal openLegacyConfigDialog signal showMenuBar(bool show) pageStack.defaultColumnWidth: sidebarWidth pageStack.initialPage: [sidebarComponent, contentComponent] pageStack.separatorVisible: false + TextMetrics { + id: largerFontMetrics + + font.pixelSize: largerFontSize + text: "M" + } + contextDrawer: Kirigami.OverlayDrawer { id: contextDrawer width: defaultContextDrawerWidth edge: Qt.RightEdge modal: true handleVisible: drawerOpen drawerOpen: false background: Rectangle { color: Kirigami.Theme.viewBackgroundColor } onDrawerOpenChanged: { if (drawerOpen) { userList.forceActiveFocus(); userList.currentIndex = -1; } } leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 Rectangle { id: topicArea visible: viewModel.currentView && viewModel.currentView.description != "" anchors.top: parent.top anchors.left: parent.left anchors.right: parent.right height: visible ? channelName.height + topic.contentHeight + (Kirigami.Units.smallSpacing * 4): 0 color: KUIC.ExtraColors.spotColor Kirigami.Heading { id: channelName x: (Kirigami.Units.smallSpacing * 2) level: 2 text: viewModel.currentView ? viewModel.currentView.name : "" color: KUIC.ExtraColors.alternateSpotTextColor opacity: 1.0 // Override } Text { id: topic x: (Kirigami.Units.smallSpacing * 2) y: channelName.height + (Kirigami.Units.smallSpacing * 2) width: parent.width - (Kirigami.Units.smallSpacing * 4) text: viewModel.currentView ? viewModel.currentView.description : "" textFormat: Text.StyledText font.pixelSize: largerFontSize color: KUIC.ExtraColors.spotTextColor wrapMode: Text.WordWrap onLinkActivated: Qt.openUrlExternally(link) } } MouseArea { anchors.fill: parent onClicked: userList.forceActiveFocus() } KUIC.ListView { id: userList anchors.top: topicArea.bottom anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom visible: viewModel.currentView && "userModel" in viewModel.currentView QQC2.ScrollBar.vertical: QQC2.ScrollBar {} onHeightChanged: { if (currentIndex != -1) { positionViewAtIndex(currentIndex, ListView.Contain); } } clip: true currentIndex: -1 onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) model: visible ? viewModel.currentView.userModel : null onModelChanged: currentIndex = -1 delegate: ListItem { width: userList.width text: model.display textMargin: Kirigami.Units.gridUnit function openQuery() { viewModel.currentServer.addQuery(model.display); contextDrawer.close(); } onClicked: { userList.forceActiveFocus(); userList.currentIndex = index; } onDoubleClicked: openQuery(); Keys.onEnterPressed: { event.accept = true; openQuery(); } Keys.onReturnPressed: { event.accept = true; openQuery(); } } Keys.onUpPressed: { event.accept = true; if (currentIndex == -1) { currentIndex = 0; return; } decrementCurrentIndex(); } Keys.onDownPressed: { event.accept = true; if (currentIndex == -1) { currentIndex = 0; return; } incrementCurrentIndex(); } Keys.onPressed: { // WIPQTQUICK TODO Evaluating text is not good enough, needs real key event fwd // to make things like deadkeys work if (event.text != "" && inputField && !inputField.activeFocus) { contextDrawer.close(); event.accept = true; inputField.textForward(event.text); } } } } MouseArea { id: dragHandle visible: pageStack.wideMode anchors.top: parent.top anchors.bottom: parent.bottom x: sidebarWidth - (width / 2) width: Kirigami.Units.devicePixelRatio * 2 property int dragRange: (Kirigami.Units.gridUnit * 5) property int _lastX: -1 cursorShape: Qt.SplitHCursor onPressed: _lastX = mouseX onPositionChanged: { if (mouse.x > _lastX) { sidebarWidth = Math.min((defaultSidebarWidth + dragRange), sidebarWidth + (mouse.x - _lastX)); } else if (mouse.x < _lastX) { sidebarWidth = Math.max((defaultSidebarWidth - dragRange), sidebarWidth - (_lastX - mouse.x)); } } } Component { id: sidebarComponent Kirigami.Page { id: sidebar leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 property Item viewTreeList: null MouseArea { anchors.fill: parent onClicked: { if (viewTreeList) { viewTreeList.forceActiveFocus(); } } } QQC2.StackView { id: sidebarStackView anchors.fill: parent background: Rectangle { color: KUIC.ExtraColors.spotColor } initialItem: viewTreeComponent onBusyChanged: { if (!busy && depth == 2) { currentItem.currentIndex = 0; currentItem.forceActiveFocus(); } } pushEnter: Transition { YAnimator { from: sidebarStackView.height to: 0 duration: Kirigami.Units.longDuration * 2 easing.type: Easing.OutCubic } } pushExit: Transition { OpacityAnimator { from: 1.0 to: 0.0 duration: Kirigami.Units.longDuration * 2 } } popEnter: Transition { OpacityAnimator { from: 0.0 to: 1.0 duration: Kirigami.Units.longDuration * 2 } } popExit: Transition { YAnimator { from: 0 to: sidebarStackView.height duration: Kirigami.Units.longDuration * 2 easing.type: Easing.OutCubic } } - Component.onCompleted: konvApp.sidebarStackView = sidebarStackView + Component.onCompleted: konvUi.sidebarStackView = sidebarStackView } Component { id: viewTreeComponent KUIC.ListView { id: viewTreeList QQC2.ScrollBar.vertical: QQC2.ScrollBar {} clip: true model: viewModel function showView(index, view) { viewTreeList.forceActiveFocus(); viewModel.showView(view); - if (!konvApp.pageStack.wideMode) { - konvApp.pageStack.currentIndex = 1; + if (!konvUi.pageStack.wideMode) { + konvUi.pageStack.currentIndex = 1; } } delegate: Column { property int topLevelIndex: index ListItem { width: viewTreeList.width textColor: KUIC.ExtraColors.spotTextColor backgroundColor: KUIC.ExtraColors.spotColor text: model.display textMargin: Kirigami.Units.gridUnit onClicked: viewTreeList.showView(topLevelIndex, value) } DelegateModel { id: subLevelEntries model: viewModel rootIndex: modelIndex(index) delegate: ListItem { width: viewTreeList.width textColor: KUIC.ExtraColors.spotTextColor backgroundColor: KUIC.ExtraColors.spotColor text: model.display textMargin: Kirigami.Units.gridUnit * 2 onClicked: viewTreeList.showView(topLevelIndex, value) } } Column { Repeater { model: subLevelEntries } } } Keys.onUpPressed: { event.accept = true; viewModel.showPreviousView(); } Keys.onDownPressed: { event.accept = true; viewModel.showNextView(); } Component.onCompleted: sidebar.viewTreeList = viewTreeList } } Component { id: settingsTreeComponent QQC2.ScrollView { id: viewTree property alias currentIndex: settingsTreeList.currentIndex KUIC.ListView { id: settingsTreeList focus: true clip: true currentIndex: -1 onCurrentIndexChanged: positionViewAtIndex(currentIndex, ListView.Contain) model: ListModel { ListElement { name: "Dummy 1" } ListElement { name: "Dummy 2" } ListElement { name: "Dummy 3" } } delegate: ListItem { width: settingsTreeList.width textColor: KUIC.ExtraColors.spotTextColor backgroundColor: KUIC.ExtraColors.spotColor text: name textMargin: Kirigami.Units.gridUnit onIsActiveChanged: { - if (isActive && konvApp.contentStackView.depth == 1) { - konvApp.contentStackView.push("SettingsPage.qml", {"title": name}); - //konvApp.settingsModeButtons.enabled = true; + if (isActive && konvUi.contentStackView.depth == 1) { + konvUi.contentStackView.push("SettingsPage.qml", {"title": name}); + //konvUi.settingsModeButtons.enabled = true; } } onClicked: { settingsTreeList.forceActiveFocus(); settingsTreeList.currentIndex = index; } } Keys.onUpPressed: { event.accept = true; if (currentIndex == -1) { currentIndex = 0; return; } decrementCurrentIndex(); } Keys.onDownPressed: { event.accept = true; if (currentIndex == -1) { currentIndex = 0; return; } incrementCurrentIndex(); } } } } PageHandle { id: sidebarRightPaginationHandle anchors.right: parent.right - visible: !konvApp.pageStack.wideMode + visible: !konvUi.pageStack.wideMode iconName: "go-previous" iconSelected: true color: KUIC.ExtraColors.alternateSpotColor onTriggered: pageStack.currentIndex = 1 } footer: Rectangle { id: sidebarFooter width: parent.width height: footerHeight color: KUIC.ExtraColors.alternateSpotColor Rectangle { id: settingsModeToggleButton width: sidebarFooter.height height: width property bool checked: false color: checked ? Kirigami.Theme.highlightColor: KUIC.ExtraColors.alternateSpotColor onCheckedChanged: { - konvApp.settingsMode = checked; + konvUi.settingsMode = checked; if (checked) { sidebarStackView.push(settingsTreeComponent); - konvApp.contentFooterStackView.push("SettingsModeButtons.qml", {"enabled": false}); + konvUi.contentFooterStackView.push("SettingsModeButtons.qml", {"enabled": false}); } else { sidebarStackView.pop(); - if (konvApp.contentStackView.depth == 2) { - konvApp.contentStackView.pop(); - konvApp.contentFooterStackView.pop(); + if (konvUi.contentStackView.depth == 2) { + konvUi.contentStackView.pop(); + konvUi.contentFooterStackView.pop(); } - konvApp.showMenuBar(false); + konvUi.showMenuBar(false); inputField.forceActiveFocus(); } } Kirigami.Icon { anchors.centerIn: parent width: parent.width - (Kirigami.Units.smallSpacing * 4) height: width selected: true source: "application-menu" } MouseArea { anchors.fill: parent onClicked: parent.checked = !parent.checked } } QQC1.ComboBox { anchors.fill: sidebarFooter anchors.leftMargin: settingsModeToggleButton.width visible: viewModel.currentServer editable: true model: viewModel.currentServer ? [viewModel.currentServer.nickname] : [] onAccepted: { return; // WIPQTQUICK TODO Server::setNickname does something weird if (viewModel.currentServer) { viewModel.currentServer.setNickname(currentText); } } } } Keys.onPressed: { // WIPQTQUICK TODO Evaluating text is not good enough, needs real key event fwd // to make things like deadkeys work if (event.text != "" && inputField && !inputField.activeFocus) { event.accept = true; inputField.textForward(event.text); } } } } Component { id: contentComponent Kirigami.Page { leftPadding: 0 rightPadding: 0 topPadding: 0 bottomPadding: 0 onWidthChanged: { - konvApp.pageStack.currentIndex = 1; + konvUi.pageStack.currentIndex = 1; } Rectangle { anchors.fill: parent color: Kirigami.Theme.viewBackgroundColor } QQC2.StackView { id: contentStackView anchors.fill: parent anchors.bottomMargin: Kirigami.Units.smallSpacing - initialItem: textViewComponent - pushEnter: null pushExit: null popEnter: null popExit: null - Component.onCompleted: konvApp.contentStackView = contentStackView - } - - Component { - id: textViewComponent - - KUIC.ListView { - id: textViewList - - visible: !konvApp.settingsMode - - QQC2.ScrollBar.vertical: QQC2.ScrollBar {} - - onHeightChanged: positionViewAtBeginning() - - verticalLayoutDirection: ListView.BottomToTop - - model: messageModel - - delegate: Message {} - - ListView.onAdd: { - currentIndex = 0; - positionViewAtIndex(0, ListView.Contain); - } - - MouseArea { - anchors.fill: parent - - property Item hoveredMessage: null - - acceptedButtons: Qt.NoButton - hoverEnabled: true - - cursorShape: { - if (hoveredMessage) { - if (hoveredMessage.linkHovered) { - return Qt.OpenHandCursor; - } else { - return Qt.IBeamCursor; - } - } - - return Qt.ArrowCursor; - } - - onContainsMouseChanged: { - if (!containsMouse && hoveredMessage) { - hoveredMessage.selectable = false; - hoveredMessage = null; - inputField.forceActiveFocus(); - } - } - - onPositionChanged: { - var pos = mapToItem(textViewList.contentItem, mouse.x, mouse.y); - var msg = textViewList.itemAt(pos.x, pos.y); - - if (hoveredMessage && hoveredMessage != msg) { - hoveredMessage.selectable = false; - } - - hoveredMessage = msg; - - if (hoveredMessage) { - hoveredMessage.selectable = true; - } else { - inputField.forceActiveFocus(); - } - } - } + Component.onCompleted: { + contentStackView.push("TextView.qml"); + konvUi.contentStackView = contentStackView; } } footer: QQC2.StackView { id: contentFooterStackView height: footerHeight background: Rectangle { color: Qt.darker(Kirigami.Theme.viewBackgroundColor, 1.02) } initialItem: inputFieldComponent pushEnter: Transition { XAnimator { from: contentFooterStackView.width to: 0 duration: Kirigami.Units.longDuration * 2 easing.type: Easing.OutCubic } } pushExit: Transition { OpacityAnimator { from: 1.0 to: 0.0 duration: Kirigami.Units.longDuration * 2 } } popEnter: Transition { OpacityAnimator { from: 0.0 to: 1.0 duration: Kirigami.Units.longDuration * 2 } } popExit: Transition { XAnimator { from: 0 to: contentFooterStackView.width duration: Kirigami.Units.longDuration * 2 easing.type: Easing.OutCubic } } Component { id: inputFieldComponent QQC2.TextField { id: inputField background: null enabled: viewModel.currentView renderType: Text.NativeRendering font.pixelSize: largerFontSize verticalAlignment: Text.AlignVCenter wrapMode: TextEdit.NoWrap Keys.onPressed: { // WIPQTQUICK TODO Evaluating text is not good enough, needs real key event fwd - // to make things like deadkeys work + // to make things like deadkeys work if (text != "" && (event.key == Qt.Key_Enter || event.key == Qt.Key_Return)) { event.accepted = true; viewModel.currentView.sendText(text); text = ""; } } function textForward(text) { forceActiveFocus(); insert(length, text); cursorPosition = length; } Component.onCompleted: { - konvApp.inputField = inputField; + konvUi.inputField = inputField; forceActiveFocus(); } } } - Component.onCompleted: konvApp.contentFooterStackView = contentFooterStackView + Component.onCompleted: konvUi.contentFooterStackView = contentFooterStackView } PageHandle { id: contentLeftPaginationHandle anchors.left: parent.left visible: !pageStack.wideMode iconName: "go-next" onTriggered: pageStack.currentIndex = 0 } PageHandle { id: contentRightPaginationHandle anchors.right: parent.right - visible: (!konvApp.settingsMode + visible: (!konvUi.settingsMode && viewModel.currentView && !contextDrawer.drawerOpen) iconName: "go-previous" onTriggered: contextDrawer.drawerOpen ? contextDrawer.close() : contextDrawer.open() } } } }