thumbnaillist.cpp 18 KB
Newer Older
Albert Astals Cid's avatar
Albert Astals Cid committed
1
/***************************************************************************
2
 *   Copyright (C) 2004 by Albert Astals Cid <tsdgeos@terra.es>            *
Albert Astals Cid's avatar
Albert Astals Cid committed
3 4 5 6 7 8 9
 *                                                                         *
 *   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.                                   *
 ***************************************************************************/

10
// qt/kde includes
11
#include <qtimer.h>
12
#include <qpainter.h>
13
#include <klocale.h>
14 15
#include <kurl.h>
#include <kurldrag.h>
16
#include <kaction.h>
17
#include <kiconloader.h>
18
#include <kactioncollection.h>
19

Enrico Ros's avatar
Enrico Ros committed
20 21 22
// system includes
#include <math.h>

23
// local includes
Albert Astals Cid's avatar
Albert Astals Cid committed
24
#include "thumbnaillist.h"
25
#include "pagepainter.h"
26
#include "searchwidget.h"   // for SW_SEARCH_ID
27
#include "core/document.h"
28 29
#include "core/generator.h"
#include "core/page.h"
30
#include "conf/settings.h"
31

32 33 34 35
// ThumbnailWidget represents a single thumbnail in the ThumbnailList
class ThumbnailWidget : public QWidget
{
    public:
36
        ThumbnailWidget( QWidget * parent, const KPDFPage * page, ThumbnailList * tl );
37 38 39 40 41 42 43

        // set internal parameters to fit the page in the given width
        void resizeFitWidth( int width );
        // set thumbnail's selected state
        void setSelected( bool selected );

        // query methods
Enrico Ros's avatar
Enrico Ros committed
44
        int heightHint() const { return m_pixmapHeight + m_labelHeight + m_margin; }
45 46 47
        int pixmapWidth() const { return m_pixmapWidth; }
        int pixmapHeight() const { return m_pixmapHeight; }
        int pageNumber() const { return m_page->number(); }
48
        const KPDFPage * page() const { return m_page; }
49 50

    protected:
51
        void mouseReleaseEvent( QMouseEvent * e );
52 53 54
        void paintEvent(QPaintEvent *);

    private:
Enrico Ros's avatar
Enrico Ros committed
55 56 57
        // the margin around the widget
        static int const m_margin = 16;

58 59
        // used to access 'forwardRightClick( .. )' and 'getBookmarkOverlay()'
        ThumbnailList * m_tl;
60 61 62 63 64 65 66 67 68
        const KPDFPage * m_page;
        bool m_selected;
        int m_pixmapWidth, m_pixmapHeight;
        int m_labelHeight, m_labelNumber;
};


/** ThumbnailList implementation **/

69 70
ThumbnailList::ThumbnailList( QWidget *parent, KPDFDocument *document )
	: QScrollView( parent, "KPDF::Thumbnails", WNoAutoErase | WStaticContents ),
71
	m_document( document ), m_selected( 0 ), m_delayTimer( 0 ), m_bookmarkOverlay( 0 )
Albert Astals Cid's avatar
Albert Astals Cid committed
72
{
73 74 75 76 77 78 79
	// set scrollbars
	setHScrollBarMode( QScrollView::AlwaysOff );
	setVScrollBarMode( QScrollView::AlwaysOn );

	// dealing with large areas so enable clipper
	enableClipper( true );

80 81
	// widget setup: can be focused by tab and mouse click (not wheel)
	viewport()->setFocusProxy( this );
82
	viewport()->setFocusPolicy( StrongFocus );
83 84
	setResizePolicy( Manual );
	setAcceptDrops( true );
85
	setDragAutoScroll( false );
86

87 88 89
	// set contents background to the 'base' color
	viewport()->setPaletteBackgroundColor( palette().active().base() );

90
	setFrameStyle( StyledPanel | Raised );
91
	connect( this, SIGNAL(contentsMoving(int, int)), this, SLOT(slotRequestVisiblePixmaps(int, int)) );
Albert Astals Cid's avatar
Albert Astals Cid committed
92 93
}

