PageRenderTime 55ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/quassel-0.7.3/src/qtui/inputwidget.cpp

#
C++ | 553 lines | 425 code | 95 blank | 33 comment | 66 complexity | a82591094b9982e0d0af7e0d51afed6f MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0
  1. /***************************************************************************
  2. * Copyright (C) 2005-2010 by the Quassel Project *
  3. * devel@quassel-irc.org *
  4. * *
  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. *
  9. * *
  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. *
  14. * *
  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. ***************************************************************************/
  20. #include "inputwidget.h"
  21. #include "action.h"
  22. #include "actioncollection.h"
  23. #include "bufferview.h"
  24. #include "client.h"
  25. #include "iconloader.h"
  26. #include "ircuser.h"
  27. #include "networkmodel.h"
  28. #include "qtui.h"
  29. #include "qtuisettings.h"
  30. #include "tabcompleter.h"
  31. #include <QPainter>
  32. const int leftMargin = 3;
  33. InputWidget::InputWidget(QWidget *parent)
  34. : AbstractItemView(parent),
  35. _networkId(0)
  36. {
  37. ui.setupUi(this);
  38. connect(ui.ownNick, SIGNAL(activated(QString)), this, SLOT(changeNick(QString)));
  39. layout()->setAlignment(ui.ownNick, Qt::AlignBottom);
  40. layout()->setAlignment(ui.inputEdit, Qt::AlignBottom);
  41. layout()->setAlignment(ui.showStyleButton, Qt::AlignBottom);
  42. layout()->setAlignment(ui.styleFrame, Qt::AlignBottom);
  43. ui.styleFrame->setVisible(false);
  44. setFocusProxy(ui.inputEdit);
  45. ui.ownNick->setFocusProxy(ui.inputEdit);
  46. ui.ownNick->setSizeAdjustPolicy(QComboBox::AdjustToContents);
  47. ui.ownNick->installEventFilter(new MouseWheelFilter(this));
  48. ui.inputEdit->installEventFilter(this);
  49. ui.inputEdit->setMinHeight(1);
  50. ui.inputEdit->setMaxHeight(5);
  51. ui.inputEdit->setMode(MultiLineEdit::MultiLine);
  52. ui.inputEdit->setPasteProtectionEnabled(true);
  53. ui.boldButton->setIcon(SmallIcon("format-text-bold"));
  54. ui.italicButton->setIcon(SmallIcon("format-text-italic"));
  55. ui.underlineButton->setIcon(SmallIcon("format-text-underline"));
  56. ui.textcolorButton->setIcon(SmallIcon("format-text-color"));
  57. ui.highlightcolorButton->setIcon(SmallIcon("format-fill-color"));
  58. _colorMenu = new QMenu();
  59. _colorFillMenu = new QMenu();
  60. QStringList names;
  61. names << tr("White") << tr("Black") << tr("Dark blue") << tr("Dark green") << tr("Red") << tr("Dark red") << tr("Dark magenta") << tr("Orange")
  62. << tr("Yellow") << tr("Green") << tr("Dark cyan") << tr("Cyan") << tr("Blue") << tr("Magenta") << tr("Dark gray") << tr("Light gray");
  63. QPixmap pix(16, 16);
  64. for (int i = 0; i < inputLine()->mircColorMap().count(); i++) {
  65. pix.fill(inputLine()->mircColorMap().values()[i]);
  66. _colorMenu->addAction(pix, names[i])->setData(inputLine()->mircColorMap().keys()[i]);
  67. _colorFillMenu->addAction(pix, names[i])->setData(inputLine()->mircColorMap().keys()[i]);
  68. }
  69. pix.fill(Qt::transparent);
  70. _colorMenu->addAction(pix, tr("Clear Color"))->setData("");
  71. _colorFillMenu->addAction(pix, tr("Clear Color"))->setData("");
  72. ui.textcolorButton->setMenu(_colorMenu);
  73. connect(_colorMenu, SIGNAL(triggered(QAction*)), this, SLOT(colorChosen(QAction*)));
  74. ui.highlightcolorButton->setMenu(_colorFillMenu);
  75. connect(_colorFillMenu, SIGNAL(triggered(QAction*)), this, SLOT(colorHighlightChosen(QAction*)));
  76. new TabCompleter(ui.inputEdit);
  77. UiStyleSettings fs("Fonts");
  78. fs.notify("UseCustomInputWidgetFont", this, SLOT(setUseCustomFont(QVariant)));
  79. fs.notify("InputWidget", this, SLOT(setCustomFont(QVariant)));
  80. if(fs.value("UseCustomInputWidgetFont", false).toBool())
  81. setCustomFont(fs.value("InputWidget", QFont()));
  82. UiSettings s("InputWidget");
  83. #ifdef HAVE_KDE
  84. s.notify("EnableSpellCheck", this, SLOT(setEnableSpellCheck(QVariant)));
  85. setEnableSpellCheck(s.value("EnableSpellCheck", false));
  86. #endif
  87. s.notify("EnableEmacsMode", this, SLOT(setEnableEmacsMode(QVariant)));
  88. setEnableEmacsMode(s.value("EnableEmacsMode", false));
  89. s.notify("ShowNickSelector", this, SLOT(setShowNickSelector(QVariant)));
  90. setShowNickSelector(s.value("ShowNickSelector", true));
  91. s.notify("ShowStyleButtons", this, SLOT(setShowStyleButtons(QVariant)));
  92. setShowStyleButtons(s.value("ShowStyleButtons", true));
  93. s.notify("EnablePerChatHistory", this, SLOT(setEnablePerChatHistory(QVariant)));
  94. setEnablePerChatHistory(s.value("EnablePerChatHistory", false));
  95. s.notify("MaxNumLines", this, SLOT(setMaxLines(QVariant)));
  96. setMaxLines(s.value("MaxNumLines", 5));
  97. s.notify("EnableScrollBars", this, SLOT(setScrollBarsEnabled(QVariant)));
  98. setScrollBarsEnabled(s.value("EnableScrollBars", true));
  99. s.notify("EnableMultiLine", this, SLOT(setMultiLineEnabled(QVariant)));
  100. setMultiLineEnabled(s.value("EnableMultiLine", true));
  101. ActionCollection *coll = QtUi::actionCollection();
  102. Action *activateInputline = coll->add<Action>("FocusInputLine");
  103. connect(activateInputline, SIGNAL(triggered()), SLOT(setFocus()));
  104. activateInputline->setText(tr("Focus Input Line"));
  105. activateInputline->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L));
  106. connect(inputLine(), SIGNAL(currentCharFormatChanged(QTextCharFormat)), this, SLOT(currentCharFormatChanged(QTextCharFormat)));
  107. }
  108. InputWidget::~InputWidget() {
  109. }
  110. void InputWidget::setUseCustomFont(const QVariant &v) {
  111. if(v.toBool()) {
  112. UiStyleSettings fs("Fonts");
  113. setCustomFont(fs.value("InputWidget"));
  114. } else
  115. setCustomFont(QFont());
  116. }
  117. void InputWidget::setCustomFont(const QVariant &v) {
  118. QFont font = v.value<QFont>();
  119. if(font.family().isEmpty())
  120. font = QApplication::font();
  121. // we don't want font styles as this conflics with mirc code richtext editing
  122. font.setBold(false);
  123. font.setItalic(false);
  124. font.setUnderline(false);
  125. font.setStrikeOut(false);
  126. ui.inputEdit->setCustomFont(font);
  127. }
  128. void InputWidget::setEnableSpellCheck(const QVariant &v) {
  129. ui.inputEdit->setSpellCheckEnabled(v.toBool());
  130. }
  131. void InputWidget::setEnableEmacsMode(const QVariant &v) {
  132. ui.inputEdit->setEmacsMode(v.toBool());
  133. }
  134. void InputWidget::setShowNickSelector(const QVariant &v) {
  135. ui.ownNick->setVisible(v.toBool());
  136. }
  137. void InputWidget::setShowStyleButtons(const QVariant &v) {
  138. ui.showStyleButton->setVisible(v.toBool());
  139. }
  140. void InputWidget::setEnablePerChatHistory(const QVariant &v) {
  141. _perChatHistory = v.toBool();
  142. }
  143. void InputWidget::setMaxLines(const QVariant &v) {
  144. ui.inputEdit->setMaxHeight(v.toInt());
  145. }
  146. void InputWidget::setScrollBarsEnabled(const QVariant &v) {
  147. ui.inputEdit->setScrollBarsEnabled(v.toBool());
  148. }
  149. void InputWidget::setMultiLineEnabled(const QVariant &v) {
  150. ui.inputEdit->setMode(v.toBool()? MultiLineEdit::MultiLine : MultiLineEdit::SingleLine);
  151. }
  152. bool InputWidget::eventFilter(QObject *watched, QEvent *event) {
  153. if(event->type() != QEvent::KeyPress)
  154. return false;
  155. QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
  156. // keys from BufferView should be sent to (and focus) the input line
  157. BufferView *view = qobject_cast<BufferView *>(watched);
  158. if(view) {
  159. if(keyEvent->text().length() == 1 && !(keyEvent->modifiers() & (Qt::ControlModifier ^ Qt::AltModifier)) ) { // normal key press
  160. QChar c = keyEvent->text().at(0);
  161. if(c.isLetterOrNumber() || c.isSpace() || c.isPunct() || c.isSymbol()) {
  162. setFocus();
  163. QCoreApplication::sendEvent(inputLine(), keyEvent);
  164. return true;
  165. }
  166. }
  167. return false;
  168. } else if(watched == ui.inputEdit) {
  169. if(keyEvent->matches(QKeySequence::Find)) {
  170. QAction *act = GraphicalUi::actionCollection()->action("ToggleSearchBar");
  171. if(act) {
  172. act->toggle();
  173. return true;
  174. }
  175. }
  176. return false;
  177. }
  178. return false;
  179. }
  180. void InputWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous) {
  181. BufferId currentBufferId = current.data(NetworkModel::BufferIdRole).value<BufferId>();
  182. BufferId previousBufferId = previous.data(NetworkModel::BufferIdRole).value<BufferId>();
  183. if (_perChatHistory) {
  184. //backup
  185. historyMap[previousBufferId].history = inputLine()->history();
  186. historyMap[previousBufferId].tempHistory = inputLine()->tempHistory();
  187. historyMap[previousBufferId].idx = inputLine()->idx();
  188. historyMap[previousBufferId].inputLine = inputLine()->html();
  189. //restore
  190. inputLine()->setHistory(historyMap[currentBufferId].history);
  191. inputLine()->setTempHistory(historyMap[currentBufferId].tempHistory);
  192. inputLine()->setIdx(historyMap[currentBufferId].idx);
  193. inputLine()->setHtml(historyMap[currentBufferId].inputLine);
  194. inputLine()->moveCursor(QTextCursor::End,QTextCursor::MoveAnchor);
  195. // FIXME this really should be in MultiLineEdit (and the const int on top removed)
  196. QTextBlockFormat format = inputLine()->textCursor().blockFormat();
  197. format.setLeftMargin(leftMargin); // we want a little space between the frame and the contents
  198. inputLine()->textCursor().setBlockFormat(format);
  199. }
  200. NetworkId networkId = current.data(NetworkModel::NetworkIdRole).value<NetworkId>();
  201. if(networkId == _networkId)
  202. return;
  203. setNetwork(networkId);
  204. updateNickSelector();
  205. updateEnabledState();
  206. }
  207. void InputWidget::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) {
  208. QItemSelectionRange changedArea(topLeft, bottomRight);
  209. if(changedArea.contains(selectionModel()->currentIndex())) {
  210. updateEnabledState();
  211. }
  212. };
  213. void InputWidget::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) {
  214. NetworkId networkId;
  215. QModelIndex child;
  216. for(int row = start; row <= end; row++) {
  217. child = model()->index(row, 0, parent);
  218. if(NetworkModel::NetworkItemType != child.data(NetworkModel::ItemTypeRole).toInt())
  219. continue;
  220. networkId = child.data(NetworkModel::NetworkIdRole).value<NetworkId>();
  221. if(networkId == _networkId) {
  222. setNetwork(0);
  223. updateNickSelector();
  224. return;
  225. }
  226. }
  227. }
  228. void InputWidget::updateEnabledState() {
  229. // FIXME: Find a visualization for this that does not disable the widget!
  230. // Disabling kills global action shortcuts, plus users sometimes need/want to enter text
  231. // even in inactive channels.
  232. #if 0
  233. QModelIndex currentIndex = selectionModel()->currentIndex();
  234. const Network *net = Client::networkModel()->networkByIndex(currentIndex);
  235. bool enabled = false;
  236. if(net) {
  237. // disable inputline if it's a channelbuffer we parted from or...
  238. enabled = (currentIndex.data(NetworkModel::ItemActiveRole).value<bool>() || (currentIndex.data(NetworkModel::BufferTypeRole).toInt() != BufferInfo::ChannelBuffer));
  239. // ... if we're not connected to the network at all
  240. enabled &= net->isConnected();
  241. }
  242. ui.inputEdit->setEnabled(enabled);
  243. #endif
  244. }
  245. const Network *InputWidget::currentNetwork() const {
  246. return Client::network(_networkId);
  247. }
  248. BufferInfo InputWidget::currentBufferInfo() const {
  249. return selectionModel()->currentIndex().data(NetworkModel::BufferInfoRole).value<BufferInfo>();
  250. };
  251. void InputWidget::setNetwork(NetworkId networkId) {
  252. if(_networkId == networkId)
  253. return;
  254. const Network *previousNet = Client::network(_networkId);
  255. if(previousNet) {
  256. disconnect(previousNet, 0, this, 0);
  257. if(previousNet->me())
  258. disconnect(previousNet->me(), 0, this, 0);
  259. }
  260. _networkId = networkId;
  261. const Network *network = Client::network(networkId);
  262. if(network) {
  263. connect(network, SIGNAL(identitySet(IdentityId)), this, SLOT(setIdentity(IdentityId)));
  264. connectMyIrcUser();
  265. setIdentity(network->identity());
  266. } else {
  267. setIdentity(0);
  268. _networkId = 0;
  269. }
  270. }
  271. void InputWidget::connectMyIrcUser() {
  272. const Network *network = currentNetwork();
  273. if(network->me()) {
  274. connect(network->me(), SIGNAL(nickSet(const QString &)), this, SLOT(updateNickSelector()));
  275. connect(network->me(), SIGNAL(userModesSet(QString)), this, SLOT(updateNickSelector()));
  276. connect(network->me(), SIGNAL(userModesAdded(QString)), this, SLOT(updateNickSelector()));
  277. connect(network->me(), SIGNAL(userModesRemoved(QString)), this, SLOT(updateNickSelector()));
  278. connect(network->me(), SIGNAL(awaySet(bool)), this, SLOT(updateNickSelector()));
  279. disconnect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
  280. updateNickSelector();
  281. } else {
  282. connect(network, SIGNAL(myNickSet(const QString &)), this, SLOT(connectMyIrcUser()));
  283. }
  284. }
  285. void InputWidget::setIdentity(IdentityId identityId) {
  286. if(_identityId == identityId)
  287. return;
  288. const Identity *previousIdentity = Client::identity(_identityId);
  289. if(previousIdentity)
  290. disconnect(previousIdentity, 0, this, 0);
  291. _identityId = identityId;
  292. const Identity *identity = Client::identity(identityId);
  293. if(identity) {
  294. connect(identity, SIGNAL(nicksSet(QStringList)), this, SLOT(updateNickSelector()));
  295. } else {
  296. _identityId = 0;
  297. }
  298. updateNickSelector();
  299. }
  300. void InputWidget::updateNickSelector() const {
  301. ui.ownNick->clear();
  302. const Network *net = currentNetwork();
  303. if(!net)
  304. return;
  305. const Identity *identity = Client::identity(net->identity());
  306. if(!identity) {
  307. qWarning() << "InputWidget::updateNickSelector(): can't find Identity for Network" << net->networkId() << "IdentityId:" << net->identity();
  308. return;
  309. }
  310. int nickIdx;
  311. QStringList nicks = identity->nicks();
  312. if((nickIdx = nicks.indexOf(net->myNick())) == -1) {
  313. nicks.prepend(net->myNick());
  314. nickIdx = 0;
  315. }
  316. if(nicks.isEmpty())
  317. return;
  318. IrcUser *me = net->me();
  319. if(me) {
  320. nicks[nickIdx] = net->myNick();
  321. if(!me->userModes().isEmpty())
  322. nicks[nickIdx] += QString(" (+%1)").arg(me->userModes());
  323. }
  324. ui.ownNick->addItems(nicks);
  325. if(me && me->isAway())
  326. ui.ownNick->setItemData(nickIdx, SmallIcon("user-away"), Qt::DecorationRole);
  327. ui.ownNick->setCurrentIndex(nickIdx);
  328. }
  329. void InputWidget::changeNick(const QString &newNick) const {
  330. const Network *net = currentNetwork();
  331. if(!net || net->isMyNick(newNick))
  332. return;
  333. // we reset the nick selecter as we have no confirmation yet, that this will succeed.
  334. // if the action succeeds it will be properly updated anyways.
  335. updateNickSelector();
  336. Client::userInput(BufferInfo::fakeStatusBuffer(net->networkId()), QString("/NICK %1").arg(newNick));
  337. }
  338. void InputWidget::on_inputEdit_textEntered(const QString &text) {
  339. Client::userInput(currentBufferInfo(), text);
  340. ui.boldButton->setChecked(false);
  341. ui.underlineButton->setChecked(false);
  342. ui.italicButton->setChecked(false);
  343. QTextCharFormat fmt;
  344. fmt.setFontWeight(QFont::Normal);
  345. fmt.setFontUnderline(false);
  346. fmt.setFontItalic(false);
  347. fmt.clearForeground();
  348. fmt.clearBackground();
  349. inputLine()->setCurrentCharFormat(fmt);
  350. #ifdef HAVE_KDE
  351. // Set highlighter back to active in case it was deactivated by too many errors.
  352. if(ui.inputEdit->highlighter())
  353. ui.inputEdit->highlighter()->setActive(true);
  354. #endif
  355. }
  356. void InputWidget::mergeFormatOnSelection(const QTextCharFormat &format) {
  357. QTextCursor cursor = inputLine()->textCursor();
  358. cursor.mergeCharFormat(format);
  359. inputLine()->mergeCurrentCharFormat(format);
  360. }
  361. void InputWidget::setFormatOnSelection(const QTextCharFormat &format) {
  362. QTextCursor cursor = inputLine()->textCursor();
  363. cursor.setCharFormat(format);
  364. inputLine()->setCurrentCharFormat(format);
  365. }
  366. QTextCharFormat InputWidget::getFormatOfWordOrSelection() {
  367. QTextCursor cursor = inputLine()->textCursor();
  368. return cursor.charFormat();
  369. }
  370. void InputWidget::currentCharFormatChanged(const QTextCharFormat &format) {
  371. fontChanged(format.font());
  372. }
  373. void InputWidget::on_boldButton_clicked(bool checked) {
  374. QTextCharFormat fmt;
  375. fmt.setFontWeight(checked ? QFont::Bold : QFont::Normal);
  376. mergeFormatOnSelection(fmt);
  377. }
  378. void InputWidget::on_underlineButton_clicked(bool checked) {
  379. QTextCharFormat fmt;
  380. fmt.setFontUnderline(checked);
  381. mergeFormatOnSelection(fmt);
  382. }
  383. void InputWidget::on_italicButton_clicked(bool checked) {
  384. QTextCharFormat fmt;
  385. fmt.setFontItalic(checked);
  386. mergeFormatOnSelection(fmt);
  387. }
  388. void InputWidget::fontChanged(const QFont &f)
  389. {
  390. ui.boldButton->setChecked(f.bold());
  391. ui.italicButton->setChecked(f.italic());
  392. ui.underlineButton->setChecked(f.underline());
  393. }
  394. void InputWidget::colorChosen(QAction *action) {
  395. QTextCharFormat fmt;
  396. QColor color;
  397. if (qVariantValue<QString>(action->data()) == "") {
  398. color = Qt::transparent;
  399. fmt = getFormatOfWordOrSelection();
  400. fmt.clearForeground();
  401. setFormatOnSelection(fmt);
  402. }
  403. else {
  404. color = QColor(inputLine()->rgbColorFromMirc(qVariantValue<QString>(action->data())));
  405. fmt.setForeground(color);
  406. mergeFormatOnSelection(fmt);
  407. }
  408. ui.textcolorButton->setDefaultAction(action);
  409. ui.textcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-text-color"), color));
  410. }
  411. void InputWidget::colorHighlightChosen(QAction *action) {
  412. QTextCharFormat fmt;
  413. QColor color;
  414. if (qVariantValue<QString>(action->data()) == "") {
  415. color = Qt::transparent;
  416. fmt = getFormatOfWordOrSelection();
  417. fmt.clearBackground();
  418. setFormatOnSelection(fmt);
  419. }
  420. else {
  421. color = QColor(inputLine()->rgbColorFromMirc(qVariantValue<QString>(action->data())));
  422. fmt.setBackground(color);
  423. mergeFormatOnSelection(fmt);
  424. }
  425. ui.highlightcolorButton->setDefaultAction(action);
  426. ui.highlightcolorButton->setIcon(createColorToolButtonIcon(SmallIcon("format-fill-color"), color));
  427. }
  428. void InputWidget::on_showStyleButton_toggled(bool checked) {
  429. ui.styleFrame->setVisible(checked);
  430. if (checked) {
  431. ui.showStyleButton->setArrowType(Qt::LeftArrow);
  432. }
  433. else {
  434. ui.showStyleButton->setArrowType(Qt::RightArrow);
  435. }
  436. }
  437. QIcon InputWidget::createColorToolButtonIcon(const QIcon &icon, const QColor &color) {
  438. QPixmap pixmap(16, 16);
  439. pixmap.fill(Qt::transparent);
  440. QPainter painter(&pixmap);
  441. QPixmap image = icon.pixmap(16,16);
  442. QRect target(0, 0, 16, 14);
  443. QRect source(0, 0, 16, 14);
  444. painter.fillRect(QRect(0, 14, 16, 16), color);
  445. painter.drawPixmap(target, image, source);
  446. return QIcon(pixmap);
  447. }
  448. // MOUSE WHEEL FILTER
  449. MouseWheelFilter::MouseWheelFilter(QObject *parent)
  450. : QObject(parent)
  451. {
  452. }
  453. bool MouseWheelFilter::eventFilter(QObject *obj, QEvent *event) {
  454. if(event->type() != QEvent::Wheel)
  455. return QObject::eventFilter(obj, event);
  456. else
  457. return true;
  458. }