imageinsertdialog.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. #include "imageinsertdialog.h"
  2. #include <QGridLayout>
  3. #include <QVBoxLayout>
  4. #include <QUrl>
  5. #include <QRegularExpression>
  6. #include <QLabel>
  7. #include <QPushButton>
  8. #include <QRegExpValidator>
  9. #include <QSpinBox>
  10. #include <QSlider>
  11. #include <QScrollArea>
  12. #include <QFileInfo>
  13. #include <QDir>
  14. #include <QFileDialog>
  15. #include <QTimer>
  16. #include <QTemporaryFile>
  17. #include <QCheckBox>
  18. #include <vtextedit/markdownutils.h>
  19. #include <vtextedit/networkutils.h>
  20. #include <widgets/widgetsfactory.h>
  21. #include <widgets/lineedit.h>
  22. #include <utils/fileutils.h>
  23. #include <utils/pathutils.h>
  24. #include <core/sessionconfig.h>
  25. #include <core/configmgr.h>
  26. using namespace vnotex;
  27. int ImageInsertDialog::s_lastScaleSliderValue = 10;
  28. int ImageInsertDialog::s_lastScaleWidth = -1;
  29. bool ImageInsertDialog::s_fixedScaleWidth = false;
  30. ImageInsertDialog::ImageInsertDialog(const QString &p_title,
  31. const QString &p_imageTitle,
  32. const QString &p_imageAlt,
  33. const QString &p_imagePath,
  34. bool p_browserEnabled,
  35. QWidget *p_parent)
  36. : Dialog(p_parent),
  37. m_browserEnabled(p_browserEnabled)
  38. {
  39. m_imagePathCheckTimer = new QTimer(this);
  40. m_imagePathCheckTimer->setSingleShot(true);
  41. m_imagePathCheckTimer->setInterval(500);
  42. connect(m_imagePathCheckTimer, &QTimer::timeout,
  43. this, &ImageInsertDialog::checkImagePathInput);
  44. setupUI(p_title, p_imageTitle, p_imageAlt, p_imagePath);
  45. checkInput();
  46. }
  47. void ImageInsertDialog::setupUI(const QString &p_title,
  48. const QString &p_imageTitle,
  49. const QString &p_imageAlt,
  50. const QString &p_imagePath)
  51. {
  52. auto mainWidget = new QWidget(this);
  53. setCentralWidget(mainWidget);
  54. auto mainLayout = new QVBoxLayout(mainWidget);
  55. auto gridLayout = new QGridLayout();
  56. mainLayout->addLayout(gridLayout);
  57. mainLayout->addStretch();
  58. // Image Path.
  59. m_imagePathEdit = WidgetsFactory::createLineEdit(p_imagePath, mainWidget);
  60. m_imagePathEdit->setReadOnly(!m_browserEnabled);
  61. gridLayout->addWidget(new QLabel(tr("From:"), mainWidget), 0, 0, 1, 1);
  62. gridLayout->addWidget(m_imagePathEdit, 0, 1, 1, 3);
  63. connect(m_imagePathEdit, &QLineEdit::textChanged,
  64. this, [this]() {
  65. m_imagePathCheckTimer->start();
  66. });
  67. m_browseBtn = new QPushButton(tr("&Browse"), mainWidget);
  68. m_browseBtn->setEnabled(m_browserEnabled);
  69. gridLayout->addWidget(m_browseBtn, 0, 4, 1, 1);
  70. connect(m_browseBtn, &QPushButton::clicked,
  71. this, &ImageInsertDialog::browseFile);
  72. // Image Title.
  73. m_imageTitleEdit = WidgetsFactory::createLineEdit(p_imageTitle, mainWidget);
  74. auto titleValidator = new QRegExpValidator(QRegExp(vte::MarkdownUtils::c_imageTitleRegExp), m_imageTitleEdit);
  75. m_imageTitleEdit->setValidator(titleValidator);
  76. gridLayout->addWidget(new QLabel(tr("Title:"), mainWidget), 1, 0, 1, 1);
  77. gridLayout->addWidget(m_imageTitleEdit, 1, 1, 1, 3);
  78. connect(m_imageTitleEdit, &QLineEdit::textChanged,
  79. this, &ImageInsertDialog::checkInput);
  80. // Image Alt.
  81. m_imageAltEdit = WidgetsFactory::createLineEdit(p_imageAlt, mainWidget);
  82. auto altValidator = new QRegExpValidator(QRegExp(vte::MarkdownUtils::c_imageAltRegExp), m_imageAltEdit);
  83. m_imageAltEdit->setValidator(altValidator);
  84. gridLayout->addWidget(new QLabel(tr("Alt text:"), mainWidget), 2, 0, 1, 1);
  85. gridLayout->addWidget(m_imageAltEdit, 2, 1, 1, 3);
  86. // Scale.
  87. m_widthSpin = WidgetsFactory::createSpinBox(mainWidget);
  88. m_widthSpin->setMinimum(1);
  89. m_widthSpin->setSingleStep(10);
  90. m_widthSpin->setSuffix(" px");
  91. connect(m_widthSpin, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
  92. this, [this](int p_val) {
  93. if (m_image.isNull()) {
  94. return;
  95. }
  96. int height = m_image.height() * (1.0 * p_val / m_image.width());
  97. m_imageLabel->resize(p_val, height);
  98. s_lastScaleWidth = p_val;
  99. });
  100. // 0.1 to 2.0 -> 1 to 20.
  101. m_scaleSlider = new QSlider(mainWidget);
  102. m_scaleSlider->setOrientation(Qt::Horizontal);
  103. m_scaleSlider->setMinimum(1);
  104. m_scaleSlider->setMaximum(50);
  105. m_scaleSlider->setValue(s_lastScaleSliderValue);
  106. m_scaleSlider->setSingleStep(1);
  107. m_scaleSlider->setPageStep(5);
  108. connect(m_scaleSlider, &QSlider::valueChanged,
  109. this, &ImageInsertDialog::handleScaleSliderValueChanged);
  110. m_sliderLabel = new QLabel("1x", mainWidget);
  111. gridLayout->addWidget(new QLabel(tr("Scaling width:"), mainWidget), 3, 0, 1, 1);
  112. gridLayout->addWidget(m_widthSpin, 3, 1, 1, 1);
  113. gridLayout->addWidget(m_scaleSlider, 3, 2, 1, 2);
  114. gridLayout->addWidget(m_sliderLabel, 3, 4, 1, 1);
  115. {
  116. auto fixedWidthCheckBox = WidgetsFactory::createCheckBox(tr("Fixed scaling width"), mainWidget);
  117. fixedWidthCheckBox->setChecked(s_fixedScaleWidth);
  118. connect(fixedWidthCheckBox, &QCheckBox::stateChanged,
  119. this, [](int p_state) {
  120. s_fixedScaleWidth = p_state == Qt::Checked;
  121. });
  122. gridLayout->addWidget(fixedWidthCheckBox, 4, 1, 1, 1);
  123. }
  124. // Preview area.
  125. m_imageLabel = new QLabel(mainWidget);
  126. m_imageLabel->setScaledContents(true);
  127. m_previewArea = new QScrollArea(mainWidget);
  128. m_previewArea->setBackgroundRole(QPalette::Dark);
  129. m_previewArea->setWidget(m_imageLabel);
  130. m_previewArea->setMinimumSize(256, 256);
  131. gridLayout->addWidget(m_previewArea, 5, 0, 1, 5);
  132. setImageControlsVisible(false);
  133. gridLayout->setColumnStretch(0, 0);
  134. gridLayout->setColumnStretch(1, 0);
  135. gridLayout->setColumnStretch(2, 1);
  136. gridLayout->setColumnStretch(3, 1);
  137. gridLayout->setColumnStretch(4, 0);
  138. setDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
  139. setWindowTitle(p_title);
  140. }
  141. void ImageInsertDialog::setImageControlsVisible(bool p_visible)
  142. {
  143. m_widthSpin->setEnabled(p_visible);
  144. m_scaleSlider->setEnabled(p_visible);
  145. m_sliderLabel->setEnabled(p_visible);
  146. m_previewArea->setVisible(p_visible);
  147. }
  148. void ImageInsertDialog::showEvent(QShowEvent *p_event)
  149. {
  150. Dialog::showEvent(p_event);
  151. m_imageTitleEdit->selectAll();
  152. m_imageTitleEdit->setFocus();
  153. }
  154. void ImageInsertDialog::checkImagePathInput()
  155. {
  156. const QString text = m_imagePathEdit->text();
  157. QUrl url = QUrl::fromUserInput(text);
  158. if (text.isEmpty() || !url.isValid()) {
  159. setImage(QImage());
  160. return;
  161. }
  162. if (url.isLocalFile()) {
  163. const auto localFile = url.toLocalFile();
  164. if (QFileInfo::exists(localFile)) {
  165. setImage(FileUtils::imageFromFile(localFile));
  166. } else {
  167. setImage(QImage());
  168. }
  169. m_source = Source::LocalFile;
  170. } else {
  171. setImage(QImage());
  172. m_source = Source::ImageData;
  173. if (!m_downloader) {
  174. m_downloader = new vte::NetworkAccess(this);
  175. connect(m_downloader, &vte::NetworkAccess::requestFinished,
  176. this, &ImageInsertDialog::handleImageDownloaded);
  177. }
  178. m_downloader->requestAsync(url);
  179. }
  180. m_imageTitleEdit->setText(QFileInfo(text).baseName());
  181. checkInput();
  182. }
  183. void ImageInsertDialog::checkInput()
  184. {
  185. setButtonEnabled(QDialogButtonBox::Ok, !m_image.isNull());
  186. }
  187. void ImageInsertDialog::browseFile()
  188. {
  189. auto &sessionConfig = ConfigMgr::getInst().getSessionConfig();
  190. QString filePath = QFileDialog::getOpenFileName(this,
  191. tr("Select Image To Insert"),
  192. sessionConfig.getExternalMediaDefaultPath(),
  193. tr("Images (*.png *.xpm *.jpg *.bmp *.gif *.svg *.webp);;All (*.*)"));
  194. if (filePath.isEmpty()) {
  195. return;
  196. }
  197. sessionConfig.setExternalMediaDefaultPath(PathUtils::parentDirPath(filePath));
  198. m_source = Source::LocalFile;
  199. setImagePath(filePath);
  200. m_imageTitleEdit->selectAll();
  201. m_imageTitleEdit->setFocus();
  202. }
  203. QString ImageInsertDialog::getImageTitle() const
  204. {
  205. return m_imageTitleEdit->text();
  206. }
  207. QString ImageInsertDialog::getImageAltText() const
  208. {
  209. return m_imageAltEdit->text();
  210. }
  211. QString ImageInsertDialog::getImagePath() const
  212. {
  213. if (m_tempFile.isNull()) {
  214. return m_imagePathEdit->text();
  215. } else {
  216. return m_tempFile->fileName();
  217. }
  218. }
  219. ImageInsertDialog::Source ImageInsertDialog::getImageSource() const
  220. {
  221. return m_source;
  222. }
  223. void ImageInsertDialog::setImageSource(ImageInsertDialog::Source p_source)
  224. {
  225. m_source = p_source;
  226. }
  227. const QImage &ImageInsertDialog::getImage() const
  228. {
  229. return m_image;
  230. }
  231. void ImageInsertDialog::setImage(const QImage &p_image)
  232. {
  233. m_image = p_image;
  234. if (m_image.isNull()) {
  235. m_imageLabel->clear();
  236. setImageControlsVisible(false);
  237. } else {
  238. m_imageLabel->setPixmap(QPixmap::fromImage(m_image));
  239. m_imageLabel->adjustSize();
  240. m_widthSpin->setMaximum(m_image.width() * 5);
  241. if (s_fixedScaleWidth) {
  242. m_widthSpin->setValue(s_lastScaleWidth);
  243. } else {
  244. // Set the scaling widgets.
  245. if (m_scaleSlider->value() == s_lastScaleSliderValue) {
  246. // Trigger it manually.
  247. handleScaleSliderValueChanged(s_lastScaleSliderValue);
  248. } else {
  249. m_scaleSlider->setValue(s_lastScaleSliderValue);
  250. }
  251. }
  252. setImageControlsVisible(true);
  253. }
  254. checkInput();
  255. }
  256. void ImageInsertDialog::setImagePath(const QString &p_path)
  257. {
  258. m_tempFile.reset();
  259. m_imagePathEdit->setText(p_path);
  260. }
  261. int ImageInsertDialog::getScaledWidth() const
  262. {
  263. if (m_image.isNull()) {
  264. return 0;
  265. }
  266. int val = m_widthSpin->value();
  267. return val == m_image.width() ? 0 : val;
  268. }
  269. void ImageInsertDialog::handleImageDownloaded(const vte::NetworkReply &p_data, const QString &p_url)
  270. {
  271. setImage(QImage::fromData(p_data.m_data));
  272. // Save it to a temp file to avoid potential data loss via QImage.
  273. bool savedToFile = false;
  274. if (!p_data.m_data.isEmpty()) {
  275. auto format = QFileInfo(PathUtils::removeUrlParameters(p_url)).suffix();
  276. m_tempFile.reset(FileUtils::createTemporaryFile(format));
  277. if (m_tempFile->open()) {
  278. savedToFile = -1 != m_tempFile->write(p_data.m_data);
  279. m_tempFile->close();
  280. }
  281. }
  282. m_source = savedToFile ? Source::LocalFile : Source::ImageData;
  283. if (!savedToFile) {
  284. m_tempFile.reset();
  285. }
  286. }
  287. void ImageInsertDialog::handleScaleSliderValueChanged(int p_val)
  288. {
  289. if (m_image.isNull()) {
  290. return;
  291. }
  292. int width = m_image.width();
  293. qreal factor = 1.0;
  294. if (p_val != 10) {
  295. factor = p_val / 10.0;
  296. width = m_image.width() * factor;
  297. }
  298. m_widthSpin->setValue(width);
  299. m_sliderLabel->setText(QString::number(factor) + "x");
  300. s_lastScaleSliderValue = p_val;
  301. }