From 2a068c11e76c7b34afb64d443dbb1afd2e208aaa Mon Sep 17 00:00:00 2001 From: Manuel Nickschas Date: Fri, 4 May 2007 15:08:18 +0000 Subject: [PATCH] Merged changes from branch "sput" r56:61 back into trunk. This contains a lot of new stuff in all parts of quassel. It also contains a serious bug, which for some reason causes quassel to crash every few starts. Merging this only because of public request. So don't complain - I've warned you! --- CMakeLists.txt | 3 +- Doxyfile | 4 +- core/core.cpp | 66 ++- core/core.h | 5 + core/coreproxy.h | 1 + core/server.cpp | 38 +- core/server.h | 2 + gui/CMakeLists.txt | 9 +- gui/buffer.cpp | 220 ++------ gui/buffer.h | 89 +-- gui/bufferwidget.cpp | 259 +++++++++ gui/bufferwidget.h | 113 ++++ gui/channelwidgetinput.cpp | 2 +- gui/chatwidget.cpp | 721 ++++++++++++++++++------ gui/chatwidget.h | 139 +++-- gui/guiproxy.cpp | 1 + gui/guiproxy.h | 1 + gui/mainwin.cpp | 186 ++++-- gui/mainwin.h | 28 +- gui/networkview.cpp | 134 +++-- gui/networkview.h | 38 +- gui/serverlist.cpp | 2 +- gui/settingsdlg.cpp | 46 ++ gui/{settings.h => settingsdlg.h} | 18 +- gui/{settings.cpp => settingspages.cpp} | 23 +- gui/settingspages.h | 68 +++ gui/style.cpp | 28 +- gui/style.h | 12 +- gui/ui/aboutdlg.ui | 124 ++++ gui/ui/buffermgmntsettingspage.ui | 585 +++++++++++++++++++ gui/ui/bufferwidget.ui | 185 +++--- gui/ui/connectionsettingspage.ui | 18 + gui/ui/coresettingspage.ui | 18 + gui/ui/mainwin.ui | 63 ++- gui/ui/networkview.ui | 25 +- gui/ui/settingsdlg.ui | 86 +-- main/CMakeLists.txt | 4 +- main/main_gui.cpp | 5 + main/main_mono.cpp | 13 +- main/message.cpp | 29 +- main/message.h | 32 +- main/proxy_common.h | 2 +- main/settings.cpp | 63 +++ main/settings.h | 47 ++ plugins/plugin.h | 4 +- 45 files changed, 2727 insertions(+), 832 deletions(-) create mode 100644 gui/bufferwidget.cpp create mode 100644 gui/bufferwidget.h create mode 100644 gui/settingsdlg.cpp rename gui/{settings.h => settingsdlg.h} (89%) rename gui/{settings.cpp => settingspages.cpp} (74%) create mode 100644 gui/settingspages.h create mode 100644 gui/ui/aboutdlg.ui create mode 100644 gui/ui/buffermgmntsettingspage.ui create mode 100644 gui/ui/connectionsettingspage.ui create mode 100644 gui/ui/coresettingspage.ui create mode 100644 main/settings.cpp create mode 100644 main/settings.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f800771..92043db9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ IF(NOT BUILD_MONO AND NOT BUILD_CORE AND NOT BUILD_GUI) MESSAGE(FATAL_ERROR "\nYou have not selected which parts of Quassel I should build. Aborting.\nRun 'cmake -DBUILD=', where contains one or more of 'core', 'gui' or 'monolithic', or 'all' to build everything.\n") ENDIF(NOT BUILD_MONO AND NOT BUILD_CORE AND NOT BUILD_GUI) -SET(CMAKE_BUILD_TYPE Debug) +#SET(CMAKE_BUILD_TYPE Debug) # Define files SET(quassel_mono_SRCS main/main_mono.cpp) @@ -47,6 +47,7 @@ FIND_PACKAGE(Qt4 REQUIRED) # Set needed libraries SET(QT_USE_QTXML true) +SET(QT_USE_QTSQL true) SET(QT_USE_QTNETWORK true) SET(QT_DONT_USE_QTGUI true) # This is added later if GUI is requested INCLUDE(${QT_USE_FILE}) diff --git a/Doxyfile b/Doxyfile index 0272fd85..f10fed8e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -263,8 +263,8 @@ UML_LOOK = YES TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES -CALL_GRAPH = NO -CALLER_GRAPH = NO +CALL_GRAPH = YES +CALLER_GRAPH = YES GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png diff --git a/core/core.cpp b/core/core.cpp index b93c96e8..5bed7add 100644 --- a/core/core.cpp +++ b/core/core.cpp @@ -21,8 +21,10 @@ #include "core.h" #include "server.h" #include "global.h" +#include "util.h" #include "coreproxy.h" +#include #include Core::Core() { @@ -41,7 +43,7 @@ Core::Core() { foreach(key, s.childKeys()) { global->updateData(key, s.value(key)); } - initBackLog(); + initBackLogOld(); global->updateData("CoreReady", true); // Now that we are in sync, we can connect signals to automatically store further updates. // I don't think we care if global data changed locally or if it was updated by a client. @@ -61,6 +63,7 @@ Core::~Core() { if(f->isOpen()) f->close(); delete f; } + logDb.close(); } void Core::globalDataUpdated(QString key) { @@ -90,6 +93,7 @@ void Core::connectToIrc(QStringList networks) { connect(server, SIGNAL(nickRemoved(QString, QString)), coreProxy, SLOT(csNickRemoved(QString, QString))); connect(server, SIGNAL(nickUpdated(QString, QString, VarMap)), coreProxy, SLOT(csNickUpdated(QString, QString, VarMap))); connect(server, SIGNAL(ownNickSet(QString, QString)), coreProxy, SLOT(csOwnNickSet(QString, QString))); + connect(server, SIGNAL(queryRequested(QString, QString)), coreProxy, SLOT(csQueryRequested(QString, QString))); // add error handling connect(server, SIGNAL(connected(QString)), coreProxy, SLOT(csServerConnected(QString))); connect(server, SIGNAL(disconnected(QString)), this, SLOT(serverDisconnected(QString))); @@ -112,7 +116,7 @@ void Core::serverDisconnected(QString net) { void Core::recvMessageFromServer(Message msg) { Server *s = qobject_cast(sender()); Q_ASSERT(s); - logMessage(s->getNetwork(), msg); + logMessageOld(s->getNetwork(), msg); emit displayMsg(s->getNetwork(), msg); } @@ -122,8 +126,40 @@ void Core::recvStatusMsgFromServer(QString msg) { emit displayStatusMsg(s->getNetwork(), msg); } -// file name scheme: quassel-backlog-2006-29-10.bin void Core::initBackLog() { + QDir backLogDir = QDir(Global::quasselDir); + if(!backLogDir.exists()) { + qWarning(QString("Creating backlog directory \"%1\"...").arg(backLogDir.absolutePath()).toAscii()); + if(!backLogDir.mkpath(backLogDir.absolutePath())) { + qWarning(QString("Could not create backlog directory! Disabling logging...").toAscii()); + backLogEnabled = false; + return; + } + } + QString backLogFile = Global::quasselDir + "/quassel-backlog.sqlite"; + logDb = QSqlDatabase::addDatabase("QSQLITE", "backlog"); + logDb.setDatabaseName(backLogFile); + bool ok = logDb.open(); + if(!ok) { + qWarning(tr("Could not open backlog database: %1").arg(logDb.lastError().text()).toAscii()); + qWarning(tr("Disabling logging...").toAscii()); + } + // TODO store database version + QSqlQuery query = logDb.exec("CREATE TABLE IF NOT EXISTS backlog (" + "Time INTEGER, User TEXT, Network TEXT, Buffer TEXT, Message BLOB" + ");"); + if(query.lastError().isValid()) { + qWarning(tr("Could not create backlog table: %1").arg(query.lastError().text()).toAscii()); + qWarning(tr("Disabling logging...").toAscii()); + backLogEnabled = false; + return; + } + + backLogEnabled = true; +} + +// file name scheme: quassel-backlog-2006-29-10.bin +void Core::initBackLogOld() { backLogDir = QDir(Global::quasselDir + "/backlog"); if(!backLogDir.exists()) { qWarning(QString("Creating backlog directory \"%1\"...").arg(backLogDir.absolutePath()).toAscii()); @@ -164,7 +200,7 @@ void Core::initBackLog() { qWarning(QString("\"%1\": Version mismatch!").arg(f.fileName()).toAscii()); f.close(); continue; } - qDebug() << "Reading backlog from" << f.fileName(); + //qDebug() << "Reading backlog from" << f.fileName(); logFileDates[net] = QDate::fromString(f.fileName(), QString("'%1/quassel-backlog-'yyyy-MM-dd'.bin'").arg(dir.absolutePath())); if(!logFileDates[net].isValid()) { @@ -181,12 +217,32 @@ void Core::initBackLog() { backLogEnabled = true; } +void Core::logMessage(QString net, Message msg) { + if(!backLogEnabled) return; + QString buf; + if(msg.flags & Message::PrivMsg) { + // query + if(msg.flags & Message::Self) buf = msg.target; + else buf = nickFromMask(msg.sender); + } else { + buf = msg.target; + } + QSqlQuery query = logDb.exec(QString("INSERT INTO backlog Time, User, Network, Buffer, Message " + "VALUES %1, %2, %3, %4, %5;") + .arg(msg.timeStamp.toTime_t()).arg("Default").arg(net).arg(buf).arg(msg.text)); + if(query.lastError().isValid()) { + qWarning(tr("Error while logging to database: %1").arg(query.lastError().text()).toAscii()); + } + +} + + /** Log a core message (emitted via a displayMsg() signal) to the backlog file. * If a file for the current day does not exist, one will be created. Otherwise, messages will be appended. * The file header is the string defined by BACKLOG_STRING, followed by a quint8 specifying the format * version (BACKLOG_FORMAT). The rest is simply serialized Message objects. */ -void Core::logMessage(QString net, Message msg) { +void Core::logMessageOld(QString net, Message msg) { backLog[net].append(msg); if(!logFileDirs.contains(net)) { QDir dir(backLogDir.absolutePath() + "/" + net); diff --git a/core/core.h b/core/core.h index b2a31253..64178258 100644 --- a/core/core.h +++ b/core/core.h @@ -24,6 +24,7 @@ #include #include #include +#include #include "server.h" @@ -66,9 +67,13 @@ class Core : public QObject { QHash logFileDates; QHash logFileDirs; + QSqlDatabase logDb; + //uint getNetIdx(QString net); void initBackLog(); + void initBackLogOld(); void logMessage(QString, Message); + void logMessageOld(QString, Message); }; diff --git a/core/coreproxy.h b/core/coreproxy.h index c8850a35..8aecb18f 100644 --- a/core/coreproxy.h +++ b/core/coreproxy.h @@ -54,6 +54,7 @@ class CoreProxy : public QObject { inline void csNickRenamed(QString net, QString oldn, QString newn) { send(CS_NICK_RENAMED, net, oldn, newn); } inline void csNickUpdated(QString net, QString nick, VarMap props) { send(CS_NICK_UPDATED, net, nick, props); } inline void csOwnNickSet(QString net, QString nick) { send(CS_OWN_NICK_SET, net, nick); } + inline void csQueryRequested(QString net, QString nick) { send(CS_QUERY_REQUESTED, net, nick); } signals: void gsPutGlobalData(QString, QVariant); diff --git a/core/server.cpp b/core/server.cpp index 541816b2..89fb0aa9 100644 --- a/core/server.cpp +++ b/core/server.cpp @@ -75,7 +75,7 @@ void Server::disconnectFromIrc(QString net) { void Server::socketHasData() { while(socket.canReadLine()) { QString s = socket.readLine().trimmed(); - qDebug() << "Read" << s; + //qDebug() << "Read" << s; emit recvRawServerMsg(s); //Message *msg = Message::createFromServerString(this, s); handleServerMsg(s); @@ -132,7 +132,7 @@ void Server::userInput(QString net, QString buf, QString msg) { } void Server::putRawLine(QString s) { - qDebug() << "SentRaw: " << s; +// qDebug() << "SentRaw: " << s; s += "\r\n"; socket.write(s.toAscii()); } @@ -145,7 +145,7 @@ void Server::putCmd(QString cmd, QStringList params, QString prefix) { m += " " + params[i]; } if(!params.isEmpty()) m += " :" + params.last(); - qDebug() << "Sent: " << m; +// qDebug() << "Sent: " << m; m += "\r\n"; socket.write(m.toAscii()); } @@ -354,6 +354,11 @@ void Server::handleUserPart(QString bufname, QString msg) { putCmd("PART", params); } +void Server::handleUserQuery(QString bufname, QString msg) { + QString nick = msg.section(' ', 0, 0); + if(!nick.isEmpty()) emit queryRequested(network, nick); +} + void Server::handleUserQuit(QString bufname, QString msg) { putCmd("QUIT", QStringList(msg)); } @@ -367,7 +372,11 @@ void Server::handleUserSay(QString bufname, QString msg) { QStringList params; params << bufname << msg; putCmd("PRIVMSG", params); - emit displayMsg(Message::plain(params[0], msg, ownNick, Message::Self)); + if(isChannelName(bufname)) { + emit displayMsg(Message::plain(params[0], msg, ownNick, Message::Self)); + } else { + emit displayMsg(Message::plain(params[0], msg, ownNick, Message::Self|Message::PrivMsg)); + } } void Server::handleUserTopic(QString bufname, QString msg) { @@ -532,8 +541,16 @@ void Server::handleServerPing(QString prefix, QStringList params) { void Server::handleServerPrivmsg(QString prefix, QStringList params) { updateNickFromMask(prefix); + Q_ASSERT(params.count() >= 2); if(params.count()<2) emit displayMsg(Message::plain(params[0], "", prefix)); - else emit displayMsg(Message::plain(params[0], params[1], prefix)); + else { + if(params[0] == ownNick) { + emit displayMsg(Message::plain("", params[1], prefix, Message::PrivMsg)); + } else { + Q_ASSERT(isChannelName(params[0])); // should be channel! + emit displayMsg(Message::plain(params[0], params[1], prefix)); + } + } } void Server::handleServerQuit(QString prefix, QStringList params) { @@ -568,14 +585,15 @@ void Server::handleServer001(QString prefix, QStringList params) { // send performlist QStringList performList = networkSettings["Perform"].toString().split( "\n" ); int count = performList.count(); - for ( int a = 0; a < count; a++ ) { - if ( !performList[a].isEmpty() ) { - userInput( network, "", performList[a] ); - } + for(int a = 0; a < count; a++) { + if(!performList[a].isEmpty() ) { + userInput(network, "", performList[a]); + } } } /* RPL_ISUPPORT */ +// TODO Complete 005 handling, also use sensible defaults for non-sent stuff void Server::handleServer005(QString prefix, QStringList params) { foreach(QString p, params) { QString key = p.section("=", 0, 0); @@ -617,7 +635,7 @@ void Server::handleServer332(QString prefix, QStringList params) { /* Topic set by... */ void Server::handleServer333(QString prefix, QStringList params) { emit displayMsg(Message::server(params[0], - tr("Topic set by %1 on %2").arg(params[1]).arg(QDateTime::fromTime_t(params[2].toUInt()).toString()))); + tr("Topic set by %1 on %2").arg(params[1]).arg(QDateTime::fromTime_t(params[2].toUInt()).toString()))); } /* RPL_NAMREPLY */ diff --git a/core/server.h b/core/server.h index ba333d39..90763ac7 100644 --- a/core/server.h +++ b/core/server.h @@ -76,6 +76,7 @@ class Server : public QThread { void topicSet(QString network, QString buffer, QString topic); void setNicks(QString network, QString buffer, QStringList nicks); void ownNickSet(QString network, QString newNick); + void queryRequested(QString network, QString nick); private slots: @@ -101,6 +102,7 @@ class Server : public QThread { void handleUserNick(QString, QString); void handleUserOp(QString, QString); void handleUserPart(QString, QString); + void handleUserQuery(QString, QString); void handleUserQuit(QString, QString); void handleUserQuote(QString, QString); void handleUserSay(QString, QString); diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index a9b27aa3..fc375b70 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -1,11 +1,12 @@ -SET(gui_SRCS chatwidget.cpp channelwidgetinput.cpp mainwin.cpp serverlist.cpp buffer.cpp - identities.cpp coreconnectdlg.cpp guiproxy.cpp networkview.cpp style.cpp settings.cpp) +SET(gui_SRCS chatwidget.cpp channelwidgetinput.cpp mainwin.cpp serverlist.cpp buffer.cpp bufferwidget.cpp + identities.cpp coreconnectdlg.cpp guiproxy.cpp networkview.cpp style.cpp settingsdlg.cpp settingspages.cpp) SET(gui_HDRS style.h) SET(gui_MOCS chatwidget.h channelwidgetinput.h mainwin.h serverlist.h identities.h coreconnectdlg.h - guiproxy.h networkview.h buffer.h settings.h) + guiproxy.h networkview.h buffer.h bufferwidget.h settingsdlg.h settingspages.h) SET(gui_UICS identitiesdlg.ui identitieseditdlg.ui networkeditdlg.ui mainwin.ui nickeditdlg.ui serverlistdlg.ui servereditdlg.ui coreconnectdlg.ui ircwidget.ui - networkview.ui bufferwidget.ui settingsdlg.ui) + networkview.ui bufferwidget.ui settingsdlg.ui + buffermgmntsettingspage.ui connectionsettingspage.ui) # This prepends ui/ to the UIC names, so we don't have to do this ourselves. FOREACH(ui ${gui_UICS}) diff --git a/gui/buffer.cpp b/gui/buffer.cpp index 751da803..7c92ac66 100644 --- a/gui/buffer.cpp +++ b/gui/buffer.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -21,224 +21,100 @@ #include "buffer.h" #include "util.h" #include "chatwidget.h" +#include "bufferwidget.h" Buffer::Buffer(QString netname, QString bufname) { - networkName = netname; - bufferName = bufname; + _networkName = netname; + _bufferName = bufname; + + if(bufname.isEmpty()) type = ServerBuffer; + else if(isChannelName(bufname)) type = ChannelBuffer; + else type = QueryBuffer; - widget = 0; - chatWidget = 0; - contentsWidget = 0; active = false; +/* + QSettings s; + s.beginGroup(QString("GUI/BufferStates/%1/%2").arg(netname).arg(bufname)); + state->splitterState = s.value("Splitter").toByteArray(); + s.endGroup(); + */ + emit bufferUpdated(this); } Buffer::~Buffer() { - delete widget; - delete chatWidget; + //delete widget; + /* + QSettings s; + s.beginGroup(QString("GUI/BufferStates/%1/%2").arg(networkName).arg(bufferName)); + s.setValue("Splitter", state->splitterState); + s.endGroup(); +*/ + //delete state; + emit bufferDestroyed(this); } -void Buffer::setActive(bool a) { - if(a != active) { - active = a; - if(widget) widget->setActive(a); - } -} +void Buffer::init() { -void Buffer::displayMsg(Message msg) { - contents.append(msg); - if(widget) widget->displayMsg(msg); -} -void Buffer::userInput(QString msg) { - emit userInput(networkName, bufferName, msg); } -/* FIXME do we need this? */ -void Buffer::scrollToEnd() { - if(!widget) return; - //widget->scrollToEnd(); -} - -QWidget * Buffer::showWidget(QWidget *parent) { - - if(widget) { - return qobject_cast(widget); - } - - if(!contentsWidget) { - contentsWidget = new ChatWidgetContents(networkName, bufferName, 0); - contentsWidget->hide(); - /* FIXME do we need this? */ - for(int i = 0; i < contents.count(); i++) { - contentsWidget->appendMsg(contents[i]); - } +void Buffer::setActive(bool a) { + if(a != active) { + active = a; + emit bufferUpdated(this); } - contentsWidget->hide(); - widget = new BufferWidget(networkName, bufferName, isActive(), ownNick, contentsWidget, this, parent); - widget->setTopic(topic); - widget->updateNickList(nicks); - connect(widget, SIGNAL(userInput(QString)), this, SLOT(userInput(QString))); - return qobject_cast(widget); } -void Buffer::hideWidget() { - delete widget; - widget = 0; +void Buffer::displayMsg(Message msg) { + contents()->append(msg); + emit msgDisplayed(msg); } -void Buffer::deleteWidget() { - widget = 0; +void Buffer::prependMessages(QList msgs) { + _contents = msgs + _contents; } -QWidget * Buffer::getWidget() { - return qobject_cast(widget); +void Buffer::processUserInput(QString msg) { + // TODO User Input processing (plugins) -> well, this goes through MainWin into Core for processing... so... + emit userInput(networkName(), bufferName(), msg); } void Buffer::setTopic(QString t) { - topic = t; - if(widget) widget->setTopic(t); + _topic = t; + emit topicSet(t); + emit bufferUpdated(this); } void Buffer::addNick(QString nick, VarMap props) { - if(nick == ownNick) setActive(true); + if(nick == ownNick()) setActive(true); nicks[nick] = props; - if(widget) widget->updateNickList(nicks); + emit nickListChanged(nicks); } void Buffer::updateNick(QString nick, VarMap props) { nicks[nick] = props; - if(widget) widget->updateNickList(nicks); + emit nickListChanged(nicks); } void Buffer::renameNick(QString oldnick, QString newnick) { QVariant v = nicks.take(oldnick); nicks[newnick] = v; - if(widget) widget->updateNickList(nicks); + emit nickListChanged(nicks); } void Buffer::removeNick(QString nick) { - if(nick == ownNick) setActive(false); + if(nick == ownNick()) setActive(false); nicks.remove(nick); - if(widget) widget->updateNickList(nicks); + emit nickListChanged(nicks); } void Buffer::setOwnNick(QString nick) { - ownNick = nick; - if(widget) widget->setOwnNick(nick); + _ownNick = nick; + emit ownNickSet(nick); } /****************************************************************************************/ - /****************************************************************************************/ -BufferWidget::BufferWidget(QString netname, QString bufname, bool act, QString own, ChatWidgetContents *contents, Buffer *pBuf, QWidget *parent) : QWidget(parent) { - ui.setupUi(this); - networkName = netname; - bufferName = bufname; - active = act; - parentBuffer = pBuf; - - ui.chatWidget->init(netname, bufname, contents); - - ui.ownNick->clear(); - ui.ownNick->addItem(own); - if(bufname.isEmpty()) { - // Server Buffer - ui.nickTree->hide(); - ui.topicEdit->hide(); - ui.chanSettingsButton->hide(); - } - connect(ui.nickTree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); - connect(ui.nickTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); - connect(ui.inputEdit, SIGNAL(returnPressed()), this, SLOT(enterPressed())); - - opsExpanded = voicedExpanded = usersExpanded = true; - - ui.chatWidget->setFocusProxy(ui.inputEdit); - updateTitle(); -} - -BufferWidget::~BufferWidget() { - ui.chatWidget->takeWidget(); /* remove ownership so the chatwidget contents does not get destroyed */ - parentBuffer->deleteWidget(); /* make sure the parent buffer knows we are gone */ -} - -void BufferWidget::updateTitle() { - QString title = QString("%1 in %2 [%3]: %4").arg(ui.ownNick->currentText()).arg(bufferName).arg(networkName).arg(ui.topicEdit->text()); - setWindowTitle(title); -} - -void BufferWidget::enterPressed() { - QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts); - foreach(QString msg, lines) { - if(msg.isEmpty()) continue; - emit userInput(msg); - } - ui.inputEdit->clear(); -} - -void BufferWidget::setActive(bool act) { - if(act != active) { - active = act; - //renderContents(); - //scrollToEnd(); - } -} - -void BufferWidget::displayMsg(Message msg) { - ui.chatWidget->appendMsg(msg); -} - -void BufferWidget::setOwnNick(QString nick) { - ui.ownNick->clear(); - ui.ownNick->addItem(nick); - updateTitle(); -} - -void BufferWidget::setTopic(QString topic) { - ui.topicEdit->setText(topic); - updateTitle(); -} - -void BufferWidget::updateNickList(VarMap nicks) { - ui.nickTree->clear(); - if(nicks.count() != 1) ui.nickTree->setHeaderLabel(tr("%1 Users").arg(nicks.count())); - else ui.nickTree->setHeaderLabel(tr("1 User")); - QTreeWidgetItem *ops = new QTreeWidgetItem(); - QTreeWidgetItem *voiced = new QTreeWidgetItem(); - QTreeWidgetItem *users = new QTreeWidgetItem(); - // To sort case-insensitive, we have to put all nicks in a map which is sorted by (lowercase) key... - QMap sorted; - foreach(QString n, nicks.keys()) { sorted[n.toLower()] = n; } - foreach(QString n, sorted.keys()) { - QString nick = sorted[n]; - QString mode = nicks[nick].toMap()["Channels"].toMap()[bufferName].toMap()["Mode"].toString(); - if(mode.contains('o')) { new QTreeWidgetItem(ops, QStringList(QString("@%1").arg(nick))); } - else if(mode.contains('v')) { new QTreeWidgetItem(voiced, QStringList(QString("+%1").arg(nick))); } - else new QTreeWidgetItem(users, QStringList(nick)); - } - if(ops->childCount()) { - ops->setText(0, tr("%1 Operators").arg(ops->childCount())); - ui.nickTree->addTopLevelItem(ops); - ops->setExpanded(opsExpanded); - } else delete ops; - if(voiced->childCount()) { - voiced->setText(0, tr("%1 Voiced").arg(voiced->childCount())); - ui.nickTree->addTopLevelItem(voiced); - voiced->setExpanded(voicedExpanded); - } else delete voiced; - if(users->childCount()) { - users->setText(0, tr("%1 Users").arg(users->childCount())); - ui.nickTree->addTopLevelItem(users); - users->setExpanded(usersExpanded); - } else delete users; -} - -void BufferWidget::itemExpansionChanged(QTreeWidgetItem *item) { - if(item->child(0)->text(0).startsWith('@')) opsExpanded = item->isExpanded(); - else if(item->child(0)->text(0).startsWith('+')) voicedExpanded = item->isExpanded(); - else usersExpanded = item->isExpanded(); -} - diff --git a/gui/buffer.h b/gui/buffer.h index 4ef1112f..09f7e029 100644 --- a/gui/buffer.h +++ b/gui/buffer.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -24,14 +24,13 @@ #include #include -#include "ui_bufferwidget.h" - #include "global.h" #include "message.h" class ChatWidget; class ChatWidgetContents; class BufferWidget; +struct BufferState; //!\brief Encapsulates the contents of a single channel, query or server status context. /** A Buffer maintains a list of existing nicks and their status. New messages can be appended using @@ -44,18 +43,32 @@ class Buffer : public QObject { public: Buffer(QString network, QString buffer); ~Buffer(); + static void init(); + enum Type { ServerBuffer, ChannelBuffer, QueryBuffer }; + Type bufferType() { return type; } bool isActive() { return active; } - public: - QWidget *getWidget(); + + QString networkName() { return _networkName; } + QString bufferName() { return _bufferName; } + QList *contents() { return &_contents; } + VarMap nickList() { return nicks; } + QString topic() { return _topic; } + QString ownNick() { return _ownNick; } signals: void userInput(QString, QString, QString); - void nickListChanged(QStringList); + void msgDisplayed(Message); + void nickListChanged(VarMap nicks); + void topicSet(QString topic); + void ownNickSet(QString ownNick); + void bufferUpdated(Buffer *); + void bufferDestroyed(Buffer *); public slots: void setActive(bool active = true); void displayMsg(Message); + void prependMessages(QList); // for backlog //void recvStatusMsg(QString msg); void setTopic(QString); //void setNicks(QStringList); @@ -65,67 +78,19 @@ class Buffer : public QObject { void updateNick(QString nick, VarMap props); void setOwnNick(QString nick); - QWidget * showWidget(QWidget *parent = 0); - void hideWidget(); - void deleteWidget(); - - void scrollToEnd(); - - private slots: - void userInput(QString); + void processUserInput(QString); private: bool active; - BufferWidget *widget; - ChatWidget *chatWidget; - ChatWidgetContents *contentsWidget; - VarMap nicks; - QString topic; - QString ownNick; - QString networkName, bufferName; - - QList contents; -}; - -//!\brief Displays the contents of a Buffer. -/** A BufferWidget usually includes a topic line, a nicklist, the chat itself, and an input line. - * For server buffers or queries, there is of course no nicklist. - * The contents of the chat is rendered by a ChatWidget. - */ -class BufferWidget : public QWidget { - Q_OBJECT - - public: - BufferWidget(QString netname, QString bufname, bool active, QString ownNick, ChatWidgetContents *contents, Buffer *parentBuffer, QWidget *parent = 0); - ~BufferWidget(); + Type type; - void setActive(bool act = true); - - signals: - void userInput(QString); - - protected: - - public slots: - void displayMsg(Message); - void updateNickList(VarMap nicks); - void setOwnNick(QString ownNick); - void setTopic(QString topic); - - private slots: - void enterPressed(); - void itemExpansionChanged(QTreeWidgetItem *); - void updateTitle(); - - private: - Ui::BufferWidget ui; - Buffer *parentBuffer; - bool active; - - QString networkName; - QString bufferName; + VarMap nicks; + QString _topic; + QString _ownNick; + QString _networkName, _bufferName; + BufferState *state; - bool opsExpanded, voicedExpanded, usersExpanded; + QList _contents; }; diff --git a/gui/bufferwidget.cpp b/gui/bufferwidget.cpp new file mode 100644 index 00000000..98f8dd83 --- /dev/null +++ b/gui/bufferwidget.cpp @@ -0,0 +1,259 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "bufferwidget.h" +#include "buffer.h" +#include "chatwidget.h" +#include "settings.h" +#include "mainwin.h" + +BufferWidget::BufferWidget(QWidget *parent) : QWidget(parent) { + ui.setupUi(this); + + //layoutThread->start(); + //connect(this, SIGNAL(aboutToClose()), layoutThread, SLOT(quit())); + //connect(this, SIGNAL(layoutMessages(LayoutTask)), layoutThread, SLOT(processTask(LayoutTask)), Qt::QueuedConnection); + //layoutThread->start(); + + curBuf = 0; + //setBaseSize(QSize(600,400)); + //setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + connect(ui.inputEdit, SIGNAL(returnPressed()), this, SLOT(enterPressed())); + +} + +void BufferWidget::init() { + //layoutThread = new LayoutThread(); + layoutThread = ::layoutThread; + connect(layoutThread, SIGNAL(taskProcessed(LayoutTask)), this, SLOT(messagesLayouted(LayoutTask))); + //layoutThread->start(); + //while(!layoutThread->isRunning()) {}; +} + +BufferWidget::~BufferWidget() { + //emit aboutToClose(); + //layoutThread->wait(10000); + delete layoutThread; + foreach(BufferState *s, states.values()) { + delete s; + } +} + +void BufferWidget::setBuffer(Buffer *buf) { + BufferState *state; + curBuf = buf; + if(states.contains(buf)) { + state = states[buf]; + } else { + BufferState *s = new BufferState; + s->currentLine = Settings::guiValue(QString("BufferStates/%1/%2/currentLine").arg(buf->networkName()).arg(buf->bufferName()), -1).toInt(); + if(buf->bufferType() == Buffer::ChannelBuffer) { + s->splitterState = Settings::guiValue(QString("BufferStates/%1/%2/splitter").arg(buf->networkName()).arg(buf->bufferName())).toByteArray(); + s->splitter = new QSplitter(this); + s->chatWidget = new ChatWidget(s->splitter); + s->nickTree = new QTreeWidget(s->splitter); + s->nickTree->headerItem()->setHidden(true); + s->nickTree->setRootIsDecorated(false); + s->page = s->splitter; + updateNickList(s, buf->nickList()); + s->splitter->restoreState(s->splitterState); + connect(buf, SIGNAL(nickListChanged(VarMap)), this, SLOT(updateNickList(VarMap))); + connect(s->nickTree, SIGNAL(itemExpanded(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); + connect(s->nickTree, SIGNAL(itemCollapsed(QTreeWidgetItem *)), this, SLOT(itemExpansionChanged(QTreeWidgetItem*))); + } else { + s->splitter = 0; s->nickTree = 0; + s->chatWidget = new ChatWidget(this); + s->page = s->chatWidget; + } + s->opsExpanded = Settings::guiValue(QString("BufferStates/%1/%2/opsExpanded").arg(buf->networkName()).arg(buf->bufferName()), true).toBool(); + s->voicedExpanded = Settings::guiValue(QString("BufferStates/%1/%2/voicedExpanded").arg(buf->networkName()).arg(buf->bufferName()), true).toBool(); + s->usersExpanded = Settings::guiValue(QString("BufferStates/%1/%2/usersExpanded").arg(buf->networkName()).arg(buf->bufferName()), true).toBool(); + states[buf] = s; + state = s; + state->chatWidget->init(networkName, bufferName); + // FIXME: layout and cache all incoming messages... maybe do this in buffer? + QList *l = buf->contents(); + state->chatWidget->appendMsgList(l); + if(chatLineCache.contains(buf)) { + state->chatWidget->prependChatLines(chatLineCache[buf]); + buf->prependMessages(msgCache[buf]); + } + connect(buf, SIGNAL(msgDisplayed(Message)), state->chatWidget, SLOT(appendMsg(Message))); + connect(buf, SIGNAL(topicSet(QString)), this, SLOT(setTopic(QString))); + connect(buf, SIGNAL(ownNickSet(QString)), this, SLOT(setOwnNick(QString))); + ui.stackedWidget->addWidget(s->page); + } + ui.stackedWidget->setCurrentWidget(state->page); + ui.topicEdit->setText(buf->topic()); + chatWidget = state->chatWidget; + nickTree = state->nickTree; + splitter = state->splitter; + //ui.ownNick->set + disconnect(this, SIGNAL(userInput(QString)), 0, 0); + connect(this, SIGNAL(userInput(QString)), buf, SLOT(processUserInput(QString))); + state->chatWidget->setFocusProxy(ui.inputEdit); + ui.inputEdit->setFocus(); + ui.topicEdit->setText(state->topic); + ui.ownNick->clear(); + ui.ownNick->addItem(state->ownNick); + updateTitle(); +} + +void BufferWidget::prependMessages(Buffer *buf, QList messages) { + LayoutTask task; + task.messages = messages; + task.buffer = buf; + task.net = buf->networkName(); + task.buf = buf->bufferName(); + //emit layoutMessages(task); + layoutThread->processTask(task); +} + +void BufferWidget::messagesLayouted(LayoutTask task) { + if(states.contains(task.buffer)) { + states[task.buffer]->chatWidget->prependChatLines(task.lines); + task.buffer->prependMessages(task.messages); + } else { + msgCache[task.buffer] = task.messages + msgCache[task.buffer]; + chatLineCache[task.buffer] = task.lines + chatLineCache[task.buffer]; + } +} + +void BufferWidget::saveState() { + foreach(Buffer *buf, states.keys()) { + BufferState *s = states[buf]; + if(s->splitter) Settings::setGuiValue(QString("BufferStates/%1/%2/splitter").arg(buf->networkName()).arg(buf->bufferName()), s->splitter->saveState()); + Settings::setGuiValue(QString("BufferStates/%1/%2/currentLine").arg(buf->networkName()).arg(buf->bufferName()), s->currentLine); + Settings::setGuiValue(QString("BufferStates/%1/%2/opsExpanded").arg(buf->networkName()).arg(buf->bufferName()), s->opsExpanded); + Settings::setGuiValue(QString("BufferStates/%1/%2/voicedExpanded").arg(buf->networkName()).arg(buf->bufferName()), s->voicedExpanded); + Settings::setGuiValue(QString("BufferStates/%1/%2/usersExpanded").arg(buf->networkName()).arg(buf->bufferName()), s->usersExpanded); + } +} + +QSize BufferWidget::sizeHint() const { + return QSize(800,400); +} + +void BufferWidget::updateTitle() { + QString title = QString("%1 in %2 [%3]: %4").arg(ui.ownNick->currentText()).arg(bufferName).arg(networkName).arg(ui.topicEdit->text()); + setWindowTitle(title); +} + +void BufferWidget::enterPressed() { + QStringList lines = ui.inputEdit->text().split('\n', QString::SkipEmptyParts); + foreach(QString msg, lines) { + if(msg.isEmpty()) continue; + emit userInput(msg); + } + ui.inputEdit->clear(); +} + +void BufferWidget::setActive(bool act) { + if(act != active) { + active = act; + //renderContents(); + //scrollToEnd(); + } +} + +void BufferWidget::resizeEvent ( QResizeEvent * event ) { + //qDebug() << "resizing:" << bufferName << event->size(); + QWidget::resizeEvent(event); + +} + +/* +void BufferWidget::displayMsg(Message msg) { + chatWidget->appendMsg(msg); +} +*/ + +void BufferWidget::setOwnNick(QString nick) { + Buffer *buf = qobject_cast(sender()); + Q_ASSERT(buf); + states[buf]->ownNick = nick; + if(buf == curBuf) { + ui.ownNick->clear(); + ui.ownNick->addItem(nick); + updateTitle(); + } +} + +void BufferWidget::setTopic(QString topic) { + Buffer *buf = qobject_cast(sender()); + Q_ASSERT(buf); + states[buf]->topic = topic; + if(buf == curBuf) { + ui.topicEdit->setText(topic); + updateTitle(); + } +} + + +void BufferWidget::updateNickList(VarMap nicks) { + Buffer *buf = qobject_cast(sender()); + Q_ASSERT(buf); + updateNickList(states[buf], nicks); +} + +// TODO Use 005 +void BufferWidget::updateNickList(BufferState *state, VarMap nicks) { + QTreeWidget *tree = state->nickTree; + if(!tree) return; + tree->clear(); + if(nicks.count() != 1) tree->setHeaderLabel(tr("%1 Users").arg(nicks.count())); + else tree->setHeaderLabel(tr("1 User")); + QTreeWidgetItem *ops = new QTreeWidgetItem(); + QTreeWidgetItem *voiced = new QTreeWidgetItem(); + QTreeWidgetItem *users = new QTreeWidgetItem(); + // To sort case-insensitive, we have to put all nicks in a map which is sorted by (lowercase) key... + QMap sorted; + foreach(QString n, nicks.keys()) { sorted[n.toLower()] = n; } + foreach(QString n, sorted.keys()) { + QString nick = sorted[n]; + QString mode = nicks[nick].toMap()["Channels"].toMap()[bufferName].toMap()["Mode"].toString(); + if(mode.contains('o')) { new QTreeWidgetItem(ops, QStringList(QString("@%1").arg(nick))); } + else if(mode.contains('v')) { new QTreeWidgetItem(voiced, QStringList(QString("+%1").arg(nick))); } + else new QTreeWidgetItem(users, QStringList(nick)); + } + if(ops->childCount()) { + ops->setText(0, tr("%1 Operators").arg(ops->childCount())); + tree->addTopLevelItem(ops); + ops->setExpanded(state->opsExpanded); + } else delete ops; + if(voiced->childCount()) { + voiced->setText(0, tr("%1 Voiced").arg(voiced->childCount())); + tree->addTopLevelItem(voiced); + voiced->setExpanded(state->voicedExpanded); + } else delete voiced; + if(users->childCount()) { + users->setText(0, tr("%1 Users").arg(users->childCount())); + tree->addTopLevelItem(users); + users->setExpanded(state->usersExpanded); + } else delete users; +} + +// TODO Use 005 and additional user modes +void BufferWidget::itemExpansionChanged(QTreeWidgetItem *item) { + if(item->child(0)->text(0).startsWith('@')) states[curBuf]->opsExpanded = item->isExpanded(); + else if(item->child(0)->text(0).startsWith('+')) states[curBuf]->voicedExpanded = item->isExpanded(); + else states[curBuf]->usersExpanded = item->isExpanded(); +} + diff --git a/gui/bufferwidget.h b/gui/bufferwidget.h new file mode 100644 index 00000000..e72ebb8a --- /dev/null +++ b/gui/bufferwidget.h @@ -0,0 +1,113 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _BUFFERWIDGET_H_ +#define _BUFFERWIDGET_H_ + +#include "gui/ui_bufferwidget.h" + +#include "global.h" +#include "message.h" +#include "chatwidget.h" + +class Buffer; +struct BufferState; +class ChatWidget; +class LayoutThread; + +//!\brief Displays the contents of a Buffer. +/** A BufferWidget usually includes a topic line, a nicklist, the chat itself, and an input line. + * For server buffers or queries, there is of course no nicklist. + * The contents of the chat is rendered by a ChatWidget. + */ +class BufferWidget : public QWidget { + Q_OBJECT + + public: + BufferWidget(QWidget *parent = 0); + ~BufferWidget(); + void init(); + + QSize sizeHint() const; + + signals: + void userInput(QString msg); + void aboutToClose(); + + void layoutMessages(LayoutTask); + + protected: + + public slots: + void setBuffer(Buffer *); + void saveState(); + void prependMessages(Buffer *, QList); // for backlog processing + + protected: + void resizeEvent ( QResizeEvent * event ); + + private slots: + void enterPressed(); + void itemExpansionChanged(QTreeWidgetItem *); + void updateTitle(); + + //void displayMsg(Message); + void updateNickList(BufferState *state, VarMap nicks); + void updateNickList(VarMap nicks); + void setOwnNick(QString ownNick); + void setTopic(QString topic); + void setActive(bool act = true); + + void messagesLayouted(LayoutTask); + + + private: + Ui::BufferWidget ui; + Buffer *curBuf; + QHash states; + bool active; + + ChatWidget *chatWidget; + QSplitter *splitter; + QTreeWidget *nickTree; + + QString networkName; + QString bufferName; + + LayoutThread *layoutThread; + QHash > chatLineCache; + QHash > msgCache; +}; + +struct BufferState { + ChatWidget *chatWidget; + QTreeWidget *nickTree; + QSplitter *splitter; + QWidget *page; + Buffer *buffer; + QByteArray splitterState; + QString topic, ownNick; + QString inputLine; + int currentLine; + int lineOffset; + bool opsExpanded, voicedExpanded, usersExpanded; +}; + +#endif diff --git a/gui/channelwidgetinput.cpp b/gui/channelwidgetinput.cpp index c029bdd7..893623d2 100644 --- a/gui/channelwidgetinput.cpp +++ b/gui/channelwidgetinput.cpp @@ -36,7 +36,7 @@ void ChannelWidgetInput::keyPressEvent(QKeyEvent * event) { event->accept(); } else if(event->key() == Qt::Key_Tab) { // Tabcomplete - if(cursorPosition() == text().length()) { + if(cursorPosition() == text().length()) { } diff --git a/gui/chatwidget.cpp b/gui/chatwidget.cpp index 03a9d1f6..331fdb0b 100644 --- a/gui/chatwidget.cpp +++ b/gui/chatwidget.cpp @@ -24,159 +24,229 @@ #include #include -ChatWidget::ChatWidget(QWidget *parent) : QScrollArea(parent) { +ChatWidget::ChatWidget(QWidget *parent) : QAbstractScrollArea(parent) { + scrollTimer = new QTimer(this); + scrollTimer->setSingleShot(false); + scrollTimer->setInterval(100); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setMinimumSize(QSize(400,400)); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - setAlignment(Qt::AlignLeft | Qt::AlignTop); - + bottomLine = -1; + height = 0; + ycoords.append(0); + pointerPosition = QPoint(0,0); + connect(verticalScrollBar(), SIGNAL(actionTriggered(int)), this, SLOT(scrollBarAction(int))); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrollBarValChanged(int))); } -void ChatWidget::init(QString netname, QString bufname, ChatWidgetContents *contentsWidget) { +void ChatWidget::init(QString netname, QString bufname) { networkName = netname; bufferName = bufname; - //setAlignment(Qt::AlignBottom); - contents = contentsWidget; - setWidget(contents); - //setWidgetResizable(true); - //contents->setWidth(contents->sizeHint().width()); - contents->setFocusProxy(this); - //contents->show(); - //setAlignment(Qt::AlignBottom); + setBackgroundRole(QPalette::Base); + setFont(QFont("Fixed")); + tsWidth = 90; + senderWidth = 100; + computePositions(); + adjustScrollBar(); + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + //verticalScrollBar()->setPageStep(viewport()->height()); + //verticalScrollBar()->setSingleStep(20); + //verticalScrollBar()->setMinimum(0); + //verticalScrollBar()->setMaximum((int)height - verticalScrollBar()->pageStep()); + + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setMouseTracking(true); + mouseMode = Normal; + selectionMode = NoSelection; + connect(scrollTimer, SIGNAL(timeout()), this, SLOT(handleScrollTimer())); } ChatWidget::~ChatWidget() { - + //qDebug() << "destroying chatwidget" << bufferName; + foreach(ChatLine *l, lines) { + delete l; + } } -void ChatWidget::clear() { - //contents->clear(); +QSize ChatWidget::sizeHint() const { + //qDebug() << size(); + return size(); } -void ChatWidget::appendMsg(Message msg) { - contents->appendMsg(msg); - //qDebug() << "appending" << msg.text; - +void ChatWidget::adjustScrollBar() { + verticalScrollBar()->setPageStep(viewport()->height()); + verticalScrollBar()->setSingleStep(20); + verticalScrollBar()->setMinimum(0); + verticalScrollBar()->setMaximum((int)height - verticalScrollBar()->pageStep()); + //qDebug() << height << viewport()->height() << verticalScrollBar()->pageStep(); + //if(bottomLine < 0) { + // verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + //} else { + //int bot = verticalScrollBar()->value() + viewport()->height(); //qDebug() << bottomLine; + //verticalScrollBar()->setValue(qMax(0, (int)ycoords[bottomLine+1] - viewport()->height())); + //} } -void ChatWidget::resizeEvent(QResizeEvent *event) { - //qDebug() << bufferName << isVisible() << event->size(); - contents->setWidth(event->size().width()); - //setAlignment(Qt::AlignBottom); - QScrollArea::resizeEvent(event); +void ChatWidget::scrollBarValChanged(int val) { + return; + if(val >= verticalScrollBar()->maximum()) bottomLine = -1; + else { + int bot = val + viewport()->height(); + int line = yToLineIdx(bot); + //bottomLine = line; + } } -/*************************************************************************************/ - -ChatWidgetContents::ChatWidgetContents(QString net, QString buf, QWidget *parent) : QWidget(parent) { - networkName = net; - bufferName = buf; - layoutTimer = new QTimer(this); - layoutTimer->setSingleShot(true); - connect(layoutTimer, SIGNAL(timeout()), this, SLOT(triggerLayout())); +void ChatWidget::scrollBarAction(int action) { + switch(action) { + case QScrollBar::SliderSingleStepAdd: + // More elaborate. But what with loooong lines? + // verticalScrollBar()->setValue((int)ycoords[yToLineIdx(verticalScrollBar()->value() + viewport()->height()) + 1] - viewport()->height()); + break; + case QScrollBar::SliderSingleStepSub: + //verticalScrollBar()->setValue((int)ycoords[yToLineIdx(verticalScrollBar()->value())]); + break; - setBackgroundRole(QPalette::Base); - setFont(QFont("Fixed")); + } - ycoords.append(0); - tsWidth = 90; - senderWidth = 100; - //textWidth = 400; - computePositions(); - //setFixedWidth((int)(tsWidth + senderWidth + textWidth + 20)); - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - setMouseTracking(true); - mouseMode = Normal; - selectionMode = NoSelection; +} - doLayout = false; +void ChatWidget::handleScrollTimer() { + if(mouseMode == MarkText || mouseMode == MarkLines) { + if(pointerPosition.y() > viewport()->height()) { + verticalScrollBar()->setValue(verticalScrollBar()->value() + pointerPosition.y() - viewport()->height()); + handleMouseMoveEvent(QPoint(pointerPosition.x(), viewport()->height())); + } else if(pointerPosition.y() < 0) { + verticalScrollBar()->setValue(verticalScrollBar()->value() + pointerPosition.y()); + handleMouseMoveEvent(QPoint(pointerPosition.x(), 0)); + } + } } -ChatWidgetContents::~ChatWidgetContents() { - delete layoutTimer; - foreach(ChatLine *l, lines) { - delete l; +void ChatWidget::ensureVisible(int line) { + int top = verticalScrollBar()->value(); + int bot = top + viewport()->height(); + if(ycoords[line+1] > bot) { + verticalScrollBar()->setValue(qMax(0, (int)ycoords[line+1] - viewport()->height())); + } else if(ycoords[line] < top) { + verticalScrollBar()->setValue((int)ycoords[line]); } + } -QSize ChatWidgetContents::sizeHint() const { - //qDebug() << size(); - return size(); +void ChatWidget::clear() { + //contents->clear(); } -void ChatWidgetContents::paintEvent(QPaintEvent *event) { - QPainter painter(this); - qreal top = event->rect().top(); - qreal bot = top + event->rect().height(); - int idx = yToLineIdx(top); - if(idx < 0) return; - for(int i = idx; i < lines.count() ; i++) { - lines[i]->draw(&painter, QPointF(0, ycoords[i])); - if(ycoords[i+1] > bot) return; +void ChatWidget::prependChatLines(QList clist) { + QList tmpy; tmpy.append(0); + qreal h = 0; + foreach(ChatLine *l, clist) { + h += l->layout(tsWidth, senderWidth, textWidth); + tmpy.append(h); } + ycoords.removeFirst(); + for(int i = 0; i < ycoords.count(); i++) ycoords[i] += h; + ycoords = tmpy + ycoords; + lines = clist + lines; + height += h; + /* Fix all variables containing line numbers */ + int i = clist.count(); + dragStartLine += i; + curLine += i; + selectionStart += i; selectionEnd += i; selectionEnd += i; + //if(bottomLine >= 0) bottomLine += i; + adjustScrollBar(); + //verticalScrollBar()->setPageStep(viewport()->height()); + //verticalScrollBar()->setSingleStep(20); + //verticalScrollBar()->setMaximum((int)height - verticalScrollBar()->pageStep()); + verticalScrollBar()->setValue(verticalScrollBar()->value() + (int)h); + viewport()->update(); } -void ChatWidgetContents::appendMsg(Message msg) { +void ChatWidget::appendMsg(Message msg) { ChatLine *line = new ChatLine(msg, networkName, bufferName); qreal h = line->layout(tsWidth, senderWidth, textWidth); ycoords.append(h + ycoords[ycoords.count() - 1]); - setFixedHeight((int)ycoords[ycoords.count()-1]); + height += h; + bool flg = (verticalScrollBar()->value() == verticalScrollBar()->maximum()); + adjustScrollBar(); + if(flg) verticalScrollBar()->setValue(verticalScrollBar()->maximum()); lines.append(line); - update(); - return; - + viewport()->update(); } -void ChatWidgetContents::clear() { - - +void ChatWidget::appendMsgList(QList *list) { + foreach(Message msg, *list) { + ChatLine *line = new ChatLine(msg, networkName, bufferName); + qreal h = line->layout(tsWidth, senderWidth, textWidth); + ycoords.append(h + ycoords[ycoords.count() - 1]); + height += h; + lines.append(line); + } + bool flg = (verticalScrollBar()->value() == verticalScrollBar()->maximum()); + adjustScrollBar(); + if(flg) verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + viewport()->update(); } //!\brief Computes the different x position vars for given tsWidth and senderWidth. -void ChatWidgetContents::computePositions() { +void ChatWidget::computePositions() { senderX = tsWidth + Style::sepTsSender(); textX = senderX + senderWidth + Style::sepSenderText(); tsGrabPos = tsWidth + (int)Style::sepTsSender()/2; senderGrabPos = senderX + senderWidth + (int)Style::sepSenderText()/2; - textWidth = size().width() - textX; + textWidth = viewport()->size().width() - textX; } -void ChatWidgetContents::setWidth(qreal w) { - textWidth = (int)w - (Style::sepTsSender() + Style::sepSenderText()) - tsWidth - senderWidth; - setFixedWidth((int)w); - layout(); +void ChatWidget::resizeEvent(QResizeEvent *event) { + //qDebug() << bufferName << isVisible() << event->size() << event->oldSize(); + /*if(event->oldSize().isValid())*/ + //contents->setWidth(event->size().width()); + //setAlignment(Qt::AlignBottom); + if(event->size().width() != event->oldSize().width()) { + computePositions(); + layout(); + } + //adjustScrollBar(); + //qDebug() << viewport()->size() << viewport()->height(); + //QAbstractScrollArea::resizeEvent(event); + //qDebug() << viewport()->size() << viewport()->geometry(); } -//!\brief Trigger layout (used by layoutTimer only). -/** This method is triggered by the layoutTimer. Layout the widget if it has been postponed earlier. - */ -void ChatWidgetContents::triggerLayout() { - layout(true); +void ChatWidget::paintEvent(QPaintEvent *event) { + QPainter painter(viewport()); + + //qDebug() << verticalScrollBar()->value(); + painter.translate(0, -verticalScrollBar()->value()); + int top = event->rect().top() + verticalScrollBar()->value(); + int bot = top + event->rect().height(); + int idx = yToLineIdx(top); + if(idx < 0) return; + for(int i = idx; i < lines.count() ; i++) { + lines[i]->draw(&painter, QPointF(0, ycoords[i])); + if(ycoords[i+1] > bot) return; + } } //!\brief Layout the widget. -/** The contents of the widget is re-layouted completely. Since this could take a while if the widget - * is huge, we don't want to trigger the layout procedure too often (causing layout calls to pile up). - * We use a timer that ensures layouting is only done if the last one has finished. - */ -void ChatWidgetContents::layout(bool timer) { - if(layoutTimer->isActive()) { - // Layouting too fast. We set a flag though, so that a final layout is done when the timer runs out. - doLayout = true; - return; - } - if(timer && !doLayout) return; // Only check doLayout if we have been triggered by the timer! +void ChatWidget::layout() { + // TODO fix scrollbars + //int botLine = yToLineIdx(verticalScrollBar()->value() + qreal y = 0; for(int i = 0; i < lines.count(); i++) { qreal h = lines[i]->layout(tsWidth, senderWidth, textWidth); ycoords[i+1] = h + ycoords[i]; } - setFixedHeight((int)ycoords[ycoords.count()-1]); - update(); - doLayout = false; // Clear previous layout requests - layoutTimer->start(50); // Minimum time until we start the next layout + height = ycoords[ycoords.count()-1]; + adjustScrollBar(); + verticalScrollBar()->setValue(verticalScrollBar()->maximum()); + viewport()->update(); } -int ChatWidgetContents::yToLineIdx(qreal y) { +int ChatWidget::yToLineIdx(qreal y) { if(y >= ycoords[ycoords.count()-1]) ycoords.count()-1; if(ycoords.count() <= 1) return 0; int uidx = 0; @@ -190,9 +260,11 @@ int ChatWidgetContents::yToLineIdx(qreal y) { } } -void ChatWidgetContents::mousePressEvent(QMouseEvent *event) { +void ChatWidget::mousePressEvent(QMouseEvent *event) { + if(lines.isEmpty()) return; + QPoint pos = event->pos() + QPoint(0, verticalScrollBar()->value()); if(event->button() == Qt::LeftButton) { - dragStartPos = event->pos(); + dragStartPos = pos; dragStartMode = Normal; switch(mouseMode) { case Normal: @@ -203,8 +275,8 @@ void ChatWidgetContents::mousePressEvent(QMouseEvent *event) { dragStartMode = DragTextSep; setCursor(Qt::ClosedHandCursor); } else { - dragStartLine = yToLineIdx(event->pos().y()); - dragStartCursor = lines[dragStartLine]->posToCursor(QPointF(event->pos().x(), event->pos().y()-ycoords[dragStartLine])); + dragStartLine = yToLineIdx(pos.y()); + dragStartCursor = lines[dragStartLine]->posToCursor(QPointF(pos.x(), pos.y()-ycoords[dragStartLine])); } mouseMode = Pressed; break; @@ -212,7 +284,15 @@ void ChatWidgetContents::mousePressEvent(QMouseEvent *event) { } } -void ChatWidgetContents::mouseReleaseEvent(QMouseEvent *event) { +void ChatWidget::mouseDoubleClickEvent(QMouseEvent *event) { + + + +} + +void ChatWidget::mouseReleaseEvent(QMouseEvent *event) { + //QPoint pos = event->pos() + QPoint(0, verticalScrollBar()->value()); + if(event->button() == Qt::LeftButton) { dragStartPos = QPoint(); if(mousePos == OverTsSep || mousePos == OverTextSep) setCursor(Qt::OpenHandCursor); @@ -250,10 +330,28 @@ void ChatWidgetContents::mouseReleaseEvent(QMouseEvent *event) { /** This is called by Qt whenever the mouse moves. Here we have to do most of the mouse handling, * such as changing column widths, marking text or initiating drag & drop. */ -void ChatWidgetContents::mouseMoveEvent(QMouseEvent *event) { +void ChatWidget::mouseMoveEvent(QMouseEvent *event) { + QPoint pos = event->pos(); pointerPosition = pos; + // Scroll if mouse pointer leaves widget while dragging + if((mouseMode == MarkText || mouseMode == MarkLines) && (pos.y() > viewport()->height() || pos.y() < 0)) { + if(!scrollTimer->isActive()) { + scrollTimer->start(); + } + } else { + if(scrollTimer->isActive()) { + scrollTimer->stop(); + } + } + handleMouseMoveEvent(pos); +} + +void ChatWidget::handleMouseMoveEvent(const QPoint &_pos) { + // FIXME + if(lines.count() <= 0) return; // Set some basic properties of the current position - int x = event->pos().x(); - int y = event->pos().y(); + QPoint pos = _pos + QPoint(0, verticalScrollBar()->value()); + int x = pos.x(); + int y = pos.y(); MousePos oldpos = mousePos; if(x >= tsGrabPos - 3 && x <= tsGrabPos + 3) mousePos = OverTsSep; else if(x >= senderGrabPos - 3 && x <= senderGrabPos + 3) mousePos = OverTextSep; @@ -263,14 +361,23 @@ void ChatWidgetContents::mouseMoveEvent(QMouseEvent *event) { switch(mouseMode) { // No special mode. Set mouse cursor if appropriate. case Normal: - if(oldpos != mousePos) { - if(mousePos == OverTsSep || mousePos == OverTextSep) setCursor(Qt::OpenHandCursor); - else setCursor(Qt::ArrowCursor); + { + //if(oldpos != mousePos) { + if(mousePos == OverTsSep || mousePos == OverTextSep) setCursor(Qt::OpenHandCursor); + else { + int l = yToLineIdx(y); + int c = lines[l]->posToCursor(QPointF(x, y - ycoords[l])); + if(c >= 0 && lines[l]->isUrl(c)) { + setCursor(Qt::PointingHandCursor); + } else { + setCursor(Qt::ArrowCursor); + } } + } break; // Left button pressed. Might initiate marking or drag & drop if we moved past the drag distance. case Pressed: - if(!dragStartPos.isNull() && (dragStartPos - event->pos()).manhattanLength() >= QApplication::startDragDistance()) { + if(!dragStartPos.isNull() && (dragStartPos - pos).manhattanLength() >= QApplication::startDragDistance()) { // Moving a column separator? if(dragStartMode == DragTsSep) mouseMode = DragTsSep; else if(dragStartMode == DragTextSep) mouseMode = DragTextSep; @@ -297,6 +404,7 @@ void ChatWidgetContents::mouseMoveEvent(QMouseEvent *event) { mouseMode = Normal; // Otherwise, clear the selection and start text marking! } else { + setCursor(Qt::ArrowCursor); clearSelection(); if(dragStartCursor < 0) { mouseMode = MarkLines; curLine = -1; } else mouseMode = MarkText; @@ -330,36 +438,42 @@ void ChatWidgetContents::mouseMoveEvent(QMouseEvent *event) { if(c != curCursor) { curCursor = c; lines[curLine]->setSelection(ChatLine::Partial, dragStartCursor, c); - update(); + viewport()->update(); } } else { mouseMode = MarkLines; selectionStart = qMin(curLine, dragStartLine); selectionEnd = qMax(curLine, dragStartLine); for(int i = selectionStart; i <= selectionEnd; i++) lines[i]->setSelection(ChatLine::Full); - update(); + viewport()->update(); } } else if(mouseMode == MarkLines) { // Line marking int l = yToLineIdx(y); if(l != curLine) { selectionStart = qMin(l, dragStartLine); selectionEnd = qMax(l, dragStartLine); - if(curLine >= 0 && curLine < selectionStart) { - for(int i = curLine; i < selectionStart; i++) lines[i]->setSelection(ChatLine::None); - } else if(curLine > selectionEnd) { - for(int i = selectionEnd+1; i <= curLine; i++) lines[i]->setSelection(ChatLine::None); - } else if(selectionStart < curLine && l < curLine) { + if(curLine < 0) { + Q_ASSERT(selectionStart == selectionEnd); + lines[l]->setSelection(ChatLine::Full); + } else { + if(curLine < selectionStart) { + for(int i = curLine; i < selectionStart; i++) lines[i]->setSelection(ChatLine::None); + } else if(curLine > selectionEnd) { + for(int i = selectionEnd+1; i <= curLine; i++) lines[i]->setSelection(ChatLine::None); + } else if(selectionStart < curLine && l < curLine) { for(int i = selectionStart; i < curLine; i++) lines[i]->setSelection(ChatLine::Full); - } else if(curLine < selectionEnd && l > curLine) { - for(int i = curLine+1; i <= selectionEnd; i++) lines[i]->setSelection(ChatLine::Full); + } else if(curLine < selectionEnd && l > curLine) { + for(int i = curLine+1; i <= selectionEnd; i++) lines[i]->setSelection(ChatLine::Full); + } } curLine = l; - update(); + //ensureVisible(l); + viewport()->update(); } } } //!\brief Clear current text selection. -void ChatWidgetContents::clearSelection() { +void ChatWidget::clearSelection() { if(selectionMode == TextSelected) { lines[selectionLine]->setSelection(ChatLine::None); } else if(selectionMode == LinesSelected) { @@ -368,11 +482,11 @@ void ChatWidgetContents::clearSelection() { } } selectionMode = NoSelection; - update(); + viewport()->update(); } //!\brief Convert current selection to human-readable string. -QString ChatWidgetContents::selectionToString() { +QString ChatWidget::selectionToString() { //TODO Make selection format configurable! if(selectionMode == NoSelection) return ""; if(selectionMode == LinesSelected) { @@ -391,7 +505,7 @@ QString ChatWidgetContents::selectionToString() { //!\brief Construct a ChatLine object from a message. /** - * \param m The message to be layouted and rendered + * \param m The message to be layouted and rendered * \param net The network name * \param buf The buffer name */ @@ -425,13 +539,13 @@ void ChatLine::formatMsg(Message msg) { case Message::Error: s = tr("%De*"); t = tr("%De%1").arg(text); break; case Message::Join: - s = tr("%Dj-->"); t = tr("%Dj%DN%1%DN %DH(%2@%3)%DH has joined %DC%4%DC").arg(nick, user, host, bufferName); break; + s = tr("%Dj-->"); t = tr("%Dj%DN%DU%1%DU%DN %DH(%2@%3)%DH has joined %DC%DU%4%DU%DC").arg(nick, user, host, bufferName); break; case Message::Part: - s = tr("%Dp<--"); t = tr("%Dp%DN%1%DN %DH(%2@%3)%DH has left %DC%4%DC").arg(nick, user, host, bufferName); + s = tr("%Dp<--"); t = tr("%Dp%DN%DU%1%DU%DN %DH(%2@%3)%DH has left %DC%DU%4%DU%DC").arg(nick, user, host, bufferName); if(!text.isEmpty()) t = QString("%1 (%2)").arg(t).arg(text); break; case Message::Quit: - s = tr("%Dq<--"); t = tr("%Dq%DN%1%DN %DH(%2@%3)%DH has quit").arg(nick, user, host); + s = tr("%Dq<--"); t = tr("%Dq%DN%DU%1%DU%DN %DH(%2@%3)%DH has quit").arg(nick, user, host); if(!text.isEmpty()) t = QString("%1 (%2)").arg(t).arg(text); break; case Message::Kick: @@ -439,19 +553,19 @@ void ChatLine::formatMsg(Message msg) { QString victim = text.section(" ", 0, 0); //if(victim == ui.ownNick->currentText()) victim = tr("you"); QString kickmsg = text.section(" ", 1); - t = tr("%Dk%DN%1%DN has kicked %DN%2%DN from %DC%3%DC").arg(nick).arg(victim).arg(bufferName); + t = tr("%Dk%DN%DU%1%DU%DN has kicked %DN%DU%2%DU%DN from %DC%DU%3%DU%DC").arg(nick).arg(victim).arg(bufferName); if(!kickmsg.isEmpty()) t = QString("%1 (%2)").arg(t).arg(kickmsg); } break; case Message::Nick: s = tr("%Dr<->"); - if(nick == msg.text) t = tr("You are now known as %DN%1%DN").arg(msg.text); - else t = tr("%DN%1%DN is now known as %DN%2%DN").arg(nick, msg.text); + if(nick == msg.text) t = tr("%DrYou are now known as %DN%1%DN").arg(msg.text); + else t = tr("%Dr%DN%1%DN is now known as %DN%DU%2%DU%DN").arg(nick, msg.text); break; case Message::Mode: s = tr("%Dm***"); - if(nick.isEmpty()) t = tr("User mode: %DM%1%DM").arg(msg.text); - else t = tr("Mode %DM%1%DM by %DN%2%DN").arg(msg.text, nick); + if(nick.isEmpty()) t = tr("%DmUser mode: %DM%1%DM").arg(msg.text); + else t = tr("%DmMode %DM%1%DM by %DN%DU%2%DU%DN").arg(msg.text, nick); break; default: s = tr("%De%1").arg(msg.sender); @@ -461,42 +575,52 @@ void ChatLine::formatMsg(Message msg) { tsFormatted = Style::internalToFormatted(c); senderFormatted = Style::internalToFormatted(s); textFormatted = Style::internalToFormatted(t); - tsLayout.setText(tsFormatted.text); tsLayout.setAdditionalFormats(tsFormatted.formats); - tsOption.setWrapMode(QTextOption::NoWrap); - tsLayout.setTextOption(tsOption); - senderLayout.setText(senderFormatted.text); senderLayout.setAdditionalFormats(senderFormatted.formats); - senderOption.setAlignment(Qt::AlignRight); senderOption.setWrapMode(QTextOption::ManualWrap); - senderLayout.setTextOption(senderOption); - textLayout.setText(textFormatted.text); textLayout.setAdditionalFormats(textFormatted.formats); - textOption.setWrapMode(QTextOption::WrapAnywhere); // seems to do what we want, apparently - textLayout.setTextOption(textOption); -} - -//!\brief Return the cursor position for the given coordinate pos. -/** - * \param pos The position relative to the ChatLine - * \return The cursor position, or -2 for timestamp, or -1 for sender - */ -int ChatLine::posToCursor(QPointF pos) { - if(pos.x() < tsWidth + (int)Style::sepTsSender()/2) return -2; - qreal textStart = tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText(); - if(pos.x() < textStart) return -1; - for(int l = textLayout.lineCount() - 1; l >=0; l--) { - QTextLine line = textLayout.lineAt(l); - if(pos.y() >= line.position().y()) { - int p = line.xToCursor(pos.x() - textStart, QTextLine::CursorOnCharacter); - return p; + precomputeLine(); +} + +QList ChatLine::calcFormatRanges(const Style::FormattedString &fs, QTextLayout::FormatRange additional) { + QList ranges; + QList formats = fs.formats; + formats.append(additional); + int cur = -1; + FormatRange range, lastrange; + for(int i = 0; i < fs.text.length(); i++) { + QTextCharFormat format; + foreach(QTextLayout::FormatRange f, formats) { + if(i >= f.start && i < f.start + f.length) format.merge(f.format); } + if(cur < 0) { + range.start = 0; range.length = 1; range.format= format; + cur = 0; + } else { + if(format == range.format) range.length++; + else { + QFontMetrics metrics(range.format.font()); + range.height = metrics.lineSpacing(); + ranges.append(range); + range.start = i; range.length = 1; range.format = format; + cur++; + } + } + } + if(cur >= 0) { + QFontMetrics metrics(range.format.font()); + range.height = metrics.lineSpacing(); + ranges.append(range); } + return ranges; } void ChatLine::setSelection(SelectionMode mode, int start, int end) { selectionMode = mode; - tsFormat.clear(); senderFormat.clear(); textFormat.clear(); + //tsFormat.clear(); senderFormat.clear(); textFormat.clear(); QPalette pal = QApplication::palette(); QTextLayout::FormatRange tsSel, senderSel, textSel; switch (mode) { case None: + tsFormat = calcFormatRanges(tsFormatted); + senderFormat = calcFormatRanges(senderFormatted); + textFormat = calcFormatRanges(textFormatted); break; case Partial: selectionStart = qMin(start, end); selectionEnd = qMax(start, end); @@ -504,15 +628,23 @@ void ChatLine::setSelection(SelectionMode mode, int start, int end) { textSel.format.setBackground(pal.brush(QPalette::Highlight)); textSel.start = selectionStart; textSel.length = selectionEnd - selectionStart; - textFormat.append(textSel); + //textFormat.append(textSel); + textFormat = calcFormatRanges(textFormatted, textSel); + foreach(FormatRange fr, textFormat); break; case Full: tsSel.format.setForeground(pal.brush(QPalette::HighlightedText)); - tsSel.start = 0; tsSel.length = tsLayout.text().length(); tsFormat.append(tsSel); + tsSel.format.setBackground(pal.brush(QPalette::Highlight)); + tsSel.start = 0; tsSel.length = tsFormatted.text.length(); + tsFormat = calcFormatRanges(tsFormatted, tsSel); senderSel.format.setForeground(pal.brush(QPalette::HighlightedText)); - senderSel.start = 0; senderSel.length = senderLayout.text().length(); senderFormat.append(senderSel); + senderSel.format.setBackground(pal.brush(QPalette::Highlight)); + senderSel.start = 0; senderSel.length = senderFormatted.text.length(); + senderFormat = calcFormatRanges(senderFormatted, senderSel); textSel.format.setForeground(pal.brush(QPalette::HighlightedText)); - textSel.start = 0; textSel.length = textLayout.text().length(); textFormat.append(textSel); + textSel.format.setBackground(pal.brush(QPalette::Highlight)); + textSel.start = 0; textSel.length = textFormatted.text.length(); + textFormat = calcFormatRanges(textFormatted, textSel); break; } } @@ -522,56 +654,273 @@ QDateTime ChatLine::getTimeStamp() { } QString ChatLine::getSender() { - return senderLayout.text(); + return senderFormatted.text; } QString ChatLine::getText() { - return textLayout.text(); + return textFormatted.text; +} + +bool ChatLine::isUrl(int c) { + if(c < 0 || c >= charUrlIdx.count()) return false;; + return charUrlIdx[c] >= 0; +} + +QUrl ChatLine::getUrl(int c) { + if(c < 0 || c >= charUrlIdx.count()) return QUrl(); + int i = charUrlIdx[c]; + if(i >= 0) return textFormatted.urls[i].url; + else return QUrl(); +} + +//!\brief Return the cursor position for the given coordinate pos. +/** + * \param pos The position relative to the ChatLine + * \return The cursor position, [or -3 for invalid,] or -2 for timestamp, or -1 for sender + */ +int ChatLine::posToCursor(QPointF pos) { + if(pos.x() < tsWidth + (int)Style::sepTsSender()/2) return -2; + qreal textStart = tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText(); + if(pos.x() < textStart) return -1; + int x = (int)(pos.x() - textStart); + for(int l = lineLayouts.count() - 1; l >=0; l--) { + LineLayout line = lineLayouts[l]; + if(pos.y() >= line.y) { + int offset = charPos[line.start]; x += offset; + for(int i = line.start + line.length - 1; i >= line.start; i--) { + if((charPos[i] + charPos[i+1])/2 <= x) return i+1; // FIXME: Optimize this! + } + return line.start; + } + } +} + +void ChatLine::precomputeLine() { + tsFormat = calcFormatRanges(tsFormatted); + senderFormat = calcFormatRanges(senderFormatted); + textFormat = calcFormatRanges(textFormatted); + + minHeight = 0; + foreach(FormatRange fr, tsFormat) minHeight = qMax(minHeight, fr.height); + foreach(FormatRange fr, senderFormat) minHeight = qMax(minHeight, fr.height); + + words.clear(); + charPos.resize(textFormatted.text.length() + 1); + charHeights.resize(textFormatted.text.length()); + charUrlIdx.fill(-1, textFormatted.text.length()); + for(int i = 0; i < textFormatted.urls.count(); i++) { + Style::UrlInfo url = textFormatted.urls[i]; + for(int j = url.start; j < url.end; j++) charUrlIdx[j] = i; + } + if(!textFormat.count()) return; + int idx = 0; int cnt = 0; int w = 0; int h = 0; + QFontMetrics metrics(textFormat[0].format.font()); + Word wr; + wr.start = -1; wr.trailing = -1; + for(int i = 0; i < textFormatted.text.length(); ) { + charPos[i] = w; charHeights[i] = textFormat[idx].height; + w += metrics.charWidth(textFormatted.text, i); + if(!textFormatted.text[i].isSpace()) { + if(wr.trailing >= 0) { + // new word after space + words.append(wr); + wr.start = -1; + } + if(wr.start < 0) { + wr.start = i; wr.length = 1; wr.trailing = -1; wr.height = textFormat[idx].height; + } else { + wr.length++; wr.height = qMax(wr.height, textFormat[idx].height); + } + } else { + if(wr.start < 0) { + wr.start = i; wr.length = 0; wr.trailing = 1; wr.height = 0; + } else { + wr.trailing++; + } + } + if(++i < textFormatted.text.length() && ++cnt >= textFormat[idx].length) { + cnt = 0; idx++; + Q_ASSERT(idx < textFormat.count()); + metrics = QFontMetrics(textFormat[idx].format.font()); + } + } + charPos[textFormatted.text.length()] = w; + if(wr.start >= 0) words.append(wr); } qreal ChatLine::layout(qreal tsw, qreal senderw, qreal textw) { tsWidth = tsw; senderWidth = senderw; textWidth = textw; - QTextLine tl; - tsLayout.beginLayout(); - tl = tsLayout.createLine(); - tl.setLineWidth(tsWidth); - tl.setPosition(QPointF(0, 0)); - tsLayout.endLayout(); - - senderLayout.beginLayout(); - tl = senderLayout.createLine(); - tl.setLineWidth(senderWidth); - tl.setPosition(QPointF(0, 0)); - senderLayout.endLayout(); - - qreal h = 0; - textLayout.beginLayout(); - while(1) { - tl = textLayout.createLine(); - if(!tl.isValid()) break; - tl.setLineWidth(textWidth); - tl.setPosition(QPointF(0, h)); - h += tl.height(); + if(textw <= 0) return minHeight; + lineLayouts.clear(); LineLayout line; + int h = 0; + int offset = 0; int numWords = 0; + line.y = 0; + line.start = 0; + line.height = minHeight; // first line needs room for ts and sender + for(int i = 0; i < words.count(); i++) { + int lastpos = charPos[words[i].start + words[i].length]; // We use charPos[lastchar + 1], 'coz last char needs to fit + if(lastpos - offset <= textw) { + line.height = qMax(line.height, words[i].height); + line.length = words[i].start + words[i].length - line.start; + numWords++; + } else { + // we need to wrap! + if(numWords > 0) { + // ok, we had some words before, so store the layout and start a new line + h += line.height; + line.length = words[i-1].start + words[i-1].length - line.start; + lineLayouts.append(line); + line.y += line.height; + line.start = words[i].start; + line.height = words[i].height; + offset = charPos[words[i].start]; + } + numWords = 1; + // check if the word fits into the current line + if(lastpos - offset <= textw) { + line.length = words[i].length; + } else { + // we need to break a word in the middle + int border = (int)textw + offset; // save some additions + line.start = words[i].start; + line.length = 1; + line.height = charHeights[line.start]; + int j = line.start + 1; + for(int l = 1; l < words[i].length; j++, l++) { + if(charPos[j+1] < border) { + line.length++; + line.height = qMax(line.height, charHeights[j]); + continue; + } else { + h += line.height; + lineLayouts.append(line); + line.y += line.height; + line.start = j; + line.height = charHeights[j]; + line.length = 1; + offset = charPos[j]; + border = (int)textw + offset; + } + } + } + } + } + h += line.height; + if(numWords > 0) { + lineLayouts.append(line); } - textLayout.endLayout(); hght = h; - return h; + return hght; } //!\brief Draw ChatLine on the given QPainter at the given position. void ChatLine::draw(QPainter *p, const QPointF &pos) { QPalette pal = QApplication::palette(); + if(selectionMode == Full) { p->setPen(Qt::NoPen); p->setBrush(pal.brush(QPalette::Highlight)); p->drawRect(QRectF(pos, QSizeF(tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText() + textWidth, height()))); } else if(selectionMode == Partial) { - } + } /* p->setClipRect(QRectF(pos, QSizeF(tsWidth, height()))); tsLayout.draw(p, pos, tsFormat); p->setClipRect(QRectF(pos + QPointF(tsWidth + Style::sepTsSender(), 0), QSizeF(senderWidth, height()))); senderLayout.draw(p, pos + QPointF(tsWidth + Style::sepTsSender(), 0), senderFormat); p->setClipping(false); textLayout.draw(p, pos + QPointF(tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText(), 0), textFormat); + */ + //p->setClipRect(QRectF(pos, QSizeF(tsWidth, 15))); + //p->drawRect(QRectF(pos, QSizeF(tsWidth, minHeight))); + p->setBackgroundMode(Qt::OpaqueMode); + QPointF tp = pos; + QRectF rect(pos, QSizeF(tsWidth, minHeight)); + QRectF brect; + foreach(FormatRange fr, tsFormat) { + p->setFont(fr.format.font()); + p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); + p->drawText(rect, Qt::AlignLeft|Qt::TextSingleLine, tsFormatted.text.mid(fr.start, fr.length), &brect); + rect.setLeft(brect.right()); + } + rect = QRectF(pos + QPointF(tsWidth + Style::sepTsSender(), 0), QSizeF(senderWidth, minHeight)); + for(int i = senderFormat.count() - 1; i >= 0; i--) { + FormatRange fr = senderFormat[i]; + p->setFont(fr.format.font()); p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); + p->drawText(rect, Qt::AlignRight|Qt::TextSingleLine, senderFormatted.text.mid(fr.start, fr.length), &brect); + rect.setRight(brect.left()); + } + QPointF tpos = pos + QPointF(tsWidth + Style::sepTsSender() + senderWidth + Style::sepSenderText(), 0); + qreal h = 0; int l = 0; + rect = QRectF(tpos + QPointF(0, h), QSizeF(textWidth, lineLayouts[l].height)); + int offset = 0; + foreach(FormatRange fr, textFormat) { + if(l >= lineLayouts.count()) break; + p->setFont(fr.format.font()); p->setPen(QPen(fr.format.foreground(), 0)); p->setBackground(fr.format.background()); + int start, end, frend, llend; + do { + frend = fr.start + fr.length; + if(frend <= lineLayouts[l].start) break; + llend = lineLayouts[l].start + lineLayouts[l].length; + start = qMax(fr.start, lineLayouts[l].start); end = qMin(frend, llend); + rect.setLeft(tpos.x() + charPos[start] - offset); + p->drawText(rect, Qt::AlignLeft|Qt::TextSingleLine, textFormatted.text.mid(start, end - start), &brect); + if(llend <= end) { + h += lineLayouts[l].height; + l++; + if(l < lineLayouts.count()) { + rect = QRectF(tpos + QPointF(0, h), QSizeF(textWidth, lineLayouts[l].height)); + offset = charPos[lineLayouts[l].start]; + } + } + } while(end < frend && l < lineLayouts.count()); + } +} + +/******************************************************************************************************************/ + +LayoutThread::LayoutThread() : QThread() { + mutex.lock(); + abort = false; + mutex.unlock(); + +} + +LayoutThread::~LayoutThread() { + mutex.lock(); + abort = true; + mutex.unlock(); + condition.wakeOne(); + wait(); +} + +void LayoutThread::processTask(LayoutTask task) { + if(!isRunning()) start(); + Q_ASSERT(isRunning()); + mutex.lock(); + queue.append(task); + condition.wakeOne(); + mutex.unlock(); +} + +void LayoutThread::run() { + forever { + mutex.lock(); + if(!queue.count()) { + condition.wait(&mutex); + } + if(abort) { + mutex.unlock(); return; + } + Q_ASSERT(queue.count()); //qDebug() << "process"; + LayoutTask task = queue.takeFirst(); + mutex.unlock(); + foreach(Message msg, task.messages) { + //qDebug() << msg.text; + ChatLine *line = new ChatLine(msg, task.net, task.buf); + task.lines.append(line); + } + emit taskProcessed(task); + //msleep(500); + } } diff --git a/gui/chatwidget.h b/gui/chatwidget.h index 93ee1173..16418242 100644 --- a/gui/chatwidget.h +++ b/gui/chatwidget.h @@ -23,9 +23,11 @@ #include "style.h" #include "message.h" +#include "buffer.h" +#include #include -class ChatWidgetContents; +class ChatLine; //!\brief Scroll area showing part of the chat messages for a given buffer. /** The contents of the scroll area, i.e. a widget of type ChatWidgetContents, @@ -38,62 +40,39 @@ class ChatWidgetContents; * Because we use this as a custom widget in Qt Designer, we cannot use a constructor that takes custom * parameters. Instead, it is mandatory to call init() before using this widget. */ -class ChatWidget : public QScrollArea { +class ChatWidget : public QAbstractScrollArea { Q_OBJECT public: ChatWidget(QWidget *parent = 0); ~ChatWidget(); - void init(QString net, QString buf, ChatWidgetContents *contents); - - public slots: - void clear(); - void appendMsg(Message); - - protected: - void resizeEvent(QResizeEvent *event); - - private: - QString networkName, bufferName; - ChatWidgetContents *contents; -}; - -class ChatLine; - -//!\brief Renders the complete contents of a Buffer. -/** Usually, this widget is used within a scroll - * area. Because Qt's rich-text rendering engine is much too slow for our purposes, we do everything - * except for the actual glyph painting ourselves. While this makes managing text quite cumbersome - * (we cannot, for example, use any of Qt's text editing, selecting or layouting features), it is - * also orders of magnitudes faster than any of the usual methods. - */ -class ChatWidgetContents : public QWidget { - Q_OBJECT + void init(QString net, QString buf); - public: - ChatWidgetContents(QString net, QString buf, QWidget *parent = 0); - ~ChatWidgetContents(); - //int heightForWidth(int w) const; virtual QSize sizeHint() const; public slots: void clear(); + void prependChatLines(QList); void appendMsg(Message); - void setWidth(qreal); + void appendMsgList(QList *); protected: - virtual void paintEvent(QPaintEvent *event); - - protected slots: + virtual void resizeEvent(QResizeEvent *event); + virtual void paintEvent(QPaintEvent * event); virtual void mousePressEvent(QMouseEvent *event); virtual void mouseReleaseEvent(QMouseEvent *event); virtual void mouseMoveEvent(QMouseEvent *event); + virtual void mouseDoubleClickEvent(QMouseEvent *event); private slots: - void layout(bool timer = false); - void triggerLayout(); + void layout(); + void scrollBarAction(int); + void scrollBarValChanged(int); + void ensureVisible(int line); + void handleScrollTimer(); private: + QString networkName, bufferName; enum SelectionMode { NoSelection, TextSelected, LinesSelected }; enum MouseMode { Normal, Pressed, DragTsSep, DragTextSep, MarkText, MarkLines }; enum MousePos { None, OverTsSep, OverTextSep, OverUrl }; @@ -107,12 +86,13 @@ class ChatWidgetContents : public QWidget { int curLine; SelectionMode selectionMode; int selectionStart, selectionEnd, selectionLine; - QString networkName, bufferName; - QTimer *layoutTimer; - bool doLayout; + + int bottomLine, bottomLineOffset; QList lines; QList ycoords; + QTimer *scrollTimer; + QPoint pointerPosition; int senderX; int textX; @@ -123,15 +103,20 @@ class ChatWidgetContents : public QWidget { int senderGrabPos; void computePositions(); - qreal width; + int width; qreal height; qreal y; + void adjustScrollBar(); + int yToLineIdx(qreal y); void clearSelection(); QString selectionToString(); + void handleMouseMoveEvent(const QPoint &pos); + }; +//FIXME: chatline doku //!\brief Containing the layout and providing the rendering of a single message. /** A ChatLine takes a Message object, * formats it (by turning the various message types into a human-readable form and afterwards pumping it through @@ -161,22 +146,82 @@ class ChatLine : public QObject { QDateTime getTimeStamp(); QString getSender(); QString getText(); + + bool isUrl(int pos); + QUrl getUrl(int pos); + + public slots: + private: qreal hght; Message msg; QString networkName, bufferName; - //QString ts, nick, text; qreal tsWidth, senderWidth, textWidth; - QTextLayout tsLayout; - QTextLayout senderLayout; - QTextLayout textLayout; - QVector tsFormat, senderFormat, textFormat; - Style::StringFormats tsFormatted, senderFormatted, textFormatted; + Style::FormattedString tsFormatted, senderFormatted, textFormatted; + + struct FormatRange { + int start; + int length; + int height; + QTextCharFormat format; + }; + struct Word { + int start; + int length; + int trailing; + int height; + }; + struct LineLayout { + int y; + int height; + int start; + int length; + }; + QVector charPos; + QVector charWidths; + QVector charHeights; + QVector charUrlIdx; + QList tsFormat, senderFormat, textFormat; + QList words; + QList lineLayouts; + int minHeight; SelectionMode selectionMode; int selectionStart, selectionEnd; void formatMsg(Message); + void precomputeLine(); + QList calcFormatRanges(const Style::FormattedString &, QTextLayout::FormatRange additional = QTextLayout::FormatRange()); }; +struct LayoutTask { + QList messages; + Buffer *buffer; + QString net, buf; + QList lines; +}; + +Q_DECLARE_METATYPE(LayoutTask); + +class LayoutThread : public QThread { + Q_OBJECT + + public: + LayoutThread(); + virtual ~LayoutThread(); + virtual void run(); + + public: + void processTask(LayoutTask task); + + signals: + void taskProcessed(LayoutTask task); + + private: + QList queue; + QMutex mutex; + QWaitCondition condition; + bool abort; + +}; #endif diff --git a/gui/guiproxy.cpp b/gui/guiproxy.cpp index 3657623d..3c3c0b37 100644 --- a/gui/guiproxy.cpp +++ b/gui/guiproxy.cpp @@ -43,6 +43,7 @@ void GUIProxy::recv(CoreSignal sig, QVariant arg1, QVariant arg2, QVariant arg3) case CS_NICK_RENAMED: emit csNickRenamed(arg1.toString(), arg2.toString(), arg3.toString()); break; case CS_NICK_UPDATED: emit csNickUpdated(arg1.toString(), arg2.toString(), arg3.toMap()); break; case CS_OWN_NICK_SET: emit csOwnNickSet(arg1.toString(), arg2.toString()); break; + case CS_QUERY_REQUESTED: emit csQueryRequested(arg1.toString(), arg2.toString()); break; default: qWarning() << "Unknown signal in GUIProxy::recv: " << sig; } diff --git a/gui/guiproxy.h b/gui/guiproxy.h index c8ac87e6..e07599b3 100644 --- a/gui/guiproxy.h +++ b/gui/guiproxy.h @@ -64,6 +64,7 @@ class GUIProxy : public QObject { void csNickRenamed(QString, QString, QString); void csNickUpdated(QString, QString, VarMap); void csOwnNickSet(QString, QString); + void csQueryRequested(QString, QString); void coreConnected(); void coreDisconnected(); diff --git a/gui/mainwin.cpp b/gui/mainwin.cpp index e9fe9e45..5beefdf2 100644 --- a/gui/mainwin.cpp +++ b/gui/mainwin.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -20,7 +20,9 @@ #include #include +#include +#include "util.h" #include "global.h" #include "message.h" #include "guiproxy.h" @@ -30,31 +32,25 @@ #include "networkview.h" #include "serverlist.h" #include "coreconnectdlg.h" -#include "settings.h" +#include "settingsdlg.h" +#include "settingspages.h" + +LayoutThread *layoutThread; MainWin::MainWin() : QMainWindow() { ui.setupUi(this); - widget = 0; - + //widget = 0; + qDebug() << "Available DB drivers: " << QSqlDatabase::drivers (); setWindowTitle("Quassel IRC"); setWindowIcon(QIcon(":/qirc-icon.png")); setWindowIconText("Quassel IRC"); - QSettings s; - s.beginGroup("Geometry"); - resize(s.value("MainWinSize", QSize(500, 400)).toSize()); - move(s.value("MainWinPos", QPoint(50, 50)).toPoint()); - s.endGroup(); - //workspace = new QWorkspace(this); //setCentralWidget(workspace); statusBar()->showMessage(tr("Waiting for core...")); +} - netView = new NetworkView("", this); - netView->setAllowedAreas(Qt::RightDockWidgetArea|Qt::LeftDockWidgetArea); - addDockWidget(Qt::LeftDockWidgetArea, netView); - connect(netView, SIGNAL(bufferSelected(QString, QString)), this, SLOT(showBuffer(QString, QString))); - connect(this, SIGNAL(bufferSelected(QString, QString)), netView, SLOT(selectBuffer(QString, QString))); +void MainWin::init() { connect(guiProxy, SIGNAL(csServerState(QString, QVariant)), this, SLOT(recvNetworkState(QString, QVariant))); connect(guiProxy, SIGNAL(csServerConnected(QString)), this, SLOT(networkConnected(QString))); @@ -70,25 +66,64 @@ MainWin::MainWin() : QMainWindow() { connect(guiProxy, SIGNAL(csOwnNickSet(QString, QString)), this, SLOT(setOwnNick(QString, QString))); connect(this, SIGNAL(sendInput( QString, QString, QString )), guiProxy, SLOT(gsUserInput(QString, QString, QString))); + layoutThread = new LayoutThread(); + layoutThread->start(); + while(!layoutThread->isRunning()) {}; + ui.bufferWidget->init(); + show(); syncToCore(); statusBar()->showMessage(tr("Ready.")); - - buffersUpdated(); + systray = new QSystemTrayIcon(this); + systray->setIcon(QIcon(":/qirc-icon.png")); + systray->show(); serverListDlg = new ServerListDlg(this); serverListDlg->setVisible(serverListDlg->showOnStartup()); - settingsDlg = new SettingsDlg(this); - settingsDlg->setVisible(false); + + setupSettingsDlg(); + + //Buffer::init(); setupMenus(); + setupViews(); + + //bufferWidget = 0; + + QSettings s; + s.beginGroup("Geometry"); + //resize(s.value("MainWinSize", QSize(500, 400)).toSize()); + //move(s.value("MainWinPos", QPoint(50, 50)).toPoint()); + if(s.contains("MainWinState")) restoreState(s.value("MainWinState").toByteArray()); + s.endGroup(); // replay backlog + // FIXME do this right + QHash > hash; + Buffer *b; + foreach(QString net, coreBackLog.keys()) { + //if(net != "MoepNet") continue; while(coreBackLog[net].count()) { - recvMessage(net, coreBackLog[net].takeFirst()); + //recvMessage(net, coreBackLog[net].takeFirst()); + Message msg = coreBackLog[net].takeLast(); + if(msg.flags & Message::PrivMsg) { + // query + if(msg.flags & Message::Self) b = getBuffer(net, msg.target); + else b = getBuffer(net, nickFromMask(msg.sender)); + } else { + b = getBuffer(net, msg.target); + } + hash[b].prepend(msg); + if(hash[b].count() >= 5) { + ui.bufferWidget->prependMessages(b, hash.take(b)); + } } } - /* + foreach(Buffer *buf, hash.keys()) { + ui.bufferWidget->prependMessages(buf, hash.take(buf)); + } + +/* foreach(QString key, buffers.keys()) { foreach(Buffer *b, buffers[key].values()) { QWidget *widget = b->showWidget(this); @@ -96,7 +131,8 @@ MainWin::MainWin() : QMainWindow() { widget->show(); } } - */ +*/ + s.beginGroup("Buffers"); QString net = s.value("CurrentNetwork", "").toString(); QString buf = s.value("CurrentBuffer", "").toString(); @@ -112,11 +148,62 @@ MainWin::MainWin() : QMainWindow() { } } +MainWin::~MainWin() { + typedef QHash BufHash; + foreach(BufHash h, buffers.values()) { + foreach(Buffer *b, h.values()) { + delete b; + } + } +} + +/* This is implemented in settingspages.cpp */ +/* +void MainWin::setupSettingsDlg() { + +} +*/ + void MainWin::setupMenus() { connect(ui.actionNetworkList, SIGNAL(triggered()), this, SLOT(showServerList())); connect(ui.actionEditIdentities, SIGNAL(triggered()), serverListDlg, SLOT(editIdentities())); connect(ui.actionSettingsDlg, SIGNAL(triggered()), this, SLOT(showSettingsDlg())); - ui.actionSettingsDlg->setEnabled(false); + //ui.actionSettingsDlg->setEnabled(false); + connect(ui.actionAboutQt, SIGNAL(triggered()), QApplication::instance(), SLOT(aboutQt())); +} + +void MainWin::setupViews() { + NetworkView *all = new NetworkView(tr("All Buffers"), NetworkView::AllNets); + registerNetView(all); + addDockWidget(Qt::LeftDockWidgetArea, all); + NetworkView *allchans = new NetworkView(tr("All Channels"), NetworkView::AllNets|NetworkView::NoQueries|NetworkView::NoServers); + registerNetView(allchans); + addDockWidget(Qt::LeftDockWidgetArea, allchans); + NetworkView *allqrys = new NetworkView(tr("All Queries"), NetworkView::AllNets|NetworkView::NoChannels|NetworkView::NoServers); + registerNetView(allqrys); + addDockWidget(Qt::RightDockWidgetArea, allqrys); + NetworkView *allnets = new NetworkView(tr("All Networks"), NetworkView::AllNets|NetworkView::NoChannels|NetworkView::NoQueries); + registerNetView(allnets); + addDockWidget(Qt::RightDockWidgetArea, allnets); + + ui.menuViews->addSeparator(); +} + +void MainWin::registerNetView(NetworkView *view) { + connect(this, SIGNAL(bufferSelected(Buffer *)), view, SLOT(selectBuffer(Buffer *))); + connect(this, SIGNAL(bufferUpdated(Buffer *)), view, SLOT(bufferUpdated(Buffer *))); + connect(this, SIGNAL(bufferDestroyed(Buffer *)), view, SLOT(bufferDestroyed(Buffer *))); + connect(view, SIGNAL(bufferSelected(Buffer *)), this, SLOT(showBuffer(Buffer *))); + QList bufs; + typedef QHash bufhash; + QList foo = buffers.values(); + foreach(bufhash h, foo) { + bufs += h.values(); + } + view->setBuffers(bufs); + view->setAllowedAreas(Qt::RightDockWidgetArea|Qt::LeftDockWidgetArea); + netViews.append(view); + ui.menuViews->addAction(view->toggleViewAction()); } void MainWin::showServerList() { @@ -133,15 +220,18 @@ void MainWin::showSettingsDlg() { void MainWin::closeEvent(QCloseEvent *event) { //if (userReallyWantsToQuit()) { + ui.bufferWidget->saveState(); QSettings s; s.beginGroup("Geometry"); s.setValue("MainWinSize", size()); s.setValue("MainWinPos", pos()); + s.setValue("MainWinState", saveState()); s.endGroup(); s.beginGroup("Buffers"); s.setValue("CurrentNetwork", currentNetwork); s.setValue("CurrentBuffer", currentBuffer); s.endGroup(); + delete systray; event->accept(); //} else { //event->ignore(); @@ -149,23 +239,15 @@ void MainWin::closeEvent(QCloseEvent *event) } void MainWin::showBuffer(QString net, QString buf) { - currentBuffer = buf; currentNetwork = net; - Buffer *b = getBuffer(net, buf); - QWidget *old = widget; - widget = b->showWidget(this); - if(widget == old) return; - //workspace->addWindow(widget); - //widget->show(); - setCentralWidget(widget); - widget->show(); - //workspace->setActiveWindow(widget); - widget->setFocus(); - //workspace->setFocus(); - widget->activateWindow(); - widget->setFocus(Qt::MouseFocusReason); - focusNextChild(); - //workspace->tile(); - emit bufferSelected(net, buf); + showBuffer(getBuffer(net, buf)); +} + +void MainWin::showBuffer(Buffer *b) { + currentBuffer = b->bufferName(); currentNetwork = b->networkName(); + //emit bufferSelected(b); + //qApp->processEvents(); + ui.bufferWidget->setBuffer(b); + emit bufferSelected(b); } void MainWin::networkConnected(QString net) { @@ -173,7 +255,7 @@ void MainWin::networkConnected(QString net) { Buffer *b = getBuffer(net, ""); b->setActive(true); b->displayMsg(Message::server("", tr("Connected."))); - buffersUpdated(); + // TODO buffersUpdated(); } void MainWin::networkDisconnected(QString net) { @@ -192,16 +274,14 @@ Buffer * MainWin::getBuffer(QString net, QString buf) { Buffer *b = new Buffer(net, buf); b->setOwnNick(ownNick[net]); connect(b, SIGNAL(userInput(QString, QString, QString)), this, SLOT(userInput(QString, QString, QString))); + connect(b, SIGNAL(bufferUpdated(Buffer *)), this, SIGNAL(bufferUpdated(Buffer *))); + connect(b, SIGNAL(bufferDestroyed(Buffer *)), this, SIGNAL(bufferDestroyed(Buffer *))); buffers[net][buf] = b; - buffersUpdated(); + emit bufferUpdated(b); } return buffers[net][buf]; } -void MainWin::buffersUpdated() { - netView->buffersUpdated(buffers); -} - void MainWin::recvNetworkState(QString net, QVariant state) { connected[net] = true; setOwnNick(net, state.toMap()["OwnNick"].toString()); @@ -215,11 +295,17 @@ void MainWin::recvNetworkState(QString net, QVariant state) { foreach(QString nick, n.keys()) { addNick(net, nick, n[nick].toMap()); } - buffersUpdated(); } void MainWin::recvMessage(QString net, Message msg) { - Buffer *b = getBuffer(net, msg.target); + Buffer *b; + if(msg.flags & Message::PrivMsg) { + // query + if(msg.flags & Message::Self) b = getBuffer(net, msg.target); + else b = getBuffer(net, nickFromMask(msg.sender)); + } else { + b = getBuffer(net, msg.target); + } b->displayMsg(msg); } @@ -236,7 +322,6 @@ void MainWin::setTopic(QString net, QString buf, QString topic) { if(!connected[net]) return; Buffer *b = getBuffer(net, buf); b->setTopic(topic); - buffersUpdated(); //if(!b->isActive()) { // b->setActive(true); // buffersUpdated(); @@ -255,7 +340,6 @@ void MainWin::addNick(QString net, QString nick, VarMap props) { foreach(QString bufname, c) { getBuffer(net, bufname)->addNick(nick, props); } - buffersUpdated(); } void MainWin::renameNick(QString net, QString oldnick, QString newnick) { @@ -279,7 +363,6 @@ void MainWin::updateNick(QString net, QString nick, VarMap props) { if(!newchans.contains(c)) getBuffer(net, c)->removeNick(nick); } nicks[net][nick] = props; - buffersUpdated(); } void MainWin::removeNick(QString net, QString nick) { @@ -289,7 +372,6 @@ void MainWin::removeNick(QString net, QString nick) { getBuffer(net, bufname)->removeNick(nick); } nicks[net].remove(nick); - buffersUpdated(); } void MainWin::setOwnNick(QString net, QString nick) { diff --git a/gui/mainwin.h b/gui/mainwin.h index 2d2516a0..ff3596ec 100644 --- a/gui/mainwin.h +++ b/gui/mainwin.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -26,13 +26,17 @@ #include "global.h" #include "message.h" +#include "chatwidget.h" class ServerListDlg; class CoreConnectDlg; class NetworkView; class Buffer; +class BufferWidget; class SettingsDlg; +extern LayoutThread *layoutThread; + //!\brief The main window and central object of Quassel GUI. /** In addition to displaying the main window including standard stuff like a menubar, * dockwidgets and of course the chat window, this class also stores all data it @@ -43,13 +47,20 @@ class MainWin : public QMainWindow { public: MainWin(); + ~MainWin(); + + void init(); + void registerNetView(NetworkView *); protected: void closeEvent(QCloseEvent *event); signals: void sendInput(QString network, QString buffer, QString message); - void bufferSelected(QString net, QString buffer); + void bufferSelected(Buffer *); + void bufferUpdated(Buffer *); + void bufferDestroyed(Buffer *); + void backlogReceived(Buffer *, QList); private slots: void userInput(QString, QString, QString); @@ -70,17 +81,22 @@ class MainWin : public QMainWindow { void showSettingsDlg(); void showBuffer(QString net, QString buf); + void showBuffer(Buffer *); private: Ui::MainWin ui; void setupMenus(); + void setupViews(); + void setupSettingsDlg(); void syncToCore(); // implemented in main_mono.cpp or main_gui.cpp Buffer * getBuffer(QString net, QString buf); - void buffersUpdated(); + //void buffersUpdated(); - QWorkspace *workspace; - QWidget *widget; + QSystemTrayIcon *systray; + //QWorkspace *workspace; + //QWidget *widget; + //BufferWidget *bufferWidget; ServerListDlg *serverListDlg; CoreConnectDlg *coreConnectDlg; @@ -93,7 +109,7 @@ class MainWin : public QMainWindow { QHash ownNick; QHash > coreBackLog; - NetworkView *netView; + QList netViews; }; #endif diff --git a/gui/networkview.cpp b/gui/networkview.cpp index be08b3b5..c97e2880 100644 --- a/gui/networkview.cpp +++ b/gui/networkview.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -24,8 +24,10 @@ NetworkViewWidget::NetworkViewWidget(QWidget *parent) : QWidget(parent) { ui.setupUi(this); + //setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); } + QSize NetworkViewWidget::sizeHint() const { return QSize(150,100); @@ -33,59 +35,107 @@ QSize NetworkViewWidget::sizeHint() const { /**************************************************************************/ -NetworkView::NetworkView(QString net, QWidget *parent) : network(net), QDockWidget(parent) { - if(!net.isEmpty()) setWindowTitle(net); - else setWindowTitle(tr("All Buffers")); +NetworkView::NetworkView(QString n, int m, QStringList nets, QWidget *parent) : QDockWidget(parent) { + setObjectName(QString("View-"+n)); // should be unique for mainwindow state! + name = n; mode = m; + setWindowTitle(name); + networks = nets; + currentBuffer = 0; setWidget(new NetworkViewWidget(this)); tree = qobject_cast(widget())->tree(); - tree->setSortingEnabled(false); + tree->header()->hide(); + tree->setSortingEnabled(true); connect(tree, SIGNAL(itemClicked(QTreeWidgetItem*, int)), this, SLOT(itemClicked(QTreeWidgetItem*))); } -void NetworkView::buffersUpdated(BufferHash buffers) { - tree->clear(); items.clear(); - if(network.isEmpty()) { - tree->setHeaderLabel(tr("Networks")); - foreach(QString net, buffers.keys()) { - QTreeWidgetItem *netItem = new QTreeWidgetItem(QStringList(net)); - foreach(QString buf, buffers[net].keys()) { - Buffer *b = buffers[net][buf]; - QStringList label; - if(buf.isEmpty()) label << tr("Status"); - else label << buf; - QTreeWidgetItem *item = new QTreeWidgetItem(netItem, label); - items[net][buf] = item; - VarMap d; - d["Network"] = net; - d["Buffer"] = buf; - item->setData(0, Qt::UserRole, d); - if(!b->isActive()) { - item->setForeground(0, QColor("grey")); - } +void NetworkView::setBuffers(QList buffers) { + tree->clear(); bufitems.clear(); netitems.clear(); + foreach(Buffer *b, buffers) { + bufferUpdated(b); + } +} + +void NetworkView::bufferUpdated(Buffer *b) { + if(bufitems.contains(b)) { + // FIXME this looks ugly + QTreeWidgetItem *item = bufitems[b]->parent()->takeChild(bufitems[b]->parent()->indexOfChild(bufitems[b])); + delete item; + bufitems.remove(b); + } + if(shouldShow(b)) { + QString net = b->networkName(); + QString buf = b->bufferName(); + QTreeWidgetItem *item; + QStringList label; + if(b->bufferType() == Buffer::ServerBuffer) label << tr("Status"); + else label << buf; + if((mode & SomeNets) || ( mode & AllNets)) { + if(!netitems.contains(net)) { + netitems[net] = new QTreeWidgetItem(tree, QStringList(net)); + netitems[net]->setFlags(Qt::ItemIsEnabled); } - VarMap d; - d["Network"] = net; d["Buffer"] = ""; - netItem->setData(0, Qt::UserRole, d); - netItem->setFlags(netItem->flags() & ~Qt::ItemIsSelectable); - tree->addTopLevelItem(netItem); - netItem->setExpanded(true); + QTreeWidgetItem *ni = netitems[net]; + ni->setExpanded(true); + ni->setFlags(Qt::ItemIsEnabled); + item = new QTreeWidgetItem(ni, label); + } else { + item = new QTreeWidgetItem(label); + } + //item->setFlags(Qt::ItemIsEnabled); + bufitems[b] = item; + // TODO Use style engine! + if(!b->isActive()) { + item->setForeground(0, QColor("grey")); + } + if(b == currentBuffer) { + item->setSelected(true); + } + } + foreach(QString key, netitems.keys()) { + if(!netitems[key]->childCount()) { + delete netitems[key]; + QTreeWidgetItem *item = netitems[key]->parent()->takeChild(netitems[key]->parent()->indexOfChild(netitems[key])); + delete item; + netitems.remove(key); } - if(items[currentNetwork][currentBuffer]) items[currentNetwork][currentBuffer]->setSelected(true); } } +bool NetworkView::shouldShow(Buffer *b) { + bool f = false; + if((mode & NoActive) && b->isActive()) return false; + if((mode & NoInactive) && !b->isActive()) return false; + if((mode & NoChannels) && b->bufferType() == Buffer::ChannelBuffer) return false; + if((mode & NoQueries) && b->bufferType() == Buffer::QueryBuffer) return false; + if((mode & NoServers) && b->bufferType() == Buffer::ServerBuffer) return false; + if((mode & SomeNets) && !networks.contains(b->networkName())) return false; + return true; +} + +void NetworkView::bufferDestroyed(Buffer *b) { + + +} + void NetworkView::itemClicked(QTreeWidgetItem *item) { - if(network.isEmpty()) { - VarMap d = item->data(0, Qt::UserRole).toMap(); - QString net = d["Network"].toString(); QString buf = d["Buffer"].toString(); - if(!net.isEmpty()) { - emit bufferSelected(net, buf); - currentNetwork = net; currentBuffer = buf; - } + Buffer *b = bufitems.key(item); + if(b) emit bufferSelected(b); + else { + b = currentBuffer; + if(bufitems.contains(b)) bufitems[b]->setSelected(true); + item->setExpanded(!item->isExpanded()); } } -void NetworkView::selectBuffer(QString net, QString buf) { - if(items[net][buf]) items[net][buf]->setSelected(true); - currentNetwork = net; currentBuffer = buf; +void NetworkView::selectBuffer(Buffer *b) { + QTreeWidgetItem *item = 0; + if(bufitems.contains(b)) item = bufitems[b]; + QList sel = tree->selectedItems(); + foreach(QTreeWidgetItem *i, sel) { if(i != item) i->setSelected(false); } + if(item) { + item->setSelected(true); + currentBuffer = b; + } else { + currentBuffer = 0; + } } diff --git a/gui/networkview.h b/gui/networkview.h index ac870773..2ed94b66 100644 --- a/gui/networkview.h +++ b/gui/networkview.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -32,13 +32,11 @@ class NetworkViewWidget : public QWidget { public: NetworkViewWidget(QWidget *parent = 0); + QTreeWidget *tree() { return ui.tree; } virtual QSize sizeHint () const; - public slots: - - signals: void bufferSelected(QString net, QString buf); @@ -54,25 +52,41 @@ class NetworkView : public QDockWidget { Q_OBJECT public: - NetworkView(QString network, QWidget *parent = 0); + enum Mode { + NoActive = 0x01, NoInactive = 0x02, + SomeNets = 0x04, AllNets = 0x08, + NoChannels = 0x10, NoQueries = 0x20, NoServers = 0x40 + }; + + NetworkView(QString name, int mode, QStringList nets = QStringList(), QWidget *parent = 0); + void setMode(int mode, QStringList nets = QStringList()); + void setName(QString name); + public slots: - void buffersUpdated(BufferHash); - void selectBuffer(QString net, QString buf); + void bufferUpdated(Buffer *); + void bufferDestroyed(Buffer *); + void setBuffers(QList); + void selectBuffer(Buffer *); signals: - void bufferSelected(QString net, QString buf); + void bufferSelected(Buffer *); private slots: void itemClicked(QTreeWidgetItem *item); private: - QString network; - QString currentNetwork, currentBuffer; - QHash > buffers; - QHash > items; + int mode; + QString name; + QStringList networks; + Buffer *currentBuffer; + //QHash > buffers; + QHash bufitems; + QHash netitems; + //QHash > items; QTreeWidget *tree; + bool shouldShow(Buffer *); }; diff --git a/gui/serverlist.cpp b/gui/serverlist.cpp index 55200e81..f49708ca 100644 --- a/gui/serverlist.cpp +++ b/gui/serverlist.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * diff --git a/gui/settingsdlg.cpp b/gui/settingsdlg.cpp new file mode 100644 index 00000000..06aeb0b6 --- /dev/null +++ b/gui/settingsdlg.cpp @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "settingsdlg.h" + +SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { + ui.setupUi(this); + + currentWidget = 0; + + ui.settingsFrame->setWidgetResizable(true); + ui.settingsTree->setRootIsDecorated(false); +} + +void SettingsDlg::registerSettingsPage(SettingsInterface *sp) { + QWidget *w = sp->settingsWidget(); + w->setParent(this); + ui.settingsFrame->setWidget(w); + + QTreeWidgetItem *cat; + QList cats = ui.settingsTree->findItems(sp->category(), Qt::MatchExactly); + if(!cats.count()) { + cat = new QTreeWidgetItem(ui.settingsTree, QStringList(sp->category())); + cat->setExpanded(true); + cat->setFlags(Qt::ItemIsEnabled); + } else cat = cats[0]; + new QTreeWidgetItem(cat, QStringList(sp->title())); + +} diff --git a/gui/settings.h b/gui/settingsdlg.h similarity index 89% rename from gui/settings.h rename to gui/settingsdlg.h index ed9be0ed..12fac466 100644 --- a/gui/settings.h +++ b/gui/settingsdlg.h @@ -18,8 +18,8 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#ifndef _SETTINGS_H_ -#define _SETTINGS_H_ +#ifndef _SETTINGSDLG_H_ +#define _SETTINGSDLG_H_ #include #include "ui_settingsdlg.h" @@ -37,7 +37,21 @@ class SettingsDlg : public QDialog { private: Ui::SettingsDlg ui; + QWidget *currentWidget; }; +/* +class CoreSettingsPage : public QWidget, SettingsInterface { + Q_OBJECT + + public: + + + private: + Ui::CoreSettingsPage ui; + +}; +*/ + #endif diff --git a/gui/settings.cpp b/gui/settingspages.cpp similarity index 74% rename from gui/settings.cpp rename to gui/settingspages.cpp index a4efaeaf..b67fd3ae 100644 --- a/gui/settings.cpp +++ b/gui/settingspages.cpp @@ -18,9 +18,28 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ -#include "settings.h" +#include "mainwin.h" +#include "settingspages.h" -SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { +void MainWin::setupSettingsDlg() { + settingsDlg = new SettingsDlg(this); + settingsDlg->setVisible(false); + + settingsDlg->registerSettingsPage(new BufferManagementSettingsPage()); + //settingsDlg->registerSettingsPage(new ConnectionSettingsPage()); + +} + + +BufferManagementSettingsPage::BufferManagementSettingsPage() { ui.setupUi(this); + ui.tree->headerItem()->setText(0, tr("Buffers")); + setEnabled(false); } + +ConnectionSettingsPage::ConnectionSettingsPage() { + ui.setupUi(this); + +} + diff --git a/gui/settingspages.h b/gui/settingspages.h new file mode 100644 index 00000000..f7d0f855 --- /dev/null +++ b/gui/settingspages.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _SETTINGSPAGES_H_ +#define _SETTINGSPAGES_H_ + +#include +#include + +#include "plugin.h" +#include "settingsdlg.h" +#include "ui_buffermgmntsettingspage.h" +#include "ui_connectionsettingspage.h" + +class BufferManagementSettingsPage : public QWidget, public SettingsInterface { + Q_OBJECT + + public: + QString category() { return tr("Buffers"); } + QString title() { return tr("Buffer Management"); } + QWidget *settingsWidget() { return this; } + + BufferManagementSettingsPage(); + + + private: + Ui::BufferManagementSettingsPage ui; + +}; + +class ConnectionSettingsPage : public QWidget, public SettingsInterface { + Q_OBJECT + + public: + QString category() { return tr("Behavior"); } + QString title() { return tr("Connection"); } + QWidget *settingsWidget() { return this; } + + ConnectionSettingsPage(); + + + private: + Ui::ConnectionSettingsPage ui; + +}; + + + + + +#endif diff --git a/gui/style.cpp b/gui/style.cpp index 8837872e..62105529 100644 --- a/gui/style.cpp +++ b/gui/style.cpp @@ -40,7 +40,7 @@ void Style::init() { colors["15"] = QColor("silver"); QTextCharFormat def; - //def.setFont(QFont("Lucida Mono")); + def.setFont(QFont("Verdana",9)); formats["default"] = def; // %B - 0x02 - bold @@ -97,15 +97,15 @@ void Style::init() { formats["%Dj"] = join; // %Dp - part QTextCharFormat part; - part.setForeground(QBrush("firebrick")); + part.setForeground(QBrush("indianred")); formats["%Dp"] = part; // %Dq - quit QTextCharFormat quit; - quit.setForeground(QBrush("firebrick")); + quit.setForeground(QBrush("indianred")); formats["%Dq"] = quit; // %Dk - kick QTextCharFormat kick; - kick.setForeground(QBrush("firebrick")); + kick.setForeground(QBrush("indianred")); formats["%Dk"] = kick; // %Dr - nick rename QTextCharFormat nren; @@ -143,7 +143,11 @@ void Style::init() { QTextCharFormat flags; flags.setFontWeight(QFont::Bold); formats["%DM"] = flags; - + // %DU - clickable URL + QTextCharFormat url; + url.setFontUnderline(true); + url.setAnchor(true); + formats["%DU"] = url; } QString Style::mircToInternal(QString mirc) { @@ -162,10 +166,10 @@ QString Style::mircToInternal(QString mirc) { * describing the formats of the string. * \param s string in internal format (% style format codes) */ -Style::StringFormats Style::internalToFormatted(QString s) { +Style::FormattedString Style::internalToFormatted(QString s) { QHash toggles; QString p; - StringFormats sf; + FormattedString sf; QTextLayout::FormatRange rng; rng.format = formats["default"]; rng.start = 0; rng.length = -1; sf.formats.append(rng); toggles["default"] = sf.formats.count() - 1; @@ -231,6 +235,16 @@ Style::StringFormats Style::internalToFormatted(QString s) { if(s[i] == 'D') i++; if(toggles.contains(key)) { sf.formats[toggles[key]].length = j - sf.formats[toggles[key]].start; + if(key == "%DU") { + // URL handling + // FIXME check for and handle format codes within URLs + QString u = s.mid(i - sf.formats[toggles[key]].length - 2, sf.formats[toggles[key]].length); + UrlInfo url; + url.start = sf.formats[toggles[key]].start; + url.end = j; + url.url = QUrl(u); + sf.urls.append(url); + } toggles.remove(key); } else { QTextLayout::FormatRange range; diff --git a/gui/style.h b/gui/style.h index 19c3be69..861d5c59 100644 --- a/gui/style.h +++ b/gui/style.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2005/06 by The Quassel Team * + * Copyright (C) 2005-07 by The Quassel Team * * devel@quassel-irc.org * * * * This program is free software; you can redistribute it and/or modify * @@ -29,14 +29,20 @@ class Style { public: static void init(); - struct StringFormats { + struct UrlInfo { + int start, end; + QUrl url; + }; + + struct FormattedString { QString text; QList formats; + QList urls; }; static QString mircToInternal(QString); //static QString internalToMirc(QString); - static StringFormats internalToFormatted(QString); + static FormattedString internalToFormatted(QString); static int sepTsSender() { return 10; } static int sepSenderText() { return 10; } diff --git a/gui/ui/aboutdlg.ui b/gui/ui/aboutdlg.ui new file mode 100644 index 00000000..9740c85c --- /dev/null +++ b/gui/ui/aboutdlg.ui @@ -0,0 +1,124 @@ + + AboutDlg + + + Qt::ApplicationModal + + + + 0 + 0 + 618 + 415 + + + + Dialog + + + + + 10 + 10 + 351 + 20 + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Quassel IRC </span>(pre-release)</p></body></html> + + + + + + 200 + 360 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + 20 + 40 + 581 + 301 + + + + 0 + + + + About + + + + + Authors + + + + + Licence Agreement + + + + + 10 + 10 + 561 + 251 + + + + + + + + + + buttonBox + accepted() + AboutDlg + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AboutDlg + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/gui/ui/buffermgmntsettingspage.ui b/gui/ui/buffermgmntsettingspage.ui new file mode 100644 index 00000000..34505a87 --- /dev/null +++ b/gui/ui/buffermgmntsettingspage.ui @@ -0,0 +1,585 @@ + + BufferManagementSettingsPage + + + + 0 + 0 + 672 + 488 + + + + Form + + + + 9 + + + 6 + + + + + QFrame::NoFrame + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Buffer Management</span></p></body></html> + + + + + + + Qt::Horizontal + + + + + + + 0 + + + 6 + + + + + false + + + + + + + 0 + + + 6 + + + + + Statistics + + + false + + + + 9 + + + 6 + + + + + Created: 01.04.2007 +Lines: 1234 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + + + 1 + + + + Defines how many messages the local Quassel client shows for this buffer. This is independent from the central backlog storage in the core. + + + Local Display + + + + 9 + + + 6 + + + + + Override default display options + + + true + + + false + + + + 9 + + + 6 + + + + + Show all messages + + + + + + + 0 + + + 6 + + + + + + 5 + 0 + 0 + 0 + + + + Show last + + + + + + + + 5 + 0 + 0 + 0 + + + + 99999 + + + 1 + + + 1000 + + + + + + + messages + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 6 + + + + + Show messages from the last + + + + + + + 1000 + + + 1 + + + 30 + + + + + + + false + + + days + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Hide buffer locally + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Save as default + + + + + + + + + + + + + Backlog Storage + + + + 9 + + + 6 + + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Bitstream Vera Sans'; font-size:11pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Warning:</span> This affects the global message storage (backlog) for the selected buffer!</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + Override default storage settings + + + true + + + false + + + + 9 + + + 6 + + + + + Keep all messages + + + + + + + 0 + + + 6 + + + + + + 5 + 0 + 0 + 0 + + + + Keep last + + + + + + + + 5 + 0 + 0 + 0 + + + + 99999 + + + 1 + + + 1000 + + + + + + + messages + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 6 + + + + + Keep messages from the last + + + + + + + 1000 + + + 1 + + + 30 + + + + + + + false + + + days + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Save as default + + + + + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delete permanently + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + diff --git a/gui/ui/bufferwidget.ui b/gui/ui/bufferwidget.ui index 854d4918..9cb2da08 100644 --- a/gui/ui/bufferwidget.ui +++ b/gui/ui/bufferwidget.ui @@ -5,15 +5,15 @@ 0 0 - 713 - 519 + 787 + 551 - 3 - 3 - 0 + 7 + 7 + 5 0 @@ -31,7 +31,7 @@ - 9 + 0 6 @@ -46,6 +46,9 @@ + + false + Qt::ClickFocus @@ -78,120 +81,66 @@ - - - Qt::Horizontal + + + + 7 + 5 + 0 + 0 + - - - - 5 - 5 - 6 - 0 - - - - - - - 5 - 5 - 1 - 0 - - - - Qt::LeftToRight - - - true - - - Qt::ElideRight - - - true - - - false - - - false - - - 1 - - - - 32 Users - - - - - @ Operators - - - - New Sub Item - - - - - New Item - - - - - New Item - - - - - - 4 Voiced + + 0 + + + + + 0 - - - New Item - - - - - New Item - - - - - New Item - - - - - - 19 Users + + 6 - - New Sub Item - + + + QFrame::StyledPanel + + + QFrame::Plain + + + + 9 + + + 6 + + + + + + Trebuchet MS + + + + <html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'Trebuchet MS'; font-size:11pt; font-weight:400; font-style:normal; text-decoration:none;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-size:60pt; color:#000055">Quassel IRC</span></p></body></html> + + + true + + + Qt::AlignCenter + + + + + - - - New Item - - - - - New Item - - - - - New Item - - - + @@ -227,11 +176,6 @@ - - ChatWidget - QWidget -
chatwidget.h
-
ChannelWidgetInput QLineEdit @@ -241,7 +185,6 @@ inputEdit ownNick - nickTree topicEdit chanSettingsButton diff --git a/gui/ui/connectionsettingspage.ui b/gui/ui/connectionsettingspage.ui new file mode 100644 index 00000000..82fff87d --- /dev/null +++ b/gui/ui/connectionsettingspage.ui @@ -0,0 +1,18 @@ + + ConnectionSettingsPage + + + + 0 + 0 + 666 + 457 + + + + Form + + + + + diff --git a/gui/ui/coresettingspage.ui b/gui/ui/coresettingspage.ui new file mode 100644 index 00000000..2e632703 --- /dev/null +++ b/gui/ui/coresettingspage.ui @@ -0,0 +1,18 @@ + + CoreSettingsPage + + + + 0 + 0 + 693 + 482 + + + + Form + + + + + diff --git a/gui/ui/mainwin.ui b/gui/ui/mainwin.ui index 32f9fa84..9e25be8d 100644 --- a/gui/ui/mainwin.ui +++ b/gui/ui/mainwin.ui @@ -12,14 +12,34 @@ MainWindow - + + + + 7 + 7 + 0 + 0 + + + + + 9 + + + 6 + + + + + + 0 0 800 - 32 + 28 @@ -38,11 +58,6 @@ - - - Views - - Settings @@ -53,10 +68,19 @@ + + + Views + + + + Help + + @@ -130,7 +154,32 @@ F7 + + + false + + + Manage Views... + + + + + About Qt... + + + + + About Quassel IRC... + + + + + BufferWidget + QWidget +
bufferwidget.h
+
+
diff --git a/gui/ui/networkview.ui b/gui/ui/networkview.ui index a30e8681..205afc82 100644 --- a/gui/ui/networkview.ui +++ b/gui/ui/networkview.ui @@ -5,34 +5,40 @@ 0 0 - 151 - 294 + 116 + 183 5 - 1 + 5 0 0 + + + 100 + 100 + + - Form + NetView - + - 9 + 4 - 6 + 0 5 - 1 + 5 0 0 @@ -43,6 +49,9 @@ 16777215 + + false + diff --git a/gui/ui/settingsdlg.ui b/gui/ui/settingsdlg.ui index 771012a2..a2097a4b 100644 --- a/gui/ui/settingsdlg.ui +++ b/gui/ui/settingsdlg.ui @@ -5,8 +5,8 @@ 0 0 - 848 - 514 + 808 + 568 @@ -20,49 +20,42 @@ 6 - - - 0 - - - 6 + + + Qt::Horizontal - - - - - 5 - 7 - 0 - 0 - - - - - Settings - - - - - - - - - 3 - 5 - 0 - 0 - + + + + 5 + 7 + 0 + 0 + + + + + Settings - - QFrame::StyledPanel - - - QFrame::Raised - - - - + +
+ + + + 5 + 5 + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + @@ -76,6 +69,13 @@ + + + QScrollArea + QFrame +
QScrollArea
+
+
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index bfdb7af4..32b82ba3 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,5 @@ -SET(main_SRCS global.cpp logger.cpp util.cpp message.cpp) -SET(main_HDRS util.h message.h) +SET(main_SRCS global.cpp logger.cpp util.cpp message.cpp settings.cpp) +SET(main_HDRS util.h message.h settings.h) SET(main_MOCS global.h logger.h) QT4_WRAP_CPP(_MOC ${main_MOCS}) diff --git a/main/main_gui.cpp b/main/main_gui.cpp index 3ee9f9f2..2d9c4e78 100644 --- a/main/main_gui.cpp +++ b/main/main_gui.cpp @@ -28,6 +28,7 @@ #include "guiproxy.h" #include "coreconnectdlg.h" #include "util.h" +#include "chatwidget.h" #include "mainwin.h" @@ -37,6 +38,8 @@ int main(int argc, char **argv) { QApplication::setApplicationName("Quassel IRC"); QApplication::setOrganizationName("The Quassel Team"); + qRegisterMetaType("LayoutTask"); + Global::runMode = Global::GUIOnly; Global::quasselDir = QDir::homePath() + "/.quassel"; @@ -46,9 +49,11 @@ int main(int argc, char **argv) { Style::init(); MainWin mainWin; + mainWin.init(); int exitCode = app.exec(); delete guiProxy; delete global; + return exitCode; } void MainWin::syncToCore() { diff --git a/main/main_mono.cpp b/main/main_mono.cpp index 268f3c1a..584c2207 100644 --- a/main/main_mono.cpp +++ b/main/main_mono.cpp @@ -27,6 +27,8 @@ #include "global.h" #include "guiproxy.h" #include "coreproxy.h" +#include "settings.h" +#include "chatwidget.h" #include "mainwin.h" @@ -36,22 +38,29 @@ int main(int argc, char **argv) { QApplication::setApplicationName("Quassel IRC"); QApplication::setOrganizationName("The Quassel Team"); + qRegisterMetaType("LayoutTask"); + Global::runMode = Global::Monolithic; Global::quasselDir = QDir::homePath() + "/.quassel"; + //settings = new Settings(); global = new Global(); guiProxy = new GUIProxy(); coreProxy = new CoreProxy(); + Settings::init(); Style::init(); - MainWin mainWin; - mainWin.show(); + MainWin *mainWin = new MainWin(); + mainWin->show(); + mainWin->init(); int exitCode = app.exec(); delete core; delete guiProxy; delete coreProxy; delete global; + delete mainWin; + //delete settings; return exitCode; } diff --git a/main/message.cpp b/main/message.cpp index 160c8288..6d1e7a8a 100644 --- a/main/message.cpp +++ b/main/message.cpp @@ -21,55 +21,55 @@ #include "message.h" #include -Message Message::plain(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::plain(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Plain, _text, _sender, _flags); } -Message Message::notice(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::notice(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Notice, _text, _sender, _flags); } -Message Message::action(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::action(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Action, _text, _sender, _flags); } -Message Message::kick(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::kick(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Kick, _text, _sender, _flags); } -Message Message::join(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::join(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Join, _text, _sender, _flags); } -Message Message::part(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::part(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Part, _text, _sender, _flags); } -Message Message::nick(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::nick(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Nick, _text, _sender, _flags); } -Message Message::mode(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::mode(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Mode, _text, _sender, _flags); } -Message Message::quit(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::quit(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Quit, _text, _sender, _flags); } -Message Message::kill(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::kill(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Kill, _text, _sender, _flags); } -Message Message::server(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::server(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Server, _text, _sender, _flags); } -Message Message::info(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::info(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Info, _text, _sender, _flags); } -Message Message::error(QString _target, QString _text, QString _sender, Flags _flags) { +Message Message::error(QString _target, QString _text, QString _sender, quint8 _flags) { return Message(_target, Error, _text, _sender, _flags); } @@ -85,10 +85,11 @@ QDataStream &operator>>(QDataStream &in, Message &msg) { QByteArray s, m, targ; in >> ts >> t >> f >> targ >> s >> m; msg.type = (Message::Type)t; - msg.flags = (Message::Flags)f; + msg.flags = (quint8)f; msg.timeStamp = QDateTime::fromTime_t(ts); msg.target = QString::fromUtf8(targ); msg.sender = QString::fromUtf8(s); msg.text = QString::fromUtf8(m); return in; } + diff --git a/main/message.h b/main/message.h index 711d7ad4..2426a0c8 100644 --- a/main/message.h +++ b/main/message.h @@ -27,31 +27,31 @@ struct Message { /** The different types a message can have for display */ enum Type { Plain, Notice, Action, Nick, Mode, Join, Part, Quit, Kick, Kill, Server, Info, Error }; - enum Flags { None = 0, Self = 1, Highlight = 2 }; + enum Flags { None = 0, Self = 1, PrivMsg = 2, Highlight = 4 }; Type type; - Flags flags; + quint8 flags; QString target; QString sender; QString text; QDateTime timeStamp; - Message(QString _target = "", Type _type = Plain, QString _text = "", QString _sender = "", Flags _flags = None) + Message(QString _target = "", Type _type = Plain, QString _text = "", QString _sender = "", quint8 _flags = None) : target(_target), text(_text), sender(_sender), type(_type), flags(_flags) { timeStamp = QDateTime::currentDateTime().toUTC(); } - static Message plain(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message notice(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message action(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message nick(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message mode(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message join(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message part(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message quit(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message kick(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message kill(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message server(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message info(QString _target, QString _text, QString _sender = "", Flags _flags = None); - static Message error(QString _target, QString _text, QString _sender = "", Flags _flags = None); + static Message plain(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message notice(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message action(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message nick(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message mode(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message join(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message part(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message quit(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message kick(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message kill(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message server(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message info(QString _target, QString _text, QString _sender = "", quint8 _flags = None); + static Message error(QString _target, QString _text, QString _sender = "", quint8 _flags = None); }; QDataStream &operator<<(QDataStream &out, const Message &msg); diff --git a/main/proxy_common.h b/main/proxy_common.h index 5fefd563..8ca9afb2 100644 --- a/main/proxy_common.h +++ b/main/proxy_common.h @@ -28,7 +28,7 @@ enum GUISignal { GS_CLIENT_INIT, GS_USER_INPUT, GS_REQUEST_CONNECT, GS_UPDATE_GL enum CoreSignal { CS_CORE_STATE, CS_SERVER_CONNECTED, CS_SERVER_DISCONNECTED, CS_SERVER_STATE, CS_DISPLAY_MSG, CS_DISPLAY_STATUS_MSG, CS_UPDATE_GLOBAL_DATA, CS_MODE_SET, CS_TOPIC_SET, CS_SET_NICKS, CS_NICK_ADDED, CS_NICK_REMOVED, CS_NICK_RENAMED, CS_NICK_UPDATED, - CS_OWN_NICK_SET, + CS_OWN_NICK_SET, CS_QUERY_REQUESTED }; diff --git a/main/settings.cpp b/main/settings.cpp new file mode 100644 index 00000000..6f2ba0f0 --- /dev/null +++ b/main/settings.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "settings.h" + + +Settings *settings; + + +void Settings::init() { + curProfile = QObject::tr("Default"); +} +/* +Settings::~Settings() { + qDebug() << "destructing"; + +} +*/ + +void Settings::setProfile(const QString &profile) { + curProfile = profile; +} + +void Settings::setGuiValue(const QString &key, const QVariant &value) { + QSettings s; + //s.setValue("GUI/Default/BufferStates/QuakeNet/#quassel/voicedExpanded", true); + //QString k = QString("GUI/%1/%2").arg(curProfile).arg(key); + s.setValue(QString("GUI/%1/%2").arg(curProfile).arg(key), value); +} + +QVariant Settings::guiValue(const QString &key, const QVariant &defaultValue) { + QSettings s; + return s.value(QString("GUI/%1/%2").arg(curProfile).arg(key), defaultValue); +} + +void Settings::setCoreValue(const QString &user, const QString &key, const QVariant &value) { + QSettings s; + s.setValue(QString("Core/%1/%2").arg(user).arg(key), value); +} + +QVariant Settings::coreValue(const QString &user, const QString &key, const QVariant &defaultValue) { + QSettings s; + return s.value(QString("Core/%1/%2").arg(user).arg(key), defaultValue); +} + +QString Settings::curProfile; diff --git a/main/settings.h b/main/settings.h new file mode 100644 index 00000000..f2e0d384 --- /dev/null +++ b/main/settings.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2005-07 by The Quassel Team * + * devel@quassel-irc.org * + * * + * 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. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include + +class Settings { + + public: + //Settings(); + //~Settings(); + static void init(); + static void setProfile(const QString &string); + static QString profile(); + + static void setGuiValue(const QString &key, const QVariant &value); + static QVariant guiValue (const QString &key, const QVariant &defaultValue = QVariant()); + static void setCoreValue(const QString &user, const QString &key, const QVariant &value); + static QVariant coreValue (const QString &user, const QString& key, const QVariant &defaultValue = QVariant()); + + private: + static QString curProfile; + +}; + +//extern Settings *settings; + +#endif diff --git a/plugins/plugin.h b/plugins/plugin.h index 5fa70524..6c4ef442 100644 --- a/plugins/plugin.h +++ b/plugins/plugin.h @@ -63,7 +63,9 @@ Q_DECLARE_INTERFACE(CorePluginInterface, */ class SettingsInterface { public: - virtual QWidget *settingsWidget(QWidget *parent = 0) = 0; + virtual QString category() = 0; + virtual QString title() = 0; + virtual QWidget *settingsWidget() = 0; }; -- 2.20.1