1 /***************************************************************************
2 * Copyright (C) 2004 by Kita Developers *
3 * ikemo@users.sourceforge.jp *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
13 #include <QtCore/QFile>
14 #include <QtCore/QRegExp>
15 #include <QtCore/QStringList>
16 #include <QtCore/QTextCodec>
17 #include <QtGui/QApplication>
18 #include <QtGui/QClipboard>
19 #include <QtGui/QHBoxLayout>
20 #include <QtGui/QHeaderView>
21 #include <QtGui/QMouseEvent>
22 #include <QtGui/QSpacerItem>
23 #include <QtGui/QTreeWidget>
24 #include <QtGui/QVBoxLayout>
27 #include <kcombobox.h>
31 #include <kmessagebox.h>
33 #include <kstandarddirs.h>
34 #include <kio/netaccess.h>
36 #include "viewmediator.h"
37 #include "kitaui/listviewitem.h"
38 #include "libkita/boarddatabase.h"
39 #include "libkita/favoriteboards.h"
40 #include "libkita/globalconfig.h"
41 #include "libkita/kita_misc.h"
47 QString category_name;
48 QStringList boardNameList;
49 QStringList boardUrlList;
53 /*--------------------------------------*/
55 BBSView::BBSView(QWidget *parent) : QWidget(parent), m_favorites(0)
57 /* copied from Base class */
58 bbsViewBaseLayout = new QVBoxLayout(this);
60 layout10 = new QHBoxLayout(0);
62 searchCombo = new KComboBox(this);
63 searchCombo->setEditable(true);
64 searchCombo->setMaxCount(10);
65 layout10->addWidget(searchCombo);
66 spacer2 = new QSpacerItem(467, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
67 layout10->addItem(spacer2);
68 bbsViewBaseLayout->addLayout(layout10);
70 m_boardList = new QTreeWidget(this);
71 m_boardList->setAlternatingRowColors(true);
72 m_boardList->setRootIsDecorated(true);
73 bbsViewBaseLayout->addWidget(m_boardList);
74 resize(QSize(600, 482).expandedTo(minimumSizeHint()));
77 m_boardList->setHeaderLabel(i18nc("@title:column", "Board Name"));
78 m_boardList->header()->setClickable(false);
80 connect(m_boardList, SIGNAL(itemActivated(QTreeWidgetItem*, int)),
81 SLOT(loadBoard(QTreeWidgetItem*)));
82 connect(FavoriteBoards::getInstance(), SIGNAL(changed()),
83 SLOT(refreshFavoriteBoards()));
84 connect(searchCombo, SIGNAL(textChanged(const QString&)),
85 SLOT(filter(const QString&)));
88 = new KAction(i18nc("@action:inmenu", "Open with Web Browser"), this);
89 m_copyUrlAct = new KAction(i18nc("@action:inmenu", "Copy URL"), this);
91 = new KAction(i18nc("@action:inmenu", "Copy title and URL"), this);
92 m_removeFromFavoritesAct
93 = new KAction(i18nc("@action:inmenu", "Remove from Favorites"), this);
95 = new KAction(i18nc("@action:inmenu", "Add to Favorites"), this);
104 * download board list, parse, and write to "board_list"
106 * @see updateBoardList()
107 * @see GlobalConfig::boardListURL()
109 bool BBSView::downloadBoardList()
112 QList<QString> oldUrls;
113 QList<QString> newUrls;
116 QString url = GlobalConfig::boardListUrl();
117 if (! KIO::NetAccess::download(url, tmpFile, 0)) {
122 if (! file.open(QIODevice::ReadOnly)) {
126 QTextStream stream(&file);
127 QTextCodec* codec = QTextCodec::codecForName("Shift-JIS");
128 stream.setCodec(codec);
129 QString html = stream.readAll();
133 QList<Category> categoryList = getCategoryList(html);
134 QList<Category>::iterator it;
136 /* Are there new boards or moved boards ? */
139 for (it = categoryList.begin(); it != categoryList.end(); ++it) {
140 Category category = (*it);
141 QList<QString> board_url_list = category.boardUrlList;
142 if (board_url_list.isEmpty()) continue;
145 for (QList<QString>::iterator it2 = board_url_list.begin();
146 it2 != board_url_list.end(); ++it2) {
148 QString boardUrl = *it2;
149 QString boardName = category.boardNameList[ count ];
151 BoardDatabase db(boardUrl);
152 int ret = db.recordBoard(
153 boardName, oldUrl, Board_Unknown, true /* test only */);
154 if (ret == Board_recordNew) {
155 newBoards += boardName + " ( " + category.category_name
156 + " ) " + boardUrl + '\n';
158 if (ret == Board_recordMoved) {
159 oldBoards += boardName + " ( " + category.category_name
160 + " ) " + oldUrl + " -> " + boardUrl + '\n';
168 const int maxNewBoard = 64;
170 /* show new board names */
171 if (!newBoards.isEmpty() && newBoards.count("\n") < maxNewBoard) {
173 QStringList boardList = newBoards.split('\n');
174 KMessageBox::informationList(this,
175 i18n("New boards:\n\n"),
179 /* show moved board names */
180 if (!oldBoards.isEmpty()) {
182 QStringList boardList = oldBoards.split('\n');
183 KMessageBox::informationList(this,
184 i18n("These boards were moved:\n\n")
185 + i18n("\nPlease create the backup of those caches.\n"),
189 if ((!newBoards.isEmpty() && newBoards.count("\n") < maxNewBoard)
190 || !oldBoards.isEmpty()) {
192 int ret = KMessageBox::warningYesNoCancel(this,
193 i18n("Do you really want to update board list?"), "kita",
194 KStandardGuiItem::yes(),
195 KGuiItem(i18nc("@action:button", "Copy")),
196 KStandardGuiItem::cancel(),
197 QString(), KMessageBox::Dangerous);
198 if (ret == KMessageBox::No) {
199 QString str = i18n("New boards:\n\n") + newBoards + '\n'
200 + i18n("These boards were moved:\n\n") + oldBoards;
201 QApplication::clipboard() ->setText(str , QClipboard::Clipboard);
202 QApplication::clipboard() ->setText(str , QClipboard::Selection);
203 } else if (ret == KMessageBox::Cancel)
205 } else if (newBoards.isEmpty() && oldBoards.isEmpty()) {
207 KMessageBox::information(this, i18n("no new boards"), "Kita");
211 // if moved URL exists. move files.
212 for(int i=0; i<oldUrls.count(); i++) {
213 QString oldUrl = oldUrls[ i ];
214 QString newUrl = newUrls[ i ];
216 // qDebug("move %s -> %s", oldURL.latin1(), newURL.latin1());
218 db.moveBoard(oldUrl, newUrl);
222 QString configPath = KStandardDirs::locateLocal("appdata", "board_list");
223 KConfig config(configPath);
224 for (it = categoryList.begin(); it != categoryList.end(); ++it) {
225 Category category = (*it);
226 QList<QString> board_url_list = category.boardUrlList;
228 if (board_url_list.isEmpty()) {
232 list << category.category_name;
233 KConfigGroup group = config.group(category.category_name);
235 QList<QString>::iterator it2;
237 for (it2 = board_url_list.begin(); it2 != board_url_list.end(); ++it2) {
238 QString key = QString("item%1").arg(count);
239 QString boardUrl = *it2;
240 QString boardName = category.boardNameList[ count ];
243 tmpList << boardName;
244 group.writeEntry(key, tmpList);
248 KConfigGroup group2 = config.group("");
249 group2.writeEntry("Categories", list);
251 QDateTime now = QDateTime::currentDateTime();
252 QString logPath = KStandardDirs::locateLocal("appdata", "board_log.txt");
254 QFile logfile(logPath);
255 if (logfile.open(QIODevice::WriteOnly | QIODevice::Append)) {
256 QTextStream stream(&logfile);
257 stream.setCodec("UTF-8");
259 stream << "Date: " << now.toString("yyyy/MM/dd hh:mm:ss") << endl;
260 stream << i18n("New boards:\n\n") + newBoards + '\n';
261 stream << i18n("These boards were moved:\n\n") + oldBoards;
262 stream << "----------------------------------------" << endl;
269 void BBSView::updateBoardList()
271 if (downloadBoardList()) showBoardList();
275 * parse "board list" and show board list. don't download.
277 * @see updateBoardList()
278 * @see downloadBoardList()
280 void BBSView::showBoardList()
283 m_boardList->clear();
286 QString configPath = KStandardDirs::locateLocal("appdata", "board_list");
287 KConfig config(configPath);
288 QStringList category_list = config.group("").readEntry("Categories", QStringList());
290 ListViewItem* previous = 0;
291 QStringList::iterator it;
292 for (it = category_list.begin(); it != category_list.end(); ++it) {
293 QString category = (*it);
295 KConfigGroup group = config.group(category);
296 ListViewItem* categoryItem = new ListViewItem(
297 m_boardList, previous, QStringList(category));
298 ListViewItem* previousBoard = 0;
300 for (int count = 0; ; count++) {
301 QString key = QString("item%1").arg(count);
302 QStringList value = group.readEntry(key, QStringList());
303 if (value.count() != 2) {
306 QString boardUrl = value[ 0 ];
307 QString boardName = value[ 1 ];
308 if (boardUrl.contains('/') != 4 || boardUrl.right(1) != "/") {
309 // OK: http://pc8.2ch.net/linux/
310 // NG: http://www.machi.to/
311 // NG: http://find.2ch.net/enq/board.php
315 BoardDatabase db(boardUrl);
316 db.recordBoard(boardName, oldUrl);
318 previousBoard = new ListViewItem(categoryItem, previousBoard,
319 QStringList() << boardName << boardUrl);
321 previous = categoryItem;
324 QString boardUrl = "http://jbbs.livedoor.jp/computer/18420/" ;
325 QString boardName = i18nc("@item:inlistbox", "Kita Board");
327 new ListViewItem(m_boardList, 0, QStringList() << boardName << boardUrl);
328 BoardDatabase db(boardUrl);
329 db.recordBoard(boardName, oldUrl);
332 refreshFavoriteBoards();
337 void BBSView::loadExtBoard()
339 QString configPath = KStandardDirs::locateLocal("appdata", "extbrd.conf");
340 QFile file(configPath);
341 if (file.open(QIODevice::ReadOnly)) {
343 QTextStream stream(&file);
344 stream.setCodec("UTF-8");
349 ListViewItem* categoryItem
350 = new ListViewItem(m_boardList, QStringList("extboard"));
352 while (!(str = stream.readLine()).isEmpty()) {
353 if (! str.isEmpty()) {
354 QStringList list = str.split("<>");
356 if (list.size() == 2 || list.size() == 3) {
357 QString board_title = list[ 0 ];
358 QString board_url = list[ 1 ];
360 new ListViewItem(categoryItem,
361 QStringList() << board_title << board_url);
363 int type = Board_Unknown;
364 if (list.size() == 3) type = list[ 2 ].toInt();
365 BoardDatabase db(board_url);
366 db.recordBoard(board_title, oldUrl, type);
375 QList<Category> BBSView::getCategoryList(const QString& html) const
377 QList<Category> result;
379 QStringList lines = html.split('\n');
380 QStringList::iterator it;
382 Category current_category;
383 current_category.category_name.clear();
384 current_category.boardNameList.clear();
385 current_category.boardUrlList.clear();
386 for (it = lines.begin(); it != lines.end(); ++it) {
387 QString category_name = getCategory(*it);
388 if (!category_name.isEmpty()) {
389 if (!current_category.category_name.isEmpty()) {
390 result.append(current_category);
392 current_category.category_name = category_name;
393 current_category.boardNameList.clear();
394 current_category.boardUrlList.clear();
396 QRegExp regexp("<A HREF=([^ ]*).*>(.*)</A>", Qt::CaseInsensitive);
397 if (regexp.indexIn((*it)) != -1) {
398 QString url = regexp.cap(1);
399 QString title = regexp.cap(2);
401 && !current_category.category_name.isEmpty()) {
402 current_category.boardNameList.append(title);
403 current_category.boardUrlList.append(url);
411 void BBSView::refreshFavoriteBoards()
414 m_favorites = new ListViewItem(m_boardList, 0,
415 QStringList(i18nc("@item:inlistbox", "Favorites")));
420 QTreeWidgetItem* child = m_favorites->child(0);
422 } while (m_favorites->childCount() != 0);
425 QList<KUrl> boards = FavoriteBoards::boards();
426 QList<KUrl>::iterator it;
428 for (it = boards.begin(); it != boards.end(); ++it) {
429 QString boardUrl = (*it).prettyUrl();
430 BoardDatabase db(boardUrl);
431 QString boardName = db.boardName();
433 new ListViewItem(m_favorites, QStringList() << boardName << boardUrl);
437 void BBSView::loadBoard(QTreeWidgetItem* item)
441 QString boardName = item->text(0);
442 BoardDatabase db(item->text(1));
443 QString boardUrl = db.boardUrl();
445 if (boardUrl.isEmpty()) {
449 ViewMediator::getInstance()->openBoard(boardUrl);
452 void BBSView::setFont(const QFont& font)
454 m_boardList->setFont(font);
455 searchCombo->setFont(font);
458 /* public slot :*/ /* virtual */
459 void BBSView::setFocus()
461 m_boardList->setFocus();
464 void BBSView::slotMenuOpenWithBrowser()
466 QTreeWidgetItem* item = m_boardList->currentItem();
467 QString boardName = item->text(0);
468 KUrl boardUrl = item->text(1);
469 KRun::runUrl(boardUrl, "text/html", 0);
472 void BBSView::contextMenuEvent(QContextMenuEvent* e)
474 QPoint point = e->globalPos();
475 QTreeWidgetItem* item
476 = m_boardList->itemAt(m_boardList->viewport()->mapFromGlobal(point));
482 popup.addAction(m_openWithBrowserAct);
483 popup.addAction(m_copyUrlAct);
484 popup.addAction(m_copyTitleAndUrlAct);
485 item->parent() == m_favorites ? popup.addAction(m_removeFromFavoritesAct)
486 : popup.addAction(m_addToFavoritesAct);
488 QString boardName = item->text(0);
489 KUrl boardUrl = item->text(1);
490 BoardDatabase db(boardUrl);
491 KUrl boardUrl_upToDate = db.boardUrl();
492 QClipboard* clipboard = QApplication::clipboard();
494 QAction *action = popup.exec(point);
495 if (action == m_openWithBrowserAct) {
496 KRun::runUrl(boardUrl, "text/html", this);
497 } else if (action == m_copyUrlAct) {
498 clipboard->setText(boardUrl_upToDate.prettyUrl(),
499 QClipboard::Clipboard);
500 clipboard->setText(boardUrl_upToDate.prettyUrl(),
501 QClipboard::Selection);
502 } else if (action == m_copyTitleAndUrlAct) {
503 clipboard->setText(boardName + '\n' + boardUrl_upToDate.prettyUrl(),
504 QClipboard::Clipboard);
505 clipboard->setText(boardName + '\n' + boardUrl_upToDate.prettyUrl(),
506 QClipboard::Selection);
507 } else if (action == m_addToFavoritesAct) {
508 FavoriteBoards::append(boardUrl_upToDate);
509 } else if (action == m_removeFromFavoritesAct) {
510 FavoriteBoards::remove(boardUrl);
514 void BBSView::loadOpened()
517 = KStandardDirs::locateLocal("appdata", "board_state.conf");
518 KConfig config(configPath);
520 QStringList openedList
521 = config.group("").readEntry("Opened", QStringList());
523 for (int i = 0, j = m_boardList->topLevelItemCount(); i < j; i++) {
524 QTreeWidgetItem* item = m_boardList->topLevelItem(i);
525 QString categoryName = item->text(0);
526 if (openedList.indexOf(categoryName) != -1) {
527 item->setExpanded(true);
532 void BBSView::saveOpened()
534 QStringList openedList;
535 for (int i = 0, j = m_boardList->topLevelItemCount(); i < j; i++) {
536 QTreeWidgetItem* item = m_boardList->topLevelItem(i);
537 QString categoryName = item->text(0);
538 if (item->isExpanded()) {
539 openedList << categoryName;
543 = KStandardDirs::locateLocal("appdata", "board_state.conf");
544 KConfig config(configPath);
546 config.group("").writeEntry("Opened", openedList);
550 * search and show matched item.
552 * @param str string to search.
554 void BBSView::filter(const QString& str)
557 for (int i = 0, j = m_boardList->topLevelItemCount(); i < j; i++) {
558 bool matched = false;
559 QTreeWidgetItem* categoryItem = m_boardList->topLevelItem(i);
561 for (int k = 0, l = categoryItem->childCount(); k < l; k++) {
562 QTreeWidgetItem* boardItem = categoryItem->child(k);
563 QString boardName = boardItem->text(0);
564 if (boardName.contains(str, Qt::CaseInsensitive)) {
565 boardItem->setHidden(false);
568 boardItem->setHidden(true);
571 categoryItem->setHidden(!matched);
576 void BBSView::mousePressEvent(QMouseEvent *e)
578 QTreeWidgetItem* item = m_boardList->itemAt(e->pos());
581 QString boardName = item->text(0);
582 BoardDatabase db(item->text(1));
583 QString boardUrl = db.boardUrl();
584 if (boardUrl.isEmpty()) {
585 item->setExpanded(!item->isExpanded());
589 switch (e->button()) {
591 ViewMediator::getInstance()->openBoard(boardUrl);
594 ViewMediator::getInstance()->openBoard(boardUrl);