1
0

fb2head.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  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 "fb2view.hpp"
  14. #include "fb2utils.h"
  15. //---------------------------------------------------------------------------
  16. // Fb2Scheme::Fb2
  17. //---------------------------------------------------------------------------
  18. Fb2Scheme::Fb2::Fb2()
  19. {
  20. QFile file(":/fb2/FictionBook2.1.xsd");
  21. if (file.open(QIODevice::ReadOnly)) setContent(&file);
  22. }
  23. //---------------------------------------------------------------------------
  24. // Fb2Scheme
  25. //---------------------------------------------------------------------------
  26. const QDomDocument & Fb2Scheme::fb2()
  27. {
  28. static const Fb2 doc;
  29. return doc;
  30. }
  31. FB2_BEGIN_KEYHASH(Fb2Scheme)
  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 Fb2Scheme::items(QStringList &list) const
  38. {
  39. Fb2Scheme 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. Fb2Scheme Fb2Scheme::typeScheme() const
  57. {
  58. QString typeName = type();
  59. if (typeName.isEmpty()) return *this;
  60. Fb2Scheme 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 Fb2Scheme();
  68. }
  69. QString Fb2Scheme::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 Fb2Scheme::type() const
  80. {
  81. if (isNull()) return QString();
  82. QString result = attribute("type");
  83. if (!result.isEmpty()) return result;
  84. Fb2Scheme child = firstChildElement("xs:complexType");
  85. child = child.firstChildElement("xs:complexContent");
  86. child = child.firstChildElement("xs:extension");
  87. return child.attribute("base");
  88. }
  89. Fb2Scheme Fb2Scheme::element(const QString &name) const
  90. {
  91. Fb2Scheme parent = *this;
  92. if (parent.isNull()) {
  93. parent = fb2().documentElement();
  94. parent = parent.element("FictionBook");
  95. }
  96. Fb2Scheme 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. Fb2Scheme 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 Fb2Scheme();
  122. }
  123. //---------------------------------------------------------------------------
  124. // Fb2HeadItem
  125. //---------------------------------------------------------------------------
  126. Fb2HeadItem::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(Fb2HeadItem)
  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. Fb2HeadItem::Fb2HeadItem(QWebElement &element, Fb2HeadItem *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. Fb2HeadItem::~Fb2HeadItem()
  171. {
  172. foreach (Fb2HeadItem * item, m_list) {
  173. delete item;
  174. }
  175. }
  176. Fb2HeadItem * Fb2HeadItem::append(const QString name)
  177. {
  178. m_element.appendInside("<div></div>");
  179. QWebElement element = m_element.lastChild();
  180. element.addClass(name);
  181. Fb2HeadItem * child = new Fb2HeadItem(element, this);
  182. m_list << child;
  183. return child;
  184. }
  185. void Fb2HeadItem::addChildren(QWebElement &parent)
  186. {
  187. QWebElement child = parent.firstChild();
  188. while (!child.isNull()) {
  189. QString tag = child.tagName().toLower();
  190. if (tag == "div") {
  191. m_list << new Fb2HeadItem(child, this);
  192. } else if (tag == "img") {
  193. m_list << new Fb2HeadItem(child, this);
  194. } else {
  195. addChildren(child);
  196. }
  197. child = child.nextSibling();
  198. }
  199. }
  200. Fb2HeadItem * Fb2HeadItem::item(const QModelIndex &index) const
  201. {
  202. int row = index.row();
  203. if (row < 0 || row >= m_list.size()) return NULL;
  204. return m_list[row];
  205. }
  206. Fb2HeadItem * Fb2HeadItem::item(int row) const
  207. {
  208. if (row < 0 || row >= m_list.size()) return NULL;
  209. return m_list[row];
  210. }
  211. QString Fb2HeadItem::text(int col) const
  212. {
  213. switch (col) {
  214. case 0: return QString("<%1> %2").arg(m_name).arg(hint());
  215. case 1: return value();
  216. case 2: return scheme().info();
  217. case 3: return scheme().type();
  218. case 4: return scheme().attribute("minOccurs");
  219. case 5: return scheme().attribute("maxOccurs");
  220. }
  221. return QString();
  222. }
  223. QString Fb2HeadItem::hint() const
  224. {
  225. static HintHash hints;
  226. HintHash::const_iterator it = hints.find(m_name);
  227. if (it == hints.end()) return QString();
  228. return it.value();
  229. }
  230. QString Fb2HeadItem::value() const
  231. {
  232. switch (toKeyword(m_name)) {
  233. case Auth : {
  234. QString result = sub("last-name");
  235. result += " " + sub("first-name");
  236. result += " " + sub("middle-name");
  237. return result.simplified();
  238. } break;
  239. case Cover : {
  240. QString text;
  241. foreach (Fb2HeadItem * item, m_list) {
  242. if (item->m_name == "image") {
  243. if (!text.isEmpty()) text += ", ";
  244. text += item->value();
  245. }
  246. }
  247. return text;
  248. } break;
  249. case Image : {
  250. return m_element.attribute("src");
  251. } break;
  252. case Seqn : {
  253. QString text = m_element.attribute("fb2_name");
  254. QString numb = m_element.attribute("fb2_number");
  255. if (numb.isEmpty() || numb == "0") return text;
  256. return text + ", " + tr("#") + numb;
  257. } break;
  258. default: ;
  259. }
  260. if (m_list.count()) return QString();
  261. return m_element.toPlainText().simplified();
  262. }
  263. QString Fb2HeadItem::sub(const QString &key) const
  264. {
  265. foreach (Fb2HeadItem * item, m_list) {
  266. if (item->m_name == key) return item->value();
  267. }
  268. return QString();
  269. }
  270. Fb2Scheme Fb2HeadItem::scheme() const
  271. {
  272. Fb2Scheme parent = m_parent ? m_parent->scheme() : Fb2Scheme();
  273. return parent.element(m_name);
  274. }
  275. void Fb2HeadItem::remove(int row)
  276. {
  277. if (row < 0 || row >= count()) return;
  278. m_list[row]->m_element.removeFromDocument();
  279. m_list.removeAt(row);
  280. }
  281. //---------------------------------------------------------------------------
  282. // Fb2HeadModel
  283. //---------------------------------------------------------------------------
  284. Fb2HeadModel::Fb2HeadModel(QWebView &view, QObject *parent)
  285. : QAbstractItemModel(parent)
  286. , m_view(view)
  287. , m_root(NULL)
  288. {
  289. QWebElement doc = view.page()->mainFrame()->documentElement();
  290. QWebElement head = doc.findFirst("div.description");
  291. if (head.isNull()) return;
  292. m_root = new Fb2HeadItem(head);
  293. }
  294. Fb2HeadModel::~Fb2HeadModel()
  295. {
  296. if (m_root) delete m_root;
  297. }
  298. void Fb2HeadModel::expand(QTreeView *view)
  299. {
  300. QModelIndex parent = QModelIndex();
  301. int count = rowCount(parent);
  302. for (int i = 0; i < count; i++) {
  303. QModelIndex child = index(i, 0, parent);
  304. Fb2HeadItem *node = item(child);
  305. if (!node) continue;
  306. view->expand(child);
  307. int count = rowCount(child);
  308. for (int j = 0; j < count; j++) {
  309. QModelIndex ch = index(j, 0, child);
  310. Fb2HeadItem *node = item(ch);
  311. if (node) view->expand(ch);
  312. }
  313. }
  314. }
  315. Fb2HeadItem * Fb2HeadModel::item(const QModelIndex &index) const
  316. {
  317. if (index.isValid()) {
  318. return static_cast<Fb2HeadItem*>(index.internalPointer());
  319. } else {
  320. return 0;
  321. }
  322. }
  323. int Fb2HeadModel::columnCount(const QModelIndex &parent) const
  324. {
  325. Q_UNUSED(parent);
  326. return 6;
  327. }
  328. QModelIndex Fb2HeadModel::index(int row, int column, const QModelIndex &parent) const
  329. {
  330. if (!m_root || row < 0 || column < 0) return QModelIndex();
  331. if (!parent.isValid() && m_root) {
  332. return createIndex(row, column, (void*)m_root);
  333. }
  334. if (Fb2HeadItem *owner = item(parent)) {
  335. if (Fb2HeadItem *child = owner->item(row)) {
  336. return createIndex(row, column, (void*)child);
  337. }
  338. }
  339. return QModelIndex();
  340. }
  341. QModelIndex Fb2HeadModel::parent(const QModelIndex &child) const
  342. {
  343. if (Fb2HeadItem * node = static_cast<Fb2HeadItem*>(child.internalPointer())) {
  344. if (Fb2HeadItem * parent = node->parent()) {
  345. if (Fb2HeadItem * owner = parent->parent()) {
  346. return createIndex(owner->index(parent), 0, (void*)parent);
  347. } else {
  348. return createIndex(0, 0, (void*)parent);
  349. }
  350. }
  351. }
  352. return QModelIndex();
  353. }
  354. int Fb2HeadModel::rowCount(const QModelIndex &parent) const
  355. {
  356. if (parent.column() > 0) return 0;
  357. if (!parent.isValid()) return m_root ? 1 : 0;
  358. Fb2HeadItem *owner = item(parent);
  359. return owner ? owner->count() : 0;
  360. }
  361. QVariant Fb2HeadModel::data(const QModelIndex &index, int role) const
  362. {
  363. if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant();
  364. Fb2HeadItem * i = item(index);
  365. return i ? i->text(index.column()) : QVariant();
  366. }
  367. QVariant Fb2HeadModel::headerData(int section, Qt::Orientation orientation, int role) const
  368. {
  369. if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
  370. switch (section) {
  371. case 0: return tr("Key");
  372. case 1: return tr("Value");
  373. }
  374. }
  375. return QVariant();
  376. }
  377. void Fb2HeadItem::setText(const QString &text)
  378. {
  379. m_text = text;
  380. }
  381. bool Fb2HeadModel::setData(const QModelIndex &index, const QVariant &value, int role)
  382. {
  383. if (role != Qt::EditRole) return false;
  384. Fb2HeadItem * i = item(index);
  385. if (!i) return false;
  386. i->setText(value.toString());
  387. emit dataChanged(index, index);
  388. return true;
  389. }
  390. Qt::ItemFlags Fb2HeadModel::flags(const QModelIndex &index) const
  391. {
  392. if (!index.isValid()) return 0;
  393. Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
  394. if (index.column() == 1) flags |= Qt::ItemIsEditable;
  395. return flags;
  396. }
  397. //---------------------------------------------------------------------------
  398. // Fb2TreeView
  399. //---------------------------------------------------------------------------
  400. Fb2HeadView::Fb2HeadView(Fb2WebView &view, QWidget *parent)
  401. : QTreeView(parent)
  402. , m_view(view)
  403. {
  404. QAction * act;
  405. setContextMenuPolicy(Qt::ActionsContextMenu);
  406. actionInsert = act = new QAction(FB2::icon("list-add"), tr("&Append"), this);
  407. act->setShortcutContext(Qt::WidgetShortcut);
  408. act->setShortcut(QKeySequence("Insert"));
  409. act->setPriority(QAction::LowPriority);
  410. connect(act, SIGNAL(triggered()), SLOT(appendNode()));
  411. addAction(act);
  412. actionModify = act = new QAction(FB2::icon("list-add"), tr("&Modify"), this);
  413. act->setPriority(QAction::LowPriority);
  414. actionDelete = act = new QAction(FB2::icon("list-remove"), tr("&Delete"), this);
  415. act->setShortcutContext(Qt::WidgetShortcut);
  416. act->setShortcut(QKeySequence("Delete"));
  417. act->setPriority(QAction::LowPriority);
  418. connect(act, SIGNAL(triggered()), SLOT(removeNode()));
  419. addAction(act);
  420. //setItemDelegate(new QItemDelegate(this));
  421. setRootIsDecorated(false);
  422. setSelectionBehavior(QAbstractItemView::SelectRows);
  423. setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
  424. connect(&m_view, SIGNAL(loadFinished(bool)), SLOT(updateTree()));
  425. connect(this, SIGNAL(activated(QModelIndex)), SLOT(activated(QModelIndex)));
  426. connect(this, SIGNAL(collapsed(QModelIndex)), SLOT(collapsed(QModelIndex)));
  427. header()->setDefaultSectionSize(200);
  428. connect(actionModify, SIGNAL(triggered()), SLOT(editCurrent()));
  429. }
  430. void Fb2HeadView::initToolbar(QToolBar &toolbar)
  431. {
  432. toolbar.addSeparator();
  433. toolbar.addAction(actionInsert);
  434. toolbar.addAction(actionDelete);
  435. }
  436. void Fb2HeadView::updateTree()
  437. {
  438. Fb2HeadModel * model = new Fb2HeadModel(m_view, this);
  439. setModel(model);
  440. model->expand(this);
  441. }
  442. void Fb2HeadView::editCurrent()
  443. {
  444. if (!model()) return;
  445. QModelIndex current = currentIndex();
  446. if (!current.isValid()) return;
  447. QModelIndex parent = model()->parent(current);
  448. QModelIndex index = model()->index(current.row(), 1, parent);
  449. setCurrentIndex(index);
  450. edit(index);
  451. }
  452. void Fb2HeadView::activated(const QModelIndex &index)
  453. {
  454. if (index.isValid() && index.column() == 1) edit(index);
  455. showStatus(index);
  456. }
  457. void Fb2HeadView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
  458. {
  459. QTreeView::currentChanged(current, previous);
  460. showStatus(current);
  461. }
  462. void Fb2HeadView::showStatus(const QModelIndex &current)
  463. {
  464. if (!model()) return;
  465. if (!current.isValid()) return;
  466. QModelIndex parent = model()->parent(current);
  467. QModelIndex index = model()->index(current.row(), 2, parent);
  468. emit status(model()->data(index).toString());
  469. }
  470. void Fb2HeadView::collapsed(const QModelIndex &index)
  471. {
  472. if (model() && !model()->parent(index).isValid()) {
  473. expand(index);
  474. }
  475. }
  476. void Fb2HeadView::appendNode()
  477. {
  478. Fb2HeadModel * m = qobject_cast<Fb2HeadModel*>(model());
  479. if (!m) return;
  480. QModelIndex current = currentIndex();
  481. Fb2HeadItem * item = m->item(current);
  482. if (!item) return;
  483. QString name = item->name().toLower();
  484. if (name == "annotation" || name == "history") {
  485. current = m->parent(current);
  486. item = m->item(current);
  487. }
  488. QStringList list;
  489. item->scheme().items(list);
  490. if (list.count() == 0) {
  491. current = m->parent(current);
  492. item = m->item(current);
  493. if (!item) return;
  494. item->scheme().items(list);
  495. }
  496. Fb2NodeDlg dlg(this, item->scheme(), list);
  497. if (dlg.exec()) {
  498. current = m->append(current, dlg.value());
  499. if (current.isValid()) setCurrentIndex(current);
  500. }
  501. }
  502. void Fb2HeadView::removeNode()
  503. {
  504. Fb2HeadModel * m = qobject_cast<Fb2HeadModel*>(model());
  505. if (m) m->remove(currentIndex());
  506. }
  507. QModelIndex Fb2HeadModel::append(const QModelIndex &parent, const QString &name)
  508. {
  509. Fb2HeadItem * owner = item(parent);
  510. if (!owner) return QModelIndex();
  511. int row = owner->count();
  512. beginInsertRows(parent, row, row);
  513. Fb2HeadItem * item = owner->append(name);
  514. endInsertRows();
  515. return createIndex(row, 0, (void*)item);
  516. }
  517. void Fb2HeadModel::remove(const QModelIndex &index)
  518. {
  519. int r = index.row();
  520. QModelIndex p = parent(index);
  521. beginRemoveRows(p, r, r + 1);
  522. Fb2HeadItem * i = item(p);
  523. if (i) i->remove(r);
  524. endRemoveRows();
  525. }
  526. //---------------------------------------------------------------------------
  527. // Fb2NodeDlg
  528. //---------------------------------------------------------------------------
  529. Fb2NodeDlg::Fb2NodeDlg(QWidget *parent, Fb2Scheme scheme, QStringList &list)
  530. : QDialog(parent)
  531. , m_scheme(scheme)
  532. {
  533. setWindowTitle(tr("Insert tag"));
  534. QGridLayout * layout = new QGridLayout(this);
  535. QLabel * label = new QLabel(this);
  536. label->setObjectName(QString::fromUtf8("label"));
  537. label->setText(tr("Tag name:"));
  538. layout->addWidget(label, 0, 0, 1, 1);
  539. m_combo = new QComboBox(this);
  540. m_combo->setEditable(true);
  541. m_combo->addItems(list);
  542. layout->addWidget(m_combo, 0, 1, 1, 1);
  543. m_text = new QLabel(this);
  544. m_text->setStyleSheet(QString::fromUtf8("border-style:outset;border-width:1px;border-radius:10px;padding:6px;"));
  545. m_text->setAlignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop);
  546. m_text->setMinimumSize(QSize(300, 100));
  547. m_text->setWordWrap(true);
  548. layout->addWidget(m_text, 1, 0, 1, 2);
  549. QDialogButtonBox * buttons = new QDialogButtonBox(this);
  550. buttons->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok);
  551. buttons->setOrientation(Qt::Horizontal);
  552. layout->addWidget(buttons, 2, 0, 1, 2);
  553. layout->setColumnStretch(1, 1);
  554. connect(m_combo, SIGNAL(editTextChanged(QString)), SLOT(comboChanged(QString)));
  555. connect(buttons, SIGNAL(accepted()), SLOT(accept()));
  556. connect(buttons, SIGNAL(rejected()), SLOT(reject()));
  557. if (list.count()) {
  558. m_combo->setCurrentIndex(0);
  559. comboChanged(list.first());
  560. }
  561. }
  562. void Fb2NodeDlg::comboChanged(const QString &text)
  563. {
  564. m_text->setText(m_scheme.element(text).info());
  565. }
  566. QString Fb2NodeDlg::value() const
  567. {
  568. return m_combo->currentText();
  569. }