1 /*
2 	Actiona
3     Copyright (C) 2005 Jonathan Mercier-Ganady
4 
5 	Actiona 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 3 of the License, or
8 	(at your option) any later version.
9 
10 	Actiona 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, see <http://www.gnu.org/licenses/>.
17 
18 	Contact : jmgr@jmgr.info
19 */
20 
21 #include "codelineedit.h"
22 #include "codeeditordialog.h"
23 #include "settings.h"
24 #include "scriptcompleter.h"
25 #include "parametercontainer.h"
26 #include "codelineeditbutton.h"
27 #include "script.h"
28 #include "actioninstance.h"
29 
30 #include <QMenu>
31 #include <QContextMenuEvent>
32 #include <QPaintEvent>
33 #include <QPainter>
34 #include <QStyleOption>
35 #include <QSettings>
36 #include <QRegExpValidator>
37 #include <QDebug>
38 #include <QToolButton>
39 #include <QCursor>
40 #include <QSet>
41 #include <QMessageBox>
42 #include <QStyleOptionFrame>
43 
44 namespace ActionTools
45 {
CodeLineEdit(QWidget * parent,const QRegExp & regexpValidation)46     CodeLineEdit::CodeLineEdit(QWidget *parent, const QRegExp &regexpValidation)
47         : QLineEdit(parent),
48         mParameterContainer(nullptr),
49 		mCode(false),
50 		mMultiline(false),
51 		mAllowTextCodeChange(true),
52 		mShowEditorButton(true),
53 		mEmbedded(false),
54 		mSwitchTextCode(new QAction(QIcon(QStringLiteral(":/images/code.png")), tr("Set to text/code"), this)),
55 		mOpenEditor(new QAction(QIcon(QStringLiteral(":/images/editor.png")), tr("Open editor"), this)),
56 		mRegExp(regexpValidation),
57 		mCompletionModel(nullptr),
58         mCodeButton(new CodeLineEditButton(this)),
59         mEditorButton(new CodeLineEditButton(this)),
60         mInsertButton(new CodeLineEditButton(this))
61 	{
62         connect(this, &CodeLineEdit::textChanged, this, &CodeLineEdit::onTextChanged);
63         connect(mSwitchTextCode, &QAction::triggered, this, &CodeLineEdit::reverseCode);
64         connect(mOpenEditor, &QAction::triggered, [this](){ openEditor(); });
65         connect(mCodeButton, &CodeLineEditButton::clicked, this, &CodeLineEdit::reverseCode);
66         connect(mEditorButton, &CodeLineEditButton::clicked, [this](){ openEditor(); });
67         connect(mInsertButton, &CodeLineEditButton::clicked, this, &CodeLineEdit::showVariableMenuAsPopup);
68 
69 		QSettings settings;
70 
71 		mSwitchTextCode->setShortcut(QKeySequence(settings.value(QStringLiteral("actions/switchTextCode"), QKeySequence(QStringLiteral("Ctrl+Shift+C"))).toString()));
72 		mSwitchTextCode->setShortcutContext(Qt::WidgetShortcut);
73 		mOpenEditor->setShortcut(QKeySequence(settings.value(QStringLiteral("actions/openEditorKey"), QKeySequence(QStringLiteral("Ctrl+Shift+V"))).toString()));
74 		mOpenEditor->setShortcutContext(Qt::WidgetShortcut);
75 
76 		addAction(mSwitchTextCode);
77 		addAction(mOpenEditor);
78 
79 		mCodeButton->setIcon(QIcon(QStringLiteral(":/images/code.png")));
80 		mCodeButton->setMaximumWidth(14);
81 		mCodeButton->setToolTip(tr("Click here to switch text/code"));
82 
83 		mEditorButton->setIcon(QIcon(QStringLiteral(":/images/editor.png")));
84 		mEditorButton->setMaximumWidth(18);
85 		mEditorButton->setToolTip(tr("Click here to open the editor"));
86 
87 		mInsertButton->setIcon(QIcon(QStringLiteral(":/images/insert.png")));
88         mInsertButton->setMaximumWidth(18);
89         mInsertButton->setToolTip(tr("Click here to insert a variable or a resource"));
90 
91         setMinimumWidth(minimumWidth() + mCodeButton->maximumWidth() + mEditorButton->maximumWidth() + mInsertButton->maximumWidth());
92 
93         setEmbedded(false);
94     }
95 
96     CodeLineEdit::~CodeLineEdit() = default;
97 
setCode(bool code)98 	void CodeLineEdit::setCode(bool code)
99 	{
100 		if(!mAllowTextCodeChange)
101 		   return;
102 
103 		mCode = code;
104 
105 		if(code && mCompletionModel)
106 			setCompleter(new ScriptCompleter(mCompletionModel, this));
107 		else
108 			setCompleter(nullptr);
109 
110 		if(mRegExp != QRegExp())
111 		{
112 			if(code)
113 			{
114 				delete validator();
115 				setValidator(nullptr);
116 			}
117 			else
118 				setValidator(new QRegExpValidator(mRegExp, this));
119 		}
120 
121 		update();
122 
123 		emit codeChanged(mCode);
124 	}
125 
setEmbedded(bool embedded)126 	void CodeLineEdit::setEmbedded(bool embedded)
127 	{
128 		mEmbedded = embedded;
129 
130 		int w = 0;
131 
132 		if(mAllowTextCodeChange)
133 			w += mCodeButton->maximumWidth();
134 		if(mShowEditorButton)
135 			w += mEditorButton->maximumWidth();
136 
137         w += mInsertButton->maximumWidth();
138 
139 		setStyleSheet(QStringLiteral("QLineEdit { padding-right: %1px; }").arg(w));
140 
141 		resizeButtons();
142 		update();
143 	}
144 
setAllowTextCodeChange(bool allowTextCodeChange)145 	void CodeLineEdit::setAllowTextCodeChange(bool allowTextCodeChange)
146 	{
147 		mAllowTextCodeChange = allowTextCodeChange;
148 		mSwitchTextCode->setEnabled(mAllowTextCodeChange);
149 
150 		mCodeButton->setVisible(allowTextCodeChange);
151 
152 		setEmbedded(mEmbedded);
153 
154 		resizeButtons();
155 		update();
156 	}
157 
setShowEditorButton(bool showEditorButton)158 	void CodeLineEdit::setShowEditorButton(bool showEditorButton)
159 	{
160 		mShowEditorButton = showEditorButton;
161 		mOpenEditor->setEnabled(mShowEditorButton);
162 
163 		mEditorButton->setVisible(showEditorButton);
164 
165 		setEmbedded(mEmbedded);
166 
167 		resizeButtons();
168 		update();
169 	}
170 
setFromSubParameter(const SubParameter & subParameter)171 	void CodeLineEdit::setFromSubParameter(const SubParameter &subParameter)
172 	{
173 		if(mAllowTextCodeChange)
174 			setCode(subParameter.isCode());
175 
176         setText(subParameter.value());
177 	}
178 
addShortcuts(QMenu * menu)179 	void CodeLineEdit::addShortcuts(QMenu *menu)
180 	{
181 		menu->addActions(actions());
182 	}
183 
setCompletionModel(QAbstractItemModel * completionModel)184 	void CodeLineEdit::setCompletionModel(QAbstractItemModel *completionModel)
185 	{
186 		mCompletionModel = completionModel;
187 
188 		if(mCode)
189             setCompleter(new ScriptCompleter(mCompletionModel, this));
190     }
191 
setParameterContainer(const ParameterContainer * parameterContainer)192     void CodeLineEdit::setParameterContainer(const ParameterContainer *parameterContainer)
193     {
194         mParameterContainer = parameterContainer;
195     }
196 
findVariables() const197     QSet<QString> CodeLineEdit::findVariables() const
198     {
199         return ActionTools::ActionInstance::findVariables(text(), isCode());
200     }
201 
reverseCode()202 	void CodeLineEdit::reverseCode()
203 	{
204 		setCode(!isCode());
205 	}
206 
onTextChanged(const QString & text)207     void CodeLineEdit::onTextChanged(const QString &text)
208 	{
209 		mMultiline = text.contains(QLatin1Char('\n'));
210 		setReadOnly(mMultiline);
211 	}
212 
openEditor(int line,int column)213 	void CodeLineEdit::openEditor(int line, int column)
214 	{
215 		if(!mShowEditorButton)
216 			return;
217 
218         CodeEditorDialog codeEditorDialog(mCompletionModel, createVariablesMenu(nullptr, true), createResourcesMenu(nullptr, true), this);
219 
220         codeEditorDialog.setWindowFlags(codeEditorDialog.windowFlags() | Qt::WindowContextHelpButtonHint);
221 
222 		codeEditorDialog.setText(text());
223 		codeEditorDialog.setCode(isCode());
224 		codeEditorDialog.setCurrentLine(line);
225 		codeEditorDialog.setCurrentColumn(column);
226 		codeEditorDialog.setAllowTextCodeChange(mAllowTextCodeChange);
227 
228 		if(codeEditorDialog.exec() == QDialog::Accepted)
229 		{
230 			setText(codeEditorDialog.text());
231 			setCode(codeEditorDialog.isCode());
232         }
233     }
234 
contextMenuEvent(QContextMenuEvent * event)235 	void CodeLineEdit::contextMenuEvent(QContextMenuEvent *event)
236 	{
237 		QMenu *menu = createStandardContextMenu();
238 
239 		menu->addSeparator();
240 
241 		addShortcuts(menu);
242 
243         menu->addSeparator();
244 
245         addVariablesAndResourcesMenus(menu);
246 
247 		menu->exec(event->globalPos());
248 
249 		delete menu;
250 
251 		event->accept();
252 	}
253 
resizeEvent(QResizeEvent * event)254 	void CodeLineEdit::resizeEvent(QResizeEvent *event)
255 	{
256 		resizeButtons();
257 
258         QLineEdit::resizeEvent(event);
259     }
260 
insertVariable(const QString & variable)261     void CodeLineEdit::insertVariable(const QString &variable)
262     {
263         //If a validator is set this means that the insertion will fail
264         //In this case, reset the content and set the code mode
265         if(validator())
266         {
267             if(!text().isEmpty() && QMessageBox::question(this, tr("Insert variable/resource"), tr("Inserting a variable or a resource will replace the current parameter value.\nAre you sure?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) != QMessageBox::Yes)
268                 return;
269 
270             setCode(true);
271             setText(QString());
272         }
273 
274         //Temporarily remove the completer so that we don't get a popup
275         QCompleter *currentCompleter = completer();
276         if(currentCompleter)
277         {
278             currentCompleter->setParent(nullptr);
279             setCompleter(nullptr);
280         }
281 
282         if(isCode())
283             insert(variable);
284         else
285 			insert(QStringLiteral("$") + variable);
286 
287         if(currentCompleter)
288         {
289             currentCompleter->setParent(this);
290             setCompleter(currentCompleter);
291         }
292     }
293 
insertVariable(QAction * action)294     void CodeLineEdit::insertVariable(QAction *action)
295     {
296         insertVariable(action->text());
297     }
298 
createVariablesMenu(QMenu * parentMenu,bool ignoreMultiline)299     QMenu *CodeLineEdit::createVariablesMenu(QMenu *parentMenu, bool ignoreMultiline)
300     {
301         QMenu *variablesMenu = nullptr;
302 
303         if(!ignoreMultiline && isMultiline())
304         {
305             variablesMenu = new QMenu(tr("Cannot insert in a multiline parameter"), parentMenu);
306             variablesMenu->setEnabled(false);
307         }
308         else
309         {
310             Q_ASSERT(mParameterContainer);
311             variablesMenu = mParameterContainer->createVariablesMenu(parentMenu);
312             if(variablesMenu)
313             {
314                 variablesMenu->setTitle(tr("Insert variable"));
315             }
316             else
317             {
318                 variablesMenu = new QMenu(tr("No variables to insert"), parentMenu);
319                 variablesMenu->setEnabled(false);
320             }
321         }
322 
323 		variablesMenu->setIcon(QIcon(QStringLiteral(":/images/variable.png")));
324 
325         return variablesMenu;
326     }
327 
createResourcesMenu(QMenu * parentMenu,bool ignoreMultiline)328     QMenu *CodeLineEdit::createResourcesMenu(QMenu *parentMenu, bool ignoreMultiline)
329     {
330         QMenu *resourceMenu = nullptr;
331 
332         if(!ignoreMultiline && isMultiline())
333         {
334             resourceMenu = new QMenu(tr("Cannot insert in a multiline parameter"), parentMenu);
335             resourceMenu->setEnabled(false);
336         }
337         else
338         {
339             Q_ASSERT(mParameterContainer);
340             resourceMenu = mParameterContainer->createResourcesMenu(parentMenu);
341             if(resourceMenu)
342                 resourceMenu->setTitle(tr("Insert resource"));
343             else
344             {
345                 resourceMenu = new QMenu(tr("No resources to insert"), parentMenu);
346                 resourceMenu->setEnabled(false);
347             }
348         }
349 
350 		resourceMenu->setIcon(QIcon(QStringLiteral(":/images/resource.png")));
351 
352         return resourceMenu;
353     }
354 
showVariableMenuAsPopup()355     void CodeLineEdit::showVariableMenuAsPopup()
356     {
357         auto menu = new QMenu;
358 
359         addVariablesAndResourcesMenus(menu);
360 
361         menu->exec(QCursor::pos());
362 
363         delete menu;
364     }
365 
resizeButtons()366 	void CodeLineEdit::resizeButtons()
367 	{
368 		QRect codeButtonGeometry;
369         QRect editorButtonGeometry;
370         QRect insertButtonGeometry;
371 
372 		codeButtonGeometry.setX(rect().right() - mCodeButton->maximumWidth() + (mEmbedded ? 1 : 0));
373 		codeButtonGeometry.setY(rect().top() + (mEmbedded ? -1 : 0));
374 		codeButtonGeometry.setWidth(mCodeButton->maximumWidth());
375 		codeButtonGeometry.setHeight(height() + (mEmbedded ? 2 : 0));
376 
377 		mCodeButton->setGeometry(codeButtonGeometry);
378 
379         insertButtonGeometry.setX(rect().right()
380                                   - (mShowEditorButton ? mEditorButton->maximumWidth() : 0)
381                                   - (mAllowTextCodeChange ? codeButtonGeometry.width() : 0)
382                                   + (mEmbedded ? 2 : 1));
383         insertButtonGeometry.setY(rect().top() + (mEmbedded ? -1 : 0));
384         insertButtonGeometry.setWidth(mInsertButton->maximumWidth());
385         insertButtonGeometry.setHeight(height() + (mEmbedded ? 2 : 0));
386 
387         mInsertButton->setGeometry(insertButtonGeometry);
388 
389         editorButtonGeometry.setX(rect().right()
390                                   - (mShowEditorButton ? mEditorButton->maximumWidth() : 0)
391                                   - (mAllowTextCodeChange ? codeButtonGeometry.width() : 0)
392                                   - insertButtonGeometry.width()
393                                   + (mEmbedded ? 2 : 1));
394         editorButtonGeometry.setY(rect().top() + (mEmbedded ? -1 : 0));
395         editorButtonGeometry.setWidth(mEditorButton->maximumWidth());
396         editorButtonGeometry.setHeight(height() + (mEmbedded ? 2 : 0));
397 
398         mEditorButton->setGeometry(editorButtonGeometry);
399     }
400 
addVariablesAndResourcesMenus(QMenu * menu)401     void CodeLineEdit::addVariablesAndResourcesMenus(QMenu *menu)
402     {
403         QMenu *variablesMenu = createVariablesMenu(menu);
404         connect(variablesMenu, &QMenu::triggered, this, static_cast<void (CodeLineEdit::*)(QAction *action)>(&CodeLineEdit::insertVariable));
405         menu->addMenu(variablesMenu);
406 
407         QMenu *resourcesMenu = createResourcesMenu(menu);
408         connect(resourcesMenu, &QMenu::triggered, this, static_cast<void (CodeLineEdit::*)(QAction *action)>(&CodeLineEdit::insertVariable));
409         menu->addMenu(resourcesMenu);
410     }
411 
mouseMoveEvent(QMouseEvent * event)412 	void CodeLineEdit::mouseMoveEvent(QMouseEvent *event)
413 	{
414 		if(!mMultiline)
415 			QLineEdit::mouseMoveEvent(event);
416 		else
417 			event->ignore();
418 	}
419 
mouseDoubleClickEvent(QMouseEvent * event)420 	void CodeLineEdit::mouseDoubleClickEvent(QMouseEvent *event)
421 	{
422 		if(mMultiline)
423 			emit openEditor();
424 
425 		QLineEdit::mouseDoubleClickEvent(event);
426 	}
427 
paintEvent(QPaintEvent * event)428 	void CodeLineEdit::paintEvent(QPaintEvent *event)
429 	{
430 		if(!mMultiline)
431 			QLineEdit::paintEvent(event);
432 
433 		if(mMultiline || mCode)
434 		{
435 			QPainter painter(this);
436 
437 			if(mMultiline)
438 			{
439                 QStyleOptionFrame panel;
440 				panel.initFrom(this);
441 
442 				if(!mEmbedded)
443 				{
444 					panel.lineWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &panel, this);
445 					panel.midLineWidth = 0;
446 					panel.state |= QStyle::State_Sunken;
447 
448 					style()->drawPrimitive(QStyle::PE_PanelLineEdit, &panel, &painter, this);
449 				}
450 
451 				painter.setBrush(panel.palette.text());
452 				QFont italicFont = font();
453 				italicFont.setStyle(QFont::StyleItalic);
454 				painter.setFont(italicFont);
455 
456 				QPalette pal = palette();
457 				pal.setCurrentColorGroup(QPalette::Disabled);
458 
459                 style()->drawItemText(&painter, rect(), Qt::AlignCenter, pal, false, tr("Multiline, double-click to edit"), QPalette::Text);
460 			}
461 
462 			if(mCode)
463 			{
464 				QPolygon polygon;
465 				QColor color;
466 
467 				if(isEnabled())
468 					color = QColor(255, 0, 0, 200);
469 				else
470 					color = QColor(100, 0, 0, 200);
471 
472 				painter.setPen(Qt::NoPen);
473 
474 				int offset = (mEmbedded ? 0 : 4);
475 
476 				polygon << QPoint(offset, offset)
477 						<< QPoint(6 + offset, offset)
478 						<< QPoint(offset, 6 + offset);
479 
480 				painter.setBrush(QBrush(color));
481 				painter.drawPolygon(polygon);
482 			}
483         }
484     }
485 }
486