1 | #include "PrCommentsList.h" |
2 | |
3 | #include <GitServerCache.h> |
4 | #include <GitHubRestApi.h> |
5 | #include <GitLabRestApi.h> |
6 | #include <CircularPixmap.h> |
7 | #include <SourceCodeReview.h> |
8 | #include <AvatarHelper.h> |
9 | #include <CodeReviewComment.h> |
10 | #include <ButtonLink.hpp> |
11 | #include <Colors.h> |
12 | #include <previewpage.h> |
13 | #include <GitQlientSettings.h> |
14 | |
15 | #include <QNetworkAccessManager> |
16 | #include <QVBoxLayout> |
17 | #include <QLabel> |
18 | #include <QScrollArea> |
19 | #include <QDir> |
20 | #include <QFile> |
21 | #include <QStandardPaths> |
22 | #include <QNetworkReply> |
23 | #include <QTextEdit> |
24 | #include <QPropertyAnimation> |
25 | #include <QSequentialAnimationGroup> |
26 | #include <QPushButton> |
27 | #include <QIcon> |
28 | #include <QScrollBar> |
29 | //#include <QWebChannel> |
30 | //#include <QWebEngineView> |
31 | |
32 | using namespace GitServer; |
33 | |
34 | PrCommentsList::(const QSharedPointer<GitServerCache> &gitServerCache, QWidget *parent) |
35 | : QFrame(parent) |
36 | , mMutex(QMutex::Recursive) |
37 | , mGitServerCache(gitServerCache) |
38 | , mManager(new QNetworkAccessManager()) |
39 | { |
40 | setObjectName("IssuesViewFrame" ); |
41 | } |
42 | |
43 | PrCommentsList::() |
44 | { |
45 | delete mManager; |
46 | } |
47 | |
48 | void PrCommentsList::(PrCommentsList::Config config, int issueNumber) |
49 | { |
50 | QMutexLocker lock(&mMutex); |
51 | |
52 | connect(mGitServerCache.get(), &GitServerCache::issueUpdated, this, &PrCommentsList::processComments, |
53 | Qt::UniqueConnection); |
54 | connect(mGitServerCache.get(), &GitServerCache::prReviewsReceived, this, &PrCommentsList::onReviewsReceived, |
55 | Qt::UniqueConnection); |
56 | |
57 | mConfig = config; |
58 | mIssueNumber = issueNumber; |
59 | |
60 | auto issue = config == Config::Issues ? mGitServerCache->getIssue(mIssueNumber) |
61 | : mGitServerCache->getPullRequest(mIssueNumber); |
62 | |
63 | delete mIssuesFrame; |
64 | delete mScroll; |
65 | delete layout(); |
66 | |
67 | mCommentsFrame = nullptr; |
68 | |
69 | mIssuesLayout = new QVBoxLayout(); |
70 | mIssuesLayout->setContentsMargins(QMargins()); |
71 | mIssuesLayout->setSpacing(30); |
72 | |
73 | mInputTextEdit = new QTextEdit(); |
74 | mInputTextEdit->setPlaceholderText(tr("Add your comment..." )); |
75 | mInputTextEdit->setObjectName("AddReviewInput" ); |
76 | |
77 | const auto cancel = new QPushButton(tr("Cancel" )); |
78 | const auto add = new QPushButton(tr("Comment" )); |
79 | connect(cancel, &QPushButton::clicked, this, [this]() { |
80 | mInputTextEdit->clear(); |
81 | mInputFrame->setVisible(false); |
82 | }); |
83 | |
84 | connect(add, &QPushButton::clicked, this, [issue, this]() { |
85 | connect(mGitServerCache.get(), &GitServerCache::issueUpdated, this, &PrCommentsList::processComments, |
86 | Qt::UniqueConnection); |
87 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
88 | mGitServerCache->getApi()->addIssueComment(issue, mInputTextEdit->toMarkdown()); |
89 | #else |
90 | mGitServerCache->getApi()->addIssueComment(issue, mInputTextEdit->toPlainText()); |
91 | #endif |
92 | mInputTextEdit->clear(); |
93 | mInputFrame->setVisible(false); |
94 | }); |
95 | |
96 | const auto btnsLayout = new QHBoxLayout(); |
97 | btnsLayout->setContentsMargins(QMargins()); |
98 | btnsLayout->setSpacing(0); |
99 | btnsLayout->addWidget(cancel); |
100 | btnsLayout->addStretch(); |
101 | btnsLayout->addWidget(add); |
102 | |
103 | const auto inputLayout = new QVBoxLayout(); |
104 | inputLayout->setContentsMargins(20, 20, 20, 10); |
105 | inputLayout->setSpacing(10); |
106 | inputLayout->addWidget(mInputTextEdit); |
107 | inputLayout->addLayout(btnsLayout); |
108 | |
109 | mInputFrame = new QFrame(); |
110 | mInputFrame->setFixedHeight(200); |
111 | mInputFrame->setLayout(inputLayout); |
112 | mInputFrame->setVisible(false); |
113 | |
114 | const auto descriptionFrame = new QFrame(); |
115 | descriptionFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); |
116 | |
117 | const auto bodyLayout = new QVBoxLayout(); |
118 | bodyLayout->setContentsMargins(20, 20, 20, 20); |
119 | bodyLayout->setAlignment(Qt::AlignTop); |
120 | bodyLayout->setSpacing(30); |
121 | bodyLayout->addWidget(descriptionFrame); |
122 | bodyLayout->addLayout(mIssuesLayout); |
123 | bodyLayout->addStretch(); |
124 | |
125 | mIssuesFrame = new QFrame(); |
126 | mIssuesFrame->setObjectName("IssuesViewFrame" ); |
127 | mIssuesFrame->setLayout(bodyLayout); |
128 | |
129 | mScroll = new QScrollArea(); |
130 | mScroll->setWidgetResizable(true); |
131 | mScroll->setWidget(mIssuesFrame); |
132 | |
133 | const auto aLayout = new QVBoxLayout(this); |
134 | aLayout->setContentsMargins(QMargins()); |
135 | aLayout->setSpacing(0); |
136 | aLayout->addWidget(mScroll); |
137 | aLayout->addWidget(mInputFrame); |
138 | |
139 | const auto creationLayout = new QHBoxLayout(); |
140 | creationLayout->setContentsMargins(QMargins()); |
141 | creationLayout->setSpacing(0); |
142 | |
143 | const auto days = issue.creation.daysTo(QDateTime::currentDateTime()); |
144 | const auto whenText = days <= 30 |
145 | ? days != 0 ? tr(" %1 days ago" ).arg(days) : tr(" today" ) |
146 | : tr(" on %1" ).arg(issue.creation.date().toString(QLocale().dateFormat(QLocale::ShortFormat))); |
147 | |
148 | const auto creationLabel = new QLabel(); |
149 | creationLabel->setText(tr("<i>Created by <b>%1</b>%2</i> - " ).arg(issue.creator.name, whenText)); |
150 | creationLabel->setToolTip(issue.creation.toString(QLocale().dateTimeFormat(QLocale::ShortFormat))); |
151 | |
152 | creationLayout->addWidget(creationLabel); |
153 | |
154 | if (!issue.assignees.isEmpty()) |
155 | { |
156 | const auto assignedLayout = new QHBoxLayout(); |
157 | assignedLayout->setContentsMargins(QMargins()); |
158 | assignedLayout->setSpacing(0); |
159 | |
160 | assignedLayout->addWidget(new QLabel(tr("<i>Assigned to </i>" ))); |
161 | |
162 | auto count = 0; |
163 | const auto totalAssignees = issue.assignees.count(); |
164 | for (auto &assignee : issue.assignees) |
165 | { |
166 | const auto assigneLabel = new QLabel(QString("<i><b>%1</b></i>" ).arg(assignee.name)); |
167 | assigneLabel->setObjectName("CreatorLink" ); |
168 | assignedLayout->addWidget(assigneLabel); |
169 | |
170 | if (count++ < totalAssignees - 1) |
171 | assignedLayout->addWidget(new QLabel(", " )); |
172 | } |
173 | |
174 | creationLayout->addLayout(assignedLayout); |
175 | } |
176 | else |
177 | creationLayout->addWidget(new QLabel(tr("<i>Unassigned</i>" ))); |
178 | |
179 | creationLayout->addStretch(); |
180 | |
181 | for (auto &label : issue.labels) |
182 | { |
183 | auto labelWidget = new QLabel(); |
184 | labelWidget->setStyleSheet(QString("QLabel {" |
185 | "background-color: #%1;" |
186 | "border-radius: 7px;" |
187 | "min-height: 15px;" |
188 | "max-height: 15px;" |
189 | "min-width: 15px;" |
190 | "max-width: 15px;}" ) |
191 | .arg(label.colorHex)); |
192 | labelWidget->setToolTip(label.name); |
193 | creationLayout->addWidget(labelWidget); |
194 | creationLayout->addItem(new QSpacerItem(10, 1, QSizePolicy::Fixed, QSizePolicy::Fixed)); |
195 | } |
196 | |
197 | if (!issue.milestone.title.isEmpty()) |
198 | { |
199 | const auto milestone = new QLabel(QString("%1" ).arg(issue.milestone.title)); |
200 | milestone->setObjectName("IssueLabel" ); |
201 | creationLayout->addWidget(milestone); |
202 | } |
203 | |
204 | const auto layout = new QVBoxLayout(descriptionFrame); |
205 | layout->setContentsMargins(QMargins()); |
206 | layout->setSpacing(10); |
207 | layout->addLayout(creationLayout); |
208 | |
209 | const auto bodyDescFrame = new QFrame(); |
210 | bodyDescFrame->setObjectName("IssueDescription" ); |
211 | |
212 | const auto bodyDescLayout = new QVBoxLayout(bodyDescFrame); |
213 | bodyDescLayout->setContentsMargins(10, 10, 10, 10); |
214 | |
215 | GitQlientSettings settings("" ); |
216 | const auto colorSchema = settings.globalValue("colorSchema" , "dark" ).toString(); |
217 | const auto style = colorSchema == "dark" ? QString::fromUtf8("dark" ) : QString::fromUtf8("bright" ); |
218 | |
219 | // QPointer<QWebEngineView> body = new QWebEngineView(); |
220 | |
221 | // PreviewPage *page = new PreviewPage(this); |
222 | // body->setPage(page); |
223 | |
224 | // QWebChannel *channel = new QWebChannel(this); |
225 | // channel->registerObject(QStringLiteral("content"), &m_content); |
226 | // page->setWebChannel(channel); |
227 | |
228 | // body->setUrl(QUrl(QString("qrc:/resources/index_%1.html").arg(style))); |
229 | // body->setFixedHeight(20); |
230 | |
231 | // connect(page, &PreviewPage::contentsSizeChanged, this, [body](const QSizeF size) { |
232 | // if (body) |
233 | // body->setFixedHeight(size.height()); |
234 | // }); |
235 | |
236 | m_content.setText(QString::fromUtf8(issue.body)); |
237 | |
238 | // bodyDescLayout->addWidget(body); |
239 | layout->addWidget(bodyDescFrame); |
240 | |
241 | descriptionFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); |
242 | |
243 | const auto separator = new QFrame(); |
244 | separator->setObjectName("orangeHSeparator" ); |
245 | |
246 | mIssuesLayout->addWidget(separator); |
247 | |
248 | if (mConfig == Config::Issues) |
249 | mGitServerCache->getApi()->requestComments(issue.number); |
250 | else |
251 | { |
252 | mGitServerCache->getApi()->requestComments(mIssueNumber); |
253 | mGitServerCache->getApi()->requestReviews(mIssueNumber); |
254 | } |
255 | } |
256 | |
257 | void PrCommentsList::(int frameId) |
258 | { |
259 | const auto daFrame = mComments.value(frameId); |
260 | |
261 | mScroll->ensureWidgetVisible(daFrame); |
262 | |
263 | const auto animationGoup = new QSequentialAnimationGroup(); |
264 | auto animation = new QPropertyAnimation(daFrame, "color" ); |
265 | animation->setDuration(500); |
266 | animation->setStartValue(highlightCommentStart); |
267 | animation->setEndValue(highlightCommentEnd); |
268 | animationGoup->addAnimation(animation); |
269 | |
270 | animation = new QPropertyAnimation(daFrame, "color" ); |
271 | animation->setDuration(500); |
272 | animation->setStartValue(highlightCommentEnd); |
273 | animation->setEndValue(highlightCommentStart); |
274 | animationGoup->addAnimation(animation); |
275 | |
276 | animationGoup->start(); |
277 | } |
278 | |
279 | void PrCommentsList::() |
280 | { |
281 | mInputFrame->setVisible(true); |
282 | mInputTextEdit->setFocus(); |
283 | } |
284 | |
285 | void PrCommentsList::(const Issue &issue) |
286 | { |
287 | QMutexLocker lock(&mMutex); |
288 | |
289 | disconnect(mGitServerCache.get(), &GitServerCache::issueUpdated, this, &PrCommentsList::processComments); |
290 | |
291 | if (mIssueNumber != issue.number) |
292 | return; |
293 | |
294 | delete mCommentsFrame; |
295 | |
296 | mCommentsFrame = new QFrame(); |
297 | |
298 | mIssuesLayout->addWidget(mCommentsFrame); |
299 | |
300 | const auto = new QVBoxLayout(mCommentsFrame); |
301 | commentsLayout->setContentsMargins(QMargins()); |
302 | commentsLayout->setSpacing(30); |
303 | |
304 | for (auto & : issue.comments) |
305 | { |
306 | const auto layout = createBubbleForComment(comment); |
307 | commentsLayout->addLayout(layout); |
308 | } |
309 | |
310 | commentsLayout->addStretch(); |
311 | } |
312 | |
313 | QLabel *PrCommentsList::(const QDateTime &dt, const QString &prefix) |
314 | { |
315 | const auto days = dt.daysTo(QDateTime::currentDateTime()); |
316 | const auto whenText = days <= 30 ? days != 0 ? tr(" %1 days ago" ).arg(days) : tr(" today" ) |
317 | : tr(" on %1" ).arg(dt.date().toString(QLocale().dateFormat(QLocale::ShortFormat))); |
318 | |
319 | const auto label = prefix.isEmpty() ? new QLabel(whenText) : new QLabel(prefix + whenText); |
320 | label->setToolTip(dt.toString(QLocale().dateFormat(QLocale::ShortFormat))); |
321 | |
322 | return label; |
323 | } |
324 | |
325 | void PrCommentsList::() |
326 | { |
327 | QMutexLocker lock(&mMutex); |
328 | |
329 | auto pr = mGitServerCache->getPullRequest(mIssueNumber); |
330 | |
331 | mFrameLinks.clear(); |
332 | mComments.clear(); |
333 | |
334 | const auto originalPr = pr; |
335 | |
336 | QMultiMap<QDateTime, QLayout *> bubblesMap; |
337 | |
338 | for (const auto & : qAsConst(pr.comments)) |
339 | { |
340 | const auto layout = createBubbleForComment(comment); |
341 | bubblesMap.insert(comment.creation, layout); |
342 | } |
343 | |
344 | for (const auto &review : qAsConst(pr.reviews)) |
345 | { |
346 | const auto layouts = new QVBoxLayout(); |
347 | |
348 | const auto reviewLayout = createBubbleForReview(review); |
349 | |
350 | if (reviewLayout) |
351 | layouts->addLayout(reviewLayout); |
352 | |
353 | auto codeReviewsLayouts = createBubbleForCodeReview(review.id, pr.reviewComment); |
354 | |
355 | for (auto layout : codeReviewsLayouts) |
356 | layouts->addLayout(layout); |
357 | |
358 | bubblesMap.insert(review.creation, layouts); |
359 | } |
360 | |
361 | delete mCommentsFrame; |
362 | mCommentsFrame = new QFrame(); |
363 | |
364 | const auto reviewsLayout = new QVBoxLayout(mCommentsFrame); |
365 | reviewsLayout->setContentsMargins(QMargins()); |
366 | reviewsLayout->setSpacing(30); |
367 | |
368 | for (auto layout : bubblesMap) |
369 | { |
370 | if (layout) |
371 | reviewsLayout->addLayout(layout); |
372 | } |
373 | |
374 | reviewsLayout->addStretch(); |
375 | reviewsLayout->addItem(new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding)); |
376 | |
377 | emit frameReviewLink(originalPr, mFrameLinks); |
378 | |
379 | mIssuesLayout->addWidget(mCommentsFrame); |
380 | } |
381 | |
382 | QLayout *PrCommentsList::(const Comment &) |
383 | { |
384 | const auto creationLayout = new QHBoxLayout(); |
385 | creationLayout->setContentsMargins(QMargins()); |
386 | creationLayout->setSpacing(0); |
387 | creationLayout->addWidget(new QLabel(tr("Comment by " ))); |
388 | |
389 | const auto creator = new QLabel(QString("<b>%1</b>" ).arg(comment.creator.name)); |
390 | creator->setObjectName("CreatorLink" ); |
391 | |
392 | creationLayout->addWidget(creator); |
393 | creationLayout->addWidget(createHeadline(comment.creation)); |
394 | creationLayout->addStretch(); |
395 | creationLayout->addWidget(new QLabel(comment.association)); |
396 | |
397 | GitQlientSettings settings("" ); |
398 | const auto colorSchema = settings.globalValue("colorSchema" , "dark" ).toString(); |
399 | const auto style = colorSchema == "dark" ? QString::fromUtf8("dark" ) : QString::fromUtf8("bright" ); |
400 | |
401 | const auto doc = new Document(this); |
402 | m_commentContents.append(doc); |
403 | |
404 | // const auto channel = new QWebChannel(this); |
405 | // channel->registerObject(QStringLiteral("content"), doc); |
406 | |
407 | // const auto page = new PreviewPage(this); |
408 | // page->setWebChannel(channel); |
409 | |
410 | // QPointer<QWebEngineView> body = new QWebEngineView(); |
411 | |
412 | // connect(page, &PreviewPage::contentsSizeChanged, this, [body](const QSizeF size) { |
413 | // if (body) |
414 | // body->setFixedHeight(size.height()); |
415 | // }); |
416 | |
417 | // body->setPage(page); |
418 | // body->setUrl(QUrl(QString("qrc:/resources/index_%1.html").arg(style))); |
419 | // body->setFixedHeight(20); |
420 | |
421 | doc->setText(comment.body.trimmed()); |
422 | |
423 | const auto frame = new QFrame(); |
424 | frame->setObjectName("IssueIntro" ); |
425 | |
426 | const auto innerLayout = new QVBoxLayout(frame); |
427 | innerLayout->setContentsMargins(10, 10, 10, 10); |
428 | innerLayout->setSpacing(5); |
429 | innerLayout->addLayout(creationLayout); |
430 | innerLayout->addSpacing(20); |
431 | // innerLayout->addWidget(body); |
432 | innerLayout->addStretch(); |
433 | |
434 | const auto layout = new QHBoxLayout(); |
435 | layout->setContentsMargins(QMargins()); |
436 | layout->setSpacing(30); |
437 | layout->addSpacing(30); |
438 | layout->addWidget(createAvatar(comment.creator.name, comment.creator.avatar)); |
439 | layout->addWidget(frame); |
440 | |
441 | return layout; |
442 | } |
443 | |
444 | QLayout *PrCommentsList::(const Review &review) |
445 | { |
446 | const auto frame = new QFrame(); |
447 | QString ; |
448 | QLabel *label = nullptr; |
449 | |
450 | if (review.state == QString::fromUtf8("CHANGES_REQUESTED" )) |
451 | { |
452 | frame->setObjectName("IssueIntroChangesRequested" ); |
453 | |
454 | header = tr("<b>%1</b> (%2) requested changes to the PR " ).arg(review.creator.name, review.association.toLower()); |
455 | } |
456 | else if (review.state == QString::fromUtf8("APPROVED" )) |
457 | { |
458 | frame->setObjectName("IssueIntroApproved" ); |
459 | |
460 | header = tr("<b>%1</b> (%2) approved the PR " ).arg(review.creator.name, review.association.toLower()); |
461 | } |
462 | else if (review.state == QString::fromUtf8("COMMENTED" )) |
463 | { |
464 | if (review.body.isEmpty()) |
465 | { |
466 | delete frame; |
467 | return nullptr; |
468 | } |
469 | |
470 | frame->setObjectName("IssueIntroCommented" ); |
471 | |
472 | header = tr("<b>%1</b> (%2) reviewed the PR and added some comments " ) |
473 | .arg(review.creator.name, review.association.toLower()); |
474 | |
475 | label = createHeadline(review.creation, header); |
476 | label->setText(label->text().append(" <p>%1</p>" ).arg(review.body)); |
477 | } |
478 | |
479 | if (!label) |
480 | label = createHeadline(review.creation, header); |
481 | |
482 | const auto creationLayout = new QHBoxLayout(); |
483 | creationLayout->setContentsMargins(QMargins()); |
484 | creationLayout->setSpacing(0); |
485 | creationLayout->addWidget(label); |
486 | creationLayout->addStretch(); |
487 | |
488 | const auto innerLayout = new QVBoxLayout(frame); |
489 | innerLayout->setContentsMargins(10, 10, 10, 10); |
490 | innerLayout->setSpacing(20); |
491 | innerLayout->addLayout(creationLayout); |
492 | |
493 | const auto layout = new QHBoxLayout(); |
494 | layout->setContentsMargins(QMargins()); |
495 | layout->setSpacing(30); |
496 | layout->addSpacing(30); |
497 | layout->addWidget(createAvatar(review.creator.name, review.creator.avatar)); |
498 | layout->addWidget(frame); |
499 | |
500 | return layout; |
501 | } |
502 | |
503 | QVector<QLayout *> PrCommentsList::(int reviewId, QVector<CodeReview> &) |
504 | { |
505 | QMap<int, QVector<CodeReview>> reviews; |
506 | QVector<int> codeReviewIds; |
507 | QVector<QLayout *> listOfCodeReviews; |
508 | |
509 | auto iter = comments.begin(); |
510 | |
511 | while (iter != comments.end()) |
512 | { |
513 | if (iter->reviewId == reviewId) |
514 | { |
515 | codeReviewIds.append(iter->id); |
516 | reviews[iter->id].append(*iter); |
517 | comments.erase(iter); |
518 | } |
519 | else if (codeReviewIds.contains(iter->replyToId)) |
520 | { |
521 | reviews[iter->replyToId].append(*iter); |
522 | comments.erase(iter); |
523 | } |
524 | else |
525 | ++iter; |
526 | } |
527 | |
528 | if (!reviews.isEmpty()) |
529 | { |
530 | for (auto &codeReviews : reviews) |
531 | { |
532 | std::sort(codeReviews.begin(), codeReviews.end(), |
533 | [](const CodeReview &r1, const CodeReview &r2) { return r1.creation < r2.creation; }); |
534 | |
535 | const auto review = codeReviews.constFirst(); |
536 | |
537 | const auto creationLayout = new QHBoxLayout(); |
538 | creationLayout->setContentsMargins(QMargins()); |
539 | creationLayout->setSpacing(0); |
540 | |
541 | const auto |
542 | = QString("<b>%1</b> (%2) started a review " ).arg(review.creator.name, review.association.toLower()); |
543 | |
544 | creationLayout->addWidget(createHeadline(review.creation, header)); |
545 | creationLayout->addStretch(); |
546 | |
547 | const auto codeReviewFrame = new QFrame(); |
548 | const auto frame = new HighlightningFrame(); |
549 | |
550 | const auto innerLayout = new QVBoxLayout(frame); |
551 | innerLayout->setContentsMargins(10, 10, 10, 10); |
552 | innerLayout->setSpacing(20); |
553 | innerLayout->addLayout(creationLayout); |
554 | |
555 | const auto codeReviewLayout = new QVBoxLayout(codeReviewFrame); |
556 | |
557 | innerLayout->addWidget(codeReviewFrame); |
558 | |
559 | const auto code = new SourceCodeReview(review.diff.file, review.diff.diff, review.diff.line); |
560 | |
561 | codeReviewLayout->addWidget(code); |
562 | codeReviewLayout->addSpacing(20); |
563 | |
564 | const auto = new QVBoxLayout(); |
565 | commentsLayout->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); |
566 | commentsLayout->setContentsMargins(QMargins()); |
567 | commentsLayout->setSpacing(20); |
568 | |
569 | for (auto & : codeReviews) |
570 | commentsLayout->addWidget(new CodeReviewComment(comment)); |
571 | |
572 | codeReviewLayout->addLayout(commentsLayout); |
573 | |
574 | if (review.outdated) |
575 | { |
576 | const auto outdatedLabel = new ButtonLink(tr("Outdated" )); |
577 | outdatedLabel->setObjectName("OutdatedLabel" ); |
578 | creationLayout->addWidget(outdatedLabel); |
579 | |
580 | codeReviewFrame->setVisible(false); |
581 | |
582 | connect(outdatedLabel, &ButtonLink::clicked, this, |
583 | [codeReviewFrame]() { codeReviewFrame->setVisible(!codeReviewFrame->isVisible()); }); |
584 | } |
585 | else |
586 | { |
587 | const auto = new QPushButton(); |
588 | addComment->setCheckable(true); |
589 | addComment->setChecked(false); |
590 | addComment->setIcon(QIcon(":/icons/add_comment" )); |
591 | addComment->setToolTip(tr("Add new comment" )); |
592 | |
593 | creationLayout->addWidget(addComment); |
594 | |
595 | const auto inputTextEdit = new QTextEdit(); |
596 | inputTextEdit->setPlaceholderText(tr("Add your comment..." )); |
597 | inputTextEdit->setObjectName("AddReviewInput" ); |
598 | |
599 | const auto cancel = new QPushButton(tr("Cancel" )); |
600 | const auto add = new QPushButton(tr("Comment" )); |
601 | const auto btnsLayout = new QHBoxLayout(); |
602 | btnsLayout->setContentsMargins(QMargins()); |
603 | btnsLayout->setSpacing(0); |
604 | btnsLayout->addWidget(cancel); |
605 | btnsLayout->addStretch(); |
606 | btnsLayout->addWidget(add); |
607 | |
608 | const auto inputFrame = new QFrame(); |
609 | inputFrame->setVisible(false); |
610 | const auto inputLayout = new QVBoxLayout(inputFrame); |
611 | inputLayout->setContentsMargins(QMargins()); |
612 | inputLayout->addSpacing(20); |
613 | inputLayout->setSpacing(10); |
614 | inputLayout->addWidget(inputTextEdit); |
615 | inputLayout->addLayout(btnsLayout); |
616 | |
617 | codeReviewLayout->addWidget(inputFrame); |
618 | |
619 | connect(add, &QPushButton::clicked, this, [this, inputTextEdit, = review.id]() { |
620 | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) |
621 | addReplyToCodeReview(commentId, inputTextEdit->toMarkdown().trimmed()); |
622 | #else |
623 | addReplyToCodeReview(commentId, inputTextEdit->toPlainText().trimmed()); |
624 | #endif |
625 | }); |
626 | connect(cancel, &QPushButton::clicked, this, [inputTextEdit, addComment]() { |
627 | inputTextEdit->clear(); |
628 | addComment->toggle(); |
629 | }); |
630 | connect(addComment, &QPushButton::toggled, this, [inputFrame, inputTextEdit](bool checked) { |
631 | inputFrame->setVisible(checked); |
632 | inputTextEdit->setFocus(); |
633 | }); |
634 | } |
635 | |
636 | mFrameLinks.insert(mCommentId, review.id); |
637 | mComments.insert(mCommentId, frame); |
638 | |
639 | ++mCommentId; |
640 | |
641 | const auto layout = new QHBoxLayout(); |
642 | layout->setContentsMargins(QMargins()); |
643 | layout->setSpacing(30); |
644 | layout->addSpacing(30); |
645 | layout->addWidget(createAvatar(review.creator.name, review.creator.avatar)); |
646 | layout->addWidget(frame); |
647 | |
648 | listOfCodeReviews.append(layout); |
649 | } |
650 | } |
651 | |
652 | return listOfCodeReviews; |
653 | } |
654 | |
655 | void PrCommentsList::(int , const QString &message) |
656 | { |
657 | mGitServerCache->getApi()->replyCodeReview(mIssueNumber, commentId, message); |
658 | } |
659 | |
660 | HighlightningFrame::HighlightningFrame(QWidget *parent) |
661 | : QFrame(parent) |
662 | { |
663 | setObjectName("IssueIntro" ); |
664 | } |
665 | |
666 | void HighlightningFrame::setColor(QColor color) |
667 | { |
668 | setStyleSheet(QString("#IssueIntro { background-color: rgb(%1, %2, %3); }" ) |
669 | .arg(color.red()) |
670 | .arg(color.green()) |
671 | .arg(color.blue())); |
672 | } |
673 | |
674 | QColor HighlightningFrame::color() |
675 | { |
676 | return Qt::black; // getter is not really needed for now |
677 | } |
678 | |