messages.cpp 12.1 KB
Newer Older
1
/*****************************************************************************
Pere Orga's avatar
Pere Orga committed
2
 * messages.cpp : Information about an item
3
 ****************************************************************************
4
 * Copyright (C) 2006-2011 the VideoLAN team
5
 * $Id$
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * Authors: Jean-Baptiste Kempf <jb (at) videolan.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/
23 24 25
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
26

27
#include <QPlainTextEdit>
28
#include <QTextCursor>
29
#include <QTextBlock>
30 31 32
#include <QFileDialog>
#include <QTextStream>
#include <QMessageBox>
33 34 35
#include <QTabWidget>
#include <QTreeWidget>
#include <QTreeWidgetItem>
36
#include <QMutex>
37
#include <QLineEdit>
38
#include <QScrollBar>
39 40
#include <QMutex>
#include <QMutexLocker>
41

42 43
#include <assert.h>

44 45 46 47 48
#include <vlc_common.h>
#include <vlc_input_item.h>

#include "dialogs/messages.hpp"

49
enum {
50
    MsgEvent_Type = QEvent::User + MsgEventTypeOffset + 1,
51 52 53 54 55
};

class MsgEvent : public QEvent
{
public:
56
    MsgEvent( int, const vlc_log_t *, const char * );
57 58 59 60 61 62 63

    int priority;
    uintptr_t object_id;
    QString object_type;
    QString header;
    QString module;
    QString text;
64 65
};

66
MsgEvent::MsgEvent( int type, const vlc_log_t *msg, const char *text )
67
    : QEvent( (QEvent::Type)MsgEvent_Type ),
68
      priority( type ),
69 70 71 72
      object_id( msg->i_object_id ),
      object_type( qfu(msg->psz_object_type) ),
      header( qfu(msg->psz_header) ),
      module( qfu(msg->psz_module) ),
73
      text( qfu(text) )
74 75 76
{
}

77 78
MessagesDialog::MessagesDialog( intf_thread_t *_p_intf)
               : QVLCFrame( _p_intf )
79
{
80
    setWindowTitle( qtr( "Messages" ) );
Nick Pope's avatar
Nick Pope committed
81
    setWindowRole( "vlc-messages" );
82 83
    /* Build Ui */
    ui.setupUi( this );
84 85
    ui.bottomButtonsBox->addButton( new QPushButton( qtr("&Close"), this ),
                                         QDialogButtonBox::RejectRole );
86

87
    /* Modules tree */
88
    ui.modulesTree->setHeaderHidden( true );
89

90
    /* Buttons and general layout */
91
    ui.saveLogButton->setToolTip( qtr( "Saves all the displayed logs to a file" ) );
92

93 94
    int i_verbosity = var_InheritInteger( p_intf, "verbose" );
    changeVerbosity( i_verbosity );
95
    ui.verbosityBox->setValue( qMin( i_verbosity, 2 ) );
96

97 98 99
    getSettings()->beginGroup( "Messages" );
    ui.filterEdit->setText( getSettings()->value( "messages-filter" ).toString() );
    getSettings()->endGroup();
100

101
    updateButton = new QPushButton( QIcon(":/update.svg"), "" );
102
    updateButton->setFlat( true );
103 104
    ui.mainTab->setCornerWidget( updateButton );

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
#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

120
    tabChanged(0);
121

122
    BUTTONACT( updateButton, updateOrClear() );
123
    BUTTONACT( ui.saveLogButton, save() );
124 125
    CONNECT( ui.filterEdit, editingFinished(), this, updateConfig() );
    CONNECT( ui.filterEdit, textChanged(QString), this, filterMessages() );
126 127
    CONNECT( ui.bottomButtonsBox, rejected(), this, hide() );
    CONNECT( ui.verbosityBox, valueChanged( int ),
128
             this, changeVerbosity( int ) );
129

130 131
    CONNECT( ui.mainTab, currentChanged( int ), this, tabChanged( int ) );

132
    /* General action */
133
    restoreWidgetPosition( "Messages", QSize( 600, 450 ) );
134 135

    /* Hook up to LibVLC messaging */
