/*****************************************************************************
- * Messages.cpp : Information about an item
+ * messages.cpp : Information about an item
****************************************************************************
- * Copyright (C) 2006-2007 the VideoLAN team
+ * Copyright (C) 2006-2011 the VideoLAN team
* $Id$
*
* Authors: Jean-Baptiste Kempf <jb (at) videolan.org>
#endif
#include "dialogs/messages.hpp"
-#include "dialogs_provider.hpp"
-#include <QSpacerItem>
-#include <QSpinBox>
-#include <QLabel>
-#include <QTextEdit>
+#include <QPlainTextEdit>
#include <QTextCursor>
+#include <QTextBlock>
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
#include <QTabWidget>
#include <QTreeWidget>
#include <QTreeWidgetItem>
-#include <QHeaderView>
+#include <QMutex>
+#include <QLineEdit>
+#include <QScrollBar>
+#include <QMutex>
+#include <QMutexLocker>
-MessagesDialog *MessagesDialog::instance = NULL;
+#include <assert.h>
+
+enum {
+ MsgEvent_Type = QEvent::User + MsgEventTypeOffset + 1,
+};
+
+class MsgEvent : public QEvent
+{
+public:
+ MsgEvent( int, const vlc_log_t *, const char * );
+
+ int priority;
+ uintptr_t object_id;
+ QString object_type;
+ QString header;
+ QString module;
+ QString text;
+};
+
+MsgEvent::MsgEvent( int type, const vlc_log_t *msg, const char *text )
+ : QEvent( (QEvent::Type)MsgEvent_Type ),
+ priority( type ),
+ object_id( msg->i_object_id ),
+ object_type( qfu(msg->psz_object_type) ),
+ header( qfu(msg->psz_header) ),
+ module( qfu(msg->psz_module) ),
+ text( qfu(text) )
+{
+}
MessagesDialog::MessagesDialog( intf_thread_t *_p_intf)
: QVLCFrame( _p_intf )
{
setWindowTitle( qtr( "Messages" ) );
+ setWindowRole( "vlc-messages" );
+ /* Build Ui */
+ ui.setupUi( this );
+ ui.bottomButtonsBox->addButton( new QPushButton( qtr("&Close"), this ),
+ QDialogButtonBox::RejectRole );
- /* General widgets */
- QGridLayout *mainLayout = new QGridLayout( this );
- mainTab = new QTabWidget( this );
- mainTab->setTabPosition( QTabWidget::North );
-
-
- /* Messages */
- QWidget *msgWidget = new QWidget;
- QGridLayout *msgLayout = new QGridLayout( msgWidget );
+ /* Modules tree */
+ ui.modulesTree->setHeaderHidden( true );
- messages = new QTextEdit();
- messages->setReadOnly( true );
- messages->setGeometry( 0, 0, 440, 600 );
- messages->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
+ /* Buttons and general layout */
+ ui.saveLogButton->setToolTip( qtr( "Saves all the displayed logs to a file" ) );
+
+ int i_verbosity = var_InheritInteger( p_intf, "verbose" );
+ changeVerbosity( i_verbosity );
+ ui.verbosityBox->setValue( qMin( i_verbosity, 2 ) );
+
+ getSettings()->beginGroup( "Messages" );
+ ui.filterEdit->setText( getSettings()->value( "messages-filter" ).toString() );
+ getSettings()->endGroup();
+
+ updateButton = new QPushButton( QIcon(":/update"), "" );
+ updateButton->setFlat( true );
+ ui.mainTab->setCornerWidget( updateButton );
+
+#ifndef NDEBUG
+ QWidget *pldebugTab = new QWidget();
+ QVBoxLayout *pldebugTabLayout = new QVBoxLayout();
+ pldebugTab->setLayout( pldebugTabLayout );
+ ui.mainTab->addTab( pldebugTab, "Playlist Tree" );
+ pldebugTree = new QTreeWidget();
+ pldebugTree->headerItem()->setText( 0, "Name" );
+ pldebugTree->headerItem()->setText( 1, "PL id" );
+ pldebugTree->headerItem()->setText( 2, "Item id" );
+ pldebugTree->headerItem()->setText( 3, "PL flags" );
+ pldebugTree->headerItem()->setText( 4, "Item flags" );
+ pldebugTree->setColumnCount( 5 );
+ pldebugTabLayout->addWidget( pldebugTree );
+#endif
- msgLayout->addWidget( messages, 0, 0, 1, 0 );
- mainTab->addTab( msgWidget, qtr( "Messages" ) );
- ON_TIMEOUT( updateLog() );
+ tabChanged(0);
+ BUTTONACT( updateButton, updateOrClear() );
+ BUTTONACT( ui.saveLogButton, save() );
+ CONNECT( ui.filterEdit, editingFinished(), this, updateConfig() );
+ CONNECT( ui.filterEdit, textChanged(QString), this, filterMessages() );
+ CONNECT( ui.bottomButtonsBox, rejected(), this, hide() );
+ CONNECT( ui.verbosityBox, valueChanged( int ),
+ this, changeVerbosity( int ) );
- /* Modules tree */
- QWidget *treeWidget = new QWidget;
- QGridLayout *treeLayout = new QGridLayout( treeWidget );
+ CONNECT( ui.mainTab, currentChanged( int ), this, tabChanged( int ) );
- modulesTree = new QTreeWidget();
- modulesTree->header()->hide();
+ /* General action */
+ restoreWidgetPosition( "Messages", QSize( 600, 450 ) );
- treeLayout->addWidget( modulesTree, 0, 0, 1, 0 );
- mainTab->addTab( treeWidget, qtr( "Modules tree" ) );
+ /* Hook up to LibVLC messaging */
+ vlc_LogSet( p_intf->p_libvlc, MsgCallback, this );
+ buildTree( NULL, VLC_OBJECT( p_intf->p_libvlc ) );
+}
- /* Buttons and general layout */
- QPushButton *closeButton = new QPushButton( qtr( "&Close" ) );
- closeButton->setDefault( true );
- clearUpdateButton = new QPushButton( qtr( "&Clear" ) );
- saveLogButton = new QPushButton( qtr( "&Save as..." ) );
-
- verbosityBox = new QSpinBox();
- verbosityBox->setRange( 0, 2 );
- verbosityBox->setValue( config_GetInt( p_intf, "verbose" ) );
- verbosityBox->setWrapping( true );
- verbosityBox->setMaximumWidth( 50 );
-
- verbosityLabel = new QLabel( qtr( "Verbosity Level" ) );
-
- mainLayout->addWidget( mainTab, 0, 0, 1, 0 );
- mainLayout->addWidget( verbosityLabel, 1, 0, 1, 1 );
- mainLayout->addWidget( verbosityBox, 1, 1 );
- mainLayout->addWidget( saveLogButton, 1, 3 );
- mainLayout->addWidget( clearUpdateButton, 1, 4 );
- mainLayout->addWidget( closeButton, 1, 5 );
-
- BUTTONACT( closeButton, hide() );
- BUTTONACT( clearUpdateButton, clearOrUpdate() );
- BUTTONACT( saveLogButton, save() );
- CONNECT( mainTab, currentChanged( int ),
- this, updateTab( int ) );
+MessagesDialog::~MessagesDialog()
+{
+ saveWidgetPosition( "Messages" );
+ vlc_LogSet( p_intf->p_libvlc, NULL, NULL );
+};
- /* General action */
- readSettings( "Messages", QSize( 600, 450 ) );
+void MessagesDialog::changeVerbosity( int i_verbosity )
+{
+ vlc_atomic_set( &this->verbosity, i_verbosity );
}
-void MessagesDialog::updateTab( int index )
+void MessagesDialog::updateConfig()
{
- /* Second tab : modules tree */
- if( index == 1 )
- {
- verbosityLabel->hide();
- verbosityBox->hide();
- clearUpdateButton->setText( qtr( "&Update" ) );
- saveLogButton->hide();
- updateTree();
- }
- /* First tab : messages */
- else
- {
- verbosityLabel->show();
- verbosityBox->show();
- clearUpdateButton->setText( qtr( "&Clear" ) );
- saveLogButton->show();
- }
+ getSettings()->beginGroup( "Messages" );
+ getSettings()->setValue( "messages-filter", ui.filterEdit->text() );
+ getSettings()->endGroup();
}
-void MessagesDialog::updateLog()
+void MessagesDialog::filterMessages()
{
- msg_subscription_t *p_sub = p_intf->p_sys->p_sub;
- int i_start;
-
- vlc_mutex_lock( p_sub->p_lock );
- int i_stop = *p_sub->pi_stop;
- vlc_mutex_unlock( p_sub->p_lock );
+ QMutexLocker locker( &messageLocker );
+ QPlainTextEdit *messages = ui.messages;
+ QTextBlock block = messages->document()->firstBlock();
- if( p_sub->i_start != i_stop )
+ while( block.isValid() )
{
- messages->textCursor().movePosition( QTextCursor::End );
+ block.setVisible( matchFilter( block.text().toLower() ) );
+ block = block.next();
+ }
- for( i_start = p_sub->i_start;
- i_start != i_stop;
- i_start = (i_start+1) % VLC_MSG_QSIZE )
- {
- if( p_sub->p_msg[i_start].i_type == VLC_MSG_INFO ||
- p_sub->p_msg[i_start].i_type == VLC_MSG_ERR ||
- p_sub->p_msg[i_start].i_type == VLC_MSG_WARN &&
- verbosityBox->value() >= 1 ||
- p_sub->p_msg[i_start].i_type == VLC_MSG_DBG &&
- verbosityBox->value() >= 2 )
- {
- messages->setFontItalic( true );
- messages->setTextColor( "darkBlue" );
- messages->insertPlainText( qfu( p_sub->p_msg[i_start].psz_module ) );
- }
- else
- continue;
-
- switch( p_sub->p_msg[i_start].i_type )
- {
- case VLC_MSG_INFO:
- messages->setTextColor( "blue" );
- messages->insertPlainText( " info: " );
- break;
- case VLC_MSG_ERR:
- messages->setTextColor( "red" );
- messages->insertPlainText( " error: " );
- break;
- case VLC_MSG_WARN:
- messages->setTextColor( "green" );
- messages->insertPlainText( " warning: " );
- break;
- case VLC_MSG_DBG:
- default:
- messages->setTextColor( "grey" );
- messages->insertPlainText( " debug: " );
- break;
- }
-
- /* Add message Regular black Font */
- messages->setFontItalic( false );
- messages->setTextColor( "black" );
- messages->insertPlainText( qfu(p_sub->p_msg[i_start].psz_msg) );
- messages->insertPlainText( "\n" );
- }
- messages->ensureCursorVisible();
+ /* Consider the whole QTextDocument as dirty now */
+ messages->document()->markContentsDirty( 0, messages->document()->characterCount() );
- vlc_mutex_lock( p_sub->p_lock );
- p_sub->i_start = i_start;
- vlc_mutex_unlock( p_sub->p_lock );
- }
+ /* FIXME This solves a bug (Qt?) with the viewport not resizing the
+ vertical scroll bar when one or more QTextBlock are hidden */
+ QSize vsize = messages->viewport()->size();
+ messages->viewport()->resize( vsize + QSize( 1, 1 ) );
+ messages->viewport()->resize( vsize );
}
-void MessagesDialog::buildTree( QTreeWidgetItem *parentItem,
- vlc_object_t *p_obj )
+bool MessagesDialog::matchFilter( const QString& text )
{
- vlc_object_yield( p_obj );
- QTreeWidgetItem *item;
+ const QString& filter = ui.filterEdit->text();
- if( parentItem )
- item = new QTreeWidgetItem( parentItem );
- else
- item = new QTreeWidgetItem( modulesTree );
+ if( filter.isEmpty() || text.contains( filter.toLower() ) )
+ return true;
+ return false;
+}
- if( p_obj->psz_object_name )
- item->setText( 0, qfu( p_obj->psz_object_type ) + " \"" +
- qfu( p_obj->psz_object_name ) + "\" (" +
- QString::number(p_obj->i_object_id) + ")" );
- else
- item->setText( 0, qfu( p_obj->psz_object_type ) + " (" +
- QString::number(p_obj->i_object_id) + ")" );
+void MessagesDialog::sinkMessage( const MsgEvent *msg )
+{
+ QMutexLocker locker( &messageLocker );
- item->setExpanded( true );
+ QPlainTextEdit *messages = ui.messages;
+ /* Only scroll if the viewport is at the end.
+ Don't bug user by auto-changing/losing viewport on insert(). */
+ bool b_autoscroll = ( messages->verticalScrollBar()->value()
+ + messages->verticalScrollBar()->pageStep()
+ >= messages->verticalScrollBar()->maximum() );
+
+ /* Copy selected text to the clipboard */
+ if( messages->textCursor().hasSelection() )
+ messages->copy();
+
+ /* Fix selected text bug */
+ if( !messages->textCursor().atEnd() ||
+ messages->textCursor().anchor() != messages->textCursor().position() )
+ messages->moveCursor( QTextCursor::End );
+
+ /* Start a new logic block so we can hide it on-demand */
+ messages->textCursor().insertBlock();
+
+ QString buf = QString( "<i><font color='darkblue'>%1</font>" ).arg( msg->module );
- for( int i=0; i < p_obj->i_children; i++ )
+ switch ( msg->priority )
{
- buildTree( item, p_obj->pp_children[i]);
+ case VLC_MSG_INFO:
+ buf += "<font color='blue'> info: </font>";
+ break;
+ case VLC_MSG_ERR:
+ buf += "<font color='red'> error: </font>";
+ break;
+ case VLC_MSG_WARN:
+ buf += "<font color='green'> warning: </font>";
+ break;
+ case VLC_MSG_DBG:
+ default:
+ buf += "<font color='grey'> debug: </font>";
+ break;
}
- vlc_object_release( p_obj );
-}
+ /* Insert the prefix */
+ messages->textCursor().insertHtml( buf /* + "</i>" */ );
-void MessagesDialog::clearOrUpdate()
-{
- if( mainTab->currentIndex() )
- updateTree();
- else
- clear();
-}
+ /* Insert the message */
+ messages->textCursor().insertHtml( msg->text );
-void MessagesDialog::updateTree()
-{
- modulesTree->clear();
+ /* Pass the new message thru the filter */
+ QTextBlock b = messages->document()->lastBlock();
+ b.setVisible( matchFilter( b.text() ) );
- buildTree( NULL, VLC_OBJECT( p_intf->p_libvlc ) );
+ /* Tell the QTextDocument to recompute the size of the given area */
+ messages->document()->markContentsDirty( b.position(), b.length() );
+
+ if ( b_autoscroll ) messages->ensureCursorVisible();
}
-void MessagesDialog::clear()
+void MessagesDialog::customEvent( QEvent *event )
{
- messages->clear();
+ MsgEvent *msge = static_cast<MsgEvent *>(event);
+
+ assert( msge );
+ sinkMessage( msge );
}
bool MessagesDialog::save()
{
QString saveLogFileName = QFileDialog::getSaveFileName(
- this, qtr( "Choose a filename to save the logs under..." ),
- qfu( p_intf->p_libvlc->psz_homedir ),
+ this, qtr( "Save log file as..." ),
+ QVLCUserDir( VLC_DOCUMENTS_DIR ),
qtr( "Texts / Logs (*.log *.txt);; All (*.*) ") );
if( !saveLogFileName.isNull() )
QFile file( saveLogFileName );
if ( !file.open( QFile::WriteOnly | QFile::Text ) ) {
QMessageBox::warning( this, qtr( "Application" ),
- qtr( "Cannot write file %1:\n%2." )
+ qtr( "Cannot write to file %1:\n%2." )
.arg( saveLogFileName )
.arg( file.errorString() ) );
return false;
}
QTextStream out( &file );
- out << messages->toPlainText() << "\n";
+ QTextBlock block = ui.messages->document()->firstBlock();
+ while( block.isValid() )
+ {
+ if( block.isVisible() )
+ out << block.text() << "\n";
+
+ block = block.next();
+ }
return true;
}
return false;
}
+
+void MessagesDialog::buildTree( QTreeWidgetItem *parentItem,
+ vlc_object_t *p_obj )
+{
+ QTreeWidgetItem *item;
+
+ if( parentItem )
+ item = new QTreeWidgetItem( parentItem );
+ else
+ item = new QTreeWidgetItem( ui.modulesTree );
+
+ char *name = vlc_object_get_name( p_obj );
+ item->setText( 0, QString("%1%2 (0x%3)")
+ .arg( qfu( p_obj->psz_object_type ) )
+ .arg( ( name != NULL )
+ ? QString( " \"%1\"" ).arg( qfu( name ) )
+ : "" )
+ .arg( (uintptr_t)p_obj, 0, 16 )
+ );
+ free( name );
+ item->setExpanded( true );
+
+ vlc_list_t *l = vlc_list_children( p_obj );
+ for( int i=0; i < l->i_count; i++ )
+ buildTree( item, l->p_values[i].p_object );
+ vlc_list_release( l );
+}
+
+void MessagesDialog::updateOrClear()
+{
+ if( ui.mainTab->currentIndex() == 1)
+ {
+ ui.modulesTree->clear();
+ buildTree( NULL, VLC_OBJECT( p_intf->p_libvlc ) );
+ }
+ else if( ui.mainTab->currentIndex() == 0 )
+ ui.messages->clear();
+#ifndef NDEBUG
+ else
+ updatePLTree();
+#endif
+}
+
+void MessagesDialog::tabChanged( int i )
+{
+ updateButton->setIcon( i != 0 ? QIcon(":/update") : QIcon(":/toolbar/clear") );
+ updateButton->setToolTip( i != 0 ? qtr("Update the tree")
+ : qtr("Clear the messages") );
+}
+
+void MessagesDialog::MsgCallback( void *self, int type, const vlc_log_t *item,
+ const char *format, va_list ap )
+{
+ MessagesDialog *dialog = (MessagesDialog *)self;
+ char *str;
+ int verbosity = vlc_atomic_get( &dialog->verbosity );
+
+ if( verbosity < 0 || verbosity < (type - VLC_MSG_ERR)
+ || unlikely(vasprintf( &str, format, ap ) == -1) )
+ return;
+
+ int canc = vlc_savecancel();
+ QApplication::postEvent( dialog, new MsgEvent( type, item, str ) );
+ vlc_restorecancel( canc );
+ free( str );
+}
+
+#ifndef NDEBUG
+static QTreeWidgetItem * PLWalk( playlist_item_t *p_node )
+{
+ QTreeWidgetItem *current = new QTreeWidgetItem();
+ current->setText( 0, qfu( p_node->p_input->psz_name ) );
+ current->setToolTip( 0, qfu( p_node->p_input->psz_uri ) );
+ current->setText( 1, QString("%1").arg( p_node->i_id ) );
+ current->setText( 2, QString("%1").arg( p_node->p_input->i_id ) );
+ current->setText( 3, QString("0x%1").arg( p_node->i_flags, 0, 16 ) );
+ current->setText( 4, QString("0x%1").arg( p_node->p_input->i_type, 0, 16 ) );
+ for ( int i = 0; p_node->i_children > 0 && i < p_node->i_children; i++ )
+ current->addChild( PLWalk( p_node->pp_children[ i ] ) );
+ return current;
+}
+
+void MessagesDialog::updatePLTree()
+{
+ playlist_t *p_playlist = THEPL;
+ pldebugTree->clear();
+ PL_LOCK;
+ pldebugTree->addTopLevelItem( PLWalk( p_playlist->p_root_category ) );
+ PL_UNLOCK;
+ pldebugTree->expandAll();
+ for ( int i=0; i< 5; i++ )
+ pldebugTree->resizeColumnToContents( i );
+}
+#endif