OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / perforce / perforceplugin.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file.  Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights.  These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "perforceplugin.h"
35
36 #include "changenumberdialog.h"
37 #include "pendingchangesdialog.h"
38 #include "perforceconstants.h"
39 #include "perforceeditor.h"
40 #include "perforcesubmiteditor.h"
41 #include "perforceversioncontrol.h"
42 #include "perforcechecker.h"
43 #include "settingspage.h"
44
45 #include <coreplugin/actionmanager/actionmanager.h>
46 #include <coreplugin/actionmanager/actioncontainer.h>
47 #include <coreplugin/actionmanager/command.h>
48 #include <coreplugin/uniqueidmanager.h>
49 #include <coreplugin/coreconstants.h>
50 #include <coreplugin/editormanager/editormanager.h>
51 #include <coreplugin/filemanager.h>
52 #include <coreplugin/icore.h>
53 #include <coreplugin/messagemanager.h>
54 #include <coreplugin/mimedatabase.h>
55 #include <locator/commandlocator.h>
56 #include <utils/qtcassert.h>
57 #include <utils/synchronousprocess.h>
58 #include <utils/parameteraction.h>
59 #include <vcsbase/basevcseditorfactory.h>
60 #include <vcsbase/basevcssubmiteditorfactory.h>
61 #include <vcsbase/vcsbaseeditor.h>
62 #include <vcsbase/vcsbaseoutputwindow.h>
63
64 #include <QtCore/QtPlugin>
65 #include <QtCore/QDebug>
66 #include <QtCore/QDir>
67 #include <QtCore/QFileInfo>
68 #include <QtCore/QSettings>
69 #include <QtCore/QTemporaryFile>
70 #include <QtCore/QTextCodec>
71
72 #include <QtGui/QAction>
73 #include <QtGui/QFileDialog>
74 #include <QtGui/QMainWindow>
75 #include <QtGui/QMenu>
76 #include <QtGui/QMessageBox>
77
78 static const VCSBase::VCSBaseEditorParameters editorParameters[] = {
79 {
80     VCSBase::RegularCommandOutput,
81     Perforce::Constants::PERFORCE_COMMANDLOG_EDITOR_ID,
82     Perforce::Constants::PERFORCE_COMMANDLOG_EDITOR_DISPLAY_NAME,
83     Perforce::Constants::PERFORCE_COMMANDLOG_EDITOR_CONTEXT,
84     "application/vnd.nokia.text.scs_commandlog",
85     "scslog"},
86 {   VCSBase::LogOutput,
87     Perforce::Constants::PERFORCE_LOG_EDITOR_ID,
88     Perforce::Constants::PERFORCE_LOG_EDITOR_DISPLAY_NAME,
89     Perforce::Constants::PERFORCE_LOG_EDITOR_CONTEXT,
90     "application/vnd.nokia.text.scs_filelog",
91     "scsfilelog"},
92 {    VCSBase::AnnotateOutput,
93      Perforce::Constants::PERFORCE_ANNOTATION_EDITOR_ID,
94      Perforce::Constants::PERFORCE_ANNOTATION_EDITOR_DISPLAY_NAME,
95      Perforce::Constants::PERFORCE_ANNOTATION_EDITOR_CONTEXT,
96     "application/vnd.nokia.text.scs_annotation",
97     "scsannotate"},
98 {   VCSBase::DiffOutput,
99     Perforce::Constants::PERFORCE_DIFF_EDITOR_ID,
100     Perforce::Constants::PERFORCE_DIFF_EDITOR_DISPLAY_NAME,
101     Perforce::Constants::PERFORCE_DIFF_EDITOR_CONTEXT,
102     "text/x-patch","diff"}
103 };
104
105 // Utility to find a parameter set by type
106 static inline const VCSBase::VCSBaseEditorParameters *findType(int ie)
107 {
108     const VCSBase::EditorContentType et = static_cast<VCSBase::EditorContentType>(ie);
109     return  VCSBase::VCSBaseEditor::findType(editorParameters, sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters), et);
110 }
111
112 static inline QString debugCodec(const QTextCodec *c)
113 {
114     return c ? QString::fromAscii(c->name()) : QString::fromAscii("Null codec");
115 }
116
117 // Ensure adding "..." to relative paths which is p4's convention
118 // for the current directory
119 static inline QStringList perforceRelativeFileArguments(const QStringList &args)
120 {
121     if (args.isEmpty())
122         return QStringList(QLatin1String("..."));
123     QTC_ASSERT(args.size() == 1, return QStringList())
124     QStringList p4Args = args;
125     p4Args.front() += QLatin1String("/...");
126     return p4Args;
127 }
128
129 static inline QStringList perforceRelativeProjectDirectory(const VCSBase::VCSBasePluginState &s)
130 {
131     return perforceRelativeFileArguments(s.relativeCurrentProject());
132 }
133
134 // Clean user setting off diff-binary for 'p4 resolve' and 'p4 diff'.
135 static inline QProcessEnvironment overrideDiffEnvironmentVariable()
136 {
137     QProcessEnvironment rc = QProcessEnvironment::systemEnvironment();
138     rc.remove(QLatin1String("P4DIFF"));
139     return rc;
140 }
141
142 static const char * const CMD_ID_PERFORCE_MENU = "Perforce.Menu";
143 static const char * const CMD_ID_EDIT = "Perforce.Edit";
144 static const char * const CMD_ID_ADD = "Perforce.Add";
145 static const char * const CMD_ID_DELETE_FILE = "Perforce.Delete";
146 static const char * const CMD_ID_OPENED = "Perforce.Opened";
147 static const char * const CMD_ID_PROJECTLOG = "Perforce.ProjectLog";
148 static const char * const CMD_ID_REPOSITORYLOG = "Perforce.RepositoryLog";
149 static const char * const CMD_ID_REVERT = "Perforce.Revert";
150 static const char * const CMD_ID_DIFF_CURRENT = "Perforce.DiffCurrent";
151 static const char * const CMD_ID_DIFF_PROJECT = "Perforce.DiffProject";
152 static const char * const CMD_ID_UPDATE_PROJECT = "Perforce.UpdateProject";
153 static const char * const CMD_ID_REVERT_PROJECT = "Perforce.RevertProject";
154 static const char * const CMD_ID_REVERT_UNCHANGED_PROJECT = "Perforce.RevertUnchangedProject";
155 static const char * const CMD_ID_DIFF_ALL = "Perforce.DiffAll";
156 static const char * const CMD_ID_RESOLVE = "Perforce.Resolve";
157 static const char * const CMD_ID_SUBMIT = "Perforce.Submit";
158 static const char * const CMD_ID_PENDING_CHANGES = "Perforce.PendingChanges";
159 static const char * const CMD_ID_DESCRIBE = "Perforce.Describe";
160 static const char * const CMD_ID_ANNOTATE_CURRENT = "Perforce.AnnotateCurrent";
161 static const char * const CMD_ID_ANNOTATE = "Perforce.Annotate";
162 static const char * const CMD_ID_FILELOG_CURRENT = "Perforce.FilelogCurrent";
163 static const char * const CMD_ID_FILELOG = "Perforce.Filelog";
164 static const char * const CMD_ID_UPDATEALL = "Perforce.UpdateAll";
165 static const char * const CMD_ID_SEPARATOR1 = "Perforce.Separator1";
166 static const char * const CMD_ID_SEPARATOR2 = "Perforce.Separator2";
167 static const char * const CMD_ID_SEPARATOR3 = "Perforce.Separator3";
168
169 ////
170 // PerforcePlugin
171 ////
172
173 namespace Perforce {
174 namespace Internal {
175
176 PerforceResponse::PerforceResponse() :
177     error(true),
178     exitCode(-1)
179 {
180 }
181
182 PerforcePlugin *PerforcePlugin::m_perforcePluginInstance = NULL;
183
184 PerforcePlugin::PerforcePlugin() :
185     VCSBase::VCSBasePlugin(QLatin1String(Constants::PERFORCE_SUBMIT_EDITOR_ID)),
186     m_commandLocator(0),
187     m_editAction(0),
188     m_addAction(0),
189     m_deleteAction(0),
190     m_openedAction(0),
191     m_revertFileAction(0),
192     m_diffFileAction(0),
193     m_diffProjectAction(0),
194     m_updateProjectAction(0),
195     m_revertProjectAction(0),
196     m_revertUnchangedAction(0),
197     m_diffAllAction(0),
198     m_submitProjectAction(0),
199     m_pendingAction(0),
200     m_describeAction(0),
201     m_annotateCurrentAction(0),
202     m_annotateAction(0),
203     m_filelogCurrentAction(0),
204     m_filelogAction(0),
205     m_logProjectAction(0),
206     m_logRepositoryAction(0),
207     m_submitCurrentLogAction(0),
208     m_updateAllAction(0),
209     m_submitActionTriggered(false),
210     m_diffSelectedFiles(0),
211     m_undoAction(0),
212     m_redoAction(0)
213 {
214 }
215
216 static const VCSBase::VCSBaseSubmitEditorParameters submitParameters = {
217     Perforce::Constants::SUBMIT_MIMETYPE,
218     Perforce::Constants::PERFORCE_SUBMIT_EDITOR_ID,
219     Perforce::Constants::PERFORCE_SUBMIT_EDITOR_DISPLAY_NAME,
220     Perforce::Constants::PERFORCESUBMITEDITOR_CONTEXT
221 };
222
223 static inline Core::Command *createSeparator(QObject *parent,
224                                              Core::ActionManager *ami,
225                                              const char *id,
226                                              const Core::Context &globalcontext)
227 {
228     QAction *tmpaction = new QAction(parent);
229     tmpaction->setSeparator(true);
230     return ami->registerAction(tmpaction, id, globalcontext);
231 }
232
233 bool PerforcePlugin::initialize(const QStringList & /* arguments */, QString *errorMessage)
234 {
235     typedef VCSBase::VCSEditorFactory<PerforceEditor> PerforceEditorFactory;
236     typedef VCSBase::VCSSubmitEditorFactory<PerforceSubmitEditor> PerforceSubmitEditorFactory;
237
238     VCSBase::VCSBasePlugin::initialize(new PerforceVersionControl(this));
239
240     Core::ICore *core = Core::ICore::instance();
241     if (!core->mimeDatabase()->addMimeTypes(QLatin1String(":/trolltech.perforce/Perforce.mimetypes.xml"), errorMessage))
242         return false;
243     m_perforcePluginInstance = this;
244
245     if (QSettings *settings = core->settings())
246         m_settings.fromSettings(settings);
247
248     addAutoReleasedObject(new SettingsPage);
249
250     // Editor factories
251     addAutoReleasedObject(new PerforceSubmitEditorFactory(&submitParameters));
252
253     static const char *describeSlot = SLOT(describe(QString,QString));
254     const int editorCount = sizeof(editorParameters)/sizeof(VCSBase::VCSBaseEditorParameters);
255     for (int i = 0; i < editorCount; i++)
256         addAutoReleasedObject(new PerforceEditorFactory(editorParameters + i, this, describeSlot));
257
258     const QString prefix = QLatin1String("p4");
259     m_commandLocator = new Locator::CommandLocator(QLatin1String("Perforce"), prefix, prefix);
260     addAutoReleasedObject(m_commandLocator);
261
262     //register actions
263     Core::ActionManager *am = Core::ICore::instance()->actionManager();
264
265     Core::ActionContainer *mtools =
266         am->actionContainer(Core::Constants::M_TOOLS);
267
268     Core::ActionContainer *mperforce =
269         am->createMenu(Core::Id(CMD_ID_PERFORCE_MENU));
270     mperforce->menu()->setTitle(tr("&Perforce"));
271     mtools->addMenu(mperforce);
272     m_menuAction = mperforce->menu()->menuAction();
273
274     Core::Context globalcontext(Core::Constants::C_GLOBAL);
275     Core::Context perforcesubmitcontext(Constants::PERFORCESUBMITEDITOR_CONTEXT);
276
277     Core::Command *command;
278
279     m_diffFileAction = new Utils::ParameterAction(tr("Diff Current File"), tr("Diff \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
280     command = am->registerAction(m_diffFileAction, CMD_ID_DIFF_CURRENT, globalcontext);
281     command->setAttribute(Core::Command::CA_UpdateText);
282     command->setDefaultText(tr("Diff Current File"));
283     connect(m_diffFileAction, SIGNAL(triggered()), this, SLOT(diffCurrentFile()));
284     mperforce->addAction(command);
285     m_commandLocator->appendCommand(command);
286
287     m_annotateCurrentAction = new Utils::ParameterAction(tr("Annotate Current File"), tr("Annotate \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
288     command = am->registerAction(m_annotateCurrentAction, CMD_ID_ANNOTATE_CURRENT, globalcontext);
289     command->setAttribute(Core::Command::CA_UpdateText);
290     command->setDefaultText(tr("Annotate Current File"));
291     connect(m_annotateCurrentAction, SIGNAL(triggered()), this, SLOT(annotateCurrentFile()));
292     mperforce->addAction(command);
293     m_commandLocator->appendCommand(command);
294
295     m_filelogCurrentAction = new Utils::ParameterAction(tr("Filelog Current File"), tr("Filelog \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
296     command = am->registerAction(m_filelogCurrentAction, CMD_ID_FILELOG_CURRENT, globalcontext);
297     command->setAttribute(Core::Command::CA_UpdateText);
298     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+F")));
299     command->setDefaultText(tr("Filelog Current File"));
300     connect(m_filelogCurrentAction, SIGNAL(triggered()), this, SLOT(filelogCurrentFile()));
301     mperforce->addAction(command);
302     m_commandLocator->appendCommand(command);
303
304     mperforce->addAction(createSeparator(this, am, "Perforce.Sep.Edit", globalcontext));
305
306     m_editAction = new Utils::ParameterAction(tr("Edit"), tr("Edit \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
307     command = am->registerAction(m_editAction, CMD_ID_EDIT, globalcontext);
308     command->setAttribute(Core::Command::CA_UpdateText);
309     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+E")));
310     command->setDefaultText(tr("Edit File"));
311     connect(m_editAction, SIGNAL(triggered()), this, SLOT(openCurrentFile()));
312     mperforce->addAction(command);
313     m_commandLocator->appendCommand(command);
314
315     m_addAction = new Utils::ParameterAction(tr("Add"), tr("Add \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
316     command = am->registerAction(m_addAction, CMD_ID_ADD, globalcontext);
317     command->setAttribute(Core::Command::CA_UpdateText);
318     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+A")));
319     command->setDefaultText(tr("Add File"));
320     connect(m_addAction, SIGNAL(triggered()), this, SLOT(addCurrentFile()));
321     mperforce->addAction(command);
322     m_commandLocator->appendCommand(command);
323
324     m_deleteAction = new Utils::ParameterAction(tr("Delete..."), tr("Delete \"%1\"..."), Utils::ParameterAction::EnabledWithParameter, this);
325     command = am->registerAction(m_deleteAction, CMD_ID_DELETE_FILE, globalcontext);
326     command->setAttribute(Core::Command::CA_UpdateText);
327     command->setDefaultText(tr("Delete File"));
328     connect(m_deleteAction, SIGNAL(triggered()), this, SLOT(promptToDeleteCurrentFile()));
329     mperforce->addAction(command);
330     m_commandLocator->appendCommand(command);
331
332     m_revertFileAction = new Utils::ParameterAction(tr("Revert"), tr("Revert \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
333     command = am->registerAction(m_revertFileAction, CMD_ID_REVERT, globalcontext);
334     command->setAttribute(Core::Command::CA_UpdateText);
335     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+R")));
336     command->setDefaultText(tr("Revert File"));
337     connect(m_revertFileAction, SIGNAL(triggered()), this, SLOT(revertCurrentFile()));
338     mperforce->addAction(command);
339     m_commandLocator->appendCommand(command);
340
341     mperforce->addAction(createSeparator(this, am, "Perforce.Sep.Project", globalcontext));
342
343     const QString diffProjectDefaultText = tr("Diff Current Project/Session");
344     m_diffProjectAction = new Utils::ParameterAction(diffProjectDefaultText, tr("Diff Project \"%1\""), Utils::ParameterAction::AlwaysEnabled, this);
345     command = am->registerAction(m_diffProjectAction, CMD_ID_DIFF_PROJECT, globalcontext);
346     command->setAttribute(Core::Command::CA_UpdateText);
347     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+D")));
348     command->setDefaultText(diffProjectDefaultText);
349     connect(m_diffProjectAction, SIGNAL(triggered()), this, SLOT(diffCurrentProject()));
350     mperforce->addAction(command);
351     m_commandLocator->appendCommand(command);
352
353     m_logProjectAction = new Utils::ParameterAction(tr("Log Project"), tr("Log Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
354     command = am->registerAction(m_logProjectAction, CMD_ID_PROJECTLOG, globalcontext);
355     command->setAttribute(Core::Command::CA_UpdateText);
356     connect(m_logProjectAction, SIGNAL(triggered()), this, SLOT(logProject()));
357     mperforce->addAction(command);
358     m_commandLocator->appendCommand(command);
359
360     m_submitProjectAction = new Utils::ParameterAction(tr("Submit Project"), tr("Submit Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
361     command = am->registerAction(m_submitProjectAction, CMD_ID_SUBMIT, globalcontext);
362     command->setAttribute(Core::Command::CA_UpdateText);
363     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+S")));
364     connect(m_submitProjectAction, SIGNAL(triggered()), this, SLOT(startSubmitProject()));
365     mperforce->addAction(command);
366     m_commandLocator->appendCommand(command);
367
368     const QString updateProjectDefaultText = tr("Update Current Project");
369     m_updateProjectAction = new Utils::ParameterAction(updateProjectDefaultText, tr("Update Project \"%1\""), Utils::ParameterAction::AlwaysEnabled, this);
370     command = am->registerAction(m_updateProjectAction, CMD_ID_UPDATE_PROJECT, globalcontext);
371     command->setDefaultText(updateProjectDefaultText);
372     command->setAttribute(Core::Command::CA_UpdateText);
373     connect(m_updateProjectAction, SIGNAL(triggered()), this, SLOT(updateCurrentProject()));
374     mperforce->addAction(command);
375     m_commandLocator->appendCommand(command);
376
377     m_revertUnchangedAction = new Utils::ParameterAction(tr("Revert Unchanged"), tr("Revert Unchanged Files of Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
378     command = am->registerAction(m_revertUnchangedAction, CMD_ID_REVERT_UNCHANGED_PROJECT, globalcontext);
379     command->setAttribute(Core::Command::CA_UpdateText);
380     connect(m_revertUnchangedAction, SIGNAL(triggered()), this, SLOT(revertUnchangedCurrentProject()));
381     mperforce->addAction(command);
382     m_commandLocator->appendCommand(command);
383
384     m_revertProjectAction = new Utils::ParameterAction(tr("Revert Project"), tr("Revert Project \"%1\""), Utils::ParameterAction::EnabledWithParameter, this);
385     command = am->registerAction(m_revertProjectAction, CMD_ID_REVERT_PROJECT, globalcontext);
386     command->setAttribute(Core::Command::CA_UpdateText);
387     connect(m_revertProjectAction, SIGNAL(triggered()), this, SLOT(revertCurrentProject()));
388     mperforce->addAction(command);
389     m_commandLocator->appendCommand(command);
390
391     mperforce->addAction(createSeparator(this, am, "Perforce.Sep.Repository", globalcontext));
392
393     m_diffAllAction = new QAction(tr("Diff Opened Files"), this);
394     command = am->registerAction(m_diffAllAction, CMD_ID_DIFF_ALL, globalcontext);
395     connect(m_diffAllAction, SIGNAL(triggered()), this, SLOT(diffAllOpened()));
396     mperforce->addAction(command);
397     m_commandLocator->appendCommand(command);
398
399     m_openedAction = new QAction(tr("Opened"), this);
400     command = am->registerAction(m_openedAction, CMD_ID_OPENED, globalcontext);
401     command->setDefaultKeySequence(QKeySequence(tr("Alt+P,Alt+O")));
402     connect(m_openedAction, SIGNAL(triggered()), this, SLOT(printOpenedFileList()));
403     mperforce->addAction(command);
404     m_commandLocator->appendCommand(command);
405
406     m_logRepositoryAction = new QAction(tr("Repository Log"), this);
407     command = am->registerAction(m_logRepositoryAction, CMD_ID_REPOSITORYLOG, globalcontext);
408     connect(m_logRepositoryAction, SIGNAL(triggered()), this, SLOT(logRepository()));
409     mperforce->addAction(command);
410     m_commandLocator->appendCommand(command);
411
412     m_pendingAction = new QAction(tr("Pending Changes..."), this);
413     command = am->registerAction(m_pendingAction, CMD_ID_PENDING_CHANGES, globalcontext);
414     connect(m_pendingAction, SIGNAL(triggered()), this, SLOT(printPendingChanges()));
415     mperforce->addAction(command);
416     m_commandLocator->appendCommand(command);
417
418     m_updateAllAction = new QAction(tr("Update All"), this);
419     command = am->registerAction(m_updateAllAction, CMD_ID_UPDATEALL, globalcontext);
420     connect(m_updateAllAction, SIGNAL(triggered()), this, SLOT(updateAll()));
421     mperforce->addAction(command);
422     m_commandLocator->appendCommand(command);
423
424     mperforce->addAction(createSeparator(this, am, "Perforce.Sep.Dialogs", globalcontext));
425
426     m_describeAction = new QAction(tr("Describe..."), this);
427     command = am->registerAction(m_describeAction, CMD_ID_DESCRIBE, globalcontext);
428     connect(m_describeAction, SIGNAL(triggered()), this, SLOT(describeChange()));
429     mperforce->addAction(command);
430
431     m_annotateAction = new QAction(tr("Annotate..."), this);
432     command = am->registerAction(m_annotateAction, CMD_ID_ANNOTATE, globalcontext);
433     connect(m_annotateAction, SIGNAL(triggered()), this, SLOT(annotate()));
434     mperforce->addAction(command);
435
436     m_filelogAction = new QAction(tr("Filelog..."), this);
437     command = am->registerAction(m_filelogAction, CMD_ID_FILELOG, globalcontext);
438     connect(m_filelogAction, SIGNAL(triggered()), this, SLOT(filelog()));
439     mperforce->addAction(command);
440
441     m_submitCurrentLogAction = new QAction(VCSBase::VCSBaseSubmitEditor::submitIcon(), tr("Submit"), this);
442     command = am->registerAction(m_submitCurrentLogAction, Constants::SUBMIT_CURRENT, perforcesubmitcontext);
443     command->setAttribute(Core::Command::CA_UpdateText);
444     connect(m_submitCurrentLogAction, SIGNAL(triggered()), this, SLOT(submitCurrentLog()));
445
446     m_diffSelectedFiles = new QAction(VCSBase::VCSBaseSubmitEditor::diffIcon(), tr("Diff Selected Files"), this);
447     command = am->registerAction(m_diffSelectedFiles, Constants::DIFF_SELECTED, perforcesubmitcontext);
448
449     m_undoAction = new QAction(tr("&Undo"), this);
450     command = am->registerAction(m_undoAction, Core::Constants::UNDO, perforcesubmitcontext);
451
452     m_redoAction = new QAction(tr("&Redo"), this);
453     command = am->registerAction(m_redoAction, Core::Constants::REDO, perforcesubmitcontext);
454
455     return true;
456 }
457
458 void PerforcePlugin::extensionsInitialized()
459 {
460     VCSBase::VCSBasePlugin::extensionsInitialized();
461     getTopLevel();
462 }
463
464 void PerforcePlugin::openCurrentFile()
465 {
466     const VCSBase::VCSBasePluginState state = currentState();
467     QTC_ASSERT(state.hasFile(), return)
468     vcsOpen(state.currentFileTopLevel(), state.relativeCurrentFile());
469 }
470
471 void PerforcePlugin::addCurrentFile()
472 {
473     const VCSBase::VCSBasePluginState state = currentState();
474     QTC_ASSERT(state.hasFile(), return)
475     vcsAdd(state.currentFileTopLevel(), state.relativeCurrentFile());
476 }
477
478 void PerforcePlugin::revertCurrentFile()
479 {
480     const VCSBase::VCSBasePluginState state = currentState();
481     QTC_ASSERT(state.hasFile(), return)
482
483     QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(state.currentFile());
484     QStringList args;
485     args << QLatin1String("diff") << QLatin1String("-sa") << state.relativeCurrentFile();
486     PerforceResponse result = runP4Cmd(state.currentFileTopLevel(), args,
487                                        RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow,
488                                        QStringList(), QByteArray(), codec);
489     if (result.error)
490         return;
491     // "foo.cpp - file(s) not opened on this client."
492     if (result.stdOut.isEmpty() || result.stdOut.contains(QLatin1String(" - ")))
493         return;
494
495     const bool doNotRevert = QMessageBox::warning(0, tr("p4 revert"),
496                              tr("The file has been changed. Do you want to revert it?"),
497                              QMessageBox::Yes, QMessageBox::No) == QMessageBox::No;
498     if (doNotRevert)
499         return;
500
501     Core::FileChangeBlocker fcb(state.currentFile());
502     args.clear();
503     args << QLatin1String("revert") << state.relativeCurrentFile();
504     PerforceResponse result2 = runP4Cmd(state.currentFileTopLevel(), args,
505                                         CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
506     if (!result2.error)
507         perforceVersionControl()->emitFilesChanged(QStringList(state.currentFile()));
508 }
509
510 void PerforcePlugin::diffCurrentFile()
511 {
512     const VCSBase::VCSBasePluginState state = currentState();
513     QTC_ASSERT(state.hasFile(), return)
514     p4Diff(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()));
515 }
516
517 void PerforcePlugin::diffCurrentProject()
518 {
519     const VCSBase::VCSBasePluginState state = currentState();
520     QTC_ASSERT(state.hasProject(), return)
521     p4Diff(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state));
522 }
523
524 void PerforcePlugin::diffAllOpened()
525 {
526     p4Diff(m_settings.topLevel(), QStringList());
527 }
528
529 void PerforcePlugin::updateCurrentProject()
530 {
531     const VCSBase::VCSBasePluginState state = currentState();
532     QTC_ASSERT(state.hasProject(), return)
533     updateCheckout(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state));
534 }
535
536 void PerforcePlugin::updateAll()
537 {
538     updateCheckout(m_settings.topLevel());
539 }
540
541 void PerforcePlugin::revertCurrentProject()
542 {
543     const VCSBase::VCSBasePluginState state = currentState();
544     QTC_ASSERT(state.hasProject(), return)
545
546     const QString msg = tr("Do you want to revert all changes to the project \"%1\"?").arg(state.currentProjectName());
547     if (QMessageBox::warning(0, tr("p4 revert"), msg, QMessageBox::Yes, QMessageBox::No) == QMessageBox::No)
548         return;
549     revertProject(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state), false);
550 }
551
552 void PerforcePlugin::revertUnchangedCurrentProject()
553 {
554     // revert -a.
555     const VCSBase::VCSBasePluginState state = currentState();
556     QTC_ASSERT(state.hasProject(), return)
557     revertProject(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state), true);
558 }
559
560 bool PerforcePlugin::revertProject(const QString &workingDir, const QStringList &pathArgs, bool unchangedOnly)
561 {
562     QStringList args(QLatin1String("revert"));
563     if (unchangedOnly)
564         args.push_back(QLatin1String("-a"));
565     args.append(pathArgs);
566     const PerforceResponse resp = runP4Cmd(workingDir, args,
567                                            RunFullySynchronous|CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
568     return !resp.error;
569 }
570
571 void PerforcePlugin::updateCheckout(const QString &workingDir, const QStringList &dirs)
572 {
573     QStringList args(QLatin1String("sync"));
574     args.append(dirs);
575     const PerforceResponse resp = runP4Cmd(workingDir, args,
576                                            CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
577     if (dirs.empty()) {
578         if (!workingDir.isEmpty())
579             perforceVersionControl()->emitRepositoryChanged(workingDir);
580     } else {
581         const QChar slash = QLatin1Char('/');
582         foreach(const QString &dir, dirs)
583             perforceVersionControl()->emitRepositoryChanged(workingDir + slash + dir);
584     }
585 }
586
587 void PerforcePlugin::printOpenedFileList()
588 {
589     const PerforceResponse perforceResponse
590             = runP4Cmd(m_settings.topLevel(), QStringList(QLatin1String("opened")),
591                        CommandToWindow|StdErrToWindow|ErrorToWindow);
592     if (perforceResponse.error || perforceResponse.stdOut.isEmpty())
593         return;
594     // reformat "//depot/file.cpp#1 - description" into "file.cpp # - description"
595     // for context menu opening to work. This produces absolute paths, then.
596     VCSBase::VCSBaseOutputWindow *outWin = VCSBase::VCSBaseOutputWindow::instance();
597     QString errorMessage;
598     QString mapped;
599     const QChar delimiter = QLatin1Char('#');
600     foreach (const QString &line, perforceResponse.stdOut.split(QLatin1Char('\n'))) {
601         mapped.clear();
602         const int delimiterPos = line.indexOf(delimiter);
603         if (delimiterPos > 0)
604             mapped = fileNameFromPerforceName(line.left(delimiterPos), true, &errorMessage);
605         if (mapped.isEmpty()) {
606             outWin->appendSilently(line);
607         } else {
608             outWin->appendSilently(mapped + QLatin1Char(' ') + line.mid(delimiterPos));
609         }
610     }
611     outWin->popup();
612 }
613
614 void PerforcePlugin::startSubmitProject()
615 {
616
617     if (VCSBase::VCSBaseSubmitEditor::raiseSubmitEditor())
618         return;
619
620     if (isCommitEditorOpen()) {
621         VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Another submit is currently executed."));
622         return;
623     }
624
625     const VCSBase::VCSBasePluginState state = currentState();
626     QTC_ASSERT(state.hasProject(), return)
627
628     QTemporaryFile changeTmpFile;
629     changeTmpFile.setAutoRemove(false);
630     if (!changeTmpFile.open()) {
631         VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot create temporary file."));
632         cleanCommitMessageFile();
633         return;
634     }
635     // Revert all unchanged files.
636     if (!revertProject(state.currentProjectTopLevel(), perforceRelativeProjectDirectory(state), true))
637         return;
638     // Start a change
639     QStringList args;
640
641     args << QLatin1String("change") << QLatin1String("-o");
642     PerforceResponse result = runP4Cmd(state.currentProjectTopLevel(), args,
643                                        RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
644     if (result.error) {
645         cleanCommitMessageFile();
646         return;
647     }
648
649     m_commitMessageFileName = changeTmpFile.fileName();
650     changeTmpFile.write(result.stdOut.toAscii());
651     changeTmpFile.close();
652
653     args.clear();
654     args << QLatin1String("fstat");
655     args.append(perforceRelativeProjectDirectory(state));
656     PerforceResponse fstatResult = runP4Cmd(state.currentProjectTopLevel(), args,
657                                             RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
658     if (fstatResult.error) {
659         cleanCommitMessageFile();
660         return;
661     }
662
663     QStringList fstatLines = fstatResult.stdOut.split(QLatin1Char('\n'));
664     QStringList depotFileNames;
665     foreach (const QString &line, fstatLines) {
666         if (line.startsWith(QLatin1String("... depotFile")))
667             depotFileNames.append(line.mid(14));
668     }
669     if (depotFileNames.isEmpty()) {
670         VCSBase::VCSBaseOutputWindow::instance()->appendWarning(tr("Project has no files"));
671         cleanCommitMessageFile();
672         return;
673     }
674
675     openPerforceSubmitEditor(m_commitMessageFileName, depotFileNames);
676 }
677
678 Core::IEditor *PerforcePlugin::openPerforceSubmitEditor(const QString &fileName, const QStringList &depotFileNames)
679 {
680     Core::EditorManager *editorManager = Core::EditorManager::instance();
681     Core::IEditor *editor = editorManager->openEditor(fileName, Constants::PERFORCE_SUBMIT_EDITOR_ID,
682                                                       Core::EditorManager::ModeSwitch);
683     PerforceSubmitEditor *submitEditor = static_cast<PerforceSubmitEditor*>(editor);
684     submitEditor->restrictToProjectFiles(depotFileNames);
685     submitEditor->registerActions(m_undoAction, m_redoAction, m_submitCurrentLogAction, m_diffSelectedFiles);
686     connect(submitEditor, SIGNAL(diffSelectedFiles(QStringList)), this, SLOT(slotSubmitDiff(QStringList)));
687     submitEditor->setCheckScriptWorkingDirectory(m_commitWorkingDirectory);
688     return editor;
689 }
690
691 void PerforcePlugin::printPendingChanges()
692 {
693     qApp->setOverrideCursor(Qt::WaitCursor);
694     PendingChangesDialog dia(pendingChangesData(), Core::ICore::instance()->mainWindow());
695     qApp->restoreOverrideCursor();
696     if (dia.exec() == QDialog::Accepted) {
697         const int i = dia.changeNumber();
698         QStringList args(QLatin1String("submit"));
699         args << QLatin1String("-c") << QString::number(i);
700         runP4Cmd(m_settings.topLevel(), args,
701                  CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
702     }
703 }
704
705 void PerforcePlugin::describeChange()
706 {
707     ChangeNumberDialog dia;
708     if (dia.exec() == QDialog::Accepted && dia.number() > 0)
709         describe(QString(), QString::number(dia.number()));
710 }
711
712 void PerforcePlugin::annotateCurrentFile()
713 {
714     const VCSBase::VCSBasePluginState state = currentState();
715     QTC_ASSERT(state.hasFile(), return)
716     annotate(state.currentFileTopLevel(), state.relativeCurrentFile());
717 }
718
719 void PerforcePlugin::annotate()
720 {
721     const QString file = QFileDialog::getOpenFileName(0, tr("p4 annotate"));
722     if (!file.isEmpty()) {
723         const QFileInfo fi(file);
724         annotate(fi.absolutePath(), fi.fileName());
725     }
726 }
727
728 void PerforcePlugin::vcsAnnotate(const QString &file, const QString &revision, int lineNumber)
729 {
730     const QFileInfo fi(file);
731     annotate(fi.absolutePath(), fi.fileName(), revision, lineNumber);
732 }
733
734 void PerforcePlugin::annotate(const QString &workingDir,
735                               const QString &fileName,
736                               const QString &changeList /* = QString() */,
737                               int lineNumber /* = -1 */)
738 {
739     const QStringList files = QStringList(fileName);
740     QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
741     const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files, changeList);
742     const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
743     QStringList args;
744     args << QLatin1String("annotate") << QLatin1String("-cqi");
745     if (changeList.isEmpty()) {
746         args << fileName;
747     } else {
748         args << (fileName + QLatin1Char('@') + changeList);
749     }
750     const PerforceResponse result = runP4Cmd(workingDir, args,
751                                              CommandToWindow|StdErrToWindow|ErrorToWindow,
752                                              QStringList(), QByteArray(), codec);
753     if (!result.error) {
754         if (lineNumber < 1)
755             lineNumber = VCSBase::VCSBaseEditor::lineNumberOfCurrentEditor();
756         const QFileInfo fi(fileName);
757         Core::IEditor *ed = showOutputInEditor(tr("p4 annotate %1").arg(id),
758                                                result.stdOut, VCSBase::AnnotateOutput,
759                                                source, codec);
760         VCSBase::VCSBaseEditor::gotoLineOfEditor(ed, lineNumber);
761     }
762 }
763
764 void PerforcePlugin::filelogCurrentFile()
765 {
766     const VCSBase::VCSBasePluginState state = currentState();
767     QTC_ASSERT(state.hasFile(), return)
768     filelog(state.currentFileTopLevel(), QStringList(state.relativeCurrentFile()), true);
769 }
770
771 void PerforcePlugin::filelog()
772 {
773     const QString file = QFileDialog::getOpenFileName(0, tr("p4 filelog"));
774     if (!file.isEmpty()) {
775         const QFileInfo fi(file);
776         filelog(fi.absolutePath(), QStringList(fi.fileName()));
777     }
778 }
779
780 void PerforcePlugin::logProject()
781 {
782     const VCSBase::VCSBasePluginState state = currentState();
783     QTC_ASSERT(state.hasProject(), return)
784     filelog(state.currentProjectTopLevel(), perforceRelativeFileArguments(state.relativeCurrentProject()));
785 }
786
787 void PerforcePlugin::logRepository()
788 {
789     const VCSBase::VCSBasePluginState state = currentState();
790     QTC_ASSERT(state.hasTopLevel(), return)
791     filelog(state.topLevel(), perforceRelativeFileArguments(QStringList()));
792 }
793
794 void PerforcePlugin::filelog(const QString &workingDir, const QStringList &fileNames,
795                              bool enableAnnotationContextMenu)
796 {
797     const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, fileNames);
798     QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, fileNames);
799     QStringList args;
800     args << QLatin1String("filelog") << QLatin1String("-li");
801     if (m_settings.logCount() > 0)
802         args << QLatin1String("-m") << QString::number(m_settings.logCount());
803     args.append(fileNames);
804     const PerforceResponse result = runP4Cmd(workingDir, args,
805                                              CommandToWindow|StdErrToWindow|ErrorToWindow,
806                                              QStringList(), QByteArray(), codec);
807     if (!result.error) {
808         const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, fileNames);
809         Core::IEditor *editor = showOutputInEditor(tr("p4 filelog %1").arg(id), result.stdOut,
810                                 VCSBase::LogOutput, source, codec);
811         if (enableAnnotationContextMenu)
812             VCSBase::VCSBaseEditor::getVcsBaseEditor(editor)->setFileLogAnnotateEnabled(true);
813     }
814 }
815
816 void PerforcePlugin::updateActions(VCSBase::VCSBasePlugin::ActionState as)
817 {
818     if (!enableMenuAction(as, m_menuAction)) {
819         m_commandLocator->setEnabled(false);
820         return;        
821     }
822     const bool hasTopLevel = currentState().hasTopLevel();
823     m_commandLocator->setEnabled(hasTopLevel);
824     m_logRepositoryAction->setEnabled(hasTopLevel);
825
826     const QString fileName = currentState().currentFileName();
827     m_editAction->setParameter(fileName);
828     m_addAction->setParameter(fileName);
829     m_deleteAction->setParameter(fileName);
830     m_revertFileAction->setParameter(fileName);
831     m_diffFileAction->setParameter(fileName);
832     m_annotateCurrentAction->setParameter(fileName);
833     m_filelogCurrentAction->setParameter(fileName);
834
835     const QString projectName = currentState().currentProjectName();
836     m_logProjectAction->setParameter(projectName);
837     m_updateProjectAction->setParameter(projectName);
838     m_diffProjectAction->setParameter(projectName);
839     m_submitProjectAction->setParameter(projectName);
840     m_revertProjectAction->setParameter(projectName);
841     m_revertUnchangedAction->setParameter(projectName);
842
843     m_diffAllAction->setEnabled(true);
844     m_openedAction->setEnabled(true);
845     m_describeAction->setEnabled(true);
846     m_annotateAction->setEnabled(true);
847     m_filelogAction->setEnabled(true);
848     m_pendingAction->setEnabled(true);
849     m_updateAllAction->setEnabled(true);
850 }
851
852 bool PerforcePlugin::managesDirectory(const QString &directory, QString *topLevel /* = 0 */)
853 {
854     const bool rc = managesDirectoryFstat(directory);
855     if (topLevel) {
856         if (rc) {
857             *topLevel = m_settings.topLevelSymLinkTarget();
858         } else {
859             topLevel->clear();
860         }
861     }
862     return rc;
863 }
864
865 bool PerforcePlugin::managesDirectoryFstat(const QString &directory)
866 {
867     if (!m_settings.isValid())
868         return false;
869     // Cached?
870     const ManagedDirectoryCache::const_iterator cit = m_managedDirectoryCache.constFind(directory);
871     if (cit != m_managedDirectoryCache.constEnd())
872         return cit.value();
873     // Determine value and insert into cache
874     bool managed = false;
875     do {
876         // Quick check: Must be at or below top level and not "../../other_path"
877         const QStringList relativeDirArgs = m_settings.relativeToTopLevelArguments(directory);
878         if (!relativeDirArgs.empty() && relativeDirArgs.front().startsWith(QLatin1String("..")))
879             break;
880         // Is it actually managed by perforce?
881         QStringList args;
882         args << QLatin1String("fstat") << QLatin1String("-m1") << perforceRelativeFileArguments(relativeDirArgs);
883         const PerforceResponse result = runP4Cmd(m_settings.topLevel(), args,
884                                                  RunFullySynchronous);
885         managed = result.stdOut.contains("depotFile") || result.stdErr.contains("... - no such file(s)");
886     } while(false);
887
888     m_managedDirectoryCache.insert(directory, managed);
889     return managed;
890 }
891
892 bool PerforcePlugin::vcsOpen(const QString &workingDir, const QString &fileName)
893 {
894     if (Perforce::Constants::debug)
895         qDebug() << "PerforcePlugin::vcsOpen" << workingDir << fileName;
896     QStringList args;
897     args << QLatin1String("edit") << QDir::toNativeSeparators(fileName);
898     const PerforceResponse result = runP4Cmd(workingDir, args,
899                                        CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
900     return !result.error;
901 }
902
903 bool PerforcePlugin::vcsAdd(const QString &workingDir, const QString &fileName)
904 {
905     QStringList args;
906     args << QLatin1String("add") << fileName;
907     const PerforceResponse result = runP4Cmd(workingDir, args,
908                                        CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
909     return !result.error;
910 }
911
912 bool PerforcePlugin::vcsDelete(const QString &workingDir, const QString &fileName)
913 {
914
915     QStringList args;
916     args << QLatin1String("revert") << fileName;
917     const PerforceResponse revertResult = runP4Cmd(workingDir, args,
918                                        CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
919     if (revertResult.error)
920         return false;
921     args.clear();
922     args << QLatin1String("delete") << fileName;
923     const PerforceResponse deleteResult = runP4Cmd(workingDir, args,
924                                              CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
925     // TODO need to carefully parse the actual messages from perforce
926     // or do a fstat before to decide what to do
927
928     // Different states are:
929     // File is in depot and unopened => p4 delete %
930     // File is in depot and opened => p4 revert %, p4 delete %
931     // File is not in depot => p4 revert %
932     return !deleteResult.error;
933 }
934
935 bool PerforcePlugin::vcsMove(const QString &workingDir, const QString &from, const QString &to)
936 {
937     // TODO verify this works
938     QStringList args;
939     args << QLatin1String("edit") << from;
940     const PerforceResponse editResult = runP4Cmd(workingDir, args,
941                                                  RunFullySynchronous|CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
942     if (editResult.error)
943         return false;
944     args.clear();
945     args << QLatin1String("move") << from << to;
946     const PerforceResponse moveResult = runP4Cmd(workingDir, args,
947                                                  RunFullySynchronous|CommandToWindow|StdOutToWindow|StdErrToWindow|ErrorToWindow);
948     return !moveResult.error;
949 }
950
951 // Write extra args to temporary file
952 QSharedPointer<QTemporaryFile>
953         PerforcePlugin::createTemporaryArgumentFile(const QStringList &extraArgs) const
954 {
955     if (extraArgs.isEmpty())
956         return QSharedPointer<QTemporaryFile>();
957     // create pattern
958     if (m_tempFilePattern.isEmpty()) {
959         m_tempFilePattern = QDir::tempPath();
960         if (!m_tempFilePattern.endsWith(QDir::separator()))
961             m_tempFilePattern += QDir::separator();
962         m_tempFilePattern += QLatin1String("qtc_p4_XXXXXX.args");
963     }
964     QSharedPointer<QTemporaryFile> rc(new QTemporaryFile(m_tempFilePattern));
965     rc->setAutoRemove(true);
966     if (!rc->open()) {
967         qWarning("Could not create temporary file: %s. Appending all file names to command line.",
968                  qPrintable(rc->errorString()));
969         return QSharedPointer<QTemporaryFile>();
970     }
971     const int last = extraArgs.size() - 1;
972     for (int i = 0; i <= last; i++) {
973         rc->write(extraArgs.at(i).toLocal8Bit());
974         if (i != last)
975             rc->write("\n");
976     }
977     rc->close();
978     return rc;
979 }
980
981 // Run messages
982
983 static inline QString msgNotStarted(const QString &cmd)
984 {
985     return PerforcePlugin::tr("Could not start perforce '%1'. Please check your settings in the preferences.").arg(cmd);
986 }
987
988 static inline QString msgTimeout(int timeOut)
989 {
990     return PerforcePlugin::tr("Perforce did not respond within timeout limit (%1 ms).").arg(timeOut );
991 }
992
993 static inline QString msgCrash()
994 {
995     return PerforcePlugin::tr("The process terminated abnormally.");
996 }
997
998 static inline QString msgExitCode(int ex)
999 {
1000     return PerforcePlugin::tr("The process terminated with exit code %1.").arg(ex);
1001 }
1002
1003 // Run using a SynchronousProcess, emitting signals to the message window
1004 PerforceResponse PerforcePlugin::synchronousProcess(const QString &workingDir,
1005                                                     const QStringList &args,
1006                                                     unsigned flags,
1007                                                     const QByteArray &stdInput,
1008                                                     QTextCodec *outputCodec) const
1009 {
1010     QTC_ASSERT(stdInput.isEmpty(), return PerforceResponse()) // Not supported here
1011
1012     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
1013     // Run, connect stderr to the output window
1014     Utils::SynchronousProcess process;
1015     const int timeOut = (flags & LongTimeOut) ? m_settings.longTimeOutMS() : m_settings.timeOutMS();
1016     process.setTimeout(timeOut);
1017     process.setStdOutCodec(outputCodec);
1018     if (flags & OverrideDiffEnvironment)
1019         process.setProcessEnvironment(overrideDiffEnvironmentVariable());
1020     if (!workingDir.isEmpty())
1021         process.setWorkingDirectory(workingDir);
1022
1023     // connect stderr to the output window if desired
1024     if (flags & StdErrToWindow) {
1025         process.setStdErrBufferedSignalsEnabled(true);
1026         connect(&process, SIGNAL(stdErrBuffered(QString,bool)), outputWindow, SLOT(append(QString)));
1027     }
1028
1029     // connect stdout to the output window if desired
1030     if (flags & StdOutToWindow) {
1031         process.setStdOutBufferedSignalsEnabled(true);
1032         connect(&process, SIGNAL(stdOutBuffered(QString,bool)), outputWindow, SLOT(append(QString)));
1033     }
1034     if (Perforce::Constants::debug)
1035         qDebug() << "PerforcePlugin::run syncp actual args [" << process.workingDirectory() << ']' << args;
1036     process.setTimeOutMessageBoxEnabled(true);
1037     const Utils::SynchronousProcessResponse sp_resp = process.run(m_settings.p4Command(), args);
1038     if (Perforce::Constants::debug)
1039         qDebug() << sp_resp;
1040
1041     PerforceResponse response;
1042     response.error = true;
1043     response.exitCode = sp_resp.exitCode;
1044     response.stdErr = sp_resp.stdErr;
1045     response.stdOut = sp_resp.stdOut;
1046     switch (sp_resp.result) {
1047     case Utils::SynchronousProcessResponse::Finished:
1048         response.error = false;
1049         break;
1050     case Utils::SynchronousProcessResponse::FinishedError:
1051         response.message = msgExitCode(sp_resp.exitCode);
1052         response.error = !(flags & IgnoreExitCode);
1053         break;
1054     case Utils::SynchronousProcessResponse::TerminatedAbnormally:
1055         response.message = msgCrash();
1056         break;
1057     case Utils::SynchronousProcessResponse::StartFailed:
1058         response.message = msgNotStarted(m_settings.p4Command());
1059         break;
1060     case Utils::SynchronousProcessResponse::Hang:
1061         response.message = msgCrash();
1062         break;
1063     }
1064     return response;
1065 }
1066
1067 // Run using a QProcess, for short queries
1068 PerforceResponse PerforcePlugin::fullySynchronousProcess(const QString &workingDir,
1069                                                          const QStringList &args,
1070                                                          unsigned flags,
1071                                                          const QByteArray &stdInput,
1072                                                          QTextCodec *outputCodec) const
1073 {
1074     QProcess process;
1075
1076     if (flags & OverrideDiffEnvironment)
1077         process.setProcessEnvironment(overrideDiffEnvironmentVariable());
1078     if (!workingDir.isEmpty())
1079         process.setWorkingDirectory(workingDir);
1080
1081     if (Perforce::Constants::debug)
1082         qDebug() << "PerforcePlugin::run fully syncp actual args [" << process.workingDirectory() << ']' << args;
1083
1084     PerforceResponse response;
1085     process.start(m_settings.p4Command(), args);
1086     if (stdInput.isEmpty())
1087         process.closeWriteChannel();
1088
1089     if (!process.waitForStarted(3000)) {
1090         response.error = true;
1091         response.message = msgNotStarted(m_settings.p4Command());
1092         return response;
1093     }
1094     if (!stdInput.isEmpty()) {
1095         if (process.write(stdInput) == -1) {
1096             Utils::SynchronousProcess::stopProcess(process);
1097             response.error = true;
1098             response.message = tr("Unable to write input data to process %1: %2").
1099                                arg(QDir::toNativeSeparators(m_settings.p4Command()),
1100                                    process.errorString());
1101             return response;
1102         }
1103         process.closeWriteChannel();
1104     }
1105
1106     QByteArray stdOut;
1107     QByteArray stdErr;
1108     const int timeOut = (flags & LongTimeOut) ? m_settings.longTimeOutMS() : m_settings.timeOutMS();
1109     if (!Utils::SynchronousProcess::readDataFromProcess(process, timeOut, &stdOut, &stdErr, true)) {
1110         Utils::SynchronousProcess::stopProcess(process);
1111         response.error = true;
1112         response.message = msgTimeout(timeOut);
1113         return response;
1114     }
1115     if (process.exitStatus() != QProcess::NormalExit) {
1116         response.error = true;
1117         response.message = msgCrash();
1118         return response;
1119     }
1120     response.exitCode = process.exitCode();
1121     response.error = response.exitCode ? !(flags & IgnoreExitCode) : false;
1122     response.stdErr = QString::fromLocal8Bit(stdErr);
1123     response.stdOut = outputCodec ? outputCodec->toUnicode(stdOut.constData(), stdOut.size()) :
1124                                     QString::fromLocal8Bit(stdOut);
1125     const QChar cr = QLatin1Char('\r');
1126     response.stdErr.remove(cr);
1127     response.stdOut.remove(cr);
1128     // Logging
1129     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
1130     if ((flags & StdErrToWindow) && !response.stdErr.isEmpty())
1131         outputWindow->append(response.stdErr);
1132     if ((flags & StdOutToWindow) && !response.stdOut.isEmpty())
1133         outputWindow->append(response.stdOut);
1134     return response;
1135 }
1136
1137 PerforceResponse PerforcePlugin::runP4Cmd(const QString &workingDir,
1138                                           const QStringList &args,
1139                                           unsigned flags,
1140                                           const QStringList &extraArgs,
1141                                           const QByteArray &stdInput,
1142                                           QTextCodec *outputCodec) const
1143 {
1144     if (Perforce::Constants::debug)
1145         qDebug() << "PerforcePlugin::runP4Cmd [" << workingDir << ']' << args << extraArgs << stdInput << debugCodec(outputCodec);
1146
1147     VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
1148     if (!m_settings.isValid()) {
1149         PerforceResponse invalidConfigResponse;
1150         invalidConfigResponse.error = true;
1151         invalidConfigResponse.message = tr("Perforce is not correctly configured.");
1152         outputWindow->appendError(invalidConfigResponse.message);
1153         return invalidConfigResponse;
1154     }
1155     QStringList actualArgs = m_settings.commonP4Arguments(workingDir);
1156     QSharedPointer<QTemporaryFile> tempFile = createTemporaryArgumentFile(extraArgs);
1157     if (!tempFile.isNull())
1158         actualArgs << QLatin1String("-x") << tempFile->fileName();
1159     actualArgs.append(args);
1160
1161     if (flags & CommandToWindow)
1162         outputWindow->appendCommand(workingDir, m_settings.p4Command(), actualArgs);
1163
1164     if (flags & ShowBusyCursor)
1165         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
1166
1167     const PerforceResponse  response = (flags & RunFullySynchronous)  ?
1168         fullySynchronousProcess(workingDir, actualArgs, flags, stdInput, outputCodec) :
1169         synchronousProcess(workingDir, actualArgs, flags, stdInput, outputCodec);
1170
1171     if (flags & ShowBusyCursor)
1172         QApplication::restoreOverrideCursor();
1173
1174     if (response.error) {
1175         if (Perforce::Constants::debug)
1176             qDebug() << response.message;
1177         if (flags & ErrorToWindow)
1178             outputWindow->appendError(response.message);
1179     }
1180     return response;
1181 }
1182
1183 Core::IEditor * PerforcePlugin::showOutputInEditor(const QString& title, const QString output,
1184                                                    int editorType,
1185                                                    const QString &source,
1186                                                    QTextCodec *codec)
1187 {
1188     const VCSBase::VCSBaseEditorParameters *params = findType(editorType);
1189     QTC_ASSERT(params, return 0);
1190     const QString id = params->id;
1191     if (Perforce::Constants::debug)
1192         qDebug() << "PerforcePlugin::showOutputInEditor" << title << id <<  "Size= " << output.size() <<  " Type=" << editorType << debugCodec(codec);
1193     QString s = title;
1194     Core::IEditor *editor = Core::EditorManager::instance()->openEditorWithContents(id, &s, output);
1195     connect(editor, SIGNAL(annotateRevisionRequested(QString,QString,int)),
1196             this, SLOT(vcsAnnotate(QString,QString,int)));
1197     PerforceEditor *e = qobject_cast<PerforceEditor*>(editor->widget());
1198     if (!e)
1199         return 0;
1200     e->setForceReadOnly(true);
1201     e->setSource(source);
1202     s.replace(QLatin1Char(' '), QLatin1Char('_'));
1203     e->setSuggestedFileName(s);
1204     if (codec)
1205         e->setCodec(codec);
1206     Core::IEditor *ie = e->editableInterface();
1207     Core::EditorManager::instance()->activateEditor(ie, Core::EditorManager::ModeSwitch);
1208     return ie;
1209 }
1210
1211 void PerforcePlugin::slotSubmitDiff(const QStringList &files)
1212 {
1213     p4Diff(m_commitWorkingDirectory, files);
1214 }
1215
1216 void PerforcePlugin::p4Diff(const QString &workingDir, const QStringList &files)
1217 {
1218     Core::IEditor *existingEditor = 0;
1219
1220     QTextCodec *codec = VCSBase::VCSBaseEditor::getCodec(workingDir, files);
1221     const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDir, files);
1222     const QString source = VCSBase::VCSBaseEditor::getSource(workingDir, files);
1223
1224     // Reuse existing editors for that id
1225     foreach (Core::IEditor *ed, Core::EditorManager::instance()->openedEditors()) {
1226         if (ed->file()->property("originalFileName").toString() == id) {
1227             existingEditor = ed;
1228             break;
1229         }
1230     }
1231     // Split arguments according to size
1232     QStringList args;
1233     args << QLatin1String("diff") << QLatin1String("-du");
1234     QStringList extraArgs;
1235     if (files.size() > 1) {
1236         extraArgs = files;
1237     } else {
1238         args.append(files);
1239     }
1240     const unsigned flags = CommandToWindow|StdErrToWindow|ErrorToWindow|OverrideDiffEnvironment;
1241     const PerforceResponse result = runP4Cmd(workingDir, args, flags,
1242                                              extraArgs, QByteArray(), codec);
1243     if (result.error)
1244         return;
1245
1246     if (existingEditor) {
1247         existingEditor->createNew(result.stdOut);
1248         Core::EditorManager::instance()->activateEditor(existingEditor, Core::EditorManager::ModeSwitch);
1249     } else {
1250         Core::IEditor *editor = showOutputInEditor(tr("p4 diff %1").arg(id), result.stdOut, VCSBase::DiffOutput,
1251                                                    VCSBase::VCSBaseEditor::getSource(workingDir, files),
1252                                                    codec);
1253         editor->file()->setProperty("originalFileName", id);
1254     }
1255 }
1256
1257 void PerforcePlugin::describe(const QString & source, const QString &n)
1258 {
1259     QTextCodec *codec = source.isEmpty() ? static_cast<QTextCodec *>(0) : VCSBase::VCSBaseEditor::getCodec(source);
1260     QStringList args;
1261     args << QLatin1String("describe") << QLatin1String("-du") << n;
1262     const PerforceResponse result = runP4Cmd(m_settings.topLevel(), args, CommandToWindow|StdErrToWindow|ErrorToWindow,
1263                                              QStringList(), QByteArray(), codec);
1264     if (!result.error)
1265         showOutputInEditor(tr("p4 describe %1").arg(n), result.stdOut, VCSBase::DiffOutput, source, codec);
1266 }
1267
1268 void PerforcePlugin::submitCurrentLog()
1269 {
1270     m_submitActionTriggered = true;
1271     Core::EditorManager *em = Core::EditorManager::instance();
1272     em->closeEditors(QList<Core::IEditor*>() << em->currentEditor());
1273 }
1274
1275 void PerforcePlugin::cleanCommitMessageFile()
1276 {
1277     if (!m_commitMessageFileName.isEmpty()) {
1278         QFile::remove(m_commitMessageFileName);
1279         m_commitMessageFileName.clear();
1280         m_commitWorkingDirectory.clear();
1281     }
1282 }
1283
1284 bool PerforcePlugin::isCommitEditorOpen() const
1285 {
1286     return !m_commitMessageFileName.isEmpty();
1287 }
1288
1289 bool PerforcePlugin::submitEditorAboutToClose(VCSBase::VCSBaseSubmitEditor *submitEditor)
1290 {
1291     if (!isCommitEditorOpen())
1292         return true;
1293     Core::IFile *fileIFace = submitEditor->file();
1294     const PerforceSubmitEditor *perforceEditor = qobject_cast<PerforceSubmitEditor *>(submitEditor);
1295     if (!fileIFace || !perforceEditor)
1296         return true;
1297     // Prompt the user. Force a prompt unless submit was actually invoked (that
1298     // is, the editor was closed or shutdown).
1299     bool wantsPrompt = m_settings.promptToSubmit();
1300     const VCSBase::VCSBaseSubmitEditor::PromptSubmitResult answer =
1301             perforceEditor->promptSubmit(tr("Closing p4 Editor"),
1302                                          tr("Do you want to submit this change list?"),
1303                                          tr("The commit message check failed. Do you want to submit this change list"),
1304                                          &wantsPrompt, !m_submitActionTriggered);
1305     m_submitActionTriggered = false;
1306
1307     if (answer == VCSBase::VCSBaseSubmitEditor::SubmitCanceled)
1308         return false;
1309
1310     // Set without triggering the checking mechanism
1311     if (wantsPrompt != m_settings.promptToSubmit()) {
1312         m_settings.setPromptToSubmit(wantsPrompt);
1313         m_settings.toSettings(Core::ICore::instance()->settings());
1314     }
1315     Core::FileManager *fileManager = Core::ICore::instance()->fileManager();
1316     fileManager->blockFileChange(fileIFace);
1317     fileIFace->save();
1318     fileManager->unblockFileChange(fileIFace);
1319     if (answer == VCSBase::VCSBaseSubmitEditor::SubmitDiscarded) {
1320         cleanCommitMessageFile();
1321         return true;
1322     }
1323     // Pipe file into p4 submit -i
1324     QFile commitMessageFile(m_commitMessageFileName);
1325     if (!commitMessageFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
1326         VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("Cannot open temporary file."));
1327         return false;
1328     }
1329
1330     const QByteArray changeDescription = commitMessageFile.readAll();
1331     commitMessageFile.close();
1332     QStringList submitArgs;
1333     submitArgs << QLatin1String("submit") << QLatin1String("-i");
1334     const PerforceResponse submitResponse = runP4Cmd(m_settings.topLevelSymLinkTarget(), submitArgs,
1335                                                      LongTimeOut|RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow|ShowBusyCursor,
1336                                                      QStringList(), changeDescription);
1337     if (submitResponse.error) {
1338         VCSBase::VCSBaseOutputWindow::instance()->appendError(tr("p4 submit failed: %1").arg(submitResponse.message));
1339         return false;
1340     }
1341     VCSBase::VCSBaseOutputWindow::instance()->append(submitResponse.stdOut);
1342     if (submitResponse.stdOut.contains(QLatin1String("Out of date files must be resolved or reverted)")))
1343         QMessageBox::warning(submitEditor->widget(), tr("Pending change"), tr("Could not submit the change, because your workspace was out of date. Created a pending submit instead."));
1344
1345     cleanCommitMessageFile();
1346     return true;
1347 }
1348
1349 QString PerforcePlugin::clientFilePath(const QString &serverFilePath)
1350 {
1351     QTC_ASSERT(m_settings.isValid(), return QString())
1352
1353     QStringList args;
1354     args << QLatin1String("fstat") << serverFilePath;
1355     const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args,
1356                                                ShowBusyCursor|RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
1357     if (response.error)
1358         return QString();
1359
1360     QRegExp r(QLatin1String("\\.\\.\\.\\sclientFile\\s(.+)\n"));
1361     r.setMinimal(true);
1362     const QString path = r.indexIn(response.stdOut) != -1 ? r.cap(1).trimmed() : QString();
1363     if (Perforce::Constants::debug)
1364         qDebug() << "clientFilePath" << serverFilePath << path;
1365     return path;
1366 }
1367
1368 QString PerforcePlugin::pendingChangesData()
1369 {
1370     QTC_ASSERT(m_settings.isValid(), return QString())
1371
1372     QStringList args = QStringList(QLatin1String("info"));
1373     const PerforceResponse userResponse = runP4Cmd(m_settings.topLevelSymLinkTarget(), args,
1374                                                RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
1375     if (userResponse.error)
1376         return QString();
1377
1378     QRegExp r(QLatin1String("User\\sname:\\s(\\S+)\\s*\n"));
1379     QTC_ASSERT(r.isValid(), return QString())
1380     r.setMinimal(true);
1381     const QString user = r.indexIn(userResponse.stdOut) != -1 ? r.cap(1).trimmed() : QString();
1382     if (user.isEmpty())
1383         return QString();
1384     args.clear();
1385     args << QLatin1String("changes") << QLatin1String("-s") << QLatin1String("pending") << QLatin1String("-u") << user;
1386     const PerforceResponse dataResponse = runP4Cmd(m_settings.topLevelSymLinkTarget(), args,
1387                                                    RunFullySynchronous|CommandToWindow|StdErrToWindow|ErrorToWindow);
1388     return dataResponse.error ? QString() : dataResponse.stdOut;
1389 }
1390
1391 PerforcePlugin::~PerforcePlugin()
1392 {
1393 }
1394
1395 const PerforceSettings& PerforcePlugin::settings() const
1396 {
1397     return m_settings;
1398 }
1399
1400 void PerforcePlugin::setSettings(const Settings &newSettings)
1401 {
1402     if (newSettings != m_settings.settings()) {
1403         m_settings.setSettings(newSettings);
1404         m_managedDirectoryCache.clear();
1405         m_settings.toSettings(Core::ICore::instance()->settings());
1406         getTopLevel();
1407     }
1408 }
1409
1410 static inline QString msgWhereFailed(const QString & file, const QString &why)
1411 {
1412     //: Failed to run p4 "where" to resolve a Perforce file name to a local
1413     //: file system name.
1414     return PerforcePlugin::tr("Error running \"where\" on %1: %2").
1415             arg(QDir::toNativeSeparators(file), why);
1416 }
1417
1418 // Map a perforce name "//xx" to its real name in the file system
1419 QString PerforcePlugin::fileNameFromPerforceName(const QString& perforceName,
1420                                                  bool quiet,
1421                                                  QString *errorMessage) const
1422 {
1423     // All happy, already mapped
1424     if (!perforceName.startsWith(QLatin1String("//")))
1425         return perforceName;
1426     // "where" remaps the file to client file tree
1427     QStringList args;
1428     args << QLatin1String("where") << perforceName;
1429     unsigned flags = RunFullySynchronous;
1430     if (!quiet)
1431         flags |= CommandToWindow|StdErrToWindow|ErrorToWindow;
1432     const PerforceResponse response = runP4Cmd(m_settings.topLevelSymLinkTarget(), args, flags);
1433     if (response.error) {
1434         *errorMessage = msgWhereFailed(perforceName, response.message);
1435         return QString::null;
1436     }
1437
1438     QString output = response.stdOut;
1439     if (output.endsWith(QLatin1Char('\r')))
1440         output.chop(1);
1441     if (output.endsWith(QLatin1Char('\n')))
1442         output.chop(1);
1443
1444     if (output.isEmpty()) {
1445         //: File is not managed by Perforce
1446         *errorMessage = msgWhereFailed(perforceName, tr("The file is not mapped"));
1447         return QString();
1448     }
1449     const QString p4fileSpec = output.mid(output.lastIndexOf(QLatin1Char(' ')) + 1);
1450     const QString rc = m_settings.mapToFileSystem(p4fileSpec);
1451     if (Perforce::Constants::debug)
1452         qDebug() << "fileNameFromPerforceName" << perforceName << p4fileSpec << rc;
1453     return rc;
1454 }
1455
1456 PerforcePlugin *PerforcePlugin::perforcePluginInstance()
1457 {
1458     QTC_ASSERT(m_perforcePluginInstance, return 0);
1459     return m_perforcePluginInstance;
1460 }
1461
1462 PerforceVersionControl *PerforcePlugin::perforceVersionControl() const
1463 {
1464     return static_cast<PerforceVersionControl *>(versionControl());
1465 }
1466
1467 void PerforcePlugin::slotTopLevelFound(const QString &t)
1468 {
1469     m_settings.setTopLevel(t);
1470     const QString msg = tr("Perforce repository: %1").
1471                         arg(QDir::toNativeSeparators(t));
1472     VCSBase::VCSBaseOutputWindow::instance()->appendSilently(msg);
1473     if (Perforce::Constants::debug)
1474         qDebug() << "P4: " << t;
1475 }
1476
1477 void PerforcePlugin::slotTopLevelFailed(const QString &errorMessage)
1478 {
1479     VCSBase::VCSBaseOutputWindow::instance()->appendSilently(tr("Perforce: Unable to determine the repository: %1").arg(errorMessage));
1480     if (Perforce::Constants::debug)
1481         qDebug() << errorMessage;
1482 }
1483
1484 void PerforcePlugin::getTopLevel()
1485 {
1486     // Run a new checker
1487     if (m_settings.p4Command().isEmpty())
1488         return;
1489     PerforceChecker *checker = new PerforceChecker(this);
1490     connect(checker, SIGNAL(failed(QString)), this, SLOT(slotTopLevelFailed(QString)));
1491     connect(checker, SIGNAL(failed(QString)), checker, SLOT(deleteLater()));
1492     connect(checker, SIGNAL(succeeded(QString)), this, SLOT(slotTopLevelFound(QString)));
1493     connect(checker, SIGNAL(succeeded(QString)),checker, SLOT(deleteLater()));
1494     checker->start(m_settings.p4Command(), m_settings.commonP4Arguments(QString()), 30000);
1495 }
1496
1497 }
1498 }
1499
1500 Q_EXPORT_PLUGIN(Perforce::Internal::PerforcePlugin)