136
    vlc_LogSet( p_intf->obj.libvlc, MsgCallback, this );
137

138
    buildTree( NULL, VLC_OBJECT( p_intf->obj.libvlc ) );
139 140
}

141
MessagesDialog::~MessagesDialog()
142
{
143
    saveWidgetPosition( "Messages" );
144
    vlc_LogSet( p_intf->obj.libvlc, NULL, NULL );
145 146
};

147
void MessagesDialog::changeVerbosity( int i_verbosity )
148
{
149
    verbosity = i_verbosity;
150 151
}

152 153
void MessagesDialog::updateConfig()
{
154 155 156 157
    getSettings()->beginGroup( "Messages" );
    getSettings()->setValue( "messages-filter", ui.filterEdit->text() );
    getSettings()->endGroup();
}
158

159 160 161 162 163
void MessagesDialog::filterMessages()
{
    QMutexLocker locker( &messageLocker );
    QPlainTextEdit *messages = ui.messages;
    QTextBlock block = messages->document()->firstBlock();
164

165 166 167 168
    while( block.isValid() )
    {
        block.setVisible( matchFilter( block.text().toLower() ) );
        block = block.next();
169
    }
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

    /* Consider the whole QTextDocument as dirty now */
    messages->document()->markContentsDirty( 0, messages->document()->characterCount() );

    /* 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 );
}

bool MessagesDialog::matchFilter( const QString& text )
{
    const QString& filter = ui.filterEdit->text();

    if( filter.isEmpty() || text.contains( filter.toLower() ) )
        return true;
    return false;
188 189
}

190
void MessagesDialog::sinkMessage( const MsgEvent *msg )
191
{
192
    QMutexLocker locker( &messageLocker );
193

194
    QPlainTextEdit *messages = ui.messages;
195
    /* Only scroll if the viewport is at the end.
196
       Don't bug user by auto-changing/losing viewport on insert(). */
197 198 199 200
    bool b_autoscroll = ( messages->verticalScrollBar()->value()
                          + messages->verticalScrollBar()->pageStep()
                          >= messages->verticalScrollBar()->maximum() );

201 202 203 204 205 206 207 208
    /* 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 );
209

210 211 212 213
    /* 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 );
214

215
    switch ( msg->priority )
216
    {
217
        case VLC_MSG_INFO:
218
            buf += "<font color='blue'> info: </font>";
219 220
            break;
        case VLC_MSG_ERR:
221
            buf += "<font color='red'> error: </font>";
222 223
            break;
        case VLC_MSG_WARN:
224
            buf += "<font color='green'> warning: </font>";
225 226 227
            break;
        case VLC_MSG_DBG:
        default:
228
            buf += "<font color='grey'> debug: </font>";
229
            break;
230
    }
231

232 233 234 235 236 237 238 239 240 241 242 243 244
    /* Insert the prefix */
    messages->textCursor().insertHtml( buf /* + "</i>" */ );

    /* Insert the message */
    messages->textCursor().insertHtml( msg->text );

    /* Pass the new message thru the filter */
    QTextBlock b = messages->document()->lastBlock();
    b.setVisible( matchFilter( b.text() ) );

    /* Tell the QTextDocument to recompute the size of the given area */
    messages->document()->markContentsDirty( b.position(), b.length() );

245
    if ( b_autoscroll ) messages->ensureCursorVisible();
246
}
247

248 249
void MessagesDialog::customEvent( QEvent *event )
{
250
    MsgEvent *msge = static_cast<MsgEvent *>(event);
251

252
    assert( msge );
253
    sinkMessage( msge );
254 255
}

256
bool MessagesDialog::save()
257 258
{
    QString saveLogFileName = QFileDialog::getSaveFileName(
259
            this, qtr( "Save log file as..." ),
260
            QVLCUserDir( VLC_DOCUMENTS_DIR ),
261
            qtr( "Texts/Logs (*.log *.txt);; All (*.*)") );
262

263
    if( !saveLogFileName.isNull() )
264
    {
265 266 267
        QFile file( saveLogFileName );
        if ( !file.open( QFile::WriteOnly | QFile::Text ) ) {
            QMessageBox::warning( this, qtr( "Application" ),
268
                    qtr( "Cannot write to file %1:\n%2." )
269 270
                    .arg( saveLogFileName )
                    .arg( file.errorString() ) );
271 272 273
            return false;
        }

274
        QTextStream out( &file );
275

276 277 278 279 280 281 282 283
        QTextBlock block = ui.messages->document()->firstBlock();
        while( block.isValid() )
        {
            if( block.isVisible() )
                out << block.text() << "\n";

            block = block.next();
        }
284 285 286 287
        return true;
    }
    return false;
}
288

