OSDN Git Service

use url() instead of prettyURL()
[kita/kita.git] / kita / src / part / kitathreadview.cpp
1 /***************************************************************************
2  *   Copyright (C) 2003 by Hideki Ikemoto                                  *
3  *   ikemo@users.sourceforge.jp                                            *
4  *   linuxÈÄ ¥³¥ó¥«¥é¤×¤é¤°¤¤¤ó¤Î¿Í                                        *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation; either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  ***************************************************************************/
11
12 #include "kitathreadview.h"
13
14 #include <kurl.h>
15 #include <khtmlview.h>
16 #include <kiconloader.h>
17 #include <klocale.h>
18 #include <kaction.h>
19 #include <kmessagebox.h>
20 #include <kdebug.h>
21
22 #include <dom/html_inline.h>
23 #include <dom/html_base.h>
24 #include <dom/html_list.h>
25
26 #include <qlayout.h>
27 #include <qtoolbutton.h>
28 #include <qlabel.h>
29 #include <qcombobox.h>
30 #include <qprogressdialog.h>
31 #include <qcursor.h>
32 #include <qtextbrowser.h>
33 #include <qmessagebox.h>
34 #include <qdatetime.h>
35
36 #include "kitahtmlpart.h"
37 #include "kitawritedialog.h"
38 #include "../kitacacheinfo.h"
39 #include "kita2ch.h"
40
41 #include "libkita/comment.h"
42 #include "libkita/qcp932codec.h"
43 #include "libkita/favoritethreads.h"
44
45 #define MAX_LABEL_LENGTH 60
46
47 static const char* cookie_title = "½ñ¤­¹þ¤ß³Îǧ";
48 static const char* cookie_message =
49 "Åê¹Æ³Îǧ\n"
50 "¡¦Åê¹Æ¤µ¤ì¤¿ÆâÍƤϥ³¥Ô¡¼¡¢Êݸ¡¢°úÍÑ¡¢Å¾ºÜÅù¤µ¤ì¤ë¾ì¹ç¤¬¤¢¤ê¤Þ¤¹¡£\n"
51 "¡¦Åê¹Æ¤Ë´Ø¤·¤ÆȯÀ¸¤¹¤ëÀÕǤ¤ÏÁ´¤ÆÅê¹Æ¼Ô¤Ëµ¢¤·¤Þ¤¹¡£\n"
52 "\n"
53 "Á´ÀÕǤ¤òÉ餦¤³¤È¤ò¾µÂú¤·¤Æ½ñ¤­¹þ¤ß¤Þ¤¹¤«¡©\n";
54
55 static const char* kokomade_yonda = "<p style=\"background-color: #CCCCCC; text-align: center\">"
56 "¨¡¨¡¨¡¨¡¨¡¨¡¨¡¨¡¨¡¨¡¤³¤³¤Þ¤ÇÆɤó¤À¨¡¨¡¨¡¨¡¨¡¨¡¨¡¨¡¨¡¨¡"
57 "</p>";
58
59 KitaThreadView::KitaThreadView(QWidget *parent, const char *name)
60     : KitaThreadViewBase(parent, name)
61     , m_access( 0 )
62     , m_parent( parent )
63     , m_popup( 0 )
64 {
65   m_threadPart = new KitaHTMLPart(threadFrame);
66   QHBoxLayout* aLayout = new QHBoxLayout(threadFrame);
67   aLayout->addWidget(m_threadPart->view());
68
69   m_threadPart->setZoomFactor(120); // XXX
70   {
71     SearchButton->setPixmap( SmallIcon("find") );
72     HighLightButton->setPixmap( SmallIcon("idea") );
73     ReloadButton->setPixmap( SmallIcon("reload") );
74     GobackAnchorButton->setPixmap( SmallIcon("2leftarrow") );
75     BookmarkButton->setPixmap( SmallIcon("bookmark_add") );
76   }
77
78   setAcceptDrops( true ); // DND Drop eneble: 2nd stage. - enable on "KitaThreadView" widget and disable on the others(child widgets of "KitaThreadView").
79   threadFrame->setAcceptDrops( false ); // don't treat Drop event on child.
80   m_threadPart->view()->setAcceptDrops( false ); // don't treat Drop event on child.
81
82   m_threadPart->enableMetaRefresh( false ); //disable <meta refresh="...">
83
84
85   connect( writeButton, SIGNAL(clicked()), SLOT(slotWriteButtonClicked()));
86   connect( m_threadPart, SIGNAL( nodeActivated(const DOM::Node &) ), SLOT( slotDOMNodeActivated(const DOM::Node &) ) );
87   connect( m_threadPart, SIGNAL( onURL(const QString&) ), SLOT( slotOnURL(const QString&) ) );
88   connect( m_threadPart, SIGNAL( setLocationBarURL(const QString &) ),
89                          SIGNAL( setLocationBarURL(const QString &) ) );
90   connect( BookmarkButton, SIGNAL( toggled(bool) ), SLOT( slotBookmarkButtonClicked(bool) ) );
91   connect( SearchButton, SIGNAL( clicked() ), SLOT( slotSearchButton() ) );
92   connect( SearchCombo, SIGNAL( activated(int) ), SLOT( slotSearchButton() ) );
93   connect( HighLightButton, SIGNAL( toggled(bool) ), SLOT( slotHighLightenButton(bool) ) );
94   connect( GobackAnchorButton, SIGNAL( clicked() ), m_threadPart, SLOT( gobackAnchor() ) );
95   connect( ReloadButton, SIGNAL( clicked() ), SLOT( slotReloadButton() ) );
96
97   KParts::BrowserExtension * ext = m_threadPart->browserExtension();
98   connect( ext, SIGNAL( openURLRequest(const KURL&, const KParts::URLArgs&) ),
99                  SLOT( slotOpenURLRequest(const KURL&, const KParts::URLArgs&) ) );
100   connect( ext, SIGNAL( createNewWindow (const KURL&, const KParts::URLArgs&) ),
101                 SIGNAL( createNewWindow (const KURL&, const KParts::URLArgs&) ) );
102   connect( ext, SIGNAL( setLocationBarURL(const QString &) ),
103                 SIGNAL( setLocationBarURL(const QString &) ) );
104   connect( ext, SIGNAL( enableAction(const char*, bool) ),
105                 SIGNAL( enableAction(const char*, bool) ) );
106
107   connect( ext, SIGNAL( popupMenu(KXMLGUIClient *, const QPoint&, const KURL&, const QString&, mode_t) ),
108       this, SLOT( slotPopupMenu(KXMLGUIClient *, const QPoint&, const KURL&, const QString&, mode_t) ) );
109
110 }
111
112 KitaThreadView::~KitaThreadView() {}
113
114 const QString KitaThreadView::threadName() const
115 {
116   return m_thread.name();
117 }
118
119 const KURL KitaThreadView::threadURL() const
120 {
121   return m_thread.url();
122 }
123
124 void KitaThreadView::slotDOMNodeActivated(const DOM::Node &node)
125 {
126   { //process Anchor tags. Anchor tags not proccessed here cause 'emit KParts::BrowserExtention::openURLRequest()'
127     DOM::HTMLAnchorElement anchor = node;
128
129     if ( ! anchor.href().isEmpty() )
130     {
131       kdDebug() << "AnchorNodeActivated::" << endl;
132     } // end: anchor.href().isEmpty()
133   } // end of Anchor tags.
134 }
135
136 void KitaThreadView::showThread( const Kita::Thread& thread )
137 {
138   { //reset member variables associated with a thread.
139     m_threadPart->reset();
140   }
141
142   m_thread = thread;
143
144   m_access = new Kita::ThreadAccess( thread );
145   connect( m_access, SIGNAL( redirection( const QString& ) ), SIGNAL( setLocationBarURL( const QString& ) ) );
146   QString result = m_access->get();
147   m_serverTime = getServerTimeFromHttpHeaders( m_access->getHeader() );
148   update( result );
149 }
150
151 int KitaThreadView::getServerTimeFromHttpHeaders( const QString& headers )
152 {
153   // parse HTTP headers
154   QStringList headerList = QStringList::split("\n", headers);
155   QRegExp regexp("Date: (...), (..) (...) (....) (..:..:..) .*");
156   QString dateStr = headerList.grep(regexp)[0];
157   if(regexp.search(dateStr) == -1) {
158     // invalid date format
159     return QDateTime::currentDateTime().toTime_t();
160   } else {
161     // I hate this format ;p
162     QString usLocalDateStr = regexp.cap(1) + " " + regexp.cap(3) + " " +
163                              regexp.cap(2) + " " + regexp.cap(5) + " " + regexp.cap(4);
164
165     // 1970/01/01 00:00:00 GMT
166     QDateTime zeroTime(QDate(1970, 1, 1), QTime(0, 0));
167     return zeroTime.secsTo(QDateTime::fromString(usLocalDateStr));
168   }
169 }
170
171 void KitaThreadView::setSubjectLabel(const QString& boardName, const QString& threadName)
172 {
173   QString disp;
174   if ( boardName.isEmpty() ) {
175     disp = threadName;
176   } else {
177     disp = QString("[%1] %2").arg(boardName).arg(threadName);
178   }
179
180   disp.truncate( MAX_LABEL_LENGTH );
181   subjectLabel->setText( disp );
182 }
183
184 void KitaThreadView::update( const QString& result )
185 {
186   QStringList lines = QStringList::split( "\n", result );
187   QString text;
188   int num = 0;
189
190   int total = result.length();
191   int step = 0;
192   int divide = total / ( 100 / 5 );
193   int next = divide;
194   int prevResNum = m_thread.resNum();
195   QProgressDialog * progress = new QProgressDialog( m_parent );
196   progress->setTotalSteps( static_cast<int>(total * 0.7) );
197   progress->setLabelText( "Parse DAT file.....");
198   progress->show();
199
200   text += "<html><head>";
201   text += "</head><body>";
202
203   for( QStringList::iterator it = lines.begin(); it != lines.end(); ++it ) {
204     QString line = (*it);
205
206     if( line.isEmpty() ) {
207       continue;
208     }
209
210     Kita::Comment comment(line);
211
212     num++;
213     if(num == 1) {
214       m_thread.setName(comment.getSubject());
215     }
216     text += comment.toHtml(num);
217
218     if( prevResNum == num ) {
219       text += QString::fromLocal8Bit( kokomade_yonda );
220     }
221
222     if( (step+=line.length()) > next ) {
223       progress->setProgress( step );
224       next += divide;
225     }
226   }
227   delete progress;
228
229   m_thread.setResNum(num);
230   emit thread( m_thread );
231
232   setSubjectLabel( m_thread.boardName(), m_thread.name() );
233
234   text += footer( prevResNum );
235   text += "</body></html>";
236
237   m_threadPart->displayContentsAndGotoURL( text, m_thread );
238   updateButton();
239
240   emit showThreadCompleted( m_thread.url() );
241 }
242
243 void KitaThreadView::updateButton()
244 {
245   writeButton->setEnabled( true );
246   BookmarkButton->setEnabled( true );
247   ReloadButton->setEnabled( true );
248
249   if ( HighLightButton->isOn() ) {
250     HighLightButton->toggle();
251   }
252   if( FavoriteThreads::getInstance()->contains( m_thread.datURL().url() ) ) {
253     BookmarkButton->setOn( true );
254   } else {
255     BookmarkButton->setOn( false );
256   }
257 }
258
259 const QString KitaThreadView::footer( int prevResNum ) const
260 {
261   QString text;
262   int target;
263   for ( target = 1; target < m_thread.resNum(); target += 100 ) {
264     text += QString(" <a href=\"#%1\">%3</a> ").arg(target).arg(target);
265   }
266   text += QString(" <a href=\"#%1\">New</a> ").arg( prevResNum + 1 );
267   text += "<br/>";
268
269   return text;
270 }
271
272 void KitaThreadView::slotShowErrorDialog( const QString& input, const KURL& )
273 {
274   kdDebug() << "'" << input << "'" << endl;
275   Kita::WriteResult writeResult(input);
276
277   kdDebug() << "code = " << writeResult.code() << endl;
278   switch( writeResult.code() ) {
279   case Kita::K2ch_Unknown:
280     // probably OK.
281     emit writeSucceeded();
282     slotReloadButton();
283     break;
284   case Kita::K2ch_True:
285   case Kita::K2ch_False:
286   case Kita::K2ch_Error:
287   case Kita::K2ch_Check:
288     KMessageBox::error(0, writeResult.message(), writeResult.title());
289     break;
290   case Kita::K2ch_Cookie:
291     if( KMessageBox::questionYesNo(0, QString::fromLocal8Bit(cookie_message),
292                                       QString::fromLocal8Bit(cookie_title)) == KMessageBox::Yes) {
293       KitaWriteDialog* dialog = openDialog( m_postInfo );
294       dialog->postMessage();
295     } else {
296       KitaWriteDialog* dialog = openDialog( m_postInfo );
297       dialog->show();
298     }
299     break;
300   default:
301     break;
302   }
303 }
304
305 KitaWriteDialog* KitaThreadView::openDialog( const Kita::PostInfo& info )
306 {
307   KitaWriteDialog* new_dialog = KitaWriteDialog::open( info, m_thread.boardName(), m_thread.name() );
308   connect( new_dialog, SIGNAL( postStarted( KIO::Job *, const Kita::PostInfo& ) ),
309            this, SLOT( slotPostStarted( KIO::Job*, const Kita::PostInfo&) ) );
310   connect( new_dialog, SIGNAL( postResponse(const QString&, const KURL&) ),
311            this, SLOT( slotShowErrorDialog(const QString&, const KURL&) ) );
312   connect( new_dialog, SIGNAL( postResponse(const QString&, const KURL&) ),
313            this, SIGNAL( postResponse(const QString&, const KURL&) ) );
314   return new_dialog;
315 }
316
317 void KitaThreadView::slotWriteButtonClicked()
318 {
319 //  QSjisCodec cp932Codec;
320   KURL bbscgiURL = KURL( m_thread.boardUrl(), "../test/bbs.cgi");
321   bbscgiURL.setProtocol( "http" );
322
323   Kita::PostInfo info;
324   info.host = bbscgiURL.host();
325   info.bbs = m_thread.boardId();
326   info.key = m_thread.datID();
327   info.time = QString("%1").arg(m_serverTime);
328
329   KitaWriteDialog* dialog = openDialog( info );
330   dialog->show(); // work asynchronus.
331 }
332
333 void KitaThreadView::slotSearchButton()
334 {
335   insertSearchCombo();
336   QStringList list = parseSearchQuery( SearchCombo->currentText() );
337   searchNext( list );
338 }
339
340 void KitaThreadView::slotHighLightenButton(bool yes)
341 {
342   insertSearchCombo();
343   QStringList list = parseSearchQuery( SearchCombo->currentText() );
344   m_threadPart->highLighten( yes, list );
345 }
346
347 void KitaThreadView::insertSearchCombo()
348 {
349   for( int count = 0; count < SearchCombo->count(); ++count ) {
350     if ( SearchCombo->text( count ) == SearchCombo->currentText() ) {
351       // found
352       return;
353     }
354   }
355   SearchCombo->insertItem( SearchCombo->currentText() );
356 }
357
358 QStringList KitaThreadView::parseSearchQuery(const QString &input) const
359 {
360   QStringList tmp = QStringList::split( ' ', input );
361   QStringList ret_list;
362   QRegExp truncSpace("\\s*$");
363   QStringList::iterator it = tmp.begin();
364   for( ; it != tmp.end(); ++it )
365     ret_list += (*it).replace( truncSpace, "" );
366   return ret_list;
367 }
368
369 void KitaThreadView::searchNext(const QStringList &query)
370 {
371   if ( query.isEmpty() ) return;
372   if ( ! HighLightButton->isOn() ) {
373     HighLightButton->toggle();
374     m_threadPart->resetHit(); //A next jump-search target reset to '0'.
375     // Process works asynchronusly. So Firstly, we don't do jump-search as a simple solution.
376     return;
377   }
378   if ( query != m_threadPart->prevQuery() ) {
379     m_threadPart->highLighten( true, query );
380     m_threadPart->resetHit(); //A next jump-search target reset to '0'.
381     return;
382   }
383
384   m_threadPart->gotoAnchor( QString("highlighten%1").arg( m_threadPart->nextHit() )  );
385 }
386
387 void KitaThreadView::slotOnURL(const QString& url)
388 {
389   emit signalChangeStatusbar(url);
390
391   // TODO: enterEvent()¤ò¼ÂÁõ¤·¤Æ¡¢Æþ¤Ã¤Æ¤­¤¿¤È¤­¤Ëtip¤òɽ¼¨¤¹¤ë¤³¤È¡£
392   if ( ! url.isEmpty() ) {
393     KHTMLView* view = m_threadPart->view();
394
395     if( m_popup ) {
396       delete m_popup;
397     }
398
399     m_popup = new QFrame( view, "res_popup", WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WType_TopLevel );
400     m_browser = new QTextBrowser( m_popup );
401     m_browser->zoomOut( static_cast<int>( m_browser->pointSize() * 0.3 ) );
402     m_browser->setPaletteBackgroundColor( "yellow" );
403     m_browser->setWordWrap( QTextEdit::NoWrap );
404     m_browser->setResizePolicy( QScrollView::AutoOne );
405     connect( m_threadPart->view(), SIGNAL( leave() ), SLOT( hidePopup() ) );
406
407     int refNum = 0;
408     if ( url.at(0) == '#' ) {
409       refNum = url.mid(1).toInt();
410     } else {
411       KURL filteredURL = filterReadCGI( KURL( m_thread.datURL(), url ) );
412       refNum = filteredURL.ref().toInt();
413     }
414
415     if( refNum != 0 ) {
416       DOM::NodeList comments = m_threadPart->htmlDocument().body().getElementsByTagName( "dl" );
417       DOM::HTMLDListElement comment_dl = static_cast<DOM::HTMLDListElement>(comments.item( refNum - 1 ));
418       QString innerHTML = comment_dl.innerHTML().string();
419
420       m_browser->setText( innerHTML );
421       m_browser->resize( m_browser->contentsWidth() + 10, m_browser->contentsHeight() );
422       m_popup->adjustSize();
423
424       QPoint pos = QCursor::pos();
425       pos -= QPoint( 0, m_popup->height() ) + QPoint( -10, 10 );
426       m_popup->move( pos );
427
428       m_popup->show();
429     }
430   } else {
431     m_popup->hide();
432   }
433 }
434
435 void KitaThreadView::hidePopup()
436 {
437   if( m_popup ) {
438     m_popup->hide();
439   }
440 }
441
442 void KitaThreadView::slotPopupMenu( KXMLGUIClient *client, const QPoint &global, const KURL &url, const QString &mimeType, mode_t mode)
443 {
444   KActionCollection * collection = client->actionCollection();
445   KAction * action;
446   action = new KAction( i18n("goback anchor"), SmallIcon("idea"), KShortcut(), m_threadPart, SLOT( gobackAnchor() ), collection, "goback_anchor" );
447   emit popupMenu(client, global, url, mimeType, mode);
448 }
449
450 void KitaThreadView::slotOpenURLRequest(const KURL& url, const KParts::URLArgs& args)
451 {
452   if ( url.url().at(0) == '#' ) {
453     m_threadPart->gotoAnchor( url.ref() );
454     return;
455   }
456   KURL datURL = filterReadCGI( url );
457   if ( datURL.host() == m_thread.datURL().host()
458     && datURL.path() == m_thread.datURL().path() )
459   {
460     if ( datURL.hasRef() ) m_threadPart->gotoAnchor( datURL.ref() );
461     return;
462   }
463   emit openURLRequest(datURL, args);
464 }
465
466 void KitaThreadView::slotReloadButton()
467 {
468   showThread( m_thread );
469 }
470
471 KURL KitaThreadView::filterReadCGI(const KURL& url)
472 {
473   KURL  newURL = url;
474   if ( url.path().contains("/test/read.cgi") ) {
475     newURL.setProtocol( m_thread.datURL().protocol() );
476     QString tmp = url.path().section("/test/read.cgi", 1);
477
478     QString newPath = QString( "/%1/dat/%2.dat" )
479                .arg( tmp.section('/', 1, 1) )
480                .arg( tmp.section('/', 2, 2) );
481     newURL.setPath( newPath );
482
483     QString refBase = tmp.section('/', 3);
484     if ( ! refBase.isEmpty() ) {
485       QString newRef =  refBase.section('-', 0, 0);
486       if( ! newRef.isEmpty() ) newURL.setRef( newRef );
487       else if ( refBase.at(0) == '-' ) newURL.setRef("1");
488       else newURL.setRef( refBase );
489     }
490   }
491   kdDebug() << "newURL: " << newURL.url() << endl;
492   return newURL;
493 }
494
495 void KitaThreadView::setFont( const QFont& font )
496 {
497   m_threadPart->setStandardFont(font.family());
498   subjectLabel->setFont(font);
499 }
500
501 void KitaThreadView::slotPostStarted( KIO::Job*, const Kita::PostInfo& info)
502 {
503   m_postInfo = info;
504 }
505
506 void KitaThreadView::slotBookmarkButtonClicked( bool on )
507 {
508   emit bookmarked( m_thread, on );
509 }
510
511 void KitaThreadView::killJob()
512 {
513   if( m_access ) {
514     m_access->killJob();
515   }
516 }
517 // vim:sw=2: