fb2head.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. #include "fb2head.hpp"
  2. #include <QtDebug>
  3. #include <QAction>
  4. #include <QDialogButtonBox>
  5. #include <QGridLayout>
  6. #include <QHeaderView>
  7. #include <QToolBar>
  8. #include <QWebFrame>
  9. #include <QWebPage>
  10. #include <QWebView>
  11. #include <QItemDelegate>
  12. #include <QTreeView>
  13. #include "fb2text.hpp"
  14. #include "fb2utils.h"
  15. //---------------------------------------------------------------------------
  16. // FbScheme::Fb
  17. //---------------------------------------------------------------------------
  18. FbScheme::Fb::Fb()
  19. {
  20. QFile file(":/fb2/FictionBook2.1.xsd");
  21. if (file.open(QIODevice::ReadOnly)) setContent(&file);
  22. }
  23. //---------------------------------------------------------------------------
  24. // FbScheme
  25. //---------------------------------------------------------------------------
  26. const QDomDocument & FbScheme::fb2()
  27. {
  28. static const Fb doc;
  29. return doc;
  30. }
  31. FB2_BEGIN_KEYHASH(FbScheme)
  32. FB2_KEY( XsElement , "xs:element" );
  33. FB2_KEY( XsChoice , "xs:choice" );
  34. FB2_KEY( XsComplexType , "xs:complexType" );
  35. FB2_KEY( XsSequence , "xs:sequence" );
  36. FB2_END_KEYHASH
  37. void FbScheme::items(QStringList &list) const
  38. {
  39. FbScheme child = typeScheme().firstChildElement();
  40. while (!child.isNull()) {
  41. switch (toKeyword(child.tagName())) {
  42. case XsElement: {
  43. QString name = child.attribute("name");
  44. if (!list.contains(name)) list << name;
  45. } break;
  46. case XsChoice:
  47. case XsComplexType:
  48. case XsSequence: {
  49. child.items(list);
  50. } break;
  51. default: ;
  52. }
  53. child = child.nextSiblingElement();
  54. }
  55. }
  56. FbScheme FbScheme::typeScheme() const
  57. {
  58. QString typeName = type();
  59. if (typeName.isEmpty()) return *this;
  60. FbScheme child = fb2().firstChildElement("xs:schema").firstChildElement();
  61. while (!child.isNull()) {
  62. if (child.tagName() == "xs:complexType") {
  63. if (child.attribute("name") == typeName) return child.element(typeName);
  64. }
  65. child = child.nextSiblingElement();
  66. }
  67. return FbScheme();
  68. }
  69. QString FbScheme::info() const
  70. {
  71. QDomElement element = *this;
  72. if (element.isNull()) return QString();
  73. element = element.firstChildElement("xs:annotation");
  74. if (element.isNull()) return QString();
  75. element = element.firstChildElement("xs:documentation");
  76. if (element.isNull()) return QString();
  77. return element.text();
  78. }
  79. QString FbScheme::type() const
  80. {
  81. if (isNull()) return QString();
  82. QString result = attribute("type");
  83. if (!result.isEmpty()) return result;
  84. FbScheme child = firstChildElement("xs:complexType");
  85. child = child.firstChildElement("xs:complexContent");
  86. child = child.firstChildElement("xs:extension");
  87. return child.attribute("base");
  88. }
  89. FbScheme FbScheme::element(const QString &name) const
  90. {
  91. FbScheme parent = *this;
  92. if (parent.isNull()) {
  93. parent = fb2().documentElement();
  94. parent = parent.element("FictionBook");
  95. }
  96. FbScheme child = parent.firstChildElement();
  97. while (!child.isNull()) {
  98. switch (toKeyword(child.tagName())) {
  99. case XsElement: {
  100. if (child.attribute("name") == name) return child;
  101. } break;
  102. case XsChoice:
  103. case XsComplexType:
  104. case XsSequence: {
  105. FbScheme result = child.element(name);
  106. if (!result.isNull()) return result;
  107. } break;
  108. default: ;
  109. }
  110. child = child.nextSiblingElement();
  111. }
  112. QString type = this->type();
  113. if (type.isEmpty()) return *this;
  114. child = fb2().firstChildElement("xs:schema").firstChildElement();
  115. while (!child.isNull()) {
  116. if (child.tagName() == "xs:complexType") {
  117. if (child.attribute("name") == type) return child.element(name);
  118. }
  119. child = child.nextSiblingElement();
  120. }
  121. return FbScheme();
  122. }
  123. //---------------------------------------------------------------------------
  124. // FbHeadItem
  125. //---------------------------------------------------------------------------
  126. FbHeadItem::HintHash::HintHash()
  127. {
  128. insert( "title-info" , tr( "Book" ));
  129. insert( "document-info" , tr( "File" ));
  130. insert( "publish-info" , tr( "Publish" ));
  131. insert( "custom-info" , tr( "Add-ons" ));
  132. insert( "genre" , tr( "Genre" ));
  133. insert( "author" , tr( "Author" ));
  134. insert( "book-title" , tr( "Title" ));
  135. insert( "annotation" , tr( "Annotation" ));
  136. insert( "coverpage" , tr( "Cover" ));
  137. insert( "date" , tr( "Date" ));
  138. insert( "lang" , tr( "Language" ));
  139. insert( "translator" , tr( "Translator" ));
  140. insert( "sequence" , tr( "Sequence" ));
  141. insert( "first-name" , tr( "First name" ));
  142. insert( "middle-name" , tr( "Middle name" ));
  143. insert( "last-name" , tr( "Last name" ));
  144. insert( "history" , tr( "History" ));
  145. }
  146. FB2_BEGIN_KEYHASH(FbHeadItem)
  147. FB2_KEY( Auth , "author" );
  148. FB2_KEY( Cover , "coverpage" );
  149. FB2_KEY( Image , "image" );
  150. FB2_KEY( Seqn , "sequence" );
  151. FB2_END_KEYHASH
  152. FbHeadItem::FbHeadItem(QWebElement &element, FbHeadItem *parent)
  153. : QObject(parent)
  154. , m_element(element)
  155. , m_parent(parent)
  156. {
  157. m_name = element.tagName().toLower();
  158. m_id = element.attribute("id");
  159. if (m_name == "div") {
  160. QString style = element.attribute("class").toLower();
  161. if (!style.isEmpty()) m_name = style;
  162. if (style == "annotation") return;
  163. if (style == "history") return;
  164. } else if (m_name == "img") {
  165. m_name = "image";
  166. m_text = element.attribute("alt");
  167. }
  168. addChildren(element);
  169. }
  170. FbHeadItem::~FbHeadItem()
  171. {
  172. foreach (FbHeadItem * item, m_list) {
  173. delete item;
  174. }
  175. }
  176. FbHeadItem * FbHeadItem::append(const QString name)
  177. {
  178. m_element.appendInside("<div></div>");
  179. QWebElement element = m_element.lastChild();
  180. element.addClass(name);
  181. if (name == "annotation" || name == "history") {
  182. element.appendInside("<p><br></p>");
  183. }
  184. FbHeadItem * child = new FbHeadItem(element, this);
  185. m_list << child;
  186. return child;
  187. }
  188. void FbHeadItem::addChildren(QWebElement &parent)
  189. {
  190. QWebElement child = parent.firstChild();
  191. while (!child.isNull()) {
  192. QString tag = child.tagName().toLower();
  193. if (tag == "div") {
  194. m_list << new FbHeadItem(child, this);
  195. } else if (tag == "img") {
  196. m_list << new FbHeadItem(child, this);
  197. } else {
  198. addChildren(child);
  199. }
  200. child = child.nextSibling();
  201. }
  202. }
  203. FbHeadItem * FbHeadItem::item(const QModelIndex &index) const
  204. {
  205. int row = index.row();
  206. if (row < 0 || row >= m_list.size()) return NULL;
  207. return m_list[row];
  208. }
  209. FbHeadItem * FbHeadItem::item(int row) const
  210. {
  211. if (row < 0 || row >= m_list.size()) return NULL;
  212. return m_list[row];
  213. }
  214. QString FbHeadItem::text(int col) const
  215. {
  216. switch (col) {
  217. case 0: return QString("<%1> %2").arg(m_name).arg(hint());
  218. case 1: return value();
  219. case 2: return scheme().info();
  220. case 3: return scheme().type();
  221. case 4: return scheme().attribute("minOccurs");
  222. case 5: return scheme().attribute("maxOccurs");
  223. }
  224. return QString();
  225. }
  226. QString FbHeadItem::hint() const
  227. {
  228. static HintHash hints;
  229. HintHash::const_iterator it = hints.find(m_name);
  230. if (it == hints.end()) return QString();
  231. return it.value();
  232. }
  233. QString FbHeadItem::value() const
  234. {
  235. switch (toKeyword(m_name)) {
  236. case Auth : {
  237. QString result = sub("last-name");
  238. result += " " + sub("first-name");
  239. result += " " + sub("middle-name");
  240. return result.simplified();
  241. } break;
  242. case Cover : {
  243. QString text;
  244. foreach (FbHeadItem * item, m_list) {
  245. if (item->m_name == "image") {
  246. if (!text.isEmpty()) text += ", ";
  247. text += item->value();
  248. }
  249. }
  250. return text;
  251. } break;
  252. case Image : {
  253. return m_element.attribute("src");
  254. } break;
  255. case Seqn : {
  256. QString text = m_element.attribute("fb2_name");
  257. QString numb = m_element.attribute("fb2_number");
  258. if (numb.isEmpty() || numb == "0") return text;
  259. return text + ", " + tr("#") + numb;
  260. } break;
  261. default: ;
  262. }
  263. if (m_list.count()) return QString();
  264. return m_element.toPlainText().simplified();
  265. }
  266. QString FbHeadItem::sub(const QString &key) const
  267. {
  268. foreach (FbHeadItem * item, m_list) {
  269. if (item->m_name == key) return item->value();
  270. }
  271. return QString();
  272. }
  273. FbScheme FbHeadItem::scheme() const
  274. {
  275. FbScheme parent = m_parent ? m_parent->scheme() : FbScheme();
  276. return parent.element(m_name);
  277. }
  278. void FbHeadItem::remove(int row)
  279. {
  280. if (row < 0 || row >= count()) return;
  281. m_list[row]->m_element.removeFromDocument();
  282. m_list.removeAt(row);
  283. }
  284. //---------------------------------------------------------------------------
  285. // FbHeadModel
  286. //---------------------------------------------------------------------------
  287. FbHeadModel::FbHeadModel(QWebView &view, QObject *parent)
  288. : QAbstractItemModel(parent)
  289. , m_view(view)
  290. , m_root(NULL)
  291. {
  292. QWebElement doc = view.page()->mainFrame()->documentElement();
  293. QWebElement head = doc.findFirst("div.description");
  294. if (head.isNull()) return;
  295. m_root = new FbHeadItem(head);
  296. }
  297. FbHeadModel::~FbHeadModel()
  298. {
  299. if (m_root) delete m_root;
  300. }
  301. void FbHeadModel::expand(QTreeView *view)
  302. {
  303. QModelIndex parent = QModelIndex();
  304. int count = rowCount(parent);
  305. for (int i = 0; i < count; i++) {
  306. QModelIndex child = index(i, 0, parent);
  307. FbHeadItem *node = item(child);
  308. if (!node) continue;
  309. view->expand(child);
  310. int count = rowCount(child);
  311. for (int j = 0; j < count; j++) {
  312. QModelIndex ch = index(j, 0, child);
  313. FbHeadItem *node = item(ch);
  314. if (node) view->expand(ch);
  315. }
  316. }
  317. }
  318. FbHeadItem * FbHeadModel::item(const QModelIndex &index) const
  319. {
  320. if (index.isValid()) {
  321. return static_cast<FbHeadItem*>(index.internalPointer());
  322. } else {
  323. return 0;
  324. }
  325. }
  326. int FbHeadModel::columnCount(const QModelIndex &parent) const
  327. {
  328. Q_UNUSED(parent);
  329. return 6;
  330. }
  331. QModelIndex FbHeadModel::index(int row, int column, const QModelIndex &parent) const
  332. {
  333. if (!m_root || row < 0 || column < 0) return QModelIndex();
  334. if (!parent.isValid() && m_root) {
  335. return createIndex(row, column, (void*)m_root);
  336. }
  337. if (FbHeadItem *owner = item(parent)) {
  338. if (FbHeadItem *child = owner->item(row)) {
  339. return createIndex(row, column, (void*)child);
  340. }
  341. }
  342. return QModelIndex();
  343. }
  344. QModelIndex FbHeadModel::parent(const QModelIndex &child) const
  345. {
  346. if (FbHeadItem * node = static_cast<FbHeadItem*>(child.internalPointer())) {
  347. if (FbHeadItem * parent = node->parent()) {
  348. if (FbHeadItem * owner = parent->parent()) {
  349. return createIndex(owner->index(parent), 0, (void*)parent);
  350. } else {
  351. return createIndex(0, 0, (void*)parent);
  352. }
  353. }
  354. }
  355. return QModelIndex();
  356. }
  357. int FbHeadModel::rowCount(const QModelIndex &parent) const
  358. {
  359. if (parent.column() > 0) return 0;
  360. if (!parent.isValid()) return m_root ? 1 : 0;
  361. FbHeadItem *owner = item(parent);
  362. return owner ? owner->count() : 0;
  363. }
  364. QVariant FbHeadModel::data(const QModelIndex &index, int role) const
  365. {
  366. if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant();
  367. FbHeadItem * i = item(index);
  368. return i ? i->text(index.column()) : QVariant();
  369. }
  370. QVariant FbHeadModel::headerData(int section, Qt::Orientation orientation, int role) const
  371. {
  372. if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
  373. switch (section) {
  374. case 0: return tr("Key");
  375. case 1: return tr("Value");
  376. }
  377. }
  378. return QVariant();
  379. }
  380. void FbHeadItem::setText(const QString &text)
  381. {
  382. m_text = text;
  383. }
  384. bool FbHeadModel::setData(const QModelIndex &index, const QVariant &value, int role)
  385. {
  386. if (role != Qt::EditRole) return false;
  387. FbHeadItem * i = item(index);
  388. if (!i) return false;
  389. i->setText(value.toString());
  390. emit dataChanged(index, index);
  391. return true;
  392. }
  393. Qt::ItemFlags FbHeadModel::flags(const QModelIndex &index) const
  394. {
  395. if (!index.isValid()) return 0;
  396. Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
  397. if (index.column() == 1) flags |= Qt::ItemIsEditable;
  398. return flags;
  399. }
  400. //---------------------------------------------------------------------------
  401. // FbTreeView
  402. //---------------------------------------------------------------------------
  403. FbHeadView::FbHeadView(FbTextEdit &view, QWidget *parent)
  404. : QTreeView(parent)
  405. , m_view(view)
  406. {
  407. QAction * act;
  408. setContextMenuPolicy(Qt::ActionsContextMenu);
  409. actionInsert = act = new QAction(FbIcon("list-add"), tr("&Append"), this);
  410. act->setShortcutContext(Qt::WidgetShortcut);
  411. act->setShortcut(Qt::Key_Insert);
  412. act->setPriority(QAction::LowPriority);
  413. connect(act, SIGNAL(triggered()), SLOT(appendNode()));
  414. addAction(act);
  415. actionModify = act = new QAction(FbIcon("list-add"), tr("&Modify"), this);
  416. act->setPriority(QAction::LowPriority);
  417. actionDelete = act = new QAction(FbIcon("list-remove"), tr("&Delete"), this);
  418. act->setShortcutContext(Qt::WidgetShortcut);
  419. act->setShortcut(Qt::Key_Delete);
  420. act->setPriority(QAction::LowPriority);
  421. connect(act, SIGNAL(triggered()), SLOT(removeNode()));
  422. addAction(act);
  423. //setItemDelegate(new QItemDelegate(this));
  424. setRootIsDecorated(false);
  425. setSelectionBehavior(QAbstractItemView::SelectRows);
  426. setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
  427. connect(&m_view, SIGNAL(loadFinished(bool)), SLOT(updateTree()));
  428. connect(this, SIGNAL(activated(QModelIndex)), SLOT(activated(QModelIndex)));
  429. connect(this, SIGNAL(collapsed(QModelIndex)), SLOT(collapsed(QModelIndex)));
  430. header()->setDefaultSectionSize(200);
  431. connect(actionModify, SIGNAL(triggered()), SLOT(editCurrent()));
  432. }
  433. void FbHeadView::initToolbar(QToolBar &toolbar)
  434. {
  435. toolbar.addSeparator();
  436. toolbar.addAction(actionInsert);
  437. toolbar.addAction(actionDelete);
  438. }
  439. void FbHeadView::updateTree()
  440. {
  441. FbHeadModel * model = new FbHeadModel(m_view, this);
  442. setModel(model);
  443. model->expand(this);
  444. }
  445. void FbHeadView::editCurrent()
  446. {
  447. if (!model()) return;
  448. QModelIndex current = currentIndex();
  449. if (!current.isValid()) return;
  450. QModelIndex parent = model()->parent(current);
  451. QModelIndex index = model()->index(current.row(), 1, parent);
  452. setCurrentIndex(index);
  453. edit(index);
  454. }
  455. void FbHeadView::activated(const QModelIndex &index)
  456. {
  457. if (index.isValid() && index.column() == 1) edit(index);
  458. showStatus(index);
  459. }
  460. void FbHeadView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
  461. {
  462. QTreeView::currentChanged(current, previous);
  463. showStatus(current);
  464. }
  465. void FbHeadView::showStatus(const QModelIndex &current)
  466. {
  467. if (!model()) return;
  468. if (!current.isValid()) return;
  469. QModelIndex parent = model()->parent(current);
  470. QModelIndex index = model()->index(current.row(), 2, parent);
  471. emit status(model()->data(index).toString());
  472. }
  473. void FbHeadView::collapsed(const QModelIndex &index)
  474. {
  475. if (model() && !model()->parent(index).isValid()) {
  476. expand(index);
  477. }
  478. }
  479. void FbHeadView::appendNode()
  480. {
  481. FbHeadModel * m = qobject_cast<FbHeadModel*>(model());
  482. if (!m) return;
  483. QModelIndex current = currentIndex();
  484. FbHeadItem * item = m->item(current);
  485. if (!item) return;
  486. QString name = item->name().toLower();
  487. if (name == "annotation" || name == "history") {
  488. current = m->parent(current);
  489. item = m->item(current);
  490. }
  491. QStringList list;
  492. item->scheme().items(list);
  493. if (list.count() == 0) {
  494. current = m->parent(current);
  495. item = m->item(current);
  496. if (!item) return;
  497. item->scheme().items(list);
  498. }
  499. FbNodeDlg dlg(this, item->scheme(), list);
  500. if (dlg.exec()) {
  501. current = m->append(current, dlg.value());
  502. if (current.isValid()) setCurrentIndex(current);
  503. }
  504. }
  505. void FbHeadView::removeNode()
  506. {
  507. FbHeadModel * m = qobject_cast<FbHeadModel*>(model());
  508. if (m) m->remove(currentIndex());
  509. }
  510. QModelIndex FbHeadModel::append(const QModelIndex &parent, const QString &name)
  511. {
  512. FbHeadItem * owner = item(parent);
  513. if (!owner) return QModelIndex();
  514. int row = owner->count();
  515. beginInsertRows(parent, row, row);
  516. FbHeadItem * item = owner->append(name);
  517. endInsertRows();
  518. return createIndex(row, 0, (void*)item);
  519. }
  520. void FbHeadModel::remove(const QModelIndex &index)
  521. {
  522. int r = index.row();
  523. QModelIndex p = parent(index);
  524. beginRemoveRows(p, r, r + 1);
  525. FbHeadItem * i = item(p);
  526. if (i) i->remove(r);
  527. endRemoveRows();
  528. }
  529. //---------------------------------------------------------------------------
  530. // FbNodeDlg
  531. //---------------------------------------------------------------------------
  532. FbNodeDlg::FbNodeDlg(QWidget *parent, FbScheme scheme, QStringList &list)
  533. : QDialog(parent)
  534. , m_scheme(scheme)
  535. {
  536. setWindowTitle(tr("Insert tag"));
  537. QGridLayout * layout = new QGridLayout(this);
  538. QLabel * label = new QLabel(this);
  539. label->setObjectName(QString::fromUtf8("label"));
  540. label->setText(tr("Tag name:"));
  541. layout->addWidget(label, 0, 0, 1, 1);
  542. m_combo = new QComboBox(this);
  543. m_combo->setEditable(true);
  544. m_combo->addItems(list);
  545. layout->addWidget(m_combo, 0, 1, 1, 1);
  546. m_text = new QLabel(this);
  547. m_text->setStyleSheet(QString::fromUtf8("border-style:outset;border-width:1px;border-radius:10px;padding:6px;"));
  548. m_text->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop);
  549. m_text->setMinimumSize(QSize(300, 100));
  550. m_text->setWordWrap(true);
  551. layout->addWidget(m_text, 1, 0, 1, 2);
  552. QDialogButtonBox * buttons = new QDialogButtonBox(this);
  553. buttons->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
  554. buttons->setOrientation(Qt::Horizontal);
  555. layout->addWidget(buttons, 2, 0, 1, 2);
  556. layout->setColumnStretch(1, 1);
  557. connect(m_combo, SIGNAL(editTextChanged(QString)), SLOT(comboChanged(QString)));
  558. connect(buttons, SIGNAL(accepted()), SLOT(accept()));
  559. connect(buttons, SIGNAL(rejected()), SLOT(reject()));
  560. if (list.count()) {
  561. m_combo->setCurrentIndex(0);
  562. comboChanged(list.first());
  563. }
  564. }
  565. void FbNodeDlg::comboChanged(const QString &text)
  566. {
  567. m_text->setText(m_scheme.element(text).info());
  568. }
  569. QString FbNodeDlg::value() const
  570. {
  571. return m_combo->currentText();
  572. }