|
23 | 23 | #include <QDialogButtonBox> |
24 | 24 | #include <QFormLayout> |
25 | 25 | #include <QGuiApplication> |
| 26 | +#include <QHBoxLayout> |
26 | 27 | #include <QKeyEvent> |
27 | 28 | #include <QLabel> |
28 | 29 | #include <QLineEdit> |
|
33 | 34 | #include <QScreen> |
34 | 35 | #include <QStandardItem> |
35 | 36 | #include <QStandardItemModel> |
| 37 | +#include <QStyle> |
36 | 38 | #include <QVBoxLayout> |
37 | 39 |
|
38 | 40 | namespace { |
@@ -234,6 +236,21 @@ void BranchPickerPopup::showItemMenu(const QModelIndex &index) |
234 | 236 | }); |
235 | 237 | } |
236 | 238 |
|
| 239 | + if (!isRemote) { |
| 240 | + menu.addSeparator(); |
| 241 | + QAction *aRename = menu.addAction(tr("&Rename…")); |
| 242 | + connect(aRename, &QAction::triggered, this, [this, payload]() { |
| 243 | + showRenameBranchDialog(payload); |
| 244 | + }); |
| 245 | + |
| 246 | + if (!isCurrent) { |
| 247 | + QAction *aDelete = menu.addAction(tr("&Delete…")); |
| 248 | + connect(aDelete, &QAction::triggered, this, [this, payload]() { |
| 249 | + showDeleteBranchDialog(payload); |
| 250 | + }); |
| 251 | + } |
| 252 | + } |
| 253 | + |
237 | 254 | const QRect itemRect = m_list->visualRect(index); |
238 | 255 | const QPoint pos = m_list->mapToGlobal(itemRect.topRight()); |
239 | 256 | menu.exec(pos); |
@@ -303,10 +320,134 @@ bool BranchPickerPopup::eventFilter(QObject *o, QEvent *e) |
303 | 320 | return true; |
304 | 321 | } |
305 | 322 | } |
| 323 | + if (o == m_list) { |
| 324 | + if (ke->key() == Qt::Key_F2) { |
| 325 | + const QModelIndex idx = m_list->currentIndex(); |
| 326 | + if (idx.isValid()) { |
| 327 | + auto *it = m_model->itemFromIndex(idx); |
| 328 | + if (it && it->data(RoleKind).toInt() == 0) { |
| 329 | + showRenameBranchDialog(it->data(RolePayload).toString()); |
| 330 | + return true; |
| 331 | + } |
| 332 | + } |
| 333 | + } |
| 334 | + if (ke->key() == Qt::Key_Delete) { |
| 335 | + const QModelIndex idx = m_list->currentIndex(); |
| 336 | + if (idx.isValid()) { |
| 337 | + auto *it = m_model->itemFromIndex(idx); |
| 338 | + if (it && it->data(RoleKind).toInt() == 0) { |
| 339 | + const QString payload = it->data(RolePayload).toString(); |
| 340 | + if (payload != m_current) { |
| 341 | + showDeleteBranchDialog(payload); |
| 342 | + return true; |
| 343 | + } |
| 344 | + } |
| 345 | + } |
| 346 | + } |
| 347 | + } |
306 | 348 | if (ke->key() == Qt::Key_Escape) { |
307 | 349 | close(); |
308 | 350 | return true; |
309 | 351 | } |
310 | 352 | } |
311 | 353 | return QWidget::eventFilter(o, e); |
312 | 354 | } |
| 355 | + |
| 356 | +void BranchPickerPopup::showRenameBranchDialog(const QString &branchName) |
| 357 | +{ |
| 358 | + QDialog dlg(this); |
| 359 | + dlg.setWindowTitle(tr("Rename Branch '%1'").arg(branchName)); |
| 360 | + dlg.setMinimumWidth(320); |
| 361 | + |
| 362 | + auto *layout = new QVBoxLayout(&dlg); |
| 363 | + |
| 364 | + auto *nameEdit = new QLineEdit(&dlg); |
| 365 | + nameEdit->setText(branchName); |
| 366 | + |
| 367 | + auto *previewLabel = new QLabel(&dlg); |
| 368 | + previewLabel->setStyleSheet(QStringLiteral("color: gray; font-size: 11px;")); |
| 369 | + |
| 370 | + auto *remoteCheck = new QCheckBox(tr("Update remote tracking"), &dlg); |
| 371 | + remoteCheck->setChecked(false); |
| 372 | + remoteCheck->setVisible(m_hasRemote); |
| 373 | + |
| 374 | + auto *remoteHint = new QLabel(tr("(deletes old remote ref, pushes new)"), &dlg); |
| 375 | + remoteHint->setStyleSheet(QStringLiteral("color: gray; font-size: 10px;")); |
| 376 | + remoteHint->setVisible(m_hasRemote); |
| 377 | + |
| 378 | + auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); |
| 379 | + buttons->button(QDialogButtonBox::Ok)->setText(tr("Rename")); |
| 380 | + buttons->button(QDialogButtonBox::Ok)->setEnabled(false); |
| 381 | + |
| 382 | + layout->addWidget(new QLabel(tr("Rename branch '<b>%1</b>' to:").arg(branchName.toHtmlEscaped()), &dlg)); |
| 383 | + layout->addWidget(nameEdit); |
| 384 | + layout->addWidget(previewLabel); |
| 385 | + layout->addWidget(remoteCheck); |
| 386 | + layout->addWidget(remoteHint); |
| 387 | + layout->addWidget(buttons); |
| 388 | + |
| 389 | + connect(nameEdit, &QLineEdit::textChanged, &dlg, [&](const QString &text) { |
| 390 | + const QString sanitized = sanitizeBranchName(text); |
| 391 | + if (sanitized.isEmpty() || sanitized == branchName) { |
| 392 | + previewLabel->clear(); |
| 393 | + buttons->button(QDialogButtonBox::Ok)->setEnabled(false); |
| 394 | + } else { |
| 395 | + previewLabel->setText(tr("→ %1").arg(sanitized)); |
| 396 | + buttons->button(QDialogButtonBox::Ok)->setEnabled(!m_local.contains(sanitized)); |
| 397 | + } |
| 398 | + }); |
| 399 | + connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); |
| 400 | + connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); |
| 401 | + |
| 402 | + // Smart selection: select after last '/' or all if no '/' |
| 403 | + const int slashPos = branchName.lastIndexOf(QLatin1Char('/')); |
| 404 | + if (slashPos >= 0) { |
| 405 | + nameEdit->setSelection(slashPos + 1, branchName.length() - slashPos - 1); |
| 406 | + } else { |
| 407 | + nameEdit->selectAll(); |
| 408 | + } |
| 409 | + nameEdit->setFocus(); |
| 410 | + |
| 411 | + if (dlg.exec() == QDialog::Accepted) { |
| 412 | + const QString newName = sanitizeBranchName(nameEdit->text()); |
| 413 | + if (!newName.isEmpty() && newName != branchName) { |
| 414 | + emit renameBranchRequested(branchName, newName, remoteCheck->isChecked()); |
| 415 | + close(); |
| 416 | + } |
| 417 | + } |
| 418 | +} |
| 419 | + |
| 420 | +void BranchPickerPopup::showDeleteBranchDialog(const QString &branchName) |
| 421 | +{ |
| 422 | + QDialog dlg(this); |
| 423 | + dlg.setWindowTitle(tr("Delete Branch")); |
| 424 | + dlg.setMinimumWidth(320); |
| 425 | + |
| 426 | + auto *layout = new QVBoxLayout(&dlg); |
| 427 | + |
| 428 | + auto *topRow = new QHBoxLayout; |
| 429 | + auto *icon = new QLabel(&dlg); |
| 430 | + icon->setPixmap(style()->standardPixmap(QStyle::SP_MessageBoxWarning)); |
| 431 | + auto *msg = new QLabel(tr("Are you sure you want to delete branch '<b>%1</b>'?") |
| 432 | + .arg(branchName.toHtmlEscaped()), &dlg); |
| 433 | + msg->setWordWrap(true); |
| 434 | + topRow->addWidget(icon, 0, Qt::AlignTop); |
| 435 | + topRow->addWidget(msg, 1); |
| 436 | + layout->addLayout(topRow); |
| 437 | + |
| 438 | + auto *forceCheck = new QCheckBox(tr("Force delete (even if not fully merged)"), &dlg); |
| 439 | + forceCheck->setChecked(false); |
| 440 | + layout->addWidget(forceCheck); |
| 441 | + |
| 442 | + auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg); |
| 443 | + buttons->button(QDialogButtonBox::Ok)->setText(tr("Delete")); |
| 444 | + layout->addWidget(buttons); |
| 445 | + |
| 446 | + connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); |
| 447 | + connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); |
| 448 | + |
| 449 | + if (dlg.exec() == QDialog::Accepted) { |
| 450 | + emit deleteBranchRequested(branchName, forceCheck->isChecked()); |
| 451 | + close(); |
| 452 | + } |
| 453 | +} |
0 commit comments