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 &currentTabName)
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