1 | /*************************************************************************** |
2 | * Copyright (C) 2005-2014 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 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * |
19 | ***************************************************************************/ |
20 | |
21 | #include "topicwidget.h" |
22 | |
23 | #include "client.h" |
24 | #include "iconloader.h" |
25 | #include "networkmodel.h" |
26 | #include "uisettings.h" |
27 | #include "graphicalui.h" |
28 | #include "uistyle.h" |
29 | |
30 | TopicWidget::TopicWidget(QWidget *parent) |
31 | : AbstractItemView(parent) |
32 | { |
33 | ui.setupUi(this); |
34 | ui.topicEditButton->setIcon(SmallIcon("edit-rename" )); |
35 | ui.topicLineEdit->setLineWrapEnabled(true); |
36 | ui.topicLineEdit->installEventFilter(this); |
37 | |
38 | connect(ui.topicLabel, SIGNAL(clickableActivated(Clickable)), SLOT(clickableActivated(Clickable))); |
39 | connect(ui.topicLineEdit, SIGNAL(noTextEntered()), SLOT(on_topicLineEdit_textEntered())); |
40 | |
41 | UiSettings s("TopicWidget" ); |
42 | s.notify("DynamicResize" , this, SLOT(updateResizeMode())); |
43 | s.notify("ResizeOnHover" , this, SLOT(updateResizeMode())); |
44 | updateResizeMode(); |
45 | |
46 | UiStyleSettings fs("Fonts" ); |
47 | fs.notify("UseCustomTopicWidgetFont" , this, SLOT(setUseCustomFont(QVariant))); |
48 | fs.notify("TopicWidget" , this, SLOT(setCustomFont(QVariant))); |
49 | if (fs.value("UseCustomTopicWidgetFont" , false).toBool()) |
50 | setCustomFont(fs.value("TopicWidget" , QFont())); |
51 | |
52 | _mouseEntered = false; |
53 | _readonly = false; |
54 | } |
55 | |
56 | |
57 | void TopicWidget::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
58 | { |
59 | Q_UNUSED(previous); |
60 | setTopic(current); |
61 | } |
62 | |
63 | |
64 | void TopicWidget::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) |
65 | { |
66 | QItemSelectionRange changedArea(topLeft, bottomRight); |
67 | QModelIndex currentTopicIndex = selectionModel()->currentIndex().sibling(selectionModel()->currentIndex().row(), 1); |
68 | if (changedArea.contains(currentTopicIndex)) |
69 | setTopic(selectionModel()->currentIndex()); |
70 | }; |
71 | |
72 | void TopicWidget::setUseCustomFont(const QVariant &v) |
73 | { |
74 | if (v.toBool()) { |
75 | UiStyleSettings fs("Fonts" ); |
76 | setCustomFont(fs.value("TopicWidget" ).value<QFont>()); |
77 | } |
78 | else |
79 | setCustomFont(QFont()); |
80 | } |
81 | |
82 | |
83 | void TopicWidget::setCustomFont(const QVariant &v) |
84 | { |
85 | UiStyleSettings fs("Fonts" ); |
86 | if (!fs.value("UseCustomTopicWidgetFont" , false).toBool()) |
87 | return; |
88 | |
89 | setCustomFont(v.value<QFont>()); |
90 | } |
91 | |
92 | |
93 | void TopicWidget::setCustomFont(const QFont &f) |
94 | { |
95 | QFont font = f; |
96 | if (font.family().isEmpty()) |
97 | font = QApplication::font(); |
98 | |
99 | ui.topicLineEdit->setCustomFont(font); |
100 | ui.topicLabel->setCustomFont(font); |
101 | } |
102 | |
103 | |
104 | void TopicWidget::setTopic(const QModelIndex &index) |
105 | { |
106 | QString newtopic; |
107 | bool readonly = true; |
108 | |
109 | BufferId id = index.data(NetworkModel::BufferIdRole).value<BufferId>(); |
110 | if (id.isValid()) { |
111 | QModelIndex index0 = index.sibling(index.row(), 0); |
112 | const Network *network = Client::network(Client::networkModel()->networkId(id)); |
113 | |
114 | switch (Client::networkModel()->bufferType(id)) { |
115 | case BufferInfo::StatusBuffer: |
116 | if (network) { |
117 | #if QT_VERSION < 0x050000 |
118 | newtopic = QString("%1 (%2) | %3 | %4" ) |
119 | .arg(Qt::escape(network->networkName())) |
120 | .arg(Qt::escape(network->currentServer())) |
121 | .arg(tr("Users: %1" ).arg(network->ircUsers().count())) |
122 | .arg(tr("Lag: %1 msecs" ).arg(network->latency())); |
123 | #else |
124 | newtopic = QString("%1 (%2) | %3 | %4" ) |
125 | .arg(network->networkName().toHtmlEscaped()) |
126 | .arg(network->currentServer().toHtmlEscaped()) |
127 | .arg(tr("Users: %1" ).arg(network->ircUsers().count())) |
128 | .arg(tr("Lag: %1 msecs" ).arg(network->latency())); |
129 | #endif |
130 | } |
131 | else { |
132 | newtopic = index0.data(Qt::DisplayRole).toString(); |
133 | } |
134 | break; |
135 | |
136 | case BufferInfo::ChannelBuffer: |
137 | newtopic = index.sibling(index.row(), 1).data().toString(); |
138 | readonly = false; |
139 | break; |
140 | |
141 | case BufferInfo::QueryBuffer: |
142 | { |
143 | QString nickname = index0.data(Qt::DisplayRole).toString(); |
144 | if (network) { |
145 | const IrcUser *user = network->ircUser(nickname); |
146 | if (user) { |
147 | newtopic = QString("%1%2%3 | %4@%5" ).arg(nickname) |
148 | .arg(user->userModes().isEmpty() ? QString() : QString(" (+%1)" ).arg(user->userModes())) |
149 | .arg(user->realName().isEmpty() ? QString() : QString(" | %1" ).arg(user->realName())) |
150 | .arg(user->user()) |
151 | .arg(user->host()); |
152 | } |
153 | else { // no such user |
154 | newtopic = nickname; |
155 | } |
156 | } |
157 | else { // no valid Network-Obj. |
158 | newtopic = nickname; |
159 | } |
160 | break; |
161 | } |
162 | default: |
163 | newtopic = index0.data(Qt::DisplayRole).toString(); |
164 | } |
165 | } |
166 | |
167 | _topic = sanitizeTopic(newtopic); |
168 | _readonly = readonly; |
169 | |
170 | ui.topicEditButton->setVisible(!_readonly); |
171 | ui.topicLabel->setText(_topic); |
172 | ui.topicLineEdit->setPlainText(_topic); |
173 | switchPlain(); |
174 | } |
175 | |
176 | |
177 | void TopicWidget::setReadOnly(const bool &readonly) |
178 | { |
179 | if (_readonly == readonly) |
180 | return; |
181 | |
182 | _readonly = readonly; |
183 | } |
184 | |
185 | |
186 | void TopicWidget::updateResizeMode() |
187 | { |
188 | StyledLabel::ResizeMode mode = StyledLabel::NoResize; |
189 | UiSettings s("TopicWidget" ); |
190 | if (s.value("DynamicResize" , true).toBool()) { |
191 | if (s.value("ResizeOnHover" , true).toBool()) |
192 | mode = StyledLabel::ResizeOnHover; |
193 | else |
194 | mode = StyledLabel::DynamicResize; |
195 | } |
196 | |
197 | ui.topicLabel->setResizeMode(mode); |
198 | } |
199 | |
200 | |
201 | void TopicWidget::clickableActivated(const Clickable &click) |
202 | { |
203 | NetworkId networkId = selectionModel()->currentIndex().data(NetworkModel::NetworkIdRole).value<NetworkId>(); |
204 | UiStyle::StyledString sstr = GraphicalUi::uiStyle()->styleString(GraphicalUi::uiStyle()->mircToInternal(_topic), UiStyle::PlainMsg); |
205 | click.activate(networkId, sstr.plainText); |
206 | } |
207 | |
208 | |
209 | void TopicWidget::on_topicLineEdit_textEntered() |
210 | { |
211 | QModelIndex currentIdx = currentIndex(); |
212 | if (currentIdx.isValid() && currentIdx.data(NetworkModel::BufferTypeRole) == BufferInfo::ChannelBuffer) { |
213 | BufferInfo bufferInfo = currentIdx.data(NetworkModel::BufferInfoRole).value<BufferInfo>(); |
214 | if (ui.topicLineEdit->text().isEmpty()) |
215 | Client::userInput(bufferInfo, QString("/quote TOPIC %1 :" ).arg(bufferInfo.bufferName())); |
216 | else |
217 | Client::userInput(bufferInfo, QString("/topic %1" ).arg(ui.topicLineEdit->text())); |
218 | } |
219 | switchPlain(); |
220 | } |
221 | |
222 | |
223 | void TopicWidget::on_topicEditButton_clicked() |
224 | { |
225 | switchEditable(); |
226 | } |
227 | |
228 | |
229 | void TopicWidget::switchEditable() |
230 | { |
231 | ui.stackedWidget->setCurrentIndex(1); |
232 | ui.topicLineEdit->setFocus(); |
233 | ui.topicLineEdit->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); |
234 | updateGeometry(); |
235 | } |
236 | |
237 | |
238 | void TopicWidget::switchPlain() |
239 | { |
240 | ui.stackedWidget->setCurrentIndex(0); |
241 | ui.topicLineEdit->setPlainText(_topic); |
242 | updateGeometry(); |
243 | emit switchedPlain(); |
244 | } |
245 | |
246 | |
247 | // filter for the input widget to switch back to normal mode |
248 | bool TopicWidget::eventFilter(QObject *obj, QEvent *event) |
249 | { |
250 | if (event->type() == QEvent::FocusOut && !_mouseEntered) { |
251 | switchPlain(); |
252 | return true; |
253 | } |
254 | |
255 | if (event->type() == QEvent::Enter) { |
256 | _mouseEntered = true; |
257 | } |
258 | |
259 | if (event->type() == QEvent::Leave) { |
260 | _mouseEntered = false; |
261 | } |
262 | |
263 | if (event->type() != QEvent::KeyRelease) |
264 | return QObject::eventFilter(obj, event); |
265 | |
266 | QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); |
267 | |
268 | if (keyEvent->key() == Qt::Key_Escape) { |
269 | switchPlain(); |
270 | return true; |
271 | } |
272 | |
273 | return false; |
274 | } |
275 | |
276 | QString TopicWidget::sanitizeTopic(const QString& topic) |
277 | { |
278 | // Normally, you don't have new lines in topic messages |
279 | // But the use of "plain text" functionnality from Qt replaces |
280 | // some unicode characters with a new line, which then triggers |
281 | // a stack overflow later |
282 | QString result(topic); |
283 | result.replace(QChar::ParagraphSeparator, " " ); |
284 | result.replace(QChar::LineSeparator, " " ); |
285 | |
286 | return result; |
287 | } |
288 | |