1 /*
2 Copyright (c) 2021, Lukas Holecek <hluk@email.cz>
3
4 This file is part of CopyQ.
5
6 CopyQ is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 CopyQ is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with CopyQ. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "scriptableitemselection.h"
21 #include "scriptableproxy.h"
22
23 #include "scriptable/scriptablebytearray.h"
24
25 #include <QJSEngine>
26 #include <QJSValueIterator>
27
28 namespace {
29
toDataMap(const QJSValue & value)30 QVariantMap toDataMap(const QJSValue &value)
31 {
32 QVariantMap result;
33 QJSValueIterator it(value);
34 while ( it.hasNext() ) {
35 it.next();
36 const auto bytes = toByteArray( it.value() );
37 result.insert(it.name(), bytes);
38 }
39 return result;
40 }
41
toDataMapList(const QJSValue & value)42 QVariantList toDataMapList(const QJSValue &value)
43 {
44 QVariantList result;
45 const quint32 length = value.property("length").toUInt();
46 for ( quint32 i = 0; i < length; ++i ) {
47 const auto item = value.property(i);
48 result.append( toDataMap(item) );
49 }
50 return result;
51 }
52
toIntVector(const QJSValue & value)53 QVector<int> toIntVector(const QJSValue &value)
54 {
55 QVector<int> result;
56 const quint32 length = value.property("length").toUInt();
57 for ( quint32 i = 0; i < length; ++i ) {
58 const auto item = value.property(i);
59 result.append( item.toInt() );
60 }
61 return result;
62 }
63
toRegularExpression(const QJSValue & value)64 QRegularExpression toRegularExpression(const QJSValue &value)
65 {
66 // If argument is invalid/not-regexp, create an invalid regex to match nothing.
67 if ( !value.isRegExp() )
68 return QRegularExpression("(");
69
70 const QVariant variant = value.toVariant();
71 QRegularExpression regexp = variant.toRegularExpression();
72
73 // Support for Qt 5.12.z and below.
74 if ( !variant.canConvert<QRegularExpression>() ) {
75 const QRegExp reOld = variant.toRegExp();
76 const auto caseSensitivity =
77 reOld.caseSensitivity() == Qt::CaseInsensitive
78 ? QRegularExpression::CaseInsensitiveOption
79 : QRegularExpression::NoPatternOption;
80 regexp = QRegularExpression(reOld.pattern(), caseSensitivity);
81 }
82
83 return regexp;
84 }
85
86 } // namespace
87
ScriptableItemSelection(const QString & tabName)88 ScriptableItemSelection::ScriptableItemSelection(const QString &tabName)
89 : m_tabName(tabName)
90 {
91 }
92
~ScriptableItemSelection()93 ScriptableItemSelection::~ScriptableItemSelection()
94 {
95 if (m_proxy)
96 m_proxy->destroySelection(m_id);
97 }
98
length()99 QJSValue ScriptableItemSelection::length()
100 {
101 return m_proxy->selectionGetSize(m_id);
102 }
103
tab()104 QJSValue ScriptableItemSelection::tab()
105 {
106 return m_proxy->selectionGetTabName(m_id);
107 }
108
valueOf()109 QJSValue ScriptableItemSelection::valueOf()
110 {
111 return toString();
112 }
113
str()114 QJSValue ScriptableItemSelection::str()
115 {
116 return toString();
117 }
118
toString()119 QString ScriptableItemSelection::toString()
120 {
121 const auto rows = m_proxy->selectionGetRows(m_id);
122 const auto tabName = m_proxy->selectionGetTabName(m_id);
123
124 QString rowString;
125 auto it1 = rows.constBegin();
126 while (it1 != rows.constEnd()) {
127 auto it2 = std::adjacent_find(it1, rows.constEnd(), [](int lhs, int rhs) {
128 return lhs + 1 != rhs;
129 });
130 if (it2 == rows.constEnd())
131 --it2;
132 if (it1 == it2)
133 rowString.append(QStringLiteral("%1,").arg(*it1));
134 else if (it1 + 1 == it2)
135 rowString.append(QStringLiteral("%1,%2,").arg(*it1).arg(*it2));
136 else
137 rowString.append(QStringLiteral("%1..%2,").arg(*it1).arg(*it2));
138 it1 = it2 + 1;
139 }
140 rowString.chop(1);
141
142 return QStringLiteral("ItemSelection(tab=\"%1\", rows=[%2])")
143 .arg(tabName, rowString);
144 }
145
selectAll()146 QJSValue ScriptableItemSelection::selectAll()
147 {
148 m_proxy->selectionSelectAll(m_id);
149 return m_self;
150 }
151
select(const QJSValue & re,const QString & mimeFormat)152 QJSValue ScriptableItemSelection::select(const QJSValue &re, const QString &mimeFormat)
153 {
154 const QVariant regexp = re.isUndefined() ? QVariant() : toRegularExpression(re);
155 m_proxy->selectionSelect(m_id, regexp, mimeFormat);
156 return m_self;
157 }
158
selectRemovable()159 QJSValue ScriptableItemSelection::selectRemovable()
160 {
161 m_proxy->selectionSelectRemovable(m_id);
162 return m_self;
163 }
164
invert()165 QJSValue ScriptableItemSelection::invert()
166 {
167 m_proxy->selectionInvert(m_id);
168 return m_self;
169 }
170
deselectIndexes(const QJSValue & indexes)171 QJSValue ScriptableItemSelection::deselectIndexes(const QJSValue &indexes)
172 {
173 const auto indexes2 = toIntVector(indexes);
174 m_proxy->selectionDeselectIndexes(m_id, indexes2);
175 return m_self;
176 }
177
deselectSelection(const QJSValue & selection)178 QJSValue ScriptableItemSelection::deselectSelection(const QJSValue &selection)
179 {
180 const auto other = qobject_cast<ScriptableItemSelection*>(selection.toQObject());
181 m_proxy->selectionDeselectSelection(m_id, other->m_id);
182 return m_self;
183 }
184
current()185 QJSValue ScriptableItemSelection::current()
186 {
187 m_proxy->selectionGetCurrent(m_id);
188 return m_self;
189 }
190
removeAll()191 QJSValue ScriptableItemSelection::removeAll()
192 {
193 m_proxy->selectionRemoveAll(m_id);
194 return m_self;
195 }
196
copy()197 QJSValue ScriptableItemSelection::copy()
198 {
199 const int newId = m_proxy->selectionCopy(m_id);
200 auto cloneQObj = new ScriptableItemSelection(m_tabName);
201 const QJSValue clone = qjsEngine(this)->newQObject(cloneQObj);
202 cloneQObj->m_proxy = m_proxy;
203 cloneQObj->m_id = newId;
204 cloneQObj->m_self = clone;
205 return clone;
206 }
207
rows()208 QJSValue ScriptableItemSelection::rows()
209 {
210 const auto rows = m_proxy->selectionGetRows(m_id);
211 QJSValue array = qjsEngine(this)->newArray(rows.size());
212 for ( int i = 0; i < rows.size(); ++i )
213 array.setProperty( static_cast<quint32>(i), QJSValue(rows[i]) );
214 return array;
215 }
216
itemAtIndex(int index)217 QJSValue ScriptableItemSelection::itemAtIndex(int index)
218 {
219 const auto item = m_proxy->selectionGetItemIndex(m_id, index);
220 return qjsEngine(this)->toScriptValue(item);
221 }
222
setItemAtIndex(int index,const QJSValue & item)223 QJSValue ScriptableItemSelection::setItemAtIndex(int index, const QJSValue &item)
224 {
225 const QVariantMap data = toDataMap(item);
226 m_proxy->selectionSetItemIndex(m_id, index, data);
227 return m_self;
228 }
229
items()230 QJSValue ScriptableItemSelection::items()
231 {
232 const QVariantList dataList = m_proxy->selectionGetItemsData(m_id);
233 return qjsEngine(this)->toScriptValue(dataList);
234 }
235
setItems(const QJSValue & items)236 QJSValue ScriptableItemSelection::setItems(const QJSValue &items)
237 {
238 const QVariantList dataList = toDataMapList(items);
239 m_proxy->selectionSetItemsData(m_id, dataList);
240 return m_self;
241 }
242
itemsFormat(const QJSValue & format)243 QJSValue ScriptableItemSelection::itemsFormat(const QJSValue &format)
244 {
245 const QVariantList dataList = m_proxy->selectionGetItemsFormat(m_id, ::toString(format));
246 return qjsEngine(this)->toScriptValue(dataList);
247 }
248
setItemsFormat(const QJSValue & format,const QJSValue & value)249 QJSValue ScriptableItemSelection::setItemsFormat(const QJSValue &format, const QJSValue &value)
250 {
251 const QVariant variant = value.isUndefined() ? QVariant() : QVariant(toByteArray(value));
252 m_proxy->selectionSetItemsFormat(m_id, ::toString(format), variant);
253 return m_self;
254 }
255
move(int row)256 QJSValue ScriptableItemSelection::move(int row)
257 {
258 m_proxy->selectionMove(m_id, row);
259 return m_self;
260 }
261
init(const QJSValue & self,ScriptableProxy * proxy,const QString & currentTabName)262 void ScriptableItemSelection::init(const QJSValue &self, ScriptableProxy *proxy, const QString ¤tTabName)
263 {
264 m_self = self;
265 m_proxy = proxy;
266 connect(m_proxy, &QObject::destroyed, this, [this](){ m_proxy = nullptr; });
267
268 if ( m_tabName.isEmpty() )
269 m_tabName = currentTabName;
270
271 m_id = m_proxy->createSelection(m_tabName);
272 }
273