289 290 291 292 293 294 295 296
void MessagesDialog::buildTree( QTreeWidgetItem *parentItem,
                                vlc_object_t *p_obj )
{
    QTreeWidgetItem *item;

    if( parentItem )
        item = new QTreeWidgetItem( parentItem );
    else
297
        item = new QTreeWidgetItem( ui.modulesTree );
298

299
    char *name = vlc_object_get_name( p_obj );
300
    item->setText( 0, QString("%1%2 (0x%3)")
301
                   .arg( qfu( p_obj->obj.object_type ) )
302 303 304
                   .arg( ( name != NULL )
                         ? QString( " \"%1\"" ).arg( qfu( name ) )
                             : "" )
305
                   .arg( (uintptr_t)p_obj, 0, 16 )
306 307
                 );
    free( name );
308 309
    item->setExpanded( true );

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
    size_t count = 0, size;
    vlc_object_t **tab = NULL;

    do
    {
        delete[] tab;
        size = count;
        tab = new vlc_object_t *[size];
        count = vlc_list_children(p_obj, tab, size);
    }
    while (size < count);

    for (size_t i = 0; i < count ; i++)
    {
        buildTree( item, tab[i] );
        vlc_object_release(tab[i]);
    }

    delete[] tab;
329 330
}

331
void MessagesDialog::updateOrClear()
332
{
333 334 335
    if( ui.mainTab->currentIndex() == 1)
    {
        ui.modulesTree->clear();
336
        buildTree( NULL, VLC_OBJECT( p_intf->obj.libvlc ) );
337
    }
338
    else if( ui.mainTab->currentIndex() == 0 )
339
        ui.messages->clear();
340 341 342 343
#ifndef NDEBUG
    else
        updatePLTree();
#endif
344 345
}

346 347
void MessagesDialog::tabChanged( int i )
{
348
    updateButton->setIcon( i != 0 ? QIcon(":/update.svg") : QIcon(":/toolbar/clear.svg") );
349
    updateButton->setToolTip( i != 0 ? qtr("Update the tree")
350
                                     : qtr("Clear the messages") );
351 352
}

353
void MessagesDialog::MsgCallback( void *self, int type, const vlc_log_t *item,
354
                                  const char *format, va_list ap )
355
{
356
    MessagesDialog *dialog = (MessagesDialog *)self;
357
    char *str;
358
    int verbosity = dialog->verbosity.load();
359

360 361
    if( verbosity < 0 || verbosity < (type - VLC_MSG_ERR)
     || unlikely(vasprintf( &str, format, ap ) == -1) )
362
        return;
363

364
    int canc = vlc_savecancel();
365
    QApplication::postEvent( dialog, new MsgEvent( type, item, str ) );
366
    vlc_restorecancel( canc );
367
    free( str );
368
}
369 370 371 372 373

#ifndef NDEBUG
static QTreeWidgetItem * PLWalk( playlist_item_t *p_node )
{
    QTreeWidgetItem *current = new QTreeWidgetItem();
374 375 376 377 378 379 380 381 382
    if(p_node->p_input)
    {
        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( (uintptr_t)p_node->p_input ) );
        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 ) );
    }
383 384 385 386 387 388 389 390 391
    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();
392 393 394

    {
        vlc_playlist_locker pl_lock ( THEPL );
395
        pldebugTree->addTopLevelItem( PLWalk( &p_playlist->root ) );
396 397
    }

398 399 400 401 402
    pldebugTree->expandAll();
    for ( int i=0; i< 5; i++ )
        pldebugTree->resizeColumnToContents( i );
}
#endif