fb2imgs.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543
  1. #include "fb2imgs.hpp"
  2. #include <QAbstractListModel>
  3. #include <QBuffer>
  4. #include <QCryptographicHash>
  5. #include <QDialogButtonBox>
  6. #include <QFileDialog>
  7. #include <QFileInfo>
  8. #include <QImageReader>
  9. #include <QLabel>
  10. #include <QLineEdit>
  11. #include <QSplitter>
  12. #include <QUrl>
  13. #include <QVBoxLayout>
  14. #include <QWebFrame>
  15. #include <QTabWidget>
  16. #include <QtDebug>
  17. #include "fb2list.hpp"
  18. #include "fb2page.hpp"
  19. #include "fb2text.hpp"
  20. #include "fb2utils.h"
  21. static QString imgHtml(const QUrl &url)
  22. {
  23. return QString("<img src=\"%1\" valign=center align=center width=100%>").arg(url.toString());
  24. }
  25. //---------------------------------------------------------------------------
  26. // FbBinary
  27. //---------------------------------------------------------------------------
  28. FbBinary::FbBinary(const QString &name)
  29. : QTemporaryFile()
  30. , m_name(name)
  31. , m_size(0)
  32. {
  33. }
  34. qint64 FbBinary::write(QByteArray &data)
  35. {
  36. open();
  37. if (m_hash.isEmpty()) m_hash = md5(data);
  38. m_size = QTemporaryFile::write(data);
  39. QBuffer buffer(&data);
  40. buffer.open(QIODevice::ReadOnly);
  41. m_type = QImageReader::imageFormat(&buffer);
  42. close();
  43. return m_size;
  44. }
  45. QString FbBinary::md5(const QByteArray &data)
  46. {
  47. return QCryptographicHash::hash(data, QCryptographicHash::Md5).toBase64();
  48. }
  49. QByteArray FbBinary::data()
  50. {
  51. open();
  52. QByteArray data = readAll();
  53. close();
  54. return data;
  55. }
  56. //---------------------------------------------------------------------------
  57. // FbStore
  58. //---------------------------------------------------------------------------
  59. FbStore::FbStore(QObject *parent)
  60. : QObject(parent)
  61. {
  62. }
  63. FbStore::~FbStore()
  64. {
  65. FbTemporaryIterator it(*this);
  66. while (it.hasNext()) delete it.next();
  67. }
  68. void FbStore::binary(const QString &name, const QByteArray &data)
  69. {
  70. set(name, data);
  71. }
  72. QString FbStore::add(const QString &path, QByteArray &data)
  73. {
  74. QString hash = FbBinary::md5(data);
  75. QString name = this->name(hash);
  76. if (name.isEmpty()) {
  77. name = newName(path);
  78. FbBinary * temp = new FbBinary(name);
  79. temp->setHash(hash);
  80. temp->write(data);
  81. append(temp);
  82. }
  83. return name;
  84. }
  85. QString FbStore::newName(const QString &path)
  86. {
  87. QFileInfo info(path);
  88. QString name = info.fileName();
  89. if (!exists(name)) return name;
  90. QString base = info.baseName();
  91. QString suff = info.suffix();
  92. for (int i = 1; ; ++i) {
  93. QString name = QString("%1(%2).%3").arg(base).arg(i).arg(suff);
  94. if (exists(name)) continue;
  95. return name;
  96. }
  97. }
  98. FbBinary * FbStore::get(const QString &name) const
  99. {
  100. FbTemporaryIterator it(*this);
  101. while (it.hasNext()) {
  102. FbBinary * file = it.next();
  103. if (file->name() == name) return file;
  104. }
  105. return NULL;
  106. }
  107. QByteArray FbStore::data(const QString &name) const
  108. {
  109. FbTemporaryIterator it(*this);
  110. while (it.hasNext()) {
  111. FbBinary *file = it.next();
  112. if (file->name() == name) return file->data();
  113. }
  114. return QByteArray();
  115. }
  116. const QString & FbStore::set(const QString &name, QByteArray data, const QString &hash)
  117. {
  118. FbBinary * file = get(name);
  119. if (!file) append(file = new FbBinary(name));
  120. file->setHash(hash);
  121. file->write(data);
  122. return file->hash();
  123. }
  124. QString FbStore::name(const QString &hash) const
  125. {
  126. FbTemporaryIterator it(*this);
  127. while (it.hasNext()) {
  128. FbBinary *file = it.next();
  129. if (file->hash() == hash) return file->name();
  130. }
  131. return QString();
  132. }
  133. bool FbStore::exists(const QString &name) const
  134. {
  135. FbTemporaryIterator it(*this);
  136. while (it.hasNext()) {
  137. FbBinary *file = it.next();
  138. if (file->name() == name) return true;
  139. }
  140. return false;
  141. }
  142. #if 0
  143. //---------------------------------------------------------------------------
  144. // FbNetworkDiskCache
  145. //---------------------------------------------------------------------------
  146. QNetworkCacheMetaData FbNetworkDiskCache::metaData(const QUrl &url)
  147. {
  148. qCritical() << url.toString();
  149. return QNetworkDiskCache::metaData(url);
  150. }
  151. QIODevice * FbNetworkDiskCache::data(const QUrl &url)
  152. {
  153. qCritical() << url.toString();
  154. return QNetworkDiskCache::data(url);
  155. }
  156. #endif
  157. //---------------------------------------------------------------------------
  158. // FbImageReply
  159. //---------------------------------------------------------------------------
  160. FbImageReply::FbImageReply(QNetworkAccessManager::Operation op, const QNetworkRequest &request, const QByteArray &data)
  161. : QNetworkReply()
  162. , content(data)
  163. , offset(0)
  164. {
  165. setOperation(op);
  166. setRequest(request);
  167. setUrl(request.url());
  168. open(ReadOnly | Unbuffered);
  169. setHeader(QNetworkRequest::ContentLengthHeader, QVariant(content.size()));
  170. setAttribute(QNetworkRequest::CacheSaveControlAttribute, QVariant(false));
  171. QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
  172. QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
  173. }
  174. qint64 FbImageReply::readData(char *data, qint64 maxSize)
  175. {
  176. if (offset >= content.size()) return -1;
  177. QMetaObject::invokeMethod(this, "readyRead", Qt::QueuedConnection);
  178. qint64 number = qMin(maxSize, content.size() - offset);
  179. memcpy(data, content.constData() + offset, number);
  180. offset += number;
  181. return number;
  182. }
  183. //---------------------------------------------------------------------------
  184. // FbNetworkAccessManager
  185. //
  186. // http://doc.trolltech.com/qq/32/qq32-webkit-protocols.html
  187. //---------------------------------------------------------------------------
  188. FbNetworkAccessManager::FbNetworkAccessManager(QObject *parent)
  189. : QNetworkAccessManager(parent)
  190. , m_store(new FbStore(this))
  191. {
  192. }
  193. void FbNetworkAccessManager::setStore(const QUrl url, FbStore *store)
  194. {
  195. m_path = url.path();
  196. if (m_store) delete m_store;
  197. if (!store) store = new FbStore(this);
  198. store->setParent(this);
  199. m_store = store;
  200. }
  201. QNetworkReply * FbNetworkAccessManager::createRequest(Operation op, const QNetworkRequest &request, QIODevice *outgoingData)
  202. {
  203. if (m_store) {
  204. const QUrl &url = request.url();
  205. const QString path = url.path();
  206. if (url.scheme() == "fb2" && path == m_path) {
  207. QString name = request.url().fragment();
  208. QByteArray data = m_store->data(name);
  209. return new FbImageReply(op, request, data);
  210. }
  211. }
  212. return QNetworkAccessManager::createRequest(op, request, outgoingData);
  213. }
  214. QVariant FbNetworkAccessManager::info(int row, int col) const
  215. {
  216. if (!m_store) return QVariant();
  217. if (0 <= row && row < count()) {
  218. FbBinary *file = m_store->at(row);
  219. switch (col) {
  220. case 2: return file->type();
  221. case 3: return file->size();
  222. }
  223. return m_store->at(row)->name();
  224. }
  225. return QVariant();
  226. }
  227. QByteArray FbNetworkAccessManager::data(int index) const
  228. {
  229. if (!m_store) return QByteArray();
  230. if (0 <= index && index < count()) {
  231. return m_store->at(index)->data();
  232. }
  233. return QByteArray();
  234. }
  235. //---------------------------------------------------------------------------
  236. // FbComboCtrl
  237. //---------------------------------------------------------------------------
  238. FbComboCtrl::FbComboCtrl(QWidget *parent)
  239. : QLineEdit(parent)
  240. {
  241. button = new QToolButton(this);
  242. button->setCursor(Qt::ArrowCursor);
  243. button->setFocusPolicy(Qt::NoFocus);
  244. connect(button, SIGNAL(clicked()), SIGNAL(popup()));
  245. QHBoxLayout *layout = new QHBoxLayout(this);
  246. layout->addWidget(button, 0, Qt::AlignRight);
  247. layout->setSpacing(0);
  248. layout->setMargin(0);
  249. }
  250. void FbComboCtrl::resizeEvent(QResizeEvent* event)
  251. {
  252. QLineEdit::resizeEvent(event);
  253. QMargins margins(0, 0, button->width(), 0);
  254. setTextMargins(margins);
  255. }
  256. void FbComboCtrl::setIcon(const QIcon &icon)
  257. {
  258. button->setIcon(icon);
  259. }
  260. //---------------------------------------------------------------------------
  261. // FbImageDlg::FbTab
  262. //---------------------------------------------------------------------------
  263. FbImageDlg::FbTab::FbTab(QWidget* parent, QAbstractItemModel *model)
  264. : QWidget(parent)
  265. , combo(0)
  266. , edit(0)
  267. {
  268. QGridLayout * layout = new QGridLayout(this);
  269. label = new QLabel(this);
  270. label->setText(tr("File name:"));
  271. layout->addWidget(label, 0, 0, 1, 1);
  272. QWidget *control;
  273. if (model) {
  274. control = combo = new QComboBox(this);
  275. combo->setModel(model);
  276. } else {
  277. control = edit = new FbComboCtrl(this);
  278. }
  279. QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  280. control->setSizePolicy(sizePolicy);
  281. layout->addWidget(control, 0, 1, 1, 1);
  282. QFrame *frame = new FbTextFrame(this);
  283. frame->setMinimumSize(QSize(300, 200));
  284. layout->addWidget(frame, 1, 0, 1, 2);
  285. preview = new QWebView(this);
  286. frame->layout()->addWidget(preview);
  287. }
  288. //---------------------------------------------------------------------------
  289. // FbImageDlg
  290. //---------------------------------------------------------------------------
  291. FbImageDlg::FbImageDlg(FbTextEdit *text)
  292. : QDialog(text)
  293. , owner(text)
  294. , tabFile(0)
  295. , tabPict(0)
  296. {
  297. setWindowTitle(tr("Insert picture"));
  298. QLayout *layout = new QVBoxLayout(this);
  299. notebook = new QTabWidget(this);
  300. layout->addWidget(notebook);
  301. QDialogButtonBox *buttons = new QDialogButtonBox(this);
  302. buttons->setOrientation(Qt::Horizontal);
  303. buttons->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
  304. layout->addWidget(buttons);
  305. connect(buttons, SIGNAL(accepted()), SLOT(accept()));
  306. connect(buttons, SIGNAL(rejected()), SLOT(reject()));
  307. connect(notebook, SIGNAL(currentChanged(int)), SLOT(notebookChanged(int)));
  308. tabFile = new FbTab(notebook);
  309. tabFile->edit->setIcon(FbIcon("document-open"));
  310. tabFile->preview->setHtml(QString());
  311. connect(tabFile->edit, SIGNAL(textChanged(QString)), SLOT(filenameChanged(QString)));
  312. connect(tabFile->edit, SIGNAL(popup()), SLOT(selectFile()));
  313. notebook->addTab(tabFile, tr("Select file"));
  314. if (text->store()->count()) {
  315. FbImgsModel *model = new FbImgsModel(text, this);
  316. tabPict = new FbTab(notebook, model);
  317. tabPict->combo->setCurrentIndex(0);
  318. tabPict->preview->setHtml(QString(), text->url());
  319. tabPict->preview->page()->setNetworkAccessManager(text->page()->networkAccessManager());
  320. notebook->addTab(tabPict, tr("From collection"));
  321. connect(tabPict->combo, SIGNAL(activated(QString)), SLOT(pictureActivated(QString)));
  322. }
  323. tabFile->edit->setFocus();
  324. resize(minimumSizeHint());
  325. }
  326. void FbImageDlg::notebookChanged(int index)
  327. {
  328. if (index) {
  329. disconnect(notebook, SIGNAL(currentChanged(int)), this, SLOT(notebookChanged(int)));
  330. if (tabPict) pictureActivated(tabPict->combo->itemText(0));
  331. }
  332. }
  333. void FbImageDlg::selectFile()
  334. {
  335. QString filters;
  336. filters += tr("Common Graphics (*.png *.jpg *.jpeg *.gif)") += ";;";
  337. filters += tr("Portable Network Graphics (PNG) (*.png)") += ";;";
  338. filters += tr("JPEG (*.jpg *.jpeg)") += ";;";
  339. filters += tr("Graphics Interchange Format (*.gif)") += ";;";
  340. filters += tr("All Files (*)");
  341. QWidget *p = qobject_cast<QWidget*>(parent());
  342. QString path = QFileDialog::getOpenFileName(p, tr("Insert image..."), QString(), filters);
  343. if (path.isEmpty()) return;
  344. tabFile->edit->setText(path);
  345. }
  346. void FbImageDlg::filenameChanged(const QString & text)
  347. {
  348. if (QFileInfo(text).exists()) {
  349. QUrl url = QUrl::fromLocalFile(text);
  350. preview(tabFile->preview, url);
  351. } else {
  352. tabFile->preview->setHtml(QString());
  353. }
  354. }
  355. void FbImageDlg::pictureActivated(const QString & text)
  356. {
  357. QUrl url = tabPict->preview->url();
  358. url.setFragment(text);
  359. preview(tabPict->preview, url);
  360. }
  361. void FbImageDlg::preview(QWebView *preview, const QUrl &url)
  362. {
  363. preview->setHtml(imgHtml(url), preview->url());
  364. }
  365. QString FbImageDlg::result() const
  366. {
  367. if (tabPict && notebook->currentWidget() == tabPict) {
  368. return tabPict->combo->currentText();
  369. } else if (notebook->currentWidget() == tabFile) {
  370. QString path = tabFile->edit->text();
  371. QFile file(path);
  372. if (file.open(QIODevice::ReadOnly)) {
  373. QNetworkAccessManager *m = owner->page()->networkAccessManager();
  374. FbNetworkAccessManager *manager = qobject_cast<FbNetworkAccessManager*>(m);
  375. QByteArray data = file.readAll();
  376. return manager->add(path, data);
  377. }
  378. }
  379. return QString();
  380. }
  381. //---------------------------------------------------------------------------
  382. // FbImgsModel
  383. //---------------------------------------------------------------------------
  384. FbImgsModel::FbImgsModel(FbTextEdit *text, QObject *parent)
  385. : QAbstractListModel(parent)
  386. {
  387. manager = qobject_cast<FbNetworkAccessManager*>(text->page()->networkAccessManager());
  388. }
  389. int FbImgsModel::columnCount(const QModelIndex &parent) const
  390. {
  391. Q_UNUSED(parent);
  392. return 4;
  393. }
  394. int FbImgsModel::rowCount(const QModelIndex &parent) const
  395. {
  396. return parent.isValid() ? 0 : manager->count();
  397. }
  398. QVariant FbImgsModel::headerData(int section, Qt::Orientation orientation, int role) const
  399. {
  400. if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
  401. switch (section) {
  402. case 1: return tr("File name");
  403. case 2: return tr("Type");
  404. case 3: return tr("Size");
  405. }
  406. }
  407. return QVariant();
  408. }
  409. QVariant FbImgsModel::data(const QModelIndex &index, int role) const
  410. {
  411. if (index.isValid()) {
  412. switch (role) {
  413. case Qt::DisplayRole: {
  414. return manager->info(index.row(), index.column());
  415. } break;
  416. case Qt::TextAlignmentRole: {
  417. switch (index.column()) {
  418. case 3: return Qt::AlignRight;
  419. default: return Qt::AlignLeft;
  420. }
  421. }
  422. }
  423. }
  424. return QVariant();
  425. }
  426. //---------------------------------------------------------------------------
  427. // FbImgsWidget
  428. //---------------------------------------------------------------------------
  429. FbImgsWidget::FbImgsWidget(FbTextEdit *text, QWidget* parent)
  430. : QWidget(parent)
  431. , m_text(text)
  432. {
  433. QVBoxLayout *layout = new QVBoxLayout(this);
  434. layout->setSpacing(0);
  435. layout->setContentsMargins(0, 0, 0, 0);
  436. QSplitter *splitter = new QSplitter(Qt::Vertical, this);
  437. m_list = new FbListView(splitter);
  438. splitter->addWidget(m_list);
  439. FbTextFrame *frame = new FbTextFrame(splitter);
  440. splitter->addWidget(frame);
  441. m_view = new FbTextBase(frame);
  442. m_view->page()->setNetworkAccessManager(text->page()->networkAccessManager());
  443. frame->layout()->addWidget(m_view);
  444. splitter->setSizes(QList<int>() << 100 << 100);
  445. layout->addWidget(splitter);
  446. connect(m_text, SIGNAL(loadFinished(bool)), SLOT(loadFinished()));
  447. connect(m_list, SIGNAL(showCurrent(QString)), SLOT(showCurrent(QString)));
  448. loadFinished();
  449. }
  450. void FbImgsWidget::loadFinished()
  451. {
  452. if (QAbstractItemModel *m = m_list->model()) m->deleteLater();
  453. m_view->load(QUrl());
  454. m_list->setModel(new FbImgsModel(m_text, this));
  455. m_list->reset();
  456. m_list->resizeColumnToContents(1);
  457. m_list->resizeColumnToContents(2);
  458. m_list->resizeColumnToContents(3);
  459. m_list->setColumnHidden(0, true);
  460. }
  461. void FbImgsWidget::showCurrent(const QString &name)
  462. {
  463. QUrl url = m_text->url();
  464. url.setFragment(name);
  465. m_view->setHtml(imgHtml(url));
  466. }