00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "kdevdocumentationplugin.h"
00020
00021 #include <qfile.h>
00022 #include <qpainter.h>
00023 #include <qstyle.h>
00024 #include <qheader.h>
00025 #include <qtextstream.h>
00026
00027 #include <kstandarddirs.h>
00028 #include <kiconloader.h>
00029 #include <kconfig.h>
00030 #include <kdebug.h>
00031 #include <kdirwatch.h>
00032 #include <klocale.h>
00033
00034
00035
00036 DocumentationItem::DocumentationItem(DocumentationItem::Type type, KListView *parent,
00037 const QString &name)
00038 :KListViewItem(parent, name), m_type(type)
00039 {
00040 init();
00041 }
00042
00043 DocumentationItem::DocumentationItem(DocumentationItem::Type type, KListViewItem *parent,
00044 const QString &name)
00045 :KListViewItem(parent, name), m_type(type)
00046 {
00047 init();
00048 }
00049
00050 DocumentationItem::DocumentationItem(DocumentationItem::Type type, KListView *parent,
00051 KListViewItem *after, const QString &name)
00052 :KListViewItem(parent, after, name), m_type(type)
00053 {
00054 init();
00055 }
00056
00057 DocumentationItem::DocumentationItem(DocumentationItem::Type type, KListViewItem * parent,
00058 KListViewItem * after, const QString & name )
00059 :KListViewItem(parent, after, name), m_type(type)
00060 {
00061 init();
00062 }
00063
00064
00065 void DocumentationItem::init( )
00066 {
00067 QString icon;
00068
00069 switch (m_type)
00070 {
00071 case Collection:
00072 case Catalog:
00073 icon = "folder";
00074 break;
00075 case Book:
00076 icon = "contents";
00077 break;
00078 default:
00079 icon = "document";
00080 }
00081
00082 setPixmap(0, SmallIcon(icon));
00083 }
00084
00085
00086
00087
00088
00089
00090
00091
00092 DocumentationCatalogItem::DocumentationCatalogItem(DocumentationPlugin* plugin,
00093 KListView *parent, const QString &name)
00094 :DocumentationItem(DocumentationItem::Catalog, parent, name), m_plugin(plugin),
00095 isLoaded(false), isActivated(false), m_isProjectDocumentationItem(false)
00096 {
00097 setExpandable(true);
00098 m_plugin->addCatalog(this);
00099 }
00100
00101 DocumentationCatalogItem::DocumentationCatalogItem(DocumentationPlugin* plugin,
00102 DocumentationItem *parent, const QString &name)
00103 :DocumentationItem(DocumentationItem::Catalog, parent, name), m_plugin(plugin),
00104 isLoaded(false), isActivated(false), m_isProjectDocumentationItem(false)
00105 {
00106 setExpandable(true);
00107 m_plugin->addCatalog(this);
00108 }
00109
00110 DocumentationCatalogItem::~ DocumentationCatalogItem( )
00111 {
00112 m_plugin->clearCatalog(this);
00113 }
00114
00115 void DocumentationCatalogItem::setOpen(bool o)
00116 {
00117 if (o)
00118 {
00119 load();
00120 }
00121 DocumentationItem::setOpen(o);
00122 }
00123
00124 void DocumentationCatalogItem::load()
00125 {
00126 if(isLoaded)
00127 return;
00128
00129 plugin()->createTOC(this);
00130 isLoaded = true;
00131 }
00132
00133 void DocumentationCatalogItem::activate()
00134 {
00135 if (!isActivated)
00136 {
00137 plugin()->setCatalogURL(this);
00138 isActivated = true;
00139 }
00140 DocumentationItem::activate();
00141 }
00142
00143
00144
00145
00146
00147
00148 IndexItemProto::IndexItemProto(DocumentationPlugin *plugin, DocumentationCatalogItem *catalog,
00149 IndexBox *listbox, const QString &text, const QString &description)
00150 : m_listbox(listbox), m_text(text), m_description(description)
00151 {
00152 plugin->indexes[catalog].append(this);
00153 m_listbox->addIndexItem(this);
00154 }
00155
00156 IndexItemProto::~IndexItemProto()
00157 {
00158 m_listbox->removeIndexItem(this);
00159 }
00160
00161
00162
00163
00164 IndexItem::IndexItem(IndexBox *listbox, const QString &text)
00165 :QListBoxText(listbox, text), m_listbox(listbox)
00166 {
00167 }
00168
00169 IndexItem::List IndexItem::urls() const
00170 {
00171 List urlList;
00172 QValueList<IndexItemProto*> itemProtos = m_listbox->items[text()];
00173 for (QValueList<IndexItemProto*>::const_iterator it = itemProtos.begin();
00174 it != itemProtos.end(); ++it)
00175 urlList.append(qMakePair((*it)->description(), (*it)->url()));
00176 return urlList;
00177 }
00178
00179
00180
00181
00182
00183
00184 ConfigurationItem::ConfigurationItem(QListView *parent, const QString &title, const QString &url,
00185 bool indexPossible, bool fullTextSearchPossible)
00186 :QCheckListItem(parent, "", QCheckListItem::CheckBox), m_title(title), m_url(url),
00187 m_origTitle(title), m_contents(true), m_index(false), m_fullTextSearch(false),
00188 m_indexPossible(indexPossible), m_fullTextSearchPossible(fullTextSearchPossible)
00189 {
00190 setText(3, m_title);
00191 setText(4, m_url);
00192 }
00193
00194 void ConfigurationItem::paintCell(QPainter *p, const QColorGroup &cg, int column,
00195 int width, int align)
00196 {
00197 if ( (column == 0) || (column == 1) || (column == 2) )
00198 {
00199 if ( !p )
00200 return;
00201
00202 QListView *lv = listView();
00203 if ( !lv )
00204 return;
00205
00206 const BackgroundMode bgmode = lv->viewport()->backgroundMode();
00207 const QColorGroup::ColorRole crole = QPalette::backgroundRoleFromMode( bgmode );
00208 p->fillRect(0, 0, width, height(), cg.brush(crole));
00209
00210 QFontMetrics fm(lv->fontMetrics());
00211 int boxsize = lv->style().pixelMetric(QStyle::PM_CheckListButtonSize, lv);
00212 int marg = lv->itemMargin();
00213 int styleflags = QStyle::Style_Default;
00214
00215 if (((column == 0) && m_contents) || ((column == 1) && m_index) || ((column == 2) && m_fullTextSearch))
00216 styleflags |= QStyle::Style_On;
00217 else
00218 styleflags |= QStyle::Style_Off;
00219 if ((column == 0) || ((column == 1) && m_indexPossible) || ((column == 2) && m_fullTextSearchPossible))
00220 styleflags |= QStyle::Style_Enabled;
00221
00222 int x = 0;
00223 int y = 0;
00224 x += 3;
00225 if (align & AlignVCenter)
00226 y = ((height() - boxsize) / 2) + marg;
00227 else
00228 y = (fm.height() + 2 + marg - boxsize) / 2;
00229
00230 QStyleOption opt(this);
00231 lv->style().drawPrimitive(QStyle::PE_CheckListIndicator, p,
00232 QRect(x, y, boxsize, fm.height() + 2 + marg), cg, styleflags, opt);
00233
00234 return;
00235 }
00236 QListViewItem::paintCell(p, cg, column, width, align);
00237 }
00238
00239 int ConfigurationItem::width(const QFontMetrics &fm, const QListView *lv, int c) const
00240 {
00241 if ((c == 0) || (c == 1) || (c == 2))
00242 return lv->style().pixelMetric(QStyle::PM_CheckListButtonSize, lv) + 4;
00243 return QListViewItem::width(fm, lv, c);
00244 }
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255 DocumentationPlugin::DocumentationPlugin(KConfig *pluginConfig, QObject *parent, const char *name)
00256 :QObject(parent, name), config(pluginConfig), m_indexCreated(false)
00257 {
00258 }
00259
00260 DocumentationPlugin::~DocumentationPlugin()
00261 {
00262 }
00263
00264 void DocumentationPlugin::autoSetup()
00265 {
00266 config->setGroup("General");
00267 if ( ! config->readBoolEntry("Autosetup", false) )
00268 {
00269 autoSetupPlugin();
00270 config->setGroup("General");
00271 config->writeEntry("Autosetup", true);
00272 config->sync();
00273 }
00274 }
00275
00276 void DocumentationPlugin::reload()
00277 {
00278 clear();
00279 for (QValueList<DocumentationCatalogItem *>::iterator it = catalogs.begin();
00280 it != catalogs.end(); ++it)
00281 {
00282 createTOC(*it);
00283 }
00284 }
00285
00286 void DocumentationPlugin::clear()
00287 {
00288 for (QValueList<DocumentationCatalogItem *>::iterator it = catalogs.begin();
00289 it != catalogs.end(); ++it)
00290 {
00291 clearCatalog(*it);
00292 }
00293 }
00294
00295 void DocumentationPlugin::clearCatalog(DocumentationCatalogItem *item)
00296 {
00297
00298 for (QMap<QString, DocumentationCatalogItem*>::iterator it = namedCatalogs.begin();
00299 it != namedCatalogs.end(); ++it)
00300 {
00301 if (it.data() == item)
00302 namedCatalogs.remove(it);
00303 }
00304
00305 QValueList<IndexItemProto *> idx = indexes[item];
00306 for (QValueList<IndexItemProto *>::iterator it = idx.begin(); it != idx.end(); ++it)
00307 {
00308 delete *it;
00309 }
00310 indexes.remove(item);
00311
00312
00313 catalogs.remove(item);
00314 }
00315
00316 void DocumentationPlugin::createIndex(IndexBox *index)
00317 {
00318 if (m_indexCreated)
00319 return;
00320
00321 for (QValueList<DocumentationCatalogItem *>::iterator it = catalogs.begin();
00322 it != catalogs.end(); ++it)
00323 {
00324 loadIndex(index, *it);
00325 }
00326 m_indexCreated = true;
00327 }
00328
00329 void DocumentationPlugin::cacheIndex(DocumentationCatalogItem *item)
00330 {
00331 kdDebug() << "Creating index cache for " << item->text(0) << endl;
00332
00333 QString cacheName = locateLocal("data", QString("kdevdocumentation/index/cache_") + item->text(0));
00334 QFile cacheFile(cacheName);
00335 if (!cacheFile.open(IO_WriteOnly))
00336 return;
00337
00338 QTextStream str(&cacheFile);
00339 str.setEncoding(QTextStream::Unicode);
00340 str << CACHE_VERSION << endl;
00341
00342 QValueList<IndexItemProto*> catalogIndexes = indexes[item];
00343 for (QValueList<IndexItemProto*>::const_iterator it = catalogIndexes.constBegin();
00344 it != catalogIndexes.constEnd(); ++it)
00345 {
00346 str << (*it)->text() << endl;
00347 str << (*it)->description() << endl;
00348 str << (*it)->url().url() << endl;
00349 }
00350
00351 cacheFile.close();
00352 }
00353
00354 bool DocumentationPlugin::loadCachedIndex(IndexBox *index, DocumentationCatalogItem *item)
00355 {
00356 QString cacheName = locateLocal("data", QString("kdevdocumentation/index/cache_") + item->text(0));
00357 QFile cacheFile(cacheName);
00358 if (!cacheFile.open(IO_ReadOnly))
00359 return false;
00360
00361 kdDebug() << "Using cached index for item: " << item->text(0) << endl;
00362
00363 QTextStream str(&cacheFile);
00364 str.setEncoding(QTextStream::Unicode);
00365 QString cache = str.read();
00366 QStringList cacheList = QStringList::split("\n", cache, true);
00367 QString ver = cacheList.first();
00368 if (ver != CACHE_VERSION)
00369 {
00370 kdDebug() << "Wrong cache version: " << ver << endl;
00371 return false;
00372 }
00373 QStringList::const_iterator it = cacheList.begin();
00374 it++;
00375 QString s[3]; int c = 0;
00376 for (; it != cacheList.end(); ++it)
00377 {
00378 s[c] = *it;
00379 if (c == 2)
00380 {
00381 IndexItemProto *ii = new IndexItemProto(this, item, index, s[0], s[1]);
00382 ii->addURL(KURL(s[2]));
00383 c = 0;
00384 }
00385 else c++;
00386 }
00387 cacheFile.close();
00388
00389 return true;
00390 }
00391
00392 void DocumentationPlugin::addCatalog(DocumentationCatalogItem *item)
00393 {
00394 catalogs.append(item);
00395 namedCatalogs[item->text(0)] = item;
00396
00397 }
00398
00399 void DocumentationPlugin::addCatalogConfiguration(KListView *configurationView,
00400 const QString &title, const QString &url)
00401 {
00402 new ConfigurationItem(configurationView, title, url,
00403 hasCapability(Index), hasCapability(FullTextSearch));
00404 }
00405
00406 void DocumentationPlugin::editCatalogConfiguration(ConfigurationItem *configurationItem,
00407 const QString &title, const QString &url)
00408 {
00409 configurationItem->setTitle(title);
00410 configurationItem->setURL(url);
00411 }
00412
00413 void DocumentationPlugin::deleteCatalogConfiguration(const ConfigurationItem *const configurationItem)
00414 {
00415 deletedConfigurationItems << configurationItem->title();
00416 }
00417
00418 void DocumentationPlugin::clearCatalogIndex(DocumentationCatalogItem *item)
00419 {
00420
00421 QValueList<IndexItemProto *> idx = indexes[item];
00422 for (QValueList<IndexItemProto *>::iterator it = idx.begin(); it != idx.end(); ++it)
00423 {
00424 delete *it;
00425 }
00426 indexes.remove(item);
00427 }
00428
00429 void DocumentationPlugin::loadIndex(IndexBox *index, DocumentationCatalogItem *item)
00430 {
00431 if (!indexEnabled(item))
00432 return;
00433 if (!needRefreshIndex(item) && loadCachedIndex(index, item))
00434 return;
00435 createIndex(index, item);
00436 cacheIndex(item);
00437 }
00438
00439 void DocumentationPlugin::init(KListView *contents)
00440 {
00441 config->setGroup("Locations");
00442 QMap<QString, QString> entryMap = config->entryMap("Locations");
00443
00444 for (QMap<QString, QString>::const_iterator it = entryMap.begin();
00445 it != entryMap.end(); ++it)
00446 {
00447 if (catalogEnabled(it.key()))
00448 createCatalog(contents, it.key(), config->readPathEntry(it.key()));
00449 }
00450 }
00451
00452 void DocumentationPlugin::reinit(KListView *contents, IndexBox *index, QStringList restrictions)
00453 {
00454 config->setGroup("Locations");
00455 QMap<QString, QString> entryMap = config->entryMap("Locations");
00456
00457
00458 for (QStringList::const_iterator it = deletedConfigurationItems.constBegin();
00459 it != deletedConfigurationItems.constEnd(); ++it)
00460 {
00461 if (namedCatalogs.contains(*it))
00462 delete namedCatalogs[*it];
00463 }
00464 deletedConfigurationItems.clear();
00465
00466
00467 for (QMap<QString, QString>::const_iterator it = entryMap.begin();
00468 it != entryMap.end(); ++it)
00469 {
00470 config->setGroup("Locations");
00471 if (restrictions.contains(it.key()) || (!catalogEnabled(it.key())))
00472 {
00473 if (namedCatalogs.contains(it.key()))
00474 delete namedCatalogs[it.key()];
00475 }
00476 else
00477 {
00478 kdDebug() << "updating 1" << endl;
00479 if (!namedCatalogs.contains(it.key()))
00480 {
00481 DocumentationCatalogItem * item = createCatalog(contents, it.key(), config->readPathEntry(it.key()));
00482 loadIndex(index, item);
00483 index->setDirty(true);
00484
00485 }
00486 else if (!indexEnabled(namedCatalogs[it.key()]))
00487 {
00488 kdDebug() << " updating: clearCatalogIndex" << endl;
00489 clearCatalogIndex(namedCatalogs[it.key()]);
00490 }
00491 else if ( (indexEnabled(namedCatalogs[it.key()]))
00492 && (!indexes.contains(namedCatalogs[it.key()])) )
00493 {
00494 kdDebug() << " index requested " << endl;
00495 loadIndex(index, namedCatalogs[it.key()]);
00496 index->setDirty(true);
00497 }
00498 m_indexCreated = true;
00499 }
00500 }
00501 }
00502
00503 void DocumentationPlugin::loadCatalogConfiguration(KListView *configurationView)
00504 {
00505 config->setGroup("Locations");
00506 QMap<QString, QString> entryMap = config->entryMap("Locations");
00507
00508 for (QMap<QString, QString>::const_iterator it = entryMap.begin();
00509 it != entryMap.end(); ++it)
00510 {
00511 if (namedCatalogs.contains(it.key())
00512 && namedCatalogs[it.key()]->isProjectDocumentationItem())
00513 continue;
00514
00515 ConfigurationItem *item = new ConfigurationItem(configurationView, it.key(), it.data(),
00516 hasCapability(Index), hasCapability(FullTextSearch));
00517 config->setGroup("TOC Settings");
00518 item->setContents(config->readBoolEntry(item->title(), true));
00519 config->setGroup("Index Settings");
00520 item->setIndex(config->readBoolEntry(item->title(), false));
00521 config->setGroup("Search Settings");
00522 item->setFullTextSearch(config->readBoolEntry(item->title(), false));
00523 }
00524 }
00525
00526 void DocumentationPlugin::saveCatalogConfiguration(KListView *configurationView)
00527 {
00528 config->setGroup("Locations");
00529
00530 for (QStringList::const_iterator it = deletedConfigurationItems.constBegin();
00531 it != deletedConfigurationItems.constEnd(); ++it)
00532 {
00533 config->deleteEntry(*it);
00534 }
00535
00536 QListViewItemIterator it(configurationView);
00537 while (it.current())
00538 {
00539 config->setGroup("Locations");
00540 ConfigurationItem *confItem = dynamic_cast<ConfigurationItem*>(it.current());
00541 if (confItem->isChanged())
00542 config->deleteEntry(confItem->origTitle());
00543 config->writePathEntry(confItem->title(), confItem->url());
00544
00545 config->setGroup("TOC Settings");
00546 if (confItem->isChanged())
00547 config->deleteEntry(confItem->origTitle());
00548 config->writeEntry(confItem->title(), confItem->contents());
00549
00550 config->setGroup("Index Settings");
00551 if (confItem->isChanged())
00552 config->deleteEntry(confItem->origTitle());
00553 config->writeEntry(confItem->title(), confItem->index());
00554
00555 config->setGroup("Search Settings");
00556 if (confItem->isChanged())
00557 config->deleteEntry(confItem->origTitle());
00558 config->writeEntry(confItem->title(), confItem->fullTextSearch());
00559
00560 ++it;
00561 }
00562 config->sync();
00563 }
00564
00565 void DocumentationPlugin::setIndexEnabled( DocumentationCatalogItem * item, bool e )
00566 {
00567 QString group = config->group();
00568 config->setGroup("Index Settings");
00569 config->writeEntry(item->text(0), e);
00570 config->setGroup(group);
00571 }
00572
00573 bool DocumentationPlugin::indexEnabled( DocumentationCatalogItem * item ) const
00574 {
00575 QString group = config->group();
00576 config->setGroup("Index Settings");
00577 bool b = config->readBoolEntry(item->text(0), false);
00578 config->setGroup(group);
00579 return b;
00580 }
00581
00582 bool DocumentationPlugin::catalogEnabled(const QString &name) const
00583 {
00584 QString group = config->group();
00585 config->setGroup("TOC Settings");
00586 bool b = config->readBoolEntry(name, true);
00587 config->setGroup(group);
00588 return b;
00589 }
00590
00591 void DocumentationPlugin::setCatalogEnabled(const QString &name, bool e)
00592 {
00593 QString group = config->group();
00594 config->setGroup("TOC Settings");
00595 config->writeEntry(name, e);
00596 config->setGroup(group);
00597 }
00598
00599
00600
00601
00602
00603
00604 IndexBox::IndexBox(QWidget *parent, const char *name)
00605 :KListBox(parent, name), m_dirty(false)
00606 {
00607 }
00608
00609 void IndexBox::addIndexItem(IndexItemProto *item)
00610 {
00611 items[item->text()].append(item);
00612 }
00613
00614 void IndexBox::removeIndexItem(IndexItemProto *item)
00615 {
00616 QString text = item->text();
00617 items[text].remove(item);
00618 if (items[text].count() == 0)
00619 {
00620 items.remove(text);
00621 QListBoxItem *item = findItem(text, Qt::CaseSensitive | Qt::ExactMatch);
00622 if (item)
00623 delete item;
00624 }
00625 }
00626
00627 void IndexBox::fill()
00628 {
00629 for (QMap<QString, QValueList<IndexItemProto*> >::const_iterator it = items.begin();
00630 it != items.end(); ++it)
00631 {
00632 new IndexItem(this, it.key());
00633 }
00634 }
00635
00636 void IndexBox::setDirty(bool dirty)
00637 {
00638 m_dirty = dirty;
00639 }
00640
00641 void IndexBox::refill()
00642 {
00643 if (m_dirty)
00644 {
00645 clear();
00646 fill();
00647 setDirty(false);
00648 }
00649 }
00650
00651
00652 ProjectDocumentationPlugin::ProjectDocumentationPlugin(DocumentationPlugin *docPlugin, DocumentationPlugin::ProjectDocType type)
00653 :QObject(0, 0), m_docPlugin(docPlugin), m_catalog(0), m_type(type), m_contents(0), m_index(0)
00654 {
00655 kdDebug() << "ProjectDocumentationPlugin::ProjectDocumentationPlugin for type " << type << endl;
00656
00657 m_watch = new KDirWatch(this);
00658 connect(m_watch, SIGNAL(dirty(const QString&)), this, SLOT(reinit()));
00659 m_watch->startScan();
00660 }
00661
00662 ProjectDocumentationPlugin::~ProjectDocumentationPlugin()
00663 {
00664 deinit();
00665 }
00666
00667 void ProjectDocumentationPlugin::init(KListView *contents, IndexBox *index, const QString &url)
00668 {
00669 m_contents = contents;
00670 m_index = index;
00671 m_url = url;
00672
00673 if (m_catalog)
00674 deinit();
00675 m_catalog = m_docPlugin->createCatalog(contents,
00676 m_type == DocumentationPlugin::APIDocs ? i18n("Project API Documentation")
00677 : i18n("Project User Manual"), url);
00678 if (m_catalog)
00679 {
00680 m_catalog->setProjectDocumentationItem(true);
00681 m_watch->addFile(url);
00682 }
00683 }
00684
00685 void ProjectDocumentationPlugin::reinit()
00686 {
00687 deinit();
00688 if (m_contents != 0 && m_index != 0 && m_url != 0)
00689 init(m_contents, m_index, m_url);
00690 }
00691
00692 void ProjectDocumentationPlugin::deinit()
00693 {
00694 m_watch->removeFile(m_url);
00695 delete m_catalog;
00696 m_catalog = 0;
00697 }
00698
00699 QString ProjectDocumentationPlugin::pluginName() const
00700 {
00701 return m_docPlugin->pluginName();
00702 }
00703
00704 QString ProjectDocumentationPlugin::catalogURL() const
00705 {
00706 return m_url;
00707 }
00708
00709 #include "kdevdocumentationplugin.moc"