ScreenshotEditor.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. #include "ScreenshotEditor.h"
  2. #include "UiHelpers.h"
  3. #include <QApplication>
  4. #include <QGuiApplication>
  5. #include <QHBoxLayout>
  6. #include <QKeyEvent>
  7. #include <QKeySequence>
  8. #include <QMouseEvent>
  9. #include <QPainter>
  10. #include <QPainterPath>
  11. #include <QPaintEvent>
  12. #include <QPushButton>
  13. #include <QScreen>
  14. #include <QToolButton>
  15. namespace
  16. {
  17. QPixmap grabDesktop()
  18. {
  19. const QList<QScreen *> screens = QGuiApplication::screens();
  20. if (screens.isEmpty())
  21. return {};
  22. QRect virt;
  23. for (QScreen *s : screens)
  24. virt = virt.united(s->geometry());
  25. if (!virt.isValid())
  26. return {};
  27. qreal maxDpr = 1.0;
  28. for (QScreen *s : screens)
  29. maxDpr = qMax(maxDpr, s->devicePixelRatio());
  30. QPixmap canvas(qRound(virt.width() * maxDpr), qRound(virt.height() * maxDpr));
  31. canvas.fill(Qt::black);
  32. canvas.setDevicePixelRatio(maxDpr);
  33. QPainter p(&canvas);
  34. for (QScreen *s : screens)
  35. {
  36. if (!s)
  37. continue;
  38. const QPixmap g = s->grabWindow(0);
  39. if (g.isNull())
  40. continue;
  41. const QPoint topLeft = s->geometry().topLeft() - virt.topLeft();
  42. p.drawPixmap(QRect(topLeft, s->geometry().size()), g);
  43. }
  44. return canvas;
  45. }
  46. QRect virtGeometry()
  47. {
  48. QRect virt;
  49. for (QScreen *s : QGuiApplication::screens())
  50. virt = virt.united(s->geometry());
  51. return virt;
  52. }
  53. }
  54. QPixmap ScreenshotEditor::capture(QWidget *host)
  55. {
  56. Q_UNUSED(host);
  57. const QPixmap shot = grabDesktop();
  58. if (shot.isNull())
  59. return {};
  60. ScreenshotEditor dlg(shot, nullptr);
  61. dlg.setWindowModality(Qt::ApplicationModal);
  62. if (dlg.exec() != QDialog::Accepted)
  63. return {};
  64. return dlg.resultPixmap();
  65. }
  66. ScreenshotEditor::ScreenshotEditor(const QPixmap &shot, QWidget *parent)
  67. : QDialog(parent, Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint | Qt::Tool)
  68. , m_shot(shot)
  69. {
  70. setWindowModality(Qt::ApplicationModal);
  71. setMouseTracking(true);
  72. setCursor(Qt::CrossCursor);
  73. setFocusPolicy(Qt::StrongFocus);
  74. const QRect virt = virtGeometry();
  75. if (virt.isValid())
  76. setGeometry(virt);
  77. else
  78. showFullScreen();
  79. buildToolbar();
  80. }
  81. void ScreenshotEditor::buildToolbar()
  82. {
  83. m_toolbar = new QWidget(this);
  84. m_toolbar->setObjectName(QStringLiteral("ShotToolbar"));
  85. m_toolbar->setStyleSheet(QStringLiteral(
  86. "QWidget#ShotToolbar { background:#FFFFFF; border:1px solid #E5E5E5; border-radius:6px; }"));
  87. auto *lay = new QHBoxLayout(m_toolbar);
  88. lay->setContentsMargins(8, 6, 8, 6);
  89. lay->setSpacing(6);
  90. const QColor colors[] = {
  91. QColor(QStringLiteral("#FA5151")),
  92. QColor(QStringLiteral("#FFC300")),
  93. QColor(QStringLiteral("#07C160")),
  94. QColor(QStringLiteral("#10AEFF")),
  95. QColor(QStringLiteral("#FFFFFF")),
  96. QColor(QStringLiteral("#191919")),
  97. };
  98. for (const QColor &c : colors)
  99. {
  100. auto *btn = new QToolButton(m_toolbar);
  101. btn->setFixedSize(22, 22);
  102. btn->setCursor(Qt::PointingHandCursor);
  103. btn->setAutoRaise(true);
  104. const QString border = (c == QColor(QStringLiteral("#FFFFFF")))
  105. ? QStringLiteral("1px solid #CCCCCC")
  106. : QStringLiteral("1px solid transparent");
  107. btn->setStyleSheet(QStringLiteral(
  108. "QToolButton { background:%1; border:%2; border-radius:11px; }"
  109. "QToolButton:hover { border:2px solid #07C160; }")
  110. .arg(c.name(), border));
  111. connect(btn, &QToolButton::clicked, this, [this, c]() { m_color = c; });
  112. lay->addWidget(btn);
  113. }
  114. auto *undo = UiHelpers::ghostButton(QStringLiteral("撤销"), m_toolbar);
  115. undo->setFixedSize(56, 28);
  116. connect(undo, &QPushButton::clicked, this, [this]() {
  117. if (!m_strokes.isEmpty())
  118. {
  119. m_strokes.removeLast();
  120. update();
  121. }
  122. });
  123. auto *cancel = UiHelpers::ghostButton(QStringLiteral("取消"), m_toolbar);
  124. cancel->setFixedSize(56, 28);
  125. connect(cancel, &QPushButton::clicked, this, &QDialog::reject);
  126. auto *ok = UiHelpers::primaryButton(QStringLiteral("完成"), m_toolbar);
  127. ok->setFixedSize(56, 28);
  128. connect(ok, &QPushButton::clicked, this, [this]() { finish(); });
  129. lay->addWidget(undo);
  130. lay->addWidget(cancel);
  131. lay->addWidget(ok);
  132. m_toolbar->adjustSize();
  133. m_toolbar->hide();
  134. }
  135. void ScreenshotEditor::updateToolbar()
  136. {
  137. if (!m_toolbar)
  138. return;
  139. if (!m_hasSel || m_sel.width() < 8 || m_sel.height() < 8)
  140. {
  141. m_toolbar->hide();
  142. return;
  143. }
  144. m_toolbar->adjustSize();
  145. const int tw = m_toolbar->width();
  146. const int th = m_toolbar->height();
  147. int x = m_sel.right() - tw;
  148. int y = m_sel.bottom() + 8;
  149. if (x < 8)
  150. x = 8;
  151. if (x + tw > width() - 8)
  152. x = qMax(8, width() - 8 - tw);
  153. if (y + th > height() - 8)
  154. y = m_sel.top() - 8 - th;
  155. if (y < 8)
  156. y = 8;
  157. m_toolbar->move(x, y);
  158. m_toolbar->show();
  159. m_toolbar->raise();
  160. }
  161. QPoint ScreenshotEditor::toLocal(const QPoint &pos) const
  162. {
  163. return pos - m_sel.topLeft();
  164. }
  165. bool ScreenshotEditor::inSelection(const QPoint &pos) const
  166. {
  167. return m_hasSel && m_sel.adjusted(0, 0, -1, -1).contains(pos);
  168. }
  169. void ScreenshotEditor::finish()
  170. {
  171. if (!m_hasSel || m_sel.width() < 8 || m_sel.height() < 8)
  172. return;
  173. accept();
  174. }
  175. QPixmap ScreenshotEditor::resultPixmap() const
  176. {
  177. if (!m_hasSel || m_sel.width() < 2 || m_sel.height() < 2)
  178. return {};
  179. const qreal dpr = m_shot.devicePixelRatio() > 0 ? m_shot.devicePixelRatio() : 1.0;
  180. const QRect src(qRound(m_sel.x() * dpr), qRound(m_sel.y() * dpr),
  181. qRound(m_sel.width() * dpr), qRound(m_sel.height() * dpr));
  182. QPixmap out = m_shot.copy(src.intersected(m_shot.rect()));
  183. if (out.isNull())
  184. return {};
  185. out.setDevicePixelRatio(dpr);
  186. if (!m_strokes.isEmpty())
  187. {
  188. QPainter p(&out);
  189. p.setRenderHint(QPainter::Antialiasing, true);
  190. p.scale(dpr, dpr);
  191. for (const Stroke &s : m_strokes)
  192. {
  193. QPen pen(s.color, s.width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
  194. p.setPen(pen);
  195. p.setBrush(Qt::NoBrush);
  196. p.drawPath(s.path);
  197. }
  198. }
  199. return out;
  200. }
  201. void ScreenshotEditor::paintEvent(QPaintEvent *)
  202. {
  203. QPainter p(this);
  204. p.drawPixmap(rect(), m_shot);
  205. const bool liveSel = (m_selecting || m_hasSel) && m_sel.width() > 1 && m_sel.height() > 1;
  206. QPainterPath dim;
  207. dim.addRect(rect());
  208. if (liveSel)
  209. {
  210. QPainterPath hole;
  211. hole.addRect(m_sel);
  212. dim = dim.subtracted(hole);
  213. p.fillPath(dim, QColor(0, 0, 0, 120));
  214. p.setPen(QPen(QColor(QStringLiteral("#07C160")), 1));
  215. p.setBrush(Qt::NoBrush);
  216. p.drawRect(m_sel.adjusted(0, 0, -1, -1));
  217. if (m_hasSel && !m_selecting)
  218. {
  219. p.save();
  220. p.setRenderHint(QPainter::Antialiasing, true);
  221. p.setClipRect(m_sel);
  222. p.translate(m_sel.topLeft());
  223. auto drawStroke = [&p](const Stroke &s) {
  224. QPen pen(s.color, s.width, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
  225. p.setPen(pen);
  226. p.setBrush(Qt::NoBrush);
  227. p.drawPath(s.path);
  228. };
  229. for (const Stroke &s : m_strokes)
  230. drawStroke(s);
  231. if (m_drawing)
  232. drawStroke(m_cur);
  233. p.restore();
  234. }
  235. const QString t = QStringLiteral("%1 × %2").arg(m_sel.width()).arg(m_sel.height());
  236. const QFontMetrics fm(font());
  237. const int tw = fm.horizontalAdvance(t) + 10;
  238. const int th = fm.height() + 6;
  239. QRect lab(m_sel.left(), m_sel.top() - th - 4, tw, th);
  240. if (lab.top() < 0)
  241. lab.moveTop(m_sel.top() + 4);
  242. p.fillRect(lab, QColor(0, 0, 0, 160));
  243. p.setPen(Qt::white);
  244. p.drawText(lab, Qt::AlignCenter, t);
  245. }
  246. else
  247. {
  248. p.fillPath(dim, QColor(0, 0, 0, 80));
  249. if (!m_selecting)
  250. {
  251. p.setPen(QColor(255, 255, 255, 220));
  252. p.drawText(rect(), Qt::AlignCenter, QStringLiteral("拖动鼠标选择截图区域,Esc 取消"));
  253. }
  254. }
  255. }
  256. void ScreenshotEditor::mousePressEvent(QMouseEvent *event)
  257. {
  258. if (event->button() != Qt::LeftButton)
  259. return;
  260. const QPoint pos = event->pos();
  261. if (m_hasSel && inSelection(pos))
  262. {
  263. m_drawing = true;
  264. m_cur.color = m_color;
  265. m_cur.width = 3.2;
  266. m_cur.path = QPainterPath();
  267. m_cur.path.moveTo(toLocal(pos));
  268. update();
  269. return;
  270. }
  271. if (m_hasSel && m_toolbar && m_toolbar->isVisible() && m_toolbar->geometry().contains(pos))
  272. return;
  273. m_hasSel = false;
  274. m_strokes.clear();
  275. m_selecting = true;
  276. m_dragOrigin = pos;
  277. m_sel = QRect(pos, QSize(1, 1));
  278. if (m_toolbar)
  279. m_toolbar->hide();
  280. setCursor(Qt::CrossCursor);
  281. grabMouse();
  282. update();
  283. }
  284. void ScreenshotEditor::mouseMoveEvent(QMouseEvent *event)
  285. {
  286. const QPoint pos = event->pos();
  287. if (m_selecting)
  288. {
  289. m_sel = QRect(m_dragOrigin, pos).normalized();
  290. repaint();
  291. return;
  292. }
  293. if (m_drawing)
  294. {
  295. m_cur.path.lineTo(toLocal(pos));
  296. update();
  297. return;
  298. }
  299. if (m_hasSel && inSelection(pos))
  300. setCursor(Qt::CrossCursor);
  301. else if (m_hasSel)
  302. setCursor(Qt::ArrowCursor);
  303. }
  304. void ScreenshotEditor::mouseReleaseEvent(QMouseEvent *event)
  305. {
  306. if (event->button() != Qt::LeftButton)
  307. return;
  308. if (mouseGrabber() == this)
  309. releaseMouse();
  310. if (m_selecting)
  311. {
  312. m_selecting = false;
  313. m_sel = QRect(m_dragOrigin, event->pos()).normalized();
  314. if (m_sel.width() < 8 || m_sel.height() < 8)
  315. {
  316. m_hasSel = false;
  317. m_sel = QRect();
  318. if (m_toolbar)
  319. m_toolbar->hide();
  320. }
  321. else
  322. {
  323. m_hasSel = true;
  324. updateToolbar();
  325. }
  326. update();
  327. return;
  328. }
  329. if (m_drawing)
  330. {
  331. m_drawing = false;
  332. if (!m_cur.path.isEmpty())
  333. m_strokes.push_back(m_cur);
  334. m_cur = Stroke();
  335. update();
  336. }
  337. }
  338. void ScreenshotEditor::keyPressEvent(QKeyEvent *event)
  339. {
  340. if (event->key() == Qt::Key_Escape)
  341. {
  342. if (mouseGrabber() == this)
  343. releaseMouse();
  344. reject();
  345. return;
  346. }
  347. if (event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter)
  348. {
  349. finish();
  350. return;
  351. }
  352. if (event->matches(QKeySequence::Undo) || (event->modifiers() & Qt::ControlModifier
  353. && event->key() == Qt::Key_Z))
  354. {
  355. if (!m_strokes.isEmpty())
  356. {
  357. m_strokes.removeLast();
  358. update();
  359. }
  360. return;
  361. }
  362. QDialog::keyPressEvent(event);
  363. }