Salome HOME
Merge remote branch 'origin/V7_4_BR'
[modules/gui.git] / src / Qtx / QtxWebBrowser.cxx
1 // Copyright (C) 2007-2014  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 // File:      QtxWebBrowser.cxx
24 // Author:    Roman NIKOLAEV
25
26 #include "QtxWebBrowser.h"
27 #include "QtxResourceMgr.h"
28 #include "QtxSearchTool.h"
29
30 #include <QApplication>
31 #include <QButtonGroup>
32 #include <QCheckBox>
33 #include <QFileDialog>
34 #include <QFileInfo>
35 #include <QGridLayout>
36 #include <QHBoxLayout>
37 #include <QLabel>
38 #include <QMenu>
39 #include <QMenuBar>
40 #include <QMessageBox>
41 #include <QPushButton>
42 #include <QRadioButton>
43 #include <QStatusBar>
44 #include <QToolBar>
45 #include <QVBoxLayout>
46 #include <QWebView>
47 #include <QProcess>
48
49 namespace
50 {
51   bool isLocalFile( const QUrl& url )
52   {
53     QFileInfo fi( url.path() );
54     return fi.exists();
55   }
56 }
57
58 /*!
59   \class QtxWebBrowser::Searcher
60   \brief A class is used with QtxSearchTool in order to search text within the web page 
61   \internal
62 */
63
64 class QtxWebBrowser::Searcher : public QtxSearchTool::Searcher
65 {
66 public:
67   Searcher( QWebView* );
68   ~Searcher();
69
70   bool find( const QString&, QtxSearchTool* );
71   bool findNext( const QString&, QtxSearchTool* );
72   bool findPrevious( const QString&, QtxSearchTool* );
73   bool findFirst( const QString&, QtxSearchTool* );
74   bool findLast( const QString&, QtxSearchTool* );
75
76 private:
77   QWebView* myView;
78 };
79
80 /*!
81   \brief Constructor
82   \param view web view
83   \internal
84 */
85 QtxWebBrowser::Searcher::Searcher( QWebView* view ) : myView( view )
86 {
87 }
88
89 /*!
90   \brief Destructor
91   \internal
92 */
93 QtxWebBrowser::Searcher::~Searcher()
94 {
95 }
96
97 /*!
98   \brief Find specified text
99   \param text text being searched
100   \param st search tool
101   \return \c true if text has been found or \c false otherwise
102   \internal
103 */
104 bool QtxWebBrowser::Searcher::find( const QString& text, QtxSearchTool* st )
105 {
106   QWebPage::FindFlags fl = 0;
107   if ( st->isCaseSensitive() ) fl = fl | QWebPage::FindCaseSensitively;
108   if ( st->isSearchWrapped() ) fl = fl | QWebPage::FindWrapsAroundDocument;
109   return myView->findText( text, fl );
110 }
111
112 /*!
113   \brief Find next entry of specified text starting from the current position
114   \param text text being searched
115   \param st search tool
116   \return \c true if text has been found or \c false otherwise
117   \internal
118 */
119 bool QtxWebBrowser::Searcher::findNext( const QString& text, QtxSearchTool* st )
120 {
121   return find( text, st );
122 }
123
124 /*!
125   \brief Find previous entry of specified text starting from the current position
126   \param text text being searched
127   \param st search tool
128   \return \c true if text has been found or \c false otherwise
129   \internal
130 */
131 bool QtxWebBrowser::Searcher::findPrevious( const QString& text, QtxSearchTool* st )
132 {
133   QWebPage::FindFlags fl = QWebPage::FindBackward;
134   if ( st->isCaseSensitive() ) fl = fl | QWebPage::FindCaseSensitively;
135   if ( st->isSearchWrapped() ) fl = fl | QWebPage::FindWrapsAroundDocument;
136   return myView->findText( text, fl );
137 }
138
139 /*!
140   \brief Find first entry of specified text; does nothing in this implementation
141   \param text text being searched
142   \param st search tool
143   \return \c true if text has been found or \c false otherwise
144   \internal
145 */
146 bool QtxWebBrowser::Searcher::findFirst( const QString& /*text*/, QtxSearchTool* /*st*/ )
147 {
148   return false;
149 }
150
151 /*!
152   \brief Find last entry of specified text; does nothing in this implementation
153   \param text text being searched
154   \param st search tool
155   \return \c true if text has been found or \c false otherwise
156   \internal
157 */
158 bool QtxWebBrowser::Searcher::findLast( const QString& /*text*/, QtxSearchTool* /*st*/)
159 {
160   return false;
161 }
162
163
164 /*!
165   \class QtxWebBrowser::Downloader
166   \brief A dialog box that is used to process file links
167   \internal
168 */
169
170 /*!
171   \brief Constructor
172   \param fileName name of the file being opened
173   \param action default action to be used for the file
174   \param program default program to be used to open the file
175   \param parent parent widget
176   \internal
177 */
178 QtxWebBrowser::Downloader::Downloader( const QString& fileName, int action, const QString& program, QWidget* parent )
179   : QDialog( parent ), myProgram( program )
180 {
181   setModal( true );
182   setWindowTitle( QtxWebBrowser::tr( "Open URL" ) );
183   setSizeGripEnabled( true );
184
185   myFileName = new QLabel( this );
186   QRadioButton* rbOpen = new QRadioButton( QtxWebBrowser::tr( "Open in" ), this );
187   QRadioButton* rbSave = new QRadioButton( QtxWebBrowser::tr( "Save file" ), this );
188   myBrowse = new QPushButton( QtxWebBrowser::tr( "&Browse..." ),     this );
189   myRepeat = new QCheckBox( QtxWebBrowser::tr( "Use this program for all files of this type" ), this );
190
191   myAction = new QButtonGroup( this );
192   myAction->addButton( rbOpen, mOpen );
193   myAction->addButton( rbSave, mSave );
194
195   QPushButton* btnOk     = new QPushButton( QtxWebBrowser::tr( "&OK" ),     this );
196   QPushButton* btnCancel = new QPushButton( QtxWebBrowser::tr( "&Cancel" ), this );
197
198   QFont f = myFileName->font(); f.setBold( true ); myFileName->setFont( f );
199
200   QHBoxLayout* btnLayout = new QHBoxLayout;
201   btnLayout->addWidget( btnOk );
202   btnLayout->addStretch();
203   btnLayout->addWidget( btnCancel );
204
205   QGridLayout* l = new QGridLayout( this );
206   l->addWidget( new QLabel( QtxWebBrowser::tr( "You are opening the file" ), this ), 
207                             0, 0, 1, 4 );
208   l->addWidget( myFileName, 1, 1, 1, 3 );
209   l->addWidget( new QLabel( QtxWebBrowser::tr( "Please choose the action to be done" ), this ), 
210                             3, 0, 1, 4 );
211   l->addWidget( rbOpen,     4, 1, 1, 1 );
212   l->addWidget( myBrowse,   4, 2, 1, 1 );
213   l->addWidget( rbSave,     5, 1, 1, 3 );
214   l->addWidget( myRepeat,   6, 1, 1, 3 );
215   l->addLayout( btnLayout,  7, 0, 1, 4 );
216   l->setRowMinimumHeight( 2, 10 );
217
218   connect( myAction,  SIGNAL( buttonClicked( int ) ), this, SLOT( setAction( int ) ) );
219   connect( myBrowse,  SIGNAL( clicked() ), this, SLOT( browse() ) );
220   connect( btnOk,     SIGNAL( clicked() ), this, SLOT( accept() ) );
221   connect( btnCancel, SIGNAL( clicked() ), this, SLOT( reject() ) );
222
223   myFileName->setText( QFileInfo( fileName ).fileName() );
224   myAction->button( action )->click();
225 }
226
227 /*!
228   \brief Destructor
229 */
230 QtxWebBrowser::Downloader::~Downloader()
231 {
232 }
233
234 /*!
235   \brief Get action selected by the user
236   \return action being selected:
237   - 0: open file
238   - 1: save file
239 */
240 int QtxWebBrowser::Downloader::action() const
241 {
242   return myAction->checkedId();
243 }
244
245 /*!
246   \brief Get "repeat action for all such files" flag status
247   \return \c true if chosen action should be automatically done for all files of given type
248   or \c false otherwise
249 */
250 bool QtxWebBrowser::Downloader::isRepeatAction() const
251 {
252   return myRepeat->isChecked();
253 }
254
255 /*!
256   \brief Get program to be used to open chosen file
257   \return path to the program
258 */
259 QString QtxWebBrowser::Downloader::program() const
260 {
261   return myProgram;
262 }
263
264 /*!
265   \brief Set current action
266   \param action action to be done for the file:
267   - 0: open file
268   - 1: save file
269 */
270 void QtxWebBrowser::Downloader::setAction( int action )
271 {
272   myBrowse->setEnabled( action == mOpen );
273 }
274
275 /*!
276   \brief Browse program to be used to open the file
277 */
278 void QtxWebBrowser::Downloader::browse()
279 {
280   QString program = QFileDialog::getOpenFileName( this, QtxWebBrowser::tr( "Choose program" ), myProgram );
281   if ( !program.isEmpty() ) myProgram = program;
282 }
283
284
285 /*!
286   \class QtxWebBrowser
287
288   \brief The QtxWebBrowser provides a window that can display html pages from local file system.
289   
290   Only one instance of the QtxWebBrowser class can be created. To access the browser 
291   window, use static method QtxWebBrowser::webBrowser(), which creates an
292   instance of the QtxWebBrowser widget (if it is not yet created) and returns a
293   pointer to it.
294
295   You should not destroy this instance - it is done automatically after
296   closing of the browser window. To close window programmatically use 
297   method close().
298
299   Optionally resource manager can be specified to automatically store
300   action (open/save) and program to be used to download files in the
301   application preferences.
302
303   The following sample demonstrates how to use web browser.
304   In this code the browser window is created, /data/index.html file is opened
305   and scrolled to the "anchor1" anchor on this page.
306
307   \code
308   // initialize application
309   // - set resource manager
310   QtxWebBrowser::setResourceMgr(myResourceMgr);
311   // ...
312   // show HTML page
313   QtxWebBrowser::loadUrl("file:///data/index.html", "anchor1");
314   \endcode
315 */
316
317 //! The only one instance of web browser
318 QtxWebBrowser* QtxWebBrowser::myBrowser = 0;
319
320 //! Resources manager
321 QtxResourceMgr* QtxWebBrowser::myResourceMgr = 0;
322
323 /*!
324   \brief Constructor.
325   Construct the web browser.
326 */
327 QtxWebBrowser::QtxWebBrowser( ) : QMainWindow( 0 )
328 {
329   Q_INIT_RESOURCE( Qtx );
330
331   setAttribute( Qt::WA_DeleteOnClose );
332   statusBar();
333
334   QWidget* frame = new QWidget( this );
335
336   myWebView = new QWebView( frame );
337
338   myWebView->pageAction( QWebPage::Copy )->setShortcut( QKeySequence::Copy );
339   myWebView->addAction( myWebView->pageAction( QWebPage::Copy ) );
340   myWebView->pageAction( QWebPage::OpenLinkInNewWindow )->setVisible( false );
341   myWebView->pageAction( QWebPage::Back )->setText( tr( "Go Back" ) );
342   myWebView->pageAction( QWebPage::Forward )->setText( tr( "Go Forward" ) );
343   myWebView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
344
345   myFindPanel = new QtxSearchTool( frame, myWebView,
346                                    QtxSearchTool::Basic | QtxSearchTool::Case | QtxSearchTool::Wrap, 
347                                    Qt::Horizontal );
348   myFindPanel->setFrameStyle( QFrame::NoFrame | QFrame::Plain );
349   myFindPanel->setActivators( QtxSearchTool::SlashKey );
350   myFindPanel->setSearcher( new Searcher( myWebView ) );
351   myFindPanel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed );
352
353   QToolBar* toolbar = addToolBar( tr( "Navigation" ) );
354   toolbar->addAction( myWebView->pageAction( QWebPage::Back ) );
355   toolbar->addAction( myWebView->pageAction( QWebPage::Forward ) );
356
357   QMenu* fileMenu = menuBar()->addMenu( tr( "&File" ) );
358   fileMenu->addAction( QPixmap( ":/images/open.png" ), tr( "&Open..." ), 
359                        this, SLOT( open() ),
360                        QKeySequence( QKeySequence::Open ) );
361   fileMenu->addSeparator();
362   fileMenu->addAction( tr( "&Find in text..." ),
363                        myFindPanel, SLOT( find() ),
364                        QKeySequence( QKeySequence::Find ) );
365   fileMenu->addAction( tr( "&Find next" ),
366                        myFindPanel, SLOT( findNext() ),
367                        QKeySequence( QKeySequence::FindNext ) );
368   fileMenu->addAction( tr( "&Find previous" ),
369                        myFindPanel, SLOT( findPrevious() ),
370                        QKeySequence( QKeySequence::FindPrevious ) );
371   fileMenu->addSeparator();
372   fileMenu->addAction( QPixmap( ":/images/close.png" ), tr( "&Close" ),
373                        this, SLOT( close() ) );
374
375   QMenu* helpMenu = menuBar()->addMenu( tr( "&Help" ) );
376   helpMenu->addAction( tr( "&About..." ),
377                        this, SLOT( about() ) );
378   
379   QVBoxLayout* main = new QVBoxLayout( frame );
380   main->addWidget( myWebView );
381   main->addWidget( myFindPanel );
382   main->setMargin( 0 );
383   main->setSpacing( 3 );
384
385   connect( myWebView, SIGNAL( titleChanged( QString ) ), SLOT( adjustTitle() ) ); 
386   connect( myWebView, SIGNAL( loadFinished( bool ) ),    SLOT( finished( bool ) ) ); 
387   connect( myWebView, SIGNAL( linkClicked( QUrl ) ),     SLOT( linkClicked( QUrl ) ) ); 
388   connect( myWebView->page(), SIGNAL( linkHovered( QString, QString, QString ) ), 
389            SLOT( linkHovered( QString, QString, QString ) ) ); 
390   connect( myWebView->pageAction( QWebPage::DownloadLinkToDisk ), SIGNAL( activated() ),
391            SLOT( linkAction() ) );
392   disconnect( myWebView->pageAction( QWebPage::OpenLink ), 0, 0, 0 );
393   connect( myWebView->pageAction( QWebPage::OpenLink ), SIGNAL( activated() ),
394            SLOT( linkAction() ) );
395   
396   setCentralWidget( frame );
397   setFocusProxy( myWebView );
398   setWindowIcon( QPixmap( ":/images/appicon.png" ) );
399   adjustTitle();
400 }
401
402 /*!
403   \brief Destructor.
404 */
405 QtxWebBrowser::~QtxWebBrowser()
406 {
407   myBrowser = 0;
408 }
409
410 /*!
411   \brief Return the only instance of the QtxWebBrowser
412   \return instance of the QtxWebBrowser
413 */
414 QtxWebBrowser* QtxWebBrowser::webBrowser()
415 {
416   if ( !myBrowser )
417     myBrowser = new QtxWebBrowser();
418   return myBrowser;
419 }
420
421 /*!
422   \brief Load given url address and optional scroll to the specified anchor
423   \param url an url address to load
424   \param anchor an anchor to scroll page to
425 */
426 void QtxWebBrowser::loadUrl( const QString& url, const QString& anchor )
427 {
428   QString anUrl = url;
429   if( !anchor.isEmpty() ) anUrl += "#" + anchor;
430
431   Qtx::alignWidget( webBrowser(), (QWidget*)QApplication::desktop(), Qtx::AlignCenter );
432
433   QtxWebBrowser* browser = webBrowser();
434   browser->show();
435   browser->load( anUrl );
436   browser->setFocus();
437   browser->activateWindow();
438   browser->raise();
439 }
440
441 /*!
442   \brief Shutdown help browser
443 */
444 void QtxWebBrowser::shutdown()
445 {
446   if ( myBrowser )
447     myBrowser->close();
448 }
449
450 /*!
451   \brief Set resource manager
452   \param resMgr resource manager
453 */
454 void QtxWebBrowser::setResourceMgr( QtxResourceMgr* resMgr )
455 {
456   myResourceMgr = resMgr;
457 }
458
459 /*!
460   \brief Get resource manager
461   \return resource manager
462 */
463 QtxResourceMgr* QtxWebBrowser::resourceMgr() const
464 {
465   return myResourceMgr;
466 }
467
468 /*!
469   Shows About dialog box
470 */
471 void QtxWebBrowser::about()
472 {
473   QMessageBox::about( this, tr( "About %1" ).arg( tr( "Help Browser" ) ),
474                       QString( "SALOME %1" ).arg( tr( "Help Browser" ) ) );
475 }
476
477 /*!
478   \brief Called when users activated any link at the page
479   \param url URL being clicked
480   \internal
481 */
482 void QtxWebBrowser::linkClicked( const QUrl& url )
483 {
484   myWebView->page()->setLinkDelegationPolicy( QWebPage::DontDelegateLinks );
485   myWebView->load( url );
486   myWebView->page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
487 }
488
489 /*!
490   \brief Called when link is hovered
491   \param link link being hovered
492   \param title link title (if it is specified in the markup)
493   \param content provides text within the link element, e.g., text inside an HTML anchor tag
494   \internal
495 */
496 void QtxWebBrowser::linkHovered( const QString& link, const QString& /*title*/, const QString& /*context*/ )
497 {
498   QUrl url = link;
499   if ( !link.isEmpty() && isLocalFile( url ) ) myLastUrl = url;
500   statusBar()->showMessage( link );
501 }
502
503 /*!
504   \brief Update title of the window
505   \internal
506 */
507 void QtxWebBrowser::adjustTitle()
508 {
509   QString title = tr( "Help Browser" );
510   if ( !myWebView->title().isEmpty() ) title += QString( " [%1]" ).arg( myWebView->title() );
511   setWindowTitle( title );
512 }
513
514 /*
515   \brief Called when link is processed by browser
516   \param ok operation status: \c true is URL is correctly processed, \c false otherwise
517 */
518 void QtxWebBrowser::finished( bool ok )
519 {
520   if ( !ok && !myLastUrl.isEmpty() ) {
521     if ( isLocalFile( myLastUrl ) ) {
522       QString filename = myLastUrl.path();
523       QString extension = QFileInfo( filename ).suffix();
524       if ( extension == "html" || extension == "htm" ) return;
525       openLink( filename );
526     }
527   }
528 }
529
530 /*
531   \brief Called when link is processed from browser via popup menu actions
532 */
533 void QtxWebBrowser::linkAction()
534 {
535   QObject* s = sender();
536   if ( s == myWebView->pageAction( QWebPage::DownloadLinkToDisk ) ) {
537     saveLink( myLastUrl.path() );
538   }
539   else if ( s == myWebView->pageAction( QWebPage::OpenLink ) ) {
540     QString fileName  = myLastUrl.path();
541     QString extension = QFileInfo( fileName ).suffix();
542     if ( extension != "html" && extension != "htm" ) {
543       openLink( fileName, true );
544     }
545     else {
546       linkClicked( myLastUrl );
547     }
548   }
549 }
550
551 /*!
552   \brief Open file
553   \param fileName link to the file being opened
554   Opens dialog box to allow the user to choose the program to be used to open the file.
555 */
556 void QtxWebBrowser::openLink( const QString& fileName, bool force )
557 {
558   QString extension = QFileInfo( fileName ).suffix();
559   int defAction = Downloader::mOpen;
560   bool defRepeat = false;
561   QString defProgram;
562   QString resSection   = QString( "%1:%2" ).arg( "web_browser" ).arg( extension );
563   QString actionParam  = "action";
564   QString programParam = "program";
565   QString repeatParam  = "repeat";
566   
567   if ( !extension.isEmpty() && myResourceMgr ) {
568     defAction  = myResourceMgr->integerValue( resSection, actionParam, defAction );
569     defRepeat  = myResourceMgr->booleanValue( resSection, repeatParam, defRepeat );
570     defProgram = myResourceMgr->stringValue( resSection, programParam, defProgram );
571   }
572   
573   if ( force || !defRepeat || ( defAction == Downloader::mOpen && defProgram.isEmpty() ) ) {
574     Downloader downloader( fileName, defAction, defProgram, this );
575     if ( !downloader.exec() ) return;
576     defAction  = downloader.action();
577     defRepeat  = downloader.isRepeatAction();
578     defProgram = downloader.program();
579     if ( myResourceMgr ) {
580       myResourceMgr->setValue( resSection, actionParam, defAction );
581       myResourceMgr->setValue( resSection, repeatParam, defRepeat );
582       if ( defAction == Downloader::mOpen )
583         myResourceMgr->setValue( resSection, programParam, defProgram );
584     }
585   }
586   switch( defAction ) {
587   case Downloader::mOpen:
588     if ( !defProgram.isEmpty() ) {
589       QStringList parameters;
590 #ifdef WIN32
591       QString cmd = defProgram;
592 #else
593       // If Salome Qt version is lower than the system one, on KDE an unresolved symbol is raised
594       // In this case, we can try to launch the pdf viewer after unsetting the LD_LIBRARY_PATH environnement variable
595       QString cmd = "env";
596       parameters << "LD_LIBRARY_PATH=/usr/lib:/usr/lib64";
597       parameters << defProgram;
598 #endif
599       parameters << QFileInfo( myLastUrl.path() ).absoluteFilePath();
600       QProcess::startDetached( cmd, parameters );
601     }
602     break;
603   case Downloader::mSave:
604     {
605       saveLink( fileName );
606     }
607     break;
608   default:
609     break;
610   }
611 }
612
613 /*!
614   \brief Load URL
615   \param url path to the file to be opened in the browser
616 */
617 void QtxWebBrowser::load( const QString& url )
618 {
619   QString u = url;
620   if ( !u.isEmpty() )
621     myWebView->load( QUrl( u.replace('\\', '/') ) );
622 }
623
624 /*!
625   \brief Save file
626   \param fileName link to the file being saved
627   Shows "Save file" standard dialog box to allow user to choose where to save the file.
628 */
629 void QtxWebBrowser::saveLink( const QString& fileName )
630 {
631   QString newFileName = QFileDialog::getSaveFileName( this, tr( "Save file" ), fileName, 
632                                                       QString( "*.%1" ).arg( QFileInfo( fileName ).suffix() ) );
633   if ( !newFileName.isEmpty() && 
634        QFileInfo( newFileName ).canonicalFilePath() != QFileInfo( fileName ).canonicalFilePath() ) {
635     QFile toFile( newFileName );
636     QFile fromFile( fileName );
637     if ( toFile.exists() && !toFile.remove() || !fromFile.copy( newFileName ) )
638       QMessageBox::warning( this, tr( "Error"), tr( "Can't save file:\n%1" ).arg( newFileName ) );
639   }
640 }
641
642 /*!
643   \brief Open file
644   Shows "Open file" standard dialog box to allow user to choose file to open.
645 */
646 void QtxWebBrowser::open()
647 {
648   QString url;
649   if ( isLocalFile( myWebView->url() ) ) url = myWebView->url().path();
650   url = QFileDialog::getOpenFileName( this, tr( "Open file" ), url, "HTML files (*.html *.htm);; All files (*)" );
651   if ( !url.isEmpty() ) load( url );
652 }