94 95 96 97
ThumbnailList::~ThumbnailList()
{
    delete m_bookmarkOverlay;
}
98

99 100
//BEGIN DocumentObserver inherited methods 
void ThumbnailList::notifySetup( const QValueVector< KPDFPage * > & pages, bool /*documentChanged*/ )
101
{
102
	// delete all the Thumbnails
103 104 105
	QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
	for ( ; tIt != tEnd; ++tIt )
		delete *tIt;
106
	m_thumbnails.clear();
107
	m_visibleThumbnails.clear();
108 109 110
	m_selected = 0;

	if ( pages.count() < 1 )
111 112
	{
		resizeContents( 0, 0 );
113
		return;
114
	}
115

116
    // show pages containing hilighted text or bookmarked ones
117
    //RESTORE THIS int flags = Settings::filterBookmarks() ? KPDFPage::Bookmark : KPDFPage::Highlight;
118 119 120 121 122

    // if no page matches filter rule, then display all pages
    QValueVector< KPDFPage * >::const_iterator pIt = pages.begin(), pEnd = pages.end();
    bool skipCheck = true;
    for ( ; pIt != pEnd ; ++pIt )
123
        //if ( (*pIt)->attributes() & flags )
124
        if ( (*pIt)->hasHighlights( SW_SEARCH_ID ) )
Enrico Ros's avatar
Enrico Ros committed
125
            skipCheck = false;
126

127 128 129 130
    // generate Thumbnails for the given set of pages
    int width = clipper()->width(),
        totalHeight = 0;
    for ( pIt = pages.begin(); pIt != pEnd ; ++pIt )
131
        //if ( skipCheck || (*pIt)->attributes() & flags )
132
        if ( skipCheck || (*pIt)->hasHighlights( SW_SEARCH_ID ) )
133
        {
134
            ThumbnailWidget * t = new ThumbnailWidget( viewport(), *pIt, this );
135 136 137 138 139 140 141 142 143 144
            t->setFocusProxy( this );
            // add to the scrollview
            addChild( t, 0, totalHeight );
            // add to the internal queue
            m_thumbnails.push_back( t );
            // update total height (asking widget its own height)
            t->resizeFitWidth( width );
            totalHeight += t->heightHint() + 4;
            t->show();
        }
145

Enrico Ros's avatar
Enrico Ros committed
146 147
    // update scrollview's contents size (sets scrollbars limits)
    resizeContents( width, totalHeight );
148

Enrico Ros's avatar
Enrico Ros committed
149 150
    // request for thumbnail generation
    delayedRequestVisiblePixmaps( 200 );
Albert Astals Cid's avatar
Albert Astals Cid committed
151 152
}

Enrico Ros's avatar
Enrico Ros committed
153
void ThumbnailList::notifyViewportChanged( bool /*smoothMove*/ )
Albert Astals Cid's avatar
Albert Astals Cid committed
154
{
155 156 157 158 159
	// skip notifies for the current page (already selected)
	int newPage = m_document->viewport().pageNumber;
	if ( m_selected && m_selected->pageNumber() == newPage )
		return;

160
	// deselect previous thumbnail
161 162 163 164
	if ( m_selected )
		m_selected->setSelected( false );
	m_selected = 0;

165
	// select the page with viewport and ensure it's centered in the view
166
	m_vectorIndex = 0;
167 168
	QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
	for ( ; tIt != tEnd; ++tIt )
169
	{
170
		if ( (*tIt)->pageNumber() == newPage )
171
		{
172
			m_selected = *tIt;
173
			m_selected->setSelected( true );
174 175 176 177 178
			if ( Settings::syncThumbnailsViewport() )
			{
				int yOffset = QMAX( visibleHeight() / 4, m_selected->height() / 2 );
				ensureVisible( 0, childY( m_selected ) + m_selected->height()/2, 0, yOffset );
			}
179 180
			break;
		}
181
		m_vectorIndex++;
182
	}
183 184
}

