1 /***************************************************************************
2 * Copyright (C) 2005-08 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 #include "bufferview.h"
24 #include "buffermodel.h"
25 #include "bufferviewfilter.h"
26 #include "buffersettings.h"
27 #include "buffersyncer.h"
29 #include "iconloader.h"
30 #include "mappedselectionmodel.h"
32 #include "networkmodel.h"
34 #include "uisettings.h"
38 #include <QHeaderView>
39 #include <QInputDialog>
42 #include <QMessageBox>
45 /*****************************************
46 * The TreeView showing the Buffers
47 *****************************************/
48 // Please be carefull when reimplementing methods which are used to inform the view about changes to the data
49 // to be on the safe side: call QTreeView's method aswell
50 BufferView::BufferView(QWidget *parent)
52 showChannelList(tr("Show Channel List"), this),
56 showChannelList.setIcon(SmallIcon("format-list-unordered"));
58 connect(this, SIGNAL(collapsed(const QModelIndex &)), SLOT(on_collapse(const QModelIndex &)));
59 connect(this, SIGNAL(expanded(const QModelIndex &)), SLOT(on_expand(const QModelIndex &)));
61 setSelectionMode(QAbstractItemView::ExtendedSelection);
64 void BufferView::init() {
66 header()->setContextMenuPolicy(Qt::ActionsContextMenu);
73 #ifndef QT_NO_DRAGANDDROP
76 setDropIndicatorShown(true);
79 setSortingEnabled(true);
80 sortByColumn(0, Qt::AscendingOrder);
82 // activated() fails on X11 and Qtopia at least
83 #if defined Q_WS_QWS or defined Q_WS_X11
84 connect(this, SIGNAL(doubleClicked(QModelIndex)), SLOT(joinChannel(QModelIndex)));
86 // afaik this is better on Mac and Windows
87 connect(this, SIGNAL(activated(QModelIndex)), SLOT(joinChannel(QModelIndex)));
91 void BufferView::setupMenuActions() {
92 _menuActions.addAction("ConnectNet", new Action(SmallIcon("network-connect"), tr("Connect"), this));
93 _menuActions.addAction("DisconnectNet", new Action(SmallIcon("network-disconnect"), tr("Disconnect"), this));
94 _menuActions.addAction("JoinChannel", new Action(tr("Join Channel"), this));
95 _menuActions.addAction("JoinBuffer", new Action(tr("Join"), this));
96 _menuActions.addAction("PartBuffer", new Action(tr("Part"), this));
97 _menuActions.addAction("HideBuffersTemporarily", new Action(tr("Hide Buffers"), this));
98 _menuActions.addAction("HideBuffersPermanently", new Action(tr("Hide Buffers Permanently"), this));
99 _menuActions.addAction("RemoveBuffer", new Action(tr("Delete Buffer"), this));
100 _menuActions.addAction("IgnoreList", new Action(tr("Ignore List"), this));
101 _menuActions.addAction("HideJoin", new Action(tr("Joins"), this))->setCheckable(true);
102 _menuActions.addAction("HidePart", new Action(tr("Parts"), this))->setCheckable(true);
103 _menuActions.addAction("HideQuit", new Action(tr("Quits"), this))->setCheckable(true);
104 _menuActions.addAction("HideNick", new Action(tr("Nick Changes"), this))->setCheckable(true);
105 _menuActions.addAction("HideMode", new Action(tr("Mode Changes"), this))->setCheckable(true);
106 _menuActions.addAction("HideDayChange", new Action(tr("Day Change"), this))->setCheckable(true);
108 connect(&_menuActions, SIGNAL(actionTriggered(QAction *)), SLOT(actionTriggered(QAction *)));
111 void BufferView::setModel(QAbstractItemModel *model) {
112 delete selectionModel();
114 QTreeView::setModel(model);
116 // remove old Actions
117 QList<QAction *> oldactions = header()->actions();
118 foreach(QAction *action, oldactions) {
119 header()->removeAction(action);
120 action->deleteLater();
127 QAction *showSection;
128 for(int i = 1; i < model->columnCount(); i++) {
129 sectionName = (model->headerData(i, Qt::Horizontal, Qt::DisplayRole)).toString();
130 showSection = new QAction(sectionName, header());
131 showSection->setCheckable(true);
132 showSection->setChecked(!isColumnHidden(i));
133 showSection->setProperty("column", i);
134 connect(showSection, SIGNAL(toggled(bool)), this, SLOT(toggleHeader(bool)));
135 header()->addAction(showSection);
140 void BufferView::setFilteredModel(QAbstractItemModel *model_, BufferViewConfig *config) {
141 BufferViewFilter *filter = qobject_cast<BufferViewFilter *>(model());
143 filter->setConfig(config);
149 disconnect(this, 0, model(), 0);
150 disconnect(model(), 0, this, 0);
156 BufferViewFilter *filter = new BufferViewFilter(model_, config);
158 connect(filter, SIGNAL(configChanged()), this, SLOT(on_configChanged()));
163 void BufferView::setSelectionModel(QItemSelectionModel *selectionModel) {
164 if(QTreeView::selectionModel())
165 disconnect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
166 model(), SIGNAL(checkPreviousCurrentForRemoval(QModelIndex, QModelIndex)));
168 QTreeView::setSelectionModel(selectionModel);
169 BufferViewFilter *filter = qobject_cast<BufferViewFilter *>(model());
171 connect(selectionModel, SIGNAL(currentChanged(QModelIndex, QModelIndex)),
172 filter, SLOT(checkPreviousCurrentForRemoval(QModelIndex, QModelIndex)));
176 void BufferView::setConfig(BufferViewConfig *config) {
177 if(_config == config)
181 disconnect(_config, 0, this, 0);
186 connect(config, SIGNAL(networkIdSet(const NetworkId &)), this, SLOT(setRootIndexForNetworkId(const NetworkId &)));
187 setRootIndexForNetworkId(config->networkId());
189 setRootIndex(QModelIndex());
193 void BufferView::setRootIndexForNetworkId(const NetworkId &networkId) {
194 if(!networkId.isValid() || !model()) {
195 setRootIndex(QModelIndex());
197 int networkCount = model()->rowCount();
199 for(int i = 0; i < networkCount; i++) {
200 child = model()->index(i, 0);
201 if(networkId == model()->data(child, NetworkModel::NetworkIdRole).value<NetworkId>())
207 void BufferView::joinChannel(const QModelIndex &index) {
208 BufferInfo::Type bufferType = (BufferInfo::Type)index.data(NetworkModel::BufferTypeRole).value<int>();
210 if(bufferType != BufferInfo::ChannelBuffer)
213 BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
215 Client::userInput(bufferInfo, QString("/JOIN %1").arg(bufferInfo.bufferName()));
218 void BufferView::keyPressEvent(QKeyEvent *event) {
219 if(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete) {
221 removeSelectedBuffers();
223 QTreeView::keyPressEvent(event);
226 void BufferView::removeSelectedBuffers(bool permanently) {
231 QSet<BufferId> removedRows;
232 foreach(QModelIndex index, selectionModel()->selectedIndexes()) {
233 if(index.data(NetworkModel::ItemTypeRole) != NetworkModel::BufferItemType)
236 bufferId = index.data(NetworkModel::BufferIdRole).value<BufferId>();
237 if(removedRows.contains(bufferId))
240 removedRows << bufferId;
243 config()->requestRemoveBufferPermanently(bufferId);
245 config()->requestRemoveBuffer(bufferId);
249 void BufferView::rowsInserted(const QModelIndex & parent, int start, int end) {
250 QTreeView::rowsInserted(parent, start, end);
252 // ensure that newly inserted network nodes are expanded per default
253 if(parent.data(NetworkModel::ItemTypeRole) != NetworkModel::NetworkItemType)
256 if(model()->rowCount(parent) == 1 && parent.data(NetworkModel::ItemActiveRole) == true) {
257 // without updating the parent the expand will have no effect... Qt Bug?
263 void BufferView::on_configChanged() {
266 // expand all active networks... collapse inactive ones... unless manually changed
267 QModelIndex networkIdx;
269 for(int row = 0; row < model()->rowCount(); row++) {
270 networkIdx = model()->index(row, 0);
271 if(model()->rowCount(networkIdx) == 0)
274 networkId = model()->data(networkIdx, NetworkModel::NetworkIdRole).value<NetworkId>();
275 if(!networkId.isValid())
280 bool expandNetwork = false;
281 if(_expandedState.contains(networkId))
282 expandNetwork = _expandedState[networkId];
284 expandNetwork = model()->data(networkIdx, NetworkModel::ItemActiveRole).toBool();
289 collapse(networkIdx);
292 // update selection to current one
293 MappedSelectionModel *mappedSelectionModel = qobject_cast<MappedSelectionModel *>(selectionModel());
294 if(!config() || !mappedSelectionModel)
297 mappedSelectionModel->mappedSetCurrentIndex(Client::bufferModel()->standardSelectionModel()->currentIndex(), QItemSelectionModel::Current);
298 mappedSelectionModel->mappedSelect(Client::bufferModel()->standardSelectionModel()->selection(), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
301 void BufferView::on_collapse(const QModelIndex &index) {
302 storeExpandedState(index.data(NetworkModel::NetworkIdRole).value<NetworkId>(), false);
305 void BufferView::on_expand(const QModelIndex &index) {
306 storeExpandedState(index.data(NetworkModel::NetworkIdRole).value<NetworkId>(), true);
309 void BufferView::storeExpandedState(NetworkId networkId, bool expanded) {
310 _expandedState[networkId] = expanded;
313 void BufferView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
314 QTreeView::dataChanged(topLeft, bottomRight);
316 // determine how many items have been changed and if any of them is a networkitem
317 // which just swichted from active to inactive or vice versa
318 if(topLeft.data(NetworkModel::ItemTypeRole) != NetworkModel::NetworkItemType)
321 for(int i = topLeft.row(); i <= bottomRight.row(); i++) {
322 QModelIndex networkIdx = topLeft.sibling(i, 0);
323 if(model()->rowCount(networkIdx) == 0)
326 bool isActive = networkIdx.data(NetworkModel::ItemActiveRole).toBool();
328 if(isExpanded(networkIdx) != isActive) setExpanded(networkIdx, true);
330 if(isExpanded(networkIdx) != isActive) setExpanded(networkIdx, isActive);
335 void BufferView::toggleHeader(bool checked) {
336 QAction *action = qobject_cast<QAction *>(sender());
337 header()->setSectionHidden((action->property("column")).toInt(), !checked);
340 bool BufferView::checkRequirements(const QModelIndex &index, ItemActiveStates requiredActiveState) {
344 ItemActiveStates isActive = index.data(NetworkModel::ItemActiveRole).toBool()
348 if(!(isActive & requiredActiveState))
354 void BufferView::addItemToMenu(QAction *action, QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
355 addItemToMenu(action, menu, checkRequirements(index, requiredActiveState));
358 void BufferView::addItemToMenu(QAction *action, QMenu *menu, bool condition) {
360 menu->addAction(action);
361 action->setVisible(true);
363 action->setVisible(false);
367 void BufferView::addItemToMenu(QMenu *subMenu, QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
368 if(checkRequirements(index, requiredActiveState)) {
369 menu->addMenu(subMenu);
370 subMenu->setVisible(true);
372 subMenu->setVisible(false);
376 void BufferView::addSeparatorToMenu(QMenu *menu, const QModelIndex &index, ItemActiveStates requiredActiveState) {
377 if(checkRequirements(index, requiredActiveState)) {
378 menu->addSeparator();
382 QMenu *BufferView::createHideEventsSubMenu(QMenu *menu, BufferId bufferId) {
383 int filter = BufferSettings(bufferId).messageFilter();
384 _menuActions.action("HideJoin")->setChecked(filter & Message::Join);
385 _menuActions.action("HidePart")->setChecked(filter & Message::Part);
386 _menuActions.action("HideQuit")->setChecked(filter & Message::Quit);
387 _menuActions.action("HideNick")->setChecked(filter & Message::Nick);
388 _menuActions.action("HideMode")->setChecked(filter & Message::Mode);
389 _menuActions.action("HideDayChange")->setChecked(filter & Message::DayChange);
391 QMenu *hideEventsMenu = menu->addMenu(tr("Hide Events"));
392 hideEventsMenu->addAction(_menuActions.action("HideJoin"));
393 hideEventsMenu->addAction(_menuActions.action("HidePart"));
394 hideEventsMenu->addAction(_menuActions.action("HideQuit"));
395 hideEventsMenu->addAction(_menuActions.action("HideNick"));
396 hideEventsMenu->addAction(_menuActions.action("HideMode"));
397 hideEventsMenu->addAction(_menuActions.action("HideDayChange"));
398 return hideEventsMenu;
401 void BufferView::contextMenuEvent(QContextMenuEvent *event) {
402 QModelIndex index = indexAt(event->pos());
408 QMenu contextMenu(this);
409 addActionsToMenu(&contextMenu, index);
410 if(!contextMenu.actions().isEmpty())
411 contextMenu.exec(QCursor::pos());
415 void BufferView::addActionsToMenu(QMenu *contextMenu, const QModelIndex &index) {
417 const Network *network = Client::network(index.data(NetworkModel::NetworkIdRole).value<NetworkId>());
418 Q_CHECK_PTR(network);
420 NetworkModel::itemType itemType = static_cast<NetworkModel::itemType>(index.data(NetworkModel::ItemTypeRole).toInt());
423 case NetworkModel::NetworkItemType:
424 showChannelList.setData(index.data(NetworkModel::NetworkIdRole));
425 addItemToMenu(&showChannelList, contextMenu, index, ActiveState);
426 addItemToMenu(_menuActions.action("DisconnectNet"), contextMenu, network->connectionState() != Network::Disconnected);
427 addItemToMenu(_menuActions.action("ConnectNet"), contextMenu, network->connectionState() == Network::Disconnected);
428 addSeparatorToMenu(contextMenu, index, ActiveState);
429 addItemToMenu(_menuActions.action("JoinChannel"), contextMenu, index, ActiveState);
431 case NetworkModel::BufferItemType:
433 BufferInfo bufferInfo = index.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
434 switch(bufferInfo.type()) {
435 case BufferInfo::ChannelBuffer:
436 addItemToMenu(_menuActions.action("JoinBuffer"), contextMenu, index, InactiveState);
437 addItemToMenu(_menuActions.action("PartBuffer"), contextMenu, index, ActiveState);
438 addItemToMenu(_menuActions.action("HideBuffersTemporarily"), contextMenu, (bool)config());
439 addItemToMenu(_menuActions.action("HideBuffersPermanently"), contextMenu, (bool)config());
440 addItemToMenu(_menuActions.action("RemoveBuffer"), contextMenu, index, InactiveState);
441 createHideEventsSubMenu(contextMenu, bufferInfo.bufferId());
442 addItemToMenu(_menuActions.action("IgnoreList"), contextMenu);
444 case BufferInfo::QueryBuffer:
445 addItemToMenu(_menuActions.action("HideBuffersTemporarily"), contextMenu, (bool)config());
446 addItemToMenu(_menuActions.action("HideBuffersPermanently"), contextMenu, (bool)config());
447 addItemToMenu(_menuActions.action("RemoveBuffer"), contextMenu);
448 createHideEventsSubMenu(contextMenu, bufferInfo.bufferId());
451 addItemToMenu(_menuActions.action("HideBuffersTemporarily"), contextMenu, (bool)config());
452 addItemToMenu(_menuActions.action("HideBuffersPermanently"), contextMenu, (bool)config());
462 void BufferView::actionTriggered(QAction *result) {
463 const Network *network = Client::network(_menuIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>());
464 Q_CHECK_PTR(network);
466 if(network && result == _menuActions.action("ConnectNet")) {
467 network->requestConnect();
471 if(network && result == _menuActions.action("DisconnectNet")) {
472 network->requestDisconnect();
476 if(result == _menuActions.action("JoinChannel")) {
477 // FIXME no QInputDialog in Qtopia
480 QString channelName = QInputDialog::getText(this, tr("Join Channel"), tr("Input channel name:"), QLineEdit::Normal, QString(), &ok);
481 if(ok && !channelName.isEmpty()) {
482 Client::instance()->userInput(BufferInfo::fakeStatusBuffer(_menuIndex.data(NetworkModel::NetworkIdRole).value<NetworkId>()),
483 QString("/J %1").arg(channelName));
489 if(result == _menuActions.action("JoinBuffer")) {
490 BufferInfo bufferInfo = _menuIndex.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
491 Client::instance()->userInput(bufferInfo, QString("/JOIN %1").arg(bufferInfo.bufferName()));
495 if(result == _menuActions.action("PartBuffer")) {
496 BufferInfo bufferInfo = _menuIndex.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
497 Client::instance()->userInput(bufferInfo, QString("/PART"));
501 if(result == _menuActions.action("HideBuffersTemporarily")) {
502 removeSelectedBuffers();
506 if(result == _menuActions.action("HideBuffersPermanently")) {
507 removeSelectedBuffers(true);
511 if(result == _menuActions.action("RemoveBuffer")) {
512 BufferInfo bufferInfo = _menuIndex.data(NetworkModel::BufferInfoRole).value<BufferInfo>();
513 int res = QMessageBox::question(this, tr("Remove buffer permanently?"),
514 tr("Do you want to delete the buffer \"%1\" permanently? This will delete all related data, including all backlog "
515 "data, from the core's database!").arg(bufferInfo.bufferName()),
516 QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
517 if(res == QMessageBox::Yes) {
518 Client::removeBuffer(bufferInfo.bufferId());
523 if(result == _menuActions.action("HideJoin")) {
524 BufferId bufferId = _menuIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
525 BufferSettings(bufferId).filterMessage(Message::Join, result->isChecked());
528 if(result == _menuActions.action("HidePart")) {
529 BufferId bufferId = _menuIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
530 BufferSettings(bufferId).filterMessage(Message::Part, result->isChecked());
533 if(result == _menuActions.action("HideQuit")) {
534 BufferId bufferId = _menuIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
535 BufferSettings(bufferId).filterMessage(Message::Quit, result->isChecked());
538 if(result == _menuActions.action("HideNick")) {
539 BufferId bufferId = _menuIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
540 BufferSettings(bufferId).filterMessage(Message::Nick, result->isChecked());
543 if(result == _menuActions.action("HideMode")) {
544 BufferId bufferId = _menuIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
545 BufferSettings(bufferId).filterMessage(Message::Mode, result->isChecked());
548 if(result == _menuActions.action("HideDayChange")) {
549 BufferId bufferId = _menuIndex.data(NetworkModel::BufferIdRole).value<BufferId>();
550 BufferSettings(bufferId).filterMessage(Message::DayChange, result->isChecked());
554 qWarning() << "Unhandled menu action";
557 void BufferView::wheelEvent(QWheelEvent* event) {
558 if(UiSettings().value("MouseWheelChangesBuffers", QVariant(true)).toBool() == (bool)(event->modifiers() & Qt::AltModifier))
559 return QTreeView::wheelEvent(event);
561 int rowDelta = ( event->delta() > 0 ) ? -1 : 1;
562 QModelIndex currentIndex = selectionModel()->currentIndex();
563 QModelIndex resultingIndex;
564 if( model()->hasIndex( currentIndex.row() + rowDelta, currentIndex.column(), currentIndex.parent() ) )
566 resultingIndex = currentIndex.sibling( currentIndex.row() + rowDelta, currentIndex.column() );
568 else //if we scroll into a the parent node...
570 QModelIndex parent = currentIndex.parent();
571 QModelIndex aunt = parent.sibling( parent.row() + rowDelta, parent.column() );
573 resultingIndex = aunt.child( model()->rowCount( aunt ) - 1, 0 );
575 resultingIndex = aunt.child( 0, 0 );
576 if( !resultingIndex.isValid() )
579 selectionModel()->setCurrentIndex( resultingIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows );
580 selectionModel()->select( resultingIndex, QItemSelectionModel::ClearAndSelect );
585 QSize BufferView::sizeHint() const {
586 return QTreeView::sizeHint();
589 return QTreeView::sizeHint();
591 if(model()->rowCount() == 0)
592 return QSize(120, 50);
595 for(int i = 0; i < model()->columnCount(); i++) {
596 if(!isColumnHidden(i))
597 columnSize += sizeHintForColumn(i);
599 return QSize(columnSize, 50);
602 // ==============================
604 // ==============================
605 BufferViewDock::BufferViewDock(BufferViewConfig *config, QWidget *parent)
606 : QDockWidget(config->bufferViewName(), parent)
608 setObjectName("BufferViewDock-" + QString::number(config->bufferViewId()));
609 toggleViewAction()->setData(config->bufferViewId());
610 setAllowedAreas(Qt::RightDockWidgetArea|Qt::LeftDockWidgetArea);
611 connect(config, SIGNAL(bufferViewNameSet(const QString &)), this, SLOT(bufferViewRenamed(const QString &)));
614 BufferViewDock::BufferViewDock(QWidget *parent)
615 : QDockWidget(tr("All Buffers"), parent)
617 setObjectName("BufferViewDock--1");
618 toggleViewAction()->setData((int)-1);
619 setAllowedAreas(Qt::RightDockWidgetArea|Qt::LeftDockWidgetArea);
622 void BufferViewDock::bufferViewRenamed(const QString &newName) {
623 setWindowTitle(newName);
624 toggleViewAction()->setText(newName);