diff --git a/src/viewer/channeloptionsdialog.cpp b/src/viewer/channeloptionsdialog.cpp index 491a5c48..b5479ba7 100644 --- a/src/viewer/channeloptionsdialog.cpp +++ b/src/viewer/channeloptionsdialog.cpp @@ -1,601 +1,612 @@ /* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* Copyright (C) 2005-2007 Peter Simonsson Copyright (C) 2006 Dario Abatianni Copyright (C) 2006-2007 Eike Hein */ #include "channeloptionsdialog.h" #include "application.h" #include "channel.h" #include "topichistorymodel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Konversation { ChannelOptionsDialog::ChannelOptionsDialog(Channel *channel) : QDialog(channel) { setWindowTitle( i18n("Channel Settings for %1", channel->getName() ) ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ChannelOptionsDialog::changeOptions); connect(buttonBox, &QDialogButtonBox::rejected, this, &ChannelOptionsDialog::reject); mainLayout->addWidget(buttonBox); buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); Q_ASSERT(channel); m_channel = channel; m_ui.setupUi(mainWidget); m_ui.addBan->setIcon(QIcon::fromTheme("list-add")); m_ui.updateBan->setIcon(QIcon::fromTheme("edit-rename")); m_ui.removeBan->setIcon(QIcon::fromTheme("list-remove")); QStandardItemModel *modesModel = new QStandardItemModel(m_ui.otherModesList); m_ui.otherModesList->setModel(modesModel); m_ui.otherModesList->hide(); m_ui.banListSearchLine->setTreeWidget(m_ui.banList); - installEventFilter(m_ui.topicHistoryView); m_ui.topicHistoryView->setServer(m_channel->getServer()); m_ui.topicHistoryView->setModel(m_channel->getTopicHistory()); m_ui.topicHistorySearchLine->setProxy(static_cast(m_ui.topicHistoryView->model())); m_ui.topicHistorySearchLine->lineEdit()->setPlaceholderText(QString()); m_editingTopic = false; m_ui.topicEdit->setChannel(channel); m_ui.topicEdit->setMaximumLength(m_channel->getServer()->topicLength()); connect(m_ui.topicHistoryView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(topicHistoryItemClicked(QItemSelection))); connect(m_ui.toggleAdvancedModes, &QPushButton::clicked, this, &ChannelOptionsDialog::toggleAdvancedModes); connect(m_ui.topicEdit, &TopicEdit::undoAvailable, this, &ChannelOptionsDialog::topicBeingEdited); connect(this, &ChannelOptionsDialog::finished, m_ui.topicEdit, &TopicEdit::clear); connect(m_channel, &Channel::modesChanged, this, &ChannelOptionsDialog::refreshModes); connect(m_channel->getServer(), SIGNAL(channelNickChanged(QString)), this, SLOT(refreshEnableModes())); connect(m_channel, &Channel::banAdded, this, &ChannelOptionsDialog::addBan); connect(m_channel, &Channel::banRemoved, this, &ChannelOptionsDialog::removeBan); connect(m_channel, &Channel::banListCleared, m_ui.banList, &QTreeWidget::clear); connect(m_ui.addBan, &QPushButton::clicked, this, &ChannelOptionsDialog::addBanClicked); connect(m_ui.updateBan, &QPushButton::clicked, this, &ChannelOptionsDialog::updateBanClicked); connect(m_ui.removeBan, &QPushButton::clicked, this, &ChannelOptionsDialog::removeBanClicked); connect(m_ui.banList, &QTreeWidget::itemSelectionChanged, this, &ChannelOptionsDialog::banSelectionChanged); connect(m_ui.hostmask, &KLineEdit::textChanged, this, &ChannelOptionsDialog::hostmaskChanged); m_ui.topicModeChBox->setWhatsThis(whatsThisForMode('T')); m_ui.messageModeChBox->setWhatsThis(whatsThisForMode('N')); m_ui.secretModeChBox->setWhatsThis(whatsThisForMode('S')); m_ui.inviteModeChBox->setWhatsThis(whatsThisForMode('I')); m_ui.moderatedModeChBox->setWhatsThis(whatsThisForMode('M')); m_ui.keyModeChBox->setWhatsThis(whatsThisForMode('P')); m_ui.keyModeEdit->setWhatsThis(whatsThisForMode('P')); m_ui.userLimitEdit->setWhatsThis(whatsThisForMode('L')); m_ui.userLimitChBox->setWhatsThis(whatsThisForMode('L')); refreshBanList(); resize(QSize(450, 420)); } ChannelOptionsDialog::~ChannelOptionsDialog() { } void ChannelOptionsDialog::showEvent(QShowEvent* event) { if (!event->spontaneous()) { refreshAllowedChannelModes(); refreshModes(); + m_ui.topicEdit->clear(); + m_editingTopic = false; + + m_ui.topicHistoryView->selectionModel()->clearSelection(); + const QModelIndex& currentTopic = m_ui.topicHistoryView->model()->index(m_ui.topicHistoryView->model()->rowCount() - 1, 0); + m_ui.topicHistoryView->selectionModel()->select(currentTopic, QItemSelectionModel::Select); + m_ui.topicHistoryView->scrollTo(currentTopic, QAbstractItemView::EnsureVisible); + if (!m_ui.topicEdit->isReadOnly()) m_ui.topicEdit->setFocus(); KConfigGroup config(KSharedConfig::openConfig(), "ChannelOptionsDialog"); resize(config.readEntry("Size", sizeHint())); const QList& sizes = config.readEntry("SplitterSizes", QList()); if (!sizes.isEmpty()) m_ui.splitter->setSizes(sizes); Preferences::restoreColumnState(m_ui.banList, "BanList ViewSettings"); } QDialog::showEvent(event); } void ChannelOptionsDialog::hideEvent(QHideEvent* event) { KConfigGroup config(KSharedConfig::openConfig(), "ChannelOptionsDialog"); config.writeEntry("Size", size()); config.writeEntry("SplitterSizes", m_ui.splitter->sizes()); Preferences::saveColumnState(m_ui.banList, "BanList ViewSettings"); QDialog::hideEvent(event); } void ChannelOptionsDialog::changeOptions() { const QString& newTopic = topic(); const QString& oldTopic = m_channel->getTopic(); if (newTopic != oldTopic) { // Pass a ^A so we can determine if we want to clear the channel topic. if (newTopic.isEmpty()) { if (!oldTopic.isEmpty()) m_channel->sendText(Preferences::self()->commandChar() + "TOPIC " + m_channel->getName() + " \x01"); } else m_channel->sendText(Preferences::self()->commandChar() + "TOPIC " + m_channel->getName() + ' ' + newTopic); } QStringList newModeList = modes(); QStringList currentModeList = m_channel->getModeList(); QStringList rmModes; QStringList addModes; QStringList tmp; QString modeString; bool plus; QString command("MODE %1 %2%3 %4"); for(QStringList::ConstIterator it = newModeList.constBegin(); it != newModeList.constEnd(); ++it) { modeString = (*it).mid(1); plus = ((*it)[0] == '+'); tmp = currentModeList.filter(QRegExp('^' + modeString)); if(tmp.isEmpty() && plus) { m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("+").arg(modeString[0]).arg(modeString.mid(1))); } else if(!tmp.isEmpty() && !plus) { //FIXME: Bahamuth requires the key parameter for -k, but ircd breaks on -l with limit number. //Hence two versions of this. if (modeString[0] == 'k') m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("-").arg(modeString[0]).arg(modeString.mid(1))); else m_channel->getServer()->queue(command.arg(m_channel->getName()).arg("-").arg(modeString[0]).arg(QString())); } } hide(); } void ChannelOptionsDialog::toggleAdvancedModes() { bool ison = m_ui.toggleAdvancedModes->isChecked(); m_ui.otherModesList->setVisible(ison); if(ison) { m_ui.toggleAdvancedModes->setText(i18n("&Hide Advanced Modes <<")); } else { m_ui.toggleAdvancedModes->setText(i18n("&Show Advanced Modes >>")); } } void ChannelOptionsDialog::topicBeingEdited(bool edited) { m_editingTopic = edited; m_ui.topicHistoryView->setTextSelectable(edited); } QString ChannelOptionsDialog::topic() { return m_ui.topicEdit->toPlainText().replace('\n', ' '); } void ChannelOptionsDialog::topicHistoryItemClicked(const QItemSelection& selection) { if (!m_editingTopic) { + m_ui.topicEdit->clear(); + if (!selection.isEmpty()) + { + m_ui.topicEdit->setUndoRedoEnabled(false); m_ui.topicEdit->setPlainText(m_ui.topicHistoryView->model()->data(selection.indexes().first()).toString()); - else - m_ui.topicEdit->clear(); + m_ui.topicEdit->setUndoRedoEnabled(true); + } } } void ChannelOptionsDialog::refreshEnableModes(bool forceUpdate) { if(!m_channel->getOwnChannelNick() || m_channel->getOwnChannelNick()->isChanged() || forceUpdate) { // cache the value m_isAnyTypeOfOp = m_channel->getOwnChannelNick() ? m_channel->getOwnChannelNick()->isAnyTypeOfOp() : false; m_ui.topicEdit->setReadOnly(!m_isAnyTypeOfOp && m_ui.topicModeChBox->isChecked()); m_ui.topicModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.messageModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.userLimitChBox->setEnabled(m_isAnyTypeOfOp); m_ui.userLimitEdit->setEnabled(m_isAnyTypeOfOp); m_ui.inviteModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.moderatedModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.secretModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.keyModeChBox->setEnabled(m_isAnyTypeOfOp); m_ui.keyModeEdit->setEnabled(m_isAnyTypeOfOp); QStandardItemModel* model = qobject_cast(m_ui.otherModesList->model()); if (model) { QList items = model->findItems("*", Qt::MatchWildcard, 0); items += model->findItems("*", Qt::MatchWildcard, 1); foreach (QStandardItem* item, items) item->setEnabled(m_isAnyTypeOfOp); } m_ui.addBan->setEnabled(m_isAnyTypeOfOp); m_ui.updateBan->setEnabled(m_isAnyTypeOfOp); m_ui.removeBan->setEnabled(m_isAnyTypeOfOp); banSelectionChanged(); m_ui.hostmask->setEnabled(m_isAnyTypeOfOp); hostmaskChanged(m_ui.hostmask->text()); } } void ChannelOptionsDialog::refreshAllowedChannelModes() { QString modeString = m_channel->getServer()->allowedChannelModes(); // These modes are handled in a special way: ntimslkbeI modeString.remove('t'); modeString.remove('n'); modeString.remove('l'); modeString.remove('i'); modeString.remove('m'); modeString.remove('s'); modeString.remove('k'); modeString.remove('b'); modeString.remove('e'); modeString.remove('I'); modeString.remove('O'); modeString.remove('o'); modeString.remove('v'); QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); modesModel->clear(); modesModel->setHorizontalHeaderLabels(QStringList() << i18n("Mode") << i18n("Parameter")); for(int i = 0; i < modeString.length(); i++) { QList newRow; QStandardItem *item = 0; if(!Preferences::self()->useLiteralModes() && getChannelModesHash().contains(modeString[i])) item = new QStandardItem(i18nc(" ()","%1 (%2)", modeString[i], getChannelModesHash().value(modeString[i]))); else item = new QStandardItem(QString(modeString[i])); item->setData(QString(modeString[i])); item->setCheckable(true); item->setEditable(false); newRow.append(item); item = new QStandardItem(); item->setEditable(true); newRow.append(item); modesModel->invisibleRootItem()->appendRow(newRow); } } void ChannelOptionsDialog::refreshModes() { QStringList modes = m_channel->getModeList(); m_ui.topicModeChBox->setChecked(false); m_ui.messageModeChBox->setChecked(false); m_ui.userLimitChBox->setChecked(false); m_ui.userLimitEdit->setValue(0); m_ui.inviteModeChBox->setChecked(false); m_ui.moderatedModeChBox->setChecked(false); m_ui.secretModeChBox->setChecked(false); m_ui.keyModeChBox->setChecked(false); m_ui.keyModeEdit->setText(QString()); QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); for (int i = 0; i < modesModel->rowCount(); ++i) { modesModel->item(i, 0)->setCheckState(Qt::Unchecked); } char mode; foreach (const QString ¤tMode, modes) { mode = currentMode[0].toLatin1(); switch(mode) { case 't': m_ui.topicModeChBox->setChecked(true); break; case 'n': m_ui.messageModeChBox->setChecked(true); break; case 'l': m_ui.userLimitChBox->setChecked(true); m_ui.userLimitEdit->setValue(currentMode.mid(1).toInt()); break; case 'i': m_ui.inviteModeChBox->setChecked(true); break; case 'm': m_ui.moderatedModeChBox->setChecked(true); break; case 's': m_ui.secretModeChBox->setChecked(true); break; case 'k': m_ui.keyModeChBox->setChecked(true); m_ui.keyModeEdit->setText(currentMode.mid(1)); break; default: { bool found = false; QString modeString; modeString = mode; for (int i = 0; !found && i < modesModel->rowCount(); ++i) { QStandardItem *item = modesModel->item(i, 0); if (item->data().toString() == modeString) { found = true; item->setCheckState(Qt::Checked); modesModel->item(i, 1)->setText(currentMode.mid(1)); } } break; } } } refreshEnableModes(true); } QStringList ChannelOptionsDialog::modes() { QStringList modes; QString mode; mode = (m_ui.topicModeChBox->isChecked() ? "+" : "-"); mode += 't'; modes.append(mode); mode = (m_ui.messageModeChBox->isChecked() ? "+" : "-"); mode += 'n'; modes.append(mode); mode = (m_ui.userLimitChBox->isChecked() ? "+" : "-"); mode += 'l' + QString::number( m_ui.userLimitEdit->value() ); modes.append(mode); mode = (m_ui.inviteModeChBox->isChecked() ? "+" : "-"); mode += 'i'; modes.append(mode); mode = (m_ui.moderatedModeChBox->isChecked() ? "+" : "-"); mode += 'm'; modes.append(mode); mode = (m_ui.secretModeChBox->isChecked() ? "+" : "-"); mode += 's'; modes.append(mode); if (m_ui.keyModeChBox->isChecked() && !m_ui.keyModeEdit->text().isEmpty()) { mode = '+'; mode += 'k' + m_ui.keyModeEdit->text(); modes.append(mode); } else if (!m_ui.keyModeChBox->isChecked()) { mode = '-'; mode += 'k' + m_ui.keyModeEdit->text(); modes.append(mode); } QStandardItemModel *modesModel = qobject_cast(m_ui.otherModesList->model()); for (int i = 0; i < modesModel->rowCount(); ++i) { mode = (modesModel->item(i, 0)->checkState() == Qt::Checked ? "+" : "-"); mode += modesModel->item(i, 0)->data().toString() + modesModel->item(i, 1)->text(); modes.append(mode); } return modes; } // Ban List tab related functions void ChannelOptionsDialog::refreshBanList() { QStringList banlist = m_channel->getBanList(); m_ui.banList->clear(); for (QStringList::const_iterator it = --banlist.constEnd(); it != --banlist.constBegin(); --it) addBan((*it)); } void ChannelOptionsDialog::addBan(const QString& newban) { BanListViewItem *item = new BanListViewItem(m_ui.banList, newban.section(' ', 0, 0), newban.section(' ', 1, 1).section('!', 0, 0), newban.section(' ', 2 ,2).toUInt()); // set item as current item m_ui.banList->setCurrentItem(item); // update button states hostmaskChanged(m_ui.hostmask->text()); } void ChannelOptionsDialog::removeBan(const QString& ban) { QList items = m_ui.banList->findItems(ban, Qt::MatchCaseSensitive | Qt::MatchExactly, 0); if (items.count() > 0) delete items.at(0); } void ChannelOptionsDialog::addBanClicked() { QString newHostmask = m_ui.hostmask->text(); if (!newHostmask.isEmpty()) m_channel->getServer()->requestBan(QStringList(newHostmask), m_channel->getName(), QString()); } void ChannelOptionsDialog::removeBanClicked() { QString oldHostmask = m_ui.banList->currentItem()->text(0); // We delete the existing item because it's possible the server may // Modify the ban causing us not to catch it. If that happens we'll be // stuck with a stale item and a new item with the modified hostmask. delete m_ui.banList->currentItem(); // request unban m_channel->getServer()->requestUnban(oldHostmask, m_channel->getName()); } void ChannelOptionsDialog::updateBanClicked() { QString oldHostmask = m_ui.banList->currentItem()->text(0); QString newHostmask = m_ui.hostmask->text(); if (!newHostmask.isEmpty() && newHostmask.compare(oldHostmask)) { // We delete the existing item because it's possible the server may // Modify the ban causing us not to catch it. If that happens we'll be // stuck with a stale item and a new item with the modified hostmask. delete m_ui.banList->currentItem(); // request unban for the of the old hostmask m_channel->getServer()->requestUnban(oldHostmask, m_channel->getName()); // request ban for the of the old hostmask m_channel->getServer()->requestBan(QStringList(newHostmask), m_channel->getName(), QString()); } } /// Enables/disables updateBan and removeBan buttons depending on the currentItem of the banList void ChannelOptionsDialog::banSelectionChanged() { if (m_ui.banList->currentItem()) { m_ui.updateBan->setEnabled(m_isAnyTypeOfOp); m_ui.removeBan->setEnabled(m_isAnyTypeOfOp); // update line edit content m_ui.hostmask->setText(m_ui.banList->currentItem()->text(0)); } else { m_ui.updateBan->setEnabled(false); m_ui.removeBan->setEnabled(false); } } /// Enables/disables addBan and updateBan buttons depending on the value of @p text void ChannelOptionsDialog::hostmaskChanged(const QString& text) { if (text.trimmed().length() != 0) { if (m_isAnyTypeOfOp) { QList items = m_ui.banList->findItems(text, Qt::MatchExactly | Qt::MatchCaseSensitive, 0); m_ui.addBan->setEnabled(items.count() == 0); m_ui.updateBan->setEnabled(items.count() == 0 && m_ui.banList->currentItem()); } } else { m_ui.addBan->setEnabled(false); m_ui.updateBan->setEnabled(false); } } // This is our implementation of BanListViewItem BanListViewItem::BanListViewItem(QTreeWidget *parent) : QTreeWidgetItem() { parent->addTopLevelItem(this); } BanListViewItem::BanListViewItem (QTreeWidget *parent, const QString& label1, const QString& label2, uint timestamp) : QTreeWidgetItem() { setText(0, label1); setText(1, label2); m_timestamp.setTime_t(timestamp); setText(2, QLocale().toString(m_timestamp, QLocale::ShortFormat)); setData(2, Qt::UserRole, m_timestamp); parent->addTopLevelItem(this); } bool BanListViewItem::operator<(const QTreeWidgetItem &item) const { if (treeWidget()->sortColumn() == 2) { QVariant userdata = item.data(2, Qt::UserRole); if (userdata.isValid() && userdata.type() == QVariant::DateTime) { return m_timestamp < userdata.toDateTime(); } } return text(treeWidget()->sortColumn()) < item.text(treeWidget()->sortColumn()); } } QString Konversation::ChannelOptionsDialog::whatsThisForMode(char mode) { switch (mode) { case 'T': return i18n("

