1 /*
2     SPDX-FileCopyrightText: 2011 Vishesh Yadav <vishesh3y@gmail.com>
3     SPDX-FileCopyrightText: 2015 Tomasz Bojczuk <seelook@gmail.com>
4 
5     SPDX-License-Identifier: GPL-2.0-or-later
6 */
7 
8 #include "commitdialog.h"
9 #include "hgwrapper.h"
10 #include "fileviewhgpluginsettings.h"
11 
12 #include <QGroupBox>
13 #include <QGridLayout>
14 #include <QLabel>
15 #include <QActionGroup>
16 #include <QPlainTextEdit>
17 #include <QMenu>
18 #include <QAction>
19 #include <QLineEdit>
20 #include <QSplitter>
21 #include <KTextEditor/Document>
22 #include <KTextEditor/View>
23 #include <KTextEditor/Editor>
24 #include <KMessageBox>
25 #include <KLocalizedString>
26 
HgCommitDialog(QWidget * parent)27 HgCommitDialog::HgCommitDialog(QWidget *parent):
28     DialogBase(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, parent)
29 {
30     // dialog properties
31     this->setWindowTitle(xi18nc("@title:window",
32                 "<application>Hg</application> Commit"));
33 
34     okButton()->setText(xi18nc("@action:button", "Commit"));
35     okButton()->setDisabled(true);
36 
37     // To show diff between commit
38     KTextEditor::Editor *editor = KTextEditor::Editor::instance();
39     if (!editor) {
40         KMessageBox::error(this,
41                 i18n("The KTextEditor component could not be found;"
42                      "\nplease check your KDE Frameworks installation."));
43         return;
44     }
45     m_fileDiffDoc = editor->createDocument(nullptr);
46     m_fileDiffView = qobject_cast<KTextEditor::View*>(m_fileDiffDoc->createView(this));
47     m_fileDiffView->setStatusBarEnabled(false);
48     m_fileDiffDoc->setReadWrite(false);
49 
50     // Setup actions
51     m_useCurrentBranch = new QAction(this);
52     m_useCurrentBranch->setCheckable(true);
53     m_useCurrentBranch->setText(xi18nc("@action:inmenu",
54                                "Commit to current branch"));
55 
56     m_newBranch = new QAction(this);
57     m_newBranch->setCheckable(true);
58     m_newBranch->setText(xi18nc("@action:inmenu",
59                                "Create new branch"));
60 
61     m_closeBranch = new QAction(this);
62     m_closeBranch->setCheckable(true);
63     m_closeBranch->setText(xi18nc("@action:inmenu",
64                                  "Close current branch"));
65 
66     m_branchMenu = new QMenu(this);
67     m_branchMenu->addAction(m_useCurrentBranch);
68     m_branchMenu->addAction(m_newBranch);
69     m_branchMenu->addAction(m_closeBranch);
70 
71     QActionGroup *branchActionGroup = new QActionGroup(this);
72     branchActionGroup->addAction(m_useCurrentBranch);
73     branchActionGroup->addAction(m_newBranch);
74     branchActionGroup->addAction(m_closeBranch);
75     m_useCurrentBranch->setChecked(true);
76     connect(branchActionGroup, &QActionGroup::triggered,
77             this, &HgCommitDialog::slotBranchActions);
78 
79 
80     //////////////
81     // Setup UI //
82     //////////////
83 
84     // Top bar of buttons
85     QHBoxLayout *topBarLayout = new QHBoxLayout;
86     m_copyMessageButton = new QPushButton(i18n("Copy Message"));
87     m_branchButton = new QPushButton(i18n("Branch"));
88 
89     m_copyMessageMenu = new QMenu(this);
90     createCopyMessageMenu();
91 
92     topBarLayout->addWidget(new QLabel(getParentForLabel()));
93     topBarLayout->addStretch();
94     topBarLayout->addWidget(m_branchButton);
95     topBarLayout->addWidget(m_copyMessageButton);
96     m_branchButton->setMenu(m_branchMenu);
97     m_copyMessageButton->setMenu(m_copyMessageMenu);
98 
99     // the commit box itself
100     QGroupBox *messageGroupBox = new QGroupBox;
101     QVBoxLayout *commitLayout = new QVBoxLayout;
102     m_commitMessage = editor->createDocument(nullptr);
103     KTextEditor::View *messageView =
104               qobject_cast<KTextEditor::View*>(m_commitMessage->createView(this));
105     messageView->setStatusBarEnabled(false);
106     messageView->setMinimumHeight(fontMetrics().height() * 4);
107     commitLayout->addWidget(messageView);
108     messageGroupBox->setTitle(xi18nc("@title:group", "Commit Message"));
109     messageGroupBox->setLayout(commitLayout);
110 
111     // Show diff here
112     QGroupBox *diffGroupBox = new QGroupBox;
113     QVBoxLayout *diffLayout = new QVBoxLayout(diffGroupBox);
114     diffLayout->addWidget(m_fileDiffView);
115     diffGroupBox->setTitle(xi18nc("@title:group", "Diff/Content"));
116     diffGroupBox->setLayout(diffLayout);
117 
118     // Set up layout for Status, Commit and Diff boxes
119     m_verticalSplitter = new QSplitter(Qt::Horizontal);
120     m_horizontalSplitter = new QSplitter(Qt::Vertical);
121     m_horizontalSplitter->addWidget(messageGroupBox);
122     m_horizontalSplitter->addWidget(diffGroupBox);
123     m_statusList = new HgStatusList;
124     m_verticalSplitter->addWidget(m_statusList);
125     m_verticalSplitter->addWidget(m_horizontalSplitter);
126 
127     // Set up layout and container for main dialog
128     QVBoxLayout *mainLayout = new QVBoxLayout;
129     mainLayout->addLayout(topBarLayout);
130     mainLayout->addWidget(m_verticalSplitter);
131     layout()->insertLayout(0, mainLayout);
132 
133     slotBranchActions(m_useCurrentBranch);
134     slotInitDiffOutput(); // initialize with whole repo diff
135 
136     // Load saved settings
137     FileViewHgPluginSettings *settings = FileViewHgPluginSettings::self();
138     this->resize(QSize(settings->commitDialogWidth(),
139                                settings->commitDialogHeight()));
140 
141     m_verticalSplitter->setSizes(settings->verticalSplitterSizes());
142     m_horizontalSplitter->setSizes(settings->horizontalSplitterSizes());
143 
144     messageView->setFocus(); // message editor ready to get a text
145 
146     connect(m_statusList, &HgStatusList::itemSelectionChanged,
147         this, &HgCommitDialog::slotItemSelectionChanged);
148     connect(m_commitMessage, &KTextEditor::Document::textChanged,
149          this, &HgCommitDialog::slotMessageChanged);
150     connect(this, SIGNAL(finished(int)), this, SLOT(saveGeometry()));
151 }
152 
getParentForLabel()153 QString HgCommitDialog::getParentForLabel()
154 {
155     HgWrapper *hgWrapper = HgWrapper::instance();
156     QString line("<b>parents:</b> ");
157     line += hgWrapper->getParentsOfHead();
158     return line;
159 }
160 
slotInitDiffOutput()161 void HgCommitDialog::slotInitDiffOutput()
162 {
163     m_fileDiffDoc->setReadWrite(true);
164     m_fileDiffDoc->setModified(false);
165     m_fileDiffDoc->closeUrl(true);
166 
167     QString diffOut;
168     HgWrapper *hgWrapper = HgWrapper::instance();
169     hgWrapper->executeCommand(QLatin1String("diff"), QStringList(), diffOut);
170     m_fileDiffDoc->setHighlightingMode("diff");
171     m_fileDiffDoc->setText(diffOut);
172     m_fileDiffView->setCursorPosition( KTextEditor::Cursor(0, 0) );
173     m_fileDiffDoc->setReadWrite(false);
174 }
175 
slotItemSelectionChanged(const char status,const QString & fileName)176 void HgCommitDialog::slotItemSelectionChanged(const char status,
177         const QString &fileName)
178 {
179     m_fileDiffDoc->setReadWrite(true);
180     m_fileDiffDoc->setModified(false);
181     m_fileDiffDoc->closeUrl(true);
182 
183     if (status != '?') {
184         QStringList arguments;
185         QString diffOut;
186         HgWrapper *hgWrapper = HgWrapper::instance();
187 
188         arguments << fileName;
189         hgWrapper->executeCommand(QLatin1String("diff"), arguments, diffOut);
190         m_fileDiffDoc->setText(diffOut);
191         m_fileDiffDoc->setHighlightingMode("diff");
192     }
193     else {
194         QUrl url = QUrl::fromLocalFile(HgWrapper::instance()->getBaseDir());
195         url = url.adjusted(QUrl::StripTrailingSlash);
196         url.setPath(url.path() + '/' + fileName);
197         m_fileDiffDoc->openUrl(url);
198     }
199 
200     m_fileDiffDoc->setReadWrite(false);
201     m_fileDiffView->setCursorPosition( KTextEditor::Cursor(0, 0) );
202 }
203 
slotMessageChanged()204 void HgCommitDialog::slotMessageChanged()
205 {
206     okButton()->setDisabled(m_commitMessage->isEmpty());
207 }
208 
done(int r)209 void HgCommitDialog::done(int r)
210 {
211     if (r == QDialog::Accepted) {
212         QStringList files;
213         if (m_statusList->getSelectionForCommit(files)) {
214             HgWrapper *hgWrapper = HgWrapper::instance();
215             if (m_branchAction == NewBranch) {
216                 if (!hgWrapper->createBranch(m_newBranchName)) {
217                     KMessageBox::error(this,
218                             i18n("Could not create branch! Aborting commit!"));
219                     return;
220                 }
221             }
222             bool success = hgWrapper->commit(m_commitMessage->text(),
223                     files, m_branchAction==CloseBranch);
224             if (success) {
225                 QDialog::done(r);
226             }
227             else {
228                 KMessageBox::error(this, i18n("Commit unsuccessful!"));
229             }
230         }
231         else {
232             KMessageBox::error(this, i18n("No files for commit!"));
233         }
234     }
235     else {
236         QDialog::done(r);
237     }
238 }
239 
saveGeometry()240 void HgCommitDialog::saveGeometry()
241 {
242     FileViewHgPluginSettings *settings = FileViewHgPluginSettings::self();
243     settings->setCommitDialogHeight(this->height());
244     settings->setCommitDialogWidth(this->width());
245     settings->setHorizontalSplitterSizes(m_horizontalSplitter->sizes());
246     settings->setVerticalSplitterSizes(m_verticalSplitter->sizes());
247     settings->save();
248 }
249 
slotBranchActions(QAction * action)250 void HgCommitDialog::slotBranchActions(QAction *action)
251 {
252     HgWrapper *hgWrapper = HgWrapper::instance();
253     QString currentBranch;
254     hgWrapper->executeCommand(QLatin1String("branch"), QStringList(), currentBranch);
255     currentBranch.replace('\n', QString());
256     currentBranch = " (" + currentBranch + ')';
257     if (action == m_useCurrentBranch) {
258         m_branchAction = NoChanges;
259         m_branchButton->setText(i18n("Branch: Current Branch") + currentBranch);
260     }
261     else if (action == m_newBranch) {
262         NewBranchDialog diag;
263         if (diag.exec() == QDialog::Accepted) {
264            m_branchAction = NewBranch;
265            m_newBranchName = diag.getBranchName();
266            m_branchButton->setText(i18n("Branch: ") + m_newBranchName);
267         }
268         else { // restore previous check state
269             if (m_branchAction == NoChanges) {
270                 m_useCurrentBranch->setChecked(true);
271             }
272             else if (m_branchAction == CloseBranch) {
273                 m_closeBranch->setChecked(true);
274             }
275         }
276     }
277     else if (action == m_closeBranch) {
278         m_branchAction = CloseBranch;
279         m_branchButton->setText(i18n("Branch: Close Current") + currentBranch);
280     }
281 }
282 
283 /*****************/
284 /* Branch Dialog */
285 /*****************/
286 
NewBranchDialog(QWidget * parent)287 NewBranchDialog::NewBranchDialog(QWidget *parent):
288     QDialog(parent)
289 {
290     // dialog properties
291     this->setWindowTitle(xi18nc("@title:window",
292                 "<application>Hg</application> Commit: New Branch"));
293     QDialogButtonBox *buttonBox= new QDialogButtonBox(QDialogButtonBox::Cancel, this);
294     m_okButton = buttonBox->addButton(QDialogButtonBox::Ok);
295     m_okButton->setDisabled(true);
296     m_okButton->setDefault(true);
297 
298     m_branchList = HgWrapper::instance()->getBranches();
299 
300     QLabel *message = new QLabel(xi18nc("@label", "Enter new branch name"));
301     m_branchNameInput = new QLineEdit;
302     m_errorLabel = new QLabel;
303 
304     QVBoxLayout *layout = new QVBoxLayout;
305     layout->addWidget(message);
306     layout->addWidget(m_branchNameInput);
307     layout->addWidget(m_errorLabel);
308     layout->addWidget(buttonBox);
309 
310     setLayout(layout);
311 
312     connect(m_branchNameInput, &QLineEdit::textChanged,
313             this, &NewBranchDialog::slotTextChanged);
314     connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
315     connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
316 }
317 
slotTextChanged(const QString & text)318 void NewBranchDialog::slotTextChanged(const QString &text)
319 {
320     if (m_branchList.contains(text)) {
321         m_errorLabel->setText(xi18nc("@label", "<b>Branch already exists!</b>"));
322         m_okButton->setDisabled(true);
323     }
324     else if (text.length() > 0) {
325         m_errorLabel->clear();
326         m_okButton->setDisabled(false);
327     }
328     else {
329         m_errorLabel->setText(xi18nc("@label", "<b>Enter some text!</b>"));
330         m_okButton->setDisabled(true);
331     }
332 }
333 
getBranchName() const334 QString NewBranchDialog::getBranchName() const
335 {
336     return m_branchNameInput->text();
337 }
338 
createCopyMessageMenu()339 void HgCommitDialog::createCopyMessageMenu()
340 {
341     QActionGroup *actionGroup = new QActionGroup(this);
342     connect(actionGroup, &QActionGroup::triggered,
343             this, &HgCommitDialog::slotInsertCopyMessage);
344 
345     QStringList args;
346     args << QLatin1String("--limit");
347     args << QLatin1String("7");
348     args << QLatin1String("--template");
349     args << QLatin1String("{desc}\n");
350 //     args << QLatin1String("{desc|short}\n");
351 
352     HgWrapper *hgw = HgWrapper::instance();
353     QString output;
354     hgw->executeCommand(QLatin1String("log"), args, output);
355 
356     const QStringList messages = output.split('\n', Qt::SkipEmptyParts);
357     for (const QString &msg : messages) {
358         QAction *action = m_copyMessageMenu->addAction(msg.left(40)); // max 40 characters
359         action->setData(msg); // entire description into action data
360         actionGroup->addAction(action);
361     }
362 }
363 
slotInsertCopyMessage(QAction * action)364 void HgCommitDialog::slotInsertCopyMessage(QAction *action)
365 {
366     m_commitMessage->setText(action->data().toString());
367 }
368 
369 
370