185
void ThumbnailList::notifyPageChanged( int pageNumber, int /*changedFlags*/ )
186
{
187 188 189
    // only handle pixmap changed notifies (the only defined for now)
    //if ( !(changedFlags & DocumentObserver::Pixmap) )
    //    return;
190

191
    // iterate over visible items: if page(pageNumber) is one of them, repaint it
192 193 194 195 196 197 198 199 200
    QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
    for ( ; vIt != vEnd; ++vIt )
        if ( (*vIt)->pageNumber() == pageNumber )
        {
            (*vIt)->update();
            break;
        }
}

201
void ThumbnailList::notifyContentsCleared( int changedFlags )
202
{
203 204 205 206 207 208 209 210 211 212 213 214 215 216
    // if pixmaps were cleared, re-ask them
    if ( changedFlags & DocumentObserver::Pixmap )
        slotRequestVisiblePixmaps();
}

bool ThumbnailList::canUnloadPixmap( int pageNumber )
{
    // if the thubnail 'pageNumber' is one of the visible ones, forbid unloading
    QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
    for ( ; vIt != vEnd; ++vIt )
        if ( (*vIt)->pageNumber() == pageNumber )
            return false;
    // if hidden permit unloading
    return true;
217
}
218
//END DocumentObserver inherited methods 
219

220

221 222 223 224
void ThumbnailList::updateWidgets()
{
    // find all widgets that intersects the viewport and update them
    QRect viewportRect( contentsX(), contentsY(), visibleWidth(), visibleHeight() );
225 226
    QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
    for ( ; vIt != vEnd; ++vIt )
227
    {
228
        ThumbnailWidget * t = *vIt;
229
        QRect widgetRect( childX( t ), childY( t ), t->width(), t->height() );
230 231 232 233 234 235
        // update only the exposed area of the widget (saves pixels..)
        QRect relativeRect = viewportRect.intersect( widgetRect );
        if ( !relativeRect.isValid() )
            continue;
        relativeRect.moveBy( -widgetRect.left(), -widgetRect.top() );
        t->update( relativeRect );
236 237 238
    }
}

239 240 241 242 243
void ThumbnailList::forwardRightClick( const KPDFPage * p, const QPoint & t )
{
    emit rightClick( p, t );
}

244 245 246 247 248
const QPixmap * ThumbnailList::getBookmarkOverlay() const
{
    return m_bookmarkOverlay;
}

249
void ThumbnailList::slotFilterBookmarks( bool filterOn )
250
{
251 252
    // save state
    Settings::setFilterBookmarks( filterOn );
253
    // ask for the 'notifySetup' with a little trick (on reinsertion the
254 255 256
    // document sends the list again)
    m_document->removeObserver( this );
    m_document->addObserver( this );
257 258
}

259

260 261
//BEGIN widget events 
void ThumbnailList::keyPressEvent( QKeyEvent * keyEvent )
262
{
263
	if ( m_thumbnails.count() < 1 )
264
		return keyEvent->ignore();
265 266

	int nextPage = -1;
267 268 269
	if ( keyEvent->key() == Key_Up )
	{
		if ( !m_selected )
270
			nextPage = 0;
271 272
		else if ( m_vectorIndex > 0 )
			nextPage = m_thumbnails[ m_vectorIndex - 1 ]->pageNumber();
273 274 275 276
	}
	else if ( keyEvent->key() == Key_Down )
	{
		if ( !m_selected )
277
			nextPage = 0;
278 279
		else if ( m_vectorIndex < (int)m_thumbnails.count() - 1 )
			nextPage = m_thumbnails[ m_vectorIndex + 1 ]->pageNumber();
280
	}
281 282 283 284
	else if ( keyEvent->key() == Key_PageUp )
		verticalScrollBar()->subtractPage();
	else if ( keyEvent->key() == Key_PageDown )
		verticalScrollBar()->addPage();
285
	else if ( keyEvent->key() == Key_Home )
286
		nextPage = m_thumbnails[ 0 ]->pageNumber();
287
	else if ( keyEvent->key() == Key_End )
288
		nextPage = m_thumbnails[ m_thumbnails.count() - 1 ]->pageNumber();
289 290

	if ( nextPage == -1 )
291
		return keyEvent->ignore();
292

293
	keyEvent->accept();
294 295 296
	if ( m_selected )
		m_selected->setSelected( false );
	m_selected = 0;
297
	m_document->setViewportPage( nextPage );
298
}
299

