1 /**************************************************************************** 2 ** 3 ** Copyright (C) 2015 The Qt Company Ltd. 4 ** Contact: http://www.qt.io/licensing/ 5 ** 6 ** This file is part of the Qt Designer of the Qt Toolkit. 7 ** 8 ** $QT_BEGIN_LICENSE:LGPL$ 9 ** Commercial License Usage 10 ** Licensees holding valid commercial Qt licenses may use this file in 11 ** accordance with the commercial license agreement provided with the 12 ** Software or, alternatively, in accordance with the terms contained in 13 ** a written agreement between you and The Qt Company. For licensing terms 14 ** and conditions see http://www.qt.io/terms-conditions. For further 15 ** information use the contact form at http://www.qt.io/contact-us. 16 ** 17 ** GNU Lesser General Public License Usage 18 ** Alternatively, this file may be used under the terms of the GNU Lesser 19 ** General Public License version 2.1 or version 3 as published by the Free 20 ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 ** following information to ensure the GNU Lesser General Public License 23 ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 ** 26 ** As a special exception, The Qt Company gives you certain additional 27 ** rights. These rights are described in The Qt Company LGPL Exception 28 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 ** 30 ** GNU General Public License Usage 31 ** Alternatively, this file may be used under the terms of the GNU 32 ** General Public License version 3.0 as published by the Free Software 33 ** Foundation and appearing in the file LICENSE.GPL included in the 34 ** packaging of this file. Please review the following information to 35 ** ensure the GNU General Public License version 3.0 requirements will be 36 ** met: http://www.gnu.org/copyleft/gpl.html. 37 ** 38 ** $QT_END_LICENSE$ 39 ** 40 ****************************************************************************/ 41 42 #include "qdesigner_promotiondialog_p.h" 43 #include "promotionmodel_p.h" 44 #include "iconloader_p.h" 45 #include "widgetdatabase_p.h" 46 #include "signalslotdialog_p.h" 47 48 #include <QtDesigner/QDesignerFormEditorInterface> 49 #include <QtDesigner/QDesignerFormWindowInterface> 50 #include <QtDesigner/QDesignerPromotionInterface> 51 #include <QtDesigner/QDesignerWidgetDataBaseItemInterface> 52 #include <QtDesigner/QDesignerIntegrationInterface> 53 #include <abstractdialoggui_p.h> 54 55 #include <QtCore/QTimer> 56 #include <QtGui/QVBoxLayout> 57 #include <QtGui/QHBoxLayout> 58 #include <QtGui/QFormLayout> 59 #include <QtGui/QDialogButtonBox> 60 #include <QtGui/QTreeView> 61 #include <QtGui/QHeaderView> 62 #include <QtGui/QPushButton> 63 #include <QtGui/QItemSelectionModel> 64 #include <QtGui/QItemSelection> 65 #include <QtGui/QComboBox> 66 #include <QtGui/QLineEdit> 67 #include <QtGui/QCheckBox> 68 #include <QtGui/QRegExpValidator> 69 #include <QtGui/QLabel> 70 #include <QtGui/QSpacerItem> 71 #include <QtGui/QMenu> 72 #include <QtGui/QAction> 73 74 QT_BEGIN_NAMESPACE 75 76 namespace qdesigner_internal { 77 // PromotionParameters 78 struct PromotionParameters { 79 QString m_baseClass; 80 QString m_className; 81 QString m_includeFile; 82 }; 83 84 // NewPromotedClassPanel NewPromotedClassPanel(const QStringList & baseClasses,int selectedBaseClass,QWidget * parent)85 NewPromotedClassPanel::NewPromotedClassPanel(const QStringList &baseClasses, 86 int selectedBaseClass, 87 QWidget *parent) : 88 QGroupBox(parent), 89 m_baseClassCombo(new QComboBox), 90 m_classNameEdit(new QLineEdit), 91 m_includeFileEdit(new QLineEdit), 92 m_globalIncludeCheckBox(new QCheckBox), 93 m_addButton(new QPushButton(tr("Add"))) 94 { 95 setTitle(tr("New Promoted Class")); 96 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); 97 QHBoxLayout *hboxLayout = new QHBoxLayout(this); 98 99 m_classNameEdit->setValidator(new QRegExpValidator(QRegExp(QLatin1String("[_a-zA-Z:][:_a-zA-Z0-9]*")), m_classNameEdit)); 100 connect(m_classNameEdit, SIGNAL(textChanged(QString)), this, SLOT(slotNameChanged(QString))); 101 connect(m_includeFileEdit, SIGNAL(textChanged(QString)), this, SLOT(slotIncludeFileChanged(QString))); 102 103 m_baseClassCombo->setEditable(false); 104 m_baseClassCombo->addItems(baseClasses); 105 if (selectedBaseClass != -1) 106 m_baseClassCombo->setCurrentIndex(selectedBaseClass); 107 108 // Grid 109 QFormLayout *formLayout = new QFormLayout(); 110 formLayout->addRow(tr("Base class name:"), m_baseClassCombo); 111 formLayout->addRow(tr("Promoted class name:"), m_classNameEdit); 112 formLayout->addRow(tr("Header file:"), m_includeFileEdit); 113 formLayout->addRow(tr("Global include"), m_globalIncludeCheckBox); 114 hboxLayout->addLayout(formLayout); 115 hboxLayout->addItem(new QSpacerItem(15, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); 116 // Button box 117 QVBoxLayout *buttonLayout = new QVBoxLayout(); 118 119 m_addButton->setAutoDefault(false); 120 connect(m_addButton, SIGNAL(clicked()), this, SLOT(slotAdd())); 121 m_addButton->setEnabled(false); 122 buttonLayout->addWidget(m_addButton); 123 124 QPushButton *resetButton = new QPushButton(tr("Reset")); 125 resetButton->setAutoDefault(false); 126 connect(resetButton, SIGNAL(clicked()), this, SLOT(slotReset())); 127 128 buttonLayout->addWidget(resetButton); 129 buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); 130 hboxLayout->addLayout(buttonLayout); 131 132 enableButtons(); 133 } 134 slotAdd()135 void NewPromotedClassPanel::slotAdd() { 136 bool ok = false; 137 emit newPromotedClass(promotionParameters(), &ok); 138 if (ok) 139 slotReset(); 140 } 141 slotReset()142 void NewPromotedClassPanel::slotReset() { 143 const QString empty; 144 m_classNameEdit->setText(empty); 145 m_includeFileEdit->setText(empty); 146 m_globalIncludeCheckBox->setCheckState(Qt::Unchecked); 147 } 148 grabFocus()149 void NewPromotedClassPanel::grabFocus() { 150 m_classNameEdit->setFocus(Qt::OtherFocusReason); 151 } 152 slotNameChanged(const QString & className)153 void NewPromotedClassPanel::slotNameChanged(const QString &className) { 154 // Suggest a name 155 if (!className.isEmpty()) { 156 const QChar dot(QLatin1Char('.')); 157 QString suggestedHeader = m_promotedHeaderLowerCase ? 158 className.toLower() : className; 159 suggestedHeader.replace(QLatin1String("::"), QString(QLatin1Char('_'))); 160 if (!m_promotedHeaderSuffix.startsWith(dot)) 161 suggestedHeader += dot; 162 suggestedHeader += m_promotedHeaderSuffix; 163 164 const bool blocked = m_includeFileEdit->blockSignals(true); 165 m_includeFileEdit->setText(suggestedHeader); 166 m_includeFileEdit->blockSignals(blocked); 167 } 168 enableButtons(); 169 } 170 slotIncludeFileChanged(const QString &)171 void NewPromotedClassPanel::slotIncludeFileChanged(const QString &){ 172 enableButtons(); 173 } 174 enableButtons()175 void NewPromotedClassPanel::enableButtons() { 176 const bool enabled = !m_classNameEdit->text().isEmpty() && !m_includeFileEdit->text().isEmpty(); 177 m_addButton->setEnabled(enabled); 178 m_addButton->setDefault(enabled); 179 } 180 promotionParameters() const181 PromotionParameters NewPromotedClassPanel::promotionParameters() const { 182 PromotionParameters rc; 183 rc.m_baseClass = m_baseClassCombo->currentText(); 184 rc.m_className = m_classNameEdit->text(); 185 rc.m_includeFile = buildIncludeFile(m_includeFileEdit->text(), 186 m_globalIncludeCheckBox->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal); 187 return rc; 188 } 189 chooseBaseClass(const QString & baseClass)190 void NewPromotedClassPanel::chooseBaseClass(const QString &baseClass) { 191 const int index = m_baseClassCombo->findText (baseClass); 192 if (index != -1) 193 m_baseClassCombo->setCurrentIndex (index); 194 } 195 196 // --------------- QDesignerPromotionDialog QDesignerPromotionDialog(QDesignerFormEditorInterface * core,QWidget * parent,const QString & promotableWidgetClassName,QString * promoteTo)197 QDesignerPromotionDialog::QDesignerPromotionDialog(QDesignerFormEditorInterface *core, 198 QWidget *parent, 199 const QString &promotableWidgetClassName, 200 QString *promoteTo) : 201 QDialog(parent), 202 m_mode(promotableWidgetClassName.isEmpty() || promoteTo == 0 ? ModeEdit : ModeEditChooseClass), 203 m_promotableWidgetClassName(promotableWidgetClassName), 204 m_core(core), 205 m_promoteTo(promoteTo), 206 m_promotion(core->promotion()), 207 m_model(new PromotionModel(core)), 208 m_treeView(new QTreeView), 209 m_buttonBox(0), 210 m_removeButton(new QPushButton(createIconSet(QString::fromUtf8("minus.png")), QString())) 211 { 212 m_buttonBox = createButtonBox(); 213 setModal(true); 214 setWindowTitle(tr("Promoted Widgets")); 215 setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 216 217 QVBoxLayout *vboxLayout = new QVBoxLayout(this); 218 219 // tree view group 220 QGroupBox *treeViewGroup = new QGroupBox(); 221 treeViewGroup->setTitle(tr("Promoted Classes")); 222 QVBoxLayout *treeViewVBoxLayout = new QVBoxLayout(treeViewGroup); 223 // tree view 224 m_treeView->setModel (m_model); 225 m_treeView->setMinimumWidth(450); 226 m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); 227 228 connect(m_treeView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), 229 this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection))); 230 231 connect(m_treeView, SIGNAL(customContextMenuRequested(QPoint)), 232 this, SLOT(slotTreeViewContextMenu(QPoint))); 233 234 QHeaderView *headerView = m_treeView->header(); 235 headerView->setResizeMode(QHeaderView::ResizeToContents); 236 treeViewVBoxLayout->addWidget(m_treeView); 237 // remove button 238 QHBoxLayout *hboxLayout = new QHBoxLayout(); 239 hboxLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); 240 241 m_removeButton->setAutoDefault(false); 242 connect(m_removeButton, SIGNAL(clicked()), this, SLOT(slotRemove())); 243 m_removeButton->setEnabled(false); 244 hboxLayout->addWidget(m_removeButton); 245 treeViewVBoxLayout->addLayout(hboxLayout); 246 vboxLayout->addWidget(treeViewGroup); 247 // Create new panel: Try to be smart and preselect a base class. Default to QFrame 248 const QStringList &baseClassNameList = baseClassNames(m_promotion); 249 int preselectedBaseClass = -1; 250 if (m_mode == ModeEditChooseClass) { 251 preselectedBaseClass = baseClassNameList.indexOf(m_promotableWidgetClassName); 252 } 253 if (preselectedBaseClass == -1) 254 preselectedBaseClass = baseClassNameList.indexOf(QLatin1String("QFrame")); 255 256 NewPromotedClassPanel *newPromotedClassPanel = new NewPromotedClassPanel(baseClassNameList, preselectedBaseClass); 257 newPromotedClassPanel->setPromotedHeaderSuffix(core->integration()->headerSuffix()); 258 newPromotedClassPanel->setPromotedHeaderLowerCase(core->integration()->isHeaderLowercase()); 259 connect(newPromotedClassPanel, SIGNAL(newPromotedClass(PromotionParameters,bool*)), this, SLOT(slotNewPromotedClass(PromotionParameters,bool*))); 260 connect(this, SIGNAL(selectedBaseClassChanged(QString)), 261 newPromotedClassPanel, SLOT(chooseBaseClass(QString))); 262 vboxLayout->addWidget(newPromotedClassPanel); 263 // button box 264 vboxLayout->addWidget(m_buttonBox); 265 // connect model 266 connect(m_model, SIGNAL(includeFileChanged(QDesignerWidgetDataBaseItemInterface*,QString)), 267 this, SLOT(slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface*,QString))); 268 269 connect(m_model, SIGNAL(classNameChanged(QDesignerWidgetDataBaseItemInterface*,QString)), 270 this, SLOT(slotClassNameChanged(QDesignerWidgetDataBaseItemInterface*,QString))); 271 272 // focus 273 if (m_mode == ModeEditChooseClass) 274 newPromotedClassPanel->grabFocus(); 275 276 slotUpdateFromWidgetDatabase(); 277 } 278 createButtonBox()279 QDialogButtonBox *QDesignerPromotionDialog::createButtonBox() { 280 QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Close); 281 282 connect(buttonBox , SIGNAL(accepted()), this, SLOT(slotAcceptPromoteTo())); 283 buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Promote")); 284 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); 285 286 connect(buttonBox , SIGNAL(rejected()), this, SLOT(reject())); 287 return buttonBox; 288 } 289 slotUpdateFromWidgetDatabase()290 void QDesignerPromotionDialog::slotUpdateFromWidgetDatabase() { 291 m_model->updateFromWidgetDatabase(); 292 m_treeView->expandAll(); 293 m_removeButton->setEnabled(false); 294 } 295 delayedUpdateFromWidgetDatabase()296 void QDesignerPromotionDialog::delayedUpdateFromWidgetDatabase() { 297 QTimer::singleShot(0, this, SLOT(slotUpdateFromWidgetDatabase())); 298 } 299 baseClassNames(const QDesignerPromotionInterface * promotion)300 const QStringList &QDesignerPromotionDialog::baseClassNames(const QDesignerPromotionInterface *promotion) { 301 typedef QList<QDesignerWidgetDataBaseItemInterface *> WidgetDataBaseItemList; 302 static QStringList rc; 303 if (rc.empty()) { 304 // Convert the item list into a string list. 305 const WidgetDataBaseItemList dbItems = promotion->promotionBaseClasses(); 306 const WidgetDataBaseItemList::const_iterator cend = dbItems.constEnd(); 307 for (WidgetDataBaseItemList::const_iterator it = dbItems.constBegin() ; it != cend; ++it) { 308 rc.push_back( (*it)->name()); 309 } 310 } 311 return rc; 312 } 313 slotAcceptPromoteTo()314 void QDesignerPromotionDialog::slotAcceptPromoteTo() { 315 Q_ASSERT(m_mode == ModeEditChooseClass); 316 unsigned flags; 317 // Ok pressed: Promote to selected class 318 if (QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags)) { 319 if (flags & CanPromote) { 320 *m_promoteTo = dbItem ->name(); 321 accept(); 322 } 323 } 324 } 325 slotRemove()326 void QDesignerPromotionDialog::slotRemove() { 327 unsigned flags; 328 QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); 329 if (!dbItem || (flags & Referenced)) 330 return; 331 332 QString errorMessage; 333 if (m_promotion->removePromotedClass(dbItem->name(), &errorMessage)) { 334 slotUpdateFromWidgetDatabase(); 335 } else { 336 displayError(errorMessage); 337 } 338 } 339 slotSelectionChanged(const QItemSelection & selected,const QItemSelection &)340 void QDesignerPromotionDialog::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) { 341 // Enable deleting non-referenced items 342 unsigned flags; 343 const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected, flags); 344 m_removeButton->setEnabled(dbItem && !(flags & Referenced)); 345 // In choose mode, can we promote to the class? 346 if (m_mode == ModeEditChooseClass) { 347 const bool enablePromoted = flags & CanPromote; 348 m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enablePromoted); 349 m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(enablePromoted); 350 } 351 // different base? 352 if (dbItem) { 353 const QString baseClass = dbItem->extends(); 354 if (baseClass != m_lastSelectedBaseClass) { 355 m_lastSelectedBaseClass = baseClass; 356 emit selectedBaseClassChanged(m_lastSelectedBaseClass); 357 } 358 } 359 } 360 databaseItemAt(const QItemSelection & selected,unsigned & flags) const361 QDesignerWidgetDataBaseItemInterface *QDesignerPromotionDialog::databaseItemAt(const QItemSelection &selected, unsigned &flags) const { 362 flags = 0; 363 const QModelIndexList indexes = selected.indexes(); 364 if (indexes.empty()) 365 return 0; 366 367 bool referenced; 368 QDesignerWidgetDataBaseItemInterface *dbItem = m_model->databaseItemAt(indexes.front(), &referenced); 369 370 if (dbItem) { 371 if (referenced) 372 flags |= Referenced; 373 // In choose mode, can we promote to the class? 374 if (m_mode == ModeEditChooseClass && dbItem && dbItem->isPromoted() && dbItem->extends() == m_promotableWidgetClassName) 375 flags |= CanPromote; 376 377 } 378 return dbItem; 379 } 380 slotNewPromotedClass(const PromotionParameters & p,bool * ok)381 void QDesignerPromotionDialog::slotNewPromotedClass(const PromotionParameters &p, bool *ok) { 382 QString errorMessage; 383 *ok = m_promotion->addPromotedClass(p.m_baseClass, p.m_className, p.m_includeFile, &errorMessage); 384 if (*ok) { 385 // update and select 386 slotUpdateFromWidgetDatabase(); 387 const QModelIndex newClassIndex = m_model->indexOfClass(p.m_className); 388 if (newClassIndex.isValid()) { 389 m_treeView->selectionModel()->select(newClassIndex, QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows); 390 } 391 } else { 392 displayError(errorMessage); 393 } 394 } 395 slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface * dbItem,const QString & includeFile)396 void QDesignerPromotionDialog::slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &includeFile) { 397 if (includeFile.isEmpty()) { 398 delayedUpdateFromWidgetDatabase(); 399 return; 400 } 401 402 if (dbItem->includeFile() == includeFile) 403 return; 404 405 QString errorMessage; 406 if (!m_promotion->setPromotedClassIncludeFile(dbItem->name(), includeFile, &errorMessage)) { 407 displayError(errorMessage); 408 delayedUpdateFromWidgetDatabase(); 409 } 410 } 411 slotClassNameChanged(QDesignerWidgetDataBaseItemInterface * dbItem,const QString & newName)412 void QDesignerPromotionDialog::slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &newName) { 413 if (newName.isEmpty()) { 414 delayedUpdateFromWidgetDatabase(); 415 return; 416 } 417 const QString oldName = dbItem->name(); 418 if (newName == oldName) 419 return; 420 421 QString errorMessage; 422 if (!m_promotion->changePromotedClassName(oldName , newName, &errorMessage)) { 423 displayError(errorMessage); 424 delayedUpdateFromWidgetDatabase(); 425 } 426 } 427 slotTreeViewContextMenu(const QPoint & pos)428 void QDesignerPromotionDialog::slotTreeViewContextMenu(const QPoint &pos) { 429 unsigned flags; 430 const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); 431 if (!dbItem) 432 return; 433 434 QMenu menu; 435 QAction *signalSlotAction = menu.addAction(tr("Change signals/slots...")); 436 connect(signalSlotAction, SIGNAL(triggered()), this, SLOT(slotEditSignalsSlots())); 437 438 menu.exec(m_treeView->viewport()->mapToGlobal(pos)); 439 } 440 slotEditSignalsSlots()441 void QDesignerPromotionDialog::slotEditSignalsSlots() { 442 unsigned flags; 443 const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); 444 if (!dbItem) 445 return; 446 447 SignalSlotDialog::editPromotedClass(m_core, dbItem->name(), this); 448 } 449 displayError(const QString & message)450 void QDesignerPromotionDialog::displayError(const QString &message) { 451 m_core->dialogGui()->message(this, QDesignerDialogGuiInterface::PromotionErrorMessage, QMessageBox::Warning, 452 tr("%1 - Error").arg(windowTitle()), message, QMessageBox::Close); 453 } 454 } // namespace qdesigner_internal 455 456 QT_END_NAMESPACE 457