OSDN Git Service

f38264fc7afa879fef1fb464887cc63eba2c95e5
[kita/kita.git] / src / bbsview.cpp
1 /***************************************************************************
2  *   Copyright (C) 2004 by Kita Developers                                 *
3  *   ikemo@users.sourceforge.jp                                            *
4  *                                                                         *
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  ***************************************************************************/
10
11 #include "bbsview.h"
12
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>
25
26 #include <kaction.h>
27 #include <kcombobox.h>
28 #include <kconfig.h>
29 #include <klocale.h>
30 #include <kmenu.h>
31 #include <kmessagebox.h>
32 #include <krun.h>
33 #include <kstandarddirs.h>
34 #include <kio/netaccess.h>
35
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"
42
43 using namespace Kita;
44
45 struct Kita::Category
46 {
47     QString category_name;
48     QStringList boardNameList;
49     QStringList boardUrlList;
50 };
51
52
53 /*--------------------------------------*/
54
55 BBSView::BBSView(QWidget *parent) : QWidget(parent), m_favorites(0)
56 {
57     /* copied from Base class */
58     bbsViewBaseLayout = new QVBoxLayout(this); 
59
60     layout10 = new QHBoxLayout(0); 
61
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);
69
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()));
75     /* copy end */
76
77     m_boardList->setHeaderLabel(i18nc("@title:column", "Board Name"));
78     m_boardList->header()->setClickable(false);
79
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&)));
86
87     m_openWithBrowserAct
88         = new KAction(i18nc("@action:inmenu", "Open with Web Browser"), this);
89     m_copyUrlAct = new KAction(i18nc("@action:inmenu", "Copy URL"), this);
90     m_copyTitleAndUrlAct
91         = new KAction(i18nc("@action:inmenu", "Copy title and URL"), this);
92     m_removeFromFavoritesAct
93         = new KAction(i18nc("@action:inmenu", "Remove from Favorites"), this);
94     m_addToFavoritesAct
95         = new KAction(i18nc("@action:inmenu", "Add to Favorites"), this);
96 }
97
98 BBSView::~BBSView()
99 {
100     saveOpened();
101 }
102
103 /**
104  * download board list, parse, and write to "board_list"
105  *
106  * @see updateBoardList()
107  * @see GlobalConfig::boardListURL()
108  */
109 bool BBSView::downloadBoardList()
110 {
111     // moved urls.
112     QList<QString> oldUrls;
113     QList<QString> newUrls;
114
115     QString tmpFile;
116     QString url = GlobalConfig::boardListUrl();
117     if (! KIO::NetAccess::download(url, tmpFile, 0)) {
118         return false;
119     }
120
121     QFile file(tmpFile);
122     if (! file.open(QIODevice::ReadOnly)) {
123         return false;
124     }
125
126     QTextStream stream(&file);
127     QTextCodec* codec = QTextCodec::codecForName("Shift-JIS");
128     stream.setCodec(codec);
129     QString html = stream.readAll();
130
131     // parse
132     QStringList list;
133     QList<Category> categoryList = getCategoryList(html);
134     QList<Category>::iterator it;
135
136     /* Are there new boards or moved boards ? */
137     QString newBoards;
138     QString oldBoards;
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;
143
144         int count = 0;
145         for (QList<QString>::iterator it2 = board_url_list.begin();
146                 it2 != board_url_list.end(); ++it2) {
147
148             QString boardUrl = *it2;
149             QString boardName = category.boardNameList[ count ];
150             QString oldUrl;
151             BoardDatabase db(boardUrl);
152             int ret = db.enrollBoard(
153                     boardName, oldUrl, Board_Unknown, true /* test only */);
154             if (ret == Board_enrollNew) {
155                 newBoards += boardName + "  ( " + category.category_name
156                     + " )  " + boardUrl + '\n';
157             }
158             if (ret == Board_enrollMoved) {
159                 oldBoards += boardName + "  ( " + category.category_name
160                     + " )  " + oldUrl + "  ->  " + boardUrl + '\n';
161             }
162             count++;
163             oldUrls += oldUrl;
164             newUrls += boardUrl;
165         }
166     }
167
168     const int maxNewBoard = 64;
169
170     /* show new board names */
171     if (!newBoards.isEmpty() && newBoards.count("\n") < maxNewBoard) {
172
173         QStringList boardList = newBoards.split('\n');
174         KMessageBox::informationList(this,
175                                       i18n("New boards:\n\n"),
176                                       boardList, "Kita");
177     }
178
179     /* show moved board names */
180     if (!oldBoards.isEmpty()) {
181
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"),
186                 boardList, "Kita");
187     }
188
189     if ((!newBoards.isEmpty() && newBoards.count("\n") < maxNewBoard)
190             || !oldBoards.isEmpty()) {
191
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)
204             return false;
205     } else if (newBoards.isEmpty() && oldBoards.isEmpty()) {
206
207         KMessageBox::information(this, i18n("no new boards"), "Kita");
208         return false;
209     }
210
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 ];
215
216 //        qDebug("move %s -> %s", oldURL.latin1(), newURL.latin1());
217         BoardDatabase db;
218         db.moveBoard(oldUrl, newUrl);
219     }
220
221     /* save config */
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;
227
228         if (board_url_list.isEmpty()) {
229             continue;
230         }
231
232         list << category.category_name;
233         KConfigGroup group = config.group(category.category_name);
234
235         QList<QString>::iterator it2;
236         int count = 0;
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 ];
241             QStringList tmpList;
242             tmpList << boardUrl;
243             tmpList << boardName;
244             group.writeEntry(key, tmpList);
245             count++;
246         }
247     }
248     KConfigGroup group2 = config.group("");
249     group2.writeEntry("Categories", list);
250
251     QDateTime now = QDateTime::currentDateTime();
252     QString logPath = KStandardDirs::locateLocal("appdata", "board_log.txt");
253
254     QFile logfile(logPath);
255     if (logfile.open(QIODevice::WriteOnly | QIODevice::Append)) {
256         QTextStream stream(&logfile);
257         stream.setCodec("UTF-8");
258
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;
263         logfile.close();
264     }
265
266     return true;
267 }
268
269 void BBSView::updateBoardList()
270 {
271     if (downloadBoardList()) showBoardList();
272 }
273
274 /**
275   * parse "board list" and show board list. don't download.
276   *
277   * @see updateBoardList()
278   * @see downloadBoardList()
279   */
280 void BBSView::showBoardList()
281 {
282     // clear list
283     m_boardList->clear();
284     m_favorites = 0;
285
286     QString configPath = KStandardDirs::locateLocal("appdata", "board_list");
287     KConfig config(configPath);
288     QStringList category_list = config.group("").readEntry("Categories", QStringList());
289
290     ListViewItem* previous = 0;
291     QStringList::iterator it;
292     for (it = category_list.begin(); it != category_list.end(); ++it) {
293         QString category = (*it);
294
295         KConfigGroup group = config.group(category);
296         ListViewItem* categoryItem = new ListViewItem(
297                 m_boardList, previous, QStringList(category));
298         ListViewItem* previousBoard = 0;
299
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) {
304                 break;
305             }
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
312                 continue;
313             }
314             QString oldUrl;
315             BoardDatabase db(boardUrl);
316             db.enrollBoard(boardName, oldUrl);
317             db.loadBBSHistory();
318             previousBoard = new ListViewItem(categoryItem, previousBoard,
319                     QStringList() << boardName << boardUrl);
320         }
321         previous = categoryItem;
322     }
323
324     QString boardUrl = "http://jbbs.livedoor.jp/computer/18420/" ;
325     QString boardName = i18nc("@item:inlistbox", "Kita Board");
326     QString oldUrl;
327     new ListViewItem(m_boardList, 0, QStringList() << boardName << boardUrl);
328     BoardDatabase db(boardUrl);
329     db.enrollBoard(boardName, oldUrl);
330
331     loadExtBoard();
332     refreshFavoriteBoards();
333 }
334
335
336 /* private */
337 void BBSView::loadExtBoard()
338 {
339     QString configPath = KStandardDirs::locateLocal("appdata", "extbrd.conf");
340     QFile file(configPath);
341     if (file.open(QIODevice::ReadOnly)) {
342
343         QTextStream stream(&file);
344         stream.setCodec("UTF-8");
345
346         QStringList list;
347         QString str;
348
349         ListViewItem* categoryItem
350             = new ListViewItem(m_boardList, QStringList("extboard"));
351
352         while (!(str = stream.readLine()).isEmpty()) {
353             if (! str.isEmpty()) {
354                 QStringList list = str.split("<>");
355
356                 if (list.size() == 2 || list.size() == 3) {
357                     QString board_title = list[ 0 ];
358                     QString board_url = list[ 1 ];
359
360                     new ListViewItem(categoryItem,
361                                 QStringList() << board_title << board_url);
362                     QString oldUrl;
363                     int type = Board_Unknown;
364                     if (list.size() == 3) type = list[ 2 ].toInt();
365                     BoardDatabase db(board_url);
366                     db.enrollBoard(board_title, oldUrl, type);
367                 }
368             }
369         }
370
371         file.close();
372     }
373 }
374
375 QList<Category> BBSView::getCategoryList(const QString& html) const
376 {
377     QList<Category> result;
378
379     QStringList lines = html.split('\n');
380     QStringList::iterator it;
381
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);
391             }
392             current_category.category_name = category_name;
393             current_category.boardNameList.clear();
394             current_category.boardUrlList.clear();
395         } else {
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);
400                 if (isBoardUrl(url)
401                         && !current_category.category_name.isEmpty()) {
402                     current_category.boardNameList.append(title);
403                     current_category.boardUrlList.append(url);
404                 }
405             }
406         }
407     }
408     return result;
409 }
410
411 void BBSView::refreshFavoriteBoards()
412 {
413     if (!m_favorites) {
414         m_favorites = new ListViewItem(m_boardList, 0,
415                 QStringList(i18nc("@item:inlistbox", "Favorites")));
416     }
417
418     // remove all items.
419     do {
420         QTreeWidgetItem* child = m_favorites->child(0);
421         delete child;
422     } while (m_favorites->childCount() != 0);
423
424     // insert items.
425     QList<KUrl> boards = FavoriteBoards::boards();
426     QList<KUrl>::iterator it;
427
428     for (it = boards.begin(); it != boards.end(); ++it) {
429         QString boardUrl = (*it).prettyUrl();
430         BoardDatabase db(boardUrl);
431         QString boardName = db.boardName();
432
433         new ListViewItem(m_favorites, QStringList() << boardName << boardUrl);
434     }
435 }
436
437 void BBSView::loadBoard(QTreeWidgetItem* item)
438 {
439     if (! item) return ;
440
441     QString boardName = item->text(0);
442     BoardDatabase db(item->text(1));
443     QString boardUrl = db.boardUrl();
444
445     if (boardUrl.isEmpty()) {
446         return ;
447     }
448
449     ViewMediator::getInstance()->openBoard(boardUrl);
450 }
451
452 void BBSView::setFont(const QFont& font)
453 {
454     m_boardList->setFont(font);
455     searchCombo->setFont(font);
456 }
457
458 /* public slot :*/ /* virtual */
459 void BBSView::setFocus()
460 {
461     m_boardList->setFocus();
462 }
463
464 void BBSView::slotMenuOpenWithBrowser()
465 {
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);
470 }
471
472 void BBSView::contextMenuEvent(QContextMenuEvent* e)
473 {
474     QPoint point = e->globalPos();
475     QTreeWidgetItem* item
476         = m_boardList->itemAt(m_boardList->viewport()->mapFromGlobal(point));
477     if (item == 0) {
478         return;
479     }
480
481     KMenu popup(0);
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);
487
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();
493
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);
511     }
512 }
513
514 void BBSView::loadOpened()
515 {
516     QString configPath
517         = KStandardDirs::locateLocal("appdata", "board_state.conf");
518     KConfig config(configPath);
519
520     QStringList openedList
521         = config.group("").readEntry("Opened", QStringList());
522
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);
528         }
529     }
530 }
531
532 void BBSView::saveOpened()
533 {
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;
540         }
541     }
542     QString configPath
543         = KStandardDirs::locateLocal("appdata", "board_state.conf");
544     KConfig config(configPath);
545
546     config.group("").writeEntry("Opened", openedList);
547 }
548
549 /**
550  * search and show matched item.
551  *
552  * @param str string to search.
553  */
554 void BBSView::filter(const QString& str)
555 {
556
557     for (int i = 0, j = m_boardList->topLevelItemCount(); i < j; i++) {
558         bool matched = false;
559         QTreeWidgetItem* categoryItem = m_boardList->topLevelItem(i);
560
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);
566                 matched = true;
567             } else {
568                 boardItem->setHidden(true);
569             }
570         }
571         categoryItem->setHidden(!matched);
572     }
573 }
574
575
576 void BBSView::mousePressEvent(QMouseEvent *e)
577 {
578     QTreeWidgetItem* item = m_boardList->itemAt(e->pos());
579     if (!item) return;
580
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());
586         return ;
587     }
588
589     switch (e->button()) {
590     case Qt::MidButton:
591         ViewMediator::getInstance()->openBoard(boardUrl);
592         break;
593     case Qt::LeftButton:
594         ViewMediator::getInstance()->openBoard(boardUrl);
595         break;
596     default:
597         return;
598     }
599 }