300 301
void ThumbnailList::contentsMousePressEvent( QMouseEvent * e )
{
302 303
	if ( e->button() != Qt::LeftButton )
		return;
304
	int clickY = e->y();
305 306
	QValueList<ThumbnailWidget *>::iterator vIt = m_visibleThumbnails.begin(), vEnd = m_visibleThumbnails.end();
	for ( ; vIt != vEnd; ++vIt )
307
	{
308
		ThumbnailWidget * t = *vIt;
309 310 311
		int childTop = childY(t);
		if ( clickY > childTop && clickY < (childTop + t->height()) )
		{
312 313
			if ( m_document->viewport().pageNumber != t->pageNumber() )
				m_document->setViewportPage( t->pageNumber() );
314
			break;
315
		}
316 317 318
	}
}

319
void ThumbnailList::viewportResizeEvent( QResizeEvent * e )
320
{
321
	if ( m_thumbnails.count() < 1 || width() < 1 )
322
		return;
323

324 325 326
	// if width changed resize all the Thumbnails, reposition them to the
	// right place and recalculate the contents area
	if ( e->size().width() != e->oldSize().width() )
327
	{
328
		// runs the timer avoiding a thumbnail regeneration by 'contentsMoving'
Enrico Ros's avatar
Enrico Ros committed
329
		delayedRequestVisiblePixmaps( 2000 );
330

331
		// resize and reposition items
332 333
		int totalHeight = 0,
		    newWidth = e->size().width();
334 335
		QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
		for ( ; tIt != tEnd; ++tIt )
336
		{
337
			ThumbnailWidget *t = *tIt;
338
			moveChild( t, 0, totalHeight );
339
			t->resizeFitWidth( newWidth );
340 341
			totalHeight += t->heightHint() + 4;
		}
342 343 344 345

		// update scrollview's contents size (sets scrollbars limits)
		resizeContents( newWidth, totalHeight );

346 347 348
		// ensure selected item remains visible
		if ( m_selected )
			ensureVisible( 0, childY( m_selected ) + m_selected->height()/2, 0, visibleHeight()/2 );
349
	}
350
	else if ( e->size().height() <= e->oldSize().height() )
351
		return;
352 353 354 355 356 357 358 359

	// invalidate the bookmark overlay
	if ( m_bookmarkOverlay )
	{
		delete m_bookmarkOverlay;
		m_bookmarkOverlay = 0;
	}

360
	// update Thumbnails since width has changed or height has increased
Enrico Ros's avatar
Enrico Ros committed
361
	delayedRequestVisiblePixmaps( 500 );
362
}
363 364 365 366 367 368 369 370 371 372 373 374

void ThumbnailList::dragEnterEvent( QDragEnterEvent * ev )
{
    ev->accept();
}

void ThumbnailList::dropEvent( QDropEvent * ev )
{
    KURL::List lst;
    if (  KURLDrag::decode(  ev, lst ) )
        emit urlDropped( lst.first() );
}
375
//END widget events
376

