common: Add '$i:identd', '*' for empty, tooltips
[quassel.git] / src / qtui / settingspages / aliasesmodel.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 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 "aliasesmodel.h"
22
23 #include <QDebug>
24 #include <QStringList>
25
26 #include "client.h"
27 #include "signalproxy.h"
28
29 AliasesModel::AliasesModel(QObject *parent)
30     : QAbstractItemModel(parent),
31     _configChanged(false),
32     _modelReady(false)
33 {
34     // we need this signal for future connects to reset the data;
35     connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
36     connect(Client::instance(), SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
37
38     if (Client::isConnected())
39         clientConnected();
40     else
41         emit modelReady(false);
42 }
43
44
45 QVariant AliasesModel::data(const QModelIndex &index, int role) const
46 {
47     if (!_modelReady)
48         return QVariant();
49
50     if (!index.isValid() || index.row() >= rowCount() || index.column() >= columnCount())
51         return QVariant();
52
53     switch (role) {
54     case Qt::ToolTipRole:
55         switch (index.column()) {
56         case 0:
57             return tr("<b>The shortcut for the alias</b><br />"
58                       "It can be used as a regular slash command.<br /><br />"
59                       "<b>Example:</b> \"foo\" can be used per /foo");
60         case 1:
61         {
62             // To avoid overwhelming the user, organize things into a table
63             QString strTooltip;
64             QTextStream tooltip( &strTooltip, QIODevice::WriteOnly );
65             tooltip << "<qt><style>.bold { font-weight: bold; } .italic { font-style: italic; }</style>";
66
67             // Function to add a row to the tooltip table
68             auto addRow = [&](
69                     const QString& key, const QString& value = QString(), bool condition = true) {
70                 if (condition) {
71                     if (value.isEmpty()) {
72                         tooltip << "<tr><td class='italic' align='left' colspan='2'>"
73                                       << key << "</td></tr>";
74                     } else {
75                         tooltip << "<tr><td class='bold' align='left'>"
76                                       << key << "</td><td>" << value << "</td></tr>";
77                     }
78                 }
79             };
80
81             tooltip << "<p class='bold'>"
82                     << tr("The string the shortcut will be expanded to") << "</p>";
83
84             tooltip << "<p class='bold' align='center'>"
85                     << tr("Special variables") << "</p>";
86
87             // Variable option table
88             tooltip << "<table cellspacing='5' cellpadding='0'>";
89
90             // Parameter variables
91             addRow(tr("Parameter variables"));
92             addRow("$i", tr("i'th parameter"));
93             addRow("$i..j", tr("i'th to j'th parameter separated by spaces"));
94             addRow("$i..", tr("all parameters from i on separated by spaces"));
95
96             // IrcUser handling
97             addRow(tr("Nickname parameter variables"));
98             addRow("$i:account",
99                    tr("account of user identified by i'th parameter, or a '*' if logged out or "
100                       "unknown"));
101             addRow("$i:hostname",
102                    tr("hostname of user identified by i'th parameter, or a '*' if unknown"));
103             addRow("$i:ident",
104                    tr("ident of user identified by i'th parameter, or a '*' if unknown"));
105             addRow("$i:identd",
106                    tr("ident of user identified by i'th parameter if verified, or a '*' if unknown "
107                       "or unverified (prefixed with '~')"));
108
109             // General variables
110             addRow(tr("General variables"));
111             addRow("$0", tr("the whole string"));
112             addRow("$nick", tr("your current nickname"));
113             addRow("$channel", tr("the name of the selected channel"));
114
115             // End table
116             tooltip << "</table>";
117
118             // Example header
119             tooltip << "<p>"
120                     << tr("Multiple commands can be separated with semicolons") << "</p>";
121             // Example
122             tooltip << "<p>";
123             tooltip << QString("<p><span class='bold'>%1</span> %2<br />").arg(
124                            tr("Example:"), tr("\"Test $1; Test $2; Test All $0\""));
125             tooltip << tr("...will be expanded to three separate messages \"Test 1\", \"Test 2\" "
126                           "and \"Test All 1 2 3\" when called like <i>/test 1 2 3</i>")
127                     << "</p>";
128
129             // End tooltip
130             tooltip << "</qt>";
131             return strTooltip;
132         }
133         default:
134             return QVariant();
135         }
136     case Qt::DisplayRole:
137     case Qt::EditRole:
138         switch (index.column()) {
139         case 0:
140             return aliasManager()[index.row()].name;
141         case 1:
142             return aliasManager()[index.row()].expansion;
143         default:
144             return QVariant();
145         }
146     default:
147         return QVariant();
148     }
149 }
150
151
152 bool AliasesModel::setData(const QModelIndex &index, const QVariant &value, int role)
153 {
154     if (!_modelReady)
155         return false;
156
157     if (!index.isValid() || index.row() >= rowCount() || index.column() >= columnCount() || role != Qt::EditRole)
158         return false;
159
160     QString newValue = value.toString();
161     if (newValue.isEmpty())
162         return false;
163
164     switch (index.column()) {
165     case 0:
166         if (aliasManager().contains(newValue)) {
167             return false;
168         }
169         else {
170             cloneAliasManager()[index.row()].name = newValue;
171             return true;
172         }
173     case 1:
174         cloneAliasManager()[index.row()].expansion = newValue;
175         return true;
176     default:
177         return false;
178     }
179 }
180
181
182 void AliasesModel::newAlias()
183 {
184     QString newName("alias");
185     int i = 0;
186     AliasManager &manager = cloneAliasManager();
187     while (manager.contains(newName)) {
188         i++;
189         newName = QString("alias%1").arg(i);
190     }
191     beginInsertRows(QModelIndex(), rowCount(), rowCount());
192     manager.addAlias(newName, "Expansion");
193     endInsertRows();
194 }
195
196
197 void AliasesModel::loadDefaults()
198 {
199     if (!_modelReady)
200         return;
201
202     AliasManager &manager = cloneAliasManager();
203
204     if (!manager.isEmpty()) {
205         beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
206         for (int i = rowCount() - 1; i >= 0; i--)
207             manager.removeAt(i);
208         endRemoveRows();
209     }
210
211     AliasManager::AliasList defaults = AliasManager::defaults();
212     beginInsertRows(QModelIndex(), 0, defaults.count() - 1);
213     foreach(AliasManager::Alias alias, defaults) {
214         manager.addAlias(alias.name, alias.expansion);
215     }
216     endInsertRows();
217 }
218
219
220 void AliasesModel::removeAlias(int index)
221 {
222     if (index < 0 || index >= rowCount())
223         return;
224
225     AliasManager &manager = cloneAliasManager();
226     beginRemoveRows(QModelIndex(), index, index);
227     manager.removeAt(index);
228     endRemoveRows();
229 }
230
231
232 Qt::ItemFlags AliasesModel::flags(const QModelIndex &index) const
233 {
234     if (!index.isValid()) {
235         return Qt::ItemIsDropEnabled;
236     }
237     else {
238         return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
239     }
240 }
241
242
243 QVariant AliasesModel::headerData(int section, Qt::Orientation orientation, int role) const
244 {
245     QStringList header;
246     header << tr("Alias")
247            << tr("Expansion");
248
249     if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
250         return header[section];
251
252     return QVariant();
253 }
254
255
256 QModelIndex AliasesModel::index(int row, int column, const QModelIndex &parent) const
257 {
258     Q_UNUSED(parent);
259     if (row >= rowCount() || column >= columnCount())
260         return QModelIndex();
261
262     return createIndex(row, column);
263 }
264
265
266 const AliasManager &AliasesModel::aliasManager() const
267 {
268     if (_configChanged)
269         return _clonedAliasManager;
270     else
271         return *Client::aliasManager();
272 }
273
274
275 AliasManager &AliasesModel::aliasManager()
276 {
277     if (_configChanged)
278         return _clonedAliasManager;
279     else
280         return *Client::aliasManager();
281 }
282
283
284 AliasManager &AliasesModel::cloneAliasManager()
285 {
286     if (!_configChanged) {
287         _clonedAliasManager = *Client::aliasManager();
288         _configChanged = true;
289         emit configChanged(true);
290     }
291     return _clonedAliasManager;
292 }
293
294
295 void AliasesModel::revert()
296 {
297     if (!_configChanged)
298         return;
299
300     _configChanged = false;
301     emit configChanged(false);
302     beginResetModel();
303     endResetModel();
304 }
305
306
307 void AliasesModel::commit()
308 {
309     if (!_configChanged)
310         return;
311
312     Client::aliasManager()->requestUpdate(_clonedAliasManager.toVariantMap());
313     revert();
314 }
315
316
317 void AliasesModel::initDone()
318 {
319     _modelReady = true;
320     beginResetModel();
321     endResetModel();
322     emit modelReady(true);
323 }
324
325
326 void AliasesModel::clientConnected()
327 {
328     connect(Client::aliasManager(), SIGNAL(updated()), SLOT(revert()));
329     if (Client::aliasManager()->isInitialized())
330         initDone();
331     else
332         connect(Client::aliasManager(), SIGNAL(initDone()), SLOT(initDone()));
333 }
334
335
336 void AliasesModel::clientDisconnected()
337 {
338     // clear
339     _clonedAliasManager = ClientAliasManager();
340     _modelReady = false;
341     beginResetModel();
342     endResetModel();
343     emit modelReady(false);
344 }