These control the mode of the channel. Only an operator can change these.

The Topic mode means that only the channel operator can change the topic for the channel.

"); case 'N': return i18n("

These control the mode of the channel. Only an operator can change these.

No messages from outside means users who are not in the channel cannot send messages for everybody in the channel to see. Almost all channels have this set to prevent nuisance messages.

"); case 'S': return i18n("

These control the mode of the channel. Only an operator can change these.

A Secret channel will not show up in the channel list, nor will any user be able to see that you are in the channel with the WHOIS command or anything similar. Only the people that are in the same channel will know that you are in this channel, if this mode is set.

"); case 'I': return i18n("

These control the mode of the channel. Only an operator can change these.

An Invite only channel means that people can only join the channel if they are invited. To invite someone, a channel operator needs to issue the command /invite nick from within the channel.

"); case 'P': return i18n("

These control the mode of the channel. Only an operator can change these.

A Private channel is shown in a listing of all channels, but the topic is not shown. A user's WHOIS may or may not show them as being in a private channel depending on the IRC server.

"); case 'M': return i18n("

These control the mode of the channel. Only an operator can change these.

A Moderated channel is one where only operators, half-operators and those with voice can talk.

"); case 'K': return i18n("

These control the mode of the channel. Only an operator can change these.

A protected channel requires users to enter a password in order to join.

