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 ®expValidation) 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