377
//BEGIN internal SLOTS 
378
void ThumbnailList::slotRequestVisiblePixmaps( int /*newContentsX*/, int newContentsY )
379
{
380 381 382
    // if an update is already scheduled or the widget is hidden, don't proceed
    if ( (m_delayTimer && m_delayTimer->isActive()) || !isShown() )
        return;
383

384 385
    int vHeight = visibleHeight(),
        vOffset = newContentsY == -1 ? contentsY() : newContentsY;
386

387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
    // scroll from the top to the last visible thumbnail
    m_visibleThumbnails.clear();
    QValueList< PixmapRequest * > requestedPixmaps;
    QValueVector<ThumbnailWidget *>::iterator tIt = m_thumbnails.begin(), tEnd = m_thumbnails.end();
    for ( ; tIt != tEnd; ++tIt )
    {
        ThumbnailWidget * t = *tIt;
        int top = childY( t ) - vOffset;
        if ( top > vHeight )
            break;
        if ( top + t->height() < 0 )
            continue;
        // add ThumbnailWidget to visible list
        m_visibleThumbnails.push_back( t );
        // if pixmap not present add it to requests
        if ( !t->page()->hasPixmap( THUMBNAILS_ID, t->pixmapWidth(), t->pixmapHeight() ) )
Enrico Ros's avatar
Enrico Ros committed
403 404 405 406 407
        {
            PixmapRequest * p = new PixmapRequest(
                    THUMBNAILS_ID, t->pageNumber(), t->pixmapWidth(), t->pixmapHeight(), THUMBNAILS_PRIO, true );
            requestedPixmaps.push_back( p );
        }
408 409 410 411
    }

    // actually request pixmaps
    if ( !requestedPixmaps.isEmpty() )
Enrico Ros's avatar
Enrico Ros committed
412
        m_document->requestPixmaps( requestedPixmaps );
413
}
414 415 416 417 418 419 420 421 422 423 424 425 426 427

void ThumbnailList::slotDelayTimeout()
{
    // resize the bookmark overlay
    delete m_bookmarkOverlay;
    int expectedWidth = contentsWidth() / 4;
    if ( expectedWidth > 10 )
        m_bookmarkOverlay = new QPixmap( DesktopIcon( "attach", expectedWidth ) );
    else
        m_bookmarkOverlay = 0;

    // request pixmaps
    slotRequestVisiblePixmaps();
}
428
//END internal SLOTS
429

Enrico Ros's avatar
Enrico Ros committed
430
void ThumbnailList::delayedRequestVisiblePixmaps( int delayMs )
431
{
432
	if ( !m_delayTimer )
433
	{
434
		m_delayTimer = new QTimer( this );
435
		connect( m_delayTimer, SIGNAL( timeout() ), this, SLOT( slotDelayTimeout() ) );
436
	}
437
	m_delayTimer->start( delayMs, true );
438 439
}

440 441 442

/** ThumbnailWidget implementation **/

443
ThumbnailWidget::ThumbnailWidget( QWidget * parent, const KPDFPage * kp, ThumbnailList * tl )
444
    : QWidget( parent, 0, WNoAutoErase ), m_tl( tl ), m_page( kp ),
445 446 447 448 449 450 451 452
    m_selected( false ), m_pixmapWidth( 10 ), m_pixmapHeight( 10 )
{
    m_labelNumber = m_page->number() + 1;
    m_labelHeight = QFontMetrics( font() ).height();
}

void ThumbnailWidget::resizeFitWidth( int width )
{
Enrico Ros's avatar
Enrico Ros committed
453 454
    m_pixmapWidth = width - m_margin;
    m_pixmapHeight = (int)round( m_page->ratio() * (double)m_pixmapWidth );
455 456 457 458 459 460 461 462 463
    resize( width, heightHint() );
}

void ThumbnailWidget::setSelected( bool selected )
{
    // update selected state
    if ( m_selected != selected )
    {
        m_selected = selected;
Enrico Ros's avatar
Enrico Ros committed
464
        update( 0, 0, width(), height() );
465 466 467
    }
}

468 469 470 471 472 473 474 475
void ThumbnailWidget::mouseReleaseEvent( QMouseEvent * e )
{
    if ( e->button() != Qt::RightButton )
        return;

    m_tl->forwardRightClick( m_page, e->globalPos() );
}

