]> git.sesse.net Git - vlc/blob - modules/gui/qt4/dialogs/messages.cpp
Qt: message dialog, use the update button and fix the build
[vlc] / modules / gui / qt4 / dialogs / messages.cpp
1 /*****************************************************************************
2  * messages.cpp : Information about an item
3  ****************************************************************************
4  * Copyright (C) 2006-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jean-Baptiste Kempf <jb (at) videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include "dialogs/messages.hpp"
28
29 #include <QPlainTextEdit>
30 #include <QTextCursor>
31 #include <QTextBlock>
32 #include <QFileDialog>
33 #include <QTextStream>
34 #include <QMessageBox>
35 #include <QTabWidget>
36 #include <QTreeWidget>
37 #include <QTreeWidgetItem>
38 #include <QMutex>
39 #include <QLineEdit>
40 #include <QScrollBar>
41 #include <QMutex>
42 #include <QMutexLocker>
43
44 #include <assert.h>
45
46 enum {
47     MsgEvent_Type = QEvent::User + MsgEventTypeOffset + 1,
48 };
49
50 class MsgEvent : public QEvent
51 {
52 public:
53     MsgEvent( int, const msg_item_t *, const char * );
54
55     int priority;
56     uintptr_t object_id;
57     QString object_type;
58     QString header;
59     QString module;
60     QString text;
61 };
62
63 MsgEvent::MsgEvent( int type, const msg_item_t *msg, const char *text )
64     : QEvent( (QEvent::Type)MsgEvent_Type ),
65       priority( type ),
66       object_id( msg->i_object_id ),
67       object_type( qfu(msg->psz_object_type) ),
68       header( qfu(msg->psz_header) ),
69       module( qfu(msg->psz_module) ),
70       text( qfu(text) )
71 {
72 }
73
74 MessagesDialog::MessagesDialog( intf_thread_t *_p_intf)
75                : QVLCFrame( _p_intf )
76 {
77     setWindowTitle( qtr( "Messages" ) );
78     setWindowRole( "vlc-messages" );
79     /* Build Ui */
80     ui.setupUi( this );
81     ui.bottomButtonsBox->addButton( new QPushButton( qtr("&Close"), this ),
82                                          QDialogButtonBox::RejectRole );
83
84     /* Modules tree */
85     ui.modulesTree->setHeaderHidden( true );
86
87     /* Buttons and general layout */
88     ui.saveLogButton->setToolTip( qtr( "Saves all the displayed logs to a file" ) );
89
90     int i_verbosity = var_InheritInteger( p_intf, "verbose" );
91     changeVerbosity( i_verbosity );
92     ui.verbosityBox->setValue( qMin( i_verbosity, 2 ) );
93
94     getSettings()->beginGroup( "Messages" );
95     ui.filterEdit->setText( getSettings()->value( "messages-filter" ).toString() );
96     getSettings()->endGroup();
97
98     updateButton = new QPushButton( QIcon(":/update"), "" );
99     updateButton->setFlat( true );
100     ui.mainTab->setCornerWidget( updateButton );
101
102 #ifndef NDEBUG
103     QWidget *pldebugTab = new QWidget();
104     QVBoxLayout *pldebugTabLayout = new QVBoxLayout();
105     pldebugTab->setLayout( pldebugTabLayout );
106     ui.mainTab->addTab( pldebugTab, "Playlist Tree" );
107     pldebugTree = new QTreeWidget();
108     pldebugTree->headerItem()->setText( 0, "Name" );
109     pldebugTree->headerItem()->setText( 1, "PL id" );
110     pldebugTree->headerItem()->setText( 2, "Item id" );
111     pldebugTree->headerItem()->setText( 3, "PL flags" );
112     pldebugTree->headerItem()->setText( 4, "Item flags" );
113     pldebugTree->setColumnCount( 5 );
114     pldebugTabLayout->addWidget( pldebugTree );
115 #endif
116
117     tabChanged(0);
118
119     BUTTONACT( updateButton, updateOrClear() );
120     BUTTONACT( ui.saveLogButton, save() );
121     CONNECT( ui.filterEdit, editingFinished(), this, updateConfig() );
122     CONNECT( ui.filterEdit, textChanged(QString), this, filterMessages() );
123     CONNECT( ui.bottomButtonsBox, rejected(), this, hide() );
124     CONNECT( ui.verbosityBox, valueChanged( int ),
125              this, changeVerbosity( int ) );
126
127     CONNECT( ui.mainTab, currentChanged( int ), this, tabChanged( int ) );
128
129     /* General action */
130     restoreWidgetPosition( "Messages", QSize( 600, 450 ) );
131
132     /* Hook up to LibVLC messaging */
133     vlc_Subscribe( &sub, MsgCallback, this );
134
135     buildTree( NULL, VLC_OBJECT( p_intf->p_libvlc ) );
136 }
137
138 MessagesDialog::~MessagesDialog()
139 {
140     saveWidgetPosition( "Messages" );
141     vlc_Unsubscribe( &sub );
142 };
143
144 void MessagesDialog::changeVerbosity( int i_verbosity )
145 {
146     vlc_atomic_set( &this->verbosity, i_verbosity );
147 }
148
149 void MessagesDialog::updateConfig()
150 {
151     getSettings()->beginGroup( "Messages" );
152     getSettings()->setValue( "messages-filter", ui.filterEdit->text() );
153     getSettings()->endGroup();
154 }
155
156 void MessagesDialog::filterMessages()
157 {
158     QMutexLocker locker( &messageLocker );
159     QPlainTextEdit *messages = ui.messages;
160     QTextBlock block = messages->document()->firstBlock();
161
162     while( block.isValid() )
163     {
164         block.setVisible( matchFilter( block.text().toLower() ) );
165         block = block.next();
166     }
167
168     /* Consider the whole QTextDocument as dirty now */
169     messages->document()->markContentsDirty( 0, messages->document()->characterCount() );
170
171     /* FIXME This solves a bug (Qt?) with the viewport not resizing the
172        vertical scroll bar when one or more QTextBlock are hidden */
173     QSize vsize = messages->viewport()->size();
174     messages->viewport()->resize( vsize + QSize( 1, 1 ) );
175     messages->viewport()->resize( vsize );
176 }
177
178 bool MessagesDialog::matchFilter( const QString& text )
179 {
180     const QString& filter = ui.filterEdit->text();
181
182     if( filter.isEmpty() || text.contains( filter.toLower() ) )
183         return true;
184     return false;
185 }
186
187 void MessagesDialog::sinkMessage( const MsgEvent *msg )
188 {
189     QMutexLocker locker( &messageLocker );
190
191     QPlainTextEdit *messages = ui.messages;
192     /* Only scroll if the viewport is at the end.
193        Don't bug user by auto-changing/losing viewport on insert(). */
194     bool b_autoscroll = ( messages->verticalScrollBar()->value()
195                           + messages->verticalScrollBar()->pageStep()
196                           >= messages->verticalScrollBar()->maximum() );
197
198     /* Copy selected text to the clipboard */
199     if( messages->textCursor().hasSelection() )
200         messages->copy();
201
202     /* Fix selected text bug */
203     if( !messages->textCursor().atEnd() ||
204          messages->textCursor().anchor() != messages->textCursor().position() )
205          messages->moveCursor( QTextCursor::End );
206
207     /* Start a new logic block so we can hide it on-demand */
208     messages->textCursor().insertBlock();
209
210     QString buf = QString( "<i><font color='darkblue'>%1</font>" ).arg( msg->module );
211
212     switch ( msg->priority )
213     {
214         case VLC_MSG_INFO:
215             buf += "<font color='blue'> info: </font>";
216             break;
217         case VLC_MSG_ERR:
218             buf += "<font color='red'> error: </font>";
219             break;
220         case VLC_MSG_WARN:
221             buf += "<font color='green'> warning: </font>";
222             break;
223         case VLC_MSG_DBG:
224         default:
225             buf += "<font color='grey'> debug: </font>";
226             break;
227     }
228
229     /* Insert the prefix */
230     messages->textCursor().insertHtml( buf /* + "</i>" */ );
231
232     /* Insert the message */
233     messages->textCursor().insertHtml( msg->text );
234
235     /* Pass the new message thru the filter */
236     QTextBlock b = messages->document()->lastBlock();
237     b.setVisible( matchFilter( b.text() ) );
238
239     /* Tell the QTextDocument to recompute the size of the given area */
240     messages->document()->markContentsDirty( b.position(), b.length() );
241
242     if ( b_autoscroll ) messages->ensureCursorVisible();
243 }
244
245 void MessagesDialog::customEvent( QEvent *event )
246 {
247     MsgEvent *msge = static_cast<MsgEvent *>(event);
248
249     assert( msge );
250     sinkMessage( msge );
251 }
252
253 bool MessagesDialog::save()
254 {
255     QString saveLogFileName = QFileDialog::getSaveFileName(
256             this, qtr( "Save log file as..." ),
257             QVLCUserDir( VLC_DOCUMENTS_DIR ),
258             qtr( "Texts / Logs (*.log *.txt);; All (*.*) ") );
259
260     if( !saveLogFileName.isNull() )
261     {
262         QFile file( saveLogFileName );
263         if ( !file.open( QFile::WriteOnly | QFile::Text ) ) {
264             QMessageBox::warning( this, qtr( "Application" ),
265                     qtr( "Cannot write to file %1:\n%2." )
266                     .arg( saveLogFileName )
267                     .arg( file.errorString() ) );
268             return false;
269         }
270
271         QTextStream out( &file );
272
273         QTextBlock block = ui.messages->document()->firstBlock();
274         while( block.isValid() )
275         {
276             if( block.isVisible() )
277                 out << block.text() << "\n";
278
279             block = block.next();
280         }
281         return true;
282     }
283     return false;
284 }
285
286 void MessagesDialog::buildTree( QTreeWidgetItem *parentItem,
287                                 vlc_object_t *p_obj )
288 {
289     QTreeWidgetItem *item;
290
291     if( parentItem )
292         item = new QTreeWidgetItem( parentItem );
293     else
294         item = new QTreeWidgetItem( ui.modulesTree );
295
296     char *name = vlc_object_get_name( p_obj );
297     item->setText( 0, QString("%1%2 (0x%3)")
298                    .arg( qfu( p_obj->psz_object_type ) )
299                    .arg( ( name != NULL )
300                          ? QString( " \"%1\"" ).arg( qfu( name ) )
301                              : "" )
302                    .arg( (uintptr_t)p_obj, 0, 16 )
303                  );
304     free( name );
305     item->setExpanded( true );
306
307     vlc_list_t *l = vlc_list_children( p_obj );
308     for( int i=0; i < l->i_count; i++ )
309         buildTree( item, l->p_values[i].p_object );
310     vlc_list_release( l );
311 }
312
313 void MessagesDialog::updateOrClear()
314 {
315     if( ui.mainTab->currentIndex() == 1)
316     {
317         ui.modulesTree->clear();
318         buildTree( NULL, VLC_OBJECT( p_intf->p_libvlc ) );
319     }
320     else if( ui.mainTab->currentIndex() == 0 )
321         ui.messages->clear();
322 #ifndef NDEBUG
323     else
324         updatePLTree();
325 #endif
326 }
327
328 void MessagesDialog::tabChanged( int i )
329 {
330     updateButton->setIcon( i != 0 ? QIcon(":/update") : QIcon(":/toolbar/clear") );
331     updateButton->setToolTip( i != 0 ? qtr("Update the tree")
332                                      : qtr("Clear the messages") );
333 }
334
335 void MessagesDialog::MsgCallback( void *self, int type, const msg_item_t *item,
336                                   const char *format, va_list ap )
337 {
338     MessagesDialog *dialog = (MessagesDialog *)self;
339     char *str;
340     int verbosity = vlc_atomic_get( &dialog->verbosity );
341
342     if( verbosity < 0 || verbosity < (type - VLC_MSG_ERR)
343      || unlikely(vasprintf( &str, format, ap ) == -1) )
344         return;
345
346     int canc = vlc_savecancel();
347     QApplication::postEvent( dialog, new MsgEvent( type, item, str ) );
348     vlc_restorecancel( canc );
349     free( str );
350 }
351
352 #ifndef NDEBUG
353 static QTreeWidgetItem * PLWalk( playlist_item_t *p_node )
354 {
355     QTreeWidgetItem *current = new QTreeWidgetItem();
356     current->setText( 0, qfu( p_node->p_input->psz_name ) );
357     current->setToolTip( 0, qfu( p_node->p_input->psz_uri ) );
358     current->setText( 1, QString("%1").arg( p_node->i_id ) );
359     current->setText( 2, QString("%1").arg( p_node->p_input->i_id ) );
360     current->setText( 3, QString("0x%1").arg( p_node->i_flags, 0, 16 ) );
361     current->setText( 4, QString("0x%1").arg(  p_node->p_input->i_type, 0, 16 ) );
362     for ( int i = 0; p_node->i_children > 0 && i < p_node->i_children; i++ )
363         current->addChild( PLWalk( p_node->pp_children[ i ] ) );
364     return current;
365 }
366
367 void MessagesDialog::updatePLTree()
368 {
369     playlist_t *p_playlist = THEPL;
370     pldebugTree->clear();
371     PL_LOCK;
372     pldebugTree->addTopLevelItem( PLWalk( p_playlist->p_root_category ) );
373     PL_UNLOCK;
374     pldebugTree->expandAll();
375     for ( int i=0; i< 5; i++ )
376         pldebugTree->resizeColumnToContents( i );
377 }
378 #endif