"); case 'L': return i18n("

These control the mode of the channel. Only an operator can change these.

A channel that has a user Limit means that only that many users can be in the channel at any one time. Some channels have a bot that sits in the channel and changes this automatically depending on how busy the channel is.

"); default: qWarning() << "called for unknown mode" << mode; return QString(); } } diff --git a/src/viewer/topichistoryview.cpp b/src/viewer/topichistoryview.cpp index 4f111f3b..40a25e75 100644 --- a/src/viewer/topichistoryview.cpp +++ b/src/viewer/topichistoryview.cpp @@ -1,328 +1,311 @@ /* 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor appro- ved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see http://www.gnu.org/licenses/. */ /* Copyright (C) 2012 Eike Hein */ #include "topichistoryview.h" #include "application.h" #include "irccontextmenus.h" #include "topichistorymodel.h" #include #define MARGIN 2 TopicHistorySortfilterProxyModel::TopicHistorySortfilterProxyModel(QObject* parent) : KCategorizedSortFilterProxyModel(parent) { setCategorizedModel(true); } TopicHistorySortfilterProxyModel::~TopicHistorySortfilterProxyModel() { } QVariant TopicHistorySortfilterProxyModel::data(const QModelIndex& index, int role) const { if (role == KCategorizedSortFilterProxyModel::CategoryDisplayRole) { const QModelIndex& sourceIndex = mapToSource(index); const QString& author = sourceModel()->data(sourceIndex.sibling(sourceIndex.row(), 1)).toString(); const QString& timestamp = sourceModel()->data(sourceIndex.sibling(sourceIndex.row(), 2)).toString(); return i18nc("%1 is a timestamp, %2 is the author's name", "On %1 by %2", timestamp, author); } else if (role == KCategorizedSortFilterProxyModel::CategorySortRole) { const QModelIndex& sourceIndex = mapToSource(index); return sourceModel()->data(sourceIndex.sibling(sourceIndex.row(), 2)).toDateTime().toTime_t(); } return KCategorizedSortFilterProxyModel::data(index, role); } void TopicHistorySortfilterProxyModel::setSourceModel(QAbstractItemModel* model) { if (sourceModel()) disconnect(sourceModel(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); KCategorizedSortFilterProxyModel::setSourceModel(model); connect(model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(sourceDataChanged(QModelIndex,QModelIndex))); } bool TopicHistorySortfilterProxyModel::filterAcceptsColumn(int source_column, const QModelIndex& source_parent) const { Q_UNUSED(source_parent); return (source_column == 0); } void TopicHistorySortfilterProxyModel::sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { Q_UNUSED(topLeft); Q_UNUSED(bottomRight); emit layoutAboutToBeChanged(); emit layoutChanged(); } TopicHistoryLabel::TopicHistoryLabel(QWidget* parent) : KTextEdit(parent) { viewport()->setAutoFillBackground(false); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFrameStyle(QFrame::NoFrame); document()->setDocumentMargin(2); setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); setAcceptRichText(false); setReadOnly(true); setTextSelectable(false); } TopicHistoryLabel::~TopicHistoryLabel() { } void TopicHistoryLabel::setTextSelectable(bool selectable) { setAttribute(Qt::WA_TransparentForMouseEvents, !selectable); setTextInteractionFlags(selectable ? Qt::TextBrowserInteraction : Qt::NoTextInteraction); if (!selectable) setTextCursor(QTextCursor()); } TopicHistoryItemDelegate::TopicHistoryItemDelegate(QAbstractItemView* itemView, QObject* parent) : KWidgetItemDelegate(itemView, parent) { m_hiddenLabel = new TopicHistoryLabel(itemView); m_hiddenLabel->setFixedHeight(0); m_hiddenLabel->lower(); m_shownBefore = false; itemView->installEventFilter(this); } TopicHistoryItemDelegate::~TopicHistoryItemDelegate() { } bool TopicHistoryItemDelegate::eventFilter(QObject* watched, QEvent* event) { Q_UNUSED(watched); // NOTE: QTextEdit needs to have been shown at least once (and while its // parents are shown, too) before it starts to calculate the document sizes // we need in sizeHint(). if (!m_shownBefore && event->type() == QEvent::Show && !event->spontaneous()) { m_hiddenLabel->show(); m_hiddenLabel->hide(); } return false; } QList TopicHistoryItemDelegate::createItemWidgets(const QModelIndex& index) const { Q_UNUSED(index) QList widgets; TopicHistoryLabel* label = new TopicHistoryLabel(); connect(static_cast(itemView()), SIGNAL(textSelectableChanged(bool)), label, SLOT(setTextSelectable(bool))); widgets << label; return widgets; } void TopicHistoryItemDelegate::updateItemWidgets(const QList widgets, const QStyleOptionViewItem& option, const QPersistentModelIndex& index) const { if (widgets.isEmpty()) return; TopicHistoryView* historyView = static_cast(itemView()); TopicHistoryLabel* label = static_cast(widgets[0]); QPalette::ColorRole colorRole = !historyView->textSelectable() && historyView->selectionModel()->isRowSelected(index.row(), index.parent()) ? QPalette::HighlightedText : QPalette::Text; QPalette::ColorGroup colorGroup = historyView->hasFocus() ? QPalette::Active : QPalette::Inactive; const QColor& color = historyView->palette().color(colorGroup, colorRole); label->setTextColor(color); label->setPlainText(index.model()->data(index).toString()); label->setGeometry(QRect(0, 0, option.rect.width(), option.rect.height() - (2 * MARGIN))); } void TopicHistoryItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(index); if (!static_cast(itemView())->textSelectable()) { QStyleOptionViewItem* hack = const_cast(&option); hack->rect.setHeight(hack->rect.height() - (2 * MARGIN) - 1); itemView()->style()->drawPrimitive(QStyle::PE_PanelItemViewItem, hack, painter, 0); } } QSize TopicHistoryItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { Q_UNUSED(option); m_hiddenLabel->setPlainText(index.model()->data(index).toString()); m_hiddenLabel->setFixedWidth(itemView()->viewport()->width() - (2 * static_cast(itemView())->categorySpacing()) - (2 * MARGIN)); int documentHeight = m_hiddenLabel->document()->size().toSize().height(); return QSize(itemView()->viewport()->width(), documentHeight + (2 * MARGIN)); } TopicHistoryView::TopicHistoryView(QWidget* parent): KCategorizedView(parent) { m_proxyModel = new TopicHistorySortfilterProxyModel(this); m_textSelectable = false; setCategoryDrawer(new KCategoryDrawer(this)); setModelColumn(0); setItemDelegateForColumn(0, new TopicHistoryItemDelegate(this, this)); setVerticalScrollMode(QListView::ScrollPerPixel); setWhatsThis(i18n("This is a list of all topics that have been set for this channel " "while its tab was open.\n\n" "If the same topic is set multiple times consecutively by someone, " "only the final occurrence is shown.\n\n" "When you select a topic in the list, the edit field below it will " "receive its text. Once you start modifying the contents of the field, " "however, the list will switch from the regular entry selection mode to " "allowing you to perform text selection on the entries in case you may " "wish to incorporate some of their text into the new topic. To return to " "entry selection mode and a synchronized edit field, undo back to the " "original text or close and reopen the dialog.")); connect(Application::instance(), &Application::appearanceChanged, this, &TopicHistoryView::updateSelectedItemWidgets); } TopicHistoryView::~TopicHistoryView() { } bool TopicHistoryView::textSelectable() const { return m_textSelectable; } void TopicHistoryView::setTextSelectable(bool selectable) { if (selectable != m_textSelectable) { m_textSelectable = selectable; updateSelectedItemWidgets(); emit textSelectableChanged(selectable); } } void TopicHistoryView::setModel(QAbstractItemModel* model) { m_proxyModel->setSourceModel(model); KCategorizedView::setModel(m_proxyModel); } -bool TopicHistoryView::eventFilter(QObject* watched, QEvent* event) -{ - Q_UNUSED(watched); - - if (event->type() == QEvent::Show && !event->spontaneous()) - { - selectionModel()->clearSelection(); - - const QModelIndex& currentTopic = model()->index(model()->rowCount() - 1, 0); - - selectionModel()->select(currentTopic, QItemSelectionModel::Select); - scrollTo(currentTopic, QAbstractItemView::EnsureVisible); - } - - return false; -} - void TopicHistoryView::resizeEvent(QResizeEvent* event) { KCategorizedView::resizeEvent(event); const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); if (!selectedIndexes.isEmpty()) scrollTo(selectedIndexes.first(), QAbstractItemView::EnsureVisible); } void TopicHistoryView::contextMenuEvent(QContextMenuEvent* event) { const QModelIndex& sourceIndex = m_proxyModel->mapToSource(indexAt(event->pos())); QAbstractItemModel* sourceModel = m_proxyModel->sourceModel(); const QString& text = sourceModel->data(sourceModel->index(sourceIndex.row(), 0)).toString(); QString author = sourceModel->data(sourceModel->index(sourceIndex.row(), 1)).toString(); if (author == TopicHistoryModel::authorPlaceholder()) author.clear(); IrcContextMenus::topicHistoryMenu(event->globalPos(), m_server, text, author); } void TopicHistoryView::updateSelectedItemWidgets() { // KWidgetItemDelegate::updateItemWidgets() is documented to run in response to // data changes, so in order to update our widgets outside of data changes we // fake a data change. const QModelIndexList selectedIndexes = selectionModel()->selectedIndexes(); if (!selectedIndexes.isEmpty()) m_proxyModel->dataChanged(selectedIndexes.first(), selectedIndexes.first()); } void TopicHistoryView::updateGeometries() { KCategorizedView::updateGeometries(); setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } diff --git a/src/viewer/topichistoryview.h b/src/viewer/topichistoryview.h index d3365632..8e155d21 100644 --- a/src/viewer/topichistoryview.h +++ b/src/viewer/topichistoryview.h @@ -1,142 +1,140 @@ /* 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) version 3 or any later version accepted by the membership of KDE e.V. (or its successor appro- ved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. 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, see http://www.gnu.org/licenses/. */ /* Copyright (C) 2005-2007 Peter Simonsson Copyright (C) 2012 Eike Hein */ #ifndef TOPICHISTORYVIEW_H #define TOPICHISTORYVIEW_H #include #include #include #include class Server; class TopicHistorySortfilterProxyModel : public KCategorizedSortFilterProxyModel { Q_OBJECT friend class TopicHistoryView; public: explicit TopicHistorySortfilterProxyModel(QObject* parent = 0); ~TopicHistorySortfilterProxyModel(); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; void setSourceModel(QAbstractItemModel* model); protected: bool filterAcceptsColumn ( int source_column, const QModelIndex & source_parent ) const; private Q_SLOTS: void sourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); }; class TopicHistoryLabel : public KTextEdit { Q_OBJECT public: explicit TopicHistoryLabel(QWidget* parent = 0); ~TopicHistoryLabel(); public Q_SLOTS: void setTextSelectable(bool selectable); }; class TopicHistoryItemDelegate : public KWidgetItemDelegate { Q_OBJECT public: explicit TopicHistoryItemDelegate(QAbstractItemView* itemView, QObject* parent = 0); ~TopicHistoryItemDelegate(); void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; bool eventFilter(QObject* watched, QEvent* event); protected: QList createItemWidgets (const QModelIndex& index) const; void updateItemWidgets(const QList widgets, const QStyleOptionViewItem& option, const QPersistentModelIndex& index) const; private: TopicHistoryLabel* m_hiddenLabel; bool m_shownBefore; }; class TopicHistoryView : public KCategorizedView { Q_OBJECT public: explicit TopicHistoryView(QWidget* parent = 0); ~TopicHistoryView(); void setServer(Server* server) { m_server = server; } bool textSelectable() const; void setTextSelectable(bool selectable); void setModel(QAbstractItemModel* model); - bool eventFilter(QObject* watched, QEvent* event); - Q_SIGNALS: void textSelectableChanged(bool selectable); protected: void resizeEvent(QResizeEvent* event); void contextMenuEvent (QContextMenuEvent* event); void updateGeometries(); private Q_SLOTS: void updateSelectedItemWidgets(); private: Server* m_server; TopicHistorySortfilterProxyModel* m_proxyModel; bool m_textSelectable; }; #endif struct Topic;