1818
1919#include " BranchPickerPopup.h"
2020
21+ #include < QCheckBox>
22+ #include < QDialog>
23+ #include < QDialogButtonBox>
24+ #include < QFormLayout>
2125#include < QGuiApplication>
2226#include < QKeyEvent>
27+ #include < QLabel>
2328#include < QLineEdit>
2429#include < QListView>
30+ #include < QMenu>
31+ #include < QPushButton>
2532#include < QRegularExpression>
2633#include < QScreen>
2734#include < QStandardItem>
2835#include < QStandardItemModel>
2936#include < QVBoxLayout>
3037
3138namespace {
32- constexpr int RoleKind = Qt::UserRole + 1 ; // 0 = local, 1 = remote, 2 = create-current, 3 = create-default
33- constexpr int RolePayload = Qt::UserRole + 2 ; // branch name or create base
39+ constexpr int RoleKind = Qt::UserRole + 1 ; // 0 = local, 1 = remote
40+ constexpr int RolePayload = Qt::UserRole + 2 ; // branch name
3441}
3542
3643QString BranchPickerPopup::sanitizeBranchName (const QString &raw)
@@ -72,16 +79,18 @@ BranchPickerPopup::BranchPickerPopup(QWidget *parent)
7279 setMinimumHeight (360 );
7380
7481 connect (m_filter, &QLineEdit::textChanged, this , &BranchPickerPopup::rebuild);
75- connect (m_list, &QAbstractItemView::activated , this , &BranchPickerPopup::onActivated);
82+ connect (m_list, &QAbstractItemView::clicked , this , &BranchPickerPopup::onActivated);
7683}
7784
7885void BranchPickerPopup::setBranches (const QStringList &local, const QStringList &remote,
79- const QString ¤t, const QString &defaultBranch)
86+ const QString ¤t, const QString &defaultBranch,
87+ bool detachedHead)
8088{
8189 m_local = local;
8290 m_remote = remote;
8391 m_current = current;
8492 m_default = defaultBranch;
93+ m_detachedHead = detachedHead;
8594 m_filter->clear ();
8695 rebuild ();
8796}
@@ -112,6 +121,7 @@ void BranchPickerPopup::rebuild()
112121 auto f = it->font (); f.setBold (true ); it->setFont (f);
113122 m_model->appendRow (it);
114123 };
124+
115125 auto matches = [&](const QString &name) {
116126 return q.isEmpty () || name.toLower ().contains (qLower);
117127 };
@@ -124,8 +134,8 @@ void BranchPickerPopup::rebuild()
124134 addHeader (tr (" ── Local ──" ));
125135 for (const auto &b : locals) {
126136 QString display = b;
127- if (b == m_current) display = QStringLiteral (" ✓ " ) + b;
128- else display = QStringLiteral (" " ) + b;
137+ if (b == m_current) display = QStringLiteral (" ✓ " ) + b + QStringLiteral ( " › " ) ;
138+ else display = QStringLiteral (" " ) + b + QStringLiteral ( " › " ) ;
129139 auto *it = new QStandardItem (display);
130140 it->setData (0 , RoleKind);
131141 it->setData (b, RolePayload);
@@ -139,42 +149,20 @@ void BranchPickerPopup::rebuild()
139149 if (!remotes.isEmpty ()) {
140150 addHeader (tr (" ── Remote ──" ));
141151 for (const auto &b : remotes) {
142- auto *it = new QStandardItem (QStringLiteral (" " ) + b);
152+ auto *it = new QStandardItem (QStringLiteral (" " ) + b + QStringLiteral ( " › " ) );
143153 it->setData (1 , RoleKind);
144154 it->setData (b, RolePayload);
145155 m_model->appendRow (it);
146156 sawAny = true ;
147157 }
148158 }
149159
150- // "Create branch from query" — only when query has no exact local match.
151- const QString sanitized = sanitizeBranchName (q);
152- if (!sanitized.isEmpty () && !m_local.contains (sanitized)) {
153- addHeader (tr (" ── Create ──" ));
154- QString fromCurrent = m_current.isEmpty () ? m_default : m_current;
155- if (!fromCurrent.isEmpty ()) {
156- auto *it = new QStandardItem (tr (" + Create \" %1\" from %2" ).arg (sanitized, fromCurrent));
157- it->setData (2 , RoleKind);
158- it->setData (sanitized, RolePayload);
159- m_model->appendRow (it);
160- sawAny = true ;
161- }
162- if (!m_default.isEmpty () && m_default != fromCurrent) {
163- auto *it = new QStandardItem (tr (" + Create \" %1\" from %2 (default)" ).arg (sanitized, m_default));
164- it->setData (3 , RoleKind);
165- it->setData (sanitized, RolePayload);
166- m_model->appendRow (it);
167- sawAny = true ;
168- }
169- }
170-
171160 if (!sawAny) {
172161 auto *it = new QStandardItem (tr (" No matches" ));
173162 it->setEnabled (false );
174163 m_model->appendRow (it);
175164 }
176165
177- // Auto-select first enabled item.
178166 for (int r = 0 ; r < m_model->rowCount (); ++r) {
179167 if (m_model->item (r)->isEnabled ()) {
180168 m_list->setCurrentIndex (m_model->index (r, 0 ));
@@ -186,22 +174,95 @@ void BranchPickerPopup::rebuild()
186174void BranchPickerPopup::onActivated (const QModelIndex &index)
187175{
188176 if (!index.isValid ()) return ;
177+ auto *it = m_model->itemFromIndex (index);
178+ if (!it || !it->isEnabled ()) return ;
179+ showItemMenu (index);
180+ }
181+
182+ void BranchPickerPopup::showItemMenu (const QModelIndex &index)
183+ {
189184 auto *it = m_model->itemFromIndex (index);
190185 if (!it || !it->isEnabled ()) return ;
191186 const int kind = it->data (RoleKind).toInt ();
192187 const QString payload = it->data (RolePayload).toString ();
193- if (kind == 0 ) {
194- emit branchSelected (payload);
195- close ();
196- } else if (kind == 1 ) {
197- emit branchSelected (payload);
198- close ();
199- } else if (kind == 2 ) {
200- emit createBranchRequested (payload, m_current.isEmpty () ? m_default : m_current);
201- close ();
202- } else if (kind == 3 ) {
203- emit createBranchRequested (payload, m_default);
204- close ();
188+ const bool isRemote = (kind == 1 );
189+ const bool isCurrent = (!isRemote && payload == m_current);
190+
191+ QMenu menu (this );
192+
193+ if (!isCurrent) {
194+ QAction *aCheckout = menu.addAction (tr (" &Checkout" ));
195+ connect (aCheckout, &QAction::triggered, this , [this , payload]() {
196+ emit checkoutRequested (payload);
197+ close ();
198+ });
199+ }
200+
201+ QAction *aNewBranch = menu.addAction (tr (" &New Branch…" ));
202+ connect (aNewBranch, &QAction::triggered, this , [this , payload]() {
203+ showNewBranchDialog (payload);
204+ });
205+
206+ if (isRemote && !m_detachedHead) {
207+ QAction *aSetUpstream = menu.addAction (tr (" &Set as Upstream" ));
208+ connect (aSetUpstream, &QAction::triggered, this , [this , payload]() {
209+ emit setUpstreamRequested (payload);
210+ close ();
211+ });
212+ }
213+
214+ const QRect itemRect = m_list->visualRect (index);
215+ const QPoint pos = m_list->mapToGlobal (itemRect.topRight ());
216+ menu.exec (pos);
217+ }
218+
219+ void BranchPickerPopup::showNewBranchDialog (const QString &base)
220+ {
221+ QDialog dlg (this );
222+ dlg.setWindowTitle (tr (" New Branch from %1" ).arg (base));
223+ dlg.setMinimumWidth (320 );
224+
225+ auto *layout = new QVBoxLayout (&dlg);
226+
227+ auto *nameEdit = new QLineEdit (&dlg);
228+ nameEdit->setPlaceholderText (tr (" Branch name" ));
229+
230+ auto *previewLabel = new QLabel (&dlg);
231+ previewLabel->setStyleSheet (QStringLiteral (" color: gray; font-size: 11px;" ));
232+
233+ auto *upstreamCheck = new QCheckBox (tr (" Set upstream tracking" ), &dlg);
234+ upstreamCheck->setChecked (true );
235+
236+ auto *buttons = new QDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
237+ buttons->button (QDialogButtonBox::Ok)->setEnabled (false );
238+
239+ layout->addWidget (new QLabel (tr (" Create new branch from <b>%1</b>:" ).arg (base), &dlg));
240+ layout->addWidget (nameEdit);
241+ layout->addWidget (previewLabel);
242+ layout->addWidget (upstreamCheck);
243+ layout->addWidget (buttons);
244+
245+ connect (nameEdit, &QLineEdit::textChanged, &dlg, [&](const QString &text) {
246+ const QString sanitized = sanitizeBranchName (text);
247+ if (sanitized.isEmpty ()) {
248+ previewLabel->clear ();
249+ buttons->button (QDialogButtonBox::Ok)->setEnabled (false );
250+ } else {
251+ previewLabel->setText (tr (" → %1" ).arg (sanitized));
252+ buttons->button (QDialogButtonBox::Ok)->setEnabled (!m_local.contains (sanitized));
253+ }
254+ });
255+ connect (buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
256+ connect (buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
257+
258+ nameEdit->setFocus ();
259+
260+ if (dlg.exec () == QDialog::Accepted) {
261+ const QString name = sanitizeBranchName (nameEdit->text ());
262+ if (!name.isEmpty ()) {
263+ emit createBranchRequested (name, base, upstreamCheck->isChecked ());
264+ close ();
265+ }
205266 }
206267}
207268
0 commit comments