1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program 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 this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "ActorCfgModel.h"
23 
24 #include <QDebug>
25 
26 #include <U2Core/Log.h>
27 #include <U2Core/U2SafePoints.h>
28 
29 #include <U2Lang/BaseTypes.h>
30 #include <U2Lang/Datatype.h>
31 #include <U2Lang/IntegralBusType.h>
32 #include <U2Lang/URLAttribute.h>
33 #include <U2Lang/WorkflowSettings.h>
34 #include <U2Lang/WorkflowUtils.h>
35 
36 #include "WorkflowEditor.h"
37 #include "WorkflowEditorDelegates.h"
38 
39 namespace U2 {
40 
41 /*****************************
42  * ActorCfgModel
43  *****************************/
44 static const int KEY_COLUMN = 0;
45 static const int VALUE_COLUMN = 1;
46 static const int SCRIPT_COLUMN = 2;
47 
ActorCfgModel(QObject * parent,SchemaConfig * _schemaConfig)48 ActorCfgModel::ActorCfgModel(QObject *parent, SchemaConfig *_schemaConfig)
49     : QAbstractTableModel(parent), schemaConfig(_schemaConfig), subject(nullptr), scriptMode(false) {
50     scriptDelegate = new AttributeScriptDelegate();
51 }
52 
~ActorCfgModel()53 ActorCfgModel::~ActorCfgModel() {
54     delete scriptDelegate;
55 }
56 
setActor(Actor * cfg)57 void ActorCfgModel::setActor(Actor *cfg) {
58     listValues.clear();
59     attrs.clear();
60     subject = cfg;
61     if (nullptr != cfg) {
62         attrs = cfg->getAttributes();
63         setupAttributesScripts();
64 
65         ConfigurationEditor *editor = subject->getEditor();
66         if (nullptr != editor) {
67             foreach (Attribute *attr, attrs) {
68                 PropertyDelegate *delegate = editor->getDelegate(attr->getId());
69                 if (nullptr != delegate) {
70                     delegate->setSchemaConfig(schemaConfig);
71                 }
72             }
73         }
74     }
75     beginResetModel();
76     endResetModel();
77 }
78 
dumpDescriptors(const QList<Descriptor> & descriptors)79 void dumpDescriptors(const QList<Descriptor> &descriptors) {
80     foreach (const Descriptor &d, descriptors) {
81         qDebug() << d.getId() << d.getDisplayName();
82     }
83 }
84 
setupAttributesScripts()85 void ActorCfgModel::setupAttributesScripts() {
86     foreach (Attribute *attribute, attrs) {
87         assert(attribute != nullptr);
88         attribute->getAttributeScript().clearScriptVars();
89 
90         DataTypePtr attributeType = attribute->getAttributeType();
91         // FIXME: add support for all types in scripting
92         if (attributeType != BaseTypes::STRING_TYPE() && attributeType != BaseTypes::NUM_TYPE()) {
93             continue;
94         }
95 
96         foreach (const PortDescriptor *descr, subject->getProto()->getPortDesciptors()) {
97             if (descr->isInput()) {
98                 DataTypePtr dataTypePtr = descr->getType();
99                 if (dataTypePtr->isMap()) {
100                     QMap<Descriptor, DataTypePtr> map = dataTypePtr->getDatatypesMap();
101                     foreach (const Descriptor &desc, map.keys()) {
102                         QString id = desc.getId().replace(QRegExp("[^a-zA-Z0-9_]"), "_");
103                         if (id.at(0).isDigit()) {
104                             id.prepend("_");
105                         }
106                         Descriptor d(id, desc.getDisplayName(), desc.getDocumentation());
107                         attribute->getAttributeScript().setScriptVar(d, QVariant());
108                     }
109                 } else if (dataTypePtr->isList()) {
110                     foreach (const Descriptor &typeDescr, dataTypePtr->getAllDescriptors()) {
111                         QString id = typeDescr.getId().replace(QRegExp("[^a-zA-Z0-9_]"), "_");
112                         if (id.at(0).isDigit()) {
113                             id.prepend("_");
114                         }
115                         Descriptor d(id, typeDescr.getDisplayName(), typeDescr.getDocumentation());
116                         attribute->getAttributeScript().setScriptVar(d, QVariant());
117                     }
118                 } else {
119                     QString id = dataTypePtr->getId().replace(QRegExp("[^a-zA-Z0-9_]"), "_");
120                     if (id.at(0).isDigit()) {
121                         id.prepend("_");
122                     }
123                     QString displayName = dataTypePtr->getDisplayName();
124                     QString doc = dataTypePtr->getDocumentation();
125                     attribute->getAttributeScript().setScriptVar(Descriptor(id, displayName, doc), QVariant());
126                 }
127             }
128         }
129 
130         QString attrVarName = attribute->getDisplayName();
131         QString id = attribute->getId().replace(QRegExp("[^a-zA-Z0-9_]"), "_");
132         if (id.at(0).isDigit()) {
133             id.prepend("_");
134         }
135         attribute->getAttributeScript().setScriptVar(Descriptor(id, attrVarName, attribute->getDocumentation()), QVariant());
136     }
137 }
138 
update()139 void ActorCfgModel::update() {
140     beginResetModel();
141     endResetModel();
142 }
143 
columnCount(const QModelIndex &) const144 int ActorCfgModel::columnCount(const QModelIndex &) const {
145     if (scriptMode) {
146         return 3;  // key, value and script
147     } else {
148         return 2;
149     }
150 }
151 
rowCount(const QModelIndex & parent) const152 int ActorCfgModel::rowCount(const QModelIndex &parent) const {
153     if (parent.isValid()) {
154         return 0;
155     }
156 
157     return attrs.isEmpty() || parent.isValid() ? 0 : attrs.size() /*rows*/;
158 }
159 
isVisible(Attribute * a) const160 bool ActorCfgModel::isVisible(Attribute *a) const {
161     CHECK(nullptr != subject, true);
162     if (nullptr != dynamic_cast<URLAttribute *>(a)) {
163         return false;
164     }
165     return subject->isAttributeVisible(a);
166 }
167 
flags(const QModelIndex & index) const168 Qt::ItemFlags ActorCfgModel::flags(const QModelIndex &index) const {
169     int col = index.column();
170     int row = index.row();
171 
172     Attribute *currentAttribute = getAttributeByRow(row);
173     SAFE_POINT(nullptr != currentAttribute, "Unexpected attribute", Qt::NoItemFlags);
174     if (!isVisible(currentAttribute)) {
175         return Qt::NoItemFlags;
176     }
177 
178     switch (col) {
179         case KEY_COLUMN:
180             return Qt::ItemIsEnabled;
181         case VALUE_COLUMN:
182             return row < attrs.size() ? Qt::ItemIsEditable | Qt::ItemIsEnabled : Qt::ItemIsEnabled;
183         case SCRIPT_COLUMN: {
184             if (row < attrs.size()) {
185                 // FIXME: add support for all types in scripting
186                 if (currentAttribute->getAttributeType() != BaseTypes::STRING_TYPE() && currentAttribute->getAttributeType() != BaseTypes::NUM_TYPE()) {
187                     return Qt::ItemIsEnabled;
188                 } else {
189                     return Qt::ItemIsEditable | Qt::ItemIsEnabled;
190                 }
191             } else {
192                 return Qt::ItemIsEnabled;
193             }
194         }
195         default:
196             assert(false);
197     }
198     // unreachable code
199     return Qt::NoItemFlags;
200 }
201 
headerData(int section,Qt::Orientation orientation,int role) const202 QVariant ActorCfgModel::headerData(int section, Qt::Orientation orientation, int role) const {
203     if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
204         switch (section) {
205             case KEY_COLUMN:
206                 return WorkflowEditor::tr("Name");
207             case VALUE_COLUMN:
208                 return WorkflowEditor::tr("Value");
209             case SCRIPT_COLUMN:
210                 return WorkflowEditor::tr("Script");
211             default:
212                 assert(false);
213         }
214     }
215     // unreachable code
216     return QVariant();
217 }
218 
setAttributeValue(const Attribute * attr,QVariant & attrValue) const219 bool ActorCfgModel::setAttributeValue(const Attribute *attr, QVariant &attrValue) const {
220     assert(attr != nullptr);
221     bool isDefaultVal = attr->isDefaultValue();
222 
223     attrValue = attr->getAttributePureValue();
224     return isDefaultVal;
225 }
226 
getAttributeByRow(int row) const227 Attribute *ActorCfgModel::getAttributeByRow(int row) const {
228     SAFE_POINT(row < attrs.size(), "Unexpected row requested", nullptr);
229     return attrs.at(row);
230 }
231 
modelIndexById(const QString & id) const232 QModelIndex ActorCfgModel::modelIndexById(const QString &id) const {
233     for (int i = 0; i < attrs.size(); i++) {
234         Attribute *a = getAttributeByRow(i);
235         if (nullptr != a && a->getId() == id) {
236             QModelIndex modelIndex = index(i, 1);
237             return modelIndex;
238         }
239     }
240     return QModelIndex();
241 }
242 
data(const QModelIndex & index,int role) const243 QVariant ActorCfgModel::data(const QModelIndex &index, int role) const {
244     const Attribute *currentAttribute = getAttributeByRow(index.row());
245     SAFE_POINT(nullptr != currentAttribute, "Invalid attribute", QVariant());
246     if (role == DescriptorRole) {  // descriptor that will be shown in under editor. 'propDoc' in WorkflowEditor
247         return qVariantFromValue<Descriptor>(*currentAttribute);
248     }
249 
250     int col = index.column();
251     switch (col) {
252         case KEY_COLUMN: {
253             switch (role) {
254                 case Qt::DisplayRole:
255                     return currentAttribute->getDisplayName();
256                 case Qt::ToolTipRole:
257                     return currentAttribute->getDocumentation();
258                 case Qt::FontRole:
259                     if (currentAttribute->isRequiredAttribute()) {
260                         QFont fnt;
261                         fnt.setBold(true);
262                         return QVariant(fnt);
263                     }
264                     return QVariant();
265                 default:
266                     return QVariant();
267             }
268         }
269         case VALUE_COLUMN: {
270             if (role == ConfigurationEditor::ItemListValueRole) {
271                 return listValues.value(currentAttribute->getId());
272             }
273 
274             QVariant attributeValue;
275             bool isDefaultVal = setAttributeValue(currentAttribute, attributeValue);
276             ConfigurationEditor *confEditor = subject->getEditor();
277             PropertyDelegate *propertyDelegate = confEditor ? confEditor->getDelegate(currentAttribute->getId()) : nullptr;
278             switch (role) {
279                 case Qt::DisplayRole:
280                 case Qt::ToolTipRole: {
281                     if (propertyDelegate) {
282                         return propertyDelegate->getDisplayValue(attributeValue);
283                     } else {
284                         QString valueStr = WorkflowUtils::getStringForParameterDisplayRole(attributeValue);
285                         return !valueStr.isEmpty() ? valueStr : attributeValue;
286                     }
287                 }
288                 case Qt::ForegroundRole:
289                     return isDefaultVal ? QVariant(QColor(Qt::gray)) : QVariant();
290                 case DelegateRole:
291                     return qVariantFromValue<PropertyDelegate *>(propertyDelegate);
292                 case Qt::EditRole:
293                 case ConfigurationEditor::ItemValueRole:
294                     return attributeValue;
295                 default:
296                     return QVariant();
297             }
298         }
299         case SCRIPT_COLUMN: {
300             // FIXME: add support for all types in scripting
301             if (currentAttribute->getAttributeType() != BaseTypes::STRING_TYPE() && currentAttribute->getAttributeType() != BaseTypes::NUM_TYPE()) {
302                 if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
303                     return QVariant(tr("N/A"));
304                 } else {
305                     return QVariant();
306                 }
307             }
308 
309             // for STRING type
310             switch (role) {
311                 case Qt::DisplayRole:
312                 case Qt::ToolTipRole:
313                     return scriptDelegate ? scriptDelegate->getDisplayValue(qVariantFromValue<AttributeScript>(currentAttribute->getAttributeScript())) : QVariant();
314                 case Qt::ForegroundRole:
315                     return currentAttribute->getAttributeScript().isEmpty() ? QVariant(QColor(Qt::gray)) : QVariant();
316                 case DelegateRole:
317                     assert(scriptDelegate != nullptr);
318                     return qVariantFromValue<PropertyDelegate *>(scriptDelegate);
319                 case Qt::EditRole:
320                 case ConfigurationEditor::ItemValueRole:
321                     return qVariantFromValue<AttributeScript>(currentAttribute->getAttributeScript());
322                 default:
323                     return QVariant();
324             }
325         }
326         default:
327             assert(false);
328     }
329     // unreachable code
330     return QVariant();
331 }
332 
canSetData(Attribute * attr,const QVariant & value)333 bool ActorCfgModel::canSetData(Attribute *attr, const QVariant &value) {
334     bool dir = false;
335     bool isOutUrlAttr = RFSUtils::isOutUrlAttribute(attr, subject, dir);
336     CHECK(isOutUrlAttr, true);
337 
338     RunFileSystem *rfs = schemaConfig->getRFS();
339     return rfs->canAdd(value.toString(), dir);
340 }
341 
342 namespace {
343 
getTags(Actor * subject,const QString & attrId)344 DelegateTags *getTags(Actor *subject, const QString &attrId) {
345     ConfigurationEditor *editor = subject->getEditor();
346     CHECK(nullptr != editor, nullptr);
347     PropertyDelegate *delegate = editor->getDelegate(attrId);
348     CHECK(nullptr != delegate, nullptr);
349     return delegate->tags();
350 }
351 
352 }  // namespace
353 
getAttributeRelatedVisibility(Attribute * changedAttr,const QMap<Attribute *,bool> & foundRelatedAttrs) const354 QMap<Attribute *, bool> ActorCfgModel::getAttributeRelatedVisibility(Attribute *changedAttr, const QMap<Attribute *, bool> &foundRelatedAttrs) const {
355     QMap<Attribute *, bool> relatedAttributesVisibility = foundRelatedAttrs;
356     foreach (Attribute *a, attrs) {
357         if (a != changedAttr && !relatedAttributesVisibility.contains(a)) {
358             foreach (const AttributeRelation *rel, a->getRelations()) {
359                 if (rel->getRelatedAttrId() == changedAttr->getId()) {
360                     relatedAttributesVisibility.insert(a, isVisible(a));
361                     const QMap<Attribute *, bool> dependentAttributeVisibility = getAttributeRelatedVisibility(a, relatedAttributesVisibility);
362                     foreach (Attribute *dependentAttr, dependentAttributeVisibility.keys()) {
363                         relatedAttributesVisibility[dependentAttr] = dependentAttributeVisibility[dependentAttr];
364                     }
365                 }
366             }
367         }
368     }
369     return relatedAttributesVisibility;
370 }
371 
checkIfAttributeVisibilityChanged(const QMap<Attribute *,bool> & attributeVisibility)372 void ActorCfgModel::checkIfAttributeVisibilityChanged(const QMap<Attribute *, bool> &attributeVisibility) {
373     foreach (Attribute *a, attributeVisibility.keys()) {
374         if (attributeVisibility[a] != isVisible(a)) {
375             const QModelIndex affectedIndex = modelIndexById(a->getId());
376             emit dataChanged(affectedIndex, affectedIndex);
377         }
378     }
379 }
380 
setData(const QModelIndex & index,const QVariant & value,int role)381 bool ActorCfgModel::setData(const QModelIndex &index, const QVariant &value, int role) {
382     int col = index.column();
383     Attribute *editingAttribute = getAttributeByRow(index.row());
384     SAFE_POINT(editingAttribute != nullptr, "Invalid attribute detected", false);
385 
386     switch (col) {
387         case VALUE_COLUMN: {
388             switch (role) {
389                 case ConfigurationEditor::ItemListValueRole: {
390                     listValues.insert(editingAttribute->getId(), value);
391                     return true;
392                 }
393                 case Qt::EditRole:
394                 case ConfigurationEditor::ItemValueRole: {
395                     QMap<Attribute *, bool> relatedAttributesVisibility = getAttributeRelatedVisibility(editingAttribute);
396 
397                     const QString &key = editingAttribute->getId();
398                     if (editingAttribute->getAttributePureValue() != value) {
399                         subject->setParameter(key, value);
400                         emit dataChanged(index, index);
401                         uiLog.trace("committed property change");
402                     }
403                     foreach (const AttributeRelation *relation, editingAttribute->getRelations()) {
404                         if (relation->valueChangingRelation()) {
405                             DelegateTags *inf = getTags(subject, editingAttribute->getId());
406                             DelegateTags *dep = getTags(subject, relation->getRelatedAttrId());
407                             Attribute *depAttr = subject->getParameter(relation->getRelatedAttrId());
408                             QVariant newValue = relation->getAffectResult(value, depAttr->getAttributePureValue(), inf, dep);
409 
410                             if (canSetData(depAttr, newValue)) {
411                                 QModelIndex idx = modelIndexById(relation->getRelatedAttrId());
412                                 setData(idx, newValue);
413                             }
414                         }
415                     }
416                     checkIfAttributeVisibilityChanged(relatedAttributesVisibility);
417                     subject->updateItemsAvailability(editingAttribute);
418 
419                     return true;
420                 }
421                 default:
422                     return false;
423             }
424         }
425         case SCRIPT_COLUMN: {
426             switch (role) {
427                 case Qt::EditRole:
428                 case ConfigurationEditor::ItemValueRole: {
429                     AttributeScript attrScript = value.value<AttributeScript>();
430                     editingAttribute->getAttributeScript().setScriptText(attrScript.getScriptText());
431                     emit dataChanged(index, index);
432                     uiLog.trace(QString("user script for '%1' attribute updated").arg(editingAttribute->getDisplayName()));
433                     return true;
434                 }
435                 default:
436                     return false;
437             }
438         }
439         default:
440             assert(false);
441     }
442 
443     // unreachable code
444     return false;
445 }
446 
changeScriptMode(bool _mode)447 void ActorCfgModel::changeScriptMode(bool _mode) {
448     scriptMode = _mode;
449     update();
450 }
451 
getScriptMode() const452 bool ActorCfgModel::getScriptMode() const {
453     return scriptMode;
454 }
455 
456 }  // namespace U2
457