476 477
void ThumbnailWidget::paintEvent( QPaintEvent * e )
{
Enrico Ros's avatar
Enrico Ros committed
478
    int width = m_pixmapWidth + m_margin;
479
    QRect clipRect = e->rect();
Enrico Ros's avatar
Enrico Ros committed
480 481
    if ( !clipRect.isValid() )
        return;
482 483
    QPainter p( this );

Enrico Ros's avatar
Enrico Ros committed
484 485 486
    // draw the bottom label + highlight mark
    QColor fillColor = m_selected ? palette().active().highlight() : palette().active().base();
    p.fillRect( clipRect, fillColor );
Enrico Ros's avatar
Enrico Ros committed
487
    p.setPen( m_selected ? palette().active().highlightedText() : palette().active().text() );
Enrico Ros's avatar
Enrico Ros committed
488
    p.drawText( 0, m_pixmapHeight + m_margin, width, m_labelHeight, Qt::AlignCenter, QString::number( m_labelNumber ) );
489 490

    // draw page outline and pixmap
Enrico Ros's avatar
Enrico Ros committed
491
    if ( clipRect.top() < m_pixmapHeight + m_margin )
492
    {
493
        // if page is bookmarked draw a colored border
494
        bool isBookmarked = m_page->hasBookmark();
495 496
        // draw the inner rect
        p.setPen( isBookmarked ? QColor( 0xFF8000 ) : Qt::black );
Enrico Ros's avatar
Enrico Ros committed
497
        p.drawRect( m_margin/2 - 1, m_margin/2 - 1, m_pixmapWidth + 2, m_pixmapHeight + 2 );
498 499 500 501 502
        // draw the clear rect
        p.setPen( isBookmarked ? QColor( 0x804000 ) : palette().active().base() );
        // draw the bottom and right shadow edges
        if ( !isBookmarked )
        {
Enrico Ros's avatar
Enrico Ros committed
503 504 505 506 507
            int left, right, bottom, top;
            left = m_margin/2 + 1;
            right = m_margin/2 + m_pixmapWidth + 1;
            bottom = m_pixmapHeight + m_margin/2 + 1;
            top = m_margin/2 + 1;
508
            p.setPen( Qt::gray );
Enrico Ros's avatar
Enrico Ros committed
509 510
            p.drawLine( left, bottom, right, bottom );
            p.drawLine( right, top, right, bottom );
511
        }
512

513
        // draw the page using the shared PagePainter class
Enrico Ros's avatar
Enrico Ros committed
514 515
        p.translate( m_margin/2, m_margin/2 );
        clipRect.moveBy( -m_margin/2, -m_margin/2 );
516
        clipRect = clipRect.intersect( QRect( 0, 0, m_pixmapWidth, m_pixmapHeight ) );
Enrico Ros's avatar
Enrico Ros committed
517
        if ( clipRect.isValid() )
518
        {
519 520
            int flags = PagePainter::Accessibility | PagePainter::Highlights |
                        PagePainter::Annotations;
Enrico Ros's avatar
Enrico Ros committed
521
            PagePainter::paintPageOnPainter( &p, m_page, THUMBNAILS_ID, flags,
522
                                             m_pixmapWidth, m_pixmapHeight, clipRect );
523
        }
524 525 526 527 528 529 530 531 532

        // draw the bookmark overlay on the top-right corner
        const QPixmap * bookmarkPixmap = m_tl->getBookmarkOverlay();
        if ( isBookmarked && bookmarkPixmap )
        {
            int pixW = bookmarkPixmap->width(),
                pixH = bookmarkPixmap->height();
            clipRect = clipRect.intersect( QRect( m_pixmapWidth - pixW, 0, pixW, pixH ) );
            if ( clipRect.isValid() )
533
                p.drawPixmap( m_pixmapWidth - pixW, -pixH/8, *bookmarkPixmap );
534
        }
535 536 537
    }
}

538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562

/** ThumbnailsController implementation **/

#define FILTERB_ID  1

ThumbnailController::ThumbnailController( QWidget * parent, ThumbnailList * list )
    : KToolBar( parent, "ThumbsControlBar" )
{
    // change toolbar appearance
    setMargin( 3 );
    setFlat( true );
    setIconSize( 16 );
    setMovingEnabled( false );

    // insert a togglebutton [show only bookmarked pages]
    //insertSeparator();
    insertButton( "bookmark", FILTERB_ID, SIGNAL( toggled( bool ) ),
                  list, SLOT( slotFilterBookmarks( bool ) ),
                  true, i18n( "Show bookmarked pages only" ) );
    setToggle( FILTERB_ID );
    setButton( FILTERB_ID, Settings::filterBookmarks() );
    //insertLineSeparator();
}


Albert Astals Cid's avatar
Albert Astals Cid committed
563
#include "thumbnaillist.moc"