make kmk fill the type columns of lists with correct info; also add some comments...
[kmk.git] / src / kmk.cpp
blob05892b9f247058938b0e75787c040fa6cc34b2c8
1 /***************************************************************************
2 * KMK - KDE Music Cataloger - the tool for personal *
3 * audio collection management *
4 * *
5 * Copyright (C) 2006,2007 by Plamen Petrov *
6 * carpo@abv.bg *
7 * *
8 * This program is free software; you can redistribute it and/or modify *
9 * it under the terms of the GNU General Public License as published by *
10 * the Free Software Foundation; either version 2 of the License, or *
11 * (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License *
19 * along with this program; if not, write to the *
20 * Free Software Foundation, Inc., *
21 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
22 ***************************************************************************/
24 #include <klocale.h>
25 #include <kstdaccel.h>
26 #include <kmenubar.h>
27 #include <kpopupmenu.h>
28 #include <kprogress.h>
29 #include <kmessagebox.h>
30 #include <kapplication.h>
31 #include <kconfig.h>
32 #include <qfiledialog.h>
33 #include <qtabwidget.h>
34 #include <qprocess.h>
35 #include <qdir.h>
37 #include "tag.h"
38 #include "fileref.h"
39 #include "tstring.h"
40 #include "tfile.h"
42 #include "kmk.h"
43 #include "kmksettings_dialog.h"
44 #include "kmk_progress_disp.h"
45 #include "kmktaged.h"
47 #define _KMK_UPDATE_PERIOD 250
48 #define _KMK_NEWCATALOG "NEW.kmk"
49 #define A__PLAY 1
50 #define A__ENQUEUE 0
51 #undef __KMK_DEBUG
53 const QString player_bin = "xmms";
55 kmk::kmk()
56 : KMainWindow( 0, i18n("KDE Music Kataloger") )
58 // allocate settings object
59 s = new KmkGlobalSettings;
60 // be safe here
61 Q_CHECK_PTR( s );
62 if ( !s ) kdDebug() << "no mem for s!" << endl;
63 // if all is fine - then read the settings
64 s->readSettings( kapp->config() );
65 // save the caption - this one will be used when we need to restore it
66 savedCaption = this->caption();
67 init_interface();
69 // set config object to read GUI group
70 kapp->config()->setGroup( "GUI" );
71 // reads the splitter settings from the config object
72 splitter1->setSizes( kapp->config()->readIntListEntry( "splitter config" ) );
73 // then moves the window to its last position
74 move( kapp->config()->readPointEntry( "main win position" ) );
76 kmkSmooth = new QTime();
77 kmkProgress = new kmk_progress_disp();
78 kmkProgressTimer = new QTimer();
80 player = new KmkExtPlayer( this, 0 );
82 KPopupMenu * file = new KPopupMenu( this );
83 // Actions
84 fileNewAction = KStdAction::openNew(this,
85 SLOT(slotFileNew()),actionCollection());
86 fileOpenAction = KStdAction::open(this,
87 SLOT(slotFileOpen()),actionCollection());
88 catalogAddNewFolderAction = new KAction( i18n("Add new folder to catalog"), KShortcut(""), this,
89 SLOT(slotCatalogAddNewFolder()), actionCollection(), "add_new_folder" );
90 fileSaveAction = KStdAction::save(this,
91 SLOT(slotFileSave()),actionCollection());
92 fileSaveAsAction = KStdAction::saveAs(this,
93 SLOT(slotFileSaveAs()),actionCollection());
94 fileCatalogFileStats = new KAction( i18n("Catalog statistics"), KShortcut(""), this,
95 SLOT(slotCatalogFileStats()), actionCollection(), "catalog_stats" );
96 fileCatalogFileClose = KStdAction::close(this,
97 SLOT(slotCatalogFileClose()),actionCollection());
98 fileQuitAction = KStdAction::quit(this,
99 SLOT(slotFileQuit()),actionCollection());
100 listTableToggleTitleAction = new KAction( i18n("Toggle Title"), KShortcut(""), this,
101 SLOT(slotListTableToggleTitle()), actionCollection(), "toggle_title" );
102 TagEditAction = new KAction( i18n("Tag editor"), KShortcut(""), this,
103 SLOT(slotTagEdit()), actionCollection(), "tag_editor" );
104 listTableLocateAction = new KAction( i18n("Locate"), KShortcut(""), this,
105 SLOT(slotLocateRequested()), actionCollection(), "locate" );
106 programSettingsAction = KStdAction::preferences(this,
107 SLOT(slotProgramSettings()),actionCollection());
108 playerPreviousAction = new KAction( i18n("Previous "), KShortcut("ALT+Z"), this,
109 SLOT(slotPlayerPrevious()), actionCollection(), "prev");
110 playerPlayAction = new KAction(i18n("Play "), KShortcut("ALT+X"), this,
111 SLOT(slotPlayerPlay()), actionCollection(), "play");
112 playerPauseAction = new KAction(i18n("Pause "), KShortcut("ALT+C"), this,
113 SLOT(slotPlayerPause()), actionCollection(), "pause");
114 playerStopAction = new KAction(i18n("Stop "), KShortcut("ALT+V"), this,
115 SLOT(slotPlayerStop()), actionCollection(), "stop");
116 playerNextAction = new KAction(i18n("Next "), KShortcut("ALT+B"), this,
117 SLOT(slotPlayerNext()), actionCollection(), "next");
118 playerEnqueueAction = new KAction(i18n("Enqueue selection "), KShortcut(""), this,
119 SLOT(slotPlayerEnqueue()), actionCollection(), "enqueue");
120 playerEnqueueDirAction = new KAction(i18n("Enqueue selected folder ONLY "), KShortcut(""), this,
121 SLOT(slotPlayerEnqueueDir()), actionCollection(), "enqueue_dir");
122 playerEnqueueDirSubdirsAction = new KAction(i18n("Enqueue folder with subfolders "), KShortcut(""), this,
123 SLOT(slotPlayerEnqueueDirSubdirs()), actionCollection(), "enqueue_dir_subdirs");
124 playerPlayDirAction = new KAction(i18n("Play selected folder ONLY "), KShortcut(""), this,
125 SLOT(slotPlayerPlayDir()), actionCollection(), "play_dir");
126 playerPlayDirSubdirsAction = new KAction(i18n("Play folder with subfolders "), KShortcut(""), this,
127 SLOT(slotPlayerPlayDirSubdirs()), actionCollection(), "play_dir_subdirs");
128 playerPlaySelectionAction = new KAction(i18n("Play selection "), KShortcut(""), this,
129 SLOT(slotPlayerPlaySelection()), actionCollection(), "play_sel");
130 // File menu actions
131 fileNewAction->plug( file );
132 fileOpenAction->plug( file );
133 // catalogAddNewFolderAction->setEnabled( FALSE );
134 catalogAddNewFolderAction->plug( file );
135 fileSaveAction->setEnabled( FALSE );
136 fileSaveAction->plug( file );
137 fileSaveAsAction->setEnabled( FALSE );
138 fileSaveAsAction->plug( file );
139 fileCatalogFileStats->setEnabled( FALSE );
140 fileCatalogFileStats->plug( file );
141 fileCatalogFileClose->setEnabled( FALSE );
142 fileCatalogFileClose->plug( file );
143 file->insertSeparator();
144 fileQuitAction->plug( file );
145 menuBar()->insertItem( i18n("Catalog"), file );
146 // Tools menu actions
147 file = new KPopupMenu( this );
148 TagEditAction->plug( file );
149 menuBar()->insertItem( i18n("Tools"), file );
150 // Player controls menu actions
151 file = new KPopupMenu( this );
152 playerPreviousAction->plug( file );
153 playerPlayAction->plug( file );
154 playerPauseAction->plug( file );
155 playerStopAction->plug( file );
156 playerNextAction->plug( file );
157 menuBar()->insertItem( i18n("Player controls"), file );
158 // Settings menu actions
159 file = new KPopupMenu( this );
160 programSettingsAction->plug( file );
161 // listTableToggleTitleAction->plug( file );
162 menuBar()->insertItem( i18n("Settings"), file );
163 // Help menu actions
164 file = helpMenu(); menuBar()->insertItem( i18n("Help"), file );
166 // Toolbar menu
167 KToolBar * fileToolsToolbar = new KToolBar( this, "File operations" );
168 fileToolsToolbar->setOrientation( Qt::Horizontal );
169 fileToolsToolbar->setIconText( KToolBar::IconOnly );
170 fileToolsToolbar->setLabel( i18n("File operations") );
172 fileNewAction->plug( fileToolsToolbar );
173 fileOpenAction->plug( fileToolsToolbar );
174 fileSaveAction->plug( fileToolsToolbar );
175 fileToolsToolbar->insertSeparator();
176 fileQuitAction->plug( fileToolsToolbar );
178 KToolBar* playerActionsToolbar = new KToolBar( this, "Player controls" );
179 playerActionsToolbar->setOrientation( Qt::Horizontal );
180 playerActionsToolbar->setIconText( KToolBar::TextOnly );
181 playerActionsToolbar->setLabel( i18n("Player controls") );
182 playerPreviousAction->plug( playerActionsToolbar );
183 playerPlayAction->plug( playerActionsToolbar );
184 playerPauseAction->plug( playerActionsToolbar );
185 playerStopAction->plug( playerActionsToolbar );
186 playerNextAction->plug( playerActionsToolbar );
188 moveDockWindow( fileToolsToolbar, Top );
189 moveDockWindow( playerActionsToolbar, Top );
191 // popup menus
192 CTree_PopupMenu = new QPopupMenu( this, "catalog tree popup" );
193 fileNewAction->plug( CTree_PopupMenu );
194 catalogAddNewFolderAction->plug( CTree_PopupMenu );
195 CTree_PopupMenu->insertSeparator(2);
196 playerPlayDirAction->plug( CTree_PopupMenu );
197 playerPlayDirSubdirsAction->plug( CTree_PopupMenu );
198 CTree_PopupMenu->insertSeparator(5);
199 playerEnqueueDirAction->plug( CTree_PopupMenu );
200 playerEnqueueDirSubdirsAction->plug( CTree_PopupMenu );
201 CTree_PopupMenu->insertSeparator(8);
203 CList_PopupMenu = new QPopupMenu( this, "files list popup" );
204 playerPlaySelectionAction->plug( CList_PopupMenu );
205 playerPlaySelectionAction->setEnabled( FALSE );
206 playerEnqueueAction->plug( CList_PopupMenu );
207 playerEnqueueAction->setEnabled( FALSE );
208 CList_PopupMenu->insertSeparator(2);
209 TagEditAction->plug( CList_PopupMenu );
210 TagEditAction->setEnabled( FALSE );
212 CSearchList_PopupMenu = new QPopupMenu( this, "search list popup" );
213 playerPlaySelectionAction->plug( CSearchList_PopupMenu );
214 playerEnqueueAction->plug( CSearchList_PopupMenu );
215 CSearchList_PopupMenu->insertSeparator(2);
216 TagEditAction->plug( CSearchList_PopupMenu );
217 listTableLocateAction->plug( CSearchList_PopupMenu );
219 if( s->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: connecting signals to slots..." << endl;
221 connect( treeListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
222 this, SLOT( slotTreeListViewPopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
223 connect( filesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
224 this, SLOT( slotFileListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
225 connect( searchFilesListView, SIGNAL( contextMenuRequested( QListViewItem*, const QPoint &, int ) ),
226 this, SLOT( slotSearchListTablePopupMenuRequested( QListViewItem*, const QPoint &, int ) ) );
227 connect( treeListView, SIGNAL( currentChanged( QListViewItem* ) ),
228 this, SLOT( slotTreeListViewCurrentChanged( QListViewItem* ) ) );
229 connect( leSearchFor, SIGNAL( returnPressed() ),
230 this, SLOT( slotSearchButtonClicked() ) );
231 connect( pbSearch, SIGNAL( clicked() ),
232 this, SLOT( slotSearchButtonClicked() ) );
233 connect( pbClear, SIGNAL( clicked() ),
234 this, SLOT( slotClearButtonClicked() ) );
235 connect( pbDone, SIGNAL( clicked() ),
236 this, SLOT( slotDoneButtonClicked() ) );
238 if( s->dbg() & KMK_DBG_SIGNALS ) kdDebug() << "constructor: done!" << endl;
240 FileSaveCalledFromFileSaveAs = FALSE;
242 QDir tmp_dir; tmp_dir.mkdir( "/tmp/kmk", TRUE );
244 setAutoSaveSettings();
245 applyMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
247 clearCatalogData();
248 if ( s->loadLast() )
250 if ( !s->lastCatalogUsed().isEmpty() )
252 show();
253 loadCatalog( s->lastCatalogUsed() );
258 void kmk::init_interface()
260 main_container_widget = new QWidget( this, "main_container_widget" );
262 setCentralWidget( main_container_widget );
264 kmkwidgetbaseLayout = new QVBoxLayout( main_container_widget, 11, 6, "kmkwidgetbaseLayout");
266 tabWidget1 = new QTabWidget( main_container_widget, "level1_tabwidget" );
268 tab = new QWidget( tabWidget1, "level1_tab1" );
269 tabLayout = new QVBoxLayout( tab, 11, 6, "tab1_Layout");
271 splitter1 = new QSplitter( tab, "splitter1" );
272 splitter1->setMinimumSize( QSize( 182, 60 ) );
273 splitter1->setOrientation( QSplitter::Horizontal );
275 treeListView = new kmk_tree_KListView( splitter1, "treeListView" );
276 treeListView->addColumn( i18n( "Catalog tree " ) );
277 treeListView->setResizePolicy( KListView::AutoOneFit );
278 treeListView->setShowSortIndicator( TRUE );
279 treeListView->setRootIsDecorated( TRUE );
280 treeListView->setItemsMovable( TRUE );
281 treeListView->setDragEnabled( TRUE );
282 treeListView->setDropVisualizer( TRUE );
284 filesListView = new kmk_files_KListView( splitter1, "filesListView" );
285 filesListView->addColumn( i18n( "Folder" ) );
286 filesListView->addColumn( i18n( "Filename" ) );
287 filesListView->addColumn( i18n( "Artist" ) );
288 filesListView->addColumn( i18n( "Title" ) );
289 filesListView->addColumn( i18n( "Album" ) );
290 filesListView->addColumn( i18n( "Genre" ) );
291 filesListView->addColumn( i18n( "Comment" ) );
292 filesListView->addColumn( i18n( "Year" ) );
293 filesListView->addColumn( i18n( "Track #" ) );
294 filesListView->addColumn( i18n( "Duration" ) );
295 filesListView->addColumn( i18n( "Size" ) );
296 filesListView->addColumn( i18n( "Bitrate" ) );
297 filesListView->addColumn( i18n( "Sampling rate" ) );
298 filesListView->addColumn( i18n( "Channels" ) );
299 filesListView->addColumn( i18n( "Type" ) );
300 filesListView->addColumn( i18n( "READ ONLY" ) );
301 filesListView->setResizePolicy( KListView::AutoOneFit );
302 filesListView->restoreLayout( kapp->config(), "listView" );
303 filesListView->setAllColumnsShowFocus( TRUE );
304 filesListView->setShowSortIndicator( TRUE );
305 filesListView->setRootIsDecorated( FALSE );
306 filesListView->setItemsMovable( FALSE );
307 filesListView->setSelectionMode( QListView::Extended );
308 filesListView->setDragEnabled( TRUE );
309 filesListView->setDropVisualizer( TRUE );
310 tabLayout->addWidget( splitter1 );
311 tabWidget1->insertTab( tab, i18n("Catalog") );
313 tab_2 = new QWidget( tabWidget1, "level1_tab2" );
314 tabLayout_2 = new QVBoxLayout( tab_2, 11, 6, "tab2_Layout");
316 layout5 = new QVBoxLayout( 0, 0, 6, "layout5");
318 tabWidget2 = new QTabWidget( tab_2, "level2_tabwidget" );
319 tabWidget2->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)0, (QSizePolicy::SizeType)0, 0, 0,
320 tabWidget2->sizePolicy().hasHeightForWidth() ) );
321 tabWidget2->setMinimumSize( QSize( 480, 158 ) );
323 tab_3 = new QWidget( tabWidget2, "level2_tab1" );
325 pbSearch = new QPushButton( tab_3, "pbSearch" );
326 pbSearch->setGeometry( QRect( 370, 10, 100, 26 ) );
327 pbSearch->setText( i18n( "Go!" ) );
328 pbSearch->setDefault( TRUE );
330 pbClear = new QPushButton( tab_3, "pbClear" );
331 pbClear->setGeometry( QRect( 370, 50, 100, 26 ) );
332 pbClear->setText( i18n( "Clear" ) );
334 pbDone = new QPushButton( tab_3, "pbDone" );
335 pbDone->setGeometry( QRect( 370, 90, 100, 26 ) );
336 pbDone->setText( i18n( "Done" ) );
338 frame1 = new QFrame( tab_3, "frame1" );
339 frame1->setGeometry( QRect( 10, 10, 350, 110 ) );
340 frame1->setFrameShape( QFrame::StyledPanel );
341 frame1->setFrameShadow( QFrame::Raised );
343 textLabel1 = new QLabel( frame1, "textLabel1" );
344 textLabel1->setGeometry( QRect( 10, 10, 100, 16 ) );
345 textLabel1->setText( i18n( "Look for" ) );
347 leSearchFor = new KLineEdit( frame1, "leSearchFor" );
348 leSearchFor->setGeometry( QRect( 10, 30, 330, 24 ) );
349 tabWidget2->insertTab( tab_3, i18n("Base") );
351 tab_4 = new QWidget( tabWidget2, "level2_tab2" );
352 tabWidget2->insertTab( tab_4, i18n("Advanced") );
353 layout5->addWidget( tabWidget2 );
355 searchFilesListView = new kmk_files_KListView( tab_2, "searchFilesListView" );
356 searchFilesListView->addColumn( i18n( "Folder" ) );
357 searchFilesListView->addColumn( i18n( "Filename" ) );
358 searchFilesListView->addColumn( i18n( "Artist" ) );
359 searchFilesListView->addColumn( i18n( "Title" ) );
360 searchFilesListView->addColumn( i18n( "Album" ) );
361 searchFilesListView->addColumn( i18n( "Genre" ) );
362 searchFilesListView->addColumn( i18n( "Comment" ) );
363 searchFilesListView->addColumn( i18n( "Year" ) );
364 searchFilesListView->addColumn( i18n( "Track #" ) );
365 searchFilesListView->addColumn( i18n( "Duration" ) );
366 searchFilesListView->addColumn( i18n( "Size" ) );
367 searchFilesListView->addColumn( i18n( "Bitrate" ) );
368 searchFilesListView->addColumn( i18n( "Sampling rate" ) );
369 searchFilesListView->addColumn( i18n( "Channels" ) );
370 searchFilesListView->addColumn( i18n( "Type" ) );
371 searchFilesListView->addColumn( i18n( "READ ONLY" ) );
372 searchFilesListView->setResizePolicy( KListView::AutoOneFit );
373 searchFilesListView->restoreLayout( kapp->config(), "searchListView" );
374 searchFilesListView->setAllColumnsShowFocus( TRUE );
375 searchFilesListView->setShowSortIndicator( TRUE );
376 searchFilesListView->setRootIsDecorated( FALSE );
377 searchFilesListView->setItemsMovable( FALSE );
378 searchFilesListView->setDragEnabled( TRUE );
379 searchFilesListView->setDropVisualizer( TRUE );
380 searchFilesListView->setSelectionMode( QListView::Extended );
381 layout5->addWidget( searchFilesListView );
382 tabLayout_2->addLayout( layout5 );
383 tabWidget1->insertTab( tab_2, i18n("Search") );
384 kmkwidgetbaseLayout->addWidget( tabWidget1 );
385 // resize( QSize(742, 507).expandedTo(minimumSizeHint()) );
386 // clearWState( WState_Polished );
388 // tab order
389 setTabOrder( tabWidget1, treeListView );
390 setTabOrder( treeListView, filesListView );
391 setTabOrder( filesListView, tabWidget2 );
392 setTabOrder( tabWidget2, leSearchFor );
393 setTabOrder( leSearchFor, pbSearch );
394 setTabOrder( pbSearch, pbClear );
395 setTabOrder( pbClear, pbDone );
396 setTabOrder( pbDone, searchFilesListView );
399 kmk::~kmk()
402 filesListView->saveLayout( kapp->config(), "listView" );
403 searchFilesListView->saveLayout( kapp->config(), "searchListView" );
405 if( s->dbg() & KMK_DBG_OTHER ) kdDebug() << "destructor: shutting down kmk "VERSION"..." << endl;
406 saveMainWindowSettings( kapp->config(), QString::fromLatin1("MainWindow") );
407 Q_CHECK_PTR( s );
408 switch ( CatalogState )
410 case NoCatalog:
411 s->setLastCatalogUsed( "" );
412 break;
413 case Modified:
414 case Saved:
415 s->setLastCatalogUsed( catalogFileName );
416 break;
417 default:
418 break;
420 s->saveSettings( kapp->config() );
422 kapp->config()->setGroup( "GUI" );
423 kapp->config()->writeEntry( "splitter config", splitter1->sizes() );
424 kapp->config()->writeEntry( "main win position", pos() );
426 clearCatalogData();
427 if (kmkProgress) delete ( kmkProgress );
428 if (kmkProgressTimer) delete ( kmkProgressTimer );
429 if (kmkSmooth) delete ( kmkSmooth );
430 if (player) delete ( player );
432 if( s->dbg() & KMK_DBG_OTHER ) kdDebug() << "destructor: done!" << endl;
434 delete s;
437 void kmk::slotUpdateProgressDisp()
439 if ( files_to_read )
440 kmkProgress->kPrg->setProgress( total_files );
441 /* qDebug( " setting progres to "+QString::number( (int)((total_bytes/(float)bytes_to_read)*100 )) );
442 qDebug( " need to read: "+QString::number( bytes_to_read ) );
443 qDebug( " read: "+QString::number( total_bytes ) );*/
446 void kmk::slotFileNew()
448 if( CatalogState == Modified )
449 switch( KMessageBox::questionYesNoCancel( this,
450 i18n("The catalog [%1] contains unsaved changes.\n"
451 "Do you want to save the changes?")
452 .arg(catalogFileName),
453 i18n("KDE Music Kataloger") ) ) {
454 case KMessageBox::Yes: // Save
455 slotFileSave();
456 break;
457 case KMessageBox::No: // Discard - just continue
458 break;
459 case KMessageBox::Cancel: // Cancel creating new catalog
460 return;
461 break;
463 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
464 fd->setCaption( i18n("Select directory to scan") );
465 fd->setMode( QFileDialog::Directory );
466 fd->setFilter( "*" );
467 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
468 t->exec(); */
469 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
470 if ( fd->exec() == QDialog::Accepted ) {
471 // reset counters;
472 clearCatalogData();
473 subDlevel = 0;
474 QString dirName = fd->selectedFile();
475 // determine bytes to read;
476 kmkSmooth->start();
477 kmkProgress->kPrg->setTotalSteps(0);
478 kmkProgress->kPrg->setProgress(0);
479 kmkProgress->show();
480 QTime t; t.start();
481 break_long_disk_operation = FALSE;
482 bytes_to_read = 0; files_to_read = 0;
483 bytes_to_read_by_traverse( dirName );
484 if ( files_to_read )
486 if( s->dbg() & KMK_DBG_FS_OPS )
487 kdDebug() << "slotFileNew: Time elapsed: " << t.elapsed()
488 << " ms, need to read: " << (bytes_to_read/(1024*1024))
489 << " MB, files to read: " << files_to_read << endl;
490 t.restart();
491 kmkProgress->kPrg->setTotalSteps( files_to_read );
492 // arm timer event; 300 ms interval - can be done in constructor
493 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
494 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
495 // show widget, displaying load progress
496 // start timer
497 traverse_tree( dirName ); // should update total_bytes as it scans
498 setCatalogStateAndUpdate( Modified );
499 average_file_size = ( total_bytes/total_files );
500 if( s->dbg() & KMK_DBG_FS_OPS & KMK_DBG_OTHER )
502 kdDebug() << "slotFileNew: Total time: " << t.elapsed() << " ms, read: "
503 << (total_bytes/(1024*1024)) << " MB, files read: " << total_files << endl;
504 kdDebug() << "slotFileNew:STATS: total_files = " << total_files << ";" << endl;
505 kdDebug() << "slotFileNew:STATS: total_folders = " << total_folders << ";" << endl;
506 kdDebug() << "slotFileNew:STATS: total_bytes = " << total_bytes << " B;" << endl;
507 kdDebug() << "slotFileNew:STATS: total_playtime = " << total_play_time << " sec;" << endl;
508 kdDebug() << "slotFileNew:STATS: average_file_size = " << average_file_size << " B;" << endl;
510 kmkProgress->hide();
511 // hide widget
512 // stop timer
513 // Enable actions wich work only with data...
514 slotTreeListViewCurrentChanged( (QListViewItem*) treeListView->firstChild() );
515 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
517 else
519 setCatalogStateAndUpdate( NoCatalog );
520 clearCatalogData();
521 subDlevel = 0;
522 KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
523 i18n("KDE Music Kataloger") );
526 delete( fd );
529 void kmk::slotFileOpen()
531 if( CatalogState == Modified )
532 switch( KMessageBox::questionYesNoCancel( this,
533 i18n("The catalog [%1] contains unsaved changes.\n"
534 "Do you want to save the changes?")
535 .arg(catalogFileName),
536 i18n("KDE Music Kataloger") ) ) {
537 case KMessageBox::Yes: // Save
538 slotFileSave();
539 break;
540 case KMessageBox::No: // Discard - just continue
541 break;
542 case KMessageBox::Cancel: // Cancel creating new catalog
543 return;
544 break;
546 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
547 fd->setCaption( i18n("Select catalog file to load") );
548 fd->setMode( QFileDialog::ExistingFile );
549 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
550 if ( fd->exec() != QDialog::Accepted ) delete fd;
551 else
553 QString fileName = fd->selectedFile(); delete fd;
554 qApp->processEvents();
555 loadCatalog( fileName );
556 Q_CHECK_PTR( s );
557 s->setLastCatalogUsed( fileName );
558 s->saveSettings( kapp->config() );
562 void kmk::slotCatalogAddNewFolder()
564 QFileDialog* fd = new QFileDialog( this, "file dialog", TRUE );
565 fd->setCaption( i18n("Select directory to scan") );
566 fd->setMode( QFileDialog::Directory );
567 fd->setFilter( "*" );
568 /* KFileDialog * t = new KFileDialog( ":&lt;scandir&gt;", "*", this, i18n("Select directory to scan"), TRUE );
569 t->exec(); */
570 // = KFileDialog::getExistingDirectory( ":&lt;scandir&gt;", this, i18n("Select directory to scan") );
571 if ( fd->exec() == QDialog::Accepted )
573 subDlevel = 0;
574 QString dirName = fd->selectedFile();
575 if ( catalog_has_dir( dirName ) )
577 KMessageBox::sorry( this, i18n("Sorry, but the folder [%1]\nis already present in this catalog!")
578 .arg(dirName),i18n("KDE Music Kataloger") );
579 delete fd;
580 return;
582 // clearCatalogData();
583 // reset counters;
584 Q_ULLONG bc01 = total_bytes; Q_ULLONG bc02 = total_files;
585 Q_ULLONG bc03 = total_folders; Q_ULLONG bc04 = total_play_time;
586 bytes_to_read = 0; files_to_read = 0;
587 total_bytes = 0; total_files = 0;
588 total_folders = 0; total_play_time = 0;
589 // determine bytes to read;
590 kmkSmooth->start();
591 kmkProgress->kPrg->setProgress(0);
592 kmkProgress->show();
593 QTime t; t.start();
594 break_long_disk_operation = FALSE;
595 bytes_to_read_by_traverse( dirName );
596 if( s->dbg() & KMK_DBG_FS_OPS )
597 kdDebug() << "slotCatalogAddNewFolder: Time elapsed: " << t.elapsed()
598 << " ms, need to read: " << (bytes_to_read/(1024*1024))
599 << " MB, files to read: " << files_to_read << endl;
600 t.restart();
601 // arm timer event
602 if ( files_to_read )
604 connect( kmkProgressTimer, SIGNAL( timeout() ), this, SLOT( slotUpdateProgressDisp() ) );
605 kmkProgressTimer->start(_KMK_UPDATE_PERIOD,FALSE);
606 // show widget, displaying load progress
607 // start timer
608 traverse_tree( dirName ); // should update total_bytes as it scans
609 setCatalogStateAndUpdate( Modified );
610 // kdDebug() << "Total time: " << t.elapsed() << " ms, read: " << (total_bytes/(1024*1024))
611 // << " MB, files read: " << total_files << endl;
612 total_bytes += bc01; total_files += bc02;
613 total_folders += bc03; total_play_time += bc04;
614 if ( total_files ) average_file_size = ( total_bytes/total_files );
615 if( s->dbg() & KMK_DBG_FS_OPS & KMK_DBG_OTHER )
617 kdDebug() << "slotCatalogAddNewFolder:STATS: total_files = " << total_files << ";" << endl;
618 kdDebug() << "slotCatalogAddNewFolder:STATS: total_folders = " << total_folders << ";" << endl;
619 kdDebug() << "slotCatalogAddNewFolder:STATS: total_bytes = " << total_bytes << " B;" << endl;
620 kdDebug() << "slotCatalogAddNewFolder:STATS: total_playtime = " << total_play_time << " sec;" << endl;
621 kdDebug() << "slotCatalogAddNewFolder:STATS: average_file_size = " << average_file_size << " B;" << endl;
623 // hide widget
624 kmkProgress->hide();
625 // stop timer
626 // QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSaveAs() ) );
628 else KMessageBox::sorry( this, i18n("There are no files of supported types in [%1].").arg(dirName),
629 i18n("KDE Music Kataloger") );
631 delete( fd );
634 /** Saves the catalog in memory to the file @p catalogFileName;
635 * If the string, being a filename in @p catalogFileName does not have
636 * a .kmk extension - this slot adds it
638 void kmk::slotFileSave()
640 if( CatalogState != Modified ) return;
641 /* We need to check whether catalogFileName exists, and what permissions it has.
642 If there is a problem - inform the user. */
643 bool fileOK=TRUE;
644 if( catalogFileName.isEmpty() || catalogFileName.isNull() )
645 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
646 QFileInfo * _cf = new QFileInfo( catalogFileName );
647 /* Make sure .kmk is appended to the file name...*/
648 if (_cf->extension(FALSE).lower().compare("kmk") != 0)
649 { catalogFileName.append(".kmk"); _cf->setFile( catalogFileName ); }
650 if( (_cf->exists())&&(!_cf->isWritable()) ){ /* File is NOT OK, existing, but not writable. Inform. */
651 fileOK = FALSE;
652 KMessageBox::sorry( this, i18n("Could not open %1 for writing!").arg(catalogFileName),
653 i18n("KDE Music Kataloger") );
655 if( (_cf->exists())&&(!_cf->isFile()) ){ /* File is NOT OK, it is not even a file. Inform. */
656 fileOK = FALSE;
657 KMessageBox::sorry( this, i18n("The filesystem item %1 exists, but it is not a file!").arg(catalogFileName),
658 i18n("KDE Music Kataloger") );
660 if( (FileSaveCalledFromFileSaveAs) && (_cf->exists())
661 && (_cf->isWritable()) ) /* File is OK, but we need to overwrite it... ask what to do. */
663 if( KMessageBox::questionYesNo( this,
664 i18n("A file called [%1] already exists.\nDo you want to overwrite it?").arg(catalogFileName),
665 i18n("KDE Music Kataloger") ) != KMessageBox::Yes ) fileOK = FALSE;
667 FileSaveCalledFromFileSaveAs = FALSE;
668 if( fileOK ) /* After the checks above are passed - lets write! */
670 QDomDocument doc( "KMK_catalog_file" ); QDomElement e = doc.createElement( "KMK_catalog_file_data" );
671 e.setAttribute( "Version", 1 ); doc.appendChild( e );
672 QDomElement e2 = doc.createElement( "CatalogFile_Statistics" ); e.appendChild( e2 );
673 e2.setAttribute( "Total_files_count", (double) total_files );
674 e2.setAttribute( "Total_bytes_count", (double) total_bytes );
675 e2.setAttribute( "Total_dirs_count", (double) total_folders );
676 e2.setAttribute( "Total_secs_count", (double) total_play_time );
677 e2.setAttribute( "Average_file_size", (double) average_file_size );
678 e2 = doc.createElement( "CatalogFile_TreeDescription" ); e.appendChild( e2 );
680 generateListViewXML( treeListView, doc, e2 );
682 QDomElement tag;
683 e2 = doc.createElement( "CatalogFile_Objects" ); e.appendChild( e2 );
684 e2.setAttribute( "Total_MObjs_count", (double) MusicCatalog.count() );
686 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
688 /* ask our DOM Document object to create a new DomElement, and fill this
689 new element with appropriate data */
690 tag = doc.createElement( "MObj" );
691 tag.setAttribute( "Folder", (*it).Folder().utf8() );
692 tag.setAttribute( "Filename", (*it).FileName().utf8() );
693 tag.setAttribute( "Artist", (*it).Artist().utf8() );
694 tag.setAttribute( "Title", (*it).Title().utf8() );
695 tag.setAttribute( "Album", (*it).Album().utf8() );
696 tag.setAttribute( "Genre", (*it).Genre().utf8() );
697 tag.setAttribute( "Comment", (*it).Comment().utf8() );
698 tag.setAttribute( "Year", (*it).Year() );
699 tag.setAttribute( "TrackNumber", (*it).TrackNum() );
700 tag.setAttribute( "Length", (*it).Length() );
701 tag.setAttribute( "Modified", (*it).ModifiedTime() );
702 tag.setAttribute( "Size", (*it).FileSize() );
703 tag.setAttribute( "Channels", (*it).Channels() );
704 tag.setAttribute( "BitRate", (*it).BitRate() );
705 tag.setAttribute( "SampleRate", (*it).SampleRate() );
706 tag.setAttribute( "IsReadOnly", ( ((*it).IsReadOnly()) ? "YES" : "NO" ) );
707 tag.setAttribute( "IsFolder", ( ((*it).IsDir()) ? "YES" : "NO" ) );
708 /* Then, ask the DOM Document to add this new element in the correct place... */
709 e2.appendChild( tag );
712 QString xml = doc.toString(2);
713 QFile file( catalogFileName );
714 if ( file.open( IO_WriteOnly ) ) {
715 file.writeBlock( xml, xml.length() );
716 file.flush();
717 file.close();
718 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
719 setCatalogStateAndUpdate( Saved );
720 } else QMessageBox::warning( this, i18n("KDE Music Kataloger"), i18n("Could not open %1 for writing!").arg(catalogFileName),
721 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
723 else this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
726 void kmk::slotFileSaveAs()
728 QFileDialog* fd = new QFileDialog( 0, "file dialog", TRUE );
729 fd->setCaption( i18n("Select file to save catalog to") );
730 fd->setMode( QFileDialog::AnyFile );
731 fd->setFilter( i18n("All KMK catalog files %1").arg("(*.kmk)") );
732 QString fileName;
733 if ( fd->exec() == QDialog::Accepted )
735 catalogFileName = fd->selectedFile();
736 setCatalogStateAndUpdate( Modified );
737 FileSaveCalledFromFileSaveAs = TRUE;
738 QTimer::singleShot( _KMK_UPDATE_PERIOD, this, SLOT( slotFileSave() ) );
740 delete fd;
743 void kmk::slotCatalogFileClose()
745 if( CatalogState == Modified )
746 switch( KMessageBox::questionYesNoCancel( this,
747 i18n("The catalog [%1] contains unsaved changes.\n"
748 "Do you want to save the changes?")
749 .arg(catalogFileName),
750 i18n("KDE Music Kataloger") ) ) {
751 case KMessageBox::Yes: // Save
752 slotFileSave();
753 break;
754 case KMessageBox::No: // Discard - just continue
755 break;
756 case KMessageBox::Cancel: // Cancel opreation
757 return;
758 break;
760 // Ensure there is no data laying around...
761 clearCatalogData();
764 void kmk::slotCatalogFileStats()
766 QString tmp = formatted_string_from_seconds( total_play_time );
767 KMessageBox::information( 0,
768 i18n("<h3>Catalog statistics</h3>"
769 "<h4>This catalog represents:</h4>"
770 "<pre>Total capacity : %1 MB<br/>"
771 "Total files : %2<br/>"
772 "Total folders : %3<br/>"
773 "Total playtime : %4<br/>"
774 "Average filesize : %5 MB</pre>")
775 .arg(total_bytes/(float)(1024*1024),-8,'f',1).arg(total_files,-8).arg(total_folders,-8)
776 .arg(tmp).arg(average_file_size/(float)(1024*1024),-8,'f',3),
777 i18n("KDE Music Kataloger"));
780 void kmk::slotFileQuit()
782 close();
785 void kmk::closeEvent( QCloseEvent* ce )
787 if( !ce )
789 if( s->dbg() & KMK_DBG_SIGNALS & KMK_DBG_OTHER )
790 kdDebug() << "closeEvent: bad object" << endl;
791 return;
793 if( CatalogState == Modified )
794 switch( KMessageBox::questionYesNoCancel( this,
795 i18n("The catalog [%1] contains unsaved changes.\n"
796 "Do you want to save the changes before exiting?")
797 .arg(catalogFileName),
798 i18n("KDE Music Kataloger") ) ) {
799 case KMessageBox::Yes: // Save & Exit
800 slotFileSave();
801 ce->accept();
802 break;
803 case KMessageBox::No: // Discard - just Exit
804 ce->accept();
805 break;
806 case KMessageBox::Cancel: // Cancel - no nothing
807 ce->ignore();
808 break;
810 else ce->accept();
813 void kmk::slotProgramSettings()
815 // WARNING: kmkSettingsDialog has Qt::WDestructiveClose flag set, it WILL destroy itself
816 // also, it internally calls exec() - so we only create it, passing relevant data
817 // to its contructor
818 uint * k = new uint;
819 s->saveSettings( kapp->config() );
820 new kmkSettingsDialog( k );
821 // if( k ) // config changed? if so - re-read it..
822 s->readSettings( kapp->config() );
823 player->handleConfigChange();
824 /* If we need to choose the font kmk uses to display the lists and stuff,
825 here is how its done:
827 QFont f ( "Courier", 16, 50 );
828 main_container_widget->setFont( f );
832 void kmk::slotTagEdit()
834 MmDataPList *files = new MmDataPList; if( !files ) return;
835 MmData *tmp = 0; // zero-init our MmData pointer
836 KListViewItem *node = 0;
837 if( _popup_at_search )
838 node = (KListViewItem*) searchFilesListView->firstChild();
839 else
840 node = (KListViewItem*) filesListView->firstChild();
842 while ( node )
844 if ( node->isSelected() )
846 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
847 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
849 node = (KListViewItem*) node->nextSibling();
852 if ( files->isEmpty() ) { delete files; return; }
854 /** kmkTagEdit's constructor takes care of everything for us -
855 * we don't even need to call show() or exec() - it is done automagically;
857 uint k = 0;
858 new kmkTagEdit( files, &k );
859 if( s->dbg() & KMK_DBG_SIGNALS & KMK_DBG_OTHER )
860 kdDebug() << "slotTagEdit: TagEditDialog returns: " << k << endl;
861 if( k ) {
862 setCatalogStateAndUpdate( Modified );
863 slotTreeListViewCurrentChanged( treeListView->currentItem() );
865 delete files;
868 void kmk::slotListTableToggleTitle()
870 // QTable* tbl = ((kmkWidget*) kmk_widg )->FileList();
871 // tbl->hideColumn(1);
874 void kmk::slotPlayerPrevious()
876 player->previous();
879 void kmk::slotPlayerPlay()
881 player->play();
884 void kmk::slotPlayerPause()
886 player->pause();
889 void kmk::slotPlayerStop()
891 player->stop();
894 void kmk::slotPlayerNext()
896 player->next();
900 * In this member we use the fact, that when QTable's selectionMode() is MultiRow,
901 * meaning that whole rows selection is only allowed, QTable passes these
902 * rows as seperate selections - 2 successive rows will give us 2 selections
903 * with each selections topRow() and bottomRow() showing the number of the selected
904 * row in the table ---THIS ALL IS FALSE
906 void kmk::slotPlayerPlaySelection()
908 MmDataPList *files = new MmDataPList;
909 MmData *tmp = 0; // zero-init our MmData pointer
910 KListViewItem *node = 0;
911 if( _popup_at_search)
912 node = (KListViewItem*) searchFilesListView->firstChild();
913 else
914 node = (KListViewItem*) filesListView->firstChild();
916 while ( node )
918 if ( node->isSelected() )
920 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
921 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
923 node = (KListViewItem*) node->nextSibling();
926 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
927 QFile file( flNm );
928 if ( file.open( IO_WriteOnly ) )
930 QTextStream stream( &file );
931 stream << "#EXTM3U" << endl;
932 for ( tmp = files->first(); tmp; tmp = files->next() )
934 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
935 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
937 file.close();
939 player->playPlaylist( flNm );
941 // file.remove();
943 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
944 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
945 delete files;
948 void kmk::slotPlayerEnqueue()
950 MmDataPList *files = new MmDataPList;
951 MmData *tmp = 0; // zero-init our MmData pointer
952 KListViewItem *node = 0;
953 if( _popup_at_search)
954 node = (KListViewItem*) searchFilesListView->firstChild();
955 else
956 node = (KListViewItem*) filesListView->firstChild();
958 while ( node )
960 if ( node->isSelected() )
962 tmp = findMmData( node->text( 0 ), node->text( 1 ) );
963 if ( tmp ) if ( (*tmp).IsDir()==FALSE ) files->append( tmp );
965 node = (KListViewItem*) node->nextSibling();
968 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
969 QFile file( flNm );
970 if ( file.open( IO_WriteOnly ) )
972 QTextStream stream( &file );
973 stream << "#EXTM3U" << endl;
974 for ( tmp = files->first(); tmp; tmp = files->next() )
976 stream << "#EXTINF:" << tmp->Length() << ", " << tmp->Artist() << " - " << tmp->Title() << endl;
977 stream << tmp->Folder() << "/" << tmp->FileName() << endl;
979 file.close();
981 player->addPlaylist( flNm );
984 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
985 QMessageBox::Cancel,QMessageBox::NoButton,QMessageBox::NoButton );
986 delete files;
990 void kmk::slotPlayerEnqueueDir()
992 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
993 playerDir( A__ENQUEUE ); // here we enqueue
996 void kmk::slotPlayerEnqueueDirSubdirs()
998 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
999 playerDir( A__ENQUEUE ); // here we enqueue
1002 void kmk::slotPlayerPlayDir()
1004 _kmk_include_subdirs = FALSE; //tell playerDir to NOT include subfolder(s) contents
1005 playerDir( A__PLAY ); // and here we play
1008 void kmk::slotPlayerPlayDirSubdirs()
1010 _kmk_include_subdirs = TRUE; //tell playerDir to include subfolder(s) contents
1011 playerDir( A__PLAY ); // and here we play
1014 void kmk::slotTreeListViewPopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1016 /* Take care of annoying warnings ..... */
1017 Q_UNUSED(itm); Q_UNUSED(col);
1018 CTree_PopupMenu->popup( pos );
1021 void kmk::slotTreeListViewCurrentChanged( QListViewItem * itm )
1023 if ( MusicCatalog.isEmpty() ) { kdDebug() << "LVupdate: called with empty catalog" << endl; return; }
1024 // TagEditAction->setEnabled( FALSE );
1025 QListViewItem* lv = itm; QString p;
1026 // kdDebug() << "treeListView dropHighlighter() says: " << treeListView->dropHighlighter() << endl;
1028 // QTime ptime; ptime.start();
1030 while ( TRUE ) {
1031 p.prepend( lv->text(0) );
1032 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) p.prepend( "/" ); lv = lv->parent(); }
1033 else break;
1035 // kdDebug() << "LVupdate: user clicked: " << p << endl;
1036 // kdDebug() << "(kmk):LVupdate: determine hole selected folder name time: " << ptime.elapsed() << " ms." << endl;
1037 // ptime.restart();
1039 MmDataList::iterator it;
1040 bool found = FALSE;
1041 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1042 if( ((*it).IsDir()) && ( (*it).Folder().compare(p)==0 ) ){ found = TRUE; break; }
1043 int folder_files = (found) ? (*it).FileSize() : 0;
1045 // kdDebug() << "(kmk):LVupdate: locate folder as *MmData time: " << ptime.elapsed() << " ms. ff=" << folder_files << endl;
1046 // ptime.restart();
1048 // disble list for manipulation : flicker care
1049 filesListView->setEnabled( FALSE );
1050 while( folder_files > filesListView->childCount() )
1051 new KListViewItem( (QListView*) filesListView, 0 );
1052 while( folder_files < filesListView->childCount() ) {
1053 KListViewItem* t = (KListViewItem*) filesListView->lastItem();
1054 if ( t ) delete t;
1056 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1057 // ptime.restart();
1059 if ( found ) {
1060 it++;
1061 while ( (( (*it).Folder().compare(p)!=0 )) ) it++;
1063 QDateTime dt;
1064 KListViewItem * sel_item = 0;
1065 KListViewItem * new_item = (KListViewItem*) filesListView->firstChild();
1066 for ( int k=0; k<folder_files; it++,k++ )
1068 if ( !new_item ) qFatal("Something horrible happened!");
1069 if ( locateMode )
1070 if ( ( (*it).Folder().compare( DelSelDir ) == 0 ) && ( (*it).FileName().compare( DelSelFile ) == 0 ) )
1071 { sel_item = new_item; locateMode = FALSE; }
1072 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1073 new_item->setText( 1, (*it).FileName() ); // file
1074 new_item->setText( 2, (*it).Artist() ); // artist
1075 new_item->setText( 3, (*it).Title() ); // title
1076 new_item->setText( 4, (*it).Album() ); // album
1077 new_item->setText( 5, (*it).Genre() ); // genre
1078 new_item->setText( 6, (*it).Comment() ); // comment
1079 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1080 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1081 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1082 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1083 // dt.setTime_t( (*it).ModifiedTime() );
1084 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1085 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1086 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1087 QFileInfo ext_info( (*it).FileName() );
1088 new_item->setText( 14, ext_info.extension(FALSE).upper() ); // type
1089 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1090 new_item = (KListViewItem*) new_item->nextSibling();
1093 // kdDebug() << "(kmk):LVupdate: table fill time: " << ptime.elapsed() << " ms." << endl;
1094 // ptime.restart();
1096 // update table contets after manipulation
1097 filesListView->clearSelection();
1098 // here we update the first 5 columns' width
1099 Q_CHECK_PTR( s );
1100 // honor user setting whether he/she wants auto-column-width, or likes speed more
1101 if ( s->autoColumnWidth() )
1102 for ( ushort k=0; k<7; k++) filesListView->adjustColumn( k );
1103 if ( sel_item ) {
1104 filesListView->ensureItemVisible( sel_item );
1105 filesListView->setSelected( sel_item, TRUE );
1106 DelSelDir = ""; DelSelFile = "";
1108 filesListView->setEnabled( TRUE );
1111 void kmk::slotFileListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1113 Q_UNUSED(itm); Q_UNUSED(col);
1114 _popup_at_search = FALSE;
1115 CList_PopupMenu->exec( pos );
1118 void kmk::slotSearchListTablePopupMenuRequested( QListViewItem* itm, const QPoint &pos, int col )
1120 Q_UNUSED(itm); Q_UNUSED(col);
1121 _popup_at_search = TRUE;
1122 CSearchList_PopupMenu->exec( pos );
1126 * This function locates the correct place in the catalog tree, selects the
1127 * correct folder and then informs the method updating
1128 * the file list to select AND show the reqested file
1130 void kmk::slotLocateRequested()
1132 // this below is for locating results in the same folder to work,
1133 // without the need to manually change the currently selected folder
1134 // !!! kmk_widg->TreeList()->setCurrentItem( listViewRootItem );
1135 KListViewItem *node = (KListViewItem*) searchFilesListView->firstChild();
1136 if ( !node ) return;
1138 // inform the apropriate method that we are in locate mode:
1139 // set all relevant vars
1140 while ( node )
1142 // as we are called - and this is LOCATE slot - only ONE item in the search
1143 // results should be selected; if there are more - they are ignored
1144 if ( node->isSelected() )
1146 DelSelDir = node->text( 0 );
1147 DelSelFile = node->text( 1 );
1148 locateMode = TRUE;
1149 break;
1151 node = (KListViewItem*) node->nextSibling();
1154 // kdDebug() << " locating " << DelSelDir << " | " << DelSelFile << endl;
1155 QString ci;
1156 // get a list of all listview items to iterate over them
1157 QListViewItemIterator it( treeListView );
1158 while ( it.current() )
1160 QListViewItem *item = it.current();
1161 ci = QString::null;
1162 // this while loop exits when it reaches top level; result is slash ("/") separated path in ci
1163 bool not_found = TRUE;
1164 while ( not_found ) {
1165 ci.prepend( item->text(0) );
1166 if( item->parent() ) { if( item->parent()->text(0).compare("/") != 0 ) ci.prepend( "/" ); item = item->parent(); }
1167 else break;
1169 // kdDebug() << " ci now is " << ci << endl;
1170 item = it.current();
1171 if ( DelSelDir.compare( ci ) == 0 )
1173 treeListView->ensureItemVisible ( item );
1174 if( treeListView->currentItem() == item )
1175 slotTreeListViewCurrentChanged( item );
1176 else
1177 treeListView->setCurrentItem( item );
1178 not_found = FALSE;
1179 break;
1181 ++it;
1183 // switch from search tab to catalog view tab
1184 tabWidget1->setCurrentPage(0);
1187 void kmk::slotSearchButtonClicked()
1189 // if( MusicCatalog.isEmpty() ) return;
1190 QString p = leSearchFor->text();
1191 // TagEditAction->setEnabled( FALSE );
1193 // QTime ptime; ptime.start();
1195 QDateTime dt; long new_rows=0;
1196 // disble table for manipulation : flicker care
1197 searchFilesListView->setEnabled( FALSE );
1198 // Count the actuall matches
1199 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1201 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1202 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1203 ((*it).Album().contains(p,FALSE)>0) ) ) )
1204 { new_rows++; }
1206 // kdDebug() << new_rows << " rows should be added." << endl;
1207 // If the current search KListView has too many items (rows) - remove the unneeded
1208 while( new_rows > searchFilesListView->childCount() )
1209 new KListViewItem( (QListView*) searchFilesListView, 0 );
1210 // If the search KListView's items count is low - add the needed items (rows)
1211 while( new_rows < searchFilesListView->childCount() ) {
1212 KListViewItem* t = (KListViewItem*) searchFilesListView->lastItem();
1213 if ( t ) delete t;
1215 // kdDebug() << "(kmk):LVupdate: table setup time: " << ptime.elapsed() << " ms." << endl;
1216 // ptime.restart();
1218 // If there are any matches at all...
1219 if ( new_rows )
1221 // use this var as a counter
1222 new_rows = 0;
1223 // and new_item is a pointer we'll set to the item we are currently adjusting...
1224 // all the items we need should have been added with the previous loops
1225 KListViewItem * new_item = (KListViewItem*) searchFilesListView->firstChild();
1226 // This is the exact same loop as above, but here will do something with what matches
1227 for( MmDataList::iterator it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1229 if( (!(*it).IsDir()) && ( ((*it).Folder().compare(p)==0) || (((*it).FileName().contains(p,FALSE)>0) ||
1230 ((*it).Artist().contains(p,FALSE)>0) ) || (((*it).Title().contains(p,FALSE)>0) ||
1231 ((*it).Album().contains(p,FALSE)>0) ) ) )
1233 if ( !new_item ) qFatal("slotSearchButtonClicked: supposedly \"equal\" checks are giving "
1234 "different results!");
1235 new_item->setText( 0, (*it).Folder() ); // folder - for use in play or enqueue file(s) action
1236 new_item->setText( 1, (*it).FileName() ); // file
1237 new_item->setText( 2, (*it).Artist() ); // artist
1238 new_item->setText( 3, (*it).Title() ); // title
1239 new_item->setText( 4, (*it).Album() ); // album
1240 new_item->setText( 5, (*it).Genre() ); // genre
1241 new_item->setText( 6, (*it).Comment() ); // comment
1242 new_item->setText( 7, ((*it).Year()!=0)?QString::number( (*it).Year() ):"" ); // song year
1243 new_item->setText( 8, ((*it).TrackNum()!=0)?QString::number( (*it).TrackNum() ):"" ); // track number
1244 new_item->setText( 9, formatted_string_from_seconds( (*it).Length() ) ); // length
1245 new_item->setText( 10, QString::number( (*it).FileSize() / 1024 ) + " kB" ); // file size
1246 // dt.setTime_t( (*it).ModifiedTime() );
1247 new_item->setText( 11, QString::number( (*it).BitRate()) ); // bitrate
1248 new_item->setText( 12, QString::number( (*it).SampleRate()) ); // samplerate
1249 new_item->setText( 13, QString::number( (*it).Channels()) ); // channels
1250 QFileInfo ext_info( (*it).FileName() );
1251 new_item->setText( 14, ext_info.extension(FALSE).upper() ); // type
1252 new_item->setText( 15, (*it).IsReadOnly()?"YES":"_no_" ); // read only
1253 new_item = (KListViewItem*) new_item->nextSibling();
1254 new_rows++;
1257 // kdDebug() << new_rows << " rows have been added." << endl;
1258 // update table contets after manipulation
1259 searchFilesListView->clearSelection();
1260 // here we update the first 5 columns' width
1261 Q_CHECK_PTR( s );
1262 // honor user setting whether he/she wants auto-column-width, or likes speed more
1263 if ( s->autoColumnWidth() )
1264 for ( ushort k=0; k<7; k++) searchFilesListView->adjustColumn( k );
1266 searchFilesListView->setEnabled( TRUE );
1267 if(!(new_rows)) KMessageBox::sorry( this, i18n("No files were found, that match your search criterias!"),
1268 i18n("KDE Music Kataloger") );
1271 void kmk::slotClearButtonClicked()
1273 leSearchFor->clear();
1274 searchFilesListView->clear();
1277 void kmk::slotDoneButtonClicked()
1279 filesListView->clearSelection();
1280 tabWidget1->setCurrentPage(0);
1285 /**============================================================================================================**
1286 ** NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS NON SLOTS **
1287 **============================================================================================================**/
1289 void kmk::clearCatalogData( const bool UpdateState )
1291 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1292 kdDebug() << "clearCatalogData: clearing all in-memory catalog data and stats...." << endl;
1293 // clear catalog tree
1294 MusicCatalog.clear();
1295 treeListView->clear();
1296 listViewRootItem = (KListViewItem*) treeListView;
1297 cur_vlItem = listViewRootItem;
1298 // clear table showing files
1299 filesListView->clear();
1300 // clear table showing search result files
1301 searchFilesListView->clear();
1302 // reset stats counters, locate mode helper variables
1303 locateMode = FALSE; DelSelFile = ""; DelSelDir = "";
1304 bytes_to_read = 0; total_bytes = 0; total_files = 0;
1305 total_folders = 0; total_play_time = 0; average_file_size = 0;
1306 // and finally, update state, title and actions accordingly
1307 if( UpdateState ) setCatalogStateAndUpdate( NoCatalog );
1308 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1309 kdDebug() << "clearCatalogData: ...done!" << endl;
1312 void kmk::setCatalogStateAndUpdate( const kmk::CatalogStateEnum state )
1314 if( s->dbg() & KMK_DBG_OTHER )
1315 switch (state) {
1316 case kmk::NoCatalog:
1317 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"NO CATALOG\"..." << endl;
1318 break;
1319 case kmk::Modified:
1320 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"MODIFIED\"..." << endl;
1321 break;
1322 case kmk::Saved:
1323 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"SAVED\"..." << endl;
1324 break;
1325 default:
1326 kdDebug() << "setCatalogStateAndUpdate: setting catalog state to \"UNSPECIFIED\"..." << endl;
1327 break;
1329 CatalogState = state;
1330 switch (state) {
1331 case kmk::NoCatalog:
1332 catalogFileName = QDir::homeDirPath()+"/"+_KMK_NEWCATALOG;
1333 this->setCaption( i18n("No catalog - ") + savedCaption );
1334 fileSaveAction->setEnabled( FALSE );
1335 fileSaveAsAction->setEnabled( FALSE );
1336 fileCatalogFileStats->setEnabled( FALSE );
1337 fileCatalogFileClose->setEnabled( FALSE );
1338 catalogAddNewFolderAction->setEnabled( FALSE );
1339 TagEditAction->setEnabled( TRUE );
1340 listTableLocateAction->setEnabled( FALSE );
1341 playerEnqueueAction->setEnabled( FALSE );
1342 playerEnqueueDirAction->setEnabled( FALSE );
1343 playerEnqueueDirSubdirsAction->setEnabled( FALSE );
1344 playerPlayDirAction->setEnabled( FALSE );
1345 playerPlayDirSubdirsAction->setEnabled( FALSE );
1346 playerPlaySelectionAction->setEnabled( FALSE );
1347 main_container_widget->setEnabled( FALSE );
1348 break;
1349 case kmk::Modified:
1350 this->setCaption( i18n("Catalog [%1][UNSAVED] - ").arg(catalogFileName) + savedCaption );
1351 fileSaveAction->setEnabled( TRUE );
1352 fileSaveAsAction->setEnabled( TRUE );
1353 fileCatalogFileStats->setEnabled( TRUE );
1354 fileCatalogFileClose->setEnabled( TRUE );
1355 catalogAddNewFolderAction->setEnabled( TRUE );
1356 TagEditAction->setEnabled( TRUE );
1357 listTableLocateAction->setEnabled( TRUE );
1358 playerEnqueueAction->setEnabled( TRUE );
1359 playerEnqueueDirAction->setEnabled( TRUE );
1360 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1361 playerPlayDirAction->setEnabled( TRUE );
1362 playerPlayDirSubdirsAction->setEnabled( TRUE );
1363 playerPlaySelectionAction->setEnabled( TRUE );
1364 main_container_widget->setEnabled( TRUE );
1365 break;
1366 case kmk::Saved:
1367 this->setCaption( i18n("Catalog [%1] - ").arg(catalogFileName) + savedCaption );
1368 fileSaveAction->setEnabled( FALSE );
1369 fileSaveAsAction->setEnabled( TRUE );
1370 fileCatalogFileStats->setEnabled( TRUE );
1371 fileCatalogFileClose->setEnabled( TRUE );
1372 catalogAddNewFolderAction->setEnabled( TRUE );
1373 TagEditAction->setEnabled( TRUE );
1374 listTableLocateAction->setEnabled( TRUE );
1375 playerEnqueueAction->setEnabled( TRUE );
1376 playerEnqueueDirAction->setEnabled( TRUE );
1377 playerEnqueueDirSubdirsAction->setEnabled( TRUE );
1378 playerPlayDirAction->setEnabled( TRUE );
1379 playerPlayDirSubdirsAction->setEnabled( TRUE );
1380 playerPlaySelectionAction->setEnabled( TRUE );
1381 main_container_widget->setEnabled( TRUE );
1382 break;
1383 default:
1384 break;
1386 if( s->dbg() & KMK_DBG_OTHER )
1387 kdDebug() << "setCatalogStateAndUpdate: done!" << endl;
1390 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1392 unsigned long kmk::traverse_tree( const QString& dir )
1394 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1395 { kmkSmooth->restart(); qApp->processEvents(); }
1396 /* Here we mark our tree depth - subDlevel is a kmk private var */
1397 subDlevel++;
1398 //qWarning( "looking in \""+dir+"\"...");
1399 /* Create a QDir object, set to the "dir-for-scan"
1400 The filter is set to DIRECTORIEs, without symlinks */
1401 QDir d ( dir );
1402 d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1403 /* This gets our "dir-for-scan" subdirs' in @p list */
1404 const QFileInfoList *list = d.entryInfoList();
1405 QFileInfoListIterator it( *list );
1406 /* @p fi is used to get the dir name and last modification time */
1407 QFileInfo *fi = new QFileInfo( d.absPath() );
1408 /* Create a new MmDataList entry with the extracted above data
1409 for the "dir-for-scan" and set @p new_dir to point at it;
1410 we need a pointer to this new item, because if we add any files,
1411 contained in this dir, or its subdirs, we must update its
1412 MmFileSize field according to this below... */
1413 MmDataList::iterator new_dir =
1414 /* Some explanation : as we use the same elements to store info
1415 for audio files, and the dirs containing them - we do this trick:
1416 if the MmData item holds info on DIRECTORY - we set its fields like this:
1417 MmFolder - set to the ABSOLUTE DIR NAME
1418 MmFileName - set to the SHORT DIR NAME
1419 MmIsFolder - set to TRUE
1420 MmReadOnly - wether or not we have permission to write in DIR
1421 MmLength - set to the number of files in "dir-to-scan" subdirs
1422 MmFileSize - set to the number of files contained - updated later */
1423 MusicCatalog.append( MmData( d.absPath(), // folder
1424 d.dirName(), // filename
1425 "", // artist
1426 "", // title
1427 "", // album
1428 "", // genre
1429 "", // comment
1430 0, // year
1431 0, // track number
1432 0, // file size - updated later
1433 fi->lastModified().toTime_t(), // modified time
1434 fi->isWritable(), // read only? - we may use this to determine if we can "rename" dirs
1435 TRUE, // is dir?
1436 0, // audio duration (length) in secs - upd. later
1437 0, // channels
1438 0, // samplerate
1439 0 ) ); // bitrate
1440 /* We are done with @p fi for now - so, get rid of it */
1441 delete fi;
1442 /* Create a new KListView entry, pointed by @p ptr and if it is at
1443 the catalog root make its name the absolute path
1444 to "dir-for-scan", otherwise use short dir name;
1445 we set this item's parent to be what's pointed by kmk's private
1446 var cur_vlItem - it should point to what should be this item's
1447 parent in the catalog tree */
1448 KListViewItem *ptr;
1449 //////////////////////// kdDebug() << "about to crash..." << endl;
1450 if ( subDlevel == 1 ) ptr = new KListViewItem( (QListView*) treeListView, d.absPath() );
1451 else ptr = new KListViewItem( (QListViewItem*) cur_vlItem, d.dirName() );
1452 //////////////////////// kdDebug() << "Strange, did not crash...." << endl;
1453 /* Set the new catalog tree item to be closed, non-expandable:
1454 if it happens to be parent of another - we will fix it later */
1455 ptr->setOpen( FALSE ); ptr->setExpandable( FALSE );
1456 /* Here we initialize our subdirs' loaded files counter @p lf */
1457 unsigned long lf = 0;
1458 /* This checks if "dir-for-scan" has any subdirs */
1459 if ( !list->isEmpty() )
1461 /* And if it has, we go over the list of "dir-for-scan" subdirs */
1462 while ( (fi = it.current()) != 0 )
1464 /* Check if these subdirs are not the FS reserved "." and ".."
1465 FIX: AND that we can also access them !!! */
1466 if ( (fi->isReadable()) && (fi->isExecutable()) )
1467 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1469 /* So, we've found a valid "dir-for-scan" subdirectory.
1470 If you remember - we used kmk's private var cur_vlItem as a parent
1471 of the KListViewItem we added earlier; so we want it to be
1472 "preset" when we are called, don't we? Let's make sure it is! */
1473 cur_vlItem = ptr;
1474 /* We are setup and ready to call ourselves recursievely on this one.
1475 Because at the end of traverse_tree we provide a count of the
1476 actually added files to the catalog, including the added files
1477 from each subdir, lets increase @p lf with the numer of files in
1478 the directory we want to check...
1479 Here is the tracking : */
1480 lf += traverse_tree( d.filePath( fi->fileName() ) );
1482 /* Then go to the next in the list */
1483 ++it;
1485 /* We finished calling ourselves recursievly. So, if the subdirs'
1486 scan added any files - set the new catalog item to represent this
1487 accordingly - we hold subentries, so we need to be expandable;
1488 Maybe the user could set wheter or not the new catalog is already
1489 open or collapsed */
1490 if ( lf > 0 ) { ptr->setOpen( FALSE ); ptr->setExpandable( TRUE ); }
1491 //qWarning("having "+QString::number(lf)+" files reported back...");
1493 /* Then we set our "dir-for-scan" filter to files, without symlinks */
1494 d.setFilter( QDir::Files | QDir::NoSymLinks );
1495 /* And get a list of its contents, after the filtering is apllied */
1496 const QFileInfoList *list2 = d.entryInfoList();
1497 QFileInfoListIterator it2( *list2 );
1498 QFileInfo *fi2;
1499 /* These we will use for storage of the exctracted data from files */
1500 QString art, ttl, alb, gen, cmt;
1501 /* This will be our "dir-for-scan" ONLY (i.e. without including
1502 the count from the subdirs) loaded files counter; zero-init it.
1503 Nowadays there are file systems, capable of handling enormous amounts
1504 of files in a single directory - make sure we don't choke on someones
1505 huge collection, by using extra large counter.*/
1506 unsigned long loaded_files = 0;
1507 /* A cycle to iterate over the files in "dir-for-scan" */
1508 while ( (fi2 = it2.current()) != 0 )
1510 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1511 { kmkSmooth->restart(); qApp->processEvents(); }
1512 /* If the files aren't with the supported extensions - ignore them;
1513 We call QFileInfo->extension(FALSE) because we need only chars after LAST '.' */
1514 if ( fi2->isReadable() )
1515 // LOSSLESS first
1516 if ( ( fi2->extension(FALSE).lower().compare("wv" ) == 0 ) || // wav-pack
1517 ( fi2->extension(FALSE).lower().compare("tta" ) == 0 ) || // true-audio
1518 ( fi2->extension(FALSE).lower().compare("ape" ) == 0 ) || // monkey's audio
1519 /* Is it correct for a FLAC file to have any other extension than "flac"?
1520 For example - can it be named "my_audio_file.fla"? Ideas, advices.... */
1521 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) || // flac
1522 // LOSSY next
1523 ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) || // mp3
1524 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) || // ogg-vorbis
1525 ( fi2->extension(FALSE).lower().compare("spx") == 0 ) || // ogg-speex
1526 ( fi2->extension(FALSE).lower().compare("mpc") == 0 ) ) // muse-pack
1528 /* Init our storage for this file... */
1529 art = ""; ttl = ""; alb = ""; gen = ""; cmt = "";
1530 using namespace TagLib;
1531 /* Use TagLib's functions to extract meta data and audio properties;
1532 This tells TagLib to read tags, and be as fast as possible */
1533 if( s->dbg() & KMK_DBG_FS_OPS )
1534 kdDebug() << "traverse_tree: Now checking: " << d.filePath( fi2->fileName().latin1() ) << endl;
1535 TagLib::FileRef f( d.filePath( fi2->fileName().latin1() ), TRUE, TagLib::AudioProperties::Fast );
1536 /* If TagLib found the file to be valid - then add it to the catalog.
1537 Maybe here would be a good place to update some global catalog stats,
1538 like total storage used by the collection we are cataloguining, number
1539 of files in it, average bitrate, highest one, lowest one, biggest file,
1540 smallest file... and anything else someone might consider "interesting"!*/
1541 if( f.file()->isValid() )
1543 TagLib::String s;
1544 s = f.tag()->artist(); art = s.toCString();
1545 s = f.tag()->title(); ttl = s.toCString();
1546 s = f.tag()->album(); alb = s.toCString();
1547 s = f.tag()->genre(); gen = s.toCString();
1548 s = f.tag()->comment(); cmt = s.toCString();
1549 /* Some of the extracted file data is in our storage vars now.
1550 Create a new MmData item, and fill it with what we've read. */
1551 MmData ni = MmData();
1552 ni.setFolder( d.absPath() );
1553 ni.setFileName( fi2->fileName() );
1554 ni.setArtist( art );
1555 ni.setTitle( ttl );
1556 ni.setAlbum( alb );
1557 ni.setGenre( gen );
1558 ni.setComment( cmt );
1559 ni.setYear( f.tag()->year() );
1560 ni.setTrackNum( f.tag()->track() );
1561 ni.setFileSize( fi2->size() );
1562 // qDebug( "To "+QString::number( total_bytes )+" B, adding "+QString::number((Q_ULLONG) fi2->size())+" B." );
1563 total_bytes += (Q_ULLONG) fi2->size();
1564 ni.setModifiedTime( fi2->lastModified().toTime_t() );
1565 /* How about using Qt's functions QFile and QDir to check this?
1566 Not a problem! I've checked it - TagLib gives correct data; in a bunch of mp3s, I
1567 set to 2 of them to have only read acces - TagLib said exactly this! The only thing
1568 left in mind is speed, but... how do I check IT? Pl.Petrov */
1569 ni.setIsReadOnly( f.file()->readOnly() );
1570 ni.setIsDir( FALSE ); // we are adding a file
1571 ni.setLength( f.audioProperties()->length() ); total_play_time += ni.Length();
1572 ni.setChannels( f.audioProperties()->channels() );
1573 ni.setSampleRate( f.audioProperties()->sampleRate() );
1574 ni.setBitRate( f.audioProperties()->bitrate() );
1575 /* Now, after we've filled our new catalog item - lets add it
1576 to the catalog list */
1577 MusicCatalog.append( ni );
1578 /* We said we will keep track of loaded files number - do so */
1579 loaded_files++; total_files++;
1582 /* Go to the next file in "dir-for-scan"... */
1583 ++it2;
1585 /* Now here is what we've got so far:
1586 o) new_dir - it is a pointer to the MmData item,
1587 containing the "dir-for-scan" directory info;
1588 o) ptr - a pointer the KListViewItem, which we created and added to the catalog tree;
1589 o) lf - contains the number of files added from all "dir-for-scan" SUBDIRs;
1590 o) loaded_files - contains the number of files added from "dir-for-scan" itself;
1591 If any of the last two in the list above is non-zero... */
1592 if ( loaded_files || lf )
1593 /* ...we must update the MmData item, containing the info for "dir-for-scan" to comply with this:
1594 [ ...if a MmData item holds info for a directory,
1595 its MmFileSize field holds a count of the files contained
1596 in it, !!! NOT !!! including its subdirs. ]
1597 We also update our overall folder counter.*/
1598 { (*new_dir).setFileSize( loaded_files ); (*new_dir).setLength( lf ); total_folders++; }
1599 /* If the sum of the last two is zero (0), or... lets put it this way -
1600 if both of them are zeros - then the first two ought to be removed; */
1601 else { MusicCatalog.remove( new_dir ); delete( ptr ); }
1602 /* We are done on this level - return subDlevel to where it was... */
1603 subDlevel--;
1604 /* And then return the sum of files in "dir-for-scan" and the files in
1605 its subdirectories.*/
1606 return (loaded_files + lf);
1609 /* WARNING !!! OBSESIVE USE OF RECURSION!!!!
1611 void kmk::bytes_to_read_by_traverse( const QString& dir )
1613 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1614 { kmkSmooth->restart(); qApp->processEvents(); }
1615 // kdDebug() << "called with param " << dir << endl;
1616 QDir d ( dir ); d.setFilter( QDir::Dirs | QDir::NoSymLinks );
1617 const QFileInfoList *list = d.entryInfoList(); QFileInfoListIterator it( *list );
1618 QFileInfo *fi = new QFileInfo();
1619 if ( !list->isEmpty() )
1620 while ( (fi = it.current()) != 0 )
1622 if ( (fi->isReadable()) && (fi->isExecutable()) )
1623 if ( (fi->fileName().compare(".")!=0) && (fi->fileName().compare("..")!=0) )
1625 if ( !fi->isDir() ) kdDebug() << "...and we are trying to do dir op on non-dir!" << endl;
1626 else bytes_to_read_by_traverse( d.filePath( fi->fileName() ) );
1628 ++it;
1630 d.setFilter( QDir::Files | QDir::NoSymLinks ); const QFileInfoList *list2 = d.entryInfoList();
1631 QFileInfoListIterator it2( *list2 ); QFileInfo *fi2;
1632 while ( (fi2 = it2.current()) != 0 )
1634 if(kmkSmooth->elapsed() >= _KMK_UPDATE_PERIOD)
1635 { kmkSmooth->restart(); qApp->processEvents(); }
1636 if ( fi2->isReadable() )
1637 if ( ( fi2->extension(FALSE).lower().compare("mp3") == 0 ) ||
1638 ( fi2->extension(FALSE).lower().compare("ogg") == 0 ) ||
1639 ( fi2->extension(FALSE).lower().compare("flac") == 0 ) )
1640 { bytes_to_read += (Q_ULLONG) fi2->size(); files_to_read++; }
1641 ++it2;
1645 /* Some neat code I am really proud of: fast and effective; doesn't tollerate storage errors, though... */
1646 uint kmk::generateListViewSubtreeXML( const KListViewItem *item, QDomDocument doc, QDomElement e, const uint add_lv )
1648 if( item != 0 )
1650 // kdDebug() << "DOMelem <tree_item Name=\"" << item->text(0) <<"\" M=\"" << add_lv << "\" />" << endl;
1652 QDomElement dscr =
1653 doc.createElement( "FolderTreeDescriptor" );
1654 dscr.setAttribute( "FolderName", item->text(0) );
1655 dscr.setAttribute( "GoUpHowMuch", add_lv );
1656 e.appendChild( dscr );
1658 uint levels_added=0;
1659 KListViewItem * some_child = (KListViewItem*) item->firstChild();
1660 while( some_child )
1662 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
1663 some_child = (KListViewItem*) some_child->nextSibling();
1666 return ++levels_added;
1668 else return 0;
1671 void kmk::generateListViewXML( const KListView *list, QDomDocument doc, QDomElement e )
1673 if( list != 0 )
1675 uint levels_added=0;
1676 KListViewItem * some_child = (KListViewItem*) list->firstChild();
1677 while( some_child )
1679 levels_added = generateListViewSubtreeXML( some_child, doc, e, levels_added );
1680 some_child = (KListViewItem*) some_child->nextSibling();
1687 * Finds all files in TreeList currentItem and all its subfolders
1688 * and passes them to player_bin with parameter @param act
1689 * NOTE this function respects _kmk_include_subdirs
1690 * NOTE 2: in a rethink of what the function should do,
1691 * the new behaviour is that it writes all the relevant files
1692 * in a .M3U or .PLS playlist file (stored with a randomly generated
1693 * name in /tmp/kmk) and passes it to the player with @p act
1695 void kmk::playerDir( uint act ) // act>=1 - play; act==0 - enqueue
1697 QListViewItem* lv = treeListView->currentItem();
1698 if ( lv != 0 )
1700 // generate some random based filename for the playlist file
1701 QString flNm = "/tmp/kmk/kmk_lst_"+QString::number( random() )+".m3u";
1702 QFile file( flNm );
1703 if ( file.open( IO_WriteOnly ) )
1705 QTextStream stream( &file );
1706 stream << "#EXTM3U\n";
1707 // determine the folder we use as root and save it in @p d
1708 QString d;
1709 while ( TRUE ) {
1710 d.prepend( lv->text(0) );
1711 if( lv->parent() ) { if( lv->parent()->text(0).compare("/") != 0 ) d.prepend( "/" ); lv = lv->parent(); }
1712 else break;
1714 // do the acctual adding of files to the playlist....
1715 MmDataList::iterator it;
1716 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1717 if ( _kmk_include_subdirs )
1719 if ( ( !((*it).IsDir()) ) && ( (( (*it).Folder().compare(d)==0 )) || ( (*it).Folder().contains(d+"/",TRUE)==1 ) ) )
1721 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
1722 stream << (*it).Folder()+"/"+(*it).FileName() << "\n";
1725 else
1727 if ( ( !((*it).IsDir()) ) && ( (*it).Folder().compare(d)==0 ) )
1729 stream << "#EXTINF:" << (*it).Length() << " ," << (*it).Artist() << " - " << (*it).Title() << "\n";
1730 stream << (*it).Folder()+"/"+(*it).FileName()<<"\n";
1733 file.close();
1735 if (act) player->playPlaylist( flNm );
1736 else player->addPlaylist( flNm );
1737 // file.remove();
1739 else QMessageBox::warning( this, i18n("KDE Music Kataloger"),i18n("Could not open %1 for writing!").arg(flNm),
1740 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
1744 MmData* kmk::findMmData( const QString& folder, const QString& filename )
1746 bool found = FALSE;
1747 MmDataList::iterator it;
1748 for( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1750 if( ((*it).Folder().compare(folder)==0) && ((*it).FileName().compare(filename)==0) )
1752 found = TRUE;
1753 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1754 kdDebug() << "findMmData: called once. " << endl;
1755 break;
1758 if( found ) return &(*it);
1759 else return 0;
1762 const QString kmk::formatted_string_from_seconds( const Q_ULLONG t )
1764 QString ttime = "";
1765 unsigned short secs, mins, hours, years;
1766 secs = mins = hours = years = 0;
1767 uint days = 0;
1769 if ( t == 0 ) return ttime;
1770 years = t / 30758400;
1771 days = (t - (years * 30758400)) / 86400;
1772 hours = (t - ((years * 30758400) + (days * 86400))) / 3600;
1773 mins = (t - ((years * 30758400) + (days * 86400) + (hours * 3600))) / 60;
1774 secs = t % 60;
1775 if ( years ) if ( years > 1 ) ttime.append( i18n("%1 years").arg(years) );
1776 else ttime.append( i18n("1 year") );
1777 if ( days )
1779 if ( years ) ttime.append(", ");
1780 if ( days > 1 ) ttime.append( i18n("%1 days").arg(days) );
1781 else ttime.append( i18n("1 day") );
1783 if ( (days || years) && (hours || mins || secs) ) ttime.append(", ");
1784 if ( hours )
1786 if ( hours>9 ) ttime.append( QString("%1:").arg(hours) );
1787 else ttime.append( QString("0%1:").arg(hours) );
1789 if ( secs || mins || hours )
1791 if ( mins>9 ) ttime.append( QString("%1:").arg(mins) );
1792 else ttime.append( QString("0%1:").arg(mins) );
1794 if ( secs || mins || hours )
1796 if ( secs>9 ) ttime.append( QString("%1").arg(secs) );
1797 else ttime.append( QString("0%1").arg(secs) );
1799 return ttime;
1802 const bool kmk::catalog_has_dir( const QString & looked_for )
1804 bool found = FALSE;
1805 MmDataList::iterator it;
1807 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1808 kdDebug() << "catalog_has_dir: Now will look for dir [" << looked_for << "]..." << endl;
1809 // QDir gives us paths ending on "/" and in the catalog - folders don't
1810 // end on "/"; so, strip that last slash "/" symbol
1811 QString stripped_looked_for = looked_for;
1812 if ( stripped_looked_for.length()>1 )
1813 stripped_looked_for.setLength( stripped_looked_for.length() - 1 );
1814 for ( it = MusicCatalog.begin(); it != MusicCatalog.end(); ++it )
1816 if ( (*it).IsDir() )
1818 if ( (*it).Folder().compare( stripped_looked_for )==0 )
1820 found = TRUE;
1821 if( s->dbg() & KMK_DBG_CAT_MEM_OPS )
1822 kdDebug() << "catalog_has_dir: ...found! " << endl;
1823 break;
1828 return found;
1831 void kmk::loadCatalog( const QString & fileName )
1833 QDomDocument doc = QDomDocument::QDomDocument();
1834 QFile file( fileName );
1835 if ( !file.open( IO_ReadOnly | IO_Raw ) ) {
1836 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
1837 i18n("Could not open %1 for reading!").arg(fileName),
1838 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
1839 return;
1841 this->setCaption( i18n("Checking integrity of [%1] - ").arg(fileName) );
1842 this->repaint(); qApp->processEvents();
1843 // sleep( 5 ); kdDebug() << " sleeping..." << endl;
1844 QString xml_parse_err = "test"; int err_ln = 0; int err_cl = 0;
1845 QTime parse_time; parse_time.start();
1846 if ( !doc.setContent( &file, TRUE, &xml_parse_err, &err_ln, &err_cl ) ) {
1847 file.close();
1848 // qWarning( QString::number( *err_ln )+ " " + QString::number( *err_cl ) );
1849 QMessageBox::warning( this, i18n("KDE Music Kataloger"),
1850 i18n("Error parsing catalog file %1 !\n MSG: %2 on line %3, column %4.")
1851 .arg(fileName).arg(xml_parse_err.ascii()).arg(QString::number( err_ln )).arg(QString::number( err_cl )),
1852 QMessageBox::Abort,QMessageBox::NoButton,QMessageBox::NoButton );
1853 this->setCaption( savedCaption );
1854 return;
1856 file.close();
1857 if( s->dbg() & KMK_DBG_CATALOG_IO )
1858 kdDebug() << "loadCatalog: parse_file(SAX2 parse): elapsed time: "
1859 << parse_time.elapsed() << " ms." << endl;
1860 parse_time.restart();
1861 clearCatalogData();
1862 this->setCaption( i18n("Loading [%1]...").arg(fileName) );
1863 catalogFileName = fileName;
1864 qApp->processEvents();
1866 /** READ CATALOG VERSION INFO - if none is found - inform, and bail out */
1867 bool process = TRUE;
1868 ulong nodes_to_add = 0;
1869 QDomNodeList ml = doc.elementsByTagName( "KMK_catalog_file_data" ); QDomNode n; QDomElement e; QDomAttr a;
1870 for ( uint i = 0; i<ml.count(); i++)
1872 n = ml.item( i );
1873 if ( n.isElement() )
1874 { e = n.toElement();
1875 QString tx = e.attribute("Version");
1876 if( !tx.isNull() )
1878 if( tx.toInt() > 1 ) KMessageBox::sorry( this, i18n("The catalog [%1] is saved in new format (v.%2),"
1879 "so some data will not be recognized.").arg(fileName).arg(tx.toInt()),i18n("KDE Music Kataloger") );
1880 if( tx.toInt() == 0 ) process = FALSE;
1884 if( s->dbg() & KMK_DBG_CATALOG_IO )
1885 kdDebug() << "loadCatalog: parse_file(new read): catalog version read time: "
1886 << parse_time.elapsed() << " ms." << endl;
1887 parse_time.restart();
1888 if( process )
1890 ml = doc.elementsByTagName( "CatalogFile_Statistics" );
1891 for ( uint i = 0; i<ml.count(); i++)
1893 n = ml.item( i );
1894 if ( n.isElement() )
1895 { e = n.toElement();
1896 QString tx = e.attribute("Total_files_count");
1897 if( !tx.isNull() ) total_files = (Q_ULLONG) tx.toDouble();
1898 tx = e.attribute("Total_bytes_count");
1899 if( !tx.isNull() ) total_bytes = (Q_ULLONG) tx.toDouble();
1900 tx = e.attribute("Total_dirs_count");
1901 if( !tx.isNull() ) total_folders = (Q_ULLONG) tx.toDouble();
1902 tx = e.attribute("Total_secs_count");
1903 if( !tx.isNull() ) total_play_time = (Q_ULLONG) tx.toDouble();
1904 tx = e.attribute("Average_file_size");
1905 if( !tx.isNull() ) average_file_size = (Q_ULLONG) tx.toDouble();
1908 if( s->dbg() & KMK_DBG_CATALOG_IO )
1909 kdDebug() << "loadCatalog: parse_file(new read): catalog stats read time: "
1910 << parse_time.elapsed() << " ms." << endl;
1911 parse_time.restart();
1914 /** READ THE FOLDERS STRUCTURE AUXILARY DATA - version, counters, etc */
1915 /* Currently - IRRELEVANT
1916 ml = doc.elementsByTagName( "CatalogFile_TreeDescription" );
1917 for ( uint i = 0; i<ml.count(); i++)
1919 n = ml.item( i );
1920 if ( n.isAttr() )
1921 { a = n.toAttr();
1922 #ifdef __KMK_DEBUG
1923 kdDebug() << "(kmk): i="<<i<<"; found attr; name: "<< a.name()<<"; value: "<< a.value() << endl;
1924 #endif
1926 if ( n.isElement() )
1927 { e = n.toElement();
1928 #ifdef __KMK_DEBUG
1929 kdDebug() << "(kmk): i="<<i<<"; found elem; tag: "<< e.tagName() << endl;
1930 #endif
1935 /** READ THE FOLDERS STRUCTURE AND RECREATE IT IN KLISTVIEW */
1936 if( process )
1938 KListViewItem *CURRENT = 0; bool warned = FALSE;
1939 ml = doc.elementsByTagName( "FolderTreeDescriptor" );
1940 for ( uint i = 0; i<ml.count(); i++)
1942 n = ml.item( i );
1943 if ( n.isElement() )
1944 { e = n.toElement();
1945 QString nm = e.attribute("FolderName");
1946 if( nm.isNull() ) kdDebug() << "READ A NULL FOLDER NAME!" << endl;
1947 QString up = e.attribute("GoUpHowMuch");
1948 if( up.isNull() ) kdDebug() << "READ A NULL GO_UP_HOW_MUCH!"<< endl;
1949 uint t = up.toUInt();
1950 while( (t) && (CURRENT) )
1952 CURRENT = (KListViewItem*)CURRENT->parent();
1953 t--;
1955 if( t && (!warned) ) { KMessageBox::sorry( this,
1956 i18n("The catalog file you are trying to load is messed up. "
1957 "You get this message, so it passed XML sanity checks. That means almost for sure "
1958 "that it has been edited by hand - you are better off if you recreate it via Catalog->New. "
1959 "The scanning is fast enough anyway (around 10GB mp3 files scanned per minute)!\n"
1960 "You have been warned - don't complain if something goes wrong."),
1961 i18n("KDE Music Kataloger") );
1962 warned = TRUE; }
1963 if( s->dbg() & KMK_DBG_OTHER )
1964 if(CURRENT)
1965 kdDebug() << "loadCatalog: " << i << ":(t="<<t<<") adding "<<nm<<" at "<<CURRENT->text(0)<<endl;
1966 else
1967 kdDebug() << "loadCatalog: " << i << ":(t="<<t<<") adding "<<nm<<" at ROOT."<<endl;
1968 if(CURRENT) CURRENT = new KListViewItem( (QListViewItem*) CURRENT, nm );
1969 else CURRENT = new KListViewItem( (QListView*) treeListView, nm );
1970 //kdDebug() << i << ":-------------> "<<CURRENT->text(0)<<"'s parent() is: " << CURRENT->parent() << endl;
1973 if( s->dbg() & KMK_DBG_CATALOG_IO )
1974 kdDebug() << "loadCatalog: parse_file(new read): folder tree reconstruction time: "
1975 << parse_time.elapsed() << " ms." << endl;
1976 parse_time.restart();
1977 qApp->processEvents();
1980 /** READ CATALOG ITEMS AUXILARY DATA - objects number, size, etc */
1981 if( process )
1983 ml = doc.elementsByTagName( "CatalogFile_Objects" );
1984 for ( uint i = 0; i<ml.count(); i++)
1986 n = ml.item( i );
1987 if ( n.isElement() )
1988 { e = n.toElement();
1989 QString tx = e.attribute("Total_MObjs_count");
1990 if( !tx.isNull() ) nodes_to_add = tx.toULong();
1993 if( s->dbg() & KMK_DBG_CATALOG_IO )
1994 kdDebug() << "loadCatalog: parse_file(new read): catalog objects count read time: "
1995 << parse_time.elapsed() << " ms." << endl;
1996 parse_time.restart();
1999 /** READ ACTUAL AUDIO FILE DATA INTO MusicCatalog */
2000 if( process )
2002 ml = doc.elementsByTagName( "MObj" ); MmData node; uint tags_found;
2003 for ( uint i = 0; i<ml.count(); i++)
2005 tags_found = 0;
2006 n = ml.item( i );
2007 if ( n.isElement() )
2008 { e = n.toElement(); QString
2009 tx = e.attribute("Folder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2010 else { node.setFolder( tx ); tags_found++; }
2011 tx = e.attribute("Filename"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2012 else { node.setFileName( tx ); tags_found++; }
2013 tx = e.attribute("Artist"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2014 else { node.setArtist( tx ); tags_found++; }
2015 tx = e.attribute("Title"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2016 else { node.setTitle( tx ); tags_found++; }
2017 tx = e.attribute("Album"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2018 else { node.setAlbum( tx ); tags_found++; }
2019 tx = e.attribute("Genre"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2020 else { node.setGenre( tx ); tags_found++; }
2021 tx = e.attribute("Comment"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2022 else { node.setComment( tx ); tags_found++; }
2023 tx = e.attribute("Year"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2024 else { node.setYear( tx.toInt() ); tags_found++; }
2025 tx = e.attribute("TrackNumber"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2026 else { node.setTrackNum( tx.toInt() ); tags_found++; }
2027 tx = e.attribute("Length"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2028 else { node.setLength( tx.toInt() ); tags_found++; }
2029 tx = e.attribute("Modified"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2030 else { node.setModifiedTime( tx.toInt() ); tags_found++; }
2031 tx = e.attribute("Size"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2032 else { node.setFileSize( tx.toInt() ); tags_found++; }
2033 tx = e.attribute("Channels"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2034 else { node.setChannels( tx.toInt() ); tags_found++; }
2035 tx = e.attribute("BitRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2036 else { node.setBitRate( tx.toInt() ); tags_found++; }
2037 tx = e.attribute("SampleRate"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2038 else { node.setSampleRate( tx.toInt() ); tags_found++; }
2039 tx = e.attribute("IsReadOnly"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2040 else { node.setIsReadOnly( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2041 tx = e.attribute("IsFolder"); if( tx.isNull() ) kdDebug() << "READ NULL XML ATTRIBUTE!" << endl;
2042 else { node.setIsDir( (tx.compare("YES")==0) ? TRUE:FALSE ); tags_found++; }
2044 if ( tags_found )
2046 qApp->processEvents();
2047 MusicCatalog.append( node );
2048 if( s->dbg() & KMK_DBG_OTHER )
2049 kdDebug() << "loadCatalog: parse_file(new read): just added " << i << "-th object;" << endl;
2052 if( s->dbg() & KMK_DBG_CATALOG_IO )
2053 kdDebug() << "loadCatalog: parse_file(new read): node read and add time: "
2054 << parse_time.elapsed() << " ms." << endl;
2055 parse_time.restart();
2058 if( ! process )
2059 KMessageBox::sorry( this, i18n("No catalog markings found in [%1].\n"
2060 "This can happen if the file is corrupt.")
2061 .arg(catalogFileName),i18n("KDE Music Kataloger") );
2062 else {
2063 setCatalogStateAndUpdate( Saved );
2064 fileCatalogFileStats->setEnabled( TRUE );
2065 fileCatalogFileClose->setEnabled( TRUE );
2070 #include "kmk.moc"