File indexing completed on 2024-04-28 04:37:47
0001 /* 0002 SPDX-FileCopyrightText: 2008 Andreas Pakulat <apaku@gmx.de> 0003 SPDX-FileCopyrightText: 2010 Aleix Pol Gonzalez <aleixpol@kde.org> 0004 SPDX-FileCopyrightText: 2017-2018 Friedrich W. H. Kossebau <kossebau@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "vcspluginhelper.h" 0010 0011 #include <QAction> 0012 #include <QApplication> 0013 #include <QClipboard> 0014 #include <QDialogButtonBox> 0015 #include <QFileInfo> 0016 #include <QMenu> 0017 #include <QTimer> 0018 #include <QVBoxLayout> 0019 #include <QVariant> 0020 0021 #include <KLocalizedString> 0022 #include <KMessageBox> 0023 #include <KParts/MainWindow> 0024 #include <KTextEditor/AnnotationInterface> 0025 #include <KTextEditor/Document> 0026 #include <KTextEditor/ModificationInterface> 0027 #include <KTextEditor/View> 0028 0029 #include <interfaces/context.h> 0030 #include <interfaces/contextmenuextension.h> 0031 #include <interfaces/icore.h> 0032 #include <interfaces/idocument.h> 0033 #include <interfaces/idocumentcontroller.h> 0034 #include <interfaces/iplugin.h> 0035 #include <interfaces/iplugincontroller.h> 0036 #include <interfaces/iproject.h> 0037 #include <interfaces/iprojectcontroller.h> 0038 #include <interfaces/iruncontroller.h> 0039 #include <interfaces/isession.h> 0040 #include <interfaces/iuicontroller.h> 0041 #include <util/path.h> 0042 #include <util/scopeddialog.h> 0043 #include <vcs/interfaces/ibasicversioncontrol.h> 0044 #include <vcs/models/vcsannotationmodel.h> 0045 #include <vcs/widgets/vcsannotationitemdelegate.h> 0046 #include <vcs/widgets/vcseventwidget.h> 0047 #include <vcs/widgets/vcscommitdialog.h> 0048 #include <vcs/vcsjob.h> 0049 #include <vcs/vcsrevision.h> 0050 #include <vcs/vcslocation.h> 0051 #include <vcs/vcsdiff.h> 0052 0053 #include "interfaces/idistributedversioncontrol.h" 0054 #include "vcsevent.h" 0055 #include "debug.h" 0056 #include "widgets/vcsdiffpatchsources.h" 0057 0058 namespace KDevelop 0059 { 0060 0061 class VcsPluginHelperPrivate 0062 { 0063 public: 0064 IPlugin * plugin; 0065 IBasicVersionControl * vcs; 0066 0067 QList<QUrl> ctxUrls; 0068 QAction* commitAction; 0069 QAction* addAction; 0070 QAction* updateAction; 0071 QAction* historyAction; 0072 QAction* annotationAction; 0073 QAction* diffToBaseAction; 0074 QAction* revertAction; 0075 QAction* diffForRevAction; 0076 QAction* diffForRevGlobalAction; 0077 QAction* pushAction; 0078 QAction* pullAction; 0079 0080 void createActions(VcsPluginHelper* parent) { 0081 auto iconWithFallback = [] (const QString &icon, const QString &fallback) { 0082 return QIcon::fromTheme(icon, QIcon::fromTheme(fallback)); 0083 }; 0084 commitAction = new QAction(iconWithFallback(QStringLiteral("vcs-commit"), QStringLiteral("svn-commit")), i18nc("@action:inmenu", "Commit..."), parent); 0085 updateAction = new QAction(iconWithFallback(QStringLiteral("vcs-pull"), QStringLiteral("svn-update")), i18nc("@action:inmenu", "Update"), parent); 0086 addAction = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18nc("@action:inmenu", "Add"), parent); 0087 diffToBaseAction = new QAction(iconWithFallback(QStringLiteral("vcs-diff"), QStringLiteral("text-x-patch")), i18nc("@action:inmenu", "Show Differences..."), parent); 0088 revertAction = new QAction(QIcon::fromTheme(QStringLiteral("archive-remove")), i18nc("@action:inmenu", "Revert"), parent); 0089 historyAction = new QAction(QIcon::fromTheme(QStringLiteral("view-history")), i18nc("@action:inmenu revision history", "History..."), parent); 0090 annotationAction = new QAction(QIcon::fromTheme(QStringLiteral("user-properties")), i18nc("@action:inmenu", "Annotation..."), parent); 0091 diffForRevAction = new QAction(iconWithFallback(QStringLiteral("vcs-diff"), QStringLiteral("text-x-patch")), i18nc("@action:inmenu", "Show Diff..."), parent); 0092 diffForRevGlobalAction = new QAction(iconWithFallback(QStringLiteral("vcs-diff"), QStringLiteral("text-x-patch")), i18nc("@action:inmenu", "Show Diff (All Files)..."), parent); 0093 pushAction = new QAction(iconWithFallback(QStringLiteral("vcs-push"), QStringLiteral("arrow-up-double")), i18nc("@action:inmenu", "Push"), parent); 0094 pullAction = new QAction(iconWithFallback(QStringLiteral("vcs-pull"), QStringLiteral("arrow-down-double")), i18nc("@action:inmenu", "Pull"), parent); 0095 0096 QObject::connect(commitAction, &QAction::triggered, parent, &VcsPluginHelper::commit); 0097 QObject::connect(addAction, &QAction::triggered, parent, &VcsPluginHelper::add); 0098 QObject::connect(updateAction, &QAction::triggered, parent, &VcsPluginHelper::update); 0099 QObject::connect(diffToBaseAction, &QAction::triggered, parent, &VcsPluginHelper::diffToBase); 0100 QObject::connect(revertAction, &QAction::triggered, parent, &VcsPluginHelper::revert); 0101 QObject::connect(historyAction, &QAction::triggered, parent, [=] { parent->history(); }); 0102 QObject::connect(annotationAction, &QAction::triggered, parent, &VcsPluginHelper::annotation); 0103 QObject::connect(diffForRevAction, &QAction::triggered, parent, QOverload<>::of(&VcsPluginHelper::diffForRev)); 0104 QObject::connect(diffForRevGlobalAction, &QAction::triggered, parent, &VcsPluginHelper::diffForRevGlobal); 0105 QObject::connect(pullAction, &QAction::triggered, parent, &VcsPluginHelper::pull); 0106 QObject::connect(pushAction, &QAction::triggered, parent, &VcsPluginHelper::push); 0107 } 0108 0109 bool allLocalFiles(const QList<QUrl>& urls) 0110 { 0111 bool ret=true; 0112 for (const QUrl& url : urls) { 0113 QFileInfo info(url.toLocalFile()); 0114 ret &= info.isFile(); 0115 } 0116 return ret; 0117 } 0118 0119 QMenu* createMenu(QWidget* parent) 0120 { 0121 auto* menu = new QMenu(vcs->name(), parent); 0122 menu->setIcon(QIcon::fromTheme(ICore::self()->pluginController()->pluginInfo(plugin).iconName())); 0123 menu->addAction(commitAction); 0124 if(plugin->extension<IDistributedVersionControl>()) { 0125 menu->addAction(pushAction); 0126 menu->addAction(pullAction); 0127 } else { 0128 menu->addAction(updateAction); 0129 } 0130 menu->addSeparator(); 0131 menu->addAction(addAction); 0132 menu->addAction(revertAction); 0133 menu->addSeparator(); 0134 menu->addAction(historyAction); 0135 menu->addAction(annotationAction); 0136 menu->addAction(diffToBaseAction); 0137 0138 const bool singleVersionedFile = ctxUrls.size() == 1 && vcs->isVersionControlled(ctxUrls.constFirst()); 0139 historyAction->setEnabled(singleVersionedFile); 0140 annotationAction->setEnabled(singleVersionedFile && allLocalFiles(ctxUrls)); 0141 diffToBaseAction->setEnabled(singleVersionedFile); 0142 commitAction->setEnabled(singleVersionedFile); 0143 0144 return menu; 0145 } 0146 }; 0147 0148 0149 VcsPluginHelper::VcsPluginHelper(KDevelop::IPlugin* parent, KDevelop::IBasicVersionControl* vcs) 0150 : QObject(parent) 0151 , d_ptr(new VcsPluginHelperPrivate()) 0152 { 0153 Q_D(VcsPluginHelper); 0154 0155 Q_ASSERT(vcs); 0156 Q_ASSERT(parent); 0157 d->plugin = parent; 0158 d->vcs = vcs; 0159 d->createActions(this); 0160 } 0161 0162 VcsPluginHelper::~VcsPluginHelper() 0163 {} 0164 0165 void VcsPluginHelper::addContextDocument(const QUrl &url) 0166 { 0167 Q_D(VcsPluginHelper); 0168 0169 d->ctxUrls.append(url); 0170 } 0171 0172 void VcsPluginHelper::disposeEventually(KTextEditor::View *, bool dont) 0173 { 0174 if ( ! dont ) { 0175 deleteLater(); 0176 } 0177 } 0178 0179 void VcsPluginHelper::disposeEventually(KTextEditor::Document *) 0180 { 0181 deleteLater(); 0182 } 0183 0184 void VcsPluginHelper::setupFromContext(Context* context) 0185 { 0186 Q_D(VcsPluginHelper); 0187 0188 d->ctxUrls = context->urls(); 0189 } 0190 0191 QList<QUrl> VcsPluginHelper::contextUrlList() const 0192 { 0193 Q_D(const VcsPluginHelper); 0194 0195 return d->ctxUrls; 0196 } 0197 0198 QMenu* VcsPluginHelper::commonActions(QWidget* parent) 0199 { 0200 Q_D(VcsPluginHelper); 0201 0202 /* TODO: the following logic to determine which actions need to be enabled 0203 * or disabled does not work properly. What needs to be implemented is that 0204 * project items that are vc-controlled enable all except add, project 0205 * items that are not vc-controlled enable add action. For urls that cannot 0206 * be made into a project item, or if the project has no associated VC 0207 * plugin we need to check whether a VC controls the parent dir, if we have 0208 * one we assume the urls can be added but are not currently controlled. If 0209 * the url is already version controlled then just enable all except add 0210 */ 0211 return d->createMenu(parent); 0212 } 0213 0214 #define EXECUTE_VCS_METHOD( method ) \ 0215 d->plugin->core()->runController()->registerJob( d->vcs-> method ( d->ctxUrls ) ) 0216 0217 #define SINGLEURL_SETUP_VARS \ 0218 KDevelop::IBasicVersionControl* iface = d->vcs;\ 0219 const QUrl &url = d->ctxUrls.front(); 0220 0221 0222 void VcsPluginHelper::revert() 0223 { 0224 Q_D(VcsPluginHelper); 0225 0226 VcsJob* job=d->vcs->revert(d->ctxUrls); 0227 connect(job, &VcsJob::finished, this, &VcsPluginHelper::revertDone); 0228 0229 for (const QUrl& url : qAsConst(d->ctxUrls)) { 0230 IDocument* doc=ICore::self()->documentController()->documentForUrl(url); 0231 0232 if(doc && doc->textDocument()) { 0233 auto* modif = qobject_cast<KTextEditor::ModificationInterface*>(doc->textDocument()); 0234 if (modif) { 0235 modif->setModifiedOnDiskWarning(false); 0236 } 0237 doc->textDocument()->setModified(false); 0238 } 0239 } 0240 job->setProperty("urls", QVariant::fromValue(d->ctxUrls)); 0241 0242 d->plugin->core()->runController()->registerJob(job); 0243 } 0244 0245 void VcsPluginHelper::revertDone(KJob* job) 0246 { 0247 auto* modificationTimer = new QTimer; 0248 modificationTimer->setInterval(100); 0249 connect(modificationTimer, &QTimer::timeout, this, &VcsPluginHelper::delayedModificationWarningOn); 0250 connect(modificationTimer, &QTimer::timeout, modificationTimer, &QTimer::deleteLater); 0251 0252 0253 modificationTimer->setProperty("urls", job->property("urls")); 0254 modificationTimer->start(); 0255 } 0256 0257 void VcsPluginHelper::delayedModificationWarningOn() 0258 { 0259 QObject* timer = sender(); 0260 const QList<QUrl> urls = timer->property("urls").value<QList<QUrl>>(); 0261 0262 for (const QUrl& url : urls) { 0263 IDocument* doc=ICore::self()->documentController()->documentForUrl(url); 0264 0265 if(doc) { 0266 doc->reload(); 0267 0268 auto* modif = qobject_cast<KTextEditor::ModificationInterface*>(doc->textDocument()); 0269 modif->setModifiedOnDiskWarning(true); 0270 } 0271 } 0272 } 0273 0274 0275 void VcsPluginHelper::diffJobFinished(KJob* job) 0276 { 0277 auto* vcsjob = qobject_cast<KDevelop::VcsJob*>(job); 0278 Q_ASSERT(vcsjob); 0279 0280 if (vcsjob->status() == KDevelop::VcsJob::JobSucceeded) { 0281 KDevelop::VcsDiff d = vcsjob->fetchResults().value<KDevelop::VcsDiff>(); 0282 if(d.isEmpty()) 0283 KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), 0284 i18n("There are no differences."), 0285 i18nc("@title:window", "VCS Support")); 0286 else { 0287 auto* patch=new VCSDiffPatchSource(d); 0288 showVcsDiff(patch); 0289 } 0290 } else { 0291 KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18nc("@title:window", "Unable to Get Differences")); 0292 } 0293 } 0294 0295 void VcsPluginHelper::diffToBase() 0296 { 0297 Q_D(VcsPluginHelper); 0298 0299 SINGLEURL_SETUP_VARS 0300 if (!ICore::self()->documentController()->saveAllDocuments()) { 0301 return; 0302 } 0303 0304 auto* patch =new VCSDiffPatchSource(new VCSStandardDiffUpdater(iface, url)); 0305 showVcsDiff(patch); 0306 } 0307 0308 void VcsPluginHelper::diffForRev() 0309 { 0310 Q_D(VcsPluginHelper); 0311 0312 if (d->ctxUrls.isEmpty()) { 0313 return; 0314 } 0315 diffForRev(d->ctxUrls.first()); 0316 } 0317 0318 void VcsPluginHelper::diffForRevGlobal() 0319 { 0320 Q_D(VcsPluginHelper); 0321 0322 if (d->ctxUrls.isEmpty()) { 0323 return; 0324 } 0325 QUrl url = d->ctxUrls.first(); 0326 IProject* project = ICore::self()->projectController()->findProjectForUrl( url ); 0327 if( project ) { 0328 url = project->path().toUrl(); 0329 } 0330 0331 diffForRev(url); 0332 } 0333 0334 void VcsPluginHelper::diffForRev(const QUrl& url) 0335 { 0336 Q_D(VcsPluginHelper); 0337 0338 auto* action = qobject_cast<QAction*>( sender() ); 0339 Q_ASSERT(action); 0340 Q_ASSERT(action->data().canConvert<VcsRevision>()); 0341 VcsRevision rev = action->data().value<VcsRevision>(); 0342 0343 ICore::self()->documentController()->saveAllDocuments(); 0344 VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); 0345 KDevelop::VcsJob* job = d->vcs->diff(url, prev, rev ); 0346 0347 connect(job, &VcsJob::finished, this, &VcsPluginHelper::diffJobFinished); 0348 d->plugin->core()->runController()->registerJob(job); 0349 } 0350 0351 void VcsPluginHelper::history(const VcsRevision& rev) 0352 { 0353 Q_D(VcsPluginHelper); 0354 0355 SINGLEURL_SETUP_VARS 0356 auto* dlg = new QDialog(ICore::self()->uiController()->activeMainWindow()); 0357 dlg->setAttribute(Qt::WA_DeleteOnClose); 0358 dlg->setWindowTitle(i18nc("@title:window %1: path or URL, %2: name of a version control system", 0359 "%2 History (%1)", url.toDisplayString(QUrl::PreferLocalFile), iface->name())); 0360 0361 auto *mainLayout = new QVBoxLayout(dlg); 0362 0363 auto* logWidget = new KDevelop::VcsEventWidget(url, rev, iface, dlg); 0364 mainLayout->addWidget(logWidget); 0365 0366 auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); 0367 dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); 0368 dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); 0369 mainLayout->addWidget(buttonBox); 0370 0371 dlg->show(); 0372 } 0373 0374 void VcsPluginHelper::annotation() 0375 { 0376 Q_D(VcsPluginHelper); 0377 0378 SINGLEURL_SETUP_VARS 0379 KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); 0380 0381 if (!doc) 0382 doc = ICore::self()->documentController()->openDocument(url); 0383 0384 KTextEditor::View* view = doc ? doc->activeTextView() : nullptr; 0385 KTextEditor::AnnotationInterface* annotateiface = qobject_cast<KTextEditor::AnnotationInterface*>(doc->textDocument()); 0386 auto viewiface = qobject_cast<KTextEditor::AnnotationViewInterface*>(view); 0387 if (viewiface && viewiface->isAnnotationBorderVisible()) { 0388 viewiface->setAnnotationBorderVisible(false); 0389 return; 0390 } 0391 0392 if (doc && doc->textDocument() && iface) { 0393 KDevelop::VcsJob* job = iface->annotate(url); 0394 if( !job ) 0395 { 0396 qCWarning(VCS) << "Couldn't create annotate job for:" << url << "with iface:" << iface << dynamic_cast<KDevelop::IPlugin*>( iface ); 0397 return; 0398 } 0399 0400 QColor foreground(Qt::black); 0401 QColor background(Qt::white); 0402 if (view) { 0403 KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); 0404 foreground = style->foreground().color(); 0405 if (style->hasProperty(QTextFormat::BackgroundBrush)) { 0406 background = style->background().color(); 0407 } 0408 } 0409 0410 if (annotateiface && viewiface) { 0411 // TODO: only create model if there is none yet (e.g. from another view) 0412 auto* model = new KDevelop::VcsAnnotationModel(job, url, doc->textDocument(), 0413 foreground, background); 0414 annotateiface->setAnnotationModel(model); 0415 0416 auto viewifaceV2 = qobject_cast<KTextEditor::AnnotationViewInterfaceV2*>(view); 0417 if (viewifaceV2) { 0418 // TODO: only create delegate if there is none yet 0419 auto delegate = new VcsAnnotationItemDelegate(view, model, view); 0420 viewifaceV2->setAnnotationItemDelegate(delegate); 0421 viewifaceV2->setAnnotationUniformItemSizes(true); 0422 } 0423 0424 viewiface->setAnnotationBorderVisible(true); 0425 // can't use new signal slot syntax here, AnnotationInterface is not a QObject 0426 connect(view, SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), 0427 this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); 0428 connect(view, SIGNAL(annotationBorderVisibilityChanged(View*,bool)), 0429 this, SLOT(handleAnnotationBorderVisibilityChanged(View*,bool))); 0430 } else { 0431 KMessageBox::error(nullptr, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor.")); 0432 delete job; 0433 } 0434 } else { 0435 KMessageBox::error(nullptr, i18n("Cannot execute annotate action because the " 0436 "document was not found, or was not a text document:\n%1", url.toDisplayString(QUrl::PreferLocalFile))); 0437 } 0438 } 0439 0440 void VcsPluginHelper::annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line ) 0441 { 0442 Q_D(VcsPluginHelper); 0443 0444 auto viewifaceV2 = qobject_cast<KTextEditor::AnnotationViewInterfaceV2*>(view); 0445 if (viewifaceV2) { 0446 viewifaceV2->annotationItemDelegate()->hideTooltip(view); 0447 } 0448 0449 KTextEditor::AnnotationInterface* annotateiface = 0450 qobject_cast<KTextEditor::AnnotationInterface*>(view->document()); 0451 0452 auto* model = qobject_cast<VcsAnnotationModel*>( annotateiface->annotationModel() ); 0453 Q_ASSERT(model); 0454 0455 VcsRevision rev = model->revisionForLine(line); 0456 // check if the user clicked on a row without revision information 0457 if (rev.revisionType() == VcsRevision::Invalid) { 0458 // in this case, do not action depending on revision information 0459 return; 0460 } 0461 0462 d->diffForRevAction->setData(QVariant::fromValue(rev)); 0463 d->diffForRevGlobalAction->setData(QVariant::fromValue(rev)); 0464 menu->addSeparator(); 0465 menu->addAction(d->diffForRevAction); 0466 menu->addAction(d->diffForRevGlobalAction); 0467 0468 QAction* copyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18nc("@action:inmenu", "Copy Revision Id")); 0469 connect(copyAction, &QAction::triggered, this, [rev]() { 0470 QApplication::clipboard()->setText(rev.revisionValue().toString()); 0471 }); 0472 0473 QAction* historyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("view-history")), i18nc("@action:inmenu revision history", "History...")); 0474 connect(historyAction, &QAction::triggered, this, [this, rev]() { 0475 history(rev); 0476 }); 0477 } 0478 0479 void VcsPluginHelper::handleAnnotationBorderVisibilityChanged(View* view, bool visible) 0480 { 0481 if (visible) { 0482 return; 0483 } 0484 0485 disconnect(view, SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), 0486 this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); 0487 0488 disconnect(view, SIGNAL(annotationBorderVisibilityChanged(View*,bool)), 0489 this, SLOT(handleAnnotationBorderVisibilityChanged(View*,bool))); 0490 0491 // TODO: remove the model if last user of it 0492 } 0493 0494 void VcsPluginHelper::update() 0495 { 0496 Q_D(VcsPluginHelper); 0497 0498 EXECUTE_VCS_METHOD(update); 0499 } 0500 0501 void VcsPluginHelper::add() 0502 { 0503 Q_D(VcsPluginHelper); 0504 0505 EXECUTE_VCS_METHOD(add); 0506 } 0507 0508 void VcsPluginHelper::commit() 0509 { 0510 Q_D(VcsPluginHelper); 0511 0512 Q_ASSERT(!d->ctxUrls.isEmpty()); 0513 ICore::self()->documentController()->saveAllDocuments(); 0514 0515 QUrl url = d->ctxUrls.first(); 0516 0517 // We start the commit UI no matter whether there is real differences, as it can also be used to commit untracked files 0518 auto* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(d->vcs, url)); 0519 0520 bool ret = showVcsDiff(patchSource); 0521 0522 if(!ret) { 0523 ScopedDialog<VcsCommitDialog> commitDialog(patchSource); 0524 commitDialog->setCommitCandidates(patchSource->infos()); 0525 commitDialog->exec(); 0526 } 0527 } 0528 0529 void VcsPluginHelper::push() 0530 { 0531 Q_D(VcsPluginHelper); 0532 0533 for (const QUrl& url : qAsConst(d->ctxUrls)) { 0534 VcsJob* job = d->plugin->extension<IDistributedVersionControl>()->push(url, VcsLocation()); 0535 ICore::self()->runController()->registerJob(job); 0536 } 0537 } 0538 0539 void VcsPluginHelper::pull() 0540 { 0541 Q_D(VcsPluginHelper); 0542 0543 for (const QUrl& url : qAsConst(d->ctxUrls)) { 0544 VcsJob* job = d->plugin->extension<IDistributedVersionControl>()->pull(VcsLocation(), url); 0545 ICore::self()->runController()->registerJob(job); 0546 } 0547 } 0548 0549 } 0550 0551 #include "moc_vcspluginhelper.cpp"