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